[Scummvm-git-logs] scummvm master -> a307f3d6cb00f4d6ea454efa6cb2f17225ca9028
mgerhardy
noreply at scummvm.org
Mon Aug 31 19:43:19 UTC 2026
This automated email contains information about 14 new commits which have been
pushed to the 'scummvm' repo located at https://api.github.com/repos/scummvm/scummvm .
Summary:
1b90e5aae2 MACS2: reduced code duplication in amiga resource loading
db20d5ede4 MACS2: support translating the ui labels for the action bar
ada2aa0d38 MACS2: reset version back to 1 - the game was never officially supported
59e2ff7b3c MACS2: several ux enhancement fixes for the custom action bar
57873094c1 MACS2: use the dialogue font for german special chars
9f6feb29d7 MACS2: convert to enums
54fb2284b9 MACS2: renamed members
95f33ac6be MACS2: cleanup amiga resource loading
0377e6b5b0 MACS2: fixed CID 1685788
eef25e7b2c MACS2: replaced magic number
2b6c76b41a MACS2: fixed portrait palette
a40f1cdd83 MACS2: amiga palette fixes
ca4601c2ec MACS2: reduced code duplication
a307f3d6cb MACS2: amiga cleanup
Commit: 1b90e5aae2727a0b83cb7f938fb3001fc6ca74e3
https://github.com/scummvm/scummvm/commit/1b90e5aae2727a0b83cb7f938fb3001fc6ca74e3
Author: Martin Gerhardy (martin.gerhardy at gmail.com)
Date: 2026-08-31T21:14:09+02:00
Commit Message:
MACS2: reduced code duplication in amiga resource loading
Changed paths:
engines/macs2/amiga_archive.h
engines/macs2/amiga_decode.cpp
engines/macs2/amiga_decode.h
engines/macs2/amiga_resources.cpp
diff --git a/engines/macs2/amiga_archive.h b/engines/macs2/amiga_archive.h
index 07995fcb39a..5054ebc5a03 100644
--- a/engines/macs2/amiga_archive.h
+++ b/engines/macs2/amiga_archive.h
@@ -58,15 +58,15 @@ struct AmigaInfoData {
uint16 sceneCount = 0;
uint16 volumeCount = 0;
uint16 mdirSize = 0;
- /** 16x 12-bit 0x0RGB UI colors from MXIN (Ghidra DAT_002379dc). */
+ /** 16x 12-bit 0x0RGB UI colors from MXIN. */
uint16 uiPaletteAmiga[16] = {0};
/**
- * Panel border line/corner color indices from MXIN (Ghidra g_awPanelBorderColorIndices).
+ * Panel border line/corner color indices from MXIN.
* Demo: 18,19,20,21,22.
*/
uint16 panelBorderColorIndices[5] = {0};
/**
- * Panel darken luminance->color table (Ghidra g_awPanelDarkenColorIndices).
+ * Panel darken luminance->color table.
* Demo: 28,29,29,60,61,31,62,23. Indexed by (7 - (R4+G4+B4)/0x18).
*/
uint16 panelDarkenColorIndices[8] = {0};
diff --git a/engines/macs2/amiga_decode.cpp b/engines/macs2/amiga_decode.cpp
index 48bf8984821..0b85b7f1ae5 100644
--- a/engines/macs2/amiga_decode.cpp
+++ b/engines/macs2/amiga_decode.cpp
@@ -50,7 +50,7 @@ bool parseAmigaMxoo(const byte *mxoo, uint32 mxooSize, AmigaMxooInfo &out) {
if (READ_BE_UINT16(body + 0x0C) != 0x0101)
return false;
- for (uint i = 0; i < 21; i++)
+ for (uint i = 0; i < ARRAYSIZE(out.slotOffsets); i++)
out.slotOffsets[i] = READ_BE_UINT32(body + 0x0E + i * 4);
out.extraOffset = READ_BE_UINT32(body + 0x62);
return true;
@@ -311,8 +311,6 @@ bool convertAmigaPortraitAtlasToDosBlob(const byte *mxoo, uint32 mxooSize, uint3
return false;
const uint32 absExtra = 12 + bodyRelativeExtraOffset;
- // animateDialoguePortrait @ 0022f79c: copy 14400 bytes; blit D3=D4=D6=0x50,
- // D5=0xF0. drawSprite plane loop uses 6 separated planes -> 30*80*6=14400.
// Atlas is 240x80 (3x80x80 frames). Color from planes 0..4.
const uint16 atlasW = 240;
const uint16 atlasH = 80;
@@ -374,12 +372,6 @@ bool convertAmigaPortraitAtlasToDosBlob(const byte *mxoo, uint32 mxooSize, uint3
atlas.data() + (uint32)y * atlasW + (uint32)f * frameW,
frameW);
}
- // Keep Amiga COLOR indices 0..31. Demo portraits (OO_*) use only the
- // copper high bank COLOR17..31 (chrome / skin / navy cap) - the same
- // registers animateDialoguePortrait blits into on hardware. Remapping
- // into a private 0xD0 bank drifted from live copper and caused the
- // Freunde-dialogue palette mismatch vs FS-UAE.
- // Color 0 stays transparent (drawSprite skips 0).
off += frameBytes;
}
return true;
@@ -402,6 +394,24 @@ static bool decompressPp20ToBuffer(const byte *src, uint32 srcLen, Common::Array
return true;
}
+void amiga12ToVga6(uint16 rgb, byte &r6, byte &g6, byte &b6) {
+ const byte r4 = (rgb >> 8) & 0xF;
+ const byte g4 = (rgb >> 4) & 0xF;
+ const byte b4 = rgb & 0xF;
+ r6 = (byte)((r4 * 63) / 15);
+ g6 = (byte)((g4 * 63) / 15);
+ b6 = (byte)((b4 * 63) / 15);
+}
+
+void amiga12ToRgb8(uint16 rgb, byte &r, byte &g, byte &b) {
+ const byte r4 = (rgb >> 8) & 0xF;
+ const byte g4 = (rgb >> 4) & 0xF;
+ const byte b4 = rgb & 0xF;
+ r = (byte)(r4 * 17);
+ g = (byte)(g4 * 17);
+ b = (byte)(b4 * 17);
+}
+
bool decodeAmigaMxmmSceneBackground(const byte *mxmm, uint32 mxmmSize,
Common::Array<byte> &outPixels,
Graphics::Palette &outPalette,
@@ -453,19 +463,9 @@ bool decodeAmigaMxmmSceneBackground(const byte *mxmm, uint32 mxmmSize,
const byte *copper = screen.data() + kAmigaSceneCopperOffset;
uint16 base16[16];
- for (uint i = 0; i < 16; i++)
+ for (uint i = 0; i < ARRAYSIZE(base16); i++)
base16[i] = READ_BE_UINT16(copper + i * 2);
const byte *lineColors = copper + 0x20; // 200 x 16 x u16BE
-
- auto amiga12ToRgb8 = [](uint16 rgb, byte &r, byte &g, byte &b) {
- const byte r4 = (rgb >> 8) & 0xF;
- const byte g4 = (rgb >> 4) & 0xF;
- const byte b4 = rgb & 0xF;
- r = (byte)(r4 * 17);
- g = (byte)(g4 * 17);
- b = (byte)(b4 * 17);
- };
-
auto buildPal32 = [&](uint16 y, byte pal32[32][3]) {
amiga12ToRgb8(base16[0], pal32[0][0], pal32[0][1], pal32[0][2]);
for (uint i = 0; i < 16; i++) {
diff --git a/engines/macs2/amiga_decode.h b/engines/macs2/amiga_decode.h
index bc21397725b..a4de2cc5571 100644
--- a/engines/macs2/amiga_decode.h
+++ b/engines/macs2/amiga_decode.h
@@ -70,6 +70,7 @@ struct AmigaAnimSlotInfo {
bool parseAmigaMxoo(const byte *mxoo, uint32 mxooSize, AmigaMxooInfo &out);
bool inspectAmigaAnimSlot(const byte *mxoo, uint32 mxooSize, uint32 bodyRelativeOffset, AmigaAnimSlotInfo &out);
+void amiga12ToVga6(uint16 rgb, byte &r6, byte &g6, byte &b6);
/** Decode one frame of planar Amiga anim data to chunky 8bpp (color planes 0..4). */
bool decodeAmigaPlanarFrame(const byte *planar, uint16 width, uint16 height, uint16 frameIndex,
diff --git a/engines/macs2/amiga_resources.cpp b/engines/macs2/amiga_resources.cpp
index 1c4afeab99d..9923ab88972 100644
--- a/engines/macs2/amiga_resources.cpp
+++ b/engines/macs2/amiga_resources.cpp
@@ -37,7 +37,6 @@
namespace Macs2 {
-
bool Macs2Engine::loadAmigaSceneBackground(uint32 sceneResourceId) {
if (!_amigaArchive || sceneResourceId == 0 || sceneResourceId > 0xFFFF)
return false;
@@ -81,20 +80,15 @@ bool Macs2Engine::loadAmigaSceneBackground(uint32 sceneResourceId) {
// Keep Info UI chrome colors in the high VGA indices used by panel drawing.
if (_amigaArchive->getInfo().loaded) {
const AmigaInfoData &info = _amigaArchive->getInfo();
- auto amiga12ToVga6 = [](uint16 rgb, byte &r6, byte &g6, byte &b6) {
- const byte r4 = (rgb >> 8) & 0xF;
- const byte g4 = (rgb >> 4) & 0xF;
- const byte b4 = rgb & 0xF;
- r6 = (byte)((r4 * 63) / 15);
- g6 = (byte)((g4 * 63) / 15);
- b6 = (byte)((b4 * 63) / 15);
- };
- for (uint i = 0; i < 16; i++) {
- byte r6, g6, b6;
- amiga12ToVga6(info.uiPaletteAmiga[i], r6, g6, b6);
+ for (uint i = 0; i < ARRAYSIZE(info.uiPaletteAmiga); i++) {
const uint idx = 0xF0 + i;
if (idx >= 256)
break;
+ uint16 rgb = info.uiPaletteAmiga[i];
+ byte r6;
+ byte g6;
+ byte b6;
+ amiga12ToVga6(rgb, r6, g6, b6);
_palVanilla.set(idx, r6, g6, b6);
}
}
@@ -105,10 +99,11 @@ bool Macs2Engine::loadAmigaSceneBackground(uint32 sceneResourceId) {
buildAmigaPanelRemapTable();
applyPaletteDarkening();
- _depthMap.fillRect(Common::Rect(0, 0, kScreenWidth, kGameHeight), 0);
- _pathfindingMap.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);
+ Common::Rect screenRect(0, 0, kScreenWidth, kGameHeight);
+ _depthMap.fillRect(screenRect, 0);
+ _pathfindingMap.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)) {
@@ -216,7 +211,6 @@ bool Macs2Engine::loadAmigaSceneBackground(uint32 sceneResourceId) {
return true;
}
-// --- loadAmigaMxffFont (macs2.cpp:2807-2866) ---
bool Macs2Engine::loadAmigaMxffFont() {
if (!_amigaArchive || !_amigaArchive->hasResource(kAmigaResFF, 0))
return false;
@@ -340,7 +334,6 @@ bool Macs2Engine::loadAmigaOverlayFont(uint8 resourceIndex) {
return true;
}
-// --- loadAmigaCursorResource (macs2.cpp:2868-2894) ---
bool Macs2Engine::loadAmigaCursorResource(uint16 resourceId, AnimFrame &out) {
out = AnimFrame();
if (!_amigaArchive || resourceId == 0)
@@ -369,7 +362,6 @@ bool Macs2Engine::loadAmigaCursorResource(uint16 resourceId, AnimFrame &out) {
return true;
}
-// --- installAmigaPortraitPalette (macs2.cpp:2896-2937) ---
void Macs2Engine::installAmigaPortraitPalette(bool copyFromPlayfield) {
// Portraits now keep Amiga COLOR indices and share the playfield copper
// high bank (COLOR17..31), matching animateDialoguePortrait on hardware.
@@ -384,15 +376,6 @@ void Macs2Engine::installAmigaPortraitPalette(bool copyFromPlayfield) {
if (_amigaNativePlayfieldPalette)
return;
- auto amiga12ToVga6 = [](uint16 rgb, byte &r6, byte &g6, byte &b6) {
- const byte r4 = (rgb >> 8) & 0xF;
- const byte g4 = (rgb >> 4) & 0xF;
- const byte b4 = rgb & 0xF;
- r6 = (byte)((r4 * 63) / 15);
- g6 = (byte)((g4 * 63) / 15);
- b6 = (byte)((b4 * 63) / 15);
- };
-
static const uint16 kHighBankFallback[15] = {
0x0BBA, 0x0EB8, 0x0C96, 0x0A74, 0x0963, 0x0741, 0x0000, 0x049E,
0x0C00, 0x0DDC, 0x0EEE, 0x0887, 0x0776, 0x0006, 0x0520
@@ -403,7 +386,7 @@ void Macs2Engine::installAmigaPortraitPalette(bool copyFromPlayfield) {
if (_amigaArchive && _amigaArchive->getInfo().loaded)
highSrc = &_amigaArchive->getInfo().uiPaletteAmiga[1];
- for (uint i = 0; i < 15; i++) {
+ for (uint i = 0; i < ARRAYSIZE(kHighBankFallback); i++) {
byte r6, g6, b6;
amiga12ToVga6(highSrc[i], r6, g6, b6);
const uint idx = 17 + i;
@@ -411,22 +394,12 @@ void Macs2Engine::installAmigaPortraitPalette(bool copyFromPlayfield) {
}
}
-// --- applyAmigaUiPalette (macs2.cpp:2939-2990) ---
void Macs2Engine::applyAmigaUiPalette() {
if (!_amigaArchive || !_amigaArchive->getInfo().loaded)
return;
const AmigaInfoData &info = _amigaArchive->getInfo();
- auto amiga12ToVga6 = [](uint16 rgb, byte &r6, byte &g6, byte &b6) {
- const byte r4 = (rgb >> 8) & 0xF;
- const byte g4 = (rgb >> 4) & 0xF;
- const byte b4 = rgb & 0xF;
- r6 = (byte)((r4 * 63) / 15);
- g6 = (byte)((g4 * 63) / 15);
- b6 = (byte)((b4 * 63) / 15);
- };
-
// Provisional playfield until an MM_* copper list is loaded.
// Copper base16 layout: COLOR00 + COLOR17..31 = MXIN ui[0..15]. COLOR01..16
// stay a visible ramp (overwritten per-line by scene copper).
@@ -443,7 +416,7 @@ void Macs2Engine::applyAmigaUiPalette() {
}
// Amiga UI colors also live in the high indices used by panel/chrome drawing.
- for (uint i = 0; i < 16; i++) {
+ for (uint i = 0; i < ARRAYSIZE(info.uiPaletteAmiga); i++) {
amiga12ToVga6(info.uiPaletteAmiga[i], r6, g6, b6);
const uint idx = 0xF0 + i;
if (idx >= Graphics::PALETTE_COUNT)
@@ -456,19 +429,13 @@ void Macs2Engine::applyAmigaUiPalette() {
applyPaletteDarkening();
}
-// --- buildAmigaPanelRemapTable (macs2.cpp:2992-3018) ---
void Macs2Engine::buildAmigaPanelRemapTable() {
- // Ghidra fill_ui_panel_darken_remap @ 002221fe:
- // bucket = 7 - (R4+G4+B4)/0x18, then index g_awPanelDarkenColorIndices[bucket].
- // Those indices are playfield copper slots on Amiga. ScummVM must not paint wood
- // RGB into those slots (breaks intro/scene art). Instead map buckets onto the
- // private MXIN UI bank already installed at 0xF0..0xFF (opaque brown ramp).
if (_panelRemapTable.size() != 0x100)
_panelRemapTable.resize(0x100);
// MXIN UI[0..5] = wood ramp (BBA..741). UI[6] is 0x000 - never use it for fill.
static const byte kUiWood[8] = {0, 1, 2, 3, 4, 5, 2, 3};
- for (uint i = 0; i < 0x100; i++) {
+ for (uint i = 0; i < 256; i++) {
byte r6, g6, b6;
_palVanilla.get(i, r6, g6, b6);
const uint r4 = (r6 * 15) / 63;
@@ -562,7 +529,6 @@ void Macs2Engine::readAmigaResources() {
applyAmigaUiPalette();
- // Native MXFF dialogue font (Ghidra drawText @ 00224492). Prefer over DOS MCS glyphs.
const bool loadedMxff = loadAmigaMxffFont();
// Load every OO resource as a GameObject. Object index = resource id + 1.
@@ -700,8 +666,6 @@ void Macs2Engine::readAmigaResources() {
loadedObjects++;
}
- // Protagonist object slot (OO_0000 -> object 1). Do NOT place him in the room here -
- // the intro/scene MXMM scripts call moveObject on scene init (same as DOS).
GameObject *protagonist = GameObjects::instance()._objects[0];
if (protagonist == nullptr) {
protagonist = new GameObject();
@@ -715,8 +679,6 @@ void Macs2Engine::readAmigaResources() {
Scenes::instance()._currentActorIndex = 1;
- // Info MXIN u32 @ offset 8 = starting MM resource id (demo: 40 -> intro MM_0040).
- // Script-visible scene id = MM id + 1 (Ghidra load_scene_mxmm / FUN_002215fa).
uint16 startResourceId = _amigaArchive->getInfo().startSceneResourceId;
if (startResourceId == 0 || !_amigaArchive->hasResource(kAmigaResMM, startResourceId)) {
if (_amigaArchive->hasResource(kAmigaResMM, 40))
@@ -744,7 +706,6 @@ void Macs2Engine::readAmigaResources() {
_scenePaletteMode = 1;
_paletteDarkenPercent = 0;
- // Fonts: MXFF dialogue font from DataA (standalone Amiga demo - no DOS MCS).
if (!loadedMxff)
warning("Amiga: no MXFF font FF_0000 in DataA - text may be missing");
Commit: db20d5ede42f442281b64a9b04540e72a0219a19
https://github.com/scummvm/scummvm/commit/db20d5ede42f442281b64a9b04540e72a0219a19
Author: Martin Gerhardy (martin.gerhardy at gmail.com)
Date: 2026-08-31T21:14:09+02:00
Commit Message:
MACS2: support translating the ui labels for the action bar
Changed paths:
engines/macs2/actionbar.cpp
engines/macs2/actionbar.h
engines/macs2/gameobjects.h
engines/macs2/macs2.cpp
engines/macs2/macs2.h
engines/macs2/view1.cpp
diff --git a/engines/macs2/actionbar.cpp b/engines/macs2/actionbar.cpp
index 6870c42b0c1..d362b19f0fa 100644
--- a/engines/macs2/actionbar.cpp
+++ b/engines/macs2/actionbar.cpp
@@ -23,6 +23,7 @@
#include "common/debug.h"
#include "common/system.h"
+#include "common/translation.h"
#include "engines/savestate.h"
#include "gui/message.h"
#include "macs2/detection.h"
@@ -33,22 +34,22 @@
namespace Macs2 {
-static Common::String getObjectDisplayName(const GameObject *obj) {
- if (!obj)
- return Common::String();
-
- const GameObjects &objects = GameObjects::instance();
- if (obj->_index < objects._objectNames.size() && !objects._objectNames[obj->_index].empty())
- return objects._objectNames[obj->_index];
+namespace {
- return Common::String();
+// German source keys for macs2_translation.dat msgctxt "uilabel".
+Common::String uiText(const char *source) {
+ if (g_engine)
+ return g_engine->translateUiLabel(source);
+ return source;
}
+} // namespace
+
const ActionBar::VerbDef ActionBar::kVerbs[4] = {
- {"Walk", Script::MouseMode::Walk},
- {"Look", Script::MouseMode::Look},
- {"Use", Script::MouseMode::Use},
- {"Talk", Script::MouseMode::Talk}
+ {"Gehen", Script::MouseMode::Walk},
+ {"Schauen", Script::MouseMode::Look},
+ {"Benutzen", Script::MouseMode::Use},
+ {"Reden", Script::MouseMode::Talk}
};
ActionBar::ActionBar(View1 *view)
@@ -166,27 +167,7 @@ void ActionBar::drawUIButton(const Common::Rect &rect, bool pressed, Graphics::M
}
void ActionBar::drawSentenceLine(Graphics::ManagedSurface &s) {
- Common::String sentence;
- const Script::MouseMode mode = g_engine->_scriptExecutor->_cursorMode;
-
- if (mode == Script::MouseMode::UseInventory && _view->_activeInventoryItem) {
- sentence = "Use";
- const Common::String itemName = getObjectDisplayName(_view->_activeInventoryItem);
- if (!itemName.empty())
- sentence += " " + itemName;
- } else if (_activeVerbIndex >= 0 && _activeVerbIndex < 4) {
- sentence = kVerbs[_activeVerbIndex].label;
- }
- if (!_sentenceObject.empty()) {
- if (!sentence.empty()) {
- if (mode == Script::MouseMode::UseInventory)
- sentence += " with ";
- else
- sentence += " ";
- }
- sentence += _sentenceObject;
- }
-
+ Common::String sentence = buildSentenceLine();
if (sentence.empty())
return;
@@ -209,9 +190,10 @@ void ActionBar::drawVerbBar(Graphics::ManagedSurface &s) {
drawUIButton(r, isActive || isHovered, s);
- const int textX = r.left + (r.width() - (int)strlen(kVerbs[i].label) * 6) / 2;
+ const Common::String label = uiText(kVerbs[i].label);
+ const int textX = r.left + (r.width() - (int)label.size() * 6) / 2;
const int textY = r.top + (r.height() - (int)g_engine->maxGlyphHeight) / 2;
- _view->renderStringTo(textX, textY, kVerbs[i].label, s);
+ _view->renderStringTo(textX, textY, label, s);
}
}
@@ -431,6 +413,7 @@ void ActionBar::handleMouseMoveScumm(const Common::Point &pos) {
break;
if (getInvItemRect(i).contains(pos)) {
_hoveredItemIndex = i;
+ updateSentenceLine(getObjectHotspotName(items[itemIdx]->_index));
break;
}
}
@@ -497,53 +480,68 @@ void ActionBar::refreshSaveSlotNames() {
if (desc.getSaveSlot() != -1 && !desc.getDescription().empty())
g_engine->_saveSlotNames.push_back(desc.getDescription());
else
- g_engine->_saveSlotNames.push_back(Common::String::format("--- Slot %d ---", slot + 1));
+ g_engine->_saveSlotNames.push_back(Common::String::format(uiText("--- Platz %d ---").c_str(), slot + 1));
+ }
+}
+
+Common::String ActionBar::translatedVerbLabel(Script::MouseMode mode) const {
+ if (mode == Script::MouseMode::UseInventory) {
+ mode = Script::MouseMode::Use;
+ }
+ for (int i = 0; i < ARRAYSIZE(kVerbs); i++) {
+ if (kVerbs[i].mode == mode) {
+ return uiText(kVerbs[i].label);
+ }
}
+ return uiText("Gehen");
+}
+
+Common::String ActionBar::currentTargetDisplayName() const {
+ if (!_sentenceObject.empty())
+ return _sentenceObject;
+
+ const Common::Point mouse = g_system->getEventManager()->getMousePos();
+ if (isPointInUI(mouse))
+ return Common::String();
+
+ uint16 hoverId = _view->getHitObjectID(mouse);
+ if (hoverId == 0)
+ hoverId = g_engine->getHotspotAtPoint(mouse);
+ return lookupInteractionDisplayName(hoverId);
}
-Common::String ActionBar::buildNativeSentenceLine() const {
+Common::String ActionBar::buildSentenceLine() const {
const Script::MouseMode mode = g_engine->_scriptExecutor->_cursorMode;
- const char *verb = "Walk";
- if (mode == Script::MouseMode::Look)
- verb = "Look";
- else if (mode == Script::MouseMode::Talk)
- verb = "Talk";
- else if (mode == Script::MouseMode::Use || mode == Script::MouseMode::UseInventory)
- verb = "Use";
- else if (mode == Script::MouseMode::PanelCursor || mode == Script::MouseMode::Disabled)
+ if (mode == Script::MouseMode::PanelCursor || mode == Script::MouseMode::Disabled) {
return Common::String();
+ }
- Common::String line(verb);
+ const Common::String targetName = currentTargetDisplayName();
+ Common::String itemName;
if (_view->_activeInventoryItem != nullptr) {
- const Common::String itemName = getObjectDisplayName(_view->_activeInventoryItem);
- if (!itemName.empty()) {
- line += " ";
- line += itemName;
- line += " with";
- }
+ itemName = getObjectHotspotName(_view->_activeInventoryItem->_index);
}
- uint16 hoverId = 0;
- const Common::Point mouse = g_system->getEventManager()->getMousePos();
- if (!isPointInUI(mouse)) {
- hoverId = _view->getHitObjectID(mouse);
- if (hoverId == 0)
- hoverId = g_engine->getHotspotAtPoint(mouse);
- }
- if (hoverId >= 0x400) {
- const uint16 objIndex = hoverId - 0x400;
- if (objIndex < GameObjects::instance()._objectNames.size() &&
- !GameObjects::instance()._objectNames[objIndex].empty()) {
- line += " ";
- line += GameObjects::instance()._objectNames[objIndex];
+ if (mode == Script::MouseMode::UseInventory && !itemName.empty()) {
+ if (!targetName.empty())
+ return Common::String::format(uiText("Benutze %s mit %s").c_str(), itemName.c_str(), targetName.c_str());
+ return Common::String::format(uiText("Benutze %s").c_str(), itemName.c_str());
+ }
+
+ if (!targetName.empty()) {
+ switch (mode) {
+ case Script::MouseMode::Look:
+ return Common::String::format(uiText("Schaue an %s").c_str(), targetName.c_str());
+ case Script::MouseMode::Talk:
+ return Common::String::format(uiText("Rede mit %s").c_str(), targetName.c_str());
+ case Script::MouseMode::Use:
+ return Common::String::format(uiText("Benutze %s").c_str(), targetName.c_str());
+ default:
+ return Common::String::format(uiText("Gehe zu %s").c_str(), targetName.c_str());
}
- } else if (_view->_hoverHotspotId != 0 &&
- _view->_hoverHotspotId < GameObjects::instance()._objectNames.size() &&
- !GameObjects::instance()._objectNames[_view->_hoverHotspotId].empty()) {
- line += " ";
- line += GameObjects::instance()._objectNames[_view->_hoverHotspotId];
}
- return line;
+
+ return translatedVerbLabel(mode);
}
const HudButton *ActionBar::findHudButtonAt(const Common::Point &pos, int *outIndex) const {
@@ -664,7 +662,7 @@ void ActionBar::drawNative(Graphics::ManagedSurface &s) {
const GlyphData *font = g_engine->numGlyphs ? g_engine->_glyphs : panelFont;
const uint16 fontCount = g_engine->numGlyphs ? g_engine->numGlyphs : panelFontCount;
if (fontCount != 0) {
- Common::String sentence = buildNativeSentenceLine();
+ Common::String sentence = buildSentenceLine();
if (!sentence.empty()) {
const uint16 maxW = (uint16)(g_engine->screenWidth() - 16);
while (sentence.size() > 1) {
@@ -775,7 +773,7 @@ bool ActionBar::handleClickNative(const Common::Point &pos) {
if (g_engine->_optionsSubMode == 2) {
g_engine->loadGameState(slot);
} else if (g_engine->_optionsSubMode == 1) {
- Common::String name = Common::String::format("Save %d", slot + 1);
+ Common::String name = Common::String::format(uiText("Spielstand %d").c_str(), slot + 1);
if (row < g_engine->_saveSlotNames.size() &&
!g_engine->_saveSlotNames[row].empty() &&
!g_engine->_saveSlotNames[row].hasPrefix("---"))
@@ -848,9 +846,7 @@ bool ActionBar::handleClickNative(const Common::Point &pos) {
g_engine->softRestart();
return true;
} else if (id == 0x21) {
- ::GUI::MessageDialog quitDialog(
- Common::U32String("Quit the game?"),
- Common::U32String("Quit"), Common::U32String("Cancel"));
+ ::GUI::MessageDialog quitDialog(_("Quit the game?"), _("Quit"), _("Cancel"));
if (quitDialog.runModal() == ::GUI::kMessageOK)
Engine::quitGame();
} else if (id == 0x14 || id == 0x16) {
diff --git a/engines/macs2/actionbar.h b/engines/macs2/actionbar.h
index 9b6a52577be..242b6995d36 100644
--- a/engines/macs2/actionbar.h
+++ b/engines/macs2/actionbar.h
@@ -108,7 +108,9 @@ private:
bool handleClickNative(const Common::Point &pos);
void handleMouseMoveNative(const Common::Point &pos);
void refreshSaveSlotNames();
- Common::String buildNativeSentenceLine() const;
+ Common::String translatedVerbLabel(Script::MouseMode mode) const;
+ Common::String currentTargetDisplayName() const;
+ Common::String buildSentenceLine() const;
const HudButton *findHudButtonAt(const Common::Point &pos, int *outIndex = nullptr) const;
View1 *_view;
diff --git a/engines/macs2/gameobjects.h b/engines/macs2/gameobjects.h
index 92f91956833..0a6f9ac3666 100644
--- a/engines/macs2/gameobjects.h
+++ b/engines/macs2/gameobjects.h
@@ -225,11 +225,6 @@ public:
class GameObjects : public Common::Singleton<GameObjects> {
public:
- // Maximum of 200h objects
- // How to address them in the original code:
- // mov di,[bp+6h]
- // shl di, 2h;
- // les di, [di + 77Ch]
Common::Array<GameObject *> _objects;
Common::Array<Common::String> _objectNames;
diff --git a/engines/macs2/macs2.cpp b/engines/macs2/macs2.cpp
index ef068af1524..0341b8d59a2 100644
--- a/engines/macs2/macs2.cpp
+++ b/engines/macs2/macs2.cpp
@@ -54,6 +54,34 @@ namespace Macs2 {
static constexpr const char *kGameSpeedModeConfigKey = "macs2_game_speed_mode";
+namespace {
+
+static constexpr uint16 kMaxSceneObjects = 0x200;
+
+Common::U32String hotspotLabelToU32(const Common::String &name) {
+ if (name.empty())
+ return Common::U32String();
+ return Common::U32String(name.c_str(), Common::kDos850);
+}
+
+bool isMapModeActive() {
+ if (g_events == nullptr)
+ return false;
+ View1 *view = (View1 *)g_events->findView("View1");
+ return view != nullptr && view->_currentMode == ViewMode::VM_HELP;
+}
+
+Common::Point getSceneObjectHotspotPosition(View1 *view, GameObject *obj) {
+ if (view != nullptr) {
+ Character *character = view->getCharacterByIndex(obj->_index);
+ if (character != nullptr && !character->_markedForDeletion)
+ return character->getPosition();
+ }
+ return obj->_position;
+}
+
+} // namespace
+
void resetCharacterWalkPath(Character *character) {
if (character == nullptr || character->_gameObject == nullptr)
return;
@@ -2399,10 +2427,6 @@ uint16 Macs2Engine::getHotspotAtPoint(const Common::Point &p) {
return 0;
}
-namespace {
-
-static constexpr uint16 kMaxSceneObjects = 0x200;
-
Common::String getObjectHotspotName(uint16 objectIndex) {
const GameObjects &objects = GameObjects::instance();
if (objectIndex > 0 && objectIndex < objects._objectNames.size() && !objects._objectNames[objectIndex].empty()) {
@@ -2413,30 +2437,14 @@ Common::String getObjectHotspotName(uint16 objectIndex) {
return Common::String();
}
-Common::U32String hotspotLabelToU32(const Common::String &name) {
- if (name.empty())
- return Common::U32String();
- return Common::U32String(name.c_str(), Common::kDos850);
-}
-
-bool isMapModeActive() {
- if (g_events == nullptr)
- return false;
- View1 *view = (View1 *)g_events->findView("View1");
- return view != nullptr && view->_currentMode == ViewMode::VM_HELP;
-}
-
-Common::Point getSceneObjectHotspotPosition(View1 *view, GameObject *obj) {
- if (view != nullptr) {
- Character *character = view->getCharacterByIndex(obj->_index);
- if (character != nullptr && !character->_markedForDeletion)
- return character->getPosition();
- }
- return obj->_position;
+Common::String lookupInteractionDisplayName(uint16 interactionId) {
+ if (interactionId >= 0x800)
+ return lookupSceneHotspotName((uint16)Scenes::instance()._currentSceneIndex, (uint16)(interactionId - 0x800));
+ if (interactionId >= 0x400)
+ return getObjectHotspotName((uint16)(interactionId - 0x400));
+ return Common::String();
}
-} // namespace
-
void Macs2Engine::rebuildHotspotSnapshot() const {
_hotspotSnapshot.currentSceneIndex = Scenes::instance()._currentSceneIndex;
_hotspotSnapshot.numHotspots = _numHotspots;
@@ -2579,7 +2587,7 @@ void Macs2Engine::getHotspotPositions(Common::Array<Graphics::HotspotInfo> &hots
if (isCharacter && GameObjects::isNpcIndex(entry.index))
hotspotType = Graphics::kHotspotNPC;
- const Common::String name = getObjectHotspotName(entry.index);
+ const Common::String &name = getObjectHotspotName(entry.index);
hotspots.push_back(Graphics::HotspotInfo(screenPos, hotspotLabelToU32(name), hotspotType));
}
}
@@ -2732,7 +2740,7 @@ void Macs2Engine::loadTranslation() {
}
uint16 version = f->readUint16LE();
- if (version != 1 && version != 2) {
+ if (version < 1 || version > 3) {
warning("Unsupported macs2_translation.dat version %u", version);
delete f;
return;
@@ -2741,8 +2749,11 @@ void Macs2Engine::loadTranslation() {
uint16 numScenes = f->readUint16LE();
uint16 numObjects = f->readUint16LE();
uint16 numHotspotLabels = 0;
+ uint16 numUiLabels = 0;
if (version >= 2)
numHotspotLabels = f->readUint16LE();
+ if (version >= 3)
+ numUiLabels = f->readUint16LE();
// Read index tables
struct IndexEntry {
@@ -2797,25 +2808,31 @@ void Macs2Engine::loadTranslation() {
_objectTranslations[objectIndex[i].id] = entry;
}
+ auto readLabelMap = [f](uint16 count, Common::HashMap<Common::String, Common::String> &out) {
+ for (uint16 i = 0; i < count; i++) {
+ uint16 keyLen = f->readUint16LE();
+ Common::String key;
+ for (uint16 k = 0; k < keyLen; k++)
+ key += (char)f->readByte();
+ uint16 valLen = f->readUint16LE();
+ Common::String val;
+ for (uint16 k = 0; k < valLen; k++)
+ val += (char)f->readByte();
+ if (!key.empty() && !val.empty())
+ out[key] = val;
+ }
+ };
+
_hotspotLabelTranslations.clear();
- if (numHotspotLabels > 0)
+ _uiLabelTranslations.clear();
+ if (numHotspotLabels > 0 || numUiLabels > 0)
f->seek(stringDataEnd);
- for (uint16 i = 0; i < numHotspotLabels; i++) {
- uint16 keyLen = f->readUint16LE();
- Common::String key;
- for (uint16 k = 0; k < keyLen; k++)
- key += (char)f->readByte();
- uint16 valLen = f->readUint16LE();
- Common::String val;
- for (uint16 k = 0; k < valLen; k++)
- val += (char)f->readByte();
- if (!key.empty() && !val.empty())
- _hotspotLabelTranslations[key] = val;
- }
+ readLabelMap(numHotspotLabels, _hotspotLabelTranslations);
+ readLabelMap(numUiLabels, _uiLabelTranslations);
delete f;
- debug("Loaded macs2_translation.dat: %u scenes, %u objects, %u overlay labels",
- numScenes, numObjects, (uint)_hotspotLabelTranslations.size());
+ debug("Loaded macs2_translation.dat: %u scenes, %u objects, %u overlay labels, %u UI labels",
+ numScenes, numObjects, (uint)_hotspotLabelTranslations.size(), (uint)_uiLabelTranslations.size());
}
Common::String Macs2Engine::translateHotspotLabel(const Common::String &cp850Name) const {
@@ -2827,6 +2844,15 @@ Common::String Macs2Engine::translateHotspotLabel(const Common::String &cp850Nam
return cp850Name;
}
+Common::String Macs2Engine::translateUiLabel(const Common::String &source) const {
+ if (source.empty() || !(getFeatures() & GF_TRANSLATED))
+ return source;
+ auto it = _uiLabelTranslations.find(source);
+ if (it != _uiLabelTranslations.end())
+ return it->_value;
+ return source;
+}
+
Common::StringArray Macs2Engine::decodeStrings(Common::MemoryReadStream *stream, int offset, int numStrings, int sceneId, int objectId) {
Common::StringArray result(numStrings);
stream->seek(offset);
diff --git a/engines/macs2/macs2.h b/engines/macs2/macs2.h
index 2cf882fe2d8..d4dfc5c1fac 100644
--- a/engines/macs2/macs2.h
+++ b/engines/macs2/macs2.h
@@ -733,8 +733,11 @@ public:
Common::HashMap<uint32, TranslationEntry> _sceneTranslations;
Common::HashMap<uint32, TranslationEntry> _objectTranslations;
Common::HashMap<Common::String, Common::String> _hotspotLabelTranslations;
+ Common::HashMap<Common::String, Common::String> _uiLabelTranslations;
void loadTranslation();
Common::String translateHotspotLabel(const Common::String &cp850Name) const;
+ /** Action-bar / HUD chrome; German source key, same lookup rules as hotspot labels. */
+ Common::String translateUiLabel(const Common::String &source) const;
// Compute the sequential string index at the given byte offset in a string blob
int computeStringIndex(Common::MemoryReadStream *stream, int targetOffset);
@@ -926,6 +929,9 @@ public:
extern Macs2Engine *g_engine;
#define SHOULD_QUIT ::Macs2::g_engine->shouldQuit()
+Common::String getObjectHotspotName(uint16 objectIndex);
+/** Display name for a hit id: 0x400+object or 0x800+scene hotspot. */
+Common::String lookupInteractionDisplayName(uint16 interactionId);
} // End of namespace Macs2
diff --git a/engines/macs2/view1.cpp b/engines/macs2/view1.cpp
index 38a0607f30e..896e5000798 100644
--- a/engines/macs2/view1.cpp
+++ b/engines/macs2/view1.cpp
@@ -2074,14 +2074,9 @@ bool View1::msgMouseMove(const MouseMoveMessage &msg) {
uint16 index = getHitObjectID(msg._pos);
if (index == 0)
index = g_engine->getHotspotAtPoint(msg._pos);
- if (index != 0 && index >= 0x400) {
- const uint16 objIndex = index - 0x400;
- if (objIndex < GameObjects::instance()._objectNames.size()) {
- const Common::String &name = GameObjects::instance()._objectNames[objIndex];
- if (!name.empty())
- _actionBar->updateSentenceLine(name);
- }
- }
+ const Common::String name = lookupInteractionDisplayName(index);
+ if (!name.empty())
+ _actionBar->updateSentenceLine(name);
}
}
Commit: ada2aa0d38394775d27d08519358ac7ee2132142
https://github.com/scummvm/scummvm/commit/ada2aa0d38394775d27d08519358ac7ee2132142
Author: Martin Gerhardy (martin.gerhardy at gmail.com)
Date: 2026-08-31T21:14:09+02:00
Commit Message:
MACS2: reset version back to 1 - the game was never officially supported
Changed paths:
engines/macs2/macs2.cpp
diff --git a/engines/macs2/macs2.cpp b/engines/macs2/macs2.cpp
index 0341b8d59a2..203c4a89d08 100644
--- a/engines/macs2/macs2.cpp
+++ b/engines/macs2/macs2.cpp
@@ -2740,7 +2740,7 @@ void Macs2Engine::loadTranslation() {
}
uint16 version = f->readUint16LE();
- if (version < 1 || version > 3) {
+ if (version != 1) {
warning("Unsupported macs2_translation.dat version %u", version);
delete f;
return;
@@ -2748,12 +2748,8 @@ void Macs2Engine::loadTranslation() {
uint16 numScenes = f->readUint16LE();
uint16 numObjects = f->readUint16LE();
- uint16 numHotspotLabels = 0;
- uint16 numUiLabels = 0;
- if (version >= 2)
- numHotspotLabels = f->readUint16LE();
- if (version >= 3)
- numUiLabels = f->readUint16LE();
+ uint16 numHotspotLabels = f->readUint16LE();
+ uint16 numUiLabels = f->readUint16LE();
// Read index tables
struct IndexEntry {
Commit: 59e2ff7b3ca403e8a5776d2511df00b12c5ebbee
https://github.com/scummvm/scummvm/commit/59e2ff7b3ca403e8a5776d2511df00b12c5ebbee
Author: Martin Gerhardy (martin.gerhardy at gmail.com)
Date: 2026-08-31T21:14:09+02:00
Commit Message:
MACS2: several ux enhancement fixes for the custom action bar
Changed paths:
engines/macs2/actionbar.cpp
engines/macs2/actionbar.h
engines/macs2/macs2.cpp
engines/macs2/macs2.h
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 d362b19f0fa..63158f7cc90 100644
--- a/engines/macs2/actionbar.cpp
+++ b/engines/macs2/actionbar.cpp
@@ -46,10 +46,10 @@ Common::String uiText(const char *source) {
} // namespace
const ActionBar::VerbDef ActionBar::kVerbs[4] = {
- {"Gehen", Script::MouseMode::Walk},
- {"Schauen", Script::MouseMode::Look},
- {"Benutzen", Script::MouseMode::Use},
- {"Reden", Script::MouseMode::Talk}
+ {"Gehe", Script::MouseMode::Walk},
+ {"Schaue", Script::MouseMode::Look},
+ {"Benutze", Script::MouseMode::Use},
+ {"Rede", Script::MouseMode::Talk}
};
ActionBar::ActionBar(View1 *view)
@@ -77,15 +77,25 @@ void ActionBar::syncInventory() {
void ActionBar::rebuildProtagonistItems() {
_protagonistItems.clear();
+ const uint16 invScene = Scenes::instance()._currentActorIndex + 0x400;
- if (_view->isInventorySourceProtagonist()) {
- _protagonistItems = _view->_inventoryItems;
- return;
+ // Keep the engine list order, then append any inventory objects it missed
+ // (pickup / moveObject can update sceneIndex before _inventoryItems).
+ for (GameObject *obj : _view->_inventoryItems) {
+ if (obj && obj->_sceneIndex == invScene)
+ _protagonistItems.push_back(obj);
}
-
- const uint16 invScene = Scenes::instance()._currentActorIndex + 0x400;
for (GameObject *obj : GameObjects::instance()._objects) {
- if (obj && obj->_sceneIndex == invScene)
+ if (!obj || obj->_sceneIndex != invScene)
+ continue;
+ bool listed = false;
+ for (GameObject *listedObj : _protagonistItems) {
+ if (listedObj == obj) {
+ listed = true;
+ break;
+ }
+ }
+ if (!listed)
_protagonistItems.push_back(obj);
}
}
@@ -166,16 +176,23 @@ 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::drawSentenceLine(Graphics::ManagedSurface &s) {
Common::String sentence = buildSentenceLine();
if (sentence.empty())
return;
- const bool usePanelFont = g_engine->numPanelGlyphs > 0;
- const GlyphData *font = usePanelFont ? g_engine->_panelGlyphs : g_engine->_glyphs;
- const uint16 fontCount = usePanelFont ? g_engine->numPanelGlyphs : g_engine->numGlyphs;
- const int glyphH = usePanelFont ? g_engine->maxPanelGlyphHeight : g_engine->maxGlyphHeight;
- if (usePanelFont)
+ const GlyphData *font = nullptr;
+ uint16 fontCount = 0;
+ int glyphH = 0;
+ actionBarFont(font, fontCount, glyphH);
+ if (g_engine->numPanelGlyphs > 0)
sentence.toUppercase();
const int textY = kUITop + MAX(0, (kSentenceH - glyphH) / 2);
const int textX = MAX(0, (kScreenWidth - _view->measureStringWithFont(sentence, font, fontCount)) / 2);
@@ -183,6 +200,11 @@ void ActionBar::drawSentenceLine(Graphics::ManagedSurface &s) {
}
void ActionBar::drawVerbBar(Graphics::ManagedSurface &s) {
+ const GlyphData *font = nullptr;
+ uint16 fontCount = 0;
+ int glyphH = 0;
+ actionBarFont(font, fontCount, glyphH);
+
for (int i = 0; i < ARRAYSIZE(kVerbs); i++) {
const Common::Rect r = getVerbRect(i);
const bool isActive = (i == _activeVerbIndex);
@@ -190,10 +212,13 @@ void ActionBar::drawVerbBar(Graphics::ManagedSurface &s) {
drawUIButton(r, isActive || isHovered, s);
- const Common::String label = uiText(kVerbs[i].label);
- const int textX = r.left + (r.width() - (int)label.size() * 6) / 2;
- const int textY = r.top + (r.height() - (int)g_engine->maxGlyphHeight) / 2;
- _view->renderStringTo(textX, textY, label, s);
+ Common::String label = uiText(kVerbs[i].label);
+ if (g_engine->numPanelGlyphs > 0)
+ label.toUppercase();
+ const int textW = _view->measureStringWithFont(label, font, fontCount);
+ const int textX = r.left + (r.width() - textW) / 2;
+ const int textY = r.top + (r.height() - glyphH) / 2;
+ _view->renderStringWithFontTo(textX, textY, label, font, fontCount, s);
}
}
@@ -209,7 +234,13 @@ int ActionBar::getScrollButtonWidth() const {
}
int ActionBar::getInvArrowX() const {
- return kInvX;
+ return kBarPadX + kVerbCols * kVerbW + kVerbInvGap;
+}
+
+int ActionBar::getInvItemWidth() const {
+ const int scrollW = getScrollButtonWidth();
+ const int available = kScreenWidth - getInvArrowX() - kBarPadX - 2 * scrollW;
+ return MAX(24, available / kInvCols);
}
void ActionBar::drawScrollButton(Graphics::ManagedSurface &s, const Common::Rect &rect,
@@ -437,7 +468,7 @@ void ActionBar::clearSentenceObject() {
Common::Rect ActionBar::getVerbRect(int index) const {
const int col = index % kVerbCols;
const int row = index / kVerbCols;
- const int x = col * kVerbW;
+ const int x = kBarPadX + col * kVerbW;
const int y = kVerbY + row * kVerbH;
return Common::Rect(x, y, x + kVerbW, y + kVerbH);
}
@@ -446,9 +477,10 @@ Common::Rect ActionBar::getInvItemRect(int index) const {
const int col = index % kInvCols;
const int row = index / kInvCols;
const int scrollW = getScrollButtonWidth();
- const int x = getInvArrowX() + scrollW + col * kInvItemW;
+ const int itemW = getInvItemWidth();
+ const int x = getInvArrowX() + scrollW + col * itemW;
const int y = kVerbY + row * kInvItemH;
- return Common::Rect(x, y, x + kInvItemW, y + kInvItemH);
+ return Common::Rect(x, y, x + itemW, y + kInvItemH);
}
Common::Rect ActionBar::getInvScrollLeftRect() const {
@@ -465,7 +497,7 @@ bool ActionBar::isPointInInventoryStrip(const Common::Point &pos) const {
Common::Rect ActionBar::getInvScrollRightRect() const {
const int scrollW = getScrollButtonWidth();
- const int x = getInvArrowX() + scrollW + kInvCols * kInvItemW;
+ const int x = getInvArrowX() + scrollW + kInvCols * getInvItemWidth();
return Common::Rect(x, kVerbY, x + scrollW, kVerbY + kVerbH * kVerbRows);
}
@@ -493,7 +525,7 @@ Common::String ActionBar::translatedVerbLabel(Script::MouseMode mode) const {
return uiText(kVerbs[i].label);
}
}
- return uiText("Gehen");
+ return uiText("Gehe");
}
Common::String ActionBar::currentTargetDisplayName() const {
@@ -504,6 +536,14 @@ Common::String ActionBar::currentTargetDisplayName() const {
if (isPointInUI(mouse))
return Common::String();
+ if (_view->_uiPanelState == View1::kUiPanelContainerInventory ||
+ _view->_uiPanelState == View1::kUiPanelInventory) {
+ GameObject *hovered = _view->getClickedInventoryItem(mouse);
+ if (hovered != nullptr)
+ return getObjectHotspotName(hovered->_index);
+ return Common::String();
+ }
+
uint16 hoverId = _view->getHitObjectID(mouse);
if (hoverId == 0)
hoverId = g_engine->getHotspotAtPoint(mouse);
diff --git a/engines/macs2/actionbar.h b/engines/macs2/actionbar.h
index 242b6995d36..600df652fee 100644
--- a/engines/macs2/actionbar.h
+++ b/engines/macs2/actionbar.h
@@ -33,6 +33,7 @@ namespace Macs2 {
class View1;
class GameObject;
struct AnimFrame;
+struct GlyphData;
struct HudButton;
/**
@@ -63,12 +64,12 @@ private:
static constexpr int kUITop = kGameHeight;
static constexpr int kSentenceY = kGameHeight;
static constexpr int kVerbY = kGameHeight + kSentenceH;
- static constexpr int kVerbW = 64;
+ static constexpr int kBarPadX = 6;
+ static constexpr int kVerbW = 78;
static constexpr int kVerbH = 25;
static constexpr int kVerbCols = 2;
static constexpr int kVerbRows = 2;
- static constexpr int kInvX = 128;
- static constexpr int kInvItemW = 34;
+ static constexpr int kVerbInvGap = 4;
static constexpr int kInvItemH = 25;
static constexpr int kInvIconInset = 1;
static constexpr int kInvCols = 4;
@@ -84,6 +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 drawSentenceLine(Graphics::ManagedSurface &s);
void drawVerbBar(Graphics::ManagedSurface &s);
void drawInventoryStrip(Graphics::ManagedSurface &s);
@@ -93,6 +95,7 @@ private:
int getScrollButtonWidth() const;
int getInvArrowX() const;
+ int getInvItemWidth() const;
Common::Array<GameObject *> getProtagonistItems() const;
diff --git a/engines/macs2/macs2.cpp b/engines/macs2/macs2.cpp
index 203c4a89d08..11df6a165bd 100644
--- a/engines/macs2/macs2.cpp
+++ b/engines/macs2/macs2.cpp
@@ -1277,6 +1277,10 @@ void Macs2Engine::changeScene(uint32 newSceneIndex, bool executeScript) {
if (!loadSceneGraphics(newSceneIndex))
error("changeScene(): Failed to load scene graphics for scene %u", newSceneIndex);
+ _menuMode = 1;
+ _optionsSubMode = 0;
+ _bottomHudVisible = true;
+
View1 *currentView = (View1 *)findView("View1");
if (currentView != nullptr) {
// Do not push _pal here: scriptChangeScene fades the previous
@@ -1399,13 +1403,12 @@ void Macs2Engine::changeScene(uint32 newSceneIndex, bool executeScript) {
if (!loadSceneGraphics(newSceneIndex))
error("changeScene(): Failed to load scene graphics for scene %u", newSceneIndex);
- // V2 starts with the main DisplayMenu bar visible; scene
- // scripts (e.g. world map / overview map) may hideActionBar during isSceneInit
- if (isV2()) {
- _menuMode = 1;
- _optionsSubMode = 0;
- _bottomHudVisible = true;
- }
+ // Scene change starts with the main HUD shown. v2 scripts may hide it
+ // during init (overview map). v1 has no hide/show opcodes; kEnhUIUX uses
+ // the same flag so a scene change restores the strip.
+ _menuMode = 1;
+ _optionsSubMode = 0;
+ _bottomHudVisible = true;
// Refresh characters
View1 *currentView = (View1 *)findView("View1");
@@ -2306,6 +2309,7 @@ void Macs2Engine::nextCursorMode() {
setCursorMode(Script::MouseMode::Use);
break;
case Script::MouseMode::Use:
+ case Script::MouseMode::UseInventory:
setCursorMode(Script::MouseMode::Walk);
break;
default:
@@ -2315,15 +2319,22 @@ void Macs2Engine::nextCursorMode() {
}
void Macs2Engine::setBottomHudVisible(bool visible) {
- _bottomHudVisible = visible;
+ // hide/show opcodes toggle menuMode (0 vs 1). Cursor mode alone never hides
+ // the HUD. Native skin restores the cursor saved on hide.
if (hasNativeHudAssets()) {
if (visible) {
- if (_menuMode == 0)
+ if (_menuMode == 0) {
_menuMode = 1;
+ if (_scriptExecutor)
+ setCursorMode(_savedMenuCursorMode);
+ }
} else {
+ if (_menuMode == 1 && _scriptExecutor)
+ _savedMenuCursorMode = _scriptExecutor->_cursorMode;
_menuMode = 0;
}
}
+ _bottomHudVisible = visible;
}
void Macs2Engine::setCursorMode(Script::MouseMode newMode) {
diff --git a/engines/macs2/macs2.h b/engines/macs2/macs2.h
index d4dfc5c1fac..0d98ebe4d04 100644
--- a/engines/macs2/macs2.h
+++ b/engines/macs2/macs2.h
@@ -776,8 +776,9 @@ public:
/**
* Bottom HUD / action-bar visibility (dialect-neutral).
- * Driven by showActionBar / hideActionBar; Scumm verb strip and native
- * HUDs both respect this flag. Native skin also maps visible <-> menuMode.
+ * hide/show opcodes toggle menuMode (0 vs 1) and restore the cursor saved
+ * on hide. The Scumm kEnhUIUX strip uses the same flag; cursor mode alone
+ * never hides the bar.
*/
bool isBottomHudVisible() const { return _bottomHudVisible; }
void setBottomHudVisible(bool visible);
diff --git a/engines/macs2/scriptexecutor.cpp b/engines/macs2/scriptexecutor.cpp
index 741b9afb92b..f80068e24fc 100644
--- a/engines/macs2/scriptexecutor.cpp
+++ b/engines/macs2/scriptexecutor.cpp
@@ -1115,6 +1115,8 @@ OpcodeResult Script::ScriptExecutor::scriptMoveObject() {
}
}
}
+ if (currentView->_inventorySource != nullptr)
+ currentView->setInventorySource(currentView->_inventorySource);
// Check from sortObjectsByDepth (1008:0da6): if the moved object is the active
// inventory item cursor, clear it and reset cursor mode from UseInventory to Use.
@@ -3830,19 +3832,11 @@ OpcodeResult ScriptExecutor::scriptShowActionBar() {
if (currentView == nullptr)
return OpcodeResult::Continue;
- if (_engine->hasNativeHudAssets()) {
- // Dialect-v2: if MenuMode==0 -> MenuMode=1, restore saved cursor, redraw.
- if (_engine->_menuMode == 0) {
- _engine->setBottomHudVisible(true);
- _engine->setCursorMode(_engine->_savedMenuCursorMode);
- currentView->updateCursor();
- currentView->redraw();
- }
- return OpcodeResult::Continue;
- }
-
- if (currentView->hasPersistentActionBar()) {
+ // v2 opcode 0x65. v1 tables end at waitForAdlib; kEnhUIUX uses the same
+ // setBottomHudVisible switch (menuMode 0 -> 1 + cursor restore).
+ if (_engine->hasNativeHudAssets() || currentView->hasPersistentActionBar()) {
_engine->setBottomHudVisible(true);
+ currentView->updateCursor();
currentView->redraw();
return OpcodeResult::Continue;
}
@@ -3858,16 +3852,8 @@ OpcodeResult ScriptExecutor::scriptHideActionBar() {
if (currentView == nullptr)
return OpcodeResult::Continue;
- if (_engine->hasNativeHudAssets()) {
- // Dialect-v2: save cursor when leaving main HUD, set MenuMode=0.
- if (_engine->_menuMode == 1)
- _engine->_savedMenuCursorMode = _cursorMode;
- _engine->setBottomHudVisible(false);
- currentView->redraw();
- return OpcodeResult::Continue;
- }
-
- if (currentView->hasPersistentActionBar()) {
+ // v2 opcode 0x66. Same flag as show; classic v1 popup is a separate path.
+ if (_engine->hasNativeHudAssets() || currentView->hasPersistentActionBar()) {
_engine->setBottomHudVisible(false);
currentView->redraw();
return OpcodeResult::Continue;
@@ -4307,7 +4293,10 @@ void ScriptExecutor::run(bool firstRun) {
}
_state = ExecutorState::Executing;
step();
- syncScriptIsExecutingFlag();
+ // step() rewinds the scene script to 0 when it goes Idle. A pos<end check
+ // would then mark g_wScriptIsExecuting again and steal every scene click.
+ if (_state != ExecutorState::Idle)
+ syncScriptIsExecutingFlag();
}
uint32 ScriptExecutor::effectiveScriptEnd() const {
diff --git a/engines/macs2/view1.cpp b/engines/macs2/view1.cpp
index 896e5000798..19a2b3c4761 100644
--- a/engines/macs2/view1.cpp
+++ b/engines/macs2/view1.cpp
@@ -171,7 +171,6 @@ void View1::ensureActionBar() {
if (_innerBounds.width() != sw || _innerBounds.height() != sh) {
_bounds = Common::Rect(0, 0, sw, sh);
_innerBounds = _bounds;
- ::initGraphics(sw, sh);
}
}
@@ -194,13 +193,12 @@ bool View1::shouldShowActionBar() const {
return false;
if (_currentMode == ViewMode::VM_HELP)
return false;
- if (_isShowingTextBox || _isShowingDialoguePanel)
- return false;
- // Keep the strip visible during container inventory so items can be taken
- // into the protagonist inventory (Drop/Take is suppressed on that panel).
if (_uiPanelState == kUiPanelSaveLoad)
return false;
- if (g_engine->_scriptExecutor->_cursorMode == Script::MouseMode::Disabled)
+ // Scumm strip only: native HUD stays up during speech/choices (DisplayMenu
+ // is independent of AddText / TalkTo; mode 4 draws choices in the panel).
+ if (!g_engine->hasNativeHudAssets() &&
+ (_isShowingDialoguePanel || _isDialogueChoiceInputActive || _isShowingTextBox))
return false;
// Use the actor object table directly; Character lookup can lag behind scene changes.
@@ -259,6 +257,7 @@ void View1::openInventory(GameObject *newInventorySource) {
if (hasPersistentActionBar() && newInventorySource->_index == Scenes::instance()._currentActorIndex) {
if (_actionBar)
_actionBar->syncInventory();
+ redraw();
return;
}
@@ -363,7 +362,8 @@ void View1::refreshProtagonistInventoryAfterLoad(uint16 actorIndex) {
}
bool View1::isInventorySourceProtagonist() const {
- return _inventorySource->_index == 1;
+ return _inventorySource &&
+ _inventorySource->_index == Scenes::instance()._currentActorIndex;
}
void View1::transferInventoryItem(GameObject *item, GameObject *targetContainer) {
@@ -1124,7 +1124,7 @@ void View1::transferPickupTarget(GameObject *targetObject) {
}
}
- if (_inventorySource != nullptr && _inventorySource->_index == actorIndex) {
+ if (_inventorySource == nullptr || _inventorySource->_index == actorIndex) {
bool alreadyListed = false;
for (const GameObject *item : _inventoryItems) {
if (item->_index == targetObject->_index) {
@@ -1144,6 +1144,7 @@ void View1::transferPickupTarget(GameObject *targetObject) {
}
}
+
if (_activeInventoryItem != nullptr && _activeInventoryItem->_index == targetObject->_index) {
_activeInventoryItem = nullptr;
if (executor->_cursorMode == Script::MouseMode::UseInventory) {
@@ -1701,6 +1702,41 @@ bool View1::handleHelpClick(const MouseDownMessage &msg) {
return true;
}
+void View1::walkToScreenPosition(const Common::Point &pos) {
+ Character *protagonist = getCharacterByIndex(Scenes::instance()._currentActorIndex);
+ if (protagonist == nullptr) {
+ debugC(kDebugScript, "Ignoring walk click without active actor character in the scene");
+ return;
+ }
+
+ Common::Point target = pos;
+ Common::Point charPos = protagonist->getPosition();
+
+ int16 targetY = target.y;
+ int16 targetX = target.x;
+ g_engine->snapToWalkablePosition(&targetY, &targetX, charPos.y, charPos.x);
+ target.x = targetX;
+ target.y = targetY;
+
+ 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 = abs(protagonist->_targetPosition.x - charPos.x);
+ protagonist->_stepDeltaY = abs(protagonist->_targetPosition.y - charPos.y);
+ protagonist->_stepError = 0;
+ protagonist->_stepDirectionSet = false;
+ g_engine->_scriptExecutor->saveWalkRuntime(protagonist, protagonist->_gameObject);
+}
+
bool View1::handleInput(const MouseDownMessage &msg) {
if (msg._button == MouseMessage::MB_LEFT) {
// Help mode (depth-based scene preview) from handleInput (1008:e8bf).
@@ -1791,89 +1827,46 @@ bool View1::handleInput(const MouseDownMessage &msg) {
return true;
}
- if (g_engine->_scriptExecutor->_cursorMode == Script::MouseMode::Walk) {
- if (shouldShowActionBar() && msg._pos.y >= actionBarTopY())
- return true;
-
- Character *protagonist = getCharacterByIndex(Scenes::instance()._currentActorIndex);
- if (protagonist == nullptr) {
- debugC(kDebugScript, "Ignoring walk click without active actor character in the scene");
- return true;
- }
-
- Common::Point target = msg._pos;
- Common::Point charPos = protagonist->getPosition();
-
- // Snap target to nearest walkable position (1008:9be2)
- int16 targetY = target.y;
- int16 targetX = target.x;
- g_engine->snapToWalkablePosition(&targetY, &targetX, charPos.y, charPos.x);
- target.x = targetX;
- target.y = targetY;
-
- // handleInput (1008:e8bf): isPathWalkable(targetY, targetX, charY, charX).
- // calculatePath only when direct line fails AND target tile is walkable (< 0xC8).
- 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 = abs(protagonist->_targetPosition.x - charPos.x);
- protagonist->_stepDeltaY = abs(protagonist->_targetPosition.y - charPos.y);
- protagonist->_stepError = 0;
- protagonist->_stepDirectionSet = false;
- g_engine->_scriptExecutor->saveWalkRuntime(protagonist, protagonist->_gameObject);
- return true;
- }
-
- // Check if we hit something
if (shouldShowActionBar() && msg._pos.y >= actionBarTopY())
return true;
- // Original order: getHotspotAtPoint first, then drawCharactersAndHitTest overrides.
- // Our order (objects first, fallback to background) produces the same result.
- uint16 index = getHitObjectID(Common::Point(msg._pos.x, msg._pos.y));
- if (index == 0) {
- index = g_engine->getHotspotAtPoint(msg._pos);
- }
- if (index != 0) {
- debugC(kDebugScript, "*** New interaction started");
-
- // Binary (handleInput 1008:ef2d): stop character movement before interaction.
- // Sets runtime target/finalDest to current position, clears path state.
- Character *protagonist = getCharacterByIndex(Scenes::instance()._currentActorIndex);
- if (protagonist != nullptr) {
- Common::Point pos = protagonist->getPosition();
- protagonist->_targetPosition = pos;
- protagonist->_pathFinalDestination = pos;
- protagonist->_path.clear();
- protagonist->_currentPathIndex = 0;
- }
+ const Script::MouseMode mode = g_engine->_scriptExecutor->_cursorMode;
- // Binary (handleInput 1008:ef8f): if mode != 0x17, clear inventory item ID.
- // Note: the binary does NOT touch g_wInventoryActionFlag here.
- if (g_engine->_scriptExecutor->_cursorMode != Script::MouseMode::UseInventory) {
- g_engine->_scriptExecutor->_interactedInventoryItemId = 0;
- _activeInventoryItem = nullptr;
- }
+ // Walk never hit-tests; other verbs interact when a target is under the cursor.
+ // Empty-ground clicks walk so the persistent verb bar does not trap the player
+ // in Look/Use/Talk/UseInventory with no way to move.
+ if (mode != Script::MouseMode::Walk) {
+ uint16 index = getHitObjectID(Common::Point(msg._pos.x, msg._pos.y));
+ if (index == 0)
+ index = g_engine->getHotspotAtPoint(msg._pos);
+ if (index != 0) {
+ debugC(kDebugScript, "*** New interaction started");
+
+ Character *protagonist = getCharacterByIndex(Scenes::instance()._currentActorIndex);
+ if (protagonist != nullptr) {
+ Common::Point pos = protagonist->getPosition();
+ protagonist->_targetPosition = pos;
+ protagonist->_pathFinalDestination = pos;
+ protagonist->_path.clear();
+ protagonist->_currentPathIndex = 0;
+ }
- g_engine->_scriptExecutor->_interactedObjectID = index;
+ if (mode != Script::MouseMode::UseInventory) {
+ g_engine->_scriptExecutor->_interactedInventoryItemId = 0;
+ _activeInventoryItem = nullptr;
+ }
- // Binary: runScriptExecutor() - internally rewinds scene script when
- // g_wScriptIsExecuting==0 (which it is here, since we're in the
- // "not executing" branch of handleInput).
- g_engine->runScriptExecutor(false);
+ g_engine->_scriptExecutor->_interactedObjectID = index;
+ g_engine->runScriptExecutor(false);
+ g_engine->_scriptExecutor->_interactedObjectID = 0;
+ return true;
+ }
+ }
- // Binary: only g_wInteractedObjectId is cleared after runScriptExecutor.
- g_engine->_scriptExecutor->_interactedObjectID = 0;
+ if (mode == Script::MouseMode::Walk || mode == Script::MouseMode::Look ||
+ mode == Script::MouseMode::Use || mode == Script::MouseMode::Talk ||
+ mode == Script::MouseMode::UseInventory) {
+ walkToScreenPosition(msg._pos);
}
return true;
} else if (msg._button == MouseMessage::MB_RIGHT) {
@@ -2069,6 +2062,16 @@ bool View1::msgMouseMove(const MouseMoveMessage &msg) {
if (shouldShowActionBar() && _actionBar) {
if (_actionBar->isPointInUI(msg._pos)) {
_actionBar->handleMouseMove(msg._pos);
+ } else if (_uiPanelState == kUiPanelContainerInventory || _uiPanelState == kUiPanelInventory) {
+ // Inventory popup is modal: do not punch through to scene objects
+ // or areas behind the dialog.
+ _actionBar->clearSentenceObject();
+ GameObject *hovered = getClickedInventoryItem(msg._pos);
+ if (hovered != nullptr) {
+ const Common::String name = getObjectHotspotName(hovered->_index);
+ if (!name.empty())
+ _actionBar->updateSentenceLine(name);
+ }
} else if (msg._pos.y < actionBarTopY()) {
_actionBar->clearSentenceObject();
uint16 index = getHitObjectID(msg._pos);
@@ -2317,19 +2320,23 @@ void View1::draw() {
if (hasPersistentActionBar()) {
ensureActionBar();
- if (_actionBar && !_isShowingTextBox && !_isShowingDialoguePanel) {
+ if (_actionBar) {
const int sw = g_engine->screenWidth();
const int sh = g_engine->screenHeight();
Graphics::ManagedSurface fullScreen(*g_events->getScreen(), Common::Rect(0, 0, sw, sh));
if (shouldShowActionBar()) {
_actionBar->draw(fullScreen);
+ // Glyphs use setPixel and do not dirty the Screen.
+ g_events->getScreen()->addDirtyRect(Common::Rect(0, actionBarTopY(), sw, sh));
} else if (g_engine->hasNativeHudAssets() && g_engine->_menuMode == 0) {
- // hideActionBar / overview map: leave playfield pixels alone so
- // scene art (and hotspots) remain visible in the former panel band.
+ // hideActionBar / overview map: leave playfield pixels in the
+ // former panel band so scene art and hotspots stay visible.
} else {
const int top = actionBarTopY();
- if (top >= 0 && top < sh)
+ if (top >= 0 && top < sh) {
fullScreen.fillRect(Common::Rect(0, top, sw, sh), 0);
+ g_events->getScreen()->addDirtyRect(Common::Rect(0, top, sw, sh));
+ }
}
}
}
@@ -3062,13 +3069,13 @@ void View1::drawSprite(int16 x, int16 y, uint16 width, uint16 height, byte *data
if (finalX >= 0 && finalX < s.w && finalY >= 0 && finalY < s.h) {
if (clipToGameArea && finalY >= actionBarTopY())
continue;
- // Check for depth
- uint8 bgDepth = g_engine->_depthMap.getPixel(finalX, finalY);
- // Depth test: draw pixel only if depth map value < character depth
- // (verified: drawSpriteTransparent at 1010:0ed1 uses *depthMap < param_4)
- if (!useDepth || bgDepth < depth) {
- s.setPixel(x + actualX, y + currentY, val);
+ if (useDepth) {
+ if (finalX >= g_engine->_depthMap.w || finalY >= g_engine->_depthMap.h)
+ continue;
+ if (g_engine->_depthMap.getPixel(finalX, finalY) >= depth)
+ continue;
}
+ s.setPixel(x + actualX, y + currentY, val);
}
}
}
@@ -3400,6 +3407,8 @@ void View1::drawBorderSide(const Common::Point &pos, const Common::Point &size,
uint16 currentX = clippingRect.left;
uint16 currentY = clippingRect.top;
const AnimFrame &sprite = g_engine->_imageResources[31];
+ if (sprite._width == 0 || sprite._height == 0 || sprite._data.empty())
+ return;
while (currentY < clippingRect.bottom) {
while (currentX < clippingRect.right) {
diff --git a/engines/macs2/view1.h b/engines/macs2/view1.h
index b2aa1f49f68..f86ac6f35d0 100644
--- a/engines/macs2/view1.h
+++ b/engines/macs2/view1.h
@@ -269,6 +269,7 @@ private:
// if pending != 0 -> uiBackgroundRestorePending=1; flip; runScriptExecutor; pending=0.
void runInventoryPanelScriptIfPending(bool excludeCloseButton);
bool handleActionBarClick(const MouseDownMessage &msg);
+ void walkToScreenPosition(const Common::Point &pos);
// Input state machine from handleInput (1008:e8bf).
// The original game's input handler has two major branches:
Commit: 57873094c10e283e28eda5a3def059cc10496ccf
https://github.com/scummvm/scummvm/commit/57873094c10e283e28eda5a3def059cc10496ccf
Author: Martin Gerhardy (martin.gerhardy at gmail.com)
Date: 2026-08-31T21:14:09+02:00
Commit Message:
MACS2: use the dialogue font for german special chars
Changed paths:
engines/macs2/actionbar.cpp
diff --git a/engines/macs2/actionbar.cpp b/engines/macs2/actionbar.cpp
index 63158f7cc90..b69a877f1c8 100644
--- a/engines/macs2/actionbar.cpp
+++ b/engines/macs2/actionbar.cpp
@@ -188,12 +188,13 @@ void ActionBar::drawSentenceLine(Graphics::ManagedSurface &s) {
if (sentence.empty())
return;
- const GlyphData *font = nullptr;
- uint16 fontCount = 0;
- int glyphH = 0;
- actionBarFont(font, fontCount, glyphH);
- if (g_engine->numPanelGlyphs > 0)
- sentence.toUppercase();
+ // 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 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);
Commit: 9f6feb29d74268703b595eebdfa8373da301bdfe
https://github.com/scummvm/scummvm/commit/9f6feb29d74268703b595eebdfa8373da301bdfe
Author: Martin Gerhardy (martin.gerhardy at gmail.com)
Date: 2026-08-31T21:14:09+02:00
Commit Message:
MACS2: convert to enums
Changed paths:
engines/macs2/actionbar.cpp
engines/macs2/macs2.cpp
engines/macs2/macs2.h
engines/macs2/scriptexecutor.cpp
engines/macs2/view1.cpp
diff --git a/engines/macs2/actionbar.cpp b/engines/macs2/actionbar.cpp
index b69a877f1c8..d2e4bba351c 100644
--- a/engines/macs2/actionbar.cpp
+++ b/engines/macs2/actionbar.cpp
@@ -59,7 +59,7 @@ ActionBar::ActionBar(View1 *view)
bool ActionBar::isPointInUI(const Common::Point &pos) const {
if (useNativeSkin()) {
- if (g_engine->_menuMode == 0)
+ if (g_engine->_menuMode == MenuMode::Hidden)
return false;
return pos.y >= (int16)g_engine->_panelTopY;
}
@@ -591,10 +591,10 @@ const HudButton *ActionBar::findHudButtonAt(const Common::Point &pos, int *outIn
if (!isPointInUI(pos))
return nullptr;
const uint16 panelTop = g_engine->_panelTopY;
- const uint16 menuMode = g_engine->_menuMode;
+ const MenuMode menuMode = g_engine->_menuMode;
for (uint i = 0; i < g_engine->_hudButtons.size(); i++) {
const HudButton &btn = g_engine->_hudButtons[i];
- if (btn.menuId != menuMode || btn.frame._data.empty())
+ if (btn.menuId != (uint16)menuMode || btn.frame._data.empty())
continue;
const AnimFrame &hitFrame = btn.frame;
const Common::Point local(pos.x - btn.x, pos.y - (int)panelTop - btn.y);
@@ -614,11 +614,11 @@ const HudButton *ActionBar::findHudButtonAt(const Common::Point &pos, int *outIn
void ActionBar::drawNative(Graphics::ManagedSurface &s) {
if (!g_engine->hasNativeHudAssets())
return;
- if (g_engine->_menuMode == 0)
+ if (g_engine->_menuMode == MenuMode::Hidden)
return;
const uint16 panelTop = g_engine->_panelTopY;
- const uint16 menuMode = g_engine->_menuMode;
+ const MenuMode menuMode = g_engine->_menuMode;
const int megaIndex = (int)menuMode - 1;
if (megaIndex >= 0 && megaIndex < 6 && g_engine->_hudMegapicLoaded[megaIndex]) {
const Graphics::ManagedSurface &mega = g_engine->_hudMegapics[megaIndex];
@@ -628,20 +628,20 @@ void ActionBar::drawNative(Graphics::ManagedSurface &s) {
}
for (const HudButton &btn : g_engine->_hudButtons) {
- if (btn.menuId != menuMode || btn.frame._data.empty())
+ if (btn.menuId != (uint16)menuMode || btn.frame._data.empty())
continue;
const AnimFrame *frame = &btn.frame;
const Script::MouseMode mode = g_engine->_scriptExecutor->_cursorMode;
const bool selected =
- (menuMode == 1 &&
+ (menuMode == MenuMode::Main &&
((btn.buttonId == 1 && mode == Script::MouseMode::Walk) ||
(btn.buttonId == 2 && mode == Script::MouseMode::Look) ||
(btn.buttonId == 3 && mode == Script::MouseMode::Talk) ||
(btn.buttonId == 4 && (mode == Script::MouseMode::Use ||
mode == Script::MouseMode::UseInventory)))) ||
- (menuMode == 2 &&
- ((btn.buttonId == 0x1e && g_engine->_optionsSubMode == 1) ||
- (btn.buttonId == 0x1f && g_engine->_optionsSubMode == 2)));
+ (menuMode == MenuMode::Options &&
+ ((btn.buttonId == 0x1e && g_engine->_optionsSubMode == OptionsSubMode::Save) ||
+ (btn.buttonId == 0x1f && g_engine->_optionsSubMode == OptionsSubMode::Load)));
const bool pressed = (_pressedButtonId != 0 && btn.buttonId == _pressedButtonId);
const bool hovered = (_hoveredButtonId != 0 && btn.buttonId == _hoveredButtonId);
@@ -663,7 +663,7 @@ void ActionBar::drawNative(Graphics::ManagedSurface &s) {
const GlyphData *panelFont = g_engine->numPanelGlyphs ? g_engine->_panelGlyphs : g_engine->_glyphs;
const uint16 panelFontCount = g_engine->numPanelGlyphs ? g_engine->numPanelGlyphs : g_engine->numGlyphs;
- if (menuMode == 1) {
+ if (menuMode == MenuMode::Main) {
if (_view->_inventorySource == nullptr ||
_view->_inventorySource->_index != Scenes::instance()._currentActorIndex)
_view->setInventorySource(GameObjects::instance().getProtagonistObject());
@@ -718,7 +718,7 @@ void ActionBar::drawNative(Graphics::ManagedSurface &s) {
_view->renderStringWithFontTo((uint16)textX, (uint16)textY, sentence, font, fontCount, s);
}
}
- } else if (menuMode == 2 && panelFontCount != 0) {
+ } else if (menuMode == MenuMode::Options && panelFontCount != 0) {
if (g_engine->_saveSlotNames.empty())
refreshSaveSlotNames();
for (uint i = 0; i < g_engine->_saveSlotNames.size() && i < lineCount; i++) {
@@ -731,8 +731,8 @@ void ActionBar::drawNative(Graphics::ManagedSurface &s) {
_view->renderStringWithFontTo(optTextX, panelTop + optTextY + (int)i * linePitch,
name, panelFont, panelFontCount, s);
}
- } else if (menuMode == 4 && panelFontCount != 0 && _view->_isDialogueChoiceInputActive) {
- // Dialogue choice list at layout[5..6]; wired when assets set menuMode 4.
+ } else if (menuMode == MenuMode::DialogueList && panelFontCount != 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];
const uint16 pitch = g_engine->_hudTextLayout[4] ? g_engine->_hudTextLayout[4] : 10;
@@ -754,9 +754,9 @@ bool ActionBar::handleClickNative(const Common::Point &pos) {
const uint16 panelTop = g_engine->_panelTopY;
const int localY = pos.y - (int)panelTop;
- const uint16 menuMode = g_engine->_menuMode;
+ const MenuMode menuMode = g_engine->_menuMode;
- if (menuMode == 1) {
+ if (menuMode == MenuMode::Main) {
const uint16 cols = g_engine->_inventCols;
const uint16 rows = g_engine->_inventRows;
const uint16 slotW = g_engine->_inventSlotW;
@@ -800,7 +800,7 @@ bool ActionBar::handleClickNative(const Common::Point &pos) {
}
}
- if (menuMode == 2 && g_engine->_optionsSubMode != 0) {
+ if (menuMode == MenuMode::Options && g_engine->_optionsSubMode != OptionsSubMode::None) {
const uint16 textX = g_engine->_hudTextLayout[0];
const uint16 textY = g_engine->_hudTextLayout[1];
const uint16 textMaxW = g_engine->_hudTextLayout[2] ? g_engine->_hudTextLayout[2] : 212;
@@ -811,9 +811,9 @@ bool ActionBar::handleClickNative(const Common::Point &pos) {
const uint16 row = (uint16)((localY - textY) / linePitch);
const uint16 start = g_engine->_saveListScroll == 0 ? 1 : g_engine->_saveListScroll;
const int slot = (int)start - 1 + (int)row;
- if (g_engine->_optionsSubMode == 2) {
+ if (g_engine->_optionsSubMode == OptionsSubMode::Load) {
g_engine->loadGameState(slot);
- } else if (g_engine->_optionsSubMode == 1) {
+ } else if (g_engine->_optionsSubMode == OptionsSubMode::Save) {
Common::String name = Common::String::format(uiText("Spielstand %d").c_str(), slot + 1);
if (row < g_engine->_saveSlotNames.size() &&
!g_engine->_saveSlotNames[row].empty() &&
@@ -827,7 +827,7 @@ bool ActionBar::handleClickNative(const Common::Point &pos) {
}
}
- if (menuMode == 4 && _view->_isDialogueChoiceInputActive) {
+ if (menuMode == MenuMode::DialogueList && _view->_isDialogueChoiceInputActive) {
const uint16 dlgX = g_engine->_hudTextLayout[5];
const uint16 dlgY = g_engine->_hudTextLayout[6];
const uint16 pitch = g_engine->_hudTextLayout[4] ? g_engine->_hudTextLayout[4] : 10;
@@ -868,20 +868,20 @@ bool ActionBar::handleClickNative(const Common::Point &pos) {
g_engine->setCursorMode(Script::MouseMode::Use);
} else if (id == 0x33) {
g_engine->_savedMenuCursorMode = g_engine->_scriptExecutor->_cursorMode;
- g_engine->_menuMode = 2;
- g_engine->_optionsSubMode = 0;
+ g_engine->_menuMode = MenuMode::Options;
+ g_engine->_optionsSubMode = OptionsSubMode::None;
g_engine->_saveListScroll = 1;
refreshSaveSlotNames();
g_engine->setCursorMode(Script::MouseMode::PanelCursor);
} else if (id == 0x32) {
- g_engine->_menuMode = 1;
- g_engine->_optionsSubMode = 0;
+ g_engine->_menuMode = MenuMode::Main;
+ g_engine->_optionsSubMode = OptionsSubMode::None;
g_engine->setCursorMode(g_engine->_savedMenuCursorMode);
} else if (id == 0x1e) {
- g_engine->_optionsSubMode = 1;
+ g_engine->_optionsSubMode = OptionsSubMode::Save;
refreshSaveSlotNames();
} else if (id == 0x1f) {
- g_engine->_optionsSubMode = 2;
+ g_engine->_optionsSubMode = OptionsSubMode::Load;
refreshSaveSlotNames();
} else if (id == 0x20) {
g_engine->softRestart();
@@ -942,7 +942,7 @@ bool ActionBar::handleClickNative(const Common::Point &pos) {
} else if (id == 0x41) {
g_engine->_scriptExecutor->_textEnabled = false;
} else {
- debugC(1, kDebugScript, "ActionBar: unhandled button id=0x%x menu=%u", id, menuMode);
+ debugC(1, kDebugScript, "ActionBar: unhandled button id=0x%x menu=%u", id, (uint)menuMode);
}
_view->updateCursor();
_view->redraw();
diff --git a/engines/macs2/macs2.cpp b/engines/macs2/macs2.cpp
index 11df6a165bd..a276dea557d 100644
--- a/engines/macs2/macs2.cpp
+++ b/engines/macs2/macs2.cpp
@@ -290,8 +290,8 @@ void Macs2Engine::loadResourceFileV2() {
}
_panelTopY = 0;
_panelHeight = 0;
- _menuMode = 1;
- _optionsSubMode = 0;
+ _menuMode = MenuMode::Main;
+ _optionsSubMode = OptionsSubMode::None;
_savedMenuCursorMode = Script::MouseMode::Walk;
_inventScroll = 1;
memset(_hudTextLayout, 0, sizeof(_hudTextLayout));
@@ -673,8 +673,8 @@ void Macs2Engine::softRestart() {
stopSpeech();
clearDeltaAnim();
_skipSpeed = 1;
- _menuMode = 1;
- _optionsSubMode = 0;
+ _menuMode = MenuMode::Main;
+ _optionsSubMode = OptionsSubMode::None;
_inventScroll = 1;
_saveListScroll = 1;
@@ -1277,8 +1277,8 @@ void Macs2Engine::changeScene(uint32 newSceneIndex, bool executeScript) {
if (!loadSceneGraphics(newSceneIndex))
error("changeScene(): Failed to load scene graphics for scene %u", newSceneIndex);
- _menuMode = 1;
- _optionsSubMode = 0;
+ _menuMode = MenuMode::Main;
+ _optionsSubMode = OptionsSubMode::None;
_bottomHudVisible = true;
View1 *currentView = (View1 *)findView("View1");
@@ -1406,8 +1406,8 @@ void Macs2Engine::changeScene(uint32 newSceneIndex, bool executeScript) {
// Scene change starts with the main HUD shown. v2 scripts may hide it
// during init (overview map). v1 has no hide/show opcodes; kEnhUIUX uses
// the same flag so a scene change restores the strip.
- _menuMode = 1;
- _optionsSubMode = 0;
+ _menuMode = MenuMode::Main;
+ _optionsSubMode = OptionsSubMode::None;
_bottomHudVisible = true;
// Refresh characters
@@ -2319,19 +2319,19 @@ void Macs2Engine::nextCursorMode() {
}
void Macs2Engine::setBottomHudVisible(bool visible) {
- // hide/show opcodes toggle menuMode (0 vs 1). Cursor mode alone never hides
+ // hide/show opcodes toggle Hidden vs Main. Cursor mode alone never hides
// the HUD. Native skin restores the cursor saved on hide.
if (hasNativeHudAssets()) {
if (visible) {
- if (_menuMode == 0) {
- _menuMode = 1;
+ if (_menuMode == MenuMode::Hidden) {
+ _menuMode = MenuMode::Main;
if (_scriptExecutor)
setCursorMode(_savedMenuCursorMode);
}
} else {
- if (_menuMode == 1 && _scriptExecutor)
+ if (_menuMode == MenuMode::Main && _scriptExecutor)
_savedMenuCursorMode = _scriptExecutor->_cursorMode;
- _menuMode = 0;
+ _menuMode = MenuMode::Hidden;
}
}
_bottomHudVisible = visible;
diff --git a/engines/macs2/macs2.h b/engines/macs2/macs2.h
index 0d98ebe4d04..0732cb399ad 100644
--- a/engines/macs2/macs2.h
+++ b/engines/macs2/macs2.h
@@ -123,6 +123,19 @@ struct AnimFrame : public Sprite {
Common::Point getBottomMiddleOffset(uint16 scale = 100) const;
};
+enum class MenuMode : uint16 {
+ Hidden = 0,
+ Main = 1,
+ Options = 2,
+ DialogueList = 4
+};
+
+enum class OptionsSubMode : uint16 {
+ None = 0,
+ Save = 1,
+ Load = 2
+};
+
/** Persistent native HUD button (megapic panel skin). */
struct HudButton {
int16 x = 0;
@@ -131,7 +144,7 @@ struct HudButton {
uint16 activeStep = 0;
uint16 hoverStep = 0;
uint16 buttonId = 0; // 1=Walk, 2=Look, 3=Talk, 4=Use, 0x33=Options, ...
- uint16 menuId = 0; // 1=main bar, 2=options, ...
+ uint16 menuId = 0; // MenuMode value from assets; 7 = cursor-map entry
AnimFrame frame;
AnimFrame activeFrame;
AnimFrame hoverFrame;
@@ -554,10 +567,8 @@ public:
/** Y where the bottom HUD starts; scene above this is interactive. */
uint16 _panelTopY = 0;
uint16 _panelHeight = 0;
- /** 0=hidden, 1=main verbs/invent, 2=options, 4=dialogue list. */
- uint16 _menuMode = 1;
- /** 0=none, 1=save, 2=load (options submenu). */
- uint16 _optionsSubMode = 0;
+ MenuMode _menuMode = MenuMode::Main;
+ OptionsSubMode _optionsSubMode = OptionsSubMode::None;
Script::MouseMode _savedMenuCursorMode = Script::MouseMode::Walk;
uint16 _inventScroll = 1;
uint16 _inventOriginX = 0;
@@ -776,7 +787,7 @@ public:
/**
* Bottom HUD / action-bar visibility (dialect-neutral).
- * hide/show opcodes toggle menuMode (0 vs 1) and restore the cursor saved
+ * hide/show opcodes toggle MenuMode Hidden vs Main and restore the cursor saved
* on hide. The Scumm kEnhUIUX strip uses the same flag; cursor mode alone
* never hides the bar.
*/
diff --git a/engines/macs2/scriptexecutor.cpp b/engines/macs2/scriptexecutor.cpp
index f80068e24fc..ef32d5c2e36 100644
--- a/engines/macs2/scriptexecutor.cpp
+++ b/engines/macs2/scriptexecutor.cpp
@@ -3833,7 +3833,7 @@ OpcodeResult ScriptExecutor::scriptShowActionBar() {
return OpcodeResult::Continue;
// v2 opcode 0x65. v1 tables end at waitForAdlib; kEnhUIUX uses the same
- // setBottomHudVisible switch (menuMode 0 -> 1 + cursor restore).
+ // setBottomHudVisible switch (MenuMode Hidden -> Main + cursor restore).
if (_engine->hasNativeHudAssets() || currentView->hasPersistentActionBar()) {
_engine->setBottomHudVisible(true);
currentView->updateCursor();
diff --git a/engines/macs2/view1.cpp b/engines/macs2/view1.cpp
index 19a2b3c4761..7017558bca3 100644
--- a/engines/macs2/view1.cpp
+++ b/engines/macs2/view1.cpp
@@ -181,7 +181,7 @@ bool View1::hasPersistentActionBar() const {
int View1::actionBarTopY() const {
if (_actionBar && shouldShowActionBar())
return _actionBar->gameAreaBottomY();
- if (g_engine->hasNativeHudAssets() && g_engine->isBottomHudVisible() && g_engine->_menuMode != 0)
+ if (g_engine->hasNativeHudAssets() && g_engine->isBottomHudVisible() && g_engine->_menuMode != MenuMode::Hidden)
return (int)g_engine->_panelTopY;
return g_engine->gameHeight();
}
@@ -2328,7 +2328,7 @@ void View1::draw() {
_actionBar->draw(fullScreen);
// Glyphs use setPixel and do not dirty the Screen.
g_events->getScreen()->addDirtyRect(Common::Rect(0, actionBarTopY(), sw, sh));
- } else if (g_engine->hasNativeHudAssets() && g_engine->_menuMode == 0) {
+ } else if (g_engine->hasNativeHudAssets() && g_engine->_menuMode == MenuMode::Hidden) {
// hideActionBar / overview map: leave playfield pixels in the
// former panel band so scene art and hotspots stay visible.
} else {
Commit: 54fb2284b9a725a084e9fd7c89559ee17b39bd16
https://github.com/scummvm/scummvm/commit/54fb2284b9a725a084e9fd7c89559ee17b39bd16
Author: Martin Gerhardy (martin.gerhardy at gmail.com)
Date: 2026-08-31T21:14:09+02:00
Commit Message:
MACS2: renamed members
Changed paths:
engines/macs2/actionbar.cpp
engines/macs2/amiga_decode.cpp
engines/macs2/amiga_resources.cpp
engines/macs2/debugtools.cpp
engines/macs2/macs2.cpp
engines/macs2/macs2.h
engines/macs2/view1.cpp
diff --git a/engines/macs2/actionbar.cpp b/engines/macs2/actionbar.cpp
index d2e4bba351c..311eff50830 100644
--- a/engines/macs2/actionbar.cpp
+++ b/engines/macs2/actionbar.cpp
@@ -179,8 +179,8 @@ void ActionBar::drawUIButton(const Common::Rect &rect, bool pressed, Graphics::M
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;
+ fontCount = usePanelFont ? g_engine->numPanelGlyphs : g_engine->_numGlyphs;
+ glyphH = usePanelFont ? (int)g_engine->maxPanelGlyphHeight : (int)g_engine->_maxGlyphHeight;
}
void ActionBar::drawSentenceLine(Graphics::ManagedSurface &s) {
@@ -190,8 +190,8 @@ void ActionBar::drawSentenceLine(Graphics::ManagedSurface &s) {
// 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;
+ uint16 fontCount = g_engine->_numGlyphs;
+ int glyphH = (int)g_engine->_maxGlyphHeight;
if (fontCount == 0)
actionBarFont(font, fontCount, glyphH);
@@ -661,7 +661,7 @@ void ActionBar::drawNative(Graphics::ManagedSurface &s) {
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 uint16 panelFontCount = g_engine->numPanelGlyphs ? g_engine->numPanelGlyphs : g_engine->_numGlyphs;
if (menuMode == MenuMode::Main) {
if (_view->_inventorySource == nullptr ||
@@ -700,8 +700,8 @@ 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;
+ const GlyphData *font = g_engine->_numGlyphs ? g_engine->_glyphs : panelFont;
+ const uint16 fontCount = g_engine->_numGlyphs ? g_engine->_numGlyphs : panelFontCount;
if (fontCount != 0) {
Common::String sentence = buildSentenceLine();
if (!sentence.empty()) {
@@ -713,7 +713,7 @@ void ActionBar::drawNative(Graphics::ManagedSurface &s) {
}
const int textW = _view->measureStringWithFont(sentence, font, fontCount);
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->_maxGlyphHeight ? (int)g_engine->_maxGlyphHeight : 12;
const int textY = MAX(0, (int)panelTop - glyphH - 2);
_view->renderStringWithFontTo((uint16)textX, (uint16)textY, sentence, font, fontCount, s);
}
diff --git a/engines/macs2/amiga_decode.cpp b/engines/macs2/amiga_decode.cpp
index 0b85b7f1ae5..2b93a4826b8 100644
--- a/engines/macs2/amiga_decode.cpp
+++ b/engines/macs2/amiga_decode.cpp
@@ -379,7 +379,7 @@ bool convertAmigaPortraitAtlasToDosBlob(const byte *mxoo, uint32 mxooSize, uint3
static bool decompressPp20ToBuffer(const byte *src, uint32 srcLen, Common::Array<byte> &out) {
out.clear();
- if (!src || srcLen < 12 || READ_BE_UINT32(src) != MKTAG('P', 'P', '2', '0'))
+ if (!src)
return false;
uint32 outLen = 0;
diff --git a/engines/macs2/amiga_resources.cpp b/engines/macs2/amiga_resources.cpp
index 9923ab88972..62a0eb6175b 100644
--- a/engines/macs2/amiga_resources.cpp
+++ b/engines/macs2/amiga_resources.cpp
@@ -230,8 +230,8 @@ bool Macs2Engine::loadAmigaMxffFont() {
if (!decodeAmigaMxffFont(mxff.data(), size, glyphs) || glyphs.empty())
return false;
- numGlyphs = 0;
- maxGlyphHeight = 0;
+ _numGlyphs = 0;
+ _maxGlyphHeight = 0;
amigaTextLinePitch = 0;
if (size >= 0x0A) {
const uint16 atlasRows = READ_BE_UINT16(mxff.data() + 8);
@@ -256,20 +256,20 @@ bool Macs2Engine::loadAmigaMxffFont() {
else if (c < 16)
_glyphs[i]._data[p] = (byte)(0xF0 + c);
}
- maxGlyphHeight = MAX(maxGlyphHeight, _glyphs[i]._height);
- numGlyphs++;
+ _maxGlyphHeight = MAX(_maxGlyphHeight, _glyphs[i]._height);
+ _numGlyphs++;
}
- if (amigaTextLinePitch == 0 && maxGlyphHeight > 1)
- amigaTextLinePitch = (uint16)(maxGlyphHeight - 1);
+ if (amigaTextLinePitch == 0 && _maxGlyphHeight > 1)
+ amigaTextLinePitch = (uint16)(_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++)
+ numPanelGlyphs = _numGlyphs;
+ maxPanelGlyphHeight = _maxGlyphHeight;
+ for (uint i = 0; i < _numGlyphs; i++)
_panelGlyphs[i] = _glyphs[i];
debugC(1, kDebugFilePath, "Amiga: loaded MXFF font FF_0000 (%u glyphs, height %u, linePitch %u)",
- numGlyphs, maxGlyphHeight, amigaTextLinePitch);
- return numGlyphs > 0;
+ _numGlyphs, _maxGlyphHeight, amigaTextLinePitch);
+ return _numGlyphs > 0;
}
bool Macs2Engine::loadAmigaOverlayFontResource(uint16 ffId) {
@@ -324,12 +324,12 @@ bool Macs2Engine::loadAmigaOverlayFont(uint8 resourceIndex) {
}
// Fall back to the already-loaded main MXFF dialogue font.
- if (numGlyphs == 0)
+ if (_numGlyphs == 0)
return false;
- numOverlayGlyphs = numGlyphs;
- maxOverlayGlyphHeight = maxGlyphHeight;
- for (uint i = 0; i < numGlyphs; i++)
+ numOverlayGlyphs = _numGlyphs;
+ maxOverlayGlyphHeight = _maxGlyphHeight;
+ for (uint i = 0; i < _numGlyphs; i++)
_overlayGlyphs[i] = _glyphs[i];
return true;
}
diff --git a/engines/macs2/debugtools.cpp b/engines/macs2/debugtools.cpp
index e000b31cd04..6a3335373a6 100644
--- a/engines/macs2/debugtools.cpp
+++ b/engines/macs2/debugtools.cpp
@@ -791,7 +791,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->_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 a276dea557d..3e4f3dfc18d 100644
--- a/engines/macs2/macs2.cpp
+++ b/engines/macs2/macs2.cpp
@@ -235,12 +235,12 @@ void Macs2Engine::loadResourceFileV1() {
uint32 font1SizeField = _fileStream->readUint32LE(); // skip size field
(void)font1SizeField;
uint16 font1GlyphCount = _fileStream->readUint16LE();
- maxGlyphHeight = 0;
+ _maxGlyphHeight = 0;
for (uint i = 0; i < font1GlyphCount; i++) {
_glyphs[i].readFromMemory(_fileStream);
- maxGlyphHeight = MAX(_glyphs[i]._height, maxGlyphHeight);
+ _maxGlyphHeight = MAX(_glyphs[i]._height, _maxGlyphHeight);
}
- numGlyphs = font1GlyphCount;
+ _numGlyphs = font1GlyphCount;
// Font 2: clean sans-serif font used by save/load panel (scene data offset 0x1044)
uint32 font2SizeField = _fileStream->readUint32LE();
@@ -273,7 +273,7 @@ void Macs2Engine::loadResourceFileV2() {
// TalkVol + Font1 + SysFont + 0x400 map offsets
_shadingTable.clear();
_shadingTable.resize(0x800, 0);
- numGlyphs = 0;
+ _numGlyphs = 0;
numPanelGlyphs = 0;
memset(_mapSceneOffsets, 0, sizeof(_mapSceneOffsets));
_imageResources.clear();
@@ -478,7 +478,7 @@ void Macs2Engine::loadResourceFileV2() {
_fileStream->seek(fontStart + (int64)fontSize, SEEK_SET);
return true;
};
- if (!loadSizedFont(_glyphs, numGlyphs, maxGlyphHeight))
+ if (!loadSizedFont(_glyphs, _numGlyphs, _maxGlyphHeight))
warning("readGlobalAssetsV2: failed loading Font1");
if (!loadSizedFont(_panelGlyphs, numPanelGlyphs, maxPanelGlyphHeight))
warning("readGlobalAssetsV2: failed loading SysFont");
@@ -503,7 +503,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);
+ _numGlyphs, numPanelGlyphs);
_fileStream->seek(kMcsV2ActorIndexOffset, SEEK_SET);
bootstrapMcsActorsObjectsAndScene();
}
@@ -1883,7 +1883,7 @@ bool Macs2Engine::loadOverlayFont(uint8 resourceIndex, uint16 executingObjectID)
}
bool Macs2Engine::findGlyph(char c, GlyphData &out) const {
- for (int i = 0; i < numGlyphs; i++) {
+ for (int i = 0; i < _numGlyphs; i++) {
if (_glyphs[i]._ascii == c) {
out = _glyphs[i];
return true;
diff --git a/engines/macs2/macs2.h b/engines/macs2/macs2.h
index 0732cb399ad..6c471e31fac 100644
--- a/engines/macs2/macs2.h
+++ b/engines/macs2/macs2.h
@@ -144,7 +144,7 @@ struct HudButton {
uint16 activeStep = 0;
uint16 hoverStep = 0;
uint16 buttonId = 0; // 1=Walk, 2=Look, 3=Talk, 4=Use, 0x33=Options, ...
- uint16 menuId = 0; // MenuMode value from assets; 7 = cursor-map entry
+ uint16 menuId = 0;
AnimFrame frame;
AnimFrame activeFrame;
AnimFrame hoverFrame;
@@ -518,8 +518,8 @@ public:
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;
+ uint16 _numGlyphs = 79;
+ uint16 _maxGlyphHeight;
AnimFrame _animFrames[6];
// 6 flag/decoration animation frames at fixed file offset 0x6A5941, each followed by 6 padding bytes
@@ -843,8 +843,8 @@ public:
*/
int dialogLineHeight() const {
if (isAmiga())
- return amigaTextLinePitch ? (int)amigaTextLinePitch : (int)maxGlyphHeight;
- return (int)maxGlyphHeight + dialogLineGap();
+ return amigaTextLinePitch ? (int)amigaTextLinePitch : (int)_maxGlyphHeight;
+ return (int)_maxGlyphHeight + dialogLineGap();
}
/** Depth-map compare Y for sprite occlusion (halved on v2 full-res depth). */
diff --git a/engines/macs2/view1.cpp b/engines/macs2/view1.cpp
index 7017558bca3..30ac21bf633 100644
--- a/engines/macs2/view1.cpp
+++ b/engines/macs2/view1.cpp
@@ -770,7 +770,7 @@ void View1::drawOverlayTextEntries() {
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;
- uint16 fontCount = g_engine->numOverlayGlyphs > 0 ? g_engine->numOverlayGlyphs : g_engine->numGlyphs;
+ uint16 fontCount = g_engine->numOverlayGlyphs > 0 ? g_engine->numOverlayGlyphs : g_engine->_numGlyphs;
if (entry.alignment == 1) {
x -= measureStringWithFont(text, font, fontCount);
@@ -4474,7 +4474,7 @@ void View1::drawOriginalSaveLoadPanel(Graphics::ManagedSurface &s) {
label = "NONE";
}
const GlyphData *font = g_engine->numPanelGlyphs > 0 ? g_engine->_panelGlyphs : g_engine->_glyphs;
- uint16 fontCount = g_engine->numPanelGlyphs > 0 ? g_engine->numPanelGlyphs : g_engine->numGlyphs;
+ uint16 fontCount = g_engine->numPanelGlyphs > 0 ? g_engine->numPanelGlyphs : g_engine->_numGlyphs;
label.toUppercase();
renderStringWithFont(panelX + 6, panelY + 6 + slot * 0xc, label, font, fontCount);
}
Commit: 95f33ac6be681b5543d63da725fc0624566051bc
https://github.com/scummvm/scummvm/commit/95f33ac6be681b5543d63da725fc0624566051bc
Author: Martin Gerhardy (martin.gerhardy at gmail.com)
Date: 2026-08-31T21:14:09+02:00
Commit Message:
MACS2: cleanup amiga resource loading
Changed paths:
engines/macs2/amiga_decode.cpp
engines/macs2/amiga_decode.h
engines/macs2/amiga_resources.cpp
diff --git a/engines/macs2/amiga_decode.cpp b/engines/macs2/amiga_decode.cpp
index 2b93a4826b8..9582047efe6 100644
--- a/engines/macs2/amiga_decode.cpp
+++ b/engines/macs2/amiga_decode.cpp
@@ -71,8 +71,6 @@ bool inspectAmigaAnimSlot(const byte *mxoo, uint32 mxooSize, uint32 bodyRelative
out.repeatCounter = READ_BE_UINT16(p + 4);
out.loopStart = READ_BE_UINT16(p + 6);
- // Sequence length follows the same pattern as the DOS blob: headerHint is often
- // the frame count; sequence payload is max(hint - 2, 0) bytes, padded to even.
const uint16 seqBytes = (out.headerHint >= 2) ? (uint16)(out.headerHint - 2) : 0;
const uint16 seqPadded = (seqBytes + 1) & ~1;
const uint32 metaOff = absOff + 8 + seqPadded;
@@ -114,10 +112,6 @@ bool decodeAmigaPlanarFrame(const byte *planar, uint16 width, uint16 height, uin
outPixels.resize((uint32)width * height);
Common::fill(outPixels.begin(), outPixels.end(), 0);
- // Anim slots store 6 bitplanes; colors use planes 0..4 (indices 0..31).
- // Plane 5 is not applied as a sprite mask here - doing so clears almost all
- // opaque pixels in the demo's character anims. Color 0 remains transparent
- // for the existing DOS draw path.
for (uint16 y = 0; y < height; y++) {
for (uint16 x = 0; x < width; x++) {
byte color = 0;
@@ -143,11 +137,7 @@ bool convertAmigaAnimSlotToDosBlob(const byte *mxoo, uint32 mxooSize, uint32 bod
const byte *planar = mxoo + info.pixelOffset;
- // DOS AnimBlobView layout (LE):
- // +0x00 unknown, +0x02 seqPos, +0x04 repeat, +0x06 loopStart, +0x08 delay,
- // +0x0A seqLenMinusOne; sequence at +0x0C (N bytes);
- // frames at +0x0B + (N+1) = +0x0C + N: uint16 frameCount, then per-frame records
- // Frame: ox,oy,unk,w,h (LE) + chunky pixels
+ // DOS AnimBlobView layout
Common::Array<byte> sequence;
for (uint16 i = 0; i < info.frameCount; i++)
sequence.push_back((byte)(10 + i));
@@ -209,8 +199,6 @@ bool extractAmigaStringBlock(const byte *mxoo, uint32 mxooSize, Common::Array<by
AmigaMxooInfo info;
if (!parseAmigaMxoo(mxoo, mxooSize, info))
return false;
- // String section: u16BE pad, u16BE size, then length-prefixed plaintext entries.
- // Script string offsets are relative to the entries (after the 4-byte header).
if (info.stringOffset + 4 > mxooSize)
return false;
const uint16 size = READ_BE_UINT16(mxoo + info.stringOffset + 2);
@@ -283,8 +271,6 @@ bool convertAmigaSimpleSpriteToDosBlob(const byte *mxoo, uint32 mxooSize, Common
}
}
- // seqLenMinusOne=0 -> sequenceLength=1 -> frame data at 0x0C:
- // uint16 frameCount, then ox,oy,unk,w,h + pixels.
const uint32 total = 0x0C + 2 + 10 + (uint32)width * height;
outBlob.resize(total);
byte *dst = outBlob.data();
@@ -419,16 +405,16 @@ bool decodeAmigaMxmmSceneBackground(const byte *mxmm, uint32 mxmmSize,
outPixels.clear();
outColorCount = 0;
outPalette = Graphics::Palette(Graphics::PALETTE_COUNT);
- if (!mxmm || mxmmSize < 14)
+ if (!mxmm || mxmmSize < kAmigaMxmmMinSize)
return false;
if (READ_BE_UINT32(mxmm) != MKTAG('M', 'X', 'M', 'M'))
return false;
- const uint32 chunk0Size = READ_BE_UINT32(mxmm + 10);
- if (chunk0Size == 0 || 14 + chunk0Size > mxmmSize)
+ const uint32 chunk0Size = READ_BE_UINT32(mxmm + kAmigaMxmmHeaderSize);
+ if (chunk0Size == 0 || kAmigaMxmmMinSize + chunk0Size > mxmmSize)
return false;
- const byte *chunk0 = mxmm + 14;
+ const byte *chunk0 = mxmm + kAmigaMxmmMinSize;
Common::Array<byte> screen;
if (READ_BE_UINT32(chunk0) == MKTAG('P', 'P', '2', '0')) {
if (!decompressPp20ToBuffer(chunk0, chunk0Size, screen))
@@ -462,31 +448,31 @@ bool decodeAmigaMxmmSceneBackground(const byte *mxmm, uint32 mxmmSize,
}
const byte *copper = screen.data() + kAmigaSceneCopperOffset;
- uint16 base16[16];
+ uint16 base16[kAmigaSceneCopperColorCount];
for (uint i = 0; i < ARRAYSIZE(base16); i++)
base16[i] = READ_BE_UINT16(copper + i * 2);
- const byte *lineColors = copper + 0x20; // 200 x 16 x u16BE
- auto buildPal32 = [&](uint16 y, byte pal32[32][3]) {
+ const byte *lineColors = copper + kAmigaSceneCopperBaseBytes;
+ auto buildPal32 = [&](uint16 y, byte pal32[kAmigaColorRegisterCount][3]) {
amiga12ToRgb8(base16[0], pal32[0][0], pal32[0][1], pal32[0][2]);
- for (uint i = 0; i < 16; i++) {
- const uint16 c = READ_BE_UINT16(lineColors + (uint32)y * 32 + i * 2);
+ for (uint i = 0; i < kAmigaSceneCopperColorCount; i++) {
+ const uint16 c = READ_BE_UINT16(lineColors + (uint32)y * kAmigaSceneCopperLineBytes + i * 2);
amiga12ToRgb8(c, pal32[1 + i][0], pal32[1 + i][1], pal32[1 + i][2]);
}
- for (uint i = 1; i < 16; i++)
- amiga12ToRgb8(base16[i], pal32[16 + i][0], pal32[16 + i][1], pal32[16 + i][2]);
+ for (uint i = 1; i < kAmigaSceneCopperColorCount; i++)
+ amiga12ToRgb8(base16[i], pal32[kAmigaSceneCopperColorCount + i][0], pal32[kAmigaSceneCopperColorCount + i][1], pal32[kAmigaSceneCopperColorCount + i][2]);
};
// Reserve 0..31 for Amiga COLOR registers (sprites) and 32..63 for EHB.
- byte staticPal[32][3];
+ byte staticPal[kAmigaColorRegisterCount][3];
buildPal32(0, staticPal);
- for (uint i = 0; i < 32; i++)
+ for (uint i = 0; i < kAmigaColorRegisterCount; i++)
outPalette.set(i, staticPal[i][0], staticPal[i][1], staticPal[i][2]);
- for (uint i = 0; i < 32; i++)
- outPalette.set(32 + i, (byte)(staticPal[i][0] / 2), (byte)(staticPal[i][1] / 2), (byte)(staticPal[i][2] / 2));
- outColorCount = 64;
+ for (uint i = 0; i < kAmigaColorRegisterCount; i++)
+ outPalette.set(kAmigaColorRegisterCount + i, (byte)(staticPal[i][0] / 2), (byte)(staticPal[i][1] / 2), (byte)(staticPal[i][2] / 2));
+ outColorCount = kAmigaEhbPaletteCount;
Common::HashMap<uint32, byte> colorToIndex;
- for (uint i = 0; i < 64; i++) {
+ for (uint i = 0; i < kAmigaEhbPaletteCount; i++) {
byte r, g, b;
outPalette.get(i, r, g, b);
const uint32 key = ((uint32)r << 16) | ((uint32)g << 8) | b;
@@ -496,14 +482,14 @@ bool decodeAmigaMxmmSceneBackground(const byte *mxmm, uint32 mxmmSize,
outPixels.resize((uint)kAmigaSceneWidth * kAmigaSceneHeight);
for (uint16 y = 0; y < kAmigaSceneHeight; y++) {
- byte pal32[32][3];
+ byte pal32[kAmigaColorRegisterCount][3];
buildPal32(y, pal32);
for (uint16 x = 0; x < kAmigaSceneWidth; x++) {
byte idx = planarIndex[(uint32)y * kAmigaSceneWidth + x];
byte r, g, b;
- if (idx >= 32) {
- const byte base = (byte)(idx - 32);
+ if (idx >= kAmigaColorRegisterCount) {
+ const byte base = (byte)(idx - kAmigaColorRegisterCount);
r = (byte)(pal32[base][0] / 2);
g = (byte)(pal32[base][1] / 2);
b = (byte)(pal32[base][2] / 2);
@@ -523,7 +509,7 @@ bool decodeAmigaMxmmSceneBackground(const byte *mxmm, uint32 mxmmSize,
outPalette.set(outIdx, r, g, b);
outColorCount++;
} else {
- outIdx = idx < 64 ? idx : (byte)(idx & 31);
+ outIdx = idx < kAmigaEhbPaletteCount ? idx : (byte)(idx & (kAmigaColorRegisterCount - 1));
}
outPixels[(uint32)y * kAmigaSceneWidth + x] = outIdx;
}
@@ -532,27 +518,59 @@ bool decodeAmigaMxmmSceneBackground(const byte *mxmm, uint32 mxmmSize,
return outColorCount > 0;
}
-static bool amigaMxmmIterChunks(const byte *mxmm, uint32 mxmmSize, uint32 &outTrailerOff,
- Common::Array<const byte *> *outChunkPtrs,
- Common::Array<uint32> *outChunkSizes) {
- outTrailerOff = 0;
- if (!mxmm || mxmmSize < 14 || READ_BE_UINT32(mxmm) != MKTAG('M', 'X', 'M', 'M'))
- return false;
-
- uint32 pos = 10;
- while (pos + 4 <= mxmmSize) {
- const uint32 chunkSize = READ_BE_UINT32(mxmm + pos);
- if (chunkSize == 0)
- break;
- if (pos + 4 + chunkSize > mxmmSize)
- break;
- if (outChunkPtrs && outChunkSizes) {
- outChunkPtrs->push_back(mxmm + pos + 4);
- outChunkSizes->push_back(chunkSize);
- }
- pos += 4 + chunkSize;
+// load_scene_mxmm sequential slots after the 10-byte MXMM header.
+enum AmigaMxmmSlot {
+ kAmigaMxmmSlotBg = 0,
+ kAmigaMxmmSlotMap0 = 1,
+ kAmigaMxmmSlotMap1 = 2,
+ kAmigaMxmmSlotMap2 = 3,
+ kAmigaMxmmSlotMxaa = 4,
+ kAmigaMxmmSlotCount = 5
+};
+
+struct AmigaMxmmLayout {
+ const byte *slot[kAmigaMxmmSlotCount];
+ uint32 slotSize[kAmigaMxmmSlotCount];
+ const byte *script;
+ uint32 scriptSize;
+ const byte *extra;
+ uint32 extraSize;
+ uint32 tableOff;
+};
+
+static bool amigaMxmmReadSizedBlob(const byte *mxmm, uint32 mxmmSize, uint32 &pos,
+ const byte *&outPtr, uint32 &outSize) {
+ outPtr = nullptr;
+ outSize = 0;
+ if (pos + 4 > mxmmSize)
+ return false;
+ const int32 sz = (int32)READ_BE_UINT32(mxmm + pos);
+ pos += 4;
+ if (sz < 1)
+ return true;
+ if (pos + (uint32)sz > mxmmSize)
+ return false;
+ outPtr = mxmm + pos;
+ outSize = (uint32)sz;
+ pos += (uint32)sz;
+ return true;
+}
+
+static bool parseAmigaMxmmLayout(const byte *mxmm, uint32 mxmmSize, AmigaMxmmLayout &out) {
+ out = AmigaMxmmLayout();
+ if (!mxmm || mxmmSize < kAmigaMxmmMinSize || READ_BE_UINT32(mxmm) != MKTAG('M', 'X', 'M', 'M'))
+ return false;
+
+ uint32 pos = kAmigaMxmmHeaderSize;
+ for (int i = 0; i < kAmigaMxmmSlotCount; i++) {
+ if (!amigaMxmmReadSizedBlob(mxmm, mxmmSize, pos, out.slot[i], out.slotSize[i]))
+ return false;
}
- outTrailerOff = pos;
+ if (!amigaMxmmReadSizedBlob(mxmm, mxmmSize, pos, out.script, out.scriptSize))
+ return false;
+ if (!amigaMxmmReadSizedBlob(mxmm, mxmmSize, pos, out.extra, out.extraSize))
+ return false;
+ out.tableOff = pos;
return true;
}
@@ -567,40 +585,39 @@ static bool decompressAmigaChunkToBuffer(const byte *chunk, uint32 chunkSize, Co
return true;
}
-/**
- * Advance past MXMM size-prefixed chunks and the trailer script / optional extra
- * blob so pos sits at the pathfinding table preamble (native load_scene_mxmm
- * after bindLoadedScriptBlobToSlot @ 00221d90).
- */
-static bool amigaMxmmSeekSceneTables(const byte *mxmm, uint32 mxmmSize, uint32 &outPos) {
- outPos = 0;
- uint32 pos = 0;
- if (!amigaMxmmIterChunks(mxmm, mxmmSize, pos, nullptr, nullptr))
- return false;
+static bool amigaMxmmDecodeLengthPrefixedBlob(const byte *payload, uint32 payloadSize,
+ Common::Array<byte> &out) {
+ out.clear();
+ if (!payload || payloadSize == 0)
+ return true;
- // Empty MXAA (and any other leading zero size words) before the script payload.
- while (pos + 4 <= mxmmSize && READ_BE_UINT32(mxmm + pos) == 0)
- pos += 4;
- if (pos + 4 > mxmmSize)
+ Common::Array<byte> blob;
+ if (payloadSize >= 4 && READ_BE_UINT32(payload) == MKTAG('P', 'P', '2', '0')) {
+ if (!decompressPp20ToBuffer(payload, payloadSize, blob))
+ return false;
+ } else {
+ blob.resize(payloadSize);
+ memcpy(blob.data(), payload, payloadSize);
+ }
+ if (blob.size() < 4)
return false;
- const uint32 scriptPayloadSize = READ_BE_UINT32(mxmm + pos);
- pos += 4;
- if (scriptPayloadSize == 0 || pos + scriptPayloadSize > mxmmSize)
+ const uint32 innerLen = READ_BE_UINT32(blob.data());
+ if (innerLen == 0)
+ return true;
+ if (4 + innerLen > blob.size())
return false;
- pos += scriptPayloadSize;
+ out.resize(innerLen);
+ memcpy(out.data(), blob.data() + 4, innerLen);
+ return true;
+}
- if (pos + 4 > mxmmSize)
+static bool amigaMxmmSeekSceneTables(const byte *mxmm, uint32 mxmmSize, uint32 &outPos) {
+ outPos = 0;
+ AmigaMxmmLayout layout;
+ if (!parseAmigaMxmmLayout(mxmm, mxmmSize, layout))
return false;
- const uint32 extraSize = READ_BE_UINT32(mxmm + pos);
- pos += 4;
- if (extraSize > 0) {
- if (pos + extraSize > mxmmSize)
- return false;
- pos += extraSize;
- }
-
- outPos = pos;
+ outPos = layout.tableOff;
return true;
}
@@ -610,77 +627,14 @@ bool extractAmigaMxmmSceneScript(const byte *mxmm, uint32 mxmmSize,
outScript.clear();
outStrings.clear();
- uint32 trailerOff = 0;
- if (!amigaMxmmIterChunks(mxmm, mxmmSize, trailerOff, nullptr, nullptr))
+ AmigaMxmmLayout layout;
+ if (!parseAmigaMxmmLayout(mxmm, mxmmSize, layout))
return false;
- if (trailerOff >= mxmmSize)
+ if (!amigaMxmmDecodeLengthPrefixedBlob(layout.script, layout.scriptSize, outScript))
return false;
-
- const byte *trailer = mxmm + trailerOff;
- uint32 trailerSize = mxmmSize - trailerOff;
- uint32 pos = 0;
-
- // Skip leading empty u32 markers (optional trailing chunk sizes of 0).
- while (pos + 8 <= trailerSize && READ_BE_UINT32(trailer + pos) == 0)
- pos += 4;
-
- if (pos + 8 > trailerSize)
+ if (layout.extraSize != 0 &&
+ !amigaMxmmDecodeLengthPrefixedBlob(layout.extra, layout.extraSize, outStrings))
return false;
-
- // Native reads one size-prefixed payload; the first u32 inside is often an
- // inner bytecode length (e.g. 690) with the remaining bytes as script body.
- const uint32 scriptPayloadSize = READ_BE_UINT32(trailer + pos);
- pos += 4;
- if (scriptPayloadSize == 0 || pos + scriptPayloadSize > trailerSize)
- return false;
-
- const byte *payload = trailer + pos;
- pos += scriptPayloadSize;
-
- uint32 scriptSize = scriptPayloadSize;
- const byte *script = payload;
- if (scriptPayloadSize >= 6) {
- const uint32 innerLen = READ_BE_UINT32(payload);
- if (innerLen >= 2 && innerLen + 4 <= scriptPayloadSize &&
- (innerLen + 4 == scriptPayloadSize || innerLen + 4 + 1 == scriptPayloadSize)) {
- script = payload + 4;
- scriptSize = innerLen;
- }
- }
-
- // Accept empty/tiny stubs (e.g. single 0x18) and real scene scripts.
- if (scriptSize >= 2) {
- const byte op = script[0];
- const byte len = script[1];
- if (len > 32 || (uint32)2 + len > scriptSize)
- return false;
- if (op != 0x04 && op != 0x05 && op != 0x01 && op != 0x0F && op != 0x0C && op != 0x18 && op != 0x07)
- return false;
- }
-
- outScript.resize(scriptSize);
- memcpy(outScript.data(), script, scriptSize);
-
- // Optional extra blob: outer u32 size, payload often starts with an inner
- // u32 string-bytes length (Ghidra load_scene_mxmm stage 0x19).
- if (pos + 4 <= trailerSize) {
- const uint32 extraSize = READ_BE_UINT32(trailer + pos);
- if (extraSize > 0 && pos + 4 + extraSize <= trailerSize) {
- const byte *extra = trailer + pos + 4;
- uint32 stringBytes = extraSize;
- const byte *stringData = extra;
- if (extraSize >= 4) {
- const uint32 inner = READ_BE_UINT32(extra);
- if (inner > 0 && inner + 4 <= extraSize) {
- stringData = extra + 4;
- stringBytes = inner;
- }
- }
- outStrings.resize(stringBytes);
- memcpy(outStrings.data(), stringData, stringBytes);
- }
- }
-
return !outScript.empty();
}
@@ -688,11 +642,9 @@ static bool amigaMxmmGetTrailerTableBase(const byte *mxmm, uint32 mxmmSize, uint
outPos = 0;
if (!amigaMxmmSeekSceneTables(mxmm, mxmmSize, outPos))
return false;
- // Native @ 00221d90: skip u16, then 0xA2 pathfinding, skip u16, 0x22 hotspot, 0xA walk.
- const uint32 kTableBytes = 2 + 0xA2 + 2 + 0x22 + 10;
- if (outPos + kTableBytes > mxmmSize)
+ if (outPos + kAmigaMxmmTrailerTablesSize > mxmmSize)
return false;
- outPos += 2; // preamble u16
+ outPos += kAmigaMxmmTrailerPreambleSize;
return true;
}
@@ -708,23 +660,20 @@ bool extractAmigaMxmmScenePathfinding(const byte *mxmm, uint32 mxmmSize,
const byte *table = mxmm + pos;
outNumPoints = READ_BE_UINT16(table);
- if (outNumPoints > 16)
- outNumPoints = 16;
+ if (outNumPoints > kAmigaMxmmPathfindingNodeCount)
+ outNumPoints = kAmigaMxmmPathfindingNodeCount;
- outNodes.resize(16);
- for (uint i = 0; i < 16; i++) {
- const byte *node = table + 2 + i * 10;
+ outNodes.resize(kAmigaMxmmPathfindingNodeCount);
+ for (uint i = 0; i < kAmigaMxmmPathfindingNodeCount; i++) {
+ const byte *node = table + kAmigaMxmmWordSize + i * kAmigaMxmmPathfindingNodeSize;
AmigaPathfindingNode &dst = outNodes[i];
- dst.x = READ_BE_UINT16(node + 0);
- dst.y = READ_BE_UINT16(node + 2);
- dst.adjacent[0] = node[4];
- dst.adjacent[1] = node[5];
- dst.adjacent[2] = node[6];
- dst.adjacent[3] = node[7];
- // DOS/Amiga: last word is connection count (not padding). BE on Amiga.
- dst.numConnections = READ_BE_UINT16(node + 8);
- if (dst.numConnections > 4)
- dst.numConnections = 4;
+ dst.x = READ_BE_UINT16(node + kAmigaMxmmPathfindingNodeXOffset);
+ dst.y = READ_BE_UINT16(node + kAmigaMxmmPathfindingNodeYOffset);
+ for (uint a = 0; a < kAmigaMxmmPathfindingMaxAdj; a++)
+ dst.adjacent[a] = node[kAmigaMxmmPathfindingNodeAdjOffset + a];
+ dst.numConnections = READ_BE_UINT16(node + kAmigaMxmmPathfindingNodeConnOffset);
+ if (dst.numConnections > kAmigaMxmmPathfindingMaxAdj)
+ dst.numConnections = kAmigaMxmmPathfindingMaxAdj;
}
return true;
}
@@ -745,14 +694,13 @@ bool extractAmigaMxmmSceneWalkParams(const byte *mxmm, uint32 mxmmSize,
if (!amigaMxmmGetTrailerTableBase(mxmm, mxmmSize, pos))
return false;
- // Skip pathfinding (0xA2) + preamble u16 + hotspot (0x22).
- pos += 0xA2 + 2 + 0x22;
+ pos += kAmigaMxmmPathfindingToWalkOffset;
- outWalkDepthThresholdY = READ_BE_UINT16(mxmm + pos + 0);
- outWalkDepthScaleFactor = READ_BE_UINT16(mxmm + pos + 2);
- outWalkBaseSpeedPct = READ_BE_UINT16(mxmm + pos + 4);
- outScenePaletteMode = READ_BE_UINT16(mxmm + pos + 6);
- outPaletteDarkenPercent = READ_BE_UINT16(mxmm + pos + 8);
+ outWalkDepthThresholdY = READ_BE_UINT16(mxmm + pos + kAmigaMxmmWalkDepthThresholdYOffset);
+ outWalkDepthScaleFactor = READ_BE_UINT16(mxmm + pos + kAmigaMxmmWalkDepthScaleFactorOffset);
+ outWalkBaseSpeedPct = READ_BE_UINT16(mxmm + pos + kAmigaMxmmWalkBaseSpeedPctOffset);
+ outScenePaletteMode = READ_BE_UINT16(mxmm + pos + kAmigaMxmmWalkScenePaletteModeOffset);
+ outPaletteDarkenPercent = READ_BE_UINT16(mxmm + pos + kAmigaMxmmWalkPaletteDarkenPercentOffset);
return true;
}
@@ -764,46 +712,27 @@ bool extractAmigaMxmmSceneMaps(const byte *mxmm, uint32 mxmmSize,
outDepth.clear();
outShadow.clear();
- uint32 trailerOff = 0;
- Common::Array<const byte *> chunkPtrs;
- Common::Array<uint32> chunkSizes;
- if (!amigaMxmmIterChunks(mxmm, mxmmSize, trailerOff, &chunkPtrs, &chunkSizes))
- return false;
- if (chunkPtrs.size() < 2)
+ AmigaMxmmLayout layout;
+ if (!parseAmigaMxmmLayout(mxmm, mxmmSize, layout))
return false;
const uint32 kMapBytes = (uint32)kAmigaSceneWidth * kAmigaSceneHeight; // 64000
- Common::Array<byte> maps[3];
- uint mapCount = 0;
-
- // Chunk0 is the planar screen; subsequent 64000-byte buffers are maps.
- for (uint i = 1; i < chunkPtrs.size() && mapCount < 3; i++) {
+ auto decodeMap = [&](int slot, Common::Array<byte> &dest) -> bool {
+ if (layout.slotSize[slot] == 0)
+ return false;
Common::Array<byte> decoded;
- if (!decompressAmigaChunkToBuffer(chunkPtrs[i], chunkSizes[i], decoded))
- continue;
+ if (!decompressAmigaChunkToBuffer(layout.slot[slot], layout.slotSize[slot], decoded))
+ return false;
if (decoded.size() != kMapBytes)
- continue;
- maps[mapCount++] = Common::move(decoded);
- }
+ return false;
+ dest = Common::move(decoded);
+ return true;
+ };
- if (mapCount == 0)
- return false;
-
- // Native load_scene_mxmm order (NOT DOS MCS order):
- // Map0 -> depth / draw mask (g_pSceneMap0Buffer; buildDepthMaskFromMap0)
- // Map1 -> walkability (g_pSceneMap1Buffer; getWalkabilityAt)
- // Map2 -> MXCC hotspot RLE (not a 64000 surface; see extractAmigaMxmmMxccHotspotMap)
- // DOS MCS is depth, pathfinding, shadow, hotspot. Swapping Map0/Map1 into
- // ScummVM's pathfinding slot made MM_0004 treat the depth mask as walk
- // blockers (255 walls), so off-screen walkTo cancelled and waitForWalk
- // completed early. Map1 on that demo is all zeros (= open, like DOS scene 5).
- if (mapCount >= 1)
- outDepth = Common::move(maps[0]);
- if (mapCount >= 2)
- outPathfinding = Common::move(maps[1]);
- if (mapCount >= 3)
- outShadow = Common::move(maps[2]);
- return true;
+ const bool gotDepth = decodeMap(kAmigaMxmmSlotMap0, outDepth);
+ const bool gotPath = decodeMap(kAmigaMxmmSlotMap1, outPathfinding);
+ // Map2 is MXCC, not a 64000 shadow surface.
+ return gotDepth || gotPath;
}
bool extractAmigaMxmmSceneHotspotColors(const byte *mxmm, uint32 mxmmSize,
@@ -816,19 +745,16 @@ bool extractAmigaMxmmSceneHotspotColors(const byte *mxmm, uint32 mxmmSize,
if (!amigaMxmmGetTrailerTableBase(mxmm, mxmmSize, pos))
return false;
- // Skip pathfinding (0xA2) + preamble u16 before hotspot table.
- pos += 0xA2 + 2;
- if (pos + 0x22 > mxmmSize)
+ pos += kAmigaMxmmPathfindingToHotspotOffset;
+ if (pos + kAmigaMxmmHotspotTableSize > mxmmSize)
return false;
outNumHotspots = READ_BE_UINT16(mxmm + pos);
- if (outNumHotspots > 16)
- outNumHotspots = 16;
+ if (outNumHotspots > kAmigaMxmmPathfindingNodeCount)
+ outNumHotspots = kAmigaMxmmPathfindingNodeCount;
- // Same 0x20 byte layout as DOS (f9 00 fe 00 ...). On LE hosts, memcpy into
- // uint16 yields color in the low byte - matches getHotspotAtPoint.
- outColorTable.resize(0x20 / sizeof(uint16));
- memcpy(outColorTable.data(), mxmm + pos + 2, 0x20);
+ outColorTable.resize(kAmigaMxmmHotspotColorBytes / sizeof(uint16));
+ memcpy(outColorTable.data(), mxmm + pos + kAmigaMxmmWordSize, kAmigaMxmmHotspotColorBytes);
return true;
}
@@ -836,59 +762,44 @@ bool extractAmigaMxmmMxccHotspotMap(const byte *mxmm, uint32 mxmmSize,
Common::Array<byte> &outHotspotMap) {
outHotspotMap.clear();
- uint32 trailerOff = 0;
- Common::Array<const byte *> chunkPtrs;
- Common::Array<uint32> chunkSizes;
- if (!amigaMxmmIterChunks(mxmm, mxmmSize, trailerOff, &chunkPtrs, &chunkSizes))
+ AmigaMxmmLayout layout;
+ if (!parseAmigaMxmmLayout(mxmm, mxmmSize, layout))
return false;
- const byte *mxcc = nullptr;
- uint32 mxccSize = 0;
- for (uint i = 0; i < chunkPtrs.size(); i++) {
- if (chunkSizes[i] >= 0x19A + 320 &&
- READ_BE_UINT32(chunkPtrs[i]) == MKTAG('M', 'X', 'C', 'C')) {
- mxcc = chunkPtrs[i];
- mxccSize = chunkSizes[i];
- break;
- }
- }
- if (!mxcc)
+ const byte *mxcc = layout.slot[kAmigaMxmmSlotMap2];
+ const uint32 mxccSize = layout.slotSize[kAmigaMxmmSlotMap2];
+ if (!mxcc || mxccSize < kAmigaMxccRowDataOffset + kAmigaSceneWidth ||
+ READ_BE_UINT32(mxcc) != MKTAG('M', 'X', 'C', 'C'))
return false;
- // Header: MXCC + ver u16BE + pad u16BE + payloadSize u16BE (Ghidra Map2).
- if (READ_BE_UINT16(mxcc + 4) != 1)
+ if (READ_BE_UINT16(mxcc + kAmigaMxccVersionOffset) != 1)
return false;
- const byte marker = mxcc[5];
- const uint32 kRowTableOff = 0x0A;
- const uint32 kRowDataBase = 0x19A;
- const uint kRows = 200;
- const uint kWidth = 320;
- if (kRowTableOff + kRows * 2 > mxccSize || kRowDataBase > mxccSize)
+ const byte marker = mxcc[kAmigaMxccMarkerOffset];
+ if (kAmigaMxccRowDataOffset > mxccSize)
return false;
- outHotspotMap.resize(kWidth * kRows);
+ outHotspotMap.resize((uint)kAmigaSceneWidth * kAmigaSceneHeight);
Common::fill(outHotspotMap.begin(), outHotspotMap.end(), 0);
- for (uint y = 0; y < kRows; y++) {
- // decodeMxccRunLengthAt: y==0 uses offset 0; else BE word at +0x0A+(y-1)*2.
+ for (uint y = 0; y < kAmigaSceneHeight; y++) {
uint32 rowOff = 0;
if (y > 0)
- rowOff = READ_BE_UINT16(mxcc + kRowTableOff + (y - 1) * 2);
+ rowOff = READ_BE_UINT16(mxcc + kAmigaMxccRowTableOffset + (y - 1) * 2);
- uint32 p = kRowDataBase + rowOff;
+ uint32 p = kAmigaMxccRowDataOffset + rowOff;
uint x = 0;
- while (x < kWidth && p < mxccSize) {
+ while (x < kAmigaSceneWidth && p < mxccSize) {
const byte b = mxcc[p++];
if (b == marker) {
if (p + 1 >= mxccSize)
break;
const byte color = mxcc[p++];
const byte run = mxcc[p++];
- for (uint r = 0; r < run && x < kWidth; r++)
- outHotspotMap[y * kWidth + x++] = color;
+ for (uint r = 0; r < run && x < kAmigaSceneWidth; r++)
+ outHotspotMap[y * kAmigaSceneWidth + x++] = color;
} else {
- outHotspotMap[y * kWidth + x++] = b;
+ outHotspotMap[y * kAmigaSceneWidth + x++] = b;
}
}
}
@@ -896,16 +807,13 @@ bool extractAmigaMxmmMxccHotspotMap(const byte *mxmm, uint32 mxmmSize,
}
bool amigaMxmmHasMxaaOverlay(const byte *mxmm, uint32 mxmmSize) {
- uint32 pos = 0;
- if (!amigaMxmmIterChunks(mxmm, mxmmSize, pos, nullptr, nullptr))
- return false;
- // Native: immediately after Map2 comes the MXAA size word (0 = absent).
- if (pos + 4 > mxmmSize)
+ AmigaMxmmLayout layout;
+ if (!parseAmigaMxmmLayout(mxmm, mxmmSize, layout))
return false;
- const uint32 mxaaSize = READ_BE_UINT32(mxmm + pos);
- if (mxaaSize == 0 || pos + 4 + mxaaSize > mxmmSize)
+ if (layout.slotSize[kAmigaMxmmSlotMxaa] == 0)
return false;
- const byte *blob = mxmm + pos + 4;
+ const byte *blob = layout.slot[kAmigaMxmmSlotMxaa];
+ const uint32 mxaaSize = layout.slotSize[kAmigaMxmmSlotMxaa];
if (mxaaSize >= 4 && READ_BE_UINT32(blob) == MKTAG('M', 'X', 'A', 'A'))
return true;
if (mxaaSize >= 4 && READ_BE_UINT32(blob) == MKTAG('P', 'P', '2', '0')) {
@@ -930,14 +838,14 @@ bool decodeAmigaMxffFont(const byte *mxff, uint32 mxffSize, Common::Array<AmigaM
const uint16 atlasRows = READ_BE_UINT16(mxff + 8);
const uint16 atlasWidthPixels = READ_BE_UINT16(mxff + 0xA);
const uint16 planes = READ_BE_UINT16(mxff + 0xC);
- if (blitHeight == 0 || atlasRows == 0 || atlasWidthPixels < 8 || planes != 6)
+ if (blitHeight == 0 || atlasRows == 0 || atlasWidthPixels < 8 || planes != kAmigaScenePlanes)
return false;
const uint32 rowBytes = (uint32)(atlasWidthPixels >> 3);
if (rowBytes == 0)
return false;
const uint32 planeBytes = rowBytes * atlasRows;
- const uint32 atlasBytes = planeBytes * 6;
+ const uint32 atlasBytes = planeBytes * kAmigaScenePlanes;
const uint32 atlasOff = 0x10A;
if (atlasOff + atlasBytes > mxffSize)
return false;
@@ -946,14 +854,13 @@ bool decodeAmigaMxffFont(const byte *mxff, uint32 mxffSize, Common::Array<AmigaM
const byte *widths = mxff + 0x8C;
const byte *atlas = mxff + atlasOff;
const uint16 glyphHeight = MIN(blitHeight, atlasRows);
- const uint16 cellWidth = 16; // drawText: glyphIndex * (height>>3) byte offset -> 16px cells
+ const uint16 cellWidth = 16;
- // chars 0x20..0x20+0x7D (0x7E entries)
for (uint i = 0; i < 0x7E; i++) {
const byte ascii = (byte)(0x20 + i);
const byte glyphIndex = charmap[i];
const byte advance = widths[i];
- // 0x4E in the map is unused/empty (Ghidra charmap filler).
+ // 0x4E in the map is unused/empty
if (glyphIndex == 0x4E || advance == 0)
continue;
@@ -974,7 +881,7 @@ bool decodeAmigaMxffFont(const byte *mxff, uint32 mxffSize, Common::Array<AmigaM
byte color = 0;
const uint32 bit = absX & 7;
const uint32 byteInRow = absX >> 3;
- for (uint16 plane = 0; plane < 6; plane++) {
+ for (uint16 plane = 0; plane < kAmigaScenePlanes; plane++) {
const byte *planeRow = atlas + plane * planeBytes + (uint32)y * rowBytes;
if (planeRow[byteInRow] & (0x80 >> bit))
color |= (byte)(1 << plane);
@@ -996,13 +903,10 @@ bool extractAmigaMxosPcm(const byte *mxos, uint32 mxosSize, Common::Array<byte>
if (READ_BE_UINT32(mxos) != MKTAG('M', 'X', 'O', 'S'))
return false;
- // Demo MXOS: word at +0x10 is the byte offset of the first sample block.
const uint16 sampleOff = READ_BE_UINT16(mxos + 0x10);
if (sampleOff < 0x14 || sampleOff >= mxosSize)
return false;
- // Optional Paula period near the sample header (word after offset on several demos).
- // period 0 -> keep 8000 Hz. NTSC color clock / period ~= Paula playback rate.
if ((uint32)sampleOff + 4 <= mxosSize) {
const uint16 period = READ_BE_UINT16(mxos + 0x12);
if (period >= 0x50 && period <= 0x400) {
@@ -1017,7 +921,6 @@ bool extractAmigaMxosPcm(const byte *mxos, uint32 mxosSize, Common::Array<byte>
return false;
outPcm.resize(pcmBytes);
- // Paula samples are signed 8-bit; MacsAudioStream expects unsigned (value-128)*256.
for (uint32 i = 0; i < pcmBytes; i++)
outPcm[i] = (byte)((int8)mxos[sampleOff + i] + 128);
diff --git a/engines/macs2/amiga_decode.h b/engines/macs2/amiga_decode.h
index a4de2cc5571..1f3d6da4943 100644
--- a/engines/macs2/amiga_decode.h
+++ b/engines/macs2/amiga_decode.h
@@ -28,19 +28,6 @@
namespace Macs2 {
-/**
- * Amiga MXOO object body layout (after the 12-byte MXOO header):
- * +0x00..0x0B padding
- * +0x0C signature 0x0101
- * +0x0E 21 x uint32BE slot offsets (0 / 0xFFFFFFFF = empty)
- * +0x62 uint32BE offset of extra/portrait section (often end of anims)
- *
- * Each anim slot is planar 6-plane frame data with a short BE header.
- * Script bytecode is identical to DOS (LE operands). Strings are plaintext
- * with uint16BE length prefixes (no XOR cipher).
- *
- * Game object index = Amiga OO resource id + 1 (OO_0000 -> object 1).
- */
struct AmigaMxooInfo {
uint32 scriptOffset = 0;
uint32 stringOffset = 0;
@@ -71,103 +58,74 @@ struct AmigaAnimSlotInfo {
bool parseAmigaMxoo(const byte *mxoo, uint32 mxooSize, AmigaMxooInfo &out);
bool inspectAmigaAnimSlot(const byte *mxoo, uint32 mxooSize, uint32 bodyRelativeOffset, AmigaAnimSlotInfo &out);
void amiga12ToVga6(uint16 rgb, byte &r6, byte &g6, byte &b6);
-
-/** Decode one frame of planar Amiga anim data to chunky 8bpp (color planes 0..4). */
bool decodeAmigaPlanarFrame(const byte *planar, uint16 width, uint16 height, uint16 frameIndex,
uint16 frameCount, Common::Array<byte> &outPixels);
-
-/**
- * Convert an Amiga anim slot into a DOS-compatible animation blob so the
- * existing AnimBlobView / renderer path can consume it.
- */
bool convertAmigaAnimSlotToDosBlob(const byte *mxoo, uint32 mxooSize, uint32 bodyRelativeOffset,
Common::Array<byte> &outBlob);
-
-/** Extract script bytecode (without the MXOO script section header). */
bool extractAmigaScript(const byte *mxoo, uint32 mxooSize, Common::Array<byte> &outScript);
-
-/**
- * Extract Amiga string entries (u16BE length + plaintext), skipping the
- * MXOO string-section header. Offsets used by scripts are relative to this block.
- */
bool extractAmigaStringBlock(const byte *mxoo, uint32 mxooSize, Common::Array<byte> &outStrings);
-
-/** Convert a simple planar MXOO sprite (cursor/icon) into a 1-frame DOS anim blob. */
bool convertAmigaSimpleSpriteToDosBlob(const byte *mxoo, uint32 mxooSize, Common::Array<byte> &outBlob);
-
-/**
- * Convert Amiga dialogue portrait atlas (body+0x62) into a DOS anim blob.
- * Layout: 240x80x6 separated planar = three 80x80 faces (D5=0xF0, D3/D4/D6=0x50,
- * 6 plane blit in animateDialoguePortrait @ 0022f79c). Color from planes 0..4.
- * Pixels keep Amiga COLOR indices 0..31 (playfield copper); demo portraits use
- * only COLOR17..31 from the copper high bank / MXIN chrome ramp.
- */
bool convertAmigaPortraitAtlasToDosBlob(const byte *mxoo, uint32 mxooSize, uint32 bodyRelativeExtraOffset,
Common::Array<byte> &outBlob);
-/** Decompressed MXMM chunk0 screen buffer size (6x8000 planes + copper block). */
enum : uint32 {
- kAmigaSceneScreenSize = 54432, // 0xD4A0
- kAmigaSceneCopperOffset = 0xBB80,
- kAmigaSceneCopperSize = 0x1920, // 16 base colors + 200x16 line colors
- kAmigaScenePlaneBytes = 8000 // 40x200
-};
-enum : uint16 {
kAmigaSceneWidth = 320,
kAmigaSceneHeight = 200,
- kAmigaScenePlanes = 6 // BPLCON0 = 0x6200 -> EHB
+ kAmigaScenePlanes = 6,
+ kAmigaSceneScreenSize = 54432,
+ kAmigaSceneCopperOffset = 0xBB80,
+ kAmigaSceneCopperSize = 0x1920, // 16 base colors + 200x16 line colors
+ kAmigaSceneCopperColorCount = 16,
+ kAmigaSceneCopperLineBytes = kAmigaSceneCopperColorCount * 2, // 16 x u16BE
+ kAmigaSceneCopperBaseBytes = kAmigaSceneCopperLineBytes, // static COLOR0..15 before per-line colors
+ kAmigaScenePlaneBytes = 8000, // 40x200
+ kAmigaColorRegisterCount = 32,
+ kAmigaEhbPaletteCount = 64,
+
+ /** MXMM + version u16 + pad u16 + scene id u16. */
+ kAmigaMxmmHeaderSize = 10,
+ kAmigaMxmmMinSize = kAmigaMxmmHeaderSize + 4,
+
+ kAmigaMxmmWordSize = 2, // u16BE count / preamble
+ kAmigaMxmmTrailerPreambleSize = kAmigaMxmmWordSize,
+ kAmigaMxmmPathfindingNodeCount = 16,
+ kAmigaMxmmPathfindingNodeSize = 10,
+ kAmigaMxmmPathfindingMaxAdj = 4,
+ kAmigaMxmmPathfindingNodeXOffset = 0,
+ kAmigaMxmmPathfindingNodeYOffset = 2,
+ kAmigaMxmmPathfindingNodeAdjOffset = 4,
+ kAmigaMxmmPathfindingNodeConnOffset = 8,
+ kAmigaMxmmPathfindingTableSize = kAmigaMxmmWordSize +
+ kAmigaMxmmPathfindingNodeCount * kAmigaMxmmPathfindingNodeSize, // 0xA2
+ kAmigaMxmmHotspotColorBytes = 0x20,
+ kAmigaMxmmHotspotTableSize = kAmigaMxmmWordSize + kAmigaMxmmHotspotColorBytes, // 0x22
+ kAmigaMxmmWalkPaletteSize = 10, // 5 x u16BE
+ kAmigaMxmmWalkDepthThresholdYOffset = 0,
+ kAmigaMxmmWalkDepthScaleFactorOffset = 2,
+ kAmigaMxmmWalkBaseSpeedPctOffset = 4,
+ kAmigaMxmmWalkScenePaletteModeOffset = 6,
+ kAmigaMxmmWalkPaletteDarkenPercentOffset = 8,
+ kAmigaMxmmPathfindingToHotspotOffset = kAmigaMxmmPathfindingTableSize + kAmigaMxmmTrailerPreambleSize,
+ kAmigaMxmmPathfindingToWalkOffset = kAmigaMxmmPathfindingToHotspotOffset + kAmigaMxmmHotspotTableSize,
+ kAmigaMxmmTrailerTablesSize = kAmigaMxmmTrailerPreambleSize + kAmigaMxmmPathfindingTableSize +
+ kAmigaMxmmTrailerPreambleSize + kAmigaMxmmHotspotTableSize +
+ kAmigaMxmmWalkPaletteSize,
+
+ kAmigaMxccVersionOffset = 4,
+ kAmigaMxccMarkerOffset = 5,
+ kAmigaMxccRowTableOffset = 0x0A,
+ kAmigaMxccRowDataOffset = kAmigaMxccRowTableOffset + kAmigaSceneHeight * 2 // 0x19A
};
-/**
- * Decode MXMM scene package chunk0 into chunky 8bpp 320x200 pixels and an RGB8
- * palette (up to 256 entries).
- *
- * Palette layout (matches Amiga copper / sprite drawing):
- * - indices 0..31: COLOR00..31 from the copper base block + first scanline
- * - indices 32..63: Extra HalfBrite of 0..31 (BPLCON0 0x6200)
- * - indices 64..*: extra colors needed for per-scanline copper differences
- *
- * Character/OO sprites use planes 0..4 against COLOR00..31, so 0..31 must stay
- * stable Amiga hardware colors.
- */
bool decodeAmigaMxmmSceneBackground(const byte *mxmm, uint32 mxmmSize,
Common::Array<byte> &outPixels,
Graphics::Palette &outPalette,
uint &outColorCount);
-/**
- * Extract scene script + string block from an MXMM package trailer.
- *
- * After the size-prefixed chunks (BG / maps / MXCC / ...), the trailer is:
- * [optional u32 zero markers]
- * u32BE scriptSize
- * u32BE unknown (ignored)
- * script[scriptSize] - DOS-identical LE bytecode
- * [optional] u32BE stringBytes + u16BE-length plaintext entries
- *
- * Ghidra: load_scene_mxmm reads this after planar BG / map chunks.
- * Script-visible scene ids are MM_resource_id + 1 (changeScene subtracts 1
- * before MM lookup; curScene is set to mmId+1 after load).
- */
bool extractAmigaMxmmSceneScript(const byte *mxmm, uint32 mxmmSize,
Common::Array<byte> &outScript,
Common::Array<byte> &outStrings);
-/**
- * Extract scene walk / palette scalars from the MXMM trailer tables.
- *
- * Ghidra load_scene_mxmm @ 00221de8 reads 10 bytes after pathfinding (0xA2) and
- * hotspot (0x22) tables into DAT_002376da..e2 (five BE u16s), matching DOS
- * scene+0x51FD..0x5205:
- * [0] walk depth threshold Y
- * [1] walk depth scale factor
- * [2] walk base speed percent
- * [3] scene palette mode
- * [4] palette darken percent
- *
- * Without these, ScummVM's DOS walkAlongPath formula collapses to 1 px/frame
- * when the Amiga native path never loads MCS scene metadata.
- */
bool extractAmigaMxmmSceneWalkParams(const byte *mxmm, uint32 mxmmSize,
uint16 &outWalkDepthThresholdY,
uint16 &outWalkDepthScaleFactor,
@@ -175,73 +133,31 @@ bool extractAmigaMxmmSceneWalkParams(const byte *mxmm, uint32 mxmmSize,
uint16 &outScenePaletteMode,
uint16 &outPaletteDarkenPercent);
-/**
- * One pathfinding graph node from the MXMM trailer 0xA2 table
- * (Ghidra load_scene_mxmm @ 00221d9a -> 00248794).
- *
- * Layout (BE), matching DOS MCS 16x10-byte nodes:
- * u16 x, u16 y, u8 adj[4], u16 unused
- * Prefixed by u16BE active node count (DOS scene+0x51F7).
- */
struct AmigaPathfindingNode {
uint16 x = 0;
uint16 y = 0;
- byte adjacent[4] = {0, 0, 0, 0};
+ byte adjacent[kAmigaMxmmPathfindingMaxAdj] = {};
uint16 numConnections = 0;
};
-/**
- * Extract pathfinding node graph from the MXMM trailer (0xA2 bytes after a
- * skipped u16). Always yields 16 node slots; outNumPoints is the active count.
- * Without this, Amiga scenes keep _numPathfindingPoints==0, calculatePath fails,
- * walks cancel early, and waitForWalk completes at the stuck position.
- */
bool extractAmigaMxmmScenePathfinding(const byte *mxmm, uint32 mxmmSize,
uint16 &outNumPoints,
Common::Array<AmigaPathfindingNode> &outNodes);
-/**
- * Decode MXMM map chunks (320x200) after chunk0.
- * Native order: Map0=depth (g_pSceneMap0Buffer), Map1=walkability
- * (g_pSceneMap1Buffer). Map2 holds MXCC (hotspot RLE), not a 64000 shadow -
- * use extractAmigaMxmmMxccHotspotMap for that. A third 64000 PP20 (rare) is
- * returned as outShadow when present.
- */
bool extractAmigaMxmmSceneMaps(const byte *mxmm, uint32 mxmmSize,
Common::Array<byte> &outPathfinding,
Common::Array<byte> &outDepth,
Common::Array<byte> &outShadow);
-/**
- * Extract hotspot color table from the MXMM trailer (0x22 bytes after pathfinding).
- * Layout matches DOS scene+0x50C3: u16BE numHotspots + 0x20 bytes color words
- * (color in the first byte of each pair / low byte once stored as uint16 on LE).
- * MM_0004: num=7, colors 249,254,250,251,253,252,248 - same as DOS scene 5.
- */
bool extractAmigaMxmmSceneHotspotColors(const byte *mxmm, uint32 mxmmSize,
uint16 &outNumHotspots,
Common::Array<uint16> &outColorTable);
-/**
- * Decode MXCC chunk (Map2 / g_pSceneMap2Buffer) to a 320x200 hotspot pixel map.
- * Ghidra decodeMxccRunLengthAt @ 00233590: per-row RLE with marker at MXCC+5,
- * row offsets at +0x0A (200xu16BE), row data at +0x19A + offset.
- * MM_0004 decodes pixel-identical to DOS scene 5 hotspot RLE.
- * No separate 4th 64000 map chunk exists in the Amiga demo format.
- */
bool extractAmigaMxmmMxccHotspotMap(const byte *mxmm, uint32 mxmmSize,
Common::Array<byte> &outHotspotMap);
-/**
- * True if MXMM has a non-empty MXAA overlay-anim blob (native tickMxaaOverlayAnims).
- * Demo MM_0004/MM_0040 have size 0 - no consumer wired yet.
- */
bool amigaMxmmHasMxaaOverlay(const byte *mxmm, uint32 mxmmSize);
-/**
- * One decoded MXFF glyph (chunky 8bpp, color 0 = transparent).
- * Ghidra drawText @ 00224492: charmap at +0x0E, widths at +0x8C, atlas at +0x10A.
- */
struct AmigaMxffGlyph {
byte ascii = 0;
uint16 width = 0;
@@ -249,22 +165,8 @@ struct AmigaMxffGlyph {
Common::Array<byte> pixels; // width * height
};
-/**
- * Decode native Amiga MXFF font (FF_0000) into chunky glyphs for GlyphData.
- *
- * Header (BE): magic MXFF, ver=1 at +4, blit height at +6, atlas rows at +8,
- * atlas width-pixels at +0xA (row bytes = value>>3), planes=6 at +0xC.
- * Glyph index map: +0x0E (0x7E bytes for chars 0x20..).
- * Advance widths: +0x8C (0x7E bytes).
- * Planar atlas: +0x10A, rowBytes * atlasRows * 6 planes; cell X = glyphIndex * 16.
- */
bool decodeAmigaMxffFont(const byte *mxff, uint32 mxffSize, Common::Array<AmigaMxffGlyph> &outGlyphs);
-/**
- * Extract unsigned 8-bit PCM (+ optional rate) from an MXOS container for MacsAudioStream.
- * Returns false if magic/version/layout is not recognized.
- * outRateHz is set from Paula period when present (NTSC clock / period), else 8000.
- */
bool extractAmigaMxosPcm(const byte *mxos, uint32 mxosSize, Common::Array<byte> &outPcm, uint16 &outRateHz);
} // End of namespace Macs2
diff --git a/engines/macs2/amiga_resources.cpp b/engines/macs2/amiga_resources.cpp
index 62a0eb6175b..565d5b68802 100644
--- a/engines/macs2/amiga_resources.cpp
+++ b/engines/macs2/amiga_resources.cpp
@@ -120,8 +120,6 @@ bool Macs2Engine::loadAmigaSceneBackground(uint32 sceneResourceId) {
blitMap(_shadowMap, shadowMap);
}
- // Hotspot colors (trailer 0x22) + MXCC RLE map (Map2). No 4th 64000 hotspot
- // chunk on Amiga - decodeMxccRunLengthAt expands MXCC to the DOS hotspot map.
_numHotspots = 0;
_hotspotColorTable.clear();
uint16 numHotspots = 0;
@@ -143,20 +141,11 @@ bool Macs2Engine::loadAmigaSceneBackground(uint32 sceneResourceId) {
_backgroundAnimationsBlobs.clear();
_mapImageFileOffset = 0;
_mapSubSceneTableFilePos = 0;
- // Trailer 0x40+0x80 after walk params: native copies to 0024888c/00248954.
- // Demo scenes are all zeros; no other xrefs in this binary - leave
- // _sceneResourceOffsets alone (Amiga loads by archive type/id).
- // MXAA: size word after Map2 is 0 on MM_0004/MM_0040; tickMxaaOverlayAnims
- // has nothing to run until a scene ships a real MXAA blob.
if (amigaMxmmHasMxaaOverlay(mxmm.data(), size)) {
debugC(1, kDebugFilePath, "Amiga: MM_%04u has MXAA overlay data (not loaded yet)",
(uint)sceneResourceId);
}
- // Pathfinding graph + walk depth/speed live in the MXMM trailer (Ghidra
- // load_scene_mxmm @ 00221d90). Without nodes, calculatePath always fails and
- // walkAlongPath cancels with finalDest=current - waitForWalk then completes
- // immediately while the actor is still short of the script target.
_pathfindingPoints.clear();
_numPathfindingPoints = 0;
uint16 numPfPoints = 0;
@@ -178,8 +167,6 @@ bool Macs2Engine::loadAmigaSceneBackground(uint32 sceneResourceId) {
}
}
- // Walk depth/speed percent (Ghidra g_abSceneWalkPaletteParams / DOS 0x51FD..).
- // Leaving base at 0 makes walkAlongPath clamp to 1 px/frame and cancel early.
uint16 walkThreshY = 0, walkScale = 0, walkBasePct = 0, palMode = 0, darken = 0;
if (extractAmigaMxmmSceneWalkParams(mxmm.data(), size, walkThreshY, walkScale, walkBasePct, palMode,
darken)) {
@@ -189,7 +176,6 @@ bool Macs2Engine::loadAmigaSceneBackground(uint32 sceneResourceId) {
_scenePaletteMode = palMode != 0 ? palMode : 1;
_paletteDarkenPercent = darken;
} else {
- // Empty stubs (e.g. MM_0040) and DOS flat rooms use 100/100/100.
_walkDepthThresholdY = 100;
_walkDepthScaleFactor = 100;
_walkBaseSpeedPct = 100;
@@ -197,7 +183,6 @@ bool Macs2Engine::loadAmigaSceneBackground(uint32 sceneResourceId) {
_paletteDarkenPercent = 0;
}
- // Scene script/strings live in the MXMM trailer (not the global scene_table stub).
_amigaPendingSceneScript.clear();
_amigaPendingSceneStrings.clear();
extractAmigaMxmmSceneScript(mxmm.data(), size, _amigaPendingSceneScript, _amigaPendingSceneStrings);
Commit: 0377e6b5b03b4971905ef03b8ffb2b1ed1b9af37
https://github.com/scummvm/scummvm/commit/0377e6b5b03b4971905ef03b8ffb2b1ed1b9af37
Author: Martin Gerhardy (martin.gerhardy at gmail.com)
Date: 2026-08-31T21:14:09+02:00
Commit Message:
MACS2: fixed CID 1685788
Changed paths:
engines/macs2/scriptexecutor.cpp
diff --git a/engines/macs2/scriptexecutor.cpp b/engines/macs2/scriptexecutor.cpp
index ef32d5c2e36..67f9c2bd469 100644
--- a/engines/macs2/scriptexecutor.cpp
+++ b/engines/macs2/scriptexecutor.cpp
@@ -616,11 +616,11 @@ void ScriptExecutor::step() {
// Continue execution
// Check if the currently executing script is at the end
- if (_stream && _stream->pos() >= effectiveScriptEnd()) {
+ if (!_stream || _stream->pos() >= effectiveScriptEnd()) {
syncScriptIsExecutingFlag();
// Binary (runScriptExecutor 1008:e3e7): if script finishes while
// g_wScriptSkippable is still set, treat as error 0x11 and abort.
- if (_scriptSkippable) {
+ if (_stream && _scriptSkippable) {
setScriptError(0x11);
_scriptSkippable = false;
shouldContinue = false;
@@ -648,7 +648,7 @@ void ScriptExecutor::step() {
return;
}
syncScriptIsExecutingFlag();
- if (_stream && _stream->pos() >= effectiveScriptEnd()) {
+ if (_stream->pos() >= effectiveScriptEnd()) {
if (_scriptSkippable) {
setScriptError(0x11);
_scriptSkippable = false;
Commit: eef25e7b2c9b5e56b9595105ff2d6e8c0e6fb21f
https://github.com/scummvm/scummvm/commit/eef25e7b2c9b5e56b9595105ff2d6e8c0e6fb21f
Author: Martin Gerhardy (martin.gerhardy at gmail.com)
Date: 2026-08-31T21:14:09+02:00
Commit Message:
MACS2: replaced magic number
Changed paths:
engines/macs2/amiga_resources.cpp
diff --git a/engines/macs2/amiga_resources.cpp b/engines/macs2/amiga_resources.cpp
index 565d5b68802..8893c5e9355 100644
--- a/engines/macs2/amiga_resources.cpp
+++ b/engines/macs2/amiga_resources.cpp
@@ -37,6 +37,15 @@
namespace Macs2 {
+static void blitMap(Graphics::ManagedSurface &dest, const Common::Array<byte> &src) {
+ if (src.size() != (uint)kScreenWidth * kGameHeight)
+ return;
+ for (int y = 0; y < kGameHeight; y++) {
+ for (int x = 0; x < kScreenWidth; x++)
+ dest.setPixel(x, y, src[(uint)y * kScreenWidth + x]);
+ }
+}
+
bool Macs2Engine::loadAmigaSceneBackground(uint32 sceneResourceId) {
if (!_amigaArchive || sceneResourceId == 0 || sceneResourceId > 0xFFFF)
return false;
@@ -107,14 +116,6 @@ bool Macs2Engine::loadAmigaSceneBackground(uint32 sceneResourceId) {
Common::Array<byte> pathMap, depthMap, shadowMap;
if (extractAmigaMxmmSceneMaps(mxmm.data(), size, pathMap, depthMap, shadowMap)) {
- auto blitMap = [](Graphics::ManagedSurface &dest, const Common::Array<byte> &src) {
- if (src.size() != (uint)kScreenWidth * kGameHeight)
- return;
- for (int y = 0; y < kGameHeight; y++) {
- for (int x = 0; x < kScreenWidth; x++)
- dest.setPixel(x, y, src[(uint)y * kScreenWidth + x]);
- }
- };
blitMap(_pathfindingMap, pathMap);
blitMap(_depthMap, depthMap);
blitMap(_shadowMap, shadowMap);
@@ -555,8 +556,6 @@ void Macs2Engine::readAmigaResources() {
gameObject->_sceneIndex = 0;
gameObject->_orientation = 11;
gameObject->_verticalOffsetScale = 0;
- // MXOO has no DOS +0x185/+0x186 flag bytes. Defaults stay false until we
- // infer character-style rendering below (or a script opcode sets them).
while (gameObject->_blobs.size() < 0x15)
gameObject->_blobs.push_back(Common::Array<uint8>());
@@ -589,7 +588,7 @@ void Macs2Engine::readAmigaResources() {
static const uint16 kAmigaWalkSpeeds[8] = {2, 4, 6, 4, 2, 4, 6, 4};
static const uint8 kMirrorOrientToSource[6][2] = {
{6, 4}, {7, 3}, {8, 2}, {14, 12}, {15, 11}, {16, 10}};
- for (uint i = 0; i < 8; i++) {
+ for (uint i = 0; i < ARRAYSIZE(kAmigaWalkSpeeds); i++) {
if (i < gameObject->_blobWalkSpeeds.size())
gameObject->_blobWalkSpeeds[i] = kAmigaWalkSpeeds[i];
}
@@ -694,8 +693,6 @@ void Macs2Engine::readAmigaResources() {
if (!loadedMxff)
warning("Amiga: no MXFF font FF_0000 in DataA - text may be missing");
- // Border chrome uses fixed slots 30/31/32. Empty/zero-size tiles make
- // drawBorderSide spin forever (no events, frameWait appears stuck).
auto ensureBorderTile = [this](uint index, byte color, uint16 w, uint16 h) {
if (index >= _imageResources.size())
return;
Commit: 2b6c76b41a05efbe6b929d9c6c3a0aeba0968d73
https://github.com/scummvm/scummvm/commit/2b6c76b41a05efbe6b929d9c6c3a0aeba0968d73
Author: Martin Gerhardy (martin.gerhardy at gmail.com)
Date: 2026-08-31T21:14:09+02:00
Commit Message:
MACS2: fixed portrait palette
Changed paths:
engines/macs2/amiga_decode.cpp
engines/macs2/amiga_decode.h
diff --git a/engines/macs2/amiga_decode.cpp b/engines/macs2/amiga_decode.cpp
index 9582047efe6..f524f9e9dc2 100644
--- a/engines/macs2/amiga_decode.cpp
+++ b/engines/macs2/amiga_decode.cpp
@@ -318,12 +318,12 @@ bool convertAmigaPortraitAtlasToDosBlob(const byte *mxoo, uint32 mxooSize, uint3
byte color = 0;
const uint32 bitIndex = (uint32)x & 7;
const uint32 byteInRow = (uint32)x >> 3;
- for (uint16 plane = 0; plane < 5; plane++) {
+ for (uint16 plane = 0; plane < kPlanes; plane++) {
const byte *planeRow = src + plane * planeBytes + (uint32)y * rowBytes;
if (planeRow[byteInRow] & (0x80 >> bitIndex))
color |= (byte)(1 << plane);
}
- atlas[(uint32)y * atlasW + x] = color;
+ atlas[(uint32)y * atlasW + x] = remapAmigaCopperIndexToStableUi(color);
}
}
@@ -380,6 +380,27 @@ static bool decompressPp20ToBuffer(const byte *src, uint32 srcLen, Common::Array
return true;
}
+byte remapAmigaCopperIndexToStableUi(byte color) {
+ if (color == 0)
+ return 0;
+
+ const bool ehb = color >= kAmigaColorRegisterCount;
+ const byte base = ehb ? (byte)(color - kAmigaColorRegisterCount) : color;
+ byte ui;
+ if (base >= 17 && base <= 31)
+ ui = (byte)(0xF0 + (base - 16));
+ else if (base < 16)
+ ui = (byte)(0xF0 + base);
+ else
+ ui = base;
+
+ if (!ehb)
+ return ui;
+ if (ui < 0xF0)
+ return (byte)(kAmigaColorRegisterCount + base);
+ return (byte)(0xE0 + (ui - 0xF0));
+}
+
void amiga12ToVga6(uint16 rgb, byte &r6, byte &g6, byte &b6) {
const byte r4 = (rgb >> 8) & 0xF;
const byte g4 = (rgb >> 4) & 0xF;
diff --git a/engines/macs2/amiga_decode.h b/engines/macs2/amiga_decode.h
index 1f3d6da4943..4c977459457 100644
--- a/engines/macs2/amiga_decode.h
+++ b/engines/macs2/amiga_decode.h
@@ -58,6 +58,7 @@ struct AmigaAnimSlotInfo {
bool parseAmigaMxoo(const byte *mxoo, uint32 mxooSize, AmigaMxooInfo &out);
bool inspectAmigaAnimSlot(const byte *mxoo, uint32 mxooSize, uint32 bodyRelativeOffset, AmigaAnimSlotInfo &out);
void amiga12ToVga6(uint16 rgb, byte &r6, byte &g6, byte &b6);
+byte remapAmigaCopperIndexToStableUi(byte color);
bool decodeAmigaPlanarFrame(const byte *planar, uint16 width, uint16 height, uint16 frameIndex,
uint16 frameCount, Common::Array<byte> &outPixels);
bool convertAmigaAnimSlotToDosBlob(const byte *mxoo, uint32 mxooSize, uint32 bodyRelativeOffset,
Commit: a40f1cdd83f0b3f4f2296d34e2b3fc8a3498c8f9
https://github.com/scummvm/scummvm/commit/a40f1cdd83f0b3f4f2296d34e2b3fc8a3498c8f9
Author: Martin Gerhardy (martin.gerhardy at gmail.com)
Date: 2026-08-31T21:14:25+02:00
Commit Message:
MACS2: amiga palette fixes
Changed paths:
engines/macs2/amiga_decode.cpp
engines/macs2/amiga_decode.h
engines/macs2/amiga_resources.cpp
engines/macs2/macs2.cpp
engines/macs2/macs2.h
engines/macs2/view1.cpp
diff --git a/engines/macs2/amiga_decode.cpp b/engines/macs2/amiga_decode.cpp
index f524f9e9dc2..632da989470 100644
--- a/engines/macs2/amiga_decode.cpp
+++ b/engines/macs2/amiga_decode.cpp
@@ -117,7 +117,7 @@ bool decodeAmigaPlanarFrame(const byte *planar, uint16 width, uint16 height, uin
byte color = 0;
const uint32 bitIndex = (uint32)x & 7;
const uint32 byteInRow = (uint32)x >> 3;
- for (uint16 plane = 0; plane < 5; plane++) {
+ for (uint16 plane = 0; plane < kPlanes; plane++) {
const byte *planeRow = frameBase + plane * planeBytes + (uint32)y * rowBytes;
if (planeRow[byteInRow] & (0x80 >> bitIndex))
color |= (byte)(1 << plane);
@@ -422,8 +422,10 @@ void amiga12ToRgb8(uint16 rgb, byte &r, byte &g, byte &b) {
bool decodeAmigaMxmmSceneBackground(const byte *mxmm, uint32 mxmmSize,
Common::Array<byte> &outPixels,
Graphics::Palette &outPalette,
- uint &outColorCount) {
+ uint &outColorCount,
+ Common::Array<byte> &outLineCopperPal) {
outPixels.clear();
+ outLineCopperPal.clear();
outColorCount = 0;
outPalette = Graphics::Palette(Graphics::PALETTE_COUNT);
if (!mxmm || mxmmSize < kAmigaMxmmMinSize)
@@ -524,7 +526,7 @@ bool decodeAmigaMxmmSceneBackground(const byte *mxmm, uint32 mxmmSize,
byte outIdx;
if (colorToIndex.contains(key)) {
outIdx = colorToIndex[key];
- } else if (outColorCount < 256) {
+ } else if (outColorCount < 0xE0) {
outIdx = (byte)outColorCount;
colorToIndex[key] = outIdx;
outPalette.set(outIdx, r, g, b);
@@ -536,6 +538,33 @@ bool decodeAmigaMxmmSceneBackground(const byte *mxmm, uint32 mxmmSize,
}
}
+ auto internRgb = [&](byte r, byte g, byte b, byte fallback) -> byte {
+ const uint32 key = ((uint32)r << 16) | ((uint32)g << 8) | b;
+ if (colorToIndex.contains(key))
+ return colorToIndex[key];
+ if (outColorCount < 0xE0) {
+ const byte outIdx = (byte)outColorCount;
+ colorToIndex[key] = outIdx;
+ outPalette.set(outIdx, r, g, b);
+ outColorCount++;
+ return outIdx;
+ }
+ return fallback;
+ };
+
+ outLineCopperPal.resize((uint)kAmigaSceneHeight * kAmigaEhbPaletteCount);
+ for (uint16 y = 0; y < kAmigaSceneHeight; y++) {
+ byte pal32[kAmigaColorRegisterCount][3];
+ buildPal32(y, pal32);
+ for (uint c = 0; c < kAmigaColorRegisterCount; c++) {
+ outLineCopperPal[(uint32)y * kAmigaEhbPaletteCount + c] =
+ internRgb(pal32[c][0], pal32[c][1], pal32[c][2], (byte)c);
+ outLineCopperPal[(uint32)y * kAmigaEhbPaletteCount + kAmigaColorRegisterCount + c] =
+ internRgb((byte)(pal32[c][0] / 2), (byte)(pal32[c][1] / 2), (byte)(pal32[c][2] / 2),
+ (byte)(kAmigaColorRegisterCount + c));
+ }
+ }
+
return outColorCount > 0;
}
diff --git a/engines/macs2/amiga_decode.h b/engines/macs2/amiga_decode.h
index 4c977459457..df2cd582cd8 100644
--- a/engines/macs2/amiga_decode.h
+++ b/engines/macs2/amiga_decode.h
@@ -121,7 +121,8 @@ enum : uint32 {
bool decodeAmigaMxmmSceneBackground(const byte *mxmm, uint32 mxmmSize,
Common::Array<byte> &outPixels,
Graphics::Palette &outPalette,
- uint &outColorCount);
+ uint &outColorCount,
+ Common::Array<byte> &outLineCopperPal);
bool extractAmigaMxmmSceneScript(const byte *mxmm, uint32 mxmmSize,
Common::Array<byte> &outScript,
diff --git a/engines/macs2/amiga_resources.cpp b/engines/macs2/amiga_resources.cpp
index 8893c5e9355..6521eb8da1e 100644
--- a/engines/macs2/amiga_resources.cpp
+++ b/engines/macs2/amiga_resources.cpp
@@ -66,7 +66,8 @@ bool Macs2Engine::loadAmigaSceneBackground(uint32 sceneResourceId) {
Common::Array<byte> pixels;
Graphics::Palette paletteRgb(Graphics::PALETTE_COUNT);
uint colorCount = 0;
- if (!decodeAmigaMxmmSceneBackground(mxmm.data(), size, pixels, paletteRgb, colorCount))
+ Common::Array<byte> lineCopperPal;
+ if (!decodeAmigaMxmmSceneBackground(mxmm.data(), size, pixels, paletteRgb, colorCount, lineCopperPal))
return false;
if (pixels.size() != (uint)kScreenWidth * kGameHeight || colorCount == 0)
return false;
@@ -102,6 +103,7 @@ bool Macs2Engine::loadAmigaSceneBackground(uint32 sceneResourceId) {
}
}
_amigaNativePlayfieldPalette = true;
+ _amigaLineCopperPal = Common::move(lineCopperPal);
// Portraits share playfield COLOR17..31 (copper high bank). Native copper
// already filled those slots; installAmigaPortraitPalette is a no-op here.
installAmigaPortraitPalette(true);
@@ -349,34 +351,28 @@ bool Macs2Engine::loadAmigaCursorResource(uint16 resourceId, AnimFrame &out) {
}
void Macs2Engine::installAmigaPortraitPalette(bool copyFromPlayfield) {
- // Portraits now keep Amiga COLOR indices and share the playfield copper
- // high bank (COLOR17..31), matching animateDialoguePortrait on hardware.
- // Demo OO_* atlases never touch COLOR01..16.
- //
- // When native copper is not resident (pre-scene), seed
- // COLOR17..31 from MXIN chrome so portraits are not left on the provisional
- // ramp from applyAmigaUiPalette. copyFromPlayfield is ignored - live copper
- // already owns those slots after loadAmigaSceneBackground.
- (void)copyFromPlayfield;
-
- if (_amigaNativePlayfieldPalette)
- return;
-
static const uint16 kHighBankFallback[15] = {
0x0BBA, 0x0EB8, 0x0C96, 0x0A74, 0x0963, 0x0741, 0x0000, 0x049E,
0x0C00, 0x0DDC, 0x0EEE, 0x0887, 0x0776, 0x0006, 0x0520
};
- // Copper base16: COLOR00 + COLOR17..31 == MXIN ui[0..15].
const uint16 *highSrc = kHighBankFallback;
if (_amigaArchive && _amigaArchive->getInfo().loaded)
highSrc = &_amigaArchive->getInfo().uiPaletteAmiga[1];
- for (uint i = 0; i < ARRAYSIZE(kHighBankFallback); i++) {
+ if (!_amigaNativePlayfieldPalette) {
+ // Copper base16: COLOR00 + COLOR17..31 == MXIN ui[0..15].
+ for (uint i = 0; i < ARRAYSIZE(kHighBankFallback); i++) {
+ byte r6, g6, b6;
+ amiga12ToVga6(highSrc[i], r6, g6, b6);
+ _palVanilla.set(17 + i, r6, g6, b6);
+ }
+ }
+
+ for (uint i = 0; i < 16; i++) {
byte r6, g6, b6;
- amiga12ToVga6(highSrc[i], r6, g6, b6);
- const uint idx = 17 + i;
- _palVanilla.set(idx, r6, g6, b6);
+ _palVanilla.get(0xF0 + i, r6, g6, b6);
+ _palVanilla.set(0xE0 + i, (byte)(r6 / 2), (byte)(g6 / 2), (byte)(b6 / 2));
}
}
@@ -386,9 +382,6 @@ void Macs2Engine::applyAmigaUiPalette() {
const AmigaInfoData &info = _amigaArchive->getInfo();
- // Provisional playfield until an MM_* copper list is loaded.
- // Copper base16 layout: COLOR00 + COLOR17..31 = MXIN ui[0..15]. COLOR01..16
- // stay a visible ramp (overwritten per-line by scene copper).
byte r6, g6, b6;
amiga12ToVga6(info.uiPaletteAmiga[0], r6, g6, b6);
_palVanilla.set(0, 0, 0, 0);
@@ -410,6 +403,7 @@ void Macs2Engine::applyAmigaUiPalette() {
_palVanilla.set(idx, r6, g6, b6);
}
_amigaNativePlayfieldPalette = false;
+ _amigaLineCopperPal.clear();
installAmigaPortraitPalette(false);
buildAmigaPanelRemapTable();
applyPaletteDarkening();
diff --git a/engines/macs2/macs2.cpp b/engines/macs2/macs2.cpp
index 3e4f3dfc18d..d018cb6e924 100644
--- a/engines/macs2/macs2.cpp
+++ b/engines/macs2/macs2.cpp
@@ -3697,9 +3697,15 @@ void Macs2Engine::applyPaletteDarkening() {
for (uint i = 0; i < Graphics::PALETTE_COUNT; i++) {
byte r, g, b;
_palVanilla.get(i, r, g, b);
- _pal.set(i, (byte)((r * brightnessFactor / 100 * 259 + 33) >> 6),
- (byte)((g * brightnessFactor / 100 * 259 + 33) >> 6),
- (byte)((b * brightnessFactor / 100 * 259 + 33) >> 6));
+ if (isAmiga()) {
+ _pal.set(i, (byte)((r * brightnessFactor / 100 * 255) / 63),
+ (byte)((g * brightnessFactor / 100 * 255) / 63),
+ (byte)((b * brightnessFactor / 100 * 255) / 63));
+ } else {
+ _pal.set(i, (byte)((r * brightnessFactor / 100 * 259 + 33) >> 6),
+ (byte)((g * brightnessFactor / 100 * 259 + 33) >> 6),
+ (byte)((b * brightnessFactor / 100 * 259 + 33) >> 6));
+ }
}
}
diff --git a/engines/macs2/macs2.h b/engines/macs2/macs2.h
index 6c471e31fac..80366d91055 100644
--- a/engines/macs2/macs2.h
+++ b/engines/macs2/macs2.h
@@ -548,6 +548,7 @@ public:
uint16 amigaTextLinePitch = 0;
/** True after loadAmigaSceneBackground installed copper colors in 0..31. */
bool _amigaNativePlayfieldPalette = false;
+ Common::Array<byte> _amigaLineCopperPal;
/** Amiga DataA/Mdir archive (owned). Null on DOS. */
Macs2AmigaArchive *_amigaArchive = nullptr;
/** Filled by loadAmigaSceneBackground; consumed by Amiga changeScene. */
diff --git a/engines/macs2/view1.cpp b/engines/macs2/view1.cpp
index 30ac21bf633..57a2691fdc4 100644
--- a/engines/macs2/view1.cpp
+++ b/engines/macs2/view1.cpp
@@ -33,6 +33,7 @@
#include "macs2/debugtools.h"
#include "macs2/detection.h"
#include "macs2/gameobjects.h"
+#include "macs2/amiga_decode.h"
#include "macs2/macs2.h"
#include "macs2/music.h"
#include "macs2/actionbar.h"
@@ -66,6 +67,110 @@ void resetObjectDrawBounds(GameObject *obj) {
obj->resetDrawBounds();
}
+void plotAmigaUiPixel(Graphics::ManagedSurface &s, int x, int y, byte color) {
+ if (x < 0 || y < 0 || x >= s.w || y >= s.h)
+ return;
+ s.setPixel(x, y, color);
+}
+
+void drawAmigaUiLine(Graphics::ManagedSurface &s, int x0, int y0, int x1, int y1, byte color) {
+ int dx = ABS(x1 - x0);
+ int sx = x0 < x1 ? 1 : -1;
+ int dy = -ABS(y1 - y0);
+ int sy = y0 < y1 ? 1 : -1;
+ int err = dx + dy;
+ for (;;) {
+ plotAmigaUiPixel(s, x0, y0, color);
+ if (x0 == x1 && y0 == y1)
+ break;
+ const int e2 = 2 * err;
+ if (e2 >= dy) {
+ err += dy;
+ x0 += sx;
+ }
+ if (e2 <= dx) {
+ err += dx;
+ y0 += sy;
+ }
+ }
+}
+
+byte amigaPanelBorderColor(int tableIndex) {
+ uint16 copper = (uint16)(18 + tableIndex);
+ if (g_engine->_amigaArchive && g_engine->_amigaArchive->getInfo().loaded) {
+ const uint16 *idx = g_engine->_amigaArchive->getInfo().panelBorderColorIndices;
+ if (tableIndex >= 0 && tableIndex < 5 && idx[tableIndex] != 0)
+ copper = idx[tableIndex];
+ }
+ return remapAmigaCopperIndexToStableUi((byte)copper);
+}
+
+void fillAmigaPanelInterior(Graphics::ManagedSurface &s, int x, int y, int w, int h) {
+ // UAE SOLID panels are opaque dark wood, not scene-through SHADE (that
+ // would be sky-blue at the top of the screen). Pattern sheet BSS is zero;
+ // COLOR21/22 (963/741) EHB matches the emulator interior (~78,51,21).
+ const byte a = remapAmigaCopperIndexToStableUi(21);
+ const byte bCol = remapAmigaCopperIndexToStableUi(22);
+ byte ehbA = a;
+ byte ehbB = bCol;
+ if (a >= 0xF0)
+ ehbA = (byte)(0xE0 + (a - 0xF0));
+ if (bCol >= 0xF0)
+ ehbB = (byte)(0xE0 + (bCol - 0xF0));
+ for (int oy = 0; oy < h; oy++) {
+ for (int ox = 0; ox < w; ox++)
+ plotAmigaUiPixel(s, x + ox, y + oy, ((ox ^ oy) & 1) ? ehbA : ehbB);
+ }
+}
+
+void drawAmigaPanelBevel(Graphics::ManagedSurface &s, int x, int y, int w, int h) {
+ const byte c0 = amigaPanelBorderColor(0);
+ const byte c1 = amigaPanelBorderColor(1);
+ const byte c2 = amigaPanelBorderColor(2);
+ const byte c3 = amigaPanelBorderColor(3);
+ const byte c4 = amigaPanelBorderColor(4);
+ const int midX = x + (w >> 1);
+ const int midY = y + (h >> 1);
+
+ drawAmigaUiLine(s, x, y, midX, y, c0);
+ drawAmigaUiLine(s, x, y + h, midX, y + h, c3);
+ drawAmigaUiLine(s, midX, y, x + w, y, c1);
+ drawAmigaUiLine(s, midX, y + h, x + w, y + h, c4);
+ drawAmigaUiLine(s, x, y, x, midY, c0);
+ drawAmigaUiLine(s, x + w, y, x + w, midY, c3);
+ drawAmigaUiLine(s, x, midY, x, y + h, c1);
+ drawAmigaUiLine(s, x + w, midY, x + w, y + h + 1, c4);
+
+ plotAmigaUiPixel(s, x + w, y, c2);
+ plotAmigaUiPixel(s, x, y + h, c2);
+ plotAmigaUiPixel(s, midX + 2, y, c0);
+ plotAmigaUiPixel(s, midX + 2, y + h, c3);
+ plotAmigaUiPixel(s, x, midY + 2, c0);
+ plotAmigaUiPixel(s, x + w, midY + 2, c3);
+}
+
+void drawAmigaUiPanel(const Common::Point &pos, const Common::Point &size, Graphics::ManagedSurface &s) {
+ const int x = pos.x;
+ const int y = pos.y;
+ const int w = size.x - 1;
+ const int h = size.y - 1;
+ if (w < 1 || h < 1)
+ return;
+ fillAmigaPanelInterior(s, x, y, w, h);
+ drawAmigaPanelBevel(s, x, y, w, h);
+}
+
+byte remapAmigaPlayfieldIndex(byte val, int y) {
+ if (val >= kAmigaEhbPaletteCount)
+ return val;
+ const Common::Array<byte> &map = g_engine->_amigaLineCopperPal;
+ if (map.size() < (uint)kAmigaSceneHeight * kAmigaEhbPaletteCount)
+ return val;
+ if (y < 0 || y >= (int)kAmigaSceneHeight)
+ return val;
+ return map[(uint)y * kAmigaEhbPaletteCount + val];
+}
+
// Build a screen-clipped erase rect from the previous frame's sprite bounds.
// Returns false when there is nothing on-screen to erase.
bool buildClippedEraseRect(int32 left, int32 top, uint16 width, uint16 height,
@@ -628,17 +733,20 @@ void View1::drawCurrentSpeaker(Graphics::ManagedSurface &s) {
AnimFrame *leftPortrait = currentSpeechActData.speaker->getCurrentPortrait(false, 0);
AnimFrame *rightPortrait = currentSpeechActData.speaker->getCurrentPortrait(true, 0);
- // See l0037_B462: for the calculations below
- // Draw the border
- const int portraitWidth = MAX<int>(leftPortrait ? leftPortrait->_width : 0, rightPortrait ? rightPortrait->_width : 0);
- const int portraitHeight = MAX<int>(leftPortrait ? leftPortrait->_height : 0, rightPortrait ? rightPortrait->_height : 0);
- const int borderPad = g_engine->portraitBorderPad();
- const int contentInset = g_engine->portraitContentInset();
- const Common::Point borderSize(portraitWidth + borderPad, portraitHeight + borderPad);
- drawBorder(currentSpeechActData.position, borderSize, s);
-
- // Draw the portrait over the border
- Common::Point pos = currentSpeechActData.position + Common::Point(contentInset, contentInset);
+ Common::Point pos = currentSpeechActData.position;
+ if (g_engine->isAmiga()) {
+ drawAmigaUiPanel(currentSpeechActData.position,
+ Common::Point(frame->_width + 2, frame->_height + 2), s);
+ pos += Common::Point(1, 1);
+ } else {
+ const int portraitWidth = MAX<int>(leftPortrait ? leftPortrait->_width : 0, rightPortrait ? rightPortrait->_width : 0);
+ const int portraitHeight = MAX<int>(leftPortrait ? leftPortrait->_height : 0, rightPortrait ? rightPortrait->_height : 0);
+ const int borderPad = g_engine->portraitBorderPad();
+ const int contentInset = g_engine->portraitContentInset();
+ const Common::Point borderSize(portraitWidth + borderPad, portraitHeight + borderPad);
+ drawBorder(currentSpeechActData.position, borderSize, s);
+ pos += Common::Point(contentInset, contentInset);
+ }
drawSprite(pos, frame->_width, frame->_height, frame->_data.data(), s, false);
delete frame;
delete leftPortrait;
@@ -3075,7 +3183,9 @@ void View1::drawSprite(int16 x, int16 y, uint16 width, uint16 height, byte *data
if (g_engine->_depthMap.getPixel(finalX, finalY) >= depth)
continue;
}
- s.setPixel(x + actualX, y + currentY, val);
+ if (g_engine->isAmiga())
+ val = remapAmigaPlayfieldIndex(val, finalY);
+ s.setPixel(finalX, finalY, val);
}
}
}
@@ -3097,8 +3207,11 @@ void View1::drawSpriteClipped(uint16 x, uint16 y, Common::Rect &clippingRect, ui
if (val != 0) {
const int px = x + currentX;
const int py = y + currentY;
- if (clippingRect.contains(px, py) && px < s.w && py < s.h)
+ if (clippingRect.contains(px, py) && px < s.w && py < s.h) {
+ if (g_engine->isAmiga())
+ val = remapAmigaPlayfieldIndex(val, py);
s.setPixel(px, py, val);
+ }
}
}
}
@@ -3146,7 +3259,7 @@ void View1::drawSpriteFitted(const Common::Rect &bounds, const Sprite &sprite, G
if (px < inner.left || px >= inner.right || px < 0 || px >= s.w || py < 0 || py >= s.h)
continue;
- s.setPixel(px, py, val);
+ s.setPixel(px, py, g_engine->isAmiga() ? remapAmigaPlayfieldIndex(val, py) : val);
}
}
}
@@ -3199,8 +3312,10 @@ void View1::drawSpriteScaled(int shadingTableOffset, uint8 depthThreshold, int16
const uint8 color = srcPixels[srcRow + srcX];
if (color != 0) {
const byte bg = s.getPixel(screenX, screenY);
- s.setPixel(screenX, screenY,
- applyShadingTable(color, shadingTableOffset, bg, useMaskedShading));
+ byte drawn = applyShadingTable(color, shadingTableOffset, bg, useMaskedShading);
+ if (g_engine->isAmiga())
+ drawn = remapAmigaPlayfieldIndex(drawn, screenY);
+ s.setPixel(screenX, screenY, drawn);
}
}
}
@@ -3235,8 +3350,10 @@ void View1::drawSpriteTransparent(int shadingTableOffset, uint8 depthThreshold,
if (color != 0 && screenX >= 0 && screenX < s.w &&
g_engine->_depthMap.getPixel(screenX, screenY) < depthThreshold) {
const byte bg = s.getPixel(screenX, screenY);
- s.setPixel(screenX, screenY,
- applyShadingTable(color, shadingTableOffset, bg, useMaskedShading));
+ byte drawn = applyShadingTable(color, shadingTableOffset, bg, useMaskedShading);
+ if (g_engine->isAmiga())
+ drawn = remapAmigaPlayfieldIndex(drawn, screenY);
+ s.setPixel(screenX, screenY, drawn);
}
screenX++;
@@ -3375,9 +3492,14 @@ void View1::drawNinePatchBorder(const Common::Point &pos, const Common::Point &s
void View1::drawBorder(const Common::Point &pos, const Common::Point &size, Graphics::ManagedSurface &s) {
// fn0037_A65D proc
- constexpr uint16 border = 6;
debugC(kDebugScript, "Render border: pos=(%d,%d) size=(%d,%d)", pos.x, pos.y, size.x, size.y);
+ if (g_engine->isAmiga()) {
+ drawAmigaUiPanel(pos, size, s);
+ return;
+ }
+
+ constexpr uint16 border = 6;
drawDarkRectangle(pos.x + 1, pos.y + 1, size.x - 1, size.y - 1);
// Four textured border sides
Commit: ca4601c2ec2160e7df74d2228cdf884ae84ab1d6
https://github.com/scummvm/scummvm/commit/ca4601c2ec2160e7df74d2228cdf884ae84ab1d6
Author: Martin Gerhardy (martin.gerhardy at gmail.com)
Date: 2026-08-31T21:21:05+02:00
Commit Message:
MACS2: reduced code duplication
Changed paths:
engines/macs2/view1.cpp
diff --git a/engines/macs2/view1.cpp b/engines/macs2/view1.cpp
index 57a2691fdc4..f247e503480 100644
--- a/engines/macs2/view1.cpp
+++ b/engines/macs2/view1.cpp
@@ -160,15 +160,14 @@ void drawAmigaUiPanel(const Common::Point &pos, const Common::Point &size, Graph
drawAmigaPanelBevel(s, x, y, w, h);
}
-byte remapAmigaPlayfieldIndex(byte val, int y) {
- if (val >= kAmigaEhbPaletteCount)
- return val;
- const Common::Array<byte> &map = g_engine->_amigaLineCopperPal;
- if (map.size() < (uint)kAmigaSceneHeight * kAmigaEhbPaletteCount)
- return val;
- if (y < 0 || y >= (int)kAmigaSceneHeight)
- return val;
- return map[(uint)y * kAmigaEhbPaletteCount + val];
+void setPixel(Graphics::ManagedSurface &s, int x, int y, byte color) {
+ if (g_engine->isAmiga() && color < kAmigaEhbPaletteCount &&
+ y >= 0 && y < (int)kAmigaSceneHeight) {
+ const Common::Array<byte> &map = g_engine->_amigaLineCopperPal;
+ if (map.size() >= (uint)kAmigaSceneHeight * kAmigaEhbPaletteCount)
+ color = map[(uint)y * kAmigaEhbPaletteCount + color];
+ }
+ s.setPixel(x, y, color);
}
// Build a screen-clipped erase rect from the previous frame's sprite bounds.
@@ -3183,9 +3182,7 @@ void View1::drawSprite(int16 x, int16 y, uint16 width, uint16 height, byte *data
if (g_engine->_depthMap.getPixel(finalX, finalY) >= depth)
continue;
}
- if (g_engine->isAmiga())
- val = remapAmigaPlayfieldIndex(val, finalY);
- s.setPixel(finalX, finalY, val);
+ setPixel(s, finalX, finalY, val);
}
}
}
@@ -3207,11 +3204,8 @@ void View1::drawSpriteClipped(uint16 x, uint16 y, Common::Rect &clippingRect, ui
if (val != 0) {
const int px = x + currentX;
const int py = y + currentY;
- if (clippingRect.contains(px, py) && px < s.w && py < s.h) {
- if (g_engine->isAmiga())
- val = remapAmigaPlayfieldIndex(val, py);
- s.setPixel(px, py, val);
- }
+ if (clippingRect.contains(px, py) && px < s.w && py < s.h)
+ setPixel(s, px, py, val);
}
}
}
@@ -3259,7 +3253,7 @@ void View1::drawSpriteFitted(const Common::Rect &bounds, const Sprite &sprite, G
if (px < inner.left || px >= inner.right || px < 0 || px >= s.w || py < 0 || py >= s.h)
continue;
- s.setPixel(px, py, g_engine->isAmiga() ? remapAmigaPlayfieldIndex(val, py) : val);
+ setPixel(s, px, py, val);
}
}
}
@@ -3312,10 +3306,7 @@ void View1::drawSpriteScaled(int shadingTableOffset, uint8 depthThreshold, int16
const uint8 color = srcPixels[srcRow + srcX];
if (color != 0) {
const byte bg = s.getPixel(screenX, screenY);
- byte drawn = applyShadingTable(color, shadingTableOffset, bg, useMaskedShading);
- if (g_engine->isAmiga())
- drawn = remapAmigaPlayfieldIndex(drawn, screenY);
- s.setPixel(screenX, screenY, drawn);
+ setPixel(s, screenX, screenY, applyShadingTable(color, shadingTableOffset, bg, useMaskedShading));
}
}
}
@@ -3350,10 +3341,7 @@ void View1::drawSpriteTransparent(int shadingTableOffset, uint8 depthThreshold,
if (color != 0 && screenX >= 0 && screenX < s.w &&
g_engine->_depthMap.getPixel(screenX, screenY) < depthThreshold) {
const byte bg = s.getPixel(screenX, screenY);
- byte drawn = applyShadingTable(color, shadingTableOffset, bg, useMaskedShading);
- if (g_engine->isAmiga())
- drawn = remapAmigaPlayfieldIndex(drawn, screenY);
- s.setPixel(screenX, screenY, drawn);
+ setPixel(s, screenX, screenY, applyShadingTable(color, shadingTableOffset, bg, useMaskedShading));
}
screenX++;
Commit: a307f3d6cb00f4d6ea454efa6cb2f17225ca9028
https://github.com/scummvm/scummvm/commit/a307f3d6cb00f4d6ea454efa6cb2f17225ca9028
Author: Martin Gerhardy (martin.gerhardy at gmail.com)
Date: 2026-08-31T21:42:47+02:00
Commit Message:
MACS2: amiga cleanup
Changed paths:
engines/macs2/view1.cpp
diff --git a/engines/macs2/view1.cpp b/engines/macs2/view1.cpp
index f247e503480..64e8f09be54 100644
--- a/engines/macs2/view1.cpp
+++ b/engines/macs2/view1.cpp
@@ -67,20 +67,20 @@ void resetObjectDrawBounds(GameObject *obj) {
obj->resetDrawBounds();
}
-void plotAmigaUiPixel(Graphics::ManagedSurface &s, int x, int y, byte color) {
+void setPixelClipped(Graphics::ManagedSurface &s, int x, int y, byte color) {
if (x < 0 || y < 0 || x >= s.w || y >= s.h)
return;
s.setPixel(x, y, color);
}
-void drawAmigaUiLine(Graphics::ManagedSurface &s, int x0, int y0, int x1, int y1, byte color) {
+void drawLine(Graphics::ManagedSurface &s, int x0, int y0, int x1, int y1, byte color) {
int dx = ABS(x1 - x0);
int sx = x0 < x1 ? 1 : -1;
int dy = -ABS(y1 - y0);
int sy = y0 < y1 ? 1 : -1;
int err = dx + dy;
for (;;) {
- plotAmigaUiPixel(s, x0, y0, color);
+ setPixelClipped(s, x0, y0, color);
if (x0 == x1 && y0 == y1)
break;
const int e2 = 2 * err;
@@ -105,10 +105,17 @@ byte amigaPanelBorderColor(int tableIndex) {
return remapAmigaCopperIndexToStableUi((byte)copper);
}
-void fillAmigaPanelInterior(Graphics::ManagedSurface &s, int x, int y, int w, int h) {
- // UAE SOLID panels are opaque dark wood, not scene-through SHADE (that
- // would be sky-blue at the top of the screen). Pattern sheet BSS is zero;
- // COLOR21/22 (963/741) EHB matches the emulator interior (~78,51,21).
+void drawAmigaUiPanel(const Common::Point &pos, const Common::Point &size, Graphics::ManagedSurface &s) {
+ const int x = pos.x;
+ const int y = pos.y;
+ const int w = size.x - 1;
+ const int h = size.y - 1;
+ if (w < 1 || h < 1) {
+ return;
+ }
+
+ // background
+ // TODO: the pattern is wrong
const byte a = remapAmigaCopperIndexToStableUi(21);
const byte bCol = remapAmigaCopperIndexToStableUi(22);
byte ehbA = a;
@@ -118,46 +125,27 @@ void fillAmigaPanelInterior(Graphics::ManagedSurface &s, int x, int y, int w, in
if (bCol >= 0xF0)
ehbB = (byte)(0xE0 + (bCol - 0xF0));
for (int oy = 0; oy < h; oy++) {
- for (int ox = 0; ox < w; ox++)
- plotAmigaUiPixel(s, x + ox, y + oy, ((ox ^ oy) & 1) ? ehbA : ehbB);
+ for (int ox = 0; ox < w; ox++) {
+ setPixelClipped(s, x + ox, y + oy, ((ox ^ oy) & 1) ? ehbA : ehbB);
+ }
}
-}
-void drawAmigaPanelBevel(Graphics::ManagedSurface &s, int x, int y, int w, int h) {
+ // borders
const byte c0 = amigaPanelBorderColor(0);
const byte c1 = amigaPanelBorderColor(1);
- const byte c2 = amigaPanelBorderColor(2);
- const byte c3 = amigaPanelBorderColor(3);
- const byte c4 = amigaPanelBorderColor(4);
+ const byte c2 = amigaPanelBorderColor(3);
+ const byte c3 = amigaPanelBorderColor(4);
const int midX = x + (w >> 1);
const int midY = y + (h >> 1);
- drawAmigaUiLine(s, x, y, midX, y, c0);
- drawAmigaUiLine(s, x, y + h, midX, y + h, c3);
- drawAmigaUiLine(s, midX, y, x + w, y, c1);
- drawAmigaUiLine(s, midX, y + h, x + w, y + h, c4);
- drawAmigaUiLine(s, x, y, x, midY, c0);
- drawAmigaUiLine(s, x + w, y, x + w, midY, c3);
- drawAmigaUiLine(s, x, midY, x, y + h, c1);
- drawAmigaUiLine(s, x + w, midY, x + w, y + h + 1, c4);
-
- plotAmigaUiPixel(s, x + w, y, c2);
- plotAmigaUiPixel(s, x, y + h, c2);
- plotAmigaUiPixel(s, midX + 2, y, c0);
- plotAmigaUiPixel(s, midX + 2, y + h, c3);
- plotAmigaUiPixel(s, x, midY + 2, c0);
- plotAmigaUiPixel(s, x + w, midY + 2, c3);
-}
-
-void drawAmigaUiPanel(const Common::Point &pos, const Common::Point &size, Graphics::ManagedSurface &s) {
- const int x = pos.x;
- const int y = pos.y;
- const int w = size.x - 1;
- const int h = size.y - 1;
- if (w < 1 || h < 1)
- return;
- fillAmigaPanelInterior(s, x, y, w, h);
- drawAmigaPanelBevel(s, x, y, w, h);
+ drawLine(s, x, y, midX, y, c0);
+ drawLine(s, x, y + h, midX, y + h, c2);
+ drawLine(s, midX, y, x + w, y, c1);
+ drawLine(s, midX, y + h, x + w, y + h, c3);
+ drawLine(s, x, y, x, midY, c0);
+ drawLine(s, x + w, y, x + w, midY, c2);
+ drawLine(s, x, midY, x, y + h, c1);
+ drawLine(s, x + w, midY, x + w, y + h, c3);
}
void setPixel(Graphics::ManagedSurface &s, int x, int y, byte color) {
@@ -177,8 +165,6 @@ bool buildClippedEraseRect(int32 left, int32 top, uint16 width, uint16 height,
if (width == 0 && height == 0)
return false;
- // drawAllCharacters @ 1008:90a2: dirty right/bottom are inclusive (+1 padding),
- // Common::Rect uses exclusive right/bottom (+1 more).
const int32 exclRight = left + (int32)width + 2;
const int32 exclBottom = top + (int32)height + 2;
if (exclRight <= 0 || exclBottom <= 0 || left >= screenW || top >= screenH)
@@ -194,7 +180,6 @@ bool buildClippedEraseRect(int32 left, int32 top, uint16 width, uint16 height,
if (clipLeft < -32768 || clipTop < -32768 || clipRight > 32767 || clipBottom > 32767)
return false;
- // Avoid Common::Rect(x1,y1,x2,y2) constructor (asserts on invalid input).
out.left = (int16)clipLeft;
out.top = (int16)clipTop;
out.right = (int16)clipRight;
@@ -355,7 +340,7 @@ void View1::openInventory(GameObject *newInventorySource) {
}
setInventorySource(newInventorySource);
- _pendingPanelRequest = kPanelRequestNone; // Binary: g_wPendingPanelRequest = 0
+ _pendingPanelRequest = kPanelRequestNone;
// SCUMM verb UI: protagonist inventory is always visible in the strip.
if (hasPersistentActionBar() && newInventorySource->_index == Scenes::instance()._currentActorIndex) {
@@ -373,7 +358,6 @@ void View1::openInventory(GameObject *newInventorySource) {
_activeInventoryItem = nullptr;
g_engine->_scriptExecutor->_inventoryActionFlag = false;
g_engine->_scriptExecutor->_inventoryCombineFlag = false;
- // Binary drawProtagonistInventoryPanel (1008:45aa): unconditionally calls setCursorMode(0x15)
g_engine->setCursorMode(Script::MouseMode::Use);
updateCursor();
redraw();
@@ -651,7 +635,6 @@ AnimFrame *View1::getInventoryIcon(GameObject *gameObject) {
}
void View1::drawDarkRectangle(uint16 x, uint16 y, uint16 width, uint16 height) {
- // drawAnimFrameScaled @ 1010:1399: remap each background pixel through per-scene 256-byte table
Graphics::ManagedSurface s = getSurface();
for (uint16 xOffset = 0; xOffset < width; xOffset++) {
for (uint16 yOffset = 0; yOffset < height; yOffset++) {
@@ -733,11 +716,7 @@ void View1::drawCurrentSpeaker(Graphics::ManagedSurface &s) {
AnimFrame *rightPortrait = currentSpeechActData.speaker->getCurrentPortrait(true, 0);
Common::Point pos = currentSpeechActData.position;
- if (g_engine->isAmiga()) {
- drawAmigaUiPanel(currentSpeechActData.position,
- Common::Point(frame->_width + 2, frame->_height + 2), s);
- pos += Common::Point(1, 1);
- } else {
+ if (!g_engine->isAmiga()) {
const int portraitWidth = MAX<int>(leftPortrait ? leftPortrait->_width : 0, rightPortrait ? rightPortrait->_width : 0);
const int portraitHeight = MAX<int>(leftPortrait ? leftPortrait->_height : 0, rightPortrait ? rightPortrait->_height : 0);
const int borderPad = g_engine->portraitBorderPad();
@@ -894,9 +873,6 @@ void View1::drawOverlayTextEntries() {
}
void View1::showStringBox(const Common::StringArray &sa) {
- // This calculation can be found at l0037_B368:
- // int borderWidth = 10;
- // int padding = 3;
const int padW = g_engine->dialogPadW();
const int padH = g_engine->dialogPadH();
const int textInset = g_engine->dialogTextInset();
@@ -910,7 +886,6 @@ void View1::showStringBox(const Common::StringArray &sa) {
Graphics::ManagedSurface s = getSurface();
drawBorder(_stringBoxPosition, Common::Point(totalWidth, totalHeight), s);
- // TODO range based
int lineOffset = _stringBoxPosition.y + textInset;
for (auto iter = sa.begin(); iter < sa.end(); iter++) {
logRenderedText("TextBox", _stringBoxPosition.x + textInset, lineOffset, *iter);
More information about the Scummvm-git-logs
mailing list