diff --git a/src/File.cxx b/src/File.cxx index a2eec0c..2ce645d 100644 --- a/src/File.cxx +++ b/src/File.cxx @@ -30,7 +30,7 @@ using namespace Amoebax; -#if !defined (IS_WIN32_HOST) && !defined (IS_GP2X_HOST) && !defined (IS_OSX_HOST) +#if !defined (IS_WIN32_HOST) && !defined (IS_GP2X_HOST) && !defined (IS_OSX_HOST) && !defined (__EMSCRIPTEN__) #include "binreloc.h" /// @@ -66,7 +66,7 @@ locateDataDir (void) return dataDir; } -#endif // !IS_WIN32_HOST && !IS_GP2X_HOST && !IS_OSX_HOST +#endif // !IS_WIN32_HOST && !IS_GP2X_HOST && !IS_OSX_HOST && !__EMSCRIPTEN__ #if defined (IS_WIN32_HOST) static const char *k_DirectorySeparator = "\\"; @@ -74,7 +74,7 @@ static const char *k_DirectorySeparator = "\\"; static const char *k_DirectorySeparator = "/"; #endif // IS_WIN32_HOST -#if defined (IS_WIN32_HOST) || defined (IS_GP2X_HOST) || defined (IS_OSX_HOST) +#if defined (IS_WIN32_HOST) || defined (IS_GP2X_HOST) || defined (IS_OSX_HOST) || defined (__EMSCRIPTEN__) static const char *k_DataDirectory = "."; #else // !IS_WIN32_HOST && !IS_GP2X_HOST && !IS_OSX_HOST static const char *k_DataDirectory = locateDataDir (); diff --git a/src/Surface.cxx b/src/Surface.cxx index 4a3d6c2..2ba7ff4 100644 --- a/src/Surface.cxx +++ b/src/Surface.cxx @@ -60,23 +60,15 @@ Surface::Surface (const Surface &surface): { if ( 0 != surface.m_SDLSurface ) { + // SDL 2 blits with alpha onto an empty surface would premultiply the + // colors, so the pixels are copied as they are. SDL_Surface *tempSDLSurface = - SDL_CreateRGBSurface (surface.m_SDLSurface->flags | SDL_SRCALPHA, - surface.m_SDLSurface->w, - surface.m_SDLSurface->h, - surface.m_SDLSurface->format->BitsPerPixel, - surface.m_SDLSurface->format->Rmask, - surface.m_SDLSurface->format->Gmask, - surface.m_SDLSurface->format->Bmask, - surface.m_SDLSurface->format->Amask); + SDL_ConvertSurface (surface.m_SDLSurface, + surface.m_SDLSurface->format, 0); if ( NULL == tempSDLSurface ) { throw std::runtime_error (SDL_GetError ()); } - if ( SDL_BlitSurface (surface.m_SDLSurface, NULL, tempSDLSurface, NULL) < 0 ) - { - throw std::runtime_error (SDL_GetError ()); - } std::swap (m_SDLSurface, tempSDLSurface); SDL_FreeSurface (tempSDLSurface); } @@ -129,15 +121,18 @@ Surface::blit (uint16_t sourceX, uint16_t sourceY, uint16_t destinationX, uint16_t destinationY, SDL_Surface *destination) { + // The coordinates are unsigned 16-bit values that can hold a negative + // position wrapped around; SDL 1.2 rectangles read them back as signed, + // SDL 2 ones (32-bit) need it done here. SDL_Rect sourceRect; - sourceRect.x = sourceX; - sourceRect.y = sourceY; + sourceRect.x = static_cast(sourceX); + sourceRect.y = static_cast(sourceY); sourceRect.w = sourceWidth; sourceRect.h = sourceHeight; SDL_Rect destinationRect; - destinationRect.x = destinationX; - destinationRect.y = destinationY; + destinationRect.x = static_cast(destinationX); + destinationRect.y = static_cast(destinationY); destinationRect.w = sourceWidth; destinationRect.h = sourceHeight; diff --git a/src/System.cxx b/src/System.cxx index bd56427..a36e47b 100644 --- a/src/System.cxx +++ b/src/System.cxx @@ -47,6 +47,10 @@ extern "C" void OSXAlert (const char *title, const char *message); #include "System.h" #include "VideoErrorState.h" +#if defined (__EMSCRIPTEN__) +extern "C" void fredygames_save (void); +#endif // __EMSCRIPTEN__ + using namespace Amoebax; /// The buffer system to use for audio. @@ -538,18 +542,6 @@ System::run (void) break; #if !defined (IS_GP2X_HOST) - case SDL_ACTIVEEVENT: - if ( event.active.state & SDL_APPACTIVE ) - { - // Window is minimized. - if ( 0 == event.active.gain ) - { - Music::pause (); - pause (); - } - } - break; - case SDL_KEYDOWN: // ALT+RETURN is reserved to toggle full screen if ( SDLK_RETURN == event.key.keysym.sym && @@ -561,12 +553,28 @@ System::run (void) { m_ActiveState->keyDown (event.key.keysym.sym); } - // Check if unicode translation is enabled - // and send the unicode code if so. - if ( isUnicodeTranslationEnabled () && - 0 != event.key.keysym.unicode ) + break; + + case SDL_TEXTINPUT: + // SDL 2 sends the typed characters (UTF-8) in their + // own event, since keysym.unicode is gone. + if ( isUnicodeTranslationEnabled () ) { - m_ActiveState->unicodeCharacterPressed (event.key.keysym.unicode); + const unsigned char *text = + reinterpret_cast(event.text.text); + uint16_t character = 0; + if ( text[0] < 0x80 ) + { + character = text[0]; + } + else if ( (text[0] & 0xE0) == 0xC0 && 0 != text[1] ) + { + character = ((text[0] & 0x1F) << 6) | (text[1] & 0x3F); + } + if ( 0 != character ) + { + m_ActiveState->unicodeCharacterPressed (character); + } } break; @@ -638,6 +646,19 @@ System::run (void) // Refresh the screen. SDL_Flip (getScreenSDLSurface ()); +#if defined (__EMSCRIPTEN__) + // Browser build: writes the options (and so the high scores) every + // 20 seconds, so the page can keep them in the browser. + { + static int framesSinceSave = 0; + if ( ++framesSinceSave >= 600 ) + { + framesSinceSave = 0; + fredygames_save (); + } + } +#endif // __EMSCRIPTEN__ + // Deletes the not longer used states. if ( !m_StatesToDelete.empty () ) { diff --git a/src/System.h b/src/System.h index 2d13d5e..ea8f0b4 100644 --- a/src/System.h +++ b/src/System.h @@ -208,8 +208,8 @@ namespace Amoebax inline void System::invalidateScreenRegion (SDL_Rect *region) { - m_InvalidatedRegion.x1 = std::min (m_InvalidatedRegion.x1, region->x); - m_InvalidatedRegion.y1 = std::min (m_InvalidatedRegion.y1, region->y); + m_InvalidatedRegion.x1 = std::min (m_InvalidatedRegion.x1, static_cast(region->x)); + m_InvalidatedRegion.y1 = std::min (m_InvalidatedRegion.y1, static_cast(region->y)); m_InvalidatedRegion.x2 = std::max (m_InvalidatedRegion.x2, static_cast(region->x + region->w)); diff --git a/src/UnixOptions.cxx b/src/UnixOptions.cxx index 0ffa7ac..9d550fc 100644 --- a/src/UnixOptions.cxx +++ b/src/UnixOptions.cxx @@ -48,17 +48,26 @@ UnixOptions::~UnixOptions (void) writeOptions (); } -#if defined (IS_GP2X_HOST) /// /// \brief Forces to write the current options to the file. /// void UnixOptions::flush (void) { - writeOptions (); - sync (); + // Never overwrites the file before the options were read from it. + if ( m_OptionsAlreadyLoaded ) + { + writeOptions (); + } +} + +#if defined (__EMSCRIPTEN__) +extern "C" void +fredygames_save (void) +{ + static_cast(Options::getInstance ()).flush (); } -#endif // IS_GP2X_HOST +#endif // __EMSCRIPTEN__ bool UnixOptions::getBooleanValue (const std::string §ion, diff --git a/src/UnixOptions.h b/src/UnixOptions.h index ec7d4ce..31e17ea 100644 --- a/src/UnixOptions.h +++ b/src/UnixOptions.h @@ -33,9 +33,7 @@ namespace Amoebax public: UnixOptions (void); virtual ~UnixOptions (void); -#if defined (IS_GP2X_HOST) void flush (void); -#endif // IS_GP2X_HOST protected: virtual bool getBooleanValue (const std::string §ion, diff --git a/src/amxsdl12.c b/src/amxsdl12.c new file mode 100644 index 0000000..6f607f1 --- /dev/null +++ b/src/amxsdl12.c @@ -0,0 +1,100 @@ +/* + * amxsdl12.c: see amxsdl12.h. The game draws on the surface of SDL_SetVideoMode (here a 32-bit + * surface in memory); each SDL_Flip uploads it to a streaming texture and shows it. + * Copyright (C) 2026 FredyGames. Distributed under the GNU GPL version 2 or later, like the game. + */ +#include "amxsdl12.h" + +#undef SDL_SetVideoMode +#undef SDL_Flip +#undef SDL_DisplayFormatAlpha +#undef SDL_SetAlpha +#undef SDL_SetColorKey +#undef SDL_PollEvent +#undef SDL_EnableUNICODE + +static SDL_Window *window = NULL; +static SDL_Renderer *renderer = NULL; +static SDL_Texture *texture = NULL; +static SDL_Surface *screen = NULL; + +SDL_Surface *amx_SetVideoMode(int width, int height, int bpp, Uint32 flags) +{ + (void)bpp; (void)flags; + if (window == NULL) { + window = SDL_CreateWindow("Amoebax", 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; + } + if (texture != NULL) + SDL_DestroyTexture(texture); + if (screen != NULL) + SDL_FreeSurface(screen); + SDL_SetWindowSize(window, width, height); + texture = SDL_CreateTexture(renderer, SDL_PIXELFORMAT_RGB888, SDL_TEXTUREACCESS_STREAMING, width, height); + screen = SDL_CreateRGBSurfaceWithFormat(0, width, height, 32, SDL_PIXELFORMAT_RGB888); + return screen; +} + +int amx_Flip(SDL_Surface *surface) +{ + if (surface != screen || texture == NULL) + return -1; + SDL_UpdateTexture(texture, NULL, screen->pixels, screen->pitch); + SDL_RenderClear(renderer); + SDL_RenderCopy(renderer, texture, NULL, NULL); + SDL_RenderPresent(renderer); + return 0; +} + +/* 32-bit ARGB, with the alpha channel of the image (blending on). */ +SDL_Surface *amx_DisplayFormatAlpha(SDL_Surface *surface) +{ + return SDL_ConvertSurfaceFormat(surface, SDL_PIXELFORMAT_ARGB8888, 0); +} + +/* SDL 1.2 per-surface alpha: SDL_SRCALPHA turns blending on with that alpha, no flag turns it off. */ +int amx_SetAlpha(SDL_Surface *surface, Uint32 flags, Uint8 alpha) +{ + if (flags & SDL_SRCALPHA) { + SDL_SetSurfaceBlendMode(surface, SDL_BLENDMODE_BLEND); + return SDL_SetSurfaceAlphaMod(surface, alpha); + } + SDL_SetSurfaceBlendMode(surface, surface->format->Amask ? SDL_BLENDMODE_BLEND : SDL_BLENDMODE_NONE); + return SDL_SetSurfaceAlphaMod(surface, 255); +} + +int amx_SetColorKey(SDL_Surface *surface, Uint32 flags, Uint32 key) +{ + return SDL_SetColorKey(surface, (flags & SDL_SRCCOLORKEY) ? SDL_TRUE : SDL_FALSE, key); +} + +/* SDL 1.2 had no key repeat unless asked for; SDL 2 always has it. Repeats are dropped here. */ +int amx_PollEvent(SDL_Event *event) +{ + while (SDL_PollEvent(event)) { + if (event->type == SDL_KEYDOWN && event->key.repeat) + continue; + return 1; + } + return 0; +} + +int amx_EnableUNICODE(int enable) +{ + if (enable) + SDL_StartTextInput(); + else + SDL_StopTextInput(); + return 0; +} + +void amx_WM_SetCaption(const char *title, const char *icon) +{ + (void)icon; + if (window != NULL && title != NULL) + SDL_SetWindowTitle(window, title); +} diff --git a/src/amxsdl12.h b/src/amxsdl12.h new file mode 100644 index 0000000..919d7b6 --- /dev/null +++ b/src/amxsdl12.h @@ -0,0 +1,50 @@ +/* + * amxsdl12.h: the part of the SDL 1.2 API that Amoebax uses, on top of SDL 2, for the fredygames.com + * browser build (Emscripten). Included before every source file (-include). Written for that build. + * Copyright (C) 2026 FredyGames. Distributed under the GNU GPL version 2 or later, like the game. + */ +#ifndef AMXSDL12_H +#define AMXSDL12_H + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +typedef SDL_Keycode SDLKey; + +#define SDL_SWSURFACE 0x00000000 +#define SDL_FULLSCREEN 0x80000000 +#define SDL_SRCCOLORKEY 0x00001000 +#define SDL_SRCALPHA 0x00010000 +#define SDL_ENABLE 1 +#define SDL_DISABLE 0 + +SDL_Surface *amx_SetVideoMode(int width, int height, int bpp, Uint32 flags); +int amx_Flip(SDL_Surface *screen); +SDL_Surface *amx_DisplayFormatAlpha(SDL_Surface *surface); +int amx_SetAlpha(SDL_Surface *surface, Uint32 flags, Uint8 alpha); +int amx_SetColorKey(SDL_Surface *surface, Uint32 flags, Uint32 key); +int amx_PollEvent(SDL_Event *event); +int amx_EnableUNICODE(int enable); +void amx_WM_SetCaption(const char *title, const char *icon); + +#ifdef __cplusplus +} +#endif + +#define SDL_SetVideoMode amx_SetVideoMode +#define SDL_Flip amx_Flip +#define SDL_DisplayFormatAlpha amx_DisplayFormatAlpha +#define SDL_SetAlpha amx_SetAlpha +#define SDL_SetColorKey amx_SetColorKey +#define SDL_PollEvent amx_PollEvent +#define SDL_EnableUNICODE amx_EnableUNICODE +#define SDL_WM_SetCaption amx_WM_SetCaption +/* The frame limiter waits with SDL_Delay: here that hands the page back to the browser (Asyncify). */ +#undef SDL_Delay +#define SDL_Delay(ms) emscripten_sleep(ms) + +#endif