[Scummvm-git-logs] scummvm master -> e502b122c9064d2811274316972dcd94cb439ef4
neuromancer
noreply at scummvm.org
Fri Jun 26 18:38:01 UTC 2026
This automated email contains information about 7 new commits which have been
pushed to the 'scummvm' repo located at https://api.github.com/repos/scummvm/scummvm .
Summary:
d0ed0e9810 EEM: proper selection of clues in EEM2 Mac
ee8eee5869 EEM: improved setup screen in EEM2 Mac
4ec3f34aec EEM: implemented approach screens in EEM2 Mac
4762d3458d EEM: refactored ui.cpp into two files
2e596e7adb EEM: refactored map related code into several functions
4db53b24af EEM: refactored coordinate handling
e502b122c9 EEM: fixed regression in EEM2 DOS intro
Commit: d0ed0e98100f2075a5b00145b84f53930e9e1ca2
https://github.com/scummvm/scummvm/commit/d0ed0e98100f2075a5b00145b84f53930e9e1ca2
Author: neuromancer (gustavo.grieco at gmail.com)
Date: 2026-06-26T20:37:27+02:00
Commit Message:
EEM: proper selection of clues in EEM2 Mac
Changed paths:
engines/eem/clues.cpp
engines/eem/graphics.cpp
engines/eem/ui.cpp
diff --git a/engines/eem/clues.cpp b/engines/eem/clues.cpp
index 398e1064b15..bf7b09e54a2 100644
--- a/engines/eem/clues.cpp
+++ b/engines/eem/clues.cpp
@@ -86,7 +86,7 @@ uint markClueBlockNotebookEntries(Mystery &mystery, const byte *clueBlock,
if (number == 0 || number > 32)
return 0;
- // EEM2 entries are 84 bytes (0x54) with the notebook list at entry+0x40;
+ // EEM2 entries are 84 bytes (0x54) with the notebook list at entry+0x3c;
// EEM1 entries are 62 bytes with the list at entry+0x30. See displayClue.
const uint stride = isLondon ? 0x54 : 62;
const uint noteOffat = isLondon ? 0x3c : 0x30;
@@ -740,8 +740,9 @@ void EEMEngine::doInitClues() {
displayFloppyBriefing(ib);
} else {
const byte *briefingClues = ib + 4;
- // _DisplayClue calls _AddNotebook for each ClueEntry note list at
- // +0x30..+0x39. Mark starting notes before the first PDA visit.
+ // _DisplayClue calls _AddNotebook for each ClueEntry note list
+ // (EEM1 +0x30..+0x39, EEM2 +0x3c..+0x45). Mark starting notes
+ // before the first PDA visit.
const uint marked = markClueBlockNotebookEntries(_mystery, briefingClues,
isLondon());
if (marked != 0)
@@ -964,7 +965,8 @@ void EEMEngine::displayClue(const byte *clueBlock) {
}
}
- // ClueEntry layout (62 bytes):
+ // ClueEntry layout. EEM1 entries are 62 bytes; EEM2/London entries
+ // extend this to 0x54 bytes and move the side-effect lists below.
// +0..1, +2..3: p0 tx, ty +4..5, +6..7: p1 tx, ty
// +8..9, +10..11: bubText offset p0/p1 (rel. TextBlock; -1 = none)
// +12..13, +14..15: balloon pic ID p0/p1
@@ -972,8 +974,9 @@ void EEMEngine::displayClue(const byte *clueBlock) {
// +0x14/+0x16: bubX, bubY (partner 1)
// +0x18: Jenny voice (1-based)
// +0x1a: Jake voice (1-based)
- // +0x30..+0x39: 5 notebook entries (-1 terminated)
- // +0x3a..+0x3b: KD-anim number (-1 = none)
+ // EEM1 +0x30..+0x39 / EEM2 +0x3c..+0x45:
+ // 5 notebook entries (-1 terminated)
+ // EEM1 +0x3a / EEM2 +0x4e: KD-anim number (-1 = none)
for (uint i = 0; i < number && !shouldQuit(); i++) {
g_system->copyRectToScreen(bg.getPixels(), bg.pitch, 0, 0, sw, sh);
const byte *c = clueBlock + 4 + i * stride;
diff --git a/engines/eem/graphics.cpp b/engines/eem/graphics.cpp
index 6f220e2e26a..343653921a1 100644
--- a/engines/eem/graphics.cpp
+++ b/engines/eem/graphics.cpp
@@ -703,8 +703,12 @@ void EEMEngine::doInterfaceHelp(uint num) {
debugC(1, kDebugScript, "doInterfaceHelp(%u): showing pics 0x%x, 0x%x",
num, kHelpPics[num][0], kHelpPics[num][1]);
+ const bool mac = isMacintosh();
+ const int sw = screenWidth();
+ const int sh = screenHeight();
+
// Snapshot caller's screen once: each PIC overlays the same clean BG.
- Graphics::ManagedSurface bg(kScreenWidth, kScreenHeight,
+ Graphics::ManagedSurface bg(sw, sh,
Graphics::PixelFormat::createFormatCLUT8());
{
Graphics::Surface *cur = g_system->lockScreen();
@@ -727,14 +731,18 @@ void EEMEngine::doInterfaceHelp(uint num) {
debugC(1, kDebugScript, "doInterfaceHelp: pic 0x%x = %dx%d flags=0x%x",
picId, pic.surface.w, pic.surface.h, pic.flags);
- Graphics::ManagedSurface scratch(kScreenWidth, kScreenHeight,
+ Graphics::ManagedSurface scratch(sw, sh,
Graphics::PixelFormat::createFormatCLUT8());
scratch.simpleBlitFrom(bg);
const byte transp = (byte)(pic.flags >> 8);
- scratch.transBlitFrom(pic.surface, Common::Point(0, 0),
- (uint32)transp);
+ if (mac) {
+ blitMacMaskedSurface(scratch.surfacePtr(), pic, 0, 0);
+ } else {
+ scratch.transBlitFrom(pic.surface, Common::Point(0, 0),
+ (uint32)transp);
+ }
g_system->copyRectToScreen(scratch.getPixels(), scratch.pitch,
- 0, 0, kScreenWidth, kScreenHeight);
+ 0, 0, sw, sh);
g_system->updateScreen();
bool escape = false;
diff --git a/engines/eem/ui.cpp b/engines/eem/ui.cpp
index fff43073d73..ab8cc8d99dd 100644
--- a/engines/eem/ui.cpp
+++ b/engines/eem/ui.cpp
@@ -554,20 +554,21 @@ constexpr Common::Rect kPdaPagePrevRect(Common::Point(226, 174), 21, 16);
constexpr Common::Rect kPdaHelp2Rect(Common::Point(267, 174), 21, 16);
constexpr Common::Rect kPdaLondonCloseRect(Common::Point(0, 0), 66, 79);
-// Mac _NotebookRect and _NoteButtons table at 0x177e. The coordinates are
-// native QuickDraw rects; keep them exact instead of deriving them from the
-// rounded DOS scaler.
+// Mac _NotebookRect and the TRAVIS button table use native QuickDraw rects;
+// keep them exact instead of deriving them from the rounded DOS scaler.
constexpr Common::Rect kMacNotebookTextRect(Common::Point(125, 23), 336, 269);
-constexpr Common::Rect kMacPdaNotebookRect(Common::Point(214, 334), 34, 30);
-constexpr Common::Rect kMacPdaHelpRect(Common::Point(149, 334), 35, 30);
-constexpr Common::Rect kMacPdaGalleryRect(Common::Point(251, 334), 34, 30);
+constexpr Common::Rect kMacPdaHelpRect(Common::Point(164, 336), 33, 28);
+constexpr Common::Rect kMacPdaNotebookRect(Common::Point(247, 336), 34, 28);
+constexpr Common::Rect kMacPdaGalleryRect(Common::Point(330, 336), 34, 28);
constexpr Common::Rect kMacPdaPartnerHeadHintRect(Common::Point(13, 154), 57, 57);
constexpr Common::Rect kMacPdaAccuseRect(Common::Point(288, 334), 33, 30);
-constexpr Common::Rect kMacPdaPageNextRect(Common::Point(325, 334), 33, 30);
-constexpr Common::Rect kMacPdaPagePrevRect(Common::Point(362, 334), 33, 30);
+constexpr Common::Rect kMacPdaPagePrevRect(Common::Point(112, 336), 48, 28);
+constexpr Common::Rect kMacPdaPageNextRect(Common::Point(452, 336), 42, 28);
+constexpr Common::Rect kMacPdaScrollBarRect(Common::Point(98, 336), 396, 48);
+constexpr int kMacPdaScrollBarMidX = 296;
constexpr Common::Rect kMacPdaPartnerFootMapRect(Common::Point(11, 340), 80, 44);
constexpr Common::Rect kMacPdaSiteRect(Common::Point(56, 213), 34, 48);
-constexpr Common::Rect kMacPdaHelp2Rect(Common::Point(427, 334), 33, 30);
+constexpr Common::Rect kMacPdaHelp2Rect(Common::Point(413, 336), 34, 28);
constexpr uint16 kProfilePickerRevealPic = 0x105;
constexpr int kProfilePickerRevealX = 0x3e;
@@ -636,6 +637,28 @@ Common::Rect pdaControlRect(const EEMEngine *vm, const Common::Rect &rect) {
return vm->scaleRect(rect);
}
+int macPdaScrollBarDelta(const EEMEngine *vm, int x, int y) {
+ if (!vm || !vm->isMacintosh() || !kMacPdaScrollBarRect.contains(x, y))
+ return 0;
+
+ static const Common::Rect kMacPdaScrollBarButtons[] = {
+ kMacPdaHelpRect,
+ kMacPdaNotebookRect,
+ kMacPdaGalleryRect,
+ kMacPdaAccuseRect,
+ kMacPdaHelp2Rect
+ };
+ for (uint i = 0; i < ARRAYSIZE(kMacPdaScrollBarButtons); i++) {
+ if (kMacPdaScrollBarButtons[i].contains(x, y))
+ return 0;
+ }
+ return x < kMacPdaScrollBarMidX ? -1 : 1;
+}
+
+bool macPdaScrollBarAt(const EEMEngine *vm, int x, int y) {
+ return macPdaScrollBarDelta(vm, x, y) != 0;
+}
+
bool notebookButtonAt(const EEMEngine *vm, int x, int y) {
return pdaControlRect(vm, kPdaHelpRect).contains(x, y) ||
pdaControlRect(vm, kPdaGalleryRect).contains(x, y) ||
@@ -645,7 +668,8 @@ bool notebookButtonAt(const EEMEngine *vm, int x, int y) {
pdaControlRect(vm, kPdaPagePrevRect).contains(x, y) ||
pdaControlRect(vm, kPdaHelp2Rect).contains(x, y) ||
pdaControlRect(vm, kPdaPartnerFootMapRect).contains(x, y) ||
- pdaControlRect(vm, kPdaSiteRect).contains(x, y);
+ pdaControlRect(vm, kPdaSiteRect).contains(x, y) ||
+ macPdaScrollBarAt(vm, x, y);
}
bool notebookButtonAt(int x, int y) {
@@ -667,6 +691,7 @@ bool galleryButtonAt(const EEMEngine *vm, int x, int y) {
pdaControlRect(vm, kPdaAccuseRect).contains(x, y) ||
pdaControlRect(vm, kPdaNotebookRect).contains(x, y) ||
pdaControlRect(vm, kPdaHelpRect).contains(x, y) ||
+ pdaControlRect(vm, kPdaHelp2Rect).contains(x, y) ||
pdaControlRect(vm, kPdaPartnerHeadHintRect).contains(x, y);
}
@@ -707,6 +732,18 @@ bool gallerySlotAt(const Common::Array<Common::Rect> &rects,
return false;
}
+void blitTravisBackground(Graphics::ManagedSurface &dst, const Picture &pic,
+ bool mac) {
+ if (mac) {
+ Graphics::ManagedSurface bg;
+ bg.copyFrom(pic.surface);
+ remapMacSurfaceEndpoints(bg, getMacSpritePaletteMap());
+ dst.simpleBlitFrom(bg);
+ } else {
+ dst.simpleBlitFrom(pic.surface);
+ }
+}
+
const byte *advanceFloppyDialogRecords(const byte *rec, uint count,
const byte *end, bool mac = false) {
if (!rec || (!end && count != 0))
@@ -3060,6 +3097,19 @@ void EEMEngine::doNotebook() {
dirty = true;
continue;
}
+ const int scrollDelta =
+ macPdaScrollBarDelta(this, ev.mouse.x, ev.mouse.y);
+ if (scrollDelta < 0) {
+ if (page > 0)
+ page--;
+ dirty = true;
+ continue;
+ }
+ if (scrollDelta > 0) {
+ page++;
+ dirty = true;
+ continue;
+ }
}
}
if (exitFlag)
@@ -3130,7 +3180,7 @@ void EEMEngine::drawNotebookFrame(int &page) {
Picture frame;
if (_picsArchive.getPicture(0x3f, frame))
- scratch.simpleBlitFrom(frame.surface);
+ blitTravisBackground(scratch, frame, isMacintosh());
blitPdaPartner(scratch, _aniArchive, _partner, kPdaNotebookPartner,
g_system->getMillis(), isMacintosh());
@@ -3342,6 +3392,15 @@ void EEMEngine::doGallery() {
lastDraw = 0;
continue;
}
+ if (pdaControlRect(this, kPdaHelp2Rect).contains(ev.mouse.x,
+ ev.mouse.y)) {
+ setInteractiveMouseCursor(false);
+ doInterfaceHelp(0);
+ if (isMacintosh())
+ setSitePalette(0);
+ lastDraw = 0;
+ continue;
+ }
if (pdaControlRect(this, kPdaPartnerHeadHintRect)
.contains(ev.mouse.x, ev.mouse.y)) {
setInteractiveMouseCursor(false);
@@ -3440,7 +3499,7 @@ bool EEMEngine::moreInfo(const byte *gd, uint suspectIdx,
Graphics::PixelFormat::createFormatCLUT8());
ms.clear();
if (haveBg)
- ms.simpleBlitFrom(galBg.surface);
+ blitTravisBackground(ms, galBg, mac);
blitPdaPartner(ms, _aniArchive, _partner, kPdaGalleryPartner,
g_system->getMillis(), mac);
@@ -3490,8 +3549,7 @@ bool EEMEngine::moreInfo(const byte *gd, uint suspectIdx,
// Defer to next page.
break;
}
- const byte color = _mystery._noteSelected[clueId]
- ? 0x3C : 0x5C;
+ const byte color = _mystery._noteSelected[clueId] ? 0x3C : 0x5C;
for (uint l = 0; l < wrapped.size(); l++) {
_font.drawString(&ms, wrapped[l], rx,
yPos + (int)l * lineH, MAX<int>(8, rw), color);
@@ -3557,6 +3615,14 @@ bool EEMEngine::moreInfo(const byte *gd, uint suspectIdx,
redraw = true;
break;
}
+ if (e2.type == Common::EVENT_MOUSEMOVE) {
+ const int scrollDelta =
+ macPdaScrollBarDelta(this, e2.mouse.x, e2.mouse.y);
+ setInteractiveMouseCursor(
+ galleryButtonAt(this, e2.mouse.x, e2.mouse.y) ||
+ (scrollDelta < 0 && hasPrev) ||
+ (scrollDelta > 0 && hasMore));
+ }
if (e2.type == Common::EVENT_LBUTTONDOWN) {
const int mx = e2.mouse.x;
const int my = e2.mouse.y;
@@ -3612,6 +3678,18 @@ bool EEMEngine::moreInfo(const byte *gd, uint suspectIdx,
prev = true;
break;
}
+ const int scrollDelta =
+ macPdaScrollBarDelta(this, mx, my);
+ if (scrollDelta < 0) {
+ if (hasPrev)
+ prev = true;
+ break;
+ }
+ if (scrollDelta > 0) {
+ if (hasMore)
+ advance = true;
+ break;
+ }
if (pdaControlRect(this, kPdaPartnerHeadHintRect).contains(mx, my)) {
// Case 3: _KDHelp.
setInteractiveMouseCursor(false);
@@ -3677,7 +3755,7 @@ void EEMEngine::drawGalleryFrame(const byte *gd, uint8 numSuspects,
scratch.clear();
if (haveBg)
- scratch.simpleBlitFrom(galBg.surface);
+ blitTravisBackground(scratch, galBg, mac);
blitPdaPartner(scratch, _aniArchive, _partner, kPdaGalleryPartner,
g_system->getMillis(), mac);
@@ -4607,7 +4685,9 @@ Common::String EEMEngine::accuseNoteText(uint clueId,
(const char *)(ctx.bufBaseNotes + textOff),
_playerName, _partner);
}
- if (isMacintosh() && ctx.bufBaseNotes) {
+ // Only the compact EEM1 Mac mystery data uses 8-byte NoteIndex records.
+ // EEM2 London Mac loose scripts keep the London 2-byte table.
+ if (isMacintosh() && !isLondon() && ctx.bufBaseNotes) {
const uint16 textOff = READ_LE_UINT16(ctx.ni + clueId * 8);
if (textOff == 0 || textOff >= _mystery.dataSize())
return Common::String();
@@ -4658,7 +4738,7 @@ void EEMEngine::accuseDrawScreen(const AccuseNotesCtx &ctx) {
Graphics::PixelFormat::createFormatCLUT8());
scratch.clear();
if (ctx.haveBg)
- scratch.simpleBlitFrom(ctx.accuseBg->surface);
+ blitTravisBackground(scratch, *ctx.accuseBg, mac);
blitPdaPartner(scratch, _aniArchive, _partner, kPdaGalleryPartner,
g_system->getMillis(), mac);
@@ -4738,6 +4818,9 @@ bool EEMEngine::doAccuseNotes() {
if (!ni)
return false;
+ if (isMacintosh())
+ setSitePalette(0);
+
Picture accuseBg;
const bool haveBg = _picsArchive.getPicture(0x1a7, accuseBg);
@@ -4894,6 +4977,21 @@ bool EEMEngine::doAccuseNotes() {
}
continue;
}
+ const int scrollDelta = macPdaScrollBarDelta(this, mx, my);
+ if (scrollDelta < 0) {
+ if (page > 0) {
+ page--;
+ dirty = true;
+ }
+ continue;
+ }
+ if (scrollDelta > 0) {
+ if (page + 1 < numPages) {
+ page++;
+ dirty = true;
+ }
+ continue;
+ }
if (btnPartner.contains(mx, my)) {
doHelp();
if (isMacintosh())
@@ -4907,9 +5005,8 @@ bool EEMEngine::doAccuseNotes() {
if (_mystery._noteSelected[found[i]])
selected++;
}
- if (selected == expected) {
+ if (selected == expected)
return true;
- }
continue;
}
// Toggle clue under cursor.
Commit: ee8eee5869c1bb08372f02d27eb69279129a3c5a
https://github.com/scummvm/scummvm/commit/ee8eee5869c1bb08372f02d27eb69279129a3c5a
Author: neuromancer (gustavo.grieco at gmail.com)
Date: 2026-06-26T20:37:27+02:00
Commit Message:
EEM: improved setup screen in EEM2 Mac
Changed paths:
engines/eem/ui.cpp
diff --git a/engines/eem/ui.cpp b/engines/eem/ui.cpp
index ab8cc8d99dd..178fb2f8a9e 100644
--- a/engines/eem/ui.cpp
+++ b/engines/eem/ui.cpp
@@ -392,8 +392,25 @@ constexpr Common::Rect kLonSetMusicOff(Common::Point(128, 85), 18, 7); // 0x142
constexpr Common::Rect kLonSetHiOn (Common::Point(106, 110), 29, 8); // 0x1414
constexpr Common::Rect kLonSetHiOff (Common::Point(106, 100), 29, 8); // 0x140c
+// Mac setup PIC 0x40 is native 512x384 art. The selected-option labels are
+// encoded as 0xfe mask components in the picture itself, not DOS-scaled rects.
+constexpr Common::Rect kMacLonSetJake (Common::Point(172, 87), 75, 14);
+constexpr Common::Rect kMacLonSetJenny (Common::Point(172, 106), 75, 14);
+constexpr Common::Rect kMacLonSetVoiceOn (Common::Point(172, 139), 28, 14);
+constexpr Common::Rect kMacLonSetVoiceOff(Common::Point(205, 139), 28, 14);
+constexpr Common::Rect kMacLonSetMusicOn (Common::Point(172, 166), 28, 14);
+constexpr Common::Rect kMacLonSetMusicOff(Common::Point(205, 166), 28, 14);
+constexpr Common::Rect kMacLonSetHiOn (Common::Point(172, 222), 44, 14);
+constexpr Common::Rect kMacLonSetHiOff (Common::Point(172, 203), 44, 14);
+
+Common::Rect londonSetupTextRect(const EEMEngine *vm,
+ const Common::Rect &dosRect,
+ const Common::Rect &macRect) {
+ return vm && vm->isMacintosh() ? macRect : dosRect;
+}
+
void swapColors(Graphics::ManagedSurface &dst,
- const Common::Rect &r, byte from, byte to) {
+ const Common::Rect &r, byte from, byte to) {
const int x1 = MAX<int>(0, r.left);
const int y1 = MAX<int>(0, r.top);
const int x2 = MIN<int>(dst.w, r.right);
@@ -2277,7 +2294,10 @@ void EEMEngine::setupLeave() {
}
// `_SetupSettings @ 2046:0008`
void EEMEngine::setupDrawScreenLondon() {
- Graphics::ManagedSurface scratch(kScreenWidth, kScreenHeight,
+ if (isMacintosh())
+ setSitePalette(0);
+
+ Graphics::ManagedSurface scratch(screenWidth(), screenHeight(),
Graphics::PixelFormat::createFormatCLUT8());
scratch.clear();
Picture bg;
@@ -2286,21 +2306,29 @@ void EEMEngine::setupDrawScreenLondon() {
const byte kKey = 0xFE, kBright = 0x15, kDim = 0x00;
- swapColors(scratch, kLonSetJake, kKey, _partner == kPartnerJake ? kBright : kDim);
- swapColors(scratch, kLonSetJenny, kKey, _partner == kPartnerJenny ? kBright : kDim);
+ swapColors(scratch, londonSetupTextRect(this, kLonSetJake, kMacLonSetJake),
+ kKey, _partner == kPartnerJake ? kBright : kDim);
+ swapColors(scratch, londonSetupTextRect(this, kLonSetJenny, kMacLonSetJenny),
+ kKey, _partner == kPartnerJenny ? kBright : kDim);
- swapColors(scratch, kLonSetVoiceOn, kKey, _voiceOn ? kBright : kDim);
- swapColors(scratch, kLonSetVoiceOff, kKey, _voiceOn ? kDim : kBright);
+ swapColors(scratch, londonSetupTextRect(this, kLonSetVoiceOn, kMacLonSetVoiceOn),
+ kKey, _voiceOn ? kBright : kDim);
+ swapColors(scratch, londonSetupTextRect(this, kLonSetVoiceOff, kMacLonSetVoiceOff),
+ kKey, _voiceOn ? kDim : kBright);
- swapColors(scratch, kLonSetMusicOn, kKey, _musicOn ? kBright : kDim);
- swapColors(scratch, kLonSetMusicOff, kKey, _musicOn ? kDim : kBright);
+ swapColors(scratch, londonSetupTextRect(this, kLonSetMusicOn, kMacLonSetMusicOn),
+ kKey, _musicOn ? kBright : kDim);
+ swapColors(scratch, londonSetupTextRect(this, kLonSetMusicOff, kMacLonSetMusicOff),
+ kKey, _musicOn ? kDim : kBright);
const bool hiOn = !ConfMan.getBool("hide_highlight_boxes");
- swapColors(scratch, kLonSetHiOn, kKey, hiOn ? kBright : kDim);
- swapColors(scratch, kLonSetHiOff, kKey, hiOn ? kDim : kBright);
+ swapColors(scratch, londonSetupTextRect(this, kLonSetHiOn, kMacLonSetHiOn),
+ kKey, hiOn ? kBright : kDim);
+ swapColors(scratch, londonSetupTextRect(this, kLonSetHiOff, kMacLonSetHiOff),
+ kKey, hiOn ? kDim : kBright);
g_system->copyRectToScreen(scratch.getPixels(), scratch.pitch,
- 0, 0, kScreenWidth, kScreenHeight);
+ 0, 0, scratch.w, scratch.h);
g_system->updateScreen();
}
@@ -2308,19 +2336,19 @@ void EEMEngine::setupShowSavedConfirm() {
Picture pic;
if (!_picsArchive.getPicture(0x203, pic) || pic.surface.empty())
return;
- Graphics::ManagedSurface scratch(kScreenWidth, kScreenHeight,
+ Graphics::ManagedSurface scratch(screenWidth(), screenHeight(),
Graphics::PixelFormat::createFormatCLUT8());
Graphics::Surface *cur = g_system->lockScreen();
if (cur) {
scratch.simpleBlitFrom(*cur);
g_system->unlockScreen();
}
- const int sx = MAX<int>(0, (kScreenWidth - pic.surface.w) / 2);
- const int sy = MAX<int>(0, (kScreenHeight - pic.surface.h) / 2);
+ const int sx = MAX<int>(0, (scratch.w - pic.surface.w) / 2);
+ const int sy = MAX<int>(0, (scratch.h - pic.surface.h) / 2);
scratch.transBlitFrom(pic.surface, Common::Point(sx, sy),
(uint32)(byte)(pic.flags >> 8));
g_system->copyRectToScreen(scratch.getPixels(), scratch.pitch,
- 0, 0, kScreenWidth, kScreenHeight);
+ 0, 0, scratch.w, scratch.h);
g_system->updateScreen();
while (!shouldQuit()) {
Common::Event ev;
@@ -2374,7 +2402,10 @@ void EEMEngine::doSetupLondon() {
}
if (ev.type != Common::EVENT_LBUTTONDOWN)
continue;
- const int mx = ev.mouse.x, my = ev.mouse.y;
+ const Common::Point mouse(unscaleX(ev.mouse.x),
+ unscaleY(ev.mouse.y));
+ const int mx = mouse.x;
+ const int my = mouse.y;
if (kPartnerBtn.contains(mx, my)) {
_partner = (_partner == kPartnerJake) ? kPartnerJenny
Commit: 4ec3f34aec0ae977906925c0d7a02f05f7e8a1c8
https://github.com/scummvm/scummvm/commit/4ec3f34aec0ae977906925c0d7a02f05f7e8a1c8
Author: neuromancer (gustavo.grieco at gmail.com)
Date: 2026-06-26T20:37:27+02:00
Commit Message:
EEM: implemented approach screens in EEM2 Mac
Changed paths:
engines/eem/eem.cpp
engines/eem/site.cpp
engines/eem/ui.cpp
diff --git a/engines/eem/eem.cpp b/engines/eem/eem.cpp
index 3c75db5cb99..66c9ea28a74 100644
--- a/engines/eem/eem.cpp
+++ b/engines/eem/eem.cpp
@@ -413,6 +413,83 @@ static void addMacResourceSearchPaths() {
SearchMan.addDirectory("eem-mac-rsrc-sibling", siblingRsrcDir);
}
+Common::FSNode findChildDirectoryIgnoreCase(const Common::FSNode &dir,
+ const char *name) {
+ Common::FSList children;
+ if (!dir.exists() || !dir.isDirectory() ||
+ !dir.getChildren(children, Common::FSNode::kListDirectoriesOnly))
+ return Common::FSNode();
+
+ for (Common::FSList::const_iterator it = children.begin();
+ it != children.end(); ++it) {
+ if (it->getName().equalsIgnoreCase(name))
+ return *it;
+ }
+
+ return Common::FSNode();
+}
+
+void addMacLondonDirectoryIfPresent(const Common::FSNode &dir,
+ const char *archiveName,
+ int depth = 1) {
+ if (dir.exists() && dir.isDirectory() &&
+ !SearchMan.hasArchive(archiveName))
+ SearchMan.addDirectory(archiveName, dir, 0, depth);
+}
+
+void addMacLondonSearchPathsFrom(const Common::FSNode &base,
+ const char *archivePrefix) {
+ if (!base.exists() || !base.isDirectory())
+ return;
+
+ Common::FSNode cd = findChildDirectoryIgnoreCase(base, "EEM2 CD");
+ if (!cd.exists()) {
+ Common::FSNode wrapper = findChildDirectoryIgnoreCase(base, "EEM_London");
+ if (wrapper.exists())
+ cd = findChildDirectoryIgnoreCase(wrapper, "EEM2 CD");
+ }
+ if (!cd.exists() && base.getName().equalsIgnoreCase("EEM2 CD"))
+ cd = base;
+ if (!cd.exists()) {
+ const Common::String baseName = base.getName();
+ if (baseName.equalsIgnoreCase("Data Files") ||
+ baseName.equalsIgnoreCase("Mac Scripts") ||
+ baseName.equalsIgnoreCase("Anim Files"))
+ cd = base.getParent();
+ }
+ if (!cd.exists())
+ return;
+
+ Common::String dataArchive = Common::String::format(
+ "%s-data-files", archivePrefix);
+ Common::String scriptsArchive = Common::String::format(
+ "%s-mac-scripts", archivePrefix);
+ Common::String animArchive = Common::String::format(
+ "%s-anim-files", archivePrefix);
+ Common::String appArchive = Common::String::format(
+ "%s-app", archivePrefix);
+
+ const Common::FSNode dataDir = findChildDirectoryIgnoreCase(cd, "Data Files");
+ const Common::FSNode scriptsDir = findChildDirectoryIgnoreCase(cd, "Mac Scripts");
+ const Common::FSNode animDir = findChildDirectoryIgnoreCase(cd, "Anim Files");
+
+ addMacLondonDirectoryIfPresent(dataDir, dataArchive.c_str(), 2);
+ addMacLondonDirectoryIfPresent(scriptsDir, scriptsArchive.c_str(), 3);
+ addMacLondonDirectoryIfPresent(animDir, animArchive.c_str(), 2);
+
+ if (scriptsDir.exists()) {
+ Common::String approachesArchive = Common::String::format(
+ "%s-approaches", archivePrefix);
+ addMacLondonDirectoryIfPresent(
+ findChildDirectoryIgnoreCase(scriptsDir, "Approaches"),
+ approachesArchive.c_str());
+ }
+
+ const Common::FSNode app =
+ findChildDirectoryIgnoreCase(cd.getParent(), "EEM London CD");
+ addMacLondonDirectoryIfPresent(app, appArchive.c_str(), 2);
+}
+
static bool loadMacFontResource(EEMFont &font, uint16 resourceId, int size) {
addMacResourceSearchPaths();
@@ -855,17 +932,19 @@ bool EEMEngine::openArchives() {
// the disc root or at the "EEM2 CD" folder.
if (mac && isLondon()) {
const Common::FSNode gameDir(ConfMan.getPath("path"));
+ addMacLondonSearchPathsFrom(gameDir, "eem-london-game");
+ addMacLondonSearchPathsFrom(gameDir.getParent(), "eem-london-parent");
// Disc-root layout (recommended -- this also reaches the "EEM London
// CD" app that holds the Mac fonts/sound). The "/"-separated names
// descend two levels (see Common::addSubDirectoryMatching).
- SearchMan.addSubDirectoryMatching(gameDir, "EEM2 CD/Data Files");
- SearchMan.addSubDirectoryMatching(gameDir, "EEM2 CD/Mac Scripts");
- SearchMan.addSubDirectoryMatching(gameDir, "EEM2 CD/Anim Files");
- SearchMan.addSubDirectoryMatching(gameDir, "EEM London CD");
+ SearchMan.addSubDirectoryMatching(gameDir, "EEM2 CD/Data Files", 0, 2);
+ SearchMan.addSubDirectoryMatching(gameDir, "EEM2 CD/Mac Scripts", 0, 3);
+ SearchMan.addSubDirectoryMatching(gameDir, "EEM2 CD/Anim Files", 0, 2);
+ SearchMan.addSubDirectoryMatching(gameDir, "EEM London CD", 0, 2);
// ...or the user pointed ScummVM straight at the "EEM2 CD" folder.
- SearchMan.addSubDirectoryMatching(gameDir, "Data Files");
- SearchMan.addSubDirectoryMatching(gameDir, "Mac Scripts");
- SearchMan.addSubDirectoryMatching(gameDir, "Anim Files");
+ SearchMan.addSubDirectoryMatching(gameDir, "Data Files", 0, 2);
+ SearchMan.addSubDirectoryMatching(gameDir, "Mac Scripts", 0, 3);
+ SearchMan.addSubDirectoryMatching(gameDir, "Anim Files", 0, 2);
}
// The EEM1 Mac release can be played straight from its floppy installer.
diff --git a/engines/eem/site.cpp b/engines/eem/site.cpp
index d9f888732ce..ec49e304f40 100644
--- a/engines/eem/site.cpp
+++ b/engines/eem/site.cpp
@@ -973,18 +973,20 @@ void SiteScreen::enter(uint siteNum, bool resetPartnerMood) {
const byte *sd = _mystery->siteData(siteNum);
const bool playArrival = _vm->shouldPlaySiteArrival(siteNum);
+ const bool london = _vm->isLondon();
+ const uint16 approachId = (london && sd) ? READ_LE_UINT16(sd + 2) : 0xffff;
- if (playArrival) {
- if (_vm->isLondon()) {
- bool showedApproach = false;
- const uint16 approachId = sd ? READ_LE_UINT16(sd + 2) : 0xffff;
- if (firstVisit && approachId != 0xffff)
- showedApproach = _vm->doLondonApproach(approachId);
- if (!showedApproach)
- playLondonTravelAnimation(_mystery->_lastSite, siteNum);
- } else {
- _vm->startTravelMusic();
+ if (london) {
+ if (playArrival)
+ playLondonTravelAnimation(_mystery->_lastSite, siteNum);
+ if (firstVisit && approachId != 0xffff) {
+ debugC(1, kDebugSite,
+ "London approach: site %u first visit, approach %u",
+ siteNum, approachId);
+ _vm->doLondonApproach(approachId);
}
+ } else if (playArrival) {
+ _vm->startTravelMusic();
}
const bool compactSite = _vm->isFloppy() ||
diff --git a/engines/eem/ui.cpp b/engines/eem/ui.cpp
index 178fb2f8a9e..1eb8624e259 100644
--- a/engines/eem/ui.cpp
+++ b/engines/eem/ui.cpp
@@ -87,6 +87,18 @@ const LondonApproachButton kLondonApproachButtons[4] = {
{ 287, 139, 307, 157, 287, 139, 0x360 }, // Previous
};
+const uint16 kMacLondonApproachBackgroundBasePic = 0x38c;
+const int kMacLondonApproachMovieX = 114;
+const int kMacLondonApproachMovieY = 23;
+const int kMacLondonApproachPlayLeft = 9;
+const int kMacLondonApproachPlayTop = 277;
+const int kMacLondonApproachPlayRight = 60;
+const int kMacLondonApproachPlayBottom = 332;
+const int kMacLondonApproachDoneLeft = 452;
+const int kMacLondonApproachDoneTop = 277;
+const int kMacLondonApproachDoneRight = 504;
+const int kMacLondonApproachDoneBottom = 332;
+
byte mapVisitedMarkerColor(byte color) {
switch (color) {
case 0xf7:
@@ -248,8 +260,17 @@ static uint16 readScriptU16(const byte *p, bool bigEndian) {
return bigEndian ? READ_BE_UINT16(p) : READ_LE_UINT16(p);
}
+Common::String cleanLondonApproachPage(const byte *start, uint32 len) {
+ Common::String page((const char *)start, len);
+ while (!page.empty() &&
+ (page.lastChar() == '\r' || page.lastChar() == '\n' ||
+ (byte)page.lastChar() == 0xff))
+ page.deleteLastChar();
+ return page;
+}
+
bool loadLondonApproachData(uint16 approachId, LondonApproachData &out,
- bool macintosh) {
+ bool macintosh) {
static const char *const kDosPatterns[] = {
"A%u.BIN"
};
@@ -288,11 +309,19 @@ bool loadLondonApproachData(uint16 approachId, LondonApproachData &out,
out.videoId = readScriptU16(data.data() + 0, macintosh);
out.videoX = readScriptU16(data.data() + 2, macintosh);
out.videoY = readScriptU16(data.data() + 4, macintosh);
- const int16 x1 = (int16)readScriptU16(data.data() + 6, macintosh);
- const int16 y1 = (int16)readScriptU16(data.data() + 8, macintosh);
- const int16 x2 = (int16)readScriptU16(data.data() + 10, macintosh);
- const int16 y2 = (int16)readScriptU16(data.data() + 12, macintosh);
- out.textRect = Common::Rect(x1, y1, x2, y2);
+ if (macintosh) {
+ const int16 top = (int16)readScriptU16(data.data() + 6, true);
+ const int16 left = (int16)readScriptU16(data.data() + 8, true);
+ const int16 bottom = (int16)readScriptU16(data.data() + 10, true);
+ const int16 right = (int16)readScriptU16(data.data() + 12, true);
+ out.textRect = Common::Rect(left, top, right, bottom);
+ } else {
+ const int16 x1 = (int16)readScriptU16(data.data() + 6, false);
+ const int16 y1 = (int16)readScriptU16(data.data() + 8, false);
+ const int16 x2 = (int16)readScriptU16(data.data() + 10, false);
+ const int16 y2 = (int16)readScriptU16(data.data() + 12, false);
+ out.textRect = Common::Rect(x1, y1, x2, y2);
+ }
const uint16 pageCount = readScriptU16(data.data() + 14, macintosh);
out.pages.clear();
@@ -303,8 +332,8 @@ bool loadLondonApproachData(uint16 approachId, LondonApproachData &out,
const uint32 start = pos;
while (pos < size && data[pos] != '\n')
pos++;
- out.pages.push_back(Common::String((const char *)data.data() + start,
- pos - start));
+ out.pages.push_back(cleanLondonApproachPage(data.data() + start,
+ pos - start));
if (pos < size)
pos++; // skip the '\n' separator
}
@@ -376,6 +405,39 @@ bool decodeLondonApproachFirstFrame(uint16 videoId,
return true;
}
+byte closestPaletteIndex(const byte *palette, byte r, byte g, byte b,
+ byte fallback) {
+ if (!palette)
+ return fallback;
+
+ uint bestDist = 0xffffffff;
+ byte best = fallback;
+ for (uint i = 0; i < 256; i++) {
+ const int dr = (int)palette[i * 3 + 0] - r;
+ const int dg = (int)palette[i * 3 + 1] - g;
+ const int db = (int)palette[i * 3 + 2] - b;
+ const uint dist = (uint)(dr * dr + dg * dg + db * db);
+ if (dist < bestDist) {
+ bestDist = dist;
+ best = (byte)i;
+ }
+ }
+ return best;
+}
+
+void remapSurfaceColor(Graphics::ManagedSurface &surface, byte from, byte to) {
+ if (surface.empty() || from == to)
+ return;
+
+ for (int y = 0; y < surface.h; y++) {
+ byte *row = (byte *)surface.getBasePtr(0, y);
+ for (int x = 0; x < surface.w; x++) {
+ if (row[x] == from)
+ row[x] = to;
+ }
+ }
+}
+
// Setup-screen highlight rects
constexpr Common::Rect kSetupKid1Rect (Common::Point( 99, 44), 49, 8);
constexpr Common::Rect kSetupKid2Rect (Common::Point( 99, 54), 49, 8);
@@ -4245,9 +4307,6 @@ bool EEMEngine::doLondonApproach(uint16 approachId) {
const int sw = screenWidth();
const int sh = screenHeight();
- MacSpritePaletteMap macPaletteMap = {0x00, 0xFF};
- if (mac)
- macPaletteMap = getMacSpritePaletteMap();
Graphics::ManagedSurface base(sw, sh,
Graphics::PixelFormat::createFormatCLUT8());
@@ -4256,38 +4315,67 @@ bool EEMEngine::doLondonApproach(uint16 approachId) {
decodeLondonApproachFirstFrame(data.videoId, base, palette, mac);
if (!haveVideo)
base.clear();
+ if (mac) {
+ Picture background;
+ const uint16 backgroundPic =
+ kMacLondonApproachBackgroundBasePic + data.videoId;
+ if (_picsArchive.getPicture(backgroundPic, background) &&
+ !background.surface.empty()) {
+ if (haveVideo) {
+ const byte black =
+ closestPaletteIndex(palette, 0x00, 0x00, 0x00, 0xfe);
+ remapSurfaceColor(background.surface, 0xff, black);
+ }
+ base.clear();
+ base.simpleBlitFrom(background.surface);
+ }
+ }
Picture buttonPics[ARRAYSIZE(kLondonApproachButtons)];
bool haveButtons[ARRAYSIZE(kLondonApproachButtons)] = {};
- for (uint i = 0; i < ARRAYSIZE(kLondonApproachButtons); i++)
- haveButtons[i] = _picsArchive.getPicture(
+ for (uint i = 0; i < ARRAYSIZE(kLondonApproachButtons); i++) {
+ // DOS `_DoApproach` loads PICS 0x361/0x362/0x35f/0x360 as the
+ // four control sprites. In the Mac London data those IDs are
+ // full-screen scrapbook pages, not buttons.
+ haveButtons[i] = !mac && _picsArchive.getPicture(
kLondonApproachButtons[i].picId, buttonPics[i]);
+ }
auto buttonRect = [&](uint idx) {
- const Common::Rect r = kLondonApproachButtons[idx].rect();
- return mac ? scaleRect(r) : r;
+ return kLondonApproachButtons[idx].rect();
};
auto buttonPoint = [&](uint idx) {
const LondonApproachButton &b = kLondonApproachButtons[idx];
- return Common::Point(mac ? scaleX(b.x) : b.x,
- mac ? scaleY(b.y) : b.y);
+ return Common::Point(b.x, b.y);
};
-
- auto drawButtonFallback = [&](Graphics::ManagedSurface &dst, uint idx) {
- static const char *const kLabels[4] = { "OK", "PLAY", ">", "<" };
- const Common::Rect r = buttonRect(idx);
- dst.fillRect(r, 0);
- _font.drawString(&dst, kLabels[idx], r.left + 1, r.top + 5,
- r.width(), 0x0f);
+ const int movieX = mac ? kMacLondonApproachMovieX : (int)data.videoX;
+ const int movieY = mac ? kMacLondonApproachMovieY : (int)data.videoY;
+ auto approachTextRect = [&]() {
+ Common::Rect textRect = data.textRect;
+ if (mac)
+ textRect = scaleRect(textRect);
+ return textRect.findIntersectingRect(Common::Rect(sw, sh));
};
+ auto macApproachButton = [&](const Common::Point &mouse) {
+ if (mouse.x >= kMacLondonApproachDoneLeft &&
+ mouse.x < kMacLondonApproachDoneRight &&
+ mouse.y >= kMacLondonApproachDoneTop &&
+ mouse.y < kMacLondonApproachDoneBottom)
+ return 0;
+ if (mouse.x >= kMacLondonApproachPlayLeft &&
+ mouse.x < kMacLondonApproachPlayRight &&
+ mouse.y >= kMacLondonApproachPlayTop &&
+ mouse.y < kMacLondonApproachPlayBottom)
+ return 1;
+ return -1;
+ };
+ const byte approachTextColor =
+ closestPaletteIndex(haveVideo ? palette : nullptr, 0, 0, 0,
+ mac ? (byte)0xfe : (byte)1);
- auto drawScreen = [&](uint page) {
- Graphics::ManagedSurface scratch(sw, sh,
- Graphics::PixelFormat::createFormatCLUT8());
- scratch.simpleBlitFrom(base);
-
- const Common::Rect textRect =
- data.textRect.findIntersectingRect(Common::Rect(sw, sh));
+ auto drawApproachOverlay = [&](Graphics::ManagedSurface &scratch,
+ uint page) {
+ const Common::Rect textRect = approachTextRect();
if (!textRect.isEmpty()) {
if (page < data.pages.size()) {
Common::Array<Common::String> wrapped;
@@ -4299,7 +4387,7 @@ bool EEMEngine::doLondonApproach(uint16 approachId) {
_font.drawString(&scratch, wrapped[i], textRect.left,
textRect.top + (int)i * lineH,
textRect.width(),
- mac ? macPaletteMap.black : 1);
+ approachTextColor);
}
}
}
@@ -4307,24 +4395,25 @@ bool EEMEngine::doLondonApproach(uint16 approachId) {
for (uint i = 0; i < ARRAYSIZE(kLondonApproachButtons); i++) {
if (haveButtons[i]) {
const Common::Point p = buttonPoint(i);
- if (mac)
- blitMacMaskedSurface(scratch.surfacePtr(),
- buttonPics[i], p.x, p.y, false,
- macPaletteMap);
- else
- scratch.transBlitFrom(buttonPics[i].surface, p,
- (uint32)(byte)(buttonPics[i].flags >> 8));
- } else {
- drawButtonFallback(scratch, i);
+ scratch.transBlitFrom(buttonPics[i].surface, p,
+ (uint32)(byte)(buttonPics[i].flags >> 8));
}
}
+ };
+
+ auto drawScreen = [&](uint page) {
+ Graphics::ManagedSurface scratch(sw, sh,
+ Graphics::PixelFormat::createFormatCLUT8());
+ scratch.clear();
+ scratch.simpleBlitFrom(base);
+ drawApproachOverlay(scratch, page);
g_system->copyRectToScreen(scratch.getPixels(), scratch.pitch,
0, 0, sw, sh);
g_system->updateScreen();
};
- auto playVideo = [&]() {
+ auto playVideo = [&](uint page) {
if (mac) {
Video::FlicDecoder flic;
Common::String name;
@@ -4335,22 +4424,24 @@ bool EEMEngine::doLondonApproach(uint16 approachId) {
}
flic.start();
- (void)flic.decodeNextFrame(); // first frame is already the background
- const int videoBandH = scaleY(0x82);
while (!shouldQuit() && !flic.endOfVideo()) {
const Graphics::Surface *frame = flic.decodeNextFrame();
if (!frame)
break;
- const int copyW = MIN<int>(frame->w - (int)data.videoX,
- sw - (int)data.videoX);
- const int copyH = MIN<int>(videoBandH,
- MIN<int>(frame->h - (int)data.videoY,
- sh - (int)data.videoY));
+ Graphics::ManagedSurface scratch(sw, sh,
+ Graphics::PixelFormat::createFormatCLUT8());
+ scratch.clear();
+ scratch.simpleBlitFrom(base);
+ const int copyW = MIN<int>(frame->w, sw - movieX);
+ const int copyH = MIN<int>(frame->h, sh - movieY);
if (copyW <= 0 || copyH <= 0)
break;
- g_system->copyRectToScreen(
- (const byte *)frame->getBasePtr(data.videoX, data.videoY),
- frame->pitch, data.videoX, data.videoY, copyW, copyH);
+ scratch.copyRectToSurface(frame->getPixels(), frame->pitch,
+ movieX, movieY,
+ copyW, copyH);
+ drawApproachOverlay(scratch, page);
+ g_system->copyRectToScreen(scratch.getPixels(), scratch.pitch,
+ 0, 0, sw, sh);
if (flic.hasDirtyPalette()) {
const byte *fpal = flic.getPalette();
if (fpal)
@@ -4359,8 +4450,9 @@ bool EEMEngine::doLondonApproach(uint16 approachId) {
g_system->updateScreen();
const uint32 start = g_system->getMillis();
+ const uint32 delay = MAX<uint32>(10, flic.getTimeToNextFrame());
bool skip = false;
- while (g_system->getMillis() - start < 120 && !skip) {
+ while (g_system->getMillis() - start < delay && !skip) {
Common::Event ev;
while (g_system->getEventManager()->pollEvent(ev)) {
if (ev.type == Common::EVENT_QUIT ||
@@ -4389,11 +4481,10 @@ bool EEMEngine::doLondonApproach(uint16 approachId) {
}
// Frame 0 is already the static background. Replay frames 1..end
- // into the upper video area, leaving text/buttons untouched.
+ // with the approach text/buttons composited above it.
(void)anm.nextFrame();
const int copyW = MIN<int>(anm.width(), sw - (int)data.videoX);
- const int copyH = MIN<int>(0x82, MIN<int>(anm.height(),
- sh - (int)data.videoY));
+ const int copyH = MIN<int>(anm.height(), sh - (int)data.videoY);
if (copyW <= 0 || copyH <= 0)
return;
@@ -4401,9 +4492,15 @@ bool EEMEngine::doLondonApproach(uint16 approachId) {
const byte *frame = anm.nextFrame();
if (!frame)
break;
- g_system->copyRectToScreen(
- frame + data.videoY * anm.width() + data.videoX,
- anm.width(), data.videoX, data.videoY, copyW, copyH);
+ Graphics::ManagedSurface scratch(sw, sh,
+ Graphics::PixelFormat::createFormatCLUT8());
+ scratch.clear();
+ scratch.copyRectToSurface(frame, anm.width(),
+ data.videoX, data.videoY,
+ copyW, copyH);
+ drawApproachOverlay(scratch, page);
+ g_system->copyRectToScreen(scratch.getPixels(), scratch.pitch,
+ 0, 0, sw, sh);
g_system->updateScreen();
const uint32 start = g_system->getMillis();
@@ -4440,6 +4537,10 @@ bool EEMEngine::doLondonApproach(uint16 approachId) {
fadePaletteFromBlack(palette);
else
setSitePalette(0x3b);
+ if (haveVideo) {
+ playVideo(page);
+ drawScreen(page);
+ }
bool done = false;
uint32 lastShimmer = g_system->getMillis();
while (!shouldQuit() && !done) {
@@ -4452,10 +4553,15 @@ bool EEMEngine::doLondonApproach(uint16 approachId) {
}
if (ev.type == Common::EVENT_MOUSEMOVE) {
bool overButton = false;
- for (uint i = 0; i < ARRAYSIZE(kLondonApproachButtons); i++) {
- if (buttonRect(i).contains(ev.mouse.x, ev.mouse.y)) {
- overButton = true;
- break;
+ if (mac) {
+ overButton = macApproachButton(
+ Common::Point(ev.mouse.x, ev.mouse.y)) >= 0;
+ } else {
+ for (uint i = 0; i < ARRAYSIZE(kLondonApproachButtons); i++) {
+ if (buttonRect(i).contains(ev.mouse.x, ev.mouse.y)) {
+ overButton = true;
+ break;
+ }
}
}
setInteractiveMouseCursor(overButton);
@@ -4468,7 +4574,7 @@ bool EEMEngine::doLondonApproach(uint16 approachId) {
break;
}
if (ev.kbd.keycode == Common::KEYCODE_SPACE) {
- playVideo();
+ playVideo(page);
drawScreen(page);
} else if (ev.kbd.keycode == Common::KEYCODE_RIGHT ||
ev.kbd.keycode == Common::KEYCODE_DOWN) {
@@ -4485,12 +4591,25 @@ bool EEMEngine::doLondonApproach(uint16 approachId) {
}
}
if (ev.type == Common::EVENT_LBUTTONDOWN) {
+ if (mac) {
+ const int button = macApproachButton(
+ Common::Point(ev.mouse.x, ev.mouse.y));
+ if (button == 0) {
+ done = true;
+ break;
+ }
+ if (button == 1) {
+ playVideo(page);
+ drawScreen(page);
+ }
+ continue;
+ }
if (buttonRect(0).contains(ev.mouse.x, ev.mouse.y)) {
done = true;
break;
}
if (buttonRect(1).contains(ev.mouse.x, ev.mouse.y)) {
- playVideo();
+ playVideo(page);
drawScreen(page);
} else if (buttonRect(2).contains(ev.mouse.x, ev.mouse.y)) {
if (page + 1 < data.pages.size()) {
@@ -4505,7 +4624,7 @@ bool EEMEngine::doLondonApproach(uint16 approachId) {
}
}
}
- if (haveVideo) {
+ if (haveVideo && !mac) {
const uint32 now = g_system->getMillis();
if (now - lastShimmer >= 70) {
lastShimmer = now;
Commit: 4762d3458d0625272035b430b82dea1b70223487
https://github.com/scummvm/scummvm/commit/4762d3458d0625272035b430b82dea1b70223487
Author: neuromancer (gustavo.grieco at gmail.com)
Date: 2026-06-26T20:37:27+02:00
Commit Message:
EEM: refactored ui.cpp into two files
Changed paths:
A engines/eem/map_ui.cpp
engines/eem/module.mk
engines/eem/ui.cpp
diff --git a/engines/eem/map_ui.cpp b/engines/eem/map_ui.cpp
new file mode 100644
index 00000000000..36eec5c649b
--- /dev/null
+++ b/engines/eem/map_ui.cpp
@@ -0,0 +1,1319 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "common/algorithm.h"
+#include "common/debug.h"
+#include "common/events.h"
+#include "common/file.h"
+#include "common/path.h"
+#include "common/system.h"
+#include "common/textconsole.h"
+
+#include "graphics/cursorman.h"
+#include "graphics/managed_surface.h"
+#include "graphics/paletteman.h"
+
+#include "video/flic_decoder.h"
+
+#include "eem/animation.h"
+#include "eem/eem.h"
+#include "eem/music.h"
+#include "eem/site.h"
+
+namespace EEM {
+
+struct LondonApproachData {
+ uint16 videoId = 0;
+ uint16 videoX = 0;
+ uint16 videoY = 0;
+ Common::Rect textRect;
+ Common::Array<Common::String> pages;
+};
+
+struct LondonApproachButton {
+ int x1;
+ int y1;
+ int x2;
+ int y2;
+ int x;
+ int y;
+ uint16 picId;
+
+ Common::Rect rect() const { return Common::Rect(x1, y1, x2, y2); }
+};
+
+const LondonApproachButton kLondonApproachButtons[4] = {
+ { 10, 139, 35, 157, 11, 139, 0x361 }, // Done
+ { 10, 172, 35, 190, 11, 172, 0x362 }, // Play
+ { 287, 172, 307, 190, 287, 172, 0x35f }, // Next
+ { 287, 139, 307, 157, 287, 139, 0x360 }, // Previous
+};
+
+const uint16 kMacLondonApproachBackgroundBasePic = 0x38c;
+const int kMacLondonApproachMovieX = 114;
+const int kMacLondonApproachMovieY = 23;
+const int kMacLondonApproachPlayLeft = 9;
+const int kMacLondonApproachPlayTop = 277;
+const int kMacLondonApproachPlayRight = 60;
+const int kMacLondonApproachPlayBottom = 332;
+const int kMacLondonApproachDoneLeft = 452;
+const int kMacLondonApproachDoneTop = 277;
+const int kMacLondonApproachDoneRight = 504;
+const int kMacLondonApproachDoneBottom = 332;
+
+byte mapVisitedMarkerColor(byte color) {
+ switch (color) {
+ case 0xf7:
+ case 0xfb:
+ case 0xfd:
+ return 0x1b;
+ case 0xf8:
+ case 0xf9:
+ case 0xfa:
+ case 0xfc:
+ case 0xfe:
+ return 0x19;
+ default:
+ return color;
+ }
+}
+
+void blitBigMapMarker(Graphics::ManagedSurface &dstSurface, const Picture &marker,
+ int x, int y, bool useVisitedColors) {
+ const byte transp = (byte)(marker.flags >> 8);
+ for (int row = 0; row < marker.surface.h; row++) {
+ const int dstY = y + row;
+ if (dstY < 0 || dstY >= dstSurface.h)
+ continue;
+ const byte *src = (const byte *)marker.surface.getBasePtr(0, row);
+ byte *dst = (byte *)dstSurface.getBasePtr(0, dstY);
+ for (int col = 0; col < marker.surface.w; col++) {
+ const int dstX = x + col;
+ if (dstX < 0 || dstX >= dstSurface.w)
+ continue;
+ if (src[col] != transp)
+ dst[dstX] = useVisitedColors ? mapVisitedMarkerColor(src[col])
+ : src[col];
+ }
+ }
+}
+
+void blitMacBigMapPartnerFrame(Graphics::ManagedSurface &dstSurface,
+ const Picture &frame, int anchorX,
+ int anchorY) {
+ const byte transp = (byte)(frame.flags >> 8);
+ const int x = anchorX - (int)(int16)frame.miscflags;
+ const int y = anchorY - (int)(int16)frame.rowoff;
+ for (int row = 0; row < frame.surface.h; row++) {
+ const int dstY = y + row;
+ if (dstY < 0 || dstY >= dstSurface.h)
+ continue;
+ const byte *src = (const byte *)frame.surface.getBasePtr(0, row);
+ byte *dst = (byte *)dstSurface.getBasePtr(0, dstY);
+ for (int col = 0; col < frame.surface.w; col++) {
+ const int dstX = x + col;
+ if (dstX < 0 || dstX >= dstSurface.w)
+ continue;
+ const byte color = src[col];
+ if (color != transp) {
+ // The map partner frames are authored against the overview
+ // ColorTable: 0 is white and 0xff is black. The detail-map
+ // ColorTable swaps those endpoints.
+ if (color == 0x00)
+ dst[dstX] = 0xff;
+ else if (color == 0xff)
+ dst[dstX] = 0x00;
+ else
+ dst[dstX] = color;
+ }
+ }
+ }
+}
+
+struct BigMapEntryInfo {
+ uint16 overviewX = 0;
+ uint16 overviewY = 0;
+ uint16 detailX = 0;
+ uint16 detailY = 0;
+ uint16 buttonId = 0;
+ uint16 crime = 0;
+};
+
+bool readBigMapEntryInfo(const byte *entry, bool floppy, bool macintosh,
+ BigMapEntryInfo &out) {
+ if (!entry)
+ return false;
+
+ if (macintosh) {
+ out.detailX = READ_LE_UINT16(entry + 0x0);
+ out.detailY = READ_LE_UINT16(entry + 0x2);
+ out.buttonId = entry[0x5];
+ out.overviewX = READ_LE_UINT16(entry + 0x6);
+ out.overviewY = READ_LE_UINT16(entry + 0x8);
+ out.crime = READ_LE_UINT16(entry + 0xa);
+ return true;
+ }
+
+ if (floppy) {
+ out.detailX = READ_LE_UINT16(entry + 0x0);
+ out.detailY = READ_LE_UINT16(entry + 0x2);
+ out.buttonId = entry[0x4];
+ out.overviewX = READ_LE_UINT16(entry + 0x6);
+ out.overviewY = READ_LE_UINT16(entry + 0x8);
+ out.crime = entry[0xa];
+ return true;
+ }
+
+ out.buttonId = READ_LE_UINT16(entry + 0x0);
+ out.overviewX = READ_LE_UINT16(entry + 0x4);
+ out.overviewY = READ_LE_UINT16(entry + 0x6);
+ out.detailX = READ_LE_UINT16(entry + 0x8);
+ out.detailY = READ_LE_UINT16(entry + 0xa);
+ out.crime = READ_LE_UINT16(entry + 0xc);
+ return true;
+}
+
+const uint32 kMacMapColorCycleDelayMs = 25 * 1000 / 60;
+
+bool loadMacBigMapPixels(Common::Array<byte> &mapPixels,
+ uint16 &mapW, uint16 &mapH) {
+ DBDArchive bigMapArchive;
+ if (!bigMapArchive.open(Common::Path("BIGMAP.DBD"),
+ Common::Path("BIGMAP.DBX"), true)) {
+ warning("doBigMap: BIGMAP archive missing");
+ return false;
+ }
+
+ Picture mapPic;
+ if (!bigMapArchive.loadEntry(0, mapPic) || mapPic.surface.empty()) {
+ warning("doBigMap: BIGMAP.DBD entry 0 failed to load");
+ return false;
+ }
+ if (mapPic.surface.w <= 0 || mapPic.surface.h <= 0)
+ return false;
+
+ mapW = (uint16)mapPic.surface.w;
+ mapH = (uint16)mapPic.surface.h;
+ mapPixels.resize((uint32)mapW * mapH);
+ for (uint y = 0; y < mapH; y++) {
+ const byte *src = (const byte *)mapPic.surface.getBasePtr(0, y);
+ byte *dst = mapPixels.data() + y * mapW;
+ // BIGMAP.DBD uses 0xff for dark coast/detail pixels, but the Mac
+ // detail-map palette keeps 0xff white for the UI frame/buttons.
+ for (uint x = 0; x < mapW; x++)
+ dst[x] = src[x] == 0xff ? 0x00 : src[x];
+ }
+ return true;
+}
+
+static bool openNumberedScriptFile(Common::File &f, Common::String &name,
+ uint num, const char *const *patterns,
+ uint patternCount) {
+ for (uint i = 0; i < patternCount; i++) {
+ name = Common::String::format(patterns[i], num);
+ if (f.open(Common::Path(name)))
+ return true;
+ }
+ name.clear();
+ return false;
+}
+
+static uint16 readScriptU16(const byte *p, bool bigEndian) {
+ return bigEndian ? READ_BE_UINT16(p) : READ_LE_UINT16(p);
+}
+
+Common::String cleanLondonApproachPage(const byte *start, uint32 len) {
+ Common::String page((const char *)start, len);
+ while (!page.empty() &&
+ (page.lastChar() == '\r' || page.lastChar() == '\n' ||
+ (byte)page.lastChar() == 0xff))
+ page.deleteLastChar();
+ return page;
+}
+
+bool loadLondonApproachData(uint16 approachId, LondonApproachData &out,
+ bool macintosh) {
+ static const char *const kDosPatterns[] = {
+ "A%u.BIN"
+ };
+ static const char *const kMacPatterns[] = {
+ "Approaches/a%u.bin",
+ "Approaches/A%u.BIN",
+ "a%u.bin",
+ "A%u.BIN"
+ };
+
+ Common::File f;
+ Common::String name;
+ const bool opened = macintosh
+ ? openNumberedScriptFile(f, name, approachId, kMacPatterns,
+ ARRAYSIZE(kMacPatterns))
+ : openNumberedScriptFile(f, name, approachId, kDosPatterns,
+ ARRAYSIZE(kDosPatterns));
+ if (!opened) {
+ warning("London approach: cannot open approach %u", approachId);
+ return false;
+ }
+
+ const uint32 size = f.size();
+ if (size < 16) {
+ warning("London approach: %s is too short", name.c_str());
+ return false;
+ }
+
+ Common::Array<byte> data;
+ data.resize(size);
+ if (f.read(data.data(), size) != size) {
+ warning("London approach: short read on %s", name.c_str());
+ return false;
+ }
+
+ out.videoId = readScriptU16(data.data() + 0, macintosh);
+ out.videoX = readScriptU16(data.data() + 2, macintosh);
+ out.videoY = readScriptU16(data.data() + 4, macintosh);
+ if (macintosh) {
+ const int16 top = (int16)readScriptU16(data.data() + 6, true);
+ const int16 left = (int16)readScriptU16(data.data() + 8, true);
+ const int16 bottom = (int16)readScriptU16(data.data() + 10, true);
+ const int16 right = (int16)readScriptU16(data.data() + 12, true);
+ out.textRect = Common::Rect(left, top, right, bottom);
+ } else {
+ const int16 x1 = (int16)readScriptU16(data.data() + 6, false);
+ const int16 y1 = (int16)readScriptU16(data.data() + 8, false);
+ const int16 x2 = (int16)readScriptU16(data.data() + 10, false);
+ const int16 y2 = (int16)readScriptU16(data.data() + 12, false);
+ out.textRect = Common::Rect(x1, y1, x2, y2);
+ }
+
+ const uint16 pageCount = readScriptU16(data.data() + 14, macintosh);
+ out.pages.clear();
+ uint32 pos = 16;
+ // `_DoApproach @ 1717:009b` reads the pages with `_fgets` into 255-byte
+ // slots, so each page is one NEWLINE-terminated record, NOT NUL-separated.
+ for (uint16 i = 0; i < pageCount && pos < size; i++) {
+ const uint32 start = pos;
+ while (pos < size && data[pos] != '\n')
+ pos++;
+ out.pages.push_back(cleanLondonApproachPage(data.data() + start,
+ pos - start));
+ if (pos < size)
+ pos++; // skip the '\n' separator
+ }
+ return out.videoId != 0 && !out.pages.empty();
+}
+
+bool openLondonApproachFlic(uint16 videoId, Video::FlicDecoder &flic,
+ Common::String &name) {
+ static const char *const kPatterns[] = {
+ "VIDEO%02u.FLC",
+ "video%02u.FLC"
+ };
+
+ for (uint i = 0; i < ARRAYSIZE(kPatterns); i++) {
+ name = Common::String::format(kPatterns[i], videoId);
+ if (flic.loadFile(Common::Path(name)))
+ return true;
+ }
+ name.clear();
+ return false;
+}
+
+bool decodeLondonApproachFirstFrame(uint16 videoId,
+ Graphics::ManagedSurface &base,
+ byte *palette, bool macintosh) {
+ if (macintosh) {
+ Video::FlicDecoder flic;
+ Common::String name;
+ if (!openLondonApproachFlic(videoId, flic, name)) {
+ warning("London approach: cannot open FLC video %u", videoId);
+ return false;
+ }
+
+ flic.start();
+ const Graphics::Surface *frame = flic.decodeNextFrame();
+ if (!frame) {
+ warning("London approach: %s has no first frame", name.c_str());
+ return false;
+ }
+
+ const byte *fpal = flic.getPalette();
+ if (fpal)
+ memcpy(palette, fpal, 768);
+ base.clear();
+ const int w = MIN<int>(frame->w, base.w);
+ const int h = MIN<int>(frame->h, base.h);
+ base.copyRectToSurface(frame->getPixels(), frame->pitch, 0, 0, w, h);
+ return true;
+ }
+
+ const Common::String name = Common::String::format("VIDEO%02u.A", videoId);
+ ANMDecoder anm;
+ if (!anm.open(Common::Path(name))) {
+ warning("London approach: cannot open %s", name.c_str());
+ return false;
+ }
+
+ anm.getPalette8(palette);
+ const byte *frame = anm.nextFrame();
+ if (!frame) {
+ warning("London approach: %s has no first frame", name.c_str());
+ return false;
+ }
+
+ base.clear();
+ const int w = MIN<int>(anm.width(), base.w);
+ const int h = MIN<int>(anm.height(), base.h);
+ base.copyRectToSurface(frame, anm.width(), 0, 0, w, h);
+ return true;
+}
+
+byte closestPaletteIndex(const byte *palette, byte r, byte g, byte b,
+ byte fallback) {
+ if (!palette)
+ return fallback;
+
+ uint bestDist = 0xffffffff;
+ byte best = fallback;
+ for (uint i = 0; i < 256; i++) {
+ const int dr = (int)palette[i * 3 + 0] - r;
+ const int dg = (int)palette[i * 3 + 1] - g;
+ const int db = (int)palette[i * 3 + 2] - b;
+ const uint dist = (uint)(dr * dr + dg * dg + db * db);
+ if (dist < bestDist) {
+ bestDist = dist;
+ best = (byte)i;
+ }
+ }
+ return best;
+}
+
+void remapSurfaceColor(Graphics::ManagedSurface &surface, byte from, byte to) {
+ if (surface.empty() || from == to)
+ return;
+
+ for (int y = 0; y < surface.h; y++) {
+ byte *row = (byte *)surface.getBasePtr(0, y);
+ for (int x = 0; x < surface.w; x++) {
+ if (row[x] == from)
+ row[x] = to;
+ }
+ }
+}
+
+// `_DoBigMap @ 20fe:09e7` two stage:
+// Stage 1 (Overview): PIC 0x42 + site icons at MapData[+4/+6]
+// (`_DrawBigMapButtons @ 20fe:0877`). Click in BigMapWindow
+// returns scroll (mouseX*2 - 0x74, mouseY*2 - 0x55).
+// Stage 2 (Detail): PIC 0x43 frame + 0xe9Ã0xab BIGMAP.PIC viewport
+// at (2,2). Icons stamped at MapData[+8/+0xa] (`_StampButtons @
+// 20fe:0d2f`). Click icon = travel.
+// MapData entry (14 bytes): +0..3 ???, +4 BigMapX, +6 BigMapY,
+// +8 SmallMapX, +0xa SmallMapY, +0xc crime-flag.
+void EEMEngine::doBigMap() {
+
+ if (!_mystery.isLoaded())
+ return;
+
+ if (isLondon()) {
+ _mystery._pendingSiteJump = 0;
+ _mystery._siteReturnDepth = 0;
+ memset(_mystery._siteReturnStack, 0, sizeof(_mystery._siteReturnStack));
+ }
+
+ CursorMan.showMouse(true);
+
+ while (!shouldQuit()) {
+ setInteractiveMouseCursor(false);
+ setSitePalette(isLondon() ? 0x3b : 0x24);
+
+ const uint32 mapStartTick = g_system->getMillis();
+ drawBigMapOverview(0);
+ uint32 mapLastTick = mapStartTick;
+ uint32 mapLastCycleTick = mapStartTick;
+
+ const bool mac = isMacintosh();
+ const Common::Rect bigMapWindowBase(0, 0, 247, 192);
+ const Common::Rect kBigMapWindow =
+ mac ? scaleRect(bigMapWindowBase) : bigMapWindowBase;
+ const Common::Rect setupBtnBase = isFloppy()
+ ? Common::Rect(251, 3, 315, 42)
+ : (isLondon() ? Common::Rect(252, 1, 315, 42)
+ : Common::Rect(252, 4, 315, 42));
+ const Common::Rect kSetupBtnRect =
+ mac ? scaleRect(setupBtnBase) : setupBtnBase;
+
+ bool wantZoom = false;
+ int zoomX = 0;
+ int zoomY = 0;
+ while (!shouldQuit()) {
+ Common::Event ev;
+ while (g_system->getEventManager()->pollEvent(ev)) {
+ if (ev.type == Common::EVENT_QUIT ||
+ ev.type == Common::EVENT_RETURN_TO_LAUNCHER)
+ return;
+ if (ev.type == Common::EVENT_KEYDOWN &&
+ ev.kbd.keycode == Common::KEYCODE_ESCAPE) {
+ openMainMenuDialog();
+ continue;
+ }
+ if (ev.type == Common::EVENT_LBUTTONDOWN) {
+ if (kSetupBtnRect.contains(ev.mouse.x, ev.mouse.y)) {
+ _nextScreen = kScreenSetup;
+ return;
+ }
+
+ if (kBigMapWindow.contains(ev.mouse.x, ev.mouse.y)) {
+ if (mac) {
+ zoomX = ev.mouse.x - kBigMapWindow.left;
+ zoomY = ev.mouse.y - kBigMapWindow.top;
+ } else {
+ int sx = ev.mouse.x * 2;
+ int sy = ev.mouse.y * 2;
+ sx = (sx < 0x75) ? 0 : sx - 0x74;
+ sy = (sy < 0x56) ? 0 : sy - 0x55;
+ zoomX = sx;
+ zoomY = sy;
+ }
+ wantZoom = true;
+ break;
+ }
+ }
+ }
+ if (wantZoom)
+ break;
+
+ const uint32 now = g_system->getMillis();
+ if (mac && isLondon() &&
+ now - mapLastCycleTick >= kMacMapColorCycleDelayMs) {
+ mapLastCycleTick = now;
+ // Mac `_UpdateBigMap` rotates ColorTable entries 0xef..0xf2
+ // and 0xfc..0xff. Redraw immediately so Mac endpoint art maps
+ // its black pixels to the current black slot after the cycle.
+ cyclePaletteRangeReverse(0xef, 0xf2);
+ cyclePaletteRangeReverse(0xfc, 0xff);
+ drawBigMapOverview(now - mapStartTick);
+ mapLastTick = now;
+ } else if (now - mapLastTick >= 100) {
+ mapLastTick = now;
+ drawBigMapOverview(now - mapStartTick);
+ if (isLondon()) {
+ if (!mac) {
+ cyclePaletteRangeReverse(0xf4, 0xf9);
+ cyclePaletteRangeReverse(0xfa, 0xff);
+ }
+ } else {
+ cyclePaletteRangeReverse(0xf7, 0xfa);
+ cyclePaletteRangeReverse(0xfb, 0xfe);
+ }
+ }
+ g_system->updateScreen();
+ g_system->delayMillis(10);
+ }
+
+ if (!wantZoom)
+ return;
+
+ uint16 mapW = 0;
+ uint16 mapH = 0;
+ Common::Array<byte> mapPixels;
+ if (mac) {
+ if (!loadMacBigMapPixels(mapPixels, mapW, mapH))
+ return;
+ } else {
+ Common::File f;
+ if (!f.open(Common::Path("BIGMAP.PIC"))) {
+ warning("doBigMap: BIGMAP.PIC missing for detail view");
+ return;
+ }
+ mapH = f.readUint16LE();
+ mapW = f.readUint16LE();
+ if (mapW == 0 || mapH == 0)
+ return;
+ mapPixels.resize((uint32)mapW * mapH);
+ if (f.read(mapPixels.data(), mapPixels.size()) != mapPixels.size()) {
+ warning("doBigMap: short read on BIGMAP.PIC for detail view");
+ return;
+ }
+ }
+
+ const int kMapWinW = mac ? scaleX(0xe9) : 0xe9; // 233
+ const int kMapWinH = mac ? scaleY(0xab) : 0xab; // 171
+ const int kMapWinX = mac ? scaleX(2) : 2;
+ const int kMapWinY = mac ? scaleY(2) : 2;
+
+ const int maxScrollX = MAX<int>(0, (int)mapW - kMapWinW);
+ const int maxScrollY = MAX<int>(0, (int)mapH - kMapWinH);
+ int scrollX;
+ int scrollY;
+ if (mac) {
+ scrollX = zoomX * (int)mapW /
+ MAX<int>(1, kBigMapWindow.width()) - kMapWinW / 2;
+ scrollY = zoomY * (int)mapH /
+ MAX<int>(1, kBigMapWindow.height()) - kMapWinH / 2;
+ } else {
+ scrollX = zoomX;
+ scrollY = zoomY;
+ }
+ scrollX = MAX<int>(0, MIN<int>(maxScrollX, scrollX));
+ scrollY = MAX<int>(0, MIN<int>(maxScrollY, scrollY));
+
+ setSitePalette(isLondon() ? 0x3a : 0x23);
+
+ const uint32 detailStartTick = g_system->getMillis();
+ drawBigMapDetail(scrollX, scrollY, mapPixels, mapW, mapH, 0);
+ uint32 detailLastTick = detailStartTick;
+ uint32 detailLastCycleTick = detailStartTick;
+ bool returnToOverview = false;
+
+ const Common::Rect returnBase(252, 43, kScreenWidth, kScreenHeight);
+ const Common::Rect kBigMapReturnRect =
+ mac ? scaleRect(returnBase) : returnBase;
+ const Common::Rect kArrowYUp =
+ mac ? scaleRect(Common::Rect(237, 2, 247, 11))
+ : Common::Rect(237, 2, 247, 11);
+ const Common::Rect kArrowYDown =
+ mac ? scaleRect(Common::Rect(237, 163, 247, 172))
+ : Common::Rect(237, 163, 247, 172);
+ const Common::Rect kArrowXLeft =
+ mac ? scaleRect(Common::Rect(2, 175, 12, 185))
+ : Common::Rect(2, 175, 12, 185);
+ const Common::Rect kArrowXRight =
+ mac ? scaleRect(Common::Rect(224, 175, 234, 185))
+ : Common::Rect(224, 175, 234, 185);
+ const Common::Rect xSliderBase = isLondon()
+ ? Common::Rect(15, 176, 220, 184)
+ : Common::Rect(15, 175, 221, 185);
+ const Common::Rect ySliderBase = isLondon()
+ ? Common::Rect(238, 16, 246, 158)
+ : Common::Rect(237, 14, 247, 160);
+ const Common::Rect kXSlider =
+ mac ? scaleRect(xSliderBase) : xSliderBase;
+ const Common::Rect kYSlider =
+ mac ? scaleRect(ySliderBase) : ySliderBase;
+ const Common::Rect detailSetupBase = isFloppy()
+ ? Common::Rect(251, 3, 315, 42)
+ : (isLondon() ? Common::Rect(251, 3, 315, 42)
+ : Common::Rect(252, 4, 315, 42));
+ const Common::Rect kDetailSetupBtn =
+ mac ? scaleRect(detailSetupBase) : detailSetupBase;
+ const int baseArrowStep = isLondon() ? 8 : 16;
+ const int kArrowStepX = mac ? scaleX(baseArrowStep) : baseArrowStep;
+ const int kArrowStepY = mac ? scaleY(baseArrowStep) : baseArrowStep;
+ const int kSliderRange = maxScrollX;
+ const int kSliderRangeY = maxScrollY;
+ const Common::Point detailMouse =
+ g_system->getEventManager()->getMousePos();
+ setInteractiveMouseCursor(
+ kBigMapReturnRect.contains(detailMouse.x, detailMouse.y) ||
+ kDetailSetupBtn.contains(detailMouse.x, detailMouse.y));
+
+ while (!shouldQuit() && !returnToOverview) {
+ Common::Event ev;
+ bool dirty = false;
+ bool cycleTick = false;
+ while (g_system->getEventManager()->pollEvent(ev)) {
+ if (ev.type == Common::EVENT_QUIT ||
+ ev.type == Common::EVENT_RETURN_TO_LAUNCHER) {
+ setInteractiveMouseCursor(false);
+ return;
+ }
+ if (ev.type == Common::EVENT_KEYDOWN) {
+ if (ev.kbd.keycode == Common::KEYCODE_ESCAPE) {
+ openMainMenuDialog();
+ dirty = true;
+ continue;
+ }
+ if (ev.kbd.keycode == Common::KEYCODE_LEFT) {
+ scrollX = MAX<int>(0, scrollX - kArrowStepX);
+ dirty = true;
+ } else if (ev.kbd.keycode == Common::KEYCODE_RIGHT) {
+ scrollX = MIN<int>(maxScrollX, scrollX + kArrowStepX);
+ dirty = true;
+ } else if (ev.kbd.keycode == Common::KEYCODE_UP) {
+ scrollY = MAX<int>(0, scrollY - kArrowStepY);
+ dirty = true;
+ } else if (ev.kbd.keycode == Common::KEYCODE_DOWN) {
+ scrollY = MIN<int>(maxScrollY, scrollY + kArrowStepY);
+ dirty = true;
+ }
+ }
+ if (ev.type == Common::EVENT_MOUSEMOVE)
+ setInteractiveMouseCursor(
+ kBigMapReturnRect.contains(ev.mouse.x, ev.mouse.y) ||
+ kDetailSetupBtn.contains(ev.mouse.x, ev.mouse.y));
+ if (ev.type == Common::EVENT_LBUTTONDOWN) {
+ setInteractiveMouseCursor(
+ kBigMapReturnRect.contains(ev.mouse.x, ev.mouse.y) ||
+ kDetailSetupBtn.contains(ev.mouse.x, ev.mouse.y));
+ if (kDetailSetupBtn.contains(ev.mouse.x, ev.mouse.y)) {
+ _nextScreen = kScreenSetup;
+ setInteractiveMouseCursor(false);
+ return;
+ }
+ if (kBigMapReturnRect.contains(ev.mouse.x, ev.mouse.y)) {
+ returnToOverview = true;
+ break;
+ } else if (kArrowYUp.contains(ev.mouse.x, ev.mouse.y)) {
+ scrollY = MAX<int>(0, scrollY - kArrowStepY);
+ dirty = true;
+ } else if (kArrowYDown.contains(ev.mouse.x, ev.mouse.y)) {
+ scrollY = MIN<int>(MAX<int>(0, kSliderRangeY),
+ scrollY + kArrowStepY);
+ dirty = true;
+ } else if (kArrowXLeft.contains(ev.mouse.x, ev.mouse.y)) {
+ scrollX = MAX<int>(0, scrollX - kArrowStepX);
+ dirty = true;
+ } else if (kArrowXRight.contains(ev.mouse.x, ev.mouse.y)) {
+ scrollX = MIN<int>(MAX<int>(0, kSliderRange),
+ scrollX + kArrowStepX);
+ dirty = true;
+ } else if (kXSlider.contains(ev.mouse.x, ev.mouse.y)) {
+ if (kSliderRange > 0) {
+ const int t = ev.mouse.x - kXSlider.left;
+ const int tw = kXSlider.width();
+ scrollX = MAX<int>(0, MIN<int>(kSliderRange,
+ t * kSliderRange / MAX<int>(1, tw)));
+ dirty = true;
+ }
+ } else if (kYSlider.contains(ev.mouse.x, ev.mouse.y)) {
+ if (kSliderRangeY > 0) {
+ const int t = ev.mouse.y - kYSlider.top;
+ const int th = kYSlider.height();
+ scrollY = MAX<int>(0, MIN<int>(kSliderRangeY,
+ t * kSliderRangeY / MAX<int>(1, th)));
+ dirty = true;
+ }
+ } else if (ev.mouse.x >= kMapWinX &&
+ ev.mouse.x < kMapWinX + kMapWinW &&
+ ev.mouse.y >= kMapWinY &&
+ ev.mouse.y < kMapWinY + kMapWinH) {
+ // Per-site bbox from `_StampButtons` (SmallMap +8/+0xa).
+ const bool fmap = _mystery.isLoaded() && isFloppy();
+ struct DetailMapHit {
+ uint site;
+ Common::Rect rect;
+ };
+ Common::Array<DetailMapHit> hits;
+ for (uint i = 0; i < _mystery.numSites(); i++) {
+ // On-map flag alone, matching `_SearchMapButtons`.
+ if (!_mystery._onSites[i])
+ continue;
+ const byte *entry = _mystery.mapEntry(i);
+ if (!entry)
+ continue;
+ BigMapEntryInfo info;
+ if (!readBigMapEntryInfo(entry, fmap,
+ mac && _mystery.usesCompactMacData(),
+ info))
+ continue;
+ Picture button;
+ int bw = 16;
+ int bh = 16;
+ if (_buttonArchive.loadEntry(info.buttonId, button)) {
+ bw = button.surface.w;
+ bh = button.surface.h;
+ }
+ const int sx = (int)info.detailX - scrollX + kMapWinX;
+ const int sy = (int)info.detailY - scrollY + kMapWinY;
+ const Common::Rect r(sx, sy, sx + bw, sy + bh);
+ if (r.intersects(Common::Rect(kMapWinX, kMapWinY,
+ kMapWinX + kMapWinW, kMapWinY + kMapWinH))) {
+ DetailMapHit hit = { i, r };
+ hits.push_back(hit);
+ }
+ }
+ Common::sort(hits.begin(), hits.end(),
+ [](const DetailMapHit &a, const DetailMapHit &b) {
+ if (a.rect.top != b.rect.top)
+ return a.rect.top < b.rect.top;
+ return a.rect.left < b.rect.left;
+ });
+ for (uint i = 0; i < hits.size(); i++) {
+ if (hits[i].rect.contains(ev.mouse.x, ev.mouse.y)) {
+ _mystery._lastSite = _mystery._siteNumber;
+ _mystery._siteNumber = (uint16)hits[i].site;
+ setInteractiveMouseCursor(false);
+ return;
+ }
+ }
+ }
+ }
+ }
+ if (returnToOverview)
+ break;
+
+ const uint32 now = g_system->getMillis();
+ if (now - detailLastTick >= 100) {
+ detailLastTick = now;
+ dirty = true;
+ }
+ if (isLondon()) {
+ const uint32 cycleDelay = mac ? kMacMapColorCycleDelayMs : 100;
+ if (now - detailLastCycleTick >= cycleDelay) {
+ detailLastCycleTick = now;
+ cycleTick = true;
+ dirty = true;
+ }
+ }
+ if (cycleTick && isLondon()) {
+ if (mac) {
+ cyclePaletteRangeReverse(0xe9, 0xeb);
+ cyclePaletteRangeReverse(0xec, 0xef);
+ cyclePaletteRangeReverse(0xf0, 0xf2);
+ } else {
+ cyclePaletteRangeReverse(0xee, 0xf2);
+ cyclePaletteRangeReverse(0xea, 0xed);
+ }
+ }
+ if (dirty)
+ drawBigMapDetail(scrollX, scrollY, mapPixels, mapW, mapH,
+ now - detailStartTick);
+ g_system->updateScreen();
+ g_system->delayMillis(10);
+ }
+ if (!returnToOverview)
+ return;
+ }
+}
+
+bool EEMEngine::doLondonApproach(uint16 approachId) {
+ if (!isLondon())
+ return false;
+
+ LondonApproachData data;
+ const bool mac = isMacintosh();
+ if (!loadLondonApproachData(approachId, data, mac))
+ return false;
+
+ const int sw = screenWidth();
+ const int sh = screenHeight();
+
+ Graphics::ManagedSurface base(sw, sh,
+ Graphics::PixelFormat::createFormatCLUT8());
+ byte palette[768] = {};
+ const bool haveVideo =
+ decodeLondonApproachFirstFrame(data.videoId, base, palette, mac);
+ if (!haveVideo)
+ base.clear();
+ if (mac) {
+ Picture background;
+ const uint16 backgroundPic =
+ kMacLondonApproachBackgroundBasePic + data.videoId;
+ if (_picsArchive.getPicture(backgroundPic, background) &&
+ !background.surface.empty()) {
+ if (haveVideo) {
+ const byte black =
+ closestPaletteIndex(palette, 0x00, 0x00, 0x00, 0xfe);
+ remapSurfaceColor(background.surface, 0xff, black);
+ }
+ base.clear();
+ base.simpleBlitFrom(background.surface);
+ }
+ }
+
+ Picture buttonPics[ARRAYSIZE(kLondonApproachButtons)];
+ bool haveButtons[ARRAYSIZE(kLondonApproachButtons)] = {};
+ for (uint i = 0; i < ARRAYSIZE(kLondonApproachButtons); i++) {
+ // DOS `_DoApproach` loads PICS 0x361/0x362/0x35f/0x360 as the
+ // four control sprites. In the Mac London data those IDs are
+ // full-screen scrapbook pages, not buttons.
+ haveButtons[i] = !mac && _picsArchive.getPicture(
+ kLondonApproachButtons[i].picId, buttonPics[i]);
+ }
+
+ auto buttonRect = [&](uint idx) {
+ return kLondonApproachButtons[idx].rect();
+ };
+ auto buttonPoint = [&](uint idx) {
+ const LondonApproachButton &b = kLondonApproachButtons[idx];
+ return Common::Point(b.x, b.y);
+ };
+ const int movieX = mac ? kMacLondonApproachMovieX : (int)data.videoX;
+ const int movieY = mac ? kMacLondonApproachMovieY : (int)data.videoY;
+ auto approachTextRect = [&]() {
+ Common::Rect textRect = data.textRect;
+ if (mac)
+ textRect = scaleRect(textRect);
+ return textRect.findIntersectingRect(Common::Rect(sw, sh));
+ };
+ auto macApproachButton = [&](const Common::Point &mouse) {
+ if (mouse.x >= kMacLondonApproachDoneLeft &&
+ mouse.x < kMacLondonApproachDoneRight &&
+ mouse.y >= kMacLondonApproachDoneTop &&
+ mouse.y < kMacLondonApproachDoneBottom)
+ return 0;
+ if (mouse.x >= kMacLondonApproachPlayLeft &&
+ mouse.x < kMacLondonApproachPlayRight &&
+ mouse.y >= kMacLondonApproachPlayTop &&
+ mouse.y < kMacLondonApproachPlayBottom)
+ return 1;
+ return -1;
+ };
+ const byte approachTextColor =
+ closestPaletteIndex(haveVideo ? palette : nullptr, 0, 0, 0,
+ mac ? (byte)0xfe : (byte)1);
+
+ auto drawApproachOverlay = [&](Graphics::ManagedSurface &scratch,
+ uint page) {
+ const Common::Rect textRect = approachTextRect();
+ if (!textRect.isEmpty()) {
+ if (page < data.pages.size()) {
+ Common::Array<Common::String> wrapped;
+ _font.wordWrapText(data.pages[page], MAX<int>(8, textRect.width()),
+ wrapped);
+ const int lineH = _font.getFontHeight();
+ const int maxLines = MAX<int>(1, textRect.height() / lineH);
+ for (uint i = 0; i < wrapped.size() && (int)i < maxLines; i++) {
+ _font.drawString(&scratch, wrapped[i], textRect.left,
+ textRect.top + (int)i * lineH,
+ textRect.width(),
+ approachTextColor);
+ }
+ }
+ }
+
+ for (uint i = 0; i < ARRAYSIZE(kLondonApproachButtons); i++) {
+ if (haveButtons[i]) {
+ const Common::Point p = buttonPoint(i);
+ scratch.transBlitFrom(buttonPics[i].surface, p,
+ (uint32)(byte)(buttonPics[i].flags >> 8));
+ }
+ }
+ };
+
+ auto drawScreen = [&](uint page) {
+ Graphics::ManagedSurface scratch(sw, sh,
+ Graphics::PixelFormat::createFormatCLUT8());
+ scratch.clear();
+ scratch.simpleBlitFrom(base);
+ drawApproachOverlay(scratch, page);
+
+ g_system->copyRectToScreen(scratch.getPixels(), scratch.pitch,
+ 0, 0, sw, sh);
+ g_system->updateScreen();
+ };
+
+ auto playVideo = [&](uint page) {
+ if (mac) {
+ Video::FlicDecoder flic;
+ Common::String name;
+ if (!openLondonApproachFlic(data.videoId, flic, name)) {
+ warning("London approach: cannot open FLC video %u",
+ data.videoId);
+ return;
+ }
+
+ flic.start();
+ while (!shouldQuit() && !flic.endOfVideo()) {
+ const Graphics::Surface *frame = flic.decodeNextFrame();
+ if (!frame)
+ break;
+ Graphics::ManagedSurface scratch(sw, sh,
+ Graphics::PixelFormat::createFormatCLUT8());
+ scratch.clear();
+ scratch.simpleBlitFrom(base);
+ const int copyW = MIN<int>(frame->w, sw - movieX);
+ const int copyH = MIN<int>(frame->h, sh - movieY);
+ if (copyW <= 0 || copyH <= 0)
+ break;
+ scratch.copyRectToSurface(frame->getPixels(), frame->pitch,
+ movieX, movieY,
+ copyW, copyH);
+ drawApproachOverlay(scratch, page);
+ g_system->copyRectToScreen(scratch.getPixels(), scratch.pitch,
+ 0, 0, sw, sh);
+ if (flic.hasDirtyPalette()) {
+ const byte *fpal = flic.getPalette();
+ if (fpal)
+ g_system->getPaletteManager()->setPalette(fpal, 0, 256);
+ }
+ g_system->updateScreen();
+
+ const uint32 start = g_system->getMillis();
+ const uint32 delay = MAX<uint32>(10, flic.getTimeToNextFrame());
+ bool skip = false;
+ while (g_system->getMillis() - start < delay && !skip) {
+ Common::Event ev;
+ while (g_system->getEventManager()->pollEvent(ev)) {
+ if (ev.type == Common::EVENT_QUIT ||
+ ev.type == Common::EVENT_RETURN_TO_LAUNCHER)
+ return;
+ if (ev.type == Common::EVENT_LBUTTONDOWN ||
+ ev.type == Common::EVENT_KEYDOWN) {
+ skip = true;
+ break;
+ }
+ }
+ g_system->delayMillis(5);
+ }
+ if (skip)
+ break;
+ }
+ return;
+ }
+
+ const Common::String name =
+ Common::String::format("VIDEO%02u.A", data.videoId);
+ ANMDecoder anm;
+ if (!anm.open(Common::Path(name))) {
+ warning("London approach: cannot open %s", name.c_str());
+ return;
+ }
+
+ // Frame 0 is already the static background. Replay frames 1..end
+ // with the approach text/buttons composited above it.
+ (void)anm.nextFrame();
+ const int copyW = MIN<int>(anm.width(), sw - (int)data.videoX);
+ const int copyH = MIN<int>(anm.height(), sh - (int)data.videoY);
+ if (copyW <= 0 || copyH <= 0)
+ return;
+
+ while (!shouldQuit()) {
+ const byte *frame = anm.nextFrame();
+ if (!frame)
+ break;
+ Graphics::ManagedSurface scratch(sw, sh,
+ Graphics::PixelFormat::createFormatCLUT8());
+ scratch.clear();
+ scratch.copyRectToSurface(frame, anm.width(),
+ data.videoX, data.videoY,
+ copyW, copyH);
+ drawApproachOverlay(scratch, page);
+ g_system->copyRectToScreen(scratch.getPixels(), scratch.pitch,
+ 0, 0, sw, sh);
+ g_system->updateScreen();
+
+ const uint32 start = g_system->getMillis();
+ bool skip = false;
+ while (g_system->getMillis() - start < 120 && !skip) {
+ Common::Event ev;
+ while (g_system->getEventManager()->pollEvent(ev)) {
+ if (ev.type == Common::EVENT_QUIT ||
+ ev.type == Common::EVENT_RETURN_TO_LAUNCHER) {
+ return;
+ }
+ if (ev.type == Common::EVENT_LBUTTONDOWN ||
+ ev.type == Common::EVENT_KEYDOWN) {
+ skip = true;
+ break;
+ }
+ }
+ g_system->delayMillis(5);
+ }
+ if (skip)
+ break;
+ }
+ };
+
+ fadeCurrentPaletteToBlack();
+ CursorMan.showMouse(true);
+ setSiteHotspotCursorId(6);
+ if (_music && _voiceOn)
+ _music->playMus(0x27, /* loop= */ true);
+
+ uint page = 0;
+ drawScreen(page);
+ if (haveVideo)
+ fadePaletteFromBlack(palette);
+ else
+ setSitePalette(0x3b);
+ if (haveVideo) {
+ playVideo(page);
+ drawScreen(page);
+ }
+ bool done = false;
+ uint32 lastShimmer = g_system->getMillis();
+ while (!shouldQuit() && !done) {
+ Common::Event ev;
+ while (g_system->getEventManager()->pollEvent(ev)) {
+ if (ev.type == Common::EVENT_QUIT ||
+ ev.type == Common::EVENT_RETURN_TO_LAUNCHER) {
+ done = true;
+ break;
+ }
+ if (ev.type == Common::EVENT_MOUSEMOVE) {
+ bool overButton = false;
+ if (mac) {
+ overButton = macApproachButton(
+ Common::Point(ev.mouse.x, ev.mouse.y)) >= 0;
+ } else {
+ for (uint i = 0; i < ARRAYSIZE(kLondonApproachButtons); i++) {
+ if (buttonRect(i).contains(ev.mouse.x, ev.mouse.y)) {
+ overButton = true;
+ break;
+ }
+ }
+ }
+ setInteractiveMouseCursor(overButton);
+ }
+ if (ev.type == Common::EVENT_KEYDOWN) {
+ if (ev.kbd.keycode == Common::KEYCODE_ESCAPE ||
+ ev.kbd.keycode == Common::KEYCODE_RETURN ||
+ ev.kbd.keycode == Common::KEYCODE_KP_ENTER) {
+ done = true;
+ break;
+ }
+ if (ev.kbd.keycode == Common::KEYCODE_SPACE) {
+ playVideo(page);
+ drawScreen(page);
+ } else if (ev.kbd.keycode == Common::KEYCODE_RIGHT ||
+ ev.kbd.keycode == Common::KEYCODE_DOWN) {
+ if (page + 1 < data.pages.size()) {
+ page++;
+ drawScreen(page);
+ }
+ } else if (ev.kbd.keycode == Common::KEYCODE_LEFT ||
+ ev.kbd.keycode == Common::KEYCODE_UP) {
+ if (page > 0) {
+ page--;
+ drawScreen(page);
+ }
+ }
+ }
+ if (ev.type == Common::EVENT_LBUTTONDOWN) {
+ if (mac) {
+ const int button = macApproachButton(
+ Common::Point(ev.mouse.x, ev.mouse.y));
+ if (button == 0) {
+ done = true;
+ break;
+ }
+ if (button == 1) {
+ playVideo(page);
+ drawScreen(page);
+ }
+ continue;
+ }
+ if (buttonRect(0).contains(ev.mouse.x, ev.mouse.y)) {
+ done = true;
+ break;
+ }
+ if (buttonRect(1).contains(ev.mouse.x, ev.mouse.y)) {
+ playVideo(page);
+ drawScreen(page);
+ } else if (buttonRect(2).contains(ev.mouse.x, ev.mouse.y)) {
+ if (page + 1 < data.pages.size()) {
+ page++;
+ drawScreen(page);
+ }
+ } else if (buttonRect(3).contains(ev.mouse.x, ev.mouse.y)) {
+ if (page > 0) {
+ page--;
+ drawScreen(page);
+ }
+ }
+ }
+ }
+ if (haveVideo && !mac) {
+ const uint32 now = g_system->getMillis();
+ if (now - lastShimmer >= 70) {
+ lastShimmer = now;
+ cyclePaletteRange(0xF3, 0xF7);
+ }
+ }
+ g_system->updateScreen();
+ g_system->delayMillis(10);
+ }
+
+ setInteractiveMouseCursor(false);
+ setSiteHotspotCursorId(0);
+ stopMusic();
+ return true;
+}
+
+void EEMEngine::drawBigMapOverview(uint32 elapsedMs) {
+ const bool mac = isMacintosh();
+ const int sw = screenWidth();
+ const int sh = screenHeight();
+ Graphics::ManagedSurface scratch(sw, sh,
+ Graphics::PixelFormat::createFormatCLUT8());
+ scratch.clear();
+
+ Picture frame;
+ if (_picsArchive.getPicture(0x42, frame)) {
+ if (mac)
+ remapMacSurfaceEndpoints(frame.surface, getMacSpritePaletteMap());
+ scratch.simpleBlitFrom(frame.surface);
+ }
+
+ // Marker PICs from `_main`:
+ // EEM1 CD @ 1a35:0f59: Done=0x20d, Site=0xc5, Crime=0xc6.
+ // EEM2 CD @ 1abf:11a6: Done=0x006, Site=0xc5, Crime=0xc6.
+ Picture done;
+ Picture normal;
+ Picture crimeM;
+ const bool haveDone = _picsArchive.getPicture(isLondon() ? 0x006 : 0x20d, done) &&
+ done.surface.w < 64 && done.surface.h < 64;
+ const bool haveNormal = _picsArchive.getPicture(0xc5, normal) &&
+ normal.surface.w < 64 && normal.surface.h < 64;
+ const bool haveCrime = _picsArchive.getPicture(0xc6, crimeM) &&
+ crimeM.surface.w < 64 && crimeM.surface.h < 64;
+
+ for (uint i = 0; i < _mystery.numSites(); i++) {
+ // `_DrawBigMapButtons` gates markers on the on-map flag alone, never the
+ // current site: a sublocation (in-site jump, never flagged) must not draw.
+ if (!_mystery._onSites[i])
+ continue;
+ const byte *entry = _mystery.mapEntry(i);
+ if (!entry)
+ continue;
+
+ const bool floppy = _mystery.isLoaded() && isFloppy();
+ BigMapEntryInfo info;
+ if (!readBigMapEntryInfo(entry, floppy,
+ mac && _mystery.usesCompactMacData(), info))
+ continue;
+ const bool isDone = (i < Mystery::kVisitedSiteCap)
+ && _mystery._visitedSite[i];
+
+ const Picture *m = nullptr;
+ bool useVisitedColors = false;
+ if (isDone && haveDone) {
+ m = &done;
+ } else if (isDone && haveNormal) {
+ m = &normal;
+ useVisitedColors = true;
+ } else if (info.crime != 0 && haveCrime) {
+ m = &crimeM;
+ } else if (haveNormal) {
+ m = &normal;
+ }
+
+ if (m) {
+ blitBigMapMarker(scratch, *m, (int)info.overviewX,
+ (int)info.overviewY,
+ useVisitedColors);
+ } else {
+ const int mx = (int)info.overviewX;
+ const int my = (int)info.overviewY;
+ const Common::Rect mark(mx - 3, my - 3, mx + 4, my + 4);
+ scratch.fillRect(mark, 0x0F);
+ }
+ }
+
+ const uint kMapAniId = (_partner == kPartnerJake) ? 0x14 : 0x12;
+ Animation mapAnim;
+ if (_aniArchive.loadAnimation(kMapAniId, mapAnim) && !mapAnim.empty()) {
+ const uint frameIdx = bigMapPartnerFrameAtTick((uint)mapAnim.size(),
+ elapsedMs, isLondon());
+
+ const int anchorX = mac ? scaleX(0xfd) : 0xfd;
+ const int anchorY = mac ? scaleY(0x50) : 0x50;
+ if (mac)
+ blitMacAnimFrameAnchored(scratch.surfacePtr(), mapAnim[frameIdx],
+ anchorX, anchorY);
+ else
+ blitAnimFrameAnchored(scratch.surfacePtr(), mapAnim[frameIdx],
+ anchorX, anchorY);
+ }
+
+ g_system->copyRectToScreen(scratch.getPixels(), scratch.pitch,
+ 0, 0, sw, sh);
+ g_system->updateScreen();
+}
+
+void EEMEngine::drawBigMapDetail(int scrollX, int scrollY,
+ const Common::Array<byte> &mapPixels,
+ uint16 mapW, uint16 mapH,
+ uint32 elapsedMs) {
+
+ const bool mac = isMacintosh();
+ const int sw = screenWidth();
+ const int sh = screenHeight();
+ const int kMapWinW = mac ? scaleX(0xe9) : 0xe9;
+ const int kMapWinH = mac ? scaleY(0xab) : 0xab;
+ const int kMapWinX = mac ? scaleX(2) : 2;
+ const int kMapWinY = mac ? scaleY(2) : 2;
+
+ Graphics::ManagedSurface scratch(sw, sh,
+ Graphics::PixelFormat::createFormatCLUT8());
+ scratch.clear();
+
+ Picture frame;
+ if (_picsArchive.getPicture(0x43, frame)) {
+ // The frame and its buttons use the normal 0x00=white / 0xFF=black
+ // convention, but the detail-map ColorTable (0x23) swaps those endpoints,
+ // so normalize them or the SETUP button's black border renders white.
+ if (mac)
+ remapMacSurfaceEndpoints(frame.surface, getMacSpritePaletteMap());
+ scratch.simpleBlitFrom(frame.surface);
+ }
+
+ const int copyW = MIN<int>(mapW - scrollX, kMapWinW);
+ const int copyH = MIN<int>(mapH - scrollY, kMapWinH);
+ scratch.copyRectToSurface(mapPixels.data() + scrollY * mapW + scrollX,
+ mapW, kMapWinX, kMapWinY, copyW, copyH);
+
+ const bool floppyMap = _mystery.isLoaded() && isFloppy();
+ for (uint i = 0; i < _mystery.numSites(); i++) {
+ // `_DrawBigMapButtons` gates markers on the on-map flag alone, never the
+ // current site: a sublocation (in-site jump, never flagged) must not draw.
+ if (!_mystery._onSites[i])
+ continue;
+ const byte *entry = _mystery.mapEntry(i);
+ if (!entry)
+ continue;
+ BigMapEntryInfo info;
+ if (!readBigMapEntryInfo(entry, floppyMap,
+ mac && _mystery.usesCompactMacData(), info))
+ continue;
+ Picture button;
+ if (!_buttonArchive.loadEntry(info.buttonId, button))
+ continue;
+ const int sx = (int)info.detailX - scrollX + kMapWinX;
+ const int sy = (int)info.detailY - scrollY + kMapWinY;
+ // Crop the button blit against the viewport.
+ const int x0 = MAX<int>(sx, kMapWinX);
+ const int y0 = MAX<int>(sy, kMapWinY);
+ const int x1 = MIN<int>(sx + button.surface.w, kMapWinX + kMapWinW);
+ const int y1 = MIN<int>(sy + button.surface.h, kMapWinY + kMapWinH);
+ if (x1 > x0 && y1 > y0) {
+ const Common::Rect srcRect(x0 - sx, y0 - sy, x1 - sx, y1 - sy);
+ scratch.transBlitFrom(button.surface, srcRect,
+ Common::Point(x0, y0),
+ (uint32)(byte)(button.flags >> 8));
+ }
+ }
+
+ const uint kDetailAniId = (_partner == kPartnerJake) ? 0x13 : 0x11;
+ Animation detailAnim;
+ if (_aniArchive.loadAnimation(kDetailAniId, detailAnim) &&
+ !detailAnim.empty()) {
+ const uint frameIdx = bigMapDetailPartnerFrameAtTick(
+ (uint)detailAnim.size(), elapsedMs);
+ const int anchorX = mac ? scaleX(0x101) : 0x101;
+ const int anchorY = mac ? scaleY(0x50) : 0x50;
+ if (mac)
+ blitMacBigMapPartnerFrame(scratch, detailAnim[frameIdx],
+ anchorX, anchorY);
+ else
+ blitAnimFrameAnchored(scratch.surfacePtr(),
+ detailAnim[frameIdx],
+ anchorX, anchorY);
+ }
+
+ g_system->copyRectToScreen(scratch.getPixels(), scratch.pitch,
+ 0, 0, sw, sh);
+ g_system->updateScreen();
+}
+
+
+} // End of namespace EEM
diff --git a/engines/eem/module.mk b/engines/eem/module.mk
index 917289b1522..4b708614703 100644
--- a/engines/eem/module.mk
+++ b/engines/eem/module.mk
@@ -8,6 +8,7 @@ MODULE_OBJS = \
font.o \
graphics.o \
installer.o \
+ map_ui.o \
metaengine.o \
music.o \
mystery.o \
diff --git a/engines/eem/ui.cpp b/engines/eem/ui.cpp
index 1eb8624e259..cf4de4ea63c 100644
--- a/engines/eem/ui.cpp
+++ b/engines/eem/ui.cpp
@@ -31,9 +31,6 @@
#include "graphics/cursorman.h"
#include "graphics/managed_surface.h"
-#include "graphics/paletteman.h"
-
-#include "video/flic_decoder.h"
#include "eem/audio.h"
#include "eem/detection.h"
@@ -60,190 +57,6 @@ const GallerySlot kMacGallerySlots[5] = {
{ 306, 173 } // 4
};
-struct LondonApproachData {
- uint16 videoId = 0;
- uint16 videoX = 0;
- uint16 videoY = 0;
- Common::Rect textRect;
- Common::Array<Common::String> pages;
-};
-
-struct LondonApproachButton {
- int x1;
- int y1;
- int x2;
- int y2;
- int x;
- int y;
- uint16 picId;
-
- Common::Rect rect() const { return Common::Rect(x1, y1, x2, y2); }
-};
-
-const LondonApproachButton kLondonApproachButtons[4] = {
- { 10, 139, 35, 157, 11, 139, 0x361 }, // Done
- { 10, 172, 35, 190, 11, 172, 0x362 }, // Play
- { 287, 172, 307, 190, 287, 172, 0x35f }, // Next
- { 287, 139, 307, 157, 287, 139, 0x360 }, // Previous
-};
-
-const uint16 kMacLondonApproachBackgroundBasePic = 0x38c;
-const int kMacLondonApproachMovieX = 114;
-const int kMacLondonApproachMovieY = 23;
-const int kMacLondonApproachPlayLeft = 9;
-const int kMacLondonApproachPlayTop = 277;
-const int kMacLondonApproachPlayRight = 60;
-const int kMacLondonApproachPlayBottom = 332;
-const int kMacLondonApproachDoneLeft = 452;
-const int kMacLondonApproachDoneTop = 277;
-const int kMacLondonApproachDoneRight = 504;
-const int kMacLondonApproachDoneBottom = 332;
-
-byte mapVisitedMarkerColor(byte color) {
- switch (color) {
- case 0xf7:
- case 0xfb:
- case 0xfd:
- return 0x1b;
- case 0xf8:
- case 0xf9:
- case 0xfa:
- case 0xfc:
- case 0xfe:
- return 0x19;
- default:
- return color;
- }
-}
-
-void blitBigMapMarker(Graphics::ManagedSurface &dstSurface, const Picture &marker,
- int x, int y, bool useVisitedColors) {
- const byte transp = (byte)(marker.flags >> 8);
- for (int row = 0; row < marker.surface.h; row++) {
- const int dstY = y + row;
- if (dstY < 0 || dstY >= dstSurface.h)
- continue;
- const byte *src = (const byte *)marker.surface.getBasePtr(0, row);
- byte *dst = (byte *)dstSurface.getBasePtr(0, dstY);
- for (int col = 0; col < marker.surface.w; col++) {
- const int dstX = x + col;
- if (dstX < 0 || dstX >= dstSurface.w)
- continue;
- if (src[col] != transp)
- dst[dstX] = useVisitedColors ? mapVisitedMarkerColor(src[col])
- : src[col];
- }
- }
-}
-
-void blitMacBigMapPartnerFrame(Graphics::ManagedSurface &dstSurface,
- const Picture &frame, int anchorX,
- int anchorY) {
- const byte transp = (byte)(frame.flags >> 8);
- const int x = anchorX - (int)(int16)frame.miscflags;
- const int y = anchorY - (int)(int16)frame.rowoff;
- for (int row = 0; row < frame.surface.h; row++) {
- const int dstY = y + row;
- if (dstY < 0 || dstY >= dstSurface.h)
- continue;
- const byte *src = (const byte *)frame.surface.getBasePtr(0, row);
- byte *dst = (byte *)dstSurface.getBasePtr(0, dstY);
- for (int col = 0; col < frame.surface.w; col++) {
- const int dstX = x + col;
- if (dstX < 0 || dstX >= dstSurface.w)
- continue;
- const byte color = src[col];
- if (color != transp) {
- // The map partner frames are authored against the overview
- // ColorTable: 0 is white and 0xff is black. The detail-map
- // ColorTable swaps those endpoints.
- if (color == 0x00)
- dst[dstX] = 0xff;
- else if (color == 0xff)
- dst[dstX] = 0x00;
- else
- dst[dstX] = color;
- }
- }
- }
-}
-
-struct BigMapEntryInfo {
- uint16 overviewX = 0;
- uint16 overviewY = 0;
- uint16 detailX = 0;
- uint16 detailY = 0;
- uint16 buttonId = 0;
- uint16 crime = 0;
-};
-
-bool readBigMapEntryInfo(const byte *entry, bool floppy, bool macintosh,
- BigMapEntryInfo &out) {
- if (!entry)
- return false;
-
- if (macintosh) {
- out.detailX = READ_LE_UINT16(entry + 0x0);
- out.detailY = READ_LE_UINT16(entry + 0x2);
- out.buttonId = entry[0x5];
- out.overviewX = READ_LE_UINT16(entry + 0x6);
- out.overviewY = READ_LE_UINT16(entry + 0x8);
- out.crime = READ_LE_UINT16(entry + 0xa);
- return true;
- }
-
- if (floppy) {
- out.detailX = READ_LE_UINT16(entry + 0x0);
- out.detailY = READ_LE_UINT16(entry + 0x2);
- out.buttonId = entry[0x4];
- out.overviewX = READ_LE_UINT16(entry + 0x6);
- out.overviewY = READ_LE_UINT16(entry + 0x8);
- out.crime = entry[0xa];
- return true;
- }
-
- out.buttonId = READ_LE_UINT16(entry + 0x0);
- out.overviewX = READ_LE_UINT16(entry + 0x4);
- out.overviewY = READ_LE_UINT16(entry + 0x6);
- out.detailX = READ_LE_UINT16(entry + 0x8);
- out.detailY = READ_LE_UINT16(entry + 0xa);
- out.crime = READ_LE_UINT16(entry + 0xc);
- return true;
-}
-
-const uint32 kMacMapColorCycleDelayMs = 25 * 1000 / 60;
-
-bool loadMacBigMapPixels(Common::Array<byte> &mapPixels,
- uint16 &mapW, uint16 &mapH) {
- DBDArchive bigMapArchive;
- if (!bigMapArchive.open(Common::Path("BIGMAP.DBD"),
- Common::Path("BIGMAP.DBX"), true)) {
- warning("doBigMap: BIGMAP archive missing");
- return false;
- }
-
- Picture mapPic;
- if (!bigMapArchive.loadEntry(0, mapPic) || mapPic.surface.empty()) {
- warning("doBigMap: BIGMAP.DBD entry 0 failed to load");
- return false;
- }
- if (mapPic.surface.w <= 0 || mapPic.surface.h <= 0)
- return false;
-
- mapW = (uint16)mapPic.surface.w;
- mapH = (uint16)mapPic.surface.h;
- mapPixels.resize((uint32)mapW * mapH);
- for (uint y = 0; y < mapH; y++) {
- const byte *src = (const byte *)mapPic.surface.getBasePtr(0, y);
- byte *dst = mapPixels.data() + y * mapW;
- // BIGMAP.DBD uses 0xff for dark coast/detail pixels, but the Mac
- // detail-map palette keeps 0xff white for the UI frame/buttons.
- for (uint x = 0; x < mapW; x++)
- dst[x] = src[x] == 0xff ? 0x00 : src[x];
- }
- return true;
-}
-
static bool openNumberedScriptFile(Common::File &f, Common::String &name,
uint num, const char *const *patterns,
uint patternCount) {
@@ -260,184 +73,6 @@ static uint16 readScriptU16(const byte *p, bool bigEndian) {
return bigEndian ? READ_BE_UINT16(p) : READ_LE_UINT16(p);
}
-Common::String cleanLondonApproachPage(const byte *start, uint32 len) {
- Common::String page((const char *)start, len);
- while (!page.empty() &&
- (page.lastChar() == '\r' || page.lastChar() == '\n' ||
- (byte)page.lastChar() == 0xff))
- page.deleteLastChar();
- return page;
-}
-
-bool loadLondonApproachData(uint16 approachId, LondonApproachData &out,
- bool macintosh) {
- static const char *const kDosPatterns[] = {
- "A%u.BIN"
- };
- static const char *const kMacPatterns[] = {
- "Approaches/a%u.bin",
- "Approaches/A%u.BIN",
- "a%u.bin",
- "A%u.BIN"
- };
-
- Common::File f;
- Common::String name;
- const bool opened = macintosh
- ? openNumberedScriptFile(f, name, approachId, kMacPatterns,
- ARRAYSIZE(kMacPatterns))
- : openNumberedScriptFile(f, name, approachId, kDosPatterns,
- ARRAYSIZE(kDosPatterns));
- if (!opened) {
- warning("London approach: cannot open approach %u", approachId);
- return false;
- }
-
- const uint32 size = f.size();
- if (size < 16) {
- warning("London approach: %s is too short", name.c_str());
- return false;
- }
-
- Common::Array<byte> data;
- data.resize(size);
- if (f.read(data.data(), size) != size) {
- warning("London approach: short read on %s", name.c_str());
- return false;
- }
-
- out.videoId = readScriptU16(data.data() + 0, macintosh);
- out.videoX = readScriptU16(data.data() + 2, macintosh);
- out.videoY = readScriptU16(data.data() + 4, macintosh);
- if (macintosh) {
- const int16 top = (int16)readScriptU16(data.data() + 6, true);
- const int16 left = (int16)readScriptU16(data.data() + 8, true);
- const int16 bottom = (int16)readScriptU16(data.data() + 10, true);
- const int16 right = (int16)readScriptU16(data.data() + 12, true);
- out.textRect = Common::Rect(left, top, right, bottom);
- } else {
- const int16 x1 = (int16)readScriptU16(data.data() + 6, false);
- const int16 y1 = (int16)readScriptU16(data.data() + 8, false);
- const int16 x2 = (int16)readScriptU16(data.data() + 10, false);
- const int16 y2 = (int16)readScriptU16(data.data() + 12, false);
- out.textRect = Common::Rect(x1, y1, x2, y2);
- }
-
- const uint16 pageCount = readScriptU16(data.data() + 14, macintosh);
- out.pages.clear();
- uint32 pos = 16;
- // `_DoApproach @ 1717:009b` reads the pages with `_fgets` into 255-byte
- // slots, so each page is one NEWLINE-terminated record, NOT NUL-separated.
- for (uint16 i = 0; i < pageCount && pos < size; i++) {
- const uint32 start = pos;
- while (pos < size && data[pos] != '\n')
- pos++;
- out.pages.push_back(cleanLondonApproachPage(data.data() + start,
- pos - start));
- if (pos < size)
- pos++; // skip the '\n' separator
- }
- return out.videoId != 0 && !out.pages.empty();
-}
-
-bool openLondonApproachFlic(uint16 videoId, Video::FlicDecoder &flic,
- Common::String &name) {
- static const char *const kPatterns[] = {
- "VIDEO%02u.FLC",
- "video%02u.FLC"
- };
-
- for (uint i = 0; i < ARRAYSIZE(kPatterns); i++) {
- name = Common::String::format(kPatterns[i], videoId);
- if (flic.loadFile(Common::Path(name)))
- return true;
- }
- name.clear();
- return false;
-}
-
-bool decodeLondonApproachFirstFrame(uint16 videoId,
- Graphics::ManagedSurface &base,
- byte *palette, bool macintosh) {
- if (macintosh) {
- Video::FlicDecoder flic;
- Common::String name;
- if (!openLondonApproachFlic(videoId, flic, name)) {
- warning("London approach: cannot open FLC video %u", videoId);
- return false;
- }
-
- flic.start();
- const Graphics::Surface *frame = flic.decodeNextFrame();
- if (!frame) {
- warning("London approach: %s has no first frame", name.c_str());
- return false;
- }
-
- const byte *fpal = flic.getPalette();
- if (fpal)
- memcpy(palette, fpal, 768);
- base.clear();
- const int w = MIN<int>(frame->w, base.w);
- const int h = MIN<int>(frame->h, base.h);
- base.copyRectToSurface(frame->getPixels(), frame->pitch, 0, 0, w, h);
- return true;
- }
-
- const Common::String name = Common::String::format("VIDEO%02u.A", videoId);
- ANMDecoder anm;
- if (!anm.open(Common::Path(name))) {
- warning("London approach: cannot open %s", name.c_str());
- return false;
- }
-
- anm.getPalette8(palette);
- const byte *frame = anm.nextFrame();
- if (!frame) {
- warning("London approach: %s has no first frame", name.c_str());
- return false;
- }
-
- base.clear();
- const int w = MIN<int>(anm.width(), base.w);
- const int h = MIN<int>(anm.height(), base.h);
- base.copyRectToSurface(frame, anm.width(), 0, 0, w, h);
- return true;
-}
-
-byte closestPaletteIndex(const byte *palette, byte r, byte g, byte b,
- byte fallback) {
- if (!palette)
- return fallback;
-
- uint bestDist = 0xffffffff;
- byte best = fallback;
- for (uint i = 0; i < 256; i++) {
- const int dr = (int)palette[i * 3 + 0] - r;
- const int dg = (int)palette[i * 3 + 1] - g;
- const int db = (int)palette[i * 3 + 2] - b;
- const uint dist = (uint)(dr * dr + dg * dg + db * db);
- if (dist < bestDist) {
- bestDist = dist;
- best = (byte)i;
- }
- }
- return best;
-}
-
-void remapSurfaceColor(Graphics::ManagedSurface &surface, byte from, byte to) {
- if (surface.empty() || from == to)
- return;
-
- for (int y = 0; y < surface.h; y++) {
- byte *row = (byte *)surface.getBasePtr(0, y);
- for (int x = 0; x < surface.w; x++) {
- if (row[x] == from)
- row[x] = to;
- }
- }
-}
-
// Setup-screen highlight rects
constexpr Common::Rect kSetupKid1Rect (Common::Point( 99, 44), 49, 8);
constexpr Common::Rect kSetupKid2Rect (Common::Point( 99, 54), 49, 8);
@@ -3920,903 +3555,6 @@ void EEMEngine::drawGalleryFrame(const byte *gd, uint8 numSuspects,
g_system->updateScreen();
}
-// `_DoBigMap @ 20fe:09e7` two stage:
-// Stage 1 (Overview): PIC 0x42 + site icons at MapData[+4/+6]
-// (`_DrawBigMapButtons @ 20fe:0877`). Click in BigMapWindow
-// returns scroll (mouseX*2 - 0x74, mouseY*2 - 0x55).
-// Stage 2 (Detail): PIC 0x43 frame + 0xe9Ã0xab BIGMAP.PIC viewport
-// at (2,2). Icons stamped at MapData[+8/+0xa] (`_StampButtons @
-// 20fe:0d2f`). Click icon = travel.
-// MapData entry (14 bytes): +0..3 ???, +4 BigMapX, +6 BigMapY,
-// +8 SmallMapX, +0xa SmallMapY, +0xc crime-flag.
-void EEMEngine::doBigMap() {
-
- if (!_mystery.isLoaded())
- return;
-
- if (isLondon()) {
- _mystery._pendingSiteJump = 0;
- _mystery._siteReturnDepth = 0;
- memset(_mystery._siteReturnStack, 0, sizeof(_mystery._siteReturnStack));
- }
-
- CursorMan.showMouse(true);
-
- while (!shouldQuit()) {
- setInteractiveMouseCursor(false);
- setSitePalette(isLondon() ? 0x3b : 0x24);
-
- const uint32 mapStartTick = g_system->getMillis();
- drawBigMapOverview(0);
- uint32 mapLastTick = mapStartTick;
- uint32 mapLastCycleTick = mapStartTick;
-
- const bool mac = isMacintosh();
- const Common::Rect bigMapWindowBase(0, 0, 247, 192);
- const Common::Rect kBigMapWindow =
- mac ? scaleRect(bigMapWindowBase) : bigMapWindowBase;
- const Common::Rect setupBtnBase = isFloppy()
- ? Common::Rect(251, 3, 315, 42)
- : (isLondon() ? Common::Rect(252, 1, 315, 42)
- : Common::Rect(252, 4, 315, 42));
- const Common::Rect kSetupBtnRect =
- mac ? scaleRect(setupBtnBase) : setupBtnBase;
-
- bool wantZoom = false;
- int zoomX = 0;
- int zoomY = 0;
- while (!shouldQuit()) {
- Common::Event ev;
- while (g_system->getEventManager()->pollEvent(ev)) {
- if (ev.type == Common::EVENT_QUIT ||
- ev.type == Common::EVENT_RETURN_TO_LAUNCHER)
- return;
- if (ev.type == Common::EVENT_KEYDOWN &&
- ev.kbd.keycode == Common::KEYCODE_ESCAPE) {
- openMainMenuDialog();
- continue;
- }
- if (ev.type == Common::EVENT_LBUTTONDOWN) {
- if (kSetupBtnRect.contains(ev.mouse.x, ev.mouse.y)) {
- _nextScreen = kScreenSetup;
- return;
- }
-
- if (kBigMapWindow.contains(ev.mouse.x, ev.mouse.y)) {
- if (mac) {
- zoomX = ev.mouse.x - kBigMapWindow.left;
- zoomY = ev.mouse.y - kBigMapWindow.top;
- } else {
- int sx = ev.mouse.x * 2;
- int sy = ev.mouse.y * 2;
- sx = (sx < 0x75) ? 0 : sx - 0x74;
- sy = (sy < 0x56) ? 0 : sy - 0x55;
- zoomX = sx;
- zoomY = sy;
- }
- wantZoom = true;
- break;
- }
- }
- }
- if (wantZoom)
- break;
-
- const uint32 now = g_system->getMillis();
- if (mac && isLondon() &&
- now - mapLastCycleTick >= kMacMapColorCycleDelayMs) {
- mapLastCycleTick = now;
- // Mac `_UpdateBigMap` rotates ColorTable entries 0xef..0xf2
- // and 0xfc..0xff. Redraw immediately so Mac endpoint art maps
- // its black pixels to the current black slot after the cycle.
- cyclePaletteRangeReverse(0xef, 0xf2);
- cyclePaletteRangeReverse(0xfc, 0xff);
- drawBigMapOverview(now - mapStartTick);
- mapLastTick = now;
- } else if (now - mapLastTick >= 100) {
- mapLastTick = now;
- drawBigMapOverview(now - mapStartTick);
- if (isLondon()) {
- if (!mac) {
- cyclePaletteRangeReverse(0xf4, 0xf9);
- cyclePaletteRangeReverse(0xfa, 0xff);
- }
- } else {
- cyclePaletteRangeReverse(0xf7, 0xfa);
- cyclePaletteRangeReverse(0xfb, 0xfe);
- }
- }
- g_system->updateScreen();
- g_system->delayMillis(10);
- }
-
- if (!wantZoom)
- return;
-
- uint16 mapW = 0;
- uint16 mapH = 0;
- Common::Array<byte> mapPixels;
- if (mac) {
- if (!loadMacBigMapPixels(mapPixels, mapW, mapH))
- return;
- } else {
- Common::File f;
- if (!f.open(Common::Path("BIGMAP.PIC"))) {
- warning("doBigMap: BIGMAP.PIC missing for detail view");
- return;
- }
- mapH = f.readUint16LE();
- mapW = f.readUint16LE();
- if (mapW == 0 || mapH == 0)
- return;
- mapPixels.resize((uint32)mapW * mapH);
- if (f.read(mapPixels.data(), mapPixels.size()) != mapPixels.size()) {
- warning("doBigMap: short read on BIGMAP.PIC for detail view");
- return;
- }
- }
-
- const int kMapWinW = mac ? scaleX(0xe9) : 0xe9; // 233
- const int kMapWinH = mac ? scaleY(0xab) : 0xab; // 171
- const int kMapWinX = mac ? scaleX(2) : 2;
- const int kMapWinY = mac ? scaleY(2) : 2;
-
- const int maxScrollX = MAX<int>(0, (int)mapW - kMapWinW);
- const int maxScrollY = MAX<int>(0, (int)mapH - kMapWinH);
- int scrollX;
- int scrollY;
- if (mac) {
- scrollX = zoomX * (int)mapW /
- MAX<int>(1, kBigMapWindow.width()) - kMapWinW / 2;
- scrollY = zoomY * (int)mapH /
- MAX<int>(1, kBigMapWindow.height()) - kMapWinH / 2;
- } else {
- scrollX = zoomX;
- scrollY = zoomY;
- }
- scrollX = MAX<int>(0, MIN<int>(maxScrollX, scrollX));
- scrollY = MAX<int>(0, MIN<int>(maxScrollY, scrollY));
-
- setSitePalette(isLondon() ? 0x3a : 0x23);
-
- const uint32 detailStartTick = g_system->getMillis();
- drawBigMapDetail(scrollX, scrollY, mapPixels, mapW, mapH, 0);
- uint32 detailLastTick = detailStartTick;
- uint32 detailLastCycleTick = detailStartTick;
- bool returnToOverview = false;
-
- const Common::Rect returnBase(252, 43, kScreenWidth, kScreenHeight);
- const Common::Rect kBigMapReturnRect =
- mac ? scaleRect(returnBase) : returnBase;
- const Common::Rect kArrowYUp =
- mac ? scaleRect(Common::Rect(237, 2, 247, 11))
- : Common::Rect(237, 2, 247, 11);
- const Common::Rect kArrowYDown =
- mac ? scaleRect(Common::Rect(237, 163, 247, 172))
- : Common::Rect(237, 163, 247, 172);
- const Common::Rect kArrowXLeft =
- mac ? scaleRect(Common::Rect(2, 175, 12, 185))
- : Common::Rect(2, 175, 12, 185);
- const Common::Rect kArrowXRight =
- mac ? scaleRect(Common::Rect(224, 175, 234, 185))
- : Common::Rect(224, 175, 234, 185);
- const Common::Rect xSliderBase = isLondon()
- ? Common::Rect(15, 176, 220, 184)
- : Common::Rect(15, 175, 221, 185);
- const Common::Rect ySliderBase = isLondon()
- ? Common::Rect(238, 16, 246, 158)
- : Common::Rect(237, 14, 247, 160);
- const Common::Rect kXSlider =
- mac ? scaleRect(xSliderBase) : xSliderBase;
- const Common::Rect kYSlider =
- mac ? scaleRect(ySliderBase) : ySliderBase;
- const Common::Rect detailSetupBase = isFloppy()
- ? Common::Rect(251, 3, 315, 42)
- : (isLondon() ? Common::Rect(251, 3, 315, 42)
- : Common::Rect(252, 4, 315, 42));
- const Common::Rect kDetailSetupBtn =
- mac ? scaleRect(detailSetupBase) : detailSetupBase;
- const int baseArrowStep = isLondon() ? 8 : 16;
- const int kArrowStepX = mac ? scaleX(baseArrowStep) : baseArrowStep;
- const int kArrowStepY = mac ? scaleY(baseArrowStep) : baseArrowStep;
- const int kSliderRange = maxScrollX;
- const int kSliderRangeY = maxScrollY;
- const Common::Point detailMouse =
- g_system->getEventManager()->getMousePos();
- setInteractiveMouseCursor(
- kBigMapReturnRect.contains(detailMouse.x, detailMouse.y) ||
- kDetailSetupBtn.contains(detailMouse.x, detailMouse.y));
-
- while (!shouldQuit() && !returnToOverview) {
- Common::Event ev;
- bool dirty = false;
- bool cycleTick = false;
- while (g_system->getEventManager()->pollEvent(ev)) {
- if (ev.type == Common::EVENT_QUIT ||
- ev.type == Common::EVENT_RETURN_TO_LAUNCHER) {
- setInteractiveMouseCursor(false);
- return;
- }
- if (ev.type == Common::EVENT_KEYDOWN) {
- if (ev.kbd.keycode == Common::KEYCODE_ESCAPE) {
- openMainMenuDialog();
- dirty = true;
- continue;
- }
- if (ev.kbd.keycode == Common::KEYCODE_LEFT) {
- scrollX = MAX<int>(0, scrollX - kArrowStepX);
- dirty = true;
- } else if (ev.kbd.keycode == Common::KEYCODE_RIGHT) {
- scrollX = MIN<int>(maxScrollX, scrollX + kArrowStepX);
- dirty = true;
- } else if (ev.kbd.keycode == Common::KEYCODE_UP) {
- scrollY = MAX<int>(0, scrollY - kArrowStepY);
- dirty = true;
- } else if (ev.kbd.keycode == Common::KEYCODE_DOWN) {
- scrollY = MIN<int>(maxScrollY, scrollY + kArrowStepY);
- dirty = true;
- }
- }
- if (ev.type == Common::EVENT_MOUSEMOVE)
- setInteractiveMouseCursor(
- kBigMapReturnRect.contains(ev.mouse.x, ev.mouse.y) ||
- kDetailSetupBtn.contains(ev.mouse.x, ev.mouse.y));
- if (ev.type == Common::EVENT_LBUTTONDOWN) {
- setInteractiveMouseCursor(
- kBigMapReturnRect.contains(ev.mouse.x, ev.mouse.y) ||
- kDetailSetupBtn.contains(ev.mouse.x, ev.mouse.y));
- if (kDetailSetupBtn.contains(ev.mouse.x, ev.mouse.y)) {
- _nextScreen = kScreenSetup;
- setInteractiveMouseCursor(false);
- return;
- }
- if (kBigMapReturnRect.contains(ev.mouse.x, ev.mouse.y)) {
- returnToOverview = true;
- break;
- } else if (kArrowYUp.contains(ev.mouse.x, ev.mouse.y)) {
- scrollY = MAX<int>(0, scrollY - kArrowStepY);
- dirty = true;
- } else if (kArrowYDown.contains(ev.mouse.x, ev.mouse.y)) {
- scrollY = MIN<int>(MAX<int>(0, kSliderRangeY),
- scrollY + kArrowStepY);
- dirty = true;
- } else if (kArrowXLeft.contains(ev.mouse.x, ev.mouse.y)) {
- scrollX = MAX<int>(0, scrollX - kArrowStepX);
- dirty = true;
- } else if (kArrowXRight.contains(ev.mouse.x, ev.mouse.y)) {
- scrollX = MIN<int>(MAX<int>(0, kSliderRange),
- scrollX + kArrowStepX);
- dirty = true;
- } else if (kXSlider.contains(ev.mouse.x, ev.mouse.y)) {
- if (kSliderRange > 0) {
- const int t = ev.mouse.x - kXSlider.left;
- const int tw = kXSlider.width();
- scrollX = MAX<int>(0, MIN<int>(kSliderRange,
- t * kSliderRange / MAX<int>(1, tw)));
- dirty = true;
- }
- } else if (kYSlider.contains(ev.mouse.x, ev.mouse.y)) {
- if (kSliderRangeY > 0) {
- const int t = ev.mouse.y - kYSlider.top;
- const int th = kYSlider.height();
- scrollY = MAX<int>(0, MIN<int>(kSliderRangeY,
- t * kSliderRangeY / MAX<int>(1, th)));
- dirty = true;
- }
- } else if (ev.mouse.x >= kMapWinX &&
- ev.mouse.x < kMapWinX + kMapWinW &&
- ev.mouse.y >= kMapWinY &&
- ev.mouse.y < kMapWinY + kMapWinH) {
- // Per-site bbox from `_StampButtons` (SmallMap +8/+0xa).
- const bool fmap = _mystery.isLoaded() && isFloppy();
- struct DetailMapHit {
- uint site;
- Common::Rect rect;
- };
- Common::Array<DetailMapHit> hits;
- for (uint i = 0; i < _mystery.numSites(); i++) {
- // On-map flag alone, matching `_SearchMapButtons`.
- if (!_mystery._onSites[i])
- continue;
- const byte *entry = _mystery.mapEntry(i);
- if (!entry)
- continue;
- BigMapEntryInfo info;
- if (!readBigMapEntryInfo(entry, fmap,
- mac && _mystery.usesCompactMacData(),
- info))
- continue;
- Picture button;
- int bw = 16;
- int bh = 16;
- if (_buttonArchive.loadEntry(info.buttonId, button)) {
- bw = button.surface.w;
- bh = button.surface.h;
- }
- const int sx = (int)info.detailX - scrollX + kMapWinX;
- const int sy = (int)info.detailY - scrollY + kMapWinY;
- const Common::Rect r(sx, sy, sx + bw, sy + bh);
- if (r.intersects(Common::Rect(kMapWinX, kMapWinY,
- kMapWinX + kMapWinW, kMapWinY + kMapWinH))) {
- DetailMapHit hit = { i, r };
- hits.push_back(hit);
- }
- }
- Common::sort(hits.begin(), hits.end(),
- [](const DetailMapHit &a, const DetailMapHit &b) {
- if (a.rect.top != b.rect.top)
- return a.rect.top < b.rect.top;
- return a.rect.left < b.rect.left;
- });
- for (uint i = 0; i < hits.size(); i++) {
- if (hits[i].rect.contains(ev.mouse.x, ev.mouse.y)) {
- _mystery._lastSite = _mystery._siteNumber;
- _mystery._siteNumber = (uint16)hits[i].site;
- setInteractiveMouseCursor(false);
- return;
- }
- }
- }
- }
- }
- if (returnToOverview)
- break;
-
- const uint32 now = g_system->getMillis();
- if (now - detailLastTick >= 100) {
- detailLastTick = now;
- dirty = true;
- }
- if (isLondon()) {
- const uint32 cycleDelay = mac ? kMacMapColorCycleDelayMs : 100;
- if (now - detailLastCycleTick >= cycleDelay) {
- detailLastCycleTick = now;
- cycleTick = true;
- dirty = true;
- }
- }
- if (cycleTick && isLondon()) {
- if (mac) {
- cyclePaletteRangeReverse(0xe9, 0xeb);
- cyclePaletteRangeReverse(0xec, 0xef);
- cyclePaletteRangeReverse(0xf0, 0xf2);
- } else {
- cyclePaletteRangeReverse(0xee, 0xf2);
- cyclePaletteRangeReverse(0xea, 0xed);
- }
- }
- if (dirty)
- drawBigMapDetail(scrollX, scrollY, mapPixels, mapW, mapH,
- now - detailStartTick);
- g_system->updateScreen();
- g_system->delayMillis(10);
- }
- if (!returnToOverview)
- return;
- }
-}
-
-bool EEMEngine::doLondonApproach(uint16 approachId) {
- if (!isLondon())
- return false;
-
- LondonApproachData data;
- const bool mac = isMacintosh();
- if (!loadLondonApproachData(approachId, data, mac))
- return false;
-
- const int sw = screenWidth();
- const int sh = screenHeight();
-
- Graphics::ManagedSurface base(sw, sh,
- Graphics::PixelFormat::createFormatCLUT8());
- byte palette[768] = {};
- const bool haveVideo =
- decodeLondonApproachFirstFrame(data.videoId, base, palette, mac);
- if (!haveVideo)
- base.clear();
- if (mac) {
- Picture background;
- const uint16 backgroundPic =
- kMacLondonApproachBackgroundBasePic + data.videoId;
- if (_picsArchive.getPicture(backgroundPic, background) &&
- !background.surface.empty()) {
- if (haveVideo) {
- const byte black =
- closestPaletteIndex(palette, 0x00, 0x00, 0x00, 0xfe);
- remapSurfaceColor(background.surface, 0xff, black);
- }
- base.clear();
- base.simpleBlitFrom(background.surface);
- }
- }
-
- Picture buttonPics[ARRAYSIZE(kLondonApproachButtons)];
- bool haveButtons[ARRAYSIZE(kLondonApproachButtons)] = {};
- for (uint i = 0; i < ARRAYSIZE(kLondonApproachButtons); i++) {
- // DOS `_DoApproach` loads PICS 0x361/0x362/0x35f/0x360 as the
- // four control sprites. In the Mac London data those IDs are
- // full-screen scrapbook pages, not buttons.
- haveButtons[i] = !mac && _picsArchive.getPicture(
- kLondonApproachButtons[i].picId, buttonPics[i]);
- }
-
- auto buttonRect = [&](uint idx) {
- return kLondonApproachButtons[idx].rect();
- };
- auto buttonPoint = [&](uint idx) {
- const LondonApproachButton &b = kLondonApproachButtons[idx];
- return Common::Point(b.x, b.y);
- };
- const int movieX = mac ? kMacLondonApproachMovieX : (int)data.videoX;
- const int movieY = mac ? kMacLondonApproachMovieY : (int)data.videoY;
- auto approachTextRect = [&]() {
- Common::Rect textRect = data.textRect;
- if (mac)
- textRect = scaleRect(textRect);
- return textRect.findIntersectingRect(Common::Rect(sw, sh));
- };
- auto macApproachButton = [&](const Common::Point &mouse) {
- if (mouse.x >= kMacLondonApproachDoneLeft &&
- mouse.x < kMacLondonApproachDoneRight &&
- mouse.y >= kMacLondonApproachDoneTop &&
- mouse.y < kMacLondonApproachDoneBottom)
- return 0;
- if (mouse.x >= kMacLondonApproachPlayLeft &&
- mouse.x < kMacLondonApproachPlayRight &&
- mouse.y >= kMacLondonApproachPlayTop &&
- mouse.y < kMacLondonApproachPlayBottom)
- return 1;
- return -1;
- };
- const byte approachTextColor =
- closestPaletteIndex(haveVideo ? palette : nullptr, 0, 0, 0,
- mac ? (byte)0xfe : (byte)1);
-
- auto drawApproachOverlay = [&](Graphics::ManagedSurface &scratch,
- uint page) {
- const Common::Rect textRect = approachTextRect();
- if (!textRect.isEmpty()) {
- if (page < data.pages.size()) {
- Common::Array<Common::String> wrapped;
- _font.wordWrapText(data.pages[page], MAX<int>(8, textRect.width()),
- wrapped);
- const int lineH = _font.getFontHeight();
- const int maxLines = MAX<int>(1, textRect.height() / lineH);
- for (uint i = 0; i < wrapped.size() && (int)i < maxLines; i++) {
- _font.drawString(&scratch, wrapped[i], textRect.left,
- textRect.top + (int)i * lineH,
- textRect.width(),
- approachTextColor);
- }
- }
- }
-
- for (uint i = 0; i < ARRAYSIZE(kLondonApproachButtons); i++) {
- if (haveButtons[i]) {
- const Common::Point p = buttonPoint(i);
- scratch.transBlitFrom(buttonPics[i].surface, p,
- (uint32)(byte)(buttonPics[i].flags >> 8));
- }
- }
- };
-
- auto drawScreen = [&](uint page) {
- Graphics::ManagedSurface scratch(sw, sh,
- Graphics::PixelFormat::createFormatCLUT8());
- scratch.clear();
- scratch.simpleBlitFrom(base);
- drawApproachOverlay(scratch, page);
-
- g_system->copyRectToScreen(scratch.getPixels(), scratch.pitch,
- 0, 0, sw, sh);
- g_system->updateScreen();
- };
-
- auto playVideo = [&](uint page) {
- if (mac) {
- Video::FlicDecoder flic;
- Common::String name;
- if (!openLondonApproachFlic(data.videoId, flic, name)) {
- warning("London approach: cannot open FLC video %u",
- data.videoId);
- return;
- }
-
- flic.start();
- while (!shouldQuit() && !flic.endOfVideo()) {
- const Graphics::Surface *frame = flic.decodeNextFrame();
- if (!frame)
- break;
- Graphics::ManagedSurface scratch(sw, sh,
- Graphics::PixelFormat::createFormatCLUT8());
- scratch.clear();
- scratch.simpleBlitFrom(base);
- const int copyW = MIN<int>(frame->w, sw - movieX);
- const int copyH = MIN<int>(frame->h, sh - movieY);
- if (copyW <= 0 || copyH <= 0)
- break;
- scratch.copyRectToSurface(frame->getPixels(), frame->pitch,
- movieX, movieY,
- copyW, copyH);
- drawApproachOverlay(scratch, page);
- g_system->copyRectToScreen(scratch.getPixels(), scratch.pitch,
- 0, 0, sw, sh);
- if (flic.hasDirtyPalette()) {
- const byte *fpal = flic.getPalette();
- if (fpal)
- g_system->getPaletteManager()->setPalette(fpal, 0, 256);
- }
- g_system->updateScreen();
-
- const uint32 start = g_system->getMillis();
- const uint32 delay = MAX<uint32>(10, flic.getTimeToNextFrame());
- bool skip = false;
- while (g_system->getMillis() - start < delay && !skip) {
- Common::Event ev;
- while (g_system->getEventManager()->pollEvent(ev)) {
- if (ev.type == Common::EVENT_QUIT ||
- ev.type == Common::EVENT_RETURN_TO_LAUNCHER)
- return;
- if (ev.type == Common::EVENT_LBUTTONDOWN ||
- ev.type == Common::EVENT_KEYDOWN) {
- skip = true;
- break;
- }
- }
- g_system->delayMillis(5);
- }
- if (skip)
- break;
- }
- return;
- }
-
- const Common::String name =
- Common::String::format("VIDEO%02u.A", data.videoId);
- ANMDecoder anm;
- if (!anm.open(Common::Path(name))) {
- warning("London approach: cannot open %s", name.c_str());
- return;
- }
-
- // Frame 0 is already the static background. Replay frames 1..end
- // with the approach text/buttons composited above it.
- (void)anm.nextFrame();
- const int copyW = MIN<int>(anm.width(), sw - (int)data.videoX);
- const int copyH = MIN<int>(anm.height(), sh - (int)data.videoY);
- if (copyW <= 0 || copyH <= 0)
- return;
-
- while (!shouldQuit()) {
- const byte *frame = anm.nextFrame();
- if (!frame)
- break;
- Graphics::ManagedSurface scratch(sw, sh,
- Graphics::PixelFormat::createFormatCLUT8());
- scratch.clear();
- scratch.copyRectToSurface(frame, anm.width(),
- data.videoX, data.videoY,
- copyW, copyH);
- drawApproachOverlay(scratch, page);
- g_system->copyRectToScreen(scratch.getPixels(), scratch.pitch,
- 0, 0, sw, sh);
- g_system->updateScreen();
-
- const uint32 start = g_system->getMillis();
- bool skip = false;
- while (g_system->getMillis() - start < 120 && !skip) {
- Common::Event ev;
- while (g_system->getEventManager()->pollEvent(ev)) {
- if (ev.type == Common::EVENT_QUIT ||
- ev.type == Common::EVENT_RETURN_TO_LAUNCHER) {
- return;
- }
- if (ev.type == Common::EVENT_LBUTTONDOWN ||
- ev.type == Common::EVENT_KEYDOWN) {
- skip = true;
- break;
- }
- }
- g_system->delayMillis(5);
- }
- if (skip)
- break;
- }
- };
-
- fadeCurrentPaletteToBlack();
- CursorMan.showMouse(true);
- setSiteHotspotCursorId(6);
- if (_music && _voiceOn)
- _music->playMus(0x27, /* loop= */ true);
-
- uint page = 0;
- drawScreen(page);
- if (haveVideo)
- fadePaletteFromBlack(palette);
- else
- setSitePalette(0x3b);
- if (haveVideo) {
- playVideo(page);
- drawScreen(page);
- }
- bool done = false;
- uint32 lastShimmer = g_system->getMillis();
- while (!shouldQuit() && !done) {
- Common::Event ev;
- while (g_system->getEventManager()->pollEvent(ev)) {
- if (ev.type == Common::EVENT_QUIT ||
- ev.type == Common::EVENT_RETURN_TO_LAUNCHER) {
- done = true;
- break;
- }
- if (ev.type == Common::EVENT_MOUSEMOVE) {
- bool overButton = false;
- if (mac) {
- overButton = macApproachButton(
- Common::Point(ev.mouse.x, ev.mouse.y)) >= 0;
- } else {
- for (uint i = 0; i < ARRAYSIZE(kLondonApproachButtons); i++) {
- if (buttonRect(i).contains(ev.mouse.x, ev.mouse.y)) {
- overButton = true;
- break;
- }
- }
- }
- setInteractiveMouseCursor(overButton);
- }
- if (ev.type == Common::EVENT_KEYDOWN) {
- if (ev.kbd.keycode == Common::KEYCODE_ESCAPE ||
- ev.kbd.keycode == Common::KEYCODE_RETURN ||
- ev.kbd.keycode == Common::KEYCODE_KP_ENTER) {
- done = true;
- break;
- }
- if (ev.kbd.keycode == Common::KEYCODE_SPACE) {
- playVideo(page);
- drawScreen(page);
- } else if (ev.kbd.keycode == Common::KEYCODE_RIGHT ||
- ev.kbd.keycode == Common::KEYCODE_DOWN) {
- if (page + 1 < data.pages.size()) {
- page++;
- drawScreen(page);
- }
- } else if (ev.kbd.keycode == Common::KEYCODE_LEFT ||
- ev.kbd.keycode == Common::KEYCODE_UP) {
- if (page > 0) {
- page--;
- drawScreen(page);
- }
- }
- }
- if (ev.type == Common::EVENT_LBUTTONDOWN) {
- if (mac) {
- const int button = macApproachButton(
- Common::Point(ev.mouse.x, ev.mouse.y));
- if (button == 0) {
- done = true;
- break;
- }
- if (button == 1) {
- playVideo(page);
- drawScreen(page);
- }
- continue;
- }
- if (buttonRect(0).contains(ev.mouse.x, ev.mouse.y)) {
- done = true;
- break;
- }
- if (buttonRect(1).contains(ev.mouse.x, ev.mouse.y)) {
- playVideo(page);
- drawScreen(page);
- } else if (buttonRect(2).contains(ev.mouse.x, ev.mouse.y)) {
- if (page + 1 < data.pages.size()) {
- page++;
- drawScreen(page);
- }
- } else if (buttonRect(3).contains(ev.mouse.x, ev.mouse.y)) {
- if (page > 0) {
- page--;
- drawScreen(page);
- }
- }
- }
- }
- if (haveVideo && !mac) {
- const uint32 now = g_system->getMillis();
- if (now - lastShimmer >= 70) {
- lastShimmer = now;
- cyclePaletteRange(0xF3, 0xF7);
- }
- }
- g_system->updateScreen();
- g_system->delayMillis(10);
- }
-
- setInteractiveMouseCursor(false);
- setSiteHotspotCursorId(0);
- stopMusic();
- return true;
-}
-
-void EEMEngine::drawBigMapOverview(uint32 elapsedMs) {
- const bool mac = isMacintosh();
- const int sw = screenWidth();
- const int sh = screenHeight();
- Graphics::ManagedSurface scratch(sw, sh,
- Graphics::PixelFormat::createFormatCLUT8());
- scratch.clear();
-
- Picture frame;
- if (_picsArchive.getPicture(0x42, frame)) {
- if (mac)
- remapMacSurfaceEndpoints(frame.surface, getMacSpritePaletteMap());
- scratch.simpleBlitFrom(frame.surface);
- }
-
- // Marker PICs from `_main`:
- // EEM1 CD @ 1a35:0f59: Done=0x20d, Site=0xc5, Crime=0xc6.
- // EEM2 CD @ 1abf:11a6: Done=0x006, Site=0xc5, Crime=0xc6.
- Picture done;
- Picture normal;
- Picture crimeM;
- const bool haveDone = _picsArchive.getPicture(isLondon() ? 0x006 : 0x20d, done) &&
- done.surface.w < 64 && done.surface.h < 64;
- const bool haveNormal = _picsArchive.getPicture(0xc5, normal) &&
- normal.surface.w < 64 && normal.surface.h < 64;
- const bool haveCrime = _picsArchive.getPicture(0xc6, crimeM) &&
- crimeM.surface.w < 64 && crimeM.surface.h < 64;
-
- for (uint i = 0; i < _mystery.numSites(); i++) {
- // `_DrawBigMapButtons` gates markers on the on-map flag alone, never the
- // current site: a sublocation (in-site jump, never flagged) must not draw.
- if (!_mystery._onSites[i])
- continue;
- const byte *entry = _mystery.mapEntry(i);
- if (!entry)
- continue;
-
- const bool floppy = _mystery.isLoaded() && isFloppy();
- BigMapEntryInfo info;
- if (!readBigMapEntryInfo(entry, floppy,
- mac && _mystery.usesCompactMacData(), info))
- continue;
- const bool isDone = (i < Mystery::kVisitedSiteCap)
- && _mystery._visitedSite[i];
-
- const Picture *m = nullptr;
- bool useVisitedColors = false;
- if (isDone && haveDone) {
- m = &done;
- } else if (isDone && haveNormal) {
- m = &normal;
- useVisitedColors = true;
- } else if (info.crime != 0 && haveCrime) {
- m = &crimeM;
- } else if (haveNormal) {
- m = &normal;
- }
-
- if (m) {
- blitBigMapMarker(scratch, *m, (int)info.overviewX,
- (int)info.overviewY,
- useVisitedColors);
- } else {
- const int mx = (int)info.overviewX;
- const int my = (int)info.overviewY;
- const Common::Rect mark(mx - 3, my - 3, mx + 4, my + 4);
- scratch.fillRect(mark, 0x0F);
- }
- }
-
- const uint kMapAniId = (_partner == kPartnerJake) ? 0x14 : 0x12;
- Animation mapAnim;
- if (_aniArchive.loadAnimation(kMapAniId, mapAnim) && !mapAnim.empty()) {
- const uint frameIdx = bigMapPartnerFrameAtTick((uint)mapAnim.size(),
- elapsedMs, isLondon());
-
- const int anchorX = mac ? scaleX(0xfd) : 0xfd;
- const int anchorY = mac ? scaleY(0x50) : 0x50;
- if (mac)
- blitMacAnimFrameAnchored(scratch.surfacePtr(), mapAnim[frameIdx],
- anchorX, anchorY);
- else
- blitAnimFrameAnchored(scratch.surfacePtr(), mapAnim[frameIdx],
- anchorX, anchorY);
- }
-
- g_system->copyRectToScreen(scratch.getPixels(), scratch.pitch,
- 0, 0, sw, sh);
- g_system->updateScreen();
-}
-
-void EEMEngine::drawBigMapDetail(int scrollX, int scrollY,
- const Common::Array<byte> &mapPixels,
- uint16 mapW, uint16 mapH,
- uint32 elapsedMs) {
-
- const bool mac = isMacintosh();
- const int sw = screenWidth();
- const int sh = screenHeight();
- const int kMapWinW = mac ? scaleX(0xe9) : 0xe9;
- const int kMapWinH = mac ? scaleY(0xab) : 0xab;
- const int kMapWinX = mac ? scaleX(2) : 2;
- const int kMapWinY = mac ? scaleY(2) : 2;
-
- Graphics::ManagedSurface scratch(sw, sh,
- Graphics::PixelFormat::createFormatCLUT8());
- scratch.clear();
-
- Picture frame;
- if (_picsArchive.getPicture(0x43, frame)) {
- // The frame and its buttons use the normal 0x00=white / 0xFF=black
- // convention, but the detail-map ColorTable (0x23) swaps those endpoints,
- // so normalize them or the SETUP button's black border renders white.
- if (mac)
- remapMacSurfaceEndpoints(frame.surface, getMacSpritePaletteMap());
- scratch.simpleBlitFrom(frame.surface);
- }
-
- const int copyW = MIN<int>(mapW - scrollX, kMapWinW);
- const int copyH = MIN<int>(mapH - scrollY, kMapWinH);
- scratch.copyRectToSurface(mapPixels.data() + scrollY * mapW + scrollX,
- mapW, kMapWinX, kMapWinY, copyW, copyH);
-
- const bool floppyMap = _mystery.isLoaded() && isFloppy();
- for (uint i = 0; i < _mystery.numSites(); i++) {
- // `_DrawBigMapButtons` gates markers on the on-map flag alone, never the
- // current site: a sublocation (in-site jump, never flagged) must not draw.
- if (!_mystery._onSites[i])
- continue;
- const byte *entry = _mystery.mapEntry(i);
- if (!entry)
- continue;
- BigMapEntryInfo info;
- if (!readBigMapEntryInfo(entry, floppyMap,
- mac && _mystery.usesCompactMacData(), info))
- continue;
- Picture button;
- if (!_buttonArchive.loadEntry(info.buttonId, button))
- continue;
- const int sx = (int)info.detailX - scrollX + kMapWinX;
- const int sy = (int)info.detailY - scrollY + kMapWinY;
- // Crop the button blit against the viewport.
- const int x0 = MAX<int>(sx, kMapWinX);
- const int y0 = MAX<int>(sy, kMapWinY);
- const int x1 = MIN<int>(sx + button.surface.w, kMapWinX + kMapWinW);
- const int y1 = MIN<int>(sy + button.surface.h, kMapWinY + kMapWinH);
- if (x1 > x0 && y1 > y0) {
- const Common::Rect srcRect(x0 - sx, y0 - sy, x1 - sx, y1 - sy);
- scratch.transBlitFrom(button.surface, srcRect,
- Common::Point(x0, y0),
- (uint32)(byte)(button.flags >> 8));
- }
- }
-
- const uint kDetailAniId = (_partner == kPartnerJake) ? 0x13 : 0x11;
- Animation detailAnim;
- if (_aniArchive.loadAnimation(kDetailAniId, detailAnim) &&
- !detailAnim.empty()) {
- const uint frameIdx = bigMapDetailPartnerFrameAtTick(
- (uint)detailAnim.size(), elapsedMs);
- const int anchorX = mac ? scaleX(0x101) : 0x101;
- const int anchorY = mac ? scaleY(0x50) : 0x50;
- if (mac)
- blitMacBigMapPartnerFrame(scratch, detailAnim[frameIdx],
- anchorX, anchorY);
- else
- blitAnimFrameAnchored(scratch.surfacePtr(),
- detailAnim[frameIdx],
- anchorX, anchorY);
- }
-
- g_system->copyRectToScreen(scratch.getPixels(), scratch.pitch,
- 0, 0, sw, sh);
- g_system->updateScreen();
-}
-
// `_GetKDTextBalloon @ 1df2:0105`
uint16 EEMEngine::getKDTextBalloon(byte firstChar) const {
if (firstChar < '0' || firstChar > '9')
Commit: 2e596e7adb5578aa99416928938f82c89fd0e609
https://github.com/scummvm/scummvm/commit/2e596e7adb5578aa99416928938f82c89fd0e609
Author: neuromancer (gustavo.grieco at gmail.com)
Date: 2026-06-26T20:37:27+02:00
Commit Message:
EEM: refactored map related code into several functions
Changed paths:
engines/eem/eem.h
engines/eem/map_ui.cpp
diff --git a/engines/eem/eem.h b/engines/eem/eem.h
index 24e1ab8cfdf..12b2b1eea2f 100644
--- a/engines/eem/eem.h
+++ b/engines/eem/eem.h
@@ -24,6 +24,7 @@
#define EEM_EEM_H
#include "common/array.h"
+#include "common/events.h"
#include "common/keyboard.h"
#include "common/language.h"
#include "common/platform.h"
@@ -366,6 +367,66 @@ private:
void drawGalleryFrame(const byte *gd, uint8 numSuspects,
Common::Array<Common::Rect> &slotRects,
Common::Array<int> &slotSuspect);
+
+ struct BigMapOverviewState {
+ Common::Rect window;
+ int zoomX = 0;
+ int zoomY = 0;
+ };
+
+ struct BigMapDetailState {
+ const Common::Array<byte> *mapPixels = nullptr;
+ uint16 mapW = 0;
+ uint16 mapH = 0;
+
+ int scrollX = 0;
+ int scrollY = 0;
+ int maxScrollX = 0;
+ int maxScrollY = 0;
+
+ int mapWinX = 0;
+ int mapWinY = 0;
+ int mapWinW = 0;
+ int mapWinH = 0;
+
+ Common::Rect returnRect;
+ Common::Rect setupRect;
+ Common::Rect arrowYUp;
+ Common::Rect arrowYDown;
+ Common::Rect arrowXLeft;
+ Common::Rect arrowXRight;
+ Common::Rect xSlider;
+ Common::Rect ySlider;
+
+ int arrowStepX = 0;
+ int arrowStepY = 0;
+
+ uint32 startTick = 0;
+ uint32 lastDrawTick = 0;
+ uint32 lastCycleTick = 0;
+ };
+
+ Common::Rect bigMapScaledRect(const Common::Rect &rect) const;
+ bool bigMapRunOverview(BigMapOverviewState &state);
+ bool bigMapLoadDetailPixels(Common::Array<byte> &mapPixels,
+ uint16 &mapW, uint16 &mapH);
+ void bigMapInitDetailState(BigMapDetailState &state,
+ const Common::Array<byte> &mapPixels,
+ uint16 mapW, uint16 mapH,
+ const BigMapOverviewState &overview);
+ bool bigMapRunDetail(BigMapDetailState &state);
+ bool bigMapHandleDetailEvent(const Common::Event &ev,
+ BigMapDetailState &state,
+ bool &returnToOverview, bool &dirty);
+ void bigMapHandleDetailKey(const Common::Event &ev,
+ BigMapDetailState &state, bool &dirty);
+ bool bigMapHandleDetailMouseDown(const Common::Event &ev,
+ BigMapDetailState &state,
+ bool &returnToOverview, bool &dirty);
+ bool bigMapTrySelectDetailSite(int mouseX, int mouseY,
+ const BigMapDetailState &state);
+ void bigMapCycleOverviewPalette(bool mac);
+ void bigMapCycleDetailPalette(bool mac);
void drawBigMapOverview(uint32 elapsedMs);
void drawBigMapDetail(int scrollX, int scrollY,
const Common::Array<byte> &mapPixels,
diff --git a/engines/eem/map_ui.cpp b/engines/eem/map_ui.cpp
index 36eec5c649b..dd9bd6cf11c 100644
--- a/engines/eem/map_ui.cpp
+++ b/engines/eem/map_ui.cpp
@@ -427,369 +427,462 @@ void remapSurfaceColor(Graphics::ManagedSurface &surface, byte from, byte to) {
// 20fe:0d2f`). Click icon = travel.
// MapData entry (14 bytes): +0..3 ???, +4 BigMapX, +6 BigMapY,
// +8 SmallMapX, +0xa SmallMapY, +0xc crime-flag.
-void EEMEngine::doBigMap() {
+Common::Rect EEMEngine::bigMapScaledRect(const Common::Rect &rect) const {
+ return isMacintosh() ? scaleRect(rect) : rect;
+}
- if (!_mystery.isLoaded())
+void EEMEngine::bigMapCycleOverviewPalette(bool mac) {
+ if (isLondon()) {
+ if (mac) {
+ // Mac `_UpdateBigMap` rotates ColorTable entries 0xef..0xf2
+ // and 0xfc..0xff. Redraw happens after the cycle so Mac endpoint
+ // art maps its black pixels to the current black slot.
+ cyclePaletteRangeReverse(0xef, 0xf2);
+ cyclePaletteRangeReverse(0xfc, 0xff);
+ } else {
+ cyclePaletteRangeReverse(0xf4, 0xf9);
+ cyclePaletteRangeReverse(0xfa, 0xff);
+ }
+ } else {
+ cyclePaletteRangeReverse(0xf7, 0xfa);
+ cyclePaletteRangeReverse(0xfb, 0xfe);
+ }
+}
+
+void EEMEngine::bigMapCycleDetailPalette(bool mac) {
+ if (!isLondon())
return;
- if (isLondon()) {
- _mystery._pendingSiteJump = 0;
- _mystery._siteReturnDepth = 0;
- memset(_mystery._siteReturnStack, 0, sizeof(_mystery._siteReturnStack));
+ if (mac) {
+ cyclePaletteRangeReverse(0xe9, 0xeb);
+ cyclePaletteRangeReverse(0xec, 0xef);
+ cyclePaletteRangeReverse(0xf0, 0xf2);
+ } else {
+ cyclePaletteRangeReverse(0xee, 0xf2);
+ cyclePaletteRangeReverse(0xea, 0xed);
}
+}
- CursorMan.showMouse(true);
+bool EEMEngine::bigMapRunOverview(BigMapOverviewState &state) {
+ setInteractiveMouseCursor(false);
+ setSitePalette(isLondon() ? 0x3b : 0x24);
+
+ const bool mac = isMacintosh();
+ const Common::Rect setupBtnBase = isFloppy()
+ ? Common::Rect(251, 3, 315, 42)
+ : (isLondon() ? Common::Rect(252, 1, 315, 42)
+ : Common::Rect(252, 4, 315, 42));
+ const Common::Rect setupBtnRect = bigMapScaledRect(setupBtnBase);
+ state.window = bigMapScaledRect(Common::Rect(0, 0, 247, 192));
+ state.zoomX = 0;
+ state.zoomY = 0;
+
+ const uint32 mapStartTick = g_system->getMillis();
+ drawBigMapOverview(0);
+ uint32 mapLastTick = mapStartTick;
+ uint32 mapLastCycleTick = mapStartTick;
while (!shouldQuit()) {
- setInteractiveMouseCursor(false);
- setSitePalette(isLondon() ? 0x3b : 0x24);
-
- const uint32 mapStartTick = g_system->getMillis();
- drawBigMapOverview(0);
- uint32 mapLastTick = mapStartTick;
- uint32 mapLastCycleTick = mapStartTick;
-
- const bool mac = isMacintosh();
- const Common::Rect bigMapWindowBase(0, 0, 247, 192);
- const Common::Rect kBigMapWindow =
- mac ? scaleRect(bigMapWindowBase) : bigMapWindowBase;
- const Common::Rect setupBtnBase = isFloppy()
- ? Common::Rect(251, 3, 315, 42)
- : (isLondon() ? Common::Rect(252, 1, 315, 42)
- : Common::Rect(252, 4, 315, 42));
- const Common::Rect kSetupBtnRect =
- mac ? scaleRect(setupBtnBase) : setupBtnBase;
-
- bool wantZoom = false;
- int zoomX = 0;
- int zoomY = 0;
- while (!shouldQuit()) {
- Common::Event ev;
- while (g_system->getEventManager()->pollEvent(ev)) {
- if (ev.type == Common::EVENT_QUIT ||
- ev.type == Common::EVENT_RETURN_TO_LAUNCHER)
- return;
- if (ev.type == Common::EVENT_KEYDOWN &&
- ev.kbd.keycode == Common::KEYCODE_ESCAPE) {
- openMainMenuDialog();
- continue;
- }
- if (ev.type == Common::EVENT_LBUTTONDOWN) {
- if (kSetupBtnRect.contains(ev.mouse.x, ev.mouse.y)) {
- _nextScreen = kScreenSetup;
- return;
- }
+ Common::Event ev;
+ while (g_system->getEventManager()->pollEvent(ev)) {
+ if (ev.type == Common::EVENT_QUIT ||
+ ev.type == Common::EVENT_RETURN_TO_LAUNCHER)
+ return false;
+ if (ev.type == Common::EVENT_KEYDOWN &&
+ ev.kbd.keycode == Common::KEYCODE_ESCAPE) {
+ openMainMenuDialog();
+ continue;
+ }
+ if (ev.type != Common::EVENT_LBUTTONDOWN)
+ continue;
- if (kBigMapWindow.contains(ev.mouse.x, ev.mouse.y)) {
- if (mac) {
- zoomX = ev.mouse.x - kBigMapWindow.left;
- zoomY = ev.mouse.y - kBigMapWindow.top;
- } else {
- int sx = ev.mouse.x * 2;
- int sy = ev.mouse.y * 2;
- sx = (sx < 0x75) ? 0 : sx - 0x74;
- sy = (sy < 0x56) ? 0 : sy - 0x55;
- zoomX = sx;
- zoomY = sy;
- }
- wantZoom = true;
- break;
- }
- }
+ if (setupBtnRect.contains(ev.mouse.x, ev.mouse.y)) {
+ _nextScreen = kScreenSetup;
+ return false;
}
- if (wantZoom)
- break;
- const uint32 now = g_system->getMillis();
- if (mac && isLondon() &&
- now - mapLastCycleTick >= kMacMapColorCycleDelayMs) {
- mapLastCycleTick = now;
- // Mac `_UpdateBigMap` rotates ColorTable entries 0xef..0xf2
- // and 0xfc..0xff. Redraw immediately so Mac endpoint art maps
- // its black pixels to the current black slot after the cycle.
- cyclePaletteRangeReverse(0xef, 0xf2);
- cyclePaletteRangeReverse(0xfc, 0xff);
- drawBigMapOverview(now - mapStartTick);
- mapLastTick = now;
- } else if (now - mapLastTick >= 100) {
- mapLastTick = now;
- drawBigMapOverview(now - mapStartTick);
- if (isLondon()) {
- if (!mac) {
- cyclePaletteRangeReverse(0xf4, 0xf9);
- cyclePaletteRangeReverse(0xfa, 0xff);
- }
+ if (state.window.contains(ev.mouse.x, ev.mouse.y)) {
+ if (mac) {
+ state.zoomX = ev.mouse.x - state.window.left;
+ state.zoomY = ev.mouse.y - state.window.top;
} else {
- cyclePaletteRangeReverse(0xf7, 0xfa);
- cyclePaletteRangeReverse(0xfb, 0xfe);
+ int sx = ev.mouse.x * 2;
+ int sy = ev.mouse.y * 2;
+ sx = (sx < 0x75) ? 0 : sx - 0x74;
+ sy = (sy < 0x56) ? 0 : sy - 0x55;
+ state.zoomX = sx;
+ state.zoomY = sy;
}
+ return true;
}
- g_system->updateScreen();
- g_system->delayMillis(10);
}
- if (!wantZoom)
- return;
+ const uint32 now = g_system->getMillis();
+ if (mac && isLondon() &&
+ now - mapLastCycleTick >= kMacMapColorCycleDelayMs) {
+ mapLastCycleTick = now;
+ bigMapCycleOverviewPalette(mac);
+ drawBigMapOverview(now - mapStartTick);
+ mapLastTick = now;
+ } else if (now - mapLastTick >= 100) {
+ mapLastTick = now;
+ drawBigMapOverview(now - mapStartTick);
+ if (!mac || !isLondon())
+ bigMapCycleOverviewPalette(mac);
+ }
+ g_system->updateScreen();
+ g_system->delayMillis(10);
+ }
- uint16 mapW = 0;
- uint16 mapH = 0;
- Common::Array<byte> mapPixels;
- if (mac) {
- if (!loadMacBigMapPixels(mapPixels, mapW, mapH))
- return;
- } else {
- Common::File f;
- if (!f.open(Common::Path("BIGMAP.PIC"))) {
- warning("doBigMap: BIGMAP.PIC missing for detail view");
- return;
- }
- mapH = f.readUint16LE();
- mapW = f.readUint16LE();
- if (mapW == 0 || mapH == 0)
- return;
- mapPixels.resize((uint32)mapW * mapH);
- if (f.read(mapPixels.data(), mapPixels.size()) != mapPixels.size()) {
- warning("doBigMap: short read on BIGMAP.PIC for detail view");
- return;
- }
+ return false;
+}
+
+bool EEMEngine::bigMapLoadDetailPixels(Common::Array<byte> &mapPixels,
+ uint16 &mapW, uint16 &mapH) {
+ mapW = 0;
+ mapH = 0;
+ mapPixels.clear();
+
+ if (isMacintosh())
+ return loadMacBigMapPixels(mapPixels, mapW, mapH);
+
+ Common::File f;
+ if (!f.open(Common::Path("BIGMAP.PIC"))) {
+ warning("doBigMap: BIGMAP.PIC missing for detail view");
+ return false;
+ }
+ mapH = f.readUint16LE();
+ mapW = f.readUint16LE();
+ if (mapW == 0 || mapH == 0)
+ return false;
+ mapPixels.resize((uint32)mapW * mapH);
+ if (f.read(mapPixels.data(), mapPixels.size()) != mapPixels.size()) {
+ warning("doBigMap: short read on BIGMAP.PIC for detail view");
+ return false;
+ }
+ return true;
+}
+
+void EEMEngine::bigMapInitDetailState(BigMapDetailState &state,
+ const Common::Array<byte> &mapPixels,
+ uint16 mapW, uint16 mapH,
+ const BigMapOverviewState &overview) {
+ const bool mac = isMacintosh();
+
+ state.mapPixels = &mapPixels;
+ state.mapW = mapW;
+ state.mapH = mapH;
+ state.mapWinW = mac ? scaleX(0xe9) : 0xe9; // 233
+ state.mapWinH = mac ? scaleY(0xab) : 0xab; // 171
+ state.mapWinX = mac ? scaleX(2) : 2;
+ state.mapWinY = mac ? scaleY(2) : 2;
+ state.maxScrollX = MAX<int>(0, (int)mapW - state.mapWinW);
+ state.maxScrollY = MAX<int>(0, (int)mapH - state.mapWinH);
+
+ if (mac) {
+ state.scrollX = overview.zoomX * (int)mapW /
+ MAX<int>(1, overview.window.width()) - state.mapWinW / 2;
+ state.scrollY = overview.zoomY * (int)mapH /
+ MAX<int>(1, overview.window.height()) - state.mapWinH / 2;
+ } else {
+ state.scrollX = overview.zoomX;
+ state.scrollY = overview.zoomY;
+ }
+ state.scrollX = MAX<int>(0, MIN<int>(state.maxScrollX, state.scrollX));
+ state.scrollY = MAX<int>(0, MIN<int>(state.maxScrollY, state.scrollY));
+
+ state.returnRect = bigMapScaledRect(
+ Common::Rect(252, 43, kScreenWidth, kScreenHeight));
+ state.arrowYUp = bigMapScaledRect(Common::Rect(237, 2, 247, 11));
+ state.arrowYDown = bigMapScaledRect(Common::Rect(237, 163, 247, 172));
+ state.arrowXLeft = bigMapScaledRect(Common::Rect(2, 175, 12, 185));
+ state.arrowXRight = bigMapScaledRect(Common::Rect(224, 175, 234, 185));
+ state.xSlider = bigMapScaledRect(isLondon()
+ ? Common::Rect(15, 176, 220, 184)
+ : Common::Rect(15, 175, 221, 185));
+ state.ySlider = bigMapScaledRect(isLondon()
+ ? Common::Rect(238, 16, 246, 158)
+ : Common::Rect(237, 14, 247, 160));
+ state.setupRect = bigMapScaledRect(isFloppy()
+ ? Common::Rect(251, 3, 315, 42)
+ : (isLondon() ? Common::Rect(251, 3, 315, 42)
+ : Common::Rect(252, 4, 315, 42)));
+
+ const int baseArrowStep = isLondon() ? 8 : 16;
+ state.arrowStepX = mac ? scaleX(baseArrowStep) : baseArrowStep;
+ state.arrowStepY = mac ? scaleY(baseArrowStep) : baseArrowStep;
+}
+
+bool EEMEngine::bigMapTrySelectDetailSite(int mouseX, int mouseY,
+ const BigMapDetailState &state) {
+ if (mouseX < state.mapWinX || mouseX >= state.mapWinX + state.mapWinW ||
+ mouseY < state.mapWinY || mouseY >= state.mapWinY + state.mapWinH)
+ return false;
+
+ // Per-site bbox from `_StampButtons` (SmallMap +8/+0xa).
+ struct DetailMapHit {
+ uint site;
+ Common::Rect rect;
+ };
+ Common::Array<DetailMapHit> hits;
+ const bool floppyMap = _mystery.isLoaded() && isFloppy();
+ const bool macMap = isMacintosh() && _mystery.usesCompactMacData();
+ for (uint i = 0; i < _mystery.numSites(); i++) {
+ // On-map flag alone, matching `_SearchMapButtons`.
+ if (!_mystery._onSites[i])
+ continue;
+ const byte *entry = _mystery.mapEntry(i);
+ if (!entry)
+ continue;
+ BigMapEntryInfo info;
+ if (!readBigMapEntryInfo(entry, floppyMap, macMap, info))
+ continue;
+
+ Picture button;
+ int bw = 16;
+ int bh = 16;
+ if (_buttonArchive.loadEntry(info.buttonId, button)) {
+ bw = button.surface.w;
+ bh = button.surface.h;
+ }
+ const int sx = (int)info.detailX - state.scrollX + state.mapWinX;
+ const int sy = (int)info.detailY - state.scrollY + state.mapWinY;
+ const Common::Rect r(sx, sy, sx + bw, sy + bh);
+ if (r.intersects(Common::Rect(state.mapWinX, state.mapWinY,
+ state.mapWinX + state.mapWinW,
+ state.mapWinY + state.mapWinH))) {
+ DetailMapHit hit = { i, r };
+ hits.push_back(hit);
+ }
+ }
+ Common::sort(hits.begin(), hits.end(),
+ [](const DetailMapHit &a, const DetailMapHit &b) {
+ if (a.rect.top != b.rect.top)
+ return a.rect.top < b.rect.top;
+ return a.rect.left < b.rect.left;
+ });
+ for (uint i = 0; i < hits.size(); i++) {
+ if (hits[i].rect.contains(mouseX, mouseY)) {
+ _mystery._lastSite = _mystery._siteNumber;
+ _mystery._siteNumber = (uint16)hits[i].site;
+ return true;
}
+ }
+ return false;
+}
- const int kMapWinW = mac ? scaleX(0xe9) : 0xe9; // 233
- const int kMapWinH = mac ? scaleY(0xab) : 0xab; // 171
- const int kMapWinX = mac ? scaleX(2) : 2;
- const int kMapWinY = mac ? scaleY(2) : 2;
+void EEMEngine::bigMapHandleDetailKey(const Common::Event &ev,
+ BigMapDetailState &state,
+ bool &dirty) {
+ switch (ev.kbd.keycode) {
+ case Common::KEYCODE_ESCAPE:
+ openMainMenuDialog();
+ dirty = true;
+ break;
+ case Common::KEYCODE_LEFT:
+ state.scrollX = MAX<int>(0, state.scrollX - state.arrowStepX);
+ dirty = true;
+ break;
+ case Common::KEYCODE_RIGHT:
+ state.scrollX = MIN<int>(state.maxScrollX,
+ state.scrollX + state.arrowStepX);
+ dirty = true;
+ break;
+ case Common::KEYCODE_UP:
+ state.scrollY = MAX<int>(0, state.scrollY - state.arrowStepY);
+ dirty = true;
+ break;
+ case Common::KEYCODE_DOWN:
+ state.scrollY = MIN<int>(state.maxScrollY,
+ state.scrollY + state.arrowStepY);
+ dirty = true;
+ break;
+ default:
+ break;
+ }
+}
- const int maxScrollX = MAX<int>(0, (int)mapW - kMapWinW);
- const int maxScrollY = MAX<int>(0, (int)mapH - kMapWinH);
- int scrollX;
- int scrollY;
- if (mac) {
- scrollX = zoomX * (int)mapW /
- MAX<int>(1, kBigMapWindow.width()) - kMapWinW / 2;
- scrollY = zoomY * (int)mapH /
- MAX<int>(1, kBigMapWindow.height()) - kMapWinH / 2;
- } else {
- scrollX = zoomX;
- scrollY = zoomY;
+bool EEMEngine::bigMapHandleDetailMouseDown(const Common::Event &ev,
+ BigMapDetailState &state,
+ bool &returnToOverview,
+ bool &dirty) {
+ setInteractiveMouseCursor(
+ state.returnRect.contains(ev.mouse.x, ev.mouse.y) ||
+ state.setupRect.contains(ev.mouse.x, ev.mouse.y));
+
+ if (state.setupRect.contains(ev.mouse.x, ev.mouse.y)) {
+ _nextScreen = kScreenSetup;
+ setInteractiveMouseCursor(false);
+ return false;
+ }
+ if (state.returnRect.contains(ev.mouse.x, ev.mouse.y)) {
+ returnToOverview = true;
+ return true;
+ }
+ if (state.arrowYUp.contains(ev.mouse.x, ev.mouse.y)) {
+ state.scrollY = MAX<int>(0, state.scrollY - state.arrowStepY);
+ dirty = true;
+ return true;
+ }
+ if (state.arrowYDown.contains(ev.mouse.x, ev.mouse.y)) {
+ state.scrollY = MIN<int>(state.maxScrollY,
+ state.scrollY + state.arrowStepY);
+ dirty = true;
+ return true;
+ }
+ if (state.arrowXLeft.contains(ev.mouse.x, ev.mouse.y)) {
+ state.scrollX = MAX<int>(0, state.scrollX - state.arrowStepX);
+ dirty = true;
+ return true;
+ }
+ if (state.arrowXRight.contains(ev.mouse.x, ev.mouse.y)) {
+ state.scrollX = MIN<int>(state.maxScrollX,
+ state.scrollX + state.arrowStepX);
+ dirty = true;
+ return true;
+ }
+ if (state.xSlider.contains(ev.mouse.x, ev.mouse.y)) {
+ if (state.maxScrollX > 0) {
+ const int t = ev.mouse.x - state.xSlider.left;
+ const int tw = state.xSlider.width();
+ state.scrollX = MAX<int>(0, MIN<int>(state.maxScrollX,
+ t * state.maxScrollX / MAX<int>(1, tw)));
+ dirty = true;
+ }
+ return true;
+ }
+ if (state.ySlider.contains(ev.mouse.x, ev.mouse.y)) {
+ if (state.maxScrollY > 0) {
+ const int t = ev.mouse.y - state.ySlider.top;
+ const int th = state.ySlider.height();
+ state.scrollY = MAX<int>(0, MIN<int>(state.maxScrollY,
+ t * state.maxScrollY / MAX<int>(1, th)));
+ dirty = true;
}
- scrollX = MAX<int>(0, MIN<int>(maxScrollX, scrollX));
- scrollY = MAX<int>(0, MIN<int>(maxScrollY, scrollY));
-
- setSitePalette(isLondon() ? 0x3a : 0x23);
-
- const uint32 detailStartTick = g_system->getMillis();
- drawBigMapDetail(scrollX, scrollY, mapPixels, mapW, mapH, 0);
- uint32 detailLastTick = detailStartTick;
- uint32 detailLastCycleTick = detailStartTick;
- bool returnToOverview = false;
-
- const Common::Rect returnBase(252, 43, kScreenWidth, kScreenHeight);
- const Common::Rect kBigMapReturnRect =
- mac ? scaleRect(returnBase) : returnBase;
- const Common::Rect kArrowYUp =
- mac ? scaleRect(Common::Rect(237, 2, 247, 11))
- : Common::Rect(237, 2, 247, 11);
- const Common::Rect kArrowYDown =
- mac ? scaleRect(Common::Rect(237, 163, 247, 172))
- : Common::Rect(237, 163, 247, 172);
- const Common::Rect kArrowXLeft =
- mac ? scaleRect(Common::Rect(2, 175, 12, 185))
- : Common::Rect(2, 175, 12, 185);
- const Common::Rect kArrowXRight =
- mac ? scaleRect(Common::Rect(224, 175, 234, 185))
- : Common::Rect(224, 175, 234, 185);
- const Common::Rect xSliderBase = isLondon()
- ? Common::Rect(15, 176, 220, 184)
- : Common::Rect(15, 175, 221, 185);
- const Common::Rect ySliderBase = isLondon()
- ? Common::Rect(238, 16, 246, 158)
- : Common::Rect(237, 14, 247, 160);
- const Common::Rect kXSlider =
- mac ? scaleRect(xSliderBase) : xSliderBase;
- const Common::Rect kYSlider =
- mac ? scaleRect(ySliderBase) : ySliderBase;
- const Common::Rect detailSetupBase = isFloppy()
- ? Common::Rect(251, 3, 315, 42)
- : (isLondon() ? Common::Rect(251, 3, 315, 42)
- : Common::Rect(252, 4, 315, 42));
- const Common::Rect kDetailSetupBtn =
- mac ? scaleRect(detailSetupBase) : detailSetupBase;
- const int baseArrowStep = isLondon() ? 8 : 16;
- const int kArrowStepX = mac ? scaleX(baseArrowStep) : baseArrowStep;
- const int kArrowStepY = mac ? scaleY(baseArrowStep) : baseArrowStep;
- const int kSliderRange = maxScrollX;
- const int kSliderRangeY = maxScrollY;
- const Common::Point detailMouse =
- g_system->getEventManager()->getMousePos();
+ return true;
+ }
+ if (bigMapTrySelectDetailSite(ev.mouse.x, ev.mouse.y, state)) {
+ setInteractiveMouseCursor(false);
+ return false;
+ }
+
+ return true;
+}
+
+bool EEMEngine::bigMapHandleDetailEvent(const Common::Event &ev,
+ BigMapDetailState &state,
+ bool &returnToOverview,
+ bool &dirty) {
+ if (ev.type == Common::EVENT_QUIT ||
+ ev.type == Common::EVENT_RETURN_TO_LAUNCHER) {
+ setInteractiveMouseCursor(false);
+ return false;
+ }
+
+ if (ev.type == Common::EVENT_KEYDOWN) {
+ bigMapHandleDetailKey(ev, state, dirty);
+ return true;
+ }
+
+ if (ev.type == Common::EVENT_MOUSEMOVE) {
setInteractiveMouseCursor(
- kBigMapReturnRect.contains(detailMouse.x, detailMouse.y) ||
- kDetailSetupBtn.contains(detailMouse.x, detailMouse.y));
-
- while (!shouldQuit() && !returnToOverview) {
- Common::Event ev;
- bool dirty = false;
- bool cycleTick = false;
- while (g_system->getEventManager()->pollEvent(ev)) {
- if (ev.type == Common::EVENT_QUIT ||
- ev.type == Common::EVENT_RETURN_TO_LAUNCHER) {
- setInteractiveMouseCursor(false);
- return;
- }
- if (ev.type == Common::EVENT_KEYDOWN) {
- if (ev.kbd.keycode == Common::KEYCODE_ESCAPE) {
- openMainMenuDialog();
- dirty = true;
- continue;
- }
- if (ev.kbd.keycode == Common::KEYCODE_LEFT) {
- scrollX = MAX<int>(0, scrollX - kArrowStepX);
- dirty = true;
- } else if (ev.kbd.keycode == Common::KEYCODE_RIGHT) {
- scrollX = MIN<int>(maxScrollX, scrollX + kArrowStepX);
- dirty = true;
- } else if (ev.kbd.keycode == Common::KEYCODE_UP) {
- scrollY = MAX<int>(0, scrollY - kArrowStepY);
- dirty = true;
- } else if (ev.kbd.keycode == Common::KEYCODE_DOWN) {
- scrollY = MIN<int>(maxScrollY, scrollY + kArrowStepY);
- dirty = true;
- }
- }
- if (ev.type == Common::EVENT_MOUSEMOVE)
- setInteractiveMouseCursor(
- kBigMapReturnRect.contains(ev.mouse.x, ev.mouse.y) ||
- kDetailSetupBtn.contains(ev.mouse.x, ev.mouse.y));
- if (ev.type == Common::EVENT_LBUTTONDOWN) {
- setInteractiveMouseCursor(
- kBigMapReturnRect.contains(ev.mouse.x, ev.mouse.y) ||
- kDetailSetupBtn.contains(ev.mouse.x, ev.mouse.y));
- if (kDetailSetupBtn.contains(ev.mouse.x, ev.mouse.y)) {
- _nextScreen = kScreenSetup;
- setInteractiveMouseCursor(false);
- return;
- }
- if (kBigMapReturnRect.contains(ev.mouse.x, ev.mouse.y)) {
- returnToOverview = true;
- break;
- } else if (kArrowYUp.contains(ev.mouse.x, ev.mouse.y)) {
- scrollY = MAX<int>(0, scrollY - kArrowStepY);
- dirty = true;
- } else if (kArrowYDown.contains(ev.mouse.x, ev.mouse.y)) {
- scrollY = MIN<int>(MAX<int>(0, kSliderRangeY),
- scrollY + kArrowStepY);
- dirty = true;
- } else if (kArrowXLeft.contains(ev.mouse.x, ev.mouse.y)) {
- scrollX = MAX<int>(0, scrollX - kArrowStepX);
- dirty = true;
- } else if (kArrowXRight.contains(ev.mouse.x, ev.mouse.y)) {
- scrollX = MIN<int>(MAX<int>(0, kSliderRange),
- scrollX + kArrowStepX);
- dirty = true;
- } else if (kXSlider.contains(ev.mouse.x, ev.mouse.y)) {
- if (kSliderRange > 0) {
- const int t = ev.mouse.x - kXSlider.left;
- const int tw = kXSlider.width();
- scrollX = MAX<int>(0, MIN<int>(kSliderRange,
- t * kSliderRange / MAX<int>(1, tw)));
- dirty = true;
- }
- } else if (kYSlider.contains(ev.mouse.x, ev.mouse.y)) {
- if (kSliderRangeY > 0) {
- const int t = ev.mouse.y - kYSlider.top;
- const int th = kYSlider.height();
- scrollY = MAX<int>(0, MIN<int>(kSliderRangeY,
- t * kSliderRangeY / MAX<int>(1, th)));
- dirty = true;
- }
- } else if (ev.mouse.x >= kMapWinX &&
- ev.mouse.x < kMapWinX + kMapWinW &&
- ev.mouse.y >= kMapWinY &&
- ev.mouse.y < kMapWinY + kMapWinH) {
- // Per-site bbox from `_StampButtons` (SmallMap +8/+0xa).
- const bool fmap = _mystery.isLoaded() && isFloppy();
- struct DetailMapHit {
- uint site;
- Common::Rect rect;
- };
- Common::Array<DetailMapHit> hits;
- for (uint i = 0; i < _mystery.numSites(); i++) {
- // On-map flag alone, matching `_SearchMapButtons`.
- if (!_mystery._onSites[i])
- continue;
- const byte *entry = _mystery.mapEntry(i);
- if (!entry)
- continue;
- BigMapEntryInfo info;
- if (!readBigMapEntryInfo(entry, fmap,
- mac && _mystery.usesCompactMacData(),
- info))
- continue;
- Picture button;
- int bw = 16;
- int bh = 16;
- if (_buttonArchive.loadEntry(info.buttonId, button)) {
- bw = button.surface.w;
- bh = button.surface.h;
- }
- const int sx = (int)info.detailX - scrollX + kMapWinX;
- const int sy = (int)info.detailY - scrollY + kMapWinY;
- const Common::Rect r(sx, sy, sx + bw, sy + bh);
- if (r.intersects(Common::Rect(kMapWinX, kMapWinY,
- kMapWinX + kMapWinW, kMapWinY + kMapWinH))) {
- DetailMapHit hit = { i, r };
- hits.push_back(hit);
- }
- }
- Common::sort(hits.begin(), hits.end(),
- [](const DetailMapHit &a, const DetailMapHit &b) {
- if (a.rect.top != b.rect.top)
- return a.rect.top < b.rect.top;
- return a.rect.left < b.rect.left;
- });
- for (uint i = 0; i < hits.size(); i++) {
- if (hits[i].rect.contains(ev.mouse.x, ev.mouse.y)) {
- _mystery._lastSite = _mystery._siteNumber;
- _mystery._siteNumber = (uint16)hits[i].site;
- setInteractiveMouseCursor(false);
- return;
- }
- }
- }
- }
- }
+ state.returnRect.contains(ev.mouse.x, ev.mouse.y) ||
+ state.setupRect.contains(ev.mouse.x, ev.mouse.y));
+ return true;
+ }
+
+ if (ev.type == Common::EVENT_LBUTTONDOWN)
+ return bigMapHandleDetailMouseDown(ev, state, returnToOverview,
+ dirty);
+
+ return true;
+}
+
+bool EEMEngine::bigMapRunDetail(BigMapDetailState &state) {
+ if (!state.mapPixels)
+ return false;
+
+ const bool mac = isMacintosh();
+ setSitePalette(isLondon() ? 0x3a : 0x23);
+
+ state.startTick = g_system->getMillis();
+ state.lastDrawTick = state.startTick;
+ state.lastCycleTick = state.startTick;
+ drawBigMapDetail(state.scrollX, state.scrollY, *state.mapPixels,
+ state.mapW, state.mapH, 0);
+
+ const Common::Point detailMouse =
+ g_system->getEventManager()->getMousePos();
+ setInteractiveMouseCursor(
+ state.returnRect.contains(detailMouse.x, detailMouse.y) ||
+ state.setupRect.contains(detailMouse.x, detailMouse.y));
+
+ bool returnToOverview = false;
+ while (!shouldQuit() && !returnToOverview) {
+ Common::Event ev;
+ bool dirty = false;
+ bool cycleTick = false;
+ while (g_system->getEventManager()->pollEvent(ev)) {
+ if (!bigMapHandleDetailEvent(ev, state, returnToOverview, dirty))
+ return false;
if (returnToOverview)
break;
+ }
+ if (returnToOverview)
+ break;
- const uint32 now = g_system->getMillis();
- if (now - detailLastTick >= 100) {
- detailLastTick = now;
+ const uint32 now = g_system->getMillis();
+ if (now - state.lastDrawTick >= 100) {
+ state.lastDrawTick = now;
+ dirty = true;
+ }
+ if (isLondon()) {
+ const uint32 cycleDelay = mac ? kMacMapColorCycleDelayMs : 100;
+ if (now - state.lastCycleTick >= cycleDelay) {
+ state.lastCycleTick = now;
+ cycleTick = true;
dirty = true;
}
- if (isLondon()) {
- const uint32 cycleDelay = mac ? kMacMapColorCycleDelayMs : 100;
- if (now - detailLastCycleTick >= cycleDelay) {
- detailLastCycleTick = now;
- cycleTick = true;
- dirty = true;
- }
- }
- if (cycleTick && isLondon()) {
- if (mac) {
- cyclePaletteRangeReverse(0xe9, 0xeb);
- cyclePaletteRangeReverse(0xec, 0xef);
- cyclePaletteRangeReverse(0xf0, 0xf2);
- } else {
- cyclePaletteRangeReverse(0xee, 0xf2);
- cyclePaletteRangeReverse(0xea, 0xed);
- }
- }
- if (dirty)
- drawBigMapDetail(scrollX, scrollY, mapPixels, mapW, mapH,
- now - detailStartTick);
- g_system->updateScreen();
- g_system->delayMillis(10);
}
- if (!returnToOverview)
+ if (cycleTick)
+ bigMapCycleDetailPalette(mac);
+ if (dirty)
+ drawBigMapDetail(state.scrollX, state.scrollY, *state.mapPixels,
+ state.mapW, state.mapH,
+ now - state.startTick);
+ g_system->updateScreen();
+ g_system->delayMillis(10);
+ }
+
+ return returnToOverview;
+}
+
+void EEMEngine::doBigMap() {
+ if (!_mystery.isLoaded())
+ return;
+
+ if (isLondon()) {
+ _mystery._pendingSiteJump = 0;
+ _mystery._siteReturnDepth = 0;
+ memset(_mystery._siteReturnStack, 0, sizeof(_mystery._siteReturnStack));
+ }
+
+ CursorMan.showMouse(true);
+
+ while (!shouldQuit()) {
+ BigMapOverviewState overview;
+ if (!bigMapRunOverview(overview))
+ return;
+
+ Common::Array<byte> mapPixels;
+ uint16 mapW = 0;
+ uint16 mapH = 0;
+ if (!bigMapLoadDetailPixels(mapPixels, mapW, mapH))
+ return;
+
+ BigMapDetailState detail;
+ bigMapInitDetailState(detail, mapPixels, mapW, mapH, overview);
+ if (!bigMapRunDetail(detail))
return;
}
}
Commit: 4db53b24afaead2456813e9faad3f2ee37cb8fe8
https://github.com/scummvm/scummvm/commit/4db53b24afaead2456813e9faad3f2ee37cb8fe8
Author: neuromancer (gustavo.grieco at gmail.com)
Date: 2026-06-26T20:37:27+02:00
Commit Message:
EEM: refactored coordinate handling
Changed paths:
A engines/eem/coords.cpp
A engines/eem/coords.h
engines/eem/eem.h
engines/eem/map_ui.cpp
engines/eem/module.mk
engines/eem/site.cpp
engines/eem/ui.cpp
diff --git a/engines/eem/coords.cpp b/engines/eem/coords.cpp
new file mode 100644
index 00000000000..38cdccafbbb
--- /dev/null
+++ b/engines/eem/coords.cpp
@@ -0,0 +1,70 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "eem/coords.h"
+#include "eem/eem.h"
+
+namespace EEM {
+
+uint16 readScriptU16(const byte *p, bool bigEndian) {
+ return bigEndian ? READ_BE_UINT16(p) : READ_LE_UINT16(p);
+}
+
+int16 readScriptS16(const byte *p, bool bigEndian) {
+ return (int16)readScriptU16(p, bigEndian);
+}
+
+Common::Rect readRectXYXY(const byte *p, bool bigEndian) {
+ const int16 x1 = readScriptS16(p, bigEndian);
+ const int16 y1 = readScriptS16(p + 2, bigEndian);
+ const int16 x2 = readScriptS16(p + 4, bigEndian);
+ const int16 y2 = readScriptS16(p + 6, bigEndian);
+
+ return Common::Rect(x1, y1, x2, y2);
+}
+
+Common::Rect readDosRectLE(const byte *p) {
+ return readRectXYXY(p, false);
+}
+
+Common::Rect readMacQuickDrawRectBE(const byte *p) {
+ const int16 top = readScriptS16(p, true);
+ const int16 left = readScriptS16(p + 2, true);
+ const int16 bottom = readScriptS16(p + 4, true);
+ const int16 right = readScriptS16(p + 6, true);
+
+ return Common::Rect(left, top, right, bottom);
+}
+
+Common::Rect readMacQuickDrawRectLE(const byte *p) {
+ const int16 top = readScriptS16(p, false);
+ const int16 left = readScriptS16(p + 2, false);
+ const int16 bottom = readScriptS16(p + 4, false);
+ const int16 right = readScriptS16(p + 6, false);
+
+ return Common::Rect(left, top, right, bottom);
+}
+
+Common::Rect scaleDosRectIfMac(const EEMEngine &vm, const Common::Rect &rect) {
+ return vm.scaleRect(rect);
+}
+
+} // End of namespace EEM
diff --git a/engines/eem/coords.h b/engines/eem/coords.h
new file mode 100644
index 00000000000..fb5289c7076
--- /dev/null
+++ b/engines/eem/coords.h
@@ -0,0 +1,49 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#ifndef EEM_COORDS_H
+#define EEM_COORDS_H
+
+#include "common/rect.h"
+#include "common/scummsys.h"
+
+namespace EEM {
+
+class EEMEngine;
+
+uint16 readScriptU16(const byte *p, bool bigEndian);
+int16 readScriptS16(const byte *p, bool bigEndian);
+
+/// x1, y1, x2, y2, with the requested word endian.
+Common::Rect readRectXYXY(const byte *p, bool bigEndian);
+
+/// DOS script/site rectangle: x1, y1, x2, y2, little-endian.
+Common::Rect readDosRectLE(const byte *p);
+
+/// Mac QuickDraw rectangle: top, left, bottom, right.
+Common::Rect readMacQuickDrawRectBE(const byte *p);
+Common::Rect readMacQuickDrawRectLE(const byte *p);
+
+Common::Rect scaleDosRectIfMac(const EEMEngine &vm, const Common::Rect &rect);
+
+} // End of namespace EEM
+
+#endif
diff --git a/engines/eem/eem.h b/engines/eem/eem.h
index 12b2b1eea2f..1c827f3af39 100644
--- a/engines/eem/eem.h
+++ b/engines/eem/eem.h
@@ -406,7 +406,6 @@ private:
uint32 lastCycleTick = 0;
};
- Common::Rect bigMapScaledRect(const Common::Rect &rect) const;
bool bigMapRunOverview(BigMapOverviewState &state);
bool bigMapLoadDetailPixels(Common::Array<byte> &mapPixels,
uint16 &mapW, uint16 &mapH);
@@ -684,6 +683,8 @@ public:
}
};
+Common::Rect pdaControlRect(const EEMEngine *vm, const Common::Rect &rect);
+
} // End of namespace EEM
#endif
diff --git a/engines/eem/map_ui.cpp b/engines/eem/map_ui.cpp
index dd9bd6cf11c..b91d437f385 100644
--- a/engines/eem/map_ui.cpp
+++ b/engines/eem/map_ui.cpp
@@ -34,6 +34,7 @@
#include "video/flic_decoder.h"
#include "eem/animation.h"
+#include "eem/coords.h"
#include "eem/eem.h"
#include "eem/music.h"
#include "eem/site.h"
@@ -78,6 +79,10 @@ const int kMacLondonApproachDoneLeft = 452;
const int kMacLondonApproachDoneTop = 277;
const int kMacLondonApproachDoneRight = 504;
const int kMacLondonApproachDoneBottom = 332;
+// Mac FUN_0000849e maps overview clicks to detail scroll with
+// mouse * 2 - center, clamped to zero before subtracting.
+const int kMacBigMapScrollCenterX = 0xbf;
+const int kMacBigMapScrollCenterY = 0xab;
byte mapVisitedMarkerColor(byte color) {
switch (color) {
@@ -236,10 +241,6 @@ static bool openNumberedScriptFile(Common::File &f, Common::String &name,
return false;
}
-static uint16 readScriptU16(const byte *p, bool bigEndian) {
- return bigEndian ? READ_BE_UINT16(p) : READ_LE_UINT16(p);
-}
-
Common::String cleanLondonApproachPage(const byte *start, uint32 len) {
Common::String page((const char *)start, len);
while (!page.empty() &&
@@ -289,19 +290,8 @@ bool loadLondonApproachData(uint16 approachId, LondonApproachData &out,
out.videoId = readScriptU16(data.data() + 0, macintosh);
out.videoX = readScriptU16(data.data() + 2, macintosh);
out.videoY = readScriptU16(data.data() + 4, macintosh);
- if (macintosh) {
- const int16 top = (int16)readScriptU16(data.data() + 6, true);
- const int16 left = (int16)readScriptU16(data.data() + 8, true);
- const int16 bottom = (int16)readScriptU16(data.data() + 10, true);
- const int16 right = (int16)readScriptU16(data.data() + 12, true);
- out.textRect = Common::Rect(left, top, right, bottom);
- } else {
- const int16 x1 = (int16)readScriptU16(data.data() + 6, false);
- const int16 y1 = (int16)readScriptU16(data.data() + 8, false);
- const int16 x2 = (int16)readScriptU16(data.data() + 10, false);
- const int16 y2 = (int16)readScriptU16(data.data() + 12, false);
- out.textRect = Common::Rect(x1, y1, x2, y2);
- }
+ out.textRect = macintosh ? readMacQuickDrawRectBE(data.data() + 6)
+ : readDosRectLE(data.data() + 6);
const uint16 pageCount = readScriptU16(data.data() + 14, macintosh);
out.pages.clear();
@@ -427,10 +417,6 @@ void remapSurfaceColor(Graphics::ManagedSurface &surface, byte from, byte to) {
// 20fe:0d2f`). Click icon = travel.
// MapData entry (14 bytes): +0..3 ???, +4 BigMapX, +6 BigMapY,
// +8 SmallMapX, +0xa SmallMapY, +0xc crime-flag.
-Common::Rect EEMEngine::bigMapScaledRect(const Common::Rect &rect) const {
- return isMacintosh() ? scaleRect(rect) : rect;
-}
-
void EEMEngine::bigMapCycleOverviewPalette(bool mac) {
if (isLondon()) {
if (mac) {
@@ -472,8 +458,8 @@ bool EEMEngine::bigMapRunOverview(BigMapOverviewState &state) {
? Common::Rect(251, 3, 315, 42)
: (isLondon() ? Common::Rect(252, 1, 315, 42)
: Common::Rect(252, 4, 315, 42));
- const Common::Rect setupBtnRect = bigMapScaledRect(setupBtnBase);
- state.window = bigMapScaledRect(Common::Rect(0, 0, 247, 192));
+ const Common::Rect setupBtnRect = scaleDosRectIfMac(*this, setupBtnBase);
+ state.window = scaleDosRectIfMac(*this, Common::Rect(0, 0, 247, 192));
state.zoomX = 0;
state.zoomY = 0;
@@ -580,10 +566,12 @@ void EEMEngine::bigMapInitDetailState(BigMapDetailState &state,
state.maxScrollY = MAX<int>(0, (int)mapH - state.mapWinH);
if (mac) {
- state.scrollX = overview.zoomX * (int)mapW /
- MAX<int>(1, overview.window.width()) - state.mapWinW / 2;
- state.scrollY = overview.zoomY * (int)mapH /
- MAX<int>(1, overview.window.height()) - state.mapWinH / 2;
+ state.scrollX = overview.zoomX * 2;
+ state.scrollY = overview.zoomY * 2;
+ state.scrollX = state.scrollX < kMacBigMapScrollCenterX + 1
+ ? 0 : state.scrollX - kMacBigMapScrollCenterX;
+ state.scrollY = state.scrollY < kMacBigMapScrollCenterY + 1
+ ? 0 : state.scrollY - kMacBigMapScrollCenterY;
} else {
state.scrollX = overview.zoomX;
state.scrollY = overview.zoomY;
@@ -591,19 +579,19 @@ void EEMEngine::bigMapInitDetailState(BigMapDetailState &state,
state.scrollX = MAX<int>(0, MIN<int>(state.maxScrollX, state.scrollX));
state.scrollY = MAX<int>(0, MIN<int>(state.maxScrollY, state.scrollY));
- state.returnRect = bigMapScaledRect(
+ state.returnRect = scaleDosRectIfMac(*this,
Common::Rect(252, 43, kScreenWidth, kScreenHeight));
- state.arrowYUp = bigMapScaledRect(Common::Rect(237, 2, 247, 11));
- state.arrowYDown = bigMapScaledRect(Common::Rect(237, 163, 247, 172));
- state.arrowXLeft = bigMapScaledRect(Common::Rect(2, 175, 12, 185));
- state.arrowXRight = bigMapScaledRect(Common::Rect(224, 175, 234, 185));
- state.xSlider = bigMapScaledRect(isLondon()
+ state.arrowYUp = scaleDosRectIfMac(*this, Common::Rect(237, 2, 247, 11));
+ state.arrowYDown = scaleDosRectIfMac(*this, Common::Rect(237, 163, 247, 172));
+ state.arrowXLeft = scaleDosRectIfMac(*this, Common::Rect(2, 175, 12, 185));
+ state.arrowXRight = scaleDosRectIfMac(*this, Common::Rect(224, 175, 234, 185));
+ state.xSlider = scaleDosRectIfMac(*this, isLondon()
? Common::Rect(15, 176, 220, 184)
: Common::Rect(15, 175, 221, 185));
- state.ySlider = bigMapScaledRect(isLondon()
+ state.ySlider = scaleDosRectIfMac(*this, isLondon()
? Common::Rect(238, 16, 246, 158)
: Common::Rect(237, 14, 247, 160));
- state.setupRect = bigMapScaledRect(isFloppy()
+ state.setupRect = scaleDosRectIfMac(*this, isFloppy()
? Common::Rect(251, 3, 315, 42)
: (isLondon() ? Common::Rect(251, 3, 315, 42)
: Common::Rect(252, 4, 315, 42)));
diff --git a/engines/eem/module.mk b/engines/eem/module.mk
index 4b708614703..0ea400f8c61 100644
--- a/engines/eem/module.mk
+++ b/engines/eem/module.mk
@@ -4,6 +4,7 @@ MODULE_OBJS = \
animation.o \
audio.o \
clues.o \
+ coords.o \
eem.o \
font.o \
graphics.o \
diff --git a/engines/eem/site.cpp b/engines/eem/site.cpp
index ec49e304f40..f5db58d401a 100644
--- a/engines/eem/site.cpp
+++ b/engines/eem/site.cpp
@@ -31,6 +31,7 @@
#include "graphics/paletteman.h"
#include "eem/audio.h"
+#include "eem/coords.h"
#include "eem/detection.h"
#include "eem/eem.h"
#include "eem/mystery.h"
@@ -219,29 +220,14 @@ void blitMacAnimFrameAnchored(Graphics::Surface *dst, const Picture &p,
}
bool readHotspotRect(const byte *r, bool mac, Common::Rect &rect) {
- if (mac) {
- const int16 top = (int16)READ_LE_UINT16(r + 0);
- const int16 left = (int16)READ_LE_UINT16(r + 2);
- const int16 bottom = (int16)READ_LE_UINT16(r + 4);
- const int16 right = (int16)READ_LE_UINT16(r + 6);
- if (right <= left || bottom <= top)
- return false;
- rect = Common::Rect(left, top, right, bottom);
- return true;
- }
-
- const int16 x1 = (int16)READ_LE_UINT16(r + 0);
- const int16 y1 = (int16)READ_LE_UINT16(r + 2);
- const int16 x2 = (int16)READ_LE_UINT16(r + 4);
- const int16 y2 = (int16)READ_LE_UINT16(r + 6);
- if (x2 <= x1 || y2 <= y1)
+ rect = mac ? readMacQuickDrawRectLE(r) : readDosRectLE(r);
+ if (rect.right <= rect.left || rect.bottom <= rect.top)
return false;
- rect = Common::Rect(x1, y1, x2, y2);
return true;
}
Common::Rect siteControlRect(const EEMEngine *vm, const Common::Rect &rect) {
- return (vm && vm->isMacintosh()) ? vm->scaleRect(rect) : rect;
+ return pdaControlRect(vm, rect);
}
// `_ColorCycle @ 172b:2015` â rotate `_fpal[start..end]` by one slot:
diff --git a/engines/eem/ui.cpp b/engines/eem/ui.cpp
index cf4de4ea63c..37adc6b7d7a 100644
--- a/engines/eem/ui.cpp
+++ b/engines/eem/ui.cpp
@@ -33,6 +33,7 @@
#include "graphics/managed_surface.h"
#include "eem/audio.h"
+#include "eem/coords.h"
#include "eem/detection.h"
#include "eem/eem.h"
#include "eem/music.h"
@@ -69,10 +70,6 @@ static bool openNumberedScriptFile(Common::File &f, Common::String &name,
return false;
}
-static uint16 readScriptU16(const byte *p, bool bigEndian) {
- return bigEndian ? READ_BE_UINT16(p) : READ_LE_UINT16(p);
-}
-
// Setup-screen highlight rects
constexpr Common::Rect kSetupKid1Rect (Common::Point( 99, 44), 49, 8);
constexpr Common::Rect kSetupKid2Rect (Common::Point( 99, 54), 49, 8);
@@ -1411,9 +1408,9 @@ int EEMEngine::doShowEnding(uint num, bool firstPage) {
const int sw = screenWidth();
const int sh = screenHeight();
const Common::Rect endingPrevRect =
- macEnding ? scaleRect(kEndingPrevPageRect) : kEndingPrevPageRect;
+ scaleDosRectIfMac(*this, kEndingPrevPageRect);
const Common::Rect endingNextRect =
- macEnding ? scaleRect(kEndingNextPageRect) : kEndingNextPageRect;
+ scaleDosRectIfMac(*this, kEndingNextPageRect);
uint pageOffsets[8];
const uint pageOffsetCap =
(uint)(sizeof(pageOffsets) / sizeof(pageOffsets[0]));
@@ -1544,10 +1541,11 @@ int EEMEngine::doShowEnding(uint num, bool firstPage) {
break;
const uint16 picNum =
readScriptU16(buf.data() + off, macLooseEnding);
- x1 = readScriptU16(buf.data() + off + 2, macLooseEnding);
- y1 = readScriptU16(buf.data() + off + 4, macLooseEnding);
- x2 = readScriptU16(buf.data() + off + 6, macLooseEnding);
- (void)readScriptU16(buf.data() + off + 8, macLooseEnding); // y2 (unused â WordWrap2 takes width only)
+ const Common::Rect textRect =
+ readRectXYXY(buf.data() + off + 2, macLooseEnding);
+ x1 = textRect.left;
+ y1 = textRect.top;
+ x2 = textRect.right;
Picture bg;
if (_picsArchive.getPicture(picNum, bg))
Commit: e502b122c9064d2811274316972dcd94cb439ef4
https://github.com/scummvm/scummvm/commit/e502b122c9064d2811274316972dcd94cb439ef4
Author: neuromancer (gustavo.grieco at gmail.com)
Date: 2026-06-26T20:37:27+02:00
Commit Message:
EEM: fixed regression in EEM2 DOS intro
Changed paths:
engines/eem/eem.cpp
diff --git a/engines/eem/eem.cpp b/engines/eem/eem.cpp
index 66c9ea28a74..480ebbc889b 100644
--- a/engines/eem/eem.cpp
+++ b/engines/eem/eem.cpp
@@ -74,6 +74,8 @@ const uint kMacPicTitleLeft2 = 0x216;
const uint kMacPicTitleRight0 = 0x217;
const uint kMacPicTitleRight1 = 0x218;
const uint kMacPicTitleRight2 = 0x219;
+const uint kDosLondonPicHighScoreLogo = 0x356; // FUN_2721_084d
+const uint kDosLondonPalHighScoreLogo = 0x3d;
const uint kMacPalEAKids = 0x28;
const uint kMacPalTitle = 0x29;
const uint kPicMousePointer = 0x50; // 0x51 is the wait cursor
@@ -1776,15 +1778,17 @@ void EEMEngine::runLondonStartup() {
debugC(1, kDebugGeneral, "EEM2 (London): opening sequence");
// Opening logos. Mac London mirrors FUN_00009256: EA Kids colour-cycle,
- // publisher still, Storm still, then the FLC intro/title pair.
+ // publisher still, Storm still, then the FLC intro/title pair. DOS London
+ // uses the original ANM sequence after EA Kids instead.
if (!shouldQuit() && !_skipIntro)
showLondonEAKidsLogo();
- if (!shouldQuit() && !_skipIntro)
- showLondonLogo(0x20c, 0x3e, 3000); // publisher logo (FUN_00009074)
- if (!shouldQuit() && !_skipIntro)
- showLondonLogo(0x20b, 0x3d, 3000, /* playThunder= */ true);
if (isMacintosh()) {
+ if (!shouldQuit() && !_skipIntro)
+ showLondonLogo(0x20c, 0x3e, 3000); // publisher logo (FUN_00009074)
+ if (!shouldQuit() && !_skipIntro)
+ showLondonLogo(0x20b, 0x3d, 3000, /* playThunder= */ true);
+
// The Mac CD ships the post-logo intro as Flic movies where DOS uses
// bolt/movie/wave .ANM. KDCDINTR is the centered intro; BOOK54 is the
// full-screen animated title, held for the profile click/key.
@@ -1806,6 +1810,9 @@ void EEMEngine::runLondonStartup() {
_audio->stopVoice();
fadeCurrentPaletteToBlack();
}
+ if (!shouldQuit() && !_skipIntro)
+ showLondonLogo(kDosLondonPicHighScoreLogo,
+ kDosLondonPalHighScoreLogo, 3000);
// Intro movie with its theme (MUS00101.XMI).
if (!shouldQuit() && !_skipIntro && _music)
More information about the Scummvm-git-logs
mailing list