diff --git a/main_sdl.c b/main_sdl.c index b2b28ae..823c56a 100644 --- a/main_sdl.c +++ b/main_sdl.c @@ -61,16 +61,17 @@ #define SFG_RAYCASTING_MAX_HITS 8 #endif #else - // emscripten - #define SFG_FPS 35 - #define SFG_SCREEN_RESOLUTION_X 512 - #define SFG_SCREEN_RESOLUTION_Y 320 + // emscripten (wasmga.me: the PC version's quality at 16:10, the save in + // a file the page keeps) + #define SFG_FPS 60 + #define SFG_SCREEN_RESOLUTION_X 640 + #define SFG_SCREEN_RESOLUTION_Y 400 #define SFG_CAN_EXIT 0 - #define SFG_RESOLUTION_SCALEDOWN 2 #define SFG_DITHERED_SHADOW 1 - #define SFG_BACKGROUND_BLUR 0 - #define SFG_RAYCASTING_MAX_STEPS 18 - #define SFG_RAYCASTING_MAX_HITS 8 + #define SFG_DIMINISH_SPRITES 1 + #define SFG_HEADBOB_SHEAR (-1 * SFG_SCREEN_RESOLUTION_Y / 80) + #define SFG_BACKGROUND_BLUR 1 + #define SFG_SAVE_FILE_PATH "/save/anarch.sav" #include #endif @@ -94,6 +95,7 @@ #define SDL_DISABLE_IMMINTRIN_H 1 #include +#include #include #include @@ -120,9 +122,11 @@ void SFG_setPixel(uint16_t x, uint16_t y, uint8_t colorIndex) sdlScreen[y * SFG_SCREEN_RESOLUTION_X + x] = paletteRGB565[colorIndex]; } +uint32_t timeOffset = 0; // moves the game's clock to a restored snapshot's + uint32_t SFG_getTimeMs() { - return SDL_GetTicks(); + return SDL_GetTicks() + timeOffset; } void SFG_save(uint8_t data[SFG_SAVE_SIZE]) @@ -144,7 +148,6 @@ void SFG_save(uint8_t data[SFG_SAVE_SIZE]) uint8_t SFG_load(uint8_t data[SFG_SAVE_SIZE]) { -#ifndef __EMSCRIPTEN__ FILE *f = fopen(SFG_SAVE_FILE_PATH,"rb"); puts("SDL: opening and reading save file"); @@ -160,10 +163,6 @@ uint8_t SFG_load(uint8_t data[SFG_SAVE_SIZE]) } return 1; -#else - // no saving for web version - return 0; -#endif } void SFG_sleepMs(uint16_t timeMs) @@ -178,6 +177,112 @@ void webButton(uint8_t key, uint8_t down) // HTML button pressed { webKeyboardState[key] = down; } + +int32_t webMouseX = 0, webMouseY = 0; // movement of the grabbed pointer + +EMSCRIPTEN_KEEPALIVE void webMouse(int32_t dx, int32_t dy) +{ + webMouseX += dx; + webMouseY += dy; +} + +/* The Resume snapshot: the game, player, level and music state as they are + in memory. The level and texture pointers in them point into this build's + data, so a snapshot names the build it is from. The save (progress and + settings) stays as it is now when one is loaded. */ + +#define WEB_SNAPSHOT_MAGIC 0x31524e41 // "ANR1" + +typedef struct +{ + uint32_t magic; + uint32_t build; + uint32_t time; + uint8_t game[sizeof(SFG_game)]; + uint8_t player[sizeof(SFG_player)]; + uint8_t level[sizeof(SFG_currentLevel)]; + uint8_t music[sizeof(SFG_MusicState)]; +} WebSnapshot; + +WebSnapshot webSnapshot; + +EMSCRIPTEN_KEEPALIVE void *webSnapshotBuffer() +{ + return &webSnapshot; +} + +EMSCRIPTEN_KEEPALIVE uint32_t webSnapshotSize() +{ + return sizeof(webSnapshot); +} + +// Fills the buffer and gives its size, 0 where there is no run to carry on. +EMSCRIPTEN_KEEPALIVE uint32_t webTakeSnapshot() +{ + uint8_t s = SFG_game.state; + + if (s == SFG_GAME_STATE_INIT || s == SFG_GAME_STATE_MENU || + s == SFG_GAME_STATE_INTRO || s == SFG_GAME_STATE_OUTRO) + return 0; + + webSnapshot.magic = WEB_SNAPSHOT_MAGIC; + webSnapshot.build = (uint32_t) (uintptr_t) &SFG_levels; + webSnapshot.time = SFG_getTimeMs(); + memcpy(webSnapshot.game,&SFG_game,sizeof(SFG_game)); + memcpy(webSnapshot.player,&SFG_player,sizeof(SFG_player)); + memcpy(webSnapshot.level,&SFG_currentLevel,sizeof(SFG_currentLevel)); + memcpy(webSnapshot.music,&SFG_MusicState,sizeof(SFG_MusicState)); + + return sizeof(webSnapshot); +} + +// Loads the snapshot the page wrote into the buffer, 1 if it could. +EMSCRIPTEN_KEEPALIVE uint8_t webRestoreSnapshot() +{ + if (webSnapshot.magic != WEB_SNAPSHOT_MAGIC || + webSnapshot.build != (uint32_t) (uintptr_t) &SFG_levels) + return 0; + + uint8_t save[SFG_SAVE_SIZE], settings = SFG_game.settings; + + memcpy(save,SFG_game.save,SFG_SAVE_SIZE); + memcpy(&SFG_game,webSnapshot.game,sizeof(SFG_game)); + memcpy(&SFG_player,webSnapshot.player,sizeof(SFG_player)); + memcpy(&SFG_currentLevel,webSnapshot.level,sizeof(SFG_currentLevel)); + memcpy(&SFG_MusicState,webSnapshot.music,sizeof(SFG_MusicState)); + memcpy(SFG_game.save,save,SFG_SAVE_SIZE); + SFG_game.settings = settings; + + for (uint8_t i = 0; i < SFG_KEY_COUNT; ++i) + SFG_game.keyStates[i] = 0; + + timeOffset = 0; + timeOffset = webSnapshot.time - SFG_getTimeMs(); + SFG_game.frameTime = SFG_getTimeMs(); + webMouseX = webMouseY = 0; + + // A run in a level comes back in the menu, on continue. + if (SFG_game.state == SFG_GAME_STATE_PLAYING || + SFG_game.state == SFG_GAME_STATE_LEVEL_START || + SFG_game.state == SFG_GAME_STATE_MAP) + { + SFG_setGameState(SFG_GAME_STATE_MENU); + SFG_game.selectedMenuItem = 0; + } + + SFG_setMusic((SFG_game.settings & 0x02) ? + SFG_MUSIC_TURN_ON : SFG_MUSIC_TURN_OFF); + + return 1; +} + +// 1 once the last level is won: the run is over. +EMSCRIPTEN_KEEPALIVE uint8_t webOver() +{ + return SFG_game.state == SFG_GAME_STATE_OUTRO || + (SFG_game.state == SFG_GAME_STATE_WIN && + SFG_currentLevel.levelNumber == SFG_NUMBER_OF_LEVELS - 1); +} #endif int8_t mouseMoved = 0; /* Whether the mouse has moved since program started, @@ -185,7 +290,11 @@ int8_t mouseMoved = 0; /* Whether the mouse has moved since program started, void SFG_getMouseOffset(int16_t *x, int16_t *y) { -#ifndef __EMSCRIPTEN__ +#ifdef __EMSCRIPTEN__ + *x = RCL_max(-1000,RCL_min(1000,webMouseX)); + *y = RCL_max(-1000,RCL_min(1000,webMouseY)); + webMouseX = webMouseY = 0; +#else if (mouseMoved) { int mX, mY; @@ -242,7 +351,7 @@ int8_t SFG_keyPressed(uint8_t key) case SFG_KEY_JUMP: return k(SPACE) || b(A); break; case SFG_KEY_STRAFE_LEFT: return k(A) || k(KP_7); break; case SFG_KEY_STRAFE_RIGHT: return k(D) || k(KP_9); break; - case SFG_KEY_MAP: return k(TAB) || b(BACK); break; + case SFG_KEY_MAP: return k(TAB) || k(M) || b(BACK); break; case SFG_KEY_CYCLE_WEAPON: return k(F) || (sdlMouseButtonState & SDL_BUTTON_MMASK); break; case SFG_KEY_TOGGLE_FREELOOK: return b(LEFTSTICK) || @@ -308,6 +417,23 @@ void mainLoopIteration() sdlMouseButtonState = SDL_GetMouseState(NULL,NULL); +#ifdef __EMSCRIPTEN__ + // A hidden tab gets no frames: the game's clock waits instead of the game + // running every missed step at once when it comes back. + if (SFG_game.state != SFG_GAME_STATE_INIT) + { + uint32_t behind = SFG_getTimeMs() - SFG_game.frameTime; + + if (behind > 250) + timeOffset -= behind - SFG_MS_PER_FRAME; + } + + // Only play reads the mouse; movement in the menus and the map is dropped. + if (SFG_game.state != SFG_GAME_STATE_PLAYING && + SFG_game.state != SFG_GAME_STATE_LEVEL_START) + webMouseX = webMouseY = 0; +#endif + if (!SFG_mainLoopBody()) running = 0; @@ -319,12 +445,6 @@ void mainLoopIteration() SDL_RenderPresent(renderer); } -#ifdef __EMSCRIPTEN__ -typedef void (*em_callback_func)(void); -void emscripten_set_main_loop( - em_callback_func func, int fps, int simulate_infinite_loop); -#endif - uint16_t audioBuff[SFG_SFX_SAMPLE_COUNT]; uint16_t audioPos = 0; // audio position for the next audio buffer fill uint32_t audioUpdateFrame = 0; // game frame at which audio buffer fill happened @@ -470,7 +590,9 @@ int main(int argc, char *argv[]) sdlKeyboardState = SDL_GetKeyboardState(NULL); - sdlController = SDL_GameControllerOpen(0); +#ifndef __EMSCRIPTEN__ + sdlController = SDL_GameControllerOpen(0); // on the web the page maps controllers +#endif #if !SFG_OS_IS_MALWARE signal(SIGINT,handleSignal);