There were two distinct things going on here: 1. If we receive multiple buffered TE_GAME_PAUSE events in the same frame, we'd process all of them and create a pause menu for each. This could theoretically overflow the evloop stack and crash the game. 2. The `stage_comain` task starts before scheduling the stage main loop via `eventloop_enter`. It initializes systems that depend on tasks, and then immediatelly enters its per-frame async loop, finishing its first iteration before yielding back to `_stage_enter` and thus allowing `eventloop_enter` to be finally called. If there is a TE_GAME_PAUSE event in the queue at this point, it would be handled right there, and a pause menu would be created before the stage main loop is scheduled. This messes things up quite a bit, leaking a "zombie" pause menu into the evloop stack. After the stage is destroyed, the evloop would try to switch to the frame created for this menu. The menu's draw function would then attempt to reference free'd resources of the destroyed stage, crashing the game. This crash has actually been observed and reported (thanks @0kalekale) To fix #1, the stage now tracks its paused state and refuses to open a pause menu if one already exists. To fix #2, `stage_comain` now yields before starting its async loop, to let the stage set up its main loop early. Note that because the stage main loop runs all coroutine tasks before incrementing the frame counter, `stage_comain`'s per-frame logic would execute twice on frame 0. This is obviously wrong, but this behavior must be preserved to maintain compatibility with v1.4 replays. For that reason, the `stage_comain` loop now skips its first YIELD. This hack can be removed once v1.4 compat is no longer a concern.
88 lines
2.4 KiB
C
88 lines
2.4 KiB
C
/*
|
|
* This software is licensed under the terms of the MIT License.
|
|
* See COPYING for further information.
|
|
* ---
|
|
* Copyright (c) 2011-2019, Lukas Weber <laochailan@web.de>.
|
|
* Copyright (c) 2012-2019, Andrei Alexeyev <akari@taisei-project.org>.
|
|
*/
|
|
|
|
#pragma once
|
|
#include "taisei.h"
|
|
|
|
#include "projectile.h"
|
|
#include "boss.h"
|
|
#include "difficulty.h"
|
|
#include "util/graphics.h"
|
|
#include "dialog.h"
|
|
#include "coroutine.h"
|
|
#include "dynarray.h"
|
|
#include "stageinfo.h"
|
|
#include "resource/resource.h"
|
|
|
|
typedef struct StageClearBonus {
|
|
uint64_t base;
|
|
uint64_t lives;
|
|
uint64_t voltage;
|
|
uint64_t graze;
|
|
uint64_t total;
|
|
|
|
struct {
|
|
uint64_t base;
|
|
real diff_multiplier;
|
|
uint64_t diff_bonus;
|
|
} all_clear;
|
|
} StageClearBonus;
|
|
|
|
void stage_enter(StageInfo *stage, ResourceGroup *rg, CallChain next);
|
|
void stage_finish(int gameover);
|
|
void stage_gameover(void);
|
|
|
|
void stage_start_bgm(const char *bgm);
|
|
|
|
typedef enum ClearHazardsFlags {
|
|
CLEAR_HAZARDS_BULLETS = (1 << 0),
|
|
CLEAR_HAZARDS_LASERS = (1 << 1),
|
|
CLEAR_HAZARDS_FORCE = (1 << 2),
|
|
CLEAR_HAZARDS_NOW = (1 << 3),
|
|
CLEAR_HAZARDS_SPAWN_VOLTAGE = (1 << 4),
|
|
|
|
CLEAR_HAZARDS_ALL = CLEAR_HAZARDS_BULLETS | CLEAR_HAZARDS_LASERS,
|
|
} ClearHazardsFlags;
|
|
|
|
void stage_clear_hazards(ClearHazardsFlags flags);
|
|
void stage_clear_hazards_at(cmplx origin, double radius, ClearHazardsFlags flags);
|
|
void stage_clear_hazards_in_ellipse(Ellipse e, ClearHazardsFlags flags);
|
|
void stage_clear_hazards_predicate(bool (*predicate)(EntityInterface *ent, void *arg), void *arg, ClearHazardsFlags flags);
|
|
|
|
void stage_set_voltage_thresholds(uint easy, uint normal, uint hard, uint lunatic);
|
|
|
|
bool stage_is_cleared(void);
|
|
|
|
void stage_unlock_bgm(const char *bgm);
|
|
|
|
void stage_begin_dialog(Dialog *d) attr_nonnull_all;
|
|
|
|
void stage_shake_view(float strength);
|
|
float stage_get_view_shake_strength(void);
|
|
|
|
void stage_load_quicksave(void);
|
|
|
|
CoSched *stage_get_sched(void);
|
|
|
|
bool stage_is_demo_mode(void);
|
|
|
|
#ifdef DEBUG
|
|
#define HAVE_SKIP_MODE
|
|
#endif
|
|
|
|
#ifdef HAVE_SKIP_MODE
|
|
void _stage_bookmark(const char *name);
|
|
#define STAGE_BOOKMARK(name) _stage_bookmark(#name)
|
|
DECLARE_EXTERN_TASK(stage_bookmark, { const char *name; });
|
|
#define STAGE_BOOKMARK_DELAYED(delay, name) INVOKE_TASK_DELAYED(delay, stage_bookmark, #name)
|
|
bool stage_is_skip_mode(void);
|
|
#else
|
|
#define STAGE_BOOKMARK(name) ((void)0)
|
|
#define STAGE_BOOKMARK_DELAYED(delay, name) ((void)0)
|
|
INLINE bool stage_is_skip_mode(void) { return false; }
|
|
#endif
|