Skip to content

Commit f3a57cc

Browse files
Palette cycling animation
1 parent 517ffa5 commit f3a57cc

8 files changed

Lines changed: 208 additions & 61 deletions

File tree

CGame_Render.cpp

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,9 +50,11 @@ void CGame::Render()
5050
return;
5151
}
5252

53-
// render the map if active
53+
// update palette cycling animations before rendering the map
5454
if(MapObj && MapObj->isActive())
5555
{
56+
if(MapObj->getMap())
57+
CSurface::UpdatePaletteAnimations(MapObj->getMap()->type);
5658
CSurface::Draw(Surf_Display, MapObj->getSurface(), 0, 0);
5759
std::array<char, 100> textBuffer;
5860
// text for x and y of vertex (shown in upper left corner)

CIO/CFile.cpp

Lines changed: 43 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -352,6 +352,9 @@ bool CFile::read_lbm(FILE* fp, const boost::filesystem::path& filepath)
352352

353353
// chunk-identifier (4 Bytes)
354354
// search for the "CMAP" and skip other chunk-types
355+
const Uint16 tilesetSlot = static_cast<Uint16>(bmpArray - global::bmpArray.data());
356+
int chunkIdx = 0;
357+
355358
while(!feof(fp))
356359
{
357360
CHECK_READ(libendian::read(chunk_identifier.data(), 4, fp));
@@ -392,14 +395,53 @@ bool CFile::read_lbm(FILE* fp, const boost::filesystem::path& filepath)
392395

393396
if(strcmp(chunk_identifier.data(), "BODY") == 0)
394397
break;
395-
else
398+
else if(strcmp(chunk_identifier.data(), "CRNG") == 0)
399+
{
400+
Uint32 chunkLen;
401+
CHECK_READ(libendian::be_read_ui(&chunkLen, fp));
402+
if(chunkLen >= 8)
403+
{
404+
PaletteAnimation anim;
405+
uint16_t padding, rate, flags;
406+
CHECK_READ(libendian::be_read_us(&padding, fp));
407+
CHECK_READ(libendian::be_read_us(&rate, fp));
408+
CHECK_READ(libendian::be_read_us(&flags, fp));
409+
uint8_t firstClr, lastClr;
410+
CHECK_READ(libendian::read(&firstClr, 1, fp));
411+
CHECK_READ(libendian::read(&lastClr, 1, fp));
412+
anim.isActive = (flags & 1) != 0;
413+
anim.moveUp = (flags & 2) != 0;
414+
if(rate)
415+
anim.isActive = anim.moveUp = true;
416+
anim.rate = rate;
417+
anim.firstClr = firstClr;
418+
anim.lastClr = lastClr;
419+
anim.currentOffset = 0;
420+
anim.lastUpdateTime = SDL_GetTicks();
421+
global::paletteAnimations[tilesetSlot][chunkIdx] = anim;
422+
if(chunkLen > 8)
423+
{
424+
uint32_t remaining = chunkLen - 8;
425+
if(remaining & 1)
426+
remaining++;
427+
fseek(fp, remaining, SEEK_CUR);
428+
} else if(chunkLen & 1)
429+
fseek(fp, 1, SEEK_CUR);
430+
} else
431+
{
432+
if(chunkLen & 1)
433+
chunkLen++;
434+
fseek(fp, chunkLen, SEEK_CUR);
435+
}
436+
} else
396437
{
397438
Uint32 chunkLen;
398439
CHECK_READ(libendian::be_read_ui(&chunkLen, fp));
399440
if(chunkLen & 1)
400441
chunkLen++;
401442
fseek(fp, chunkLen, SEEK_CUR);
402443
}
444+
chunkIdx++;
403445
}
404446
if(feof(fp))
405447
return false;

CMap.h

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -150,6 +150,13 @@ class CMap
150150
render();
151151
return Surf_Map.get();
152152
}
153+
/// Get map surface palette without rendering (nullptr if not 8-bit or not created yet)
154+
SDL_Palette* getSurfacePalette() const
155+
{
156+
if(!Surf_Map || Surf_Map->format->BytesPerPixel != 1)
157+
return nullptr;
158+
return Surf_Map->format->palette;
159+
}
153160
DisplayRectangle getDisplayRect() { return displayRect; }
154161
void setDisplayRect(const DisplayRectangle& displayRect) { this->displayRect = displayRect; }
155162
auto& getPlayerHQx() { return PlayerHQx; }

