[Scummvm-git-logs] scummvm master -> fcec8c98bfb83ab1dc71883d8d86f581ae4fb49e
mgerhardy
noreply at scummvm.org
Wed Sep 2 20:01:36 UTC 2026
This automated email contains information about 7 new commits which have been
pushed to the 'scummvm' repo located at https://api.github.com/repos/scummvm/scummvm .
Summary:
85451ad75d MACS2: removed getObjectHotspotName wrapper
ed2c220f07 MACS2: cleanup position check + const
4710184096 MACS2: unified names
5476e7d881 MACS2: replaced magic numbers and removed comments
5ba656ca9e MACS2: const + reduced code duplication
e8c46eb17f MACS2: extract character class into own file
fcec8c98bf MACS2: removed Button class
Commit: 85451ad75dec7a6654835b4a791ab6172cc603a2
https://github.com/scummvm/scummvm/commit/85451ad75dec7a6654835b4a791ab6172cc603a2
Author: Martin Gerhardy (martin.gerhardy at gmail.com)
Date: 2026-09-02T22:01:21+02:00
Commit Message:
MACS2: removed getObjectHotspotName wrapper
Changed paths:
engines/macs2/actionbar.cpp
engines/macs2/debugtools.cpp
engines/macs2/macs2.cpp
engines/macs2/macs2.h
engines/macs2/view1.cpp
diff --git a/engines/macs2/actionbar.cpp b/engines/macs2/actionbar.cpp
index f5975cfcc01..409ef3e0904 100644
--- a/engines/macs2/actionbar.cpp
+++ b/engines/macs2/actionbar.cpp
@@ -28,6 +28,7 @@
#include "gui/message.h"
#include "macs2/detection.h"
#include "macs2/gameobjects.h"
+#include "macs2/hotspot_names.h"
#include "macs2/macs2.h"
#include "macs2/music.h"
#include "macs2/view1.h"
@@ -445,7 +446,7 @@ void ActionBar::handleMouseMoveScumm(const Common::Point &pos) {
break;
if (getInvItemRect(i).contains(pos)) {
_hoveredItemIndex = i;
- updateSentenceLine(getObjectHotspotName(items[itemIdx]->_index));
+ updateSentenceLine(lookupObjectHotspotName(items[itemIdx]->_index));
break;
}
}
@@ -541,7 +542,7 @@ Common::String ActionBar::currentTargetDisplayName() const {
_view->_uiPanelState == View1::kUiPanelInventory) {
GameObject *hovered = _view->getClickedInventoryItem(mouse);
if (hovered != nullptr)
- return getObjectHotspotName(hovered->_index);
+ return lookupObjectHotspotName(hovered->_index);
return Common::String();
}
@@ -560,7 +561,7 @@ Common::String ActionBar::buildSentenceLine() const {
const Common::String targetName = currentTargetDisplayName();
Common::String itemName;
if (_view->_activeInventoryItem != nullptr) {
- itemName = getObjectHotspotName(_view->_activeInventoryItem->_index);
+ itemName = lookupObjectHotspotName(_view->_activeInventoryItem->_index);
}
if (mode == Script::MouseMode::UseInventory && !itemName.empty()) {
diff --git a/engines/macs2/debugtools.cpp b/engines/macs2/debugtools.cpp
index f750ec2c8b7..599b5dd6af0 100644
--- a/engines/macs2/debugtools.cpp
+++ b/engines/macs2/debugtools.cpp
@@ -29,6 +29,7 @@
#include "common/util.h"
#include "macs2/detection.h"
#include "macs2/gameobjects.h"
+#include "macs2/hotspot_names.h"
#include "macs2/macs2.h"
#include "macs2/music.h"
#include "macs2/view1.h"
@@ -1199,7 +1200,7 @@ static void showInventoryWindow() {
if (ImGui::CollapsingHeader("Current Inventory", ImGuiTreeNodeFlags_DefaultOpen)) {
for (uint i = 0; i < view->_inventoryItems.size(); i++) {
GameObject *obj = view->_inventoryItems[i];
- Common::String name = getObjectHotspotName(obj->_index);
+ Common::String name = lookupObjectHotspotName(obj->_index);
if (name.empty())
name = "???";
Common::String utf8Name = Common::U32String(name.c_str(), Common::kDos850).encode(Common::kUtf8);
@@ -1223,7 +1224,7 @@ static void showInventoryWindow() {
continue;
if (obj->_blobs.size() <= 0x13 || obj->_blobs[0x13].empty())
continue;
- Common::String name = getObjectHotspotName(obj->_index);
+ Common::String name = lookupObjectHotspotName(obj->_index);
if (name.empty())
name = "???";
Common::String utf8Name = Common::U32String(name.c_str(), Common::kDos850).encode(Common::kUtf8);
diff --git a/engines/macs2/macs2.cpp b/engines/macs2/macs2.cpp
index 5134fb45074..e85135528eb 100644
--- a/engines/macs2/macs2.cpp
+++ b/engines/macs2/macs2.cpp
@@ -2624,16 +2624,12 @@ uint16 Macs2Engine::getHotspotAtPoint(const Common::Point &p) const {
return 0;
}
-Common::String getObjectHotspotName(uint16 objectIndex) {
- return lookupObjectHotspotName(objectIndex);
-}
-
Common::String lookupInteractionDisplayName(uint16 interactionId) {
if (interactionId >= 0x800) {
return lookupSceneHotspotName((uint16)Scenes::instance()._currentSceneIndex, (uint16)(interactionId - 0x800));
}
if (interactionId >= 0x400) {
- return getObjectHotspotName((uint16)(interactionId - 0x400));
+ return lookupObjectHotspotName((uint16)(interactionId - 0x400));
}
return Common::String();
}
@@ -2794,7 +2790,7 @@ void Macs2Engine::getHotspotPositions(Common::Array<Graphics::HotspotInfo> &hots
hotspotType = Graphics::kHotspotNPC;
}
- const Common::String &name = getObjectHotspotName(entry.index);
+ const Common::String &name = lookupObjectHotspotName(entry.index);
hotspots.emplace_back(Graphics::HotspotInfo(screenPos, hotspotLabelToU32(name), hotspotType));
}
}
diff --git a/engines/macs2/macs2.h b/engines/macs2/macs2.h
index c365b644b9b..595338aa796 100644
--- a/engines/macs2/macs2.h
+++ b/engines/macs2/macs2.h
@@ -964,7 +964,6 @@ public:
};
extern Macs2Engine *g_engine;
-Common::String getObjectHotspotName(uint16 objectIndex);
/** Display name for a hit id: 0x400+object or 0x800+scene hotspot. */
Common::String lookupInteractionDisplayName(uint16 interactionId);
diff --git a/engines/macs2/view1.cpp b/engines/macs2/view1.cpp
index 46b7fd4c09e..15756cac99f 100644
--- a/engines/macs2/view1.cpp
+++ b/engines/macs2/view1.cpp
@@ -35,6 +35,7 @@
#include "macs2/detection.h"
#include "macs2/gameobjects.h"
#include "macs2/amiga_decode.h"
+#include "macs2/hotspot_names.h"
#include "macs2/macs2.h"
#include "macs2/music.h"
#include "macs2/actionbar.h"
@@ -2155,7 +2156,7 @@ bool View1::msgMouseMove(const MouseMoveMessage &msg) {
_actionBar->clearSentenceObject();
GameObject *hovered = getClickedInventoryItem(msg._pos);
if (hovered != nullptr) {
- const Common::String &name = getObjectHotspotName(hovered->_index);
+ const Common::String &name = lookupObjectHotspotName(hovered->_index);
if (!name.empty())
_actionBar->updateSentenceLine(name);
}
@@ -2383,9 +2384,9 @@ void View1::draw() {
}
}
- GameObject *hoveredObject = getClickedInventoryItem(mousePos);
+ const GameObject *hoveredObject = getClickedInventoryItem(mousePos);
if (hoveredObject != nullptr) {
- Common::String name = getObjectHotspotName(hoveredObject->_index);
+ const Common::String &name = lookupObjectHotspotName(hoveredObject->_index);
if (!name.empty()) {
renderString(mousePos.x + 20, mousePos.y + 20, name);
} else {
Commit: ed2c220f07aa5bb203247125f3724a413b380384
https://github.com/scummvm/scummvm/commit/ed2c220f07aa5bb203247125f3724a413b380384
Author: Martin Gerhardy (martin.gerhardy at gmail.com)
Date: 2026-09-02T22:01:21+02:00
Commit Message:
MACS2: cleanup position check + const
Changed paths:
engines/macs2/view1.cpp
diff --git a/engines/macs2/view1.cpp b/engines/macs2/view1.cpp
index 15756cac99f..843e5a7b70c 100644
--- a/engines/macs2/view1.cpp
+++ b/engines/macs2/view1.cpp
@@ -2594,8 +2594,7 @@ bool View1::tick() {
// Binary gameTick (1008:e752): polls object table pos vs runtime finalDest;
// no on-screen Character is required (e.g. after moveObject to another scene).
const GameObject::StoredWalkRuntime &rt = walkObject->_storedWalkRuntime;
- if (walkObject->_position.x == rt.pathFinalDestination.x &&
- walkObject->_position.y == rt.pathFinalDestination.y) {
+ if (walkObject->_position == rt.pathFinalDestination) {
if ((int16)rt.motionTargetVerticalOffset < 0 ||
rt.motionTargetVerticalOffset == walkObject->_verticalOffsetScale) {
walkComplete = true;
@@ -2633,7 +2632,7 @@ bool View1::tick() {
}
} else if (executor->_waitForAdlibReady) {
drawSceneUpdate();
- Music *music = g_engine->getMusic();
+ const Music *music = g_engine->getMusic();
const bool ready = music->isMidiFilePlaying() ? false : music->isPlaybackReady();
if (ready) {
executor->_waitForAdlibReady = false;
Commit: 4710184096c8892cd36ca7f42fd283d9a6511125
https://github.com/scummvm/scummvm/commit/4710184096c8892cd36ca7f42fd283d9a6511125
Author: Martin Gerhardy (martin.gerhardy at gmail.com)
Date: 2026-09-02T22:01:21+02:00
Commit Message:
MACS2: unified names
Changed paths:
engines/macs2/view1.cpp
diff --git a/engines/macs2/view1.cpp b/engines/macs2/view1.cpp
index 843e5a7b70c..629c4e5141e 100644
--- a/engines/macs2/view1.cpp
+++ b/engines/macs2/view1.cpp
@@ -1206,8 +1206,8 @@ void View1::transferPickupTarget(GameObject *targetObject) {
return;
}
- Script::ScriptExecutor *executor = g_engine->_scriptExecutor;
- const uint16 actorIndex = executor->_pickupActorObjectID;
+ Script::ScriptExecutor *exec = g_engine->_scriptExecutor;
+ const uint16 actorIndex = exec->_pickupActorObjectID;
if (actorIndex == 0) {
return;
}
@@ -1222,7 +1222,7 @@ void View1::transferPickupTarget(GameObject *targetObject) {
Character *itemCharacter = getCharacterByIndex(targetObject->_index);
if (itemCharacter != nullptr) {
- executor->saveWalkRuntime(itemCharacter, targetObject);
+ exec->saveWalkRuntime(itemCharacter, targetObject);
const int index = getCharacterArrayIndex(itemCharacter);
if (index >= 0) {
itemCharacter->_markedForDeletion = true;
@@ -1255,7 +1255,7 @@ void View1::transferPickupTarget(GameObject *targetObject) {
if (_activeInventoryItem != nullptr && _activeInventoryItem->_index == targetObject->_index) {
_activeInventoryItem = nullptr;
- if (executor->_cursorMode == Script::MouseMode::UseInventory) {
+ if (exec->_cursorMode == Script::MouseMode::UseInventory) {
g_engine->setCursorMode(Script::MouseMode::Use);
updateCursor();
}
@@ -1843,7 +1843,7 @@ void View1::walkToScreenPosition(const Common::Point &pos) {
}
bool View1::handleInput(const MouseDownMessage &msg) {
- Script::ScriptExecutor *script = g_engine->_scriptExecutor;
+ Script::ScriptExecutor *exec = g_engine->_scriptExecutor;
if (msg._button == MouseMessage::MB_LEFT) {
// Help mode (depth-based scene preview) from handleInput (1008:e8bf).
// When currentMode == VM_HELP, clicking on the depth map previews scenes.
@@ -1852,8 +1852,8 @@ bool View1::handleInput(const MouseDownMessage &msg) {
}
if (shouldShowActionBar() && _actionBar && _actionBar->isPointInUI(msg._pos)) {
- if (script->_cursorMode != Script::MouseMode::Disabled) {
- _actionBar->handleClick(msg._pos, script->isExecuting());
+ if (exec->_cursorMode != Script::MouseMode::Disabled) {
+ _actionBar->handleClick(msg._pos, exec->isExecuting());
presentFrame();
}
return true;
@@ -1869,11 +1869,11 @@ bool View1::handleInput(const MouseDownMessage &msg) {
// text-box-dismiss gate before the interaction check. The text box (if any)
// is cleared as a side-effect of the script rerunning. Clear it here so the
// UI updates immediately, but do NOT consume the click.
- if (_isShowingTextBox && !script->isExecuting()) {
+ if (_isShowingTextBox && !exec->isExecuting()) {
handleTextBoxInput();
}
- if (_uiPanelState == kUiPanelInventory && !script->isExecuting()) {
+ if (_uiPanelState == kUiPanelInventory && !exec->isExecuting()) {
return handleInventoryClick(msg);
}
@@ -1881,7 +1881,7 @@ bool View1::handleInput(const MouseDownMessage &msg) {
return handleContainerInventoryClick(msg);
}
- if (_uiPanelState == kUiPanelActionBar && !script->isExecuting()) {
+ if (_uiPanelState == kUiPanelActionBar && !exec->isExecuting()) {
return handleActionBarClick(msg);
}
@@ -1889,7 +1889,7 @@ bool View1::handleInput(const MouseDownMessage &msg) {
// From handleInput (1008:f1d4): clicks during script execution are ONLY processed
// if cursor is not Disabled (0x1A). When cursor is Disabled (walk/wait in progress),
// clicks are completely ignored.
- if (script->isScriptMidExecution() && script->_cursorMode != Script::MouseMode::Disabled) {
+ if (exec->isScriptMidExecution() && exec->_cursorMode != Script::MouseMode::Disabled) {
// Binary handleInput (1008:f1d4-f225): exact sequence of unconditional checks
// 1. if g_wIsShowingTextBox != 0: handleTextBoxInput()
// 2. if g_wIsShowingDialoguePanel != 0: dismissDialoguePanel()
@@ -1916,23 +1916,23 @@ bool View1::handleInput(const MouseDownMessage &msg) {
}
}
if (!_isDialogueChoiceInputActive) {
- script->_scriptClickFlag = 0;
- script->_scriptClickX = (uint16)msg._pos.x;
- script->_scriptClickY = (uint16)msg._pos.y;
- script->_scriptClickResult = 1;
+ exec->_scriptClickFlag = 0;
+ exec->_scriptClickX = (uint16)msg._pos.x;
+ exec->_scriptClickY = (uint16)msg._pos.y;
+ exec->_scriptClickResult = 1;
g_engine->runScriptExecutor();
}
return true;
}
- if (script->isExecuting()) {
+ if (exec->isExecuting()) {
return true;
}
if (shouldShowActionBar() && msg._pos.y >= actionBarTopY())
return true;
- const Script::MouseMode mode = script->_cursorMode;
+ const Script::MouseMode mode = exec->_cursorMode;
// Walk never hit-tests; other verbs interact when a target is under the cursor.
// Empty-ground clicks walk so the persistent verb bar does not trap the player
@@ -1954,13 +1954,13 @@ bool View1::handleInput(const MouseDownMessage &msg) {
}
if (mode != Script::MouseMode::UseInventory) {
- script->_interactedInventoryItemId = 0;
+ exec->_interactedInventoryItemId = 0;
_activeInventoryItem = nullptr;
}
- script->_interactedObjectID = index;
+ exec->_interactedObjectID = index;
g_engine->runScriptExecutor(false);
- script->_interactedObjectID = 0;
+ exec->_interactedObjectID = 0;
return true;
}
}
@@ -1976,23 +1976,23 @@ bool View1::handleInput(const MouseDownMessage &msg) {
return true;
}
// Handle no other interactions during a script
- if (script->isExecuting()) {
+ if (exec->isExecuting()) {
if (!_isShowingDialoguePanel && !_isDialogueChoiceInputActive &&
!_isShowingTextBox &&
- !script->_overlayTextStageActive &&
- !script->_waitForPcmSound &&
- !script->_waitForMusicControl &&
- !script->_waitForAdlibReady &&
- !script->_waitForObjectAnimStep &&
- !script->_waitForSpecialAnimStep &&
- !script->_waitForDeltaAnim &&
- !script->_waitForDeltaSpeed &&
- script->canOpenSaveMenu()) {
+ !exec->_overlayTextStageActive &&
+ !exec->_waitForPcmSound &&
+ !exec->_waitForMusicControl &&
+ !exec->_waitForAdlibReady &&
+ !exec->_waitForObjectAnimStep &&
+ !exec->_waitForSpecialAnimStep &&
+ !exec->_waitForDeltaAnim &&
+ !exec->_waitForDeltaSpeed &&
+ exec->canOpenSaveMenu()) {
if (ConfMan.getBool("original_menus")) {
- _savedCursorMode = script->_cursorMode;
+ _savedCursorMode = exec->_cursorMode;
openOriginalSaveLoadPanel();
} else {
- _savedCursorMode = script->_cursorMode;
+ _savedCursorMode = exec->_cursorMode;
g_engine->setCursorMode(Script::MouseMode::PanelCursor);
g_engine->openMainMenuDialog();
updateCursor();
@@ -2001,7 +2001,7 @@ bool View1::handleInput(const MouseDownMessage &msg) {
return true;
}
- if (script->_cursorMode == Script::MouseMode::Disabled) {
+ if (exec->_cursorMode == Script::MouseMode::Disabled) {
return true;
}
if (hasPersistentActionBar()) {
@@ -2009,7 +2009,7 @@ bool View1::handleInput(const MouseDownMessage &msg) {
if (canCycleVerbs) {
g_engine->nextCursorMode();
_activeInventoryItem = nullptr;
- script->_interactedInventoryItemId = 0;
+ exec->_interactedInventoryItemId = 0;
if (_actionBar && shouldShowActionBar())
_actionBar->syncActiveVerbFromCursorMode();
updateCursor();
@@ -2451,29 +2451,29 @@ bool View1::tick() {
// Music fade tick from gameTick (1008:e556).
// Processes volume fade in/out each frame when active.
- Script::ScriptExecutor *se = g_engine->_scriptExecutor;
- if (se->_activeMusicSlot != 0 && se->_musicControlMode != 0) {
- const uint16 musicStep = MAX<uint16>(se->_musicControlStep, 1);
- if (se->_musicControlMode == 1) {
+ Script::ScriptExecutor *exec = g_engine->_scriptExecutor;
+ if (exec->_activeMusicSlot != 0 && exec->_musicControlMode != 0) {
+ const uint16 musicStep = MAX<uint16>(exec->_musicControlStep, 1);
+ if (exec->_musicControlMode == 1) {
// Fade out: volume -= step
- const int vol = (int)se->_musicControlVolume - (int)musicStep;
+ const int vol = (int)exec->_musicControlVolume - (int)musicStep;
if (vol < 1) {
- se->_musicControlMode = 0;
- se->_musicControlVolume = 0;
+ exec->_musicControlMode = 0;
+ exec->_musicControlVolume = 0;
} else {
- se->_musicControlVolume = vol;
+ exec->_musicControlVolume = vol;
}
- g_engine->getMusic()->setVolume(g_engine->scaledMusicVolume(se->_musicControlVolume));
+ g_engine->getMusic()->setVolume(g_engine->scaledMusicVolume(exec->_musicControlVolume));
} else {
// Fade in: volume += step. When >= 63: stop music.
- const int vol = (int)se->_musicControlVolume + (int)musicStep;
+ const int vol = (int)exec->_musicControlVolume + (int)musicStep;
if (vol >= 0x3F) {
- se->_musicControlMode = 0;
- se->_activeMusicSlot = 0;
+ exec->_musicControlMode = 0;
+ exec->_activeMusicSlot = 0;
g_engine->getMusic()->stopMusic();
} else {
- se->_musicControlVolume = vol;
- g_engine->getMusic()->setVolume(g_engine->scaledMusicVolume(se->_musicControlVolume));
+ exec->_musicControlVolume = vol;
+ g_engine->getMusic()->setVolume(g_engine->scaledMusicVolume(exec->_musicControlVolume));
}
}
}
@@ -2551,29 +2551,27 @@ bool View1::tick() {
// Binary gameTick: drawScene during dialogue/text wait is gated; if movement
// finished fires while paused on a clickable wait, don't resume the script.
if (_uiPanelState == kUiPanelNone && !_isShowingDialoguePanel && !_isShowingTextBox) {
- Script::ScriptExecutor *executor = g_engine->_scriptExecutor;
-
// Binary gameTick cascading if/else structure:
// if (frameWaitCounter == 0) { walkTarget / sound / music / adlib }
// else { drawScene(1); if counter==0 runScriptExecutor(); }
// Binary gameTick (1008:e556): each wait branch calls drawScene(1) before
// checking its completion flag and optionally resuming the script.
- if (!executor->isFrameWaitActive()) {
+ if (!exec->isFrameWaitActive()) {
// Binary gameTick (1008:e752) walk-wait polling:
// When g_wWalkTargetObjectIndex > 0, check each frame if the character
// has reached its target position AND vertical offset matches.
// Binary uses exact equality: charPos == runtime.finalDest.
// This works because walkAlongPath snaps pos/finalDest on arrival.
- uint16 walkTarget = executor->_walkTargetObjectIndex;
+ uint16 walkTarget = exec->_walkTargetObjectIndex;
if (walkTarget > 0) {
drawSceneUpdate();
GameObject *walkObject = GameObjects::getObjectByIndex(walkTarget);
if (walkObject == nullptr) {
- executor->setScriptError(0x19);
- executor->_walkTargetObjectIndex = 0;
+ exec->setScriptError(0x19);
+ exec->_walkTargetObjectIndex = 0;
} else if (walkObject->_dataOffset == 0) {
- executor->setScriptError(2);
- executor->_walkTargetObjectIndex = 0;
+ exec->setScriptError(2);
+ exec->_walkTargetObjectIndex = 0;
} else {
Character *c = getCharacterByIndex(walkTarget);
bool walkComplete = false;
@@ -2602,10 +2600,10 @@ bool View1::tick() {
}
}
if (walkComplete) {
- if (!executor->_pickupInProgress) {
+ if (!exec->_pickupInProgress) {
debugC(kDebugScript, "waitForWalk complete obj=%u", walkTarget);
- executor->debugLogActorWalkState("waitForWalk complete");
- executor->_walkTargetObjectIndex = 0;
+ exec->debugLogActorWalkState("waitForWalk complete");
+ exec->_walkTargetObjectIndex = 0;
g_engine->runScriptExecutor();
} else if (c != nullptr && c->_gameObject->_orientation != OrientationPickup) {
// Binary: pickup in progress, trigger pickup animation.
@@ -2615,84 +2613,84 @@ bool View1::tick() {
}
}
}
- } else if (executor->_waitForPcmSound) {
+ } else if (exec->_waitForPcmSound) {
drawSceneUpdate();
if (!g_engine->isSamplePlaying() && !g_engine->isSpeechPlaying()) {
debugC(kDebugScript, "waitForSound complete");
- executor->debugLogActorWalkState("waitForSound complete");
- executor->_waitForPcmSound = false;
+ exec->debugLogActorWalkState("waitForSound complete");
+ exec->_waitForPcmSound = false;
g_engine->getMusic()->setSmfDucked(false);
g_engine->runScriptExecutor();
}
- } else if (executor->_waitForMusicControl) {
+ } else if (exec->_waitForMusicControl) {
drawSceneUpdate();
- if (executor->_musicControlMode == 0) {
- executor->_waitForMusicControl = false;
+ if (exec->_musicControlMode == 0) {
+ exec->_waitForMusicControl = false;
g_engine->runScriptExecutor();
}
- } else if (executor->_waitForAdlibReady) {
+ } else if (exec->_waitForAdlibReady) {
drawSceneUpdate();
const Music *music = g_engine->getMusic();
const bool ready = music->isMidiFilePlaying() ? false : music->isPlaybackReady();
if (ready) {
- executor->_waitForAdlibReady = false;
+ exec->_waitForAdlibReady = false;
g_engine->runScriptExecutor();
}
- } else if (executor->_waitForObjectAnimStep) {
+ } else if (exec->_waitForObjectAnimStep) {
drawSceneUpdate();
bool animStepReached = false;
- const GameObject *waitObject = GameObjects::getObjectByIndex(executor->_waitObjectAnimObjectId);
+ const GameObject *waitObject = GameObjects::getObjectByIndex(exec->_waitObjectAnimObjectId);
if (waitObject != nullptr && waitObject->_dataOffset != 0) {
- const Common::Array<uint8> *blob = waitObject->getAnimSlotBlob(executor->_waitObjectAnimSlot);
+ const Common::Array<uint8> *blob = waitObject->getAnimSlotBlob(exec->_waitObjectAnimSlot);
if (blob != nullptr && !blob->empty()) {
AnimBlobView view(*blob);
if (view.isValid()) {
- animStepReached = view.sequencePosition() >= executor->_waitObjectAnimTargetStep;
+ animStepReached = view.sequencePosition() >= exec->_waitObjectAnimTargetStep;
}
}
}
if (animStepReached) {
debugC(kDebugScript, "waitObjectAnimStep complete obj=%u slot=%u step=%u",
- executor->_waitObjectAnimObjectId, executor->_waitObjectAnimSlot,
- executor->_waitObjectAnimTargetStep);
- executor->_waitForObjectAnimStep = false;
+ exec->_waitObjectAnimObjectId, exec->_waitObjectAnimSlot,
+ exec->_waitObjectAnimTargetStep);
+ exec->_waitForObjectAnimStep = false;
g_engine->runScriptExecutor();
}
- } else if (executor->_waitForSpecialAnimStep) {
+ } else if (exec->_waitForSpecialAnimStep) {
drawSceneUpdate();
bool animStepReached = false;
- const uint16 animIndex = executor->_waitSpecialAnimIndex;
+ const uint16 animIndex = exec->_waitSpecialAnimIndex;
if (animIndex > 0 && animIndex <= g_engine->_backgroundAnimationsBlobs.size()) {
const BackgroundAnimationBlob &blob = g_engine->_backgroundAnimationsBlobs[animIndex - 1];
const Common::Array<uint8> &active = blob.activeBlob();
if (!active.empty()) {
AnimBlobView view(active);
if (view.isValid())
- animStepReached = view.sequencePosition() >= executor->_waitSpecialAnimTargetStep;
+ animStepReached = view.sequencePosition() >= exec->_waitSpecialAnimTargetStep;
}
}
if (animStepReached) {
debugC(kDebugScript, "waitSpecialAnimStep complete anim=%u step=%u",
- executor->_waitSpecialAnimIndex, executor->_waitSpecialAnimTargetStep);
- executor->_waitForSpecialAnimStep = false;
+ exec->_waitSpecialAnimIndex, exec->_waitSpecialAnimTargetStep);
+ exec->_waitForSpecialAnimStep = false;
g_engine->runScriptExecutor();
}
- } else if (executor->_waitForDeltaAnim) {
+ } else if (exec->_waitForDeltaAnim) {
drawSceneUpdate();
if (!g_engine->tickDeltaPlayback()) {
debugC(kDebugScript, "waitForDeltaAnim complete");
- executor->_waitForDeltaAnim = false;
+ exec->_waitForDeltaAnim = false;
_backgroundSurface.copyFrom(g_engine->_sceneBackground);
g_engine->runScriptExecutor();
} else {
_backgroundSurface.copyFrom(g_engine->_sceneBackground);
redraw();
}
- } else if (executor->_waitForDeltaSpeed) {
+ } else if (exec->_waitForDeltaSpeed) {
drawSceneUpdate();
if (!g_engine->_deltaAnim.playing || !g_engine->tickDeltaPlayback()) {
debugC(kDebugScript, "waitForDeltaSpeed complete");
- executor->_waitForDeltaSpeed = false;
+ exec->_waitForDeltaSpeed = false;
_backgroundSurface.copyFrom(g_engine->_sceneBackground);
g_engine->runScriptExecutor();
} else {
@@ -2702,10 +2700,10 @@ bool View1::tick() {
}
} else {
drawSceneUpdate();
- if (executor->getFrameWaitCounter() == 0) {
+ if (exec->getFrameWaitCounter() == 0) {
debugC(kDebugScript, "frameWait complete");
- executor->debugLogActorWalkState("frameWait complete");
- executor->endFrameWait();
+ exec->debugLogActorWalkState("frameWait complete");
+ exec->endFrameWait();
g_engine->runScriptExecutor();
}
}
@@ -2723,13 +2721,12 @@ void View1::flushPendingCharacterDeletes() {
}
void View1::drawAllCharacters(Graphics::ManagedSurface *surface, bool fullUpdate) {
- // drawAllCharacters @ 1008:90a2
g_engine->_movementFinishedFlag = false;
sortObjectListByY();
rebuildCharacterLookupTable();
const uint16 sortedCount = _sortedObjectCount;
- Script::ScriptExecutor *executor = g_engine->_scriptExecutor;
+ Script::ScriptExecutor *exec = g_engine->_scriptExecutor;
if (fullUpdate && sortedCount > 0) {
for (uint16 local_c = 1; local_c <= sortedCount; local_c++) {
@@ -2767,7 +2764,7 @@ void View1::drawAllCharacters(Graphics::ManagedSurface *surface, bool fullUpdate
current = nullptr;
if (current != nullptr) {
- if (obj->_orientation != OrientationPickup || executor->_pickupInProgress)
+ if (obj->_orientation != OrientationPickup || exec->_pickupInProgress)
current->update();
}
}
@@ -2779,7 +2776,7 @@ void View1::drawAllCharacters(Graphics::ManagedSurface *surface, bool fullUpdate
}
// draw sorted scene objects back -> front
- if (surface != nullptr && !executor->hasScriptError() && sortedCount > 0) {
+ if (surface != nullptr && !exec->hasScriptError() && sortedCount > 0) {
const uint16 animAdvanceMode = (fullUpdate && _uiPanelState == kUiPanelNone) ? 2 : 0;
for (uint16 local_c = 1; local_c <= sortedCount; local_c++) {
@@ -2811,26 +2808,26 @@ void View1::drawAllCharacters(Graphics::ManagedSurface *surface, bool fullUpdate
const uint16 animSlot = g_engine->resolveAnimSlotIndex(obj);
if (!obj->isAnimSlotLoaded(animSlot)) {
- executor->setScriptError(10);
+ exec->setScriptError(10);
return;
}
Common::Array<uint8> *blob = obj->getAnimSlotBlob(animSlot);
if (blob == nullptr || blob->empty()) {
- executor->setScriptError(8);
+ exec->setScriptError(8);
return;
}
AnimBlobView blobView(*blob);
if (!blobView.isValid() || blobView.frameCount() == 0) {
- executor->setScriptError(blobView.frameCount() == 0 ? 0x0B : 8);
+ exec->setScriptError(blobView.frameCount() == 0 ? 0x0B : 8);
return;
}
AnimFrame frame;
if (current != nullptr) {
if (!current->fillCurrentAnimationFrame(animAdvanceMode, frame)) {
- executor->setScriptError(8);
+ exec->setScriptError(8);
return;
}
} else {
@@ -2958,22 +2955,22 @@ void View1::drawAllCharacters(Graphics::ManagedSurface *surface, bool fullUpdate
// Binary drawAllCharacters tail: movement-finished repeat run (opcode 0x27 area checks).
if (fullUpdate && g_engine->_movementFinishedFlag) {
- if (executor->isScriptWaitDeferred()) {
+ if (exec->isScriptWaitDeferred()) {
debugC(kDebugScript,
"repeatRun deferred: walkWait=%u frameWait=%u soundWait=%d musicWait=%d adlibWait=%d",
- executor->_walkTargetObjectIndex, executor->getFrameWaitCounter(),
- executor->_waitForPcmSound ? 1 : 0, executor->_waitForMusicControl ? 1 : 0,
- executor->_waitForAdlibReady ? 1 : 0);
+ exec->_walkTargetObjectIndex, exec->getFrameWaitCounter(),
+ exec->_waitForPcmSound ? 1 : 0, exec->_waitForMusicControl ? 1 : 0,
+ exec->_waitForAdlibReady ? 1 : 0);
} else {
- const Common::Point actorPos = executor->getCharPosition();
- const uint16 area = executor->getAreaAtPoint(actorPos.x, actorPos.y);
+ const Common::Point actorPos = exec->getCharPosition();
+ const uint16 area = exec->getAreaAtPoint(actorPos.x, actorPos.y);
debugC(kDebugScript, "repeatRun start: actor=(%d,%d) areaRepeatRun=%u var[122]=%u",
- actorPos.x, actorPos.y, area, executor->getVariableValue(122));
- executor->debugLogActorWalkState("repeatRun start");
- executor->_isRepeatRun = true;
+ actorPos.x, actorPos.y, area, exec->getVariableValue(122));
+ exec->debugLogActorWalkState("repeatRun start");
+ exec->_isRepeatRun = true;
g_engine->runScriptExecutor();
- executor->_isRepeatRun = false;
- executor->debugLogActorWalkState("repeatRun end");
+ exec->_isRepeatRun = false;
+ exec->debugLogActorWalkState("repeatRun end");
}
}
}
@@ -4022,7 +4019,7 @@ bool Character::shouldStepVerticalMotion() const {
}
void Character::update() {
- Script::ScriptExecutor *script = g_engine->_scriptExecutor;
+ Script::ScriptExecutor *exec = g_engine->_scriptExecutor;
if (_gameObject->_orientation == OrientationPickup) {
if (_pickedUpObject != nullptr) {
View1 *currentView = (View1 *)g_engine->findView("View1");
@@ -4034,17 +4031,17 @@ void Character::update() {
if (_pickupFrameCounter == _gameObject->_pickupFrameEnd) {
_gameObject->_orientation = _previousOrientation;
- if (script->_pickupInProgress) {
- script->_pickupInProgress = false;
- script->_pickupActorObjectID = 0;
- script->_pickupTargetObjectID = 0;
- g_engine->setCursorMode(script->_cursorModeBeforeWait);
+ if (exec->_pickupInProgress) {
+ exec->_pickupInProgress = false;
+ exec->_pickupActorObjectID = 0;
+ exec->_pickupTargetObjectID = 0;
+ g_engine->setCursorMode(exec->_cursorModeBeforeWait);
currentView->updateCursor();
}
- script->_walkTargetObjectIndex = 0;
+ exec->_walkTargetObjectIndex = 0;
_pickedUpObject = nullptr;
- script->_interactedObjectID = 0x0000;
- script->_interactedInventoryItemId = 0x0000;
+ exec->_interactedObjectID = 0x0000;
+ exec->_interactedInventoryItemId = 0x0000;
g_engine->_movementFinishedFlag = true;
return;
}
@@ -4208,7 +4205,7 @@ void Character::update() {
}
// Walkability check - binary uses getWalkabilityAt(posY, posX) >= 0xC8
if (!isWalkable(pos)) {
- const uint16 tileArea = script->getAreaAtPoint(pos.x, pos.y);
+ const uint16 tileArea = exec->getAreaAtPoint(pos.x, pos.y);
if (tileArea >= 210 && tileArea <= 215) {
debugC(kDebugPath,
"walk blocked on plate area %u at (%d,%d) walk=%u int16=%d target=(%d,%d)",
@@ -4267,7 +4264,7 @@ void Character::update() {
}
if (pixelsMoved != walkSpeed) {
- const uint16 tileArea = script->getAreaAtPoint(pos.x, pos.y);
+ const uint16 tileArea = exec->getAreaAtPoint(pos.x, pos.y);
if (tileArea >= 210 && tileArea <= 215) {
debugC(kDebugPath,
"walk cancelled pixelsMoved=%d walkSpeed=%d at (%d,%d) area=%u walk=%u finalDest=(%d,%d)",
@@ -4562,7 +4559,7 @@ void View1::handleOriginalSaveLoadClick(const Common::Point &pos) {
hasData = (!frame._data.empty() && frame._width > 0);
}
- Script::ScriptExecutor *scriptExecutor = g_engine->_scriptExecutor;
+ Script::ScriptExecutor *exec = g_engine->_scriptExecutor;
const bool isHit = (btnPos.x < clickX && btnPos.y < clickY &&
clickX < btnPos.x + btnW && clickY < btnPos.y + btnH &&
hasData &&
@@ -4580,7 +4577,7 @@ void View1::handleOriginalSaveLoadClick(const Common::Point &pos) {
// Process button action
if (i == 3) {
// Toggle music, reset clickedButton, redraw
- scriptExecutor->_soundSystemActive = !scriptExecutor->_soundSystemActive;
+ exec->_soundSystemActive = !exec->_soundSystemActive;
_clickedButtonIndex = 0;
redraw();
} else if (i == 4) {
@@ -4590,27 +4587,27 @@ void View1::handleOriginalSaveLoadClick(const Common::Point &pos) {
_saveConfirmArmed = true;
} else {
// Binary: second click arms error 0x1C and closes via button 7
- scriptExecutor->setScriptError(0x1C);
+ exec->setScriptError(0x1C);
_clickedButtonIndex = 7;
}
} else if (i == 6) {
if (!_loadConfirmArmed) {
_loadConfirmArmed = true;
} else {
- scriptExecutor->setScriptError(0x1B);
+ exec->setScriptError(0x1B);
_clickedButtonIndex = 7;
}
} else if (i == 7) {
// Binary: if music enabled AND sound active, play active music
- if (scriptExecutor->_musicEnabled &&
- scriptExecutor->_soundSystemActive) {
- const uint16 slot = scriptExecutor->_activeMusicSlot;
- if (slot != 0 && !scriptExecutor->_musicSlots[slot - 1].empty() &&
- g_engine->getMusic()->playSongData(scriptExecutor->_musicSlots[slot - 1])) {
+ if (exec->_musicEnabled &&
+ exec->_soundSystemActive) {
+ const uint16 slot = exec->_activeMusicSlot;
+ if (slot != 0 && !exec->_musicSlots[slot - 1].empty() &&
+ g_engine->getMusic()->playSongData(exec->_musicSlots[slot - 1])) {
// Original's adlibTickHandler resets g_bAdlibMasterVolume=0 (full volume).
// ScummVM layers user volume on top via scaledMusicVolume, so re-apply it.
- scriptExecutor->_musicControlMode = 0;
- scriptExecutor->_musicControlVolume = 0;
+ exec->_musicControlMode = 0;
+ exec->_musicControlVolume = 0;
g_engine->getMusic()->setVolume(g_engine->scaledMusicVolume(0));
}
}
Commit: 5476e7d8816928abc2446ad85a7893edeb14786d
https://github.com/scummvm/scummvm/commit/5476e7d8816928abc2446ad85a7893edeb14786d
Author: Martin Gerhardy (martin.gerhardy at gmail.com)
Date: 2026-09-02T22:01:21+02:00
Commit Message:
MACS2: replaced magic numbers and removed comments
Changed paths:
engines/macs2/view1.cpp
diff --git a/engines/macs2/view1.cpp b/engines/macs2/view1.cpp
index 629c4e5141e..41c49b406d8 100644
--- a/engines/macs2/view1.cpp
+++ b/engines/macs2/view1.cpp
@@ -4329,29 +4329,19 @@ void View1::openOriginalSaveLoadPanel() {
}
}
- // g_wUiPanelWidth = (g_wActionBarButtonWidth + 10) * 7 + 4
uint16 panelWidth = (maxW + 10) * 7 + 4;
- // if (g_wUiPanelWidth < 0xD4) g_wUiPanelWidth = 0xD4
if (panelWidth < 212) {
panelWidth = 212;
}
- // g_wUiPanelHeight = g_wActionBarButtonHeight + 0x8A
const uint16 panelHeight = maxH + 138;
- // g_wUiPanelX = (g_wScreenWidth >> 1) - (g_wUiPanelWidth >> 1)
const int panelX = 160 - (panelWidth >> 1);
- // g_wUiPanelY = (g_wScreenHeight >> 1) - (g_wUiPanelHeight >> 1)
const int panelY = 100 - (panelHeight >> 1);
- // g_wActionBarButtonWidth = g_wActionBarButtonWidth + 6
_saveLoadButtonWidth = maxW + 6;
- // g_wActionBarButtonHeight = g_wActionBarButtonHeight + 6
_saveLoadButtonHeight = maxH + 6;
-
_saveLoadPanelRect = Common::Rect(panelX, panelY, panelX + panelWidth, panelY + panelHeight);
- // local_6 = ((g_wScreenWidth >> 1) - (g_wActionBarButtonWidth + 4) * 7 / 2) + 2
int buttonRowX = (160 - (int)((_saveLoadButtonWidth + 4) * 7) / 2) + 2;
- // local_8 = (g_wUiPanelY + g_wUiPanelHeight - 4) - g_wActionBarButtonHeight
const int buttonRowY = (panelY + panelHeight - 4) - _saveLoadButtonHeight;
// Second loop: store button positions and draw them
@@ -4464,14 +4454,12 @@ void View1::drawOriginalSaveLoadPanel(Graphics::ManagedSurface &s) {
// Slot loop: local_4 = 0..9
for (int slot = 0; slot <= 9; slot++) {
- // drawPanelSlot(0xc, g_wUiPanelWidth - 8, g_wUiPanelY + 4 + slot * 0xc, g_wUiPanelX + 4)
+ const int slotH = 12;
const int slotX = panelX + 4;
- const int slotY = panelY + 4 + slot * 12;
+ const int slotY = panelY + 4 + slot * slotH;
const int slotW = panelW - 8;
- const int slotH = 12;
drawNinePatchBorder(Common::Point(slotX, slotY), Common::Point(slotW, slotH), kBorderPressed, false, false, s);
- // drawText at (g_wUiPanelX + 6, g_wUiPanelY + 6 + slot * 0xc)
const int idx = _saveLoadPageIndex * 10 + slot;
Common::String label;
if (idx < ARRAYSIZE(_saveSlotNames) && !_saveSlotNames[idx].empty()) {
@@ -4482,7 +4470,7 @@ void View1::drawOriginalSaveLoadPanel(Graphics::ManagedSurface &s) {
}
const GlyphData *font = g_engine->numPanelGlyphs > 0 ? g_engine->_panelGlyphs : g_engine->_glyphs;
const uint16 fontCount = g_engine->numPanelGlyphs > 0 ? g_engine->numPanelGlyphs : g_engine->_numGlyphs;
- renderStringWithFont(panelX + 6, panelY + 6 + slot * 12, label, font, fontCount);
+ renderStringWithFont(panelX + 6, panelY + 6 + slot * slotH, label, font, fontCount);
}
}
@@ -4507,8 +4495,8 @@ void View1::handleOriginalSaveLoadClick(const Common::Point &pos) {
// TODO: drawSaveLoadScrollArrows
}
- int clickX = pos.x;
- int clickY = pos.y;
+ const int clickX = pos.x;
+ const int clickY = pos.y;
// Slot loop: local_4 = 0..9
for (int slot = 0; slot <= 9; slot++) {
// Slot hit test for sub-mode 2 (save): editSaveSlotName
@@ -4556,7 +4544,7 @@ void View1::handleOriginalSaveLoadClick(const Common::Point &pos) {
bool hasData = false;
if (imgIdx < (int)g_engine->_imageResources.size()) {
const AnimFrame &frame = g_engine->_imageResources[imgIdx];
- hasData = (!frame._data.empty() && frame._width > 0);
+ hasData = !frame._data.empty() && frame._width > 0;
}
Script::ScriptExecutor *exec = g_engine->_scriptExecutor;
@@ -4599,8 +4587,7 @@ void View1::handleOriginalSaveLoadClick(const Common::Point &pos) {
}
} else if (i == 7) {
// Binary: if music enabled AND sound active, play active music
- if (exec->_musicEnabled &&
- exec->_soundSystemActive) {
+ if (exec->_musicEnabled && exec->_soundSystemActive) {
const uint16 slot = exec->_activeMusicSlot;
if (slot != 0 && !exec->_musicSlots[slot - 1].empty() &&
g_engine->getMusic()->playSongData(exec->_musicSlots[slot - 1])) {
Commit: 5ba656ca9eae1aaae479f508c79140ebb99e6c51
https://github.com/scummvm/scummvm/commit/5ba656ca9eae1aaae479f508c79140ebb99e6c51
Author: Martin Gerhardy (martin.gerhardy at gmail.com)
Date: 2026-09-02T22:01:21+02:00
Commit Message:
MACS2: const + reduced code duplication
Changed paths:
engines/macs2/view1.cpp
diff --git a/engines/macs2/view1.cpp b/engines/macs2/view1.cpp
index 41c49b406d8..29f3d89539a 100644
--- a/engines/macs2/view1.cpp
+++ b/engines/macs2/view1.cpp
@@ -3761,9 +3761,9 @@ bool Character::calculatePath(Common::Point target) {
int localBestCost = 0x7777;
int nextNode = currentNode;
for (uint a = 0; a < curPt._adjacentPoints.size(); a++) {
- int adjIdx = curPt._adjacentPoints[a];
- int cost = g_engine->computeMinCostToReachable(adjIdx, 0x7fff, _gameObject->_index, reachable, nodeCount, target);
- int edgeCost = g_engine->walkableDistance(adjIdx, currentNode);
+ const int adjIdx = curPt._adjacentPoints[a];
+ const int cost = g_engine->computeMinCostToReachable(adjIdx, 0x7fff, _gameObject->_index, reachable, nodeCount, target);
+ const int edgeCost = g_engine->walkableDistance(adjIdx, currentNode);
if (cost + edgeCost < localBestCost) {
nextNode = adjIdx;
localBestCost = cost + edgeCost;
@@ -3937,25 +3937,24 @@ Macs2::AnimFrame *Character::getCurrentPortrait(bool onRightSide, uint16 frameIn
}
uint portraitBlobIndex = 17;
- if (onRightSide && _gameObject->_blobs.size() > 18 && !_gameObject->_blobs[18].empty()) {
- portraitBlobIndex = 18;
- } else if (_gameObject->_blobs[portraitBlobIndex].empty() && _gameObject->_blobs.size() > 18 && !_gameObject->_blobs[18].empty()) {
- portraitBlobIndex = 18;
+ if (_gameObject->_blobs.size() > 18 && !_gameObject->_blobs[18].empty()) {
+ if (onRightSide || _gameObject->_blobs[portraitBlobIndex].empty()) {
+ portraitBlobIndex = 18;
+ }
}
if (_gameObject->_blobs[portraitBlobIndex].empty()) {
return nullptr;
}
- uint16 offset = BackgroundAnimationBlob::advanceAnimFrame(_gameObject->_blobs[portraitBlobIndex], true, frameIndex);
+ const uint16 offset = BackgroundAnimationBlob::advanceAnimFrame(_gameObject->_blobs[portraitBlobIndex], true, frameIndex);
// offset points to per-frame: offsetX(2), offsetY(2), unknown(2), width(2), height(2), pixels
- offset += 6; // skip to width/height/pixels
Common::Array<uint8> &blob = _gameObject->_blobs[portraitBlobIndex];
AnimFrame *result = new AnimFrame();
- result->_width = READ_LE_UINT16(&blob[offset]);
- result->_height = READ_LE_UINT16(&blob[offset + 2]);
+ result->_width = READ_LE_UINT16(&blob[offset + 6]);
+ result->_height = READ_LE_UINT16(&blob[offset + 8]);
result->_data.resize(result->_width * result->_height);
- memcpy(result->_data.data(), &blob[offset + 4], result->_width * result->_height);
+ memcpy(result->_data.data(), &blob[offset + 10], result->_width * result->_height);
return result;
}
@@ -4040,8 +4039,8 @@ void Character::update() {
}
exec->_walkTargetObjectIndex = 0;
_pickedUpObject = nullptr;
- exec->_interactedObjectID = 0x0000;
- exec->_interactedInventoryItemId = 0x0000;
+ exec->_interactedObjectID = 0;
+ exec->_interactedInventoryItemId = 0;
g_engine->_movementFinishedFlag = true;
return;
}
Commit: e8c46eb17f8802448e5fbd53164d062eb1a3a54e
https://github.com/scummvm/scummvm/commit/e8c46eb17f8802448e5fbd53164d062eb1a3a54e
Author: Martin Gerhardy (martin.gerhardy at gmail.com)
Date: 2026-09-02T22:01:21+02:00
Commit Message:
MACS2: extract character class into own file
Changed paths:
A engines/macs2/character.cpp
A engines/macs2/character.h
engines/macs2/macs2.cpp
engines/macs2/module.mk
engines/macs2/view1.cpp
engines/macs2/view1.h
diff --git a/engines/macs2/character.cpp b/engines/macs2/character.cpp
new file mode 100644
index 00000000000..b3b92f41fdb
--- /dev/null
+++ b/engines/macs2/character.cpp
@@ -0,0 +1,736 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "macs2/character.h"
+#include "common/debug.h"
+#include "common/endian.h"
+#include "common/util.h"
+#include "macs2/detection.h"
+#include "macs2/events.h"
+#include "macs2/macs2.h"
+#include "macs2/macs2_constants.h"
+#include "macs2/scriptexecutor.h"
+#include "macs2/view1.h"
+
+namespace Macs2 {
+
+void resetCharacterWalkPath(Character *character) {
+ if (character == nullptr || character->_gameObject == nullptr) {
+ return;
+ }
+ const Common::Point &pos = character->getPosition();
+ character->_path.clear();
+ character->_currentPathIndex = 0;
+ character->_targetPosition = pos;
+ character->_pathFinalDestination = pos;
+ character->_stepDeltaX = 0;
+ character->_stepDeltaY = 0;
+ character->_stepError = 0;
+ character->_stepDirectionSet = false;
+}
+
+// Effective world position for pickup walk targets and bounds-attached props.
+// Inventory items use their holder's position; attached objects use parent + offset.
+static Common::Point getObjectEffectivePosition(const GameObject *object) {
+ if (object == nullptr) {
+ return Common::Point();
+ }
+ if (object->_hasBoundsAttachment) {
+ const GameObject *parent = GameObjects::getObjectByIndex(object->_boundsAttachmentObjectID);
+ if (parent != nullptr) {
+ return Common::Point(
+ parent->_position.x + (int16)object->_boundsAttachmentValue1,
+ parent->_position.y + (int16)object->_boundsAttachmentValue2);
+ }
+ }
+ if (object->_sceneIndex > 0x400) {
+ const GameObject *holder = GameObjects::getObjectByIndex(object->_sceneIndex - 0x400);
+ if (holder != nullptr) {
+ return holder->_position;
+ }
+ }
+ return object->_position;
+}
+
+bool Character::handleWalkability(Character *c) {
+ // Wall-sliding obstacle avoidance from walkAlongPath (1008:1b8f).
+ // When the character steps into a non-walkable pixel (walkability >= 200),
+ // the original code samples walkability at +/-1 and +/-2 pixels in each
+ // axis to build a gradient vector, then slides the character along that
+ // vector until it reaches a walkable position.
+ if (c->_gameObject->_index != 1) {
+ return false;
+ }
+ if (g_engine->_scriptExecutor->isExecuting()) {
+ return false;
+ }
+
+ Common::Point pos = c->getPosition();
+ if (isWalkable(pos)) {
+ return false;
+ }
+
+ // Build a push vector by sampling the walkability map around the current
+ // position. Non-walkable neighbors push us away from them.
+ int pushX = 0;
+ int pushY = 0;
+
+ // Sample at distance 1
+ if (!isWalkable(Common::Point(pos.x + 1, pos.y)))
+ pushX -= 1;
+ if (!isWalkable(Common::Point(pos.x - 1, pos.y)))
+ pushX += 1;
+ if (!isWalkable(Common::Point(pos.x, pos.y + 1)))
+ pushY -= 1;
+ if (!isWalkable(Common::Point(pos.x, pos.y - 1)))
+ pushY += 1;
+
+ // Sample at distance 2 for stronger gradient
+ if (!isWalkable(Common::Point(pos.x + 2, pos.y)))
+ pushX -= 1;
+ if (!isWalkable(Common::Point(pos.x - 2, pos.y)))
+ pushX += 1;
+ if (!isWalkable(Common::Point(pos.x, pos.y + 2)))
+ pushY -= 1;
+ if (!isWalkable(Common::Point(pos.x, pos.y - 2)))
+ pushY += 1;
+
+ // Slide along the push vector
+ while (pushX != 0 || pushY != 0) {
+ if (pushX < 0) {
+ if (isWalkable(Common::Point(pos.x - 1, pos.y)))
+ pos.x -= 1;
+ pushX += 1;
+ }
+ if (pushX > 0) {
+ if (isWalkable(Common::Point(pos.x + 1, pos.y)))
+ pos.x += 1;
+ pushX -= 1;
+ }
+ if (pushY < 0) {
+ if (isWalkable(Common::Point(pos.x, pos.y - 1)))
+ pos.y -= 1;
+ pushY += 1;
+ }
+ if (pushY > 0) {
+ if (isWalkable(Common::Point(pos.x, pos.y + 1)))
+ pos.y += 1;
+ pushY -= 1;
+ }
+ }
+
+ c->setPosition(pos);
+ return true;
+}
+
+uint16 Character::lookupWalkability(const Common::Point &p) const {
+ return g_engine->getWalkabilityAt((int16)p.y, (int16)p.x);
+}
+
+bool Character::isWalkable(const Common::Point &p) const {
+ return Macs2Engine::isWalkabilityWalkable(lookupWalkability(p));
+}
+
+Character::Character() : _pathfindingOverlay(g_engine->screenWidth() * g_engine->gameHeight(), 0) {
+}
+
+bool Character::calculatePath(Common::Point target) {
+ // Binary calculatePath (1008:1966). Params: charY, charX, finalDestY, finalDestX, actorIndex.
+ // The binary operates on the runtime struct directly; we store equivalent state in _path etc.
+ const Common::Point &charPos = _gameObject->_position;
+ const int nodeCount = g_engine->getPathfindingNodeCount();
+
+ // Step 1: Mark reachability anchored on FINAL DESTINATION (not character)
+ // scene[i + 0x50C2] = isPathWalkable(finalDest, node[i])
+ bool reachable[kPathNodeSlots + 1] = {};
+ for (int i = 1; i <= nodeCount; i++) {
+ const Common::Point &nodePos = g_engine->_pathfindingPoints[i - 1]._position;
+ reachable[i] = g_engine->isPathWalkable(target.y, target.x, nodePos.y, nodePos.x);
+ }
+
+ // Step 2: Find best entry node (lowest combined distance to both source and dest)
+ int bestCost = 0x7777;
+ int bestNode = 0;
+ for (int i = 1; i <= nodeCount; i++) {
+ const Common::Point &nodePos = g_engine->_pathfindingPoints[i - 1]._position;
+ int costToDest = g_engine->euclideanDistance(nodePos, target);
+ int costToChar = g_engine->euclideanDistance(nodePos, charPos);
+ if (costToDest + costToChar < bestCost) {
+ // Verify this node can connect source to target
+ // Binary calls canNodeConnectSourceToTarget(destY, destX, charY, charX, i)
+ // due to calculatePath being invoked with swapped source/dest params.
+ // This means the gate check is "can node see CHARACTER" and the flood-fill
+ // checks "any node reachable from DEST" AND "any node visible from CHARACTER".
+ // TODO: validate this with a playthought:
+ // PVS-Studio V764: Possible incorrect order of arguments passed to
+ // 'canNodeConnectSourceToTarget' function: 'target' and 'charPos'.
+ // I didn't had any issues in previous runs
+ if (canNodeConnectSourceToTarget(i, target, charPos, reachable, nodeCount)) {
+ // Recompute cost (binary does this twice)
+ costToDest = g_engine->euclideanDistance(nodePos, target);
+ costToChar = g_engine->euclideanDistance(nodePos, charPos);
+ bestCost = costToDest + costToChar;
+ bestNode = i;
+ }
+ }
+ }
+
+ if (bestNode == 0) {
+ // No path found - go directly to target
+ // Binary: pathNodeCount=0, pathIndex=1
+ _path.clear();
+ _currentPathIndex = 1;
+ _targetPosition = target;
+ return false;
+ }
+
+ // Step 3: smoothPath - build path from bestNode toward a reachable node
+ _path.clear();
+ _path.push_back(bestNode);
+ int currentNode = bestNode;
+ while (!reachable[currentNode]) {
+ const PathfindingPoint &curPt = g_engine->_pathfindingPoints[currentNode - 1];
+ int localBestCost = 0x7777;
+ int nextNode = currentNode;
+ for (uint a = 0; a < curPt._adjacentPoints.size(); a++) {
+ const int adjIdx = curPt._adjacentPoints[a];
+ const int cost = g_engine->computeMinCostToReachable(adjIdx, 0x7fff, _gameObject->_index, reachable, nodeCount, target);
+ const int edgeCost = g_engine->walkableDistance(adjIdx, currentNode);
+ if (cost + edgeCost < localBestCost) {
+ nextNode = adjIdx;
+ localBestCost = cost + edgeCost;
+ }
+ }
+ currentNode = nextNode;
+ _path.push_back(currentNode);
+ if (_path.size() > kPathNodeSlots)
+ break; // safety
+ }
+
+ // Step 4: Validate path - consecutive nodes must be walkable to each other
+ for (uint i = 0; i + 1 < _path.size(); i++) {
+ const Common::Point &p1 = g_engine->_pathfindingPoints[_path[i + 1] - 1]._position;
+ const Common::Point &p2 = g_engine->_pathfindingPoints[_path[i] - 1]._position;
+ if (!g_engine->isPathWalkable(p1.y, p1.x, p2.y, p2.x)) {
+ // Path invalid - abort, go directly to target
+ _path.clear();
+ _targetPosition = target;
+ return false;
+ }
+ }
+
+ // Step 5: Skip-forward optimization - skip nodes the character can already reach directly.
+ // Binary: checks isPathWalkable(nextNode, charPos) - "can character see the next node?"
+ // Note: binary's calculatePath is called with swapped params, so its 'finalDest' param
+ // is actually the character position.
+ _currentPathIndex = 0;
+ while (_currentPathIndex + 1 < (int16)_path.size()) {
+ const Common::Point &nextNodePos = g_engine->_pathfindingPoints[_path[_currentPathIndex + 1] - 1]._position;
+ if (!g_engine->isPathWalkable(nextNodePos.y, nextNodePos.x, charPos.y, charPos.x))
+ break;
+ _currentPathIndex++;
+ }
+
+ // Set immediate target to the current path node
+ const Common::Point &firstTarget = g_engine->_pathfindingPoints[_path[_currentPathIndex] - 1]._position;
+ _targetPosition = firstTarget;
+ return true;
+}
+
+bool Character::canNodeConnectSourceToTarget(uint16 nodeIndex, const Common::Point &charPos, const Common::Point &target, const bool *reachable, int nodeCount) {
+ // Checks if node can connect source (charPos) to target:
+ // 1. Node must be able to see the target
+ // 2. Flood-fill connected component from node
+ // 3. Some node in component must see target AND some node must be seen from source
+ const Common::Point &nodePos = g_engine->_pathfindingPoints[nodeIndex - 1]._position;
+ if (!g_engine->isPathWalkable(nodePos.y, nodePos.x, target.y, target.x))
+ return false;
+
+ // Flood-fill connected nodes
+ bool visited[kPathNodeSlots + 1] = {};
+ floodFillConnectedNodes(nodeIndex, visited, nodeCount);
+
+ // Check both conditions
+ bool anySeesTarget = false;
+ bool anySeenFromSource = false;
+ for (int i = 1; i <= nodeCount; i++) {
+ if (!visited[i])
+ continue;
+ const Common::Point &p = g_engine->_pathfindingPoints[i - 1]._position;
+ if (g_engine->isPathWalkable(p.y, p.x, target.y, target.x))
+ anySeesTarget = true;
+ if (g_engine->isPathWalkable(charPos.y, charPos.x, p.y, p.x))
+ anySeenFromSource = true;
+ }
+ return anySeesTarget && anySeenFromSource;
+}
+
+void Character::floodFillConnectedNodes(int nodeIndex, bool *visited, int nodeCount) {
+ if (nodeIndex < 1 || nodeIndex > nodeCount)
+ return;
+ if (visited[nodeIndex])
+ return;
+ visited[nodeIndex] = true;
+ const PathfindingPoint &pt = g_engine->_pathfindingPoints[nodeIndex - 1];
+ for (uint i = 0; i < pt._adjacentPoints.size(); i++) {
+ floodFillConnectedNodes(pt._adjacentPoints[i], visited, nodeCount);
+ }
+}
+
+const Common::Point &Character::getPosition() const {
+ return _gameObject->_position;
+}
+
+void Character::setPosition(const Common::Point &newPosition) {
+ _gameObject->_position = newPosition;
+}
+
+uint16 Character::getVerticalOffset() const {
+ uint16 result = g_engine->getWalkabilityAt(getPosition());
+ if (Macs2Engine::isWalkabilityBlocking(result)) {
+ result = 0;
+ }
+
+ if (_gameObject->_verticalOffsetScale != 0) {
+ const int16 charY = getPosition().y;
+ const int32 depthOffset = ((int32)charY - (int32)g_engine->_walkDepthThresholdY) *
+ (int32)g_engine->_walkDepthScaleFactor / 100;
+ const uint16 scalingFactor = (uint16)((int32)g_engine->_walkBaseSpeedPct + depthOffset);
+ result = (scalingFactor * _gameObject->_verticalOffsetScale) / 100;
+ }
+
+ return result;
+}
+
+bool Character::walkAlongPath() {
+ if (_currentPathIndex >= 0 && _currentPathIndex < (int16)_path.size()) {
+ const uint16 snapIdx = _path[_currentPathIndex];
+ const Common::Point &snapPos = g_engine->_pathfindingPoints[snapIdx - 1]._position;
+ _gameObject->_position = snapPos;
+ }
+ _currentPathIndex++;
+ if (_currentPathIndex >= (int16)_path.size()) {
+ // Past end of path - walk to final destination, then stop
+ _targetPosition = _pathFinalDestination;
+ _stepDeltaX = (int16)ABS(_targetPosition.x - _gameObject->_position.x);
+ _stepDeltaY = (int16)ABS(_targetPosition.y - _gameObject->_position.y);
+ _stepError = 0;
+ _stepDirectionSet = false;
+ return false; // No more path segments after this
+ }
+ const uint16 nodeIdx = _path[_currentPathIndex];
+ const Common::Point &nodePos = g_engine->_pathfindingPoints[nodeIdx - 1]._position;
+ _targetPosition = nodePos;
+ _stepDeltaX = (int16)ABS(_targetPosition.x - _gameObject->_position.x);
+ _stepDeltaY = (int16)ABS(_targetPosition.y - _gameObject->_position.y);
+ _stepError = 0;
+ _stepDirectionSet = false;
+ return true;
+}
+
+bool Character::isAnimationMirrored() const {
+ switch (_gameObject->_orientation) {
+ case OrientationSouthWest:
+ case OrientationWest:
+ case OrientationNorthWest:
+ case OrientationStandingSouthWest:
+ case OrientationStandingWest:
+ case OrientationStandingNorthWest:
+ return true;
+ default:
+ break;
+ }
+ return false;
+}
+
+bool Character::fillCurrentAnimationFrame(uint16 advanceMode, Macs2::AnimFrame &out) const {
+ const uint16 animSlot = g_engine->resolveAnimSlotIndex(_gameObject);
+
+ Common::Array<uint8> *blobPtr = _gameObject->getAnimSlotBlob(animSlot);
+ if (blobPtr == nullptr || blobPtr->empty()) {
+ return false;
+ }
+
+ Common::Array<uint8> &blob = *blobPtr;
+ const uint16 frameStart = BackgroundAnimationBlob::advanceAnimFrame(blob, true, advanceMode);
+ out._offsetX = (int16)READ_LE_UINT16(&blob[frameStart]);
+ out._offsetY = (int16)READ_LE_UINT16(&blob[frameStart + 2]);
+ const uint16 offset = frameStart + 6;
+ out._width = READ_LE_UINT16(&blob[offset]);
+ out._height = READ_LE_UINT16(&blob[offset + 2]);
+ out._data.resize(out._width * out._height);
+ memcpy(out._data.data(), &blob[offset + 4], out._width * out._height);
+ return true;
+}
+
+Macs2::AnimFrame *Character::getCurrentPortrait(bool onRightSide, uint16 frameIndex) {
+ if (_gameObject->_blobs.size() <= 17) {
+ return nullptr;
+ }
+
+ uint portraitBlobIndex = 17;
+ if (_gameObject->_blobs.size() > 18 && !_gameObject->_blobs[18].empty()) {
+ if (onRightSide || _gameObject->_blobs[portraitBlobIndex].empty()) {
+ portraitBlobIndex = 18;
+ }
+ }
+
+ if (_gameObject->_blobs[portraitBlobIndex].empty()) {
+ return nullptr;
+ }
+
+ const uint16 offset = BackgroundAnimationBlob::advanceAnimFrame(_gameObject->_blobs[portraitBlobIndex], true, frameIndex);
+ // offset points to per-frame: offsetX(2), offsetY(2), unknown(2), width(2), height(2), pixels
+ Common::Array<uint8> &blob = _gameObject->_blobs[portraitBlobIndex];
+ AnimFrame *result = new AnimFrame();
+ result->_width = READ_LE_UINT16(&blob[offset + 6]);
+ result->_height = READ_LE_UINT16(&blob[offset + 8]);
+ result->_data.resize(result->_width * result->_height);
+ memcpy(result->_data.data(), &blob[offset + 10], result->_width * result->_height);
+ return result;
+}
+
+// Leftover lerp-era entry point. Duration and ignoreObstacles are unused.
+// Binary walkAlongPath (1008:1b8f) has no time lerp: Phase 0 sets 8-way
+// orientation and returns (1-frame delay); Phase 1 loops stepCounter 1..walkSpeed
+// with one-pixel Bresenham (error >= deltaX -> step Y else step X).
+// walkSpeed = animSpeed * (scene[0x5201] + depth) / 100, min 1;
+// depth = (posY - scene[0x51FD]) * scene[0x51FF] / 100.
+// Character::update() implements that. C++ walkAlongPath() is only the inlined
+// waypoint advance. Path setup is walkToScreenPosition / scriptWalkToPosition.
+void Character::startLerpTo(const Common::Point &target, uint32 duration, bool ignoreObstacles) {
+ _startPosition = getPosition();
+ _targetPosition = target;
+ _startTime = g_events->currentMillis;
+ _duration = duration;
+
+ // Reset Bresenham state - direction will be calculated on first Update()
+ _stepDirectionSet = false;
+ _stepDeltaX = (int16)ABS(_targetPosition.x - _startPosition.x);
+ _stepDeltaY = (int16)ABS(_targetPosition.y - _startPosition.y);
+ _stepError = 0;
+}
+
+void Character::startPickup(Macs2::GameObject *object) {
+ _pickedUpObject = object;
+ _pathFinalDestination = getObjectEffectivePosition(object);
+ _pickupFrameCounter = 0;
+ _pickupItemTransferred = false;
+
+ const Common::Point ¤t = getPosition();
+ const int16 destX = _pathFinalDestination.x;
+ const int16 destY = _pathFinalDestination.y;
+
+ _currentPathIndex = 0;
+ _path.clear();
+
+ const bool directPath = g_engine->isPathWalkable(destY, destX, current.y, current.x);
+ if (!directPath && Macs2Engine::isWalkabilityWalkable(g_engine->getWalkabilityAt(destY, destX))) {
+ calculatePath(Common::Point(destX, destY));
+ }
+
+ if (_path.empty()) {
+ _targetPosition = _pathFinalDestination;
+ }
+
+ _stepDeltaX = (int16)ABS(_targetPosition.x - current.x);
+ _stepDeltaY = (int16)ABS(_targetPosition.y - current.y);
+ _stepError = 0;
+ _stepDirectionSet = false;
+}
+
+bool Character::hasPendingVerticalMotion() const {
+ return (int16)_motionTargetVerticalOffset >= 0 &&
+ _motionTargetVerticalOffset != _gameObject->_verticalOffsetScale;
+}
+
+bool Character::shouldStepVerticalMotion() const {
+ return (int16)_motionTargetVerticalOffset < 0 ||
+ _motionTargetVerticalOffset != _gameObject->_verticalOffsetScale;
+}
+
+void Character::update() {
+ Script::ScriptExecutor *exec = g_engine->_scriptExecutor;
+ if (_gameObject->_orientation == OrientationPickup) {
+ if (_pickedUpObject != nullptr) {
+ View1 *currentView = (View1 *)g_engine->findView("View1");
+
+ if (!_pickupItemTransferred && _pickupFrameCounter == _gameObject->_pickupFrameStart) {
+ _pickupItemTransferred = true;
+ currentView->transferPickupTarget(_pickedUpObject);
+ }
+
+ if (_pickupFrameCounter == _gameObject->_pickupFrameEnd) {
+ _gameObject->_orientation = _previousOrientation;
+ if (exec->_pickupInProgress) {
+ exec->_pickupInProgress = false;
+ exec->_pickupActorObjectID = 0;
+ exec->_pickupTargetObjectID = 0;
+ g_engine->setCursorMode(exec->_cursorModeBeforeWait);
+ currentView->updateCursor();
+ }
+ exec->_walkTargetObjectIndex = 0;
+ _pickedUpObject = nullptr;
+ exec->_interactedObjectID = 0;
+ exec->_interactedInventoryItemId = 0;
+ g_engine->_movementFinishedFlag = true;
+ return;
+ }
+
+ _pickupFrameCounter++;
+ }
+ return;
+ }
+
+ Common::Point pos = getPosition();
+ const int32 depthOffset = ((int32)pos.y - (int32)g_engine->_walkDepthThresholdY) *
+ (int32)g_engine->_walkDepthScaleFactor / 100;
+ // Per-animation speed from blob data
+ // Walk speed from binary walkAlongPath
+ uint16 animSpeed = 2; // default fallback
+ ObjectOrientation orient = _gameObject->_orientation;
+ if (orient >= OrientationNorth && orient <= g_engine->maxAnimSlots() && (uint)(orient - 1) < _gameObject->_blobWalkSpeeds.size()) {
+ animSpeed = _gameObject->_blobWalkSpeeds[orient - 1];
+ if (animSpeed == 0) {
+ animSpeed = 2;
+ }
+ }
+ int walkSpeed = ((int)animSpeed * ((int)g_engine->_walkBaseSpeedPct + (int)depthOffset)) / 100;
+ if (walkSpeed < 1) {
+ walkSpeed = 1;
+ }
+
+ // Proximity arrival check from walkAlongPath
+ bool arrived = (ABS(pos.x - _targetPosition.x) <= walkSpeed) &&
+ (ABS(pos.y - _targetPosition.y) <= walkSpeed);
+ if (arrived && hasPendingVerticalMotion()) {
+ arrived = false;
+ }
+ if (arrived) {
+ const bool atFinalDest = (_targetPosition.x == _pathFinalDestination.x &&
+ _targetPosition.y == _pathFinalDestination.y);
+
+ if (!atFinalDest && !_path.empty()) {
+ // Mid-path waypoint arrival: advance to next node
+ // Binary (23b0): snap pos to current path node, advance pathIndex
+ walkAlongPath();
+ return;
+ }
+
+ // Final destination arrival (or direct walk arrival)
+ if (_gameObject->_snapToTarget) {
+ setPosition(_targetPosition);
+ _pathFinalDestination = _targetPosition;
+ } else {
+ _targetPosition = pos;
+ _pathFinalDestination = pos;
+ if ((int16)_motionTargetVerticalOffset >= 0) {
+ _motionTargetVerticalOffset = _gameObject->_verticalOffsetScale;
+ }
+ }
+ _path.clear();
+ if (hasPendingVerticalMotion()) {
+ _gameObject->_verticalOffsetScale = _motionTargetVerticalOffset;
+ _motionProgress = _motionDistanceUnits;
+ }
+ // Walk arrival: orientation changes to standing (walking dir + 8).
+ // Script resumption is handled by position polling in View1::tick().
+ const bool wasWalking = (_gameObject->_orientation < OrientationStandingNorth);
+ if (wasWalking) {
+ _gameObject->_orientation = (ObjectOrientation)(_gameObject->_orientation + OrientationNorthWest);
+ g_engine->_movementFinishedFlag = true;
+ }
+ return;
+ }
+
+ // Binary: if target==current position, skip Phase 0 turn delay (set directionCalculated=1)
+ if (!_stepDirectionSet && _targetPosition.x == pos.x && _targetPosition.y == pos.y) {
+ _stepDirectionSet = true;
+ }
+
+ // Calculate direction if not yet set (first frame of movement)
+ if (!_stepDirectionSet) {
+ _stepDirectionSet = true;
+ // Phase 0 from walkAlongPath (1008:1b8f): direction calculation.
+ // Binary returns after setting direction (1-frame turn delay).
+ const uint16 absDx = (uint16)ABS(pos.x - _targetPosition.x);
+ const uint16 absDy = (uint16)ABS(pos.y - _targetPosition.y);
+ ObjectOrientation dir = _gameObject->_orientation;
+ if (dir >= OrientationStandingNorth && dir <= OrientationStandingNorthWest)
+ dir = (ObjectOrientation)(dir - OrientationNorthWest);
+ if (dir > OrientationStandingNorthWest)
+ dir = OrientationNorth;
+ // Cardinal directions (only if animation available for that direction)
+ if (_targetPosition.y < pos.y && absDx <= absDy &&
+ _gameObject->_blobs.size() > 0 && !_gameObject->_blobs[0].empty())
+ dir = OrientationNorth;
+ if (pos.x < _targetPosition.x && absDy <= absDx &&
+ _gameObject->_blobs.size() > 2 && !_gameObject->_blobs[2].empty())
+ dir = OrientationEast;
+ if (pos.y < _targetPosition.y && absDx <= absDy &&
+ _gameObject->_blobs.size() > 4 && !_gameObject->_blobs[4].empty())
+ dir = OrientationSouth;
+ if (_targetPosition.x < pos.x && absDy <= absDx &&
+ _gameObject->_blobs.size() > 6 && !_gameObject->_blobs[6].empty())
+ dir = OrientationWest;
+ // Diagonals: absDx/4 < absDy AND absDy/2 < absDx
+ if ((absDx >> 2) < absDy && (absDy >> 1) < absDx) {
+ if (_targetPosition.y < pos.y && pos.x < _targetPosition.x &&
+ _gameObject->_blobs.size() > 1 && !_gameObject->_blobs[1].empty())
+ dir = OrientationNorthEast;
+ if (pos.x < _targetPosition.x && pos.y < _targetPosition.y &&
+ _gameObject->_blobs.size() > 3 && !_gameObject->_blobs[3].empty())
+ dir = OrientationSouthEast;
+ if (pos.y < _targetPosition.y && _targetPosition.x < pos.x &&
+ _gameObject->_blobs.size() > 5 && !_gameObject->_blobs[5].empty())
+ dir = OrientationSouthWest;
+ if (_targetPosition.x < pos.x && _targetPosition.y < pos.y &&
+ _gameObject->_blobs.size() > 7 && !_gameObject->_blobs[7].empty())
+ dir = OrientationNorthWest;
+ }
+ _gameObject->_orientation = dir;
+ _stepDeltaX = (int16)absDx;
+ _stepDeltaY = (int16)absDy;
+ _stepError = 0;
+ // 1-frame turn delay: return after setting direction (binary Phase 0)
+ return;
+ }
+
+ // Phase 1: Bresenham stepping loop - exact 1:1 match of binary (1008:1ea1..2280)
+ // Binary: stepCounter from 1 to walkSpeed, NO early break. Loop always completes.
+ // After loop: if pixelsMoved != walkSpeed -> revert pos to savedPos and cancel path.
+ int pixelsMoved = 0;
+ Common::Point savedPos = pos;
+ for (int stepCounter = 1; stepCounter <= walkSpeed; stepCounter++) {
+ savedPos = pos; // Binary: savedX/savedY at top of each iteration
+ // Bresenham: if error >= deltaX -> step Y, else step X
+ if (_stepError >= _stepDeltaX) {
+ // Step Y axis
+ if (_targetPosition.y != pos.y)
+ pixelsMoved++;
+ if (_targetPosition.y < pos.y)
+ pos.y--;
+ else if (_targetPosition.y > pos.y)
+ pos.y++;
+ _stepError -= _stepDeltaX;
+ } else {
+ // Step X axis
+ if (_targetPosition.x != pos.x)
+ pixelsMoved++;
+ if (_targetPosition.x < pos.x)
+ pos.x--;
+ else if (_targetPosition.x > pos.x)
+ pos.x++;
+ _stepError += _stepDeltaY;
+ }
+ // Vertical offset interpolation
+ if (shouldStepVerticalMotion()) {
+ _motionProgress += _motionVerticalOffsetDelta;
+ while (_motionProgress >= _motionDistanceUnits && _motionDistanceUnits > 0) {
+ _motionProgress -= _motionDistanceUnits;
+ if (_motionTargetVerticalOffset < _gameObject->_verticalOffsetScale)
+ _gameObject->_verticalOffsetScale--;
+ else if (_motionTargetVerticalOffset > _gameObject->_verticalOffsetScale)
+ _gameObject->_verticalOffsetScale++;
+ }
+ }
+ // Walkability check - binary uses getWalkabilityAt(posY, posX) >= 0xC8
+ if (!isWalkable(pos)) {
+ const uint16 tileArea = exec->getAreaAtPoint(pos.x, pos.y);
+ if (tileArea >= 210 && tileArea <= 215) {
+ debugC(kDebugPath,
+ "walk blocked on plate area %u at (%d,%d) walk=%u int16=%d target=(%d,%d)",
+ tileArea, pos.x, pos.y, lookupWalkability(pos), (int16)lookupWalkability(pos),
+ _targetPosition.x, _targetPosition.y);
+ }
+ // Revert position
+ pos = savedPos;
+ // Wall-sliding: build push vector from +/-1 and +/-2 samples
+ int pushX = 0, pushY = 0;
+ if (Macs2Engine::isWalkabilityBlocking(lookupWalkability(Common::Point(pos.x + 1, pos.y))))
+ pushX--;
+ if (Macs2Engine::isWalkabilityBlocking(lookupWalkability(Common::Point(pos.x - 1, pos.y))))
+ pushX++;
+ if (Macs2Engine::isWalkabilityBlocking(lookupWalkability(Common::Point(pos.x, pos.y + 1))))
+ pushY--;
+ if (Macs2Engine::isWalkabilityBlocking(lookupWalkability(Common::Point(pos.x, pos.y - 1))))
+ pushY++;
+ if (Macs2Engine::isWalkabilityBlocking(lookupWalkability(Common::Point(pos.x + 2, pos.y))))
+ pushX--;
+ if (Macs2Engine::isWalkabilityBlocking(lookupWalkability(Common::Point(pos.x - 2, pos.y))))
+ pushX++;
+ if (Macs2Engine::isWalkabilityBlocking(lookupWalkability(Common::Point(pos.x, pos.y + 2))))
+ pushY--;
+ if (Macs2Engine::isWalkabilityBlocking(lookupWalkability(Common::Point(pos.x, pos.y - 2))))
+ pushY++;
+ // Apply push vector
+ while (pushX != 0 || pushY != 0) {
+ if (pushX < 0) {
+ if (Macs2Engine::isWalkabilityWalkable(lookupWalkability(Common::Point(pos.x - 1, pos.y))))
+ pos.x--;
+ pushX++;
+ }
+ if (pushX > 0) {
+ if (Macs2Engine::isWalkabilityWalkable(lookupWalkability(Common::Point(pos.x + 1, pos.y))))
+ pos.x++;
+ pushX--;
+ }
+ if (pushY < 0) {
+ if (Macs2Engine::isWalkabilityWalkable(lookupWalkability(Common::Point(pos.x, pos.y - 1))))
+ pos.y--;
+ pushY++;
+ }
+ if (pushY > 0) {
+ if (Macs2Engine::isWalkabilityWalkable(lookupWalkability(Common::Point(pos.x, pos.y + 1))))
+ pos.y++;
+ pushY--;
+ }
+ }
+ // Binary: target = finalDest = pos (cancel path, but loop continues)
+ _targetPosition = pos;
+ _pathFinalDestination = pos;
+ _path.clear();
+ }
+ // Binary: loop continues unconditionally until stepCounter == walkSpeed
+ }
+
+ if (pixelsMoved != walkSpeed) {
+ const uint16 tileArea = exec->getAreaAtPoint(pos.x, pos.y);
+ if (tileArea >= 210 && tileArea <= 215) {
+ debugC(kDebugPath,
+ "walk cancelled pixelsMoved=%d walkSpeed=%d at (%d,%d) area=%u walk=%u finalDest=(%d,%d)",
+ pixelsMoved, walkSpeed, pos.x, pos.y, tileArea, lookupWalkability(pos),
+ _pathFinalDestination.x, _pathFinalDestination.y);
+ } else if (Macs2Engine::isWalkabilityBlocking(lookupWalkability(pos))) {
+ debugC(kDebugPath,
+ "walk cancelled (non-walkable) pixelsMoved=%d walkSpeed=%d at (%d,%d) walk=%u",
+ pixelsMoved, walkSpeed, pos.x, pos.y, lookupWalkability(pos));
+ }
+ pos = savedPos;
+ _targetPosition = pos;
+ _pathFinalDestination = pos;
+ _path.clear();
+ }
+
+ setPosition(pos);
+}
+
+} // namespace Macs2
diff --git a/engines/macs2/character.h b/engines/macs2/character.h
new file mode 100644
index 00000000000..a32b1a56236
--- /dev/null
+++ b/engines/macs2/character.h
@@ -0,0 +1,107 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#ifndef MACS2_CHARACTER_H
+#define MACS2_CHARACTER_H
+
+#include "common/array.h"
+#include "common/rect.h"
+#include "macs2/gameobjects.h"
+
+namespace Macs2 {
+
+struct AnimFrame;
+
+class Character {
+private:
+ Common::Point _startPosition;
+
+ uint32 _startTime = 0;
+ uint32 _duration = 0;
+
+ // If this is set, a lerp to a location becomes picking up
+ Macs2::GameObject *_pickedUpObject = nullptr;
+
+ // Handle when the character has moved into a non-walkable area, push them out if
+ // they did and return true, return false otherwise
+ bool handleWalkability(Character *c);
+
+ uint16 lookupWalkability(const Common::Point &p) const;
+ bool shouldStepVerticalMotion() const;
+ bool isAnimationMirrored() const;
+ void floodFillConnectedNodes(int nodeIndex, bool *visited, int nodeCount);
+ // Returns false if we are at the end of the path already or the path is not valid
+ bool walkAlongPath();
+ void startLerpTo(const Common::Point &target, uint32 duration, bool ignoreObstacles = false);
+ bool isWalkable(const Common::Point &p) const;
+ bool canNodeConnectSourceToTarget(uint16 nodeIndex, const Common::Point &charPos, const Common::Point &target, const bool *reachable, int nodeCount);
+
+public:
+ Character();
+
+ // Frame counter for pickup animation (runtime+0x215).
+ // Increments each frame while orientation == 0x11.
+ // At _pickupFrameStart: item is transferred to inventory.
+ // At _pickupFrameEnd: animation ends, orientation restored.
+ uint16 _pickupFrameCounter = 0;
+ bool _pickupItemTransferred = false;
+ bool _markedForDeletion = false;
+
+ ObjectOrientation _previousOrientation = OrientationNone;
+
+ // Walk state from walkAlongPath (1008:1b8f) - runtime offsets +0x00..+0x0A, +0x18, +0x33
+ Common::Point _targetPosition; // runtime[+0x00, +0x02]: next waypoint
+ int16 _stepDeltaX = 0; // runtime[+0x04]: abs(endX - startX)
+ int16 _stepDeltaY = 0; // runtime[+0x06]: abs(endY - startY)
+ int16 _stepError = 0; // runtime[+0x18]: Bresenham error accumulator
+ bool _stepDirectionSet = false; // runtime[+0x33]: direction has been calculated
+
+ Common::Array<uint16> _path;
+ int16 _currentPathIndex = 0;
+ Common::Point _pathFinalDestination;
+ Common::Array<uint8> _pathfindingOverlay;
+
+ Macs2::GameObject *_gameObject = nullptr;
+ uint16 _motionTargetVerticalOffset = 0;
+ uint16 _motionVerticalOffsetDelta = 0;
+ uint16 _motionDistanceUnits = 0;
+ uint16 _motionProgress = 0;
+ uint16 _motionStartVerticalOffset = 0;
+
+ bool calculatePath(Common::Point target);
+ void startPickup(Macs2::GameObject *object);
+
+ const Common::Point &getPosition() const;
+ void setPosition(const Common::Point &newPosition);
+
+ uint16 getVerticalOffset() const;
+ bool hasPendingVerticalMotion() const;
+ bool fillCurrentAnimationFrame(uint16 advanceMode, Macs2::AnimFrame &out) const;
+ Macs2::AnimFrame *getCurrentPortrait(bool onRightSide = false, uint16 frameIndex = 0);
+
+ void update();
+};
+
+void resetCharacterWalkPath(Character *character);
+
+} // namespace Macs2
+
+#endif
diff --git a/engines/macs2/macs2.cpp b/engines/macs2/macs2.cpp
index e85135528eb..fec3334f6e3 100644
--- a/engines/macs2/macs2.cpp
+++ b/engines/macs2/macs2.cpp
@@ -82,21 +82,6 @@ Common::Point getSceneObjectHotspotPosition(View1 *view, GameObject *obj) {
} // namespace
-void resetCharacterWalkPath(Character *character) {
- if (character == nullptr || character->_gameObject == nullptr) {
- return;
- }
- const Common::Point &pos = character->getPosition();
- character->_path.clear();
- character->_currentPathIndex = 0;
- character->_targetPosition = pos;
- character->_pathFinalDestination = pos;
- character->_stepDeltaX = 0;
- character->_stepDeltaY = 0;
- character->_stepError = 0;
- character->_stepDirectionSet = false;
-}
-
Macs2Engine *g_engine;
Graphics::ManagedSurface Macs2Engine::readRLEImage(int64 offs, Common::SeekableReadStream *stream) {
diff --git a/engines/macs2/module.mk b/engines/macs2/module.mk
index aedf8a2e2a6..4054b8fbd3b 100644
--- a/engines/macs2/module.mk
+++ b/engines/macs2/module.mk
@@ -4,6 +4,7 @@ MODULE_OBJS = \
amiga_archive.o \
amiga_decode.o \
amiga_resources.o \
+ character.o \
midiparser_macs2.o \
music.o \
dialogs.o \
diff --git a/engines/macs2/view1.cpp b/engines/macs2/view1.cpp
index 29f3d89539a..4fb9d0376bc 100644
--- a/engines/macs2/view1.cpp
+++ b/engines/macs2/view1.cpp
@@ -1178,29 +1178,6 @@ int View1::getCharacterArrayIndex(const Character *c) const {
return -1;
}
-// Effective world position for pickup walk targets and bounds-attached props.
-// Inventory items use their holder's position; attached objects use parent + offset.
-static Common::Point getObjectEffectivePosition(const GameObject *object) {
- if (object == nullptr) {
- return Common::Point();
- }
- if (object->_hasBoundsAttachment) {
- const GameObject *parent = GameObjects::getObjectByIndex(object->_boundsAttachmentObjectID);
- if (parent != nullptr) {
- return Common::Point(
- parent->_position.x + (int16)object->_boundsAttachmentValue1,
- parent->_position.y + (int16)object->_boundsAttachmentValue2);
- }
- }
- if (object->_sceneIndex > 0x400) {
- const GameObject *holder = GameObjects::getObjectByIndex(object->_sceneIndex - 0x400);
- if (holder != nullptr) {
- return holder->_position;
- }
- }
- return object->_position;
-}
-
void View1::transferPickupTarget(GameObject *targetObject) {
if (targetObject == nullptr) {
return;
@@ -3620,669 +3597,6 @@ uint16 View1::getHitObjectID(const Common::Point &pos) const {
return 0;
}
-bool Character::handleWalkability(Character *c) {
- // Wall-sliding obstacle avoidance from walkAlongPath (1008:1b8f).
- // When the character steps into a non-walkable pixel (walkability >= 200),
- // the original code samples walkability at +/-1 and +/-2 pixels in each
- // axis to build a gradient vector, then slides the character along that
- // vector until it reaches a walkable position.
- if (c->_gameObject->_index != 1) {
- return false;
- }
- if (g_engine->_scriptExecutor->isExecuting()) {
- return false;
- }
-
- Common::Point pos = c->getPosition();
- if (isWalkable(pos)) {
- return false;
- }
-
- // Build a push vector by sampling the walkability map around the current
- // position. Non-walkable neighbors push us away from them.
- int pushX = 0;
- int pushY = 0;
-
- // Sample at distance 1
- if (!isWalkable(Common::Point(pos.x + 1, pos.y)))
- pushX -= 1;
- if (!isWalkable(Common::Point(pos.x - 1, pos.y)))
- pushX += 1;
- if (!isWalkable(Common::Point(pos.x, pos.y + 1)))
- pushY -= 1;
- if (!isWalkable(Common::Point(pos.x, pos.y - 1)))
- pushY += 1;
-
- // Sample at distance 2 for stronger gradient
- if (!isWalkable(Common::Point(pos.x + 2, pos.y)))
- pushX -= 1;
- if (!isWalkable(Common::Point(pos.x - 2, pos.y)))
- pushX += 1;
- if (!isWalkable(Common::Point(pos.x, pos.y + 2)))
- pushY -= 1;
- if (!isWalkable(Common::Point(pos.x, pos.y - 2)))
- pushY += 1;
-
- // Slide along the push vector
- while (pushX != 0 || pushY != 0) {
- if (pushX < 0) {
- if (isWalkable(Common::Point(pos.x - 1, pos.y)))
- pos.x -= 1;
- pushX += 1;
- }
- if (pushX > 0) {
- if (isWalkable(Common::Point(pos.x + 1, pos.y)))
- pos.x += 1;
- pushX -= 1;
- }
- if (pushY < 0) {
- if (isWalkable(Common::Point(pos.x, pos.y - 1)))
- pos.y -= 1;
- pushY += 1;
- }
- if (pushY > 0) {
- if (isWalkable(Common::Point(pos.x, pos.y + 1)))
- pos.y += 1;
- pushY -= 1;
- }
- }
-
- c->setPosition(pos);
- return true;
-}
-
-uint16 Character::lookupWalkability(const Common::Point &p) const {
- return g_engine->getWalkabilityAt((int16)p.y, (int16)p.x);
-}
-
-bool Character::isWalkable(const Common::Point &p) const {
- return Macs2Engine::isWalkabilityWalkable(lookupWalkability(p));
-}
-
-Character::Character() : _pathfindingOverlay(g_engine->screenWidth() * g_engine->gameHeight(), 0) {
-}
-
-bool Character::calculatePath(Common::Point target) {
- // Binary calculatePath (1008:1966). Params: charY, charX, finalDestY, finalDestX, actorIndex.
- // The binary operates on the runtime struct directly; we store equivalent state in _path etc.
- const Common::Point &charPos = _gameObject->_position;
- const int nodeCount = g_engine->getPathfindingNodeCount();
-
- // Step 1: Mark reachability anchored on FINAL DESTINATION (not character)
- // scene[i + 0x50C2] = isPathWalkable(finalDest, node[i])
- bool reachable[kPathNodeSlots + 1] = {};
- for (int i = 1; i <= nodeCount; i++) {
- const Common::Point &nodePos = g_engine->_pathfindingPoints[i - 1]._position;
- reachable[i] = g_engine->isPathWalkable(target.y, target.x, nodePos.y, nodePos.x);
- }
-
- // Step 2: Find best entry node (lowest combined distance to both source and dest)
- int bestCost = 0x7777;
- int bestNode = 0;
- for (int i = 1; i <= nodeCount; i++) {
- const Common::Point &nodePos = g_engine->_pathfindingPoints[i - 1]._position;
- int costToDest = g_engine->euclideanDistance(nodePos, target);
- int costToChar = g_engine->euclideanDistance(nodePos, charPos);
- if (costToDest + costToChar < bestCost) {
- // Verify this node can connect source to target
- // Binary calls canNodeConnectSourceToTarget(destY, destX, charY, charX, i)
- // due to calculatePath being invoked with swapped source/dest params.
- // This means the gate check is "can node see CHARACTER" and the flood-fill
- // checks "any node reachable from DEST" AND "any node visible from CHARACTER".
- // TODO: validate this with a playthought:
- // PVS-Studio V764: Possible incorrect order of arguments passed to
- // 'canNodeConnectSourceToTarget' function: 'target' and 'charPos'.
- // I didn't had any issues in previous runs
- if (canNodeConnectSourceToTarget(i, target, charPos, reachable, nodeCount)) {
- // Recompute cost (binary does this twice)
- costToDest = g_engine->euclideanDistance(nodePos, target);
- costToChar = g_engine->euclideanDistance(nodePos, charPos);
- bestCost = costToDest + costToChar;
- bestNode = i;
- }
- }
- }
-
- if (bestNode == 0) {
- // No path found - go directly to target
- // Binary: pathNodeCount=0, pathIndex=1
- _path.clear();
- _currentPathIndex = 1;
- _targetPosition = target;
- return false;
- }
-
- // Step 3: smoothPath - build path from bestNode toward a reachable node
- _path.clear();
- _path.push_back(bestNode);
- int currentNode = bestNode;
- while (!reachable[currentNode]) {
- const PathfindingPoint &curPt = g_engine->_pathfindingPoints[currentNode - 1];
- int localBestCost = 0x7777;
- int nextNode = currentNode;
- for (uint a = 0; a < curPt._adjacentPoints.size(); a++) {
- const int adjIdx = curPt._adjacentPoints[a];
- const int cost = g_engine->computeMinCostToReachable(adjIdx, 0x7fff, _gameObject->_index, reachable, nodeCount, target);
- const int edgeCost = g_engine->walkableDistance(adjIdx, currentNode);
- if (cost + edgeCost < localBestCost) {
- nextNode = adjIdx;
- localBestCost = cost + edgeCost;
- }
- }
- currentNode = nextNode;
- _path.push_back(currentNode);
- if (_path.size() > kPathNodeSlots)
- break; // safety
- }
-
- // Step 4: Validate path - consecutive nodes must be walkable to each other
- for (uint i = 0; i + 1 < _path.size(); i++) {
- const Common::Point &p1 = g_engine->_pathfindingPoints[_path[i + 1] - 1]._position;
- const Common::Point &p2 = g_engine->_pathfindingPoints[_path[i] - 1]._position;
- if (!g_engine->isPathWalkable(p1.y, p1.x, p2.y, p2.x)) {
- // Path invalid - abort, go directly to target
- _path.clear();
- _targetPosition = target;
- return false;
- }
- }
-
- // Step 5: Skip-forward optimization - skip nodes the character can already reach directly.
- // Binary: checks isPathWalkable(nextNode, charPos) - "can character see the next node?"
- // Note: binary's calculatePath is called with swapped params, so its 'finalDest' param
- // is actually the character position.
- _currentPathIndex = 0;
- while (_currentPathIndex + 1 < (int16)_path.size()) {
- const Common::Point &nextNodePos = g_engine->_pathfindingPoints[_path[_currentPathIndex + 1] - 1]._position;
- if (!g_engine->isPathWalkable(nextNodePos.y, nextNodePos.x, charPos.y, charPos.x))
- break;
- _currentPathIndex++;
- }
-
- // Set immediate target to the current path node
- const Common::Point &firstTarget = g_engine->_pathfindingPoints[_path[_currentPathIndex] - 1]._position;
- _targetPosition = firstTarget;
- return true;
-}
-
-bool Character::canNodeConnectSourceToTarget(uint16 nodeIndex, const Common::Point &charPos, const Common::Point &target, const bool *reachable, int nodeCount) {
- // Checks if node can connect source (charPos) to target:
- // 1. Node must be able to see the target
- // 2. Flood-fill connected component from node
- // 3. Some node in component must see target AND some node must be seen from source
- const Common::Point &nodePos = g_engine->_pathfindingPoints[nodeIndex - 1]._position;
- if (!g_engine->isPathWalkable(nodePos.y, nodePos.x, target.y, target.x))
- return false;
-
- // Flood-fill connected nodes
- bool visited[kPathNodeSlots + 1] = {};
- floodFillConnectedNodes(nodeIndex, visited, nodeCount);
-
- // Check both conditions
- bool anySeesTarget = false;
- bool anySeenFromSource = false;
- for (int i = 1; i <= nodeCount; i++) {
- if (!visited[i])
- continue;
- const Common::Point &p = g_engine->_pathfindingPoints[i - 1]._position;
- if (g_engine->isPathWalkable(p.y, p.x, target.y, target.x))
- anySeesTarget = true;
- if (g_engine->isPathWalkable(charPos.y, charPos.x, p.y, p.x))
- anySeenFromSource = true;
- }
- return anySeesTarget && anySeenFromSource;
-}
-
-void Character::floodFillConnectedNodes(int nodeIndex, bool *visited, int nodeCount) {
- if (nodeIndex < 1 || nodeIndex > nodeCount)
- return;
- if (visited[nodeIndex])
- return;
- visited[nodeIndex] = true;
- const PathfindingPoint &pt = g_engine->_pathfindingPoints[nodeIndex - 1];
- for (uint i = 0; i < pt._adjacentPoints.size(); i++) {
- floodFillConnectedNodes(pt._adjacentPoints[i], visited, nodeCount);
- }
-}
-
-const Common::Point &Character::getPosition() const {
- return _gameObject->_position;
-}
-
-void Character::setPosition(const Common::Point &newPosition) {
- _gameObject->_position = newPosition;
-}
-
-uint16 Character::getVerticalOffset() const {
- uint16 result = g_engine->getWalkabilityAt(getPosition());
- if (Macs2Engine::isWalkabilityBlocking(result)) {
- result = 0;
- }
-
- if (_gameObject->_verticalOffsetScale != 0) {
- const int16 charY = getPosition().y;
- const int32 depthOffset = ((int32)charY - (int32)g_engine->_walkDepthThresholdY) *
- (int32)g_engine->_walkDepthScaleFactor / 100;
- const uint16 scalingFactor = (uint16)((int32)g_engine->_walkBaseSpeedPct + depthOffset);
- result = (scalingFactor * _gameObject->_verticalOffsetScale) / 100;
- }
-
- return result;
-}
-
-bool Character::walkAlongPath() {
- if (_currentPathIndex >= 0 && _currentPathIndex < (int16)_path.size()) {
- const uint16 snapIdx = _path[_currentPathIndex];
- const Common::Point &snapPos = g_engine->_pathfindingPoints[snapIdx - 1]._position;
- _gameObject->_position = snapPos;
- }
- _currentPathIndex++;
- if (_currentPathIndex >= (int16)_path.size()) {
- // Past end of path - walk to final destination, then stop
- _targetPosition = _pathFinalDestination;
- _stepDeltaX = (int16)ABS(_targetPosition.x - _gameObject->_position.x);
- _stepDeltaY = (int16)ABS(_targetPosition.y - _gameObject->_position.y);
- _stepError = 0;
- _stepDirectionSet = false;
- return false; // No more path segments after this
- }
- const uint16 nodeIdx = _path[_currentPathIndex];
- const Common::Point &nodePos = g_engine->_pathfindingPoints[nodeIdx - 1]._position;
- _targetPosition = nodePos;
- _stepDeltaX = (int16)ABS(_targetPosition.x - _gameObject->_position.x);
- _stepDeltaY = (int16)ABS(_targetPosition.y - _gameObject->_position.y);
- _stepError = 0;
- _stepDirectionSet = false;
- return true;
-}
-
-bool Character::isAnimationMirrored() const {
- switch (_gameObject->_orientation) {
- case OrientationSouthWest:
- case OrientationWest:
- case OrientationNorthWest:
- case OrientationStandingSouthWest:
- case OrientationStandingWest:
- case OrientationStandingNorthWest:
- return true;
- default:
- break;
- }
- return false;
-}
-
-bool Character::fillCurrentAnimationFrame(uint16 advanceMode, Macs2::AnimFrame &out) const {
- const uint16 animSlot = g_engine->resolveAnimSlotIndex(_gameObject);
-
- Common::Array<uint8> *blobPtr = _gameObject->getAnimSlotBlob(animSlot);
- if (blobPtr == nullptr || blobPtr->empty()) {
- return false;
- }
-
- Common::Array<uint8> &blob = *blobPtr;
- const uint16 frameStart = BackgroundAnimationBlob::advanceAnimFrame(blob, true, advanceMode);
- out._offsetX = (int16)READ_LE_UINT16(&blob[frameStart]);
- out._offsetY = (int16)READ_LE_UINT16(&blob[frameStart + 2]);
- const uint16 offset = frameStart + 6;
- out._width = READ_LE_UINT16(&blob[offset]);
- out._height = READ_LE_UINT16(&blob[offset + 2]);
- out._data.resize(out._width * out._height);
- memcpy(out._data.data(), &blob[offset + 4], out._width * out._height);
- return true;
-}
-
-Macs2::AnimFrame *Character::getCurrentPortrait(bool onRightSide, uint16 frameIndex) {
- if (_gameObject->_blobs.size() <= 17) {
- return nullptr;
- }
-
- uint portraitBlobIndex = 17;
- if (_gameObject->_blobs.size() > 18 && !_gameObject->_blobs[18].empty()) {
- if (onRightSide || _gameObject->_blobs[portraitBlobIndex].empty()) {
- portraitBlobIndex = 18;
- }
- }
-
- if (_gameObject->_blobs[portraitBlobIndex].empty()) {
- return nullptr;
- }
-
- const uint16 offset = BackgroundAnimationBlob::advanceAnimFrame(_gameObject->_blobs[portraitBlobIndex], true, frameIndex);
- // offset points to per-frame: offsetX(2), offsetY(2), unknown(2), width(2), height(2), pixels
- Common::Array<uint8> &blob = _gameObject->_blobs[portraitBlobIndex];
- AnimFrame *result = new AnimFrame();
- result->_width = READ_LE_UINT16(&blob[offset + 6]);
- result->_height = READ_LE_UINT16(&blob[offset + 8]);
- result->_data.resize(result->_width * result->_height);
- memcpy(result->_data.data(), &blob[offset + 10], result->_width * result->_height);
- return result;
-}
-
-// Leftover lerp-era entry point. Duration and ignoreObstacles are unused.
-// Binary walkAlongPath (1008:1b8f) has no time lerp: Phase 0 sets 8-way
-// orientation and returns (1-frame delay); Phase 1 loops stepCounter 1..walkSpeed
-// with one-pixel Bresenham (error >= deltaX -> step Y else step X).
-// walkSpeed = animSpeed * (scene[0x5201] + depth) / 100, min 1;
-// depth = (posY - scene[0x51FD]) * scene[0x51FF] / 100.
-// Character::update() implements that. C++ walkAlongPath() is only the inlined
-// waypoint advance. Path setup is walkToScreenPosition / scriptWalkToPosition.
-void Character::startLerpTo(const Common::Point &target, uint32 duration, bool ignoreObstacles) {
- _startPosition = getPosition();
- _targetPosition = target;
- _startTime = g_events->currentMillis;
- _duration = duration;
-
- // Reset Bresenham state - direction will be calculated on first Update()
- _stepDirectionSet = false;
- _stepDeltaX = (int16)ABS(_targetPosition.x - _startPosition.x);
- _stepDeltaY = (int16)ABS(_targetPosition.y - _startPosition.y);
- _stepError = 0;
-}
-
-void Character::startPickup(Macs2::GameObject *object) {
- _pickedUpObject = object;
- _pathFinalDestination = getObjectEffectivePosition(object);
- _pickupFrameCounter = 0;
- _pickupItemTransferred = false;
-
- const Common::Point ¤t = getPosition();
- const int16 destX = _pathFinalDestination.x;
- const int16 destY = _pathFinalDestination.y;
-
- _currentPathIndex = 0;
- _path.clear();
-
- const bool directPath = g_engine->isPathWalkable(destY, destX, current.y, current.x);
- if (!directPath && Macs2Engine::isWalkabilityWalkable(g_engine->getWalkabilityAt(destY, destX))) {
- calculatePath(Common::Point(destX, destY));
- }
-
- if (_path.empty()) {
- _targetPosition = _pathFinalDestination;
- }
-
- _stepDeltaX = (int16)ABS(_targetPosition.x - current.x);
- _stepDeltaY = (int16)ABS(_targetPosition.y - current.y);
- _stepError = 0;
- _stepDirectionSet = false;
-}
-
-bool Character::hasPendingVerticalMotion() const {
- return (int16)_motionTargetVerticalOffset >= 0 &&
- _motionTargetVerticalOffset != _gameObject->_verticalOffsetScale;
-}
-
-bool Character::shouldStepVerticalMotion() const {
- return (int16)_motionTargetVerticalOffset < 0 ||
- _motionTargetVerticalOffset != _gameObject->_verticalOffsetScale;
-}
-
-void Character::update() {
- Script::ScriptExecutor *exec = g_engine->_scriptExecutor;
- if (_gameObject->_orientation == OrientationPickup) {
- if (_pickedUpObject != nullptr) {
- View1 *currentView = (View1 *)g_engine->findView("View1");
-
- if (!_pickupItemTransferred && _pickupFrameCounter == _gameObject->_pickupFrameStart) {
- _pickupItemTransferred = true;
- currentView->transferPickupTarget(_pickedUpObject);
- }
-
- if (_pickupFrameCounter == _gameObject->_pickupFrameEnd) {
- _gameObject->_orientation = _previousOrientation;
- if (exec->_pickupInProgress) {
- exec->_pickupInProgress = false;
- exec->_pickupActorObjectID = 0;
- exec->_pickupTargetObjectID = 0;
- g_engine->setCursorMode(exec->_cursorModeBeforeWait);
- currentView->updateCursor();
- }
- exec->_walkTargetObjectIndex = 0;
- _pickedUpObject = nullptr;
- exec->_interactedObjectID = 0;
- exec->_interactedInventoryItemId = 0;
- g_engine->_movementFinishedFlag = true;
- return;
- }
-
- _pickupFrameCounter++;
- }
- return;
- }
-
- Common::Point pos = getPosition();
- const int32 depthOffset = ((int32)pos.y - (int32)g_engine->_walkDepthThresholdY) *
- (int32)g_engine->_walkDepthScaleFactor / 100;
- // Per-animation speed from blob data
- // Walk speed from binary walkAlongPath
- uint16 animSpeed = 2; // default fallback
- ObjectOrientation orient = _gameObject->_orientation;
- if (orient >= OrientationNorth && orient <= g_engine->maxAnimSlots() && (uint)(orient - 1) < _gameObject->_blobWalkSpeeds.size()) {
- animSpeed = _gameObject->_blobWalkSpeeds[orient - 1];
- if (animSpeed == 0) {
- animSpeed = 2;
- }
- }
- int walkSpeed = ((int)animSpeed * ((int)g_engine->_walkBaseSpeedPct + (int)depthOffset)) / 100;
- if (walkSpeed < 1) {
- walkSpeed = 1;
- }
-
- // Proximity arrival check from walkAlongPath
- bool arrived = (ABS(pos.x - _targetPosition.x) <= walkSpeed) &&
- (ABS(pos.y - _targetPosition.y) <= walkSpeed);
- if (arrived && hasPendingVerticalMotion()) {
- arrived = false;
- }
- if (arrived) {
- const bool atFinalDest = (_targetPosition.x == _pathFinalDestination.x &&
- _targetPosition.y == _pathFinalDestination.y);
-
- if (!atFinalDest && !_path.empty()) {
- // Mid-path waypoint arrival: advance to next node
- // Binary (23b0): snap pos to current path node, advance pathIndex
- walkAlongPath();
- return;
- }
-
- // Final destination arrival (or direct walk arrival)
- if (_gameObject->_snapToTarget) {
- setPosition(_targetPosition);
- _pathFinalDestination = _targetPosition;
- } else {
- _targetPosition = pos;
- _pathFinalDestination = pos;
- if ((int16)_motionTargetVerticalOffset >= 0) {
- _motionTargetVerticalOffset = _gameObject->_verticalOffsetScale;
- }
- }
- _path.clear();
- if (hasPendingVerticalMotion()) {
- _gameObject->_verticalOffsetScale = _motionTargetVerticalOffset;
- _motionProgress = _motionDistanceUnits;
- }
- // Walk arrival: orientation changes to standing (walking dir + 8).
- // Script resumption is handled by position polling in View1::tick().
- const bool wasWalking = (_gameObject->_orientation < OrientationStandingNorth);
- if (wasWalking) {
- _gameObject->_orientation = (ObjectOrientation)(_gameObject->_orientation + OrientationNorthWest);
- g_engine->_movementFinishedFlag = true;
- }
- return;
- }
-
- // Binary: if target==current position, skip Phase 0 turn delay (set directionCalculated=1)
- if (!_stepDirectionSet && _targetPosition.x == pos.x && _targetPosition.y == pos.y) {
- _stepDirectionSet = true;
- }
-
- // Calculate direction if not yet set (first frame of movement)
- if (!_stepDirectionSet) {
- _stepDirectionSet = true;
- // Phase 0 from walkAlongPath (1008:1b8f): direction calculation.
- // Binary returns after setting direction (1-frame turn delay).
- const uint16 absDx = (uint16)ABS(pos.x - _targetPosition.x);
- const uint16 absDy = (uint16)ABS(pos.y - _targetPosition.y);
- ObjectOrientation dir = _gameObject->_orientation;
- if (dir >= OrientationStandingNorth && dir <= OrientationStandingNorthWest)
- dir = (ObjectOrientation)(dir - OrientationNorthWest);
- if (dir > OrientationStandingNorthWest)
- dir = OrientationNorth;
- // Cardinal directions (only if animation available for that direction)
- if (_targetPosition.y < pos.y && absDx <= absDy &&
- _gameObject->_blobs.size() > 0 && !_gameObject->_blobs[0].empty())
- dir = OrientationNorth;
- if (pos.x < _targetPosition.x && absDy <= absDx &&
- _gameObject->_blobs.size() > 2 && !_gameObject->_blobs[2].empty())
- dir = OrientationEast;
- if (pos.y < _targetPosition.y && absDx <= absDy &&
- _gameObject->_blobs.size() > 4 && !_gameObject->_blobs[4].empty())
- dir = OrientationSouth;
- if (_targetPosition.x < pos.x && absDy <= absDx &&
- _gameObject->_blobs.size() > 6 && !_gameObject->_blobs[6].empty())
- dir = OrientationWest;
- // Diagonals: absDx/4 < absDy AND absDy/2 < absDx
- if ((absDx >> 2) < absDy && (absDy >> 1) < absDx) {
- if (_targetPosition.y < pos.y && pos.x < _targetPosition.x &&
- _gameObject->_blobs.size() > 1 && !_gameObject->_blobs[1].empty())
- dir = OrientationNorthEast;
- if (pos.x < _targetPosition.x && pos.y < _targetPosition.y &&
- _gameObject->_blobs.size() > 3 && !_gameObject->_blobs[3].empty())
- dir = OrientationSouthEast;
- if (pos.y < _targetPosition.y && _targetPosition.x < pos.x &&
- _gameObject->_blobs.size() > 5 && !_gameObject->_blobs[5].empty())
- dir = OrientationSouthWest;
- if (_targetPosition.x < pos.x && _targetPosition.y < pos.y &&
- _gameObject->_blobs.size() > 7 && !_gameObject->_blobs[7].empty())
- dir = OrientationNorthWest;
- }
- _gameObject->_orientation = dir;
- _stepDeltaX = (int16)absDx;
- _stepDeltaY = (int16)absDy;
- _stepError = 0;
- // 1-frame turn delay: return after setting direction (binary Phase 0)
- return;
- }
-
- // Phase 1: Bresenham stepping loop - exact 1:1 match of binary (1008:1ea1..2280)
- // Binary: stepCounter from 1 to walkSpeed, NO early break. Loop always completes.
- // After loop: if pixelsMoved != walkSpeed -> revert pos to savedPos and cancel path.
- int pixelsMoved = 0;
- Common::Point savedPos = pos;
- for (int stepCounter = 1; stepCounter <= walkSpeed; stepCounter++) {
- savedPos = pos; // Binary: savedX/savedY at top of each iteration
- // Bresenham: if error >= deltaX -> step Y, else step X
- if (_stepError >= _stepDeltaX) {
- // Step Y axis
- if (_targetPosition.y != pos.y)
- pixelsMoved++;
- if (_targetPosition.y < pos.y)
- pos.y--;
- else if (_targetPosition.y > pos.y)
- pos.y++;
- _stepError -= _stepDeltaX;
- } else {
- // Step X axis
- if (_targetPosition.x != pos.x)
- pixelsMoved++;
- if (_targetPosition.x < pos.x)
- pos.x--;
- else if (_targetPosition.x > pos.x)
- pos.x++;
- _stepError += _stepDeltaY;
- }
- // Vertical offset interpolation
- if (shouldStepVerticalMotion()) {
- _motionProgress += _motionVerticalOffsetDelta;
- while (_motionProgress >= _motionDistanceUnits && _motionDistanceUnits > 0) {
- _motionProgress -= _motionDistanceUnits;
- if (_motionTargetVerticalOffset < _gameObject->_verticalOffsetScale)
- _gameObject->_verticalOffsetScale--;
- else if (_motionTargetVerticalOffset > _gameObject->_verticalOffsetScale)
- _gameObject->_verticalOffsetScale++;
- }
- }
- // Walkability check - binary uses getWalkabilityAt(posY, posX) >= 0xC8
- if (!isWalkable(pos)) {
- const uint16 tileArea = exec->getAreaAtPoint(pos.x, pos.y);
- if (tileArea >= 210 && tileArea <= 215) {
- debugC(kDebugPath,
- "walk blocked on plate area %u at (%d,%d) walk=%u int16=%d target=(%d,%d)",
- tileArea, pos.x, pos.y, lookupWalkability(pos), (int16)lookupWalkability(pos),
- _targetPosition.x, _targetPosition.y);
- }
- // Revert position
- pos = savedPos;
- // Wall-sliding: build push vector from +/-1 and +/-2 samples
- int pushX = 0, pushY = 0;
- if (Macs2Engine::isWalkabilityBlocking(lookupWalkability(Common::Point(pos.x + 1, pos.y))))
- pushX--;
- if (Macs2Engine::isWalkabilityBlocking(lookupWalkability(Common::Point(pos.x - 1, pos.y))))
- pushX++;
- if (Macs2Engine::isWalkabilityBlocking(lookupWalkability(Common::Point(pos.x, pos.y + 1))))
- pushY--;
- if (Macs2Engine::isWalkabilityBlocking(lookupWalkability(Common::Point(pos.x, pos.y - 1))))
- pushY++;
- if (Macs2Engine::isWalkabilityBlocking(lookupWalkability(Common::Point(pos.x + 2, pos.y))))
- pushX--;
- if (Macs2Engine::isWalkabilityBlocking(lookupWalkability(Common::Point(pos.x - 2, pos.y))))
- pushX++;
- if (Macs2Engine::isWalkabilityBlocking(lookupWalkability(Common::Point(pos.x, pos.y + 2))))
- pushY--;
- if (Macs2Engine::isWalkabilityBlocking(lookupWalkability(Common::Point(pos.x, pos.y - 2))))
- pushY++;
- // Apply push vector
- while (pushX != 0 || pushY != 0) {
- if (pushX < 0) {
- if (Macs2Engine::isWalkabilityWalkable(lookupWalkability(Common::Point(pos.x - 1, pos.y))))
- pos.x--;
- pushX++;
- }
- if (pushX > 0) {
- if (Macs2Engine::isWalkabilityWalkable(lookupWalkability(Common::Point(pos.x + 1, pos.y))))
- pos.x++;
- pushX--;
- }
- if (pushY < 0) {
- if (Macs2Engine::isWalkabilityWalkable(lookupWalkability(Common::Point(pos.x, pos.y - 1))))
- pos.y--;
- pushY++;
- }
- if (pushY > 0) {
- if (Macs2Engine::isWalkabilityWalkable(lookupWalkability(Common::Point(pos.x, pos.y + 1))))
- pos.y++;
- pushY--;
- }
- }
- // Binary: target = finalDest = pos (cancel path, but loop continues)
- _targetPosition = pos;
- _pathFinalDestination = pos;
- _path.clear();
- }
- // Binary: loop continues unconditionally until stepCounter == walkSpeed
- }
-
- if (pixelsMoved != walkSpeed) {
- const uint16 tileArea = exec->getAreaAtPoint(pos.x, pos.y);
- if (tileArea >= 210 && tileArea <= 215) {
- debugC(kDebugPath,
- "walk cancelled pixelsMoved=%d walkSpeed=%d at (%d,%d) area=%u walk=%u finalDest=(%d,%d)",
- pixelsMoved, walkSpeed, pos.x, pos.y, tileArea, lookupWalkability(pos),
- _pathFinalDestination.x, _pathFinalDestination.y);
- } else if (Macs2Engine::isWalkabilityBlocking(lookupWalkability(pos))) {
- debugC(kDebugPath,
- "walk cancelled (non-walkable) pixelsMoved=%d walkSpeed=%d at (%d,%d) walk=%u",
- pixelsMoved, walkSpeed, pos.x, pos.y, lookupWalkability(pos));
- }
- pos = savedPos;
- _targetPosition = pos;
- _pathFinalDestination = pos;
- _path.clear();
- }
-
- setPosition(pos);
-}
-
bool Button::isPointInside(const Common::Point &p) const {
return false;
}
diff --git a/engines/macs2/view1.h b/engines/macs2/view1.h
index 6e30fa48b43..8a59beb7726 100644
--- a/engines/macs2/view1.h
+++ b/engines/macs2/view1.h
@@ -22,6 +22,7 @@
#ifndef MACS2_VIEW1_H
#define MACS2_VIEW1_H
+#include "macs2/character.h"
#include "macs2/events.h"
#include "macs2/gameobjects.h"
#include "macs2/macs2.h"
@@ -53,78 +54,6 @@ public:
void render(Graphics::ManagedSurface &s);
};
-class Character {
-private:
- Common::Point _startPosition;
-
- uint32 _startTime = 0;
- uint32 _duration = 0;
-
- // If this is set, a lerp to a location becomes picking up
- Macs2::GameObject *_pickedUpObject = nullptr;
-
- // Handle when the character has moved into a non-walkable area, push them out if
- // they did and return true, return false otherwise
- bool handleWalkability(Character *c);
-
- uint16 lookupWalkability(const Common::Point &p) const;
- bool shouldStepVerticalMotion() const;
- bool isAnimationMirrored() const;
- void floodFillConnectedNodes(int nodeIndex, bool *visited, int nodeCount);
- // Returns false if we are at the end of the path already or the path is not valid
- bool walkAlongPath();
- void startLerpTo(const Common::Point &target, uint32 duration, bool ignoreObstacles = false);
- bool isWalkable(const Common::Point &p) const;
- bool canNodeConnectSourceToTarget(uint16 nodeIndex, const Common::Point &charPos, const Common::Point &target, const bool *reachable, int nodeCount);
-
-public:
- Character();
-
- // Frame counter for pickup animation (runtime+0x215).
- // Increments each frame while orientation == 0x11.
- // At _pickupFrameStart: item is transferred to inventory.
- // At _pickupFrameEnd: animation ends, orientation restored.
- uint16 _pickupFrameCounter = 0;
- bool _pickupItemTransferred = false;
- bool _markedForDeletion = false;
-
- ObjectOrientation _previousOrientation = OrientationNone;
-
- // Walk state from walkAlongPath (1008:1b8f) - runtime offsets +0x00..+0x0A, +0x18, +0x33
- Common::Point _targetPosition; // runtime[+0x00, +0x02]: next waypoint
- int16 _stepDeltaX = 0; // runtime[+0x04]: abs(endX - startX)
- int16 _stepDeltaY = 0; // runtime[+0x06]: abs(endY - startY)
- int16 _stepError = 0; // runtime[+0x18]: Bresenham error accumulator
- bool _stepDirectionSet = false; // runtime[+0x33]: direction has been calculated
-
- Common::Array<uint16> _path;
- int16 _currentPathIndex = 0;
- Common::Point _pathFinalDestination;
- Common::Array<uint8> _pathfindingOverlay;
-
- Macs2::GameObject *_gameObject = nullptr;
- uint16 _motionTargetVerticalOffset = 0;
- uint16 _motionVerticalOffsetDelta = 0;
- uint16 _motionDistanceUnits = 0;
- uint16 _motionProgress = 0;
- uint16 _motionStartVerticalOffset = 0;
-
- bool calculatePath(Common::Point target);
- void startPickup(Macs2::GameObject *object);
-
- const Common::Point &getPosition() const;
- void setPosition(const Common::Point &newPosition);
-
- uint16 getVerticalOffset() const;
- bool hasPendingVerticalMotion() const;
- bool fillCurrentAnimationFrame(uint16 advanceMode, Macs2::AnimFrame &out) const;
- Macs2::AnimFrame *getCurrentPortrait(bool onRightSide = false, uint16 frameIndex = 0);
-
- void update();
-};
-
-void resetCharacterWalkPath(Character *character);
-
struct SpeechActData {
Character *speaker = nullptr;
Common::Array<Common::String> strings;
Commit: fcec8c98bfb83ab1dc71883d8d86f581ae4fb49e
https://github.com/scummvm/scummvm/commit/fcec8c98bfb83ab1dc71883d8d86f581ae4fb49e
Author: Martin Gerhardy (martin.gerhardy at gmail.com)
Date: 2026-09-02T22:01:21+02:00
Commit Message:
MACS2: removed Button class
Changed paths:
engines/macs2/macs2.cpp
engines/macs2/scriptexecutor.cpp
engines/macs2/view1.cpp
engines/macs2/view1.h
diff --git a/engines/macs2/macs2.cpp b/engines/macs2/macs2.cpp
index fec3334f6e3..343e6da93ed 100644
--- a/engines/macs2/macs2.cpp
+++ b/engines/macs2/macs2.cpp
@@ -685,7 +685,7 @@ void Macs2Engine::softRestart() {
currentView->_isShowingDialoguePanel = false;
currentView->_isDialogueChoiceInputActive = false;
currentView->_isShowingTextBox = false;
- currentView->currentSpeechActData = SpeechActData();
+ currentView->_currentSpeechActData = SpeechActData();
}
for (uint i = 0; i < GameObjects::instance()._objects.size(); i++)
@@ -1521,7 +1521,7 @@ void Macs2Engine::changeScene(uint32 newSceneIndex, bool executeScript) {
currentView->handleTextBoxInput();
currentView->_drawnStringBox.clear();
currentView->_continueScriptAfterUI = false;
- currentView->currentSpeechActData = SpeechActData();
+ currentView->_currentSpeechActData = SpeechActData();
currentView->_pendingPanelRequest = View1::kPanelRequestNone;
currentView->_activeInventoryItem = nullptr;
currentView->_uiPanelState = View1::kUiPanelNone;
diff --git a/engines/macs2/scriptexecutor.cpp b/engines/macs2/scriptexecutor.cpp
index 3f77b002e71..d1dfe05e985 100644
--- a/engines/macs2/scriptexecutor.cpp
+++ b/engines/macs2/scriptexecutor.cpp
@@ -433,7 +433,7 @@ void ScriptExecutor::scriptPrintString(bool alignRight) {
currentView->_stringBoxPosition = Common::Point(stringBoxX, stringBoxY);
currentView->_drawnStringBox = strings;
currentView->_isShowingTextBox = true;
- currentView->currentSpeechActData.speaker = nullptr;
+ currentView->_currentSpeechActData.speaker = nullptr;
currentView->_continueScriptAfterUI = true;
_engine->sayText(joinTtsLines(strings), Common::TextToSpeechManager::INTERRUPT);
currentView->redraw();
diff --git a/engines/macs2/view1.cpp b/engines/macs2/view1.cpp
index 4fb9d0376bc..6fcb9e6c9f7 100644
--- a/engines/macs2/view1.cpp
+++ b/engines/macs2/view1.cpp
@@ -224,7 +224,7 @@ const View1::BorderStyle View1::kBorderPressed = {0x1010, 0x1011, 0x1012};
View1::View1() : UIElement("View1") {
_backgroundSurface.copyFrom(g_engine->_sceneBackground);
- currentSpeechActData.onRightSide = false;
+ _currentSpeechActData.onRightSide = false;
updateCursor();
setViewPaletteSafely(g_engine->_pal);
_paletteDirty = false;
@@ -723,29 +723,29 @@ void View1::drawCurrentSpeaker(Graphics::ManagedSurface &s) {
// Cycles between frame 1 (mouth open) and frame 2 (mouth closed)
// based on a decrementing counter, creating a talking animation.
bool useAlternateBlob = false;
- if (currentSpeechActData.mouthAnimActive) {
- if (currentSpeechActData.mouthAnimCounter <= 0) {
+ if (_currentSpeechActData.mouthAnimActive) {
+ if (_currentSpeechActData.mouthAnimCounter <= 0) {
useAlternateBlob = true;
}
}
// Select portrait blob: primary (Blobs[17]) during countdown, alternate (Blobs[18]) after
// Mode 0: render current frame without advancing (advance happens in tick())
- Common::ScopedPtr<AnimFrame> frame(currentSpeechActData.speaker->getCurrentPortrait(useAlternateBlob, 0));
+ Common::ScopedPtr<AnimFrame> frame(_currentSpeechActData.speaker->getCurrentPortrait(useAlternateBlob, 0));
if (!frame) {
return;
}
- Common::ScopedPtr<AnimFrame> leftPortrait(currentSpeechActData.speaker->getCurrentPortrait(false, 0));
- Common::ScopedPtr<AnimFrame> rightPortrait(currentSpeechActData.speaker->getCurrentPortrait(true, 0));
+ Common::ScopedPtr<AnimFrame> leftPortrait(_currentSpeechActData.speaker->getCurrentPortrait(false, 0));
+ Common::ScopedPtr<AnimFrame> rightPortrait(_currentSpeechActData.speaker->getCurrentPortrait(true, 0));
- Common::Point pos = currentSpeechActData.position;
+ Common::Point pos = _currentSpeechActData.position;
if (!g_engine->isAmiga()) {
const int portraitWidth = MAX<int>(leftPortrait ? leftPortrait->_width : 0, rightPortrait ? rightPortrait->_width : 0);
const int portraitHeight = MAX<int>(leftPortrait ? leftPortrait->_height : 0, rightPortrait ? rightPortrait->_height : 0);
const int borderPad = g_engine->portraitBorderPad();
const int contentInset = g_engine->portraitContentInset();
const Common::Point borderSize(portraitWidth + borderPad, portraitHeight + borderPad);
- drawBorder(currentSpeechActData.position, borderSize, s);
+ drawBorder(_currentSpeechActData.position, borderSize, s);
pos += Common::Point(contentInset, contentInset);
}
drawSprite(pos, frame->_width, frame->_height, frame->_data.data(), s, false);
@@ -2317,7 +2317,7 @@ void View1::draw() {
}
}
}
- if (currentSpeechActData.speaker != nullptr) {
+ if (_currentSpeechActData.speaker != nullptr) {
drawCurrentSpeaker(s);
}
}
@@ -2478,16 +2478,16 @@ bool View1::tick() {
}
// Advance portrait animation once per tick
- if (_isShowingDialoguePanel && currentSpeechActData.speaker != nullptr && currentSpeechActData.mouthAnimActive) {
- Character *speaker = currentSpeechActData.speaker;
- if (currentSpeechActData.mouthAnimCounter < 1) {
+ if (_isShowingDialoguePanel && _currentSpeechActData.speaker != nullptr && _currentSpeechActData.mouthAnimActive) {
+ Character *speaker = _currentSpeechActData.speaker;
+ if (_currentSpeechActData.mouthAnimCounter < 1) {
// counter < 1: advance alternate blob (Blobs[18]) with mode 2
if (speaker->_gameObject->_blobs.size() > 18 && !speaker->_gameObject->_blobs[18].empty()) {
BackgroundAnimationBlob::advanceAnimFrame(speaker->_gameObject->_blobs[18], true, 2);
}
} else {
- currentSpeechActData.mouthAnimCounter--;
- if (currentSpeechActData.mouthAnimCounter < 1) {
+ _currentSpeechActData.mouthAnimCounter--;
+ if (_currentSpeechActData.mouthAnimCounter < 1) {
// just hit 0: reset alternate blob (Blobs[18]) with mode 1
if (speaker->_gameObject->_blobs.size() > 18 && !speaker->_gameObject->_blobs[18].empty()) {
BackgroundAnimationBlob::advanceAnimFrame(speaker->_gameObject->_blobs[18], true, 1);
@@ -3330,10 +3330,10 @@ void View1::showSpeechAct(uint16 characterIndex, const Common::Array<Common::Str
_dialogueChoiceCount = 0;
_continueScriptAfterUI = true;
- currentSpeechActData.speaker = getCharacterByIndex(characterIndex);
- currentSpeechActData.strings = strings;
- currentSpeechActData.position = position;
- currentSpeechActData.onRightSide = onRightSide;
+ _currentSpeechActData.speaker = getCharacterByIndex(characterIndex);
+ _currentSpeechActData.strings = strings;
+ _currentSpeechActData.position = position;
+ _currentSpeechActData.onRightSide = onRightSide;
const int padW = g_engine->dialogPadW();
const int padH = g_engine->dialogPadH();
@@ -3344,9 +3344,9 @@ void View1::showSpeechAct(uint16 characterIndex, const Common::Array<Common::Str
int stringBoxY = position.y;
Common::Point portraitBoxPosition = position;
- if (currentSpeechActData.speaker != nullptr) {
- AnimFrame *leftPortrait = currentSpeechActData.speaker->getCurrentPortrait(false);
- AnimFrame *rightPortrait = currentSpeechActData.speaker->getCurrentPortrait(true);
+ if (_currentSpeechActData.speaker != nullptr) {
+ AnimFrame *leftPortrait = _currentSpeechActData.speaker->getCurrentPortrait(false);
+ AnimFrame *rightPortrait = _currentSpeechActData.speaker->getCurrentPortrait(true);
const int portraitWidth = MAX<int>(leftPortrait ? leftPortrait->_width : 0, rightPortrait ? rightPortrait->_width : 0);
if (portraitWidth > 0) {
if (onRightSide) {
@@ -3360,19 +3360,19 @@ void View1::showSpeechAct(uint16 characterIndex, const Common::Array<Common::Str
delete rightPortrait;
}
- currentSpeechActData.position = portraitBoxPosition;
+ _currentSpeechActData.position = portraitBoxPosition;
// Activate mouth animation (handleTimerCallback 1008:d38b)
- currentSpeechActData.mouthAnimActive = (currentSpeechActData.speaker != nullptr);
+ _currentSpeechActData.mouthAnimActive = (_currentSpeechActData.speaker != nullptr);
// Original: PTR_LOOP_1020_1004 = sum of all line lengths (total character count)
int16 totalChars = 0;
for (const Common::String &line : strings) {
totalChars += line.size();
}
- currentSpeechActData.mouthAnimCounter = (totalChars > 0) ? totalChars : 1;
+ _currentSpeechActData.mouthAnimCounter = (totalChars > 0) ? totalChars : 1;
_stringBoxPosition = Common::Point(stringBoxX, stringBoxY);
debugC(kDebugScript, "Layout speech act: speaker=%u rawPos=(%d,%d) rightSide=%u portraitBorderPos=(%d,%d) textBorderPos=(%d,%d) textBorderSize=(%d,%d) text=\"%s\"",
characterIndex, position.x, position.y, onRightSide ? 1 : 0,
- currentSpeechActData.position.x, currentSpeechActData.position.y,
+ _currentSpeechActData.position.x, _currentSpeechActData.position.y,
_stringBoxPosition.x, _stringBoxPosition.y, totalWidth, totalHeight, joinDebugStrings(strings).c_str());
if (_autoclickActive) {
@@ -3597,13 +3597,6 @@ uint16 View1::getHitObjectID(const Common::Point &pos) const {
return 0;
}
-bool Button::isPointInside(const Common::Point &p) const {
- return false;
-}
-
-void Button::render(Graphics::ManagedSurface &s) {
-}
-
void View1::openOriginalSaveLoadPanel() {
_pendingPanelRequest = kPanelRequestNone;
_uiPanelState = kUiPanelSaveLoad;
diff --git a/engines/macs2/view1.h b/engines/macs2/view1.h
index 8a59beb7726..ae8541dd33a 100644
--- a/engines/macs2/view1.h
+++ b/engines/macs2/view1.h
@@ -43,17 +43,6 @@ enum class FadeMode {
ToBlack
};
-class Button {
-public:
- Common::Point _position;
- Common::Point _size;
- Common::String _caption;
-
- bool isPointInside(const Common::Point &p) const;
-
- void render(Graphics::ManagedSurface &s);
-};
-
struct SpeechActData {
Character *speaker = nullptr;
Common::Array<Common::String> strings;
@@ -239,7 +228,7 @@ public:
Common::StringArray _drawnStringBox;
uint16 _dialogueChoiceCount = 0;
Common::Array<uint16> _dialogueChoiceLineCounts;
- SpeechActData currentSpeechActData;
+ SpeechActData _currentSpeechActData;
Graphics::ManagedSurface _backgroundSurface;
bool _started = false;
More information about the Scummvm-git-logs
mailing list