[Scummvm-git-logs] scummvm master -> b5d5892fb4f30ec01a4ac85a70688cdec8a86d9b
neuromancer
noreply at scummvm.org
Thu Sep 10 15:45:45 UTC 2026
This automated email contains information about 10 new commits which have been
pushed to the 'scummvm' repo located at https://api.github.com/repos/scummvm/scummvm .
Summary:
6b343086b8 EEM: initial support for talkie release of eem1 (mac)
60dcce8a22 EEM: show intro for eem1 talkie (mac)
96d27fdf90 EEM: better animations and precise map locations in EEM1 talkie (mac)
67c90d9a0b EEM: implement TRAVIS scroll bar in EEM1 talkie (mac)
be891235d7 EEM: extend TRAVIS scroll bar code for EEM2 (mac)
b141102aaf EEM: review and improved case workflow for EEM1 (mac)
1710907c2d EEM: allow to come back to the map from sites in EEM1 (mac)
cee1993fd4 EEM: avoid busy waiting when playing music in EEM1 (mac)
a67c973f3b EEM: improved mouse pointer rendering for both release of EEM1 (mac)
b5d5892fb4 EEM: enabled restored content for EEM1 (mac cd)
Commit: 6b343086b888019d45d998021a57ac6f590a357c
https://github.com/scummvm/scummvm/commit/6b343086b888019d45d998021a57ac6f590a357c
Author: neuromancer (gustavo.grieco at gmail.com)
Date: 2026-09-10T17:45:23+02:00
Commit Message:
EEM: initial support for talkie release of eem1 (mac)
Changed paths:
engines/eem/audio.cpp
engines/eem/audio.h
engines/eem/detection.cpp
engines/eem/eem.cpp
engines/eem/installer.cpp
engines/eem/installer.h
engines/eem/music.cpp
engines/eem/mystery.cpp
engines/eem/mystery.h
engines/eem/resource.cpp
engines/eem/resource.h
engines/eem/ui.cpp
diff --git a/engines/eem/audio.cpp b/engines/eem/audio.cpp
index 59fb5369aa2..aedff299acc 100644
--- a/engines/eem/audio.cpp
+++ b/engines/eem/audio.cpp
@@ -225,8 +225,10 @@ void AudioPlayer::cleanMysterySounds() {
bool AudioPlayer::initMacMysterySounds(uint mysteryNum) {
const uint16 firstResourceId = 1001;
- const Common::Path candidates[2] = {
+ const Common::Path candidates[] = {
+ Common::Path(Common::String::format("M%u.DBD", mysteryNum)),
Common::Path(Common::String::format("M%02u.DBD", mysteryNum)),
+ Common::Path(Common::String::format("M%u.CPD", mysteryNum)),
Common::Path(Common::String::format("M%02u.CPD", mysteryNum))
};
@@ -259,6 +261,9 @@ void AudioPlayer::playMacSnd(uint16 resourceId, Audio::SoundHandle &handle,
Common::SeekableReadStream *stream =
openMacResource(Common::Path("EEM Sound&Music"),
MKTAG('s', 'n', 'd', ' '), resourceId);
+ if (!stream)
+ stream = openMacResource(Common::Path("Eagle Eye Mysteries CD"),
+ MKTAG('s', 'n', 'd', ' '), resourceId);
if (!stream) {
warning("AudioPlayer: Mac snd resource %u missing", resourceId);
return;
diff --git a/engines/eem/audio.h b/engines/eem/audio.h
index e2f8778c97e..b6c64ecb594 100644
--- a/engines/eem/audio.h
+++ b/engines/eem/audio.h
@@ -44,7 +44,8 @@ class EEMEngine;
* (_UncompressedSound @ 202f:03e6); otherwise PKWARE DCL Implode
* (_DeCompressSound @ 202f:02ad). Each blob starts with SB Time Constant +
* AIL block count, then 8-bit unsigned PCM.
- * - Mac mystery spool streams live in M%02d.DBD/M%02d.CPD resource forks.
+ * - Mac mystery spool streams live in M%d.DBD/M%d.CPD resource forks
+ * (some releases use two-digit numbers).
* The executable tries compressed 'csnd' by resource id, then falls back to
* plain 'snd '; ids are 1001 + the zero-based mystery voice slot.
*
@@ -86,7 +87,7 @@ public:
void playFloppyVoiceSlot(uint slot, uint partner);
/// Loads the current mystery voice bundle. DOS uses M%u.SDX/M%u.SDB;
- /// Mac uses M%02u.DBD/M%02u.CPD 'csnd'/'snd ' resources.
+ /// Mac uses M%u.DBD/M%u.CPD 'csnd'/'snd ' resources.
bool initMysterySounds(uint mysteryNum);
/// _CleanMysterySounds @ 202f:05a5.
diff --git a/engines/eem/detection.cpp b/engines/eem/detection.cpp
index cc8c06edacb..4b36092e8ed 100644
--- a/engines/eem/detection.cpp
+++ b/engines/eem/detection.cpp
@@ -32,13 +32,13 @@ const PlainGameDescriptor eemGames[] = {
{ nullptr, nullptr }
};
-// The EEM2 (London) Macintosh CD keeps its data in subfolders ("EEM2 CD" with
-// "Data Files"/"Mac Scripts", and the app in "EEM London CD"). Let the detector
-// descend into them; the DOS releases have no such folders and are unaffected.
+// Macintosh CDs keep data and scripts in separate subfolders.
static const char *const directoryGlobs[] = {
+ "EEM CD",
"EEM2 CD",
"Data Files",
"Mac Scripts",
+ "Eagle Eye Mysteries CD",
"EEM London CD",
nullptr
};
@@ -114,6 +114,16 @@ const ADGameDescription gameDescriptions[] = {
ADGF_TESTING,
GUI_OPTIONS_EEM_MAC
},
+ {
+ "eem",
+ "CD",
+ AD_ENTRY2s("pics.dbd", "d:abbef80edd830e0a85d7fdc42e6a419c", 3840029,
+ "m0.bin", "d:acd867b61ef36dd98f94bbf00cc5adb0", 12193),
+ Common::EN_ANY,
+ Common::kPlatformMacintosh,
+ ADGF_UNSTABLE,
+ GUI_OPTIONS_EEM_MAC
+ },
{
// Eagle Eye Mysteries in London
"eem2",
@@ -169,7 +179,7 @@ const DebugChannelDef debugFlagList[] = {
class EEMMetaEngineDetection : public AdvancedMetaEngineDetection<ADGameDescription> {
public:
EEMMetaEngineDetection() : AdvancedMetaEngineDetection(EEM::gameDescriptions, EEM::eemGames) {
- // Reach the EEM2 Mac data nested under "EEM2 CD/Data Files" etc.
+ // Reach the Mac CD data and script folders.
_maxScanDepth = 3;
_directoryGlobs = EEM::directoryGlobs;
}
diff --git a/engines/eem/eem.cpp b/engines/eem/eem.cpp
index 84b6edc5853..4ac0289d266 100644
--- a/engines/eem/eem.cpp
+++ b/engines/eem/eem.cpp
@@ -30,6 +30,7 @@
#include "common/path.h"
#include "common/system.h"
#include "common/textconsole.h"
+#include "common/macresman.h"
#include "engines/util.h"
@@ -431,26 +432,26 @@ Common::FSNode findChildDirectoryIgnoreCase(const Common::FSNode &dir,
return Common::FSNode();
}
-void addMacLondonDirectoryIfPresent(const Common::FSNode &dir,
- const char *archiveName,
- int depth = 1) {
+void addMacDirectoryIfPresent(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) {
+void addMacCDSearchPathsFrom(const Common::FSNode &base,
+ const char *archivePrefix, bool london) {
if (!base.exists() || !base.isDirectory())
return;
- Common::FSNode cd = findChildDirectoryIgnoreCase(base, "EEM2 CD");
- if (!cd.exists()) {
+ const char *cdName = london ? "EEM2 CD" : "EEM CD";
+ Common::FSNode cd = findChildDirectoryIgnoreCase(base, cdName);
+ if (!cd.exists() && london) {
Common::FSNode wrapper = findChildDirectoryIgnoreCase(base, "EEM_London");
if (wrapper.exists())
cd = findChildDirectoryIgnoreCase(wrapper, "EEM2 CD");
}
- if (!cd.exists() && base.getName().equalsIgnoreCase("EEM2 CD"))
+ if (!cd.exists() && base.getName().equalsIgnoreCase(cdName))
cd = base;
if (!cd.exists()) {
const Common::String baseName = base.getName();
@@ -475,32 +476,33 @@ void addMacLondonSearchPathsFrom(const Common::FSNode &base,
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);
+ addMacDirectoryIfPresent(dataDir, dataArchive.c_str(), 2);
+ addMacDirectoryIfPresent(scriptsDir, scriptsArchive.c_str(), 3);
+ addMacDirectoryIfPresent(animDir, animArchive.c_str(), 2);
if (scriptsDir.exists()) {
Common::String approachesArchive = Common::String::format(
"%s-approaches", archivePrefix);
- addMacLondonDirectoryIfPresent(
+ addMacDirectoryIfPresent(
findChildDirectoryIgnoreCase(scriptsDir, "Approaches"),
approachesArchive.c_str());
}
const Common::FSNode app =
- findChildDirectoryIgnoreCase(cd.getParent(), "EEM London CD");
- addMacLondonDirectoryIfPresent(app, appArchive.c_str(), 2);
+ findChildDirectoryIgnoreCase(cd.getParent(),
+ london ? "EEM London CD" : "Eagle Eye Mysteries CD");
+ addMacDirectoryIfPresent(app, appArchive.c_str(), 2);
}
static bool loadMacFontResource(EEMFont &font, uint16 resourceId, int size) {
addMacResourceSearchPaths();
- // The Eagle Eye fonts live in the game application's resource fork. EEM1 Mac
- // ships it as "Eagle Eye Mysteries"; EEM2 (London) Mac ships it as
- // "EEM London CD" but reuses the same FONT resource ids (3214/3209).
+ // The Mac applications share FONT resource IDs 3214/3209.
static const char *const kAppForks[] = {
"Eagle Eye Mysteries",
"rsrc/Eagle Eye Mysteries",
+ "Eagle Eye Mysteries CD",
+ "rsrc/Eagle Eye Mysteries CD",
"EEM London CD",
};
for (uint i = 0; i < ARRAYSIZE(kAppForks); i++) {
@@ -927,40 +929,22 @@ void EEMEngine::setSiteHotspotCursorId(int cursorId) {
bool EEMEngine::openArchives() {
const bool mac = isMacintosh();
- // EEM2 (London) Mac is played straight from the CD, whose data lives in
- // subfolders ("Data Files", "Mac Scripts") with the Mac app in "EEM London
- // CD". Register them so the bare-name opens below -- and the later script,
- // mystery and palette loaders -- resolve whether the user points ScummVM at
- // the disc root or at the "EEM2 CD" folder.
- if (mac && isLondon()) {
+ // Accept either the disc root or its data folder.
+ if (mac) {
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", 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", 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.
- // When those files are present, mount a virtual archive that decompresses
- // the game data (PICS.DBD, MysteryData, fonts, ...) on demand, so the opens
- // below and the Mac resource-fork lookups resolve transparently. (EEM2 Mac
- // ships loose on the CD and has no such installer.)
+ addMacCDSearchPathsFrom(gameDir, "eem-mac-game", isLondon());
+ addMacCDSearchPathsFrom(gameDir.getParent(), "eem-mac-parent", isLondon());
+ }
+
+ // EEM1 installers provide the application resources, and floppy game data.
if (mac && !isLondon() && !SearchMan.hasArchive("eem-installer")) {
const Common::FSNode gameDir(ConfMan.getPath("path"));
- if (Common::Archive *installer = createInstallerArchive(gameDir)) {
+ Common::Archive *installer = createInstallerArchive(gameDir);
+ if (!installer)
+ installer = createInstallerArchive(gameDir.getParent());
+ if (installer) {
SearchMan.add("eem-installer", installer);
debugC(1, kDebugGeneral, "Mounted Eagle Eye Mysteries Mac installer archive");
- } else {
- warning("EEMTEST: createInstallerArchive returned null");
}
}
@@ -985,7 +969,7 @@ bool EEMEngine::loadSitePalettes() {
Common::File f;
// EEM2 DOS uses "SITEPALS." (8.3); EEM1 and both Mac releases use "SITEPALS".
const char *palFile = (isLondon() && !isMacintosh()) ? "SITEPALS." : "SITEPALS";
- if (!f.open(Common::Path(palFile))) {
+ if (!openDataFile(f, Common::Path(palFile))) {
warning("%s missing", palFile);
return false;
}
@@ -1066,10 +1050,13 @@ void EEMEngine::interruptAudio(bool stopMusicToo) {
void EEMEngine::playFlc(const Common::Path &path, bool fadeIn,
bool holdLastFrame) {
Video::FlicDecoder flic;
- if (!flic.loadFile(path)) {
+ Common::ScopedPtr<Common::SeekableReadStream> stream(
+ Common::MacResManager::openFileOrDataFork(path));
+ if (!stream || !flic.loadStream(stream.get())) {
warning("playFlc: %s missing", path.toString().c_str());
return;
}
+ stream.release();
const int fw = flic.getWidth();
const int fh = flic.getHeight();
@@ -1757,7 +1744,7 @@ void EEMEngine::showLondonLogo(uint picId, uint palId, uint holdMs,
}
bool EEMEngine::startLondonTrainingMystery() {
- if (_mystery.load(0, &_rng, isMacintosh())) {
+ if (_mystery.load(0, &_rng, isMacintosh(), isLondon())) {
resetSiteArrivalState();
if (_audio)
_audio->initMysterySounds(0);
@@ -2278,7 +2265,7 @@ Common::Error EEMEngine::loadGameStream(Common::SeekableReadStream *stream) {
if (hasMystery) {
uint16 mysteryNum = 0;
s.syncAsUint16LE(mysteryNum);
- if (!_mystery.load(mysteryNum, &_rng, isMacintosh())) {
+ if (!_mystery.load(mysteryNum, &_rng, isMacintosh(), isLondon())) {
_mystery.clear();
resetSiteArrivalState();
return Common::kReadingFailed;
diff --git a/engines/eem/installer.cpp b/engines/eem/installer.cpp
index a0eeea28b00..3d34a20c233 100644
--- a/engines/eem/installer.cpp
+++ b/engines/eem/installer.cpp
@@ -381,12 +381,14 @@ private:
};
bool InstallerArchive::load(const Common::FSNode &dir) {
- // disk 1 first, then the five continuation segments, concatenated into one
- // logical stream (payload spans cross floppy boundaries).
+ // The CD installer contains only the application; floppy data spans six disks.
+ const bool cd = dir.getChild("Eagle Eye CD Installer").exists();
Common::Array<Common::String> names;
- names.push_back("Eagle Eye Installer");
- for (int i = 2; i <= 6; i++)
- names.push_back(Common::String::format("EEM Install Data %d", i));
+ names.push_back(cd ? "Eagle Eye CD Installer" : "Eagle Eye Installer");
+ if (!cd) {
+ for (int i = 2; i <= 6; i++)
+ names.push_back(Common::String::format("EEM Install Data %d", i));
+ }
_diskBase.resize(names.size() + 1); // 1-based; index 0 unused
for (uint i = 0; i < _diskBase.size(); i++)
diff --git a/engines/eem/installer.h b/engines/eem/installer.h
index 69a983f6c50..4a2b6472e19 100644
--- a/engines/eem/installer.h
+++ b/engines/eem/installer.h
@@ -47,6 +47,9 @@ namespace EEM {
// members; the application and "EEM Sound&Music" resource forks are exposed as
// Mac resource-fork alt streams so Common::MacResManager can read them.
//
+// "Eagle Eye CD Installer" uses the same format for the CD application;
+// its game data and mystery scripts are stored separately on the disc.
+//
// Returns nullptr when the installer files are absent or cannot be parsed.
Common::Archive *createInstallerArchive(const Common::FSNode &dir);
diff --git a/engines/eem/music.cpp b/engines/eem/music.cpp
index 3db172c6ece..aa4674aadc0 100644
--- a/engines/eem/music.cpp
+++ b/engines/eem/music.cpp
@@ -82,7 +82,7 @@ static Common::SeekableReadStream *expandMacMidiRunningStatus(Common::SeekableRe
output.writeUint32BE(size);
if (tag != MKTAG('M', 'T', 'r', 'k')) {
- if (output.writeStream(&stream, size) != size)
+ if (size && output.writeStream(&stream, size) != size)
return nullptr;
continue;
}
@@ -120,7 +120,8 @@ static Common::SeekableReadStream *expandMacMidiRunningStatus(Common::SeekableRe
return nullptr;
}
- if (dataSize > end - stream.pos() || output.writeStream(&stream, dataSize) != dataSize)
+ if (dataSize > end - stream.pos() ||
+ (dataSize && output.writeStream(&stream, dataSize) != dataSize))
return nullptr;
}
WRITE_BE_UINT32(output.getData() + sizeOffset, output.size() - sizeOffset - 4);
@@ -136,8 +137,14 @@ Common::SeekableReadStream *MusicPlayer::getResource(uint16 id, uint32 type) {
static const char *const kMacMusicForks[] = {
"EEM Sound&Music",
"rsrc/EEM Sound&Music",
+ "Eagle Eye Mysteries CD",
+ "rsrc/Eagle Eye Mysteries CD",
+ nullptr
+ };
+ static const char *const kMacLondonMusicForks[] = {
"EEM London CD",
"rsrc/EEM London CD",
+ nullptr
};
static const uint32 kMacMidiTypes[] = {
MKTAG('c', 'm', 'i', 'd'),
@@ -145,9 +152,9 @@ Common::SeekableReadStream *MusicPlayer::getResource(uint16 id, uint32 type) {
MKTAG('M', 'i', 'd', 'i'),
};
- const uint firstFork = _isLondon ? 2 : 0;
- for (uint i = firstFork; i < firstFork + 2; i++) {
- const Common::Path path(kMacMusicForks[i]);
+ const char *const *forks = _isLondon ? kMacLondonMusicForks : kMacMusicForks;
+ for (uint i = 0; forks[i]; i++) {
+ const Common::Path path(forks[i]);
if (type == MKTAG('M', 'I', 'D', 'I') || type == MKTAG('M', 'i', 'd', 'i')) {
for (uint j = 0; j < ARRAYSIZE(kMacMidiTypes); j++) {
Common::SeekableReadStream *stream = openMacResource(path, kMacMidiTypes[j], id);
diff --git a/engines/eem/mystery.cpp b/engines/eem/mystery.cpp
index a2cc7a3a996..45a77fb3ffc 100644
--- a/engines/eem/mystery.cpp
+++ b/engines/eem/mystery.cpp
@@ -27,6 +27,7 @@
#include "eem/detection.h"
#include "eem/mystery.h"
+#include "eem/resource.h"
namespace EEM {
@@ -49,7 +50,7 @@ static bool loadMacMysteryBlob(uint num, Common::Array<byte> &out) {
return false;
Common::File f;
- if (!f.open(Common::Path("MysteryData"))) {
+ if (!openDataFile(f, Common::Path("MysteryData"))) {
warning("Mystery::load: cannot open MysteryData");
return false;
}
@@ -91,7 +92,7 @@ static bool loadLooseMacMysteryBlob(uint num, Common::Array<byte> &out,
for (uint i = 0; i < ARRAYSIZE(kPatterns); i++) {
Common::String candidate = Common::String::format(kPatterns[i], num);
Common::File f;
- if (!f.open(Common::Path(candidate)))
+ if (!openDataFile(f, Common::Path(candidate)))
continue;
const int32 size = f.size();
@@ -229,7 +230,7 @@ static void normalizeMacMystery(Common::Array<byte> &data) {
// as big-endian for Mac.
}
-static void normalizeMacLondonMystery(Common::Array<byte> &data) {
+static void normalizeMacCDMystery(Common::Array<byte> &data) {
if (data.size() <= 0x3e)
return;
@@ -237,13 +238,12 @@ static void normalizeMacLondonMystery(Common::Array<byte> &data) {
for (uint i = 0; i < ARRAYSIZE(section); i++)
section[i] = readBE16(data, i * 2);
- // Loose EEM2 Mac scripts use the DOS London section/count header with
- // big-endian words. The structured regions before the text block are
- // CD-style clue/map/site records; strings at section[4] stay byte-exact.
+ // Mac CD scripts use the DOS CD layout with big-endian words.
+ // Strings at section[4] stay byte-exact.
swapU16Range(data, 0, section[0]);
swapU16Range(data, section[0], section[4]);
- swapU16Range(data, section[5], section[6]); // 2-byte London NoteIndex
+ swapU16Range(data, section[5], section[6]); // NoteIndex
swapU16Range(data, section[6], section[7]); // CD-style GalleryData
swapU16Range(data, section[7], section[8]); // KDTextIndex
swapU16Range(data, section[8], section[9]); // CD-style solved clue block
@@ -267,6 +267,7 @@ void Mystery::clear() {
_isFloppy = false;
_isMacintosh = false;
_isMacintoshLooseScripts = false;
+ _isLondon = false;
_floppySuspectsOff = _floppyHintBlockOff = _floppyNoteIndexOff = 0;
_floppyGalleryOff = _floppyTextOff = _floppyKDTextOff = 0;
_floppySolvedOff = 0;
@@ -292,7 +293,7 @@ void Mystery::clear() {
memset(_siteReturnStack, 0, sizeof(_siteReturnStack));
}
-bool Mystery::load(uint num, Common::RandomSource *rng, bool macintosh) {
+bool Mystery::load(uint num, Common::RandomSource *rng, bool macintosh, bool london) {
Common::String fname = Common::String::format("M%u.BIN", num);
Common::Array<byte> staging;
bool macLooseScripts = false;
@@ -300,7 +301,7 @@ bool Mystery::load(uint num, Common::RandomSource *rng, bool macintosh) {
if (macintosh) {
if (loadLooseMacMysteryBlob(num, staging, fname)) {
macLooseScripts = true;
- normalizeMacLondonMystery(staging);
+ normalizeMacCDMystery(staging);
} else {
if (!loadMacMysteryBlob(num, staging))
return false;
@@ -335,6 +336,7 @@ bool Mystery::load(uint num, Common::RandomSource *rng, bool macintosh) {
_isFloppy = false;
_isMacintosh = false;
_isMacintoshLooseScripts = false;
+ _isLondon = london;
if (macintosh) {
_isMacintosh = true;
@@ -695,7 +697,7 @@ const byte *Mystery::noteIndex() const {
uint16 Mystery::noteIndexCount() const {
if (!isLoaded())
return 0;
- if (_isMacintoshLooseScripts) {
+ if (_isLondon) {
if (_galleryOffset <= _noteOffset)
return 0;
return (uint16)((_galleryOffset - _noteOffset) / 2);
@@ -703,10 +705,10 @@ uint16 Mystery::noteIndexCount() const {
// NoteIndex runs from _noteOffset to start of GalleryData.
// CD entries: 4 bytes (u16 textOff; u16 points).
// Floppy entries: 7 bytes (u16 ?; u16 jakeOff; u16 jennyOff; u8 score)
- const uint endOffset = _isMacintosh ? _textOffset : _galleryOffset;
+ const uint endOffset = usesCompactMacData() ? _textOffset : _galleryOffset;
if (endOffset <= _noteOffset)
return 0;
- const uint stride = _isMacintosh ? 8 : (_isFloppy ? 7 : 4);
+ const uint stride = usesCompactMacData() ? 8 : (_isFloppy ? 7 : 4);
return (uint16)((endOffset - _noteOffset) / stride);
}
@@ -728,10 +730,10 @@ bool Mystery::noteHasNotebookText(uint clueId) const {
if (!ni || clueId >= cnt)
return false;
- if (_isMacintoshLooseScripts)
+ if (_isLondon)
return READ_LE_UINT16(ni + clueId * 2) != 0;
- if (_isMacintosh)
+ if (usesCompactMacData())
return READ_LE_UINT16(ni + clueId * 8) != 0;
if (!_isFloppy)
@@ -853,7 +855,7 @@ const byte *Mystery::solvedClueBlock() const {
}
int Mystery::selectedPoints() const {
- if (_isMacintoshLooseScripts)
+ if (_isLondon)
return 0;
const byte *ni = noteIndex();
@@ -896,7 +898,7 @@ int Mystery::selectedPoints() const {
continue;
// CD NoteIndex entry: 4 bytes = u16 textOff + u16 points (at +2).
// Mac rows are 8 bytes: notebook text, Jake text, Jenny text, points.
- const uint16 pts = _isMacintosh ? READ_LE_UINT16(ni + i * 8 + 6)
+ const uint16 pts = usesCompactMacData() ? READ_LE_UINT16(ni + i * 8 + 6)
: READ_LE_UINT16(ni + i * 4 + 2);
total += (int)(int16)pts;
}
@@ -957,7 +959,7 @@ int Mystery::minCluesRemaining() const {
}
int Mystery::foundPoints() const {
- if (_isMacintoshLooseScripts)
+ if (_isLondon)
return 0;
const byte *ni = noteIndex();
@@ -973,7 +975,7 @@ int Mystery::foundPoints() const {
continue;
const uint16 pts = _isFloppy
? ni[i * 7 + 6]
- : (_isMacintosh ? READ_LE_UINT16(ni + i * 8 + 6)
+ : (usesCompactMacData() ? READ_LE_UINT16(ni + i * 8 + 6)
: READ_LE_UINT16(ni + i * 4 + 2));
scores[scoreCount++] = pts;
}
diff --git a/engines/eem/mystery.h b/engines/eem/mystery.h
index 883f9edbcd6..e9401fb525a 100644
--- a/engines/eem/mystery.h
+++ b/engines/eem/mystery.h
@@ -59,10 +59,9 @@ public:
Mystery() = default;
~Mystery() = default;
- /// Load M<num>.BIN and reset per-mystery state. The Mac release stores
- /// cases in the indexed MysteryData container instead of loose files.
+ /// Load M<num>.BIN, or MysteryData for Mac floppy, and reset mystery state.
bool load(uint num, class Common::RandomSource *rng = nullptr,
- bool macintosh = false);
+ bool macintosh = false, bool london = false);
void clear();
@@ -246,6 +245,7 @@ private:
bool _isFloppy = false;
bool _isMacintosh = false;
bool _isMacintoshLooseScripts = false;
+ bool _isLondon = false;
uint16 _floppySuspectsOff = 0; ///< header[+4] suspects
uint16 _floppyHintBlockOff = 0; ///< header[+6] hint -> clue table
uint16 _floppyNoteIndexOff = 0; ///< header[+8] notes (7B/clue)
diff --git a/engines/eem/resource.cpp b/engines/eem/resource.cpp
index f61492637af..e33f0353283 100644
--- a/engines/eem/resource.cpp
+++ b/engines/eem/resource.cpp
@@ -35,6 +35,10 @@
namespace EEM {
+bool openDataFile(Common::File &file, const Common::Path &path) {
+ return file.open(Common::MacResManager::openFileOrDataFork(path), path.toString());
+}
+
DBDArchive::DBDArchive() {
}
@@ -221,13 +225,13 @@ bool DBDArchive::open(const Common::Path &dbdName, const Common::Path &dbxName,
close();
_bigEndian = bigEndian;
- if (!_dbd.open(dbdName)) {
+ if (!openDataFile(_dbd, dbdName)) {
warning("DBDArchive: cannot open %s", dbdName.toString().c_str());
return false;
}
Common::File dbx;
- if (!dbx.open(dbxName)) {
+ if (!openDataFile(dbx, dbxName)) {
warning("DBDArchive: cannot open %s", dbxName.toString().c_str());
_dbd.close();
return false;
diff --git a/engines/eem/resource.h b/engines/eem/resource.h
index 0e4567926fa..e5c7c5e2a8a 100644
--- a/engines/eem/resource.h
+++ b/engines/eem/resource.h
@@ -35,6 +35,8 @@ class SeekableReadStream;
namespace EEM {
+bool openDataFile(Common::File &file, const Common::Path &path);
+
struct DBEntry {
uint32 offset; ///< Byte offset in the .DBD file.
uint16 compressed; ///< Non-zero = PKWARE DCL ("Implode") packed payload.
diff --git a/engines/eem/ui.cpp b/engines/eem/ui.cpp
index 97e460009d6..dcef8c18198 100644
--- a/engines/eem/ui.cpp
+++ b/engines/eem/ui.cpp
@@ -63,7 +63,7 @@ static bool openNumberedScriptFile(Common::File &f, Common::String &name,
uint patternCount) {
for (uint i = 0; i < patternCount; i++) {
name = Common::String::format(patterns[i], num);
- if (f.open(Common::Path(name)))
+ if (openDataFile(f, Common::Path(name)))
return true;
}
name.clear();
@@ -542,7 +542,7 @@ bool loadMacEndingBlob(uint num, Common::Array<byte> &out, bool &looseScript) {
return false;
Common::File f;
- if (!f.open(Common::Path("MysteryData"))) {
+ if (!openDataFile(f, Common::Path("MysteryData"))) {
warning("doShowEnding: cannot open MysteryData");
return false;
}
@@ -1814,7 +1814,7 @@ void EEMEngine::doSetup() {
if (kNewCaseBtn.contains(mx, my)) {
saveProfile(_playerName);
if (isDemo()) {
- if (_mystery.load(0, &_rng, isMacintosh())) {
+ if (_mystery.load(0, &_rng, isMacintosh(), isLondon())) {
resetSiteArrivalState();
_nextScreen = kScreenInitClues;
} else {
@@ -2391,7 +2391,7 @@ void EEMEngine::doActionScreen() {
}
if (pick == kPickPractice) {
- if (!_mystery.load(0, &_rng, isMacintosh())) {
+ if (!_mystery.load(0, &_rng, isMacintosh(), isLondon())) {
warning("doActionScreen: failed to load practice mystery");
_mystery.clear();
resetSiteArrivalState();
@@ -2643,7 +2643,7 @@ void EEMEngine::doCaseSelection() {
return;
const uint mn = stageLo + selRow;
- if (!_mystery.load(mn, &_rng, isMacintosh())) {
+ if (!_mystery.load(mn, &_rng, isMacintosh(), isLondon())) {
warning("doCaseSelection: failed to load mystery %u", mn);
_mystery.clear();
return;
@@ -2875,7 +2875,7 @@ Common::String EEMEngine::notebookNoteText(uint clueId, const byte *ni,
return parseString(Common::String(p, len),
_playerName, _partner);
}
- if (isMacintosh() && !isLondon() && bufBase) {
+ if (_mystery.usesCompactMacData() && bufBase) {
const uint16 textOff = READ_LE_UINT16(ni + clueId * 8);
if (textOff == 0 || textOff >= mysSz)
return Common::String();
@@ -3570,9 +3570,7 @@ Common::String EEMEngine::accuseNoteText(uint clueId,
(const char *)(ctx.bufBaseNotes + textOff),
_playerName, _partner);
}
- // 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) {
+ if (_mystery.usesCompactMacData() && ctx.bufBaseNotes) {
const uint16 textOff = READ_LE_UINT16(ctx.ni + clueId * 8);
if (textOff == 0 || textOff >= _mystery.dataSize())
return Common::String();
Commit: 60dcce8a22a0be7d8585a770436a8a5e5200845c
https://github.com/scummvm/scummvm/commit/60dcce8a22a0be7d8585a770436a8a5e5200845c
Author: neuromancer (gustavo.grieco at gmail.com)
Date: 2026-09-10T17:45:23+02:00
Commit Message:
EEM: show intro for eem1 talkie (mac)
Changed paths:
engines/eem/audio.cpp
engines/eem/clues.cpp
engines/eem/eem.cpp
engines/eem/eem.h
engines/eem/site.cpp
diff --git a/engines/eem/audio.cpp b/engines/eem/audio.cpp
index aedff299acc..961bc84c0f4 100644
--- a/engines/eem/audio.cpp
+++ b/engines/eem/audio.cpp
@@ -49,6 +49,7 @@ struct MacSndResource {
};
const MacSndResource kMacSndResources[] = {
+ { "JAKE", 8001 }, { "JEN", 8002 },
{ "B-0003SL", 7022 }, { "B-0004SL", 7023 }, { "B-0006SL", 7021 },
{ "DING", 7000 }, { "F-0013SL", 8018 }, { "F-0016SL", 8017 },
{ "F-0061SL", 8015 }, { "F-0067SL", 8016 }, { "F-0140SL", 8020 },
diff --git a/engines/eem/clues.cpp b/engines/eem/clues.cpp
index bf7b09e54a2..78da5855fd7 100644
--- a/engines/eem/clues.cpp
+++ b/engines/eem/clues.cpp
@@ -362,7 +362,7 @@ void EEMEngine::doChoosePartner() {
}
if (_audio && !isDemo()) {
- if (isFloppy() || isMacintosh()) {
+ if (isFloppy() || (isMacintosh() && !isMacCD())) {
// Floppy _DoChoosePartner_Floppy @ 19bb:0a8e
_audio->playFloppyVoiceSlot(0x14, _partner);
} else {
diff --git a/engines/eem/eem.cpp b/engines/eem/eem.cpp
index 4ac0289d266..e3141083f3a 100644
--- a/engines/eem/eem.cpp
+++ b/engines/eem/eem.cpp
@@ -295,7 +295,8 @@ EEMEngine::EEMEngine(OSystem *syst, const ADGameDescription *gameDesc)
Common::String(gameDesc->extra).contains("Floppy"))
_variant = kVariantFloppy;
if (gameDesc && gameDesc->platform == Common::kPlatformMacintosh)
- _variant = kVariantMac;
+ _variant = gameDesc->extra && Common::String(gameDesc->extra).contains("CD")
+ ? kVariantMacCD : kVariantMac;
if (gameDesc && gameDesc->gameId &&
Common::String(gameDesc->gameId) == "eem2")
_variant = kVariantLondonCD;
@@ -1644,6 +1645,64 @@ void EEMEngine::showMacTitleIntro() {
waitIntroDelay(0xFFFFFFFFu);
}
+void EEMEngine::playMacCDIntro() {
+ // CODE 2:5884 uses one-based frame numbers and pauses for each line.
+ static const uint16 kVoiceFrames[] = {
+ 11, 15, 17, 19, 63, 64, 97, 98, 103, 104,
+ 105, 125, 126, 127, 219, 220, 311, 312, 313
+ };
+ const uint32 kFrameDelayMs = 9 * 1000 / 60;
+ Video::FlicDecoder flic;
+ Common::ScopedPtr<Common::SeekableReadStream> stream(
+ Common::MacResManager::openFileOrDataFork(Common::Path("FIN07.FLC")));
+ if (!stream || !flic.loadStream(stream.get())) {
+ warning("Mac CD intro FIN07.FLC failed to load");
+ return;
+ }
+ stream.release();
+
+ if (_audio)
+ _audio->initMysterySounds(60);
+ if (_music)
+ _music->playFile(Common::Path("THEME.XMI"), false);
+
+ uint cue = 0;
+ bool aborted = false;
+ uint32 lastFrameMs = g_system->getMillis();
+ flic.start();
+ while (!flic.endOfVideo() && !shouldQuit() && !aborted && !_skipIntro) {
+ const Graphics::Surface *frame = flic.decodeNextFrame();
+ if (!frame)
+ break;
+ // The Mac player ignores the FLC header's frame delay.
+ const uint32 elapsed = g_system->getMillis() - lastFrameMs;
+ if (waitIntroDelay(elapsed < kFrameDelayMs ? kFrameDelayMs - elapsed : 1))
+ break;
+ g_system->copyRectToScreen(frame->getPixels(), frame->pitch, 0, 0,
+ MIN<int>(frame->w, screenWidth()), MIN<int>(frame->h, screenHeight()));
+ if (flic.hasDirtyPalette())
+ g_system->getPaletteManager()->setPalette(flic.getPalette(), 0, 256);
+ g_system->updateScreen();
+ lastFrameMs = g_system->getMillis();
+
+ const uint frameNumber = flic.getCurFrame() + 1;
+ if (frameNumber == 219 && _music)
+ _music->playFile(Common::Path("THEME.XMI"), false);
+ if (cue < ARRAYSIZE(kVoiceFrames) && frameNumber == kVoiceFrames[cue]) {
+ if (_audio) {
+ _audio->spoolSound(cue);
+ while (_audio->isSpoolPlaying() && !shouldQuit() && !aborted)
+ aborted = waitIntroDelay(10);
+ _audio->stopSpool();
+ }
+ cue++;
+ }
+ }
+ if (_audio)
+ _audio->cleanMysterySounds();
+ fadeCurrentPaletteToBlack();
+}
+
void EEMEngine::runMacStartup() {
CursorMan.showMouse(false);
_skipIntro = false;
@@ -1658,6 +1717,8 @@ void EEMEngine::runMacStartup() {
showMacStillLogo(kPicStormLogo, kPalStormLogo, 3000,
/* playThunder= */ true);
+ if (!shouldQuit() && !_skipIntro && isMacCD())
+ playMacCDIntro();
if (!shouldQuit() && !_skipIntro && _music)
_music->playFile(Common::Path("THEME.XMI"), /* loop= */ true);
if (!shouldQuit() && !_skipIntro)
diff --git a/engines/eem/eem.h b/engines/eem/eem.h
index 1c827f3af39..5fa869d62a1 100644
--- a/engines/eem/eem.h
+++ b/engines/eem/eem.h
@@ -102,6 +102,7 @@ enum Variant {
kVariantFloppy = 1,
kVariantLondonCD = 2,
kVariantMac = 3,
+ kVariantMacCD = 4,
};
/// `_Partner @ 29be:7918`. Selected at the partner-pick screen
@@ -132,6 +133,7 @@ public:
Variant getVariant() const { return _variant; }
bool isFloppy() const { return _variant == kVariantFloppy || isDemo(); }
bool isLondon() const { return _variant == kVariantLondonCD; }
+ bool isMacCD() const { return _variant == kVariantMacCD; }
// London (game) and Macintosh (platform) are orthogonal -- the London CD
// shipped for both DOS and Mac -- so derive Mac-ness from the platform
// rather than the single-valued `_variant` (which can only hold one of
@@ -496,6 +498,7 @@ private:
bool waitIntroDelay(uint32 maxMs);
void runMacStartup();
+ void playMacCDIntro();
void showMacEAKidsLogo();
void showMacStillLogo(uint picId, uint palId, uint holdMs,
bool playThunder);
diff --git a/engines/eem/site.cpp b/engines/eem/site.cpp
index 8bf6a1aa982..b686d9b8b66 100644
--- a/engines/eem/site.cpp
+++ b/engines/eem/site.cpp
@@ -301,6 +301,15 @@ const uint16 kWaitAnims[7][6] = {
{ 0x06, 0x06, 0x06, 0x06, 0x50, 0x50 }, // 6
};
+// EEM1 Mac CD, CODE 7:5318, A5-0x30b2.
+const uint16 kMacWaitAnims[5][6] = {
+ { 0x00, 0x0a, 7, 7, 153, 153 },
+ { 0x03, 0x0c, 7, 7, 153, 153 },
+ { 0x01, 0x0b, 7, 7, 153, 153 },
+ { 0x04, 0x0d, 7, 7, 153, 153 },
+ { 0x02, 0x10, 7, 7, 153, 153 },
+};
+
const uint16 kKdAnimTable[6][6] = {
{ 0x03, 0x0c, 6, 6, 80, 80 }, // 0 â speaker idx 1 wait anim
{ 0x01, 0x0b, 6, 6, 80, 80 }, // 1 â same as PDA idle
@@ -1689,10 +1698,15 @@ bool SiteScreen::partnerIdleAnimParams(uint siteNum, uint16 &animId,
} else {
const uint16 speaker = READ_LE_UINT16(site + 8);
const uint16 (*waitTable)[6] = kWaitAnims;
- if (_vm->isLondon())
+ uint tableSize = ARRAYSIZE(kWaitAnims);
+ if (_vm->isMacCD()) {
+ waitTable = kMacWaitAnims;
+ tableSize = ARRAYSIZE(kMacWaitAnims);
+ } else if (_vm->isLondon()) {
waitTable = _vm->isMacintosh()
? kMacWaitAnimsLondon : kWaitAnimsLondon;
- if (speaker >= ARRAYSIZE(kWaitAnims))
+ }
+ if (speaker >= tableSize)
return false;
animId = waitTable[speaker][0 + partner];
x = (int)(int16)waitTable[speaker][2 + partner];
@@ -2097,11 +2111,13 @@ void SiteScreen::onHotspotClicked(uint siteNum, uint hotIdx) {
// frees the slot and re-activates `WaitHandle`.
bool EEMEngine::loadKdAnim(uint16 num, Animation &anim, int &px, int &py,
uint16 &animId) {
- if (num >= ARRAYSIZE(kKdAnimTable))
+ if (num >= (isMacCD() ? ARRAYSIZE(kMacWaitAnims) - 1 : ARRAYSIZE(kKdAnimTable)))
return false;
const uint16 (*kdTable)[6] = kKdAnimTable;
- if (isLondon()) {
+ if (isMacCD()) {
+ kdTable = kMacWaitAnims + 1;
+ } else if (isLondon()) {
// EEM2 Mac FUN_0000ce6e indexes `_WaitAnims + 1`
// (`lea (-0x2034,A5)`), so KD gestures share the Mac idle anchors
// for speaker rows 1..6.
Commit: 96d27fdf90e4cf4aa381d09215e00ef1b62a4a2a
https://github.com/scummvm/scummvm/commit/96d27fdf90e4cf4aa381d09215e00ef1b62a4a2a
Author: neuromancer (gustavo.grieco at gmail.com)
Date: 2026-09-10T17:45:23+02:00
Commit Message:
EEM: better animations and precise map locations in EEM1 talkie (mac)
Changed paths:
engines/eem/eem.cpp
engines/eem/eem.h
engines/eem/map_ui.cpp
engines/eem/music.cpp
engines/eem/music.h
engines/eem/site.cpp
engines/eem/site.h
diff --git a/engines/eem/eem.cpp b/engines/eem/eem.cpp
index e3141083f3a..ff2ba64a91e 100644
--- a/engines/eem/eem.cpp
+++ b/engines/eem/eem.cpp
@@ -300,7 +300,7 @@ EEMEngine::EEMEngine(OSystem *syst, const ADGameDescription *gameDesc)
if (gameDesc && gameDesc->gameId &&
Common::String(gameDesc->gameId) == "eem2")
_variant = kVariantLondonCD;
- setLondonAnimScripts(isLondon());
+ setAnimScripts(isLondon(), isMacCD());
_language = gameDesc ? gameDesc->language : Common::EN_ANY;
}
@@ -561,7 +561,7 @@ Common::Error EEMEngine::run() {
// _InitMIDI @ 20a2:013a. The demo ships no music. Mac releases use
// Halestorm song and instrument resources instead of loose DOS XMIDI files.
if (!isDemo())
- _music = new MusicPlayer(isFloppy(), isMacintosh(), isLondon());
+ _music = new MusicPlayer(isFloppy(), isMacintosh(), isLondon(), isMacCD());
// _InitDrivers @ 1ff1:0368 (SBDIG.ADV / PASDIG.ADV).
_audio = new AudioPlayer(this);
@@ -2192,6 +2192,35 @@ void EEMEngine::startLondonTravelMusic(uint8 travelKind) {
_music->playMus(track, /* loop= */ false);
}
+void EEMEngine::finishTravelMusic(bool skipped) {
+ // Mac CD fades after the entrance; DOS lets the travel tune finish.
+ if (isMacCD()) {
+ if (_music && _music->isPlaying() && !shouldQuit()) {
+ _music->fadeOut();
+ const uint32 startMs = g_system->getMillis();
+ while (_music->isFading() && !shouldQuit() &&
+ g_system->getMillis() - startMs < 5000) {
+ Common::Event event;
+ while (g_system->getEventManager()->pollEvent(event)) {
+ if (event.type == Common::EVENT_QUIT ||
+ event.type == Common::EVENT_RETURN_TO_LAUNCHER) {
+ stopMusic();
+ return;
+ }
+ }
+ g_system->updateScreen();
+ g_system->delayMillis(10);
+ }
+ }
+ stopMusic();
+ } else if (!isFloppy() && !isLondon()) {
+ if (skipped || shouldQuit())
+ stopMusic();
+ else
+ waitForMusicDone();
+ }
+}
+
void EEMEngine::waitForMusicDone(uint32 maxMs) {
if (!_music)
return;
diff --git a/engines/eem/eem.h b/engines/eem/eem.h
index 5fa869d62a1..741d6dae022 100644
--- a/engines/eem/eem.h
+++ b/engines/eem/eem.h
@@ -578,6 +578,7 @@ public:
/// `_StartTravelMusic @ 20a2:0595`. Picks `MUS%05d.XMI` from
/// `_mystery._siteNumber % 5`, one-shot.
void startTravelMusic();
+ void finishTravelMusic(bool skipped);
/// `_IsMIDIPlaying` spin + `_StopMIDI` cleanup in `_DoSiteLoop`.
void waitForMusicDone(uint32 maxMs = 60000);
diff --git a/engines/eem/map_ui.cpp b/engines/eem/map_ui.cpp
index b91d437f385..3a9c9a71a51 100644
--- a/engines/eem/map_ui.cpp
+++ b/engines/eem/map_ui.cpp
@@ -162,12 +162,12 @@ struct BigMapEntryInfo {
uint16 crime = 0;
};
-bool readBigMapEntryInfo(const byte *entry, bool floppy, bool macintosh,
+bool readBigMapEntryInfo(const byte *entry, const EEMEngine &vm, bool compactMac,
BigMapEntryInfo &out) {
if (!entry)
return false;
- if (macintosh) {
+ if (compactMac) {
out.detailX = READ_LE_UINT16(entry + 0x0);
out.detailY = READ_LE_UINT16(entry + 0x2);
out.buttonId = entry[0x5];
@@ -177,7 +177,7 @@ bool readBigMapEntryInfo(const byte *entry, bool floppy, bool macintosh,
return true;
}
- if (floppy) {
+ if (vm.isFloppy()) {
out.detailX = READ_LE_UINT16(entry + 0x0);
out.detailY = READ_LE_UINT16(entry + 0x2);
out.buttonId = entry[0x4];
@@ -193,6 +193,13 @@ bool readBigMapEntryInfo(const byte *entry, bool floppy, bool macintosh,
out.detailX = READ_LE_UINT16(entry + 0x8);
out.detailY = READ_LE_UINT16(entry + 0xa);
out.crime = READ_LE_UINT16(entry + 0xc);
+ if (vm.isMacCD()) {
+ // Mac CD scales DOS map coordinates and truncates the result (CODE 6).
+ out.overviewX = out.overviewX * kMacScreenWidth / kScreenWidth;
+ out.overviewY = out.overviewY * kMacScreenHeight / kScreenHeight;
+ out.detailX = out.detailX * kMacScreenWidth / kScreenWidth;
+ out.detailY = out.detailY * kMacScreenHeight / kScreenHeight;
+ }
return true;
}
@@ -613,8 +620,6 @@ bool EEMEngine::bigMapTrySelectDetailSite(int mouseX, int mouseY,
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])
@@ -623,7 +628,7 @@ bool EEMEngine::bigMapTrySelectDetailSite(int mouseX, int mouseY,
if (!entry)
continue;
BigMapEntryInfo info;
- if (!readBigMapEntryInfo(entry, floppyMap, macMap, info))
+ if (!readBigMapEntryInfo(entry, *this, _mystery.usesCompactMacData(), info))
continue;
Picture button;
@@ -1257,10 +1262,8 @@ void EEMEngine::drawBigMapOverview(uint32 elapsedMs) {
if (!entry)
continue;
- const bool floppy = _mystery.isLoaded() && isFloppy();
BigMapEntryInfo info;
- if (!readBigMapEntryInfo(entry, floppy,
- mac && _mystery.usesCompactMacData(), info))
+ if (!readBigMapEntryInfo(entry, *this, _mystery.usesCompactMacData(), info))
continue;
const bool isDone = (i < Mystery::kVisitedSiteCap)
&& _mystery._visitedSite[i];
@@ -1343,7 +1346,6 @@ void EEMEngine::drawBigMapDetail(int scrollX, int scrollY,
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.
@@ -1353,8 +1355,7 @@ void EEMEngine::drawBigMapDetail(int scrollX, int scrollY,
if (!entry)
continue;
BigMapEntryInfo info;
- if (!readBigMapEntryInfo(entry, floppyMap,
- mac && _mystery.usesCompactMacData(), info))
+ if (!readBigMapEntryInfo(entry, *this, _mystery.usesCompactMacData(), info))
continue;
Picture button;
if (!_buttonArchive.loadEntry(info.buttonId, button))
diff --git a/engines/eem/music.cpp b/engines/eem/music.cpp
index aa4674aadc0..3413e4ea309 100644
--- a/engines/eem/music.cpp
+++ b/engines/eem/music.cpp
@@ -212,7 +212,9 @@ uint16 macSongResourceIdForFile(const Common::Path &path) {
return kInvalidMacSongResource;
}
-uint16 macSongResourceIdForMus(uint num) {
+uint16 macSongResourceIdForMus(uint num, bool macCD) {
+ if (macCD && num < 5)
+ return 1002 + num;
static const uint16 kTravelTracks[5] = {
1004, 1002, 1003, 1006, 1005 // Travel-6/4/7/1/8
};
@@ -225,8 +227,9 @@ uint16 macSongResourceIdForMus(uint num) {
return kInvalidMacSongResource;
}
-MusicPlayer::MusicPlayer(bool isFloppy, bool isMacintosh, bool isLondon) :
- _isFloppy(isFloppy), _isMacintosh(isMacintosh), _isLondon(isLondon) {
+MusicPlayer::MusicPlayer(bool isFloppy, bool isMacintosh, bool isLondon, bool isMacCD) :
+ _isFloppy(isFloppy), _isMacintosh(isMacintosh), _isLondon(isLondon),
+ _isMacCD(isMacCD) {
if (_isMacintosh)
return;
@@ -301,6 +304,17 @@ bool MusicPlayer::isPlaying() const {
return Audio::MidiPlayer::isPlaying();
}
+void MusicPlayer::fadeOut() {
+ // Mac CD CODE 6:2358 uses Halestorm fade speed 30.
+ if (_macDriver)
+ _macDriver->doCommand(Audio::HalestormDriver::kSongFadeOut, 30);
+}
+
+bool MusicPlayer::isFading() const {
+ return _macDriver && isPlaying() &&
+ _macDriver->doCommand(Audio::HalestormDriver::kSongFadeGetState) > 2;
+}
+
void MusicPlayer::setVolume(int volume) {
if (_isMacintosh) {
_masterVolume = CLIP<int>(volume, 0, Audio::Mixer::kMaxMixerVolume);
@@ -414,10 +428,9 @@ void MusicPlayer::playFile(const Common::Path &xmiPath, bool loop) {
void MusicPlayer::playMus(uint num, bool loop) {
if (_isMacintosh) {
- // London SONG ids are 1000 + the MUS number; EEM1 uses named tracks.
const uint16 resourceId = _isLondon
? (num < kInvalidMacSongResource - 1000 ? 1000 + num : kInvalidMacSongResource)
- : macSongResourceIdForMus(num);
+ : macSongResourceIdForMus(num, _isMacCD);
playMacSongResource(resourceId, loop);
return;
}
diff --git a/engines/eem/music.h b/engines/eem/music.h
index 8aea1e8486c..47edef8d6a1 100644
--- a/engines/eem/music.h
+++ b/engines/eem/music.h
@@ -52,7 +52,8 @@ namespace EEM {
*/
class MusicPlayer : public Audio::MidiPlayer, private Audio::HalestormLoader {
public:
- explicit MusicPlayer(bool isFloppy = false, bool isMacintosh = false, bool isLondon = false);
+ explicit MusicPlayer(bool isFloppy = false, bool isMacintosh = false,
+ bool isLondon = false, bool isMacCD = false);
~MusicPlayer() override;
/// _MIDIPlayFile @ 20a2:024c. loop=true mirrors
@@ -64,6 +65,8 @@ public:
void stop() override;
bool isPlaying() const;
+ void fadeOut();
+ bool isFading() const;
void setVolume(int volume) override;
// WORKAROUND: Miles drivers handle source-channel routing themselves;
@@ -79,6 +82,7 @@ private:
const bool _isFloppy;
const bool _isMacintosh;
const bool _isLondon;
+ const bool _isMacCD;
Common::Array<byte> _xmiData;
Audio::HalestormDriver *_macDriver = nullptr;
};
diff --git a/engines/eem/site.cpp b/engines/eem/site.cpp
index b686d9b8b66..16e4f19bee9 100644
--- a/engines/eem/site.cpp
+++ b/engines/eem/site.cpp
@@ -28,6 +28,7 @@
#include "common/system.h"
#include "common/textconsole.h"
+#include "graphics/cursorman.h"
#include "graphics/paletteman.h"
#include "eem/audio.h"
@@ -274,7 +275,17 @@ void cyclePaletteRangeReverse(uint8 start, uint8 end) {
g_system->getPaletteManager()->setPalette(buf, start, count);
}
-void applyHotspotGlowPalette() {
+void applyHotspotGlowPalette(bool macCD) {
+ if (macCD) {
+ // Mac CD CODE 2:3d5e, including the searched-hotspot color.
+ static const byte kMacGlow[7 * 3] = {
+ 0x54, 0xFC, 0xFC, 0x38, 0xDC, 0xE4, 0x20, 0xBC, 0xD0,
+ 0x0C, 0x9C, 0xBC, 0x20, 0xBC, 0xD0, 0x38, 0xDC, 0xE4,
+ 0x0C, 0x9C, 0xBC
+ };
+ g_system->getPaletteManager()->setPalette(kMacGlow, 0xF8, 7);
+ return;
+ }
static const byte kAntsGlow[6 * 3] = {
0x40, 0x40, 0x00, // F9 â dim
0x80, 0x80, 0x00, // FA
@@ -361,7 +372,7 @@ const uint16 kMacWaitAnimsLondon[7][6] = {
//
// Repeated frames are the original's "frame-hold" mechanism: per-tick
// walk advances exactly one entry, so K repeats hold the frame for
-// K * `kFramePeriodMs` â K * 140 ms (e.g. [0,0,0,0,0,0,0,0,0,2] â
+// K * the frame period (e.g. [0,0,0,0,0,0,0,0,0,2] â
// nine ticks of frame 0, one tick of frame 2 = "blink with long
// idle hold"). Same scripts serve wait anims (looping) and kd-clue
// reactions (state-4 one-shot â see `_PlayAnimation`); state field
@@ -647,6 +658,85 @@ const AnimScriptLong kAnimScriptsLong[] = {
{ 0x36, 60, kScript36 },
};
+// Mac CD animation scripts that differ from DOS (A5-0x4c26).
+const uint8 kMacCDScript1a[] = {
+ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 10, 9,
+ 8, 7, 6, 5, 4, 3, 2, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0
+};
+
+const uint8 kMacCDScript1c[] = {
+ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19,
+ 20
+};
+
+const uint8 kMacCDScript22[] = {
+ 0, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 3, 3, 3, 3, 3, 4, 4, 4, 4,
+ 5, 5, 5, 5, 6, 6, 6, 6, 7, 7, 7, 7, 8, 8, 8, 8, 9, 9, 9, 9,
+ 10, 10, 10, 10, 10, 11, 11, 11, 11, 11, 12, 12, 12, 12, 12, 12, 13, 13, 13, 13,
+ 13, 14, 14, 14, 14, 15, 15, 15, 15, 16, 16, 16, 16, 16, 16, 16, 17, 17, 17, 17,
+ 18, 18, 18, 18, 19, 19, 19, 19, 20, 20, 20, 20, 21, 21, 21, 21, 22, 22, 22, 22,
+ 23, 23, 24, 24, 25, 25, 26, 26, 27, 27, 28, 28, 29, 29, 30, 30, 31, 31, 32, 32,
+ 33, 33, 34, 34, 35, 35, 36, 36, 37, 37, 38, 38, 39, 39, 40, 40, 41, 41, 42, 42,
+ 43, 43, 44, 44, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
+};
+
+const uint8 kMacCDScript25[] = {
+ 0, 1, 2, 3, 4
+};
+
+const uint8 kMacCDScript2a[] = {
+ 0, 1, 2, 2, 3, 4
+};
+
+const uint8 kMacCDScript2c[] = {
+ 0, 1, 2, 3, 4, 5, 0, 6, 7, 8, 9, 10, 11, 12, 13, 13, 13, 13, 13, 13,
+ 13, 13, 14, 14, 14, 14, 14, 14, 14, 13, 13, 13, 13, 13, 13, 15, 16, 17, 18, 19,
+ 20, 21, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0
+};
+
+const uint8 kMacCDScript30[] = {
+ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 16, 16, 16,
+ 16, 16, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33,
+ 34, 16, 16, 16, 16, 16, 16, 16, 16, 16
+};
+
+const uint8 kMacCDScript33[] = {
+ 0, 1, 2, 3, 4, 5, 6, 7
+};
+
+const uint8 kMacCDScript34[] = {
+ 0, 1, 2, 3, 4, 5
+};
+
+const uint8 kMacCDScript35[] = {
+ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16
+};
+
+const uint8 kMacCDScript36[] = {
+ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 1, 2, 3, 4, 5,
+ 6, 7, 8, 9, 10, 11, 12, 13, 14, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4,
+ 3, 2, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
+};
+
+const AnimScriptLong kAnimScriptsMacCD[] = {
+ { 0x1a, ARRAYSIZE(kMacCDScript1a), kMacCDScript1a },
+ { 0x1c, ARRAYSIZE(kMacCDScript1c), kMacCDScript1c },
+ { 0x22, ARRAYSIZE(kMacCDScript22), kMacCDScript22 },
+ { 0x25, ARRAYSIZE(kMacCDScript25), kMacCDScript25 },
+ { 0x2a, ARRAYSIZE(kMacCDScript2a), kMacCDScript2a },
+ { 0x2c, ARRAYSIZE(kMacCDScript2c), kMacCDScript2c },
+ { 0x30, ARRAYSIZE(kMacCDScript30), kMacCDScript30 },
+ { 0x33, ARRAYSIZE(kMacCDScript33), kMacCDScript33 },
+ { 0x34, ARRAYSIZE(kMacCDScript34), kMacCDScript34 },
+ { 0x35, ARRAYSIZE(kMacCDScript35), kMacCDScript35 },
+ { 0x36, ARRAYSIZE(kMacCDScript36), kMacCDScript36 },
+};
+
// `_PatientSequence` and `_ImpatientSequence` are standalone script
// pointers. CD has the data but never calls the switchers; floppy calls
// them from `_DoSiteLoop_Floppy` (via `_Switch2Patient` / `_Switch2Impatient`).
@@ -764,11 +854,13 @@ const AnimScriptLong kAnimScriptsLondonLong[] = {
{ 0x31, 62, kScript31London },
};
-// Set true for the London variant so findAnimScript uses the EEM2 tables.
+// Select the scripts and timing of the original release.
bool g_londonAnimScripts = false;
+bool g_macCDAnimScripts = false;
-void setLondonAnimScripts(bool enabled) {
- g_londonAnimScripts = enabled;
+void setAnimScripts(bool london, bool macCD) {
+ g_londonAnimScripts = london;
+ g_macCDAnimScripts = macCD;
}
struct AnimScriptRef {
@@ -776,6 +868,16 @@ struct AnimScriptRef {
uint16 len;
};
AnimScriptRef findAnimScript(uint16 seqnum) {
+ if (g_macCDAnimScripts) {
+ for (uint i = 0; i < ARRAYSIZE(kAnimScriptsMacCD); i++) {
+ if (kAnimScriptsMacCD[i].seqnum == seqnum) {
+ AnimScriptRef r;
+ r.frames = kAnimScriptsMacCD[i].frames;
+ r.len = kAnimScriptsMacCD[i].len;
+ return r;
+ }
+ }
+ }
if (g_londonAnimScripts) {
for (uint i = 0; i < ARRAYSIZE(kAnimScriptsLondon); i++) {
if (kAnimScriptsLondon[i].seqnum == seqnum) {
@@ -816,20 +918,22 @@ AnimScriptRef findAnimScript(uint16 seqnum) {
return r;
}
-// Original frame period from `_InitFrameCounter @ 1a35:01ae`:
-const uint kFramePeriodMs = 140;
+uint animationFramePeriodMs() {
+ // Mac CD CODE 2:21c4 advances after nine 60 Hz ticks.
+ return g_macCDAnimScripts ? 150 : 140;
+}
uint frameFromScriptAtTick(const uint8 *frames, uint len,
uint numFrames, uint32 tickMs) {
if (!frames || len == 0)
- return numFrames > 0 ? (uint)((tickMs / kFramePeriodMs) % numFrames) : 0;
- const uint scriptIdx = (uint)((tickMs / kFramePeriodMs) % len);
+ return numFrames > 0 ? (uint)((tickMs / animationFramePeriodMs()) % numFrames) : 0;
+ const uint scriptIdx = (uint)((tickMs / animationFramePeriodMs()) % len);
const uint frame = frames[scriptIdx];
return (numFrames > 0) ? MIN<uint>(frame, numFrames - 1) : 0;
}
// Looping path of `_UpdateAnimations`: walk the script one entry per
-// `_CheckFrameRate` tick (`kFramePeriodMs` ~= 140 ms), wrap on 0x80.
+// `_CheckFrameRate` tick, wrap on 0x80.
uint partnerFrameAtTick(uint16 seqnum, uint numFrames, uint32 tickMs) {
const AnimScriptRef s = findAnimScript(seqnum);
return frameFromScriptAtTick(s.frames, s.len, numFrames, tickMs);
@@ -837,7 +941,7 @@ uint partnerFrameAtTick(uint16 seqnum, uint numFrames, uint32 tickMs) {
uint oneShotFrameAtTick(uint16 seqnum, uint numFrames, uint32 tickMs) {
const AnimScriptRef s = findAnimScript(seqnum);
- const uint tick = (uint)(tickMs / kFramePeriodMs);
+ const uint tick = (uint)(tickMs / animationFramePeriodMs());
if (!s.frames || s.len == 0)
return numFrames > 0 ? MIN<uint>(tick, numFrames - 1) : 0;
const uint scriptIdx = MIN<uint>(tick, (uint)s.len - 1);
@@ -848,7 +952,7 @@ uint oneShotFrameAtTick(uint16 seqnum, uint numFrames, uint32 tickMs) {
uint32 oneShotDurationMs(uint16 seqnum, uint numFrames) {
const AnimScriptRef s = findAnimScript(seqnum);
const uint count = (s.frames && s.len) ? (uint)s.len : numFrames;
- return (uint32)count * kFramePeriodMs;
+ return (uint32)count * animationFramePeriodMs();
}
// Play `unfold` once, then loop `waitSeq` forever. Mirrors the
@@ -856,7 +960,7 @@ uint32 oneShotDurationMs(uint16 seqnum, uint numFrames) {
uint oneShotThenLoopFrameAtTick(const uint8 *unfold, uint unfoldLen,
const uint8 *waitSeq, uint waitSeqLen,
uint numFrames, uint32 elapsedMs) {
- const uint tick = elapsedMs / kFramePeriodMs;
+ const uint tick = elapsedMs / animationFramePeriodMs();
const uint frame = (tick < unfoldLen)
? unfold[tick]
: waitSeq[(tick - unfoldLen) % waitSeqLen];
@@ -954,10 +1058,8 @@ void SiteScreen::enter(uint siteNum, bool resetPartnerMood) {
}
_waitPhaseAnchor = g_system->getMillis();
- if (resetPartnerMood) {
+ if (resetPartnerMood)
_partnerWaitMood = kPartnerWaitDefault;
- initImpatienceCounter();
- }
const bool firstVisit = (siteNum < Mystery::kVisitedSiteCap)
&& (_mystery->_visitedSite[siteNum] == 0);
@@ -980,8 +1082,6 @@ void SiteScreen::enter(uint siteNum, bool resetPartnerMood) {
siteNum, approachId);
_vm->doLondonApproach(approachId);
}
- } else if (playArrival) {
- _vm->startTravelMusic();
}
const bool compactSite = _vm->isFloppy() ||
@@ -999,7 +1099,7 @@ void SiteScreen::enter(uint siteNum, bool resetPartnerMood) {
}
_vm->setSitePaletteForSite(sitepic);
- applyHotspotGlowPalette();
+ applyHotspotGlowPalette(_vm->isMacCD());
renderBackground(siteNum);
@@ -1008,17 +1108,24 @@ void SiteScreen::enter(uint siteNum, bool resetPartnerMood) {
renderFloppyDrops(siteNum);
else
renderStaticDrops(siteNum);
- renderAnimatedDrops(siteNum, g_system->getMillis());
+ renderAnimatedDrops(siteNum, _vm->isMacCD() ? _waitPhaseAnchor : g_system->getMillis());
+ if (!london && !_vm->isMacCD())
+ _vm->startTravelMusic();
+ const bool cursorVisible = CursorMan.isVisible();
+ if (_vm->isMacCD())
+ CursorMan.showMouse(false);
const bool skippedArrival = enterSiteAnim();
_vm->markSiteArrivalPlayed(siteNum);
- if (!_vm->isFloppy() && !_vm->isLondon()) {
- if (skippedArrival)
- _vm->stopMusic();
- else
- _vm->waitForMusicDone();
- }
+ _vm->finishTravelMusic(skippedArrival);
+ if (_vm->isMacCD())
+ CursorMan.showMouse(cursorVisible);
+ if (_vm->shouldQuit())
+ return;
renderBackground(siteNum);
}
+ _waitPhaseAnchor = g_system->getMillis();
+ if (resetPartnerMood)
+ initImpatienceCounter();
if (compactSite)
renderFloppyDrops(siteNum);
@@ -1074,7 +1181,8 @@ void SiteScreen::enter(uint siteNum, bool resetPartnerMood) {
}
void SiteScreen::initImpatienceCounter() {
- _impatientDeadlineMs = g_system->getMillis() + kImpatienceDelayMs;
+ _impatientDeadlineMs = g_system->getMillis() +
+ (_vm->isMacCD() ? 30000 : kImpatienceDelayMs);
}
bool SiteScreen::checkImpatienceCounter() {
@@ -1253,7 +1361,7 @@ void SiteScreen::run() {
const uint32 now = g_system->getMillis();
if (_snapshotSite == (int)cur &&
- now - _lastTickMs >= kFramePeriodMs) {
+ now - _lastTickMs >= animationFramePeriodMs()) {
if (checkImpatienceCounter()) {
_partnerWaitMood = kPartnerWaitImpatient;
debugC(1, kDebugSite, "Partner impatience: switched to impatient");
@@ -1269,11 +1377,89 @@ void SiteScreen::run() {
g_system->delayMillis(10);
}
}
-// `_EnterSiteAnim @ 1000:9b21`. Two phases (partner-dependent):
-// Phase 1 â skateboard scroll: anim 6 (Jake) / 0xe (Jenny).
-// Slides from (320-w, 199-h) leftward off-screen.
-// Phase 2 â KD slide-in: anim 7 (Jake) / 0xf (Jenny).
-// Slides from x=-w at y=0x8b/0x8e until x=0.
+
+static bool waitForArrivalFrame(EEMEngine *vm, uint32 deadline, bool allowSkip) {
+ while (!vm->shouldQuit()) {
+ Common::Event event;
+ while (g_system->getEventManager()->pollEvent(event)) {
+ if (event.type == Common::EVENT_QUIT ||
+ event.type == Common::EVENT_RETURN_TO_LAUNCHER)
+ return true;
+ if (allowSkip && (event.type == Common::EVENT_KEYDOWN ||
+ event.type == Common::EVENT_LBUTTONDOWN))
+ return true;
+ }
+ const int32 remaining = deadline - g_system->getMillis();
+ if (remaining <= 0)
+ return false;
+ g_system->delayMillis(MIN<int32>(remaining, 10));
+ }
+ return true;
+}
+
+// Mac CD CODE 2:4e22. Only the skate across is skippable.
+bool SiteScreen::enterMacSiteAnim(const Graphics::ManagedSurface &bg) {
+ const bool jake = _vm->getPartnerIndex() == kPartnerJake;
+ Animation skate, entry;
+ if (!_vm->getAni().loadAnimation(jake ? 6 : 14, skate) || skate.empty() ||
+ !_vm->getAni().loadAnimation(jake ? 7 : 15, entry) || entry.empty())
+ return false;
+
+ const MacSpritePaletteMap paletteMap = getMacSpritePaletteMap();
+ Graphics::ManagedSurface scratch(bg.w, bg.h, bg.format);
+ auto drawFrame = [&](const Picture &frame, int x, int y) {
+ scratch.simpleBlitFrom(bg);
+ blitMacAnimFrameAnchored(scratch.surfacePtr(), frame, x, y, paletteMap);
+ g_system->copyRectToScreen(scratch.getPixels(), scratch.pitch, 0, 0, bg.w, bg.h);
+ g_system->updateScreen();
+ };
+
+ byte palette[256 * 3];
+ g_system->getPaletteManager()->grabPalette(palette, 0, 256);
+ _vm->startTravelMusic();
+ fadePaletteFromBlack(palette);
+
+ int x = 512;
+ const int y = 383 - skate[0].surface.h;
+ uint frame = 0;
+ uint distance = 0;
+ uint tick = 0;
+ bool skipped = false;
+ const uint32 startMs = g_system->getMillis();
+ // The original's fastest-machine path moves four pixels per Mac tick.
+ do {
+ if (waitForArrivalFrame(_vm, startMs + ++tick * 1000 / 60, true)) {
+ skipped = true;
+ break;
+ }
+ x -= 4;
+ drawFrame(skate[frame], x, y);
+ distance += 4;
+ if (distance > 21) {
+ frame = (frame + 1) % skate.size();
+ distance = 0;
+ }
+ } while (x >= 0 || -x <= skate[frame].surface.w);
+
+ g_system->copyRectToScreen(bg.getPixels(), bg.pitch, 0, 0, bg.w, bg.h);
+ g_system->updateScreen();
+ if (waitForArrivalFrame(_vm, g_system->getMillis() + 500, false))
+ return true;
+
+ const int entryY = jake ? 267 : 273;
+ uint32 nextFrameMs = g_system->getMillis();
+ drawFrame(entry[0], 0, entryY);
+ // Frame zero is drawn again on the first advancing tick.
+ for (uint i = 0; i < entry.size(); i++) {
+ nextFrameMs += animationFramePeriodMs();
+ if (waitForArrivalFrame(_vm, nextFrameMs, false))
+ return true;
+ drawFrame(entry[i], 0, entryY);
+ }
+ return skipped;
+}
+
+// DOS _EnterSiteAnim at 172b:2871.
bool SiteScreen::enterSiteAnim() {
if (!_vm || !_mystery)
return false;
@@ -1292,6 +1478,8 @@ bool SiteScreen::enterSiteAnim() {
Graphics::PixelFormat::createFormatCLUT8());
bg.simpleBlitFrom(*screen);
g_system->unlockScreen();
+ if (_vm->isMacCD())
+ return enterMacSiteAnim(bg);
const bool mac = _vm->isMacintosh();
MacSpritePaletteMap macPaletteMap = {0x00, 0xFF};
if (mac)
@@ -1355,7 +1543,7 @@ bool SiteScreen::enterSiteAnim() {
return true;
}
}
- g_system->delayMillis(kFramePeriodMs);
+ g_system->delayMillis(animationFramePeriodMs());
}
return false;
}
@@ -1465,7 +1653,10 @@ void SiteScreen::renderStaticDrops(uint siteNum) {
Picture pic;
if (!_vm->getPics().getPicture(picId, pic))
continue;
- blitMaskedSurface(screen, pic, x, y);
+ if (_vm->isMacCD())
+ blitMacMaskedSurface(screen, pic, x, y);
+ else
+ blitMaskedSurface(screen, pic, x, y);
}
g_system->unlockScreen();
@@ -1514,7 +1705,7 @@ void SiteScreen::renderAnimatedDrops(uint siteNum, uint32 tickMs) {
if (!_mystery || !_vm)
return;
- if (_vm->isMacintosh())
+ if (_vm->isMacintosh() && !_vm->isMacCD())
return;
if (_vm->isFloppy()) {
@@ -1572,9 +1763,13 @@ void SiteScreen::renderAnimatedDrops(uint siteNum, uint32 tickMs) {
Animation anim;
if (!_vm->getAni().loadAnimation((uint)animId, anim) || anim.empty())
continue;
+ const uint32 elapsed = _vm->isMacCD() ? tickMs - _waitPhaseAnchor : tickMs;
const uint frameIdx = partnerFrameAtTick((uint16)animId,
- (uint)anim.size(), tickMs);
- blitAnimFrameAnchored(screen, anim[frameIdx], x, y);
+ (uint)anim.size(), elapsed);
+ if (_vm->isMacCD())
+ blitMacAnimFrameAnchored(screen, anim[frameIdx], x, y);
+ else
+ blitAnimFrameAnchored(screen, anim[frameIdx], x, y);
}
g_system->unlockScreen();
@@ -1585,7 +1780,7 @@ void SiteScreen::scanColorCycles(uint siteNum) {
if (!_mystery)
return;
- if (_vm && _vm->isMacintosh())
+ if (_vm && _vm->isMacintosh() && !_vm->isMacCD())
return;
if (_vm && _vm->isFloppy()) {
@@ -1614,8 +1809,9 @@ void SiteScreen::scanColorCycles(uint siteNum) {
const uint16 startPal = READ_LE_UINT16(site + 0x48 + i * 6 + 2);
const uint16 endPal = READ_LE_UINT16(site + 0x48 + i * 6 + 4);
ColorCycleRange r;
- r.start = (uint8)startPal;
- r.end = (uint8)endPal;
+ // Mac CODE 2:3dd2 takes one-based palette indices.
+ r.start = (uint8)(startPal - (_vm->isMacCD() ? 1 : 0));
+ r.end = (uint8)(endPal - (_vm->isMacCD() ? 1 : 0));
if (r.end > r.start)
_colorCycles.push_back(r);
}
@@ -1625,7 +1821,10 @@ void SiteScreen::applyColorCycles() {
for (uint i = 0; i < _colorCycles.size(); i++) {
cyclePaletteRange(_colorCycles[i].start, _colorCycles[i].end);
}
- cyclePaletteRange(0xF9, 0xFE);
+ if (_vm->isMacCD())
+ cyclePaletteRange(0xF8, 0xFD);
+ else
+ cyclePaletteRange(0xF9, 0xFE);
}
void SiteScreen::captureBgSnapshot() {
@@ -1861,6 +2060,28 @@ byte currentWhitePaletteIndex(byte fallback) {
return fallback;
}
+static void drawMacSiteHotspot(Graphics::Surface *screen, const Common::Rect &rect, bool seen) {
+ byte color = seen ? 0xFE : 0xF8;
+ auto plot = [&](int x, int y) {
+ Common::Rect pixel(x, y, x + 2, y + 2);
+ pixel.clip(Common::Rect(screen->w, screen->h));
+ if (!pixel.isEmpty())
+ screen->fillRect(pixel, color);
+ if (!seen && ++color > 0xFD)
+ color = 0xF8;
+ };
+ int x = rect.left;
+ int y = rect.top + 1;
+ for (; y <= rect.bottom - 3; ++y)
+ plot(x, y);
+ for (; x <= rect.right - 3; ++x)
+ plot(x, y);
+ for (; y >= rect.top + 1; --y)
+ plot(x, y);
+ for (; x >= rect.left; --x)
+ plot(x, y);
+}
+
void SiteScreen::renderHotspots(uint siteNum) {
if (ConfMan.getBool("hide_highlight_boxes"))
return;
@@ -1911,6 +2132,10 @@ void SiteScreen::renderHotspots(uint siteNum) {
seen = seenKey < Mystery::kHotSpotsCap &&
_mystery->_hotSpotsSeen[seenKey];
}
+ if (_vm->isMacCD()) {
+ drawMacSiteHotspot(screen, rect, seen);
+ continue;
+ }
if (seen) {
screen->frameRect(rect, searchedColor);
} else {
@@ -2011,7 +2236,22 @@ void SiteScreen::displayClueAndAutosave(const byte *clueBlock, bool forceSave) {
const bool hasIdle =
partnerIdleAnimParams(_mystery->_siteNumber, idleId, idleX, idleY);
- _vm->setPartnerEraseBg(&_bgSnapshot);
+ Graphics::ManagedSurface clueBg;
+ if (_vm->isMacCD()) {
+ // Keep the site's animated objects behind partner gestures.
+ const uint32 now = g_system->getMillis();
+ restoreBgSnapshot();
+ renderAnimatedDrops(_mystery->_siteNumber, now);
+ renderHotspots(_mystery->_siteNumber);
+ Graphics::Surface *screen = g_system->lockScreen();
+ if (screen) {
+ clueBg.create(screen->w, screen->h, screen->format);
+ clueBg.simpleBlitFrom(*screen);
+ g_system->unlockScreen();
+ }
+ renderPartner(_mystery->_siteNumber, now);
+ }
+ _vm->setPartnerEraseBg(clueBg.empty() ? &_bgSnapshot : &clueBg);
_vm->setPartnerIdleAnim(hasIdle, idleId, idleX, idleY);
_vm->displayClue(clueBlock);
_vm->setPartnerIdleAnim(false, 0, 0, 0);
diff --git a/engines/eem/site.h b/engines/eem/site.h
index 17576110e69..3b7b214f770 100644
--- a/engines/eem/site.h
+++ b/engines/eem/site.h
@@ -36,7 +36,7 @@ class EEMEngine;
class Mystery;
/// partnerFrameAtTick: frame index for `seqnum` at `tickMs`. Walks `kAnimScripts`
-/// at `kFramePeriodMs` per entry; wraps
+/// at the release's frame period per entry; wraps
/// on the script's 0x80 terminator. Falls back to flipbook (`tick % numFrames`)
/// when no script is registered. `numFrames` is the ANI.DBD entry's cell count,
/// used both for the fallback and to clamp script values past the asset.
@@ -48,10 +48,8 @@ uint oneShotFrameAtTick(uint16 seqnum, uint numFrames, uint32 tickMs);
/// Total time for one full play of a one-shot gesture (frame count * period).
uint32 oneShotDurationMs(uint16 seqnum, uint numFrames);
-/// Select the EEM2 ("London") animation-script table inside `findAnimScript`.
-/// EEM2 ships its own `_AnimationSequences`; many partner/KD scripts differ
-/// from EEM1's, so the engine must use the EEM2 sequences for that variant.
-void setLondonAnimScripts(bool enabled);
+void setAnimScripts(bool london, bool macCD);
+uint animationFramePeriodMs();
/// bigMapPartnerFrameAtTick: overview-map partner walk. EEM1 (11-frame anim):
/// count-up 0..8 once, then idle `_BigMapWaitSeq`. London's anim 0x14/0x12 has
@@ -101,8 +99,8 @@ void cyclePaletteRange(uint8 start, uint8 end);
/// (ENDâSTART).
void cyclePaletteRangeReverse(uint8 start, uint8 end);
-/// Load the 6-step yellow marching-ants ramp into palette 0xF9..0xFE
-void applyHotspotGlowPalette();
+/// Load the hotspot palette used by the release.
+void applyHotspotGlowPalette(bool macCD = false);
/// One hotspot (search rectangle) within a site, 14 bytes on disk.
struct Hotspot {
@@ -147,6 +145,7 @@ private:
/// Partner site-arrival sequence
bool enterSiteAnim();
+ bool enterMacSiteAnim(const Graphics::ManagedSurface &bg);
/// renderPartner: persistent in-site partner sprite
void renderPartner(uint siteNum, uint32 tickMs);
Commit: 67c90d9a0b27a9e7375a72326d4d035195e62248
https://github.com/scummvm/scummvm/commit/67c90d9a0b27a9e7375a72326d4d035195e62248
Author: neuromancer (gustavo.grieco at gmail.com)
Date: 2026-09-10T17:45:23+02:00
Commit Message:
EEM: implement TRAVIS scroll bar in EEM1 talkie (mac)
Changed paths:
engines/eem/eem.h
engines/eem/ui.cpp
diff --git a/engines/eem/eem.h b/engines/eem/eem.h
index 741d6dae022..0c6ca8cd8b2 100644
--- a/engines/eem/eem.h
+++ b/engines/eem/eem.h
@@ -45,6 +45,8 @@
namespace EEM {
+class TravisScrollBar;
+
class AudioPlayer;
class MusicPlayer;
@@ -318,7 +320,7 @@ private:
void screenDriver();
/// Re-render helpers for the corresponding `doX()` modal screens.
- void drawNotebookFrame(int &page);
+ void drawNotebookFrame(int &page, TravisScrollBar *scrollBar = nullptr);
/// Resolve a single NoteIndex entry to displayable notebook text.
/// Handles the CD (4-byte) vs floppy (7-byte) entry strides.
@@ -345,6 +347,7 @@ private:
int pageBreaksCap;
int *numPages;
int *page;
+ TravisScrollBar *scrollBar = nullptr;
};
/// One NoteIndex entry as displayable accuse-screen text.
diff --git a/engines/eem/ui.cpp b/engines/eem/ui.cpp
index dcef8c18198..a70c01a7a9d 100644
--- a/engines/eem/ui.cpp
+++ b/engines/eem/ui.cpp
@@ -152,14 +152,17 @@ int scalePdaAnchor(int value, int target, int source) {
void blitPdaPartner(Graphics::ManagedSurface &dst, DBDArchive &aniArchive,
uint8 partner, const PdaPartnerSpec &spec,
- uint32 tickMs, bool mac = false) {
+ uint32 tickMs, bool mac = false, bool macCD = false) {
+ const PdaPartnerSpec nativeSpec { 0x02, 0x02, 0x10,
+ (spec.scriptId == 0x02 && partner == kPartnerJenny) ? 4 : 7, 152 };
+ const PdaPartnerSpec &activeSpec = macCD ? nativeSpec : spec;
Animation ani;
- if (const Picture *fr = partnerFrameFor(aniArchive, partner, spec,
+ if (const Picture *fr = partnerFrameFor(aniArchive, partner, activeSpec,
tickMs, ani)) {
if (mac) {
- const int anchorX =
+ const int anchorX = macCD ? activeSpec.anchorX :
scalePdaAnchor(spec.anchorX, kMacScreenWidth, kScreenWidth);
- const int anchorY =
+ const int anchorY = macCD ? activeSpec.anchorY :
scalePdaAnchor(spec.anchorY, kMacScreenHeight, kScreenHeight);
blitMacAnimFrameAnchored(dst.surfacePtr(), *fr, anchorX,
anchorY);
@@ -281,6 +284,202 @@ 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(413, 336), 34, 28);
+// Mac CD CODE 7: TRAVIS buttons and Control Manager scrollbars.
+constexpr Common::Rect kMacCDPdaMapRect(Common::Point(149, 334), 35, 30);
+constexpr Common::Rect kMacCDPdaNotebookRect(Common::Point(249, 334), 36, 30);
+constexpr Common::Rect kMacCDPdaGalleryRect(Common::Point(327, 334), 35, 30);
+constexpr Common::Rect kMacCDPdaHelpRect(Common::Point(426, 334), 36, 30);
+constexpr Common::Rect kMacCDPdaScrollRect(Common::Point(476, 19), 16, 294);
+constexpr Common::Rect kMacCDSuspectScrollRect(Common::Point(476, 179), 16, 113);
+constexpr Common::Rect kMacCDSuspectTextRect(Common::Point(125, 179), 326, 113);
+constexpr Common::Rect kMacCDAccuseTextRect(Common::Point(126, 52), 346, 259);
+constexpr int kMacCDPdaLineHeight = 16;
+constexpr int kMacCDPdaTextInsetX = 5;
+constexpr int kMacCDPdaTextInsetY = 4;
+
+class TravisScrollBar {
+public:
+ explicit TravisScrollBar(const Common::Rect &rect) : _rect(rect) {}
+
+ void layout(const Common::Array<int> &heights, int viewHeight, int &first) {
+ _tops.clear();
+ _tops.push_back(0);
+ for (uint i = 0; i < heights.size(); ++i)
+ _tops.push_back(_tops.back() + heights[i] + 7);
+ _viewHeight = viewHeight;
+ _last = MAX<int>(0, (int)heights.size() - 1);
+ while (_last > 0 && _tops.back() - _tops[_last - 1] - 7 < _viewHeight)
+ --_last;
+ first = CLIP(first, 0, _last);
+ }
+
+ int end(int first) const {
+ int last = first;
+ while (last + 1 < (int)_tops.size() &&
+ (last == first || _tops[last + 1] - _tops[first] - 7 < _viewHeight))
+ ++last;
+ return last;
+ }
+
+ bool contains(const Common::Point &point) const {
+ return _rect.contains(point);
+ }
+
+ bool handleEvent(const Common::Event &event, int &first) {
+ if (event.type == Common::EVENT_KEYDOWN) {
+ switch (event.kbd.keycode) {
+ case Common::KEYCODE_UP:
+ move(kUp, first);
+ return true;
+ case Common::KEYCODE_DOWN:
+ move(kDown, first);
+ return true;
+ case Common::KEYCODE_LEFT:
+ case Common::KEYCODE_PAGEUP:
+ case Common::KEYCODE_BACKSPACE:
+ move(kPageUp, first);
+ return true;
+ case Common::KEYCODE_RIGHT:
+ case Common::KEYCODE_PAGEDOWN:
+ case Common::KEYCODE_SPACE:
+ move(kPageDown, first);
+ return true;
+ case Common::KEYCODE_HOME:
+ first = 0;
+ return true;
+ case Common::KEYCODE_END:
+ first = _last;
+ return true;
+ default:
+ return false;
+ }
+ }
+ if (event.type == Common::EVENT_WHEELUP || event.type == Common::EVENT_WHEELDOWN) {
+ move(event.type == Common::EVENT_WHEELUP ? kUp : kDown, first);
+ return true;
+ }
+ if (event.type == Common::EVENT_LBUTTONUP && _pressed != kNone) {
+ _pressed = kNone;
+ return true;
+ }
+ if (event.type == Common::EVENT_MOUSEMOVE && _pressed == kThumb) {
+ const int travel = _rect.height() - 3 * _rect.width();
+ const int y = CLIP<int>(event.mouse.y - _dragOffset - _rect.top - _rect.width(), 0, travel);
+ const int target = travel > 0 ? y * _tops[_last] / travel : 0;
+ first = 0;
+ while (first < _last && _tops[first] < target)
+ ++first;
+ return true;
+ }
+ if (event.type != Common::EVENT_LBUTTONDOWN || !contains(event.mouse))
+ return false;
+ if (_last == 0)
+ return true;
+ _pressed = partAt(event.mouse, first);
+ if (_pressed == kThumb)
+ _dragOffset = event.mouse.y - thumbRect(first).top;
+ else
+ move(_pressed, first);
+ _repeatAt = g_system->getMillis() + 400;
+ return true;
+ }
+
+ bool update(int &first) {
+ if (_pressed != kNone && !(g_system->getEventManager()->getButtonState() & Common::EventManager::LBUTTON)) {
+ _pressed = kNone;
+ return true;
+ }
+ if (_pressed == kNone || _pressed == kThumb || g_system->getMillis() < _repeatAt)
+ return false;
+ _repeatAt = g_system->getMillis() + 100;
+ if (partAt(g_system->getEventManager()->getMousePos(), first) != _pressed)
+ return false;
+ const int previous = first;
+ move(_pressed, first);
+ return first != previous;
+ }
+
+ void draw(Graphics::ManagedSurface &surface, int first) const {
+ const MacSpritePaletteMap colors = getMacSpritePaletteMap();
+ surface.fillRect(_rect, colors.white);
+ surface.frameRect(_rect, colors.black);
+ const int size = _rect.width();
+ if (_last > 0) {
+ for (int y = _rect.top + size; y < _rect.bottom - size; ++y) {
+ for (int x = _rect.left + 1; x < _rect.right - 1; ++x) {
+ if ((x + y) & 1)
+ *(byte *)surface.getBasePtr(x, y) = colors.black;
+ }
+ }
+ const Common::Rect thumb = thumbRect(first);
+ surface.fillRect(thumb, colors.white);
+ surface.frameRect(thumb, colors.black);
+ }
+ const byte arrow[] = { 0x08, 0x1c, 0x3e, 0x7f, 0x1c, 0x1c, 0x1c };
+ for (int direction = 0; direction < 2; ++direction) {
+ Common::Rect button(_rect.left, direction ? _rect.bottom - size : _rect.top,
+ _rect.right, direction ? _rect.bottom : _rect.top + size);
+ const bool pressed = _pressed == (direction ? kDown : kUp);
+ surface.fillRect(button, pressed ? colors.black : colors.white);
+ surface.frameRect(button, colors.black);
+ for (int y = 0; y < 7; ++y) {
+ for (int x = 0; x < 7; ++x) {
+ if ((arrow[direction ? 6 - y : y] & (1 << x)) && (_last > 0 || ((x + y) & 1)))
+ *(byte *)surface.getBasePtr(button.left + 4 + x, button.top + 4 + y) =
+ pressed ? colors.white : colors.black;
+ }
+ }
+ }
+ }
+
+private:
+ enum Part { kNone, kUp, kDown, kPageUp, kPageDown, kThumb };
+ Common::Rect _rect;
+ Common::Array<int> _tops;
+ int _viewHeight = 0;
+ int _last = 0;
+ Part _pressed = kNone;
+ int _dragOffset = 0;
+ uint32 _repeatAt = 0;
+
+ Common::Rect thumbRect(int first) const {
+ const int size = _rect.width();
+ const int y = _rect.top + size + (_last > 0 ?
+ (_rect.height() - 3 * size) * _tops[first] / _tops[_last] : 0);
+ return Common::Rect(_rect.left, y, _rect.right, y + size);
+ }
+
+ Part partAt(const Common::Point &point, int first) const {
+ if (!contains(point))
+ return kNone;
+ if (point.y < _rect.top + _rect.width())
+ return kUp;
+ if (point.y >= _rect.bottom - _rect.width())
+ return kDown;
+ const Common::Rect thumb = thumbRect(first);
+ if (point.y < thumb.top)
+ return kPageUp;
+ return point.y >= thumb.bottom ? kPageDown : kThumb;
+ }
+
+ void move(Part part, int &first) const {
+ if (part == kUp)
+ --first;
+ else if (part == kDown)
+ ++first;
+ else if (part == kPageDown)
+ first = end(first);
+ else if (part == kPageUp) {
+ const int bottom = _tops[first];
+ if (first > 0)
+ --first;
+ while (first > 0 && bottom - _tops[first - 1] < _viewHeight)
+ --first;
+ }
+ first = CLIP(first, 0, _last);
+ }
+};
+
constexpr uint16 kProfilePickerRevealPic = 0x105;
constexpr int kProfilePickerRevealX = 0x3e;
constexpr int kProfilePickerRevealY = 0xb3;
@@ -325,6 +524,18 @@ constexpr uint kRestoredContentLastMystery = 0x18;
Common::Rect pdaControlRect(const EEMEngine *vm, const Common::Rect &rect) {
if (!vm || !vm->isMacintosh())
return rect;
+ if (vm->isMacCD()) {
+ if (rect == kPdaNotebookRect)
+ return kMacCDPdaNotebookRect;
+ if (rect == kPdaHelpRect)
+ return kMacCDPdaMapRect;
+ if (rect == kPdaGalleryRect)
+ return kMacCDPdaGalleryRect;
+ if (rect == kPdaHelp2Rect)
+ return kMacCDPdaHelpRect;
+ if (rect == kPdaPageNextRect || rect == kPdaPagePrevRect || rect == kPdaPartnerFootMapRect)
+ return Common::Rect();
+ }
if (rect == kPdaNotebookRect)
return kMacPdaNotebookRect;
if (rect == kPdaHelpRect)
@@ -349,7 +560,7 @@ Common::Rect pdaControlRect(const EEMEngine *vm, const Common::Rect &rect) {
}
int macPdaScrollBarDelta(const EEMEngine *vm, int x, int y) {
- if (!vm || !vm->isMacintosh() || !kMacPdaScrollBarRect.contains(x, y))
+ if (!vm || !vm->isMacintosh() || vm->isMacCD() || !kMacPdaScrollBarRect.contains(x, y))
return 0;
static const Common::Rect kMacPdaScrollBarButtons[] = {
@@ -367,9 +578,124 @@ int macPdaScrollBarDelta(const EEMEngine *vm, int x, int y) {
}
bool macPdaScrollBarAt(const EEMEngine *vm, int x, int y) {
+ if (vm && vm->isMacCD())
+ return kMacCDPdaScrollRect.contains(x, y);
return macPdaScrollBarDelta(vm, x, y) != 0;
}
+bool pdaSiteButtonAt(const EEMEngine *vm, int x, int y) {
+ return pdaControlRect(vm, kPdaSiteRect).contains(x, y) ||
+ (vm && vm->isMacCD() && Common::Rect(0, 0, 106, 384).contains(x, y) &&
+ !kMacPdaPartnerHeadHintRect.contains(x, y));
+}
+
+bool handleMacCDPdaKey(Common::Event &event, const Common::Array<Common::Rect> *items = nullptr,
+ bool includeButtons = true) {
+ if (event.type != Common::EVENT_KEYDOWN)
+ return false;
+ const Common::Point mouse = g_system->getEventManager()->getMousePos();
+ if (event.kbd.keycode == Common::KEYCODE_TAB) {
+ const Common::Rect buttons[] = {
+ kMacCDPdaNotebookRect, kMacCDPdaGalleryRect, kMacPdaAccuseRect,
+ kMacCDPdaHelpRect, kMacCDPdaMapRect, kMacPdaSiteRect,
+ kMacPdaPartnerHeadHintRect, Common::Rect(0, 0, 106, 152)
+ };
+ Common::Array<Common::Rect> targets;
+ if (includeButtons) {
+ for (uint i = 0; i < ARRAYSIZE(buttons); ++i)
+ targets.push_back(buttons[i]);
+ }
+ if (items) {
+ for (uint i = 0; i < items->size(); ++i) {
+ if (!(*items)[i].isEmpty())
+ targets.push_back((*items)[i]);
+ }
+ }
+ if (targets.empty())
+ return true;
+ int current = -1;
+ for (uint i = 0; i < targets.size(); ++i) {
+ if (targets[i].contains(mouse)) {
+ current = i;
+ break;
+ }
+ }
+ const int step = (event.kbd.flags & Common::KBD_SHIFT) ? -1 : 1;
+ const int count = targets.size();
+ const int nextIndex = current < 0 ? (step > 0 ? 0 : count - 1) : (current + step + count) % count;
+ const Common::Rect &next = targets[nextIndex];
+ event.type = Common::EVENT_MOUSEMOVE;
+ event.mouse = Common::Point((next.left + next.right) / 2, (next.top + next.bottom) / 2);
+ g_system->warpMouse(event.mouse.x, event.mouse.y);
+ return false;
+ }
+ if (event.kbd.keycode == Common::KEYCODE_RETURN || event.kbd.keycode == Common::KEYCODE_KP_ENTER) {
+ if (kMacCDPdaScrollRect.contains(mouse))
+ return true;
+ event.type = Common::EVENT_LBUTTONDOWN;
+ event.mouse = mouse;
+ }
+ return false;
+}
+
+bool trackMacCDPdaButton(EEMEngine &vm, Common::Event &event) {
+ if (event.type != Common::EVENT_LBUTTONDOWN)
+ return false;
+ const struct {
+ Common::Rect rect;
+ uint16 picture;
+ Common::Point position;
+ } buttons[] = {
+ { kMacCDPdaMapRect, 0x108, Common::Point(148, 334) },
+ { kMacCDPdaNotebookRect, 0x10c, Common::Point(249, 335) },
+ { kMacCDPdaGalleryRect, 0x10d, Common::Point(325, 335) },
+ { kMacPdaAccuseRect, 0x10a, Common::Point(287, 335) },
+ { kMacCDPdaHelpRect, 0x10b, Common::Point(426, 335) }
+ };
+ for (uint i = 0; i < ARRAYSIZE(buttons); ++i) {
+ if (!buttons[i].rect.contains(event.mouse))
+ continue;
+ Picture picture;
+ if (!vm.getPics().getPicture(buttons[i].picture, picture))
+ return false;
+ Graphics::ManagedSurface background;
+ Graphics::Surface *screen = g_system->lockScreen();
+ background.copyFrom(*screen);
+ g_system->unlockScreen();
+ Graphics::ManagedSurface pressed;
+ pressed.copyFrom(*background.surfacePtr());
+ blitMacMaskedSurface(pressed.surfacePtr(), picture, buttons[i].position.x, buttons[i].position.y);
+ bool inside = true;
+ g_system->copyRectToScreen(pressed.getPixels(), pressed.pitch, 0, 0, pressed.w, pressed.h);
+ g_system->updateScreen();
+ while (!vm.shouldQuit() && (g_system->getEventManager()->getButtonState() & Common::EventManager::LBUTTON)) {
+ Common::Event tracking;
+ while (g_system->getEventManager()->pollEvent(tracking)) {
+ if (tracking.type == Common::EVENT_QUIT || tracking.type == Common::EVENT_RETURN_TO_LAUNCHER) {
+ event = tracking;
+ break;
+ }
+ if (tracking.type == Common::EVENT_LBUTTONUP)
+ break;
+ }
+ if (event.type == Common::EVENT_QUIT || event.type == Common::EVENT_RETURN_TO_LAUNCHER)
+ break;
+ const bool over = buttons[i].rect.contains(g_system->getEventManager()->getMousePos());
+ if (over != inside) {
+ inside = over;
+ const Graphics::ManagedSurface &image = inside ? pressed : background;
+ g_system->copyRectToScreen(image.getPixels(), image.pitch, 0, 0, image.w, image.h);
+ g_system->updateScreen();
+ }
+ g_system->delayMillis(10);
+ }
+ g_system->copyRectToScreen(background.getPixels(), background.pitch, 0, 0, background.w, background.h);
+ g_system->updateScreen();
+ return !inside && event.type == Common::EVENT_LBUTTONDOWN;
+ }
+ return false;
+}
+
bool notebookButtonAt(const EEMEngine *vm, int x, int y) {
return pdaControlRect(vm, kPdaHelpRect).contains(x, y) ||
pdaControlRect(vm, kPdaGalleryRect).contains(x, y) ||
@@ -379,7 +705,7 @@ 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) ||
+ pdaSiteButtonAt(vm, x, y) ||
macPdaScrollBarAt(vm, x, y);
}
@@ -397,7 +723,7 @@ bool galleryButtonAt(int x, int y) {
}
bool galleryButtonAt(const EEMEngine *vm, int x, int y) {
- return pdaControlRect(vm, kPdaSiteRect).contains(x, y) ||
+ return pdaSiteButtonAt(vm, x, y) ||
pdaControlRect(vm, kPdaPartnerFootMapRect).contains(x, y) ||
pdaControlRect(vm, kPdaAccuseRect).contains(x, y) ||
pdaControlRect(vm, kPdaNotebookRect).contains(x, y) ||
@@ -580,6 +906,13 @@ void cycleChooserPalette() {
cyclePaletteRange(kChooserCycleStart, kChooserCycleEnd);
}
+void cycleTravisPalette(bool macCD) {
+ if (macCD)
+ cyclePaletteRange(0xef, 0xf3);
+ else
+ cycleChooserPalette();
+}
+
void blitMaskedPicSlice(Graphics::ManagedSurface &dst, const Picture &pic,
int srcX, int srcY, int w, int h,
int dstX, int dstY) {
@@ -2692,8 +3025,8 @@ void EEMEngine::doNotebook() {
CursorMan.showMouse(true);
int page = 0;
- int hoveredNoteSlot = -1;
- (void)hoveredNoteSlot;
+ TravisScrollBar macScroll(kMacCDPdaScrollRect);
+ TravisScrollBar *scrollBar = isMacCD() ? &macScroll : nullptr;
const bool notebookFromSite = isLondon() && _lastScreen == kScreenSite;
if (_music && _voiceOn && notebookFromSite)
@@ -2701,7 +3034,7 @@ void EEMEngine::doNotebook() {
if (isMacintosh())
setSitePalette(0);
- drawNotebookFrame(page);
+ drawNotebookFrame(page, scrollBar);
Common::Point mouse = g_system->getEventManager()->getMousePos();
setInteractiveMouseCursor(notebookButtonAt(this, mouse.x, mouse.y));
@@ -2723,12 +3056,18 @@ void EEMEngine::doNotebook() {
bool dirty = false;
bool exitFlag = false;
while (g_system->getEventManager()->pollEvent(ev)) {
+ if (isMacCD() && (handleMacCDPdaKey(ev) || trackMacCDPdaButton(*this, ev)))
+ continue;
if (ev.type == Common::EVENT_QUIT ||
ev.type == Common::EVENT_RETURN_TO_LAUNCHER) {
_nextScreen = kScreenInvalid;
exitFlag = true;
break;
}
+ if (scrollBar && scrollBar->handleEvent(ev, page)) {
+ dirty = true;
+ break;
+ }
if (ev.type == Common::EVENT_MOUSEMOVE) {
setInteractiveMouseCursor(notebookButtonAt(this, ev.mouse.x,
ev.mouse.y));
@@ -2751,8 +3090,7 @@ void EEMEngine::doNotebook() {
}
}
if (ev.type == Common::EVENT_LBUTTONDOWN) {
- if (pdaControlRect(this, kPdaSiteRect).contains(ev.mouse.x,
- ev.mouse.y) ||
+ if (pdaSiteButtonAt(this, ev.mouse.x, ev.mouse.y) ||
(isLondon() &&
kPdaLondonCloseRect.contains(ev.mouse.x, ev.mouse.y))) {
_nextScreen = kScreenSite;
@@ -2788,7 +3126,7 @@ void EEMEngine::doNotebook() {
}
if (pdaControlRect(this, kPdaHelpRect).contains(ev.mouse.x,
ev.mouse.y)) {
- if (isLondon()) {
+ if (isLondon() || isMacCD()) {
_nextScreen = kScreenMapAlt;
exitFlag = true;
break;
@@ -2841,16 +3179,18 @@ void EEMEngine::doNotebook() {
break;
const uint32 now = g_system->getMillis();
+ if (scrollBar && scrollBar->update(page))
+ dirty = true;
if (dirty || now - lastDraw >= 100) {
- drawNotebookFrame(page);
+ drawNotebookFrame(page, scrollBar);
lastDraw = now;
mouse = g_system->getEventManager()->getMousePos();
setInteractiveMouseCursor(notebookButtonAt(this, mouse.x,
mouse.y));
}
- if (now - gizmoLastTick >= kChooserCycleMillis) {
+ if (now - gizmoLastTick >= (isMacCD() ? 150 : kChooserCycleMillis)) {
gizmoLastTick = now;
- cycleChooserPalette();
+ cycleTravisPalette(isMacCD());
}
g_system->updateScreen();
g_system->delayMillis(15);
@@ -2892,7 +3232,7 @@ Common::String EEMEngine::notebookNoteText(uint clueId, const byte *ni,
_playerName, _partner);
}
-void EEMEngine::drawNotebookFrame(int &page) {
+void EEMEngine::drawNotebookFrame(int &page, TravisScrollBar *scrollBar) {
const Common::Rect kNotebookRect(78, 12, 288, 152);
const Common::Rect notebookRect =
isMacintosh() ? kMacNotebookTextRect : kNotebookRect;
@@ -2908,7 +3248,7 @@ void EEMEngine::drawNotebookFrame(int &page) {
blitTravisBackground(scratch, frame, isMacintosh());
blitPdaPartner(scratch, _aniArchive, _partner, kPdaNotebookPartner,
- g_system->getMillis(), isMacintosh());
+ g_system->getMillis(), isMacintosh(), isMacCD());
// `_DrawNotes` walks `_NoteIndex` for current page; word-wraps each
// found clue in `_NotebookRect`. Selected = color 0x3c.
@@ -2934,16 +3274,20 @@ void EEMEngine::drawNotebookFrame(int &page) {
const int kRectY = notebookRect.top;
const int kRectW = notebookRect.width();
const int kRectH = notebookRect.height();
+ const int insetX = isMacCD() ? kMacCDPdaTextInsetX : 0;
+ const int insetY = isMacCD() ? kMacCDPdaTextInsetY : 0;
+ const int textWidth = kRectW - insetX;
+ const int lineH = isMacCD() ? kMacCDPdaLineHeight : _font.getFontHeight();
int clueCursor = 0;
Common::Array<int> pageStarts;
+ Common::Array<int> noteHeights;
pageStarts.push_back(0);
const bool floppyNb = isFloppy();
const byte *bufBase = _mystery.blobAt(0);
const uint32 mysSz = _mystery.dataSize();
{
- const int lineH = _font.getFontHeight();
int y = kRectY;
while (clueCursor < (int)found.size()) {
const uint clueId = found[clueCursor];
@@ -2951,8 +3295,9 @@ void EEMEngine::drawNotebookFrame(int &page) {
floppyNb, bufBase, mysSz);
// Measure height by wrapping the text without drawing.
Common::Array<Common::String> wrapped;
- _font.wordWrapText(txt, kRectW, wrapped);
+ _font.wordWrapText(txt, textWidth, wrapped);
const int h = (int)wrapped.size() * lineH;
+ noteHeights.push_back(h);
if (y + h + 7 > kRectY + kRectH) {
// Page break before this clue.
y = kRectY;
@@ -2961,17 +3306,20 @@ void EEMEngine::drawNotebookFrame(int &page) {
y += h + 7;
clueCursor++;
}
- if (page >= (int)pageStarts.size())
+ if (scrollBar)
+ scrollBar->layout(noteHeights, kRectH, page);
+ else if (page >= (int)pageStarts.size())
page = (int)pageStarts.size() - 1;
if (page < 0)
page = 0;
}
- const int startClue = (page < (int)pageStarts.size())
+ const int startClue = scrollBar ? page : (page < (int)pageStarts.size())
? pageStarts[page] : 0;
- const int endClue = (page + 1 < (int)pageStarts.size())
+ const int endClue = scrollBar ? scrollBar->end(page) : (page + 1 < (int)pageStarts.size())
? pageStarts[page + 1] : (int)found.size();
+ Graphics::ManagedSurface textSurface(scratch, notebookRect);
int y = kRectY;
for (int i = startClue; i < endClue; i++) {
const uint clueId = found[i];
@@ -2981,24 +3329,25 @@ void EEMEngine::drawNotebookFrame(int &page) {
txt = Common::String::format(
isSpanish() ? "nota %u" : "clue %u", clueId);
Common::Array<Common::String> wrapped;
- _font.wordWrapText(txt, kRectW, wrapped);
- const int lineH = _font.getFontHeight();
+ _font.wordWrapText(txt, textWidth, wrapped);
const int h = (int)wrapped.size() * lineH;
- const byte color = _mystery._noteSelected[clueId] ? 0x3C : 0x5C;
+ const byte color = !isMacCD() && _mystery._noteSelected[clueId] ? 0x3C : 0x5C;
for (uint li = 0; li < wrapped.size(); li++) {
- _font.drawString(&scratch, wrapped[li], kRectX,
- y + (int)li * lineH, kRectW, color);
+ _font.drawString(&textSurface, wrapped[li], insetX,
+ y - kRectY + insetY + (int)li * lineH, textWidth, color);
}
y += h + 7;
}
const bool isLastPage = (page + 1 >= (int)pageStarts.size());
- if (isLastPage) {
+ if (isLastPage && !isMacCD()) {
const char *kEndMarker = isSpanish()
? "-- Fin de las notas --"
: "-- End of notes --";
_font.drawString(&scratch, kEndMarker, kRectX, y, kRectW, 0x5C);
}
+ if (scrollBar)
+ scrollBar->draw(scratch, page);
g_system->copyRectToScreen(scratch.getPixels(), scratch.pitch,
0, 0, sw, sh);
@@ -3047,6 +3396,8 @@ void EEMEngine::doGallery() {
Common::Event ev;
bool exitFlag = false;
while (g_system->getEventManager()->pollEvent(ev)) {
+ if (isMacCD() && (handleMacCDPdaKey(ev, &slotRects) || trackMacCDPdaButton(*this, ev)))
+ continue;
if (ev.type == Common::EVENT_QUIT ||
ev.type == Common::EVENT_RETURN_TO_LAUNCHER) {
_nextScreen = kScreenInvalid;
@@ -3079,8 +3430,7 @@ void EEMEngine::doGallery() {
// [6] (226,247) â generic exit (0x638)
// [7] ( 7,177) â MAP = NextScreen=2 (0x5f7)
// [8] ( 35,111) â SITE = NextScreen=3 (0x5e4)
- if (pdaControlRect(this, kPdaSiteRect).contains(ev.mouse.x,
- ev.mouse.y)) {
+ if (pdaSiteButtonAt(this, ev.mouse.x, ev.mouse.y)) {
_nextScreen = kScreenSite;
exitFlag = true;
break;
@@ -3105,7 +3455,7 @@ void EEMEngine::doGallery() {
}
if (pdaControlRect(this, kPdaHelpRect).contains(ev.mouse.x,
ev.mouse.y)) {
- if (isLondon()) {
+ if (isLondon() || isMacCD()) {
_nextScreen = kScreenMapAlt;
exitFlag = true;
break;
@@ -3171,9 +3521,9 @@ void EEMEngine::doGallery() {
gallerySlotAt(slotRects, slotSuspect,
mouse.x, mouse.y));
}
- if (now - gizmoLastTick >= kChooserCycleMillis) {
+ if (now - gizmoLastTick >= (isMacCD() ? 150 : kChooserCycleMillis)) {
gizmoLastTick = now;
- cycleChooserPalette();
+ cycleTravisPalette(isMacCD());
}
g_system->updateScreen();
g_system->delayMillis(15);
@@ -3199,25 +3549,50 @@ bool EEMEngine::moreInfo(const byte *gd, uint suspectIdx,
setInteractiveMouseCursor(false);
const Common::Rect noteRectBase(78, 93, 288, 152);
- const Common::Rect noteRect = mac ? scaleRect(noteRectBase) : noteRectBase;
+ const Common::Rect noteRect = isMacCD() ? kMacCDSuspectTextRect :
+ (mac ? scaleRect(noteRectBase) : noteRectBase);
const int rx = noteRect.left;
const int ry = noteRect.top;
const int rw = noteRect.width();
const int rh = noteRect.height();
const int sw = screenWidth();
const int sh = screenHeight();
- const int lineH = _font.getFontHeight();
+ const int lineH = isMacCD() ? kMacCDPdaLineHeight : _font.getFontHeight();
+ const int insetX = isMacCD() ? kMacCDPdaTextInsetX : 0;
+ const int insetY = isMacCD() ? kMacCDPdaTextInsetY : 0;
+ const int textWidth = MAX<int>(8, rw - insetX);
const uint clueMax = compactMI ? clueCount : 30u;
const byte *ni = _mystery.noteIndex();
const uint16 niCount = isLondon()
? (uint16)(_mystery.noteSectionSize() / 2)
: _mystery.noteIndexCount();
- uint pageStart = 0;
- Common::Array<uint> pageStack;
+ int pageStart = 0;
+ Common::Array<int> pageStack;
+ TravisScrollBar macScroll(kMacCDSuspectScrollRect);
+ Common::Array<uint> macClues;
+ if (isMacCD()) {
+ Common::Array<int> heights;
+ for (uint i = 0; i < clueCount && i < clueMax; ++i) {
+ const uint clueId = READ_LE_UINT16(suspect + 0xa + i * 2);
+ if (clueId >= Mystery::kCluesFoundCap || clueId >= niCount || !_mystery._cluesFound[clueId])
+ continue;
+ const Common::String text = notebookNoteText(clueId, ni, niCount, false,
+ _mystery.blobAt(0), _mystery.dataSize());
+ if (text.empty())
+ continue;
+ Common::Array<Common::String> lines;
+ _font.wordWrapText(text, textWidth, lines);
+ heights.push_back(lines.size() * lineH);
+ macClues.push_back(clueId);
+ }
+ macScroll.layout(heights, rh, pageStart);
+ }
+ const uint entryCount = isMacCD() ? macClues.size() : MIN(clueCount, clueMax);
bool back = false;
bool exitGallery = false;
bool isFirstShow = true;
+ uint32 gizmoLastTick = g_system->getMillis();
while (!back && !shouldQuit()) {
Graphics::ManagedSurface ms(sw, sh,
@@ -3227,7 +3602,7 @@ bool EEMEngine::moreInfo(const byte *gd, uint suspectIdx,
blitTravisBackground(ms, galBg, mac);
blitPdaPartner(ms, _aniArchive, _partner, kPdaGalleryPartner,
- g_system->getMillis(), mac);
+ g_system->getMillis(), mac, isMacCD());
Picture detail;
if (_picsArchive.getPicture(detailPic, detail)) {
const Common::Point detailPos =
@@ -3243,11 +3618,13 @@ bool EEMEngine::moreInfo(const byte *gd, uint suspectIdx,
// Walk clues from pageStart; defer overflow to next page unless
// first clue is too tall to ever fit.
int yPos = ry;
+ Graphics::ManagedSurface textSurface(ms, noteRect);
bool drewAny = false;
uint k = pageStart;
bool reachedEnd = false;
- for (; k < clueCount && k < clueMax; k++) {
- const uint16 clueId = compactMI
+ const uint end = isMacCD() ? macScroll.end(pageStart) : entryCount;
+ for (; k < end; k++) {
+ const uint16 clueId = isMacCD() ? macClues[k] : compactMI
? (uint16)suspect[5 + k]
: READ_LE_UINT16(suspect + 0xa + k * 2);
if (!compactMI && clueId == 0xFFFF) {
@@ -3268,27 +3645,27 @@ bool EEMEngine::moreInfo(const byte *gd, uint suspectIdx,
continue;
Common::Array<Common::String> wrapped;
- _font.wordWrapText(txt, MAX<int>(8, rw), wrapped);
+ _font.wordWrapText(txt, textWidth, wrapped);
const int hClue = (int)wrapped.size() * lineH;
if (yPos + hClue > ry + rh && drewAny) {
// Defer to next page.
break;
}
- const byte color = _mystery._noteSelected[clueId] ? 0x3C : 0x5C;
+ const byte color = !isMacCD() && _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);
+ _font.drawString(&textSurface, wrapped[l], insetX,
+ yPos - ry + insetY + (int)l * lineH, textWidth, color);
}
yPos += hClue + 7;
drewAny = true;
}
- if (k >= clueCount || k >= clueMax)
+ if (k >= entryCount)
reachedEnd = true;
const uint pageEnd = k;
const bool hasMore = !reachedEnd;
const bool hasPrev = !pageStack.empty();
- if (pageStart == 0 && !drewAny && _font.isLoaded()) {
+ if (!isMacCD() && pageStart == 0 && !drewAny && _font.isLoaded()) {
_font.drawString(&ms,
isSpanish()
? "Aun no hay pistas para este sospechoso."
@@ -3296,7 +3673,7 @@ bool EEMEngine::moreInfo(const byte *gd, uint suspectIdx,
rx, ry, MAX<int>(8, rw), 0x5C);
}
// Header / footer text.
- if (_font.isLoaded()) {
+ if (!isMacCD() && _font.isLoaded()) {
_font.drawString(&ms,
isSpanish() ? "EXPEDIENTE" : "SUSPECT FILE",
rx, ry - 11, MAX<int>(8, rw), 0x3C);
@@ -3304,6 +3681,8 @@ bool EEMEngine::moreInfo(const byte *gd, uint suspectIdx,
isSpanish() ? "(ESC: volver)" : "(ESC: back)",
rx, ry + rh + 2, MAX<int>(8, rw), 0x3C);
}
+ if (isMacCD())
+ macScroll.draw(ms, pageStart);
g_system->copyRectToScreen(ms.getPixels(), ms.pitch,
0, 0, sw, sh);
g_system->updateScreen();
@@ -3325,10 +3704,12 @@ bool EEMEngine::moreInfo(const byte *gd, uint suspectIdx,
bool advance = false;
bool prev = false;
bool redraw = false;
- uint32 gizmoLastTick = g_system->getMillis();
+ const uint32 lastDraw = g_system->getMillis();
while (!back && !advance && !prev && !redraw && !shouldQuit()) {
Common::Event e2;
while (g_system->getEventManager()->pollEvent(e2)) {
+ if (isMacCD() && (handleMacCDPdaKey(e2) || trackMacCDPdaButton(*this, e2)))
+ continue;
if (e2.type == Common::EVENT_QUIT ||
e2.type == Common::EVENT_RETURN_TO_LAUNCHER) {
_nextScreen = kScreenInvalid;
@@ -3340,17 +3721,26 @@ bool EEMEngine::moreInfo(const byte *gd, uint suspectIdx,
redraw = true;
break;
}
+ if (isMacCD() && macScroll.handleEvent(e2, pageStart)) {
+ 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) ||
+ (isMacCD() && macScroll.contains(e2.mouse)) ||
(scrollDelta < 0 && hasPrev) ||
(scrollDelta > 0 && hasMore));
}
if (e2.type == Common::EVENT_LBUTTONDOWN) {
const int mx = e2.mouse.x;
const int my = e2.mouse.y;
+ if (isMacCD() && pdaSiteButtonAt(this, mx, my)) {
+ _nextScreen = kScreenSite;
+ return true;
+ }
debugC(2, kDebugGfx,
"MoreInfo click (%d,%d) hasMore=%d hasPrev=%d",
mx, my, (int)hasMore, (int)hasPrev);
@@ -3372,7 +3762,7 @@ bool EEMEngine::moreInfo(const byte *gd, uint suspectIdx,
back = true;
break;
}
- if (isLondon() &&
+ if ((isLondon() || isMacCD()) &&
pdaControlRect(this, kPdaHelpRect).contains(mx, my)) {
_nextScreen = kScreenMapAlt;
exitGallery = true;
@@ -3446,10 +3836,12 @@ bool EEMEngine::moreInfo(const byte *gd, uint suspectIdx,
}
const uint32 now = g_system->getMillis();
- if (now - gizmoLastTick >= kChooserCycleMillis) {
+ if (now - gizmoLastTick >= (isMacCD() ? 150 : kChooserCycleMillis)) {
gizmoLastTick = now;
- cycleChooserPalette();
+ cycleTravisPalette(isMacCD());
}
+ if (isMacCD() && (macScroll.update(pageStart) || now - lastDraw >= 150))
+ redraw = true;
g_system->updateScreen();
g_system->delayMillis(20);
}
@@ -3483,7 +3875,7 @@ void EEMEngine::drawGalleryFrame(const byte *gd, uint8 numSuspects,
blitTravisBackground(scratch, galBg, mac);
blitPdaPartner(scratch, _aniArchive, _partner, kPdaGalleryPartner,
- g_system->getMillis(), mac);
+ g_system->getMillis(), mac, isMacCD());
const bool floppy = isFloppy();
const bool compactGallery = floppy || _mystery.usesCompactMacData();
@@ -3512,7 +3904,8 @@ void EEMEngine::drawGalleryFrame(const byte *gd, uint8 numSuspects,
continue;
const int placeX = s.x;
- const int placeY = mac ? s.y : s.y + (0x48 - portrait.surface.h);
+ const int placeY = isMacCD() ? s.y + 138 - portrait.surface.h :
+ (mac ? s.y : s.y + (0x48 - portrait.surface.h));
const int w = MIN<int>(portrait.surface.w, sw - placeX);
const int h = MIN<int>(portrait.surface.h, sh - placeY);
if (w <= 0 || h <= 0)
@@ -3527,7 +3920,7 @@ void EEMEngine::drawGalleryFrame(const byte *gd, uint8 numSuspects,
slotRects[i] = Common::Rect(placeX, placeY,
placeX + w, placeY + h);
slotSuspect[i] = (int)i;
- } else {
+ } else if (!isMacCD()) {
// Undiscovered placeholder â small framed "?" box.
const int phW = mac ? 0x72 : 0x40;
const int phH = mac ? 0x90 : 0x48;
@@ -3585,6 +3978,16 @@ Common::String EEMEngine::accuseNoteText(uint clueId,
}
void EEMEngine::accuseRebuildPagination(const AccuseNotesCtx &ctx) {
+ if (ctx.scrollBar) {
+ Common::Array<int> heights;
+ for (uint i = 0; i < ctx.found->size(); ++i) {
+ Common::Array<Common::String> lines;
+ _font.wordWrapText(accuseNoteText((*ctx.found)[i], ctx), ctx.rectW - kMacCDPdaTextInsetX, lines);
+ heights.push_back(lines.size() * kMacCDPdaLineHeight);
+ }
+ ctx.scrollBar->layout(heights, ctx.rectH, *ctx.page);
+ return;
+ }
*ctx.numPages = 1;
ctx.pageBreaks[0] = 0;
const int lineH = _font.getFontHeight();
@@ -3623,19 +4026,25 @@ void EEMEngine::accuseDrawScreen(const AccuseNotesCtx &ctx) {
if (ctx.haveBg)
blitTravisBackground(scratch, *ctx.accuseBg, mac);
- blitPdaPartner(scratch, _aniArchive, _partner, kPdaGalleryPartner,
- g_system->getMillis(), mac);
+ blitPdaPartner(scratch, _aniArchive, _partner,
+ isMacCD() ? kPdaNotebookPartner : kPdaGalleryPartner,
+ g_system->getMillis(), mac, isMacCD());
Common::Array<Common::Rect> &slotRects = *ctx.slotRects;
Common::Array<uint> &slotClues = *ctx.slotClues;
const Common::Array<uint> &found = *ctx.found;
slotRects.clear();
slotClues.clear();
- const int lineH = _font.getFontHeight();
- const int startIdx = ctx.pageBreaks[*ctx.page];
- const int endIdx = (*ctx.page + 1 < *ctx.numPages)
+ const int lineH = isMacCD() ? kMacCDPdaLineHeight : _font.getFontHeight();
+ const int insetX = isMacCD() ? kMacCDPdaTextInsetX : 0;
+ const int insetY = isMacCD() ? kMacCDPdaTextInsetY : 0;
+ const int textWidth = ctx.rectW - insetX;
+ const int startIdx = ctx.scrollBar ? *ctx.page : ctx.pageBreaks[*ctx.page];
+ const int endIdx = ctx.scrollBar ? ctx.scrollBar->end(*ctx.page) : (*ctx.page + 1 < *ctx.numPages)
? ctx.pageBreaks[*ctx.page + 1]
: (int)found.size();
+ Graphics::ManagedSurface textSurface(scratch, Common::Rect(ctx.rectX, ctx.rectY,
+ ctx.rectX + ctx.rectW, ctx.rectY + ctx.rectH));
int y = ctx.rectY;
uint selectedCount = 0;
for (uint i = 0; i < found.size(); i++) {
@@ -3651,15 +4060,16 @@ void EEMEngine::accuseDrawScreen(const AccuseNotesCtx &ctx) {
txt = Common::String::format(
isSpanish() ? "nota %u" : "clue %u", clueId);
Common::Array<Common::String> wrapped;
- _font.wordWrapText(txt, ctx.rectW, wrapped);
+ _font.wordWrapText(txt, textWidth, wrapped);
const int h = (int)wrapped.size() * lineH;
const byte color = _mystery._noteSelected[clueId] ? 0x3c : 0x01;
for (uint li = 0; li < wrapped.size(); li++) {
- _font.drawString(&scratch, wrapped[li], ctx.rectX,
- y + (int)li * lineH, ctx.rectW, color);
+ _font.drawString(&textSurface, wrapped[li], insetX,
+ y - ctx.rectY + insetY + (int)li * lineH, textWidth, color);
}
slotRects.push_back(Common::Rect(ctx.rectX, y,
- ctx.rectX + ctx.rectW, y + h));
+ ctx.rectX + ctx.rectW - (isMacCD() ? 12 : 0),
+ MIN(y + h + (isMacCD() ? 6 : 0), ctx.rectY + ctx.rectH)));
slotClues.push_back(clueId);
y += h + 7;
}
@@ -3673,14 +4083,20 @@ void EEMEngine::accuseDrawScreen(const AccuseNotesCtx &ctx) {
: (remaining == 1 ? "clue" : "clues");
const Common::String counter =
Common::String::format("%u %s", remaining, clueWord);
- _font.drawString(&scratch, counter, scaleX(209), scaleY(11),
- scaleX(100), 0x0F);
+ if (isMacCD()) {
+ _font.drawString(&scratch, Common::String::format("%u", remaining), 334, 23, 16, 0x23);
+ _font.drawString(&scratch, clueWord, 350, 23, 120, 0x0f);
+ } else {
+ _font.drawString(&scratch, counter, scaleX(209), scaleY(11), scaleX(100), 0x0f);
+ }
- if (*ctx.numPages > 1) {
+ if (!isMacCD() && *ctx.numPages > 1) {
_font.drawString(&scratch,
Common::String::format("p%d/%d", *ctx.page + 1, *ctx.numPages),
ctx.rectX, scaleY(11), scaleX(60), 0x0F);
}
+ if (ctx.scrollBar)
+ ctx.scrollBar->draw(scratch, *ctx.page);
g_system->copyRectToScreen(scratch.getPixels(), scratch.pitch,
0, 0, sw, sh);
@@ -3725,7 +4141,7 @@ bool EEMEngine::doAccuseNotes() {
const Common::Rect noteRectBase(79, 27, 304, 159);
const Common::Rect noteRect =
- isMacintosh() ? scaleRect(noteRectBase) : noteRectBase;
+ isMacCD() ? kMacCDAccuseTextRect : (isMacintosh() ? scaleRect(noteRectBase) : noteRectBase);
const int rectX = noteRect.left;
const int rectY = noteRect.top;
const int rectW = noteRect.width();
@@ -3745,6 +4161,7 @@ bool EEMEngine::doAccuseNotes() {
Common::Array<uint> slotClues;
int page = 0;
+ TravisScrollBar macScroll(kMacCDPdaScrollRect);
int pageBreaks[16];
int numPages = 1;
pageBreaks[0] = 0;
@@ -3771,6 +4188,7 @@ bool EEMEngine::doAccuseNotes() {
ctx.pageBreaksCap = (int)ARRAYSIZE(pageBreaks);
ctx.numPages = &numPages;
ctx.page = &page;
+ ctx.scrollBar = isMacCD() ? &macScroll : nullptr;
accuseRebuildPagination(ctx);
accuseDrawScreen(ctx);
@@ -3779,16 +4197,24 @@ bool EEMEngine::doAccuseNotes() {
pdaControlRect(this, kPdaNotebookRect)
.contains(mouse.x, mouse.y) ||
rectListContains(slotRects, mouse.x, mouse.y));
+ uint32 lastDraw = g_system->getMillis();
+ uint32 gizmoLastTick = lastDraw;
while (!shouldQuit()) {
Common::Event ev;
bool dirty = false;
while (g_system->getEventManager()->pollEvent(ev)) {
+ if (isMacCD() && (handleMacCDPdaKey(ev, &slotRects, false) || trackMacCDPdaButton(*this, ev)))
+ continue;
if (ev.type == Common::EVENT_QUIT ||
ev.type == Common::EVENT_RETURN_TO_LAUNCHER) {
_nextScreen = kScreenInvalid;
return false;
}
+ if (ctx.scrollBar && ctx.scrollBar->handleEvent(ev, page)) {
+ dirty = true;
+ break;
+ }
if (ev.type == Common::EVENT_MOUSEMOVE) {
setInteractiveMouseCursor(
notebookButtonAt(this, ev.mouse.x, ev.mouse.y) ||
@@ -3815,7 +4241,7 @@ bool EEMEngine::doAccuseNotes() {
if (ev.type == Common::EVENT_LBUTTONDOWN) {
const int mx = ev.mouse.x;
const int my = ev.mouse.y;
- if (pdaControlRect(this, kPdaSiteRect).contains(mx, my)) {
+ if (pdaSiteButtonAt(this, mx, my)) {
_nextScreen = kScreenSite;
return false;
}
@@ -3832,7 +4258,7 @@ bool EEMEngine::doAccuseNotes() {
_nextScreen = kScreenGallery;
return false;
}
- if (isLondon() &&
+ if ((isLondon() || isMacCD()) &&
pdaControlRect(this, kPdaHelpRect).contains(mx, my)) {
_nextScreen = kScreenMapAlt;
return false;
@@ -3888,7 +4314,7 @@ bool EEMEngine::doAccuseNotes() {
if (_mystery._noteSelected[found[i]])
selected++;
}
- if (selected == expected)
+ if (isMacCD() || selected == expected)
return true;
continue;
}
@@ -3896,31 +4322,36 @@ bool EEMEngine::doAccuseNotes() {
for (uint i = 0; i < slotRects.size(); i++) {
if (slotRects[i].contains(mx, my)) {
const uint clueId = slotClues[i];
- if (!_mystery._noteSelected[clueId]) {
- uint selected = 0;
- for (uint j = 0; j < found.size(); j++) {
- if (_mystery._noteSelected[found[j]])
- selected++;
- }
- if (selected >= expected)
- break;
+ uint selected = 0;
+ for (uint j = 0; j < found.size(); j++) {
+ if (_mystery._noteSelected[found[j]])
+ selected++;
}
+ if (!_mystery._noteSelected[clueId] && selected >= expected)
+ break;
_mystery._noteSelected[clueId] =
_mystery._noteSelected[clueId] ? 0 : 1;
+ if (isMacCD() && _mystery._noteSelected[clueId] && selected + 1 == expected) {
+ accuseDrawScreen(ctx);
+ return true;
+ }
dirty = true;
break;
}
}
}
}
- if (dirty)
- accuseDrawScreen(ctx);
- static uint32 sLastTick = 0;
+ if (ctx.scrollBar && ctx.scrollBar->update(page))
+ dirty = true;
const uint32 now = g_system->getMillis();
- if (now - sLastTick >= 100) {
- sLastTick = now;
+ if (dirty || now - lastDraw >= 100) {
+ lastDraw = now;
accuseDrawScreen(ctx);
}
+ if (isMacCD() && now - gizmoLastTick >= 150) {
+ gizmoLastTick = now;
+ cycleTravisPalette(true);
+ }
g_system->updateScreen();
g_system->delayMillis(15);
}
@@ -4183,10 +4614,22 @@ void EEMEngine::doAccuse() {
int picked = -1;
uint32 lastTick = g_system->getMillis();
+ uint32 gizmoLastTick = lastTick;
bool dirty = false;
while (picked < 0 && !shouldQuit()) {
Common::Event ev;
while (g_system->getEventManager()->pollEvent(ev)) {
+ if (isMacCD()) {
+ if (ev.type == Common::EVENT_KEYDOWN &&
+ (ev.kbd.keycode == Common::KEYCODE_LEFT || ev.kbd.keycode == Common::KEYCODE_RIGHT)) {
+ ev.kbd.flags = ev.kbd.keycode == Common::KEYCODE_LEFT ? Common::KBD_SHIFT : 0;
+ ev.kbd.keycode = Common::KEYCODE_TAB;
+ }
+ if (handleMacCDPdaKey(ev, &slotRects, false))
+ continue;
+ if (ev.type == Common::EVENT_MOUSEMOVE)
+ setInteractiveMouseCursor(rectListContains(slotRects, ev.mouse.x, ev.mouse.y));
+ }
if (ev.type == Common::EVENT_QUIT ||
ev.type == Common::EVENT_RETURN_TO_LAUNCHER)
return;
@@ -4242,6 +4685,10 @@ void EEMEngine::doAccuse() {
lastTick = now;
dirty = false;
}
+ if (isMacCD() && now - gizmoLastTick >= 150) {
+ gizmoLastTick = now;
+ cycleTravisPalette(true);
+ }
g_system->updateScreen();
g_system->delayMillis(10);
}
@@ -5161,11 +5608,12 @@ void EEMEngine::drawAccuseGallery(uint8 numSuspects, const byte *gd,
Graphics::PixelFormat::createFormatCLUT8());
scratch.clear();
if (haveAccuseBg)
- scratch.simpleBlitFrom(accuseBg.surface);
+ blitTravisBackground(scratch, accuseBg, mac);
// Partner drawn first; defensive (no slot overlap).
- blitPdaPartner(scratch, _aniArchive, _partner, kPdaGalleryPartner,
- g_system->getMillis(), mac);
+ blitPdaPartner(scratch, _aniArchive, _partner,
+ isMacCD() ? kPdaNotebookPartner : kPdaGalleryPartner,
+ g_system->getMillis(), mac, isMacCD());
const GallerySlot * const slots = mac ? kMacGallerySlots : kGallerySlots;
for (uint i = 0; i < numSuspects && i < Mystery::kGalleryCap; i++) {
@@ -5193,7 +5641,8 @@ void EEMEngine::drawAccuseGallery(uint8 numSuspects, const byte *gd,
continue;
const int placeX = s.x;
- const int placeY = mac ? s.y : s.y + (0x48 - portrait.surface.h);
+ const int placeY = isMacCD() ? s.y + 138 - portrait.surface.h :
+ (mac ? s.y : s.y + (0x48 - portrait.surface.h));
const int w = MIN<int>(portrait.surface.w, sw - placeX);
const int h = MIN<int>(portrait.surface.h, sh - placeY);
if (w <= 0 || h <= 0)
@@ -5211,7 +5660,7 @@ void EEMEngine::drawAccuseGallery(uint8 numSuspects, const byte *gd,
}
// Highlight outline (original uses `_PutMouseInRect` @ 1df2:0b8e).
- if (highlighted >= 0 && highlighted < (int)slotRects.size() &&
+ if (!isMacCD() && highlighted >= 0 && highlighted < (int)slotRects.size() &&
!slotRects[highlighted].isEmpty()) {
Common::Rect r = slotRects[highlighted];
r.grow(1);
Commit: be891235d79da1794d582555aef45853886bf08f
https://github.com/scummvm/scummvm/commit/be891235d79da1794d582555aef45853886bf08f
Author: neuromancer (gustavo.grieco at gmail.com)
Date: 2026-09-10T17:45:23+02:00
Commit Message:
EEM: extend TRAVIS scroll bar code for EEM2 (mac)
Changed paths:
engines/eem/ui.cpp
diff --git a/engines/eem/ui.cpp b/engines/eem/ui.cpp
index a70c01a7a9d..99375d64ace 100644
--- a/engines/eem/ui.cpp
+++ b/engines/eem/ui.cpp
@@ -271,6 +271,7 @@ constexpr Common::Rect kPdaLondonCloseRect(Common::Point(0, 0), 66, 79);
// 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 kMacLondonNotebookTextRect(Common::Point(125, 23), 331, 274);
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);
@@ -289,13 +290,17 @@ constexpr Common::Rect kMacCDPdaMapRect(Common::Point(149, 334), 35, 30);
constexpr Common::Rect kMacCDPdaNotebookRect(Common::Point(249, 334), 36, 30);
constexpr Common::Rect kMacCDPdaGalleryRect(Common::Point(327, 334), 35, 30);
constexpr Common::Rect kMacCDPdaHelpRect(Common::Point(426, 334), 36, 30);
-constexpr Common::Rect kMacCDPdaScrollRect(Common::Point(476, 19), 16, 294);
-constexpr Common::Rect kMacCDSuspectScrollRect(Common::Point(476, 179), 16, 113);
-constexpr Common::Rect kMacCDSuspectTextRect(Common::Point(125, 179), 326, 113);
-constexpr Common::Rect kMacCDAccuseTextRect(Common::Point(126, 52), 346, 259);
-constexpr int kMacCDPdaLineHeight = 16;
-constexpr int kMacCDPdaTextInsetX = 5;
-constexpr int kMacCDPdaTextInsetY = 4;
+constexpr Common::Rect kMacTravisScrollRect(Common::Point(476, 19), 16, 294);
+constexpr Common::Rect kMacTravisSuspectScrollRect(Common::Point(476, 179), 16, 113);
+constexpr Common::Rect kMacTravisSuspectTextRect(Common::Point(125, 179), 326, 113);
+constexpr Common::Rect kMacTravisAccuseTextRect(Common::Point(126, 52), 346, 259);
+constexpr int kMacTravisLineHeight = 16;
+constexpr int kMacTravisTextInsetX = 5;
+constexpr int kMacTravisTextInsetY = 4;
+
+bool usesTravisScrollBar(const EEMEngine *vm) {
+ return vm && (vm->isMacCD() || (vm->isMacintosh() && vm->isLondon()));
+}
class TravisScrollBar {
public:
@@ -524,6 +529,8 @@ constexpr uint kRestoredContentLastMystery = 0x18;
Common::Rect pdaControlRect(const EEMEngine *vm, const Common::Rect &rect) {
if (!vm || !vm->isMacintosh())
return rect;
+ if (usesTravisScrollBar(vm) && (rect == kPdaPageNextRect || rect == kPdaPagePrevRect))
+ return Common::Rect();
if (vm->isMacCD()) {
if (rect == kPdaNotebookRect)
return kMacCDPdaNotebookRect;
@@ -533,7 +540,7 @@ Common::Rect pdaControlRect(const EEMEngine *vm, const Common::Rect &rect) {
return kMacCDPdaGalleryRect;
if (rect == kPdaHelp2Rect)
return kMacCDPdaHelpRect;
- if (rect == kPdaPageNextRect || rect == kPdaPagePrevRect || rect == kPdaPartnerFootMapRect)
+ if (rect == kPdaPartnerFootMapRect)
return Common::Rect();
}
if (rect == kPdaNotebookRect)
@@ -560,7 +567,7 @@ Common::Rect pdaControlRect(const EEMEngine *vm, const Common::Rect &rect) {
}
int macPdaScrollBarDelta(const EEMEngine *vm, int x, int y) {
- if (!vm || !vm->isMacintosh() || vm->isMacCD() || !kMacPdaScrollBarRect.contains(x, y))
+ if (!vm || !vm->isMacintosh() || usesTravisScrollBar(vm) || !kMacPdaScrollBarRect.contains(x, y))
return 0;
static const Common::Rect kMacPdaScrollBarButtons[] = {
@@ -578,8 +585,8 @@ int macPdaScrollBarDelta(const EEMEngine *vm, int x, int y) {
}
bool macPdaScrollBarAt(const EEMEngine *vm, int x, int y) {
- if (vm && vm->isMacCD())
- return kMacCDPdaScrollRect.contains(x, y);
+ if (usesTravisScrollBar(vm))
+ return kMacTravisScrollRect.contains(x, y);
return macPdaScrollBarDelta(vm, x, y) != 0;
}
@@ -589,16 +596,18 @@ bool pdaSiteButtonAt(const EEMEngine *vm, int x, int y) {
!kMacPdaPartnerHeadHintRect.contains(x, y));
}
-bool handleMacCDPdaKey(Common::Event &event, const Common::Array<Common::Rect> *items = nullptr,
- bool includeButtons = true) {
+bool handleMacTravisKey(const EEMEngine &vm, Common::Event &event,
+ const Common::Array<Common::Rect> *items = nullptr, bool includeButtons = true) {
if (event.type != Common::EVENT_KEYDOWN)
return false;
const Common::Point mouse = g_system->getEventManager()->getMousePos();
if (event.kbd.keycode == Common::KEYCODE_TAB) {
const Common::Rect buttons[] = {
- kMacCDPdaNotebookRect, kMacCDPdaGalleryRect, kMacPdaAccuseRect,
- kMacCDPdaHelpRect, kMacCDPdaMapRect, kMacPdaSiteRect,
- kMacPdaPartnerHeadHintRect, Common::Rect(0, 0, 106, 152)
+ pdaControlRect(&vm, kPdaNotebookRect), pdaControlRect(&vm, kPdaGalleryRect),
+ pdaControlRect(&vm, kPdaAccuseRect), pdaControlRect(&vm, kPdaHelp2Rect),
+ pdaControlRect(&vm, kPdaHelpRect), pdaControlRect(&vm, kPdaSiteRect),
+ pdaControlRect(&vm, kPdaPartnerHeadHintRect),
+ vm.isMacCD() ? Common::Rect(0, 0, 106, 152) : kMacPdaPartnerFootMapRect
};
Common::Array<Common::Rect> targets;
if (includeButtons) {
@@ -630,7 +639,7 @@ bool handleMacCDPdaKey(Common::Event &event, const Common::Array<Common::Rect> *
return false;
}
if (event.kbd.keycode == Common::KEYCODE_RETURN || event.kbd.keycode == Common::KEYCODE_KP_ENTER) {
- if (kMacCDPdaScrollRect.contains(mouse))
+ if (kMacTravisScrollRect.contains(mouse))
return true;
event.type = Common::EVENT_LBUTTONDOWN;
event.mouse = mouse;
@@ -3025,8 +3034,8 @@ void EEMEngine::doNotebook() {
CursorMan.showMouse(true);
int page = 0;
- TravisScrollBar macScroll(kMacCDPdaScrollRect);
- TravisScrollBar *scrollBar = isMacCD() ? &macScroll : nullptr;
+ TravisScrollBar macScroll(kMacTravisScrollRect);
+ TravisScrollBar *scrollBar = usesTravisScrollBar(this) ? &macScroll : nullptr;
const bool notebookFromSite = isLondon() && _lastScreen == kScreenSite;
if (_music && _voiceOn && notebookFromSite)
@@ -3056,7 +3065,8 @@ void EEMEngine::doNotebook() {
bool dirty = false;
bool exitFlag = false;
while (g_system->getEventManager()->pollEvent(ev)) {
- if (isMacCD() && (handleMacCDPdaKey(ev) || trackMacCDPdaButton(*this, ev)))
+ if ((scrollBar && handleMacTravisKey(*this, ev)) ||
+ (isMacCD() && trackMacCDPdaButton(*this, ev)))
continue;
if (ev.type == Common::EVENT_QUIT ||
ev.type == Common::EVENT_RETURN_TO_LAUNCHER) {
@@ -3235,7 +3245,7 @@ Common::String EEMEngine::notebookNoteText(uint clueId, const byte *ni,
void EEMEngine::drawNotebookFrame(int &page, TravisScrollBar *scrollBar) {
const Common::Rect kNotebookRect(78, 12, 288, 152);
const Common::Rect notebookRect =
- isMacintosh() ? kMacNotebookTextRect : kNotebookRect;
+ isMacintosh() ? (isLondon() ? kMacLondonNotebookTextRect : kMacNotebookTextRect) : kNotebookRect;
const int sw = screenWidth();
const int sh = screenHeight();
@@ -3274,10 +3284,10 @@ void EEMEngine::drawNotebookFrame(int &page, TravisScrollBar *scrollBar) {
const int kRectY = notebookRect.top;
const int kRectW = notebookRect.width();
const int kRectH = notebookRect.height();
- const int insetX = isMacCD() ? kMacCDPdaTextInsetX : 0;
- const int insetY = isMacCD() ? kMacCDPdaTextInsetY : 0;
+ const int insetX = scrollBar ? kMacTravisTextInsetX : 0;
+ const int insetY = scrollBar ? kMacTravisTextInsetY : 0;
const int textWidth = kRectW - insetX;
- const int lineH = isMacCD() ? kMacCDPdaLineHeight : _font.getFontHeight();
+ const int lineH = scrollBar ? kMacTravisLineHeight : _font.getFontHeight();
int clueCursor = 0;
Common::Array<int> pageStarts;
@@ -3331,7 +3341,7 @@ void EEMEngine::drawNotebookFrame(int &page, TravisScrollBar *scrollBar) {
Common::Array<Common::String> wrapped;
_font.wordWrapText(txt, textWidth, wrapped);
const int h = (int)wrapped.size() * lineH;
- const byte color = !isMacCD() && _mystery._noteSelected[clueId] ? 0x3C : 0x5C;
+ const byte color = !scrollBar && _mystery._noteSelected[clueId] ? 0x3C : 0x5C;
for (uint li = 0; li < wrapped.size(); li++) {
_font.drawString(&textSurface, wrapped[li], insetX,
y - kRectY + insetY + (int)li * lineH, textWidth, color);
@@ -3340,7 +3350,7 @@ void EEMEngine::drawNotebookFrame(int &page, TravisScrollBar *scrollBar) {
}
const bool isLastPage = (page + 1 >= (int)pageStarts.size());
- if (isLastPage && !isMacCD()) {
+ if (isLastPage && !scrollBar) {
const char *kEndMarker = isSpanish()
? "-- Fin de las notas --"
: "-- End of notes --";
@@ -3396,7 +3406,7 @@ void EEMEngine::doGallery() {
Common::Event ev;
bool exitFlag = false;
while (g_system->getEventManager()->pollEvent(ev)) {
- if (isMacCD() && (handleMacCDPdaKey(ev, &slotRects) || trackMacCDPdaButton(*this, ev)))
+ if (isMacCD() && (handleMacTravisKey(*this, ev, &slotRects) || trackMacCDPdaButton(*this, ev)))
continue;
if (ev.type == Common::EVENT_QUIT ||
ev.type == Common::EVENT_RETURN_TO_LAUNCHER) {
@@ -3535,6 +3545,7 @@ bool EEMEngine::moreInfo(const byte *gd, uint suspectIdx,
const Picture &galBg, bool haveBg) {
const bool floppyMI = isFloppy();
const bool mac = isMacintosh();
+ const bool useScrollBar = usesTravisScrollBar(this);
const bool compactMI = floppyMI || _mystery.usesCompactMacData();
const byte *suspect = compactMI
? _mystery.floppySuspectEntry(suspectIdx)
@@ -3549,7 +3560,7 @@ bool EEMEngine::moreInfo(const byte *gd, uint suspectIdx,
setInteractiveMouseCursor(false);
const Common::Rect noteRectBase(78, 93, 288, 152);
- const Common::Rect noteRect = isMacCD() ? kMacCDSuspectTextRect :
+ const Common::Rect noteRect = useScrollBar ? kMacTravisSuspectTextRect :
(mac ? scaleRect(noteRectBase) : noteRectBase);
const int rx = noteRect.left;
const int ry = noteRect.top;
@@ -3557,9 +3568,9 @@ bool EEMEngine::moreInfo(const byte *gd, uint suspectIdx,
const int rh = noteRect.height();
const int sw = screenWidth();
const int sh = screenHeight();
- const int lineH = isMacCD() ? kMacCDPdaLineHeight : _font.getFontHeight();
- const int insetX = isMacCD() ? kMacCDPdaTextInsetX : 0;
- const int insetY = isMacCD() ? kMacCDPdaTextInsetY : 0;
+ const int lineH = useScrollBar ? kMacTravisLineHeight : _font.getFontHeight();
+ const int insetX = useScrollBar ? kMacTravisTextInsetX : 0;
+ const int insetY = useScrollBar ? kMacTravisTextInsetY : 0;
const int textWidth = MAX<int>(8, rw - insetX);
const uint clueMax = compactMI ? clueCount : 30u;
const byte *ni = _mystery.noteIndex();
@@ -3569,12 +3580,12 @@ bool EEMEngine::moreInfo(const byte *gd, uint suspectIdx,
int pageStart = 0;
Common::Array<int> pageStack;
- TravisScrollBar macScroll(kMacCDSuspectScrollRect);
+ TravisScrollBar macScroll(kMacTravisSuspectScrollRect);
Common::Array<uint> macClues;
- if (isMacCD()) {
+ if (useScrollBar) {
Common::Array<int> heights;
for (uint i = 0; i < clueCount && i < clueMax; ++i) {
- const uint clueId = READ_LE_UINT16(suspect + 0xa + i * 2);
+ const uint clueId = compactMI ? suspect[5 + i] : READ_LE_UINT16(suspect + 0xa + i * 2);
if (clueId >= Mystery::kCluesFoundCap || clueId >= niCount || !_mystery._cluesFound[clueId])
continue;
const Common::String text = notebookNoteText(clueId, ni, niCount, false,
@@ -3588,7 +3599,7 @@ bool EEMEngine::moreInfo(const byte *gd, uint suspectIdx,
}
macScroll.layout(heights, rh, pageStart);
}
- const uint entryCount = isMacCD() ? macClues.size() : MIN(clueCount, clueMax);
+ const uint entryCount = useScrollBar ? macClues.size() : MIN(clueCount, clueMax);
bool back = false;
bool exitGallery = false;
bool isFirstShow = true;
@@ -3622,9 +3633,9 @@ bool EEMEngine::moreInfo(const byte *gd, uint suspectIdx,
bool drewAny = false;
uint k = pageStart;
bool reachedEnd = false;
- const uint end = isMacCD() ? macScroll.end(pageStart) : entryCount;
+ const uint end = useScrollBar ? macScroll.end(pageStart) : entryCount;
for (; k < end; k++) {
- const uint16 clueId = isMacCD() ? macClues[k] : compactMI
+ const uint16 clueId = useScrollBar ? macClues[k] : compactMI
? (uint16)suspect[5 + k]
: READ_LE_UINT16(suspect + 0xa + k * 2);
if (!compactMI && clueId == 0xFFFF) {
@@ -3651,7 +3662,7 @@ bool EEMEngine::moreInfo(const byte *gd, uint suspectIdx,
// Defer to next page.
break;
}
- const byte color = !isMacCD() && _mystery._noteSelected[clueId] ? 0x3C : 0x5C;
+ const byte color = !useScrollBar && _mystery._noteSelected[clueId] ? 0x3C : 0x5C;
for (uint l = 0; l < wrapped.size(); l++) {
_font.drawString(&textSurface, wrapped[l], insetX,
yPos - ry + insetY + (int)l * lineH, textWidth, color);
@@ -3665,7 +3676,7 @@ bool EEMEngine::moreInfo(const byte *gd, uint suspectIdx,
const bool hasMore = !reachedEnd;
const bool hasPrev = !pageStack.empty();
- if (!isMacCD() && pageStart == 0 && !drewAny && _font.isLoaded()) {
+ if (!useScrollBar && pageStart == 0 && !drewAny && _font.isLoaded()) {
_font.drawString(&ms,
isSpanish()
? "Aun no hay pistas para este sospechoso."
@@ -3673,7 +3684,7 @@ bool EEMEngine::moreInfo(const byte *gd, uint suspectIdx,
rx, ry, MAX<int>(8, rw), 0x5C);
}
// Header / footer text.
- if (!isMacCD() && _font.isLoaded()) {
+ if (!useScrollBar && _font.isLoaded()) {
_font.drawString(&ms,
isSpanish() ? "EXPEDIENTE" : "SUSPECT FILE",
rx, ry - 11, MAX<int>(8, rw), 0x3C);
@@ -3681,7 +3692,7 @@ bool EEMEngine::moreInfo(const byte *gd, uint suspectIdx,
isSpanish() ? "(ESC: volver)" : "(ESC: back)",
rx, ry + rh + 2, MAX<int>(8, rw), 0x3C);
}
- if (isMacCD())
+ if (useScrollBar)
macScroll.draw(ms, pageStart);
g_system->copyRectToScreen(ms.getPixels(), ms.pitch,
0, 0, sw, sh);
@@ -3708,7 +3719,8 @@ bool EEMEngine::moreInfo(const byte *gd, uint suspectIdx,
while (!back && !advance && !prev && !redraw && !shouldQuit()) {
Common::Event e2;
while (g_system->getEventManager()->pollEvent(e2)) {
- if (isMacCD() && (handleMacCDPdaKey(e2) || trackMacCDPdaButton(*this, e2)))
+ if ((useScrollBar && handleMacTravisKey(*this, e2)) ||
+ (isMacCD() && trackMacCDPdaButton(*this, e2)))
continue;
if (e2.type == Common::EVENT_QUIT ||
e2.type == Common::EVENT_RETURN_TO_LAUNCHER) {
@@ -3721,7 +3733,7 @@ bool EEMEngine::moreInfo(const byte *gd, uint suspectIdx,
redraw = true;
break;
}
- if (isMacCD() && macScroll.handleEvent(e2, pageStart)) {
+ if (useScrollBar && macScroll.handleEvent(e2, pageStart)) {
redraw = true;
break;
}
@@ -3730,7 +3742,7 @@ bool EEMEngine::moreInfo(const byte *gd, uint suspectIdx,
macPdaScrollBarDelta(this, e2.mouse.x, e2.mouse.y);
setInteractiveMouseCursor(
galleryButtonAt(this, e2.mouse.x, e2.mouse.y) ||
- (isMacCD() && macScroll.contains(e2.mouse)) ||
+ (useScrollBar && macScroll.contains(e2.mouse)) ||
(scrollDelta < 0 && hasPrev) ||
(scrollDelta > 0 && hasMore));
}
@@ -3840,7 +3852,7 @@ bool EEMEngine::moreInfo(const byte *gd, uint suspectIdx,
gizmoLastTick = now;
cycleTravisPalette(isMacCD());
}
- if (isMacCD() && (macScroll.update(pageStart) || now - lastDraw >= 150))
+ if (useScrollBar && (macScroll.update(pageStart) || now - lastDraw >= 150))
redraw = true;
g_system->updateScreen();
g_system->delayMillis(20);
@@ -3982,8 +3994,8 @@ void EEMEngine::accuseRebuildPagination(const AccuseNotesCtx &ctx) {
Common::Array<int> heights;
for (uint i = 0; i < ctx.found->size(); ++i) {
Common::Array<Common::String> lines;
- _font.wordWrapText(accuseNoteText((*ctx.found)[i], ctx), ctx.rectW - kMacCDPdaTextInsetX, lines);
- heights.push_back(lines.size() * kMacCDPdaLineHeight);
+ _font.wordWrapText(accuseNoteText((*ctx.found)[i], ctx), ctx.rectW - kMacTravisTextInsetX, lines);
+ heights.push_back(lines.size() * kMacTravisLineHeight);
}
ctx.scrollBar->layout(heights, ctx.rectH, *ctx.page);
return;
@@ -4035,9 +4047,9 @@ void EEMEngine::accuseDrawScreen(const AccuseNotesCtx &ctx) {
const Common::Array<uint> &found = *ctx.found;
slotRects.clear();
slotClues.clear();
- const int lineH = isMacCD() ? kMacCDPdaLineHeight : _font.getFontHeight();
- const int insetX = isMacCD() ? kMacCDPdaTextInsetX : 0;
- const int insetY = isMacCD() ? kMacCDPdaTextInsetY : 0;
+ const int lineH = ctx.scrollBar ? kMacTravisLineHeight : _font.getFontHeight();
+ const int insetX = ctx.scrollBar ? kMacTravisTextInsetX : 0;
+ const int insetY = ctx.scrollBar ? kMacTravisTextInsetY : 0;
const int textWidth = ctx.rectW - insetX;
const int startIdx = ctx.scrollBar ? *ctx.page : ctx.pageBreaks[*ctx.page];
const int endIdx = ctx.scrollBar ? ctx.scrollBar->end(*ctx.page) : (*ctx.page + 1 < *ctx.numPages)
@@ -4068,8 +4080,8 @@ void EEMEngine::accuseDrawScreen(const AccuseNotesCtx &ctx) {
y - ctx.rectY + insetY + (int)li * lineH, textWidth, color);
}
slotRects.push_back(Common::Rect(ctx.rectX, y,
- ctx.rectX + ctx.rectW - (isMacCD() ? 12 : 0),
- MIN(y + h + (isMacCD() ? 6 : 0), ctx.rectY + ctx.rectH)));
+ ctx.rectX + ctx.rectW - (ctx.scrollBar ? 12 : 0),
+ MIN(y + h + (ctx.scrollBar ? 6 : 0), ctx.rectY + ctx.rectH)));
slotClues.push_back(clueId);
y += h + 7;
}
@@ -4090,7 +4102,7 @@ void EEMEngine::accuseDrawScreen(const AccuseNotesCtx &ctx) {
_font.drawString(&scratch, counter, scaleX(209), scaleY(11), scaleX(100), 0x0f);
}
- if (!isMacCD() && *ctx.numPages > 1) {
+ if (!ctx.scrollBar && *ctx.numPages > 1) {
_font.drawString(&scratch,
Common::String::format("p%d/%d", *ctx.page + 1, *ctx.numPages),
ctx.rectX, scaleY(11), scaleX(60), 0x0F);
@@ -4141,7 +4153,7 @@ bool EEMEngine::doAccuseNotes() {
const Common::Rect noteRectBase(79, 27, 304, 159);
const Common::Rect noteRect =
- isMacCD() ? kMacCDAccuseTextRect : (isMacintosh() ? scaleRect(noteRectBase) : noteRectBase);
+ usesTravisScrollBar(this) ? kMacTravisAccuseTextRect : (isMacintosh() ? scaleRect(noteRectBase) : noteRectBase);
const int rectX = noteRect.left;
const int rectY = noteRect.top;
const int rectW = noteRect.width();
@@ -4161,7 +4173,7 @@ bool EEMEngine::doAccuseNotes() {
Common::Array<uint> slotClues;
int page = 0;
- TravisScrollBar macScroll(kMacCDPdaScrollRect);
+ TravisScrollBar macScroll(kMacTravisScrollRect);
int pageBreaks[16];
int numPages = 1;
pageBreaks[0] = 0;
@@ -4188,7 +4200,7 @@ bool EEMEngine::doAccuseNotes() {
ctx.pageBreaksCap = (int)ARRAYSIZE(pageBreaks);
ctx.numPages = &numPages;
ctx.page = &page;
- ctx.scrollBar = isMacCD() ? &macScroll : nullptr;
+ ctx.scrollBar = usesTravisScrollBar(this) ? &macScroll : nullptr;
accuseRebuildPagination(ctx);
accuseDrawScreen(ctx);
@@ -4204,7 +4216,8 @@ bool EEMEngine::doAccuseNotes() {
Common::Event ev;
bool dirty = false;
while (g_system->getEventManager()->pollEvent(ev)) {
- if (isMacCD() && (handleMacCDPdaKey(ev, &slotRects, false) || trackMacCDPdaButton(*this, ev)))
+ if ((ctx.scrollBar && handleMacTravisKey(*this, ev, &slotRects, false)) ||
+ (isMacCD() && trackMacCDPdaButton(*this, ev)))
continue;
if (ev.type == Common::EVENT_QUIT ||
ev.type == Common::EVENT_RETURN_TO_LAUNCHER) {
@@ -4625,7 +4638,7 @@ void EEMEngine::doAccuse() {
ev.kbd.flags = ev.kbd.keycode == Common::KEYCODE_LEFT ? Common::KBD_SHIFT : 0;
ev.kbd.keycode = Common::KEYCODE_TAB;
}
- if (handleMacCDPdaKey(ev, &slotRects, false))
+ if (handleMacTravisKey(*this, ev, &slotRects, false))
continue;
if (ev.type == Common::EVENT_MOUSEMOVE)
setInteractiveMouseCursor(rectListContains(slotRects, ev.mouse.x, ev.mouse.y));
Commit: b141102aaf93fdb1e2f771c4ee94e4a3f1c78822
https://github.com/scummvm/scummvm/commit/b141102aaf93fdb1e2f771c4ee94e4a3f1c78822
Author: neuromancer (gustavo.grieco at gmail.com)
Date: 2026-09-10T17:45:23+02:00
Commit Message:
EEM: review and improved case workflow for EEM1 (mac)
Changed paths:
engines/eem/clues.cpp
engines/eem/eem.cpp
engines/eem/eem.h
engines/eem/font.cpp
engines/eem/font.h
engines/eem/graphics.cpp
engines/eem/ui.cpp
diff --git a/engines/eem/clues.cpp b/engines/eem/clues.cpp
index 78da5855fd7..da80872b1b4 100644
--- a/engines/eem/clues.cpp
+++ b/engines/eem/clues.cpp
@@ -944,6 +944,7 @@ void EEMEngine::displayClue(const byte *clueBlock) {
const uint stride = isLondon() ? 0x54 : 62;
const bool mac = isMacintosh();
+ const bool macSolved = isMacCD() && clueBlock == _mystery.solvedClueBlock();
const int sw = screenWidth();
const int sh = screenHeight();
MacSpritePaletteMap macPaletteMap = {0x00, 0xFF};
@@ -964,6 +965,8 @@ void EEMEngine::displayClue(const byte *clueBlock) {
g_system->unlockScreen();
}
}
+ if (macSolved && !_partnerEraseBg.empty())
+ bg.simpleBlitFrom(_partnerEraseBg);
// ClueEntry layout. EEM1 entries are 62 bytes; EEM2/London entries
// extend this to 0x54 bytes and move the side-effect lists below.
@@ -978,6 +981,8 @@ void EEMEngine::displayClue(const byte *clueBlock) {
// 5 notebook entries (-1 terminated)
// EEM1 +0x3a / EEM2 +0x4e: KD-anim number (-1 = none)
for (uint i = 0; i < number && !shouldQuit(); i++) {
+ if (isMacCD() && _audio)
+ _audio->stopSpool();
g_system->copyRectToScreen(bg.getPixels(), bg.pitch, 0, 0, sw, sh);
const byte *c = clueBlock + 4 + i * stride;
@@ -996,10 +1001,11 @@ void EEMEngine::displayClue(const byte *clueBlock) {
uint16 kdAnimId = 0;
const bool haveKd = kdAnimNum != -1 &&
loadKdAnim((uint16)kdAnimNum, kdAnim, kdPx, kdPy, kdAnimId);
+ const bool animatePartner = haveKd || (macSolved && _hasPartnerIdle);
// Animate the gesture over the partner-less scene so it doesn't ghost
// the static partner.
- if (haveKd && _partnerEraseBg.w == sw &&
+ if (animatePartner && _partnerEraseBg.w == sw &&
_partnerEraseBg.h == sh) {
g_system->copyRectToScreen(_partnerEraseBg.getPixels(),
_partnerEraseBg.pitch, 0, 0, sw, sh);
@@ -1019,11 +1025,12 @@ void EEMEngine::displayClue(const byte *clueBlock) {
// Speaker portrait: pic[clues + i*62 - 2]. Entry 0 ID is in
// ClueBlock +2; entries N>0 read (entry-1)+0x3c (last word).
const uint16 charX = READ_LE_UINT16(c + (useP1 ? 4 : 0));
- const uint16 charY = READ_LE_UINT16(c + (useP1 ? 6 : 2));
+ // Mac CD adds 7.68 to the portrait Y and rounds to an integer.
+ const int charY = READ_LE_UINT16(c + (useP1 ? 6 : 2)) + (isMacCD() ? 8 : 0);
uint16 charPicId = (i == 0)
? READ_LE_UINT16(clueBlock + 2)
: READ_LE_UINT16(c - 2);
- if (isLondon() && charPicId == 0x13e &&
+ if ((isLondon() || isMacCD()) && charPicId == 0x13e &&
_partner == kPartnerJake)
charPicId = 0x13f;
if (charPicId != 0 && charPicId != 0xFFFF) {
@@ -1097,12 +1104,16 @@ void EEMEngine::displayClue(const byte *clueBlock) {
copyY = bubY;
}
- if (mac && textColor != 0xFF) {
+ if (isMacCD()) {
+ dialogFont.drawMacWordWrapped(&scratch, textX, textY,
+ MAX<int>(8, textW), text, textColor);
+ } else {
+ if (mac && textColor != 0xFF)
+ dialogFont.drawWordWrapped(&scratch, textX, textY,
+ MAX<int>(8, textW), text, 0xFF);
dialogFont.drawWordWrapped(&scratch, textX, textY,
- MAX<int>(8, textW), text, 0xFF);
+ MAX<int>(8, textW), text, textColor);
}
- dialogFont.drawWordWrapped(&scratch, textX, textY,
- MAX<int>(8, textW), text, textColor);
copyY = CLIP<int>(copyY, 0, sh - 1);
const int copyRows = CLIP<int>(MIN<int>(copyH, sh - copyY),
@@ -1112,7 +1123,7 @@ void EEMEngine::displayClue(const byte *clueBlock) {
scratch.pitch, 0, copyY, sw, copyRows);
// Gesture entry: let the wait loop present, so the partner-less
// base isn't flashed before the gesture's first frame.
- if (!haveKd)
+ if (!animatePartner)
g_system->updateScreen();
}
}
@@ -1139,7 +1150,7 @@ void EEMEngine::displayClue(const byte *clueBlock) {
bool haveKdBase = false;
uint kdLastFrame = (uint)-1;
const uint32 kdStartMs = g_system->getMillis();
- if (haveKd) {
+ if (animatePartner) {
Graphics::Surface *kdScr = g_system->lockScreen();
if (kdScr) {
kdBase.simpleBlitFrom(*kdScr);
@@ -1201,7 +1212,7 @@ void EEMEngine::displayClue(const byte *clueBlock) {
const uint32 kdElapsed = g_system->getMillis() - kdStartMs;
if (haveIdle && kdElapsed >= kdDurationMs) {
// Resume the looping idle wait-anim.
- const uint f = partnerFrameAtTick(_partnerIdleAnimId,
+ const uint f = partnerFrameAtTick(macSolved ? 0x02 : _partnerIdleAnimId,
(uint)idleAnim.size(), kdElapsed - kdDurationMs);
if ((!kdInIdle || f != kdLastIdleFrame) &&
f < idleAnim.size()) {
@@ -1219,7 +1230,7 @@ void EEMEngine::displayClue(const byte *clueBlock) {
g_system->copyRectToScreen(comp.getPixels(), comp.pitch,
0, 0, sw, sh);
}
- } else {
+ } else if (haveKd) {
// Gesture one-shot.
const uint f = oneShotFrameAtTick(kdAnimId,
(uint)kdAnim.size(), kdElapsed);
diff --git a/engines/eem/eem.cpp b/engines/eem/eem.cpp
index ff2ba64a91e..eef0c2a640a 100644
--- a/engines/eem/eem.cpp
+++ b/engines/eem/eem.cpp
@@ -554,6 +554,8 @@ Common::Error EEMEngine::run() {
warning("Mac FONT resource failed to load; text will not render");
if (!loadMacDialogFont(_dialogFont))
warning("Mac dialog FONT resource failed to load");
+ if (isMacCD() && !loadMacFontResource(_newspaperFont, kMacSmallFontResource, 9))
+ warning("Mac newspaper FONT resource failed to load");
} else if (!_font.load(Common::Path("FONT.FNT"))) {
warning("FONT.FNT failed to load; text will not render");
}
@@ -1776,18 +1778,18 @@ void EEMEngine::showLondonEAKidsLogo() {
fadeCurrentPaletteToBlack();
}
-void EEMEngine::showLondonLogo(uint picId, uint palId, uint holdMs,
+void EEMEngine::showStillPicture(uint picId, uint palId, uint holdMs,
bool playThunder) {
Picture pic;
if (!_picsArchive.getPicture(picId, pic) || pic.surface.empty()) {
- warning("London logo PIC 0x%x load failed", picId);
+ warning("PIC 0x%x load failed", picId);
return;
}
blitAt(pic, 0, 0);
byte target[kPalSize];
if (!getSitePalette(palId, target)) {
- warning("London palette 0x%x load failed", palId);
+ warning("Palette 0x%x load failed", palId);
return;
}
byte black[kPalSize] = {};
@@ -1833,9 +1835,9 @@ void EEMEngine::runLondonStartup() {
if (isMacintosh()) {
if (!shouldQuit() && !_skipIntro)
- showLondonLogo(0x20c, 0x3e, 3000); // publisher logo (FUN_00009074)
+ showStillPicture(0x20c, 0x3e, 3000); // publisher logo (FUN_00009074)
if (!shouldQuit() && !_skipIntro)
- showLondonLogo(0x20b, 0x3d, 3000, /* playThunder= */ true);
+ showStillPicture(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
@@ -1859,7 +1861,7 @@ void EEMEngine::runLondonStartup() {
fadeCurrentPaletteToBlack();
}
if (!shouldQuit() && !_skipIntro)
- showLondonLogo(kDosLondonPicHighScoreLogo,
+ showStillPicture(kDosLondonPicHighScoreLogo,
kDosLondonPalHighScoreLogo, 3000);
// Intro movie with its theme (MUS00101.XMI).
diff --git a/engines/eem/eem.h b/engines/eem/eem.h
index 0c6ca8cd8b2..7603a01fd8b 100644
--- a/engines/eem/eem.h
+++ b/engines/eem/eem.h
@@ -511,7 +511,7 @@ private:
/// Start London mystery 0 after a freshly-created detective chooses a partner.
bool startLondonTrainingMystery();
void showLondonEAKidsLogo();
- void showLondonLogo(uint picId, uint palId, uint holdMs,
+ void showStillPicture(uint picId, uint palId, uint holdMs,
bool playThunder = false);
void showLondonCharSelect();
void playLondonInitCluesAnim(uint16 caseType, const Picture &bg,
@@ -637,6 +637,7 @@ private:
Mystery _mystery; ///< M<n>.BIN
EEMFont _font; ///< FONT.FNT (8 px)
EEMFont _dialogFont; ///< Mac 14pt FONT used inside speech balloons.
+ EEMFont _newspaperFont;
Common::Array<byte> _sitePals; ///< 40 Ã 768 bytes, 6-bit VGA.
diff --git a/engines/eem/font.cpp b/engines/eem/font.cpp
index a426c4961ae..4b2b474c5c7 100644
--- a/engines/eem/font.cpp
+++ b/engines/eem/font.cpp
@@ -180,6 +180,21 @@ int EEMFont::drawWordWrapped(Graphics::ManagedSurface *dst, int x, int y,
return (int)lines.size() * lineH;
}
+int EEMFont::drawMacWordWrapped(Graphics::ManagedSurface *dst, int x, int y,
+ int width, const Common::String &s, uint32 color,
+ int lineHeight) const {
+ if (!_macFont || width <= 5)
+ return 0;
+
+ // Mac WriteText adds a left margin and advances the baseline first.
+ Common::Array<Common::String> lines;
+ wordWrapText(s, width - 5, lines);
+ y += lineHeight - _macFont->getFontAscent();
+ for (uint i = 0; i < lines.size(); i++)
+ drawString(dst, lines[i], x + 5, y + (int)i * lineHeight, width - 5, color);
+ return (int)lines.size() * lineHeight;
+}
+
void EEMFont::drawChar(Graphics::Surface *dst, uint32 chr, int x, int y,
uint32 color) const {
if (!dst)
diff --git a/engines/eem/font.h b/engines/eem/font.h
index 3d82be00bab..2a1432d15f2 100644
--- a/engines/eem/font.h
+++ b/engines/eem/font.h
@@ -72,6 +72,9 @@ public:
/// each line at (x, y) downward. Returns total height drawn.
int drawWordWrapped(Graphics::ManagedSurface *dst, int x, int y,
int width, const Common::String &s, uint32 color) const;
+ int drawMacWordWrapped(Graphics::ManagedSurface *dst, int x, int y,
+ int width, const Common::String &s, uint32 color,
+ int lineHeight = 16) const;
private:
void clear();
diff --git a/engines/eem/graphics.cpp b/engines/eem/graphics.cpp
index f283765f3cd..2eb36fbd15d 100644
--- a/engines/eem/graphics.cpp
+++ b/engines/eem/graphics.cpp
@@ -116,7 +116,10 @@ uint EEMEngine::getBalloonLineCapacity(uint16 balloonId, int lineH) const {
const BalloonInsets &insets = kBalloonInsetTable[idx];
int textHeight = (int)insets.indDY - (int)insets.y;
- if (isMacintosh())
+ if (isMacCD())
+ textHeight = insets.indDY * kMacScreenHeight / kScreenHeight -
+ insets.y * kMacScreenHeight / kScreenHeight;
+ else if (isMacintosh())
textHeight = scaleY(textHeight); // table is in DOS coords; Mac renders scaled
return MAX<uint>(1, textHeight / lineH + 1);
}
@@ -810,14 +813,15 @@ uint16 EEMEngine::fitBalloonToText(uint16 bubNum,
return bubNum;
const BalloonInsets &originalInsets = kBalloonInsetTable[originalId];
- const int lineH = _font.getFontHeight();
+ const int lineH = isMacCD() ? 16 : _font.getFontHeight();
const uint originalCapacity = getBalloonLineCapacity(originalId, lineH);
if (originalCapacity == 0)
return bubNum;
Common::Array<Common::String> lines;
- const int wrapW = isMacintosh() ? scaleX((int)originalInsets.w)
- : (int)originalInsets.w;
+ uint16 insetX, insetY, wrapWidth;
+ getBalloonInsets(originalId, insetX, insetY, wrapWidth);
+ const int wrapW = wrapWidth - (isMacCD() ? 5 : 0);
_font.wordWrapText(text, MAX<int>(8, wrapW), lines);
if (lines.empty())
return bubNum;
@@ -868,7 +872,12 @@ bool EEMEngine::getBalloonInsets(uint16 bubNum, uint16 &xInset,
const uint idx = bubNum & 0x7F;
if (idx >= ARRAYSIZE(kBalloonInsetTable))
return false;
- if (isMacintosh()) {
+ if (isMacCD()) {
+ // CODE 2:1f74 truncates the fixed-point products.
+ xInset = kBalloonInsetTable[idx].x * kMacScreenWidth / kScreenWidth;
+ yInset = kBalloonInsetTable[idx].y * kMacScreenHeight / kScreenHeight;
+ textW = kBalloonInsetTable[idx].w * kMacScreenWidth / kScreenWidth;
+ } else if (isMacintosh()) {
xInset = (uint16)scaleX(kBalloonInsetTable[idx].x);
yInset = (uint16)scaleY(kBalloonInsetTable[idx].y);
textW = (uint16)scaleX(kBalloonInsetTable[idx].w);
diff --git a/engines/eem/ui.cpp b/engines/eem/ui.cpp
index 99375d64ace..4b9e002bbd4 100644
--- a/engines/eem/ui.cpp
+++ b/engines/eem/ui.cpp
@@ -1747,12 +1747,15 @@ int EEMEngine::doShowEnding(uint num, bool firstPage) {
CursorMan.showMouse(true);
const bool floppyEnding = isFloppy();
+ const bool macCDEnding = isMacCD() && macLooseEnding;
const int sw = screenWidth();
const int sh = screenHeight();
- const Common::Rect endingPrevRect =
+ const Common::Rect endingPrevRect = macCDEnding ? Common::Rect(0, 20, 43, 384) :
scaleDosRectIfMac(*this, kEndingPrevPageRect);
- const Common::Rect endingNextRect =
+ const Common::Rect endingNextRect = macCDEnding ? Common::Rect(45, 20, 506, 376) :
scaleDosRectIfMac(*this, kEndingNextPageRect);
+ const Common::Rect endingExitRect = macCDEnding ? Common::Rect(0, 0, 506, 20) :
+ Common::Rect();
uint pageOffsets[8];
const uint pageOffsetCap =
(uint)ARRAYSIZE(pageOffsets);
@@ -1798,11 +1801,14 @@ int EEMEngine::doShowEnding(uint num, bool firstPage) {
for (uint p = 0; p < maxPages; p++) {
if (cursor + 10 >= size)
break;
- pageOffsets[validPages++] = cursor;
+ const uint pageStart = cursor;
// Skip the 10-byte header and find the null terminator.
cursor += 10;
while (cursor < size && buf[cursor] != 0)
cursor++;
+ if (cursor >= size)
+ break;
+ pageOffsets[validPages++] = pageStart;
cursor++; // past the null
}
}
@@ -1811,7 +1817,7 @@ int EEMEngine::doShowEnding(uint num, bool firstPage) {
const bool showFirstTryBadge =
num < sizeof(_mysteriesSolved) && _mysteriesSolved[num] == 2 &&
- (floppyEnding || ConfMan.getBool("restored_content"));
+ !macCDEnding && (floppyEnding || ConfMan.getBool("restored_content"));
Picture firstTryBadge;
const bool haveFirstTryBadge =
showFirstTryBadge &&
@@ -1821,11 +1827,12 @@ int EEMEngine::doShowEnding(uint num, bool firstPage) {
int direction = 0;
bool exitLoop = false;
bool dirty = true;
+ auto updateCursor = [&](const Common::Point &pos) {
+ setInteractiveMouseCursor(endingPrevRect.contains(pos) ||
+ endingNextRect.contains(pos) || endingExitRect.contains(pos));
+ };
const Common::Point mousePos = g_system->getEventManager()->getMousePos();
- setInteractiveMouseCursor(endingPrevRect.contains(mousePos.x,
- mousePos.y) ||
- endingNextRect.contains(mousePos.x,
- mousePos.y));
+ updateCursor(mousePos);
while (!shouldQuit() && !exitLoop) {
if (dirty) {
const uint off = pageOffsets[pageIdx];
@@ -1883,15 +1890,22 @@ int EEMEngine::doShowEnding(uint num, bool firstPage) {
break;
const uint16 picNum =
readScriptU16(buf.data() + off, macLooseEnding);
- const Common::Rect textRect =
- readRectXYXY(buf.data() + off + 2, macLooseEnding);
+ const Common::Rect textRect = macCDEnding
+ ? readMacQuickDrawRectBE(buf.data() + off + 2)
+ : readRectXYXY(buf.data() + off + 2, macLooseEnding);
x1 = textRect.left;
y1 = textRect.top;
x2 = textRect.right;
Picture bg;
- if (_picsArchive.getPicture(picNum, bg))
+ if (macCDEnding) {
+ if (_picsArchive.getPicture(kFloppyEndingBackgroundPic, bg))
+ blitTravisBackground(scratch, bg, true);
+ if (_picsArchive.getPicture(picNum, bg))
+ blitMacMaskedSurface(scratch.surfacePtr(), bg, 92, 34);
+ } else if (_picsArchive.getPicture(picNum, bg)) {
scratch.simpleBlitFrom(bg.surface);
+ }
raw = (const char *)buf.data() + off + 10;
}
@@ -1903,16 +1917,20 @@ int EEMEngine::doShowEnding(uint num, bool firstPage) {
const Common::String text = parseString(raw, _playerName, _partner);
- const EEMFont &renderFont = haveTinyFont ? tinyFont : _font;
+ const EEMFont &renderFont = macCDEnding ? _newspaperFont :
+ (haveTinyFont ? tinyFont : _font);
if (renderFont.isLoaded() && x2 > x1) {
const int textW = MIN<int>((int)x2 - (int)x1,
sw - (int)x1);
MacSpritePaletteMap macPaletteMap = {0x00, 0xFF};
if (macEnding)
macPaletteMap = getMacSpritePaletteMap();
- renderFont.drawWordWrapped(&scratch, (int)x1, (int)y1,
- textW, text,
- macEnding ? macPaletteMap.black : 0);
+ if (macCDEnding)
+ renderFont.drawMacWordWrapped(&scratch, x1, y1, textW, text, 0x2b, 13);
+ else
+ renderFont.drawWordWrapped(&scratch, (int)x1, (int)y1,
+ textW, text,
+ macEnding ? macPaletteMap.black : 0);
}
g_system->copyRectToScreen(scratch.getPixels(), scratch.pitch,
@@ -1930,9 +1948,7 @@ int EEMEngine::doShowEnding(uint num, bool firstPage) {
break;
}
if (ev.type == Common::EVENT_MOUSEMOVE)
- setInteractiveMouseCursor(
- endingPrevRect.contains(ev.mouse.x, ev.mouse.y) ||
- endingNextRect.contains(ev.mouse.x, ev.mouse.y));
+ updateCursor(ev.mouse);
if (ev.type == Common::EVENT_KEYDOWN) {
switch (ev.kbd.keycode) {
case Common::KEYCODE_ESCAPE:
@@ -1970,10 +1986,11 @@ int EEMEngine::doShowEnding(uint num, bool firstPage) {
break;
}
if (ev.type == Common::EVENT_LBUTTONDOWN) {
- setInteractiveMouseCursor(
- endingPrevRect.contains(ev.mouse.x, ev.mouse.y) ||
- endingNextRect.contains(ev.mouse.x, ev.mouse.y));
- if (endingPrevRect.contains(ev.mouse.x, ev.mouse.y)) {
+ updateCursor(ev.mouse);
+ if (endingExitRect.contains(ev.mouse)) {
+ direction = 0;
+ exitLoop = true;
+ } else if (endingPrevRect.contains(ev.mouse.x, ev.mouse.y)) {
if (pageIdx > 0) {
pageIdx--;
dirty = true;
@@ -4396,10 +4413,10 @@ void EEMEngine::drawKDBalloonOverCurrentScreen(Common::String text) {
_balloonArchive.size() > (bubNum & 0x7F) &&
_balloonArchive.loadEntry(bubNum & 0x7F, balloon);
- const int balloonX = mac ? scaleX(0x21) : 0x21;
- const int topBandH = mac ? scaleY(0x50) : 0x50;
- const int centeredMaxH = mac ? scaleY(0x4e) : 0x4e;
- int balloonY = mac ? scaleY(1) : 1;
+ const int balloonX = isMacCD() ? 100 : scaleX(0x21);
+ const int topBandH = isMacCD() ? 155 : scaleY(0x50);
+ const int centeredMaxH = isMacCD() ? 155 : scaleY(0x4e);
+ int balloonY = isMacCD() ? 1 : scaleY(1);
if (haveBalloon && balloon.surface.h < centeredMaxH)
balloonY = (topBandH - balloon.surface.h) / 2;
@@ -4425,8 +4442,12 @@ void EEMEngine::drawKDBalloonOverCurrentScreen(Common::String text) {
if (dialogFont.isLoaded()) {
const byte textColor =
mac ? macPaletteMap.black : (haveBalloon ? 0 : 0xF);
- dialogFont.drawWordWrapped(&ms, balloonX + tx, balloonY + ty,
- tw, text, textColor);
+ if (isMacCD())
+ dialogFont.drawMacWordWrapped(&ms, balloonX + tx, balloonY + ty,
+ tw, text, textColor);
+ else
+ dialogFont.drawWordWrapped(&ms, balloonX + tx, balloonY + ty,
+ tw, text, textColor);
}
copyToScreen(ms);
@@ -4461,7 +4482,7 @@ void EEMEngine::doAccuse() {
const int foundPoints = _mystery.foundPoints();
readyTier = (foundPoints == 0) ? 0
: (foundPoints < 0x32) ? 1
- : (foundPoints < 0x65) ? 2
+ : (foundPoints < (isMacCD() ? 100 : 101)) ? 2
: 3;
}
@@ -4480,7 +4501,7 @@ void EEMEngine::doAccuse() {
// final two SDB entries, but its KD digital table points kdspeak 9
// at earlier hint clips. Use the otherwise unreferenced pair. EEM1
// SDB indices, so EEM1 only.
- if (!isLondon() && _mystery.number() == 0)
+ if (!isLondon() && !isMacCD() && _mystery.number() == 0)
entryVoiceOverride = (_partner == kPartnerJake) ? 105 : 104;
break;
case 1:
@@ -4509,8 +4530,12 @@ void EEMEngine::doAccuse() {
else
_audio->sayKDDigital(entryKdIdx, entryKDSpeak, _partner);
}
- waitForInput(60000);
+ waitForInput(isMacCD() ? 0xFFFFFFFFu : 60000);
+ if (isMacCD())
+ interruptAudio();
}
+ if (shouldQuit())
+ return;
if (!canAccuse) {
_nextScreen = _lastScreen != kScreenInvalid
? (ScreenId)_lastScreen : kScreenSite;
@@ -4545,7 +4570,8 @@ void EEMEngine::doAccuse() {
// London matches the selected notes against the answer sets; EEM1/floppy
// sum clue points.
const bool accuseSolved =
- isLondon() ? _mystery.londonSolved() : _mystery.solvedCheck();
+ isLondon() ? _mystery.londonSolved() :
+ (isMacCD() ? _mystery.selectedPoints() >= 99 : _mystery.solvedCheck());
if (!accuseSolved) {
const byte *kdIdx = _mystery.kdTextIndex();
const int16 hintOff = kdIdx
@@ -4582,7 +4608,9 @@ void EEMEngine::doAccuse() {
if (_audio && kdIdx)
_audio->sayKDDigital(kdIdx, 3, _partner);
- waitForInput(20000);
+ waitForInput(isMacCD() ? 0xFFFFFFFFu : 20000);
+ if (isMacCD() && _audio)
+ _audio->stopSpool();
_nextScreen = _lastScreen != kScreenInvalid
? (ScreenId)_lastScreen : kScreenSite;
return;
@@ -4605,25 +4633,41 @@ void EEMEngine::doAccuse() {
Common::String hint =
parseString(raw ? raw : "", _playerName, _partner);
if (!hint.empty()) {
- // Render gallery first so the snapshot includes partner.
- drawAccuseGallery(num, gd, /* highlighted= */ -1,
- slotRects, slotSuspect);
+ if (isMacCD()) {
+ Picture background;
+ Graphics::ManagedSurface scratch(screenWidth(), screenHeight(),
+ Graphics::PixelFormat::createFormatCLUT8());
+ scratch.clear();
+ if (_picsArchive.getPicture(0x1a7, background))
+ blitTravisBackground(scratch, background, true);
+ blitPdaPartner(scratch, _aniArchive, _partner, kPdaNotebookPartner,
+ g_system->getMillis(), true, true);
+ copyToScreen(scratch);
+ } else {
+ drawAccuseGallery(num, gd, /* highlighted= */ -1,
+ slotRects, slotSuspect);
+ }
drawKDBalloonOverCurrentScreen(hint);
if (_audio)
_audio->sayKDDigital(kdIdx, 4, _partner);
- waitForInput(8000);
+ waitForInput(isMacCD() ? 0xFFFFFFFFu : 8000);
+ if (isMacCD() && _audio)
+ _audio->stopSpool();
}
}
}
+ if (shouldQuit())
+ return;
if (isLondon() && _music && _voiceOn)
_music->playMus(33, /* loop= */ true);
+ drawAccuseGallery(num, gd, highlighted, slotRects, slotSuspect);
// Wrap past empty slots (matches original DI advance).
- if (slotRects[highlighted].isEmpty())
+ if (slotRects[highlighted].isEmpty()) {
highlighted = nextLiveSlot(slotRects, highlighted, +1);
-
- drawAccuseGallery(num, gd, highlighted, slotRects, slotSuspect);
+ drawAccuseGallery(num, gd, highlighted, slotRects, slotSuspect);
+ }
int picked = -1;
uint32 lastTick = g_system->getMillis();
@@ -4717,15 +4761,13 @@ void EEMEngine::doAccuse() {
pickedGuilty ? "yes" : "no",
guessedRight ? "correct" : "wrong");
- // `_DisplayAlibi @ 1df2:0145`:
- // 1. MIDI 6 loser sting (gallery still visible).
- // 2. PIC 0x3e + suspect balloon + portrait at (0x82, py).
- // bindx = digit prefix (else 2). bindx<8 centres balloon,
- // bindx>=8 pins at x=0x21.
- // 3. `_SpoolSound(talk-1)` where talk = partner==0 ? gd[+6] : gd[+0].
- // 4. Partner reaction balloon @ KDTextIndex[+10], `_SayKDDigital(5)`.
- // 5. _FirstTry = 0; NextScreen = LastScreen (1df2:043f).
+ // DisplayAlibi: DOS 1df2:0145, Mac CD CODE 7:043e.
if (!guessedRight) {
+ const bool macCD = isMacCD();
+ const int sw = macCD ? screenWidth() : kScreenWidth;
+ const int sh = macCD ? screenHeight() : kScreenHeight;
+ const MacSpritePaletteMap palette = macCD ? getMacSpritePaletteMap() :
+ MacSpritePaletteMap{0x00, 0xFF};
static const uint16 kAlibiBubbles[16] = {
0x002B, 0x002C, 0x002D, 0x002E,
0x00AB, 0x00AC, 0x00AD, 0x00AE,
@@ -4765,58 +4807,67 @@ void EEMEngine::doAccuse() {
_balloonArchive.size() > (bubNum & 0x7F) &&
_balloonArchive.loadEntry(bubNum & 0x7F, balloon);
- int balloonX = 0x21;
+ int balloonX = macCD ? 100 : 0x21;
int balloonY = 1;
- int py = 0x5a;
+ int py = macCD ? 173 : 0x5a;
if (bindx < 8) {
const int bw = haveBalloon ? balloon.surface.w : 0;
const int bh = haveBalloon ? balloon.surface.h : 0;
- balloonX = (kScreenWidth - bw) / 2;
- if (bh < 0x5a) {
- balloonY = (0x5a - bh) / 2;
+ balloonX = (sw - bw) / 2;
+ if (bh < py) {
+ balloonY = (py - bh) / 2;
} else {
balloonY = 1;
- py = bh;
+ if (!macCD)
+ py = bh;
}
} else {
const int bh = haveBalloon ? balloon.surface.h : 0;
- balloonX = 0x21;
- balloonY = (bh < 0x4f) ? (0x50 - bh) / 2 : 1;
+ balloonY = macCD ? MAX(1, (155 - bh) / 2) :
+ ((bh < 0x4f) ? (0x50 - bh) / 2 : 1);
}
- Graphics::ManagedSurface base(kScreenWidth, kScreenHeight,
+ Graphics::ManagedSurface base(sw, sh,
Graphics::PixelFormat::createFormatCLUT8());
base.clear();
if (haveAlibiBg)
- base.simpleBlitFrom(alibiBg.surface);
+ blitTravisBackground(base, alibiBg, macCD);
if (haveSuspect) {
- const byte transp = (byte)(suspect.flags >> 8);
- base.transBlitFrom(suspect.surface,
- Common::Point(0x82, py),
- (uint32)transp);
+ if (macCD)
+ blitMacMaskedSurface(base.surfacePtr(), suspect, 208, py);
+ else
+ base.transBlitFrom(suspect.surface, Common::Point(0x82, py),
+ (uint32)(byte)(suspect.flags >> 8));
}
- Graphics::ManagedSurface scratch(kScreenWidth, kScreenHeight,
+ Graphics::ManagedSurface scratch(sw, sh,
Graphics::PixelFormat::createFormatCLUT8());
scratch.simpleBlitFrom(base);
if (haveBalloon) {
- const byte transp = (byte)(balloon.flags >> 8);
- scratch.transBlitFrom(balloon.surface,
- Common::Point(balloonX, balloonY),
- (uint32)transp);
+ if (macCD)
+ blitMacMaskedSurface(scratch.surfacePtr(), balloon, balloonX, balloonY,
+ (bubNum & 0x80) != 0, palette);
+ else
+ scratch.transBlitFrom(balloon.surface, Common::Point(balloonX, balloonY),
+ (uint32)(byte)(balloon.flags >> 8));
}
uint16 tx = 5, ty = 4, tw = 155;
getBalloonInsets(bubNum, tx, ty, tw);
if (_font.isLoaded() && !alibi.empty()) {
- _font.drawWordWrapped(&scratch, balloonX + tx,
- balloonY + ty, tw, alibi,
- haveBalloon ? 0 : 0xF);
+ if (macCD)
+ _font.drawMacWordWrapped(&scratch, balloonX + tx, balloonY + ty,
+ tw, alibi, palette.black);
+ else
+ _font.drawWordWrapped(&scratch, balloonX + tx,
+ balloonY + ty, tw, alibi,
+ haveBalloon ? 0 : 0xF);
}
- blitPdaPartner(scratch, _aniArchive, _partner, kPdaGalleryPartner,
- g_system->getMillis());
+ blitPdaPartner(scratch, _aniArchive, _partner,
+ macCD ? kPdaNotebookPartner : kPdaGalleryPartner,
+ g_system->getMillis(), macCD, macCD);
- if (_music && _voiceOn) {
+ if (!macCD && _music && _voiceOn) {
_music->playMus(6, /* loop= */ false);
const uint32 musStart = g_system->getMillis();
bool aborted = false;
@@ -4842,20 +4893,22 @@ void EEMEngine::doAccuse() {
_music->stop();
}
- g_system->copyRectToScreen(scratch.getPixels(), scratch.pitch,
- 0, 0, kScreenWidth, kScreenHeight);
- g_system->updateScreen();
- if (_audio && gd && !isMacintosh()) {
+ copyToScreen(scratch);
+ if (_audio && gd && (!isMacintosh() || macCD)) {
const uint16 alibiVoice =
- READ_LE_UINT16(gd + (uint)picked * 0x46 + 0x00);
+ READ_LE_UINT16(gd + (uint)picked * 0x46 + (macCD ? 4 : 0));
const uint16 jakeVoice =
READ_LE_UINT16(gd + (uint)picked * 0x46 + 0x06);
const uint16 talk =
(_partner == kPartnerJake) ? jakeVoice : alibiVoice;
- if (talk != 0)
+ if (talk != 0 && talk != 0xFFFF)
_audio->spoolSound((uint)(talk - 1));
}
- waitForInput(60000);
+ waitForInput(macCD ? 0xFFFFFFFFu : 60000);
+ if (macCD && _audio)
+ _audio->stopSpool();
+ if (shouldQuit())
+ return;
const byte *reactIdx = _mystery.kdTextIndex();
if (reactIdx) {
@@ -4876,35 +4929,41 @@ void EEMEngine::doAccuse() {
const bool haveR =
_balloonArchive.size() > (rBub & 0x7F) &&
_balloonArchive.loadEntry(rBub & 0x7F, rBalloon);
- const int rX = 0x21;
+ const int rX = macCD ? 100 : 0x21;
int rY = 1;
- if (haveR && rBalloon.surface.h < 0x4e)
- rY = (0x50 - rBalloon.surface.h) / 2;
+ if (haveR && rBalloon.surface.h < (macCD ? 155 : 0x4e))
+ rY = ((macCD ? 155 : 0x50) - rBalloon.surface.h) / 2;
scratch.simpleBlitFrom(base);
if (haveR) {
- const byte transp = (byte)(rBalloon.flags >> 8);
- scratch.transBlitFrom(rBalloon.surface,
- Common::Point(rX, rY),
- (uint32)transp);
+ if (macCD)
+ blitMacMaskedSurface(scratch.surfacePtr(), rBalloon, rX, rY);
+ else
+ scratch.transBlitFrom(rBalloon.surface, Common::Point(rX, rY),
+ (uint32)(byte)(rBalloon.flags >> 8));
}
uint16 rtx = 5, rty = 4, rtw = 155;
getBalloonInsets(rBub, rtx, rty, rtw);
if (_font.isLoaded()) {
- _font.drawWordWrapped(&scratch, rX + rtx,
- rY + rty, rtw, react,
- haveR ? 0 : 0xF);
+ if (macCD)
+ _font.drawMacWordWrapped(&scratch, rX + rtx, rY + rty,
+ rtw, react, palette.black);
+ else
+ _font.drawWordWrapped(&scratch, rX + rtx,
+ rY + rty, rtw, react,
+ haveR ? 0 : 0xF);
}
blitPdaPartner(scratch, _aniArchive, _partner,
- kPdaGalleryPartner, g_system->getMillis());
- g_system->copyRectToScreen(scratch.getPixels(),
- scratch.pitch, 0, 0, kScreenWidth, kScreenHeight);
- g_system->updateScreen();
+ macCD ? kPdaNotebookPartner : kPdaGalleryPartner,
+ g_system->getMillis(), macCD, macCD);
+ copyToScreen(scratch);
if (_audio)
_audio->sayKDDigital(reactIdx, 5, _partner);
}
}
- waitForInput(60000);
+ waitForInput(macCD ? 0xFFFFFFFFu : 60000);
+ if (macCD && _audio)
+ _audio->stopSpool();
_mystery._firstTry = false;
_nextScreen = _lastScreen != kScreenInvalid
@@ -4914,12 +4973,12 @@ void EEMEngine::doAccuse() {
{
const uint mn = _mystery.number();
- if (mn < sizeof(_mysteriesSolved)) {
- _mysteriesSolved[mn] = _mystery._firstTry ? 2 : 1;
+ if (!isMacCD()) {
+ if (mn < sizeof(_mysteriesSolved))
+ _mysteriesSolved[mn] = _mystery._firstTry ? 2 : 1;
+ advanceChainStageAfterSolve(mn);
}
- advanceChainStageAfterSolve(mn);
-
// `_DisplayCorrect` win background = `_BuildBackground(scene, 0x42, 0x14)`
// (frame PIC 0x3d + scene at 0x42,0x14, palette scene+1). EEM1 CD uses
// scene 5; EEM2/London uses scene 0x1b.
@@ -4935,8 +4994,8 @@ void EEMEngine::doAccuse() {
scratch.simpleBlitFrom(frame.surface);
if (winScene < _sitesArchive.size() &&
_sitesArchive.loadEntry(winScene, scene)) {
- const int sx = scaleX(0x42);
- const int sy = scaleY(0x14);
+ const int sx = isMacCD() ? 106 : scaleX(0x42);
+ const int sy = isMacCD() ? 38 : scaleY(0x14);
const int sw = MIN<int>(scene.surface.w, screenW - sx);
const int sh = MIN<int>(scene.surface.h, screenH - sy);
if (sw > 0 && sh > 0)
@@ -4944,15 +5003,22 @@ void EEMEngine::doAccuse() {
scene.surface.pitch, sx, sy,
sw, sh);
}
+ if (isMacCD()) {
+ remapMacSurfaceEndpoints(scratch, getMacSpritePaletteMap());
+ setPartnerEraseBg(&scratch);
+ setPartnerIdleAnim(true, _partner == kPartnerJake ? 0x02 : 0x10, 7, 152);
+ }
blitPdaPartner(scratch, _aniArchive, _partner,
- kPdaGalleryPartner, g_system->getMillis(),
- isMacintosh());
+ isMacCD() ? kPdaNotebookPartner : kPdaGalleryPartner,
+ g_system->getMillis(), isMacintosh(), isMacCD());
g_system->copyRectToScreen(scratch.getPixels(), scratch.pitch,
0, 0, screenW, screenH);
g_system->updateScreen();
- if (_music && _voiceOn)
+ if (_music && (isMacCD() ? _musicOn : _voiceOn))
_music->playMus(5, /* loop= */ false);
+ if (isMacCD())
+ waitForMusicDone();
const byte *solved = _mystery.solvedClueBlock();
if (isMacintosh() && _mystery.usesCompactMacData() && solved) {
@@ -4982,6 +5048,15 @@ void EEMEngine::doAccuse() {
} else if (solved) {
displayClue(solved);
}
+ if (isMacCD()) {
+ setPartnerIdleAnim(false, 0, 0, 0);
+ setPartnerEraseBg(nullptr);
+ if (shouldQuit())
+ return;
+ if (mn < sizeof(_mysteriesSolved))
+ _mysteriesSolved[mn] = _mystery._firstTry ? 2 : 1;
+ advanceChainStageAfterSolve(mn);
+ }
if (_music && _voiceOn)
_music->stop();
@@ -4989,9 +5064,13 @@ void EEMEngine::doAccuse() {
playAnm(Common::Path(isLondon() ? "SCRAP.ANM" : "SCRAPBK.ANI"),
120, /* holdLastFrame= */ false);
displayScrapbookExtra(mn);
+ } else if (isMacCD()) {
+ fadeCurrentPaletteToBlack();
+ showStillPicture(0x20d, 0x25, 4000);
}
- doShowEnding(mn);
+ if (!shouldQuit())
+ doShowEnding(mn);
_mystery.clear();
const Common::Error err = saveProfile(_playerName);
Commit: 1710907c2d907e3208de599e88dcc2e6215cc500
https://github.com/scummvm/scummvm/commit/1710907c2d907e3208de599e88dcc2e6215cc500
Author: neuromancer (gustavo.grieco at gmail.com)
Date: 2026-09-10T17:45:23+02:00
Commit Message:
EEM: allow to come back to the map from sites in EEM1 (mac)
Changed paths:
engines/eem/site.cpp
diff --git a/engines/eem/site.cpp b/engines/eem/site.cpp
index 16e4f19bee9..93550c31ceb 100644
--- a/engines/eem/site.cpp
+++ b/engines/eem/site.cpp
@@ -228,6 +228,9 @@ bool readHotspotRect(const byte *r, bool mac, Common::Rect &rect) {
}
Common::Rect siteControlRect(const EEMEngine *vm, const Common::Rect &rect) {
+ // Mac CD SiteButtons retain the feet shortcut outside TRAVIS.
+ if (vm && vm->isMacCD() && rect == kPdaPartnerFootMapRect)
+ return Common::Rect(11, 340, 91, 384);
return pdaControlRect(vm, rect);
}
Commit: cee1993fd4909a43f69b8915d3f5b4c2c6fde5d8
https://github.com/scummvm/scummvm/commit/cee1993fd4909a43f69b8915d3f5b4c2c6fde5d8
Author: neuromancer (gustavo.grieco at gmail.com)
Date: 2026-09-10T17:45:24+02:00
Commit Message:
EEM: avoid busy waiting when playing music in EEM1 (mac)
Changed paths:
engines/eem/music.cpp
engines/eem/music.h
diff --git a/engines/eem/music.cpp b/engines/eem/music.cpp
index 3413e4ea309..4944db5c8cc 100644
--- a/engines/eem/music.cpp
+++ b/engines/eem/music.cpp
@@ -27,6 +27,7 @@
#include "common/debug.h"
#include "common/endian.h"
#include "common/file.h"
+#include "common/macresman.h"
#include "common/memstream.h"
#include "common/stream.h"
#include "common/system.h"
@@ -133,7 +134,7 @@ static Common::SeekableReadStream *expandMacMidiRunningStatus(Common::SeekableRe
return expanded.readStream(expanded.size());
}
-Common::SeekableReadStream *MusicPlayer::getResource(uint16 id, uint32 type) {
+void MusicPlayer::openMacMusicResources() {
static const char *const kMacMusicForks[] = {
"EEM Sound&Music",
"rsrc/EEM Sound&Music",
@@ -146,18 +147,33 @@ Common::SeekableReadStream *MusicPlayer::getResource(uint16 id, uint32 type) {
"rsrc/EEM London CD",
nullptr
};
+ const char *const *forks = _isLondon ? kMacLondonMusicForks : kMacMusicForks;
+ for (uint i = 0; forks[i]; i++)
+ _macResourcePaths.push_back(Common::Path(forks[i]));
+
+ // Keep installer forks open; reopening them decompresses the whole application.
+ _macResources = new Common::MacResManager[_macResourcePaths.size()];
+ for (uint i = 0; i < _macResourcePaths.size(); i++)
+ _macResources[i].open(_macResourcePaths[i]);
+}
+
+Common::SeekableReadStream *MusicPlayer::getMacMusicResource(uint file, uint16 id, uint32 type) {
+ if (_macResources[file].hasResFork())
+ return _macResources[file].getResource(type, id);
+ return openMacResource(_macResourcePaths[file], type, id);
+}
+
+Common::SeekableReadStream *MusicPlayer::getResource(uint16 id, uint32 type) {
static const uint32 kMacMidiTypes[] = {
MKTAG('c', 'm', 'i', 'd'),
MKTAG('M', 'I', 'D', 'I'),
MKTAG('M', 'i', 'd', 'i'),
};
- const char *const *forks = _isLondon ? kMacLondonMusicForks : kMacMusicForks;
- for (uint i = 0; forks[i]; i++) {
- const Common::Path path(forks[i]);
+ for (uint i = 0; i < _macResourcePaths.size(); i++) {
if (type == MKTAG('M', 'I', 'D', 'I') || type == MKTAG('M', 'i', 'd', 'i')) {
for (uint j = 0; j < ARRAYSIZE(kMacMidiTypes); j++) {
- Common::SeekableReadStream *stream = openMacResource(path, kMacMidiTypes[j], id);
+ Common::SeekableReadStream *stream = getMacMusicResource(i, id, kMacMidiTypes[j]);
if (stream) {
Common::SeekableReadStream *expanded = expandMacMidiRunningStatus(*stream);
delete stream;
@@ -169,7 +185,7 @@ Common::SeekableReadStream *MusicPlayer::getResource(uint16 id, uint32 type) {
continue;
} else if (type == MKTAG('s', 'n', 'd', ' ')) {
// London stores most instrument samples as delta-compressed csnd.
- Common::SeekableReadStream *packed = openMacResource(path, MKTAG('c', 's', 'n', 'd'), id);
+ Common::SeekableReadStream *packed = getMacMusicResource(i, id, MKTAG('c', 's', 'n', 'd'));
if (packed) {
Common::SeekableReadStream *stream = decompressMacSound(*packed);
delete packed;
@@ -177,7 +193,7 @@ Common::SeekableReadStream *MusicPlayer::getResource(uint16 id, uint32 type) {
}
}
- Common::SeekableReadStream *stream = openMacResource(path, type, id);
+ Common::SeekableReadStream *stream = getMacMusicResource(i, id, type);
if (stream)
return stream;
}
@@ -230,8 +246,10 @@ uint16 macSongResourceIdForMus(uint num, bool macCD) {
MusicPlayer::MusicPlayer(bool isFloppy, bool isMacintosh, bool isLondon, bool isMacCD) :
_isFloppy(isFloppy), _isMacintosh(isMacintosh), _isLondon(isLondon),
_isMacCD(isMacCD) {
- if (_isMacintosh)
+ if (_isMacintosh) {
+ openMacMusicResources();
return;
+ }
// _InitMIDI @ 20a2:013a â `_AIL_register_driver` against
// ADLIB.ADV / SBFM.ADV / MT32MPU.ADV. We honour the launcher's
@@ -285,6 +303,7 @@ MusicPlayer::MusicPlayer(bool isFloppy, bool isMacintosh, bool isLondon, bool is
MusicPlayer::~MusicPlayer() {
stop();
+ delete[] _macResources;
}
void MusicPlayer::stop() {
diff --git a/engines/eem/music.h b/engines/eem/music.h
index 47edef8d6a1..5628ba8999c 100644
--- a/engines/eem/music.h
+++ b/engines/eem/music.h
@@ -29,6 +29,10 @@
#include "common/path.h"
#include "common/scummsys.h"
+namespace Common {
+class MacResManager;
+}
+
namespace EEM {
/**
@@ -74,6 +78,8 @@ public:
void send(uint32 b) override;
private:
+ void openMacMusicResources();
+ Common::SeekableReadStream *getMacMusicResource(uint file, uint16 id, uint32 type);
void playMacSongResource(uint16 resourceId, bool loop);
void startLoadedMusic(const Common::String &name, bool loop);
Common::SeekableReadStream *getResource(uint16 id, uint32 type) override;
@@ -84,6 +90,8 @@ private:
const bool _isLondon;
const bool _isMacCD;
Common::Array<byte> _xmiData;
+ Common::Array<Common::Path> _macResourcePaths;
+ Common::MacResManager *_macResources = nullptr;
Audio::HalestormDriver *_macDriver = nullptr;
};
Commit: a67c973f3b32454ffb3ae870c90760207f54301c
https://github.com/scummvm/scummvm/commit/a67c973f3b32454ffb3ae870c90760207f54301c
Author: neuromancer (gustavo.grieco at gmail.com)
Date: 2026-09-10T17:45:24+02:00
Commit Message:
EEM: improved mouse pointer rendering for both release of EEM1 (mac)
Changed paths:
engines/eem/detection.cpp
engines/eem/eem.cpp
engines/eem/eem.h
diff --git a/engines/eem/detection.cpp b/engines/eem/detection.cpp
index 4b36092e8ed..00fab8b8bac 100644
--- a/engines/eem/detection.cpp
+++ b/engines/eem/detection.cpp
@@ -92,7 +92,7 @@ const ADGameDescription gameDescriptions[] = {
},
{
"eem",
- "",
+ "Floppy",
AD_ENTRY2s("MysteryData", "d94c087c27e68cc299d7c5e737e458f9", 941029,
"PICS.DBD", "8905041070ff1352666d98cd78d5501c", 3800445),
Common::EN_ANY,
@@ -106,7 +106,7 @@ const ADGameDescription gameDescriptions[] = {
// the fly (see installer.cpp). "d:" hashes the data fork so this matches
// both the raw-fork (CD/floppy) and MacBinary-wrapped captures.
"eem",
- "",
+ "Floppy",
AD_ENTRY2s("Eagle Eye Installer", "d:08440dbf0cb47fb57e522f050159ffaa", 1391577,
"EEM Install Data 2", "aebccc677e149b37285f291f6ac72f57", 1446428),
Common::EN_ANY,
diff --git a/engines/eem/eem.cpp b/engines/eem/eem.cpp
index eef0c2a640a..82b2413ae0a 100644
--- a/engines/eem/eem.cpp
+++ b/engines/eem/eem.cpp
@@ -36,6 +36,7 @@
#include "graphics/cursorman.h"
#include "graphics/maccursor.h"
+#include "graphics/macgui/macwindowmanager.h"
#include "graphics/paletteman.h"
#include "video/flic_decoder.h"
@@ -224,15 +225,22 @@ void setInteractiveCursorPalette(const Picture &cursor, byte transparent) {
CursorMan.replaceCursorPalette(palette, 0, 256);
}
-bool installMacLondonCursor(uint16 resourceId) {
- static const char *const kAppForks[] = {
+bool installMacCursor(uint16 resourceId, bool london) {
+ static const char *const kMacCDAppForks[] = {
+ "Eagle Eye Mysteries CD",
+ "rsrc/Eagle Eye Mysteries CD",
+ nullptr
+ };
+ static const char *const kLondonAppForks[] = {
"EEM London CD",
- "rsrc/EEM London CD"
+ "rsrc/EEM London CD",
+ nullptr
};
- for (uint i = 0; i < ARRAYSIZE(kAppForks); i++) {
+ const char *const *appForks = london ? kLondonAppForks : kMacCDAppForks;
+ for (uint i = 0; appForks[i]; i++) {
Common::ScopedPtr<Common::SeekableReadStream> crsrStream(
- openMacResource(Common::Path(kAppForks[i]),
+ openMacResource(Common::Path(appForks[i]),
MKTAG('c', 'r', 's', 'r'), resourceId));
if (crsrStream) {
Graphics::MacCursor macCursor;
@@ -248,12 +256,26 @@ bool installMacLondonCursor(uint16 resourceId) {
}
void installMouseCursor(DBDArchive &pics, bool interactive, bool mac,
- bool london) {
- if (mac && london) {
- // EEM2 (London) Mac keeps pointers as 'crsr' colour cursors in the
- // application resource fork; the DOS cursor PIC ids are mostly 1x1
- // stubs or unrelated full-screen pictures in this release.
- if (installMacLondonCursor(kLondonMacCursorCrsrs[0]))
+ bool london, bool macCD) {
+ if (mac && !london && !macCD) {
+ // Mac floppy CODE 2:484c selects QuickDraw's standard arrow.
+ const byte *data, *palette, *mask;
+ int width, height, hotspotX, hotspotY, transparent;
+ if (Graphics::MacWindowManager::getBuiltInCursorData(Graphics::kMacCursorArrow,
+ data, palette, mask, width, height, hotspotX, hotspotY, transparent)) {
+ CursorMan.replaceCursor(data, width, height, hotspotX, hotspotY,
+ transparent, nullptr, mask);
+ byte colors[6];
+ memcpy(colors, palette, sizeof(colors));
+ if (interactive)
+ colors[0] = 0xff;
+ CursorMan.replaceCursorPalette(colors, 0, 2);
+ return;
+ }
+ }
+ if (mac && (london || macCD)) {
+ // Mac CD CODE 2:3aea selects the same native arrow as Mac London.
+ if (installMacCursor(kLondonMacCursorCrsrs[0], london))
return;
}
@@ -296,7 +318,7 @@ EEMEngine::EEMEngine(OSystem *syst, const ADGameDescription *gameDesc)
_variant = kVariantFloppy;
if (gameDesc && gameDesc->platform == Common::kPlatformMacintosh)
_variant = gameDesc->extra && Common::String(gameDesc->extra).contains("CD")
- ? kVariantMacCD : kVariantMac;
+ ? kVariantMacCD : kVariantMacFloppy;
if (gameDesc && gameDesc->gameId &&
Common::String(gameDesc->gameId) == "eem2")
_variant = kVariantLondonCD;
@@ -570,7 +592,7 @@ Common::Error EEMEngine::run() {
_audio->setVoiceEnabled(_voiceOn);
syncSoundSettings();
- installMouseCursor(_picsArchive, false, isMacintosh(), isLondon());
+ installMouseCursor(_picsArchive, false, isMacintosh(), isLondon(), isMacCD());
CursorMan.showMouse(false);
// _AllBlack @ 172b:0d4b.
@@ -872,7 +894,13 @@ void EEMEngine::setInteractiveMouseCursor(bool active) {
return;
_interactiveMouseCursor = active;
- installMouseCursor(_picsArchive, active, isMacintosh(), isLondon());
+ if (isMacCD()) {
+ // Native arrow 132 uses white (0) and black (1); only recolour its fill.
+ const byte palette[] = { 0xff, 0xff, 0xff, (byte)(active ? 0xff : 0), 0, 0 };
+ CursorMan.replaceCursorPalette(palette, 0, 2);
+ return;
+ }
+ installMouseCursor(_picsArchive, active, isMacintosh(), isLondon(), isMacCD());
// The red-outline highlight replaced any London cursor shape; force the
// next setSiteHotspotCursorId to reinstall.
_siteCursorId = -1;
@@ -899,7 +927,7 @@ void EEMEngine::setSiteHotspotCursorId(int cursorId) {
uint16 resourceId = kLondonMacCursorCrsrs[cursorId];
if (cursorId == 4 || cursorId == 5)
resourceId = (_partner == kPartnerJenny) ? 139 : 138;
- if (!installMacLondonCursor(resourceId)) {
+ if (!installMacCursor(resourceId, true)) {
warning("EEM2 Mac: cursor %d ('crsr' %u) missing",
cursorId, resourceId);
return;
diff --git a/engines/eem/eem.h b/engines/eem/eem.h
index 7603a01fd8b..76c74a18ffe 100644
--- a/engines/eem/eem.h
+++ b/engines/eem/eem.h
@@ -100,11 +100,11 @@ enum ScreenId {
/// Distribution variant from `ADGameDescription::extra` (set by
/// `gameDescriptions[]` in `detection.cpp`).
enum Variant {
- kVariantCD = 0,
- kVariantFloppy = 1,
- kVariantLondonCD = 2,
- kVariantMac = 3,
- kVariantMacCD = 4,
+ kVariantCD = 0,
+ kVariantFloppy = 1,
+ kVariantLondonCD = 2,
+ kVariantMacFloppy = 3,
+ kVariantMacCD = 4,
};
/// `_Partner @ 29be:7918`. Selected at the partner-pick screen
@@ -198,8 +198,7 @@ public:
const EEMFont &getFont() const { return _font; }
uint8 getPartnerIndex() const { return _partner; }
- /// Interactive-region cursor. DOS/EEM1 uses a red-outline pointer; Mac
- /// London uses the original Color QuickDraw arrow.
+ /// Highlight interactive regions; Mac London retains its native colours.
void setInteractiveMouseCursor(bool active);
/// Interactive cursor over searchable hotspots.
Commit: b5d5892fb4f30ec01a4ac85a70688cdec8a86d9b
https://github.com/scummvm/scummvm/commit/b5d5892fb4f30ec01a4ac85a70688cdec8a86d9b
Author: neuromancer (gustavo.grieco at gmail.com)
Date: 2026-09-10T17:45:24+02:00
Commit Message:
EEM: enabled restored content for EEM1 (mac cd)
Changed paths:
engines/eem/audio.cpp
engines/eem/clues.cpp
engines/eem/detection.cpp
engines/eem/eem.cpp
engines/eem/eem.h
engines/eem/ui.cpp
diff --git a/engines/eem/audio.cpp b/engines/eem/audio.cpp
index 961bc84c0f4..29c0b6ece2e 100644
--- a/engines/eem/audio.cpp
+++ b/engines/eem/audio.cpp
@@ -478,6 +478,11 @@ bool AudioPlayer::playMacMysterySound(uint num) {
resourceId);
return false;
}
+ // Some unused speech resources contain only a two-sample placeholder.
+ if (audioStream->getLength().msecs() == 0) {
+ delete audioStream;
+ return false;
+ }
_mixer->playStream(Audio::Mixer::kSFXSoundType, &_spoolHandle,
audioStream, -1, Audio::Mixer::kMaxChannelVolume,
diff --git a/engines/eem/clues.cpp b/engines/eem/clues.cpp
index da80872b1b4..3d88c85b522 100644
--- a/engines/eem/clues.cpp
+++ b/engines/eem/clues.cpp
@@ -932,15 +932,16 @@ void EEMEngine::applyClueSideEffects(const byte *c) {
// +0..1: number (entry count; 0 = no briefing)
// +2..3: pic ID for entry 0; entry N>0 uses (entry-1).lastWord
// +4..: array of 62-byte entries
-void EEMEngine::displayClue(const byte *clueBlock) {
+void EEMEngine::displayClue(const byte *clueBlock, uint maxEntries) {
if (!clueBlock || !_mystery.isLoaded())
return;
const uint16 number = READ_LE_UINT16(clueBlock);
- debugC(1, kDebugScript, "displayClue: %u entries", number);
// number == 0 = no briefing (e.g. mystery 0 case-type 4); >32 = bad ptr.
if (number == 0 || number > 32)
return;
+ const uint count = MIN<uint>(number, maxEntries);
+ debugC(1, kDebugScript, "displayClue: %u entries", count);
const uint stride = isLondon() ? 0x54 : 62;
const bool mac = isMacintosh();
@@ -980,7 +981,7 @@ void EEMEngine::displayClue(const byte *clueBlock) {
// 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++) {
+ for (uint i = 0; i < count && !shouldQuit(); i++) {
if (isMacCD() && _audio)
_audio->stopSpool();
g_system->copyRectToScreen(bg.getPixels(), bg.pitch, 0, 0, sw, sh);
@@ -1254,7 +1255,7 @@ void EEMEngine::displayClue(const byte *clueBlock) {
g_system->delayMillis(10);
}
if (skipAll) {
- for (uint k = i; k < number; k++)
+ for (uint k = i; k < count; k++)
applyClueSideEffects(clueBlock + 4 + k * stride);
return;
}
diff --git a/engines/eem/detection.cpp b/engines/eem/detection.cpp
index 00fab8b8bac..29e1033a925 100644
--- a/engines/eem/detection.cpp
+++ b/engines/eem/detection.cpp
@@ -47,6 +47,7 @@ static const char *const directoryGlobs[] = {
#define GUI_OPTIONS_EEM_CD GUIO6(GAMEOPTION_HIDE_HIGHLIGHT_BOXES, GAMEOPTION_FIT_DIALOG_BALLOONS, GAMEOPTION_SKIP_REPEATED_CASES, GAMEOPTION_RESTORED_CONTENT, GUIO_MIDIADLIB, GUIO_MIDIMT32)
#define GUI_OPTIONS_EEM_DEMO GUIO2(GAMEOPTION_HIDE_HIGHLIGHT_BOXES, GUIO_NOMIDI)
#define GUI_OPTIONS_EEM_MAC GUIO3(GAMEOPTION_HIDE_HIGHLIGHT_BOXES, GAMEOPTION_FIT_DIALOG_BALLOONS, GUIO_NOMIDI)
+#define GUI_OPTIONS_EEM_MAC_CD GUIO4(GAMEOPTION_HIDE_HIGHLIGHT_BOXES, GAMEOPTION_FIT_DIALOG_BALLOONS, GAMEOPTION_RESTORED_CONTENT, GUIO_NOMIDI)
const ADGameDescription gameDescriptions[] = {
{
@@ -122,7 +123,7 @@ const ADGameDescription gameDescriptions[] = {
Common::EN_ANY,
Common::kPlatformMacintosh,
ADGF_UNSTABLE,
- GUI_OPTIONS_EEM_MAC
+ GUI_OPTIONS_EEM_MAC_CD
},
{
// Eagle Eye Mysteries in London
diff --git a/engines/eem/eem.cpp b/engines/eem/eem.cpp
index 82b2413ae0a..2ad0385e342 100644
--- a/engines/eem/eem.cpp
+++ b/engines/eem/eem.cpp
@@ -1807,21 +1807,25 @@ void EEMEngine::showLondonEAKidsLogo() {
}
void EEMEngine::showStillPicture(uint picId, uint palId, uint holdMs,
- bool playThunder) {
+ bool playThunder, bool holdLastFrame) {
Picture pic;
if (!_picsArchive.getPicture(picId, pic) || pic.surface.empty()) {
warning("PIC 0x%x load failed", picId);
return;
}
- blitAt(pic, 0, 0);
-
byte target[kPalSize];
if (!getSitePalette(palId, target)) {
warning("Palette 0x%x load failed", palId);
return;
}
+ // Mac _LoadPalette puts white at 0 and black at 255.
+ if (isMacintosh() && target[0] == 0 && target[1] == 0 && target[2] == 0) {
+ for (uint i = 0; i < 3; i++)
+ SWAP(target[i], target[255 * 3 + i]);
+ }
byte black[kPalSize] = {};
g_system->getPaletteManager()->setPalette(black, 0, 256);
+ blitAt(pic, 0, 0);
g_system->updateScreen();
fadePaletteFromBlack(target);
@@ -1831,7 +1835,8 @@ void EEMEngine::showStillPicture(uint picId, uint palId, uint holdMs,
waitForInput(holdMs);
if (_audio)
_audio->stopVoice();
- fadeCurrentPaletteToBlack();
+ if (!holdLastFrame)
+ fadeCurrentPaletteToBlack();
}
bool EEMEngine::startLondonTrainingMystery() {
diff --git a/engines/eem/eem.h b/engines/eem/eem.h
index 76c74a18ffe..d293ed3e3af 100644
--- a/engines/eem/eem.h
+++ b/engines/eem/eem.h
@@ -210,7 +210,7 @@ public:
void setSiteHotspotCursorId(int cursorId);
/// `_DisplayClue @ 2404:05e6`.
- void displayClue(const byte *clueBlock);
+ void displayClue(const byte *clueBlock, uint maxEntries = 32);
/// EEM2/London `_DoPuzzle @ 2542:1482`. A clue entry can gate the rest of
/// itself behind a "check the manual / a real map" puzzle
@@ -364,6 +364,7 @@ private:
void floppyKDHint(uint kdSlot, const byte *kdIdx,
const byte *bufBase, uint32 mysSize);
void displayScrapbookExtra(uint mysteryNum);
+ void displayMacPracticeScrapbook();
void accuseDrawGallery(int highlighted,
Common::Array<Common::Rect> &rects,
Common::Array<int> &suspects, uint8 num,
@@ -511,7 +512,7 @@ private:
bool startLondonTrainingMystery();
void showLondonEAKidsLogo();
void showStillPicture(uint picId, uint palId, uint holdMs,
- bool playThunder = false);
+ bool playThunder = false, bool holdLastFrame = false);
void showLondonCharSelect();
void playLondonInitCluesAnim(uint16 caseType, const Picture &bg,
bool haveBriefingBg);
diff --git a/engines/eem/ui.cpp b/engines/eem/ui.cpp
index 4b9e002bbd4..de7851710a6 100644
--- a/engines/eem/ui.cpp
+++ b/engines/eem/ui.cpp
@@ -255,6 +255,7 @@ constexpr Common::Rect kEndingNextPageRect(Common::Point(292, 0), 28, kScreenHei
constexpr uint16 kFloppyEndingBackgroundPic = 0x8b;
constexpr uint16 kFirstTryBadgePic = 0x205;
constexpr Common::Point kFirstTryBadgePos(0x1e, 9);
+constexpr Common::Point kMacFirstTryBadgePos(43, 13);
constexpr uint kMacMysteryDataTableOffset = 0x08cd;
constexpr uint kMacMysteryDataMysteryCount = 55;
constexpr uint kMacMysteryDataEndingCount = 55;
@@ -769,6 +770,15 @@ bool restoredContentVoiceAppliesTo(uint mysteryNum) {
mysteryNum <= kRestoredContentLastMystery;
}
+Common::Point scrapbookExtraPosition(uint16 x, uint16 y, bool mac) {
+ if (!mac)
+ return Common::Point(x, y);
+ // Match the fixed-point conversion used by the Mac floppy scripts.
+ const uint32 xScale = (kMacScreenWidth << 16) / kScreenWidth;
+ const uint32 yScale = (kMacScreenHeight << 16) / kScreenHeight;
+ return Common::Point((x * xScale) >> 16, (y * yScale) >> 16);
+}
+
bool gallerySlotAt(const Common::Array<Common::Rect> &rects,
const Common::Array<int> &suspects, int x, int y) {
for (uint i = 0; i < rects.size() && i < suspects.size(); i++) {
@@ -1817,7 +1827,7 @@ int EEMEngine::doShowEnding(uint num, bool firstPage) {
const bool showFirstTryBadge =
num < sizeof(_mysteriesSolved) && _mysteriesSolved[num] == 2 &&
- !macCDEnding && (floppyEnding || ConfMan.getBool("restored_content"));
+ (floppyEnding || ConfMan.getBool("restored_content"));
Picture firstTryBadge;
const bool haveFirstTryBadge =
showFirstTryBadge &&
@@ -1910,9 +1920,14 @@ int EEMEngine::doShowEnding(uint num, bool firstPage) {
}
if (pageIdx == 0 && haveFirstTryBadge) {
- const byte transp = (byte)(firstTryBadge.flags >> 8);
- scratch.transBlitFrom(firstTryBadge.surface,
- kFirstTryBadgePos, transp);
+ if (macEnding) {
+ blitMacMaskedSurface(scratch.surfacePtr(), firstTryBadge,
+ kMacFirstTryBadgePos.x, kMacFirstTryBadgePos.y);
+ } else {
+ const byte transp = (byte)(firstTryBadge.flags >> 8);
+ scratch.transBlitFrom(firstTryBadge.surface,
+ kFirstTryBadgePos, transp);
+ }
}
const Common::String text = parseString(raw, _playerName, _partner);
@@ -4973,6 +4988,8 @@ void EEMEngine::doAccuse() {
{
const uint mn = _mystery.number();
+ const bool macRestored = isMacCD() && ConfMan.getBool("restored_content") &&
+ (mn == 0 || _restoredContentDataLoaded);
if (!isMacCD()) {
if (mn < sizeof(_mysteriesSolved))
_mysteriesSolved[mn] = _mystery._firstTry ? 2 : 1;
@@ -5046,7 +5063,9 @@ void EEMEngine::doAccuse() {
displayFloppyDialogRecords(records, count, 1);
}
} else if (solved) {
- displayClue(solved);
+ const uint count = READ_LE_UINT16(solved);
+ // The practice CD script already includes the three scrapbook lines.
+ displayClue(solved, macRestored && mn == 0 && count > 3 ? count - 3 : count);
}
if (isMacCD()) {
setPartnerIdleAnim(false, 0, 0, 0);
@@ -5066,7 +5085,11 @@ void EEMEngine::doAccuse() {
displayScrapbookExtra(mn);
} else if (isMacCD()) {
fadeCurrentPaletteToBlack();
- showStillPicture(0x20d, 0x25, 4000);
+ showStillPicture(0x20d, 0x25, 4000, false, macRestored);
+ if (!shouldQuit() && macRestored) {
+ displayScrapbookExtra(mn);
+ fadeCurrentPaletteToBlack();
+ }
}
if (!shouldQuit())
@@ -5155,12 +5178,50 @@ void EEMEngine::floppyKDHint(uint kdSlot, const byte *kdIdx,
}
}
+void EEMEngine::displayMacPracticeScrapbook() {
+ const byte *solved = _mystery.solvedClueBlock();
+ const uint count = solved ? READ_LE_UINT16(solved) : 0;
+ if (count <= 3 || count > 32)
+ return;
+
+ byte dialogue[4 + 3 * 62] = {};
+ WRITE_LE_UINT16(dialogue, 3);
+ memcpy(dialogue + 4, solved + 4 + (count - 3) * 62, 3 * 62);
+
+ // Keep the CD partner's text and speech, using the seated balloon positions.
+ const Common::Point position = _partner == kPartnerJake
+ ? Common::Point(31, 32) : Common::Point(135, 28);
+ for (uint i = 0; i < 3; i++) {
+ byte *entry = dialogue + 4 + i * 62;
+ for (uint partner = 0; partner < 2; partner++) {
+ WRITE_LE_UINT16(entry + 12 + partner * 2, 0x98);
+ WRITE_LE_UINT16(entry + 16 + partner * 4, position.x);
+ WRITE_LE_UINT16(entry + 18 + partner * 4, position.y);
+ }
+ WRITE_LE_UINT16(entry + 58, 0xFFFF);
+ WRITE_LE_UINT16(entry + 60, 0);
+ }
+ displayClue(dialogue);
+}
+
void EEMEngine::displayScrapbookExtra(uint mysteryNum) {
if (isFloppy() || !ConfMan.getBool("restored_content") ||
- !_restoredContentDataLoaded ||
mysteryNum >= kScrapbookExtraCaseCount || !_font.isLoaded())
return;
+ if (isMacCD() && mysteryNum == 0) {
+ displayMacPracticeScrapbook();
+ return;
+ }
+ if (!_restoredContentDataLoaded)
+ return;
+
+ const bool mac = isMacCD();
+ const int sw = screenWidth();
+ const int sh = screenHeight();
+ const EEMFont &dialogFont = mac && _dialogFont.isLoaded() ? _dialogFont : _font;
+ const MacSpritePaletteMap paletteMap = mac ? getMacSpritePaletteMap() : MacSpritePaletteMap();
+
Common::File file;
if (!file.open(Common::Path(kScrapbookExtraFilename))) {
warning("EEM restored content unavailable: %s missing",
@@ -5193,7 +5254,7 @@ void EEMEngine::displayScrapbookExtra(uint mysteryNum) {
!file.seek(recordsOffset))
return;
- Graphics::ManagedSurface bg(kScreenWidth, kScreenHeight,
+ Graphics::ManagedSurface bg(sw, sh,
Graphics::PixelFormat::createFormatCLUT8());
bg.clear();
if (Graphics::Surface *screen = g_system->lockScreen()) {
@@ -5215,6 +5276,8 @@ void EEMEngine::displayScrapbookExtra(uint mysteryNum) {
const uint16 voiceNancy = file.readUint16LE();
const uint16 jakeTextSize = file.readUint16LE();
const uint16 jennyTextSize = file.readUint16LE();
+ const Common::Point picPos = scrapbookExtraPosition(picX, picY, mac);
+ const Common::Point balloonPos = scrapbookExtraPosition(balloonX, balloonY, mac);
if (file.eos() || file.err())
return;
@@ -5231,18 +5294,21 @@ void EEMEngine::displayScrapbookExtra(uint mysteryNum) {
if (text.empty())
continue;
- Graphics::ManagedSurface scratch(kScreenWidth, kScreenHeight,
+ Graphics::ManagedSurface scratch(sw, sh,
Graphics::PixelFormat::createFormatCLUT8());
scratch.simpleBlitFrom(*bg.surfacePtr());
if (picId != 0 && picId != 0xFFFF) {
Picture pic;
if (_picsArchive.getPicture(picId, pic) &&
- picX < kScreenWidth && picY < kScreenHeight) {
- const byte transp = (byte)(pic.flags >> 8);
- scratch.transBlitFrom(pic.surface,
- Common::Point(picX, picY),
- (uint32)transp);
+ picPos.x < sw && picPos.y < sh) {
+ if (mac) {
+ blitMacMaskedSurface(scratch.surfacePtr(), pic,
+ picPos.x, picPos.y, false, paletteMap);
+ } else {
+ const byte transp = (byte)(pic.flags >> 8);
+ scratch.transBlitFrom(pic.surface, picPos, (uint32)transp);
+ }
}
}
@@ -5258,20 +5324,27 @@ void EEMEngine::displayScrapbookExtra(uint mysteryNum) {
uint16 textYInset = 4;
uint16 textWidth = 155;
if (haveBalloon) {
- const byte transp = (byte)(balloon.flags >> 8);
- scratch.transBlitFrom(balloon.surface,
- Common::Point(balloonX, balloonY),
- (uint32)transp, flipBalloon);
+ if (mac) {
+ blitMacMaskedSurface(scratch.surfacePtr(), balloon,
+ balloonPos.x, balloonPos.y, flipBalloon, paletteMap);
+ } else {
+ const byte transp = (byte)(balloon.flags >> 8);
+ scratch.transBlitFrom(balloon.surface, balloonPos,
+ (uint32)transp, flipBalloon);
+ }
getBalloonInsets(balloonId, textXInset, textYInset, textWidth);
}
- _font.drawWordWrapped(&scratch, balloonX + textXInset,
- balloonY + textYInset,
- MAX<int>(8, (int)textWidth), text,
- haveBalloon ? 0 : 0xF);
+ if (mac) {
+ dialogFont.drawMacWordWrapped(&scratch, balloonPos.x + textXInset,
+ balloonPos.y + textYInset, MAX<int>(8, textWidth), text, paletteMap.black);
+ } else {
+ dialogFont.drawWordWrapped(&scratch, balloonPos.x + textXInset,
+ balloonPos.y + textYInset, MAX<int>(8, textWidth), text, haveBalloon ? 0 : 0xF);
+ }
g_system->copyRectToScreen(scratch.getPixels(), scratch.pitch,
- 0, 0, kScreenWidth, kScreenHeight);
+ 0, 0, sw, sh);
g_system->updateScreen();
if (_audio && restoredContentVoiceAppliesTo(mysteryNum)) {
More information about the Scummvm-git-logs
mailing list