[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