[Scummvm-git-logs] scummvm master -> 08e53baf6090e3d794da607b2512ba529e88fd39
alexbevi
noreply at scummvm.org
Wed Sep 2 11:18:21 UTC 2026
This automated email contains information about 2 new commits which have been
pushed to the 'scummvm' repo located at https://api.github.com/repos/scummvm/scummvm .
Summary:
d1c4ec6f49 HARVESTER: Preserve NPC animation anchors
08e53baf60 ASYLUM: Implement cinematic matte palette transitions
Commit: d1c4ec6f49e9ae35081c7943a6ea417c96b33c7b
https://github.com/scummvm/scummvm/commit/d1c4ec6f49e9ae35081c7943a6ea417c96b33c7b
Author: Alex Bevilacqua (alex at alexbevi.com)
Date: 2026-09-02T07:02:45-04:00
Commit Message:
HARVESTER: Preserve NPC animation anchors
NPCs would incorrectly appear to stutter when their idle animation looped
Assisted-by: Codex:gpt-5.6-sol
Changed paths:
engines/harvester/detection.cpp
engines/harvester/flow.cpp
engines/harvester/room.cpp
engines/harvester/runtime_entity.cpp
diff --git a/engines/harvester/detection.cpp b/engines/harvester/detection.cpp
index b4bd28a326b..3d6e4b28c11 100644
--- a/engines/harvester/detection.cpp
+++ b/engines/harvester/detection.cpp
@@ -28,7 +28,7 @@ const DebugChannelDef HarvesterMetaEngineDetection::debugFlagList[] = {
{ Harvester::kDebugGeneral, "general", "General debug level" },
{ Harvester::kDebugCursor, "cursor", "Cursor animation, hover, and placement debug" },
{ Harvester::kDebugRoom, "room", "Room setup, scene population, and interaction debug" },
- { Harvester::kDebugPlayer, "player", "Player animation, movement, and spawn debug" },
+ { Harvester::kDebugPlayer, "player", "Player and NPC animation, movement, and spawn debug" },
{ Harvester::kDebugPathfinding, "pathfinding", "Player pathfinding target, blocker, and movement-band debug" },
{ Harvester::kDebugCombat, "combat", "Room combat pursuit, attack, and damage debug" },
{ Harvester::kDebugResources, "resources", "Resource loading" },
diff --git a/engines/harvester/flow.cpp b/engines/harvester/flow.cpp
index ee4bc9c7d54..4e00c58e22b 100644
--- a/engines/harvester/flow.cpp
+++ b/engines/harvester/flow.cpp
@@ -2271,6 +2271,12 @@ bool Flow::populateRoomSceneEntities(RoomSetupState &state,
npc.deathDamageType != 0 &&
npc.runtimeState >= 0;
entity->setHitTestMode(isCorpse ? kRuntimeEntityHitTestNone : kRuntimeEntityHitTestOpaquePixels);
+ // Native spawn positions frame 0 before selecting ambient or corpse frame banks.
+ if (!applyRoomActorPlacementInternal(state, *entity,
+ npc.posX, npc.posY, (float)npc.posZ, nullptr, false)) {
+ debug(1, "Harvester: unable to apply room npc placement for '%s'",
+ npc.npcName.c_str());
+ }
if (isCorpse) {
const int corpseFrame = MIN(entity->getLastFrame(), npc.runtimeState);
entity->setAnimationFrameRange(corpseFrame, corpseFrame, false);
@@ -2283,12 +2289,6 @@ bool Flow::populateRoomSceneEntities(RoomSetupState &state,
if (npc.frameDelay > 0)
entity->setAnimationRate(npc.frameDelay);
}
- // Native room NPCs come from spawn_abm_entity_base, which leaves the depth-scale flag cleared.
- if (!applyRoomActorPlacementInternal(state, *entity,
- npc.posX, npc.posY, (float)npc.posZ, nullptr, false)) {
- debug(1, "Harvester: unable to apply room npc placement for '%s'",
- npc.npcName.c_str());
- }
entityManager->reinsertSceneEntity(entity);
debugC(1, kDebugRoom,
"Harvester: scene npc spawned room='%s' npc='%s' class=0x%x pos=(%d,%d,z=%.2f) frame_delay=%d model='%s' active=%d visible=%d",
diff --git a/engines/harvester/room.cpp b/engines/harvester/room.cpp
index 65814b5bcaf..7852a62a8aa 100644
--- a/engines/harvester/room.cpp
+++ b/engines/harvester/room.cpp
@@ -855,15 +855,19 @@ Common::Error RoomSystem::runRoomLoop(Flow &flow, const Common::String &targetNa
if (!entityManager || !shouldSpawnNpc)
return nullptr;
- const int initialFrame = preservedCorpse ? npc.runtimeState : 0;
Entity *entity = entityManager->spawnSceneActorEntity(
- npc.npcName, npc.modelPath, Common::Point(npc.posX, npc.posY), (float)npc.posZ, initialFrame);
+ npc.npcName, npc.modelPath, Common::Point(npc.posX, npc.posY), (float)npc.posZ, 0);
if (!entity)
return nullptr;
entity->setClassId(kRuntimeEntityClassNpc);
entity->setZExtent(kNativeNpcMonsterZExtent);
entity->setHitTestMode(preservedCorpse ? kRuntimeEntityHitTestNone : kRuntimeEntityHitTestOpaquePixels);
+ entity->setVisible(true);
+ if (!applyRoomNpcPlacement(*entity, npc)) {
+ removeSceneEntityByName(npc.npcName);
+ return nullptr;
+ }
if (preservedCorpse) {
const int corpseFrame = MIN(entity->getLastFrame(), npc.runtimeState);
entity->setAnimationFrameRange(corpseFrame, corpseFrame, false);
@@ -874,11 +878,6 @@ Common::Error RoomSystem::runRoomLoop(Flow &flow, const Common::String &targetNa
entity->setAnimationFrameRange(0, MIN(entity->getLastFrame(), kRoomNpcAmbientLastFrame), true);
entity->setAnimationRate(npc.frameDelay > 0 ? npc.frameDelay : 0);
}
- entity->setVisible(true);
- if (!applyRoomNpcPlacement(*entity, npc)) {
- removeSceneEntityByName(npc.npcName);
- return nullptr;
- }
entityManager->reinsertSceneEntity(entity);
return entity;
};
@@ -1458,12 +1457,7 @@ Common::Error RoomSystem::runRoomLoop(Flow &flow, const Common::String &targetNa
if (!entityManager)
return;
- for (const NpcRecord &npc : scene.state.roomNpcs) {
- Entity *entity = entityManager->findSceneEntityByName(npc.npcName);
- if (!entity)
- continue;
- (void)applyRoomNpcPlacement(*entity, npc);
- }
+ // Class-4 NPCs retain their spawn base through ambient and death animation banks.
for (const MonsterRecord &monster : scene.state.roomMonsters) {
Entity *entity = entityManager->findSceneEntityByName(monster.monsterName);
if (!entity)
diff --git a/engines/harvester/runtime_entity.cpp b/engines/harvester/runtime_entity.cpp
index 52ea2c13d77..5aec9b654e0 100644
--- a/engines/harvester/runtime_entity.cpp
+++ b/engines/harvester/runtime_entity.cpp
@@ -245,12 +245,31 @@ bool Entity::loadAbmResource(ResourceManager &resources, const Common::String &p
}
void Entity::setPosition(int x, int y, float z) {
+ const int previousX = _x;
+ const int previousY = _y;
+ const float previousZ = _z;
+ const Common::Point previousDrawOrigin = getDrawOrigin();
const bool anchorChanged = _x != x || _y != y;
_x = x;
_y = y;
_z = z;
if (anchorChanged)
updateScreenBaseFromCurrentFrame();
+
+ if ((anchorChanged || previousZ != z) && _classId == kRuntimeEntityClassNpc) {
+ int width = 0;
+ int height = 0;
+ int xOffset = 0;
+ int yOffset = 0;
+ (void)getCurrentFrameMetrics(width, height, xOffset, yOffset);
+ const Common::Point drawOrigin = getDrawOrigin();
+ debugC(3, kDebugPlayer,
+ "Harvester: npc position npc='%s' frame=%d entity=(%d,%d,z=%.2f)->(%d,%d,z=%.2f) frame_size=%dx%d frame_offset=(%d,%d) draw=(%d,%d)->(%d,%d)",
+ _name.c_str(), _currentFrame,
+ previousX, previousY, (double)previousZ, _x, _y, (double)_z,
+ width, height, xOffset, yOffset,
+ previousDrawOrigin.x, previousDrawOrigin.y, drawOrigin.x, drawOrigin.y);
+ }
}
void Entity::setAnchorMode(RuntimeEntityAnchorMode anchorMode) {
@@ -259,6 +278,7 @@ void Entity::setAnchorMode(RuntimeEntityAnchorMode anchorMode) {
}
void Entity::setAnimationRate(int rate) {
+ const int previousRate = _animationRate;
if (rate == 0)
_animationTickInterval = 0;
else
@@ -267,11 +287,24 @@ void Entity::setAnimationRate(int rate) {
if (rate != _animationRate) {
_nextAnimationTick = 0;
_animationRate = rate;
+ if (_classId == kRuntimeEntityClassNpc) {
+ debugC(2, kDebugPlayer,
+ "Harvester: npc animation rate npc='%s' rate=%d->%d interval_ticks=%u next_tick=%u",
+ _name.c_str(), previousRate, _animationRate,
+ _animationTickInterval, _nextAnimationTick);
+ }
}
}
void Entity::setAnimationEnabled(bool enabled) {
+ const bool wasEnabled = _animationEnabled;
_animationEnabled = enabled && !_frames.empty() && _currentFrame >= 0;
+ if (wasEnabled != _animationEnabled && _classId == kRuntimeEntityClassNpc) {
+ debugC(2, kDebugPlayer,
+ "Harvester: npc animation enabled npc='%s' enabled=%d->%d frame=%d range=%d..%d",
+ _name.c_str(), wasEnabled, _animationEnabled,
+ _currentFrame, _firstFrame, _lastFrame);
+ }
}
void Entity::setCurrentFrame(int frame) {
@@ -286,6 +319,9 @@ void Entity::setAnimationFrameRange(int firstFrame, int lastFrame, bool looping)
if (_frames.empty())
return;
+ const int previousFirstFrame = _firstFrame;
+ const int previousLastFrame = _lastFrame;
+ const bool wasLooping = _looping;
firstFrame = CLIP<int>(firstFrame, 0, (int)_frames.size() - 1);
lastFrame = CLIP<int>(lastFrame, 0, (int)_frames.size() - 1);
if (lastFrame < firstFrame)
@@ -301,6 +337,14 @@ void Entity::setAnimationFrameRange(int firstFrame, int lastFrame, bool looping)
advanceAnimationFrame(_firstFrame);
else
updateBoundsFromCurrentFrame();
+
+ if (_classId == kRuntimeEntityClassNpc) {
+ debugC(2, kDebugPlayer,
+ "Harvester: npc animation range npc='%s' frames=%d..%d->%d..%d current=%d looping=%d->%d enabled=%d",
+ _name.c_str(), previousFirstFrame, previousLastFrame,
+ _firstFrame, _lastFrame, _currentFrame,
+ wasLooping, _looping, _animationEnabled);
+ }
}
void Entity::setAnimationSequence(int sequence) {
@@ -394,9 +438,31 @@ bool Entity::tickVisualState(uint32 now) {
if (now < _nextAnimationTick)
return false;
+ const int previousFrameIndex = _currentFrame;
+ const bool wasPlayingBackwards = _playBackwards;
+ const AbmFrame &previousFrame = _frames[(uint)previousFrameIndex];
+ const Common::Point previousDrawOrigin = getDrawOrigin();
advanceAnimationFrame(_playBackwards ? -1 : -2);
_nextAnimationTick = now + _animationTickInterval;
_animationAdvancedLastTick = true;
+
+ if (_classId == kRuntimeEntityClassNpc) {
+ const AbmFrame ¤tFrame = _frames[(uint)_currentFrame];
+ const Common::Point drawOrigin = getDrawOrigin();
+ const bool loopReset = _looping && !_pingPong &&
+ ((!wasPlayingBackwards && previousFrameIndex == _lastFrame && _currentFrame == _firstFrame) ||
+ (wasPlayingBackwards && previousFrameIndex == _firstFrame && _currentFrame == _lastFrame));
+ debugC(3, kDebugPlayer,
+ "Harvester: npc animation advance npc='%s' frame=%d->%d range=%d..%d loop_reset=%d backwards=%d->%d rate=%d interval=%u entity=(%d,%d,z=%.2f) previous=(size=%ux%u offset=%d,%d draw=%d,%d) current=(size=%ux%u offset=%d,%d draw=%d,%d)",
+ _name.c_str(),
+ previousFrameIndex, _currentFrame, _firstFrame, _lastFrame,
+ loopReset, wasPlayingBackwards, _playBackwards,
+ _animationRate, _animationTickInterval, _x, _y, (double)_z,
+ previousFrame.width, previousFrame.height, previousFrame.xOffset, previousFrame.yOffset,
+ previousDrawOrigin.x, previousDrawOrigin.y,
+ currentFrame.width, currentFrame.height, currentFrame.xOffset, currentFrame.yOffset,
+ drawOrigin.x, drawOrigin.y);
+ }
return true;
}
Commit: 08e53baf6090e3d794da607b2512ba529e88fd39
https://github.com/scummvm/scummvm/commit/08e53baf6090e3d794da607b2512ba529e88fd39
Author: Alex Bevilacqua (alex at alexbevi.com)
Date: 2026-09-02T07:18:00-04:00
Commit Message:
ASYLUM: Implement cinematic matte palette transitions
The original game builds a grayscale target when movie playback starts. Its
shared scene transition blends toward that palette while the matte bars close
and back to the selected scene palette while they open.
Assisted-by: Codex:gpt-5.6-sol
Changed paths:
engines/asylum/resources/encounters.cpp
engines/asylum/resources/encounters.h
engines/asylum/resources/script.cpp
engines/asylum/system/screen.cpp
engines/asylum/system/screen.h
diff --git a/engines/asylum/resources/encounters.cpp b/engines/asylum/resources/encounters.cpp
index 40b01f94cad..110dc0d8125 100644
--- a/engines/asylum/resources/encounters.cpp
+++ b/engines/asylum/resources/encounters.cpp
@@ -1219,7 +1219,7 @@ void Encounter::drawScreen() {
if (getSharedData()->getMatteInitialized()) {
getScreen()->drawWideScreenBars(82);
- getScreen()->updatePalette();
+ getScreen()->copyGrayPaletteToWorkingPalette();
getScreen()->setupPalette(NULL, 0, 0);
getScreen()->paletteFade(0, 25, 10);
} else {
@@ -1243,7 +1243,7 @@ void Encounter::drawScreen() {
getScene()->updateScreen();
getScreen()->drawWideScreenBars(82);
- getScreen()->updatePalette(0);
+ getScreen()->copyGrayPaletteToWorkingPalette();
getScreen()->setupPalette(NULL, 0, 0);
if (getSharedData()->getMattePlaySound() /* Scene::updateScreen() does script processing, so the value might have changed */
@@ -1268,7 +1268,7 @@ void Encounter::drawScreen() {
ResourceId paletteId = getWorld()->actions[getScene()->getActor()->getActionIndex3()]->paletteResourceId;
getScreen()->setPaletteGamma(paletteId ? paletteId : getWorld()->currentPaletteId);
- updatePalette1();
+ blendPaletteFromGray();
getScreen()->setupPalette(NULL, 0, 0);
}
} else {
@@ -1277,7 +1277,7 @@ void Encounter::drawScreen() {
getScreen()->setPaletteGamma(getWorld()->currentPaletteId);
- updatePalette2();
+ blendPaletteToGray();
getScreen()->setupPalette(NULL, 0, 0);
}
}
@@ -1492,12 +1492,16 @@ bool Encounter::updateScreen() {
return false;
}
-void Encounter::updatePalette1() {
- debugC(kDebugLevelEncounter, "[Encounter::updatePalette1] Not implemented!");
+void Encounter::blendPaletteFromGray() {
+ const int32 step = getSharedData()->getMatteBarHeight() - 90;
+ debugC(kDebugLevelEncounter, "[Encounter] Blending palette from grayscale: step %d/80", step);
+ getScreen()->blendPaletteFromGray(step, 80);
}
-void Encounter::updatePalette2() {
- debugC(kDebugLevelEncounter, "[Encounter::updatePalette2] Not implemented!");
+void Encounter::blendPaletteToGray() {
+ const int32 step = getSharedData()->getMatteBarHeight();
+ debugC(kDebugLevelEncounter, "[Encounter] Blending palette to grayscale: step %d/85", step);
+ getScreen()->blendPaletteToGray(step, 85);
}
//////////////////////////////////////////////////////////////////////////
diff --git a/engines/asylum/resources/encounters.h b/engines/asylum/resources/encounters.h
index 070d6a5a425..fe69324b392 100644
--- a/engines/asylum/resources/encounters.h
+++ b/engines/asylum/resources/encounters.h
@@ -257,8 +257,8 @@ private:
void updateDrawingStatus1(int32 rectIndex);
void updateDrawingStatus2(int32 rectIndex);
bool updateScreen();
- void updatePalette1();
- void updatePalette2();
+ void blendPaletteFromGray();
+ void blendPaletteToGray();
bool isKeywordVisible(int16 keyword) const { return (bool)(BYTE1(keyword) & kKeywordOptionsVisible); }
bool isKeywordDisabled(int16 keyword) const { return (bool)(BYTE1(keyword) & kKeywordOptionsDisabled); }
diff --git a/engines/asylum/resources/script.cpp b/engines/asylum/resources/script.cpp
index 95bdf2e13bf..d14dd4187cc 100644
--- a/engines/asylum/resources/script.cpp
+++ b/engines/asylum/resources/script.cpp
@@ -1259,7 +1259,7 @@ IMPLEMENT_OPCODE(CreatePalette)
return;
}
- getScreen()->updatePalette(cmd->param1);
+ getScreen()->blendScenePaletteForFadeStep(cmd->param1);
_processNextEntry = true;
++cmd->param1;
diff --git a/engines/asylum/system/screen.cpp b/engines/asylum/system/screen.cpp
index 71ac4c4bbb7..177e058ff2e 100644
--- a/engines/asylum/system/screen.cpp
+++ b/engines/asylum/system/screen.cpp
@@ -312,14 +312,13 @@ void Screen::setupPalette(byte *buffer, int start, int count) {
_vm->_system->getPaletteManager()->setPalette(_mainPalette, 0, 256);
}
-void Screen::updatePalette() {
- // FIXME: This is used to replace all the inline code to setup the palette before calls to setupPalette/paletteFade
- // See if all that code can really be factorized into a single function or not
- debugC(kDebugLevelScene, "[Screen::updatePalette] Not implemented!");
+void Screen::copyGrayPaletteToWorkingPalette() {
+ memcpy(_mainPalette + 3, _currentPalette + 3, sizeof(_mainPalette) - 6);
+ debugC(kDebugLevelScene, "[Screen] Copied grayscale scene palette to the working palette");
}
-void Screen::updatePalette(int32 param) {
- if (param >= 21) {
+void Screen::blendScenePaletteForFadeStep(int32 step) {
+ if (step >= 21) {
for (uint32 j = 3; j < ARRAYSIZE(_mainPalette) - 3; j += 3) {
_mainPalette[j] = _currentPalette[j];
_mainPalette[j + 1] = _currentPalette[j + 1];
@@ -338,17 +337,30 @@ void Screen::updatePalette(int32 param) {
byte *paletteData = getPaletteData(paletteId);
paletteData += 4;
- float fParam = param / 20.0;
+ float fraction = step / 20.0;
for (uint32 j = 3; j < ARRAYSIZE(_mainPalette) - 3; j += 3) {
- _mainPalette[j] = (byte)((1.0 - fParam) * 4 * paletteData[j] + fParam * _currentPalette[j]);
- _mainPalette[j + 1] = (byte)((1.0 - fParam) * 4 * paletteData[j + 1] + fParam * _currentPalette[j + 1]);
- _mainPalette[j + 2] = (byte)((1.0 - fParam) * 4 * paletteData[j + 2] + fParam * _currentPalette[j + 2]);
+ _mainPalette[j] = (byte)((1.0 - fraction) * 4 * paletteData[j] + fraction * _currentPalette[j]);
+ _mainPalette[j + 1] = (byte)((1.0 - fraction) * 4 * paletteData[j + 1] + fraction * _currentPalette[j + 1]);
+ _mainPalette[j + 2] = (byte)((1.0 - fraction) * 4 * paletteData[j + 2] + fraction * _currentPalette[j + 2]);
}
setupPalette(nullptr, 0, 0);
}
}
+void Screen::blendPaletteFromGray(int32 step, int32 stepCount) {
+ blendPalette(_currentPalette, _mainPalette, step, stepCount);
+}
+
+void Screen::blendPaletteToGray(int32 step, int32 stepCount) {
+ blendPalette(_mainPalette, _currentPalette, step, stepCount);
+}
+
+void Screen::blendPalette(const byte *from, const byte *to, int32 step, int32 stepCount) {
+ for (uint32 i = 3; i < ARRAYSIZE(_mainPalette) - 3; ++i)
+ _mainPalette[i] = (byte)(from[i] + ((int32)to[i] - from[i]) * step / stepCount);
+}
+
//////////////////////////////////////////////////////////////////////////
// Palette fading
//////////////////////////////////////////////////////////////////////////
diff --git a/engines/asylum/system/screen.h b/engines/asylum/system/screen.h
index 0165cd313a5..824c6b4e6a4 100644
--- a/engines/asylum/system/screen.h
+++ b/engines/asylum/system/screen.h
@@ -106,8 +106,10 @@ public:
const byte *getPalette() { return _mainPalette; }
void setMainPalette(const byte *data);
void loadGrayPalette();
- void updatePalette();
- void updatePalette(int32 param);
+ void copyGrayPaletteToWorkingPalette();
+ void blendScenePaletteForFadeStep(int32 step);
+ void blendPaletteFromGray(int32 step, int32 stepCount);
+ void blendPaletteToGray(int32 step, int32 stepCount);
void setupPalette(byte *buffer, int start, int count);
bool isFading() { return _isFading; }
@@ -175,6 +177,7 @@ private:
Common::Queue<FadeParameters> _fadeQueue;
byte *getPaletteData(ResourceId id);
+ void blendPalette(const byte *from, const byte *to, int32 step, int32 stepCount);
void setPaletteGamma(byte *data, byte *target = NULL);
void stopQueuedPaletteFade();
More information about the Scummvm-git-logs
mailing list