From a9c05703aece765cad5a3fea8825e2ce759db127 Mon Sep 17 00:00:00 2001 From: Morgan Christiansson Date: Sun, 5 Jul 2026 18:40:04 +0200 Subject: [PATCH 1/4] Palette cycling animation --- CGame_Render.cpp | 2 + CIO/CFile.cpp | 44 ++++++++++- CMap.h | 7 ++ CSurface.cpp | 188 ++++++++++++++++++++++++++++++++-------------- CSurface.h | 6 +- globals.cpp | 2 + globals.h | 5 ++ include/defines.h | 13 ++++ 8 files changed, 207 insertions(+), 60 deletions(-) diff --git a/CGame_Render.cpp b/CGame_Render.cpp index 3fb5b4b..63b82aa 100644 --- a/CGame_Render.cpp +++ b/CGame_Render.cpp @@ -55,6 +55,8 @@ void CGame::Render() // render the map if active if(MapObj && MapObj->isActive()) { + if(MapObj->getMap()) + CSurface::UpdatePaletteAnimations(MapObj->getMap()->type); if(auto* mapSurf = MapObj->getSurface()) { std::array textBuffer; diff --git a/CIO/CFile.cpp b/CIO/CFile.cpp index 078d1d0..da8e80d 100644 --- a/CIO/CFile.cpp +++ b/CIO/CFile.cpp @@ -352,6 +352,9 @@ bool CFile::read_lbm(FILE* fp, const boost::filesystem::path& filepath) // chunk-identifier (4 Bytes) // search for the "CMAP" and skip other chunk-types + const Uint16 tilesetSlot = static_cast(bmpArray - global::bmpArray.data()); + int chunkIdx = 0; + while(!feof(fp)) { CHECK_READ(libendian::read(chunk_identifier.data(), 4, fp)); @@ -392,7 +395,45 @@ bool CFile::read_lbm(FILE* fp, const boost::filesystem::path& filepath) if(strcmp(chunk_identifier.data(), "BODY") == 0) break; - else + else if(strcmp(chunk_identifier.data(), "CRNG") == 0) + { + Uint32 chunkLen; + CHECK_READ(libendian::be_read_ui(&chunkLen, fp)); + if(chunkLen >= 8) + { + PaletteAnimation anim; + uint16_t padding, rate, flags; + CHECK_READ(libendian::be_read_us(&padding, fp)); + CHECK_READ(libendian::be_read_us(&rate, fp)); + CHECK_READ(libendian::be_read_us(&flags, fp)); + uint8_t firstClr, lastClr; + CHECK_READ(libendian::read(&firstClr, 1, fp)); + CHECK_READ(libendian::read(&lastClr, 1, fp)); + anim.isActive = (flags & 1) != 0; + anim.moveUp = (flags & 2) != 0; + if(rate) + anim.isActive = anim.moveUp = true; + anim.rate = rate; + anim.firstClr = firstClr; + anim.lastClr = lastClr; + anim.currentOffset = 0; + anim.lastUpdateTime = SDL_GetTicks(); + global::paletteAnimations[tilesetSlot][chunkIdx] = anim; + if(chunkLen > 8) + { + uint32_t remaining = chunkLen - 8; + if(remaining & 1) + remaining++; + fseek(fp, remaining, SEEK_CUR); + } else if(chunkLen & 1) + fseek(fp, 1, SEEK_CUR); + } else + { + if(chunkLen & 1) + chunkLen++; + fseek(fp, chunkLen, SEEK_CUR); + } + } else { Uint32 chunkLen; CHECK_READ(libendian::be_read_ui(&chunkLen, fp)); @@ -400,6 +441,7 @@ bool CFile::read_lbm(FILE* fp, const boost::filesystem::path& filepath) chunkLen++; fseek(fp, chunkLen, SEEK_CUR); } + chunkIdx++; } if(feof(fp)) return false; diff --git a/CMap.h b/CMap.h index fbb3352..7fdad53 100644 --- a/CMap.h +++ b/CMap.h @@ -150,6 +150,13 @@ class CMap render(); return Surf_Map.get(); } + /// Returns nullptr if the surface is not 8-bit or not created yet + SDL_Palette* getSurfacePalette() const + { + if(!Surf_Map || Surf_Map->format->BytesPerPixel != 1) + return nullptr; + return Surf_Map->format->palette; + } DisplayRectangle getDisplayRect() { return displayRect; } void setDisplayRect(const DisplayRectangle& displayRect) { this->displayRect = displayRect; } auto& getPlayerHQx() { return PlayerHQx; } diff --git a/CSurface.cpp b/CSurface.cpp index 730f3f4..e8163f5 100644 --- a/CSurface.cpp +++ b/CSurface.cpp @@ -15,12 +15,15 @@ #include #include #include +#include // Disable SGE's internal surface locking once at startup; terrain drawing is // the only remaining consumer of SGE functions and the caller already handles // locking. Was originally called in CGame::Init(). static bool sgeLockOff = (sge_Lock_OFF(), true); +static Uint16 tilesetIdxForMapType(MapType mapType, bool want32bit); + namespace { const TerrainDesc* getTerrainDesc(const bobMAP& map, Uint8 rawTextureId) { @@ -623,11 +626,10 @@ bool GetAdjustedPoints(const DisplayRectangle& displayRect, const bobMAP& myMap, } } // namespace -void CSurface::GetTerrainTextureCoords(MapType mapType, TriangleTerrainType texture, bool isRSU, int texture_move, - Point16& upper, Point16& left, Point16& right, Point16& upper2, Point16& left2, - Point16& right2) +void CSurface::GetTerrainTextureCoords(MapType mapType, TriangleTerrainType texture, bool isRSU, Point16& upper, + Point16& left, Point16& right, Point16& upper2, Point16& left2, Point16& right2) { - const auto animOffset = Point16(-texture_move, texture_move); + // Offset-shifting animation replaced by palette cycling animation (UpdatePaletteAnimations) switch(texture) { // in case of USD-Triangle "upper.x" and "upper.y" means "lowerX" and "lowerY" @@ -657,14 +659,14 @@ void CSurface::GetTerrainTextureCoords(MapType mapType, TriangleTerrainType text { if(isRSU) { - upper2 = Point16(231, 61) + animOffset; - left2 = Point16(207, 62) + animOffset; - right2 = Point16(223, 78) + animOffset; + upper2 = Point16(231, 61); + left2 = Point16(207, 62); + right2 = Point16(223, 78); } else { - upper2 = Point16(224, 79) + animOffset; - left2 = Point16(232, 62) + animOffset; - right2 = Point16(245, 76) + animOffset; + upper2 = Point16(224, 79); + left2 = Point16(232, 62); + right2 = Point16(245, 76); } } break; @@ -676,14 +678,14 @@ void CSurface::GetTerrainTextureCoords(MapType mapType, TriangleTerrainType text { if(isRSU) { - upper2 = Point16(231, 61) + animOffset; - left2 = Point16(207, 62) + animOffset; - right2 = Point16(223, 78) + animOffset; + upper2 = Point16(231, 61); + left2 = Point16(207, 62); + right2 = Point16(223, 78); } else { - upper2 = Point16(224, 79) + animOffset; - left2 = Point16(232, 62) + animOffset; - right2 = Point16(245, 76) + animOffset; + upper2 = Point16(224, 79); + left2 = Point16(232, 62); + right2 = Point16(245, 76); } } break; @@ -700,14 +702,14 @@ void CSurface::GetTerrainTextureCoords(MapType mapType, TriangleTerrainType text case TRIANGLE_TEXTURE_WATER__: if(isRSU) { - upper = Point16(231, 61) + animOffset; - left = Point16(207, 62) + animOffset; - right = Point16(223, 78) + animOffset; + upper = Point16(231, 61); + left = Point16(207, 62); + right = Point16(223, 78); } else { - upper = Point16(224, 79) + animOffset; - left = Point16(232, 62) + animOffset; - right = Point16(245, 76) + animOffset; + upper = Point16(224, 79); + left = Point16(232, 62); + right = Point16(245, 76); } break; case TRIANGLE_TEXTURE_MEADOW1: @@ -753,14 +755,14 @@ void CSurface::GetTerrainTextureCoords(MapType mapType, TriangleTerrainType text case TRIANGLE_TEXTURE_LAVA: if(isRSU) { - upper = Point16(231, 117) + animOffset; - left = Point16(207, 118) + animOffset; - right = Point16(223, 134) + animOffset; + upper = Point16(231, 117); + left = Point16(207, 118); + right = Point16(223, 134); } else { - upper = Point16(224, 135) + animOffset; - left = Point16(232, 118) + animOffset; - right = Point16(245, 132) + animOffset; + upper = Point16(224, 135); + left = Point16(232, 118); + right = Point16(245, 132); } break; case TRIANGLE_TEXTURE_MINING_MEADOW: @@ -793,10 +795,8 @@ void CSurface::DrawTriangle(SDL_Surface* display, const DisplayRectangle& displa // from it's surrounded water, i use this color keys below. These are the color values for the water texture. // I wrote a special SGE-Function that uses these color keys and ignores them in the Surf_Tileset. static std::array colorkeys = {14191, 14195, 13167, 13159, 11119}; - static int texture_move = 0; static int roundCount = 0; static Uint32 roundTimeObjects = SDL_GetTicks(); - static Uint32 roundTimeTextures = SDL_GetTicks(); if(SDL_GetTicks() - roundTimeObjects > 30) { roundTimeObjects = SDL_GetTicks(); @@ -805,34 +805,10 @@ void CSurface::DrawTriangle(SDL_Surface* display, const DisplayRectangle& displa else roundCount++; } - if(SDL_GetTicks() - roundTimeTextures > 170) - { - roundTimeTextures = SDL_GetTicks(); - texture_move++; - if(texture_move > 14) - texture_move = 0; - } SDL_Surface* Surf_Tileset; - switch(type) - { - case MAP_GREENLAND: - default: - Surf_Tileset = global::bmpArray[global::s2->getMapObj()->getBitsPerPixel() == 8 ? TILESET_GREENLAND_8BPP : - TILESET_GREENLAND_32BPP] - .surface.get(); - break; - case MAP_WASTELAND: - Surf_Tileset = global::bmpArray[global::s2->getMapObj()->getBitsPerPixel() == 8 ? TILESET_WASTELAND_8BPP : - TILESET_WASTELAND_32BPP] - .surface.get(); - break; - case MAP_WINTERLAND: - Surf_Tileset = global::bmpArray[global::s2->getMapObj()->getBitsPerPixel() == 8 ? TILESET_WINTERLAND_8BPP : - TILESET_WINTERLAND_32BPP] - .surface.get(); - break; - } + const bool want32 = global::s2->getMapObj()->getBitsPerPixel() != 8; + Surf_Tileset = global::bmpArray[tilesetIdxForMapType(type, want32)].surface.get(); bool const isRSU = p1.y < p2.y; @@ -842,7 +818,7 @@ void CSurface::DrawTriangle(SDL_Surface* display, const DisplayRectangle& displa Point16 upper, left, right, upper2, left2, right2; auto const texture = TriangleTerrainType((isRSU ? P1.rsuTexture : P2.usdTexture) & ~0x40); // Mask out harbor bit - GetTerrainTextureCoords(type, texture, isRSU, texture_move, upper, left, right, upper2, left2, right2); + GetTerrainTextureCoords(type, texture, isRSU, upper, left, right, upper2, left2, right2); // draw the triangle // do not shade water and lava @@ -1534,3 +1510,101 @@ float CSurface::absf(float a) else return a * (-1); } + +/// Map a MapType to the 8-bit or 32-bit tileset bmpArray slot +static Uint16 tilesetIdxForMapType(MapType mapType, bool want32bit) +{ + switch(mapType) + { + case MAP_GREENLAND: + default: return want32bit ? TILESET_GREENLAND_32BPP : TILESET_GREENLAND_8BPP; + case MAP_WASTELAND: return want32bit ? TILESET_WASTELAND_32BPP : TILESET_WASTELAND_8BPP; + case MAP_WINTERLAND: return want32bit ? TILESET_WINTERLAND_32BPP : TILESET_WINTERLAND_8BPP; + } +} + +/// Apply a delta rotation to a single palette range +static void rotatePaletteRange(SDL_Palette* pal, uint8_t firstClr, int colorCount, int deltaOffset, bool moveUp) +{ + std::array rotated; + memcpy(rotated.data(), pal->colors, sizeof(SDL_Color) * 256); + for(int i = 0; i < colorCount; i++) + { + int srcIdx = moveUp ? (i - deltaOffset + colorCount) % colorCount : (i + deltaOffset) % colorCount; + rotated[firstClr + i] = pal->colors[firstClr + srcIdx]; + } + SDL_SetPaletteColors(pal, rotated.data() + firstClr, firstClr, colorCount); +} + +void CSurface::UpdatePaletteAnimations(MapType mapType) +{ + const Uint16 tilesetIdx8 = tilesetIdxForMapType(mapType, false); + auto animIt = global::paletteAnimations.find(tilesetIdx8); + if(animIt == global::paletteAnimations.end() || animIt->second.empty()) + return; + + auto& animMap = animIt->second; + auto* surf8 = global::bmpArray[tilesetIdx8].surface.get(); + if(!surf8 || !surf8->format->palette) + return; + + SDL_Palette* mapPal = + (global::s2 && global::s2->getMapObj()) ? global::s2->getMapObj()->getSurfacePalette() : nullptr; + + // 32-bit tileset surface for blitting after palette updates + const Uint16 tilesetIdx32 = tilesetIdxForMapType(mapType, true); + auto* surf32 = global::bmpArray[tilesetIdx32].surface.get(); + + const Uint32 now = SDL_GetTicks(); + bool anyUpdate = false; + + for(auto& [palAnimIdx, anim] : animMap) + { + if(!anim.isActive || anim.rate == 0) + continue; + + const int colorCount = anim.lastClr - anim.firstClr + 1; + if(colorCount <= 1) + continue; + + // Time per step: (8192/30) / rate seconds, converted to ms + const float intervalMs = (8192.0f / 30.0f) * 1000.0f / anim.rate; + + const uint32_t elapsed = now - anim.lastUpdateTime; + if(elapsed < static_cast(intervalMs)) + continue; + + int steps = static_cast(elapsed / intervalMs); + anim.lastUpdateTime += static_cast(steps * intervalMs); + + int newOffset = anim.moveUp ? (anim.currentOffset + steps) % colorCount : + (anim.currentOffset - steps + colorCount * steps) % colorCount; + int deltaOffset = (newOffset - anim.lastAppliedOffset + colorCount) % colorCount; + if(deltaOffset == 0) + continue; + anim.lastAppliedOffset = newOffset; + anim.currentOffset = newOffset; + + rotatePaletteRange(surf8->format->palette, anim.firstClr, colorCount, deltaOffset, anim.moveUp); + if(mapPal) + rotatePaletteRange(mapPal, anim.firstClr, colorCount, deltaOffset, anim.moveUp); + anyUpdate = true; + } + + if(!anyUpdate) + return; + + // Blit 8-bit tileset to 32-bit so the rotated palette takes effect in 32bpp mode + if(surf32 && surf8) + { + if(SDL_MUSTLOCK(surf32)) + SDL_LockSurface(surf32); + if(SDL_MUSTLOCK(surf8)) + SDL_LockSurface(surf8); + SDL_BlitSurface(surf8, nullptr, surf32, nullptr); + if(SDL_MUSTLOCK(surf8)) + SDL_UnlockSurface(surf8); + if(SDL_MUSTLOCK(surf32)) + SDL_UnlockSurface(surf32); + } +} diff --git a/CSurface.h b/CSurface.h index 3677579..072f2b2 100644 --- a/CSurface.h +++ b/CSurface.h @@ -59,6 +59,8 @@ class CSurface static void get_nodeVectors(bobMAP& myMap); static void update_shading(bobMAP& myMap, Position pos); + /// Update palette cycling animations for the given map type's tileset + static void UpdatePaletteAnimations(MapType mapType); private: // to decide what to draw, triangle-textures or objects and texture-borders @@ -73,7 +75,7 @@ class CSurface static void update_flatVectors(bobMAP& myMap, Position pos); // update nodeVector based on new flatVectors around it static void update_nodeVector(bobMAP& myMap, Position pos); - static void GetTerrainTextureCoords(MapType mapType, TriangleTerrainType texture, bool isRSU, int texture_move, - Point16& upper, Point16& left, Point16& right, Point16& upper2, Point16& left2, + static void GetTerrainTextureCoords(MapType mapType, TriangleTerrainType texture, bool isRSU, Point16& upper, + Point16& left, Point16& right, Point16& upper2, Point16& left2, Point16& right2); }; diff --git a/globals.cpp b/globals.cpp index da4edcf..4a3b4da 100644 --- a/globals.cpp +++ b/globals.cpp @@ -12,6 +12,8 @@ std::vector global::bmpArray(MAXBOBBMP); std::vector global::shadowArray(MAXBOBSHADOW); // array for all palettes std::vector global::palArray(MAXBOBPAL); +// Palette animation entries keyed by the bmpArray slot of the 8-bit tileset surface. +std::map> global::paletteAnimations; // the game object CGame* global::s2; diff --git a/globals.h b/globals.h index 5b4dbaf..f015e13 100644 --- a/globals.h +++ b/globals.h @@ -8,12 +8,14 @@ #include "gameData/WorldDescription.h" #include #include +#include #include class CGame; struct bobBMP; struct bobSHADOW; struct bobPAL; +struct PaletteAnimation; namespace global { // array for all pictures @@ -22,6 +24,9 @@ extern std::vector bmpArray; extern std::vector shadowArray; // array for all palettes extern std::vector palArray; +// Palette animation entries keyed by the bmpArray slot of the 8-bit tileset surface. +// Inner map keyed by the CRNG chunk's index in the tileset LBM file. +extern std::map> paletteAnimations; // the game object extern CGame* s2; // Path to game data (must not be empty!) diff --git a/include/defines.h b/include/defines.h index 5adf11c..53ca26b 100644 --- a/include/defines.h +++ b/include/defines.h @@ -91,6 +91,19 @@ struct bobPAL std::array colors; }; +// Palette animation parsed from a CRNG chunk in an LBM file +struct PaletteAnimation +{ + bool isActive = false; + bool moveUp = true; + uint16_t rate = 16384; + uint8_t firstClr = 0; + uint8_t lastClr = 0; + int currentOffset = 0; + int lastAppliedOffset = 0; + uint32_t lastUpdateTime = 0; +}; + // Structure for Bobtype 7 (Shadow-Bitmaps) struct bobSHADOW { From 7aed1d00e236123d8df3abd01b98d2d554461f7c Mon Sep 17 00:00:00 2001 From: Morgan Christiansson Date: Sun, 5 Jul 2026 21:24:30 +0200 Subject: [PATCH 2/4] Address review feedback --- CIO/CFile.cpp | 32 ++++++++++++++++++-------------- CSurface.cpp | 43 ++++++++++++++++++++----------------------- globals.cpp | 2 +- globals.h | 3 +-- include/defines.h | 15 +++++++-------- 5 files changed, 47 insertions(+), 48 deletions(-) diff --git a/CIO/CFile.cpp b/CIO/CFile.cpp index da8e80d..87cf1a2 100644 --- a/CIO/CFile.cpp +++ b/CIO/CFile.cpp @@ -348,13 +348,13 @@ bool CFile::read_lbm(FILE* fp, const boost::filesystem::path& filepath) // skip unknown data (length - 20 x 1 Byte) // fseek(fp, length-20, SEEK_CUR); + const Uint16 tilesetSlot = static_cast(bmpArray - global::bmpArray.data()); + int chunkIdx = 0; + /* READ SECOND CHUNK "CMAP" */ // chunk-identifier (4 Bytes) // search for the "CMAP" and skip other chunk-types - const Uint16 tilesetSlot = static_cast(bmpArray - global::bmpArray.data()); - int chunkIdx = 0; - while(!feof(fp)) { CHECK_READ(libendian::read(chunk_identifier.data(), 4, fp)); @@ -401,7 +401,6 @@ bool CFile::read_lbm(FILE* fp, const boost::filesystem::path& filepath) CHECK_READ(libendian::be_read_ui(&chunkLen, fp)); if(chunkLen >= 8) { - PaletteAnimation anim; uint16_t padding, rate, flags; CHECK_READ(libendian::be_read_us(&padding, fp)); CHECK_READ(libendian::be_read_us(&rate, fp)); @@ -409,16 +408,21 @@ bool CFile::read_lbm(FILE* fp, const boost::filesystem::path& filepath) uint8_t firstClr, lastClr; CHECK_READ(libendian::read(&firstClr, 1, fp)); CHECK_READ(libendian::read(&lastClr, 1, fp)); - anim.isActive = (flags & 1) != 0; - anim.moveUp = (flags & 2) != 0; - if(rate) - anim.isActive = anim.moveUp = true; - anim.rate = rate; - anim.firstClr = firstClr; - anim.lastClr = lastClr; - anim.currentOffset = 0; - anim.lastUpdateTime = SDL_GetTicks(); - global::paletteAnimations[tilesetSlot][chunkIdx] = anim; + // Only register valid animations: at least 2 colors and non-zero rate + if(rate > 0 && lastClr > firstClr) + { + PaletteAnimation anim; + anim.moveUp = (flags & 2) != 0; + if(rate) + anim.moveUp = true; + anim.rate = rate; + anim.firstClr = firstClr; + anim.lastClr = lastClr; + anim.currentOffset = 0; + anim.lastAppliedOffset = 0; + anim.lastUpdateTime = SDL_GetTicks(); + global::paletteAnimations[tilesetSlot][chunkIdx] = anim; + } if(chunkLen > 8) { uint32_t remaining = chunkLen - 8; diff --git a/CSurface.cpp b/CSurface.cpp index e8163f5..3346780 100644 --- a/CSurface.cpp +++ b/CSurface.cpp @@ -22,7 +22,7 @@ // locking. Was originally called in CGame::Init(). static bool sgeLockOff = (sge_Lock_OFF(), true); -static Uint16 tilesetIdxForMapType(MapType mapType, bool want32bit); +static unsigned tilesetIdxForMapType(MapType mapType, bool want32bit); namespace { const TerrainDesc* getTerrainDesc(const bobMAP& map, Uint8 rawTextureId) @@ -1512,7 +1512,7 @@ float CSurface::absf(float a) } /// Map a MapType to the 8-bit or 32-bit tileset bmpArray slot -static Uint16 tilesetIdxForMapType(MapType mapType, bool want32bit) +static unsigned tilesetIdxForMapType(MapType mapType, bool want32bit) { switch(mapType) { @@ -1538,8 +1538,8 @@ static void rotatePaletteRange(SDL_Palette* pal, uint8_t firstClr, int colorCoun void CSurface::UpdatePaletteAnimations(MapType mapType) { - const Uint16 tilesetIdx8 = tilesetIdxForMapType(mapType, false); - auto animIt = global::paletteAnimations.find(tilesetIdx8); + const auto tilesetIdx8 = tilesetIdxForMapType(mapType, false); + auto animIt = global::paletteAnimations.find(static_cast(tilesetIdx8)); if(animIt == global::paletteAnimations.end() || animIt->second.empty()) return; @@ -1552,15 +1552,15 @@ void CSurface::UpdatePaletteAnimations(MapType mapType) (global::s2 && global::s2->getMapObj()) ? global::s2->getMapObj()->getSurfacePalette() : nullptr; // 32-bit tileset surface for blitting after palette updates - const Uint16 tilesetIdx32 = tilesetIdxForMapType(mapType, true); - auto* surf32 = global::bmpArray[tilesetIdx32].surface.get(); + const auto tilesetIdx32 = tilesetIdxForMapType(mapType, true); + auto& surf32 = *global::bmpArray[tilesetIdx32].surface; - const Uint32 now = SDL_GetTicks(); + const unsigned now = SDL_GetTicks(); bool anyUpdate = false; for(auto& [palAnimIdx, anim] : animMap) { - if(!anim.isActive || anim.rate == 0) + if(anim.rate == 0) continue; const int colorCount = anim.lastClr - anim.firstClr + 1; @@ -1570,12 +1570,12 @@ void CSurface::UpdatePaletteAnimations(MapType mapType) // Time per step: (8192/30) / rate seconds, converted to ms const float intervalMs = (8192.0f / 30.0f) * 1000.0f / anim.rate; - const uint32_t elapsed = now - anim.lastUpdateTime; - if(elapsed < static_cast(intervalMs)) + const unsigned elapsed = now - anim.lastUpdateTime; + if(elapsed < static_cast(intervalMs)) continue; int steps = static_cast(elapsed / intervalMs); - anim.lastUpdateTime += static_cast(steps * intervalMs); + anim.lastUpdateTime += static_cast(steps * intervalMs); int newOffset = anim.moveUp ? (anim.currentOffset + steps) % colorCount : (anim.currentOffset - steps + colorCount * steps) % colorCount; @@ -1595,16 +1595,13 @@ void CSurface::UpdatePaletteAnimations(MapType mapType) return; // Blit 8-bit tileset to 32-bit so the rotated palette takes effect in 32bpp mode - if(surf32 && surf8) - { - if(SDL_MUSTLOCK(surf32)) - SDL_LockSurface(surf32); - if(SDL_MUSTLOCK(surf8)) - SDL_LockSurface(surf8); - SDL_BlitSurface(surf8, nullptr, surf32, nullptr); - if(SDL_MUSTLOCK(surf8)) - SDL_UnlockSurface(surf8); - if(SDL_MUSTLOCK(surf32)) - SDL_UnlockSurface(surf32); - } + if(SDL_MUSTLOCK(&surf32)) + SDL_LockSurface(&surf32); + if(SDL_MUSTLOCK(surf8)) + SDL_LockSurface(surf8); + SDL_BlitSurface(surf8, nullptr, &surf32, nullptr); + if(SDL_MUSTLOCK(surf8)) + SDL_UnlockSurface(surf8); + if(SDL_MUSTLOCK(&surf32)) + SDL_UnlockSurface(&surf32); } diff --git a/globals.cpp b/globals.cpp index 4a3b4da..94f7ffe 100644 --- a/globals.cpp +++ b/globals.cpp @@ -12,7 +12,7 @@ std::vector global::bmpArray(MAXBOBBMP); std::vector global::shadowArray(MAXBOBSHADOW); // array for all palettes std::vector global::palArray(MAXBOBPAL); -// Palette animation entries keyed by the bmpArray slot of the 8-bit tileset surface. +// Palette animations keyed by (bmpArray slot of 8-bit tileset) -> (CRNG chunk index). std::map> global::paletteAnimations; // the game object CGame* global::s2; diff --git a/globals.h b/globals.h index f015e13..78e9890 100644 --- a/globals.h +++ b/globals.h @@ -24,8 +24,7 @@ extern std::vector bmpArray; extern std::vector shadowArray; // array for all palettes extern std::vector palArray; -// Palette animation entries keyed by the bmpArray slot of the 8-bit tileset surface. -// Inner map keyed by the CRNG chunk's index in the tileset LBM file. +// Palette animations keyed by (bmpArray slot of 8-bit tileset) -> (CRNG chunk index). extern std::map> paletteAnimations; // the game object extern CGame* s2; diff --git a/include/defines.h b/include/defines.h index 53ca26b..b6e571e 100644 --- a/include/defines.h +++ b/include/defines.h @@ -94,14 +94,13 @@ struct bobPAL // Palette animation parsed from a CRNG chunk in an LBM file struct PaletteAnimation { - bool isActive = false; - bool moveUp = true; - uint16_t rate = 16384; - uint8_t firstClr = 0; - uint8_t lastClr = 0; - int currentOffset = 0; - int lastAppliedOffset = 0; - uint32_t lastUpdateTime = 0; + bool moveUp; + uint16_t rate; + uint8_t firstClr; + uint8_t lastClr; + int currentOffset; + int lastAppliedOffset; + unsigned lastUpdateTime; }; // Structure for Bobtype 7 (Shadow-Bitmaps) From be12fec29ecb5b56c7ceb2debd696224264ebaad Mon Sep 17 00:00:00 2001 From: Morgan Christiansson Date: Mon, 6 Jul 2026 07:06:24 +0200 Subject: [PATCH 3/4] ice floe animation --- CSurface.cpp | 37 +++++++------------------------------ 1 file changed, 7 insertions(+), 30 deletions(-) diff --git a/CSurface.cpp b/CSurface.cpp index 3346780..b5b358b 100644 --- a/CSurface.cpp +++ b/CSurface.cpp @@ -789,12 +789,6 @@ void CSurface::DrawTriangle(SDL_Surface* display, const DisplayRectangle& displa return; // for moving water, lava, objects and so on - // This is very tricky: there are ice floes in the winterland and the water under this floes is moving. - // I don't know how this works in original settlers 2 but i solved it this way: - // i texture the triangle with normal water and then draw the floe over it. To Extract the floe - // from it's surrounded water, i use this color keys below. These are the color values for the water texture. - // I wrote a special SGE-Function that uses these color keys and ignores them in the Surf_Tileset. - static std::array colorkeys = {14191, 14195, 13167, 13159, 11119}; static int roundCount = 0; static Uint32 roundTimeObjects = SDL_GetTicks(); if(SDL_GetTicks() - roundTimeObjects > 30) @@ -828,30 +822,13 @@ void CSurface::DrawTriangle(SDL_Surface* display, const DisplayRectangle& displa left.y, right.x, right.y); else { - // draw special winterland textures with moving water (ice floe textures) - if(type == MAP_WINTERLAND && (texture == TRIANGLE_TEXTURE_SNOW || texture == TRIANGLE_TEXTURE_SWAMP)) - { - sge_TexturedTrigon(display, p1.x, p1.y, p2.x, p2.y, p3.x, p3.y, Surf_Tileset, upper2.x, upper2.y, - left2.x, left2.y, right2.x, right2.y); - if(global::s2->getMapObj()->getBitsPerPixel() == 8) - sge_PreCalcFadedTexturedTrigonColorKeys(display, p1.x, p1.y, p2.x, p2.y, p3.x, p3.y, Surf_Tileset, - upper.x, upper.y, left.x, left.y, right.x, right.y, - P1.shading << 8, P2.shading << 8, P3.shading << 8, - gouData[type], colorkeys.data(), colorkeys.size()); - else - sge_FadedTexturedTrigonColorKeys(display, p1.x, p1.y, p2.x, p2.y, p3.x, p3.y, Surf_Tileset, upper.x, - upper.y, left.x, left.y, right.x, right.y, P1.i, P2.i, P3.i, - colorkeys.data(), colorkeys.size()); - } else - { - if(global::s2->getMapObj()->getBitsPerPixel() == 8) - sge_PreCalcFadedTexturedTrigon(display, p1.x, p1.y, p2.x, p2.y, p3.x, p3.y, Surf_Tileset, upper.x, - upper.y, left.x, left.y, right.x, right.y, P1.shading << 8, - P2.shading << 8, P3.shading << 8, gouData[type]); - else - sge_FadedTexturedTrigon(display, p1.x, p1.y, p2.x, p2.y, p3.x, p3.y, Surf_Tileset, upper.x, upper.y, - left.x, left.y, right.x, right.y, P1.i, P2.i, P3.i); - } + if(global::s2->getMapObj()->getBitsPerPixel() == 8) + sge_PreCalcFadedTexturedTrigon(display, p1.x, p1.y, p2.x, p2.y, p3.x, p3.y, Surf_Tileset, upper.x, + upper.y, left.x, left.y, right.x, right.y, P1.shading << 8, + P2.shading << 8, P3.shading << 8, gouData[type]); + else + sge_FadedTexturedTrigon(display, p1.x, p1.y, p2.x, p2.y, p3.x, p3.y, Surf_Tileset, upper.x, upper.y, + left.x, left.y, right.x, right.y, P1.i, P2.i, P3.i); } return; } From b95f75653da4a982f843bc6eb12d7b910add8f67 Mon Sep 17 00:00:00 2001 From: Morgan Christiansson Date: Mon, 6 Jul 2026 19:45:55 +0200 Subject: [PATCH 4/4] palette animations are per tileset not sprite --- CIO/CFile.cpp | 10 +++++++--- CSurface.cpp | 8 +++----- globals.cpp | 4 ++-- globals.h | 7 ++++--- 4 files changed, 16 insertions(+), 13 deletions(-) diff --git a/CIO/CFile.cpp b/CIO/CFile.cpp index 87cf1a2..8e6c89b 100644 --- a/CIO/CFile.cpp +++ b/CIO/CFile.cpp @@ -349,7 +349,6 @@ bool CFile::read_lbm(FILE* fp, const boost::filesystem::path& filepath) // fseek(fp, length-20, SEEK_CUR); const Uint16 tilesetSlot = static_cast(bmpArray - global::bmpArray.data()); - int chunkIdx = 0; /* READ SECOND CHUNK "CMAP" */ @@ -421,7 +420,13 @@ bool CFile::read_lbm(FILE* fp, const boost::filesystem::path& filepath) anim.currentOffset = 0; anim.lastAppliedOffset = 0; anim.lastUpdateTime = SDL_GetTicks(); - global::paletteAnimations[tilesetSlot][chunkIdx] = anim; + switch(tilesetSlot) + { + case TILESET_GREENLAND_8BPP: global::paletteAnimations[MAP_GREENLAND].push_back(anim); break; + case TILESET_WASTELAND_8BPP: global::paletteAnimations[MAP_WASTELAND].push_back(anim); break; + case TILESET_WINTERLAND_8BPP: global::paletteAnimations[MAP_WINTERLAND].push_back(anim); break; + default: break; + } } if(chunkLen > 8) { @@ -445,7 +450,6 @@ bool CFile::read_lbm(FILE* fp, const boost::filesystem::path& filepath) chunkLen++; fseek(fp, chunkLen, SEEK_CUR); } - chunkIdx++; } if(feof(fp)) return false; diff --git a/CSurface.cpp b/CSurface.cpp index b5b358b..74f0ed4 100644 --- a/CSurface.cpp +++ b/CSurface.cpp @@ -1515,12 +1515,10 @@ static void rotatePaletteRange(SDL_Palette* pal, uint8_t firstClr, int colorCoun void CSurface::UpdatePaletteAnimations(MapType mapType) { - const auto tilesetIdx8 = tilesetIdxForMapType(mapType, false); - auto animIt = global::paletteAnimations.find(static_cast(tilesetIdx8)); - if(animIt == global::paletteAnimations.end() || animIt->second.empty()) + if(global::paletteAnimations[mapType].empty()) return; - auto& animMap = animIt->second; + const auto tilesetIdx8 = tilesetIdxForMapType(mapType, false); auto* surf8 = global::bmpArray[tilesetIdx8].surface.get(); if(!surf8 || !surf8->format->palette) return; @@ -1535,7 +1533,7 @@ void CSurface::UpdatePaletteAnimations(MapType mapType) const unsigned now = SDL_GetTicks(); bool anyUpdate = false; - for(auto& [palAnimIdx, anim] : animMap) + for(auto& anim : global::paletteAnimations[mapType]) { if(anim.rate == 0) continue; diff --git a/globals.cpp b/globals.cpp index 94f7ffe..2000b73 100644 --- a/globals.cpp +++ b/globals.cpp @@ -12,8 +12,8 @@ std::vector global::bmpArray(MAXBOBBMP); std::vector global::shadowArray(MAXBOBSHADOW); // array for all palettes std::vector global::palArray(MAXBOBPAL); -// Palette animations keyed by (bmpArray slot of 8-bit tileset) -> (CRNG chunk index). -std::map> global::paletteAnimations; +// Palette animations per tileset, indexed by MapType (0=Greenland, 1=Wasteland, 2=Winterland). +std::array, 3> global::paletteAnimations; // the game object CGame* global::s2; diff --git a/globals.h b/globals.h index 78e9890..695cadb 100644 --- a/globals.h +++ b/globals.h @@ -8,7 +8,7 @@ #include "gameData/WorldDescription.h" #include #include -#include +#include #include class CGame; @@ -24,8 +24,9 @@ extern std::vector bmpArray; extern std::vector shadowArray; // array for all palettes extern std::vector palArray; -// Palette animations keyed by (bmpArray slot of 8-bit tileset) -> (CRNG chunk index). -extern std::map> paletteAnimations; +// Palette animations per tileset, indexed by MapType (0=Greenland, 1=Wasteland, 2=Winterland). +// Each entry is a list of CRNG chunk animations applied to the whole tileset palette. +extern std::array, 3> paletteAnimations; // the game object extern CGame* s2; // Path to game data (must not be empty!)