From 2ca6406f9d167aed42dd50301d350273b7258b11 Mon Sep 17 00:00:00 2001 From: Morgan Christiansson Date: Mon, 29 Jun 2026 20:58:12 +0200 Subject: [PATCH] Palette cycling animation --- CGame_Render.cpp | 4 +- CIO/CFile.cpp | 283 ++++++++++++++++++++++++++++------------------ CMap.h | 7 ++ CSurface.cpp | 158 +++++++++++++++++++------- CSurface.h | 6 +- globals.cpp | 2 + globals.h | 3 + include/defines.h | 13 +++ 8 files changed, 326 insertions(+), 150 deletions(-) diff --git a/CGame_Render.cpp b/CGame_Render.cpp index 3fc316c..55381c2 100644 --- a/CGame_Render.cpp +++ b/CGame_Render.cpp @@ -53,9 +53,11 @@ void CGame::Render() return; } - // render the map if active + // update palette cycling animations before rendering the map if(MapObj && MapObj->isActive()) { + if(MapObj->getMap()) + CSurface::UpdatePaletteAnimations(MapObj->getMap()->type); CSurface::Draw(Surf_Display, MapObj->getSurface(), 0, 0); std::array textBuffer; // text for x and y of vertex (shown in upper left corner) diff --git a/CIO/CFile.cpp b/CIO/CFile.cpp index 023d89f..c15fc41 100644 --- a/CIO/CFile.cpp +++ b/CIO/CFile.cpp @@ -350,143 +350,208 @@ bool CFile::read_lbm(FILE* fp, const boost::filesystem::path& filepath) /* READ SECOND CHUNK "CMAP" */ // chunk-identifier (4 Bytes) - // search for the "CMAP" and skip other chunk-types - while(!feof(fp)) + // search for the "CMAP" and skip/parse other chunk-types { - CHECK_READ(libendian::read(chunk_identifier.data(), 4, fp)); + // Local collection of CRNG (palette animation) chunks + std::vector crngData; - if(strcmp(chunk_identifier.data(), "CMAP") == 0) - break; - else - { + // Helper to parse a CRNG chunk (caller already read chunk ID, we read length+data) + auto readCRNG = [&]() -> bool { Uint32 chunkLen; - CHECK_READ(libendian::be_read_ui(&chunkLen, fp)); - if(chunkLen & 1) - chunkLen++; - fseek(fp, chunkLen, SEEK_CUR); - } - } - if(feof(fp)) - return false; - // length of data block - CHECK_READ(libendian::be_read_ui(&length, fp)); - // must be 768 (RGB = 3 Byte x 256 Colors) - if(length != 3u * 256u) - return false; - // palette - for(auto& color : colors) - { - CHECK_READ(libendian::read(&color.r, 1, fp)); - CHECK_READ(libendian::read(&color.g, 1, fp)); - CHECK_READ(libendian::read(&color.b, 1, fp)); - } - - /* READ THIRD CHUNK "BODY" */ + if(!libendian::be_read_ui(&chunkLen, fp)) + return false; + if(chunkLen >= 8) + { + PaletteAnimData anim; + uint16_t padding, rate, flags; + if(!libendian::be_read_us(&padding, fp)) + return false; + if(!libendian::be_read_us(&rate, fp)) + return false; + if(!libendian::be_read_us(&flags, fp)) + return false; + uint8_t firstClr, lastClr; + if(!libendian::read(&firstClr, 1, fp)) + return false; + if(!libendian::read(&lastClr, 1, fp)) + return false; + 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(); + crngData.push_back(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); + } + return true; + }; - // chunk-identifier (4 Bytes) - // search for the "BODY" and skip other chunk-types - while(!feof(fp)) - { - CHECK_READ(libendian::read(chunk_identifier.data(), 4, fp)); + while(!feof(fp)) + { + CHECK_READ(libendian::read(chunk_identifier.data(), 4, fp)); - if(strcmp(chunk_identifier.data(), "BODY") == 0) - break; - else + if(strcmp(chunk_identifier.data(), "CMAP") == 0) + break; + else if(strcmp(chunk_identifier.data(), "CRNG") == 0) + { + CHECK_READ(readCRNG()); + } else + { + Uint32 chunkLen; + CHECK_READ(libendian::be_read_ui(&chunkLen, fp)); + if(chunkLen & 1) + chunkLen++; + fseek(fp, chunkLen, SEEK_CUR); + } + } + if(feof(fp)) + return false; + // length of data block + CHECK_READ(libendian::be_read_ui(&length, fp)); + // must be 768 (RGB = 3 Byte x 256 Colors) + if(length != 3u * 256u) + return false; + // palette + for(auto& color : colors) { - Uint32 chunkLen; - CHECK_READ(libendian::be_read_ui(&chunkLen, fp)); - if(chunkLen & 1) - chunkLen++; - fseek(fp, chunkLen, SEEK_CUR); + CHECK_READ(libendian::read(&color.r, 1, fp)); + CHECK_READ(libendian::read(&color.g, 1, fp)); + CHECK_READ(libendian::read(&color.b, 1, fp)); } - } - if(feof(fp)) - return false; - // length of data block - CHECK_READ(libendian::be_read_ui(&length, fp)); - // now we are ready to read the picture lines and fill the surface, so lets create one - if(!(bmpArray->surface = makePalSurface(bmpArray->w, bmpArray->h, colors))) - { - std::cerr << "Failed to create surface: " << SDL_GetError() << std::endl; - return false; - } + /* READ THIRD CHUNK "BODY" */ - if(compression_flag == 0) - { - // picture uncompressed - // main loop for reading picture lines - for(Position pos{0, 0}; pos.y < bmpArray->h; pos.y++) + // chunk-identifier (4 Bytes) + // search for the "BODY" and skip/parse other chunk-types + while(!feof(fp)) { - // loop for reading pixels of the actual picture line - for(pos.x = 0; pos.x < bmpArray->w; pos.x++) + CHECK_READ(libendian::read(chunk_identifier.data(), 4, fp)); + + if(strcmp(chunk_identifier.data(), "BODY") == 0) + break; + else if(strcmp(chunk_identifier.data(), "CRNG") == 0) { - // read color value (1 Byte) - CHECK_READ(libendian::read(&color_value, 1, fp)); - // draw - CSurface::DrawPixel_Color(bmpArray->surface.get(), pos, (Uint32)color_value); + CHECK_READ(readCRNG()); + } else + { + Uint32 chunkLen; + CHECK_READ(libendian::be_read_ui(&chunkLen, fp)); + if(chunkLen & 1) + chunkLen++; + fseek(fp, chunkLen, SEEK_CUR); } } + if(feof(fp)) + return false; + // length of data block + CHECK_READ(libendian::be_read_ui(&length, fp)); - } else if(compression_flag == 1) - { - // picture compressed + // now we are ready to read the picture lines and fill the surface, so lets create one + if(!(bmpArray->surface = makePalSurface(bmpArray->w, bmpArray->h, colors))) + { + std::cerr << "Failed to create surface: " << SDL_GetError() << std::endl; + return false; + } - // main loop for reading picture lines - for(Position pos{0, 0}; pos.y < bmpArray->h; pos.y++) + if(compression_flag == 0) { - // loop for reading pixels of the actual picture line - //(cause of a kind of RLE-Compression we cannot read the pixels sequentially) - //'x' will be incremented WITHIN the loop - for(pos.x = 0; pos.x < bmpArray->w;) + // picture uncompressed + // main loop for reading picture lines + for(Position pos{0, 0}; pos.y < bmpArray->h; pos.y++) { - // read compression type - CHECK_READ(libendian::read(&ctype, 1, fp)); + // loop for reading pixels of the actual picture line + for(pos.x = 0; pos.x < bmpArray->w; pos.x++) + { + // read color value (1 Byte) + CHECK_READ(libendian::read(&color_value, 1, fp)); + // draw + CSurface::DrawPixel_Color(bmpArray->surface.get(), pos, (Uint32)color_value); + } + } - if(ctype >= 0) + } else if(compression_flag == 1) + { + // picture compressed + + // main loop for reading picture lines + for(Position pos{0, 0}; pos.y < bmpArray->h; pos.y++) + { + // loop for reading pixels of the actual picture line + //(cause of a kind of RLE-Compression we cannot read the pixels sequentially) + //'x' will be incremented WITHIN the loop + for(pos.x = 0; pos.x < bmpArray->w;) { - // following 'ctype + 1' pixels are uncompressed - for(int k = 0; k < ctype + 1; k++, pos.x++) + // read compression type + CHECK_READ(libendian::read(&ctype, 1, fp)); + + if(ctype >= 0) { - // read color value (1 Byte) + // following 'ctype + 1' pixels are uncompressed + for(int k = 0; k < ctype + 1; k++, pos.x++) + { + // read color value (1 Byte) + CHECK_READ(libendian::read(&color_value, 1, fp)); + // draw + CSurface::DrawPixel_Color(bmpArray->surface.get(), pos, (Uint32)color_value); + } + } else if(ctype >= -127) + { + // draw the following byte '-ctype + 1' times to the surface CHECK_READ(libendian::read(&color_value, 1, fp)); - // draw - CSurface::DrawPixel_Color(bmpArray->surface.get(), pos, (Uint32)color_value); - } - } else if(ctype >= -127) - { - // draw the following byte '-ctype + 1' times to the surface - CHECK_READ(libendian::read(&color_value, 1, fp)); - for(int k = 0; k < -ctype + 1; k++, pos.x++) - CSurface::DrawPixel_Color(bmpArray->surface.get(), pos, (Uint32)color_value); - } else // if (ctype == -128) - { - // ignore + for(int k = 0; k < -ctype + 1; k++, pos.x++) + CSurface::DrawPixel_Color(bmpArray->surface.get(), pos, (Uint32)color_value); + } else // if (ctype == -128) + { + // ignore + } } } - } - } else - return false; - - // if this is a texture file, we need a secondary 32-bit surface for SGE and we set a color key at both surfaces - if(filepath.filename() == "TEX5.LBM" || filepath.filename() == "TEX6.LBM" || filepath.filename() == "TEX7.LBM" - || filepath.filename() == "TEXTUR_0.LBM" || filepath.filename() == "TEXTUR_3.LBM") - { - SDL_SetColorKey(bmpArray->surface.get(), SDL_TRUE, SDL_MapRGB(bmpArray->surface->format, 0, 0, 0)); + } else + return false; - bmpArray++; - if((bmpArray->surface = makeRGBSurface((bmpArray - 1)->w, (bmpArray - 1)->h))) + // if this is a texture file, we need a secondary 32-bit surface for SGE and we set a color key at both surfaces + if(filepath.filename() == "TEX5.LBM" || filepath.filename() == "TEX6.LBM" || filepath.filename() == "TEX7.LBM" + || filepath.filename() == "TEXTUR_0.LBM" || filepath.filename() == "TEXTUR_3.LBM") { + // Store palette animation data for the 8-bit tileset + // bmpArray currently points to the 8-bit slot + const Uint16 bmpIdx8 = static_cast(bmpArray - global::bmpArray.data()); + if(global::tilesetAnimData.size() <= bmpIdx8) + global::tilesetAnimData.resize(bmpIdx8 + 1); + global::tilesetAnimData[bmpIdx8] = std::move(crngData); + SDL_SetColorKey(bmpArray->surface.get(), SDL_TRUE, SDL_MapRGB(bmpArray->surface->format, 0, 0, 0)); - CSurface::Draw(bmpArray->surface, (bmpArray - 1)->surface); - } else - bmpArray--; - } - // we are finished, the surface is filled - // increment bmpArray for the next picture - bmpArray++; + bmpArray++; + if((bmpArray->surface = makeRGBSurface((bmpArray - 1)->w, (bmpArray - 1)->h))) + { + SDL_SetColorKey(bmpArray->surface.get(), SDL_TRUE, SDL_MapRGB(bmpArray->surface->format, 0, 0, 0)); + CSurface::Draw(bmpArray->surface, (bmpArray - 1)->surface); + } else + bmpArray--; + } + + // we are finished, the surface is filled + // increment bmpArray for the next picture + bmpArray++; + } // end of scope for crngData return true; } diff --git a/CMap.h b/CMap.h index 431f2c3..9e8321a 100644 --- a/CMap.h +++ b/CMap.h @@ -150,6 +150,13 @@ class CMap render(); return Surf_Map.get(); } + /// Get map surface palette without rendering (nullptr if 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 2602c9f..cfaa862 100644 --- a/CSurface.cpp +++ b/CSurface.cpp @@ -15,6 +15,7 @@ #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 @@ -644,11 +645,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" @@ -678,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; @@ -697,14 +697,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; @@ -721,14 +721,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: @@ -774,14 +774,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: @@ -814,10 +814,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(); @@ -826,13 +824,6 @@ 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) @@ -863,7 +854,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 @@ -1555,3 +1546,94 @@ float CSurface::absf(float a) else return a * (-1); } + +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); + if(tilesetIdx8 >= global::tilesetAnimData.size() || global::tilesetAnimData[tilesetIdx8].empty()) + return; + + auto& animData = global::tilesetAnimData[tilesetIdx8]; + 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; + + const Uint32 now = SDL_GetTicks(); + bool anyUpdate = false; + const float intervalMs = 630.0f * 5.0f / 16.0f; + + for(auto& anim : animData) + { + if(!anim.isActive) + continue; + + const int colorCount = anim.lastClr - anim.firstClr + 1; + if(colorCount <= 1) + continue; + + 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; + + const Uint16 tilesetIdx32 = tilesetIdxForMapType(mapType, true); + auto* surf32 = global::bmpArray[tilesetIdx32].surface.get(); + 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 6b32021..fe8e049 100644 --- a/CSurface.h +++ b/CSurface.h @@ -62,6 +62,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 @@ -76,7 +78,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..d6a8974 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 data per tileset (indexed by 8-bit tileset bmpArray index) +std::vector> global::tilesetAnimData; // the game object CGame* global::s2; diff --git a/globals.h b/globals.h index 5b4dbaf..169b25d 100644 --- a/globals.h +++ b/globals.h @@ -14,6 +14,7 @@ class CGame; struct bobBMP; struct bobSHADOW; struct bobPAL; +struct PaletteAnimData; namespace global { // array for all pictures @@ -22,6 +23,8 @@ extern std::vector bmpArray; extern std::vector shadowArray; // array for all palettes extern std::vector palArray; +// Palette animation data per tileset (indexed by the 8-bit tileset's bmpArray index) +extern std::vector> tilesetAnimData; // 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..0b8d96b 100644 --- a/include/defines.h +++ b/include/defines.h @@ -91,6 +91,19 @@ struct bobPAL std::array colors; }; +// Palette animation data parsed from CRNG chunks in LBM files +struct PaletteAnimData +{ + 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 {