diff -ruN a/src/io/sdl_compat.cpp b/src/io/sdl_compat.cpp --- a/src/io/sdl_compat.cpp +++ b/src/io/sdl_compat.cpp @@ -20,6 +20,9 @@ #include #include +#ifdef __EMSCRIPTEN__ +#include +#endif #ifdef HAVE_SDL_MIXER /* @@ -57,6 +60,9 @@ SDL_Event e; for (;;) { +#ifdef __EMSCRIPTEN__ + emscripten_sleep(10); +#endif if (SDL_PollEvent(&e)) { if (e.type == SDL_KEYUP) { int s, m; @@ -82,6 +88,10 @@ while (SDL_PollEvent(&e)) { ; } +#ifdef __EMSCRIPTEN__ + /* the browser only delivers events when the game gives the turn back */ + emscripten_sleep(0); +#endif } /** @@ -229,7 +239,7 @@ exit(1); } - free(p); + /* SDL_mixer 2 may keep reading from the data while the music plays: it stays allocated */ return mod; #else diff -ruN a/src/io/sdl1compat.cpp b/src/io/sdl1compat.cpp --- a/src/io/sdl1compat.cpp +++ b/src/io/sdl1compat.cpp @@ -0,0 +1,227 @@ +/* + * The part of the SDL 1.2 API that Triplane Classic uses, on top of SDL2 + * (fredygames.com browser build, with Emscripten). + * + * The game draws into an 8-bit surface (the "screen"), with a 256-colour + * palette. Every flip converts it to RGB and copies it to a texture. + */ +#include +#include +#include "io/dksfile.h" + +#undef SDL_PollEvent +#undef SDL_PeepEvents +#undef SDL_GetKeyName +#undef Mix_LoadMUS_RW + +Uint8 sdl1_key_state[SDLK_LAST]; + +static SDL_Window *window = NULL; +static SDL_Renderer *renderer = NULL; +static SDL_Texture *texture = NULL; +static SDL_Surface *screen = NULL; +static SDL_Palette *palette = NULL; +static SDL_VideoInfo video_info; +static Uint32 *rgb = NULL; + +const SDL_VideoInfo *SDL_GetVideoInfo(void) { + memset(&video_info, 0, sizeof(video_info)); + video_info.wm_available = 1; + video_info.vfmt = screen ? screen->format : NULL; + return &video_info; +} + +SDL_Surface *SDL_SetVideoMode(int width, int height, int bpp, Uint32 flags) { + if (palette == NULL) { + palette = SDL_AllocPalette(256); + } + if (window == NULL) { + window = SDL_CreateWindow("Triplane Classic", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, width, height, 0); + if (window == NULL) { + return NULL; + } + renderer = SDL_CreateRenderer(window, -1, 0); + if (renderer == NULL) { + return NULL; + } + } else { + SDL_SetWindowSize(window, width, height); + } + if (screen != NULL) { + SDL_FreeSurface(screen); + screen = NULL; + } + if (texture != NULL) { + SDL_DestroyTexture(texture); + texture = NULL; + } + free(rgb); + texture = SDL_CreateTexture(renderer, SDL_PIXELFORMAT_RGB888, SDL_TEXTUREACCESS_STREAMING, width, height); + screen = SDL_CreateRGBSurfaceWithFormat(0, width, height, 8, SDL_PIXELFORMAT_INDEX8); + rgb = (Uint32 *) malloc(width * height * sizeof(Uint32)); + if (texture == NULL || screen == NULL || rgb == NULL) { + return NULL; + } + SDL_SetSurfacePalette(screen, palette); + return screen; +} + +int SDL_Flip(SDL_Surface * surface) { + if (surface != screen || texture == NULL) { + return 0; + } + Uint32 colors[256]; + for (int i = 0; i < 256; i++) { + const SDL_Color *c = &palette->colors[i]; + colors[i] = (c->r << 16) | (c->g << 8) | c->b; + } + const Uint8 *in = (const Uint8 *) screen->pixels; + Uint32 *out = rgb; + for (int y = 0; y < screen->h; y++) { + for (int x = 0; x < screen->w; x++) { + *out++ = colors[in[x]]; + } + in += screen->pitch; + } + SDL_UpdateTexture(texture, NULL, rgb, screen->w * sizeof(Uint32)); + SDL_RenderClear(renderer); + SDL_RenderCopy(renderer, texture, NULL, NULL); + SDL_RenderPresent(renderer); + emscripten_sleep(0); + return 0; +} + +int SDL_SetPalette(SDL_Surface * surface, int flags, const SDL_Color * colors, int firstcolor, int ncolors) { + if (surface == NULL || surface->format->palette == NULL) { + return 0; + } + return SDL_SetPaletteColors(surface->format->palette, colors, firstcolor, ncolors) == 0; +} + +/* The copy shares the screen palette, as an 8-bit surface of SDL 1.2 would look to the game. */ +SDL_Surface *SDL_DisplayFormat(SDL_Surface * surface) { + SDL_Surface *copy = SDL_CreateRGBSurfaceWithFormat(0, surface->w, surface->h, 8, SDL_PIXELFORMAT_INDEX8); + if (copy == NULL) { + return NULL; + } + SDL_SetSurfacePalette(copy, palette); + for (int y = 0; y < surface->h; y++) { + memcpy((Uint8 *) copy->pixels + y * copy->pitch, (Uint8 *) surface->pixels + y * surface->pitch, surface->w); + } + Uint32 key; + if (SDL_GetColorKey(surface, &key) == 0) { + SDL_SetColorKey(copy, SDL_TRUE, key); + } + return copy; +} + +void SDL_WM_SetCaption(const char *title, const char *icon) { +} + +/* SDL2 key code -> SDL 1.2 key code (0 when the game has no use for it) */ +static int sdl1_keycode(SDL_Keycode sym) { + if (sym < 128) { + return sym; + } + switch (sym) { + case SDLK_UP: return 273; + case SDLK_DOWN: return 274; + case SDLK_RIGHT: return 275; + case SDLK_LEFT: return 276; + case SDLK_INSERT: return 277; + case SDLK_HOME: return 278; + case SDLK_END: return 279; + case SDLK_PAGEUP: return 280; + case SDLK_PAGEDOWN: return 281; + case SDLK_KP_0: return 256; + case SDLK_KP_1: return 257; + case SDLK_KP_2: return 258; + case SDLK_KP_3: return 259; + case SDLK_KP_4: return 260; + case SDLK_KP_5: return 261; + case SDLK_KP_6: return 262; + case SDLK_KP_7: return 263; + case SDLK_KP_8: return 264; + case SDLK_KP_9: return 265; + case SDLK_KP_PERIOD: return 266; + case SDLK_KP_DIVIDE: return 267; + case SDLK_KP_MULTIPLY: return 268; + case SDLK_KP_MINUS: return 269; + case SDLK_KP_PLUS: return 270; + case SDLK_KP_ENTER: return 271; + case SDLK_KP_EQUALS: return 272; + case SDLK_NUMLOCKCLEAR: return 300; + case SDLK_CAPSLOCK: return 301; + case SDLK_SCROLLLOCK: return 302; + case SDLK_RSHIFT: return 303; + case SDLK_LSHIFT: return 304; + case SDLK_RCTRL: return 305; + case SDLK_LCTRL: return 306; + case SDLK_RALT: return 307; + case SDLK_LALT: return 308; + case SDLK_PAUSE: return 316; + default: + if (sym >= SDLK_F1 && sym <= SDLK_F12) { + return 282 + (sym - SDLK_F1); + } + return 0; + } +} + +/* SDL 1.2 key code -> SDL2 key code */ +static SDL_Keycode sdl2_keycode(int code) { + switch (code) { + case 273: return SDLK_UP; + case 274: return SDLK_DOWN; + case 275: return SDLK_RIGHT; + case 276: return SDLK_LEFT; + case 277: return SDLK_INSERT; + case 278: return SDLK_HOME; + case 279: return SDLK_END; + case 280: return SDLK_PAGEUP; + case 281: return SDLK_PAGEDOWN; + case 266: return SDLK_KP_PERIOD; + case 267: return SDLK_KP_DIVIDE; + case 268: return SDLK_KP_MULTIPLY; + case 269: return SDLK_KP_MINUS; + case 270: return SDLK_KP_PLUS; + case 271: return SDLK_KP_ENTER; + case 272: return SDLK_KP_EQUALS; + case 300: return SDLK_NUMLOCKCLEAR; + case 301: return SDLK_CAPSLOCK; + case 302: return SDLK_SCROLLLOCK; + case 303: return SDLK_RSHIFT; + case 304: return SDLK_LSHIFT; + case 305: return SDLK_RCTRL; + case 306: return SDLK_LCTRL; + case 307: return SDLK_RALT; + case 308: return SDLK_LALT; + case 316: return SDLK_PAUSE; + default: + if (code >= 256 && code <= 265) { + static const SDL_Keycode pad[10] = { SDLK_KP_0, SDLK_KP_1, SDLK_KP_2, SDLK_KP_3, SDLK_KP_4, + SDLK_KP_5, SDLK_KP_6, SDLK_KP_7, SDLK_KP_8, SDLK_KP_9 }; + return pad[code - 256]; + } + if (code >= 282 && code <= 293) { + return SDLK_F1 + (code - 282); + } + return code; + } +} + +const char *sdl1_key_name(int code) { + return SDL_GetKeyName(sdl2_keycode(code)); +} + +int sdl1_poll_event(SDL_Event * event) { + int got = SDL_PollEvent(event); + if (got && (event->type == SDL_KEYDOWN || event->type == SDL_KEYUP)) { + int code = sdl1_keycode(event->key.keysym.sym); + event->key.keysym.sym = (SDL_Keycode) code; + if (code > 0 && code < SDLK_LAST) { + sdl1_key_state[code] = (event->type == SDL_KEYDOWN); + } + } + return got; +} diff -ruN a/src/io/sdl1compat.h b/src/io/sdl1compat.h --- a/src/io/sdl1compat.h +++ b/src/io/sdl1compat.h @@ -0,0 +1,182 @@ +/* + * The part of the SDL 1.2 API that Triplane Classic uses, on top of SDL2 + * (fredygames.com browser build, with Emscripten). Force-included in every + * source file of the build. + * + * The game keeps its key bindings as SDL 1.2 key codes and indexes the key + * state array with them, so the key codes stay in the SDL 1.2 numbering + * here: events are translated as they are polled. + */ +#ifndef SDL1COMPAT_H +#define SDL1COMPAT_H + +#ifdef __cplusplus +#include +#include + +/* SDL_SetVideoMode, SDL_SetPalette and colour key flags */ +#define SDL_HWSURFACE 0x00000001 +#define SDL_ANYFORMAT 0x10000000 +#define SDL_HWPALETTE 0x20000000 +#define SDL_DOUBLEBUF 0x40000000 +#define SDL_FULLSCREEN 0x80000000 +#define SDL_LOGPAL 0x01 +#define SDL_PHYSPAL 0x02 +#define SDL_SRCCOLORKEY 0x00001000 + +typedef struct { + Uint32 hw_available:1; + Uint32 wm_available:1; + Uint32 blit_hw:1; + Uint32 blit_hw_CC:1; + Uint32 blit_hw_A:1; + Uint32 blit_sw:1; + Uint32 blit_sw_CC:1; + Uint32 blit_sw_A:1; + Uint32 blit_fill:1; + Uint32 video_mem; + SDL_PixelFormat *vfmt; +} SDL_VideoInfo; + +const SDL_VideoInfo *SDL_GetVideoInfo(void); +SDL_Surface *SDL_SetVideoMode(int width, int height, int bpp, Uint32 flags); +int SDL_Flip(SDL_Surface * screen); +int SDL_SetPalette(SDL_Surface * surface, int flags, const SDL_Color * colors, int firstcolor, int ncolors); +SDL_Surface *SDL_DisplayFormat(SDL_Surface * surface); +void SDL_WM_SetCaption(const char *title, const char *icon); + +/* Key codes of SDL 1.2 */ +#undef SDLK_LAST +#define SDLK_LAST 323 +extern Uint8 sdl1_key_state[SDLK_LAST]; +#define SDL_GetKeyState(numkeys) ((void) (numkeys), sdl1_key_state) + +#ifndef SDL1COMPAT_IMPL +#undef SDLK_1 +#undef SDLK_2 +#undef SDLK_4 +#undef SDLK_a +#undef SDLK_CAPSLOCK +#undef SDLK_COMMA +#undef SDLK_d +#undef SDLK_DELETE +#undef SDLK_DOWN +#undef SDLK_e +#undef SDLK_END +#undef SDLK_ESCAPE +#undef SDLK_f +#undef SDLK_F1 +#undef SDLK_F2 +#undef SDLK_F3 +#undef SDLK_F4 +#undef SDLK_F5 +#undef SDLK_F6 +#undef SDLK_F7 +#undef SDLK_F8 +#undef SDLK_g +#undef SDLK_h +#undef SDLK_HOME +#undef SDLK_INSERT +#undef SDLK_j +#undef SDLK_KP_PLUS +#undef SDLK_l +#undef SDLK_LEFT +#undef SDLK_LSHIFT +#undef SDLK_MINUS +#undef SDLK_o +#undef SDLK_p +#undef SDLK_PAGEDOWN +#undef SDLK_PAGEUP +#undef SDLK_PAUSE +#undef SDLK_PLUS +#undef SDLK_q +#undef SDLK_r +#undef SDLK_RIGHT +#undef SDLK_RSHIFT +#undef SDLK_s +#undef SDLK_t +#undef SDLK_TAB +#undef SDLK_UP +#undef SDLK_v +#undef SDLK_w +#undef SDLK_x +#undef SDLK_y +#undef SDLK_z +#undef SDLK_NUMLOCK +#undef SDLK_SCROLLOCK +#define SDLK_TAB 9 +#define SDLK_ESCAPE 27 +#define SDLK_COMMA 44 +#define SDLK_MINUS 45 +#define SDLK_PLUS 43 +#define SDLK_1 49 +#define SDLK_2 50 +#define SDLK_4 52 +#define SDLK_a 97 +#define SDLK_d 100 +#define SDLK_e 101 +#define SDLK_f 102 +#define SDLK_g 103 +#define SDLK_h 104 +#define SDLK_j 106 +#define SDLK_l 108 +#define SDLK_o 111 +#define SDLK_p 112 +#define SDLK_q 113 +#define SDLK_r 114 +#define SDLK_s 115 +#define SDLK_t 116 +#define SDLK_v 118 +#define SDLK_w 119 +#define SDLK_x 120 +#define SDLK_y 121 +#define SDLK_z 122 +#define SDLK_DELETE 127 +#define SDLK_KP5 261 +#define SDLK_KP_PLUS 270 +#define SDLK_UP 273 +#define SDLK_DOWN 274 +#define SDLK_RIGHT 275 +#define SDLK_LEFT 276 +#define SDLK_INSERT 277 +#define SDLK_HOME 278 +#define SDLK_END 279 +#define SDLK_PAGEUP 280 +#define SDLK_PAGEDOWN 281 +#define SDLK_F1 282 +#define SDLK_F2 283 +#define SDLK_F3 284 +#define SDLK_F4 285 +#define SDLK_F5 286 +#define SDLK_F6 287 +#define SDLK_F7 288 +#define SDLK_F8 289 +#define SDLK_NUMLOCK 300 +#define SDLK_CAPSLOCK 301 +#define SDLK_SCROLLOCK 302 +#define SDLK_RSHIFT 303 +#define SDLK_LSHIFT 304 +#define SDLK_PAUSE 316 +#endif + +/* Events: SDL_PollEvent keeps the key state and gives SDL 1.2 key codes */ +int sdl1_poll_event(SDL_Event * event); +#define SDL_PollEvent(e) sdl1_poll_event(e) +#define SDL_PeepEvents(e, n, a, m) SDL_PeepEvents(e, n, a, SDL_FIRSTEVENT, SDL_LASTEVENT) + +typedef int SDLKey; +const char *sdl1_key_name(int code); +#define SDL_GetKeyName(k) sdl1_key_name(k) + +/* The pointer cannot be moved by a web page */ +#define SDL_WarpMouse(x, y) ((void) 0) + +/* No joysticks in this build */ +#define SDL_NumJoysticks() 0 +#define SDL_JoystickOpened(n) 0 + +/* SDL_mixer 2 loads music with an extra argument (free the stream) */ +#define Mix_LoadMUS_RW(rw) Mix_LoadMUS_RW(rw, 0) +#endif + +#endif diff -ruN a/src/io/timing.cpp b/src/io/timing.cpp --- a/src/io/timing.cpp +++ b/src/io/timing.cpp @@ -35,9 +35,15 @@ target_tick = viimeinen_aika + 1000 / fps; while (SDL_GetTicks() <= target_tick) { uint32_t diff = target_tick - SDL_GetTicks(); +#ifdef __EMSCRIPTEN__ + /* hand the turn back to the browser; it paints and delivers key events in between */ + (void) diff; + SDL_Delay(1); +#else if (diff > 10 && diff < 1000) { SDL_Delay(diff); } +#endif } viimeinen_aika += 1000 / fps; diff -ruN a/src/triplane.cpp b/src/triplane.cpp --- a/src/triplane.cpp +++ b/src/triplane.cpp @@ -3818,6 +3818,16 @@ return 0; } +#ifdef __EMSCRIPTEN__ +#include +/* Called by the page when the tab is hidden or closed: the game only writes the pilots + * and the options when it ends (and the pilots after a mission). */ +extern "C" EMSCRIPTEN_KEEPALIVE void fredygames_save(void) { + save_roster(); + save_config(); +} +#endif + void loading_text(const char *teksti) { if (loading_texts) { printf("%s\n", teksti);