[Scummvm-git-logs] scummvm master -> c7ca3456d20b9fc6f979fe54b297a6ed5de382e5
mgerhardy
noreply at scummvm.org
Thu Sep 3 17:52:53 UTC 2026
This automated email contains information about 3 new commits which have been
pushed to the 'scummvm' repo located at https://api.github.com/repos/scummvm/scummvm .
Summary:
746cb6513f MACS2: extract pathfinding component
8a1680afbe MACS2: extract text code
c7ca3456d2 MACS2: don't read opl values from exe, but embed them in the engine code
Commit: 746cb6513f171b76afc2c2e2003708d74f57a5ea
https://github.com/scummvm/scummvm/commit/746cb6513f171b76afc2c2e2003708d74f57a5ea
Author: Martin Gerhardy (martin.gerhardy at gmail.com)
Date: 2026-09-03T19:50:40+02:00
Commit Message:
MACS2: extract pathfinding component
Changed paths:
A engines/macs2/pathfinding.cpp
A engines/macs2/pathfinding.h
engines/macs2/amiga_resources.cpp
engines/macs2/character.cpp
engines/macs2/character.h
engines/macs2/debugtools.cpp
engines/macs2/macs2.cpp
engines/macs2/macs2.h
engines/macs2/module.mk
engines/macs2/saveload.cpp
engines/macs2/scriptexecutor.cpp
engines/macs2/view1.cpp
diff --git a/engines/macs2/amiga_resources.cpp b/engines/macs2/amiga_resources.cpp
index a566d8a4860..200d8639ae3 100644
--- a/engines/macs2/amiga_resources.cpp
+++ b/engines/macs2/amiga_resources.cpp
@@ -112,13 +112,13 @@ bool Macs2Engine::loadAmigaSceneBackground(uint32 sceneResourceId) {
Common::Rect screenRect(0, 0, kScreenWidth, kGameHeight);
_depthMap.fillRect(screenRect, 0);
- _pathfindingMap.fillRect(screenRect, 0);
+ _pathfinding._map.fillRect(screenRect, 0);
_shadowMap.fillRect(screenRect, 0);
_hotspotMap.fillRect(screenRect, 0);
Common::Array<byte> pathMap, depthMap, shadowMap;
if (extractAmigaMxmmSceneMaps(mxmm.data(), size, pathMap, depthMap, shadowMap)) {
- blitMap(_pathfindingMap, pathMap);
+ blitMap(_pathfinding._map, pathMap);
blitMap(_depthMap, depthMap);
blitMap(_shadowMap, shadowMap);
}
@@ -149,12 +149,12 @@ bool Macs2Engine::loadAmigaSceneBackground(uint32 sceneResourceId) {
(uint)sceneResourceId);
}
- _pathfindingPoints.clear();
- _numPathfindingPoints = 0;
+ _pathfinding._points.clear();
+ _pathfinding._numPoints = 0;
uint16 numPfPoints = 0;
Common::Array<AmigaPathfindingNode> pfNodes;
if (extractAmigaMxmmScenePathfinding(mxmm.data(), size, numPfPoints, pfNodes)) {
- _numPathfindingPoints = numPfPoints;
+ _pathfinding._numPoints = numPfPoints;
for (uint i = 0; i < pfNodes.size(); i++) {
PathfindingPoint current;
current._index = (uint16)i;
@@ -166,7 +166,7 @@ bool Macs2Engine::loadAmigaSceneBackground(uint32 sceneResourceId) {
if (pfNodes[i].adjacent[j] != 0)
current._adjacentPoints.push_back(pfNodes[i].adjacent[j]);
}
- _pathfindingPoints.push_back(current);
+ _pathfinding._points.push_back(current);
}
}
@@ -194,7 +194,7 @@ bool Macs2Engine::loadAmigaSceneBackground(uint32 sceneResourceId) {
"Amiga: loaded native MM_%04u (script %u bytes, strings %u bytes, pfNodes %u, "
"hotspots %u, walk %u/%u/%u)",
(uint)sceneResourceId, (uint)_amigaPendingSceneScript.size(),
- (uint)_amigaPendingSceneStrings.size(), (uint)_numPathfindingPoints, (uint)_numHotspots,
+ (uint)_amigaPendingSceneStrings.size(), (uint)_pathfinding._numPoints, (uint)_numHotspots,
(uint)_walkDepthThresholdY, (uint)_walkDepthScaleFactor, (uint)_walkBaseSpeedPct);
return true;
}
@@ -671,7 +671,7 @@ void Macs2Engine::readAmigaResources() {
_sceneBackground.create(kScreenWidth, kGameHeight, Graphics::PixelFormat::createFormatCLUT8());
_depthMap.create(kScreenWidth, kGameHeight, Graphics::PixelFormat::createFormatCLUT8());
- _pathfindingMap.create(kScreenWidth, kGameHeight, Graphics::PixelFormat::createFormatCLUT8());
+ _pathfinding.createMap(kScreenWidth, kGameHeight);
_shadowMap.create(kScreenWidth, kGameHeight, Graphics::PixelFormat::createFormatCLUT8());
_hotspotMap.create(kScreenWidth, kGameHeight, Graphics::PixelFormat::createFormatCLUT8());
_shadingTable.resize(0x800);
@@ -680,7 +680,7 @@ void Macs2Engine::readAmigaResources() {
buildAmigaPanelRemapTable();
_numHotspots = 0;
- _numPathfindingPoints = 0;
+ _pathfinding._numPoints = 0;
_scenePaletteMode = 1;
_paletteDarkenPercent = 0;
diff --git a/engines/macs2/character.cpp b/engines/macs2/character.cpp
index b3b92f41fdb..5d88f38e15a 100644
--- a/engines/macs2/character.cpp
+++ b/engines/macs2/character.cpp
@@ -27,6 +27,7 @@
#include "macs2/events.h"
#include "macs2/macs2.h"
#include "macs2/macs2_constants.h"
+#include "macs2/pathfinding.h"
#include "macs2/scriptexecutor.h"
#include "macs2/view1.h"
@@ -153,146 +154,41 @@ Character::Character() : _pathfindingOverlay(g_engine->screenWidth() * g_engine-
}
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
- }
+ const PathRoute route = g_engine->_pathfinding.calculateRoute(_gameObject->_position, target);
+ _path = route.nodes;
+ _currentPathIndex = route.startIndex;
+ _targetPosition = route.found ? route.firstWaypoint : target;
+ return route.found;
+}
- // 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;
- }
- }
+void Character::setWalkTarget(const Common::Point &target, bool snap) {
+ Common::Point dest = target;
+ const Common::Point current = getPosition();
+ Pathfinding &pf = g_engine->_pathfinding;
+ if (snap)
+ pf.snapToWalkable(&dest.y, &dest.x, current.y, current.x);
- // 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.
+ _pathFinalDestination = dest;
_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;
+ _path.clear();
- // 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;
+ const bool direct = pf.isLineWalkable(dest.y, dest.x, current.y, current.x);
+ if (snap) {
+ if (direct || Pathfinding::isWalkabilityBlocking(pf.walkabilityAt(dest.y, dest.x)))
+ _targetPosition = dest;
+ else if (!calculatePath(dest))
+ _targetPosition = dest;
+ } else {
+ if (!direct && Pathfinding::isWalkabilityWalkable(pf.walkabilityAt(dest.y, dest.x)))
+ calculatePath(dest);
+ if (_path.empty())
+ _targetPosition = dest;
}
- 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);
- }
+ _stepDeltaX = (int16)ABS(_targetPosition.x - current.x);
+ _stepDeltaY = (int16)ABS(_targetPosition.y - current.y);
+ _stepError = 0;
+ _stepDirectionSet = false;
}
const Common::Point &Character::getPosition() const {
@@ -323,7 +219,7 @@ uint16 Character::getVerticalOffset() const {
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;
+ const Common::Point &snapPos = g_engine->_pathfinding._points[snapIdx - 1]._position;
_gameObject->_position = snapPos;
}
_currentPathIndex++;
@@ -337,7 +233,7 @@ bool Character::walkAlongPath() {
return false; // No more path segments after this
}
const uint16 nodeIdx = _path[_currentPathIndex];
- const Common::Point &nodePos = g_engine->_pathfindingPoints[nodeIdx - 1]._position;
+ const Common::Point &nodePos = g_engine->_pathfinding._points[nodeIdx - 1]._position;
_targetPosition = nodePos;
_stepDeltaX = (int16)ABS(_targetPosition.x - _gameObject->_position.x);
_stepDeltaY = (int16)ABS(_targetPosition.y - _gameObject->_position.y);
@@ -431,30 +327,9 @@ void Character::startLerpTo(const Common::Point &target, uint32 duration, bool i
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;
+ setWalkTarget(getObjectEffectivePosition(object), false);
}
bool Character::hasPendingVerticalMotion() const {
diff --git a/engines/macs2/character.h b/engines/macs2/character.h
index a32b1a56236..5dad7d68258 100644
--- a/engines/macs2/character.h
+++ b/engines/macs2/character.h
@@ -47,12 +47,10 @@ private:
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();
@@ -87,6 +85,7 @@ public:
uint16 _motionStartVerticalOffset = 0;
bool calculatePath(Common::Point target);
+ void setWalkTarget(const Common::Point &target, bool snap);
void startPickup(Macs2::GameObject *object);
const Common::Point &getPosition() const;
diff --git a/engines/macs2/debugtools.cpp b/engines/macs2/debugtools.cpp
index 599b5dd6af0..3a1b57c61fc 100644
--- a/engines/macs2/debugtools.cpp
+++ b/engines/macs2/debugtools.cpp
@@ -1309,17 +1309,17 @@ static void showSceneMapsWindow() {
static Graphics::ManagedSurface overlayComposite;
if (selectedTab == 0) {
- surface = &g_engine->_pathfindingMap;
+ surface = &g_engine->_pathfinding._map;
} else if (selectedTab == 1) {
surface = &g_engine->_depthMap;
} else if (selectedTab == 2) {
// Composite: pathfinding map + character path overlay + pathfinding points
- overlayComposite.copyFrom(g_engine->_pathfindingMap);
+ overlayComposite.copyFrom(g_engine->_pathfinding._map);
View1 *view = (View1 *)g_engine->findView("View1");
if (view) {
// Draw pathfinding point nodes and connections
for (int i = 0; i < 16; i++) {
- PathfindingPoint &pt = g_engine->_pathfindingPoints[i];
+ PathfindingPoint &pt = g_engine->_pathfinding._points[i];
if (pt._position.x >= 0 && pt._position.x < kScreenWidth && pt._position.y >= 0 && pt._position.y < kGameHeight) {
// Draw cross at node
for (int d = -2; d <= 2; d++) {
@@ -1335,7 +1335,7 @@ static void showSceneMapsWindow() {
for (uint8 adj : pt._adjacentPoints) {
if (adj == 0 || adj > 16)
continue;
- PathfindingPoint &other = g_engine->_pathfindingPoints[adj - 1];
+ PathfindingPoint &other = g_engine->_pathfinding._points[adj - 1];
overlayComposite.drawLine(pt._position.x, pt._position.y, other._position.x, other._position.y, 0xFE);
}
}
@@ -1403,7 +1403,7 @@ static void showSceneMapsWindow() {
ImDrawList *dl = ImGui::GetWindowDrawList();
ImVec2 imgOrigin = ImGui::GetItemRectMin();
for (int i = 0; i < 16; i++) {
- PathfindingPoint &pt = g_engine->_pathfindingPoints[i];
+ PathfindingPoint &pt = g_engine->_pathfinding._points[i];
if (pt._position.x >= 0 && pt._position.x < kScreenWidth && pt._position.y >= 0 && pt._position.y < kGameHeight) {
char buf[4];
snprintf(buf, sizeof(buf), "%d", i);
@@ -1528,7 +1528,7 @@ static void showSceneMapsWindow() {
ImGui::Text("_walkDepthThresholdY=%u _walkDepthScaleFactor=%u _walkBaseSpeedPct=%u",
g_engine->_walkDepthThresholdY, g_engine->_walkDepthScaleFactor, g_engine->_walkBaseSpeedPct);
ImGui::Text("Pathfinding points: %u Path nodes: %u",
- (uint)g_engine->_pathfindingPoints.size(), (uint)g_engine->_path.size());
+ (uint)g_engine->_pathfinding._points.size(), (uint)g_engine->_path.size());
// Node detail table
if (ImGui::CollapsingHeader("Node Graph", ImGuiTreeNodeFlags_DefaultOpen)) {
@@ -1536,8 +1536,8 @@ static void showSceneMapsWindow() {
Character *protagonist = view ? view->getCharacterByIndex(Scenes::instance()._currentActorIndex) : nullptr;
Common::Point charPos = protagonist ? protagonist->getPosition() : Common::Point(0, 0);
- for (int i = 0; i < (int)g_engine->_pathfindingPoints.size(); i++) {
- const PathfindingPoint &pt = g_engine->_pathfindingPoints[i];
+ for (int i = 0; i < (int)g_engine->_pathfinding._points.size(); i++) {
+ const PathfindingPoint &pt = g_engine->_pathfinding._points[i];
// Check reachability from character
bool reachable = protagonist && g_engine->isPathWalkable(charPos.y, charPos.x, pt._position.y, pt._position.x);
// Check if node is in current path
diff --git a/engines/macs2/macs2.cpp b/engines/macs2/macs2.cpp
index 343e6da93ed..754b73fa498 100644
--- a/engines/macs2/macs2.cpp
+++ b/engines/macs2/macs2.cpp
@@ -199,7 +199,7 @@ void Macs2Engine::readResourceFile() {
const int gh = gameHeight();
_sceneBackground.create(sw, gh, Graphics::PixelFormat::createFormatCLUT8());
_depthMap.create(sw, gh, Graphics::PixelFormat::createFormatCLUT8());
- _pathfindingMap.create(sw, gh, Graphics::PixelFormat::createFormatCLUT8());
+ _pathfinding.createMap(sw, gh);
_shadowMap.create(sw, gh, Graphics::PixelFormat::createFormatCLUT8());
_hotspotMap.create(sw, gh, Graphics::PixelFormat::createFormatCLUT8());
changeScene(Scenes::instance()._currentSceneIndex);
@@ -1004,11 +1004,11 @@ bool Macs2Engine::loadSceneGraphics(uint32 sceneIndex) {
_sceneBackground.fillRect(Common::Rect(0, 0, kScreenWidth, kGameHeight), 0);
_depthMap.fillRect(Common::Rect(0, 0, kScreenWidth, kGameHeight), 0);
- _pathfindingMap.fillRect(Common::Rect(0, 0, kScreenWidth, kGameHeight), 0);
+ _pathfinding._map.fillRect(Common::Rect(0, 0, kScreenWidth, kGameHeight), 0);
_shadowMap.fillRect(Common::Rect(0, 0, kScreenWidth, kGameHeight), 0);
_hotspotMap.fillRect(Common::Rect(0, 0, kScreenWidth, kGameHeight), 0);
_numHotspots = 0;
- _numPathfindingPoints = 0;
+ _pathfinding._numPoints = 0;
_walkDepthThresholdY = 100;
_walkDepthScaleFactor = 100;
_walkBaseSpeedPct = 100;
@@ -1106,7 +1106,7 @@ bool Macs2Engine::loadSceneGraphicsV1(uint32 sceneIndex) {
Graphics::ManagedSurface pathfindingRLE = readRLEImage(_fileStream->pos(), _fileStream);
// Walkability/pathfinding map at scene offset 0x2017
- _pathfindingMap.blitFrom(pathfindingRLE);
+ _pathfinding._map.blitFrom(pathfindingRLE);
// Shadow/shading intensity map for character rendering
Graphics::ManagedSurface shadowRLE = readRLEImage(_fileStream->pos(), _fileStream);
@@ -1118,7 +1118,7 @@ bool Macs2Engine::loadSceneGraphicsV1(uint32 sceneIndex) {
// Pretty sure that this is the pathfinding points. We address them starting
// Load pathfinding nodes (16 entries x 10 bytes at scene+0x5023)
- _pathfindingPoints.clear();
+ _pathfinding._points.clear();
for (int i = 0; i < 16; i++) {
PathfindingPoint current;
current._index = i;
@@ -1130,7 +1130,7 @@ bool Macs2Engine::loadSceneGraphicsV1(uint32 sceneIndex) {
current._adjacentPoints.clear();
for (uint16 j = 0; j < numConnections && j < 4; j++)
current._adjacentPoints.push_back(adj[j]);
- _pathfindingPoints.push_back(current);
+ _pathfinding._points.push_back(current);
}
_numHotspots = _fileStream->readUint16LE();
@@ -1143,7 +1143,7 @@ bool Macs2Engine::loadSceneGraphicsV1(uint32 sceneIndex) {
readBackgroundAnimations(_fileStream);
updateAllBackgroundAnimationDepthMaps();
- _numPathfindingPoints = _fileStream->readUint16LE();
+ _pathfinding._numPoints = _fileStream->readUint16LE();
// Offset 51F9h
_fileStream->readUint16LE();
@@ -1237,7 +1237,7 @@ bool Macs2Engine::loadSceneGraphicsV2(uint32 sceneIndex) {
Graphics::ManagedSurface half;
if (!readMegaPicImage(stream, kScreenWidth, kGameHeight, half))
return false;
- upscaleHalfRes(half, _pathfindingMap);
+ upscaleHalfRes(half, _pathfinding._map);
if (!readMegaPicImage(stream, kScreenWidth, kGameHeight, half))
return false;
@@ -1247,7 +1247,7 @@ bool Macs2Engine::loadSceneGraphicsV2(uint32 sceneIndex) {
return false;
upscaleHalfRes(half, _hotspotMap);
- _pathfindingPoints.clear();
+ _pathfinding._points.clear();
for (int i = 0; i < 16; i++) {
PathfindingPoint current;
current._index = i;
@@ -1260,7 +1260,7 @@ bool Macs2Engine::loadSceneGraphicsV2(uint32 sceneIndex) {
current._adjacentPoints.clear();
for (uint16 j = 0; j < numConnections && j < 4; j++)
current._adjacentPoints.push_back(adj[j]);
- _pathfindingPoints.push_back(current);
+ _pathfinding._points.push_back(current);
}
stream->skip(352);
@@ -1320,9 +1320,9 @@ bool Macs2Engine::loadSceneGraphicsV2(uint32 sceneIndex) {
}
}
- _numPathfindingPoints = stream->readUint16LE();
- if (_numPathfindingPoints == 0 || _numPathfindingPoints > 16)
- _numPathfindingPoints = 16;
+ _pathfinding._numPoints = stream->readUint16LE();
+ if (_pathfinding._numPoints == 0 || _pathfinding._numPoints > 16)
+ _pathfinding._numPoints = 16;
(void)stream->readUint16LE();
(void)stream->readUint16LE();
_walkDepthThresholdY = (uint16)(stream->readUint16LE() << 1);
@@ -1356,7 +1356,7 @@ void Macs2Engine::changeScene(uint32 newSceneIndex, bool executeScript) {
// Release old scene resources
_backgroundAnimations.clear();
_backgroundAnimationsBlobs.clear();
- memset(_areaOverrides, 0, sizeof(_areaOverrides));
+ _pathfinding.clearAreaOverrides();
if (isAmiga()) {
// Amiga scripts use scene ids = MM_resource_id + 1 (Ghidra FUN_002215fa
@@ -1477,7 +1477,7 @@ void Macs2Engine::changeScene(uint32 newSceneIndex, bool executeScript) {
Scenes::instance()._currentSceneSpecialAnimOffsets.clear();
_scriptExecutor->setScript(Scenes::instance()._currentSceneScript);
- _pathfindingOverrides.clear();
+ _pathfinding.clearWalkOverrides();
for (uint i = 0; i < _hotspotOverrides.size(); i++) {
_hotspotOverrides[i] = 0xFFFF;
}
@@ -1584,7 +1584,7 @@ void Macs2Engine::changeScene(uint32 newSceneIndex, bool executeScript) {
// Reset overrides before running the new scene's script (original placement:
// memsetBytes(0, 200, sceneData+0x528D) and memsetBytes(0xffff, 0x20, sceneData+0x5BD3)
// happen after all scene data is loaded, before script execution)
- _pathfindingOverrides.clear();
+ _pathfinding.clearWalkOverrides();
for (uint i = 0; i < _hotspotOverrides.size(); i++) {
_hotspotOverrides[i] = 0xFFFF;
}
@@ -2032,28 +2032,6 @@ bool Macs2Engine::findGlyph(char c, GlyphData &out) const {
return false;
}
-// getWalkabilityAt (1008:0e8c)
-// Params: (param_1=y, param_2=x)
-// Bounds: x<0 || x>=screenWidth || y<0 || y>=gameHeight -> return 0
-// Lookup: scene[y*4 + 0x2017] -> row pointer, then byte at [rowPtr + x]
-// Values 0xC8..0xEF: override range - checks scene[value*5 + 0x4EA5]:
-// If override disabled (flag==0): returns 0xFF
-// If override enabled (flag!=0): returns scene[value*5 + 0x4EA6]
-uint16 Macs2Engine::getWalkabilityAt(int16 y, int16 x) {
- if (x < 0 || x >= screenWidth() || y < 0 || y >= gameHeight() || _pathfindingMap.w == 0) {
- return 0;
- }
- uint16 value = _pathfindingMap.getPixel(x, y);
- if (value >= 0xC8 && value <= 0xEF) {
- uint16 overrideResult;
- if (getPathfindingOverride(value, overrideResult)) {
- return overrideResult;
- }
- return 0xFF;
- }
- return value;
-}
-
void Macs2Engine::updateBackgroundAnimationDepthMap(size_t animIndex) {
if (isV2() || _sceneDepthMap.w == 0 || animIndex >= _backgroundAnimations.size()) {
return;
@@ -2126,346 +2104,6 @@ void Macs2Engine::updateAllBackgroundAnimationDepthMaps() {
}
}
-// Params: (pTargetY, pTargetX, charY, charX)
-// Modifies *pTargetY and *pTargetX in place.
-void Macs2Engine::snapToWalkablePosition(int16 *pTargetY, int16 *pTargetX, int16 charY, int16 charX) {
- int16 savedX = *pTargetX;
- int16 savedY = *pTargetY;
- const int16 maxY = (int16)gameHeightLast();
- const int16 maxX = (int16)screenWidthLast();
-
- // Phase 1: Scan downward with depth constraint
- // Condition: walkability >= 200 OR (targetY - walkability) < savedY
- while (true) {
- uint16 w = getWalkabilityAt(*pTargetY, savedX);
- if (isWalkabilityWalkable(w) && (*pTargetY - (int16)w >= savedY)) {
- break;
- }
- if (*pTargetY >= maxY) {
- break;
- }
- *pTargetY = *pTargetY + 1;
- }
-
- // Phase 2: Continue scanning to bottom for best depth match
- int16 scanY = *pTargetY;
- while (scanY <= maxY) {
- uint16 w = getWalkabilityAt(scanY, *pTargetX);
- if (scanY - (int16)w == savedY) {
- *pTargetY = scanY;
- }
- if (scanY == maxY) {
- break;
- }
- scanY++;
- }
-
- // Phase 3: If at screen bottom and still non-walkable, scan upward
- if (*pTargetY == maxY) {
- uint16 w = getWalkabilityAt(*pTargetY, *pTargetX);
- if (isWalkabilityBlocking(w)) {
- while (isWalkabilityBlocking(w) && *pTargetY > 0) {
- *pTargetY = *pTargetY - 1;
- w = getWalkabilityAt(*pTargetY, *pTargetX);
- }
- }
- }
-
- // Phase 4: If still non-walkable, scan X toward character
- uint16 w = getWalkabilityAt(*pTargetY, *pTargetX);
- if (isWalkabilityBlocking(w)) {
- *pTargetY = savedY;
- if (charX < *pTargetX) {
- while (true) {
- uint16 w2 = getWalkabilityAt(*pTargetY, *pTargetX);
- if (isWalkabilityWalkable(w2)) {
- break;
- }
- if (*pTargetX <= 0) {
- break;
- }
- *pTargetX = *pTargetX - 1;
- }
- } else {
- while (true) {
- uint16 w2 = getWalkabilityAt(*pTargetY, *pTargetX);
- if (isWalkabilityWalkable(w2)) {
- break;
- }
- if (*pTargetX >= maxX) {
- break;
- }
- *pTargetX = *pTargetX + 1;
- }
- }
- // Phase 5: If all failed, fall back to character position
- uint16 w2 = getWalkabilityAt(*pTargetY, *pTargetX);
- if (isWalkabilityBlocking(w2)) {
- *pTargetX = charX;
- *pTargetY = charY;
- }
- }
-
- // Phase 6: Gradient-based wall push
- int16 pushX = 0;
- int16 pushY = 0;
- if (isWalkabilityBlocking(getWalkabilityAt(*pTargetY, *pTargetX + 1))) {
- pushX--;
- }
- if (isWalkabilityBlocking(getWalkabilityAt(*pTargetY, *pTargetX - 1))) {
- pushX++;
- }
- if (isWalkabilityBlocking(getWalkabilityAt(*pTargetY + 1, *pTargetX))) {
- pushY--;
- }
- if (isWalkabilityBlocking(getWalkabilityAt(*pTargetY - 1, *pTargetX))) {
- pushY++;
- }
- if (isWalkabilityBlocking(getWalkabilityAt(*pTargetY, *pTargetX + 2))) {
- pushX--;
- }
- if (isWalkabilityBlocking(getWalkabilityAt(*pTargetY, *pTargetX - 2))) {
- pushX++;
- }
- if (isWalkabilityBlocking(getWalkabilityAt(*pTargetY + 2, *pTargetX))) {
- pushY--;
- }
- if (isWalkabilityBlocking(getWalkabilityAt(*pTargetY - 2, *pTargetX))) {
- pushY++;
- }
-
- while (pushX != 0 || pushY != 0) {
- if (pushX < 0) {
- if (isWalkabilityWalkable(getWalkabilityAt(*pTargetY, *pTargetX - 1))) {
- *pTargetX = *pTargetX - 1;
- }
- pushX++;
- }
- if (pushX > 0) {
- if (isWalkabilityWalkable(getWalkabilityAt(*pTargetY, *pTargetX + 1))) {
- *pTargetX = *pTargetX + 1;
- }
- pushX--;
- }
- if (pushY < 0) {
- if (isWalkabilityWalkable(getWalkabilityAt(*pTargetY - 1, *pTargetX))) {
- *pTargetY = *pTargetY - 1;
- }
- pushY++;
- }
- if (pushY > 0) {
- if (isWalkabilityWalkable(getWalkabilityAt(*pTargetY + 1, *pTargetX))) {
- *pTargetY = *pTargetY + 1;
- }
- pushY--;
- }
- }
-}
-
-bool Macs2Engine::getPathfindingOverride(uint16 index, uint16 &result) const {
- for (const PathfindingAreaOverride ¤t : _pathfindingOverrides) {
- if (current._index == index && current._active) {
- result = current._overrideValue;
- return true;
- }
- }
- return false;
-}
-void Macs2Engine::setPathfindingOverride(uint16 index, uint16 overrideValue) {
- removePathfindingOverride(index);
- PathfindingAreaOverride override;
- override._active = true;
- override._index = index;
- override._overrideValue = overrideValue;
- _pathfindingOverrides.push_back(override);
-}
-
-uint16 Macs2Engine::getPathfindingOverride2(uint16 index) const {
- if (index < AREA_OVERRIDE_MIN || index > AREA_OVERRIDE_MAX) {
- return 0;
- }
- return _areaOverrides[index - AREA_OVERRIDE_MIN];
-}
-
-void Macs2Engine::removePathfindingOverride(uint16 index) {
- for (uint i = 0; i < _pathfindingOverrides.size(); i++) {
- PathfindingAreaOverride ¤t = _pathfindingOverrides[i];
- if (current._index == index) {
- _pathfindingOverrides.remove_at(i);
- return;
- }
- }
-};
-
-// Params: (param_1=y1, param_2=x1, param_3=y2, param_4=x2)
-// Traces from (x2,y2) toward (x1,y1). Checks walkability only on major-axis steps.
-// Uses unsigned 16-bit error accumulator with wrapping arithmetic.
-// Returns true if entire line is walkable (all sampled pixels < 0xC8).
-bool Macs2Engine::isPathWalkable(int16 y1, int16 x1, int16 y2, int16 x2) {
- uint16 error = 0;
- int16 curX = x2;
- int16 curY = y2;
- uint16 absDx = (uint16)ABS((int)(x2 - x1));
- uint16 absDy = (uint16)ABS((int)(y2 - y1));
- bool result = true;
-
- do {
- bool steppedX;
- if (error >= absDx) {
- if (y1 < y2) {
- curY--;
- }
- if (y2 < y1) {
- curY++;
- }
- error -= absDx;
- steppedX = false;
- } else {
- if (x1 < x2) {
- curX--;
- }
- if (x2 < x1) {
- curX++;
- }
- error += absDy;
- steppedX = true;
- }
-
- if (absDx > absDy && steppedX) {
- if (isWalkabilityBlocking(getWalkabilityAt(curY, curX))) {
- result = false;
- }
- }
- if (absDx <= absDy && !steppedX) {
- if (isWalkabilityBlocking(getWalkabilityAt(curY, curX))) {
- result = false;
- }
- }
- } while (curX != x1 || curY != y1);
-
- return result;
-}
-
-// integer Euclidean distance approximation.
-// Iterates i from 0 until i^2 >= dx^2 + dy^2. Capped at 0x500.
-int Macs2Engine::euclideanDistance(const Common::Point &a, const Common::Point &b) {
- int32 dx = ABS((int)(b.x - a.x));
- int32 dy = ABS((int)(b.y - a.y));
- int32 distSq = dx * dx + dy * dy;
- int i = 0;
- while (i < 0x500 && (int32)i * i < distSq) {
- i++;
- }
- return i;
-}
-
-// distance between two nodes IF walkable, else 0x500.
-// Uses binary search on precomputed squared-distance table (scene+0x61DC) for O(log n) sqrt.
-int Macs2Engine::walkableDistance(int nodeA, int nodeB) {
- const Common::Point &a = _pathfindingPoints[nodeA - 1]._position;
- const Common::Point &b = _pathfindingPoints[nodeB - 1]._position;
- if (!isPathWalkable(a.y, a.x, b.y, b.x)) {
- return 0x500;
- }
- // Binary search for integer sqrt(dx^2 + dy^2), matching binary at 1008:1293
- int32 dx = ABS((int)(b.x - a.x));
- int32 dy = ABS((int)(b.y - a.y));
- int32 distSq = dx * dx + dy * dy;
- int result = 0x280;
- int step = 0x280;
- do {
- step = step >> 1;
- if ((int32)result * result >= distSq) {
- result -= step;
- } else {
- result += step;
- }
- } while (step > 1);
- return result;
-}
-
-// recursive DFS cost to reach a reachable node.
-// Full recursive DFS with visited-stack cycle detection matching binary exactly.
-// Terminal: returns walkableDistance(node, finalDest) when node is reachable.
-// Recursive: min(computeMinCostToReachable(adj)) + walkableDistance(bestAdj, current).
-int Macs2Engine::computeMinCostToReachable(int nodeIndex, int prevNode, uint16 actorIndex, const bool *reachable, int nodeCount, const Common::Point &finalDest) {
- // Push current node to visited stack
- _visitedCount++;
- _visitedStack[_visitedCount] = nodeIndex;
-
- int result;
- const Common::Point &nodePos = _pathfindingPoints[nodeIndex - 1]._position;
-
- if (reachable[nodeIndex]) {
- // Terminal: return walkable distance from this node to finalDest
- // Binary calls walkableDistance(nodePos, finalDest) = findPathNode(1008:1293)
- if (!isPathWalkable(nodePos.y, nodePos.x, finalDest.y, finalDest.x)) {
- result = 0x500;
- } else {
- int32 dx = ABS((int)(finalDest.x - nodePos.x));
- int32 dy = ABS((int)(finalDest.y - nodePos.y));
- int32 distSq = dx * dx + dy * dy;
- int dist = 640;
- int step = 320;
- do {
- step = step >> 1;
- if ((int32)dist * dist >= distSq) {
- dist -= step;
- } else {
- dist += step;
- }
- } while (step > 1);
- result = dist;
- }
- _visitedCount--;
- return result;
- }
-
- int bestCost = 0x7777;
- int bestAdj = 0;
- const PathfindingPoint &pt = _pathfindingPoints[nodeIndex - 1];
- int adjCount = (int)pt._adjacentPoints.size();
-
- if (adjCount > 0) {
- for (int i = 0; i < adjCount; i++) {
- const int adj = pt._adjacentPoints[i];
- if (adj == prevNode) {
- continue;
- }
-
- // Check visited stack
- bool alreadyVisited = false;
- for (int j = 1; j < _visitedCount; j++) {
- if (_visitedStack[j] == adj) {
- alreadyVisited = true;
- break;
- }
- }
- if (alreadyVisited) {
- continue;
- }
-
- // Recursive call
- const int cost = computeMinCostToReachable(adj, nodeIndex, actorIndex, reachable, nodeCount, finalDest);
- if (cost < bestCost) {
- bestAdj = adj;
- bestCost = cost;
- }
- }
- }
-
- if (bestCost < 0x7777) {
- // Add edge cost: walkable distance from bestAdj to current node
- result = bestCost + walkableDistance(bestAdj, nodeIndex);
- } else {
- result = 0x7777;
- }
-
- // Pop visited stack
- _visitedCount--;
- return result;
-}
-
void Macs2Engine::nextCursorMode() {
switch (_scriptExecutor->_cursorMode) {
case Script::MouseMode::Talk:
@@ -2860,10 +2498,6 @@ bool Macs2Engine::readInputFrame(uint16 &mouseX, uint16 &mouseY, uint16 &buttons
return !_inputPlaybackStream->eos();
}
-uint16 Macs2Engine::getWalkabilityAt(const Common::Point &p) {
- return getWalkabilityAt((int16)p.y, (int16)p.x);
-}
-
int Macs2Engine::measureString(const Common::String &s) {
int sum = 0;
GlyphData currentGlyph;
diff --git a/engines/macs2/macs2.h b/engines/macs2/macs2.h
index 595338aa796..082f4d0b1b7 100644
--- a/engines/macs2/macs2.h
+++ b/engines/macs2/macs2.h
@@ -42,6 +42,7 @@
#include "macs2/amiga_archive.h"
#include "macs2/events.h"
#include "macs2/macs2_constants.h"
+#include "macs2/pathfinding.h"
#include "macs2/scriptexecutor.h"
namespace Macs2 {
@@ -279,24 +280,6 @@ struct AnimBlobView {
}
};
-struct PathfindingPoint {
- uint8 _index;
- Common::Point _position;
- Common::Array<uint8> _adjacentPoints;
-};
-
-struct PathfindingAreaOverride {
- bool _active;
- uint16 _index;
- uint16 _overrideValue;
-};
-
-// Area override table at scene+0x4EA8 (indexed by pathfinding value 0xC8..0xEF)
-// Set by opcode 0x4D, read by getAreaAtPoint (1008:101d)
-#define AREA_OVERRIDE_MIN 200
-#define AREA_OVERRIDE_MAX 239
-#define AREA_OVERRIDE_COUNT (AREA_OVERRIDE_MAX - AREA_OVERRIDE_MIN + 1)
-
class Macs2Engine : public Engine, public Events {
private:
const ADGameDescription *_gameDescription;
@@ -367,10 +350,6 @@ public:
// Assumes that the stream is at the start of the right section
void readImageResources(Common::SeekableReadStream *stream);
- // visited stack (matches binary's stack-frame approach, max 16 nodes)
- int _visitedStack[17] {};
- int _visitedCount = 0;
-
public:
Macs2Engine(OSystem *osystem, const ADGameDescription *gameDesc);
~Macs2Engine() override;
@@ -406,41 +385,46 @@ public:
Common::Array<Common::String> _debugOutput;
Common::Array<Common::String> _textLog;
- // Note: This is used both for pathfinding as well as for area IDs
- Graphics::ManagedSurface _pathfindingMap;
-
- Common::Array<PathfindingAreaOverride> _pathfindingOverrides;
- // Area override table at scene+value*5+0x4EA8 (for getAreaAtPoint)
- uint16 _areaOverrides[AREA_OVERRIDE_COUNT] = {0};
- Common::Array<PathfindingPoint> _pathfindingPoints;
+ Pathfinding _pathfinding;
Common::Array<Common::Point> _path;
- bool getPathfindingOverride(uint16 index, uint16 &result) const;
- void setPathfindingOverride(uint16 index, uint16 overrideValue);
+ bool getPathfindingOverride(uint16 index, uint16 &result) const {
+ return _pathfinding.getWalkOverride(index, result);
+ }
+ void setPathfindingOverride(uint16 index, uint16 overrideValue) {
+ _pathfinding.setWalkOverride(index, overrideValue);
+ }
- // Walkability threshold 0xC8 uses signed 16-bit comparison in the binary (JL/JGE).
- // Values with (int16)value < 0xC8 are walkable heights; e.g. -2 (0xFFFE) is walkable.
static inline bool isWalkabilityBlocking(uint16 value) {
- return (int16)value >= 0xC8;
+ return Pathfinding::isWalkabilityBlocking(value);
}
static inline bool isWalkabilityWalkable(uint16 value) {
- return (int16)value < 0xC8;
+ return Pathfinding::isWalkabilityWalkable(value);
}
- // This one implements the lookup relative to es:[di+4EA8h] vs. the other one at es:[di+4EA5h] and es:[di+4EA6h]
- uint16 getPathfindingOverride2(uint16 index) const;
- void removePathfindingOverride(uint16 index);
+ uint16 getPathfindingOverride2(uint16 index) const {
+ return _pathfinding.areaOverrideAt(index);
+ }
+ void removePathfindingOverride(uint16 index) {
+ _pathfinding.removeWalkOverride(index);
+ }
- uint16 getWalkabilityAt(int16 y, int16 x);
+ uint16 getWalkabilityAt(int16 y, int16 x) {
+ return _pathfinding.walkabilityAt(y, x);
+ }
+ uint16 getWalkabilityAt(const Common::Point &p) {
+ return _pathfinding.walkabilityAt(p);
+ }
/** Sync depth map with the current background animation frame (v1 gate fix). */
void updateBackgroundAnimationDepthMap(size_t animIndex);
void updateAllBackgroundAnimationDepthMaps();
- bool isPathWalkable(int16 y1, int16 x1, int16 y2, int16 x2);
- void snapToWalkablePosition(int16 *pTargetY, int16 *pTargetX, int16 charY, int16 charX);
- int getPathfindingNodeCount() const { return (int)_numPathfindingPoints; }
- int euclideanDistance(const Common::Point &a, const Common::Point &b);
- int walkableDistance(int nodeA, int nodeB);
- int computeMinCostToReachable(int nodeIndex, int prevNode, uint16 actorIndex, const bool *reachable, int nodeCount, const Common::Point &finalDest);
+ bool isPathWalkable(int16 y1, int16 x1, int16 y2, int16 x2) {
+ return _pathfinding.isLineWalkable(y1, x1, y2, x2);
+ }
+ void snapToWalkablePosition(int16 *pTargetY, int16 *pTargetX, int16 charY, int16 charX) {
+ _pathfinding.snapToWalkable(pTargetY, pTargetX, charY, charX);
+ }
+ int getPathfindingNodeCount() const { return _pathfinding.nodeCount(); }
// This is the override list living at [5BD1]
// Savegames sync 16 words into indices 1..16 (array size 0x11 during sync).
@@ -613,7 +597,6 @@ public:
Common::Array<uint16> _hotspotColorTable;
- uint16 _numPathfindingPoints;
uint16 _walkDepthThresholdY;
uint16 _walkDepthScaleFactor;
uint16 _walkBaseSpeedPct;
@@ -746,8 +729,6 @@ public:
// Schedules a run of the script the next time the executor is ticked
void scheduleRun(bool initScene = false);
- uint16 getWalkabilityAt(const Common::Point &p);
-
int measureString(const Common::String &s);
int measureStrings(const Common::StringArray &sa);
diff --git a/engines/macs2/module.mk b/engines/macs2/module.mk
index 4054b8fbd3b..f64c37fdf61 100644
--- a/engines/macs2/module.mk
+++ b/engines/macs2/module.mk
@@ -5,6 +5,7 @@ MODULE_OBJS = \
amiga_decode.o \
amiga_resources.o \
character.o \
+ pathfinding.o \
midiparser_macs2.o \
music.o \
dialogs.o \
diff --git a/engines/macs2/pathfinding.cpp b/engines/macs2/pathfinding.cpp
new file mode 100644
index 00000000000..7a7b4c27b18
--- /dev/null
+++ b/engines/macs2/pathfinding.cpp
@@ -0,0 +1,509 @@
+/* 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/pathfinding.h"
+#include "common/scummsys.h"
+#include "common/util.h"
+#include "graphics/pixelformat.h"
+
+namespace Macs2 {
+
+void Pathfinding::createMap(int width, int height) {
+ _map.create(width, height, Graphics::PixelFormat::createFormatCLUT8());
+}
+
+void Pathfinding::clearWalkOverrides() {
+ _walkOverrides.clear();
+}
+
+void Pathfinding::clearAreaOverrides() {
+ memset(_areaOverrides, 0, sizeof(_areaOverrides));
+}
+
+uint16 Pathfinding::walkabilityAt(int16 y, int16 x) const {
+ if (x < 0 || x >= _map.w || y < 0 || y >= _map.h) {
+ return 0;
+ }
+ uint16 value = _map.getPixel(x, y);
+ if (value >= 0xC8 && value <= 0xEF) {
+ uint16 overrideResult;
+ if (getWalkOverride(value, overrideResult)) {
+ return overrideResult;
+ }
+ return 0xFF;
+ }
+ return value;
+}
+
+uint16 Pathfinding::walkabilityAt(const Common::Point &p) const {
+ return walkabilityAt((int16)p.y, (int16)p.x);
+}
+
+uint16 Pathfinding::areaAt(uint16 x, uint16 y) const {
+ if (x >= (uint16)_map.w || y >= (uint16)_map.h) {
+ return 0;
+ }
+ uint16 result = _map.getPixel(x, y);
+ if (result >= AREA_OVERRIDE_MIN && result < 250) {
+ const uint16 overrideValue = areaOverrideAt(result);
+ if (overrideValue >= AREA_OVERRIDE_MIN) {
+ result = overrideValue;
+ }
+ }
+ return result;
+}
+
+bool Pathfinding::getWalkOverride(uint16 index, uint16 &result) const {
+ for (const PathfindingAreaOverride ¤t : _walkOverrides) {
+ if (current._index == index && current._active) {
+ result = current._overrideValue;
+ return true;
+ }
+ }
+ return false;
+}
+
+void Pathfinding::setWalkOverride(uint16 index, uint16 overrideValue) {
+ removeWalkOverride(index);
+ PathfindingAreaOverride override;
+ override._active = true;
+ override._index = index;
+ override._overrideValue = overrideValue;
+ _walkOverrides.push_back(override);
+}
+
+uint16 Pathfinding::areaOverrideAt(uint16 index) const {
+ if (index < AREA_OVERRIDE_MIN || index > AREA_OVERRIDE_MAX) {
+ return 0;
+ }
+ return _areaOverrides[index - AREA_OVERRIDE_MIN];
+}
+
+void Pathfinding::removeWalkOverride(uint16 index) {
+ for (uint i = 0; i < _walkOverrides.size(); i++) {
+ PathfindingAreaOverride ¤t = _walkOverrides[i];
+ if (current._index == index) {
+ _walkOverrides.remove_at(i);
+ return;
+ }
+ }
+}
+
+void Pathfinding::snapToWalkable(int16 *pTargetY, int16 *pTargetX, int16 charY, int16 charX) const {
+ if (_map.w == 0 || _map.h == 0) {
+ return;
+ }
+
+ int16 savedX = *pTargetX;
+ int16 savedY = *pTargetY;
+ const int16 maxY = (int16)(_map.h - 1);
+ const int16 maxX = (int16)(_map.w - 1);
+
+ // Phase 1: Scan downward with depth constraint
+ // Condition: walkability >= 200 OR (targetY - walkability) < savedY
+ while (true) {
+ uint16 w = walkabilityAt(*pTargetY, savedX);
+ if (isWalkabilityWalkable(w) && (*pTargetY - (int16)w >= savedY)) {
+ break;
+ }
+ if (*pTargetY >= maxY) {
+ break;
+ }
+ *pTargetY = *pTargetY + 1;
+ }
+
+ // Phase 2: Continue scanning to bottom for best depth match
+ int16 scanY = *pTargetY;
+ while (scanY <= maxY) {
+ uint16 w = walkabilityAt(scanY, *pTargetX);
+ if (scanY - (int16)w == savedY) {
+ *pTargetY = scanY;
+ }
+ if (scanY == maxY) {
+ break;
+ }
+ scanY++;
+ }
+
+ // Phase 3: If at screen bottom and still non-walkable, scan upward
+ if (*pTargetY == maxY) {
+ uint16 w = walkabilityAt(*pTargetY, *pTargetX);
+ if (isWalkabilityBlocking(w)) {
+ while (isWalkabilityBlocking(w) && *pTargetY > 0) {
+ *pTargetY = *pTargetY - 1;
+ w = walkabilityAt(*pTargetY, *pTargetX);
+ }
+ }
+ }
+
+ // Phase 4: If still non-walkable, scan X toward character
+ uint16 w = walkabilityAt(*pTargetY, *pTargetX);
+ if (isWalkabilityBlocking(w)) {
+ *pTargetY = savedY;
+ if (charX < *pTargetX) {
+ while (true) {
+ uint16 w2 = walkabilityAt(*pTargetY, *pTargetX);
+ if (isWalkabilityWalkable(w2)) {
+ break;
+ }
+ if (*pTargetX <= 0) {
+ break;
+ }
+ *pTargetX = *pTargetX - 1;
+ }
+ } else {
+ while (true) {
+ uint16 w2 = walkabilityAt(*pTargetY, *pTargetX);
+ if (isWalkabilityWalkable(w2)) {
+ break;
+ }
+ if (*pTargetX >= maxX) {
+ break;
+ }
+ *pTargetX = *pTargetX + 1;
+ }
+ }
+ // Phase 5: If all failed, fall back to character position
+ uint16 w2 = walkabilityAt(*pTargetY, *pTargetX);
+ if (isWalkabilityBlocking(w2)) {
+ *pTargetX = charX;
+ *pTargetY = charY;
+ }
+ }
+
+ // Phase 6: Gradient-based wall push
+ int16 pushX = 0;
+ int16 pushY = 0;
+ if (isWalkabilityBlocking(walkabilityAt(*pTargetY, *pTargetX + 1))) {
+ pushX--;
+ }
+ if (isWalkabilityBlocking(walkabilityAt(*pTargetY, *pTargetX - 1))) {
+ pushX++;
+ }
+ if (isWalkabilityBlocking(walkabilityAt(*pTargetY + 1, *pTargetX))) {
+ pushY--;
+ }
+ if (isWalkabilityBlocking(walkabilityAt(*pTargetY - 1, *pTargetX))) {
+ pushY++;
+ }
+ if (isWalkabilityBlocking(walkabilityAt(*pTargetY, *pTargetX + 2))) {
+ pushX--;
+ }
+ if (isWalkabilityBlocking(walkabilityAt(*pTargetY, *pTargetX - 2))) {
+ pushX++;
+ }
+ if (isWalkabilityBlocking(walkabilityAt(*pTargetY + 2, *pTargetX))) {
+ pushY--;
+ }
+ if (isWalkabilityBlocking(walkabilityAt(*pTargetY - 2, *pTargetX))) {
+ pushY++;
+ }
+
+ while (pushX != 0 || pushY != 0) {
+ if (pushX < 0) {
+ if (isWalkabilityWalkable(walkabilityAt(*pTargetY, *pTargetX - 1))) {
+ *pTargetX = *pTargetX - 1;
+ }
+ pushX++;
+ }
+ if (pushX > 0) {
+ if (isWalkabilityWalkable(walkabilityAt(*pTargetY, *pTargetX + 1))) {
+ *pTargetX = *pTargetX + 1;
+ }
+ pushX--;
+ }
+ if (pushY < 0) {
+ if (isWalkabilityWalkable(walkabilityAt(*pTargetY - 1, *pTargetX))) {
+ *pTargetY = *pTargetY - 1;
+ }
+ pushY++;
+ }
+ if (pushY > 0) {
+ if (isWalkabilityWalkable(walkabilityAt(*pTargetY + 1, *pTargetX))) {
+ *pTargetY = *pTargetY + 1;
+ }
+ pushY--;
+ }
+ }
+}
+
+bool Pathfinding::isLineWalkable(int16 y1, int16 x1, int16 y2, int16 x2) const {
+ uint16 error = 0;
+ int16 curX = x2;
+ int16 curY = y2;
+ uint16 absDx = (uint16)ABS((int)(x2 - x1));
+ uint16 absDy = (uint16)ABS((int)(y2 - y1));
+ bool result = true;
+
+ do {
+ bool steppedX;
+ if (error >= absDx) {
+ if (y1 < y2) {
+ curY--;
+ }
+ if (y2 < y1) {
+ curY++;
+ }
+ error -= absDx;
+ steppedX = false;
+ } else {
+ if (x1 < x2) {
+ curX--;
+ }
+ if (x2 < x1) {
+ curX++;
+ }
+ error += absDy;
+ steppedX = true;
+ }
+
+ if (absDx > absDy && steppedX) {
+ if (isWalkabilityBlocking(walkabilityAt(curY, curX))) {
+ result = false;
+ }
+ }
+ if (absDx <= absDy && !steppedX) {
+ if (isWalkabilityBlocking(walkabilityAt(curY, curX))) {
+ result = false;
+ }
+ }
+ } while (curX != x1 || curY != y1);
+
+ return result;
+}
+
+int Pathfinding::euclideanDistance(const Common::Point &a, const Common::Point &b) const {
+ int32 dx = ABS((int)(b.x - a.x));
+ int32 dy = ABS((int)(b.y - a.y));
+ int32 distSq = dx * dx + dy * dy;
+ int i = 0;
+ while (i < 0x500 && (int32)i * i < distSq) {
+ i++;
+ }
+ return i;
+}
+
+int Pathfinding::walkableDistance(int nodeA, int nodeB) const {
+ const Common::Point &a = _points[nodeA - 1]._position;
+ const Common::Point &b = _points[nodeB - 1]._position;
+ if (!isLineWalkable(a.y, a.x, b.y, b.x)) {
+ return 0x500;
+ }
+ int32 dx = ABS((int)(b.x - a.x));
+ int32 dy = ABS((int)(b.y - a.y));
+ int32 distSq = dx * dx + dy * dy;
+ int result = 0x280;
+ int step = 0x280;
+ do {
+ step = step >> 1;
+ if ((int32)result * result >= distSq) {
+ result -= step;
+ } else {
+ result += step;
+ }
+ } while (step > 1);
+ return result;
+}
+
+int Pathfinding::computeMinCostToReachable(int nodeIndex, int prevNode, const bool *reachable, int nodeCount, const Common::Point &finalDest) {
+ _visitedCount++;
+ _visitedStack[_visitedCount] = nodeIndex;
+
+ int result;
+ const Common::Point &nodePos = _points[nodeIndex - 1]._position;
+
+ if (reachable[nodeIndex]) {
+ if (!isLineWalkable(nodePos.y, nodePos.x, finalDest.y, finalDest.x)) {
+ result = 0x500;
+ } else {
+ int32 dx = ABS((int)(finalDest.x - nodePos.x));
+ int32 dy = ABS((int)(finalDest.y - nodePos.y));
+ int32 distSq = dx * dx + dy * dy;
+ int dist = 640;
+ int step = 320;
+ do {
+ step = step >> 1;
+ if ((int32)dist * dist >= distSq) {
+ dist -= step;
+ } else {
+ dist += step;
+ }
+ } while (step > 1);
+ result = dist;
+ }
+ _visitedCount--;
+ return result;
+ }
+
+ int bestCost = 0x7777;
+ int bestAdj = 0;
+ const PathfindingPoint &pt = _points[nodeIndex - 1];
+ int adjCount = (int)pt._adjacentPoints.size();
+
+ if (adjCount > 0) {
+ for (int i = 0; i < adjCount; i++) {
+ const int adj = pt._adjacentPoints[i];
+ if (adj == prevNode) {
+ continue;
+ }
+
+ bool alreadyVisited = false;
+ for (int j = 1; j < _visitedCount; j++) {
+ if (_visitedStack[j] == adj) {
+ alreadyVisited = true;
+ break;
+ }
+ }
+ if (alreadyVisited) {
+ continue;
+ }
+
+ const int cost = computeMinCostToReachable(adj, nodeIndex, reachable, nodeCount, finalDest);
+ if (cost < bestCost) {
+ bestAdj = adj;
+ bestCost = cost;
+ }
+ }
+ }
+
+ if (bestCost < 0x7777) {
+ result = bestCost + walkableDistance(bestAdj, nodeIndex);
+ } else {
+ result = 0x7777;
+ }
+
+ _visitedCount--;
+ return result;
+}
+
+bool Pathfinding::canNodeConnectSourceToTarget(uint16 nodeIndex, const Common::Point &charPos, const Common::Point &target, const bool *reachable, int nodeCount) const {
+ const Common::Point &nodePos = _points[nodeIndex - 1]._position;
+ if (!isLineWalkable(nodePos.y, nodePos.x, target.y, target.x))
+ return false;
+
+ bool visited[kPathNodeSlots + 1] = {};
+ floodFillConnectedNodes(nodeIndex, visited, nodeCount);
+
+ bool anySeesTarget = false;
+ bool anySeenFromSource = false;
+ for (int i = 1; i <= nodeCount; i++) {
+ if (!visited[i])
+ continue;
+ const Common::Point &p = _points[i - 1]._position;
+ if (isLineWalkable(p.y, p.x, target.y, target.x))
+ anySeesTarget = true;
+ if (isLineWalkable(charPos.y, charPos.x, p.y, p.x))
+ anySeenFromSource = true;
+ }
+ return anySeesTarget && anySeenFromSource;
+}
+
+void Pathfinding::floodFillConnectedNodes(int nodeIndex, bool *visited, int nodeCount) const {
+ if (nodeIndex < 1 || nodeIndex > nodeCount)
+ return;
+ if (visited[nodeIndex])
+ return;
+ visited[nodeIndex] = true;
+ const PathfindingPoint &pt = _points[nodeIndex - 1];
+ for (uint i = 0; i < pt._adjacentPoints.size(); i++) {
+ floodFillConnectedNodes(pt._adjacentPoints[i], visited, nodeCount);
+ }
+}
+
+PathRoute Pathfinding::calculateRoute(const Common::Point &from, const Common::Point &to) {
+ // Binary calculatePath (1008:1966). Params: charY, charX, finalDestY, finalDestX.
+ PathRoute route;
+ route.firstWaypoint = to;
+ const int count = nodeCount();
+
+ bool reachable[kPathNodeSlots + 1] = {};
+ for (int i = 1; i <= count; i++) {
+ const Common::Point &nodePos = _points[i - 1]._position;
+ reachable[i] = isLineWalkable(to.y, to.x, nodePos.y, nodePos.x);
+ }
+
+ int bestCost = 0x7777;
+ int bestNode = 0;
+ for (int i = 1; i <= count; i++) {
+ const Common::Point &nodePos = _points[i - 1]._position;
+ int costToDest = euclideanDistance(nodePos, to);
+ int costToChar = euclideanDistance(nodePos, from);
+ if (costToDest + costToChar < bestCost) {
+ // Binary calls canNodeConnectSourceToTarget(destY, destX, charY, charX, i)
+ // due to calculatePath being invoked with swapped source/dest params.
+ if (canNodeConnectSourceToTarget(i, to, from, reachable, count)) {
+ costToDest = euclideanDistance(nodePos, to);
+ costToChar = euclideanDistance(nodePos, from);
+ bestCost = costToDest + costToChar;
+ bestNode = i;
+ }
+ }
+ }
+
+ if (bestNode == 0) {
+ route.startIndex = 1;
+ return route;
+ }
+
+ route.nodes.push_back(bestNode);
+ int currentNode = bestNode;
+ while (!reachable[currentNode]) {
+ const PathfindingPoint &curPt = _points[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 = computeMinCostToReachable(adjIdx, 0x7fff, reachable, count, to);
+ const int edgeCost = walkableDistance(adjIdx, currentNode);
+ if (cost + edgeCost < localBestCost) {
+ nextNode = adjIdx;
+ localBestCost = cost + edgeCost;
+ }
+ }
+ currentNode = nextNode;
+ route.nodes.push_back(currentNode);
+ if (route.nodes.size() > kPathNodeSlots)
+ break;
+ }
+
+ for (uint i = 0; i + 1 < route.nodes.size(); i++) {
+ const Common::Point &p1 = _points[route.nodes[i + 1] - 1]._position;
+ const Common::Point &p2 = _points[route.nodes[i] - 1]._position;
+ if (!isLineWalkable(p1.y, p1.x, p2.y, p2.x)) {
+ route.nodes.clear();
+ return route;
+ }
+ }
+
+ route.startIndex = 0;
+ while (route.startIndex + 1 < (int16)route.nodes.size()) {
+ const Common::Point &nextNodePos = _points[route.nodes[route.startIndex + 1] - 1]._position;
+ if (!isLineWalkable(nextNodePos.y, nextNodePos.x, from.y, from.x))
+ break;
+ route.startIndex++;
+ }
+
+ route.firstWaypoint = _points[route.nodes[route.startIndex] - 1]._position;
+ route.found = true;
+ return route;
+}
+
+} // namespace Macs2
diff --git a/engines/macs2/pathfinding.h b/engines/macs2/pathfinding.h
new file mode 100644
index 00000000000..30aa667e906
--- /dev/null
+++ b/engines/macs2/pathfinding.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_PATHFINDING_H
+#define MACS2_PATHFINDING_H
+
+#include "common/array.h"
+#include "common/rect.h"
+#include "graphics/managed_surface.h"
+#include "macs2/macs2_constants.h"
+
+namespace Macs2 {
+
+// Area override table at scene+0x4EA8 (indexed by pathfinding value 0xC8..0xEF)
+// Set by opcode 0x4D, read by getAreaAtPoint (1008:101d)
+static constexpr uint16 AREA_OVERRIDE_MIN = 200;
+static constexpr uint16 AREA_OVERRIDE_MAX = 239;
+static constexpr uint16 AREA_OVERRIDE_COUNT = AREA_OVERRIDE_MAX - AREA_OVERRIDE_MIN + 1;
+
+struct PathfindingPoint {
+ uint8 _index = 0;
+ Common::Point _position;
+ Common::Array<uint8> _adjacentPoints;
+};
+
+struct PathfindingAreaOverride {
+ bool _active = false;
+ uint16 _index = 0;
+ uint16 _overrideValue = 0;
+};
+
+struct PathRoute {
+ Common::Array<uint16> nodes;
+ int16 startIndex = 0;
+ Common::Point firstWaypoint;
+ bool found = false;
+};
+
+class Pathfinding {
+public:
+ Graphics::ManagedSurface _map;
+ Common::Array<PathfindingPoint> _points;
+ Common::Array<PathfindingAreaOverride> _walkOverrides;
+ uint16 _areaOverrides[AREA_OVERRIDE_COUNT] = {0};
+ uint16 _numPoints = 0;
+
+ void createMap(int width, int height);
+ void clearWalkOverrides();
+ void clearAreaOverrides();
+
+ // Walkability threshold 0xC8 uses signed 16-bit comparison in the binary (JL/JGE).
+ // Values with (int16)value < 0xC8 are walkable heights; e.g. -2 (0xFFFE) is walkable.
+ static inline bool isWalkabilityBlocking(uint16 value) {
+ return (int16)value >= 0xC8;
+ }
+ static inline bool isWalkabilityWalkable(uint16 value) {
+ return (int16)value < 0xC8;
+ }
+
+ uint16 walkabilityAt(int16 y, int16 x) const;
+ uint16 walkabilityAt(const Common::Point &p) const;
+ uint16 areaAt(uint16 x, uint16 y) const;
+
+ bool getWalkOverride(uint16 index, uint16 &result) const;
+ void setWalkOverride(uint16 index, uint16 overrideValue);
+ void removeWalkOverride(uint16 index);
+ uint16 areaOverrideAt(uint16 index) const;
+
+ bool isLineWalkable(int16 y1, int16 x1, int16 y2, int16 x2) const;
+ void snapToWalkable(int16 *pTargetY, int16 *pTargetX, int16 charY, int16 charX) const;
+
+ int nodeCount() const { return (int)_numPoints; }
+ int euclideanDistance(const Common::Point &a, const Common::Point &b) const;
+ int walkableDistance(int nodeA, int nodeB) const;
+
+ PathRoute calculateRoute(const Common::Point &from, const Common::Point &to);
+
+private:
+ int _visitedStack[17] {};
+ int _visitedCount = 0;
+
+ int computeMinCostToReachable(int nodeIndex, int prevNode, const bool *reachable, int nodeCount, const Common::Point &finalDest);
+ bool canNodeConnectSourceToTarget(uint16 nodeIndex, const Common::Point &charPos, const Common::Point &target, const bool *reachable, int nodeCount) const;
+ void floodFillConnectedNodes(int nodeIndex, bool *visited, int nodeCount) const;
+};
+
+} // namespace Macs2
+
+#endif
diff --git a/engines/macs2/saveload.cpp b/engines/macs2/saveload.cpp
index feabfd36bd0..fc3c40703e5 100644
--- a/engines/macs2/saveload.cpp
+++ b/engines/macs2/saveload.cpp
@@ -304,21 +304,21 @@ Common::Error Macs2Engine::syncGameV1(Common::Serializer &s) {
// Scene data: pathfinding overrides [+0x528D]: 200 bytes ---
if (s.isLoading())
- _pathfindingOverrides.clear();
- for (int i = 0; i < ARRAYSIZE(_areaOverrides); i++) {
+ _pathfinding._walkOverrides.clear();
+ for (int i = 0; i < ARRAYSIZE(_pathfinding._areaOverrides); i++) {
uint8 active = 0;
uint16 overrideValue = 0;
uint16 remap = 0;
if (s.isSaving()) {
uint16 idx = AREA_OVERRIDE_MIN + i;
- for (const auto &ov : _pathfindingOverrides) {
+ for (const auto &ov : _pathfinding._walkOverrides) {
if (ov._index == idx && ov._active) {
active = 1;
overrideValue = ov._overrideValue;
break;
}
}
- remap = _areaOverrides[i];
+ remap = _pathfinding._areaOverrides[i];
}
s.syncAsByte(active);
s.syncAsUint16LE(overrideValue);
@@ -329,9 +329,9 @@ Common::Error Macs2Engine::syncGameV1(Common::Serializer &s) {
ov._active = true;
ov._index = AREA_OVERRIDE_MIN + i;
ov._overrideValue = overrideValue;
- _pathfindingOverrides.push_back(ov);
+ _pathfinding._walkOverrides.push_back(ov);
}
- _areaOverrides[i] = remap;
+ _pathfinding._areaOverrides[i] = remap;
}
}
diff --git a/engines/macs2/scriptexecutor.cpp b/engines/macs2/scriptexecutor.cpp
index d1dfe05e985..0933e7f0aef 100644
--- a/engines/macs2/scriptexecutor.cpp
+++ b/engines/macs2/scriptexecutor.cpp
@@ -315,18 +315,7 @@ OpcodeResult ScriptExecutor::scriptChangeAnimation() {
uint16 ScriptExecutor::getAreaAtPoint(uint16 x, uint16 y) {
// getAreaAtPoint (1008:101d). Reads the pathfinding map pixel and applies
// the area override table at sceneData + value*5 + 0x4EA8.
- if (x >= (uint16)_engine->screenWidth() || y >= (uint16)_engine->gameHeight() ||
- _engine->_pathfindingMap.w == 0) {
- return 0;
- }
- uint16 result = _engine->_pathfindingMap.getPixel(x, y);
- if (result >= AREA_OVERRIDE_MIN && result < 250) {
- uint16 overrideValue = _engine->getPathfindingOverride2(result);
- if (overrideValue >= AREA_OVERRIDE_MIN) {
- result = overrideValue;
- }
- }
- return result;
+ return _engine->_pathfinding.areaAt(x, y);
}
bool ScriptExecutor::loadIndexedResource(Common::Array<uint8> &outData, uint8 resourceIndex, uint16 /*objectTableOffset*/) {
@@ -1425,25 +1414,7 @@ OpcodeResult Script::ScriptExecutor::scriptWalkToPosition() {
c = &stackCharacter;
}
- const Common::Point current = c->getPosition();
- const Common::Point target(x, y);
-
- c->_currentPathIndex = 0;
- c->_path.clear();
- c->_pathFinalDestination = target;
-
- bool directPath = _engine->isPathWalkable(y, x, current.y, current.x);
- if (!directPath && Macs2Engine::isWalkabilityWalkable(_engine->getWalkabilityAt(y, x))) {
- c->calculatePath(target);
- }
- if (c->_path.empty()) {
- c->_targetPosition = c->_pathFinalDestination;
- }
-
- c->_stepDeltaX = (int16)ABS((int32)c->_targetPosition.x - current.x);
- c->_stepDeltaY = (int16)ABS((int32)c->_targetPosition.y - current.y);
- c->_stepError = 0;
- c->_stepDirectionSet = false;
+ c->setWalkTarget(Common::Point(x, y), false);
// Binary loadObjectData seeds runtime+0x21D from the object table vertical offset;
// scriptWalkToPosition does not change it. Match that so waitForWalk can complete
@@ -3127,7 +3098,7 @@ OpcodeResult Script::ScriptExecutor::scriptSetPathfindingRemap() {
setScriptError(0x0D);
return OpcodeResult::Continue;
}
- g_engine->_areaOverrides[sourceValue - AREA_OVERRIDE_MIN] = targetValue;
+ g_engine->_pathfinding._areaOverrides[sourceValue - AREA_OVERRIDE_MIN] = targetValue;
return OpcodeResult::Continue;
}
@@ -3955,7 +3926,7 @@ OpcodeResult ScriptExecutor::scriptLoadWalkMask() {
const bool halfRes = _engine->isV2();
const int w = halfRes ? kScreenWidth : _engine->screenWidth();
const int h = halfRes ? kGameHeight : _engine->gameHeight();
- if (!_engine->loadMaskFromResource(resourceIndex, _executingScriptObjectId, _engine->_pathfindingMap,
+ if (!_engine->loadMaskFromResource(resourceIndex, _executingScriptObjectId, _engine->_pathfinding._map,
w, h, halfRes))
warning("loadWalkMask: failed resource %u", resourceIndex);
return OpcodeResult::Continue;
diff --git a/engines/macs2/view1.cpp b/engines/macs2/view1.cpp
index 6fcb9e6c9f7..b9515c09446 100644
--- a/engines/macs2/view1.cpp
+++ b/engines/macs2/view1.cpp
@@ -926,17 +926,17 @@ void View1::drawPathfindingPoints(Graphics::ManagedSurface &s) {
yOffset = xData._height / 2;
}
for (int i = 0; i < 16; i++) {
- const PathfindingPoint ¤t = g_engine->_pathfindingPoints[i];
+ const PathfindingPoint ¤t = g_engine->_pathfinding._points[i];
renderString(current._position.x - xOffset, current._position.y - yOffset, "x");
const Common::String &number = Common::String::format("%u", i);
renderString(current._position.x - xOffset + 10, current._position.y - yOffset + 10, number.c_str());
for (uint8 adjacentIndex : current._adjacentPoints) {
- if (adjacentIndex >= g_engine->_pathfindingPoints.size()) {
+ if (adjacentIndex >= g_engine->_pathfinding._points.size()) {
continue;
}
- PathfindingPoint &other = g_engine->_pathfindingPoints[adjacentIndex - 1];
+ PathfindingPoint &other = g_engine->_pathfinding._points[adjacentIndex - 1];
s.drawLine(current._position.x, current._position.y, other._position.x, other._position.y, 0xFFFFFFFF);
}
}
@@ -1794,28 +1794,7 @@ void View1::walkToScreenPosition(const Common::Point &pos) {
return;
}
- const Common::Point &charPos = protagonist->getPosition();
-
- Common::Point target = pos;
- g_engine->snapToWalkablePosition(&target.y, &target.x, charPos.y, charPos.x);
-
- protagonist->_pathFinalDestination = target;
- protagonist->_currentPathIndex = 0;
- protagonist->_path.clear();
-
- const bool directPath = g_engine->isPathWalkable(target.y, target.x, charPos.y, charPos.x);
- if (directPath || Macs2Engine::isWalkabilityBlocking(g_engine->getWalkabilityAt(target.y, target.x))) {
- protagonist->_targetPosition = target;
- } else {
- const bool found = protagonist->calculatePath(target);
- if (!found) {
- protagonist->_targetPosition = target;
- }
- }
- protagonist->_stepDeltaX = (int16)ABS(protagonist->_targetPosition.x - charPos.x);
- protagonist->_stepDeltaY = (int16)ABS(protagonist->_targetPosition.y - charPos.y);
- protagonist->_stepError = 0;
- protagonist->_stepDirectionSet = false;
+ protagonist->setWalkTarget(pos, true);
g_engine->_scriptExecutor->saveWalkRuntime(protagonist, protagonist->_gameObject);
}
@@ -2833,7 +2812,7 @@ void View1::drawAllCharacters(Graphics::ManagedSurface *surface, bool fullUpdate
}
int16 walkabilityOffset = 0;
- if (g_engine->_pathfindingMap.w > 0) {
+ if (g_engine->_pathfinding._map.w > 0) {
walkabilityOffset = g_engine->getWalkabilityAt(charY, charX);
if (Macs2Engine::isWalkabilityBlocking((uint16)walkabilityOffset))
walkabilityOffset = 0;
Commit: 8a1680afbe2d0d3d1bf406e286379088e51aadcd
https://github.com/scummvm/scummvm/commit/8a1680afbe2d0d3d1bf406e286379088e51aadcd
Author: Martin Gerhardy (martin.gerhardy at gmail.com)
Date: 2026-09-03T19:50:40+02:00
Commit Message:
MACS2: extract text code
Changed paths:
A engines/macs2/sprite.h
A engines/macs2/text.cpp
A engines/macs2/text.h
engines/macs2/actionbar.cpp
engines/macs2/actionbar.h
engines/macs2/amiga_resources.cpp
engines/macs2/debugtools.cpp
engines/macs2/macs2.cpp
engines/macs2/macs2.h
engines/macs2/module.mk
engines/macs2/scriptexecutor.cpp
engines/macs2/view1.cpp
engines/macs2/view1.h
diff --git a/engines/macs2/actionbar.cpp b/engines/macs2/actionbar.cpp
index 409ef3e0904..03071d1ccac 100644
--- a/engines/macs2/actionbar.cpp
+++ b/engines/macs2/actionbar.cpp
@@ -177,11 +177,11 @@ void ActionBar::drawUIButton(const Common::Rect &rect, bool pressed, Graphics::M
style, false, false, s);
}
-void ActionBar::actionBarFont(const GlyphData *&font, uint16 &fontCount, int &glyphH) const {
- const bool usePanelFont = g_engine->numPanelGlyphs > 0;
- font = usePanelFont ? g_engine->_panelGlyphs : g_engine->_glyphs;
- fontCount = usePanelFont ? g_engine->numPanelGlyphs : g_engine->_numGlyphs;
- glyphH = usePanelFont ? (int)g_engine->maxPanelGlyphHeight : (int)g_engine->_maxGlyphHeight;
+void ActionBar::actionBarFont(const GlyphData *&font, uint16 &glyphCount, int &glyphH) const {
+ const bool usePanelFont = g_engine->_text.numPanelGlyphs > 0;
+ font = usePanelFont ? g_engine->_text._panelGlyphs : g_engine->_text._glyphs;
+ glyphCount = usePanelFont ? g_engine->_text.numPanelGlyphs : g_engine->_text._numGlyphs;
+ glyphH = usePanelFont ? (int)g_engine->_text.maxPanelGlyphHeight : (int)g_engine->_text._maxGlyphHeight;
}
void ActionBar::drawSentenceLine(Graphics::ManagedSurface &s) {
@@ -190,15 +190,15 @@ void ActionBar::drawSentenceLine(Graphics::ManagedSurface &s) {
return;
// Dialogue Font1 has German glyphs; the panel/save-load font does not.
- const GlyphData *font = g_engine->_glyphs;
- uint16 fontCount = g_engine->_numGlyphs;
- int glyphH = (int)g_engine->_maxGlyphHeight;
- if (fontCount == 0)
- actionBarFont(font, fontCount, glyphH);
+ const GlyphData *font = g_engine->_text._glyphs;
+ uint16 glyphCount = g_engine->_text._numGlyphs;
+ int glyphH = (int)g_engine->_text._maxGlyphHeight;
+ if (glyphCount == 0)
+ actionBarFont(font, glyphCount, glyphH);
const int textY = kUITop + MAX(0, (kSentenceH - glyphH) / 2);
- const int textX = MAX(0, (kScreenWidth - _view->measureStringWithFont(sentence, font, fontCount)) / 2);
- _view->renderStringWithFontTo(textX, textY, sentence, font, fontCount, s);
+ const int textX = MAX(0, (kScreenWidth - _view->measureStringWithFont(sentence, font, glyphCount)) / 2);
+ _view->renderStringWithFontTo(textX, textY, sentence, font, glyphCount, s);
}
void ActionBar::drawVerbBar(Graphics::ManagedSurface &s) {
@@ -215,7 +215,7 @@ void ActionBar::drawVerbBar(Graphics::ManagedSurface &s) {
drawUIButton(r, isActive || isHovered, s);
Common::String label = uiText(kVerbs[i].label);
- if (g_engine->numPanelGlyphs > 0)
+ if (g_engine->_text.numPanelGlyphs > 0)
label.toUppercase();
const int textW = _view->measureStringWithFont(label, font, fontCount);
const int textX = r.left + (r.width() - textW) / 2;
@@ -661,8 +661,8 @@ void ActionBar::drawNative(Graphics::ManagedSurface &s) {
const uint16 optTextMaxW = g_engine->_hudTextLayout[2] ? g_engine->_hudTextLayout[2] : 212;
const uint16 lineCount = g_engine->_hudTextLayout[3] ? g_engine->_hudTextLayout[3] : 9;
const uint16 linePitch = g_engine->_hudTextLayout[4] ? g_engine->_hudTextLayout[4] : 10;
- const GlyphData *panelFont = g_engine->numPanelGlyphs ? g_engine->_panelGlyphs : g_engine->_glyphs;
- const uint16 panelFontCount = g_engine->numPanelGlyphs ? g_engine->numPanelGlyphs : g_engine->_numGlyphs;
+ const GlyphData *panelGlyph = g_engine->_text.numPanelGlyphs ? g_engine->_text._panelGlyphs : g_engine->_text._glyphs;
+ const uint16 panelGlyphCount = g_engine->_text.numPanelGlyphs ? g_engine->_text.numPanelGlyphs : g_engine->_text._numGlyphs;
if (menuMode == MenuMode::Main) {
if (_view->_inventorySource == nullptr ||
@@ -701,38 +701,38 @@ void ActionBar::drawNative(Graphics::ManagedSurface &s) {
delete icon;
}
- const GlyphData *font = g_engine->_numGlyphs ? g_engine->_glyphs : panelFont;
- const uint16 fontCount = g_engine->_numGlyphs ? g_engine->_numGlyphs : panelFontCount;
- if (fontCount != 0) {
+ const GlyphData *font = g_engine->_text._numGlyphs ? g_engine->_text._glyphs : panelGlyph;
+ const uint16 glyphCount = g_engine->_text._numGlyphs ? g_engine->_text._numGlyphs : panelGlyphCount;
+ if (glyphCount != 0) {
Common::String sentence = buildSentenceLine();
if (!sentence.empty()) {
const uint16 maxW = (uint16)(g_engine->screenWidth() - 16);
while (sentence.size() > 1) {
- if ((uint16)_view->measureStringWithFont(sentence, font, fontCount) <= maxW)
+ if ((uint16)_view->measureStringWithFont(sentence, font, glyphCount) <= maxW)
break;
sentence.deleteLastChar();
}
- const int textW = _view->measureStringWithFont(sentence, font, fontCount);
+ const int textW = _view->measureStringWithFont(sentence, font, glyphCount);
const int textX = MAX(0, (g_engine->screenWidth() - textW) / 2);
- const int glyphH = g_engine->_maxGlyphHeight ? (int)g_engine->_maxGlyphHeight : 12;
+ const int glyphH = g_engine->_text._maxGlyphHeight ? (int)g_engine->_text._maxGlyphHeight : 12;
const int textY = MAX(0, (int)panelTop - glyphH - 2);
- _view->renderStringWithFontTo((uint16)textX, (uint16)textY, sentence, font, fontCount, s);
+ _view->renderStringWithFontTo((uint16)textX, (uint16)textY, sentence, font, glyphCount, s);
}
}
- } else if (menuMode == MenuMode::Options && panelFontCount != 0) {
+ } else if (menuMode == MenuMode::Options && panelGlyphCount != 0) {
if (g_engine->_saveSlotNames.empty())
refreshSaveSlotNames();
for (uint i = 0; i < g_engine->_saveSlotNames.size() && i < lineCount; i++) {
Common::String name = g_engine->_saveSlotNames[i];
while (name.size() > 1) {
- if ((uint16)_view->measureStringWithFont(name, panelFont, panelFontCount) <= optTextMaxW)
+ if ((uint16)_view->measureStringWithFont(name, panelGlyph, panelGlyphCount) <= optTextMaxW)
break;
name.deleteLastChar();
}
_view->renderStringWithFontTo(optTextX, panelTop + optTextY + (int)i * linePitch,
- name, panelFont, panelFontCount, s);
+ name, panelGlyph, panelGlyphCount, s);
}
- } else if (menuMode == MenuMode::DialogueList && panelFontCount != 0 && _view->_isDialogueChoiceInputActive) {
+ } else if (menuMode == MenuMode::DialogueList && panelGlyphCount != 0 && _view->_isDialogueChoiceInputActive) {
// Dialogue choice list at layout[5..6]; wired when assets set DialogueList.
const uint16 dlgX = g_engine->_hudTextLayout[5];
const uint16 dlgY = g_engine->_hudTextLayout[6];
@@ -743,7 +743,7 @@ void ActionBar::drawNative(Graphics::ManagedSurface &s) {
line < _view->_drawnStringBox.size();
li++, line++) {
_view->renderStringWithFontTo(dlgX, panelTop + dlgY + (int)line * pitch,
- _view->_drawnStringBox[line], panelFont, panelFontCount, s);
+ _view->_drawnStringBox[line], panelGlyph, panelGlyphCount, s);
}
}
}
diff --git a/engines/macs2/actionbar.h b/engines/macs2/actionbar.h
index 600df652fee..da77d97a984 100644
--- a/engines/macs2/actionbar.h
+++ b/engines/macs2/actionbar.h
@@ -85,7 +85,7 @@ private:
void drawScumm(Graphics::ManagedSurface &s);
bool handleClickScumm(const Common::Point &pos, bool scriptsRunning);
void handleMouseMoveScumm(const Common::Point &pos);
- void actionBarFont(const GlyphData *&font, uint16 &fontCount, int &glyphH) const;
+ void actionBarFont(const GlyphData *&font, uint16 &glyphCount, int &glyphH) const;
void drawSentenceLine(Graphics::ManagedSurface &s);
void drawVerbBar(Graphics::ManagedSurface &s);
void drawInventoryStrip(Graphics::ManagedSurface &s);
diff --git a/engines/macs2/amiga_resources.cpp b/engines/macs2/amiga_resources.cpp
index 200d8639ae3..ba48b7a4671 100644
--- a/engines/macs2/amiga_resources.cpp
+++ b/engines/macs2/amiga_resources.cpp
@@ -218,46 +218,46 @@ bool Macs2Engine::loadAmigaMxffFont() {
if (!decodeAmigaMxffFont(mxff.data(), size, glyphs) || glyphs.empty())
return false;
- _numGlyphs = 0;
- _maxGlyphHeight = 0;
- amigaTextLinePitch = 0;
+ _text._numGlyphs = 0;
+ _text._maxGlyphHeight = 0;
+ _text.amigaTextLinePitch = 0;
if (size >= 0x0A) {
const uint16 atlasRows = READ_BE_UINT16(mxff.data() + 8);
if (atlasRows > 1)
- amigaTextLinePitch = (uint16)(atlasRows - 1);
+ _text.amigaTextLinePitch = (uint16)(atlasRows - 1);
}
for (uint i = 0; i < glyphs.size() && i < 256; i++) {
- _glyphs[i]._ascii = glyphs[i].ascii;
- _glyphs[i]._width = glyphs[i].width;
- _glyphs[i]._height = glyphs[i].height;
- _glyphs[i]._data = Common::move(glyphs[i].pixels);
+ _text._glyphs[i]._ascii = glyphs[i].ascii;
+ _text._glyphs[i]._width = glyphs[i].width;
+ _text._glyphs[i]._height = glyphs[i].height;
+ _text._glyphs[i]._data = Common::move(glyphs[i].pixels);
// MXFF dialogue glyphs use copper COLOR23 (black) + COLOR27 (near-white
// outline). Remap into private UI bank 0xF0.. so outdoor copper cannot
// recolor text. COLOR17+i -> 0xF1+i (MXIN ui[1+i]); COLOR23->0xF7=ui[7]=0,
// COLOR27->0xFB=ui[11]=EEE. drawText @ 00224492 blits via drawSprite.
- for (uint p = 0; p < _glyphs[i]._data.size(); p++) {
- const byte c = _glyphs[i]._data[p];
+ for (uint p = 0; p < _text._glyphs[i]._data.size(); p++) {
+ const byte c = _text._glyphs[i]._data[p];
if (c == 0)
continue;
if (c >= 17 && c <= 31)
- _glyphs[i]._data[p] = (byte)(0xF0 + (c - 16));
+ _text._glyphs[i]._data[p] = (byte)(0xF0 + (c - 16));
else if (c < 16)
- _glyphs[i]._data[p] = (byte)(0xF0 + c);
+ _text._glyphs[i]._data[p] = (byte)(0xF0 + c);
}
- _maxGlyphHeight = MAX(_maxGlyphHeight, _glyphs[i]._height);
- _numGlyphs++;
+ _text._maxGlyphHeight = MAX(_text._maxGlyphHeight, _text._glyphs[i]._height);
+ _text._numGlyphs++;
}
- if (amigaTextLinePitch == 0 && _maxGlyphHeight > 1)
- amigaTextLinePitch = (uint16)(_maxGlyphHeight - 1);
+ if (_text.amigaTextLinePitch == 0 && _text._maxGlyphHeight > 1)
+ _text.amigaTextLinePitch = (uint16)(_text._maxGlyphHeight - 1);
// Reuse dialogue font for panel/save UI until a second MXFF exists.
- numPanelGlyphs = _numGlyphs;
- maxPanelGlyphHeight = _maxGlyphHeight;
- for (uint i = 0; i < _numGlyphs; i++)
- _panelGlyphs[i] = _glyphs[i];
+ _text.numPanelGlyphs = _text._numGlyphs;
+ _text.maxPanelGlyphHeight = _text._maxGlyphHeight;
+ for (uint i = 0; i < _text._numGlyphs; i++)
+ _text._panelGlyphs[i] = _text._glyphs[i];
debugC(1, kDebugFilePath, "Amiga: loaded MXFF font FF_0000 (%u glyphs, height %u, linePitch %u)",
- _numGlyphs, _maxGlyphHeight, amigaTextLinePitch);
- return _numGlyphs > 0;
+ _text._numGlyphs, _text._maxGlyphHeight, _text.amigaTextLinePitch);
+ return _text._numGlyphs > 0;
}
bool Macs2Engine::loadAmigaOverlayFontResource(uint16 ffId) {
@@ -279,27 +279,27 @@ bool Macs2Engine::loadAmigaOverlayFontResource(uint16 ffId) {
if (!decodeAmigaMxffFont(mxff.data(), size, glyphs) || glyphs.empty())
return false;
- numOverlayGlyphs = 0;
- maxOverlayGlyphHeight = 0;
+ _text.numOverlayGlyphs = 0;
+ _text.maxOverlayGlyphHeight = 0;
for (uint i = 0; i < glyphs.size() && i < 256; i++) {
- _overlayGlyphs[i]._ascii = glyphs[i].ascii;
- _overlayGlyphs[i]._width = glyphs[i].width;
- _overlayGlyphs[i]._height = glyphs[i].height;
- _overlayGlyphs[i]._data = Common::move(glyphs[i].pixels);
+ _text._overlayGlyphs[i]._ascii = glyphs[i].ascii;
+ _text._overlayGlyphs[i]._width = glyphs[i].width;
+ _text._overlayGlyphs[i]._height = glyphs[i].height;
+ _text._overlayGlyphs[i]._data = Common::move(glyphs[i].pixels);
// Same copper->UI-bank remap as loadAmigaMxffFont.
- for (uint p = 0; p < _overlayGlyphs[i]._data.size(); p++) {
- const byte c = _overlayGlyphs[i]._data[p];
+ for (uint p = 0; p < _text._overlayGlyphs[i]._data.size(); p++) {
+ const byte c = _text._overlayGlyphs[i]._data[p];
if (c == 0)
continue;
if (c >= 17 && c <= 31)
- _overlayGlyphs[i]._data[p] = (byte)(0xF0 + (c - 16));
+ _text._overlayGlyphs[i]._data[p] = (byte)(0xF0 + (c - 16));
else if (c < 16)
- _overlayGlyphs[i]._data[p] = (byte)(0xF0 + c);
+ _text._overlayGlyphs[i]._data[p] = (byte)(0xF0 + c);
}
- maxOverlayGlyphHeight = MAX(maxOverlayGlyphHeight, _overlayGlyphs[i]._height);
- numOverlayGlyphs++;
+ _text.maxOverlayGlyphHeight = MAX(_text.maxOverlayGlyphHeight, _text._overlayGlyphs[i]._height);
+ _text.numOverlayGlyphs++;
}
- return numOverlayGlyphs > 0;
+ return _text.numOverlayGlyphs > 0;
}
bool Macs2Engine::loadAmigaOverlayFont(uint8 resourceIndex) {
@@ -312,13 +312,13 @@ bool Macs2Engine::loadAmigaOverlayFont(uint8 resourceIndex) {
}
// Fall back to the already-loaded main MXFF dialogue font.
- if (_numGlyphs == 0)
+ if (_text._numGlyphs == 0)
return false;
- numOverlayGlyphs = _numGlyphs;
- maxOverlayGlyphHeight = _maxGlyphHeight;
- for (uint i = 0; i < _numGlyphs; i++)
- _overlayGlyphs[i] = _glyphs[i];
+ _text.numOverlayGlyphs = _text._numGlyphs;
+ _text.maxOverlayGlyphHeight = _text._maxGlyphHeight;
+ for (uint i = 0; i < _text._numGlyphs; i++)
+ _text._overlayGlyphs[i] = _text._glyphs[i];
return true;
}
diff --git a/engines/macs2/debugtools.cpp b/engines/macs2/debugtools.cpp
index 3a1b57c61fc..a6e3c8d215e 100644
--- a/engines/macs2/debugtools.cpp
+++ b/engines/macs2/debugtools.cpp
@@ -792,7 +792,7 @@ static void showVariablesWindow() {
ImGui::Text("Showing: Y | Count: %u", view->_dialogueChoiceCount);
ImGui::Text("BoxPos: (%d,%d)", view->_stringBoxPosition.x, view->_stringBoxPosition.y);
Common::Point mousePos = g_system->getEventManager()->getMousePos();
- int lineHeight = g_engine->_maxGlyphHeight + 2;
+ int lineHeight = g_engine->_text._maxGlyphHeight + 2;
int firstLineY = view->_stringBoxPosition.y + 9;
int relY = mousePos.y - firstLineY;
int hoveredChoice = -1;
diff --git a/engines/macs2/macs2.cpp b/engines/macs2/macs2.cpp
index 754b73fa498..1dd55630ec2 100644
--- a/engines/macs2/macs2.cpp
+++ b/engines/macs2/macs2.cpp
@@ -224,23 +224,23 @@ void Macs2Engine::loadResourceFileV1() {
uint32 font1SizeField = _fileStream->readUint32LE(); // skip size field
(void)font1SizeField;
uint16 font1GlyphCount = _fileStream->readUint16LE();
- _maxGlyphHeight = 0;
+ _text._maxGlyphHeight = 0;
for (uint i = 0; i < font1GlyphCount; i++) {
- _glyphs[i].readFromMemory(_fileStream);
- _maxGlyphHeight = MAX(_glyphs[i]._height, _maxGlyphHeight);
+ _text._glyphs[i].readFromMemory(_fileStream);
+ _text._maxGlyphHeight = MAX(_text._glyphs[i]._height, _text._maxGlyphHeight);
}
- _numGlyphs = font1GlyphCount;
+ _text._numGlyphs = font1GlyphCount;
// Font 2: clean sans-serif font used by save/load panel (scene data offset 0x1044)
uint32 font2SizeField = _fileStream->readUint32LE();
(void)font2SizeField;
uint16 font2GlyphCount = _fileStream->readUint16LE();
- maxPanelGlyphHeight = 0;
+ _text.maxPanelGlyphHeight = 0;
for (uint i = 0; i < font2GlyphCount && i < 256; i++) {
- _panelGlyphs[i].readFromMemory(_fileStream);
- maxPanelGlyphHeight = MAX(maxPanelGlyphHeight, _panelGlyphs[i]._height);
+ _text._panelGlyphs[i].readFromMemory(_fileStream);
+ _text.maxPanelGlyphHeight = MAX(_text.maxPanelGlyphHeight, _text._panelGlyphs[i]._height);
}
- numPanelGlyphs = font2GlyphCount;
+ _text.numPanelGlyphs = font2GlyphCount;
// Map scene offsets -> scene+0x5DDB. First entry is the help screen image offset.
for (uint i = 0; i < kMcsV1MapSceneOffsetCount; i++) {
@@ -262,8 +262,8 @@ void Macs2Engine::loadResourceFileV2() {
// TalkVol + Font1 + SysFont + 0x400 map offsets
_shadingTable.clear();
_shadingTable.resize(0x800, 0);
- _numGlyphs = 0;
- numPanelGlyphs = 0;
+ _text._numGlyphs = 0;
+ _text.numPanelGlyphs = 0;
memset(_mapSceneOffsets, 0, sizeof(_mapSceneOffsets));
_imageResources.clear();
_imageResources.resize(33);
@@ -467,9 +467,9 @@ void Macs2Engine::loadResourceFileV2() {
_fileStream->seek(fontStart + (int64)fontSize, SEEK_SET);
return true;
};
- if (!loadSizedFont(_glyphs, _numGlyphs, _maxGlyphHeight))
+ if (!loadSizedFont(_text._glyphs, _text._numGlyphs, _text._maxGlyphHeight))
warning("readGlobalAssetsV2: failed loading Font1");
- if (!loadSizedFont(_panelGlyphs, numPanelGlyphs, maxPanelGlyphHeight))
+ if (!loadSizedFont(_text._panelGlyphs, _text.numPanelGlyphs, _text.maxPanelGlyphHeight))
warning("readGlobalAssetsV2: failed loading SysFont");
for (int i = 0; i < ARRAYSIZE(_mapSceneOffsets); i++)
@@ -492,7 +492,7 @@ void Macs2Engine::loadResourceFileV2() {
"readGlobalAssetsV2: panel=%u+%u megapics=%u buttons=%u cursors=%u invent=%ux%u @(%u,%u) fonts=%u/%u",
_panelTopY, _panelHeight, megas, (uint)_hudButtons.size(), installed,
_inventCols, _inventRows, _inventOriginX, _inventOriginY,
- _numGlyphs, numPanelGlyphs);
+ _text._numGlyphs, _text.numPanelGlyphs);
_fileStream->seek(kMcsV2ActorIndexOffset, SEEK_SET);
bootstrapMcsActorsObjectsAndScene();
}
@@ -2012,26 +2012,16 @@ bool Macs2Engine::loadOverlayFont(uint8 resourceIndex, uint16 executingObjectID)
return false;
}
- numOverlayGlyphs = glyphCount;
- maxOverlayGlyphHeight = 0;
+ _text.numOverlayGlyphs = glyphCount;
+ _text.maxOverlayGlyphHeight = 0;
for (uint i = 0; i < glyphCount; i++) {
- _overlayGlyphs[i].readFromMemory(_fileStream);
- maxOverlayGlyphHeight = MAX(maxOverlayGlyphHeight, _overlayGlyphs[i]._height);
+ _text._overlayGlyphs[i].readFromMemory(_fileStream);
+ _text.maxOverlayGlyphHeight = MAX(_text.maxOverlayGlyphHeight, _text._overlayGlyphs[i]._height);
}
_fileStream->seek(oldPos, SEEK_SET);
return true;
}
-bool Macs2Engine::findGlyph(char c, GlyphData &out) const {
- for (int i = 0; i < _numGlyphs; i++) {
- if (_glyphs[i]._ascii == c) {
- out = _glyphs[i];
- return true;
- }
- }
- return false;
-}
-
void Macs2Engine::updateBackgroundAnimationDepthMap(size_t animIndex) {
if (isV2() || _sceneDepthMap.w == 0 || animIndex >= _backgroundAnimations.size()) {
return;
@@ -2498,40 +2488,6 @@ bool Macs2Engine::readInputFrame(uint16 &mouseX, uint16 &mouseY, uint16 &buttons
return !_inputPlaybackStream->eos();
}
-int Macs2Engine::measureString(const Common::String &s) {
- int sum = 0;
- GlyphData currentGlyph;
- uint16 widestGlyph = 0;
- for (auto current = s.begin(); current != s.end(); current++) {
- bool found = findGlyph(*current, currentGlyph);
- if (found) {
- widestGlyph = MAX(widestGlyph, currentGlyph._width);
- }
- }
-
- for (auto current = s.begin(); current != s.end(); current++) {
- bool found = findGlyph(*current, currentGlyph);
- if (!found) {
- sum += widestGlyph;
- } else {
- sum += currentGlyph._width + 1;
- }
- }
- return sum;
-}
-
-int Macs2Engine::measureStringsVertically(const Common::StringArray &sa) {
- return (int)sa.size() * dialogLineHeight();
-}
-
-int Macs2Engine::measureStrings(const Common::StringArray &sa) {
- int max = -1;
- for (auto iter = sa.begin(); iter != sa.end(); iter++) {
- max = MAX(measureString(*iter), max);
- }
- return max;
-}
-
int Macs2Engine::computeStringIndex(Common::MemoryReadStream *stream, int targetOffset) {
stream->seek(0);
int index = 0;
@@ -3198,22 +3154,6 @@ bool Macs2Engine::tick() {
return result;
}
-void GlyphData::readFromeFile(Common::File &file) {
- _ascii = file.readByte();
- _width = file.readUint16LE();
- _height = file.readUint16LE();
- _data.resize(_width * _height);
- file.read(_data.data(), _width * _height);
-}
-
-void GlyphData::readFromMemory(Common::SeekableReadStream *stream) {
- _ascii = stream->readByte();
- _width = stream->readUint16LE();
- _height = stream->readUint16LE();
- _data.resize(_width * _height);
- stream->read(_data.data(), _width * _height);
-}
-
void AnimFrame::readFromeFile(Common::File &file) {
_width = file.readUint16LE();
_height = file.readUint16LE();
diff --git a/engines/macs2/macs2.h b/engines/macs2/macs2.h
index 082f4d0b1b7..b94ae4fe37f 100644
--- a/engines/macs2/macs2.h
+++ b/engines/macs2/macs2.h
@@ -44,6 +44,8 @@
#include "macs2/macs2_constants.h"
#include "macs2/pathfinding.h"
#include "macs2/scriptexecutor.h"
+#include "macs2/sprite.h"
+#include "macs2/text.h"
namespace Macs2 {
@@ -99,19 +101,6 @@ public:
struct Macs2GameDescription;
class Music;
-struct Sprite {
- uint16 _width = 0;
- uint16 _height = 0;
- Common::Array<uint8> _data;
-};
-
-struct GlyphData : public Sprite {
- char _ascii = 0;
-
- void readFromeFile(Common::File &file);
- void readFromMemory(Common::SeekableReadStream *stream);
-};
-
struct AnimFrame : public Sprite {
int16 _offsetX = 0;
int16 _offsetY = 0;
@@ -435,13 +424,7 @@ public:
/** Per-cursor hotspot from native HUD button metadata (v2); (0,0) = use center. */
Common::Point _cursorHotspots[33];
- GlyphData _glyphs[256];
- GlyphData _panelGlyphs[256]; // Font 2: clean sans-serif font used by save/load panel
- GlyphData _overlayGlyphs[256];
- uint16 numOverlayGlyphs = 0;
- uint16 maxOverlayGlyphHeight = 0;
- uint16 numPanelGlyphs = 0;
- uint16 maxPanelGlyphHeight = 0;
+ Text _text;
bool loadOverlayFont(uint8 resourceIndex, uint16 executingObjectID);
/**
* Resolve scene/object resource table entry to an absolute MCS file offset.
@@ -515,15 +498,9 @@ public:
bool tickDeltaPlayback();
void applyDeltaFrameToBackground(const DeltaFrame &frame);
void playDeltaFrameSfx(uint16 displayFrame);
- // Font glyph count (79 glyphs in the resource file's font data)
- uint16 _numGlyphs = 79;
- uint16 _maxGlyphHeight;
-
AnimFrame _animFrames[6];
// 6 flag/decoration animation frames at fixed file offset 0x6A5941, each followed by 6 padding bytes
- bool findGlyph(char c, GlyphData &out) const;
-
// Character shading remap (loadResourceFile @ 1008:2e8d -> scene+0x53D3).
// Indexed as (color - 0xC0) * 0x20 + shadowIntensity (drawSpriteTransparent @ 1010:0ed1).
Common::Array<byte> _shadingTable;
@@ -542,8 +519,6 @@ public:
/** Absolute file offset of the 0x3000-byte scene/object directory. */
uint32 _mcsDirectoryOffset = kMcsV1DirectoryOffset;
- /** Amiga MXFF line pitch: measureTextWidth @ 00224420 uses (font[+8] - 1). */
- uint16 amigaTextLinePitch = 0;
/** True after loadAmigaSceneBackground installed copper colors in 0..31. */
bool _amigaNativePlayfieldPalette = false;
Common::Array<byte> _amigaLineCopperPal;
@@ -729,11 +704,6 @@ public:
// Schedules a run of the script the next time the executor is ticked
void scheduleRun(bool initScene = false);
- int measureString(const Common::String &s);
-
- int measureStrings(const Common::StringArray &sa);
- int measureStringsVertically(const Common::StringArray &sa);
-
Common::StringArray decodeStrings(Common::MemoryReadStream *stream, int offset, int numStrings, int sceneId = 0, int objectId = 0);
// --- Translation support ---
@@ -828,13 +798,13 @@ public:
int16 scaleScriptCoord(int16 coord) const { return isV2() ? (int16)(coord * 2) : coord; }
/** Dialogue / text-box chrome (DOS l0037_B368 / B462). */
- int dialogPadW() const { return isAmiga() ? 0x08 : 0x12; }
- int dialogPadH() const { return isAmiga() ? 0x08 : 0x10; }
- int dialogTextInset() const { return isAmiga() ? 0x04 : 0x09; }
+ int dialogPadW() const { return isAmiga() ? 8 : 18; }
+ int dialogPadH() const { return isAmiga() ? 8 : 16; }
+ int dialogTextInset() const { return isAmiga() ? 4 : 9; }
int dialogLineGap() const { return 2; }
- int portraitBorderPad() const { return isAmiga() ? 2 : 0x0D; }
+ int portraitBorderPad() const { return isAmiga() ? 2 : 13; }
int portraitContentInset() const { return isAmiga() ? 1 : 7; }
- int portraitTextGap() const { return isAmiga() ? 0x0A : 0x12; }
+ int portraitTextGap() const { return isAmiga() ? 10 : 18; }
/**
* Per-line step for dialogue layout.
* DOS: maxGlyphHeight + dialogLineGap(). Amiga: absolute MXFF pitch
@@ -842,8 +812,8 @@ public:
*/
int dialogLineHeight() const {
if (isAmiga())
- return amigaTextLinePitch ? (int)amigaTextLinePitch : (int)_maxGlyphHeight;
- return (int)_maxGlyphHeight + dialogLineGap();
+ return _text.amigaTextLinePitch ? (int)_text.amigaTextLinePitch : (int)_text._maxGlyphHeight;
+ return (int)_text._maxGlyphHeight + dialogLineGap();
}
/** Depth-map compare Y for sprite occlusion (halved on v2 full-res depth). */
diff --git a/engines/macs2/module.mk b/engines/macs2/module.mk
index f64c37fdf61..5caf65419a5 100644
--- a/engines/macs2/module.mk
+++ b/engines/macs2/module.mk
@@ -17,6 +17,7 @@ MODULE_OBJS = \
metaengine.o \
saveload.o \
scriptexecutor.o \
+ text.o \
actionbar.o \
view1.o
diff --git a/engines/macs2/scriptexecutor.cpp b/engines/macs2/scriptexecutor.cpp
index 0933e7f0aef..7b5a9835fa3 100644
--- a/engines/macs2/scriptexecutor.cpp
+++ b/engines/macs2/scriptexecutor.cpp
@@ -413,7 +413,8 @@ void ScriptExecutor::scriptPrintString(bool alignRight) {
int stringBoxX = x;
const int stringBoxY = y;
if (alignRight) {
- const int totalWidth = g_engine->measureStrings(strings) + g_engine->dialogPadW();
+ Text *text = &g_engine->_text;
+ const int totalWidth = text->measureStrings(strings) + g_engine->dialogPadW();
stringBoxX -= totalWidth;
}
diff --git a/engines/macs2/sprite.h b/engines/macs2/sprite.h
new file mode 100644
index 00000000000..819562ce615
--- /dev/null
+++ b/engines/macs2/sprite.h
@@ -0,0 +1,38 @@
+/* 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_SPRITE_H
+#define MACS2_SPRITE_H
+
+#include "common/array.h"
+#include "common/scummsys.h"
+
+namespace Macs2 {
+
+struct Sprite {
+ uint16 _width = 0;
+ uint16 _height = 0;
+ Common::Array<uint8> _data;
+};
+
+}
+
+#endif
diff --git a/engines/macs2/text.cpp b/engines/macs2/text.cpp
new file mode 100644
index 00000000000..24fae48ba48
--- /dev/null
+++ b/engines/macs2/text.cpp
@@ -0,0 +1,106 @@
+/* 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/text.h"
+#include "common/util.h"
+
+namespace Macs2 {
+
+void GlyphData::readFromeFile(Common::File &file) {
+ _ascii = file.readByte();
+ _width = file.readUint16LE();
+ _height = file.readUint16LE();
+ _data.resize(_width * _height);
+ file.read(_data.data(), _width * _height);
+}
+
+void GlyphData::readFromMemory(Common::SeekableReadStream *stream) {
+ _ascii = stream->readByte();
+ _width = stream->readUint16LE();
+ _height = stream->readUint16LE();
+ _data.resize(_width * _height);
+ stream->read(_data.data(), _width * _height);
+}
+
+bool Text::findGlyph(char c, GlyphData &out, const GlyphData *glyphs, uint16 numGlyphs) const {
+ for (uint16 i = 0; i < numGlyphs; i++) {
+ if (glyphs[i]._ascii == c) {
+ out = glyphs[i];
+ return true;
+ }
+ }
+ return false;
+}
+
+bool Text::findGlyph(char c, GlyphData &out) const {
+ return findGlyph(c, out, _glyphs, _numGlyphs);
+}
+
+int Text::measureString(const Common::String &s) const {
+ int sum = 0;
+ GlyphData currentGlyph;
+ uint16 widestGlyph = 0;
+ for (auto current = s.begin(); current != s.end(); current++) {
+ if (findGlyph(*current, currentGlyph)) {
+ widestGlyph = MAX(widestGlyph, currentGlyph._width);
+ }
+ }
+
+ for (auto current = s.begin(); current != s.end(); current++) {
+ if (!findGlyph(*current, currentGlyph)) {
+ sum += widestGlyph;
+ } else {
+ sum += currentGlyph._width + 1;
+ }
+ }
+ return sum;
+}
+
+int Text::measureString(const Common::String &s, const GlyphData *glyphs, uint16 numGlyphs) const {
+ int width = 0;
+ uint16 widestGlyph = 1;
+ for (uint i = 0; i < numGlyphs; i++) {
+ widestGlyph = MAX(widestGlyph, glyphs[i]._width);
+ }
+ for (auto iter = s.begin(); iter != s.end(); iter++) {
+ GlyphData glyph;
+ if (findGlyph(*iter, glyph, glyphs, numGlyphs)) {
+ width += glyph._width + 1;
+ } else {
+ width += widestGlyph;
+ }
+ }
+ return width;
+}
+
+int Text::measureStrings(const Common::StringArray &sa) const {
+ int max = -1;
+ for (auto iter = sa.begin(); iter != sa.end(); iter++) {
+ max = MAX(measureString(*iter), max);
+ }
+ return max;
+}
+
+int Text::measureStringsVertically(const Common::StringArray &sa, int lineHeight) const {
+ return (int)sa.size() * lineHeight;
+}
+
+} // namespace Macs2
diff --git a/engines/macs2/text.h b/engines/macs2/text.h
new file mode 100644
index 00000000000..ffa76d64c90
--- /dev/null
+++ b/engines/macs2/text.h
@@ -0,0 +1,66 @@
+/* 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_TEXT_H
+#define MACS2_TEXT_H
+
+#include "common/file.h"
+#include "common/str.h"
+#include "common/str-array.h"
+#include "common/stream.h"
+#include "macs2/sprite.h"
+
+namespace Macs2 {
+
+struct GlyphData : public Sprite {
+ char _ascii = 0;
+
+ void readFromeFile(Common::File &file);
+ void readFromMemory(Common::SeekableReadStream *stream);
+};
+
+class Text {
+public:
+ static constexpr uint kMaxGlyphs = 256;
+
+ GlyphData _glyphs[kMaxGlyphs];
+ GlyphData _panelGlyphs[kMaxGlyphs];
+ GlyphData _overlayGlyphs[kMaxGlyphs];
+ uint16 _numGlyphs = 79;
+ uint16 _maxGlyphHeight = 0;
+ uint16 numOverlayGlyphs = 0;
+ uint16 maxOverlayGlyphHeight = 0;
+ uint16 numPanelGlyphs = 0;
+ uint16 maxPanelGlyphHeight = 0;
+ uint16 amigaTextLinePitch = 0;
+
+ bool findGlyph(char c, GlyphData &out) const;
+ bool findGlyph(char c, GlyphData &out, const GlyphData *glyphs, uint16 numGlyphs) const;
+
+ int measureString(const Common::String &s) const;
+ int measureString(const Common::String &s, const GlyphData *glyphs, uint16 numGlyphs) const;
+ int measureStrings(const Common::StringArray &sa) const;
+ int measureStringsVertically(const Common::StringArray &sa, int lineHeight) const;
+};
+
+} // namespace Macs2
+
+#endif
diff --git a/engines/macs2/view1.cpp b/engines/macs2/view1.cpp
index b9515c09446..88f8f4f7d98 100644
--- a/engines/macs2/view1.cpp
+++ b/engines/macs2/view1.cpp
@@ -753,6 +753,7 @@ void View1::drawCurrentSpeaker(Graphics::ManagedSurface &s) {
void View1::renderString(uint16 x, uint16 y, const Common::String &s) {
Graphics::ManagedSurface surf = getSurface();
+ Text *text = &g_engine->_text;
uint16 currentX = x;
uint16 currentY = y;
@@ -760,7 +761,7 @@ void View1::renderString(uint16 x, uint16 y, const Common::String &s) {
uint16 widestGlyph = 1;
for (auto iter = s.begin(); iter != s.end(); iter++) {
GlyphData data;
- if (g_engine->findGlyph(*iter, data)) {
+ if (text->findGlyph(*iter, data)) {
widestGlyph = MAX(widestGlyph, data._width);
}
}
@@ -768,7 +769,7 @@ void View1::renderString(uint16 x, uint16 y, const Common::String &s) {
// Second pass: render with correct spacing
for (auto iter = s.begin(); iter != s.end(); iter++) {
GlyphData data;
- const bool found = g_engine->findGlyph(*iter, data);
+ const bool found = text->findGlyph(*iter, data);
if (found) {
drawSprite(currentX, currentY, data, surf, false);
currentX += data._width + 1;
@@ -786,20 +787,21 @@ void View1::renderString(const Common::Point &pos, const Common::String &s) {
}
void View1::renderStringTo(uint16 x, uint16 y, const Common::String &s, Graphics::ManagedSurface &surf) {
+ Text *text = &g_engine->_text;
uint16 currentX = x;
uint16 currentY = y;
uint16 widestGlyph = 1;
for (auto iter = s.begin(); iter != s.end(); iter++) {
GlyphData data;
- if (g_engine->findGlyph(*iter, data)) {
+ if (text->findGlyph(*iter, data)) {
widestGlyph = MAX(widestGlyph, data._width);
}
}
for (auto iter = s.begin(); iter != s.end(); iter++) {
GlyphData data;
- const bool found = g_engine->findGlyph(*iter, data);
+ const bool found = text->findGlyph(*iter, data);
if (found) {
drawSprite(currentX, currentY, data, surf, false);
currentX += data._width + 1;
@@ -810,25 +812,8 @@ void View1::renderStringTo(uint16 x, uint16 y, const Common::String &s, Graphics
}
int View1::measureStringWithFont(const Common::String &s, const GlyphData *glyphs, uint16 numGlyphs) {
- int width = 0;
- uint16 widestGlyph = 1;
- for (uint i = 0; i < numGlyphs; i++) {
- widestGlyph = MAX(widestGlyph, glyphs[i]._width);
- }
- for (auto iter = s.begin(); iter != s.end(); iter++) {
- bool found = false;
- for (uint i = 0; i < numGlyphs; i++) {
- if (glyphs[i]._ascii == *iter) {
- width += glyphs[i]._width + 1;
- found = true;
- break;
- }
- }
- if (!found) {
- width += widestGlyph;
- }
- }
- return width;
+ Text *text = &g_engine->_text;
+ return text->measureString(s, glyphs, numGlyphs);
}
void View1::renderStringWithFont(uint16 x, uint16 y, const Common::String &s, const GlyphData *glyphs, uint16 numGlyphs) {
@@ -877,8 +862,8 @@ void View1::drawOverlayTextEntries() {
int x = entry.position.x;
const Common::String &text = entry.text;
// Use overlay font if loaded, otherwise fall back to main font
- const GlyphData *font = g_engine->numOverlayGlyphs > 0 ? g_engine->_overlayGlyphs : g_engine->_glyphs;
- const uint16 fontCount = g_engine->numOverlayGlyphs > 0 ? g_engine->numOverlayGlyphs : g_engine->_numGlyphs;
+ const GlyphData *font = g_engine->_text.numOverlayGlyphs > 0 ? g_engine->_text._overlayGlyphs : g_engine->_text._glyphs;
+ const uint16 fontCount = g_engine->_text.numOverlayGlyphs > 0 ? g_engine->_text.numOverlayGlyphs : g_engine->_text._numGlyphs;
if (entry.alignment == 1) {
x -= measureStringWithFont(text, font, fontCount);
@@ -896,12 +881,13 @@ void View1::drawOverlayTextEntries() {
}
void View1::showStringBox(const Common::StringArray &sa) {
+ Text *text = &g_engine->_text;
const int padW = g_engine->dialogPadW();
const int padH = g_engine->dialogPadH();
const int textInset = g_engine->dialogTextInset();
const int lineHeight = g_engine->dialogLineHeight();
- const int totalWidth = g_engine->measureStrings(sa) + padW;
- const int totalHeight = g_engine->measureStringsVertically(sa) + padH;
+ const int totalWidth = text->measureStrings(sa) + padW;
+ const int totalHeight = text->measureStringsVertically(sa, lineHeight) + padH;
g_engine->_textLog.push_back(Common::String::format(
"Render text box: lines=%u pos=(%d,%d) size=(%d,%d) text=\"", sa.size(),
_stringBoxPosition.x, _stringBoxPosition.y, totalWidth, totalHeight) +
@@ -918,10 +904,11 @@ void View1::showStringBox(const Common::StringArray &sa) {
}
void View1::drawPathfindingPoints(Graphics::ManagedSurface &s) {
+ Text *text = &g_engine->_text;
GlyphData xData;
int xOffset = 0;
int yOffset = 0;
- if (g_engine->findGlyph('x', xData)) {
+ if (text->findGlyph('x', xData)) {
xOffset = xData._width / 2;
yOffset = xData._height / 2;
}
@@ -1137,17 +1124,18 @@ bool View1::handleDialogueChoiceClick(int clickY, int clickX) {
// Checks if click is within text box bounds (X+9..X+W-9, Y+9..Y+H-9).
// Iterates choice entries to find which line was clicked.
// Stores script index at scene+0x53B7 and clears scene+0x53B9.
+ Text *text = &g_engine->_text;
const int padW = g_engine->dialogPadW();
const int padH = g_engine->dialogPadH();
const int textInset = g_engine->dialogTextInset();
- const int boxW = g_engine->measureStrings(_drawnStringBox) + padW;
- const int boxH = g_engine->measureStringsVertically(_drawnStringBox) + padH;
+ const int lineHeight = g_engine->dialogLineHeight();
+ const int boxW = text->measureStrings(_drawnStringBox) + padW;
+ const int boxH = text->measureStringsVertically(_drawnStringBox, lineHeight) + padH;
if (clickX < _stringBoxPosition.x + textInset || clickY < _stringBoxPosition.y + textInset ||
clickX > _stringBoxPosition.x + boxW - textInset || clickY > _stringBoxPosition.y + boxH - textInset) {
return false;
}
- const int lineHeight = g_engine->dialogLineHeight();
const int firstLineY = _stringBoxPosition.y + textInset;
const int relY = clickY - firstLineY;
debug("handleDialogueChoiceClick: clickY=%d firstLineY=%d relY=%d lineHeight=%d clickedLine=%d",
@@ -3117,6 +3105,10 @@ void View1::drawSprite(int16 x, int16 y, const Sprite &sprite, Graphics::Managed
drawSprite(x, y, sprite._width, sprite._height, const_cast<byte *>(sprite._data.data()), s, mirrored, useDepth, depth, clipToGameArea);
}
+void View1::drawSprite(int16 x, int16 y, const GlyphData &glyph, Graphics::ManagedSurface &s, bool mirrored, bool useDepth, uint8 depth, bool clipToGameArea) {
+ drawSprite(x, y, glyph._width, glyph._height, const_cast<byte *>(glyph._data.data()), s, mirrored, useDepth, depth, clipToGameArea);
+}
+
void View1::drawSpriteClipped(uint16 x, uint16 y, const Common::Rect &clippingRect, uint16 width, uint16 height, const byte *const data, Graphics::ManagedSurface &s) {
for (int currentX = 0; currentX < width; currentX++) {
for (int currentY = 0; currentY < height; currentY++) {
@@ -3314,11 +3306,12 @@ void View1::showSpeechAct(uint16 characterIndex, const Common::Array<Common::Str
_currentSpeechActData.position = position;
_currentSpeechActData.onRightSide = onRightSide;
+ Text *text = &g_engine->_text;
const int padW = g_engine->dialogPadW();
const int padH = g_engine->dialogPadH();
const int portraitGap = g_engine->portraitTextGap();
- const int totalWidth = g_engine->measureStrings(strings) + padW;
- const int totalHeight = g_engine->measureStringsVertically(strings) + padH;
+ const int totalWidth = text->measureStrings(strings) + padW;
+ const int totalHeight = text->measureStringsVertically(strings, g_engine->dialogLineHeight()) + padH;
int stringBoxX = position.x;
int stringBoxY = position.y;
Common::Point portraitBoxPosition = position;
@@ -3753,8 +3746,8 @@ void View1::drawOriginalSaveLoadPanel(Graphics::ManagedSurface &s) {
} else {
label = "NONE";
}
- 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;
+ const GlyphData *font = g_engine->_text.numPanelGlyphs > 0 ? g_engine->_text._panelGlyphs : g_engine->_text._glyphs;
+ const uint16 fontCount = g_engine->_text.numPanelGlyphs > 0 ? g_engine->_text.numPanelGlyphs : g_engine->_text._numGlyphs;
renderStringWithFont(panelX + 6, panelY + 6 + slot * slotH, label, font, fontCount);
}
}
diff --git a/engines/macs2/view1.h b/engines/macs2/view1.h
index ae8541dd33a..0106c066122 100644
--- a/engines/macs2/view1.h
+++ b/engines/macs2/view1.h
@@ -200,6 +200,7 @@ private:
void drawSprite(int16 x, int16 y, uint16 width, uint16 height, byte *data, Graphics::ManagedSurface &s, bool mirrored, bool useDepth = false, uint8 depth = 0, bool clipToGameArea = false);
void drawSprite(int16 x, int16 y, const Sprite &sprite, Graphics::ManagedSurface &s, bool mirrored, bool useDepth = false, uint8 depth = 0, bool clipToGameArea = false);
+ void drawSprite(int16 x, int16 y, const GlyphData &glyph, Graphics::ManagedSurface &s, bool mirrored, bool useDepth = false, uint8 depth = 0, bool clipToGameArea = false);
void drawSprite(const Common::Point &pos, uint16 width, uint16 height, byte *data, Graphics::ManagedSurface &s, bool mirrored, bool useDepth = false, uint8 depth = 0, bool clipToGameArea = false);
void drawSpriteClipped(uint16 x, uint16 y, const Common::Rect &clippingRect, uint16 width, uint16 height, const byte *const data, Graphics::ManagedSurface &s);
Commit: c7ca3456d20b9fc6f979fe54b297a6ed5de382e5
https://github.com/scummvm/scummvm/commit/c7ca3456d20b9fc6f979fe54b297a6ed5de382e5
Author: Martin Gerhardy (martin.gerhardy at gmail.com)
Date: 2026-09-03T19:50:40+02:00
Commit Message:
MACS2: don't read opl values from exe, but embed them in the engine code
Changed paths:
engines/macs2/macs2.cpp
engines/macs2/music.cpp
engines/macs2/music.h
diff --git a/engines/macs2/macs2.cpp b/engines/macs2/macs2.cpp
index 1dd55630ec2..fe306dc2641 100644
--- a/engines/macs2/macs2.cpp
+++ b/engines/macs2/macs2.cpp
@@ -641,8 +641,6 @@ void Macs2Engine::readExecutable() {
// but the whole Data5 segment is identical (1020:0000...1020:3787)
// TODO: if there are ever other games using different versions of MCSEXEC.EXE, we should check the checksum here
- _music->readDataFromExecutable(exeFileStream.get());
-
exeFileStream->seek(0x0001B610, SEEK_SET);
exeFileStream->read(inventoryIconIndices.data(), 12);
diff --git a/engines/macs2/music.cpp b/engines/macs2/music.cpp
index 3d9a54b224f..0d74323a3be 100644
--- a/engines/macs2/music.cpp
+++ b/engines/macs2/music.cpp
@@ -25,7 +25,6 @@
#include "common/config-manager.h"
#include "common/endian.h"
#include "common/file.h"
-#include "common/memstream.h"
#include "common/util.h"
#include "engines/macs2/midiparser_macs2.h"
@@ -33,6 +32,58 @@
namespace Macs2 {
+// AdLib lookup tables from MCSEXEC.EXE Data5. Full and demo binaries match at these offsets.
+static const uint8 kOpSlotTable[18] = {
+ 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x08, 0x09,
+ 0x0A, 0x0B, 0x0C, 0x0D, 0x10, 0x11, 0x12, 0x13,
+ 0x14, 0x15
+};
+
+// Voice -> modulator operator (MCSEXEC 0x1B68D)
+static const uint8 kOpMap1[9] = {
+ 0x00, 0x01, 0x02, 0x08, 0x09, 0x0A, 0x10, 0x11, 0x12
+};
+
+// Voice -> carrier operator (MCSEXEC 0x1B696)
+static const uint8 kOpMap2[9] = {
+ 0x03, 0x04, 0x05, 0x0B, 0x0C, 0x0D, 0x13, 0x14, 0x15
+};
+
+// MIDI note -> OPL F-number low byte (MCSEXEC 0x1B69F)
+static const uint8 kFreqTableLo[128] = {
+ 0xAB, 0xB5, 0xC0, 0xCC, 0xD8, 0xE5, 0xF2, 0x01, 0x10, 0x20, 0x31, 0x43, 0x57, 0x6B, 0x81, 0x98,
+ 0xB0, 0xCA, 0xE5, 0x02, 0x20, 0x41, 0x63, 0x87, 0x57, 0x6B, 0x81, 0x98, 0xB0, 0xCA, 0xE5, 0x02,
+ 0x20, 0x41, 0x63, 0x87, 0x57, 0x6B, 0x81, 0x98, 0xB0, 0xCA, 0xE5, 0x02, 0x20, 0x41, 0x63, 0x87,
+ 0x57, 0x6B, 0x81, 0x98, 0xB0, 0xCA, 0xE5, 0x02, 0x20, 0x41, 0x63, 0x87, 0x57, 0x6B, 0x81, 0x98,
+ 0xB0, 0xCA, 0xE5, 0x02, 0x20, 0x41, 0x63, 0x87, 0x57, 0x6B, 0x81, 0x98, 0xB0, 0xCA, 0xE5, 0x02,
+ 0x20, 0x41, 0x63, 0x87, 0x57, 0x6B, 0x81, 0x98, 0xB0, 0xCA, 0xE5, 0x02, 0x20, 0x41, 0x63, 0x87,
+ 0x57, 0x6B, 0x81, 0x98, 0xB0, 0xCA, 0xE5, 0x02, 0x20, 0x41, 0x63, 0x87, 0xAE, 0xD6, 0x02, 0x30,
+ 0x60, 0x94, 0xCA, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF
+};
+
+// MIDI note -> OPL block / F-number high byte (MCSEXEC 0x1B71F)
+static const uint8 kFreqTableHi[128] = {
+ 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21,
+ 0x21, 0x21, 0x21, 0x22, 0x22, 0x22, 0x22, 0x22, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x26,
+ 0x26, 0x26, 0x26, 0x26, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x2A, 0x2A, 0x2A, 0x2A, 0x2A,
+ 0x2D, 0x2D, 0x2D, 0x2D, 0x2D, 0x2D, 0x2D, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x31, 0x31, 0x31, 0x31,
+ 0x31, 0x31, 0x31, 0x32, 0x32, 0x32, 0x32, 0x32, 0x35, 0x35, 0x35, 0x35, 0x35, 0x35, 0x35, 0x36,
+ 0x36, 0x36, 0x36, 0x36, 0x39, 0x39, 0x39, 0x39, 0x39, 0x39, 0x39, 0x3A, 0x3A, 0x3A, 0x3A, 0x3A,
+ 0x3D, 0x3D, 0x3D, 0x3D, 0x3D, 0x3D, 0x3D, 0x3E, 0x3E, 0x3E, 0x3E, 0x3E, 0x3E, 0x3E, 0x3F, 0x3F,
+ 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F
+};
+
+static const uint8 kPercVolTable[32] = {
+ 28, 25, 23, 18, 14, 11, 8, 2,
+ 50, 42, 37, 35, 34, 32, 30, 2,
+ 55, 50, 49, 48, 45, 43, 40, 2,
+ 60, 60, 58, 56, 54, 52, 50, 2
+};
+
+static const uint8 kPercOpMap[5] = {19, 20, 18, 21, 17};
+static const uint8 kPercFreqChannel[5] = {6, 7, 8, 8, 7};
+
+
Music::Music() : _opl(nullptr), _parser(nullptr), _playing(false),
_masterVolume(0), _numOplChannels(9), _instrumentDataOffset(0),
_smf(nullptr), _smfDucked(false), _smfVolumeBeforeDuck(192) {
@@ -91,8 +142,8 @@ void Music::silenceAll() {
writeReg(i + 0xB0, readReg(i + 0xB0) & 0xDF);
}
// Silence all operator volumes
- for (int i = 0; i < (int)_opSlotTable.size() && i <= 0x11; i++) {
- writeReg(_opSlotTable[i] + 0x40, 0xFF);
+ for (int i = 0; i < ARRAYSIZE(kOpSlotTable); i++) {
+ writeReg(kOpSlotTable[i] + 0x40, 0xFF);
}
}
@@ -325,10 +376,10 @@ void Music::noteOn(byte channel, byte note, byte velocity) {
// Key off, set volumes, then key on
writeReg(voice + 0xB0, 0);
- const byte reg2 = readReg(_opMap2[voice] + 0x40);
- writeReg(_opMap2[voice] + 0x40, (reg2 & 0xC0) + vol1);
- const byte reg1 = readReg(_opMap1[voice] + 0x40);
- writeReg(_opMap1[voice] + 0x40, (reg1 & 0xC0) + vol2);
+ const byte reg2 = readReg(kOpMap2[voice] + 0x40);
+ writeReg(kOpMap2[voice] + 0x40, (reg2 & 0xC0) + vol1);
+ const byte reg1 = readReg(kOpMap1[voice] + 0x40);
+ writeReg(kOpMap1[voice] + 0x40, (reg1 & 0xC0) + vol2);
_channelPitchBend[channel] = 0;
setFrequency(voice, note, 0);
@@ -337,14 +388,14 @@ void Music::noteOn(byte channel, byte note, byte velocity) {
const uint16 instBase = (uint16)_channelPrograms[channel] << 4;
const uint8 percIdx = channel - 0x0B;
- if (percIdx >= _percOpMap.size())
+ if (percIdx >= ARRAYSIZE(kPercOpMap))
return;
- const uint8 opIdx = _percOpMap[percIdx];
+ const uint8 opIdx = kPercOpMap[percIdx];
if (channel == 0x0B) {
// Bass drum: load full instrument
- loadInstrument(_percFreqChannel[percIdx], _channelPrograms[0x0B]);
+ loadInstrument(kPercFreqChannel[percIdx], _channelPrograms[0x0B]);
} else if ((uint32)_instrumentDataOffset + instBase + 9 <= _songData.size()) {
const byte *instPtr = _songData.data() + _instrumentDataOffset + instBase;
writeReg(opIdx + 0x20, instPtr[0]);
@@ -359,13 +410,13 @@ void Music::noteOn(byte channel, byte note, byte velocity) {
if ((uint32)(_instrumentDataOffset + instBase + 3) < _songData.size()) {
const byte volByte = _songData[_instrumentDataOffset + instBase + 3];
const uint8 volIdx = ((volByte & 0x3F) >> 4) * 8 + (velocity >> 4);
- if (volIdx < _percVolTable.size())
- vol = _percVolTable[volIdx] + _masterVolume;
+ if (volIdx < ARRAYSIZE(kPercVolTable))
+ vol = kPercVolTable[volIdx] + _masterVolume;
}
if (vol > 0x3F)
vol = 0x3F;
- const uint8 freqChan = _percFreqChannel[percIdx];
+ const uint8 freqChan = kPercFreqChannel[percIdx];
writeReg(freqChan + 0xB0, 0);
const byte regVal = readReg(opIdx + 0x40);
writeReg(opIdx + 0x40, vol + (regVal & 0xC0));
@@ -387,9 +438,11 @@ void Music::noteOff(byte channel, byte note) {
for (uint8 v = 0; v < _numOplChannels; v++) {
if (_voiceAge[v] == 0 && _voiceMidiChannel[v] == channel && _voiceNote[v] == note) {
// Write frequency without key-on
- const uint16 freq = ((uint16)_freqTableHi[note] << 8) | _freqTableLo[note];
- writeReg(v + 0xA0, freq & 0xFF);
- writeReg(v + 0xB0, (freq >> 8) & 0xDF); // clear key-on bit
+ if (note < ARRAYSIZE(kFreqTableLo)) {
+ const uint16 freq = ((uint16)kFreqTableHi[note] << 8) | kFreqTableLo[note];
+ writeReg(v + 0xA0, freq & 0xFF);
+ writeReg(v + 0xB0, (freq >> 8) & 0xDF); // clear key-on bit
+ }
_voiceAge[v] = 1;
break;
}
@@ -444,8 +497,8 @@ void Music::loadInstrument(uint8 voice, uint8 program) {
return;
const byte *inst = _songData.data() + _instrumentDataOffset + instBase;
- const uint8 op1 = _opMap1[voice];
- const uint8 op2 = _opMap2[voice];
+ const uint8 op1 = kOpMap1[voice];
+ const uint8 op2 = kOpMap2[voice];
writeReg(op1 + 0x20, inst[0]);
writeReg(op2 + 0x20, inst[1]);
@@ -461,19 +514,19 @@ void Music::loadInstrument(uint8 voice, uint8 program) {
}
void Music::setFrequency(uint8 voice, uint8 note, uint8 pitchBend) {
- if (note == 0xFF || note >= _freqTableLo.size())
+ if (note == 0xFF || note >= ARRAYSIZE(kFreqTableLo))
return;
- uint16 freq = ((uint16)_freqTableHi[note] << 8) | _freqTableLo[note];
+ uint16 freq = ((uint16)kFreqTableHi[note] << 8) | kFreqTableLo[note];
if (pitchBend != 0) {
if (pitchBend < 0x80) {
const uint8 nextNote = (note < 0x7F) ? note + 1 : 0x7F;
- const uint16 nextFreq = ((uint16)_freqTableHi[nextNote] << 8) | _freqTableLo[nextNote];
+ const uint16 nextFreq = ((uint16)kFreqTableHi[nextNote] << 8) | kFreqTableLo[nextNote];
freq += (uint16)((uint64)pitchBend * (nextFreq - freq) >> 7);
} else {
const uint8 prevNote = (note > 0) ? note - 1 : 0;
- const uint16 prevFreq = ((uint16)_freqTableHi[prevNote] << 8) | _freqTableLo[prevNote];
+ const uint16 prevFreq = ((uint16)kFreqTableHi[prevNote] << 8) | kFreqTableLo[prevNote];
freq -= (uint16)((uint64)pitchBend * (freq - prevFreq) >> 7);
}
}
@@ -490,40 +543,12 @@ void Music::updateDebugState() {
_debug.voices[i].note = _voiceNote[i];
_debug.voices[i].channel = _voiceMidiChannel[i];
_debug.voices[i].active = (_voiceAge[i] == 0);
- _debug.voices[i].volume = _regShadow[_opMap2.size() > (uint)i ? _opMap2[i] + 0x40 : 0] & 0x3F;
+ _debug.voices[i].volume = _regShadow[kOpMap2[i] + 0x40] & 0x3F;
_debug.regHistory[i][_debug.ringPos] = (_regShadow[i + 0xB0] & 0x20) ? 1.0f - (_debug.voices[i].volume / 63.0f) : 0.0f;
}
_debug.ringPos = (_debug.ringPos + 1) % kDebugRingSize;
}
-void Music::readDataFromExecutable(Common::MemoryReadStream *fileStream) {
- constexpr uint32 size = 255;
-
- _opSlotTable.resize(size);
- loadData(fileStream, 0x0001B669, size, _opSlotTable.data());
-
- _opMap1.resize(size);
- loadData(fileStream, 0x0001B68D, size, _opMap1.data());
-
- _opMap2.resize(size);
- loadData(fileStream, 0x0001B696, size, _opMap2.data());
-
- _freqTableLo.resize(size);
- loadData(fileStream, 0x0001B69F, size, _freqTableLo.data());
-
- _freqTableHi.resize(size);
- loadData(fileStream, 0x0001B71F, size, _freqTableHi.data());
-
- _percVolTable = {28, 25, 23, 18, 14, 11, 8, 2, 50, 42, 37, 35, 34, 32, 30, 2, 55, 50, 49, 48, 45, 43, 40, 2, 60, 60, 58, 56, 54, 52, 50, 2};
- _percOpMap = {19, 20, 18, 21, 17};
- _percFreqChannel = {6, 7, 8, 8, 7};
-}
-
-void Music::loadData(Common::MemoryReadStream *fileStream, int64 pos, uint16 size, void *target) {
- fileStream->seek(pos, SEEK_SET);
- fileStream->read(target, size);
-}
-
SmfMidiPlayer::SmfMidiPlayer() {
createDriver();
if (_driver == nullptr)
diff --git a/engines/macs2/music.h b/engines/macs2/music.h
index 5bf25ed2853..04d195ff725 100644
--- a/engines/macs2/music.h
+++ b/engines/macs2/music.h
@@ -30,10 +30,6 @@
class MidiParser;
-namespace Common {
-class MemoryReadStream;
-}
-
namespace OPL {
class OPL;
}
@@ -94,8 +90,6 @@ public:
bool isMidiFilePlaying() const;
bool hasAdlibBackend() const { return _opl != nullptr; }
- void readDataFromExecutable(Common::MemoryReadStream *fileStream);
-
// MidiDriver_BASE interface
void send(uint32 b) override;
void metaEvent(byte type, const byte *data, uint16 length) override;
@@ -170,18 +164,6 @@ private:
void stopAdlibPlayback();
void stopSmfPlayback();
bool ensureSmfPlayer();
-
- // Lookup tables from EXE
- Common::Array<uint8> _opSlotTable;
- Common::Array<uint8> _opMap1;
- Common::Array<uint8> _opMap2;
- Common::Array<uint8> _freqTableLo;
- Common::Array<uint8> _freqTableHi;
- Common::Array<uint8> _percVolTable;
- Common::Array<uint8> _percOpMap;
- Common::Array<uint8> _percFreqChannel;
-
- void loadData(Common::MemoryReadStream *stream, int64 pos, uint16 size, void *target);
};
} // End of namespace Macs2
More information about the Scummvm-git-logs
mailing list