[Scummvm-git-logs] scummvm master -> a8055d338ab2318b080b1b5d0b9689c33c93af3f
bluegr
noreply at scummvm.org
Sat Jul 11 20:13:51 UTC 2026
This automated email contains information about 15 new commits which have been
pushed to the 'scummvm' repo located at https://api.github.com/repos/scummvm/scummvm .
Summary:
6bfd09e11f HARVESTER: Restore native animation timing
85c4f5adae HARVESTER: Match native entity animation behavior
018031b052 HARVESTER: Detect the DOS demo
a3559fcfec HARVESTER: Provide DOS demo menu defaults
ca73b176c7 HARVESTER: Keep DOS demo dialogue responses isolated
0c7fcb6242 HARVESTER: Handle the demo ending command
8a7b3f5536 HARVESTER: Show the DOS demo ending screens
753a8bcac7 HARVESTER: Restore the DOS demo introduction
e8061ec602 HARVESTER: Hide CD prompts for the DOS demo
b749cfbefc HARVESTER: Run room entry actions after closeups
386868871d HARVESTER: Enforce scripted player pauses
fc689fc199 HARVESTER: Handle scripted weapon changes
bf124c1272 HARVESTER: Align scripted entities to player depth
4ba44d4f42 HARVESTER: Handle scripted player depth moves
a8055d338a HARVESTER: Match DOS demo menu prompts
Commit: 6bfd09e11f27260385259e2c6477134003ce736b
https://github.com/scummvm/scummvm/commit/6bfd09e11f27260385259e2c6477134003ce736b
Author: Alex Bevilacqua (alex at alexbevi.com)
Date: 2026-07-11T23:13:41+03:00
Commit Message:
HARVESTER: Restore native animation timing
Use the recovered centisecond clock for runtime entity animation intervals instead of the provisional 15 ms divisor. This makes rate 17 advance every 50 ms, matching HARVEST.LE and keeping actor movement synchronized with animation.
Assisted-by: Codex:gpt-5
Changed paths:
engines/harvester/runtime_entity.cpp
diff --git a/engines/harvester/runtime_entity.cpp b/engines/harvester/runtime_entity.cpp
index f1a5083eeaa..f50ee60d2f7 100644
--- a/engines/harvester/runtime_entity.cpp
+++ b/engines/harvester/runtime_entity.cpp
@@ -41,10 +41,8 @@ static const char *const kCursorResourcePath = "1:/GRAPHIC/POINTERS/POINTERS.ABM
static const float kCursorEntityZ = -100.0f;
static const int kCursorAnimationRate = 10;
static const int kFramesPerSequence = 10;
-// Animation pacing remains provisional while we compare against the original.
-// Native rate values still feed the recovered 100 / rate interval formula, but
-// this clock divisor is tuned separately from the script/timer countdown clock.
-static const uint32 kAnimationClockDivisorMs = 15;
+// The native runtime measures entity animation intervals in centiseconds.
+static const uint32 kAnimationClockDivisorMs = 10;
static const byte kTransparentPaletteIndex = 0;
static int roundToInt(float value) {
Commit: 85c4f5adae3e56d03e6beaf08ae496790b918c7c
https://github.com/scummvm/scummvm/commit/85c4f5adae3e56d03e6beaf08ae496790b918c7c
Author: Alex Bevilacqua (alex at alexbevi.com)
Date: 2026-07-11T23:13:41+03:00
Commit Message:
HARVESTER: Match native entity animation behavior
Quantize the shared entity clock like the DOS BIOS timer so scripted animation rates keep the original cadence. Also preserve rate-zero advancement and the authored horizontal offset when depth-scaling frames.
Assisted-by: Codex:gpt-5
Changed paths:
engines/harvester/runtime_entity.cpp
diff --git a/engines/harvester/runtime_entity.cpp b/engines/harvester/runtime_entity.cpp
index f50ee60d2f7..52ea2c13d77 100644
--- a/engines/harvester/runtime_entity.cpp
+++ b/engines/harvester/runtime_entity.cpp
@@ -41,8 +41,8 @@ static const char *const kCursorResourcePath = "1:/GRAPHIC/POINTERS/POINTERS.ABM
static const float kCursorEntityZ = -100.0f;
static const int kCursorAnimationRate = 10;
static const int kFramesPerSequence = 10;
-// The native runtime measures entity animation intervals in centiseconds.
-static const uint32 kAnimationClockDivisorMs = 10;
+static const uint32 kDosPitInputFrequency = 1193180;
+static const uint32 kDosPitTimerDivisor = 65536;
static const byte kTransparentPaletteIndex = 0;
static int roundToInt(float value) {
@@ -70,7 +70,15 @@ static void scaleIndexedBitmapNearest(const IndexedBitmap &source, IndexedBitmap
}
static uint32 getAnimationClockTicks() {
- return g_system ? (g_system->getMillis() / kAnimationClockDivisorMs) : 0;
+ if (!g_system)
+ return 0;
+
+ // Native obtains time through DOS int 21h/AH=2Ch. Its hundredths field
+ // advances on the 18.2 Hz BIOS timer, so preserve that quantization instead
+ // of deriving idealized centiseconds directly from wall-clock milliseconds.
+ const uint64 pitTicks = ((uint64)g_system->getMillis() * kDosPitInputFrequency) /
+ ((uint64)kDosPitTimerDivisor * 1000U);
+ return (uint32)((pitTicks * kDosPitTimerDivisor * 100U) / kDosPitInputFrequency);
}
static void blitAnimationFrame(Graphics::Screen &screen, const Common::Array<AbmFrame> &frames, uint frameIndex,
@@ -381,7 +389,7 @@ void Entity::setDepthScale(float scale) {
bool Entity::tickVisualState(uint32 now) {
_animationAdvancedLastTick = false;
- if (!_animationEnabled || _currentFrame < 0 || _animationTickInterval == 0)
+ if (!_animationEnabled || _currentFrame < 0)
return false;
if (now < _nextAnimationTick)
return false;
@@ -730,7 +738,8 @@ void Entity::rebuildScaledFrames() {
const int scaledHeight = scaleDimension(source.height, _depthScale);
scaleIndexedBitmapNearest(source, scaled, scaledWidth, scaledHeight);
- scaled.xOffset = roundToInt((float)source.xOffset * _depthScale);
+ // Native depth scaling preserves the authored horizontal frame offset.
+ scaled.xOffset = source.xOffset;
scaled.yOffset = roundToInt((float)source.yOffset * _depthScale);
}
@@ -814,8 +823,8 @@ Entity *EntityManager::spawnCursorEntity(const Common::Point &position) {
const uint32 animationInterval = _cursorEntity->getAnimationRate() == 0 ? 0 :
(100U / (uint32)_cursorEntity->getAnimationRate());
debugC(1, kDebugCursor,
- "Harvester: spawned cursor entity rate=%d intervalTicks=%u clock_divisor_ms=%u frame=%d..%d pos=(%d,%d)",
- _cursorEntity->getAnimationRate(), animationInterval, kAnimationClockDivisorMs,
+ "Harvester: spawned cursor entity rate=%d intervalTicks=%u clock_source=dos_centiseconds frame=%d..%d pos=(%d,%d)",
+ _cursorEntity->getAnimationRate(), animationInterval,
_cursorEntity->getCurrentFrame(),
_cursorEntity->getLastFrame(), position.x, position.y);
}
Commit: 018031b052efc3fb531eecce1bcb88f7ce6e8b1c
https://github.com/scummvm/scummvm/commit/018031b052efc3fb531eecce1bcb88f7ce6e8b1c
Author: Alex Bevilacqua (alex at alexbevi.com)
Date: 2026-07-11T23:13:41+03:00
Commit Message:
HARVESTER: Detect the DOS demo
Assisted-by: Codex:gpt-5.6sol
Changed paths:
engines/harvester/detection_tables.h
diff --git a/engines/harvester/detection_tables.h b/engines/harvester/detection_tables.h
index 8d5dd56ca85..1a6f84825b2 100644
--- a/engines/harvester/detection_tables.h
+++ b/engines/harvester/detection_tables.h
@@ -36,6 +36,15 @@ const ADGameDescription gameDescriptions[] = {
ADGF_UNSTABLE,
GUIO2(GAMEOPTION_GORE, GAMEOPTION_SHOW_CD_CHANGE_PROMPTS)
},
+ {
+ "harvester",
+ "Demo",
+ AD_ENTRY1s("harvest.exe", "787e43b868ebfaca614010af3ab66b6d", 1167247),
+ Common::EN_ANY,
+ Common::kPlatformDOS,
+ ADGF_DEMO | ADGF_UNSTABLE,
+ GUIO2(GAMEOPTION_GORE, GAMEOPTION_SHOW_CD_CHANGE_PROMPTS)
+ },
AD_TABLE_END_MARKER
};
Commit: a3559fcfeccbb3b06185747ff187c9a1963cebc7
https://github.com/scummvm/scummvm/commit/a3559fcfeccbb3b06185747ff187c9a1963cebc7
Author: Alex Bevilacqua (alex at alexbevi.com)
Date: 2026-07-11T23:13:41+03:00
Commit Message:
HARVESTER: Provide DOS demo menu defaults
Assisted-by: Codex:gpt-5.6sol
Changed paths:
engines/harvester/flow.cpp
engines/harvester/harvester.cpp
engines/harvester/harvester.h
diff --git a/engines/harvester/flow.cpp b/engines/harvester/flow.cpp
index 529913b05e1..d3de2ba0188 100644
--- a/engines/harvester/flow.cpp
+++ b/engines/harvester/flow.cpp
@@ -55,6 +55,14 @@ namespace {
static const char *const kQuickTipsPath = "ADJHEAD.RCS";
static const char *const kMenuPath = "MENU.INI";
static const char *const kMenuSectionName = "menu";
+static const char *const kDemoMenuItems[] = {
+ "NEW GAME",
+ "SAVE GAME",
+ "LOAD GAME",
+ "OPTIONS",
+ "HELP",
+ "QUIT GAME"
+};
static const char *const kTownMapPalettePath = "1:/GRAPHIC/PAL/HARVMAP.PAL";
static const char *const kTownMapMusicPath = "SOUND/MUSIC/MENACE.CMP";
static const char *const kTownMapBitmapPaths[] = {
@@ -1484,6 +1492,15 @@ bool Flow::loadMenuItems() {
Common::Array<byte> data;
if (!_engine.getResources()->loadFile(kMenuPath, data)) {
+ if (_engine.isDemo()) {
+ for (const char *item : kDemoMenuItems)
+ _menuItems.push_back(item);
+ debugC(1, kDebugGeneral,
+ "Harvester: using %u built-in DOS demo menu items",
+ (uint)_menuItems.size());
+ return true;
+ }
+
warning("Harvester: unable to load startup menu '%s'", kMenuPath);
return true;
}
diff --git a/engines/harvester/harvester.cpp b/engines/harvester/harvester.cpp
index 4cf399cd993..004cc839266 100644
--- a/engines/harvester/harvester.cpp
+++ b/engines/harvester/harvester.cpp
@@ -60,6 +60,10 @@ Common::String HarvesterEngine::getGameId() const {
return _gameDescription->gameId;
}
+bool HarvesterEngine::isDemo() const {
+ return (_gameDescription->flags & ADGF_DEMO) != 0;
+}
+
bool HarvesterEngine::canLoadGameStateCurrently(Common::U32String *) {
return _script != nullptr;
}
diff --git a/engines/harvester/harvester.h b/engines/harvester/harvester.h
index f363128bbb3..979a186873d 100644
--- a/engines/harvester/harvester.h
+++ b/engines/harvester/harvester.h
@@ -56,6 +56,7 @@ public:
* Returns the game Id
*/
Common::String getGameId() const;
+ bool isDemo() const;
/**
* Gets a random number
Commit: ca73b176c7bf90ea54628ac667688e302786d721
https://github.com/scummvm/scummvm/commit/ca73b176c7bf90ea54628ac667688e302786d721
Author: Alex Bevilacqua (alex at alexbevi.com)
Date: 2026-07-11T23:13:41+03:00
Commit Message:
HARVESTER: Keep DOS demo dialogue responses isolated
Assisted-by: Codex:gpt-5.6sol
Changed paths:
engines/harvester/text.cpp
diff --git a/engines/harvester/text.cpp b/engines/harvester/text.cpp
index 9ea13bd6fe3..1988b0048cd 100644
--- a/engines/harvester/text.cpp
+++ b/engines/harvester/text.cpp
@@ -28,6 +28,7 @@
#include "common/fs.h"
#include "common/ptr.h"
#include "common/stream.h"
+#include "harvester/harvester.h"
#include "harvester/parse_utils.h"
#include "harvester/resources.h"
@@ -199,7 +200,8 @@ bool Text::loadDialogueResponses(ResourceManager &resources) {
if (_dialogueResponseLines.empty())
return false;
- if (!hasNativeHankDialogueResponseLayout(_dialogueResponseLines)) {
+ if ((!g_engine || !g_engine->isDemo()) &&
+ !hasNativeHankDialogueResponseLayout(_dialogueResponseLines)) {
Common::Array<Common::String> preferredLines;
if (tryLoadPreferredDialogueResponsesFromGamePath(preferredLines))
_dialogueResponseLines = Common::move(preferredLines);
Commit: 0c7fcb6242f308d2cc3a17e202b0f87eb19021a5
https://github.com/scummvm/scummvm/commit/0c7fcb6242f308d2cc3a17e202b0f87eb19021a5
Author: Alex Bevilacqua (alex at alexbevi.com)
Date: 2026-07-11T23:13:41+03:00
Commit Message:
HARVESTER: Handle the demo ending command
Assisted-by: Codex:gpt-5.6sol
Changed paths:
engines/harvester/dialogue.cpp
engines/harvester/room.cpp
engines/harvester/room_interaction.cpp
engines/harvester/script.cpp
engines/harvester/script.h
diff --git a/engines/harvester/dialogue.cpp b/engines/harvester/dialogue.cpp
index 7eb441f5c2f..6117ff04e5c 100644
--- a/engines/harvester/dialogue.cpp
+++ b/engines/harvester/dialogue.cpp
@@ -1015,6 +1015,7 @@ public:
!interaction.musicPath.empty() || !interaction.nextRoomName.empty() ||
!interaction.cutscenePath.empty() ||
!interaction.deathFlicPath.empty() || interaction.requestMainMenu ||
+ interaction.requestDemoEnding ||
interaction.requestRoomRestart ||
interaction.requestCloseupExit ||
interaction.cdChangeDisc > 0 ||
diff --git a/engines/harvester/room.cpp b/engines/harvester/room.cpp
index fbbe6067b51..ef1be152280 100644
--- a/engines/harvester/room.cpp
+++ b/engines/harvester/room.cpp
@@ -1480,6 +1480,7 @@ Common::Error RoomSystem::runRoomLoop(Flow &flow, const Common::String &targetNa
!interaction.requestPlayerGotoXZ &&
interaction.lightingCommand == kStartupLightingCommandNone &&
!interaction.requestMainMenu &&
+ !interaction.requestDemoEnding &&
interaction.dialogueNpcName.empty() &&
interaction.dialogueContinuationTag.empty() &&
interaction.continuationTag.empty() &&
diff --git a/engines/harvester/room_interaction.cpp b/engines/harvester/room_interaction.cpp
index cd42e26c22d..ae0ed0b60ff 100644
--- a/engines/harvester/room_interaction.cpp
+++ b/engines/harvester/room_interaction.cpp
@@ -57,6 +57,11 @@ Common::Error RoomInteractionProcessor::handleInteractionResult(const Interactio
_playerState.turnActive = false;
_playerState.turnTargetFacing = -1;
_pendingRegionName.clear();
+ if (interaction.requestDemoEnding) {
+ _engine.quitGame();
+ didTransition = true;
+ return Common::kNoError;
+ }
if (interaction.requestMainMenu) {
_engine.stopMusic();
@@ -339,6 +344,7 @@ bool hasRoomEntryInteraction(const InteractionResult &interaction) {
!interaction.cutscenePath.empty() ||
!interaction.deathFlicPath.empty() ||
interaction.requestMainMenu ||
+ interaction.requestDemoEnding ||
interaction.requestCloseupExit ||
interaction.requestRoomRestart ||
interaction.requestPlayerDeath ||
diff --git a/engines/harvester/script.cpp b/engines/harvester/script.cpp
index 2ea1c39bff9..e19e41b0a76 100644
--- a/engines/harvester/script.cpp
+++ b/engines/harvester/script.cpp
@@ -1701,7 +1701,8 @@ bool Script::executeRoomEnterCommands(const Common::String &roomName,
executeCommandChain(room->onEnterCommand, "room entry command", room->roomName,
room->roomName, true,
&result.musicPath, &result.audioCommands, &result.nextRoomName, &result.roomTransition,
- &result.cutscenePath, &result.deathFlicPath, &result.requestMainMenu, &result.cdChangeDisc,
+ &result.cutscenePath, &result.deathFlicPath, &result.requestMainMenu,
+ &result.requestDemoEnding, &result.cdChangeDisc,
&result.dialogueNpcName, &result.dialogueContinuationTag, &result.continuationTag,
&result.modalText, &result.lightingCommand, &result.requestPlayerGotoXZ,
&result.playerGotoX, &result.playerGotoZ,
@@ -1722,7 +1723,8 @@ bool Script::executeRoomExitCommands(const Common::String &roomName,
executeCommandChain(room->onExitCommand, "room exit command", room->roomName,
room->roomName, false,
&result.musicPath, &result.audioCommands, nullptr, nullptr, &result.cutscenePath,
- nullptr, nullptr, &result.cdChangeDisc, &result.dialogueNpcName, &result.dialogueContinuationTag,
+ nullptr, nullptr, &result.requestDemoEnding, &result.cdChangeDisc,
+ &result.dialogueNpcName, &result.dialogueContinuationTag,
&result.continuationTag, &result.modalText, &result.lightingCommand,
&result.requestPlayerGotoXZ, &result.playerGotoX, &result.playerGotoZ,
&result.requestPlayerDeath, &result.playerDeathDamageType,
@@ -1759,7 +1761,8 @@ bool Script::resolveObjectInteraction(const ObjectRecord &object, InteractionRes
executeCommandChain(object.actionTag, "interaction command", object.objectName,
commandRoomName, true,
&result.musicPath, &result.audioCommands, &result.nextRoomName, &result.roomTransition,
- &result.cutscenePath, &result.deathFlicPath, &result.requestMainMenu, &result.cdChangeDisc,
+ &result.cutscenePath, &result.deathFlicPath, &result.requestMainMenu,
+ &result.requestDemoEnding, &result.cdChangeDisc,
&result.dialogueNpcName, &result.dialogueContinuationTag, &result.continuationTag,
&result.modalText, &result.lightingCommand, &result.requestPlayerGotoXZ,
&result.playerGotoX, &result.playerGotoZ,
@@ -1768,7 +1771,7 @@ bool Script::resolveObjectInteraction(const ObjectRecord &object, InteractionRes
&result.previousTimerRecords, &result.requestCloseupExit);
return !result.nextRoomName.empty() || !result.cutscenePath.empty() ||
- !result.deathFlicPath.empty() || result.requestMainMenu ||
+ !result.deathFlicPath.empty() || result.requestMainMenu || result.requestDemoEnding ||
!result.dialogueNpcName.empty() || !result.musicPath.empty() || !result.audioCommands.empty() ||
result.cdChangeDisc > 0 || !result.continuationTag.empty() || !result.modalText.value.empty() ||
result.lightingCommand != kStartupLightingCommandNone || result.requestPlayerGotoXZ ||
@@ -1787,7 +1790,8 @@ bool Script::resolveRegionInteraction(const RegionRecord ®ion, InteractionRes
executeCommandChain(region.actionTag, "region command", region.regionName,
commandRoomName, true,
&result.musicPath, &result.audioCommands, &result.nextRoomName, &result.roomTransition,
- &result.cutscenePath, &result.deathFlicPath, &result.requestMainMenu, &result.cdChangeDisc,
+ &result.cutscenePath, &result.deathFlicPath, &result.requestMainMenu,
+ &result.requestDemoEnding, &result.cdChangeDisc,
&result.dialogueNpcName, &result.dialogueContinuationTag, &result.continuationTag,
&result.modalText, &result.lightingCommand, &result.requestPlayerGotoXZ,
&result.playerGotoX, &result.playerGotoZ,
@@ -1795,7 +1799,7 @@ bool Script::resolveRegionInteraction(const RegionRecord ®ion, InteractionRes
&result.mutatedRuntimeState, &result.visualRuntimeStateChanged,
&result.previousTimerRecords, &result.requestCloseupExit);
return !result.nextRoomName.empty() || !result.cutscenePath.empty() ||
- !result.deathFlicPath.empty() || result.requestMainMenu ||
+ !result.deathFlicPath.empty() || result.requestMainMenu || result.requestDemoEnding ||
!result.dialogueNpcName.empty() || !result.musicPath.empty() || !result.audioCommands.empty() ||
result.cdChangeDisc > 0 || !result.continuationTag.empty() || !result.modalText.value.empty() ||
result.lightingCommand != kStartupLightingCommandNone || result.requestPlayerGotoXZ ||
@@ -1822,7 +1826,8 @@ bool Script::resolveUseItemInteraction(const Common::String &itemName, const Obj
Common::String::format("%s -> %s", itemName.c_str(), target.objectName.c_str()),
commandRoomName, true,
&result.musicPath, &result.audioCommands, &result.nextRoomName, &result.roomTransition,
- &result.cutscenePath, &result.deathFlicPath, &result.requestMainMenu, &result.cdChangeDisc,
+ &result.cutscenePath, &result.deathFlicPath, &result.requestMainMenu,
+ &result.requestDemoEnding, &result.cdChangeDisc,
&result.dialogueNpcName, &result.dialogueContinuationTag, &result.continuationTag,
&result.modalText, &result.lightingCommand, &result.requestPlayerGotoXZ,
&result.playerGotoX, &result.playerGotoZ,
@@ -1863,7 +1868,8 @@ bool Script::executeActionTag(const Common::String &tag, InteractionResult &resu
executeCommandChain(tag, "action tag", tag, roomName, allowTransitions,
&result.musicPath, &result.audioCommands, &result.nextRoomName, &result.roomTransition,
- &result.cutscenePath, &result.deathFlicPath, &result.requestMainMenu, &result.cdChangeDisc,
+ &result.cutscenePath, &result.deathFlicPath, &result.requestMainMenu,
+ &result.requestDemoEnding, &result.cdChangeDisc,
&result.dialogueNpcName, &result.dialogueContinuationTag, &result.continuationTag,
&result.modalText, &result.lightingCommand, &result.requestPlayerGotoXZ,
&result.playerGotoX, &result.playerGotoZ,
@@ -1872,7 +1878,7 @@ bool Script::executeActionTag(const Common::String &tag, InteractionResult &resu
&result.previousTimerRecords, &result.requestCloseupExit);
return !result.nextRoomName.empty() || !result.cutscenePath.empty() ||
- !result.deathFlicPath.empty() || result.requestMainMenu ||
+ !result.deathFlicPath.empty() || result.requestMainMenu || result.requestDemoEnding ||
!result.dialogueNpcName.empty() || !result.musicPath.empty() || !result.audioCommands.empty() ||
result.cdChangeDisc > 0 || !result.continuationTag.empty() || !result.modalText.value.empty() ||
result.lightingCommand != kStartupLightingCommandNone || result.requestPlayerGotoXZ ||
@@ -1901,7 +1907,8 @@ bool Script::executeTimerAction(const Common::String &timerName, InteractionResu
executeCommandChain(timer->arg2, "timer command", timer->timerName, commandRoomName,
allowTransitions,
&result.musicPath, &result.audioCommands, &result.nextRoomName, &result.roomTransition,
- &result.cutscenePath, &result.deathFlicPath, &result.requestMainMenu, &result.cdChangeDisc,
+ &result.cutscenePath, &result.deathFlicPath, &result.requestMainMenu,
+ &result.requestDemoEnding, &result.cdChangeDisc,
&result.dialogueNpcName, &result.dialogueContinuationTag, &result.continuationTag,
&result.modalText, &result.lightingCommand, &result.requestPlayerGotoXZ,
&result.playerGotoX, &result.playerGotoZ,
@@ -1910,7 +1917,7 @@ bool Script::executeTimerAction(const Common::String &timerName, InteractionResu
&result.previousTimerRecords, &result.requestCloseupExit);
return !result.nextRoomName.empty() || !result.cutscenePath.empty() ||
- !result.deathFlicPath.empty() || result.requestMainMenu ||
+ !result.deathFlicPath.empty() || result.requestMainMenu || result.requestDemoEnding ||
!result.dialogueNpcName.empty() || !result.musicPath.empty() || !result.audioCommands.empty() ||
result.cdChangeDisc > 0 || !result.continuationTag.empty() || !result.modalText.value.empty() ||
result.lightingCommand != kStartupLightingCommandNone || result.requestPlayerGotoXZ ||
@@ -2744,6 +2751,7 @@ void Script::executeCommandChain(const Common::String &initialTag, const char *c
Common::Array<AudioCommand> *audioCommands, Common::String *nextRoomName,
StartupRoomTransitionKind *roomTransition,
Common::String *cutscenePath, Common::String *deathFlicPath, bool *requestMainMenu,
+ bool *requestDemoEnding,
int *cdChangeDisc,
Common::String *dialogueNpcName, Common::String *dialogueContinuationTag,
Common::String *continuationTag, ResolvedText *modalText,
@@ -2785,6 +2793,7 @@ void Script::executeCommandChain(const Common::String &initialTag, const char *c
(cutscenePath && !cutscenePath->empty()) ||
(deathFlicPath && !deathFlicPath->empty()) ||
(requestMainMenu && *requestMainMenu) ||
+ (requestDemoEnding && *requestDemoEnding) ||
(dialogueNpcName && !dialogueNpcName->empty()) ||
(continuationTag && !continuationTag->empty()) ||
(modalText && !modalText->value.empty()) ||
@@ -3045,7 +3054,8 @@ void Script::executeCommandChain(const Common::String &initialTag, const char *c
executeCommandChain(entry, "exec list entry", execList->listName,
contextRoomName, allowTransitions,
musicPath, audioCommands, nextRoomName, roomTransition, cutscenePath,
- deathFlicPath, requestMainMenu, cdChangeDisc, dialogueNpcName, dialogueContinuationTag,
+ deathFlicPath, requestMainMenu, requestDemoEnding, cdChangeDisc,
+ dialogueNpcName, dialogueContinuationTag,
continuationTag, modalText, lightingCommand, requestPlayerGotoXZ,
playerGotoX, playerGotoZ, requestPlayerDeath, playerDeathDamageType,
mutatedRuntimeState, visualRuntimeStateChanged,
@@ -3104,6 +3114,12 @@ void Script::executeCommandChain(const Common::String &initialTag, const char *c
return;
}
+ if (command->opcodeName.equalsIgnoreCase("END_DEMO")) {
+ if (requestDemoEnding)
+ *requestDemoEnding = true;
+ return;
+ }
+
if (command->opcodeName.equalsIgnoreCase("KILL_NPC") ||
command->opcodeName.equalsIgnoreCase("MONSTERFY")) {
NpcRecord *currentNpc = findRuntimeNpc(command->arg1);
@@ -3342,6 +3358,7 @@ bool Script::probePickupBlockingCommandChain(const Common::String &initialTag,
command->opcodeName.equalsIgnoreCase("START_DIALOG") ||
command->opcodeName.equalsIgnoreCase("GOFLIC") ||
command->opcodeName.equalsIgnoreCase("GODEATHFLIC") ||
+ command->opcodeName.equalsIgnoreCase("END_DEMO") ||
command->opcodeName.equalsIgnoreCase("KILL_NPC") ||
command->opcodeName.equalsIgnoreCase("MONSTERFY") ||
command->opcodeName.equalsIgnoreCase("HEAL_PC") ||
@@ -3400,6 +3417,7 @@ bool Script::hasActionableCommandChain(const Common::String &initialTag) const {
command->opcodeName.equalsIgnoreCase("START_DIALOG") ||
command->opcodeName.equalsIgnoreCase("GOFLIC") ||
command->opcodeName.equalsIgnoreCase("GODEATHFLIC") ||
+ command->opcodeName.equalsIgnoreCase("END_DEMO") ||
command->opcodeName.equalsIgnoreCase("KILL_NPC") ||
command->opcodeName.equalsIgnoreCase("MONSTERFY") ||
command->opcodeName.equalsIgnoreCase("SHOW_TEXT") ||
diff --git a/engines/harvester/script.h b/engines/harvester/script.h
index 39daf619a45..bc3bb57d75d 100644
--- a/engines/harvester/script.h
+++ b/engines/harvester/script.h
@@ -308,6 +308,7 @@ struct InteractionResult {
Common::String roomRestartTargetName;
bool requestCloseupExit = false;
bool requestMainMenu = false;
+ bool requestDemoEnding = false;
bool abortRemainingCommandChain = false;
bool mutatedRuntimeState = false;
bool visualRuntimeStateChanged = false;
@@ -460,6 +461,7 @@ private:
Common::Array<AudioCommand> *audioCommands, Common::String *nextRoomName,
StartupRoomTransitionKind *roomTransition,
Common::String *cutscenePath, Common::String *deathFlicPath, bool *requestMainMenu,
+ bool *requestDemoEnding,
int *cdChangeDisc,
Common::String *dialogueNpcName, Common::String *dialogueContinuationTag,
Common::String *continuationTag, ResolvedText *modalText,
Commit: 8a7b3f5536c6c06197ae26a70f78c351b7c4af2c
https://github.com/scummvm/scummvm/commit/8a7b3f5536c6c06197ae26a70f78c351b7c4af2c
Author: Alex Bevilacqua (alex at alexbevi.com)
Date: 2026-07-11T23:13:41+03:00
Commit Message:
HARVESTER: Show the DOS demo ending screens
Assisted-by: Codex:gpt-5.6sol
Changed paths:
engines/harvester/flow.cpp
engines/harvester/flow.h
engines/harvester/room_interaction.cpp
diff --git a/engines/harvester/flow.cpp b/engines/harvester/flow.cpp
index d3de2ba0188..8f1174a39d9 100644
--- a/engines/harvester/flow.cpp
+++ b/engines/harvester/flow.cpp
@@ -89,6 +89,10 @@ static const char *const kPlayerActorEntityName = "PLAYER";
static const uint32 kPaletteFadeTickMs = 4;
static const float kPaletteFadeStep = 0.1f;
static const float kPaletteBrightnessBlack = 0.0f;
+static const float kPaletteBrightnessFull = 1.0f;
+static const uint32 kDemoEndingScreenDurationMs = 10000;
+static const int kDemoEndingScreenNumbers[] = { 1, 2, 3, 4, 6 };
+static const char *const kDemoEndingMusicPath = "SOUND/MUSIC/ROCKFITE.CMP";
static const float kTownMapNightPaletteBrightness = 0.6f;
// Native keeps WAIT_BM visible while room construction runs; fast local loads need a short floor.
static const uint32 kTransitionWaitFrameMinMs = 120;
@@ -1616,6 +1620,99 @@ Common::Error Flow::runQuickTips() {
return Common::kNoError;
}
+Common::Error Flow::runDemoEnding() {
+ Graphics::Screen *screen = _engine.getScreen();
+ ResourceManager *resources = _engine.getResources();
+ EntityManager *entityManager = _engine.getRuntimeEntities();
+ if (!screen || !resources)
+ return Common::kReadingFailed;
+
+ if (entityManager)
+ entityManager->hideCursor();
+ _engine.stopSound();
+ if (!_engine.playMusic(kDemoEndingMusicPath))
+ return Common::kReadingFailed;
+
+ auto fadePalette = [&](const byte *palette, float from, float to) -> Common::Error {
+ const float step = from < to ? kPaletteFadeStep : -kPaletteFadeStep;
+ for (float brightness = from; step > 0.0f ? brightness < to : brightness > to;
+ brightness += step) {
+ setScaledPalette(*screen, palette, brightness);
+ screen->makeAllDirty();
+ screen->update();
+
+ const uint32 nextTick = g_system->getMillis() + kPaletteFadeTickMs;
+ while ((int32)(nextTick - g_system->getMillis()) > 0) {
+ Common::Error eventError = Common::kNoError;
+ if (pumpTransitionEvents(eventError))
+ return eventError;
+ g_system->delayMillis(1);
+ }
+ }
+
+ setScaledPalette(*screen, palette, to);
+ screen->makeAllDirty();
+ screen->update();
+ return Common::kNoError;
+ };
+
+ for (uint i = 0; i < ARRAYSIZE(kDemoEndingScreenNumbers) && !_engine.shouldQuit(); ++i) {
+ const int screenNumber = kDemoEndingScreenNumbers[i];
+ const Common::String bitmapPath = Common::String::format(
+ "1:/GRAPHIC/OTHER/SCREEN%d.BM", screenNumber);
+ const Common::String palettePath = Common::String::format(
+ "1:/GRAPHIC/PAL/SCREEN%d.PAL", screenNumber);
+ IndexedBitmap bitmap;
+ byte palette[256 * 3];
+ if (!loadBitmapResource(*resources, bitmapPath, bitmap) ||
+ !loadPaletteResource(*resources, palettePath, palette)) {
+ warning("Harvester: unable to load DOS demo ending screen %d", screenNumber);
+ return Common::kReadingFailed;
+ }
+
+ setScaledPalette(*screen, palette, kPaletteBrightnessBlack);
+ screen->fillRect(Common::Rect(0, 0, screen->w, screen->h), 0);
+ blitBitmap(*screen, bitmap, 0, 0);
+ screen->makeAllDirty();
+ screen->update();
+
+ Common::Error fadeError = fadePalette(
+ palette, kPaletteBrightnessBlack, kPaletteBrightnessFull);
+ if (fadeError.getCode() != Common::kNoError)
+ return fadeError;
+
+ const uint32 deadline = g_system->getMillis() + kDemoEndingScreenDurationMs;
+ bool skipScreen = false;
+ while (!skipScreen && !_engine.shouldQuit() &&
+ (int32)(deadline - g_system->getMillis()) > 0) {
+ Common::Event event;
+ while (g_system->getEventManager()->pollEvent(event)) {
+ Common::Error eventError = Common::kNoError;
+ if (handleSystemEvent(event, eventError))
+ return eventError;
+ if (event.type == Common::EVENT_LBUTTONDOWN ||
+ (event.type == Common::EVENT_KEYDOWN &&
+ (event.kbd.keycode == Common::KEYCODE_SPACE ||
+ event.kbd.keycode == Common::KEYCODE_ESCAPE ||
+ event.kbd.keycode == Common::KEYCODE_RETURN))) {
+ skipScreen = true;
+ }
+ }
+ g_system->delayMillis(1);
+ }
+
+ if (i + 1 == ARRAYSIZE(kDemoEndingScreenNumbers))
+ _engine.stopMusic();
+ fadeError = fadePalette(
+ palette, kPaletteBrightnessFull, kPaletteBrightnessBlack);
+ if (fadeError.getCode() != Common::kNoError)
+ return fadeError;
+ }
+
+ _engine.quitGame();
+ return Common::kNoError;
+}
+
Common::Error Flow::runMainMenuStub() {
return _menu.runMainMenuStub(*this);
}
diff --git a/engines/harvester/flow.h b/engines/harvester/flow.h
index 0c812fcce3c..fa3a9944e43 100644
--- a/engines/harvester/flow.h
+++ b/engines/harvester/flow.h
@@ -64,6 +64,7 @@ private:
bool loadQuickTips();
bool loadMenuItems();
Common::Error runQuickTips();
+ Common::Error runDemoEnding();
Common::Error runMainMenuStub();
Common::Error runRoomMenuStub(const IndexedBitmap &backdrop, const byte *palette,
float paletteBrightness, bool canSaveGame);
diff --git a/engines/harvester/room_interaction.cpp b/engines/harvester/room_interaction.cpp
index ae0ed0b60ff..e0809b362e3 100644
--- a/engines/harvester/room_interaction.cpp
+++ b/engines/harvester/room_interaction.cpp
@@ -58,7 +58,9 @@ Common::Error RoomInteractionProcessor::handleInteractionResult(const Interactio
_playerState.turnTargetFacing = -1;
_pendingRegionName.clear();
if (interaction.requestDemoEnding) {
- _engine.quitGame();
+ Common::Error endingError = _flow.runDemoEnding();
+ if (endingError.getCode() != Common::kNoError)
+ return endingError;
didTransition = true;
return Common::kNoError;
}
Commit: 753a8bcac79d35ad44760ec72dd3ace2b7fc142f
https://github.com/scummvm/scummvm/commit/753a8bcac79d35ad44760ec72dd3ace2b7fc142f
Author: Alex Bevilacqua (alex at alexbevi.com)
Date: 2026-07-11T23:13:41+03:00
Commit Message:
HARVESTER: Restore the DOS demo introduction
Assisted-by: Codex:gpt-5.6sol
Changed paths:
engines/harvester/flow.cpp
engines/harvester/flow.h
diff --git a/engines/harvester/flow.cpp b/engines/harvester/flow.cpp
index 8f1174a39d9..be36dd27d8e 100644
--- a/engines/harvester/flow.cpp
+++ b/engines/harvester/flow.cpp
@@ -39,6 +39,7 @@
#include "graphics/framelimiter.h"
#include "harvester/cft_font.h"
#include "harvester/detection.h"
+#include "harvester/fst_player.h"
#include "harvester/harvester.h"
#include "harvester/palette_utils.h"
#include "harvester/resources.h"
@@ -93,6 +94,15 @@ static const float kPaletteBrightnessFull = 1.0f;
static const uint32 kDemoEndingScreenDurationMs = 10000;
static const int kDemoEndingScreenNumbers[] = { 1, 2, 3, 4, 6 };
static const char *const kDemoEndingMusicPath = "SOUND/MUSIC/ROCKFITE.CMP";
+static const uint32 kDemoIntroductionDurationMs = 17000;
+static const char *const kDemoIntroductionBitmapPath = "1:/GRAPHIC/OTHER/MAINMENU.BM";
+static const char *const kDemoIntroductionPalettePath = "1:/GRAPHIC/PAL/MAINMENU.PAL";
+static const char *const kDemoIntroductionMusicPath = "SOUND/MUSIC/LODGERB.CMP";
+static const char *const kDemoIntroductionFstPath = "GRAPHIC/FST/PCFALL.FST";
+static const char *const kDemoIntroductionText =
+ "After solving the enigmas of the Harvest town, you are thrust into the realm of "
+ "the Order of the Harvest Moon, a bizarre institution of secretive knowledge. "
+ "However, the only thing you really care about is escaping from this hellish reality...";
static const float kTownMapNightPaletteBrightness = 0.6f;
// Native keeps WAIT_BM visible while room construction runs; fast local loads need a short floor.
static const uint32 kTransitionWaitFrameMinMs = 120;
@@ -610,6 +620,26 @@ static void blitBitmap(Graphics::Screen &screen, const IndexedBitmap &bitmap, in
screen.copyRectToSurface(bitmap.pixels.data(), bitmap.width, x, y, bitmap.width, bitmap.height);
}
+static void blitTransparentBitmap(Graphics::Screen &screen, const IndexedBitmap &bitmap,
+ int x, int y) {
+ if (!bitmap.isValid())
+ return;
+
+ for (uint32 srcY = 0; srcY < bitmap.height; ++srcY) {
+ const int destY = y + (int)srcY;
+ if (destY < 0 || destY >= screen.h)
+ continue;
+ for (uint32 srcX = 0; srcX < bitmap.width; ++srcX) {
+ const int destX = x + (int)srcX;
+ if (destX < 0 || destX >= screen.w)
+ continue;
+ const byte color = bitmap.pixels[srcY * bitmap.width + srcX];
+ if (color != 0)
+ *(byte *)screen.getBasePtr(destX, destY) = color;
+ }
+ }
+}
+
bool captureScreenBackdrop(const Graphics::Screen &screen, IndexedBitmap &bitmap) {
if (screen.w <= 0 || screen.h <= 0 || screen.format.bytesPerPixel != 1)
return false;
@@ -1440,6 +1470,11 @@ Common::Error Flow::run() {
_engine.clearCurrentSaveRoomState();
_engine.clearPendingLoadedDialogueStateBlob();
_engine.getScript()->resetRuntimeState();
+ if (_engine.isDemo()) {
+ error = runDemoIntroduction();
+ if (error.getCode() != Common::kNoError)
+ return error;
+ }
error = runQuickTips();
if (error.getCode() != Common::kNoError)
return error;
@@ -1633,29 +1668,6 @@ Common::Error Flow::runDemoEnding() {
if (!_engine.playMusic(kDemoEndingMusicPath))
return Common::kReadingFailed;
- auto fadePalette = [&](const byte *palette, float from, float to) -> Common::Error {
- const float step = from < to ? kPaletteFadeStep : -kPaletteFadeStep;
- for (float brightness = from; step > 0.0f ? brightness < to : brightness > to;
- brightness += step) {
- setScaledPalette(*screen, palette, brightness);
- screen->makeAllDirty();
- screen->update();
-
- const uint32 nextTick = g_system->getMillis() + kPaletteFadeTickMs;
- while ((int32)(nextTick - g_system->getMillis()) > 0) {
- Common::Error eventError = Common::kNoError;
- if (pumpTransitionEvents(eventError))
- return eventError;
- g_system->delayMillis(1);
- }
- }
-
- setScaledPalette(*screen, palette, to);
- screen->makeAllDirty();
- screen->update();
- return Common::kNoError;
- };
-
for (uint i = 0; i < ARRAYSIZE(kDemoEndingScreenNumbers) && !_engine.shouldQuit(); ++i) {
const int screenNumber = kDemoEndingScreenNumbers[i];
const Common::String bitmapPath = Common::String::format(
@@ -1713,6 +1725,79 @@ Common::Error Flow::runDemoEnding() {
return Common::kNoError;
}
+Common::Error Flow::runDemoIntroduction() {
+ Graphics::Screen *screen = _engine.getScreen();
+ ResourceManager *resources = _engine.getResources();
+ EntityManager *entityManager = _engine.getRuntimeEntities();
+ const Art *art = _engine.getArt();
+ const CftFontResource *fontResource = findStartupFontByName(_engine, "TEXTFONT");
+ if (!screen || !resources || !art || !fontResource)
+ return Common::kReadingFailed;
+
+ IndexedBitmap bitmap;
+ byte palette[256 * 3];
+ if (!loadBitmapResource(*resources, kDemoIntroductionBitmapPath, bitmap) ||
+ !loadPaletteResource(*resources, kDemoIntroductionPalettePath, palette)) {
+ return Common::kReadingFailed;
+ }
+
+ HarvesterCftFont font(*fontResource);
+ if (!font.isValid())
+ return Common::kReadingFailed;
+ debugC(1, kDebugGeneral,
+ "Harvester: showing DOS demo introduction for %u ms",
+ kDemoIntroductionDurationMs);
+
+ if (entityManager)
+ entityManager->hideCursor();
+ setScaledPalette(*screen, palette, kPaletteBrightnessBlack);
+ screen->fillRect(Common::Rect(0, 0, screen->w, screen->h), 0);
+ blitBitmap(*screen, bitmap, 0, 0);
+ blitTransparentBitmap(*screen, art->getLogoBitmap(), 160, 0);
+ drawWrappedText(*screen, font, kDemoIntroductionText, 90, 200, 420, 0xce,
+ kNativeIdentTextLineSpacing);
+ screen->makeAllDirty();
+ screen->update();
+
+ if (!_engine.playMusic(kDemoIntroductionMusicPath))
+ return Common::kReadingFailed;
+ Common::Error fadeError = fadePalette(
+ palette, kPaletteBrightnessBlack, kPaletteBrightnessFull);
+ if (fadeError.getCode() != Common::kNoError)
+ return fadeError;
+
+ const uint32 deadline = g_system->getMillis() + kDemoIntroductionDurationMs;
+ bool skipIntroduction = false;
+ while (!skipIntroduction && !_engine.shouldQuit() &&
+ (int32)(deadline - g_system->getMillis()) > 0) {
+ Common::Event event;
+ while (g_system->getEventManager()->pollEvent(event)) {
+ Common::Error eventError = Common::kNoError;
+ if (handleSystemEvent(event, eventError))
+ return eventError;
+ if (event.type == Common::EVENT_LBUTTONDOWN ||
+ event.type == Common::EVENT_RBUTTONDOWN ||
+ (event.type == Common::EVENT_KEYDOWN &&
+ (event.kbd.keycode == Common::KEYCODE_SPACE ||
+ event.kbd.keycode == Common::KEYCODE_ESCAPE ||
+ event.kbd.keycode == Common::KEYCODE_RETURN))) {
+ skipIntroduction = true;
+ }
+ }
+ g_system->delayMillis(1);
+ }
+
+ fadeError = fadePalette(palette, kPaletteBrightnessFull, kPaletteBrightnessBlack);
+ if (fadeError.getCode() != Common::kNoError)
+ return fadeError;
+ _engine.stopMusic();
+
+ FstPlayer fstPlayer(_engine);
+ if (!fstPlayer.play(kDemoIntroductionFstPath))
+ return Common::kReadingFailed;
+ return Common::kNoError;
+}
+
Common::Error Flow::runMainMenuStub() {
return _menu.runMainMenuStub(*this);
}
@@ -2404,6 +2489,35 @@ Common::Error Flow::fadeInRoomScene(const byte *palette, float targetBrightness)
return Common::kNoError;
}
+Common::Error Flow::fadePalette(const byte *palette, float fromBrightness,
+ float toBrightness) {
+ Graphics::Screen *screen = _engine.getScreen();
+ if (!screen || !palette)
+ return Common::kNoError;
+
+ const float step = fromBrightness < toBrightness ? kPaletteFadeStep : -kPaletteFadeStep;
+ for (float brightness = fromBrightness;
+ step > 0.0f ? brightness < toBrightness : brightness > toBrightness;
+ brightness += step) {
+ setScaledPalette(*screen, palette, brightness);
+ screen->makeAllDirty();
+ screen->update();
+
+ const uint32 nextTick = g_system->getMillis() + kPaletteFadeTickMs;
+ while ((int32)(nextTick - g_system->getMillis()) > 0) {
+ Common::Error eventError = Common::kNoError;
+ if (pumpTransitionEvents(eventError))
+ return eventError;
+ g_system->delayMillis(1);
+ }
+ }
+
+ setScaledPalette(*screen, palette, toBrightness);
+ screen->makeAllDirty();
+ screen->update();
+ return Common::kNoError;
+}
+
bool Flow::pumpTransitionEvents(Common::Error &result) {
Common::Event event;
while (g_system->getEventManager()->pollEvent(event)) {
diff --git a/engines/harvester/flow.h b/engines/harvester/flow.h
index fa3a9944e43..d2d62d189d1 100644
--- a/engines/harvester/flow.h
+++ b/engines/harvester/flow.h
@@ -64,6 +64,7 @@ private:
bool loadQuickTips();
bool loadMenuItems();
Common::Error runQuickTips();
+ Common::Error runDemoIntroduction();
Common::Error runDemoEnding();
Common::Error runMainMenuStub();
Common::Error runRoomMenuStub(const IndexedBitmap &backdrop, const byte *palette,
@@ -80,6 +81,7 @@ private:
Common::Error beginRoomSetupTransition();
Common::Error waitForRoomSetupTransitionHold();
Common::Error fadeInRoomScene(const byte *palette, float targetBrightness);
+ Common::Error fadePalette(const byte *palette, float fromBrightness, float toBrightness);
bool pumpTransitionEvents(Common::Error &result);
void executeStartupAudioCommands(const Common::Array<AudioCommand> &commands);
void queueDialogueInteraction(const InteractionResult &interaction);
Commit: e8061ec6028dcaef91fed6eb96f916df2bcedf14
https://github.com/scummvm/scummvm/commit/e8061ec6028dcaef91fed6eb96f916df2bcedf14
Author: Alex Bevilacqua (alex at alexbevi.com)
Date: 2026-07-11T23:13:41+03:00
Commit Message:
HARVESTER: Hide CD prompts for the DOS demo
Assisted-by: Codex:gpt-5.6sol
Changed paths:
engines/harvester/detection_tables.h
diff --git a/engines/harvester/detection_tables.h b/engines/harvester/detection_tables.h
index 1a6f84825b2..6cde08153c3 100644
--- a/engines/harvester/detection_tables.h
+++ b/engines/harvester/detection_tables.h
@@ -43,7 +43,7 @@ const ADGameDescription gameDescriptions[] = {
Common::EN_ANY,
Common::kPlatformDOS,
ADGF_DEMO | ADGF_UNSTABLE,
- GUIO2(GAMEOPTION_GORE, GAMEOPTION_SHOW_CD_CHANGE_PROMPTS)
+ GUIO1(GAMEOPTION_GORE)
},
AD_TABLE_END_MARKER
Commit: b749cfbefcd696d9a5473fa45a544aa7be1c4c45
https://github.com/scummvm/scummvm/commit/b749cfbefcd696d9a5473fa45a544aa7be1c4c45
Author: Alex Bevilacqua (alex at alexbevi.com)
Date: 2026-07-11T23:13:41+03:00
Commit Message:
HARVESTER: Run room entry actions after closeups
Changed paths:
engines/harvester/room.cpp
diff --git a/engines/harvester/room.cpp b/engines/harvester/room.cpp
index ef1be152280..9aec3c91521 100644
--- a/engines/harvester/room.cpp
+++ b/engines/harvester/room.cpp
@@ -4086,6 +4086,9 @@ Common::Error RoomSystem::runRoomLoop(Flow &flow, const Common::String &targetNa
Common::Error exitError = runRoomExitCommands();
if (exitError.getCode() != Common::kNoError)
return exitError;
+ // Native closeup exits return through room_setup so the parent on-enter command runs.
+ if (canExitCloseupToParent)
+ flow.requestCloseupParentRestart();
return Common::kNoError;
}
Commit: 386868871dec35dcd9ed74d4ab9036837ec9c858
https://github.com/scummvm/scummvm/commit/386868871dec35dcd9ed74d4ab9036837ec9c858
Author: Alex Bevilacqua (alex at alexbevi.com)
Date: 2026-07-11T23:13:41+03:00
Commit Message:
HARVESTER: Enforce scripted player pauses
Changed paths:
engines/harvester/room.cpp
diff --git a/engines/harvester/room.cpp b/engines/harvester/room.cpp
index 9aec3c91521..328f6b1093d 100644
--- a/engines/harvester/room.cpp
+++ b/engines/harvester/room.cpp
@@ -3834,7 +3834,7 @@ Common::Error RoomSystem::runRoomLoop(Flow &flow, const Common::String &targetNa
}
Script *script = _engine.getScript();
if (!showingInspectText && !isPlayerCombatLocked() && script && playerState.entity &&
- script->getPlayerCurrentHitPoints() > 0) {
+ script->getPlayerCurrentHitPoints() > 0 && !script->isPlayerControlPaused()) {
playerState.hasMoveTarget = false;
playerState.turnActive = false;
playerState.turnTargetFacing = -1;
@@ -4302,10 +4302,11 @@ Common::Error RoomSystem::runRoomLoop(Flow &flow, const Common::String &targetNa
break;
}
- const bool playerCanAct =
- _engine.getScript() &&
- _engine.getScript()->getPlayerCurrentHitPoints() > 0;
- if (!playerCanAct && (moveLeft || moveRight || moveUp || moveDown ||
+ Script *script = _engine.getScript();
+ const bool playerAlive = script && script->getPlayerCurrentHitPoints() > 0;
+ const bool playerControlPaused = script && script->isPlayerControlPaused();
+ const bool playerCanAct = playerAlive && !playerControlPaused;
+ if (!playerAlive && (moveLeft || moveRight || moveUp || moveDown ||
playerState.hasMoveTarget || playerState.turnActive ||
playerState.attackActive || playerState.hitActive)) {
attackModifierHeld = false;
@@ -4313,23 +4314,25 @@ Common::Error RoomSystem::runRoomLoop(Flow &flow, const Common::String &targetNa
}
bool playerAdvancedThisFrame = false;
- Common::Error combatError = resolvePlayerAttackContact();
- if (combatError.getCode() != Common::kNoError)
- return combatError;
- if (flow.hasPendingMainMenuReturn())
- return Common::kNoError;
- if (!pendingRoomChange.empty()) {
- if (!stowCarriedRoomItemToInventory())
- return Common::kReadingFailed;
- break;
- }
- if (Player::updateAttackAnimationState(_engine, playerState)) {
- needsRedraw = true;
- }
- if (Player::updateHitAnimationState(
- _engine, scene.state, scene.sceneObjects, scene.sceneAnimations, playerState)) {
- playerAdvancedThisFrame = true;
- needsRedraw = true;
+ Common::Error combatError = Common::kNoError;
+ if (!playerControlPaused) {
+ combatError = resolvePlayerAttackContact();
+ if (combatError.getCode() != Common::kNoError)
+ return combatError;
+ if (flow.hasPendingMainMenuReturn())
+ return Common::kNoError;
+ if (!pendingRoomChange.empty()) {
+ if (!stowCarriedRoomItemToInventory())
+ return Common::kReadingFailed;
+ break;
+ }
+ if (Player::updateAttackAnimationState(_engine, playerState))
+ needsRedraw = true;
+ if (Player::updateHitAnimationState(
+ _engine, scene.state, scene.sceneObjects, scene.sceneAnimations, playerState)) {
+ playerAdvancedThisFrame = true;
+ needsRedraw = true;
+ }
}
const bool keyboardAttackRequested =
attackModifierHeld && (moveLeft || moveRight || moveUp || moveDown);
@@ -4359,7 +4362,7 @@ Common::Error RoomSystem::runRoomLoop(Flow &flow, const Common::String &targetNa
}
}
}
- if (!playerState.attackActive && !playerState.hitActive &&
+ if (playerCanAct && !playerState.attackActive && !playerState.hitActive &&
Player::updateTurnAnimationState(playerState)) {
playerAdvancedThisFrame = true;
needsRedraw = true;
Commit: fc689fc19977b7a37fbde4e63f13179f69332a35
https://github.com/scummvm/scummvm/commit/fc689fc19977b7a37fbde4e63f13179f69332a35
Author: Alex Bevilacqua (alex at alexbevi.com)
Date: 2026-07-11T23:13:41+03:00
Commit Message:
HARVESTER: Handle scripted weapon changes
Changed paths:
engines/harvester/room.cpp
engines/harvester/script.cpp
diff --git a/engines/harvester/room.cpp b/engines/harvester/room.cpp
index 328f6b1093d..4594a603330 100644
--- a/engines/harvester/room.cpp
+++ b/engines/harvester/room.cpp
@@ -1352,6 +1352,12 @@ Common::Error RoomSystem::runRoomLoop(Flow &flow, const Common::String &targetNa
scene.sceneObjects = updatedScene.sceneObjects;
scene.sceneAnimations = updatedScene.sceneAnimations;
scene.sceneRegions = updatedScene.sceneRegions;
+ const int playerCombatLoadout = script->getPlayerCombatLoadout();
+ if (playerState.entity && playerState.combatLoadout != playerCombatLoadout &&
+ !Player::syncCombatLoadoutVisual(
+ _engine, updatedState, playerState, playerCombatLoadout)) {
+ return false;
+ }
if (preservePaletteState) {
memcpy(scene.palette, previousPalette, sizeof(scene.palette));
scene.targetPaletteBrightness = previousPaletteBrightness;
diff --git a/engines/harvester/script.cpp b/engines/harvester/script.cpp
index e19e41b0a76..44e18150298 100644
--- a/engines/harvester/script.cpp
+++ b/engines/harvester/script.cpp
@@ -3184,6 +3184,13 @@ void Script::executeCommandChain(const Common::String &initialTag, const char *c
continue;
}
+ if (command->opcodeName.equalsIgnoreCase("PC_CHANGE_WEAPON")) {
+ noteCurrentRoomVisualMutation(
+ setPlayerCombatLoadout(parseAsciiIntOrZero(command->arg1)), true);
+ currentTag = command->arg4;
+ continue;
+ }
+
if (command->opcodeName.equalsIgnoreCase("KILL_PC")) {
if (requestPlayerDeath)
*requestPlayerDeath = true;
@@ -3367,6 +3374,7 @@ bool Script::probePickupBlockingCommandChain(const Common::String &initialTag,
command->opcodeName.equalsIgnoreCase("PAUSE_PC") ||
command->opcodeName.equalsIgnoreCase("RESUME_PC") ||
command->opcodeName.equalsIgnoreCase("CHANGE_CD") ||
+ command->opcodeName.equalsIgnoreCase("PC_CHANGE_WEAPON") ||
command->opcodeName.equalsIgnoreCase("PC_GOTO_XZ") ||
command->opcodeName.equalsIgnoreCase("CHANGE_LIGHTING") ||
command->opcodeName.equalsIgnoreCase("EXIT_CLOSEUP") ||
@@ -3427,6 +3435,7 @@ bool Script::hasActionableCommandChain(const Common::String &initialTag) const {
command->opcodeName.equalsIgnoreCase("PAUSE_PC") ||
command->opcodeName.equalsIgnoreCase("RESUME_PC") ||
command->opcodeName.equalsIgnoreCase("CHANGE_CD") ||
+ command->opcodeName.equalsIgnoreCase("PC_CHANGE_WEAPON") ||
command->opcodeName.equalsIgnoreCase("PC_GOTO_XZ") ||
command->opcodeName.equalsIgnoreCase("CHANGE_LIGHTING") ||
command->opcodeName.equalsIgnoreCase("EXIT_CLOSEUP") ||
Commit: bf124c1272da656de0167aa056b830bb477a2eaa
https://github.com/scummvm/scummvm/commit/bf124c1272da656de0167aa056b830bb477a2eaa
Author: Alex Bevilacqua (alex at alexbevi.com)
Date: 2026-07-11T23:13:41+03:00
Commit Message:
HARVESTER: Align scripted entities to player depth
Changed paths:
engines/harvester/room_interaction.cpp
engines/harvester/script.cpp
engines/harvester/script.h
diff --git a/engines/harvester/room_interaction.cpp b/engines/harvester/room_interaction.cpp
index e0809b362e3..8815e46a97a 100644
--- a/engines/harvester/room_interaction.cpp
+++ b/engines/harvester/room_interaction.cpp
@@ -30,6 +30,7 @@
#include "harvester/fst_player.h"
#include "harvester/harvester.h"
#include "harvester/resources.h"
+#include "harvester/runtime_entity.h"
namespace Harvester {
@@ -244,6 +245,19 @@ Common::Error RoomInteractionProcessor::handleInteractionResult(const Interactio
if (interaction.requestPlayerGotoXZ)
_callbacks.applyPlayerGotoXZ(interaction.playerGotoX, interaction.playerGotoZ);
+ if (!interaction.moveEntityToPlayerZName.empty()) {
+ EntityManager *entityManager = _engine.getRuntimeEntities();
+ Entity *entity = entityManager
+ ? entityManager->findSceneEntityByName(interaction.moveEntityToPlayerZName)
+ : nullptr;
+ if (entity && _playerState.entity) {
+ entity->setPosition(entity->getX(), entity->getY(), _playerState.entity->getZ());
+ entityManager->reinsertSceneEntity(entity);
+ debugC(1, kDebugPlayer, "Harvester: MOVE_BM2PCZ entity='%s' z=%.2f",
+ interaction.moveEntityToPlayerZName.c_str(), entity->getZ());
+ }
+ }
+
if (interaction.lightingCommand != kStartupLightingCommandNone) {
Common::Error lightingError = _callbacks.applyLightingCommand(interaction.lightingCommand);
if (lightingError.getCode() != Common::kNoError)
@@ -350,6 +364,7 @@ bool hasRoomEntryInteraction(const InteractionResult &interaction) {
interaction.requestCloseupExit ||
interaction.requestRoomRestart ||
interaction.requestPlayerDeath ||
+ !interaction.moveEntityToPlayerZName.empty() ||
interaction.requestPlayerGotoXZ ||
interaction.lightingCommand != kStartupLightingCommandNone ||
!interaction.modalText.value.empty() ||
diff --git a/engines/harvester/script.cpp b/engines/harvester/script.cpp
index 44e18150298..45a3a13c1e0 100644
--- a/engines/harvester/script.cpp
+++ b/engines/harvester/script.cpp
@@ -1704,7 +1704,8 @@ bool Script::executeRoomEnterCommands(const Common::String &roomName,
&result.cutscenePath, &result.deathFlicPath, &result.requestMainMenu,
&result.requestDemoEnding, &result.cdChangeDisc,
&result.dialogueNpcName, &result.dialogueContinuationTag, &result.continuationTag,
- &result.modalText, &result.lightingCommand, &result.requestPlayerGotoXZ,
+ &result.modalText, &result.lightingCommand, &result.moveEntityToPlayerZName,
+ &result.requestPlayerGotoXZ,
&result.playerGotoX, &result.playerGotoZ,
&result.requestPlayerDeath, &result.playerDeathDamageType,
&result.mutatedRuntimeState, &result.visualRuntimeStateChanged,
@@ -1726,6 +1727,7 @@ bool Script::executeRoomExitCommands(const Common::String &roomName,
nullptr, nullptr, &result.requestDemoEnding, &result.cdChangeDisc,
&result.dialogueNpcName, &result.dialogueContinuationTag,
&result.continuationTag, &result.modalText, &result.lightingCommand,
+ &result.moveEntityToPlayerZName,
&result.requestPlayerGotoXZ, &result.playerGotoX, &result.playerGotoZ,
&result.requestPlayerDeath, &result.playerDeathDamageType,
&result.mutatedRuntimeState, &result.visualRuntimeStateChanged,
@@ -1764,7 +1766,8 @@ bool Script::resolveObjectInteraction(const ObjectRecord &object, InteractionRes
&result.cutscenePath, &result.deathFlicPath, &result.requestMainMenu,
&result.requestDemoEnding, &result.cdChangeDisc,
&result.dialogueNpcName, &result.dialogueContinuationTag, &result.continuationTag,
- &result.modalText, &result.lightingCommand, &result.requestPlayerGotoXZ,
+ &result.modalText, &result.lightingCommand, &result.moveEntityToPlayerZName,
+ &result.requestPlayerGotoXZ,
&result.playerGotoX, &result.playerGotoZ,
&result.requestPlayerDeath, &result.playerDeathDamageType,
&result.mutatedRuntimeState, &result.visualRuntimeStateChanged,
@@ -1774,7 +1777,8 @@ bool Script::resolveObjectInteraction(const ObjectRecord &object, InteractionRes
!result.deathFlicPath.empty() || result.requestMainMenu || result.requestDemoEnding ||
!result.dialogueNpcName.empty() || !result.musicPath.empty() || !result.audioCommands.empty() ||
result.cdChangeDisc > 0 || !result.continuationTag.empty() || !result.modalText.value.empty() ||
- result.lightingCommand != kStartupLightingCommandNone || result.requestPlayerGotoXZ ||
+ result.lightingCommand != kStartupLightingCommandNone ||
+ !result.moveEntityToPlayerZName.empty() || result.requestPlayerGotoXZ ||
result.requestPlayerDeath ||
result.requestCloseupExit || result.mutatedRuntimeState ||
hasActionableCommandChain(object.actionTag);
@@ -1793,7 +1797,8 @@ bool Script::resolveRegionInteraction(const RegionRecord ®ion, InteractionRes
&result.cutscenePath, &result.deathFlicPath, &result.requestMainMenu,
&result.requestDemoEnding, &result.cdChangeDisc,
&result.dialogueNpcName, &result.dialogueContinuationTag, &result.continuationTag,
- &result.modalText, &result.lightingCommand, &result.requestPlayerGotoXZ,
+ &result.modalText, &result.lightingCommand, &result.moveEntityToPlayerZName,
+ &result.requestPlayerGotoXZ,
&result.playerGotoX, &result.playerGotoZ,
&result.requestPlayerDeath, &result.playerDeathDamageType,
&result.mutatedRuntimeState, &result.visualRuntimeStateChanged,
@@ -1802,7 +1807,8 @@ bool Script::resolveRegionInteraction(const RegionRecord ®ion, InteractionRes
!result.deathFlicPath.empty() || result.requestMainMenu || result.requestDemoEnding ||
!result.dialogueNpcName.empty() || !result.musicPath.empty() || !result.audioCommands.empty() ||
result.cdChangeDisc > 0 || !result.continuationTag.empty() || !result.modalText.value.empty() ||
- result.lightingCommand != kStartupLightingCommandNone || result.requestPlayerGotoXZ ||
+ result.lightingCommand != kStartupLightingCommandNone ||
+ !result.moveEntityToPlayerZName.empty() || result.requestPlayerGotoXZ ||
result.requestPlayerDeath ||
result.requestCloseupExit || result.mutatedRuntimeState ||
hasActionableCommandChain(region.actionTag);
@@ -1829,7 +1835,8 @@ bool Script::resolveUseItemInteraction(const Common::String &itemName, const Obj
&result.cutscenePath, &result.deathFlicPath, &result.requestMainMenu,
&result.requestDemoEnding, &result.cdChangeDisc,
&result.dialogueNpcName, &result.dialogueContinuationTag, &result.continuationTag,
- &result.modalText, &result.lightingCommand, &result.requestPlayerGotoXZ,
+ &result.modalText, &result.lightingCommand, &result.moveEntityToPlayerZName,
+ &result.requestPlayerGotoXZ,
&result.playerGotoX, &result.playerGotoZ,
&result.requestPlayerDeath, &result.playerDeathDamageType,
&result.mutatedRuntimeState, &result.visualRuntimeStateChanged,
@@ -1871,7 +1878,8 @@ bool Script::executeActionTag(const Common::String &tag, InteractionResult &resu
&result.cutscenePath, &result.deathFlicPath, &result.requestMainMenu,
&result.requestDemoEnding, &result.cdChangeDisc,
&result.dialogueNpcName, &result.dialogueContinuationTag, &result.continuationTag,
- &result.modalText, &result.lightingCommand, &result.requestPlayerGotoXZ,
+ &result.modalText, &result.lightingCommand, &result.moveEntityToPlayerZName,
+ &result.requestPlayerGotoXZ,
&result.playerGotoX, &result.playerGotoZ,
&result.requestPlayerDeath, &result.playerDeathDamageType,
&result.mutatedRuntimeState, &result.visualRuntimeStateChanged,
@@ -1881,7 +1889,8 @@ bool Script::executeActionTag(const Common::String &tag, InteractionResult &resu
!result.deathFlicPath.empty() || result.requestMainMenu || result.requestDemoEnding ||
!result.dialogueNpcName.empty() || !result.musicPath.empty() || !result.audioCommands.empty() ||
result.cdChangeDisc > 0 || !result.continuationTag.empty() || !result.modalText.value.empty() ||
- result.lightingCommand != kStartupLightingCommandNone || result.requestPlayerGotoXZ ||
+ result.lightingCommand != kStartupLightingCommandNone ||
+ !result.moveEntityToPlayerZName.empty() || result.requestPlayerGotoXZ ||
result.requestPlayerDeath ||
result.requestCloseupExit || result.mutatedRuntimeState ||
hasActionableCommandChain(tag);
@@ -1910,7 +1919,8 @@ bool Script::executeTimerAction(const Common::String &timerName, InteractionResu
&result.cutscenePath, &result.deathFlicPath, &result.requestMainMenu,
&result.requestDemoEnding, &result.cdChangeDisc,
&result.dialogueNpcName, &result.dialogueContinuationTag, &result.continuationTag,
- &result.modalText, &result.lightingCommand, &result.requestPlayerGotoXZ,
+ &result.modalText, &result.lightingCommand, &result.moveEntityToPlayerZName,
+ &result.requestPlayerGotoXZ,
&result.playerGotoX, &result.playerGotoZ,
&result.requestPlayerDeath, &result.playerDeathDamageType,
&result.mutatedRuntimeState, &result.visualRuntimeStateChanged,
@@ -1920,7 +1930,8 @@ bool Script::executeTimerAction(const Common::String &timerName, InteractionResu
!result.deathFlicPath.empty() || result.requestMainMenu || result.requestDemoEnding ||
!result.dialogueNpcName.empty() || !result.musicPath.empty() || !result.audioCommands.empty() ||
result.cdChangeDisc > 0 || !result.continuationTag.empty() || !result.modalText.value.empty() ||
- result.lightingCommand != kStartupLightingCommandNone || result.requestPlayerGotoXZ ||
+ result.lightingCommand != kStartupLightingCommandNone ||
+ !result.moveEntityToPlayerZName.empty() || result.requestPlayerGotoXZ ||
result.requestPlayerDeath ||
result.requestCloseupExit || result.mutatedRuntimeState ||
hasActionableCommandChain(timer->arg2);
@@ -2755,7 +2766,8 @@ void Script::executeCommandChain(const Common::String &initialTag, const char *c
int *cdChangeDisc,
Common::String *dialogueNpcName, Common::String *dialogueContinuationTag,
Common::String *continuationTag, ResolvedText *modalText,
- StartupLightingCommand *lightingCommand, bool *requestPlayerGotoXZ,
+ StartupLightingCommand *lightingCommand, Common::String *moveEntityToPlayerZName,
+ bool *requestPlayerGotoXZ,
int *playerGotoX, int *playerGotoZ,
bool *requestPlayerDeath, int *playerDeathDamageType,
bool *mutatedRuntimeState, bool *visualRuntimeStateChanged,
@@ -2798,6 +2810,7 @@ void Script::executeCommandChain(const Common::String &initialTag, const char *c
(continuationTag && !continuationTag->empty()) ||
(modalText && !modalText->value.empty()) ||
(lightingCommand && *lightingCommand != kStartupLightingCommandNone) ||
+ (moveEntityToPlayerZName && !moveEntityToPlayerZName->empty()) ||
(requestCloseupExit && *requestCloseupExit);
};
@@ -3056,7 +3069,8 @@ void Script::executeCommandChain(const Common::String &initialTag, const char *c
musicPath, audioCommands, nextRoomName, roomTransition, cutscenePath,
deathFlicPath, requestMainMenu, requestDemoEnding, cdChangeDisc,
dialogueNpcName, dialogueContinuationTag,
- continuationTag, modalText, lightingCommand, requestPlayerGotoXZ,
+ continuationTag, modalText, lightingCommand, moveEntityToPlayerZName,
+ requestPlayerGotoXZ,
playerGotoX, playerGotoZ, requestPlayerDeath, playerDeathDamageType,
mutatedRuntimeState, visualRuntimeStateChanged,
previousTimerRecords, requestCloseupExit);
@@ -3191,6 +3205,13 @@ void Script::executeCommandChain(const Common::String &initialTag, const char *c
continue;
}
+ if (command->opcodeName.equalsIgnoreCase("MOVE_BM2PCZ")) {
+ if (moveEntityToPlayerZName)
+ *moveEntityToPlayerZName = command->arg1;
+ currentTag = command->arg4;
+ continue;
+ }
+
if (command->opcodeName.equalsIgnoreCase("KILL_PC")) {
if (requestPlayerDeath)
*requestPlayerDeath = true;
@@ -3374,6 +3395,7 @@ bool Script::probePickupBlockingCommandChain(const Common::String &initialTag,
command->opcodeName.equalsIgnoreCase("PAUSE_PC") ||
command->opcodeName.equalsIgnoreCase("RESUME_PC") ||
command->opcodeName.equalsIgnoreCase("CHANGE_CD") ||
+ command->opcodeName.equalsIgnoreCase("MOVE_BM2PCZ") ||
command->opcodeName.equalsIgnoreCase("PC_CHANGE_WEAPON") ||
command->opcodeName.equalsIgnoreCase("PC_GOTO_XZ") ||
command->opcodeName.equalsIgnoreCase("CHANGE_LIGHTING") ||
@@ -3435,6 +3457,7 @@ bool Script::hasActionableCommandChain(const Common::String &initialTag) const {
command->opcodeName.equalsIgnoreCase("PAUSE_PC") ||
command->opcodeName.equalsIgnoreCase("RESUME_PC") ||
command->opcodeName.equalsIgnoreCase("CHANGE_CD") ||
+ command->opcodeName.equalsIgnoreCase("MOVE_BM2PCZ") ||
command->opcodeName.equalsIgnoreCase("PC_CHANGE_WEAPON") ||
command->opcodeName.equalsIgnoreCase("PC_GOTO_XZ") ||
command->opcodeName.equalsIgnoreCase("CHANGE_LIGHTING") ||
diff --git a/engines/harvester/script.h b/engines/harvester/script.h
index bc3bb57d75d..f46ef29ed9d 100644
--- a/engines/harvester/script.h
+++ b/engines/harvester/script.h
@@ -293,6 +293,7 @@ struct InteractionResult {
Common::String dialogueNpcName;
Common::String dialogueContinuationTag;
Common::String continuationTag;
+ Common::String moveEntityToPlayerZName;
ResolvedText modalText;
Common::Array<AudioCommand> audioCommands;
Common::Array<TimerRecord> previousTimerRecords;
@@ -465,7 +466,8 @@ private:
int *cdChangeDisc,
Common::String *dialogueNpcName, Common::String *dialogueContinuationTag,
Common::String *continuationTag, ResolvedText *modalText,
- StartupLightingCommand *lightingCommand, bool *requestPlayerGotoXZ,
+ StartupLightingCommand *lightingCommand, Common::String *moveEntityToPlayerZName,
+ bool *requestPlayerGotoXZ,
int *playerGotoX, int *playerGotoZ,
bool *requestPlayerDeath, int *playerDeathDamageType,
bool *mutatedRuntimeState, bool *visualRuntimeStateChanged,
Commit: 4ba44d4f42043b59f7402845a47525ef11b8c2f3
https://github.com/scummvm/scummvm/commit/4ba44d4f42043b59f7402845a47525ef11b8c2f3
Author: Alex Bevilacqua (alex at alexbevi.com)
Date: 2026-07-11T23:13:41+03:00
Commit Message:
HARVESTER: Handle scripted player depth moves
Changed paths:
engines/harvester/room.cpp
engines/harvester/room_interaction.cpp
engines/harvester/script.cpp
engines/harvester/script.h
diff --git a/engines/harvester/room.cpp b/engines/harvester/room.cpp
index 4594a603330..fd8952d8189 100644
--- a/engines/harvester/room.cpp
+++ b/engines/harvester/room.cpp
@@ -1484,6 +1484,7 @@ Common::Error RoomSystem::runRoomLoop(Flow &flow, const Common::String &targetNa
return !didTransition &&
!interaction.mutatedRuntimeState &&
!interaction.requestPlayerGotoXZ &&
+ !interaction.requestPlayerGotoZ &&
interaction.lightingCommand == kStartupLightingCommandNone &&
!interaction.requestMainMenu &&
!interaction.requestDemoEnding &&
@@ -1650,7 +1651,8 @@ Common::Error RoomSystem::runRoomLoop(Flow &flow, const Common::String &targetNa
!exitInteraction.dialogueContinuationTag.empty() ||
!exitInteraction.modalText.value.empty() ||
exitInteraction.lightingCommand != kStartupLightingCommandNone ||
- exitInteraction.requestPlayerGotoXZ) {
+ exitInteraction.requestPlayerGotoXZ ||
+ exitInteraction.requestPlayerGotoZ) {
debugC(1, kDebugRoom,
"Harvester: room exit command for '%s' produced unsupported deferred output; preserving accumulated state",
scene.state.roomName.c_str());
diff --git a/engines/harvester/room_interaction.cpp b/engines/harvester/room_interaction.cpp
index 8815e46a97a..b41f6c9f52e 100644
--- a/engines/harvester/room_interaction.cpp
+++ b/engines/harvester/room_interaction.cpp
@@ -244,6 +244,8 @@ Common::Error RoomInteractionProcessor::handleInteractionResult(const Interactio
if (interaction.requestPlayerGotoXZ)
_callbacks.applyPlayerGotoXZ(interaction.playerGotoX, interaction.playerGotoZ);
+ else if (interaction.requestPlayerGotoZ)
+ _callbacks.applyPlayerGotoXZ(_playerState.centerX, interaction.playerGotoZ);
if (!interaction.moveEntityToPlayerZName.empty()) {
EntityManager *entityManager = _engine.getRuntimeEntities();
@@ -366,6 +368,7 @@ bool hasRoomEntryInteraction(const InteractionResult &interaction) {
interaction.requestPlayerDeath ||
!interaction.moveEntityToPlayerZName.empty() ||
interaction.requestPlayerGotoXZ ||
+ interaction.requestPlayerGotoZ ||
interaction.lightingCommand != kStartupLightingCommandNone ||
!interaction.modalText.value.empty() ||
!interaction.dialogueNpcName.empty() ||
diff --git a/engines/harvester/script.cpp b/engines/harvester/script.cpp
index 45a3a13c1e0..f97f99f92a4 100644
--- a/engines/harvester/script.cpp
+++ b/engines/harvester/script.cpp
@@ -1705,7 +1705,7 @@ bool Script::executeRoomEnterCommands(const Common::String &roomName,
&result.requestDemoEnding, &result.cdChangeDisc,
&result.dialogueNpcName, &result.dialogueContinuationTag, &result.continuationTag,
&result.modalText, &result.lightingCommand, &result.moveEntityToPlayerZName,
- &result.requestPlayerGotoXZ,
+ &result.requestPlayerGotoXZ, &result.requestPlayerGotoZ,
&result.playerGotoX, &result.playerGotoZ,
&result.requestPlayerDeath, &result.playerDeathDamageType,
&result.mutatedRuntimeState, &result.visualRuntimeStateChanged,
@@ -1728,7 +1728,8 @@ bool Script::executeRoomExitCommands(const Common::String &roomName,
&result.dialogueNpcName, &result.dialogueContinuationTag,
&result.continuationTag, &result.modalText, &result.lightingCommand,
&result.moveEntityToPlayerZName,
- &result.requestPlayerGotoXZ, &result.playerGotoX, &result.playerGotoZ,
+ &result.requestPlayerGotoXZ, &result.requestPlayerGotoZ,
+ &result.playerGotoX, &result.playerGotoZ,
&result.requestPlayerDeath, &result.playerDeathDamageType,
&result.mutatedRuntimeState, &result.visualRuntimeStateChanged,
&result.previousTimerRecords, &result.requestCloseupExit);
@@ -1767,7 +1768,7 @@ bool Script::resolveObjectInteraction(const ObjectRecord &object, InteractionRes
&result.requestDemoEnding, &result.cdChangeDisc,
&result.dialogueNpcName, &result.dialogueContinuationTag, &result.continuationTag,
&result.modalText, &result.lightingCommand, &result.moveEntityToPlayerZName,
- &result.requestPlayerGotoXZ,
+ &result.requestPlayerGotoXZ, &result.requestPlayerGotoZ,
&result.playerGotoX, &result.playerGotoZ,
&result.requestPlayerDeath, &result.playerDeathDamageType,
&result.mutatedRuntimeState, &result.visualRuntimeStateChanged,
@@ -1779,6 +1780,7 @@ bool Script::resolveObjectInteraction(const ObjectRecord &object, InteractionRes
result.cdChangeDisc > 0 || !result.continuationTag.empty() || !result.modalText.value.empty() ||
result.lightingCommand != kStartupLightingCommandNone ||
!result.moveEntityToPlayerZName.empty() || result.requestPlayerGotoXZ ||
+ result.requestPlayerGotoZ ||
result.requestPlayerDeath ||
result.requestCloseupExit || result.mutatedRuntimeState ||
hasActionableCommandChain(object.actionTag);
@@ -1798,7 +1800,7 @@ bool Script::resolveRegionInteraction(const RegionRecord ®ion, InteractionRes
&result.requestDemoEnding, &result.cdChangeDisc,
&result.dialogueNpcName, &result.dialogueContinuationTag, &result.continuationTag,
&result.modalText, &result.lightingCommand, &result.moveEntityToPlayerZName,
- &result.requestPlayerGotoXZ,
+ &result.requestPlayerGotoXZ, &result.requestPlayerGotoZ,
&result.playerGotoX, &result.playerGotoZ,
&result.requestPlayerDeath, &result.playerDeathDamageType,
&result.mutatedRuntimeState, &result.visualRuntimeStateChanged,
@@ -1809,6 +1811,7 @@ bool Script::resolveRegionInteraction(const RegionRecord ®ion, InteractionRes
result.cdChangeDisc > 0 || !result.continuationTag.empty() || !result.modalText.value.empty() ||
result.lightingCommand != kStartupLightingCommandNone ||
!result.moveEntityToPlayerZName.empty() || result.requestPlayerGotoXZ ||
+ result.requestPlayerGotoZ ||
result.requestPlayerDeath ||
result.requestCloseupExit || result.mutatedRuntimeState ||
hasActionableCommandChain(region.actionTag);
@@ -1836,7 +1839,7 @@ bool Script::resolveUseItemInteraction(const Common::String &itemName, const Obj
&result.requestDemoEnding, &result.cdChangeDisc,
&result.dialogueNpcName, &result.dialogueContinuationTag, &result.continuationTag,
&result.modalText, &result.lightingCommand, &result.moveEntityToPlayerZName,
- &result.requestPlayerGotoXZ,
+ &result.requestPlayerGotoXZ, &result.requestPlayerGotoZ,
&result.playerGotoX, &result.playerGotoZ,
&result.requestPlayerDeath, &result.playerDeathDamageType,
&result.mutatedRuntimeState, &result.visualRuntimeStateChanged,
@@ -1879,7 +1882,7 @@ bool Script::executeActionTag(const Common::String &tag, InteractionResult &resu
&result.requestDemoEnding, &result.cdChangeDisc,
&result.dialogueNpcName, &result.dialogueContinuationTag, &result.continuationTag,
&result.modalText, &result.lightingCommand, &result.moveEntityToPlayerZName,
- &result.requestPlayerGotoXZ,
+ &result.requestPlayerGotoXZ, &result.requestPlayerGotoZ,
&result.playerGotoX, &result.playerGotoZ,
&result.requestPlayerDeath, &result.playerDeathDamageType,
&result.mutatedRuntimeState, &result.visualRuntimeStateChanged,
@@ -1891,6 +1894,7 @@ bool Script::executeActionTag(const Common::String &tag, InteractionResult &resu
result.cdChangeDisc > 0 || !result.continuationTag.empty() || !result.modalText.value.empty() ||
result.lightingCommand != kStartupLightingCommandNone ||
!result.moveEntityToPlayerZName.empty() || result.requestPlayerGotoXZ ||
+ result.requestPlayerGotoZ ||
result.requestPlayerDeath ||
result.requestCloseupExit || result.mutatedRuntimeState ||
hasActionableCommandChain(tag);
@@ -1920,7 +1924,7 @@ bool Script::executeTimerAction(const Common::String &timerName, InteractionResu
&result.requestDemoEnding, &result.cdChangeDisc,
&result.dialogueNpcName, &result.dialogueContinuationTag, &result.continuationTag,
&result.modalText, &result.lightingCommand, &result.moveEntityToPlayerZName,
- &result.requestPlayerGotoXZ,
+ &result.requestPlayerGotoXZ, &result.requestPlayerGotoZ,
&result.playerGotoX, &result.playerGotoZ,
&result.requestPlayerDeath, &result.playerDeathDamageType,
&result.mutatedRuntimeState, &result.visualRuntimeStateChanged,
@@ -1932,6 +1936,7 @@ bool Script::executeTimerAction(const Common::String &timerName, InteractionResu
result.cdChangeDisc > 0 || !result.continuationTag.empty() || !result.modalText.value.empty() ||
result.lightingCommand != kStartupLightingCommandNone ||
!result.moveEntityToPlayerZName.empty() || result.requestPlayerGotoXZ ||
+ result.requestPlayerGotoZ ||
result.requestPlayerDeath ||
result.requestCloseupExit || result.mutatedRuntimeState ||
hasActionableCommandChain(timer->arg2);
@@ -2767,7 +2772,7 @@ void Script::executeCommandChain(const Common::String &initialTag, const char *c
Common::String *dialogueNpcName, Common::String *dialogueContinuationTag,
Common::String *continuationTag, ResolvedText *modalText,
StartupLightingCommand *lightingCommand, Common::String *moveEntityToPlayerZName,
- bool *requestPlayerGotoXZ,
+ bool *requestPlayerGotoXZ, bool *requestPlayerGotoZ,
int *playerGotoX, int *playerGotoZ,
bool *requestPlayerDeath, int *playerDeathDamageType,
bool *mutatedRuntimeState, bool *visualRuntimeStateChanged,
@@ -3070,7 +3075,7 @@ void Script::executeCommandChain(const Common::String &initialTag, const char *c
deathFlicPath, requestMainMenu, requestDemoEnding, cdChangeDisc,
dialogueNpcName, dialogueContinuationTag,
continuationTag, modalText, lightingCommand, moveEntityToPlayerZName,
- requestPlayerGotoXZ,
+ requestPlayerGotoXZ, requestPlayerGotoZ,
playerGotoX, playerGotoZ, requestPlayerDeath, playerDeathDamageType,
mutatedRuntimeState, visualRuntimeStateChanged,
previousTimerRecords, requestCloseupExit);
@@ -3249,6 +3254,20 @@ void Script::executeCommandChain(const Common::String &initialTag, const char *c
continue;
}
+ if (command->opcodeName.equalsIgnoreCase("PC_GOTO_Z")) {
+ if (!requestPlayerGotoZ || !playerGotoZ) {
+ debug(1, "Harvester: deferred startup command '%s' for %s '%s' has no player-move context",
+ command->opcodeName.c_str(), contextLabel, contextName.c_str());
+ currentTag = command->arg4;
+ continue;
+ }
+
+ *requestPlayerGotoZ = true;
+ *playerGotoZ = parseAsciiIntOrZero(command->arg1);
+ currentTag = command->arg4;
+ continue;
+ }
+
if (command->opcodeName.equalsIgnoreCase("CHANGE_LIGHTING")) {
if (!lightingCommand) {
debug(1, "Harvester: deferred startup command '%s' for %s '%s' has no lighting context",
@@ -3398,6 +3417,7 @@ bool Script::probePickupBlockingCommandChain(const Common::String &initialTag,
command->opcodeName.equalsIgnoreCase("MOVE_BM2PCZ") ||
command->opcodeName.equalsIgnoreCase("PC_CHANGE_WEAPON") ||
command->opcodeName.equalsIgnoreCase("PC_GOTO_XZ") ||
+ command->opcodeName.equalsIgnoreCase("PC_GOTO_Z") ||
command->opcodeName.equalsIgnoreCase("CHANGE_LIGHTING") ||
command->opcodeName.equalsIgnoreCase("EXIT_CLOSEUP") ||
command->opcodeName.equalsIgnoreCase("CLOSEUP") ||
@@ -3460,6 +3480,7 @@ bool Script::hasActionableCommandChain(const Common::String &initialTag) const {
command->opcodeName.equalsIgnoreCase("MOVE_BM2PCZ") ||
command->opcodeName.equalsIgnoreCase("PC_CHANGE_WEAPON") ||
command->opcodeName.equalsIgnoreCase("PC_GOTO_XZ") ||
+ command->opcodeName.equalsIgnoreCase("PC_GOTO_Z") ||
command->opcodeName.equalsIgnoreCase("CHANGE_LIGHTING") ||
command->opcodeName.equalsIgnoreCase("EXIT_CLOSEUP") ||
command->opcodeName.equalsIgnoreCase("CLOSEUP") ||
diff --git a/engines/harvester/script.h b/engines/harvester/script.h
index f46ef29ed9d..0d253f7375f 100644
--- a/engines/harvester/script.h
+++ b/engines/harvester/script.h
@@ -305,6 +305,7 @@ struct InteractionResult {
int playerGotoZ = 0;
bool requestPlayerDeath = false;
bool requestPlayerGotoXZ = false;
+ bool requestPlayerGotoZ = false;
bool requestRoomRestart = false;
Common::String roomRestartTargetName;
bool requestCloseupExit = false;
@@ -467,7 +468,7 @@ private:
Common::String *dialogueNpcName, Common::String *dialogueContinuationTag,
Common::String *continuationTag, ResolvedText *modalText,
StartupLightingCommand *lightingCommand, Common::String *moveEntityToPlayerZName,
- bool *requestPlayerGotoXZ,
+ bool *requestPlayerGotoXZ, bool *requestPlayerGotoZ,
int *playerGotoX, int *playerGotoZ,
bool *requestPlayerDeath, int *playerDeathDamageType,
bool *mutatedRuntimeState, bool *visualRuntimeStateChanged,
Commit: a8055d338ab2318b080b1b5d0b9689c33c93af3f
https://github.com/scummvm/scummvm/commit/a8055d338ab2318b080b1b5d0b9689c33c93af3f
Author: Alex Bevilacqua (alex at alexbevi.com)
Date: 2026-07-11T23:13:41+03:00
Commit Message:
HARVESTER: Match DOS demo menu prompts
Changed paths:
engines/harvester/menu.cpp
diff --git a/engines/harvester/menu.cpp b/engines/harvester/menu.cpp
index 7b5d6fea140..29cfd3fd7c9 100644
--- a/engines/harvester/menu.cpp
+++ b/engines/harvester/menu.cpp
@@ -257,6 +257,11 @@ static bool loadRawMenuValue(const Common::Array<byte> &data, const char *key, C
static bool loadMenuTextConfig(HarvesterEngine &engine, RoomMenuTextConfig &config) {
config = RoomMenuTextConfig();
+ if (engine.isDemo()) {
+ config.clickLabel = "Click";
+ config.newGamePrompt = "START A NEW GAME?";
+ config.quitGamePrompt = " QUIT HARVESTER?";
+ }
config.optionItems.resize(kOptionsItemCount);
config.optionItems[0] = "SOUND FX";
config.optionItems[1] = "MUSIC";
More information about the Scummvm-git-logs
mailing list