2010-10-12 10:55:23 +02:00
|
|
|
/*
|
2019-08-03 19:43:48 +02:00
|
|
|
* This software is licensed under the terms of the MIT License.
|
2017-02-10 23:05:22 +01:00
|
|
|
* See COPYING for further information.
|
2011-03-05 13:44:21 +01:00
|
|
|
* ---
|
2019-01-23 21:10:43 +01:00
|
|
|
* Copyright (c) 2011-2019, Lukas Weber <laochailan@web.de>.
|
2019-07-03 20:00:56 +02:00
|
|
|
* Copyright (c) 2012-2019, Andrei Alexeyev <akari@taisei-project.org>.
|
2010-10-12 10:55:23 +02:00
|
|
|
*/
|
|
|
|
|
2017-11-25 20:45:11 +01:00
|
|
|
#include "taisei.h"
|
|
|
|
|
2017-03-06 01:25:59 +01:00
|
|
|
#include "util.h"
|
2010-10-12 10:55:23 +02:00
|
|
|
#include "stage.h"
|
|
|
|
#include "global.h"
|
2012-07-28 22:53:53 +02:00
|
|
|
#include "video.h"
|
2017-01-24 14:40:57 +01:00
|
|
|
#include "resource/bgm.h"
|
2021-05-30 03:26:21 +02:00
|
|
|
#include "replay/state.h"
|
|
|
|
#include "replay/stage.h"
|
|
|
|
#include "replay/struct.h"
|
2012-07-14 16:37:52 +02:00
|
|
|
#include "config.h"
|
|
|
|
#include "player.h"
|
2011-06-13 18:48:36 +02:00
|
|
|
#include "menu/ingamemenu.h"
|
2012-08-17 20:58:23 +02:00
|
|
|
#include "menu/gameovermenu.h"
|
2019-03-05 20:43:01 +01:00
|
|
|
#include "audio/audio.h"
|
Implemented a simple and consistent logging subsystem
The goal of this change is mainly to clean up Taisei's codebase and
improve its console output. I've been frustrated by files littered with
inconsistent printf/fprintf/warnx/errx calls for a long time, and now I
actually did something about it.
All the above functions are now considered deprecated and result in a
compile-time warning when used. Instead, the following macros should be
used:
log_debug(format, ...)
log_info(format, ...)
log_warn(format, ...)
log_err(format, ...)
As you can see, all of them have the same printf-like interface. But
they have different functionality and purpose:
log_debug is intended for very verbose and specific information. It
does nothing in release builds, much like assert(), so don't use
expressions with side-effects in its arguments.
log_info is for various status updates that are expected during
normal operation of the program.
log_warn is for non-critical failures or other things that may be
worth investigating, but don't inherently render the program
non-functional.
log_err is for when the only choice is to give up. Like errx, it
also terminates the program. Unlike errx, it actually calls abort(),
which means the cleanup functions are not ran -- but on the other
hand, you get a debuggable backtrace. However, if you're trying to
catch programming errors, consider using assert() instead.
All of them produce output that contains a timestamp, the log level
identifier, the calling function's name, and the formatted message.
The newline at the end of the format string is not required -- no, it is
actually *prohibited*. The logging system will take care of the line
breaks by itself, don't litter the code with that shit.
Internally, the logging system is based on the SDL_RWops abstraction,
and may have multiple, configurable destinations. This makes it easily
extensible. Currently, log_debug and log_info are set to write to
stdout, log_warn and log_err to stderr, and all of them also to the file
log.txt in the Taisei config directory.
Consequently, the nasty freopen hacks we used to make Taisei write to
log files on Windows are no longer needed -- which is a very good thing,
considering they probably would break if the configdir path contains
UTF-8 characters. SDL_RWFromFile does not suffer this limitation.
As an added bonus, it's also thread-safe.
Note about printf and fprintf: in very few cases, the logging system is
not a good substitute for these functions. That is, when you care about
writing exactly to stdout/stderr and about exactly how the output looks.
However, I insist on keeping the deprecation warnings on them to not
tempt anyone to use them for logging/debugging out of habit and/or
laziness.
For this reason, I've added a tsfprintf function to util.c. It is
functionally identical to fprintf, except it returns void. Yes, the name
is deliberately ugly. Avoid using it if possible, but if you must, only
use it to write to stdout or stderr. Do not write to actual files with
it, use SDL_RWops.
2017-03-13 03:51:58 +01:00
|
|
|
#include "log.h"
|
2017-04-02 16:06:18 +02:00
|
|
|
#include "stagetext.h"
|
2017-04-07 14:20:45 +02:00
|
|
|
#include "stagedraw.h"
|
2017-12-13 20:05:12 +01:00
|
|
|
#include "stageobjects.h"
|
Emscripten compatibility (#161)
* Major refactoring of the main loop(s) and control flow (WIP)
run_at_fps() is gone 🦀
Instead of nested blocking event loops, there is now an eventloop API
that manages an explicit stack of scenes. This makes Taisei a lot more
portable to async environments where spinning a loop forever without
yielding control simply is not an option, and that is the entire point
of this change.
A prime example of such an environment is the Web (via emscripten).
Taisei was able to run there through a terrible hack: inserting
emscripten_sleep calls into the loop, which would yield to the browser.
This has several major drawbacks: first of all, every function that
could possibly call emscripten_sleep must be compiled into a special
kind of bytecode, which then has to be interpreted at runtime, *much*
slower than JITed WebAssembly. And that includes *everything* down the
call stack, too! For more information, see
https://emscripten.org/docs/porting/emterpreter.html
Even though that method worked well enough for experimenting, despite
suboptimal performance, there is another obvious drawback:
emscripten_sleep is implemented via setTimeout(), which can be very
imprecise and is generally not reliable for fluid animation. Browsers
actually have an API specifically for that use case:
window.requestAnimationFrame(), but Taisei's original blocking control
flow style is simply not compatible with it. Emscripten exposes this API
with its emscripten_set_main_loop(), which the eventloop backend now
uses on that platform.
Unfortunately, C is still C, with no fancy closures or coroutines.
With blocking calls into menu/scene loops gone, the control flow is
reimplemented via so-called (pun intended) "call chains". That is
basically an euphemism for callback hell. With manual memory management
and zero type-safety. Not that the menu system wasn't shitty enough
already. I'll just keep telling myself that this is all temporary and
will be replaced with scripts in v1.4.
* improve build system for emscripten + various fixes
* squish menu bugs
* improve emscripten event loop; disable EMULATE_FUNCTION_POINTER_CASTS
Note that stock freetype does not work without
EMULATE_FUNCTION_POINTER_CASTS; use a patched version from the
"emscripten" branch here:
https://github.com/taisei-project/freetype2/tree/emscripten
* Enable -Wcast-function-type
Calling functions through incompatible pointers is nasal demons and
doesn't work in WASM.
* webgl: workaround a crash on some browsers
* emscripten improvements:
* Persist state (config, progress, replays, ...) in local IndexDB
* Simpler HTML shell (temporary)
* Enable more optimizations
* fix build if validate_glsl=false
* emscripten: improve asset packaging, with local cache
Note that even though there are rules to build audio bundles, audio
does *not* work yet. It looks like SDL2_mixer can not work without
threads, which is a problem. Yet another reason to write an OpenAL
backend - emscripten supports that natively.
* emscripten: customize the html shell
* emscripten: force "show log" checkbox unchecked initially
* emscripten: remove quit shortcut from main menu (since there's no quit)
* emscripten: log area fixes
* emscripten/webgl: workaround for fullscreen viewport issue
* emscripten: implement frameskip
* emscripter: improve framerate limiter
* align List to at least 8 bytes (shut up warnings)
* fix non-emscripten builds
* improve fullscreen handling, mainly for emscripten
* Workaround to make audio work in chromium
emscripten-core/emscripten#6511
* emscripten: better vsync handling; enable vsync & disable fxaa by default
2019-03-09 20:32:32 +01:00
|
|
|
#include "eventloop/eventloop.h"
|
2020-02-19 00:36:06 +01:00
|
|
|
#include "common_tasks.h"
|
2020-05-16 22:41:54 +02:00
|
|
|
#include "stageinfo.h"
|
2022-01-28 17:03:22 +01:00
|
|
|
#include "dynstage.h"
|
2017-02-11 12:38:50 +01:00
|
|
|
|
2020-06-24 19:51:00 +02:00
|
|
|
typedef struct StageFrameState {
|
|
|
|
StageInfo *stage;
|
|
|
|
CallChain cc;
|
|
|
|
CoSched sched;
|
2022-01-09 13:11:26 +01:00
|
|
|
Replay *quicksave;
|
2022-01-28 17:03:22 +01:00
|
|
|
bool quicksave_is_automatic;
|
2022-01-09 13:11:26 +01:00
|
|
|
bool quickload_requested;
|
2022-01-28 17:03:22 +01:00
|
|
|
uint32_t dynstage_generation;
|
2020-06-24 19:51:00 +02:00
|
|
|
int transition_delay;
|
2021-05-30 03:26:21 +02:00
|
|
|
int desync_check_freq;
|
2020-06-24 19:51:00 +02:00
|
|
|
uint16_t last_replay_fps;
|
|
|
|
float view_shake;
|
2022-01-09 13:11:26 +01:00
|
|
|
int bgm_start_time;
|
|
|
|
double bgm_start_pos;
|
2020-06-24 19:51:00 +02:00
|
|
|
} StageFrameState;
|
|
|
|
|
|
|
|
static StageFrameState *_current_stage_state; // TODO remove this shitty hack
|
|
|
|
|
|
|
|
#define BGM_FADE_LONG (2.0 * FADE_TIME / (double)FPS)
|
|
|
|
#define BGM_FADE_SHORT (FADE_TIME / (double)FPS)
|
|
|
|
|
2022-01-09 13:11:26 +01:00
|
|
|
static inline bool is_quickloading(StageFrameState *fstate) {
|
|
|
|
return fstate->quicksave && fstate->quicksave == global.replay.input.replay;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void sync_bgm(StageFrameState *fstate) {
|
|
|
|
double t = fstate->bgm_start_pos + (global.frames - fstate->bgm_start_time) / (double)FPS;
|
|
|
|
audio_bgm_seek_realtime(t);
|
|
|
|
}
|
|
|
|
|
2020-06-22 16:41:03 +02:00
|
|
|
#ifdef HAVE_SKIP_MODE
|
|
|
|
|
|
|
|
static struct {
|
|
|
|
const char *skip_to_bookmark;
|
|
|
|
bool skip_to_dialog;
|
|
|
|
bool was_skip_mode;
|
|
|
|
} skip_state;
|
|
|
|
|
|
|
|
void _stage_bookmark(const char *name) {
|
|
|
|
log_debug("Bookmark [%s] reached at %i", name, global.frames);
|
|
|
|
|
|
|
|
if(skip_state.skip_to_bookmark && !strcmp(skip_state.skip_to_bookmark, name)) {
|
|
|
|
skip_state.skip_to_bookmark = NULL;
|
|
|
|
global.plr.iddqd = false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
DEFINE_EXTERN_TASK(stage_bookmark) {
|
|
|
|
_stage_bookmark(ARGS.name);
|
|
|
|
}
|
|
|
|
|
|
|
|
bool stage_is_skip_mode(void) {
|
|
|
|
return skip_state.skip_to_bookmark || skip_state.skip_to_dialog;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void skipstate_init(void) {
|
|
|
|
skip_state.skip_to_dialog = env_get_int("TAISEI_SKIP_TO_DIALOG", 0);
|
|
|
|
skip_state.skip_to_bookmark = env_get_string_nonempty("TAISEI_SKIP_TO_BOOKMARK", NULL);
|
|
|
|
}
|
|
|
|
|
|
|
|
static LogicFrameAction skipstate_handle_frame(void) {
|
|
|
|
if(skip_state.skip_to_dialog && dialog_is_active(global.dialog)) {
|
|
|
|
skip_state.skip_to_dialog = false;
|
|
|
|
global.plr.iddqd = false;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool skip_mode = stage_is_skip_mode();
|
|
|
|
|
|
|
|
if(!skip_mode && skip_state.was_skip_mode) {
|
2022-01-09 13:11:26 +01:00
|
|
|
sync_bgm(_current_stage_state);
|
2020-06-22 16:41:03 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
skip_state.was_skip_mode = skip_mode;
|
|
|
|
|
|
|
|
if(skip_mode) {
|
|
|
|
return LFRAME_SKIP_ALWAYS;
|
|
|
|
}
|
|
|
|
|
|
|
|
if(gamekeypressed(KEY_SKIP)) {
|
|
|
|
return LFRAME_SKIP;
|
|
|
|
}
|
|
|
|
|
|
|
|
return LFRAME_WAIT;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void skipstate_shutdown(void) {
|
|
|
|
memset(&skip_state, 0, sizeof(skip_state));
|
|
|
|
}
|
|
|
|
|
|
|
|
#else
|
|
|
|
|
|
|
|
INLINE LogicFrameAction skipstate_handle_frame(void) { return LFRAME_WAIT; }
|
|
|
|
INLINE void skipstate_init(void) { }
|
|
|
|
INLINE void skipstate_shutdown(void) { }
|
|
|
|
|
|
|
|
#endif
|
|
|
|
|
2017-02-26 13:17:48 +01:00
|
|
|
static void stage_start(StageInfo *stage) {
|
2011-05-08 13:48:25 +02:00
|
|
|
global.timer = 0;
|
2011-06-13 18:48:36 +02:00
|
|
|
global.frames = 0;
|
2019-02-22 00:56:03 +01:00
|
|
|
global.gameover = 0;
|
|
|
|
global.voltage_threshold = 0;
|
2017-02-10 23:05:22 +01:00
|
|
|
|
2017-10-08 13:30:51 +02:00
|
|
|
player_stage_pre_init(&global.plr);
|
2017-02-26 13:17:48 +01:00
|
|
|
|
2020-04-26 21:27:13 +02:00
|
|
|
stats_stage_reset(&global.plr.stats);
|
|
|
|
|
2017-02-26 13:17:48 +01:00
|
|
|
if(stage->type == STAGE_SPELL) {
|
2018-01-21 10:52:54 +01:00
|
|
|
global.is_practice_mode = true;
|
2017-03-21 11:09:32 +01:00
|
|
|
global.plr.lives = 0;
|
2017-02-26 13:17:48 +01:00
|
|
|
global.plr.bombs = 0;
|
2017-09-11 21:09:30 +02:00
|
|
|
} else if(global.is_practice_mode) {
|
|
|
|
global.plr.lives = PLR_STGPRACTICE_LIVES;
|
|
|
|
global.plr.bombs = PLR_STGPRACTICE_BOMBS;
|
2018-01-20 16:54:45 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
if(global.is_practice_mode) {
|
2022-10-03 20:36:21 +02:00
|
|
|
global.plr.power_stored = config_get_int(CONFIG_PRACTICE_POWER);
|
2018-01-20 16:54:45 +01:00
|
|
|
}
|
|
|
|
|
2022-10-03 20:36:21 +02:00
|
|
|
global.plr.power_stored = iclamp(global.plr.power_stored, 0, PLR_MAX_POWER_STORED);
|
2017-03-02 11:23:30 +01:00
|
|
|
|
2020-06-22 16:41:03 +02:00
|
|
|
reset_all_sfx();
|
2010-10-12 10:55:23 +02:00
|
|
|
}
|
|
|
|
|
2017-10-10 16:06:46 +02:00
|
|
|
static bool ingame_menu_interrupts_bgm(void) {
|
|
|
|
return global.stage->type != STAGE_SPELL;
|
|
|
|
}
|
|
|
|
|
2020-06-24 19:51:00 +02:00
|
|
|
typedef struct IngameMenuContext {
|
|
|
|
CallChain next;
|
|
|
|
BGM *saved_bgm;
|
|
|
|
double saved_bgm_pos;
|
|
|
|
bool bgm_interrupted;
|
|
|
|
} IngameMenuContext;
|
|
|
|
|
|
|
|
static void setup_ingame_menu_bgm(IngameMenuContext *ctx, BGM *bgm) {
|
|
|
|
if(ingame_menu_interrupts_bgm()) {
|
|
|
|
ctx->bgm_interrupted = true;
|
|
|
|
|
|
|
|
if(bgm) {
|
|
|
|
ctx->saved_bgm = audio_bgm_current();
|
|
|
|
ctx->saved_bgm_pos = audio_bgm_tell();
|
|
|
|
audio_bgm_play(bgm, true, 0, 1);
|
|
|
|
} else {
|
|
|
|
audio_bgm_pause();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static void resume_bgm(IngameMenuContext *ctx) {
|
|
|
|
if(ctx->bgm_interrupted) {
|
|
|
|
if(ctx->saved_bgm) {
|
|
|
|
audio_bgm_play(ctx->saved_bgm, true, ctx->saved_bgm_pos, 0.5);
|
|
|
|
ctx->saved_bgm = NULL;
|
|
|
|
} else {
|
|
|
|
audio_bgm_resume();
|
|
|
|
}
|
|
|
|
|
|
|
|
ctx->bgm_interrupted = false;
|
|
|
|
}
|
2017-12-17 01:00:06 +01:00
|
|
|
}
|
|
|
|
|
Emscripten compatibility (#161)
* Major refactoring of the main loop(s) and control flow (WIP)
run_at_fps() is gone 🦀
Instead of nested blocking event loops, there is now an eventloop API
that manages an explicit stack of scenes. This makes Taisei a lot more
portable to async environments where spinning a loop forever without
yielding control simply is not an option, and that is the entire point
of this change.
A prime example of such an environment is the Web (via emscripten).
Taisei was able to run there through a terrible hack: inserting
emscripten_sleep calls into the loop, which would yield to the browser.
This has several major drawbacks: first of all, every function that
could possibly call emscripten_sleep must be compiled into a special
kind of bytecode, which then has to be interpreted at runtime, *much*
slower than JITed WebAssembly. And that includes *everything* down the
call stack, too! For more information, see
https://emscripten.org/docs/porting/emterpreter.html
Even though that method worked well enough for experimenting, despite
suboptimal performance, there is another obvious drawback:
emscripten_sleep is implemented via setTimeout(), which can be very
imprecise and is generally not reliable for fluid animation. Browsers
actually have an API specifically for that use case:
window.requestAnimationFrame(), but Taisei's original blocking control
flow style is simply not compatible with it. Emscripten exposes this API
with its emscripten_set_main_loop(), which the eventloop backend now
uses on that platform.
Unfortunately, C is still C, with no fancy closures or coroutines.
With blocking calls into menu/scene loops gone, the control flow is
reimplemented via so-called (pun intended) "call chains". That is
basically an euphemism for callback hell. With manual memory management
and zero type-safety. Not that the menu system wasn't shitty enough
already. I'll just keep telling myself that this is all temporary and
will be replaced with scripts in v1.4.
* improve build system for emscripten + various fixes
* squish menu bugs
* improve emscripten event loop; disable EMULATE_FUNCTION_POINTER_CASTS
Note that stock freetype does not work without
EMULATE_FUNCTION_POINTER_CASTS; use a patched version from the
"emscripten" branch here:
https://github.com/taisei-project/freetype2/tree/emscripten
* Enable -Wcast-function-type
Calling functions through incompatible pointers is nasal demons and
doesn't work in WASM.
* webgl: workaround a crash on some browsers
* emscripten improvements:
* Persist state (config, progress, replays, ...) in local IndexDB
* Simpler HTML shell (temporary)
* Enable more optimizations
* fix build if validate_glsl=false
* emscripten: improve asset packaging, with local cache
Note that even though there are rules to build audio bundles, audio
does *not* work yet. It looks like SDL2_mixer can not work without
threads, which is a problem. Yet another reason to write an OpenAL
backend - emscripten supports that natively.
* emscripten: customize the html shell
* emscripten: force "show log" checkbox unchecked initially
* emscripten: remove quit shortcut from main menu (since there's no quit)
* emscripten: log area fixes
* emscripten/webgl: workaround for fullscreen viewport issue
* emscripten: implement frameskip
* emscripter: improve framerate limiter
* align List to at least 8 bytes (shut up warnings)
* fix non-emscripten builds
* improve fullscreen handling, mainly for emscripten
* Workaround to make audio work in chromium
emscripten-core/emscripten#6511
* emscripten: better vsync handling; enable vsync & disable fxaa by default
2019-03-09 20:32:32 +01:00
|
|
|
static void stage_leave_ingame_menu(CallChainResult ccr) {
|
2020-06-24 19:51:00 +02:00
|
|
|
IngameMenuContext *ctx = ccr.ctx;
|
Emscripten compatibility (#161)
* Major refactoring of the main loop(s) and control flow (WIP)
run_at_fps() is gone 🦀
Instead of nested blocking event loops, there is now an eventloop API
that manages an explicit stack of scenes. This makes Taisei a lot more
portable to async environments where spinning a loop forever without
yielding control simply is not an option, and that is the entire point
of this change.
A prime example of such an environment is the Web (via emscripten).
Taisei was able to run there through a terrible hack: inserting
emscripten_sleep calls into the loop, which would yield to the browser.
This has several major drawbacks: first of all, every function that
could possibly call emscripten_sleep must be compiled into a special
kind of bytecode, which then has to be interpreted at runtime, *much*
slower than JITed WebAssembly. And that includes *everything* down the
call stack, too! For more information, see
https://emscripten.org/docs/porting/emterpreter.html
Even though that method worked well enough for experimenting, despite
suboptimal performance, there is another obvious drawback:
emscripten_sleep is implemented via setTimeout(), which can be very
imprecise and is generally not reliable for fluid animation. Browsers
actually have an API specifically for that use case:
window.requestAnimationFrame(), but Taisei's original blocking control
flow style is simply not compatible with it. Emscripten exposes this API
with its emscripten_set_main_loop(), which the eventloop backend now
uses on that platform.
Unfortunately, C is still C, with no fancy closures or coroutines.
With blocking calls into menu/scene loops gone, the control flow is
reimplemented via so-called (pun intended) "call chains". That is
basically an euphemism for callback hell. With manual memory management
and zero type-safety. Not that the menu system wasn't shitty enough
already. I'll just keep telling myself that this is all temporary and
will be replaced with scripts in v1.4.
* improve build system for emscripten + various fixes
* squish menu bugs
* improve emscripten event loop; disable EMULATE_FUNCTION_POINTER_CASTS
Note that stock freetype does not work without
EMULATE_FUNCTION_POINTER_CASTS; use a patched version from the
"emscripten" branch here:
https://github.com/taisei-project/freetype2/tree/emscripten
* Enable -Wcast-function-type
Calling functions through incompatible pointers is nasal demons and
doesn't work in WASM.
* webgl: workaround a crash on some browsers
* emscripten improvements:
* Persist state (config, progress, replays, ...) in local IndexDB
* Simpler HTML shell (temporary)
* Enable more optimizations
* fix build if validate_glsl=false
* emscripten: improve asset packaging, with local cache
Note that even though there are rules to build audio bundles, audio
does *not* work yet. It looks like SDL2_mixer can not work without
threads, which is a problem. Yet another reason to write an OpenAL
backend - emscripten supports that natively.
* emscripten: customize the html shell
* emscripten: force "show log" checkbox unchecked initially
* emscripten: remove quit shortcut from main menu (since there's no quit)
* emscripten: log area fixes
* emscripten/webgl: workaround for fullscreen viewport issue
* emscripten: implement frameskip
* emscripter: improve framerate limiter
* align List to at least 8 bytes (shut up warnings)
* fix non-emscripten builds
* improve fullscreen handling, mainly for emscripten
* Workaround to make audio work in chromium
emscripten-core/emscripten#6511
* emscripten: better vsync handling; enable vsync & disable fxaa by default
2019-03-09 20:32:32 +01:00
|
|
|
MenuData *m = ccr.result;
|
2017-10-10 16:06:46 +02:00
|
|
|
|
Emscripten compatibility (#161)
* Major refactoring of the main loop(s) and control flow (WIP)
run_at_fps() is gone 🦀
Instead of nested blocking event loops, there is now an eventloop API
that manages an explicit stack of scenes. This makes Taisei a lot more
portable to async environments where spinning a loop forever without
yielding control simply is not an option, and that is the entire point
of this change.
A prime example of such an environment is the Web (via emscripten).
Taisei was able to run there through a terrible hack: inserting
emscripten_sleep calls into the loop, which would yield to the browser.
This has several major drawbacks: first of all, every function that
could possibly call emscripten_sleep must be compiled into a special
kind of bytecode, which then has to be interpreted at runtime, *much*
slower than JITed WebAssembly. And that includes *everything* down the
call stack, too! For more information, see
https://emscripten.org/docs/porting/emterpreter.html
Even though that method worked well enough for experimenting, despite
suboptimal performance, there is another obvious drawback:
emscripten_sleep is implemented via setTimeout(), which can be very
imprecise and is generally not reliable for fluid animation. Browsers
actually have an API specifically for that use case:
window.requestAnimationFrame(), but Taisei's original blocking control
flow style is simply not compatible with it. Emscripten exposes this API
with its emscripten_set_main_loop(), which the eventloop backend now
uses on that platform.
Unfortunately, C is still C, with no fancy closures or coroutines.
With blocking calls into menu/scene loops gone, the control flow is
reimplemented via so-called (pun intended) "call chains". That is
basically an euphemism for callback hell. With manual memory management
and zero type-safety. Not that the menu system wasn't shitty enough
already. I'll just keep telling myself that this is all temporary and
will be replaced with scripts in v1.4.
* improve build system for emscripten + various fixes
* squish menu bugs
* improve emscripten event loop; disable EMULATE_FUNCTION_POINTER_CASTS
Note that stock freetype does not work without
EMULATE_FUNCTION_POINTER_CASTS; use a patched version from the
"emscripten" branch here:
https://github.com/taisei-project/freetype2/tree/emscripten
* Enable -Wcast-function-type
Calling functions through incompatible pointers is nasal demons and
doesn't work in WASM.
* webgl: workaround a crash on some browsers
* emscripten improvements:
* Persist state (config, progress, replays, ...) in local IndexDB
* Simpler HTML shell (temporary)
* Enable more optimizations
* fix build if validate_glsl=false
* emscripten: improve asset packaging, with local cache
Note that even though there are rules to build audio bundles, audio
does *not* work yet. It looks like SDL2_mixer can not work without
threads, which is a problem. Yet another reason to write an OpenAL
backend - emscripten supports that natively.
* emscripten: customize the html shell
* emscripten: force "show log" checkbox unchecked initially
* emscripten: remove quit shortcut from main menu (since there's no quit)
* emscripten: log area fixes
* emscripten/webgl: workaround for fullscreen viewport issue
* emscripten: implement frameskip
* emscripter: improve framerate limiter
* align List to at least 8 bytes (shut up warnings)
* fix non-emscripten builds
* improve fullscreen handling, mainly for emscripten
* Workaround to make audio work in chromium
emscripten-core/emscripten#6511
* emscripten: better vsync handling; enable vsync & disable fxaa by default
2019-03-09 20:32:32 +01:00
|
|
|
if(m->state != MS_Dead) {
|
|
|
|
return;
|
|
|
|
}
|
2017-10-10 16:06:46 +02:00
|
|
|
|
2019-02-22 00:56:03 +01:00
|
|
|
if(global.gameover > 0) {
|
2020-06-22 16:41:03 +02:00
|
|
|
stop_all_sfx();
|
2017-10-10 16:06:46 +02:00
|
|
|
|
2020-06-24 19:51:00 +02:00
|
|
|
if(ctx->bgm_interrupted) {
|
|
|
|
audio_bgm_stop(global.gameover == GAMEOVER_RESTART ? BGM_FADE_SHORT : BGM_FADE_LONG);
|
2017-10-10 16:06:46 +02:00
|
|
|
}
|
|
|
|
} else {
|
2020-06-24 19:51:00 +02:00
|
|
|
resume_bgm(ctx);
|
2021-11-20 13:40:37 +01:00
|
|
|
events_emit(TE_GAME_PAUSE_STATE_CHANGED, false, NULL, NULL);
|
2017-10-10 16:06:46 +02:00
|
|
|
}
|
Emscripten compatibility (#161)
* Major refactoring of the main loop(s) and control flow (WIP)
run_at_fps() is gone 🦀
Instead of nested blocking event loops, there is now an eventloop API
that manages an explicit stack of scenes. This makes Taisei a lot more
portable to async environments where spinning a loop forever without
yielding control simply is not an option, and that is the entire point
of this change.
A prime example of such an environment is the Web (via emscripten).
Taisei was able to run there through a terrible hack: inserting
emscripten_sleep calls into the loop, which would yield to the browser.
This has several major drawbacks: first of all, every function that
could possibly call emscripten_sleep must be compiled into a special
kind of bytecode, which then has to be interpreted at runtime, *much*
slower than JITed WebAssembly. And that includes *everything* down the
call stack, too! For more information, see
https://emscripten.org/docs/porting/emterpreter.html
Even though that method worked well enough for experimenting, despite
suboptimal performance, there is another obvious drawback:
emscripten_sleep is implemented via setTimeout(), which can be very
imprecise and is generally not reliable for fluid animation. Browsers
actually have an API specifically for that use case:
window.requestAnimationFrame(), but Taisei's original blocking control
flow style is simply not compatible with it. Emscripten exposes this API
with its emscripten_set_main_loop(), which the eventloop backend now
uses on that platform.
Unfortunately, C is still C, with no fancy closures or coroutines.
With blocking calls into menu/scene loops gone, the control flow is
reimplemented via so-called (pun intended) "call chains". That is
basically an euphemism for callback hell. With manual memory management
and zero type-safety. Not that the menu system wasn't shitty enough
already. I'll just keep telling myself that this is all temporary and
will be replaced with scripts in v1.4.
* improve build system for emscripten + various fixes
* squish menu bugs
* improve emscripten event loop; disable EMULATE_FUNCTION_POINTER_CASTS
Note that stock freetype does not work without
EMULATE_FUNCTION_POINTER_CASTS; use a patched version from the
"emscripten" branch here:
https://github.com/taisei-project/freetype2/tree/emscripten
* Enable -Wcast-function-type
Calling functions through incompatible pointers is nasal demons and
doesn't work in WASM.
* webgl: workaround a crash on some browsers
* emscripten improvements:
* Persist state (config, progress, replays, ...) in local IndexDB
* Simpler HTML shell (temporary)
* Enable more optimizations
* fix build if validate_glsl=false
* emscripten: improve asset packaging, with local cache
Note that even though there are rules to build audio bundles, audio
does *not* work yet. It looks like SDL2_mixer can not work without
threads, which is a problem. Yet another reason to write an OpenAL
backend - emscripten supports that natively.
* emscripten: customize the html shell
* emscripten: force "show log" checkbox unchecked initially
* emscripten: remove quit shortcut from main menu (since there's no quit)
* emscripten: log area fixes
* emscripten/webgl: workaround for fullscreen viewport issue
* emscripten: implement frameskip
* emscripter: improve framerate limiter
* align List to at least 8 bytes (shut up warnings)
* fix non-emscripten builds
* improve fullscreen handling, mainly for emscripten
* Workaround to make audio work in chromium
emscripten-core/emscripten#6511
* emscripten: better vsync handling; enable vsync & disable fxaa by default
2019-03-09 20:32:32 +01:00
|
|
|
|
2020-06-22 16:41:03 +02:00
|
|
|
resume_all_sfx();
|
|
|
|
|
2020-06-24 19:51:00 +02:00
|
|
|
run_call_chain(&ctx->next, NULL);
|
2023-01-09 04:19:31 +01:00
|
|
|
mem_free(ctx);
|
2012-07-14 16:37:52 +02:00
|
|
|
}
|
|
|
|
|
2020-06-24 19:51:00 +02:00
|
|
|
static void stage_enter_ingame_menu(MenuData *m, BGM *bgm, CallChain next) {
|
2021-11-20 13:40:37 +01:00
|
|
|
events_emit(TE_GAME_PAUSE_STATE_CHANGED, true, NULL, NULL);
|
2023-01-09 04:19:31 +01:00
|
|
|
auto ctx = ALLOC(IngameMenuContext, { .next = next });
|
2020-06-24 19:51:00 +02:00
|
|
|
setup_ingame_menu_bgm(ctx, bgm);
|
2020-06-22 16:41:03 +02:00
|
|
|
pause_all_sfx();
|
2020-06-24 19:51:00 +02:00
|
|
|
enter_menu(m, CALLCHAIN(stage_leave_ingame_menu, ctx));
|
Emscripten compatibility (#161)
* Major refactoring of the main loop(s) and control flow (WIP)
run_at_fps() is gone 🦀
Instead of nested blocking event loops, there is now an eventloop API
that manages an explicit stack of scenes. This makes Taisei a lot more
portable to async environments where spinning a loop forever without
yielding control simply is not an option, and that is the entire point
of this change.
A prime example of such an environment is the Web (via emscripten).
Taisei was able to run there through a terrible hack: inserting
emscripten_sleep calls into the loop, which would yield to the browser.
This has several major drawbacks: first of all, every function that
could possibly call emscripten_sleep must be compiled into a special
kind of bytecode, which then has to be interpreted at runtime, *much*
slower than JITed WebAssembly. And that includes *everything* down the
call stack, too! For more information, see
https://emscripten.org/docs/porting/emterpreter.html
Even though that method worked well enough for experimenting, despite
suboptimal performance, there is another obvious drawback:
emscripten_sleep is implemented via setTimeout(), which can be very
imprecise and is generally not reliable for fluid animation. Browsers
actually have an API specifically for that use case:
window.requestAnimationFrame(), but Taisei's original blocking control
flow style is simply not compatible with it. Emscripten exposes this API
with its emscripten_set_main_loop(), which the eventloop backend now
uses on that platform.
Unfortunately, C is still C, with no fancy closures or coroutines.
With blocking calls into menu/scene loops gone, the control flow is
reimplemented via so-called (pun intended) "call chains". That is
basically an euphemism for callback hell. With manual memory management
and zero type-safety. Not that the menu system wasn't shitty enough
already. I'll just keep telling myself that this is all temporary and
will be replaced with scripts in v1.4.
* improve build system for emscripten + various fixes
* squish menu bugs
* improve emscripten event loop; disable EMULATE_FUNCTION_POINTER_CASTS
Note that stock freetype does not work without
EMULATE_FUNCTION_POINTER_CASTS; use a patched version from the
"emscripten" branch here:
https://github.com/taisei-project/freetype2/tree/emscripten
* Enable -Wcast-function-type
Calling functions through incompatible pointers is nasal demons and
doesn't work in WASM.
* webgl: workaround a crash on some browsers
* emscripten improvements:
* Persist state (config, progress, replays, ...) in local IndexDB
* Simpler HTML shell (temporary)
* Enable more optimizations
* fix build if validate_glsl=false
* emscripten: improve asset packaging, with local cache
Note that even though there are rules to build audio bundles, audio
does *not* work yet. It looks like SDL2_mixer can not work without
threads, which is a problem. Yet another reason to write an OpenAL
backend - emscripten supports that natively.
* emscripten: customize the html shell
* emscripten: force "show log" checkbox unchecked initially
* emscripten: remove quit shortcut from main menu (since there's no quit)
* emscripten: log area fixes
* emscripten/webgl: workaround for fullscreen viewport issue
* emscripten: implement frameskip
* emscripter: improve framerate limiter
* align List to at least 8 bytes (shut up warnings)
* fix non-emscripten builds
* improve fullscreen handling, mainly for emscripten
* Workaround to make audio work in chromium
emscripten-core/emscripten#6511
* emscripten: better vsync handling; enable vsync & disable fxaa by default
2019-03-09 20:32:32 +01:00
|
|
|
}
|
2018-01-10 20:50:01 +01:00
|
|
|
|
Emscripten compatibility (#161)
* Major refactoring of the main loop(s) and control flow (WIP)
run_at_fps() is gone 🦀
Instead of nested blocking event loops, there is now an eventloop API
that manages an explicit stack of scenes. This makes Taisei a lot more
portable to async environments where spinning a loop forever without
yielding control simply is not an option, and that is the entire point
of this change.
A prime example of such an environment is the Web (via emscripten).
Taisei was able to run there through a terrible hack: inserting
emscripten_sleep calls into the loop, which would yield to the browser.
This has several major drawbacks: first of all, every function that
could possibly call emscripten_sleep must be compiled into a special
kind of bytecode, which then has to be interpreted at runtime, *much*
slower than JITed WebAssembly. And that includes *everything* down the
call stack, too! For more information, see
https://emscripten.org/docs/porting/emterpreter.html
Even though that method worked well enough for experimenting, despite
suboptimal performance, there is another obvious drawback:
emscripten_sleep is implemented via setTimeout(), which can be very
imprecise and is generally not reliable for fluid animation. Browsers
actually have an API specifically for that use case:
window.requestAnimationFrame(), but Taisei's original blocking control
flow style is simply not compatible with it. Emscripten exposes this API
with its emscripten_set_main_loop(), which the eventloop backend now
uses on that platform.
Unfortunately, C is still C, with no fancy closures or coroutines.
With blocking calls into menu/scene loops gone, the control flow is
reimplemented via so-called (pun intended) "call chains". That is
basically an euphemism for callback hell. With manual memory management
and zero type-safety. Not that the menu system wasn't shitty enough
already. I'll just keep telling myself that this is all temporary and
will be replaced with scripts in v1.4.
* improve build system for emscripten + various fixes
* squish menu bugs
* improve emscripten event loop; disable EMULATE_FUNCTION_POINTER_CASTS
Note that stock freetype does not work without
EMULATE_FUNCTION_POINTER_CASTS; use a patched version from the
"emscripten" branch here:
https://github.com/taisei-project/freetype2/tree/emscripten
* Enable -Wcast-function-type
Calling functions through incompatible pointers is nasal demons and
doesn't work in WASM.
* webgl: workaround a crash on some browsers
* emscripten improvements:
* Persist state (config, progress, replays, ...) in local IndexDB
* Simpler HTML shell (temporary)
* Enable more optimizations
* fix build if validate_glsl=false
* emscripten: improve asset packaging, with local cache
Note that even though there are rules to build audio bundles, audio
does *not* work yet. It looks like SDL2_mixer can not work without
threads, which is a problem. Yet another reason to write an OpenAL
backend - emscripten supports that natively.
* emscripten: customize the html shell
* emscripten: force "show log" checkbox unchecked initially
* emscripten: remove quit shortcut from main menu (since there's no quit)
* emscripten: log area fixes
* emscripten/webgl: workaround for fullscreen viewport issue
* emscripten: implement frameskip
* emscripter: improve framerate limiter
* align List to at least 8 bytes (shut up warnings)
* fix non-emscripten builds
* improve fullscreen handling, mainly for emscripten
* Workaround to make audio work in chromium
emscripten-core/emscripten#6511
* emscripten: better vsync handling; enable vsync & disable fxaa by default
2019-03-09 20:32:32 +01:00
|
|
|
void stage_pause(void) {
|
2020-06-22 16:41:03 +02:00
|
|
|
if(global.gameover == GAMEOVER_TRANSITIONING || stage_is_skip_mode()) {
|
Emscripten compatibility (#161)
* Major refactoring of the main loop(s) and control flow (WIP)
run_at_fps() is gone 🦀
Instead of nested blocking event loops, there is now an eventloop API
that manages an explicit stack of scenes. This makes Taisei a lot more
portable to async environments where spinning a loop forever without
yielding control simply is not an option, and that is the entire point
of this change.
A prime example of such an environment is the Web (via emscripten).
Taisei was able to run there through a terrible hack: inserting
emscripten_sleep calls into the loop, which would yield to the browser.
This has several major drawbacks: first of all, every function that
could possibly call emscripten_sleep must be compiled into a special
kind of bytecode, which then has to be interpreted at runtime, *much*
slower than JITed WebAssembly. And that includes *everything* down the
call stack, too! For more information, see
https://emscripten.org/docs/porting/emterpreter.html
Even though that method worked well enough for experimenting, despite
suboptimal performance, there is another obvious drawback:
emscripten_sleep is implemented via setTimeout(), which can be very
imprecise and is generally not reliable for fluid animation. Browsers
actually have an API specifically for that use case:
window.requestAnimationFrame(), but Taisei's original blocking control
flow style is simply not compatible with it. Emscripten exposes this API
with its emscripten_set_main_loop(), which the eventloop backend now
uses on that platform.
Unfortunately, C is still C, with no fancy closures or coroutines.
With blocking calls into menu/scene loops gone, the control flow is
reimplemented via so-called (pun intended) "call chains". That is
basically an euphemism for callback hell. With manual memory management
and zero type-safety. Not that the menu system wasn't shitty enough
already. I'll just keep telling myself that this is all temporary and
will be replaced with scripts in v1.4.
* improve build system for emscripten + various fixes
* squish menu bugs
* improve emscripten event loop; disable EMULATE_FUNCTION_POINTER_CASTS
Note that stock freetype does not work without
EMULATE_FUNCTION_POINTER_CASTS; use a patched version from the
"emscripten" branch here:
https://github.com/taisei-project/freetype2/tree/emscripten
* Enable -Wcast-function-type
Calling functions through incompatible pointers is nasal demons and
doesn't work in WASM.
* webgl: workaround a crash on some browsers
* emscripten improvements:
* Persist state (config, progress, replays, ...) in local IndexDB
* Simpler HTML shell (temporary)
* Enable more optimizations
* fix build if validate_glsl=false
* emscripten: improve asset packaging, with local cache
Note that even though there are rules to build audio bundles, audio
does *not* work yet. It looks like SDL2_mixer can not work without
threads, which is a problem. Yet another reason to write an OpenAL
backend - emscripten supports that natively.
* emscripten: customize the html shell
* emscripten: force "show log" checkbox unchecked initially
* emscripten: remove quit shortcut from main menu (since there's no quit)
* emscripten: log area fixes
* emscripten/webgl: workaround for fullscreen viewport issue
* emscripten: implement frameskip
* emscripter: improve framerate limiter
* align List to at least 8 bytes (shut up warnings)
* fix non-emscripten builds
* improve fullscreen handling, mainly for emscripten
* Workaround to make audio work in chromium
emscripten-core/emscripten#6511
* emscripten: better vsync handling; enable vsync & disable fxaa by default
2019-03-09 20:32:32 +01:00
|
|
|
return;
|
2018-01-10 20:50:01 +01:00
|
|
|
}
|
|
|
|
|
2021-05-30 03:26:21 +02:00
|
|
|
MenuData *m;
|
|
|
|
|
|
|
|
if(global.replay.input.replay) {
|
|
|
|
m = create_ingame_menu_replay();
|
|
|
|
} else {
|
|
|
|
m = create_ingame_menu();
|
|
|
|
}
|
2020-06-24 19:51:00 +02:00
|
|
|
|
|
|
|
stage_enter_ingame_menu(m, NULL, NO_CALLCHAIN);
|
2018-01-10 20:50:01 +01:00
|
|
|
}
|
|
|
|
|
2012-08-17 20:58:23 +02:00
|
|
|
void stage_gameover(void) {
|
2017-02-28 00:07:03 +01:00
|
|
|
if(global.stage->type == STAGE_SPELL && config_get_int(CONFIG_SPELLSTAGE_AUTORESTART)) {
|
2019-02-22 00:56:03 +01:00
|
|
|
global.gameover = GAMEOVER_RESTART;
|
2017-02-28 00:07:03 +01:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2020-06-24 19:51:00 +02:00
|
|
|
BGM *bgm = NULL;
|
|
|
|
|
|
|
|
if(ingame_menu_interrupts_bgm()) {
|
|
|
|
bgm = res_bgm("gameover");
|
|
|
|
progress_unlock_bgm("gameover");
|
|
|
|
}
|
|
|
|
|
|
|
|
stage_enter_ingame_menu(create_gameover_menu(), bgm, NO_CALLCHAIN);
|
2012-08-17 20:58:23 +02:00
|
|
|
}
|
|
|
|
|
2017-12-28 02:35:53 +01:00
|
|
|
static bool stage_input_common(SDL_Event *event, void *arg) {
|
|
|
|
TaiseiEvent type = TAISEI_EVENT(event->type);
|
|
|
|
int32_t code = event->user.code;
|
|
|
|
|
|
|
|
switch(type) {
|
|
|
|
case TE_GAME_KEY_DOWN:
|
|
|
|
switch(code) {
|
|
|
|
case KEY_STOP:
|
|
|
|
stage_finish(GAMEOVER_DEFEAT);
|
|
|
|
return true;
|
|
|
|
|
|
|
|
case KEY_RESTART:
|
|
|
|
stage_finish(GAMEOVER_RESTART);
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
case TE_GAME_PAUSE:
|
|
|
|
stage_pause();
|
|
|
|
break;
|
|
|
|
|
|
|
|
default:
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2019-02-22 00:56:03 +01:00
|
|
|
static bool stage_input_key_filter(KeyIndex key, bool is_release) {
|
|
|
|
if(key == KEY_HAHAIWIN) {
|
|
|
|
IF_DEBUG(
|
|
|
|
if(!is_release) {
|
|
|
|
stage_finish(GAMEOVER_WIN);
|
|
|
|
}
|
|
|
|
);
|
|
|
|
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
IF_NOT_DEBUG(
|
|
|
|
if(
|
|
|
|
key == KEY_IDDQD ||
|
|
|
|
key == KEY_POWERUP ||
|
|
|
|
key == KEY_POWERDOWN
|
|
|
|
) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
);
|
|
|
|
|
|
|
|
if(stage_is_cleared()) {
|
|
|
|
if(key == KEY_SHOT) {
|
|
|
|
if(
|
|
|
|
global.gameover == GAMEOVER_SCORESCREEN &&
|
|
|
|
global.frames - global.gameover_time > GAMEOVER_SCORE_DELAY * 2
|
|
|
|
) {
|
|
|
|
if(!is_release) {
|
|
|
|
stage_finish(GAMEOVER_WIN);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if(key == KEY_BOMB || key == KEY_SPECIAL) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2022-02-16 10:45:49 +01:00
|
|
|
attr_nonnull_all
|
2022-01-09 13:11:26 +01:00
|
|
|
static Replay *create_quicksave_replay(ReplayStage *rstg_src) {
|
|
|
|
ReplayStage *rstg = memdup(rstg_src, sizeof(*rstg));
|
|
|
|
rstg->num_events = 0;
|
|
|
|
memset(&rstg->events, 0, sizeof(rstg->events));
|
|
|
|
|
|
|
|
dynarray_ensure_capacity(&rstg->events, rstg_src->events.num_elements + 1);
|
|
|
|
dynarray_set_elements(&rstg->events, rstg_src->events.num_elements, rstg_src->events.data);
|
|
|
|
replay_stage_event(rstg, global.frames, EV_RESUME, 0);
|
|
|
|
|
2023-01-09 04:19:31 +01:00
|
|
|
auto rpy = ALLOC(Replay);
|
2022-01-09 13:11:26 +01:00
|
|
|
rpy->stages.num_elements = rpy->stages.capacity = 1;
|
|
|
|
rpy->stages.data = rstg;
|
|
|
|
|
|
|
|
log_info("Created quicksave replay on frame %i", global.frames);
|
|
|
|
return rpy;
|
|
|
|
}
|
|
|
|
|
|
|
|
static inline bool is_quicksave_allowed(void) {
|
|
|
|
#ifndef DEBUG
|
|
|
|
if(global.is_practice_mode) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
|
|
|
|
if(global.gameover != GAMEOVER_NONE) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2022-01-28 17:03:22 +01:00
|
|
|
static void stage_do_quicksave(StageFrameState *fstate, bool isauto) {
|
|
|
|
if(isauto && fstate->quicksave && !fstate->quicksave_is_automatic) {
|
|
|
|
// Do not overwrite a manual quicksave with an auto quicksave
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if(fstate->quicksave) {
|
|
|
|
replay_reset(fstate->quicksave);
|
2023-01-09 04:19:31 +01:00
|
|
|
mem_free(fstate->quicksave);
|
2022-01-28 17:03:22 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
fstate->quicksave = create_quicksave_replay(global.replay.output.stage);
|
|
|
|
fstate->quicksave_is_automatic = isauto;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void stage_do_quickload(StageFrameState *fstate) {
|
|
|
|
if(fstate->quicksave) {
|
|
|
|
fstate->quickload_requested = true;
|
|
|
|
} else {
|
|
|
|
log_info("No active quicksave");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-01-24 21:21:08 +01:00
|
|
|
static bool stage_input_handler_gameplay(SDL_Event *event, void *arg) {
|
2022-01-09 13:11:26 +01:00
|
|
|
StageFrameState *fstate = NOT_NULL(arg);
|
|
|
|
|
|
|
|
if(event->type == SDL_KEYDOWN && !event->key.repeat && is_quicksave_allowed()) {
|
|
|
|
if(event->key.keysym.scancode == config_get_int(CONFIG_KEY_QUICKSAVE)) {
|
2022-01-28 17:03:22 +01:00
|
|
|
stage_do_quicksave(fstate, false);
|
2022-01-09 13:11:26 +01:00
|
|
|
} else if(event->key.keysym.scancode == config_get_int(CONFIG_KEY_QUICKLOAD)) {
|
2022-01-28 17:03:22 +01:00
|
|
|
stage_do_quickload(fstate);
|
2022-01-09 13:11:26 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2017-09-29 21:03:49 +02:00
|
|
|
TaiseiEvent type = TAISEI_EVENT(event->type);
|
|
|
|
int32_t code = event->user.code;
|
|
|
|
|
2017-12-28 02:35:53 +01:00
|
|
|
if(stage_input_common(event, arg)) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2021-05-30 03:26:21 +02:00
|
|
|
if(
|
|
|
|
(type == TE_GAME_KEY_DOWN || type == TE_GAME_KEY_UP) &&
|
|
|
|
code == KEY_SHOT &&
|
|
|
|
config_get_int(CONFIG_SHOT_INVERTED)
|
|
|
|
) {
|
|
|
|
type = type == TE_GAME_KEY_DOWN ? TE_GAME_KEY_UP : TE_GAME_KEY_DOWN;
|
|
|
|
}
|
|
|
|
|
|
|
|
ReplayState *rpy = &global.replay.output;
|
|
|
|
|
2012-08-13 17:50:28 +02:00
|
|
|
switch(type) {
|
2017-09-29 21:03:49 +02:00
|
|
|
case TE_GAME_KEY_DOWN:
|
2019-02-22 00:56:03 +01:00
|
|
|
if(stage_input_key_filter(code, false)) {
|
2021-05-30 03:26:21 +02:00
|
|
|
player_event(&global.plr, NULL, rpy, EV_PRESS, code);
|
2017-02-15 18:10:56 +01:00
|
|
|
}
|
2012-08-13 17:50:28 +02:00
|
|
|
break;
|
2017-02-10 23:05:22 +01:00
|
|
|
|
2017-09-29 21:03:49 +02:00
|
|
|
case TE_GAME_KEY_UP:
|
2019-02-22 00:56:03 +01:00
|
|
|
if(stage_input_key_filter(code, true)) {
|
2021-05-30 03:26:21 +02:00
|
|
|
player_event(&global.plr, NULL, rpy, EV_RELEASE, code);
|
2019-02-22 00:56:03 +01:00
|
|
|
}
|
2012-08-07 05:28:41 +02:00
|
|
|
break;
|
2017-02-10 23:05:22 +01:00
|
|
|
|
2017-09-29 21:03:49 +02:00
|
|
|
case TE_GAME_AXIS_LR:
|
2021-05-30 03:26:21 +02:00
|
|
|
player_event(&global.plr, NULL, rpy, EV_AXIS_LR, (uint16_t)code);
|
2012-08-15 16:36:39 +02:00
|
|
|
break;
|
2017-02-10 23:05:22 +01:00
|
|
|
|
2017-09-29 21:03:49 +02:00
|
|
|
case TE_GAME_AXIS_UD:
|
2021-05-30 03:26:21 +02:00
|
|
|
player_event(&global.plr, NULL, rpy, EV_AXIS_UD, (uint16_t)code);
|
2012-08-15 16:36:39 +02:00
|
|
|
break;
|
2017-02-10 23:05:22 +01:00
|
|
|
|
2012-08-13 17:50:28 +02:00
|
|
|
default: break;
|
|
|
|
}
|
2017-09-29 21:03:49 +02:00
|
|
|
|
|
|
|
return false;
|
2012-08-13 17:50:28 +02:00
|
|
|
}
|
|
|
|
|
2019-01-24 21:21:08 +01:00
|
|
|
static bool stage_input_handler_replay(SDL_Event *event, void *arg) {
|
2017-12-28 02:35:53 +01:00
|
|
|
stage_input_common(event, arg);
|
2017-09-29 21:03:49 +02:00
|
|
|
return false;
|
2012-08-13 17:50:28 +02:00
|
|
|
}
|
|
|
|
|
2022-01-09 13:11:26 +01:00
|
|
|
struct replay_event_arg {
|
|
|
|
ReplayState *st;
|
|
|
|
ReplayEvent *resume_event;
|
|
|
|
};
|
|
|
|
|
2021-05-30 03:26:21 +02:00
|
|
|
static void handle_replay_event(ReplayEvent *e, void *arg) {
|
2022-01-09 13:11:26 +01:00
|
|
|
struct replay_event_arg *a = NOT_NULL(arg);
|
2021-05-30 03:26:21 +02:00
|
|
|
|
2022-01-09 13:11:26 +01:00
|
|
|
if(UNLIKELY(a->resume_event != NULL)) {
|
|
|
|
log_warn(
|
|
|
|
"Got replay event [%i:%02x:%04x] after resume event in the same frame, ignoring",
|
|
|
|
e->frame, e->type, e->value
|
|
|
|
);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
switch(e->type) {
|
|
|
|
case EV_OVER:
|
|
|
|
global.gameover = GAMEOVER_DEFEAT;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case EV_RESUME:
|
|
|
|
a->resume_event = e;
|
|
|
|
break;
|
|
|
|
|
|
|
|
default:
|
|
|
|
player_event(&global.plr, a->st, &global.replay.output, e->type, e->value);
|
|
|
|
break;
|
2021-05-30 03:26:21 +02:00
|
|
|
}
|
|
|
|
}
|
2017-02-10 23:05:22 +01:00
|
|
|
|
2022-01-28 17:03:22 +01:00
|
|
|
static void leave_replay_mode(StageFrameState *fstate, ReplayState *rp_in) {
|
|
|
|
if(rp_in->replay == fstate->quicksave) {
|
|
|
|
audio_sfx_set_enabled(true);
|
|
|
|
sync_bgm(fstate);
|
|
|
|
}
|
|
|
|
|
|
|
|
replay_state_deinit(rp_in);
|
|
|
|
}
|
|
|
|
|
2022-01-09 13:11:26 +01:00
|
|
|
static void replay_input(StageFrameState *fstate) {
|
|
|
|
if(!is_quickloading(fstate)) {
|
|
|
|
events_poll((EventHandler[]){
|
|
|
|
{ .proc = stage_input_handler_replay },
|
|
|
|
{ NULL }
|
|
|
|
}, EFLAG_GAME);
|
|
|
|
}
|
|
|
|
|
|
|
|
ReplayState *rp_in = &global.replay.input;
|
2017-02-10 23:05:22 +01:00
|
|
|
|
2022-01-09 13:11:26 +01:00
|
|
|
if(UNLIKELY(rp_in->mode == REPLAY_NONE)) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
struct replay_event_arg a = { .st = rp_in };
|
|
|
|
replay_state_play_advance(rp_in, global.frames, handle_replay_event, &a);
|
2012-07-14 19:46:03 +02:00
|
|
|
player_applymovement(&global.plr);
|
2022-01-09 13:11:26 +01:00
|
|
|
|
|
|
|
if(a.resume_event) {
|
2022-01-28 17:03:22 +01:00
|
|
|
leave_replay_mode(fstate, rp_in);
|
2022-01-09 13:11:26 +01:00
|
|
|
}
|
2012-07-14 19:46:03 +02:00
|
|
|
}
|
|
|
|
|
2020-06-22 16:41:03 +02:00
|
|
|
static void display_bgm_title(void) {
|
|
|
|
BGM *bgm = audio_bgm_current();
|
|
|
|
const char *title = bgm ? bgm_get_title(bgm) : NULL;
|
|
|
|
|
|
|
|
if(title) {
|
|
|
|
char txt[strlen(title) + 6];
|
|
|
|
snprintf(txt, sizeof(txt), "BGM: %s", title);
|
|
|
|
stagetext_add(txt, VIEWPORT_W-15 + I * (VIEWPORT_H-20), ALIGN_RIGHT, res_font("standard"), RGB(1, 1, 1), 30, 180, 35, 35);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static bool stage_handle_bgm_change(SDL_Event *evt, void *a) {
|
2022-01-09 13:11:26 +01:00
|
|
|
StageFrameState *fstate = NOT_NULL(a);
|
|
|
|
fstate->bgm_start_time = global.frames;
|
|
|
|
fstate->bgm_start_pos = audio_bgm_tell();
|
|
|
|
|
2020-06-22 16:41:03 +02:00
|
|
|
if(dialog_is_active(global.dialog)) {
|
|
|
|
INVOKE_TASK_WHEN(&global.dialog->events.fadeout_began, common_call_func, display_bgm_title);
|
|
|
|
} else {
|
|
|
|
display_bgm_title();
|
|
|
|
}
|
|
|
|
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2022-01-09 13:11:26 +01:00
|
|
|
static void stage_input(StageFrameState *fstate) {
|
2021-05-05 19:32:56 +02:00
|
|
|
if(stage_is_skip_mode()) {
|
|
|
|
events_poll((EventHandler[]){
|
2022-01-09 13:11:26 +01:00
|
|
|
{
|
|
|
|
.proc = stage_handle_bgm_change,
|
|
|
|
.event_type = MAKE_TAISEI_EVENT(TE_AUDIO_BGM_STARTED),
|
|
|
|
.arg = fstate,
|
|
|
|
},
|
2021-05-05 19:32:56 +02:00
|
|
|
{NULL}
|
|
|
|
}, EFLAG_NOPUMP);
|
|
|
|
} else {
|
|
|
|
events_poll((EventHandler[]){
|
2022-01-09 13:11:26 +01:00
|
|
|
{ .proc = stage_input_handler_gameplay, .arg = fstate },
|
|
|
|
{
|
|
|
|
.proc = stage_handle_bgm_change,
|
|
|
|
.event_type = MAKE_TAISEI_EVENT(TE_AUDIO_BGM_STARTED),
|
|
|
|
.arg = fstate,
|
|
|
|
},
|
2021-05-05 19:32:56 +02:00
|
|
|
{NULL}
|
|
|
|
}, EFLAG_GAME);
|
|
|
|
}
|
|
|
|
|
2021-05-30 03:26:21 +02:00
|
|
|
player_fix_input(&global.plr, &global.replay.output);
|
2017-03-07 00:57:14 +01:00
|
|
|
player_applymovement(&global.plr);
|
2010-10-12 10:55:23 +02:00
|
|
|
}
|
|
|
|
|
2017-02-26 13:17:48 +01:00
|
|
|
static void stage_logic(void) {
|
Give projectiles EVENT_BIRTH and call them with t==0 more consistently
The only case of t==0 being skipped is if the projectile was somehow
created after the projectile processing loop for this frame has been
finished. Currently that is only possible if a particle spawns a
non-particle projectile, which, ideally, should never happen. The same
problem exists with other types of entities. For example, if you have a
funny projectile rule that spawns an Enemy, that Enemy will have the 0th
frame skipped. One way to fix this is to maintain a list of all entities
in the game and process them all in a single loop, where newly spawned
entities would be appended to the tail of the list. This is also
probably the only good way to fix this, too.
Handling of all projectile events has been made mandatory to facilitate
easier debugging of subtle and/or hard to track bugs. If t<0, then the
projectile rule MUST return ACTION_ACK, signifying that it acknowledged
the event and handled it appropriately. Otherwise, it SHOULD return
ACTION_NONE or ACTION_DESTROY. In a perfect world, those just wouldn't
be conflated in the same function with update logic.
I've also cleaned up the stage_logic routine a bit. Moved most of the
dialog handling into dialog.c and gave higher priority to boss
processing. The later is currently necessary to let boss-spawned
projectiles and particles to get called with t==0.
2018-05-16 01:38:47 +02:00
|
|
|
process_boss(&global.boss);
|
2011-04-26 12:04:45 +02:00
|
|
|
process_enemies(&global.enemies);
|
2017-02-11 04:52:08 +01:00
|
|
|
process_projectiles(&global.projs, true);
|
2011-04-29 10:26:37 +02:00
|
|
|
process_items();
|
2011-04-24 15:39:17 +02:00
|
|
|
process_lasers();
|
2017-02-11 04:52:08 +01:00
|
|
|
process_projectiles(&global.particles, false);
|
2020-01-23 01:23:35 +01:00
|
|
|
|
|
|
|
if(global.dialog) {
|
|
|
|
dialog_update(global.dialog);
|
|
|
|
|
|
|
|
if((global.plr.inputflags & INFLAG_SKIP) && dialog_is_active(global.dialog)) {
|
|
|
|
dialog_page(global.dialog);
|
|
|
|
}
|
|
|
|
}
|
2017-02-10 23:05:22 +01:00
|
|
|
|
2020-06-22 16:41:03 +02:00
|
|
|
if(stage_is_skip_mode()) {
|
2019-10-11 15:58:51 +02:00
|
|
|
if(dialog_is_active(global.dialog)) {
|
2020-01-23 01:23:35 +01:00
|
|
|
dialog_page(global.dialog);
|
2019-10-11 15:58:51 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
if(global.boss) {
|
|
|
|
ent_damage(&global.boss->ent, &(DamageInfo) { 400, DMG_PLAYER_SHOT } );
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-06-22 16:41:03 +02:00
|
|
|
update_all_sfx();
|
2017-09-30 19:33:07 +02:00
|
|
|
|
2010-10-12 10:55:23 +02:00
|
|
|
global.frames++;
|
2017-02-10 23:05:22 +01:00
|
|
|
|
2019-07-03 19:50:43 +02:00
|
|
|
if(!dialog_is_active(global.dialog) && (!global.boss || boss_is_fleeing(global.boss))) {
|
2012-07-14 19:46:03 +02:00
|
|
|
global.timer++;
|
Give projectiles EVENT_BIRTH and call them with t==0 more consistently
The only case of t==0 being skipped is if the projectile was somehow
created after the projectile processing loop for this frame has been
finished. Currently that is only possible if a particle spawns a
non-particle projectile, which, ideally, should never happen. The same
problem exists with other types of entities. For example, if you have a
funny projectile rule that spawns an Enemy, that Enemy will have the 0th
frame skipped. One way to fix this is to maintain a list of all entities
in the game and process them all in a single loop, where newly spawned
entities would be appended to the tail of the list. This is also
probably the only good way to fix this, too.
Handling of all projectile events has been made mandatory to facilitate
easier debugging of subtle and/or hard to track bugs. If t<0, then the
projectile rule MUST return ACTION_ACK, signifying that it acknowledged
the event and handled it appropriately. Otherwise, it SHOULD return
ACTION_NONE or ACTION_DESTROY. In a perfect world, those just wouldn't
be conflated in the same function with update logic.
I've also cleaned up the stage_logic routine a bit. Moved most of the
dialog handling into dialog.c and gave higher priority to boss
processing. The later is currently necessary to let boss-spawned
projectiles and particles to get called with t==0.
2018-05-16 01:38:47 +02:00
|
|
|
}
|
2017-02-10 23:05:22 +01:00
|
|
|
|
2021-05-30 03:26:21 +02:00
|
|
|
if(global.replay.input.replay && global.gameover != GAMEOVER_TRANSITIONING) {
|
|
|
|
ReplayStage *rstg = global.replay.input.stage;
|
2020-04-05 04:51:00 +02:00
|
|
|
ReplayEvent *last_event = dynarray_get_ptr(&rstg->events, rstg->events.num_elements - 1);
|
|
|
|
|
2022-01-09 13:11:26 +01:00
|
|
|
if(global.frames == last_event->frame - FADE_TIME && last_event->type != EV_RESUME) {
|
2020-04-05 04:51:00 +02:00
|
|
|
stage_finish(GAMEOVER_DEFEAT);
|
|
|
|
}
|
2017-02-10 00:24:19 +01:00
|
|
|
}
|
2019-04-11 11:23:24 +02:00
|
|
|
|
|
|
|
stagetext_update();
|
2010-10-12 10:55:23 +02:00
|
|
|
}
|
|
|
|
|
2018-08-05 19:58:50 +02:00
|
|
|
void stage_clear_hazards_predicate(bool (*predicate)(EntityInterface *ent, void *arg), void *arg, ClearHazardsFlags flags) {
|
2019-03-26 16:58:38 +01:00
|
|
|
bool force = flags & CLEAR_HAZARDS_FORCE;
|
|
|
|
|
2018-01-06 10:24:46 +01:00
|
|
|
if(flags & CLEAR_HAZARDS_BULLETS) {
|
2019-01-26 02:54:57 +01:00
|
|
|
for(Projectile *p = global.projs.first, *next; p; p = next) {
|
|
|
|
next = p->next;
|
2018-08-05 19:58:50 +02:00
|
|
|
|
2019-03-26 16:58:38 +01:00
|
|
|
if(!force && !projectile_is_clearable(p)) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2018-08-05 19:58:50 +02:00
|
|
|
if(!predicate || predicate(&p->ent, arg)) {
|
2019-02-22 00:56:03 +01:00
|
|
|
clear_projectile(p, flags);
|
2018-08-05 19:58:50 +02:00
|
|
|
}
|
2018-01-06 09:54:13 +01:00
|
|
|
}
|
2017-04-06 00:46:00 +02:00
|
|
|
}
|
|
|
|
|
2018-01-06 10:24:46 +01:00
|
|
|
if(flags & CLEAR_HAZARDS_LASERS) {
|
2018-06-01 20:40:18 +02:00
|
|
|
for(Laser *l = global.lasers.first, *next; l; l = next) {
|
2018-01-06 19:23:38 +01:00
|
|
|
next = l->next;
|
2018-08-05 19:58:50 +02:00
|
|
|
|
2019-03-26 16:58:38 +01:00
|
|
|
if(!force && !laser_is_clearable(l)) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2018-08-05 19:58:50 +02:00
|
|
|
if(!predicate || predicate(&l->ent, arg)) {
|
2019-02-22 00:56:03 +01:00
|
|
|
clear_laser(l, flags);
|
2018-08-05 19:58:50 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void stage_clear_hazards(ClearHazardsFlags flags) {
|
|
|
|
stage_clear_hazards_predicate(NULL, NULL, flags);
|
|
|
|
}
|
|
|
|
|
|
|
|
static bool proximity_predicate(EntityInterface *ent, void *varg) {
|
|
|
|
Circle *area = varg;
|
|
|
|
|
|
|
|
switch(ent->type) {
|
2020-04-17 09:18:53 +02:00
|
|
|
case ENT_TYPE_ID(Projectile): {
|
2018-08-05 19:58:50 +02:00
|
|
|
Projectile *p = ENT_CAST(ent, Projectile);
|
|
|
|
return cabs(p->pos - area->origin) < area->radius;
|
2018-01-06 09:54:13 +01:00
|
|
|
}
|
2018-08-05 19:58:50 +02:00
|
|
|
|
2020-04-17 09:18:53 +02:00
|
|
|
case ENT_TYPE_ID(Laser): {
|
2018-08-05 19:58:50 +02:00
|
|
|
Laser *l = ENT_CAST(ent, Laser);
|
|
|
|
return laser_intersects_circle(l, *area);
|
|
|
|
}
|
|
|
|
|
|
|
|
default: UNREACHABLE;
|
2017-10-31 14:47:20 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-03-26 16:58:38 +01:00
|
|
|
static bool ellipse_predicate(EntityInterface *ent, void *varg) {
|
|
|
|
Ellipse *e = varg;
|
|
|
|
|
|
|
|
switch(ent->type) {
|
2020-04-17 09:18:53 +02:00
|
|
|
case ENT_TYPE_ID(Projectile): {
|
2019-03-26 16:58:38 +01:00
|
|
|
Projectile *p = ENT_CAST(ent, Projectile);
|
|
|
|
return point_in_ellipse(p->pos, *e);
|
|
|
|
}
|
|
|
|
|
2020-04-17 09:18:53 +02:00
|
|
|
case ENT_TYPE_ID(Laser): {
|
2019-03-26 16:58:38 +01:00
|
|
|
Laser *l = ENT_CAST(ent, Laser);
|
|
|
|
return laser_intersects_ellipse(l, *e);
|
|
|
|
}
|
|
|
|
|
|
|
|
default: UNREACHABLE;
|
|
|
|
}
|
|
|
|
}
|
2019-07-17 10:43:49 +02:00
|
|
|
|
2019-11-22 04:37:11 +01:00
|
|
|
void stage_clear_hazards_at(cmplx origin, double radius, ClearHazardsFlags flags) {
|
2018-08-05 19:58:50 +02:00
|
|
|
Circle area = { origin, radius };
|
|
|
|
stage_clear_hazards_predicate(proximity_predicate, &area, flags);
|
|
|
|
}
|
|
|
|
|
2019-03-26 16:58:38 +01:00
|
|
|
void stage_clear_hazards_in_ellipse(Ellipse e, ClearHazardsFlags flags) {
|
|
|
|
stage_clear_hazards_predicate(ellipse_predicate, &e, flags);
|
|
|
|
}
|
|
|
|
|
2021-08-12 22:31:06 +02:00
|
|
|
TASK(clear_dialog) {
|
2020-01-23 01:23:35 +01:00
|
|
|
assert(global.dialog != NULL);
|
|
|
|
// dialog_deinit() should've been called by dialog_end() at this point
|
|
|
|
global.dialog = NULL;
|
|
|
|
}
|
|
|
|
|
2021-08-12 22:31:06 +02:00
|
|
|
TASK(dialog_fixup_timer) {
|
2020-01-23 01:23:35 +01:00
|
|
|
// HACK: remove when global.timer is gone
|
|
|
|
global.timer++;
|
|
|
|
}
|
|
|
|
|
|
|
|
void stage_begin_dialog(Dialog *d) {
|
|
|
|
assert(global.dialog == NULL);
|
|
|
|
global.dialog = d;
|
|
|
|
dialog_init(d);
|
|
|
|
INVOKE_TASK_WHEN(&d->events.fadeout_began, dialog_fixup_timer);
|
|
|
|
INVOKE_TASK_WHEN(&d->events.fadeout_ended, clear_dialog);
|
|
|
|
}
|
|
|
|
|
2017-02-26 13:17:48 +01:00
|
|
|
static void stage_free(void) {
|
2011-04-26 22:39:50 +02:00
|
|
|
delete_enemies(&global.enemies);
|
2011-04-29 10:26:37 +02:00
|
|
|
delete_items();
|
2011-04-26 22:47:13 +02:00
|
|
|
delete_lasers();
|
2017-02-10 23:05:22 +01:00
|
|
|
|
2012-08-07 17:07:29 +02:00
|
|
|
delete_projectiles(&global.projs);
|
|
|
|
delete_projectiles(&global.particles);
|
2017-02-10 23:05:22 +01:00
|
|
|
|
2011-06-13 18:48:36 +02:00
|
|
|
if(global.dialog) {
|
2020-01-23 01:23:35 +01:00
|
|
|
dialog_deinit(global.dialog);
|
2011-06-13 18:48:36 +02:00
|
|
|
global.dialog = NULL;
|
|
|
|
}
|
2017-02-10 23:05:22 +01:00
|
|
|
|
2011-06-13 18:48:36 +02:00
|
|
|
if(global.boss) {
|
|
|
|
free_boss(global.boss);
|
|
|
|
global.boss = NULL;
|
|
|
|
}
|
2017-04-02 16:06:18 +02:00
|
|
|
|
2021-08-22 06:05:17 +02:00
|
|
|
lasers_shutdown();
|
2019-01-04 23:59:39 +01:00
|
|
|
projectiles_free();
|
2017-04-02 16:06:18 +02:00
|
|
|
stagetext_free();
|
2010-10-12 10:55:23 +02:00
|
|
|
}
|
2011-06-25 12:41:40 +02:00
|
|
|
|
2023-04-07 05:47:47 +02:00
|
|
|
static void stage_finalize(CallChainResult ccr) {
|
|
|
|
global.gameover = (intptr_t)ccr.ctx;
|
2017-02-26 13:17:48 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
void stage_finish(int gameover) {
|
2019-04-03 15:38:35 +02:00
|
|
|
if(global.gameover == GAMEOVER_TRANSITIONING) {
|
|
|
|
return;
|
|
|
|
}
|
2018-01-16 12:55:13 +01:00
|
|
|
|
2019-02-22 00:56:03 +01:00
|
|
|
int prev_gameover = global.gameover;
|
|
|
|
global.gameover_time = global.frames;
|
2017-04-06 02:58:57 +02:00
|
|
|
|
2019-02-22 00:56:03 +01:00
|
|
|
if(gameover == GAMEOVER_SCORESCREEN) {
|
|
|
|
global.gameover = GAMEOVER_SCORESCREEN;
|
|
|
|
} else {
|
|
|
|
global.gameover = GAMEOVER_TRANSITIONING;
|
2023-04-07 05:47:47 +02:00
|
|
|
CallChain cc = CALLCHAIN(stage_finalize, (void*)(intptr_t)gameover);
|
|
|
|
set_transition(TransFadeBlack, FADE_TIME, FADE_TIME*2, cc);
|
2020-06-24 19:51:00 +02:00
|
|
|
audio_bgm_stop(BGM_FADE_LONG);
|
2017-04-06 02:58:57 +02:00
|
|
|
}
|
|
|
|
|
2019-02-22 00:56:03 +01:00
|
|
|
if(
|
2021-05-30 03:26:21 +02:00
|
|
|
global.replay.input.replay == NULL &&
|
2019-02-22 00:56:03 +01:00
|
|
|
prev_gameover != GAMEOVER_SCORESCREEN &&
|
|
|
|
(gameover == GAMEOVER_SCORESCREEN || gameover == GAMEOVER_WIN)
|
|
|
|
) {
|
2020-05-16 22:41:54 +02:00
|
|
|
StageProgress *p = stageinfo_get_progress(global.stage, global.diff, true);
|
2017-04-06 02:58:57 +02:00
|
|
|
|
2019-02-22 00:56:03 +01:00
|
|
|
if(p) {
|
|
|
|
++p->num_cleared;
|
2019-04-06 20:27:39 +02:00
|
|
|
log_debug("Stage cleared %u times now", p->num_cleared);
|
2019-02-22 00:56:03 +01:00
|
|
|
}
|
2017-04-06 02:58:57 +02:00
|
|
|
}
|
2021-05-05 19:32:56 +02:00
|
|
|
|
|
|
|
if(gameover == GAMEOVER_SCORESCREEN && stage_is_skip_mode()) {
|
|
|
|
// don't get stuck in an infinite loop
|
|
|
|
taisei_quit();
|
|
|
|
}
|
2017-02-26 13:17:48 +01:00
|
|
|
}
|
|
|
|
|
2017-03-11 04:41:57 +01:00
|
|
|
static void stage_preload(void) {
|
|
|
|
difficulty_preload();
|
|
|
|
projectiles_preload();
|
|
|
|
player_preload();
|
|
|
|
items_preload();
|
|
|
|
boss_preload();
|
2021-08-22 00:27:12 +02:00
|
|
|
laserdraw_preload();
|
2019-04-23 22:15:48 +02:00
|
|
|
enemies_preload();
|
2017-03-11 04:41:57 +01:00
|
|
|
|
2019-09-12 17:33:08 +02:00
|
|
|
if(global.stage->type != STAGE_SPELL) {
|
2020-06-24 19:51:00 +02:00
|
|
|
preload_resource(RES_BGM, "gameover", RESF_DEFAULT);
|
2019-09-12 17:33:08 +02:00
|
|
|
dialog_preload();
|
|
|
|
}
|
|
|
|
|
2017-03-11 04:41:57 +01:00
|
|
|
global.stage->procs->preload();
|
|
|
|
}
|
|
|
|
|
2017-04-07 14:20:45 +02:00
|
|
|
static void display_stage_title(StageInfo *info) {
|
2020-06-09 03:33:22 +02:00
|
|
|
stagetext_add(info->title, VIEWPORT_W/2 + I * (VIEWPORT_H/2-40), ALIGN_CENTER, res_font("big"), RGB(1, 1, 1), 50, 85, 35, 35);
|
|
|
|
stagetext_add(info->subtitle, VIEWPORT_W/2 + I * (VIEWPORT_H/2), ALIGN_CENTER, res_font("standard"), RGB(1, 1, 1), 60, 85, 35, 35);
|
2017-10-02 23:23:02 +02:00
|
|
|
}
|
|
|
|
|
2020-09-29 19:56:59 +02:00
|
|
|
TASK(start_bgm, { BGM *bgm; }) {
|
|
|
|
audio_bgm_play(ARGS.bgm, true, 0, 0);
|
|
|
|
}
|
|
|
|
|
2017-10-02 23:23:02 +02:00
|
|
|
void stage_start_bgm(const char *bgm) {
|
2020-09-29 19:56:59 +02:00
|
|
|
INVOKE_TASK_DELAYED(1, start_bgm, res_bgm(bgm));
|
2017-04-07 14:20:45 +02:00
|
|
|
}
|
|
|
|
|
2019-02-22 00:56:03 +01:00
|
|
|
void stage_set_voltage_thresholds(uint easy, uint normal, uint hard, uint lunatic) {
|
|
|
|
switch(global.diff) {
|
|
|
|
case D_Easy: global.voltage_threshold = easy; return;
|
|
|
|
case D_Normal: global.voltage_threshold = normal; return;
|
|
|
|
case D_Hard: global.voltage_threshold = hard; return;
|
|
|
|
case D_Lunatic: global.voltage_threshold = lunatic; return;
|
|
|
|
default: UNREACHABLE;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
bool stage_is_cleared(void) {
|
|
|
|
return global.gameover == GAMEOVER_SCORESCREEN || global.gameover == GAMEOVER_TRANSITIONING;
|
|
|
|
}
|
|
|
|
|
2017-12-26 09:56:21 +01:00
|
|
|
static void stage_update_fps(StageFrameState *fstate) {
|
2021-05-30 03:26:21 +02:00
|
|
|
if(global.replay.output.replay) {
|
2017-12-26 12:07:40 +01:00
|
|
|
uint16_t replay_fps = (uint16_t)rint(global.fps.logic.fps);
|
2017-12-26 09:56:21 +01:00
|
|
|
|
|
|
|
if(replay_fps != fstate->last_replay_fps) {
|
2021-05-30 03:26:21 +02:00
|
|
|
replay_stage_event(global.replay.output.stage, global.frames, EV_FPS, replay_fps);
|
2017-12-26 09:56:21 +01:00
|
|
|
fstate->last_replay_fps = replay_fps;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-02-22 00:56:03 +01:00
|
|
|
static void stage_give_clear_bonus(const StageInfo *stage, StageClearBonus *bonus) {
|
|
|
|
memset(bonus, 0, sizeof(*bonus));
|
|
|
|
|
|
|
|
// FIXME: this is clunky...
|
|
|
|
if(!global.is_practice_mode && stage->type == STAGE_STORY) {
|
2020-05-16 22:41:54 +02:00
|
|
|
StageInfo *next = stageinfo_get_by_id(stage->id + 1);
|
2019-02-22 00:56:03 +01:00
|
|
|
|
|
|
|
if(next == NULL || next->type != STAGE_STORY) {
|
|
|
|
bonus->all_clear = true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if(stage->type == STAGE_STORY) {
|
|
|
|
bonus->base = stage->id * 1000000;
|
|
|
|
}
|
|
|
|
|
|
|
|
if(bonus->all_clear) {
|
|
|
|
bonus->base += global.plr.point_item_value * 100;
|
2021-04-28 12:08:38 +02:00
|
|
|
// TODO redesign this
|
|
|
|
// bonus->graze = global.plr.graze * (global.plr.point_item_value / 10);
|
2019-02-22 00:56:03 +01:00
|
|
|
}
|
|
|
|
|
2019-04-08 05:19:57 +02:00
|
|
|
bonus->voltage = imax(0, (int)global.plr.voltage - (int)global.voltage_threshold) * (global.plr.point_item_value / 25);
|
2019-02-22 00:56:03 +01:00
|
|
|
bonus->lives = global.plr.lives * global.plr.point_item_value * 5;
|
|
|
|
|
|
|
|
// TODO: maybe a difficulty multiplier?
|
|
|
|
|
2021-04-28 12:08:38 +02:00
|
|
|
bonus->total = bonus->base + bonus->voltage + bonus->lives + bonus->graze;
|
2019-04-07 00:55:13 +02:00
|
|
|
player_add_points(&global.plr, bonus->total, global.plr.pos);
|
2019-02-22 00:56:03 +01:00
|
|
|
}
|
|
|
|
|
2020-02-28 12:36:22 +01:00
|
|
|
INLINE bool stage_should_yield(void) {
|
2019-07-23 03:33:52 +02:00
|
|
|
return (global.boss && !boss_is_fleeing(global.boss)) || dialog_is_active(global.dialog);
|
|
|
|
}
|
|
|
|
|
Emscripten compatibility (#161)
* Major refactoring of the main loop(s) and control flow (WIP)
run_at_fps() is gone 🦀
Instead of nested blocking event loops, there is now an eventloop API
that manages an explicit stack of scenes. This makes Taisei a lot more
portable to async environments where spinning a loop forever without
yielding control simply is not an option, and that is the entire point
of this change.
A prime example of such an environment is the Web (via emscripten).
Taisei was able to run there through a terrible hack: inserting
emscripten_sleep calls into the loop, which would yield to the browser.
This has several major drawbacks: first of all, every function that
could possibly call emscripten_sleep must be compiled into a special
kind of bytecode, which then has to be interpreted at runtime, *much*
slower than JITed WebAssembly. And that includes *everything* down the
call stack, too! For more information, see
https://emscripten.org/docs/porting/emterpreter.html
Even though that method worked well enough for experimenting, despite
suboptimal performance, there is another obvious drawback:
emscripten_sleep is implemented via setTimeout(), which can be very
imprecise and is generally not reliable for fluid animation. Browsers
actually have an API specifically for that use case:
window.requestAnimationFrame(), but Taisei's original blocking control
flow style is simply not compatible with it. Emscripten exposes this API
with its emscripten_set_main_loop(), which the eventloop backend now
uses on that platform.
Unfortunately, C is still C, with no fancy closures or coroutines.
With blocking calls into menu/scene loops gone, the control flow is
reimplemented via so-called (pun intended) "call chains". That is
basically an euphemism for callback hell. With manual memory management
and zero type-safety. Not that the menu system wasn't shitty enough
already. I'll just keep telling myself that this is all temporary and
will be replaced with scripts in v1.4.
* improve build system for emscripten + various fixes
* squish menu bugs
* improve emscripten event loop; disable EMULATE_FUNCTION_POINTER_CASTS
Note that stock freetype does not work without
EMULATE_FUNCTION_POINTER_CASTS; use a patched version from the
"emscripten" branch here:
https://github.com/taisei-project/freetype2/tree/emscripten
* Enable -Wcast-function-type
Calling functions through incompatible pointers is nasal demons and
doesn't work in WASM.
* webgl: workaround a crash on some browsers
* emscripten improvements:
* Persist state (config, progress, replays, ...) in local IndexDB
* Simpler HTML shell (temporary)
* Enable more optimizations
* fix build if validate_glsl=false
* emscripten: improve asset packaging, with local cache
Note that even though there are rules to build audio bundles, audio
does *not* work yet. It looks like SDL2_mixer can not work without
threads, which is a problem. Yet another reason to write an OpenAL
backend - emscripten supports that natively.
* emscripten: customize the html shell
* emscripten: force "show log" checkbox unchecked initially
* emscripten: remove quit shortcut from main menu (since there's no quit)
* emscripten: log area fixes
* emscripten/webgl: workaround for fullscreen viewport issue
* emscripten: implement frameskip
* emscripter: improve framerate limiter
* align List to at least 8 bytes (shut up warnings)
* fix non-emscripten builds
* improve fullscreen handling, mainly for emscripten
* Workaround to make audio work in chromium
emscripten-core/emscripten#6511
* emscripten: better vsync handling; enable vsync & disable fxaa by default
2019-03-09 20:32:32 +01:00
|
|
|
static LogicFrameAction stage_logic_frame(void *arg) {
|
2017-10-04 07:07:04 +02:00
|
|
|
StageFrameState *fstate = arg;
|
|
|
|
StageInfo *stage = fstate->stage;
|
|
|
|
|
2017-12-26 09:56:21 +01:00
|
|
|
stage_update_fps(fstate);
|
2018-10-02 02:03:51 +02:00
|
|
|
|
2020-06-22 16:41:03 +02:00
|
|
|
if(stage_is_skip_mode()) {
|
2019-08-22 21:43:34 +02:00
|
|
|
global.plr.iddqd = true;
|
|
|
|
}
|
|
|
|
|
2020-05-16 22:42:22 +02:00
|
|
|
fapproach_p(&fstate->view_shake, 0, 1);
|
|
|
|
fapproach_asymptotic_p(&fstate->view_shake, 0, 0.05, 1e-2);
|
2018-10-02 02:03:51 +02:00
|
|
|
|
2022-12-22 16:55:56 +01:00
|
|
|
if(
|
|
|
|
global.replay.input.replay == NULL &&
|
|
|
|
fstate->dynstage_generation != dynstage_get_generation()
|
|
|
|
) {
|
|
|
|
log_info("Stages library updated, attempting to hot-reload");
|
|
|
|
stage_do_quicksave(fstate, true); // no-op if user has a manual save
|
|
|
|
stage_do_quickload(fstate);
|
2022-01-28 17:03:22 +01:00
|
|
|
}
|
|
|
|
|
2023-04-07 07:47:03 +02:00
|
|
|
if(global.gameover == GAMEOVER_TRANSITIONING) {
|
|
|
|
// Usually stage_comain will do this
|
|
|
|
events_poll(NULL, 0);
|
|
|
|
} else {
|
2019-07-23 03:33:52 +02:00
|
|
|
cosched_run_tasks(&fstate->sched);
|
|
|
|
|
2019-02-22 00:56:03 +01:00
|
|
|
if(global.gameover == GAMEOVER_SCORESCREEN && global.frames - global.gameover_time == GAMEOVER_SCORE_DELAY) {
|
|
|
|
StageClearBonus b;
|
|
|
|
stage_give_clear_bonus(stage, &b);
|
|
|
|
stage_display_clear_screen(&b);
|
|
|
|
}
|
|
|
|
|
2018-07-24 20:30:45 +02:00
|
|
|
if(stage->type == STAGE_SPELL && !global.boss && !fstate->transition_delay) {
|
|
|
|
fstate->transition_delay = 120;
|
2017-10-04 07:07:04 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-05-30 03:26:21 +02:00
|
|
|
uint16_t desync_check = (rng_u64() ^ global.plr.points) & 0xFFFF;
|
|
|
|
ReplaySyncStatus rpsync = replay_state_check_desync(&global.replay.input, global.frames, desync_check);
|
|
|
|
|
|
|
|
if(
|
|
|
|
global.replay.output.stage &&
|
|
|
|
fstate->desync_check_freq > 0 &&
|
|
|
|
!(global.frames % fstate->desync_check_freq)
|
|
|
|
) {
|
|
|
|
replay_stage_event(global.replay.output.stage, global.frames, EV_CHECK_DESYNC, desync_check);
|
|
|
|
}
|
|
|
|
|
2022-01-28 17:03:22 +01:00
|
|
|
if(rpsync == REPLAY_SYNC_FAIL) {
|
|
|
|
if(
|
|
|
|
global.is_replay_verification &&
|
|
|
|
!global.replay.output.stage
|
|
|
|
) {
|
|
|
|
exit(1);
|
|
|
|
}
|
|
|
|
|
|
|
|
if(fstate->quicksave && fstate->quicksave == global.replay.input.replay) {
|
|
|
|
log_warn("Quicksave replay desynced; resuming prematurely!");
|
|
|
|
leave_replay_mode(fstate, &global.replay.input);
|
|
|
|
}
|
2021-05-30 03:26:21 +02:00
|
|
|
}
|
|
|
|
|
2017-12-28 11:46:08 +01:00
|
|
|
if(fstate->transition_delay) {
|
2018-07-24 20:30:45 +02:00
|
|
|
if(!--fstate->transition_delay) {
|
|
|
|
stage_finish(GAMEOVER_WIN);
|
|
|
|
}
|
2017-12-28 11:46:08 +01:00
|
|
|
} else {
|
|
|
|
update_transition();
|
|
|
|
}
|
|
|
|
|
2021-05-30 03:26:21 +02:00
|
|
|
if(global.replay.input.replay == NULL && global.plr.points > progress.hiscore) {
|
2017-10-04 07:07:04 +02:00
|
|
|
progress.hiscore = global.plr.points;
|
|
|
|
}
|
|
|
|
|
2022-01-09 13:11:26 +01:00
|
|
|
if(fstate->quickload_requested) {
|
|
|
|
log_info("Quickload initiated");
|
|
|
|
return LFRAME_STOP;
|
|
|
|
}
|
|
|
|
|
2019-02-22 00:56:03 +01:00
|
|
|
if(global.gameover > 0) {
|
2017-12-26 12:07:40 +01:00
|
|
|
return LFRAME_STOP;
|
|
|
|
}
|
|
|
|
|
2022-01-09 13:11:26 +01:00
|
|
|
if(is_quickloading(fstate)) {
|
|
|
|
return LFRAME_SKIP_ALWAYS;
|
|
|
|
}
|
|
|
|
|
2020-06-22 16:41:03 +02:00
|
|
|
LogicFrameAction skipmode = skipstate_handle_frame();
|
|
|
|
if(skipmode != LFRAME_WAIT) {
|
|
|
|
return skipmode;
|
2019-08-22 21:43:34 +02:00
|
|
|
}
|
|
|
|
|
2021-08-19 00:58:08 +02:00
|
|
|
if(global.replay.input.replay && gamekeypressed(KEY_SKIP)) {
|
2017-12-26 12:07:40 +01:00
|
|
|
return LFRAME_SKIP;
|
|
|
|
}
|
|
|
|
|
|
|
|
return LFRAME_WAIT;
|
|
|
|
}
|
|
|
|
|
Emscripten compatibility (#161)
* Major refactoring of the main loop(s) and control flow (WIP)
run_at_fps() is gone 🦀
Instead of nested blocking event loops, there is now an eventloop API
that manages an explicit stack of scenes. This makes Taisei a lot more
portable to async environments where spinning a loop forever without
yielding control simply is not an option, and that is the entire point
of this change.
A prime example of such an environment is the Web (via emscripten).
Taisei was able to run there through a terrible hack: inserting
emscripten_sleep calls into the loop, which would yield to the browser.
This has several major drawbacks: first of all, every function that
could possibly call emscripten_sleep must be compiled into a special
kind of bytecode, which then has to be interpreted at runtime, *much*
slower than JITed WebAssembly. And that includes *everything* down the
call stack, too! For more information, see
https://emscripten.org/docs/porting/emterpreter.html
Even though that method worked well enough for experimenting, despite
suboptimal performance, there is another obvious drawback:
emscripten_sleep is implemented via setTimeout(), which can be very
imprecise and is generally not reliable for fluid animation. Browsers
actually have an API specifically for that use case:
window.requestAnimationFrame(), but Taisei's original blocking control
flow style is simply not compatible with it. Emscripten exposes this API
with its emscripten_set_main_loop(), which the eventloop backend now
uses on that platform.
Unfortunately, C is still C, with no fancy closures or coroutines.
With blocking calls into menu/scene loops gone, the control flow is
reimplemented via so-called (pun intended) "call chains". That is
basically an euphemism for callback hell. With manual memory management
and zero type-safety. Not that the menu system wasn't shitty enough
already. I'll just keep telling myself that this is all temporary and
will be replaced with scripts in v1.4.
* improve build system for emscripten + various fixes
* squish menu bugs
* improve emscripten event loop; disable EMULATE_FUNCTION_POINTER_CASTS
Note that stock freetype does not work without
EMULATE_FUNCTION_POINTER_CASTS; use a patched version from the
"emscripten" branch here:
https://github.com/taisei-project/freetype2/tree/emscripten
* Enable -Wcast-function-type
Calling functions through incompatible pointers is nasal demons and
doesn't work in WASM.
* webgl: workaround a crash on some browsers
* emscripten improvements:
* Persist state (config, progress, replays, ...) in local IndexDB
* Simpler HTML shell (temporary)
* Enable more optimizations
* fix build if validate_glsl=false
* emscripten: improve asset packaging, with local cache
Note that even though there are rules to build audio bundles, audio
does *not* work yet. It looks like SDL2_mixer can not work without
threads, which is a problem. Yet another reason to write an OpenAL
backend - emscripten supports that natively.
* emscripten: customize the html shell
* emscripten: force "show log" checkbox unchecked initially
* emscripten: remove quit shortcut from main menu (since there's no quit)
* emscripten: log area fixes
* emscripten/webgl: workaround for fullscreen viewport issue
* emscripten: implement frameskip
* emscripter: improve framerate limiter
* align List to at least 8 bytes (shut up warnings)
* fix non-emscripten builds
* improve fullscreen handling, mainly for emscripten
* Workaround to make audio work in chromium
emscripten-core/emscripten#6511
* emscripten: better vsync handling; enable vsync & disable fxaa by default
2019-03-09 20:32:32 +01:00
|
|
|
static RenderFrameAction stage_render_frame(void *arg) {
|
2017-12-26 12:07:40 +01:00
|
|
|
StageFrameState *fstate = arg;
|
|
|
|
StageInfo *stage = fstate->stage;
|
|
|
|
|
2020-06-22 16:41:03 +02:00
|
|
|
if(stage_is_skip_mode()) {
|
2019-08-22 21:43:34 +02:00
|
|
|
return RFRAME_DROP;
|
|
|
|
}
|
|
|
|
|
2019-11-13 01:24:01 +01:00
|
|
|
rng_lock(&global.rand_game);
|
|
|
|
rng_make_active(&global.rand_visual);
|
2017-11-15 07:05:47 +01:00
|
|
|
BEGIN_DRAW_CODE();
|
2017-10-04 07:07:04 +02:00
|
|
|
stage_draw_scene(stage);
|
2017-11-15 07:05:47 +01:00
|
|
|
END_DRAW_CODE();
|
2019-11-13 01:24:01 +01:00
|
|
|
rng_unlock(&global.rand_game);
|
|
|
|
rng_make_active(&global.rand_game);
|
2017-10-04 07:07:04 +02:00
|
|
|
draw_transition();
|
|
|
|
|
2017-12-26 12:07:40 +01:00
|
|
|
return RFRAME_SWAP;
|
2017-10-04 07:07:04 +02:00
|
|
|
}
|
|
|
|
|
Emscripten compatibility (#161)
* Major refactoring of the main loop(s) and control flow (WIP)
run_at_fps() is gone 🦀
Instead of nested blocking event loops, there is now an eventloop API
that manages an explicit stack of scenes. This makes Taisei a lot more
portable to async environments where spinning a loop forever without
yielding control simply is not an option, and that is the entire point
of this change.
A prime example of such an environment is the Web (via emscripten).
Taisei was able to run there through a terrible hack: inserting
emscripten_sleep calls into the loop, which would yield to the browser.
This has several major drawbacks: first of all, every function that
could possibly call emscripten_sleep must be compiled into a special
kind of bytecode, which then has to be interpreted at runtime, *much*
slower than JITed WebAssembly. And that includes *everything* down the
call stack, too! For more information, see
https://emscripten.org/docs/porting/emterpreter.html
Even though that method worked well enough for experimenting, despite
suboptimal performance, there is another obvious drawback:
emscripten_sleep is implemented via setTimeout(), which can be very
imprecise and is generally not reliable for fluid animation. Browsers
actually have an API specifically for that use case:
window.requestAnimationFrame(), but Taisei's original blocking control
flow style is simply not compatible with it. Emscripten exposes this API
with its emscripten_set_main_loop(), which the eventloop backend now
uses on that platform.
Unfortunately, C is still C, with no fancy closures or coroutines.
With blocking calls into menu/scene loops gone, the control flow is
reimplemented via so-called (pun intended) "call chains". That is
basically an euphemism for callback hell. With manual memory management
and zero type-safety. Not that the menu system wasn't shitty enough
already. I'll just keep telling myself that this is all temporary and
will be replaced with scripts in v1.4.
* improve build system for emscripten + various fixes
* squish menu bugs
* improve emscripten event loop; disable EMULATE_FUNCTION_POINTER_CASTS
Note that stock freetype does not work without
EMULATE_FUNCTION_POINTER_CASTS; use a patched version from the
"emscripten" branch here:
https://github.com/taisei-project/freetype2/tree/emscripten
* Enable -Wcast-function-type
Calling functions through incompatible pointers is nasal demons and
doesn't work in WASM.
* webgl: workaround a crash on some browsers
* emscripten improvements:
* Persist state (config, progress, replays, ...) in local IndexDB
* Simpler HTML shell (temporary)
* Enable more optimizations
* fix build if validate_glsl=false
* emscripten: improve asset packaging, with local cache
Note that even though there are rules to build audio bundles, audio
does *not* work yet. It looks like SDL2_mixer can not work without
threads, which is a problem. Yet another reason to write an OpenAL
backend - emscripten supports that natively.
* emscripten: customize the html shell
* emscripten: force "show log" checkbox unchecked initially
* emscripten: remove quit shortcut from main menu (since there's no quit)
* emscripten: log area fixes
* emscripten/webgl: workaround for fullscreen viewport issue
* emscripten: implement frameskip
* emscripter: improve framerate limiter
* align List to at least 8 bytes (shut up warnings)
* fix non-emscripten builds
* improve fullscreen handling, mainly for emscripten
* Workaround to make audio work in chromium
emscripten-core/emscripten#6511
* emscripten: better vsync handling; enable vsync & disable fxaa by default
2019-03-09 20:32:32 +01:00
|
|
|
static void stage_end_loop(void *ctx);
|
|
|
|
|
2019-07-23 03:33:52 +02:00
|
|
|
static void stage_stub_proc(void) { }
|
|
|
|
|
2022-12-22 16:55:56 +01:00
|
|
|
TASK(stage_comain, { StageFrameState *fstate; }) {
|
|
|
|
StageFrameState *fstate = ARGS.fstate;
|
|
|
|
StageInfo *stage = fstate->stage;
|
|
|
|
|
|
|
|
stage->procs->begin();
|
|
|
|
player_stage_post_init(&global.plr);
|
|
|
|
|
|
|
|
if(global.stage->type != STAGE_SPELL) {
|
|
|
|
display_stage_title(stage);
|
|
|
|
}
|
|
|
|
|
|
|
|
for(;;YIELD) {
|
|
|
|
if(global.replay.input.replay != NULL) {
|
|
|
|
replay_input(fstate);
|
|
|
|
} else {
|
|
|
|
stage_input(fstate);
|
|
|
|
}
|
|
|
|
|
|
|
|
if(!stage_should_yield()) {
|
|
|
|
stage->procs->event();
|
|
|
|
}
|
|
|
|
|
|
|
|
stage->procs->update();
|
|
|
|
stage_logic();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-01-28 17:03:22 +01:00
|
|
|
static void _stage_enter(
|
|
|
|
StageInfo *stage, CallChain next, Replay *quickload, bool quicksave_is_automatic
|
|
|
|
) {
|
2017-02-26 13:17:48 +01:00
|
|
|
assert(stage);
|
|
|
|
assert(stage->procs);
|
2019-07-23 03:33:52 +02:00
|
|
|
|
2022-02-05 16:58:02 +01:00
|
|
|
if(global.gameover == GAMEOVER_WIN) {
|
|
|
|
global.gameover = 0;
|
|
|
|
} else if(global.gameover) {
|
|
|
|
run_call_chain(&next, NULL);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
uint32_t dynstage_generation = dynstage_get_generation();
|
|
|
|
stageinfo_reload();
|
|
|
|
|
2019-07-23 03:33:52 +02:00
|
|
|
#define STUB_PROC(proc, stub) do {\
|
|
|
|
if(!stage->procs->proc) { \
|
|
|
|
stage->procs->proc = stub; \
|
|
|
|
log_debug(#proc " proc is missing"); \
|
|
|
|
} \
|
|
|
|
} while(0)
|
|
|
|
|
|
|
|
static const ShaderRule shader_rules_stub[1] = { NULL };
|
|
|
|
|
|
|
|
STUB_PROC(preload, stage_stub_proc);
|
|
|
|
STUB_PROC(begin, stage_stub_proc);
|
|
|
|
STUB_PROC(end, stage_stub_proc);
|
|
|
|
STUB_PROC(draw, stage_stub_proc);
|
|
|
|
STUB_PROC(event, stage_stub_proc);
|
|
|
|
STUB_PROC(update, stage_stub_proc);
|
|
|
|
STUB_PROC(shader_rules, (ShaderRule*)shader_rules_stub);
|
2017-02-26 13:17:48 +01:00
|
|
|
|
2022-01-09 13:11:26 +01:00
|
|
|
if(quickload) {
|
|
|
|
assert(global.replay.input.stage == NULL);
|
|
|
|
ReplayStage *qload_stage = dynarray_get_ptr(&quickload->stages, 0);
|
|
|
|
assert(qload_stage->stage == stage->id);
|
|
|
|
replay_state_init_play(&global.replay.input, quickload, qload_stage);
|
|
|
|
}
|
|
|
|
|
2017-02-10 11:39:42 +01:00
|
|
|
// I really want to separate all of the game state from the global struct sometime
|
2017-02-26 13:17:48 +01:00
|
|
|
global.stage = stage;
|
2017-02-10 11:39:42 +01:00
|
|
|
|
2019-01-04 23:59:39 +01:00
|
|
|
ent_init();
|
2023-04-03 03:49:39 +02:00
|
|
|
stage_objpools_init();
|
2019-08-25 01:28:46 +02:00
|
|
|
stage_draw_pre_init();
|
2017-03-11 04:41:57 +01:00
|
|
|
stage_preload();
|
2018-07-04 10:36:16 +02:00
|
|
|
stage_draw_init();
|
2021-08-22 06:05:17 +02:00
|
|
|
lasers_init();
|
2017-03-11 03:12:51 +01:00
|
|
|
|
2019-11-13 01:24:01 +01:00
|
|
|
rng_make_active(&global.rand_game);
|
2017-02-26 13:17:48 +01:00
|
|
|
stage_start(stage);
|
2017-02-10 23:05:22 +01:00
|
|
|
|
2021-05-30 03:26:21 +02:00
|
|
|
uint64_t start_time, seed;
|
2019-03-09 17:19:42 +01:00
|
|
|
|
2021-05-30 03:26:21 +02:00
|
|
|
if(global.replay.input.replay) {
|
|
|
|
ReplayStage *rstg = global.replay.input.stage;
|
2017-12-18 17:34:10 +01:00
|
|
|
|
2021-05-30 03:26:21 +02:00
|
|
|
assert(rstg != NULL);
|
|
|
|
assert(stageinfo_get_by_id(rstg->stage) == stage);
|
2017-02-10 00:24:19 +01:00
|
|
|
|
2021-05-30 03:26:21 +02:00
|
|
|
start_time = rstg->start_time;
|
|
|
|
seed = rstg->rng_seed;
|
|
|
|
global.diff = rstg->diff;
|
|
|
|
|
|
|
|
log_debug("REPLAY_PLAY mode: %d events, stage: \"%s\"", rstg->events.num_elements, stage->title);
|
|
|
|
} else {
|
|
|
|
start_time = (uint64_t)time(0);
|
|
|
|
seed = makeseed();
|
2017-04-06 02:58:57 +02:00
|
|
|
|
2020-05-16 22:41:54 +02:00
|
|
|
StageProgress *p = stageinfo_get_progress(stage, global.diff, true);
|
2017-04-06 02:58:57 +02:00
|
|
|
|
|
|
|
if(p) {
|
|
|
|
log_debug("You played this stage %u times", p->num_played);
|
|
|
|
log_debug("You cleared this stage %u times", p->num_cleared);
|
|
|
|
|
|
|
|
++p->num_played;
|
2018-10-23 05:33:16 +02:00
|
|
|
p->unlocked = true;
|
2017-04-06 02:58:57 +02:00
|
|
|
}
|
2021-05-30 03:26:21 +02:00
|
|
|
}
|
2019-02-02 12:25:06 +01:00
|
|
|
|
2021-05-30 03:26:21 +02:00
|
|
|
rng_seed(&global.rand_game, seed);
|
2017-02-10 23:05:22 +01:00
|
|
|
|
2021-05-30 03:26:21 +02:00
|
|
|
if(global.replay.input.replay) {
|
|
|
|
player_init(&global.plr);
|
|
|
|
replay_stage_sync_player_state(global.replay.input.stage, &global.plr);
|
|
|
|
}
|
2017-02-10 23:05:22 +01:00
|
|
|
|
2021-05-30 03:26:21 +02:00
|
|
|
if(global.replay.output.replay) {
|
|
|
|
global.replay.output.stage = replay_stage_new(
|
|
|
|
global.replay.output.replay,
|
|
|
|
stage,
|
|
|
|
start_time,
|
|
|
|
seed,
|
|
|
|
global.diff,
|
|
|
|
&global.plr
|
|
|
|
);
|
2017-10-08 13:30:51 +02:00
|
|
|
player_init(&global.plr);
|
2021-05-30 03:26:21 +02:00
|
|
|
replay_stage_sync_player_state(global.replay.output.stage, &global.plr);
|
2012-07-14 19:46:03 +02:00
|
|
|
}
|
2017-02-10 23:05:22 +01:00
|
|
|
|
2023-01-09 04:19:31 +01:00
|
|
|
auto fstate = ALLOC(StageFrameState, {
|
|
|
|
.stage = stage,
|
|
|
|
.cc = next,
|
|
|
|
.quicksave = quickload,
|
|
|
|
.quicksave_is_automatic = quicksave_is_automatic,
|
|
|
|
.desync_check_freq = env_get("TAISEI_REPLAY_DESYNC_CHECK_FREQUENCY", FPS * 5),
|
|
|
|
.dynstage_generation = dynstage_generation,
|
|
|
|
});
|
Emscripten compatibility (#161)
* Major refactoring of the main loop(s) and control flow (WIP)
run_at_fps() is gone 🦀
Instead of nested blocking event loops, there is now an eventloop API
that manages an explicit stack of scenes. This makes Taisei a lot more
portable to async environments where spinning a loop forever without
yielding control simply is not an option, and that is the entire point
of this change.
A prime example of such an environment is the Web (via emscripten).
Taisei was able to run there through a terrible hack: inserting
emscripten_sleep calls into the loop, which would yield to the browser.
This has several major drawbacks: first of all, every function that
could possibly call emscripten_sleep must be compiled into a special
kind of bytecode, which then has to be interpreted at runtime, *much*
slower than JITed WebAssembly. And that includes *everything* down the
call stack, too! For more information, see
https://emscripten.org/docs/porting/emterpreter.html
Even though that method worked well enough for experimenting, despite
suboptimal performance, there is another obvious drawback:
emscripten_sleep is implemented via setTimeout(), which can be very
imprecise and is generally not reliable for fluid animation. Browsers
actually have an API specifically for that use case:
window.requestAnimationFrame(), but Taisei's original blocking control
flow style is simply not compatible with it. Emscripten exposes this API
with its emscripten_set_main_loop(), which the eventloop backend now
uses on that platform.
Unfortunately, C is still C, with no fancy closures or coroutines.
With blocking calls into menu/scene loops gone, the control flow is
reimplemented via so-called (pun intended) "call chains". That is
basically an euphemism for callback hell. With manual memory management
and zero type-safety. Not that the menu system wasn't shitty enough
already. I'll just keep telling myself that this is all temporary and
will be replaced with scripts in v1.4.
* improve build system for emscripten + various fixes
* squish menu bugs
* improve emscripten event loop; disable EMULATE_FUNCTION_POINTER_CASTS
Note that stock freetype does not work without
EMULATE_FUNCTION_POINTER_CASTS; use a patched version from the
"emscripten" branch here:
https://github.com/taisei-project/freetype2/tree/emscripten
* Enable -Wcast-function-type
Calling functions through incompatible pointers is nasal demons and
doesn't work in WASM.
* webgl: workaround a crash on some browsers
* emscripten improvements:
* Persist state (config, progress, replays, ...) in local IndexDB
* Simpler HTML shell (temporary)
* Enable more optimizations
* fix build if validate_glsl=false
* emscripten: improve asset packaging, with local cache
Note that even though there are rules to build audio bundles, audio
does *not* work yet. It looks like SDL2_mixer can not work without
threads, which is a problem. Yet another reason to write an OpenAL
backend - emscripten supports that natively.
* emscripten: customize the html shell
* emscripten: force "show log" checkbox unchecked initially
* emscripten: remove quit shortcut from main menu (since there's no quit)
* emscripten: log area fixes
* emscripten/webgl: workaround for fullscreen viewport issue
* emscripten: implement frameskip
* emscripter: improve framerate limiter
* align List to at least 8 bytes (shut up warnings)
* fix non-emscripten builds
* improve fullscreen handling, mainly for emscripten
* Workaround to make audio work in chromium
emscripten-core/emscripten#6511
* emscripten: better vsync handling; enable vsync & disable fxaa by default
2019-03-09 20:32:32 +01:00
|
|
|
|
2023-01-09 04:19:31 +01:00
|
|
|
cosched_init(&fstate->sched);
|
2020-05-16 22:42:22 +02:00
|
|
|
_current_stage_state = fstate;
|
|
|
|
|
2020-06-22 16:41:03 +02:00
|
|
|
skipstate_init();
|
2019-08-22 21:43:34 +02:00
|
|
|
|
2022-01-09 13:11:26 +01:00
|
|
|
if(is_quickloading(fstate)) {
|
|
|
|
audio_sfx_set_enabled(false);
|
|
|
|
}
|
|
|
|
|
2022-12-22 16:55:56 +01:00
|
|
|
SCHED_INVOKE_TASK(&fstate->sched, stage_comain, fstate);
|
Emscripten compatibility (#161)
* Major refactoring of the main loop(s) and control flow (WIP)
run_at_fps() is gone 🦀
Instead of nested blocking event loops, there is now an eventloop API
that manages an explicit stack of scenes. This makes Taisei a lot more
portable to async environments where spinning a loop forever without
yielding control simply is not an option, and that is the entire point
of this change.
A prime example of such an environment is the Web (via emscripten).
Taisei was able to run there through a terrible hack: inserting
emscripten_sleep calls into the loop, which would yield to the browser.
This has several major drawbacks: first of all, every function that
could possibly call emscripten_sleep must be compiled into a special
kind of bytecode, which then has to be interpreted at runtime, *much*
slower than JITed WebAssembly. And that includes *everything* down the
call stack, too! For more information, see
https://emscripten.org/docs/porting/emterpreter.html
Even though that method worked well enough for experimenting, despite
suboptimal performance, there is another obvious drawback:
emscripten_sleep is implemented via setTimeout(), which can be very
imprecise and is generally not reliable for fluid animation. Browsers
actually have an API specifically for that use case:
window.requestAnimationFrame(), but Taisei's original blocking control
flow style is simply not compatible with it. Emscripten exposes this API
with its emscripten_set_main_loop(), which the eventloop backend now
uses on that platform.
Unfortunately, C is still C, with no fancy closures or coroutines.
With blocking calls into menu/scene loops gone, the control flow is
reimplemented via so-called (pun intended) "call chains". That is
basically an euphemism for callback hell. With manual memory management
and zero type-safety. Not that the menu system wasn't shitty enough
already. I'll just keep telling myself that this is all temporary and
will be replaced with scripts in v1.4.
* improve build system for emscripten + various fixes
* squish menu bugs
* improve emscripten event loop; disable EMULATE_FUNCTION_POINTER_CASTS
Note that stock freetype does not work without
EMULATE_FUNCTION_POINTER_CASTS; use a patched version from the
"emscripten" branch here:
https://github.com/taisei-project/freetype2/tree/emscripten
* Enable -Wcast-function-type
Calling functions through incompatible pointers is nasal demons and
doesn't work in WASM.
* webgl: workaround a crash on some browsers
* emscripten improvements:
* Persist state (config, progress, replays, ...) in local IndexDB
* Simpler HTML shell (temporary)
* Enable more optimizations
* fix build if validate_glsl=false
* emscripten: improve asset packaging, with local cache
Note that even though there are rules to build audio bundles, audio
does *not* work yet. It looks like SDL2_mixer can not work without
threads, which is a problem. Yet another reason to write an OpenAL
backend - emscripten supports that natively.
* emscripten: customize the html shell
* emscripten: force "show log" checkbox unchecked initially
* emscripten: remove quit shortcut from main menu (since there's no quit)
* emscripten: log area fixes
* emscripten/webgl: workaround for fullscreen viewport issue
* emscripten: implement frameskip
* emscripter: improve framerate limiter
* align List to at least 8 bytes (shut up warnings)
* fix non-emscripten builds
* improve fullscreen handling, mainly for emscripten
* Workaround to make audio work in chromium
emscripten-core/emscripten#6511
* emscripten: better vsync handling; enable vsync & disable fxaa by default
2019-03-09 20:32:32 +01:00
|
|
|
eventloop_enter(fstate, stage_logic_frame, stage_render_frame, stage_end_loop, FPS);
|
|
|
|
}
|
|
|
|
|
2022-01-09 13:11:26 +01:00
|
|
|
void stage_enter(StageInfo *stage, CallChain next) {
|
2022-01-28 17:03:22 +01:00
|
|
|
_stage_enter(stage, next, NULL, false);
|
2022-01-09 13:11:26 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
void stage_end_loop(void *ctx) {
|
Emscripten compatibility (#161)
* Major refactoring of the main loop(s) and control flow (WIP)
run_at_fps() is gone 🦀
Instead of nested blocking event loops, there is now an eventloop API
that manages an explicit stack of scenes. This makes Taisei a lot more
portable to async environments where spinning a loop forever without
yielding control simply is not an option, and that is the entire point
of this change.
A prime example of such an environment is the Web (via emscripten).
Taisei was able to run there through a terrible hack: inserting
emscripten_sleep calls into the loop, which would yield to the browser.
This has several major drawbacks: first of all, every function that
could possibly call emscripten_sleep must be compiled into a special
kind of bytecode, which then has to be interpreted at runtime, *much*
slower than JITed WebAssembly. And that includes *everything* down the
call stack, too! For more information, see
https://emscripten.org/docs/porting/emterpreter.html
Even though that method worked well enough for experimenting, despite
suboptimal performance, there is another obvious drawback:
emscripten_sleep is implemented via setTimeout(), which can be very
imprecise and is generally not reliable for fluid animation. Browsers
actually have an API specifically for that use case:
window.requestAnimationFrame(), but Taisei's original blocking control
flow style is simply not compatible with it. Emscripten exposes this API
with its emscripten_set_main_loop(), which the eventloop backend now
uses on that platform.
Unfortunately, C is still C, with no fancy closures or coroutines.
With blocking calls into menu/scene loops gone, the control flow is
reimplemented via so-called (pun intended) "call chains". That is
basically an euphemism for callback hell. With manual memory management
and zero type-safety. Not that the menu system wasn't shitty enough
already. I'll just keep telling myself that this is all temporary and
will be replaced with scripts in v1.4.
* improve build system for emscripten + various fixes
* squish menu bugs
* improve emscripten event loop; disable EMULATE_FUNCTION_POINTER_CASTS
Note that stock freetype does not work without
EMULATE_FUNCTION_POINTER_CASTS; use a patched version from the
"emscripten" branch here:
https://github.com/taisei-project/freetype2/tree/emscripten
* Enable -Wcast-function-type
Calling functions through incompatible pointers is nasal demons and
doesn't work in WASM.
* webgl: workaround a crash on some browsers
* emscripten improvements:
* Persist state (config, progress, replays, ...) in local IndexDB
* Simpler HTML shell (temporary)
* Enable more optimizations
* fix build if validate_glsl=false
* emscripten: improve asset packaging, with local cache
Note that even though there are rules to build audio bundles, audio
does *not* work yet. It looks like SDL2_mixer can not work without
threads, which is a problem. Yet another reason to write an OpenAL
backend - emscripten supports that natively.
* emscripten: customize the html shell
* emscripten: force "show log" checkbox unchecked initially
* emscripten: remove quit shortcut from main menu (since there's no quit)
* emscripten: log area fixes
* emscripten/webgl: workaround for fullscreen viewport issue
* emscripten: implement frameskip
* emscripter: improve framerate limiter
* align List to at least 8 bytes (shut up warnings)
* fix non-emscripten builds
* improve fullscreen handling, mainly for emscripten
* Workaround to make audio work in chromium
emscripten-core/emscripten#6511
* emscripten: better vsync handling; enable vsync & disable fxaa by default
2019-03-09 20:32:32 +01:00
|
|
|
StageFrameState *s = ctx;
|
2020-05-16 22:42:22 +02:00
|
|
|
assert(s == _current_stage_state);
|
2017-02-10 23:05:22 +01:00
|
|
|
|
2022-01-09 13:11:26 +01:00
|
|
|
Replay *quicksave = s->quicksave;
|
2022-01-28 17:03:22 +01:00
|
|
|
bool quicksave_is_automatic = s->quicksave_is_automatic;
|
2022-01-09 13:11:26 +01:00
|
|
|
bool is_quickload = s->quickload_requested;
|
|
|
|
|
|
|
|
if(is_quickload) {
|
|
|
|
assume(quicksave != NULL);
|
|
|
|
}
|
|
|
|
|
2021-05-30 03:26:21 +02:00
|
|
|
if(global.replay.output.replay) {
|
2022-01-09 13:11:26 +01:00
|
|
|
if(is_quickload) {
|
|
|
|
// rollback this stage, as we're about to replay it
|
|
|
|
global.replay.output.replay->stages.num_elements--;
|
|
|
|
replay_stage_destroy_events(global.replay.output.stage);
|
|
|
|
} else {
|
|
|
|
replay_stage_event(global.replay.output.stage, global.frames, EV_OVER, 0);
|
|
|
|
global.replay.output.stage->plr_points_final = global.plr.points;
|
2017-10-29 23:45:24 +01:00
|
|
|
|
2022-01-09 13:11:26 +01:00
|
|
|
if(global.gameover == GAMEOVER_WIN) {
|
|
|
|
global.replay.output.stage->flags |= REPLAY_SFLAG_CLEAR;
|
|
|
|
}
|
2017-10-29 23:45:24 +01:00
|
|
|
}
|
2012-07-16 17:47:06 +02:00
|
|
|
}
|
2017-02-10 23:05:22 +01:00
|
|
|
|
2022-01-09 13:11:26 +01:00
|
|
|
if(quicksave && !is_quickload) {
|
|
|
|
replay_reset(quicksave);
|
2023-01-09 04:19:31 +01:00
|
|
|
mem_free(quicksave);
|
2022-01-09 13:11:26 +01:00
|
|
|
}
|
|
|
|
|
Emscripten compatibility (#161)
* Major refactoring of the main loop(s) and control flow (WIP)
run_at_fps() is gone 🦀
Instead of nested blocking event loops, there is now an eventloop API
that manages an explicit stack of scenes. This makes Taisei a lot more
portable to async environments where spinning a loop forever without
yielding control simply is not an option, and that is the entire point
of this change.
A prime example of such an environment is the Web (via emscripten).
Taisei was able to run there through a terrible hack: inserting
emscripten_sleep calls into the loop, which would yield to the browser.
This has several major drawbacks: first of all, every function that
could possibly call emscripten_sleep must be compiled into a special
kind of bytecode, which then has to be interpreted at runtime, *much*
slower than JITed WebAssembly. And that includes *everything* down the
call stack, too! For more information, see
https://emscripten.org/docs/porting/emterpreter.html
Even though that method worked well enough for experimenting, despite
suboptimal performance, there is another obvious drawback:
emscripten_sleep is implemented via setTimeout(), which can be very
imprecise and is generally not reliable for fluid animation. Browsers
actually have an API specifically for that use case:
window.requestAnimationFrame(), but Taisei's original blocking control
flow style is simply not compatible with it. Emscripten exposes this API
with its emscripten_set_main_loop(), which the eventloop backend now
uses on that platform.
Unfortunately, C is still C, with no fancy closures or coroutines.
With blocking calls into menu/scene loops gone, the control flow is
reimplemented via so-called (pun intended) "call chains". That is
basically an euphemism for callback hell. With manual memory management
and zero type-safety. Not that the menu system wasn't shitty enough
already. I'll just keep telling myself that this is all temporary and
will be replaced with scripts in v1.4.
* improve build system for emscripten + various fixes
* squish menu bugs
* improve emscripten event loop; disable EMULATE_FUNCTION_POINTER_CASTS
Note that stock freetype does not work without
EMULATE_FUNCTION_POINTER_CASTS; use a patched version from the
"emscripten" branch here:
https://github.com/taisei-project/freetype2/tree/emscripten
* Enable -Wcast-function-type
Calling functions through incompatible pointers is nasal demons and
doesn't work in WASM.
* webgl: workaround a crash on some browsers
* emscripten improvements:
* Persist state (config, progress, replays, ...) in local IndexDB
* Simpler HTML shell (temporary)
* Enable more optimizations
* fix build if validate_glsl=false
* emscripten: improve asset packaging, with local cache
Note that even though there are rules to build audio bundles, audio
does *not* work yet. It looks like SDL2_mixer can not work without
threads, which is a problem. Yet another reason to write an OpenAL
backend - emscripten supports that natively.
* emscripten: customize the html shell
* emscripten: force "show log" checkbox unchecked initially
* emscripten: remove quit shortcut from main menu (since there's no quit)
* emscripten: log area fixes
* emscripten/webgl: workaround for fullscreen viewport issue
* emscripten: implement frameskip
* emscripter: improve framerate limiter
* align List to at least 8 bytes (shut up warnings)
* fix non-emscripten builds
* improve fullscreen handling, mainly for emscripten
* Workaround to make audio work in chromium
emscripten-core/emscripten#6511
* emscripten: better vsync handling; enable vsync & disable fxaa by default
2019-03-09 20:32:32 +01:00
|
|
|
s->stage->procs->end();
|
2018-07-04 10:36:16 +02:00
|
|
|
stage_draw_shutdown();
|
2020-10-10 08:41:44 +02:00
|
|
|
cosched_finish(&s->sched);
|
2017-02-26 13:17:48 +01:00
|
|
|
stage_free();
|
2017-11-25 16:51:43 +01:00
|
|
|
player_free(&global.plr);
|
2018-04-13 21:13:48 +02:00
|
|
|
ent_shutdown();
|
2020-10-10 08:41:44 +02:00
|
|
|
rng_make_active(&global.rand_visual);
|
2020-06-22 16:41:03 +02:00
|
|
|
stop_all_sfx();
|
2019-10-11 15:58:51 +02:00
|
|
|
|
Emscripten compatibility (#161)
* Major refactoring of the main loop(s) and control flow (WIP)
run_at_fps() is gone 🦀
Instead of nested blocking event loops, there is now an eventloop API
that manages an explicit stack of scenes. This makes Taisei a lot more
portable to async environments where spinning a loop forever without
yielding control simply is not an option, and that is the entire point
of this change.
A prime example of such an environment is the Web (via emscripten).
Taisei was able to run there through a terrible hack: inserting
emscripten_sleep calls into the loop, which would yield to the browser.
This has several major drawbacks: first of all, every function that
could possibly call emscripten_sleep must be compiled into a special
kind of bytecode, which then has to be interpreted at runtime, *much*
slower than JITed WebAssembly. And that includes *everything* down the
call stack, too! For more information, see
https://emscripten.org/docs/porting/emterpreter.html
Even though that method worked well enough for experimenting, despite
suboptimal performance, there is another obvious drawback:
emscripten_sleep is implemented via setTimeout(), which can be very
imprecise and is generally not reliable for fluid animation. Browsers
actually have an API specifically for that use case:
window.requestAnimationFrame(), but Taisei's original blocking control
flow style is simply not compatible with it. Emscripten exposes this API
with its emscripten_set_main_loop(), which the eventloop backend now
uses on that platform.
Unfortunately, C is still C, with no fancy closures or coroutines.
With blocking calls into menu/scene loops gone, the control flow is
reimplemented via so-called (pun intended) "call chains". That is
basically an euphemism for callback hell. With manual memory management
and zero type-safety. Not that the menu system wasn't shitty enough
already. I'll just keep telling myself that this is all temporary and
will be replaced with scripts in v1.4.
* improve build system for emscripten + various fixes
* squish menu bugs
* improve emscripten event loop; disable EMULATE_FUNCTION_POINTER_CASTS
Note that stock freetype does not work without
EMULATE_FUNCTION_POINTER_CASTS; use a patched version from the
"emscripten" branch here:
https://github.com/taisei-project/freetype2/tree/emscripten
* Enable -Wcast-function-type
Calling functions through incompatible pointers is nasal demons and
doesn't work in WASM.
* webgl: workaround a crash on some browsers
* emscripten improvements:
* Persist state (config, progress, replays, ...) in local IndexDB
* Simpler HTML shell (temporary)
* Enable more optimizations
* fix build if validate_glsl=false
* emscripten: improve asset packaging, with local cache
Note that even though there are rules to build audio bundles, audio
does *not* work yet. It looks like SDL2_mixer can not work without
threads, which is a problem. Yet another reason to write an OpenAL
backend - emscripten supports that natively.
* emscripten: customize the html shell
* emscripten: force "show log" checkbox unchecked initially
* emscripten: remove quit shortcut from main menu (since there's no quit)
* emscripten: log area fixes
* emscripten/webgl: workaround for fullscreen viewport issue
* emscripten: implement frameskip
* emscripter: improve framerate limiter
* align List to at least 8 bytes (shut up warnings)
* fix non-emscripten builds
* improve fullscreen handling, mainly for emscripten
* Workaround to make audio work in chromium
emscripten-core/emscripten#6511
* emscripten: better vsync handling; enable vsync & disable fxaa by default
2019-03-09 20:32:32 +01:00
|
|
|
taisei_commit_persistent_data();
|
2020-06-22 16:41:03 +02:00
|
|
|
skipstate_shutdown();
|
2018-05-19 04:01:16 +02:00
|
|
|
|
|
|
|
if(taisei_quit_requested()) {
|
2019-02-22 00:56:03 +01:00
|
|
|
global.gameover = GAMEOVER_ABORT;
|
2018-05-19 04:01:16 +02:00
|
|
|
}
|
Emscripten compatibility (#161)
* Major refactoring of the main loop(s) and control flow (WIP)
run_at_fps() is gone 🦀
Instead of nested blocking event loops, there is now an eventloop API
that manages an explicit stack of scenes. This makes Taisei a lot more
portable to async environments where spinning a loop forever without
yielding control simply is not an option, and that is the entire point
of this change.
A prime example of such an environment is the Web (via emscripten).
Taisei was able to run there through a terrible hack: inserting
emscripten_sleep calls into the loop, which would yield to the browser.
This has several major drawbacks: first of all, every function that
could possibly call emscripten_sleep must be compiled into a special
kind of bytecode, which then has to be interpreted at runtime, *much*
slower than JITed WebAssembly. And that includes *everything* down the
call stack, too! For more information, see
https://emscripten.org/docs/porting/emterpreter.html
Even though that method worked well enough for experimenting, despite
suboptimal performance, there is another obvious drawback:
emscripten_sleep is implemented via setTimeout(), which can be very
imprecise and is generally not reliable for fluid animation. Browsers
actually have an API specifically for that use case:
window.requestAnimationFrame(), but Taisei's original blocking control
flow style is simply not compatible with it. Emscripten exposes this API
with its emscripten_set_main_loop(), which the eventloop backend now
uses on that platform.
Unfortunately, C is still C, with no fancy closures or coroutines.
With blocking calls into menu/scene loops gone, the control flow is
reimplemented via so-called (pun intended) "call chains". That is
basically an euphemism for callback hell. With manual memory management
and zero type-safety. Not that the menu system wasn't shitty enough
already. I'll just keep telling myself that this is all temporary and
will be replaced with scripts in v1.4.
* improve build system for emscripten + various fixes
* squish menu bugs
* improve emscripten event loop; disable EMULATE_FUNCTION_POINTER_CASTS
Note that stock freetype does not work without
EMULATE_FUNCTION_POINTER_CASTS; use a patched version from the
"emscripten" branch here:
https://github.com/taisei-project/freetype2/tree/emscripten
* Enable -Wcast-function-type
Calling functions through incompatible pointers is nasal demons and
doesn't work in WASM.
* webgl: workaround a crash on some browsers
* emscripten improvements:
* Persist state (config, progress, replays, ...) in local IndexDB
* Simpler HTML shell (temporary)
* Enable more optimizations
* fix build if validate_glsl=false
* emscripten: improve asset packaging, with local cache
Note that even though there are rules to build audio bundles, audio
does *not* work yet. It looks like SDL2_mixer can not work without
threads, which is a problem. Yet another reason to write an OpenAL
backend - emscripten supports that natively.
* emscripten: customize the html shell
* emscripten: force "show log" checkbox unchecked initially
* emscripten: remove quit shortcut from main menu (since there's no quit)
* emscripten: log area fixes
* emscripten/webgl: workaround for fullscreen viewport issue
* emscripten: implement frameskip
* emscripter: improve framerate limiter
* align List to at least 8 bytes (shut up warnings)
* fix non-emscripten builds
* improve fullscreen handling, mainly for emscripten
* Workaround to make audio work in chromium
emscripten-core/emscripten#6511
* emscripten: better vsync handling; enable vsync & disable fxaa by default
2019-03-09 20:32:32 +01:00
|
|
|
|
2020-05-16 22:42:22 +02:00
|
|
|
_current_stage_state = NULL;
|
|
|
|
|
2022-01-09 13:11:26 +01:00
|
|
|
StageInfo *stginfo = s->stage;
|
2020-05-16 22:42:22 +02:00
|
|
|
CallChain cc = s->cc;
|
2022-01-09 13:11:26 +01:00
|
|
|
|
2023-01-09 04:19:31 +01:00
|
|
|
mem_free(s);
|
2020-05-16 22:42:22 +02:00
|
|
|
|
2022-01-09 13:11:26 +01:00
|
|
|
if(is_quickload) {
|
2022-01-28 17:03:22 +01:00
|
|
|
_stage_enter(stginfo, cc, quicksave, quicksave_is_automatic);
|
2022-01-09 13:11:26 +01:00
|
|
|
} else {
|
|
|
|
run_call_chain(&cc, NULL);
|
|
|
|
}
|
2011-06-25 12:41:40 +02:00
|
|
|
}
|
2019-03-11 00:21:43 +01:00
|
|
|
|
|
|
|
void stage_unlock_bgm(const char *bgm) {
|
2021-05-30 03:26:21 +02:00
|
|
|
if(global.replay.input.replay == NULL && !global.plr.stats.total.continues_used) {
|
2019-03-11 00:21:43 +01:00
|
|
|
progress_unlock_bgm(bgm);
|
|
|
|
}
|
|
|
|
}
|
2020-05-16 22:42:22 +02:00
|
|
|
|
|
|
|
void stage_shake_view(float strength) {
|
|
|
|
assume(strength >= 0);
|
|
|
|
_current_stage_state->view_shake += strength;
|
|
|
|
}
|
|
|
|
|
|
|
|
float stage_get_view_shake_strength(void) {
|
|
|
|
if(_current_stage_state) {
|
|
|
|
return _current_stage_state->view_shake;
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
2022-02-16 10:26:45 +01:00
|
|
|
|
|
|
|
void stage_load_quicksave(void) {
|
|
|
|
stage_do_quickload(NOT_NULL(_current_stage_state));
|
|
|
|
}
|
2022-12-22 16:55:56 +01:00
|
|
|
|
|
|
|
CoSched *stage_get_sched(void) {
|
|
|
|
return &NOT_NULL(_current_stage_state)->sched;
|
|
|
|
}
|