diff --git a/desktop_version/src/Graphics.cpp b/desktop_version/src/Graphics.cpp index c1d73828..b04c59a9 100644 --- a/desktop_version/src/Graphics.cpp +++ b/desktop_version/src/Graphics.cpp @@ -503,23 +503,82 @@ int Graphics::clear(void) return clear(0, 0, 0, 255); } +bool Graphics::substitute(SDL_Texture** texture) +{ + /* Either keep the given texture the same and return false, + * or substitute it for a translation and return true. */ + + if (loc::english_sprites) + { + return false; + } + + SDL_Texture* subst = NULL; + + if (*texture == grphx.im_sprites) + { + subst = grphx.im_sprites_translated; + } + else if (*texture == grphx.im_flipsprites) + { + subst = grphx.im_flipsprites_translated; + } + + if (subst == NULL) + { + return false; + } + + // Apply the same colors as on the original + Uint8 r, g, b, a; + SDL_GetTextureColorMod(*texture, &r, &g, &b); + SDL_GetTextureAlphaMod(*texture, &a); + set_texture_color_mod(subst, r, g, b); + set_texture_alpha_mod(subst, a); + + *texture = subst; + return true; +} + +void Graphics::post_substitute(SDL_Texture* subst) +{ + set_texture_color_mod(subst, 255, 255, 255); + set_texture_alpha_mod(subst, 255); +} + int Graphics::copy_texture(SDL_Texture* texture, const SDL_Rect* src, const SDL_Rect* dest) { + bool is_substituted = substitute(&texture); + const int result = SDL_RenderCopy(gameScreen.m_renderer, texture, src, dest); if (result != 0) { WHINE_ONCE_ARGS(("Could not copy texture: %s", SDL_GetError())); } + + if (is_substituted) + { + post_substitute(texture); + } + return result; } int Graphics::copy_texture(SDL_Texture* texture, const SDL_Rect* src, const SDL_Rect* dest, const double angle, const SDL_Point* center, const SDL_RendererFlip flip) { + bool is_substituted = substitute(&texture); + const int result = SDL_RenderCopyEx(gameScreen.m_renderer, texture, src, dest, angle, center, flip); if (result != 0) { WHINE_ONCE_ARGS(("Could not copy texture: %s", SDL_GetError())); } + + if (is_substituted) + { + post_substitute(texture); + } + return result; } @@ -3423,8 +3482,6 @@ static void make_array( vector.push_back(temp); } } - - VVV_freefunc(SDL_FreeSurface, *tilesheet); } bool Graphics::reloadresources(void) diff --git a/desktop_version/src/Graphics.h b/desktop_version/src/Graphics.h index 678eaac5..5cb7479c 100644 --- a/desktop_version/src/Graphics.h +++ b/desktop_version/src/Graphics.h @@ -196,6 +196,9 @@ public: int clear(int r, int g, int b, int a); int clear(void); + bool substitute(SDL_Texture** texture); + void post_substitute(SDL_Texture* subst); + int copy_texture(SDL_Texture* texture, const SDL_Rect* src, const SDL_Rect* dest); int copy_texture(SDL_Texture* texture, const SDL_Rect* src, const SDL_Rect* dest, double angle, const SDL_Point* center, SDL_RendererFlip flip); diff --git a/desktop_version/src/GraphicsResources.cpp b/desktop_version/src/GraphicsResources.cpp index 0e7cf965..29f5358a 100644 --- a/desktop_version/src/GraphicsResources.cpp +++ b/desktop_version/src/GraphicsResources.cpp @@ -1,10 +1,14 @@ #include "GraphicsResources.h" +#include + #include "Alloc.h" #include "FileSystemUtils.h" #include "GraphicsUtil.h" +#include "Localization.h" #include "Vlogging.h" #include "Screen.h" +#include "XMLUtils.h" // Used to load PNG data extern "C" @@ -260,6 +264,134 @@ static void LoadSprites(const char* filename, SDL_Texture** texture, SDL_Surface VVV_free(data); } +static void LoadSpritesTranslation( + const char* filename, + tinyxml2::XMLDocument* mask, + SDL_Surface* surface_english, + SDL_Texture** texture +) { + /* Create a sprites texture for display in another language. + * surface_english is used as a base. Parts of the translation (filename) + * will replace parts of the base, as instructed in the mask XML. */ + + if (surface_english == NULL) + { + vlog_error("LoadSpritesTranslation: English surface is NULL!"); + return; + } + + // Make a copy of the English sprites, for working with + SDL_Surface* working = GetSubSurface( + surface_english, + 0, 0, surface_english->w, surface_english->h + ); + if (working == NULL) + { + return; + } + + SDL_Surface* translated; + { + unsigned char* data; + SDL_Surface* loaded_image = LoadImageRaw(filename, &data); + translated = LoadSurfaceFromRaw(loaded_image); + + VVV_freefunc(SDL_FreeSurface, loaded_image); + VVV_free(data); + } + SDL_SetSurfaceBlendMode(translated, SDL_BLENDMODE_NONE); + + tinyxml2::XMLHandle hMask(mask); + tinyxml2::XMLElement* pElem; + + int sprite_w = 1, sprite_h = 1; + if ((pElem = mask->FirstChildElement()) != NULL) + { + sprite_w = pElem->IntAttribute("sprite_w", 1); + sprite_h = pElem->IntAttribute("sprite_h", 1); + } + + FOR_EACH_XML_ELEMENT(hMask, pElem) + { + EXPECT_ELEM(pElem, "sprite"); + + int x = pElem->IntAttribute("x", 0); + int y = pElem->IntAttribute("y", 0); + SDL_Rect src; + src.x = x * sprite_w; + src.y = y * sprite_h; + src.w = pElem->IntAttribute("w", 1) * sprite_w; + src.h = pElem->IntAttribute("h", 1) * sprite_h; + + SDL_Rect dst; + dst.x = pElem->IntAttribute("dx", x) * sprite_w; + dst.y = pElem->IntAttribute("dy", y) * sprite_h; + + SDL_BlitSurface(translated, &src, working, &dst); + } + + *texture = LoadTextureFromRaw(filename, working, TEX_WHITE); + + VVV_freefunc(SDL_FreeSurface, translated); + VVV_freefunc(SDL_FreeSurface, working); +} + +void GraphicsResources::init_translations(void) +{ + VVV_freefunc(SDL_DestroyTexture, im_sprites_translated); + VVV_freefunc(SDL_DestroyTexture, im_flipsprites_translated); + + if (loc::english_sprites) + { + return; + } + + const char* langcode = loc::lang.c_str(); + + const char* path_template = "lang/%s/graphics/%s"; + char path_xml[256]; + char path_sprites[256]; + char path_flipsprites[256]; + SDL_snprintf(path_xml, sizeof(path_xml), path_template, langcode, "spritesmask.xml"); + SDL_snprintf(path_sprites, sizeof(path_sprites), path_template, langcode, "sprites.png"); + SDL_snprintf(path_flipsprites, sizeof(path_flipsprites), path_template, langcode, "flipsprites.png"); + + /* We don't want to apply main-game translations to level-specific (custom) sprites. + * Either sprites and translations are BOTH main-game, or BOTH level-specific. + * Our pivots are the XML (it _has_ to exist for translated sprites to work) and + * graphics/sprites.png (what sense does it make to have only flipsprites). */ + if (FILESYSTEM_isAssetMounted(path_xml) != FILESYSTEM_isAssetMounted("graphics/sprites.png")) + { + return; + } + + tinyxml2::XMLDocument doc_mask; + if (!FILESYSTEM_loadAssetTiXml2Document(path_xml, doc_mask)) + { + // Only try to load the images if the XML document exists + return; + } + + if (FILESYSTEM_areAssetsInSameRealDir(path_xml, path_sprites)) + { + LoadSpritesTranslation( + path_sprites, + &doc_mask, + im_sprites_surf, + &im_sprites_translated + ); + } + if (FILESYSTEM_areAssetsInSameRealDir(path_xml, path_flipsprites)) + { + LoadSpritesTranslation( + path_flipsprites, + &doc_mask, + im_flipsprites_surf, + &im_flipsprites_translated + ); + } +} + void GraphicsResources::init(void) { LoadVariants("graphics/tiles.png", &im_tiles, &im_tiles_white, &im_tiles_tint); @@ -285,6 +417,11 @@ void GraphicsResources::init(void) im_image10 = LoadImage("graphics/ending.png"); im_image11 = LoadImage("graphics/site4.png", TEX_WHITE); + im_sprites_translated = NULL; + im_flipsprites_translated = NULL; + + init_translations(); + im_image12 = SDL_CreateTexture(gameScreen.m_renderer, SDL_PIXELFORMAT_ARGB8888, SDL_TEXTUREACCESS_TARGET, 240, 180); if (im_image12 == NULL) @@ -324,6 +461,9 @@ void GraphicsResources::destroy(void) CLEAR(im_image10); CLEAR(im_image11); CLEAR(im_image12); + + CLEAR(im_sprites_translated); + CLEAR(im_flipsprites_translated); #undef CLEAR VVV_freefunc(SDL_FreeSurface, im_sprites_surf); diff --git a/desktop_version/src/GraphicsResources.h b/desktop_version/src/GraphicsResources.h index 1d598c6b..20ba116d 100644 --- a/desktop_version/src/GraphicsResources.h +++ b/desktop_version/src/GraphicsResources.h @@ -16,6 +16,8 @@ public: void init(void); void destroy(void); + void init_translations(void); + SDL_Surface* im_sprites_surf; SDL_Surface* im_flipsprites_surf; @@ -43,6 +45,9 @@ public: SDL_Texture* im_image10; SDL_Texture* im_image11; SDL_Texture* im_image12; + + SDL_Texture* im_sprites_translated; + SDL_Texture* im_flipsprites_translated; }; #endif /* GRAPHICSRESOURCES_H */ diff --git a/desktop_version/src/Input.cpp b/desktop_version/src/Input.cpp index 912abf08..783f0214 100644 --- a/desktop_version/src/Input.cpp +++ b/desktop_version/src/Input.cpp @@ -1121,6 +1121,7 @@ static void menuactionpress(void) loc::lang = loc::languagelist[game.currentmenuoption].code; loc::loadtext(false); loc::lang_set = true; + graphics.grphx.init_translations(); } if (loc::pre_title_lang_menu) diff --git a/desktop_version/src/KeyPoll.cpp b/desktop_version/src/KeyPoll.cpp index 0bc35eb7..d2224881 100644 --- a/desktop_version/src/KeyPoll.cpp +++ b/desktop_version/src/KeyPoll.cpp @@ -177,6 +177,7 @@ void KeyPoll::Poll(void) { /* Reload language files */ loc::loadtext(false); + graphics.grphx.init_translations(); music.playef(Sound_COIN); }