CSurface.cpp

Lines changed: 131 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -15,12 +15,15 @@
1515
#include <algorithm>
1616
#include <cassert>
1717
#include <cmath>
18+
#include <cstring>
1819

1920
// Disable SGE's internal surface locking once at startup; terrain drawing is
2021
// the only remaining consumer of SGE functions and the caller already handles
2122
// locking. Was originally called in CGame::Init().
2223
static bool sgeLockOff = (sge_Lock_OFF(), true);
2324

25+
static Uint16 tilesetIdxForMapType(MapType mapType, bool want32bit);
26+
2427
namespace {
2528
const TerrainDesc* getTerrainDesc(const bobMAP& map, Uint8 rawTextureId)
2629
{
@@ -623,11 +626,10 @@ bool GetAdjustedPoints(const DisplayRectangle& displayRect, const bobMAP& myMap,
623626
}
624627
} // namespace
625628

626-
void CSurface::GetTerrainTextureCoords(MapType mapType, TriangleTerrainType texture, bool isRSU, int texture_move,
627-
Point16& upper, Point16& left, Point16& right, Point16& upper2, Point16& left2,
628-
Point16& right2)
629+
void CSurface::GetTerrainTextureCoords(MapType mapType, TriangleTerrainType texture, bool isRSU, Point16& upper,
630+
Point16& left, Point16& right, Point16& upper2, Point16& left2, Point16& right2)
629631
{
630-
const auto animOffset = Point16(-texture_move, texture_move);
632+
// Offset-shifting animation replaced by palette cycling animation (UpdatePaletteAnimations)
631633
switch(texture)
632634
{
633635
// 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
657659
{
658660
if(isRSU)
659661
{
660-
upper2 = Point16(231, 61) + animOffset;
661-
left2 = Point16(207, 62) + animOffset;
662-
right2 = Point16(223, 78) + animOffset;
662+
upper2 = Point16(231, 61);
663+
left2 = Point16(207, 62);
664+
right2 = Point16(223, 78);
663665
} else
664666
{
665-
upper2 = Point16(224, 79) + animOffset;
666-
left2 = Point16(232, 62) + animOffset;
667-
right2 = Point16(245, 76) + animOffset;
667+
upper2 = Point16(224, 79);
668+
left2 = Point16(232, 62);
669+
right2 = Point16(245, 76);
668670
}
669671
}
670672
break;
@@ -676,14 +678,14 @@ void CSurface::GetTerrainTextureCoords(MapType mapType, TriangleTerrainType text
676678
{
677679
if(isRSU)
678680
{
679-
upper2 = Point16(231, 61) + animOffset;
680-
left2 = Point16(207, 62) + animOffset;
681-
right2 = Point16(223, 78) + animOffset;
681+
upper2 = Point16(231, 61);
682+
left2 = Point16(207, 62);
683+
right2 = Point16(223, 78);
682684
} else
683685
{
684-
upper2 = Point16(224, 79) + animOffset;
685-
left2 = Point16(232, 62) + animOffset;
686-
right2 = Point16(245, 76) + animOffset;
686+
upper2 = Point16(224, 79);
687+
left2 = Point16(232, 62);
688+
right2 = Point16(245, 76);
687689
}
688690
}
689691
break;
@@ -700,14 +702,14 @@ void CSurface::GetTerrainTextureCoords(MapType mapType, TriangleTerrainType text
700702
case TRIANGLE_TEXTURE_WATER__:
701703
if(isRSU)
702704
{
703-
upper = Point16(231, 61) + animOffset;
704-
left = Point16(207, 62) + animOffset;
705-
right = Point16(223, 78) + animOffset;
705+
upper = Point16(231, 61);
706+
left = Point16(207, 62);
707+
right = Point16(223, 78);
706708
} else
707709
{
708-
upper = Point16(224, 79) + animOffset;
709-
left = Point16(232, 62) + animOffset;
710-
right = Point16(245, 76) + animOffset;
710+
upper = Point16(224, 79);
711+
left = Point16(232, 62);
712+
right = Point16(245, 76);
711713
}
712714
break;
713715
case TRIANGLE_TEXTURE_MEADOW1:
@@ -753,14 +755,14 @@ void CSurface::GetTerrainTextureCoords(MapType mapType, TriangleTerrainType text
753755
case TRIANGLE_TEXTURE_LAVA:
754756
if(isRSU)
755757
{
756-
upper = Point16(231, 117) + animOffset;
757-
left = Point16(207, 118) + animOffset;
758-
right = Point16(223, 134) + animOffset;
758+
upper = Point16(231, 117);
759+
left = Point16(207, 118);
760+
right = Point16(223, 134);
759761
} else
760762
{
761-
upper = Point16(224, 135) + animOffset;
762-
left = Point16(232, 118) + animOffset;
763-
right = Point16(245, 132) + animOffset;
763+
upper = Point16(224, 135);
764+
left = Point16(232, 118);
765+
right = Point16(245, 132);
764766
}
765767
break;
766768
case TRIANGLE_TEXTURE_MINING_MEADOW:
@@ -793,10 +795,8 @@ void CSurface::DrawTriangle(SDL_Surface* display, const DisplayRectangle& displa
793795
// from it's surrounded water, i use this color keys below. These are the color values for the water texture.
794796
// I wrote a special SGE-Function that uses these color keys and ignores them in the Surf_Tileset.
795797
static std::array<Uint32, 5> colorkeys = {14191, 14195, 13167, 13159, 11119};
796-
static int texture_move = 0;
797798
static int roundCount = 0;
798799
static Uint32 roundTimeObjects = SDL_GetTicks();
799-
static Uint32 roundTimeTextures = SDL_GetTicks();
800800
if(SDL_GetTicks() - roundTimeObjects > 30)
801801
{
802802
roundTimeObjects = SDL_GetTicks();
@@ -805,34 +805,10 @@ void CSurface::DrawTriangle(SDL_Surface* display, const DisplayRectangle& displa
805805
else
806806
roundCount++;
807807
}
808-
if(SDL_GetTicks() - roundTimeTextures > 170)
809-
{
810-
roundTimeTextures = SDL_GetTicks();
811-
texture_move++;
812-
if(texture_move > 14)
813-
texture_move = 0;
814-
}
815808

816809
SDL_Surface* Surf_Tileset;
817-
switch(type)
818-
{
819-
case MAP_GREENLAND:
820-
default:
821-
Surf_Tileset = global::bmpArray[global::s2->getMapObj()->getBitsPerPixel() == 8 ? TILESET_GREENLAND_8BPP :
822-
TILESET_GREENLAND_32BPP]
823-
.surface.get();
824-
break;
825-
case MAP_WASTELAND:
826-
Surf_Tileset = global::bmpArray[global::s2->getMapObj()->getBitsPerPixel() == 8 ? TILESET_WASTELAND_8BPP :
827-
TILESET_WASTELAND_32BPP]
828-
.surface.get();
829-
break;
830-
case MAP_WINTERLAND:
831-
Surf_Tileset = global::bmpArray[global::s2->getMapObj()->getBitsPerPixel() == 8 ? TILESET_WINTERLAND_8BPP :
832-
TILESET_WINTERLAND_32BPP]
833-
.surface.get();
834-
break;
835-
}
810+
const bool want32 = global::s2->getMapObj()->getBitsPerPixel() != 8;
811+
Surf_Tileset = global::bmpArray[tilesetIdxForMapType(type, want32)].surface.get();
836812

837813
bool const isRSU = p1.y < p2.y;
838814

@@ -842,7 +818,7 @@ void CSurface::DrawTriangle(SDL_Surface* display, const DisplayRectangle& displa
842818
Point16 upper, left, right, upper2, left2, right2;
843819
auto const texture =
844820
TriangleTerrainType((isRSU ? P1.rsuTexture : P2.usdTexture) & ~0x40); // Mask out harbor bit
845-
GetTerrainTextureCoords(type, texture, isRSU, texture_move, upper, left, right, upper2, left2, right2);
821+
GetTerrainTextureCoords(type, texture, isRSU, upper, left, right, upper2, left2, right2);
846822

847823
// draw the triangle
848824
// do not shade water and lava
@@ -1534,3 +1510,101 @@ float CSurface::absf(float a)
15341510
else
15351511
return a * (-1);
15361512
}
1513+
1514+
/// Map a MapType to the 8-bit or 32-bit tileset bmpArray slot
1515+
static Uint16 tilesetIdxForMapType(MapType mapType, bool want32bit)
1516+
{
1517+
switch(mapType)
1518+
{
1519+
case MAP_GREENLAND:
1520+
default: return want32bit ? TILESET_GREENLAND_32BPP : TILESET_GREENLAND_8BPP;
1521+
case MAP_WASTELAND: return want32bit ? TILESET_WASTELAND_32BPP : TILESET_WASTELAND_8BPP;
1522+
case MAP_WINTERLAND: return want32bit ? TILESET_WINTERLAND_32BPP : TILESET_WINTERLAND_8BPP;
1523+
}
1524+
}
1525+
1526+
/// Apply a delta rotation to a single palette range
1527+
static void rotatePaletteRange(SDL_Palette* pal, uint8_t firstClr, int colorCount, int deltaOffset, bool moveUp)
1528+
{
1529+
std::array<SDL_Color, 256> rotated;
1530+
memcpy(rotated.data(), pal->colors, sizeof(SDL_Color) * 256);
1531+
for(int i = 0; i < colorCount; i++)
1532+
{
1533+
int srcIdx = moveUp ? (i - deltaOffset + colorCount) % colorCount : (i + deltaOffset) % colorCount;
1534+
rotated[firstClr + i] = pal->colors[firstClr + srcIdx];
1535+
}
1536+
SDL_SetPaletteColors(pal, rotated.data() + firstClr, firstClr, colorCount);
1537+
}
1538+
1539+
void CSurface::UpdatePaletteAnimations(MapType mapType)
1540+
{
1541+
const Uint16 tilesetIdx8 = tilesetIdxForMapType(mapType, false);
1542+
auto animIt = global::paletteAnimations.find(tilesetIdx8);
1543+
if(animIt == global::paletteAnimations.end() || animIt->second.empty())
1544+
return;
1545+
1546+
auto& animMap = animIt->second;
1547+
auto* surf8 = global::bmpArray[tilesetIdx8].surface.get();
1548+
if(!surf8 || !surf8->format->palette)
1549+
return;
1550+
1551+
SDL_Palette* mapPal =
1552+
(global::s2 && global::s2->getMapObj()) ? global::s2->getMapObj()->getSurfacePalette() : nullptr;
1553+
1554+
// 32-bit tileset surface for blitting after palette updates
1555+
const Uint16 tilesetIdx32 = tilesetIdxForMapType(mapType, true);
1556+
auto* surf32 = global::bmpArray[tilesetIdx32].surface.get();
1557+
1558+
const Uint32 now = SDL_GetTicks();
1559+
bool anyUpdate = false;
1560+
1561+
for(auto& [palAnimIdx, anim] : animMap)
1562+
{
1563+
if(!anim.isActive || anim.rate == 0)
1564+
continue;
1565+
1566+
const int colorCount = anim.lastClr - anim.firstClr + 1;
1567+
if(colorCount <= 1)
1568+
continue;
1569+
1570+
// Time per step: (8192/30) / rate seconds, converted to ms
1571+
const float intervalMs = (8192.0f / 30.0f) * 1000.0f / anim.rate;
1572+
1573+
const uint32_t elapsed = now - anim.lastUpdateTime;
1574+
if(elapsed < static_cast<uint32_t>(intervalMs))
1575+
continue;
1576+
1577+
int steps = static_cast<int>(elapsed / intervalMs);
1578+
anim.lastUpdateTime += static_cast<uint32_t>(steps * intervalMs);
1579+
1580+
int newOffset = anim.moveUp ? (anim.currentOffset + steps) % colorCount :
1581+
(anim.currentOffset - steps + colorCount * steps) % colorCount;
1582+
int deltaOffset = (newOffset - anim.lastAppliedOffset + colorCount) % colorCount;
1583+
if(deltaOffset == 0)
1584+
continue;
1585+
anim.lastAppliedOffset = newOffset;
1586+
anim.currentOffset = newOffset;
1587+
1588+
rotatePaletteRange(surf8->format->palette, anim.firstClr, colorCount, deltaOffset, anim.moveUp);
1589+
if(mapPal)
1590+
rotatePaletteRange(mapPal, anim.firstClr, colorCount, deltaOffset, anim.moveUp);
1591+
anyUpdate = true;
1592+
}
1593+
1594+
if(!anyUpdate)
1595+
return;
1596+
1597+
// Blit 8-bit tileset to 32-bit so the rotated palette takes effect in 32bpp mode
1598+
if(surf32 && surf8)
1599+
{
1600+
if(SDL_MUSTLOCK(surf32))
1601+
SDL_LockSurface(surf32);
1602+
if(SDL_MUSTLOCK(surf8))
1603+
SDL_LockSurface(surf8);
1604+
SDL_BlitSurface(surf8, nullptr, surf32, nullptr);
1605+
if(SDL_MUSTLOCK(surf8))
1606+
SDL_UnlockSurface(surf8);
1607+
if(SDL_MUSTLOCK(surf32))
1608+
SDL_UnlockSurface(surf32);
1609+
}
1610+
}

CSurface.h

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,8 @@ class CSurface
5959

6060
static void get_nodeVectors(bobMAP& myMap);
6161
static void update_shading(bobMAP& myMap, Position pos);
62+
/// Update palette cycling animations for the given map type's tileset
63+
static void UpdatePaletteAnimations(MapType mapType);
6264

6365
private:
6466
// to decide what to draw, triangle-textures or objects and texture-borders
@@ -73,7 +75,7 @@ class CSurface
7375
static void update_flatVectors(bobMAP& myMap, Position pos);
7476
// update nodeVector based on new flatVectors around it
7577
static void update_nodeVector(bobMAP& myMap, Position pos);
76-
static void GetTerrainTextureCoords(MapType mapType, TriangleTerrainType texture, bool isRSU, int texture_move,
77-
Point16& upper, Point16& left, Point16& right, Point16& upper2, Point16& left2,
78+
static void GetTerrainTextureCoords(MapType mapType, TriangleTerrainType texture, bool isRSU, Point16& upper,
79+
Point16& left, Point16& right, Point16& upper2, Point16& left2,
7880
Point16& right2);
7981
};

globals.cpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@ std::vector<bobBMP> global::bmpArray(MAXBOBBMP);
1212
std::vector<bobSHADOW> global::shadowArray(MAXBOBSHADOW);
1313
// array for all palettes
1414
std::vector<bobPAL> global::palArray(MAXBOBPAL);
15+
// Palette animation entries keyed by the bmpArray slot of the 8-bit tileset surface.
16+
std::map<Uint16, std::map<int, PaletteAnimation>> global::paletteAnimations;
1517
// the game object
1618
CGame* global::s2;
1719

0 commit comments

Comments
 (0)