[Scummvm-git-logs] scummvm master -> d8f8194eb3a8e0dba65a32809fc963da098709f4

alexbevi noreply at scummvm.org
Thu Oct 22 00:51:32 UTC 2026


This automated email contains information about 3 new commits which have been
pushed to the 'scummvm' repo located at https://api.github.com/repos/scummvm/scummvm .

Summary:
4c0de4b66c HARVESTER: Improve monster attack contact resolution logic
8adb9f275a HARVESTER: Add bounds checking to evasive movements
d8f8194eb3 HARVESTER: Improve goto_room debug command to support disc 3 resources


Commit: 4c0de4b66c9790f4e98b87428b1173f19783fa9f
    https://github.com/scummvm/scummvm/commit/4c0de4b66c9790f4e98b87428b1173f19783fa9f
Author: Alex Bevilacqua (alex at alexbevi.com)
Date: 2026-10-21T20:51:13-04:00

Commit Message:
HARVESTER: Improve monster attack contact resolution logic

This ensures evasive (jump or duck) actions have some effect on a battle

Assisted-by: Codex:gpt-6.1-sol

Changed paths:
    engines/harvester/combat/combat_monsters.cpp


diff --git a/engines/harvester/combat/combat_monsters.cpp b/engines/harvester/combat/combat_monsters.cpp
index 519ec555df3..64e627c72bf 100644
--- a/engines/harvester/combat/combat_monsters.cpp
+++ b/engines/harvester/combat/combat_monsters.cpp
@@ -275,16 +275,23 @@ bool Combat::updateMonsterAttack(Script &script, const MonsterRecord &monster,
 	if (!combatState.attackContactResolved &&
 			combatState.attackContactFrame >= 0 &&
 			currentFrame >= combatState.attackContactFrame) {
-		combatState.attackContactResolved = true;
-		combatState.attackContactFrame = -1;
+		const bool projectile = (monster.damageType & 4) != 0;
 		const bool hasTrackedPlayerTarget =
 			playerState.entity &&
 			!combatState.attackTargetName.empty() &&
 			playerState.entity->getName().equalsIgnoreCase(combatState.attackTargetName);
-		if (!playerAlive || !hasTrackedPlayerTarget) {
-			debugC(1, kDebugCombat,
-				"Harvester: combat monster attack miss monster='%s' frame=%d player_hp=%d reason='no live target'",
-				monster.monsterName.c_str(), currentFrame, script.getPlayerCurrentHitPoints());
+		const bool hasContact = playerAlive && !playerState.deathActive && hasTrackedPlayerTarget &&
+			(projectile || entity.overlapsEntity(*playerState.entity));
+		if (hasContact || projectile) {
+			combatState.attackContactResolved = true;
+			combatState.attackContactFrame = -1;
+		}
+		if (!hasContact) {
+			if (entity.didAnimationAdvanceLastTick()) {
+				debugC(3, kDebugCombat,
+					"Harvester: combat monster attack miss monster='%s' frame=%d player_hp=%d reason='no live contact'",
+					monster.monsterName.c_str(), currentFrame, script.getPlayerCurrentHitPoints());
+			}
 		} else {
 			const int playerHitPointsBefore = script.getPlayerCurrentHitPoints();
 			const bool damageDisabled = _cheats.isPlayerDamageDisabled();
@@ -314,8 +321,8 @@ bool Combat::updateMonsterAttack(Script &script, const MonsterRecord &monster,
 					"combat_monster_hit", monster.monsterName, monster.damageType);
 				return true;
 			}
+			needsRedraw = true;
 		}
-		needsRedraw = true;
 	}
 	if (currentFrame == combatState.attackLastFrame) {
 		clearRoomMonsterAttackState(combatState);


Commit: 8adb9f275a31d0a975d31b28bd35608678d4cb62
    https://github.com/scummvm/scummvm/commit/8adb9f275a31d0a975d31b28bd35608678d4cb62
Author: Alex Bevilacqua (alex at alexbevi.com)
Date: 2026-10-21T20:51:13-04:00

Commit Message:
HARVESTER: Add bounds checking to evasive movements

Assisted-by: Codex:gpt-6.1-sol

Changed paths:
    engines/harvester/player/player.h
    engines/harvester/player/player_animation.cpp


diff --git a/engines/harvester/player/player.h b/engines/harvester/player/player.h
index 23883e64a26..4a031585c78 100644
--- a/engines/harvester/player/player.h
+++ b/engines/harvester/player/player.h
@@ -45,6 +45,7 @@ struct RoomPlayerState {
 	int evasiveFirstFrame = -1;
 	int evasiveLastFrame = -1;
 	int evasiveXStep = 0;
+	Common::Rect evasiveFrameBounds;
 	uint32 nextMovementTick = 0;
 	int keyboardWalkRequest = -1;
 	int walkHorizontalDirection = 0;
diff --git a/engines/harvester/player/player_animation.cpp b/engines/harvester/player/player_animation.cpp
index ab1d2ff48a3..4f78dea9327 100644
--- a/engines/harvester/player/player_animation.cpp
+++ b/engines/harvester/player/player_animation.cpp
@@ -38,6 +38,12 @@ namespace {
 static const int kRoomPlayerWalkAnimationRate = 17;
 static const int kRoomPlayerFastAnimationRate = 30;
 
+static int applyEvasiveFrameMovement(Entity &entity, const Common::Rect &previousBounds, int xStep) {
+	const int dx = MIN(MAX(xStep, 20 - previousBounds.left), 619 - previousBounds.right);
+	entity.setPosition(entity.getX() + dx, entity.getY(), entity.getZ());
+	return dx;
+}
+
 struct PlayerTurnAnimationRange {
 	PlayerTurnAnimationRange() {}
 	PlayerTurnAnimationRange(int firstFrame, int lastFrame, bool playBackwards)
@@ -175,10 +181,14 @@ bool Player::startEvasiveAnimation(bool moveLeft, bool moveRight, bool moveUp, b
 	playerState.walkHorizontalDirection = 0;
 	playerState.walkDepthDirection = 0;
 	playerState.nextMovementTick = 0;
+	const Common::Rect previousBounds = playerState.entity->getScreenRect();
 	playerState.entity->setAnimationFrameRange(firstFrame, lastFrame, false);
 	playerState.entity->setPlayBackwards(backwards);
 	playerState.entity->setAnimationRate(playerState.animationRate);
-	playerState.entity->setCurrentFrame(backwards ? lastFrame : firstFrame);
+	playerState.entity->setCurrentFrame(backwards ? lastFrame : firstFrame, true);
+	(void)applyEvasiveFrameMovement(*playerState.entity, previousBounds, playerState.evasiveXStep);
+	playerState.evasiveFrameBounds = playerState.entity->getScreenRect();
+	syncAnimationPlacement();
 	debugC(2, kDebugPlayer, "Harvester: player evasive state=0x%x facing=%d frames=%d..%d backwards=%d",
 		actorState, playerState.facing, firstFrame, lastFrame, backwards);
 	return true;
@@ -192,12 +202,13 @@ bool Player::updateEvasiveAnimation(HarvesterEngine &engine) {
 
 	Entity &entity = *playerState.entity;
 	const int actorState = playerState.evasiveState;
-	if (playerState.evasiveXStep != 0) {
-		entity.setPosition(entity.getX() + playerState.evasiveXStep, entity.getY(), entity.getZ());
-		syncAnimationPlacement();
-		reinsertPlayerSceneEntity(engine, playerState);
+	const int dx = applyEvasiveFrameMovement(entity, playerState.evasiveFrameBounds, playerState.evasiveXStep);
+	playerState.evasiveFrameBounds = entity.getScreenRect();
+	syncAnimationPlacement();
+	reinsertPlayerSceneEntity(engine, playerState);
+	if (dx != 0) {
 		debugC(3, kDebugPlayer, "Harvester: player evasive step state=0x%x frame=%d dx=%d",
-			actorState, entity.getCurrentFrame(), playerState.evasiveXStep);
+			actorState, entity.getCurrentFrame(), dx);
 	}
 	const bool backwards = actorState == 0x08 || actorState == 0x0f;
 	if (entity.getCurrentFrame() == (backwards ? playerState.evasiveFirstFrame : playerState.evasiveLastFrame)) {
@@ -207,7 +218,7 @@ bool Player::updateEvasiveAnimation(HarvesterEngine &engine) {
 	}
 
 	const int frameLeft = computePlayerFrameLeftX(playerState);
-	if ((actorState == 0x08 && frameLeft > 20) || (actorState == 0x0f && frameLeft < 620)) {
+	if ((actorState == 0x08 && frameLeft > 20) || (actorState == 0x0f && frameLeft < 500)) {
 		playerState.evasiveXStep = (int)(entity.getDepthScale() * (actorState == 0x08 ? -8.0f : 8.0f));
 	} else if (entity.getCurrentFrame() > playerState.evasiveFirstFrame + 2 &&
 			((actorState == 0x12 && frameLeft > 36) || (actorState == 0x13 && frameLeft < 616))) {


Commit: d8f8194eb3a8e0dba65a32809fc963da098709f4
    https://github.com/scummvm/scummvm/commit/d8f8194eb3a8e0dba65a32809fc963da098709f4
Author: Alex Bevilacqua (alex at alexbevi.com)
Date: 2026-10-21T20:51:13-04:00

Commit Message:
HARVESTER: Improve goto_room debug command to support disc 3 resources

Assisted-by: Codex:gpt-6.1-sol

Changed paths:
    engines/harvester/console.cpp
    engines/harvester/flow/flow.cpp
    engines/harvester/flow/flow.h
    engines/harvester/harvester.cpp
    engines/harvester/harvester.h


diff --git a/engines/harvester/console.cpp b/engines/harvester/console.cpp
index 023d58d2217..87e2dc22e22 100644
--- a/engines/harvester/console.cpp
+++ b/engines/harvester/console.cpp
@@ -269,45 +269,44 @@ static void buildActionChainLines(const Script &script, const Common::String &ta
 	appendCommandChain(lines, truncated, script, tag, 1, activeTags, remainingSteps);
 }
 
-static void collectSortedRoomNames(const Script &script, Common::Array<Common::String> &roomNames) {
-	roomNames.clear();
+struct DebugRoomTarget {
+	DebugRoomTarget(const Common::String &name, int disc) : roomName(name), discNumber(disc) {}
 
-	for (const RoomRecord &room : script.getRooms()) {
-		if (room.roomName.empty())
-			continue;
-
-		bool alreadyPresent = false;
-		for (const Common::String &existingName : roomNames) {
-			if (existingName.equalsIgnoreCase(room.roomName)) {
-				alreadyPresent = true;
-				break;
+	Common::String roomName;
+	int discNumber;
+};
+
+static void collectDebugRoomTargets(const Script &activeScript, int activeDisc,
+		Common::Array<DebugRoomTarget> &targets) {
+	ResourceManager resources;
+	Script probeScript;
+	for (int disc = ResourceManager::kFirstDiscNumber; disc <= ResourceManager::kLastDiscNumber; ++disc) {
+		const Script *script = &activeScript;
+		if (disc != activeDisc) {
+			if (!resources.setCurrentDisc(disc) || !probeScript.load(resources))
+				continue;
+			script = &probeScript;
+		}
+		for (const RoomRecord &room : script->getRooms()) {
+			if (room.roomName.empty())
+				continue;
+			bool alreadyPresent = false;
+			for (const DebugRoomTarget &target : targets) {
+				if (target.discNumber == disc && target.roomName.equalsIgnoreCase(room.roomName)) {
+					alreadyPresent = true;
+					break;
+				}
 			}
+			if (!alreadyPresent)
+				targets.push_back(DebugRoomTarget(room.roomName, disc));
 		}
-		if (!alreadyPresent)
-			roomNames.push_back(room.roomName);
 	}
-
-	Common::sort(roomNames.begin(), roomNames.end(), [](const Common::String &lhs, const Common::String &rhs) {
-		Common::String upperLhs = lhs;
-		Common::String upperRhs = rhs;
-		upperLhs.toUppercase();
-		upperRhs.toUppercase();
-		return upperLhs < upperRhs;
+	Common::sort(targets.begin(), targets.end(), [](const DebugRoomTarget &lhs, const DebugRoomTarget &rhs) {
+		const int order = lhs.roomName.compareToIgnoreCase(rhs.roomName);
+		return order != 0 ? order < 0 : lhs.discNumber < rhs.discNumber;
 	});
 }
 
-static bool findRoomName(const Script &script, const Common::String &candidate, Common::String &roomName) {
-	for (const RoomRecord &room : script.getRooms()) {
-		if (!room.roomName.equalsIgnoreCase(candidate))
-			continue;
-
-		roomName = room.roomName;
-		return true;
-	}
-
-	return false;
-}
-
 static void printDebugCommandUsage(Console &console) {
 	console.debugPrintf("Usage: DEBUG_COMMAND <opcode> [arg1] [arg2] [arg3] [arg4]\n");
 	console.debugPrintf("   or: DEBUG_COMMAND <opcode> [arg1] [arg2] [arg3] CHAIN <action_tag>\n");
@@ -548,48 +547,57 @@ bool Console::Cmd_debugCommand(int argc, const char **argv) {
 }
 
 bool Console::Cmd_gotoRoom(int argc, const char **argv) {
-	if (argc > 2) {
-		debugPrintf("Usage: GOTO_ROOM [room_name]\n");
+	if (argc > 3 || (argc == 3 &&
+			(argv[2][0] < '1' || argv[2][0] > '3' || argv[2][1] != '\0'))) {
+		debugPrintf("Usage: GOTO_ROOM [room_name [disc: 1-3]]\n");
 		return true;
 	}
 
 	Script *script = getActiveStartupScript();
-	if (!script) {
+	ResourceManager *resources = g_engine ? g_engine->getResources() : nullptr;
+	if (!script || !resources) {
 		debugPrintf("Harvester engine is not active\n");
 		return true;
 	}
 
-	Common::Array<Common::String> roomNames;
-	collectSortedRoomNames(*script, roomNames);
-	if (roomNames.empty()) {
-		debugPrintf("No rooms were found in HARVEST.SCR\n");
-		return true;
-	}
-
-	if (argc == 1) {
-		debugPrintf("Rooms from HARVEST.SCR (%u):\n", (uint)roomNames.size());
-		for (const Common::String &roomName : roomNames)
-			debugPrintf("  %s\n", roomName.c_str());
-		return true;
-	}
-
-	if (!g_engine || !g_engine->hasCurrentSaveRoomState()) {
+	if (argc > 1 && !g_engine->hasCurrentSaveRoomState()) {
 		debugPrintf("GOTO_ROOM is only available while a room is active\n");
 		return true;
 	}
 
-	Common::String roomName;
-	if (!findRoomName(*script, argv[1], roomName)) {
-		debugPrintf("Unknown room target '%s'\n", argv[1]);
+	Common::Array<DebugRoomTarget> targets;
+	collectDebugRoomTargets(*script, resources->getCurrentDisc(), targets);
+	if (argc == 1) {
+		debugPrintf("Rooms from available disc scripts (lowest disc is the default):\n");
+		for (uint i = 0; i < targets.size();) {
+			const Common::String roomName = targets[i].roomName;
+			Common::String discs;
+			do {
+				if (!discs.empty())
+					discs += ", ";
+				discs += Common::String::format("%d", targets[i++].discNumber);
+			} while (i < targets.size() && targets[i].roomName.equalsIgnoreCase(roomName));
+			debugPrintf("  %s [%s]\n", roomName.c_str(), discs.c_str());
+		}
 		return true;
 	}
 
-	if (!g_engine->requestDebugRoomChange(roomName)) {
-		debugPrintf("Unable to queue room change to '%s'\n", roomName.c_str());
+	const int requestedDisc = argc == 3 ? argv[2][0] - '0' : 0;
+	for (const DebugRoomTarget &target : targets) {
+		if (!target.roomName.equalsIgnoreCase(argv[1]) ||
+				(requestedDisc != 0 && target.discNumber != requestedDisc))
+			continue;
+		if (!g_engine->requestDebugRoomChange(target.roomName, target.discNumber)) {
+			debugPrintf("Unable to queue room change to '%s' on disc %d\n",
+				target.roomName.c_str(), target.discNumber);
+		} else {
+			debugPrintf("Queued room change to %s on disc %d\n", target.roomName.c_str(), target.discNumber);
+		}
 		return true;
 	}
 
-	debugPrintf("Queued room change to %s\n", roomName.c_str());
+	debugPrintf("Unknown room target '%s'%s\n", argv[1],
+		requestedDisc ? Common::String::format(" on disc %d", requestedDisc).c_str() : " on available discs");
 	return true;
 }
 
diff --git a/engines/harvester/flow/flow.cpp b/engines/harvester/flow/flow.cpp
index 8d20e005c61..b0eb0240a15 100644
--- a/engines/harvester/flow/flow.cpp
+++ b/engines/harvester/flow/flow.cpp
@@ -30,6 +30,7 @@
 #include "harvester/harvester.h"
 #include "harvester/scene/input.h"
 #include "harvester/entity.h"
+#include "harvester/resources.h"
 #include "harvester/script/script.h"
 
 namespace Harvester {
@@ -246,6 +247,7 @@ void Flow::prepareForNewGame() {
 	clearPendingGameOverReturn();
 	clearPendingNewGameRestart();
 	_pendingDebugRoomName.clear();
+	_pendingDebugRoomDisc = 0;
 	_queuedDebugInteraction = InteractionResult();
 	_hasQueuedDebugInteraction = false;
 	_engine.clearPendingLoadedSaveRoomState();
@@ -331,17 +333,21 @@ void Flow::clearPendingCloseupParentRestart() {
 	_pendingCloseupParentRestart = false;
 }
 
-bool Flow::requestDebugRoomChange(const Common::String &roomName) {
-	if (roomName.empty())
+bool Flow::requestDebugRoomChange(const Common::String &roomName, int discNumber) {
+	if (roomName.empty() || discNumber < ResourceManager::kFirstDiscNumber ||
+			discNumber > ResourceManager::kLastDiscNumber)
 		return false;
 
 	_pendingDebugRoomName = roomName;
+	_pendingDebugRoomDisc = discNumber;
 	return true;
 }
 
-Common::String Flow::takePendingDebugRoomChange() {
+Common::String Flow::takePendingDebugRoomChange(int &discNumber) {
 	const Common::String roomName = _pendingDebugRoomName;
+	discNumber = _pendingDebugRoomDisc;
 	_pendingDebugRoomName.clear();
+	_pendingDebugRoomDisc = 0;
 	return roomName;
 }
 
@@ -366,7 +372,21 @@ Common::Error Flow::runRoomLoop(const Common::String &entranceName) {
 			return Common::kNoError;
 		}
 
-		targetName = takePendingDebugRoomChange();
+		int discNumber = 0;
+		targetName = takePendingDebugRoomChange(discNumber);
+		ResourceManager *resources = _engine.getResources();
+		Script *script = _engine.getScript();
+		if (!resources || !script) {
+			--_roomLoopDepth;
+			return Common::kReadingFailed;
+		}
+		if (discNumber != resources->getCurrentDisc() &&
+				(!_engine.activateDisc(discNumber) || !script->reloadTownWorld(*resources))) {
+			warning("Harvester: unable to prepare debug room '%s' on disc %d", targetName.c_str(), discNumber);
+			--_roomLoopDepth;
+			return Common::kReadingFailed;
+		}
+		debugC(1, kDebugRoom, "Harvester: debug room change room='%s' disc=%d", targetName.c_str(), discNumber);
 		targetIsRoomName = true;
 	}
 }
diff --git a/engines/harvester/flow/flow.h b/engines/harvester/flow/flow.h
index ff8f00a775a..4f84916d08d 100644
--- a/engines/harvester/flow/flow.h
+++ b/engines/harvester/flow/flow.h
@@ -68,7 +68,7 @@ public:
 
 	bool hasQueuedDebugInteraction() const { return _hasQueuedDebugInteraction; }
 	bool queueDebugInteraction(const InteractionResult &interaction);
-	bool requestDebugRoomChange(const Common::String &roomName);
+	bool requestDebugRoomChange(const Common::String &roomName, int discNumber);
 
 	Common::Error runDemoEnding();
 	Common::Error runHelpScreen(const byte *palette, float paletteBrightness);
@@ -123,7 +123,7 @@ private:
 	void clearPendingMainMenuReturn();
 	bool hasPendingCloseupParentRestart() const;
 	void clearPendingCloseupParentRestart();
-	Common::String takePendingDebugRoomChange();
+	Common::String takePendingDebugRoomChange(int &discNumber);
 
 	HarvesterEngine &_engine;
 	Common::Array<Common::String> _quickTips;
@@ -144,6 +144,7 @@ private:
 	bool _pendingMainMenuReturn = false;
 	bool _pendingCloseupParentRestart = false;
 	Common::String _pendingDebugRoomName;
+	int _pendingDebugRoomDisc = 0;
 	uint _roomLoopDepth = 0;
 	uint32 _roomSetupTransitionShownTick = 0;
 };
diff --git a/engines/harvester/harvester.cpp b/engines/harvester/harvester.cpp
index 408667a9bff..c4ba7c52458 100644
--- a/engines/harvester/harvester.cpp
+++ b/engines/harvester/harvester.cpp
@@ -297,10 +297,10 @@ bool HarvesterEngine::requestDebugCommand(const CommandRecord &command) {
 	return _activeFlow->queueDebugInteraction(interaction);
 }
 
-bool HarvesterEngine::requestDebugRoomChange(const Common::String &roomName) {
+bool HarvesterEngine::requestDebugRoomChange(const Common::String &roomName, int discNumber) {
 	return _activeFlow &&
 		_currentSaveRoomState.valid &&
-		_activeFlow->requestDebugRoomChange(roomName);
+		_activeFlow->requestDebugRoomChange(roomName, discNumber);
 }
 
 void HarvesterEngine::setDisplayMode(int width, int height) {
diff --git a/engines/harvester/harvester.h b/engines/harvester/harvester.h
index 761205cbcad..9198688f707 100644
--- a/engines/harvester/harvester.h
+++ b/engines/harvester/harvester.h
@@ -114,7 +114,7 @@ public:
 	bool isTimerDebugEnabled() const { return _timerDebugEnabled; }
 	bool toggleTimerDebugEnabled();
 	bool requestDebugCommand(const CommandRecord &command);
-	bool requestDebugRoomChange(const Common::String &roomName);
+	bool requestDebugRoomChange(const Common::String &roomName, int discNumber);
 	void captureCurrentSaveRoomState(const Common::String &entranceName,
 		const Common::String &roomName, int playerX, int playerY, int playerZ,
 		int playerFacing, int discNumber, const Common::String &musicPath);




More information about the Scummvm-git-logs mailing list