[Scummvm-git-logs] scummvm master -> 029fcb42b700663140341694d53513630db11c69
neuromancer
noreply at scummvm.org
Fri Sep 11 06:31:07 UTC 2026
This automated email contains information about 1 new commit which have been
pushed to the 'scummvm' repo located at https://api.github.com/repos/scummvm/scummvm .
Summary:
029fcb42b7 EEM: added missing .cpp file
Commit: 029fcb42b700663140341694d53513630db11c69
https://github.com/scummvm/scummvm/commit/029fcb42b700663140341694d53513630db11c69
Author: neuromancer (gustavo.grieco at gmail.com)
Date: 2026-09-11T08:30:54+02:00
Commit Message:
EEM: added missing .cpp file
Changed paths:
A engines/eem/mac_ui.cpp
diff --git a/engines/eem/mac_ui.cpp b/engines/eem/mac_ui.cpp
new file mode 100644
index 00000000000..a72acee1a6e
--- /dev/null
+++ b/engines/eem/mac_ui.cpp
@@ -0,0 +1,471 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "common/ptr.h"
+#include "common/system.h"
+#include "graphics/cursorman.h"
+#include "graphics/macgui/macfontmanager.h"
+#include "graphics/managed_surface.h"
+#include "graphics/paletteman.h"
+#include "image/pict.h"
+
+#include "eem/audio.h"
+#include "eem/eem.h"
+#include "eem/music.h"
+#include "eem/site.h"
+
+namespace EEM {
+
+namespace {
+
+struct MacPuzzleItem {
+ Common::Rect rect;
+ byte type;
+ uint16 picture;
+ Common::String text;
+};
+
+Common::Rect readPuzzleRect(Common::SeekableReadStream &stream) {
+ const int top = stream.readSint16BE();
+ const int left = stream.readSint16BE();
+ const int bottom = stream.readSint16BE();
+ const int right = stream.readSint16BE();
+ return Common::Rect(left, top, right, bottom);
+}
+
+bool loadPuzzleDialog(uint16 id, Common::Rect &rect, Common::Array<MacPuzzleItem> &items) {
+ Common::ScopedPtr<Common::SeekableReadStream> dialog(
+ openMacResource(Common::Path("EEM London CD"), MKTAG('D', 'L', 'O', 'G'), id));
+ if (!dialog || dialog->size() < 20)
+ return false;
+ rect = readPuzzleRect(*dialog);
+ dialog->seek(18);
+ Common::ScopedPtr<Common::SeekableReadStream> list(
+ openMacResource(Common::Path("EEM London CD"), MKTAG('D', 'I', 'T', 'L'), dialog->readUint16BE()));
+ if (!list || list->size() < 2)
+ return false;
+ const uint count = list->readUint16BE() + 1;
+ for (uint i = 0; i < count; ++i) {
+ if (list->size() - list->pos() < 14)
+ return false;
+ list->skip(4);
+ MacPuzzleItem item;
+ item.rect = readPuzzleRect(*list);
+ item.rect.translate(rect.left, rect.top);
+ item.type = list->readByte();
+ const uint size = list->readByte();
+ if (size > list->size() - list->pos())
+ return false;
+ item.picture = 0;
+ if ((item.type & 0x7f) == 64 && size == 2) {
+ item.picture = list->readUint16BE();
+ } else {
+ for (uint j = 0; j < size; ++j)
+ item.text += (char)list->readByte();
+ }
+ if (size & 1)
+ list->skip(1);
+ items.push_back(item);
+ }
+ return !list->err();
+}
+
+bool loadPuzzlePicture(uint16 id, Graphics::ManagedSurface &surface, Graphics::Palette &palette,
+ bool usePicturePalette = false) {
+ Common::ScopedPtr<Common::SeekableReadStream> stream(
+ openMacResource(Common::Path("EEM London CD"), MKTAG('P', 'I', 'C', 'T'), id));
+ Image::PICTDecoder decoder;
+ if (!stream || !decoder.loadStream(*stream) || !decoder.getSurface())
+ return false;
+ const Graphics::Palette &sourcePalette = decoder.getPalette();
+ if (usePicturePalette && sourcePalette.size())
+ palette.set(sourcePalette.data(), 0, sourcePalette.size());
+ Graphics::Surface *converted = decoder.getSurface()->convertTo(
+ Graphics::PixelFormat::createFormatCLUT8(), sourcePalette.data(), sourcePalette.size(),
+ palette.data(), palette.size());
+ if (!converted)
+ return false;
+ surface.copyFrom(*converted);
+ converted->free();
+ delete converted;
+ return true;
+}
+
+void drawPuzzleButton(Graphics::ManagedSurface &surface, const Common::Rect &rect, byte color) {
+ surface.hLine(rect.left + 3, rect.top, rect.right - 4, color);
+ surface.hLine(rect.left + 3, rect.bottom - 1, rect.right - 4, color);
+ surface.vLine(rect.left, rect.top + 3, rect.bottom - 4, color);
+ surface.vLine(rect.right - 1, rect.top + 3, rect.bottom - 4, color);
+ surface.drawLine(rect.left, rect.top + 3, rect.left + 3, rect.top, color);
+ surface.drawLine(rect.right - 4, rect.top, rect.right - 1, rect.top + 3, color);
+ surface.drawLine(rect.left, rect.bottom - 4, rect.left + 3, rect.bottom - 1, color);
+ surface.drawLine(rect.right - 4, rect.bottom - 1, rect.right - 1, rect.bottom - 4, color);
+}
+
+} // namespace
+
+// Mac London CODE 6:5554/5a1a; the Mac release has no TRAVEL*.ANM files.
+bool EEMEngine::playMacLondonTravelAnimation(uint8 travelKind) {
+ static const struct TravelScene {
+ uint16 background, palette;
+ uint16 body[2], animation[2], vehicle[2][2];
+ int partnerX[2], partnerY, vehicleY[2];
+ int step;
+ uint frameDelay;
+ } kScenes[] = {
+ { 0x314, 0x38, { 0x315, 0x316 }, { 0x20, 0x21 },
+ { { 0x2bf, 0x2bf }, { 0x2bf, 0x2bf } }, { 32, 32 }, 88, { 307, 30 }, 9, 48 },
+ { 0x2bc, 0x37, { 0x2ba, 0x2b9 }, { 0x0e, 0x23 },
+ { { 0x2b7, 0x2b8 }, { 0x2b7, 0x2b8 } }, { 0, 1 }, 88, { 311, 25 }, 8, 60 },
+ { 0x2ae, 0x41, { 0, 0 }, { 0x22, 0x22 },
+ { { 0x2b4, 0x2b3 }, { 0x2b2, 0x2b1 } }, { 1, 1 }, 84, { 311, 15 }, 12, 80 }
+ };
+ if (travelKind == 0 || travelKind > ARRAYSIZE(kScenes))
+ return false;
+ const TravelScene &scene = kScenes[travelKind - 1];
+ const uint partner = _partner == kPartnerJake ? 0 : 1;
+ const bool train = travelKind == 1;
+ Picture background, body, vehicles[2];
+ Animation animation, doors;
+ byte palette[kPalSize];
+ if (!getSitePalette(scene.palette, palette) ||
+ !_picsArchive.getPicture(scene.background, background) ||
+ !_picsArchive.getPicture(scene.vehicle[partner][0], vehicles[0]) ||
+ !_picsArchive.getPicture(scene.vehicle[partner][1], vehicles[1]) ||
+ !_aniArchive.loadAnimation(scene.animation[partner], animation) || animation.empty() ||
+ (scene.body[partner] && !_picsArchive.getPicture(scene.body[partner], body)) ||
+ (train && (!_aniArchive.loadAnimation(0x1f, doors) || doors.empty()))) {
+ warning("Mac London travel: unable to load scene %u", travelKind);
+ return false;
+ }
+
+ const bool cursorVisible = CursorMan.isVisible();
+ CursorMan.showMouse(false);
+ fadeCurrentPaletteToBlack();
+ const Graphics::Palette colors(palette, 256);
+ const MacSpritePaletteMap paletteMap = {
+ colors.findBestColor(255, 255, 255), colors.findBestColor(0, 0, 0)
+ };
+ Graphics::ManagedSurface base(screenWidth(), screenHeight(), Graphics::PixelFormat::createFormatCLUT8());
+ base.simpleBlitFrom(background.surface);
+ remapMacSurfaceEndpoints(base, paletteMap);
+ if (scene.body[partner])
+ blitMacMaskedSurface(base.surfacePtr(), body, 0, 87, false, paletteMap);
+ Graphics::ManagedSurface frame(base.w, base.h, base.format);
+ uint partnerFrame = train ? 0 : MIN<uint>(1, animation.size() - 1);
+ uint doorFrame = train ? doors.size() - 1 : 0;
+ auto drawFrame = [&](uint pass, int x) {
+ frame.simpleBlitFrom(base);
+ blitMacAnimFrameAnchored(frame.surfacePtr(), vehicles[pass], x, scene.vehicleY[pass], paletteMap);
+ blitMacAnimFrameAnchored(frame.surfacePtr(), animation[partnerFrame],
+ scene.partnerX[partner], scene.partnerY, paletteMap);
+ if (train)
+ blitMacAnimFrameAnchored(frame.surfacePtr(), doors[doorFrame], 311, 155, paletteMap);
+ g_system->copyRectToScreen(frame.getPixels(), frame.pitch, 0, 0, frame.w, frame.h);
+ g_system->updateScreen();
+ };
+ drawFrame(0, (base.w + vehicles[0].surface.w) & ~3);
+ fadePaletteFromBlack(palette);
+ startLondonTravelMusic(travelKind);
+
+ // Pace the original's CPU-dependent travel loop at 30 Hz.
+ const uint kTravelUpdatesPerSecond = 30;
+ const uint32 startMs = g_system->getMillis();
+ uint tick = 1;
+ uint frameClock = 0;
+ bool skipped = false;
+ for (uint pass = 0; pass < 2 && !skipped && !shouldQuit(); ++pass) {
+ const int width = vehicles[pass].surface.w;
+ int x = (pass ? -width : base.w + width) & ~3;
+ const int step = pass ? scene.step : -scene.step;
+ const uint frameDelay = train && pass ? 128 : scene.frameDelay;
+ while (!skipped && !shouldQuit()) {
+ Common::Event event;
+ while (g_system->getEventManager()->pollEvent(event)) {
+ if (event.type == Common::EVENT_QUIT || event.type == Common::EVENT_RETURN_TO_LAUNCHER ||
+ event.type == Common::EVENT_KEYDOWN || event.type == Common::EVENT_LBUTTONDOWN)
+ skipped = true;
+ }
+ if (skipped || shouldQuit())
+ break;
+ const int32 remaining = startMs + tick * 1000 / kTravelUpdatesPerSecond - g_system->getMillis();
+ if (remaining > 0) {
+ g_system->delayMillis(MIN<int32>(remaining, 10));
+ continue;
+ }
+ ++tick;
+ x += step;
+ drawFrame(pass, x);
+ frameClock += 6;
+ if (frameClock > frameDelay) {
+ if (train) {
+ if (!pass) {
+ if (doorFrame)
+ --doorFrame;
+ if (!doorFrame)
+ partnerFrame = (partnerFrame + 1) % animation.size();
+ } else {
+ if (x < 520)
+ partnerFrame = (partnerFrame + 1) % animation.size();
+ if (x > 500)
+ doorFrame = MIN<uint>(doorFrame + 1, doors.size() - 1);
+ }
+ } else if (pass) {
+ partnerFrame = MIN<uint>(partnerFrame + 1, animation.size() - 1);
+ } else {
+ partnerFrame = (partnerFrame + 1) % animation.size();
+ }
+ frameClock = 0;
+ }
+ if (pass ? x > base.w : x < -width)
+ break;
+ }
+ }
+ if (!shouldQuit())
+ fadeCurrentPaletteToBlack();
+ finishTravelMusic(skipped);
+ CursorMan.showMouse(cursorVisible);
+ return true;
+}
+
+// Mac London CODE 7:4598 uses DLOG/DITL controls and PICT resources.
+bool EEMEngine::doMacLondonPuzzle(Common::SeekableReadStream &stream) {
+ const uint type = stream.readUint16BE();
+ const uint16 dialogId = stream.readUint16BE();
+ const uint16 backgroundId = stream.readUint16BE();
+ stream.skip(2);
+ const uint pictureCount = stream.readUint16BE();
+ if (type > 2 || pictureCount > 10 || stream.size() - stream.pos() < pictureCount * 6 + 6)
+ return false;
+ Common::Array<uint16> pictureIds;
+ for (uint i = 0; i < pictureCount; ++i) {
+ pictureIds.push_back(stream.readUint16BE());
+ stream.skip(4);
+ }
+ stream.skip(6);
+ const Common::String question = parseString(stream.readLine(), _playerName, _partner);
+ Common::String answer;
+ Common::Array<Common::Rect> selectionRects;
+ if (type == 0) {
+ stream.skip(8);
+ answer = stream.readLine();
+ } else {
+ const uint count = stream.readUint16BE();
+ if (count > pictureCount || stream.size() - stream.pos() < count * 8)
+ return false;
+ for (uint i = 0; i < count; ++i) {
+ if (type == 1)
+ selectionRects.push_back(readPuzzleRect(stream));
+ else
+ stream.skip(8);
+ }
+ }
+ Common::Rect dialogRect;
+ Common::Array<MacPuzzleItem> items;
+ if (stream.err() || !loadPuzzleDialog(dialogId, dialogRect, items) || items.size() < 5) {
+ warning("Unable to load Mac London puzzle dialog %u", dialogId);
+ return false;
+ }
+ for (uint i = 0; i < selectionRects.size(); ++i)
+ selectionRects[i].translate(dialogRect.left, dialogRect.top);
+
+ byte oldPalette[kPalSize];
+ g_system->getPaletteManager()->grabPalette(oldPalette, 0, 256);
+ Graphics::Palette palette(oldPalette, 256);
+ Graphics::ManagedSurface background;
+ if (!loadPuzzlePicture(backgroundId, background, palette, true)) {
+ warning("Mac London puzzle PICT %u missing", backgroundId);
+ return false;
+ }
+ const byte black = palette.findBestColor(0, 0, 0);
+ const byte white = palette.findBestColor(255, 255, 255);
+ Graphics::ManagedSurface saved(screenWidth(), screenHeight(), Graphics::PixelFormat::createFormatCLUT8());
+ Graphics::Surface *screen = g_system->lockScreen();
+ if (!screen)
+ return false;
+ saved.simpleBlitFrom(*screen);
+ g_system->unlockScreen();
+ Graphics::ManagedSurface base;
+ Graphics::Surface *converted = saved.surfacePtr()->convertTo(
+ Graphics::PixelFormat::createFormatCLUT8(), oldPalette, 256, palette.data(), palette.size());
+ base.copyFrom(*converted);
+ converted->free();
+ delete converted;
+ base.fillRect(dialogRect, white);
+ Common::Rect border = dialogRect;
+ border.grow(1);
+ base.frameRect(border, black);
+ for (uint i = 0; i < items.size(); ++i) {
+ if (!items[i].picture)
+ continue;
+ Graphics::ManagedSurface picture;
+ if (items[i].picture == backgroundId)
+ picture.copyFrom(background);
+ else if (!loadPuzzlePicture(items[i].picture, picture, palette))
+ continue;
+ base.blitFrom(picture, Common::Rect(picture.w, picture.h), items[i].rect);
+ }
+ Common::Array<Graphics::ManagedSurface> selectionPictures;
+ selectionPictures.resize(selectionRects.size());
+ for (uint i = 0; i < selectionPictures.size(); ++i)
+ loadPuzzlePicture(pictureIds[i], selectionPictures[i], palette);
+ Graphics::MacFontManager fontManager(0, Common::EN_ANY);
+ const Graphics::Font *font = fontManager.getFont(Graphics::MacFont(Graphics::kMacFontSystem, 12));
+ const int lineHeight = font->getFontHeight();
+ Common::Array<uint> controls;
+ for (uint i = 0; i < items.size(); ++i) {
+ if (!(items[i].type & 0x80))
+ controls.push_back(i);
+ }
+ int selected = -1;
+ uint focus = 0;
+ Common::String input;
+ uint caret = 0;
+ bool accepted = false;
+ bool done = false;
+ setInteractiveMouseCursor(false);
+ g_system->getPaletteManager()->setPalette(palette.data(), 0, 256);
+ if (type == 0)
+ g_system->setFeatureState(OSystem::kFeatureVirtualKeyboard, true);
+ while (!done && !shouldQuit()) {
+ Graphics::ManagedSurface frame;
+ frame.copyFrom(base);
+ if (selected >= 0 && (uint)selected < selectionPictures.size() && !selectionPictures[selected].empty())
+ frame.blitFrom(selectionPictures[selected], Common::Rect(selectionPictures[selected].w,
+ selectionPictures[selected].h), selectionRects[selected]);
+ for (uint i = 0; i < items.size(); ++i) {
+ const MacPuzzleItem &item = items[i];
+ const byte itemType = item.type & 0x7f;
+ const Common::Rect &r = item.rect;
+ if (itemType == 4) {
+ drawPuzzleButton(frame, r, black);
+ font->drawString(frame.surfacePtr(), item.text, r.left,
+ r.top + (r.height() - lineHeight) / 2, r.width(), black, Graphics::kTextAlignCenter);
+ if (i == 0 && (type != 0 || !input.empty())) {
+ Common::Rect ring = r;
+ ring.grow(3);
+ drawPuzzleButton(frame, ring, black);
+ }
+ } else if (itemType == 6) {
+ frame.drawEllipse(r.left, r.top + 3, r.left + 11, r.top + 14, black, false);
+ if (selected == (int)i - 3)
+ frame.drawEllipse(r.left + 3, r.top + 6, r.left + 8, r.top + 11, black, true);
+ font->drawString(frame.surfacePtr(), item.text, r.left + 18, r.top, r.width() - 18, black);
+ } else if (itemType == 8) {
+ Common::Array<Common::String> lines;
+ font->wordWrapText(item.text == "^0" ? question : item.text, r.width(), lines);
+ for (uint j = 0; j < lines.size(); ++j)
+ font->drawString(frame.surfacePtr(), lines[j], r.left, r.top + j * lineHeight, r.width(), black);
+ } else if (itemType == 16) {
+ frame.fillRect(r, white);
+ Common::Rect edge = r;
+ edge.grow(3);
+ frame.frameRect(edge, black);
+ font->drawString(frame.surfacePtr(), input, r.left, r.top, r.width(), black);
+ if ((g_system->getMillis() / 400) & 1) {
+ const int x = MIN<int>(r.right - 1, r.left + font->getStringWidth(input.substr(0, caret)));
+ frame.vLine(x, r.top, r.top + lineHeight - 1, black);
+ }
+ }
+ }
+ g_system->copyRectToScreen(frame.getPixels(), frame.pitch, 0, 0, frame.w, frame.h);
+ g_system->updateScreen();
+ Common::Event event;
+ while (g_system->getEventManager()->pollEvent(event)) {
+ int clicked = -1;
+ if (event.type == Common::EVENT_QUIT || event.type == Common::EVENT_RETURN_TO_LAUNCHER) {
+ done = true;
+ break;
+ } else if (event.type == Common::EVENT_KEYDOWN) {
+ const Common::KeyCode key = event.kbd.keycode;
+ if (key == Common::KEYCODE_ESCAPE)
+ clicked = 1;
+ else if (key == Common::KEYCODE_RETURN || key == Common::KEYCODE_KP_ENTER)
+ clicked = 0;
+ else if (key == Common::KEYCODE_TAB && !controls.empty()) {
+ focus = (focus + ((event.kbd.flags & Common::KBD_SHIFT) ? controls.size() - 1 : 1)) % controls.size();
+ const Common::Rect &r = items[controls[focus]].rect;
+ g_system->warpMouse((r.left + r.right) / 2, (r.top + r.bottom) / 2);
+ } else if (type == 0) {
+ if (key == Common::KEYCODE_BACKSPACE && caret > 0)
+ input.deleteChar(--caret);
+ else if (key == Common::KEYCODE_DELETE && caret < input.size())
+ input.deleteChar(caret);
+ else if (key == Common::KEYCODE_LEFT && caret > 0)
+ --caret;
+ else if (key == Common::KEYCODE_RIGHT && caret < input.size())
+ ++caret;
+ else if (event.kbd.ascii >= ' ' && event.kbd.ascii < 127 && input.size() < answer.size() + 3)
+ input.insertChar((char)event.kbd.ascii, caret++);
+ } else if (key == Common::KEYCODE_SPACE && !controls.empty())
+ clicked = controls[focus];
+ } else if (event.type == Common::EVENT_LBUTTONDOWN) {
+ for (uint i = 0; i < controls.size(); ++i) {
+ if (items[controls[i]].rect.contains(event.mouse)) {
+ focus = i;
+ clicked = controls[i];
+ break;
+ }
+ }
+ }
+ if (clicked == 0 && (type != 0 || !input.empty())) {
+ accepted = true;
+ done = true;
+ } else if (clicked == 1) {
+ done = true;
+ } else if (clicked >= 2 && type != 0) {
+ const int choice = clicked - (type == 1 ? 2 : 3);
+ if (choice >= 0 && (uint)choice < pictureCount)
+ selected = choice;
+ }
+ if (done)
+ break;
+ }
+ g_system->delayMillis(15);
+ }
+ if (type == 0)
+ g_system->setFeatureState(OSystem::kFeatureVirtualKeyboard, false);
+ g_system->getPaletteManager()->setPalette(oldPalette, 0, 256);
+ g_system->copyRectToScreen(saved.getPixels(), saved.pitch, 0, 0, saved.w, saved.h);
+ input.trim();
+ input.toUppercase();
+ const bool correct = accepted && (type == 0 ? input == answer : selected == 0);
+ if (!correct && !shouldQuit()) {
+ const byte *kd = _mystery.kdTextIndex();
+ const uint16 text = kd ? READ_LE_UINT16(kd + 12) : 0xffff;
+ if (text != 0xffff) {
+ if (_music && _musicOn)
+ _music->playMus(37, false);
+ drawKDBalloonOverCurrentScreen(parseString(_mystery.textAt(text), _playerName, _partner));
+ if (_audio)
+ _audio->sayKDDigital(kd, 6, _partner);
+ waitForInput(0xffffffffu);
+ if (_audio)
+ _audio->stopSpool();
+ }
+ }
+ stopMusic();
+ g_system->copyRectToScreen(saved.getPixels(), saved.pitch, 0, 0, saved.w, saved.h);
+ g_system->updateScreen();
+ setInteractiveMouseCursor(false);
+ return correct;
+}
+
+} // namespace EEM
More information about the Scummvm-git-logs
mailing list