[Scummvm-git-logs] scummvm master -> f0772668a7011da60b0f36d6e3738f72ef060ba4
sev-
noreply at scummvm.org
Thu Oct 22 17:00:08 UTC 2026
This automated email contains information about 30 new commits which have been
pushed to the 'scummvm' repo located at https://api.github.com/repos/scummvm/scummvm .
Summary:
73b4e53b30 HAROLD: BCB: Register game detection and bootstrap the engine
6495145e19 HAROLD: BCB: Load program and disc resources
c5634b525e HAROLD: BCB: Add BCB control and resource tables
c459bdf1a4 HAROLD: BCB: Load game state and interpret script expressions
5474e8358a HAROLD: BCB: Load executable text and Japanese and Korean fonts
d3942269c2 HAROLD: BCB: Play interface sounds and background audio
aef791fa10 HAROLD: BCB: Display the logo and title menu
c8ea2ba021 HAROLD: BCB: Play numbered movies and dispatch completion
9ae51aa779 HAROLD: BCB: Read timed captions from all.bin
22dec7d6fa HAROLD: BCB: Render captions and restore their backdrop
07c82a3499 HAROLD: BCB: Add movie controls and queued replay
ed1eb0c917 HAROLD: BCB: Draw the clock and apply elapsed game time
b95075a087 HAROLD: BCB: Compose conversation screens and backing
d965780f3b HAROLD: BCB: Display and select conversation choices
c3d8e70c72 HAROLD: BCB: Add map travel and gated destinations
a193a2927d HAROLD: BCB: Display chapter stills and game over
c3aded185b HAROLD: BCB: Dispatch script requests to game screens
b5b73fb8db HAROLD: BCB: Add computer categories and evidence icons
35467ac424 HAROLD: BCB: Display computer pages and transitions
09f13dc114 HAROLD: BCB: Handle computer navigation and page input
65e7fb2b8c HAROLD: BCB: Save and restore BCB game state
5e7eee3119 HAROLD: BCB: Add the in-game system menu
e70c430195 HAROLD: BCB: Add persistent volume controls
e317656c71 HAROLD: BCB: Add save slot lists and confirmation dialogs
f38062681f HAROLD: BCB: Connect game input and screen timers
d88edb28e2 HAROLD: BCB: Register engine keymaps and debugger commands
3a257cfdd1 HAROLD: BCB: Reduce rendering, font and menu audio overhead
e7e04ce66d HAROLD: BCB: Fix volume input and open System with mapped Escape
ac076b0fa2 HAROLD: BCB: Rename game to match official naming
f0772668a7 HAROLD: BCB: Use shared font loading and address engine integration review
Commit: 73b4e53b3083489b2c2eaeda9c1d646a9215a44a
https://github.com/scummvm/scummvm/commit/73b4e53b3083489b2c2eaeda9c1d646a9215a44a
Author: Scorp (scorp at mrs.mn)
Date: 2026-10-22T18:57:52+02:00
Commit Message:
HAROLD: BCB: Register game detection and bootstrap the engine
Changed paths:
A engines/harold/bcb/bcb.cpp
A engines/harold/bcb/bcb.h
A engines/harold/bcb/input.cpp
A engines/harold/bcb/ui.h
A engines/harold/configure.engine
A engines/harold/detection.cpp
A engines/harold/detection.h
A engines/harold/detection_tables.h
A engines/harold/metaengine.cpp
A engines/harold/module.mk
diff --git a/engines/harold/bcb/bcb.cpp b/engines/harold/bcb/bcb.cpp
new file mode 100644
index 00000000000..7b7e0a924b0
--- /dev/null
+++ b/engines/harold/bcb/bcb.cpp
@@ -0,0 +1,73 @@
+/* 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 "harold/bcb/bcb.h"
+#include "common/config-manager.h"
+#include "common/system.h"
+#include "common/textconsole.h"
+#include "engines/util.h"
+#include "graphics/cursorman.h"
+#include "graphics/font.h"
+#include "graphics/fonts/ttf.h"
+#include "graphics/sjis.h"
+#include "graphics/surface.h"
+#include "video/avi_decoder.h"
+
+namespace BCB {
+
+static const int kCaptionTextNativeCharacterHeight = 18;
+static const int kOwnerDrawUiTextNativeCharacterHeight = 16;
+
+BCBEngine::BCBEngine(OSystem *syst, const HaroldGameDescription *desc)
+ : Engine(syst), _gameDescription(desc) {
+}
+
+BCBEngine::~BCBEngine() {
+}
+
+Common::Error BCBEngine::run() {
+ const Graphics::PixelFormat screenFormat = Graphics::PixelFormat(4, 8, 8, 8, 8, 16, 8, 0, 24);
+
+ initGraphics(640, 480, &screenFormat);
+ CursorMan.lock(false);
+ CursorMan.setDefaultArrowCursor();
+ CursorMan.showMouse(true);
+ g_system->updateScreen();
+ _lastCursorRefreshMillis = g_system->getMillis();
+
+ mainLoop();
+ return Common::kNoError;
+}
+
+void BCBEngine::mainLoop() {
+ while (!shouldQuit()) {
+ bool screenUpdated = false;
+
+ if (!pollEvents())
+ return;
+
+ serviceCursorRefresh(screenUpdated);
+
+ delayWithEventPolling(10);
+ }
+}
+
+} // End of namespace BCB
diff --git a/engines/harold/bcb/bcb.h b/engines/harold/bcb/bcb.h
new file mode 100644
index 00000000000..24f494beace
--- /dev/null
+++ b/engines/harold/bcb/bcb.h
@@ -0,0 +1,81 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#ifndef BCB_H
+#define BCB_H
+
+#include "audio/mixer.h"
+#include "common/archive.h"
+#include "common/array.h"
+#include "common/events.h"
+#include "common/fs.h"
+#include "common/rect.h"
+#include "common/str-enc.h"
+#include "common/ustr.h"
+#include "engines/engine.h"
+#include "harold/detection.h"
+#include "harold/bcb/ui.h"
+
+namespace Graphics {
+class Font;
+class FontSJIS;
+struct Surface;
+}
+
+namespace Video {
+class AVIDecoder;
+}
+
+namespace BCB {
+
+enum {
+ kBCBDebugGeneral = 1
+};
+
+class Debugger;
+
+class BCBEngine : public Engine {
+ friend class Debugger;
+
+public:
+ BCBEngine(OSystem *syst, const HaroldGameDescription *desc);
+ ~BCBEngine();
+
+ Common::Error run() override;
+
+private:
+ void handleLeftButtonDown(const Common::Point &mousePosition);
+ bool pollEvents();
+ bool pollEvents(bool handleClicks);
+ void serviceCursorRefresh(bool screenUpdated = false);
+ void delayWithEventPolling(uint32 millis);
+ void delayWithEventPolling(uint32 millis, bool handleClicks);
+ void mainLoop();
+
+ const HaroldGameDescription *_gameDescription;
+ uint32 _lastCursorRefreshMillis = 0;
+ Common::Point _mousePosition;
+ bool _suppressClickHandling = false;
+};
+
+} // End of namespace BCB
+
+#endif // BCB_H
diff --git a/engines/harold/bcb/input.cpp b/engines/harold/bcb/input.cpp
new file mode 100644
index 00000000000..c506e28215d
--- /dev/null
+++ b/engines/harold/bcb/input.cpp
@@ -0,0 +1,103 @@
+/* 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/events.h"
+#include "common/system.h"
+#include "harold/bcb/bcb.h"
+#include "video/avi_decoder.h"
+
+namespace BCB {
+
+static const StaticRect kMainSceneMciControlHitRect = {16, 48, 416, 248};
+static const StaticRect kTitleMciControlHitRect = {120, 100, 520, 300};
+static const StaticRect kMovieQueueControlHitRect = {341, 414, 407, 452};
+static const StaticRect kSystemMenuControlHitRect = {265, 414, 330, 452};
+static const uint32 kCursorRefreshMillis = 10;
+static const int kMovieControlStripNormalSourceX = 0;
+static const int kMovieControlStripSkipPressedSourceX = 0x98;
+static const int kMovieControlStripPausePressedSourceX = 0x130;
+
+void BCBEngine::handleLeftButtonDown(const Common::Point &mousePosition) {
+}
+
+bool BCBEngine::pollEvents() {
+ return pollEvents(true);
+}
+
+bool BCBEngine::pollEvents(bool handleClicks) {
+ Common::Event event;
+
+ while (g_system->getEventManager()->pollEvent(event)) {
+ switch (event.type) {
+ case Common::EVENT_QUIT:
+ case Common::EVENT_RETURN_TO_LAUNCHER:
+ quitGame();
+ return false;
+ case Common::EVENT_MOUSEMOVE:
+ _mousePosition = event.mouse;
+ break;
+ case Common::EVENT_LBUTTONDOWN:
+ _mousePosition = event.mouse;
+ if (handleClicks && !_suppressClickHandling)
+ handleLeftButtonDown(_mousePosition);
+ break;
+ default:
+ break;
+ }
+ }
+
+ return !shouldQuit();
+}
+
+void BCBEngine::serviceCursorRefresh(bool screenUpdated) {
+ const uint32 now = g_system->getMillis();
+
+ if (screenUpdated) {
+ _lastCursorRefreshMillis = now;
+ return;
+ }
+
+ if (now - _lastCursorRefreshMillis >= kCursorRefreshMillis) {
+ _lastCursorRefreshMillis = now;
+ g_system->updateScreen();
+ }
+}
+
+void BCBEngine::delayWithEventPolling(uint32 millis) {
+ delayWithEventPolling(millis, true);
+}
+
+void BCBEngine::delayWithEventPolling(uint32 millis, bool handleClicks) {
+ const uint32 start = g_system->getMillis();
+
+ while (!shouldQuit() && g_system->getMillis() - start < millis) {
+ pollEvents(handleClicks);
+ serviceCursorRefresh();
+
+ const uint32 elapsed = g_system->getMillis() - start;
+ if (elapsed >= millis)
+ break;
+
+ g_system->delayMillis(MIN<uint32>(10, millis - elapsed));
+ }
+}
+
+} // End of namespace BCB
diff --git a/engines/harold/bcb/ui.h b/engines/harold/bcb/ui.h
new file mode 100644
index 00000000000..839d8e1bf6c
--- /dev/null
+++ b/engines/harold/bcb/ui.h
@@ -0,0 +1,65 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#ifndef BCB_UI_H
+#define BCB_UI_H
+
+#include "common/rect.h"
+
+namespace BCB {
+
+struct StaticRect {
+ int16 left;
+ int16 top;
+ int16 right;
+ int16 bottom;
+
+ operator Common::Rect() const { return Common::Rect(left, top, right, bottom); }
+ bool contains(const Common::Point &point) const { return Common::Rect(left, top, right, bottom).contains(point); }
+ int16 width() const { return right - left; }
+ int16 height() const { return bottom - top; }
+};
+
+extern const StaticRect kMapSubmodeMciControlHitRect;
+extern const StaticRect kMapSubmodeMovieBackdropRect;
+extern const int kMapSubmodeMovieBackdropSourceX;
+extern const int kMapSubmodeMovieBackdropSourceY;
+
+struct ButtonControlRecord {
+ int controlId;
+ int x;
+ int y;
+ int width;
+ int height;
+};
+
+struct StaticHitRect {
+ int16 left;
+ int16 top;
+ int16 right;
+ int16 bottom;
+
+ bool contains(const Common::Point &point) const { return Common::Rect(left, top, right, bottom).contains(point); }
+};
+
+} // End of namespace BCB
+
+#endif // BCB_UI_H
diff --git a/engines/harold/configure.engine b/engines/harold/configure.engine
new file mode 100644
index 00000000000..14c4908336b
--- /dev/null
+++ b/engines/harold/configure.engine
@@ -0,0 +1,4 @@
+# This file is included from the main "configure" script
+# add_engine [name] [desc] [build-by-default] [subengines] [base games] [deps] [components]
+add_engine harold "J.B. Harold series" no "bcb"
+add_engine bcb "Blue Chicago Blues" no "" "" "" "msmpeg4"
diff --git a/engines/harold/detection.cpp b/engines/harold/detection.cpp
new file mode 100644
index 00000000000..fbe08e10e6f
--- /dev/null
+++ b/engines/harold/detection.cpp
@@ -0,0 +1,70 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "base/plugins.h"
+#include "engines/advancedDetector.h"
+
+#include "harold/bcb/bcb.h"
+#include "harold/detection.h"
+#include "harold/detection_tables.h"
+
+static const PlainGameDescriptor haroldGames[] = {
+ {"bcb", "Blue Chicago Blues"},
+ {nullptr, nullptr}};
+
+static const DebugChannelDef debugFlagList[] = {
+ {BCB::kBCBDebugGeneral, "general", "General"},
+ DEBUG_CHANNEL_END};
+
+class HaroldMetaEngineDetection : public AdvancedMetaEngineDetection<BCB::HaroldGameDescription> {
+public:
+ HaroldMetaEngineDetection() : AdvancedMetaEngineDetection(BCB::gameDescriptions, haroldGames) {
+ _guiOptions = GUIO2(GUIO_NOMIDI, GUIO_NOSUBTITLES);
+ _maxScanDepth = 3;
+ _directoryGlobs = directoryGlobs;
+ }
+
+ const char *getName() const override {
+ return "harold";
+ }
+
+ const char *getEngineName() const override {
+ return "J.B. Harold series";
+ }
+
+ const char *getOriginalCopyright() const override {
+ return "J.B. Harold series (C) Riverhill Soft";
+ }
+
+ const DebugChannelDef *getDebugChannels() const override {
+ return debugFlagList;
+ }
+
+private:
+ static const char *const directoryGlobs[];
+};
+
+const char *const HaroldMetaEngineDetection::directoryGlobs[] = {
+ "bcb",
+ "program",
+ nullptr};
+
+REGISTER_PLUGIN_STATIC(HAROLD_DETECTION, PLUGIN_TYPE_ENGINE_DETECTION, HaroldMetaEngineDetection);
diff --git a/engines/harold/detection.h b/engines/harold/detection.h
new file mode 100644
index 00000000000..7a5fbf6fb5e
--- /dev/null
+++ b/engines/harold/detection.h
@@ -0,0 +1,42 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#ifndef HAROLD_DETECTION_H
+#define HAROLD_DETECTION_H
+
+#include "engines/advancedDetector.h"
+
+namespace BCB {
+
+enum HaroldGameType {
+ kHaroldGameBCB
+};
+
+struct HaroldGameDescription {
+ AD_GAME_DESCRIPTION_HELPERS(desc);
+
+ ADGameDescription desc;
+ HaroldGameType gameType;
+};
+
+} // End of namespace BCB
+
+#endif // HAROLD_DETECTION_H
diff --git a/engines/harold/detection_tables.h b/engines/harold/detection_tables.h
new file mode 100644
index 00000000000..2ec09feb116
--- /dev/null
+++ b/engines/harold/detection_tables.h
@@ -0,0 +1,62 @@
+/* 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/>.
+ *
+ */
+
+namespace BCB {
+
+static const HaroldGameDescription gameDescriptions[] = {
+ {
+ // Blue Chicago Blues Japanese Windows release.
+ {
+ "bcb",
+ nullptr,
+ AD_ENTRY5s(
+ "check.bin", "f3b5ba01c47a9fca664293c73c721826", 37094,
+ "l_tbl.bin", "dbbba1934727aa42f88754454e05ee09", 204,
+ "l_tim.bin", "497a7a5e13a6bf496f6ab63b694746f3", 816,
+ "all.bin", "49abcc9b900820dc4c0190d29b07cade", 142072,
+ "bcb.exe", "dcb035ac343adc54dfa1ddd5dbd1500a", 92672),
+ Common::JA_JPN,
+ Common::kPlatformWindows,
+ ADGF_UNSTABLE,
+ GUIO0()},
+ kHaroldGameBCB
+ },
+
+ {
+ {
+ "bcb",
+ nullptr,
+ AD_ENTRY5s(
+ "check.bin", "f3b5ba01c47a9fca664293c73c721826", 37094,
+ "l_tbl.bin", "dbbba1934727aa42f88754454e05ee09", 204,
+ "l_tim.bin", "497a7a5e13a6bf496f6ab63b694746f3", 816,
+ "all.bin", "49abcc9b900820dc4c0190d29b07cade", 142072,
+ "bcb.exe", "6eeccaa692b3012a24097738773722db", 153600),
+ Common::KO_KOR,
+ Common::kPlatformWindows,
+ ADGF_UNSTABLE,
+ GUIO0()},
+ kHaroldGameBCB
+ },
+
+ {AD_TABLE_END_MARKER, kHaroldGameBCB}};
+
+} // End of namespace BCB
diff --git a/engines/harold/metaengine.cpp b/engines/harold/metaengine.cpp
new file mode 100644
index 00000000000..b2a5e28861c
--- /dev/null
+++ b/engines/harold/metaengine.cpp
@@ -0,0 +1,58 @@
+/* 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/events.h"
+#include "common/translation.h"
+
+#include "backends/keymapper/action.h"
+#include "backends/keymapper/keymap.h"
+#include "backends/keymapper/standard-actions.h"
+
+#include "engines/advancedDetector.h"
+
+#include "harold/bcb/bcb.h"
+#include "harold/detection.h"
+
+class HaroldMetaEngine : public AdvancedMetaEngine<BCB::HaroldGameDescription> {
+public:
+ const char *getName() const override {
+ return "harold";
+ }
+
+ int getMaximumSaveSlot() const override { return 8; }
+ Common::Error createInstance(OSystem *syst, Engine **engine, const BCB::HaroldGameDescription *gd) const override;
+};
+
+Common::Error HaroldMetaEngine::createInstance(OSystem *syst, Engine **engine, const BCB::HaroldGameDescription *gd) const {
+ switch (gd->gameType) {
+ case BCB::kHaroldGameBCB:
+ *engine = new BCB::BCBEngine(syst, gd);
+ return Common::kNoError;
+ default:
+ return Common::kUnsupportedGameidError;
+ }
+}
+
+#if PLUGIN_ENABLED_DYNAMIC(HAROLD)
+REGISTER_PLUGIN_DYNAMIC(HAROLD, PLUGIN_TYPE_ENGINE, HaroldMetaEngine);
+#else
+REGISTER_PLUGIN_STATIC(HAROLD, PLUGIN_TYPE_ENGINE, HaroldMetaEngine);
+#endif
diff --git a/engines/harold/module.mk b/engines/harold/module.mk
new file mode 100644
index 00000000000..54fa5e7e465
--- /dev/null
+++ b/engines/harold/module.mk
@@ -0,0 +1,21 @@
+MODULE := engines/harold
+
+MODULE_OBJS := \
+ bcb/bcb.o \
+ bcb/input.o \
+ metaengine.o
+
+MODULE_DIRS += \
+ engines/harold \
+ engines/harold/bcb
+
+# This module can be built as a plugin
+ifeq ($(ENABLE_HAROLD), DYNAMIC_PLUGIN)
+PLUGIN := 1
+endif
+
+# Include common rules
+include $(srcdir)/rules.mk
+
+# Detection objects
+DETECT_OBJS += $(MODULE)/detection.o
Commit: 6495145e19e21c831198f6895092517964ebf344
https://github.com/scummvm/scummvm/commit/6495145e19e21c831198f6895092517964ebf344
Author: Scorp (scorp at mrs.mn)
Date: 2026-10-22T18:57:52+02:00
Commit Message:
HAROLD: BCB: Load program and disc resources
Changed paths:
A engines/harold/bcb/resources.cpp
A engines/harold/bcb/script.h
engines/harold/bcb/bcb.cpp
engines/harold/bcb/bcb.h
engines/harold/bcb/input.cpp
engines/harold/module.mk
diff --git a/engines/harold/bcb/bcb.cpp b/engines/harold/bcb/bcb.cpp
index 7b7e0a924b0..998cfd95647 100644
--- a/engines/harold/bcb/bcb.cpp
+++ b/engines/harold/bcb/bcb.cpp
@@ -29,6 +29,7 @@
#include "graphics/fonts/ttf.h"
#include "graphics/sjis.h"
#include "graphics/surface.h"
+#include "harold/bcb/script.h"
#include "video/avi_decoder.h"
namespace BCB {
@@ -41,11 +42,22 @@ BCBEngine::BCBEngine(OSystem *syst, const HaroldGameDescription *desc)
}
BCBEngine::~BCBEngine() {
+ if (_uiBitmap) {
+ _uiBitmap->free();
+ delete _uiBitmap;
+ }
+ if (_chipBitmap) {
+ _chipBitmap->free();
+ delete _chipBitmap;
+ }
}
Common::Error BCBEngine::run() {
const Graphics::PixelFormat screenFormat = Graphics::PixelFormat(4, 8, 8, 8, 8, 16, 8, 0, 24);
+ if (!initializeSearchPaths())
+ return Common::kPathDoesNotExist;
+
initGraphics(640, 480, &screenFormat);
CursorMan.lock(false);
CursorMan.setDefaultArrowCursor();
diff --git a/engines/harold/bcb/bcb.h b/engines/harold/bcb/bcb.h
index 24f494beace..e2e0b486687 100644
--- a/engines/harold/bcb/bcb.h
+++ b/engines/harold/bcb/bcb.h
@@ -62,6 +62,18 @@ public:
Common::Error run() override;
private:
+ bool initializeSearchPaths();
+ Common::SeekableReadStream *createMovieReadStream(int movieId) const;
+ bool switchActiveMediaDisc(int discNumber);
+ Graphics::Surface *loadBitmap(const char *fileName);
+ const Graphics::Surface *getUiBitmap(const char *fileName);
+ Graphics::Surface *loadBitmapFromStream(Common::SeekableReadStream *stream, const char *debugName) const;
+ Graphics::Surface *loadDiscBitmap(const char *relativePath) const;
+ Graphics::Surface *loadDiscBitmapFromAnyDisc(const char *relativePath) const;
+ Common::SeekableReadStream *createProgramReadStream(const char *relativePath) const;
+ Common::SeekableReadStream *createDiscReadStream(int discNumber, const char *relativePath) const;
+ Common::SeekableReadStream *createActiveDiscReadStream(const char *relativePath) const;
+ Common::SeekableReadStream *createActiveDiscHighColorReadStream(const char *relativePath) const;
void handleLeftButtonDown(const Common::Point &mousePosition);
bool pollEvents();
bool pollEvents(bool handleClicks);
@@ -71,6 +83,12 @@ private:
void mainLoop();
const HaroldGameDescription *_gameDescription;
+ Common::SearchSet _programFiles;
+ Common::SearchSet _discFiles[5];
+ Graphics::Surface *_uiBitmap = nullptr;
+ Common::String _uiBitmapName;
+ Graphics::Surface *_chipBitmap = nullptr;
+ int _activeMediaDiscNumber = 1;
uint32 _lastCursorRefreshMillis = 0;
Common::Point _mousePosition;
bool _suppressClickHandling = false;
diff --git a/engines/harold/bcb/input.cpp b/engines/harold/bcb/input.cpp
index c506e28215d..8f69fb6edb5 100644
--- a/engines/harold/bcb/input.cpp
+++ b/engines/harold/bcb/input.cpp
@@ -22,6 +22,7 @@
#include "common/events.h"
#include "common/system.h"
#include "harold/bcb/bcb.h"
+#include "harold/bcb/script.h"
#include "video/avi_decoder.h"
namespace BCB {
diff --git a/engines/harold/bcb/resources.cpp b/engines/harold/bcb/resources.cpp
new file mode 100644
index 00000000000..7588cc92460
--- /dev/null
+++ b/engines/harold/bcb/resources.cpp
@@ -0,0 +1,162 @@
+/* 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/config-manager.h"
+#include "common/ptr.h"
+#include "common/stream.h"
+#include "common/system.h"
+#include "common/textconsole.h"
+#include "graphics/surface.h"
+#include "harold/bcb/bcb.h"
+#include "harold/bcb/script.h"
+#include "image/bmp.h"
+
+namespace BCB {
+
+bool BCBEngine::initializeSearchPaths() {
+ const Common::FSNode gameDataDir(ConfMan.getPath("path"));
+ _programFiles.addDirectory(gameDataDir, 0, 1, false);
+ _programFiles.addSubDirectoryMatching(gameDataDir, "program", 0, 1, false);
+ _programFiles.addSubDirectoryMatching(gameDataDir, "bcb/program", 0, 2, false);
+ for (int i = 0; i < 5; ++i)
+ _discFiles[i].addSubDirectoryMatching(gameDataDir, Common::String::format("bcb%d", i + 1), 0, 8);
+
+ return _programFiles.hasFile("check.bin");
+}
+
+Common::SeekableReadStream *BCBEngine::createProgramReadStream(const char *relativePath) const {
+ return _programFiles.createReadStreamForMember(Common::Path(relativePath, '/'));
+}
+
+Common::SeekableReadStream *BCBEngine::createDiscReadStream(int discNumber, const char *relativePath) const {
+ if (discNumber < 1 || discNumber > 5)
+ return nullptr;
+
+ return _discFiles[discNumber - 1].createReadStreamForMember(Common::Path(relativePath, '/'));
+}
+
+Common::SeekableReadStream *BCBEngine::createActiveDiscReadStream(const char *relativePath) const {
+ return createDiscReadStream(CLIP<int>(_activeMediaDiscNumber, 1, 5), relativePath);
+}
+
+Common::SeekableReadStream *BCBEngine::createActiveDiscHighColorReadStream(const char *relativePath) const {
+ Common::SeekableReadStream *stream = createActiveDiscReadStream(relativePath);
+ if (stream)
+ return stream;
+
+ Common::String fallbackPath(relativePath);
+ int marker = fallbackPath.find("/16m/");
+ if (marker < 0)
+ return nullptr;
+
+ fallbackPath = fallbackPath.substr(0, marker) + "/256/" + fallbackPath.substr(marker + 5);
+ return createActiveDiscReadStream(fallbackPath.c_str());
+}
+
+Graphics::Surface *BCBEngine::loadBitmapFromStream(Common::SeekableReadStream *stream, const char *debugName) const {
+ Common::ScopedPtr<Common::SeekableReadStream> file(stream);
+ if (!file)
+ return nullptr;
+
+ Image::BitmapDecoder decoder;
+ if (!decoder.loadStream(*file)) {
+ warning("BCBEngine::loadBitmapFromStream(): Could not decode %s", debugName);
+ return nullptr;
+ }
+
+ const Graphics::Surface *source = decoder.getSurface();
+ if (!source)
+ return nullptr;
+
+ return source->convertTo(g_system->getScreenFormat(), decoder.getPalette().data(), decoder.getPalette().size());
+}
+
+const Graphics::Surface *BCBEngine::getUiBitmap(const char *fileName) {
+ const bool isChip = !strcmp(fileName, "16mchip.bmp");
+ Graphics::Surface *&bitmap = isChip ? _chipBitmap : _uiBitmap;
+ if (bitmap && ((!isChip && _uiBitmapName != fileName) || bitmap->format != g_system->getScreenFormat())) {
+ bitmap->free();
+ delete bitmap;
+ bitmap = nullptr;
+ }
+ if (!bitmap) {
+ bitmap = loadBitmapFromStream(createProgramReadStream(fileName), fileName);
+ if (!isChip)
+ _uiBitmapName = fileName;
+ }
+ return bitmap;
+}
+
+Graphics::Surface *BCBEngine::loadBitmap(const char *fileName) {
+ if (!strcmp(fileName, "16mmain.bmp") || !strcmp(fileName, "16mmainn.bmp") ||
+ !strcmp(fileName, "16mmap.bmp") || !strcmp(fileName, "16msys.bmp")) {
+ const Graphics::Surface *source = getUiBitmap(fileName);
+ if (!source)
+ return nullptr;
+ Graphics::Surface *copy = new Graphics::Surface();
+ copy->copyFrom(*source);
+ return copy;
+ }
+ return loadBitmapFromStream(createProgramReadStream(fileName), fileName);
+}
+
+Graphics::Surface *BCBEngine::loadDiscBitmap(const char *relativePath) const {
+ return loadBitmapFromStream(createActiveDiscHighColorReadStream(relativePath), relativePath);
+}
+
+Graphics::Surface *BCBEngine::loadDiscBitmapFromAnyDisc(const char *relativePath) const {
+ for (int discNumber = 1; discNumber <= 5; ++discNumber) {
+ Common::SeekableReadStream *stream = createDiscReadStream(discNumber, relativePath);
+ if (stream)
+ return loadBitmapFromStream(stream, relativePath);
+
+ Common::String fallbackPath(relativePath);
+ const int marker = fallbackPath.find("/16m/");
+ if (marker >= 0) {
+ fallbackPath = fallbackPath.substr(0, marker) + "/256/" + fallbackPath.substr(marker + 5);
+ stream = createDiscReadStream(discNumber, fallbackPath.c_str());
+ if (stream)
+ return loadBitmapFromStream(stream, fallbackPath.c_str());
+ }
+ }
+
+ return nullptr;
+}
+
+Common::SeekableReadStream *BCBEngine::createMovieReadStream(int movieId) const {
+ const Common::String movieName = Common::String::format("mov%04d.avi", movieId);
+ const Common::String moviePath = Common::String::format("movie/%s", movieName.c_str());
+ return createActiveDiscReadStream(moviePath.c_str());
+}
+
+bool BCBEngine::switchActiveMediaDisc(int discNumber) {
+ if (discNumber < 1 || discNumber > 5)
+ return false;
+
+ Common::ArchiveMemberList discMembers;
+ if (_discFiles[discNumber - 1].listMembers(discMembers) == 0)
+ return false;
+
+ _activeMediaDiscNumber = discNumber;
+ return true;
+}
+
+} // End of namespace BCB
diff --git a/engines/harold/bcb/script.h b/engines/harold/bcb/script.h
new file mode 100644
index 00000000000..1f6121e15d0
--- /dev/null
+++ b/engines/harold/bcb/script.h
@@ -0,0 +1,46 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#ifndef BCB_SCRIPT_H
+#define BCB_SCRIPT_H
+
+namespace BCB {
+
+enum GameStateOffset {
+ kStateDispatcherControlCode = 0x0000,
+ kStateMovieToPlayId = 0x0004,
+ kStateRequiredDiscIndex = 0x0008,
+ kStateGameClockTicks = 0x000c,
+ kStateClearDisplayBeforeMediaCheckFlag = 0x0014,
+ kStateSceneChoiceUiSetToShowId = 0x001c,
+ kStateCurrentScriptSequenceId = 0x0020,
+ kStateSelectedSceneChoiceValue = 0x0024,
+ kStateSceneChoiceExtraButtonFlag = 0x0028,
+ kStateStillImageToShowId = 0x0030,
+ kStateMovieHistory = 0x0040,
+ kStateMovieHistoryCount = 0x0088,
+ kStateComputerChoiceStateByIndex = 0x0098,
+ kStateMainSceneNightFlag = 0x0160
+};
+
+} // End of namespace BCB
+
+#endif // BCB_SCRIPT_H
diff --git a/engines/harold/module.mk b/engines/harold/module.mk
index 54fa5e7e465..cdbfe187f10 100644
--- a/engines/harold/module.mk
+++ b/engines/harold/module.mk
@@ -2,6 +2,7 @@ MODULE := engines/harold
MODULE_OBJS := \
bcb/bcb.o \
+ bcb/resources.o \
bcb/input.o \
metaengine.o
Commit: c5634b525eeb959cbd66c7970937577d059c6532
https://github.com/scummvm/scummvm/commit/c5634b525eeb959cbd66c7970937577d059c6532
Author: Scorp (scorp at mrs.mn)
Date: 2026-10-22T18:57:52+02:00
Commit Message:
HAROLD: BCB: Add BCB control and resource tables
Changed paths:
A engines/harold/bcb/static_data.cpp
A engines/harold/bcb/static_data.h
engines/harold/bcb/bcb.cpp
engines/harold/bcb/input.cpp
engines/harold/bcb/resources.cpp
engines/harold/module.mk
diff --git a/engines/harold/bcb/bcb.cpp b/engines/harold/bcb/bcb.cpp
index 998cfd95647..b669524ee2a 100644
--- a/engines/harold/bcb/bcb.cpp
+++ b/engines/harold/bcb/bcb.cpp
@@ -30,6 +30,7 @@
#include "graphics/sjis.h"
#include "graphics/surface.h"
#include "harold/bcb/script.h"
+#include "harold/bcb/static_data.h"
#include "video/avi_decoder.h"
namespace BCB {
diff --git a/engines/harold/bcb/input.cpp b/engines/harold/bcb/input.cpp
index 8f69fb6edb5..a7595b18a0c 100644
--- a/engines/harold/bcb/input.cpp
+++ b/engines/harold/bcb/input.cpp
@@ -23,6 +23,7 @@
#include "common/system.h"
#include "harold/bcb/bcb.h"
#include "harold/bcb/script.h"
+#include "harold/bcb/static_data.h"
#include "video/avi_decoder.h"
namespace BCB {
diff --git a/engines/harold/bcb/resources.cpp b/engines/harold/bcb/resources.cpp
index 7588cc92460..5d748dbc3f5 100644
--- a/engines/harold/bcb/resources.cpp
+++ b/engines/harold/bcb/resources.cpp
@@ -27,6 +27,7 @@
#include "graphics/surface.h"
#include "harold/bcb/bcb.h"
#include "harold/bcb/script.h"
+#include "harold/bcb/static_data.h"
#include "image/bmp.h"
namespace BCB {
diff --git a/engines/harold/bcb/static_data.cpp b/engines/harold/bcb/static_data.cpp
new file mode 100644
index 00000000000..29300854e33
--- /dev/null
+++ b/engines/harold/bcb/static_data.cpp
@@ -0,0 +1,213 @@
+/* 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 "harold/bcb/static_data.h"
+
+namespace BCB {
+
+const StaticRect kMapSubmodeMciControlHitRect = {120, 56, 520, 256};
+const StaticRect kMapSubmodeMovieBackdropRect = {120, 56, 540, 342};
+const int kMapSubmodeMovieBackdropSourceX = 0;
+const int kMapSubmodeMovieBackdropSourceY = 0x210;
+
+const ButtonControlRecord kTitleButtonTable[5] = {
+ {50, 0, 0, 0, 0},
+ {51, 429, 263, 162, 32},
+ {52, 429, 302, 162, 32},
+ {53, 429, 341, 162, 32},
+ {54, 429, 386, 162, 32}};
+
+const ButtonControlRecord kSystemSubmodeButtonTable[5] = {
+ {500, 0, 0, 0, 0},
+ {501, 380, 53, 240, 44},
+ {502, 380, 120, 240, 44},
+ {503, 380, 216, 240, 44},
+ {504, 505, 404, 96, 48}};
+
+const ButtonControlRecord kSystemButtonTable[9] = {
+ {550, 0, 0, 0, 0},
+ {551, 41, 46, 288, 32},
+ {552, 41, 78, 288, 32},
+ {553, 41, 110, 288, 32},
+ {554, 41, 142, 288, 32},
+ {555, 41, 174, 288, 32},
+ {556, 41, 206, 288, 32},
+ {557, 41, 238, 288, 32},
+ {558, 41, 270, 288, 32}};
+
+const ButtonControlRecord kSceneChoiceButtonTable[10] = {
+ {100, 0, 0, 0, 0},
+ {101, 432, 48, 192, 40},
+ {102, 432, 94, 192, 40},
+ {103, 432, 140, 192, 40},
+ {104, 432, 186, 192, 40},
+ {105, 432, 232, 192, 40},
+ {106, 432, 278, 192, 40},
+ {107, 432, 324, 192, 40},
+ {108, 432, 370, 192, 40},
+ {109, 432, 416, 192, 40}};
+
+const ButtonControlRecord kMapButtonTable[19] = {
+ {200, 0, 0, 0, 0},
+ {201, 371, 359, 16, 16},
+ {202, 285, 35, 16, 16},
+ {203, 395, 187, 16, 16},
+ {204, 412, 210, 16, 16},
+ {205, 283, 245, 16, 16},
+ {206, 401, 228, 16, 16},
+ {207, 438, 254, 16, 16},
+ {208, 344, 278, 16, 16},
+ {209, 371, 301, 16, 16},
+ {210, 285, 52, 16, 16},
+ {211, 495, 408, 16, 16},
+ {212, 197, 19, 16, 16},
+ {213, 308, 164, 16, 16},
+ {214, 302, 335, 16, 16},
+ {215, 234, 115, 16, 16},
+ {216, 270, 225, 16, 16},
+ {217, 317, 392, 16, 16},
+ {218, 393, 375, 16, 16}};
+
+const int kMapButtonSecondaryStateIndexTable[19] = {
+ 0, 0, 0, 41, 42, 0, 42, 44, 0, 0, 40, 0, 0, 39, 45, 46, 43, 47, 48};
+
+const ButtonControlRecord kComputerPrimaryButtonTable[2] = {
+ {600, 0, 0, 0, 0},
+ {601, 49, 40, 16, 16}};
+
+const ButtonControlRecord kComputerDetailButtonTable[3] = {
+ {750, 0, 0, 0, 0},
+ {751, 54, 50, 17, 17},
+ {752, 508, 402, 80, 24}};
+
+const ButtonControlRecord kComputerChoiceLayoutSlots[21] = {
+ {0, 0, 0, 0, 0},
+ {0, 83, 125, 64, 64},
+ {0, 171, 125, 64, 64},
+ {0, 259, 125, 64, 64},
+ {0, 347, 125, 64, 64},
+ {0, 435, 125, 64, 64},
+ {0, 83, 195, 64, 64},
+ {0, 171, 195, 64, 64},
+ {0, 259, 195, 64, 64},
+ {0, 347, 195, 64, 64},
+ {0, 435, 195, 64, 64},
+ {0, 83, 265, 64, 64},
+ {0, 171, 265, 64, 64},
+ {0, 259, 265, 64, 64},
+ {0, 347, 265, 64, 64},
+ {0, 435, 265, 64, 64},
+ {0, 83, 335, 64, 64},
+ {0, 171, 335, 64, 64},
+ {0, 259, 335, 64, 64},
+ {0, 347, 335, 64, 64},
+ {0, 435, 335, 64, 64}};
+
+const ButtonControlRecord kComputerChoiceButtonTable[30] = {
+ {701, 0, 480, 64, 64},
+ {702, 64, 480, 64, 64},
+ {703, 128, 480, 64, 64},
+ {704, 192, 480, 64, 64},
+ {705, 256, 480, 64, 64},
+ {706, 320, 480, 64, 64},
+ {707, 384, 480, 64, 64},
+ {708, 448, 480, 64, 64},
+ {709, 512, 480, 64, 64},
+ {710, 576, 480, 64, 64},
+ {711, 0, 544, 64, 64},
+ {712, 64, 544, 64, 64},
+ {713, 128, 544, 64, 64},
+ {714, 192, 544, 64, 64},
+ {715, 256, 544, 64, 64},
+ {716, 320, 544, 64, 64},
+ {717, 384, 544, 64, 64},
+ {718, 448, 544, 64, 64},
+ {719, 512, 544, 64, 64},
+ {720, 0, 0, 64, 64},
+ {721, 576, 544, 64, 64},
+ {722, 0, 608, 64, 64},
+ {723, 64, 608, 64, 64},
+ {724, 128, 608, 64, 64},
+ {725, 192, 608, 64, 64},
+ {726, 256, 608, 64, 64},
+ {727, 320, 608, 64, 64},
+ {728, 384, 608, 64, 64},
+ {729, 448, 608, 64, 64},
+ {730, 512, 608, 64, 64}};
+
+const StaticHitRect kComputerChoiceGroupHitRects[3] = {
+ {60, 67, 131, 89},
+ {137, 67, 208, 89},
+ {214, 67, 285, 89}};
+
+const byte kComputerChoiceDependencyStateIndex[30] = {
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 30,
+ 31, 32, 33, 34, 0, 0, 0, 0, 35, 0,
+ 37, 0, 0, 0, 0, 0, 0, 0, 0, 0};
+
+const CompositeBitmapVariantSet kComputerDetailBitmapVariantsBySelectedValue[31] = {
+ {0, {}},
+ {1, {"bmp/comp/pcom_01.bmp"}},
+ {1, {"bmp/comp/pcom_02.bmp"}},
+ {1, {"bmp/comp/pcom_03.bmp"}},
+ {1, {"bmp/comp/pcom_04.bmp"}},
+ {1, {"bmp/comp/pcom_05.bmp"}},
+ {1, {"bmp/comp/pcom_06.bmp"}},
+ {1, {"bmp/comp/pcom_07.bmp"}},
+ {1, {"bmp/comp/pcom_08.bmp"}},
+ {1, {"bmp/comp/pcom_09.bmp"}},
+ {1, {"bmp/comp/pcom_10.bmp"}},
+ {1, {"bmp/comp/pcom_11.bmp"}},
+ {1, {"bmp/comp/pcom_12.bmp"}},
+ {1, {"bmp/comp/pcom_13.bmp"}},
+ {1, {"bmp/comp/pcom_14.bmp"}},
+ {1, {"bmp/comp/pcom_15.bmp"}},
+ {1, {"bmp/comp/pcom_16.bmp"}},
+ {1, {"bmp/comp/pcom_17.bmp"}},
+ {1, {"bmp/comp/pcom_18.bmp"}},
+ {1, {"bmp/comp/pcom_19.bmp"}},
+ {1, {"bmp/comp/pcom_31a.bmp"}},
+ {1, {"bmp/comp/pcom_21.bmp"}},
+ {4, {"bmp/comp/pcom_22a.bmp", "bmp/comp/pcom_22b.bmp", "bmp/comp/pcom_22c.bmp", "bmp/comp/pcom_22d.bmp"}},
+ {4, {"bmp/comp/pcom_23a.bmp", "bmp/comp/pcom_23b.bmp", "bmp/comp/pcom_23c.bmp", "bmp/comp/pcom_23d.bmp"}},
+ {3, {"bmp/comp/pcom_24a.bmp", "bmp/comp/pcom_24b.bmp", "bmp/comp/pcom_24c.bmp"}},
+ {2, {"bmp/comp/pcom_25a.bmp", "bmp/comp/pcom_25b.bmp"}},
+ {2, {"bmp/comp/pcom_26a.bmp", "bmp/comp/pcom_26b.bmp"}},
+ {1, {"bmp/comp/pcom_27a.bmp"}},
+ {3, {"bmp/comp/pcom_28a.bmp", "bmp/comp/pcom_28b.bmp", "bmp/comp/pcom_28c.bmp"}},
+ {3, {"bmp/comp/pcom_29a.bmp", "bmp/comp/pcom_29b.bmp", "bmp/comp/pcom_29c.bmp"}},
+ {1, {"bmp/comp/pcom_30a.bmp"}}};
+
+const byte kSceneChoiceValueByButtonIndex[10] = {0, 1, 2, 4, 8, 16, 32, 64, 128, 255};
+
+const int kStillImageDurationTicks[8] = {100, 100, 100, 100, 100, 600, 600, 200};
+
+const char *const kStillImageHighColorPaths[8] = {
+ "bmp/still/16m/pstill_1.bmp",
+ "bmp/still/16m/pstill_2.bmp",
+ "bmp/still/16m/pstill_3.bmp",
+ "bmp/still/16m/pstill_4.bmp",
+ "bmp/still/16m/pstill_5.bmp",
+ "bmp/still/16m/pbad.bmp",
+ "bmp/still/16m/pgood.bmp",
+ "bmp/still/16m/pstaff.bmp"};
+
+} // End of namespace BCB
diff --git a/engines/harold/bcb/static_data.h b/engines/harold/bcb/static_data.h
new file mode 100644
index 00000000000..a7a9012561f
--- /dev/null
+++ b/engines/harold/bcb/static_data.h
@@ -0,0 +1,55 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#ifndef BCB_STATIC_DATA_H
+#define BCB_STATIC_DATA_H
+
+#include "common/rect.h"
+#include "common/scummsys.h"
+#include "harold/bcb/ui.h"
+
+namespace BCB {
+
+struct CompositeBitmapVariantSet {
+ uint pathCount;
+ const char *paths[4];
+};
+
+extern const ButtonControlRecord kTitleButtonTable[5];
+extern const ButtonControlRecord kSystemSubmodeButtonTable[5];
+extern const ButtonControlRecord kSystemButtonTable[9];
+extern const ButtonControlRecord kSceneChoiceButtonTable[10];
+extern const ButtonControlRecord kMapButtonTable[19];
+extern const int kMapButtonSecondaryStateIndexTable[19];
+extern const ButtonControlRecord kComputerPrimaryButtonTable[2];
+extern const ButtonControlRecord kComputerDetailButtonTable[3];
+extern const ButtonControlRecord kComputerChoiceLayoutSlots[21];
+extern const ButtonControlRecord kComputerChoiceButtonTable[30];
+extern const StaticHitRect kComputerChoiceGroupHitRects[3];
+extern const byte kComputerChoiceDependencyStateIndex[30];
+extern const CompositeBitmapVariantSet kComputerDetailBitmapVariantsBySelectedValue[31];
+extern const byte kSceneChoiceValueByButtonIndex[10];
+extern const int kStillImageDurationTicks[8];
+extern const char *const kStillImageHighColorPaths[8];
+
+} // End of namespace BCB
+
+#endif // BCB_STATIC_DATA_H
diff --git a/engines/harold/module.mk b/engines/harold/module.mk
index cdbfe187f10..d6b3f28b8f7 100644
--- a/engines/harold/module.mk
+++ b/engines/harold/module.mk
@@ -4,6 +4,7 @@ MODULE_OBJS := \
bcb/bcb.o \
bcb/resources.o \
bcb/input.o \
+ bcb/static_data.o \
metaengine.o
MODULE_DIRS += \
Commit: c459bdf1a45338c7d9eae835be4c67c55f5a013c
https://github.com/scummvm/scummvm/commit/c459bdf1a45338c7d9eae835be4c67c55f5a013c
Author: Scorp (scorp at mrs.mn)
Date: 2026-10-22T18:57:52+02:00
Commit Message:
HAROLD: BCB: Load game state and interpret script expressions
Changed paths:
A engines/harold/bcb/scene.cpp
A engines/harold/bcb/script.cpp
engines/harold/bcb/bcb.cpp
engines/harold/bcb/bcb.h
engines/harold/module.mk
diff --git a/engines/harold/bcb/bcb.cpp b/engines/harold/bcb/bcb.cpp
index b669524ee2a..9294751ec00 100644
--- a/engines/harold/bcb/bcb.cpp
+++ b/engines/harold/bcb/bcb.cpp
@@ -66,6 +66,11 @@ Common::Error BCBEngine::run() {
g_system->updateScreen();
_lastCursorRefreshMillis = g_system->getMillis();
+ if (!loadScriptData())
+ return Common::kReadingFailed;
+
+ resetGameState();
+
mainLoop();
return Common::kNoError;
}
diff --git a/engines/harold/bcb/bcb.h b/engines/harold/bcb/bcb.h
index e2e0b486687..15e2885202f 100644
--- a/engines/harold/bcb/bcb.h
+++ b/engines/harold/bcb/bcb.h
@@ -65,6 +65,19 @@ private:
bool initializeSearchPaths();
Common::SeekableReadStream *createMovieReadStream(int movieId) const;
bool switchActiveMediaDisc(int discNumber);
+ void resetGameState();
+ bool loadScriptData();
+ bool loadByteFile(const char *fileName, Common::Array<byte> &data) const;
+ void executeCurrentScriptSequence();
+ uint32 executeScriptExpression(uint32 &scriptPc, int &branchTokenSeen);
+ bool pushScriptValue(uint32 value);
+ uint32 popScriptValue();
+ void popScriptOperands(uint32 &leftValue, uint32 &rightValue);
+ uint32 dereferenceScriptValue(uint32 value);
+ uint32 &scriptOperandDword(uint32 encodedOperand);
+ uint32 &stateDword(uint byteOffset);
+ uint32 stateDword(uint byteOffset) const;
+ void notifyGameClockTicksChanged();
Graphics::Surface *loadBitmap(const char *fileName);
const Graphics::Surface *getUiBitmap(const char *fileName);
Graphics::Surface *loadBitmapFromStream(Common::SeekableReadStream *stream, const char *debugName) const;
@@ -85,12 +98,34 @@ private:
const HaroldGameDescription *_gameDescription;
Common::SearchSet _programFiles;
Common::SearchSet _discFiles[5];
+ Common::Array<byte> _scriptData;
+ Common::Array<byte> _locationTableData;
+ Common::Array<byte> _locationTimingData;
+ Common::Array<byte> _allBinData;
+ Common::Array<byte> _timedCaptionRecordData;
Graphics::Surface *_uiBitmap = nullptr;
Common::String _uiBitmapName;
Graphics::Surface *_chipBitmap = nullptr;
+ uint32 _gameState[0x100] = {};
+ uint32 _scriptValueStack[0x100] = {};
+ int _scriptStackDepth = 0;
+ bool _scriptStopFlag = false;
+ int _pendingStillElapsedTimerTicks = 0;
+ int _gameOverElapsedTimerTicks = 0;
+ int _pendingStillDurationIndex = 0;
+ int _pendingClockTickDelta = 0;
int _activeMediaDiscNumber = 1;
+ uint _timedCaptionCursorOffset = 0;
+ int _nextTimedCaptionThreshold = 99999;
+ int _queuedMoviePlaybackIndex = 0;
+ int _previousDisplayStateBeforeSystem = 0;
+ int _sceneChoiceTerminatorButtonIndex = 0;
+ int _sceneChoiceEnabledButtonLimit = 0;
+ Common::U32String _timedCaptionText;
+ int _lastLoggedGameClockTicks = -1;
uint32 _lastCursorRefreshMillis = 0;
Common::Point _mousePosition;
+ bool _sceneChoiceActive = false;
bool _suppressClickHandling = false;
};
diff --git a/engines/harold/bcb/scene.cpp b/engines/harold/bcb/scene.cpp
new file mode 100644
index 00000000000..51238c7d389
--- /dev/null
+++ b/engines/harold/bcb/scene.cpp
@@ -0,0 +1,50 @@
+/* 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/system.h"
+#include "common/textconsole.h"
+#include "graphics/font.h"
+#include "graphics/fontman.h"
+#include "graphics/surface.h"
+#include "harold/bcb/bcb.h"
+#include "harold/bcb/script.h"
+#include "harold/bcb/static_data.h"
+#include "video/avi_decoder.h"
+
+#include <math.h>
+
+namespace BCB {
+
+void BCBEngine::notifyGameClockTicksChanged() {
+ const int ticks = stateDword(kStateGameClockTicks);
+ if (_lastLoggedGameClockTicks == ticks)
+ return;
+
+ _lastLoggedGameClockTicks = ticks;
+ const int clockTicks = ticks % 1440;
+ const int dayOfMonth = 10 + CLIP<int>(stateDword(kStateRequiredDiscIndex), 0, 4);
+ const int hour = clockTicks / 60;
+ const int minute = clockTicks % 60;
+ const Common::String message = Common::String::format("BCB clock: 11/%02d %02d:%02d (ticks=%d / 0x%04x)\n", dayOfMonth, hour, minute, ticks, ticks);
+ g_system->logMessage(LogMessageType::kInfo, message.c_str());
+}
+
+} // End of namespace BCB
diff --git a/engines/harold/bcb/script.cpp b/engines/harold/bcb/script.cpp
new file mode 100644
index 00000000000..42f10090a70
--- /dev/null
+++ b/engines/harold/bcb/script.cpp
@@ -0,0 +1,287 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "common/algorithm.h"
+#include "common/file.h"
+#include "common/fs.h"
+#include "common/stream.h"
+#include "common/textconsole.h"
+
+#include "harold/bcb/bcb.h"
+#include "harold/bcb/script.h"
+
+namespace BCB {
+
+static const uint32 kScriptFalse = 0xc000;
+static const uint32 kScriptTrue = 0xffff;
+static const uint32 kScriptEndExpression = 0x7fff;
+static const uint32 kScriptSecondaryStateByteOffset = 0x98;
+
+bool BCBEngine::loadScriptData() {
+ if (!loadByteFile("check.bin", _scriptData))
+ return false;
+
+ if (!loadByteFile("l_tbl.bin", _locationTableData))
+ return false;
+
+ if (!loadByteFile("l_tim.bin", _locationTimingData))
+ return false;
+
+ if (!loadByteFile("all.bin", _allBinData))
+ return false;
+
+ return true;
+}
+
+bool BCBEngine::loadByteFile(const char *fileName, Common::Array<byte> &data) const {
+ Common::ScopedPtr<Common::SeekableReadStream> stream(createProgramReadStream(fileName));
+ if (!stream)
+ return false;
+
+ const int32 fileSize = stream->size();
+ if (fileSize <= 0)
+ return false;
+
+ data.resize(fileSize);
+ return stream->read(&data[0], fileSize) == (uint32)fileSize;
+}
+
+void BCBEngine::resetGameState() {
+ Common::fill(&_gameState[0], &_gameState[ARRAYSIZE(_gameState)], 0);
+ stateDword(kStateGameClockTicks) = 0x474;
+ stateDword(kStateCurrentScriptSequenceId) = 1;
+ _pendingStillElapsedTimerTicks = 0;
+ _pendingStillDurationIndex = 0;
+ _gameOverElapsedTimerTicks = 0;
+ _pendingClockTickDelta = 0;
+ _sceneChoiceActive = false;
+ _sceneChoiceTerminatorButtonIndex = 0;
+ _sceneChoiceEnabledButtonLimit = 0;
+ _timedCaptionRecordData.clear();
+ _timedCaptionCursorOffset = 0;
+ _nextTimedCaptionThreshold = 99999;
+ _queuedMoviePlaybackIndex = 0;
+ _previousDisplayStateBeforeSystem = 0;
+ _timedCaptionText.clear();
+ _scriptStopFlag = false;
+}
+
+void BCBEngine::executeCurrentScriptSequence() {
+ stateDword(kStateDispatcherControlCode) = 0;
+
+ const uint sequenceId = stateDword(kStateCurrentScriptSequenceId);
+ const uint sequenceTableOffset = sequenceId * 2 - 2;
+ if (sequenceId == 0 || sequenceTableOffset + 1 >= _scriptData.size()) {
+ warning("BCBEngine::executeCurrentScriptSequence(): Invalid sequence id %u", sequenceId);
+ _scriptStopFlag = true;
+ return;
+ }
+
+ uint32 scriptPc = _scriptData[sequenceTableOffset] | (_scriptData[sequenceTableOffset + 1] << 8);
+ while (!_scriptStopFlag) {
+ int branchTokenSeen = -1;
+ const uint32 result = executeScriptExpression(scriptPc, branchTokenSeen);
+ if (_scriptStopFlag || branchTokenSeen != 0)
+ break;
+
+ if (scriptPc + 1 >= _scriptData.size()) {
+ _scriptStopFlag = true;
+ break;
+ }
+
+ const int branchOffset = (int8)_scriptData[scriptPc] + (int8)_scriptData[scriptPc + 1] * 0x100;
+ if ((result & 0x1fff) == 0)
+ scriptPc += branchOffset;
+ else
+ scriptPc += 2;
+ }
+}
+
+uint32 BCBEngine::executeScriptExpression(uint32 &scriptPc, int &branchTokenSeen) {
+ _scriptStackDepth = 0;
+
+ while (!_scriptStopFlag) {
+ if (scriptPc + 1 >= _scriptData.size()) {
+ _scriptStopFlag = true;
+ return 0xffffffff;
+ }
+
+ const uint32 token = _scriptData[scriptPc] | (_scriptData[scriptPc + 1] << 8);
+ scriptPc += 2;
+
+ if (((token >> 8) & 0xc0) != 0x40) {
+ pushScriptValue(token);
+ continue;
+ }
+
+ if (token == kScriptEndExpression) {
+ if (_scriptStackDepth == 0)
+ _scriptStackDepth = 1;
+ return popScriptValue();
+ }
+
+ const uint opcode = token & 0xff;
+ uint32 leftValue = 0;
+ uint32 rightValue = 0;
+ switch (opcode) {
+ case 0x00:
+ popScriptOperands(leftValue, rightValue);
+ pushScriptValue(rightValue & leftValue);
+ break;
+ case 0x01:
+ popScriptOperands(leftValue, rightValue);
+ pushScriptValue(rightValue | leftValue);
+ break;
+ case 0x02:
+ branchTokenSeen = 0;
+ popScriptOperands(leftValue, rightValue);
+ pushScriptValue(rightValue == leftValue ? kScriptTrue : kScriptFalse);
+ break;
+ case 0x03:
+ branchTokenSeen = 0;
+ popScriptOperands(leftValue, rightValue);
+ pushScriptValue(rightValue != leftValue ? kScriptTrue : kScriptFalse);
+ break;
+ case 0x04: {
+ uint32 assignedValue = popScriptValue();
+ assignedValue = dereferenceScriptValue(assignedValue);
+ const uint32 encodedOperand = popScriptValue();
+ if (((encodedOperand >> 8) & 0xc0) != 0x80) {
+ _scriptStopFlag = true;
+ return 0xffffffff;
+ }
+ const uint byteOffset = (encodedOperand & 0x1ffe) * 2;
+ const bool writesPrimaryState = (encodedOperand & 0x2000) == 0;
+ scriptOperandDword(encodedOperand) = assignedValue;
+ if (writesPrimaryState && byteOffset == kStateGameClockTicks)
+ notifyGameClockTicksChanged();
+ pushScriptValue(assignedValue == 0 ? kScriptFalse : kScriptTrue);
+ break;
+ }
+ case 0x05:
+ branchTokenSeen = 0;
+ popScriptOperands(leftValue, rightValue);
+ pushScriptValue(leftValue < rightValue ? kScriptTrue : kScriptFalse);
+ break;
+ case 0x06:
+ branchTokenSeen = 0;
+ popScriptOperands(leftValue, rightValue);
+ pushScriptValue(leftValue > rightValue ? kScriptTrue : kScriptFalse);
+ break;
+ case 0x07:
+ branchTokenSeen = 0;
+ popScriptOperands(leftValue, rightValue);
+ pushScriptValue(leftValue <= rightValue ? kScriptTrue : kScriptFalse);
+ break;
+ case 0x08:
+ branchTokenSeen = 0;
+ popScriptOperands(leftValue, rightValue);
+ pushScriptValue(leftValue >= rightValue ? kScriptTrue : kScriptFalse);
+ break;
+ case 0x09:
+ branchTokenSeen = 0;
+ popScriptOperands(leftValue, rightValue);
+ pushScriptValue(rightValue & leftValue);
+ break;
+ case 0x0a:
+ branchTokenSeen = 0;
+ popScriptOperands(leftValue, rightValue);
+ pushScriptValue(rightValue | leftValue);
+ break;
+ case 0x0b:
+ popScriptOperands(leftValue, rightValue);
+ pushScriptValue(rightValue ^ leftValue);
+ break;
+ case 0x0c:
+ popScriptOperands(leftValue, rightValue);
+ pushScriptValue(rightValue + leftValue);
+ break;
+ case 0x0d:
+ popScriptOperands(leftValue, rightValue);
+ pushScriptValue(leftValue - rightValue);
+ break;
+ default:
+ warning("BCBEngine::executeScriptExpression(): Unknown script opcode 0x%02x", opcode);
+ _scriptStopFlag = true;
+ return 0xffffffff;
+ }
+ }
+
+ return 0xffffffff;
+}
+
+bool BCBEngine::pushScriptValue(uint32 value) {
+ if (_scriptStackDepth < 0 || _scriptStackDepth >= ARRAYSIZE(_scriptValueStack)) {
+ _scriptStopFlag = true;
+ return false;
+ }
+
+ _scriptValueStack[_scriptStackDepth++] = value;
+ return true;
+}
+
+uint32 BCBEngine::popScriptValue() {
+ if (_scriptStackDepth <= 0 || _scriptStackDepth > ARRAYSIZE(_scriptValueStack)) {
+ _scriptStopFlag = true;
+ return 0xffffffff;
+ }
+
+ return _scriptValueStack[--_scriptStackDepth];
+}
+
+void BCBEngine::popScriptOperands(uint32 &leftValue, uint32 &rightValue) {
+ rightValue = dereferenceScriptValue(popScriptValue());
+ leftValue = dereferenceScriptValue(popScriptValue());
+}
+
+uint32 BCBEngine::dereferenceScriptValue(uint32 value) {
+ if (((value >> 8) & 0xc0) == 0x80)
+ return scriptOperandDword(value);
+
+ return value;
+}
+
+uint32 &BCBEngine::scriptOperandDword(uint32 encodedOperand) {
+ const uint byteOffset = (encodedOperand & 0x1ffe) * 2;
+ if ((encodedOperand & 0x2000) == 0)
+ return stateDword(byteOffset);
+
+ return stateDword(kScriptSecondaryStateByteOffset + byteOffset);
+}
+
+uint32 &BCBEngine::stateDword(uint byteOffset) {
+ if ((byteOffset & 3) || byteOffset + 4 > sizeof(_gameState)) {
+ warning("BCBEngine::stateDword(): Invalid state offset 0x%04x", byteOffset);
+ _scriptStopFlag = true;
+ return _gameState[0];
+ }
+
+ return _gameState[byteOffset / 4];
+}
+
+uint32 BCBEngine::stateDword(uint byteOffset) const {
+ if ((byteOffset & 3) || byteOffset + 4 > sizeof(_gameState))
+ return 0;
+
+ return _gameState[byteOffset / 4];
+}
+
+} // End of namespace BCB
diff --git a/engines/harold/module.mk b/engines/harold/module.mk
index d6b3f28b8f7..521827de166 100644
--- a/engines/harold/module.mk
+++ b/engines/harold/module.mk
@@ -3,7 +3,9 @@ MODULE := engines/harold
MODULE_OBJS := \
bcb/bcb.o \
bcb/resources.o \
+ bcb/scene.o \
bcb/input.o \
+ bcb/script.o \
bcb/static_data.o \
metaengine.o
Commit: 5474e8358a6e12ee2637be8d1758aa3074541f54
https://github.com/scummvm/scummvm/commit/5474e8358a6e12ee2637be8d1758aa3074541f54
Author: Scorp (scorp at mrs.mn)
Date: 2026-10-22T18:57:52+02:00
Commit Message:
HAROLD: BCB: Load executable text and Japanese and Korean fonts
Changed paths:
A engines/harold/bcb/executable_data.cpp
engines/harold/bcb/bcb.cpp
engines/harold/bcb/bcb.h
engines/harold/module.mk
graphics/sjis.h
diff --git a/engines/harold/bcb/bcb.cpp b/engines/harold/bcb/bcb.cpp
index 9294751ec00..17b363f21d4 100644
--- a/engines/harold/bcb/bcb.cpp
+++ b/engines/harold/bcb/bcb.cpp
@@ -38,6 +38,36 @@ namespace BCB {
static const int kCaptionTextNativeCharacterHeight = 18;
static const int kOwnerDrawUiTextNativeCharacterHeight = 16;
+static Graphics::Font *loadLocalizedCjkFont(Common::Language language, int size, Graphics::TTFSizeMode sizeMode) {
+ if (language == Common::KO_KOR) {
+ Graphics::Font *font = Graphics::loadTTFFontFromArchive("NotoSansKR-Regular.otf", size, sizeMode, 0, 0, Graphics::kTTFRenderModeNormal);
+ if (font)
+ return font;
+ font = Graphics::loadTTFFontFromArchive("NotoSansJP-Regular.otf", size, sizeMode, 0, 0, Graphics::kTTFRenderModeNormal);
+ if (font)
+ return font;
+ return Graphics::loadTTFFontFromArchive("VL-Gothic-Regular.ttf", size, sizeMode, 0, 0, Graphics::kTTFRenderModeNormal);
+ }
+
+ Graphics::Font *font = Graphics::loadTTFFontFromArchive("VL-Gothic-Regular.ttf", size, sizeMode, 0, 0, Graphics::kTTFRenderModeNormal);
+ if (font)
+ return font;
+ return Graphics::loadTTFFontFromArchive("NotoSansJP-Regular.otf", size, sizeMode, 0, 0, Graphics::kTTFRenderModeNormal);
+}
+
+static Graphics::Font *loadLocalizedOwnerDrawFont(Common::Language language) {
+ if (language == Common::KO_KOR) {
+ Graphics::Font *font = Graphics::loadTTFFontFromArchive("NotoSansKR-Regular.otf", kOwnerDrawUiTextNativeCharacterHeight, Graphics::kTTFSizeModeCharacter, 0, 0, Graphics::kTTFRenderModeNormal);
+ if (font)
+ return font;
+ }
+
+ Graphics::Font *font = Graphics::loadTTFFontFromArchive("NotoSansJP-Regular.otf", kOwnerDrawUiTextNativeCharacterHeight, Graphics::kTTFSizeModeCharacter, 0, 0, Graphics::kTTFRenderModeNormal);
+ if (font)
+ return font;
+ return Graphics::loadTTFFontFromArchive("VL-Gothic-Regular.ttf", kOwnerDrawUiTextNativeCharacterHeight, Graphics::kTTFSizeModeCharacter, 0, 0, Graphics::kTTFRenderModeNormal);
+}
+
BCBEngine::BCBEngine(OSystem *syst, const HaroldGameDescription *desc)
: Engine(syst), _gameDescription(desc) {
}
@@ -51,6 +81,10 @@ BCBEngine::~BCBEngine() {
_chipBitmap->free();
delete _chipBitmap;
}
+ delete _sjisFont;
+ delete _captionTextFont;
+ delete _gameOverTextFont;
+ delete _ownerDrawUiTextFont;
}
Common::Error BCBEngine::run() {
@@ -65,10 +99,19 @@ Common::Error BCBEngine::run() {
CursorMan.showMouse(true);
g_system->updateScreen();
_lastCursorRefreshMillis = g_system->getMillis();
+ _sjisFont = Graphics::FontSJIS::createFont();
+ _captionTextFont = loadLocalizedCjkFont(_gameDescription->desc.language, kCaptionTextNativeCharacterHeight, Graphics::kTTFSizeModeCharacter);
+ _gameOverTextFont = loadLocalizedCjkFont(_gameDescription->desc.language, 20, Graphics::kTTFSizeModeCell);
+ _ownerDrawUiTextFont = loadLocalizedOwnerDrawFont(_gameDescription->desc.language);
+ if (!_sjisFont && !_captionTextFont && !_gameOverTextFont && !_ownerDrawUiTextFont)
+ warning("BCBEngine::run(): No CJK font available; captions and choice text will not render");
if (!loadScriptData())
return Common::kReadingFailed;
+ if (!loadExecutableDataTables())
+ return Common::kReadingFailed;
+
resetGameState();
mainLoop();
diff --git a/engines/harold/bcb/bcb.h b/engines/harold/bcb/bcb.h
index 15e2885202f..f1c23a52226 100644
--- a/engines/harold/bcb/bcb.h
+++ b/engines/harold/bcb/bcb.h
@@ -67,6 +67,7 @@ private:
bool switchActiveMediaDisc(int discNumber);
void resetGameState();
bool loadScriptData();
+ bool loadExecutableDataTables();
bool loadByteFile(const char *fileName, Common::Array<byte> &data) const;
void executeCurrentScriptSequence();
uint32 executeScriptExpression(uint32 &scriptPc, int &branchTokenSeen);
@@ -93,6 +94,7 @@ private:
void serviceCursorRefresh(bool screenUpdated = false);
void delayWithEventPolling(uint32 millis);
void delayWithEventPolling(uint32 millis, bool handleClicks);
+ Common::U32String decodeExecutableTextForDisplay(const Common::String &text) const;
void mainLoop();
const HaroldGameDescription *_gameDescription;
@@ -103,6 +105,25 @@ private:
Common::Array<byte> _locationTimingData;
Common::Array<byte> _allBinData;
Common::Array<byte> _timedCaptionRecordData;
+ Common::Array<Common::Array<Common::U32String> > _sceneChoiceText;
+ Common::CodePage _executableTextEncoding = Common::kWindows932;
+ Common::String _sceneChoiceMoveTextSjis;
+ Common::U32String _sceneChoiceMoveText;
+ Common::Array<Common::String> _mapButtonTextSjis;
+ Common::Array<Common::U32String> _mapButtonText;
+ Common::Array<Common::U32String> _gameOverText;
+ Common::U32String _titleExitConfirmText;
+ Common::String _saveSlotOccupiedTextFormatSjis;
+ Common::String _saveSlotEmptyTextFormatSjis;
+ Common::String _loadSlotConfirmTextFormatSjis;
+ Common::String _saveSlotOverwriteConfirmTextFormatSjis;
+ Common::String _saveSlotEmptyConfirmTextFormatSjis;
+ Common::String _noSaveDataMessageSjis;
+ Common::String _returnToTitleConfirmTextSjis;
+ Graphics::FontSJIS *_sjisFont = nullptr;
+ const Graphics::Font *_captionTextFont = nullptr;
+ const Graphics::Font *_gameOverTextFont = nullptr;
+ const Graphics::Font *_ownerDrawUiTextFont = nullptr;
Graphics::Surface *_uiBitmap = nullptr;
Common::String _uiBitmapName;
Graphics::Surface *_chipBitmap = nullptr;
diff --git a/engines/harold/bcb/executable_data.cpp b/engines/harold/bcb/executable_data.cpp
new file mode 100644
index 00000000000..b5d37e61074
--- /dev/null
+++ b/engines/harold/bcb/executable_data.cpp
@@ -0,0 +1,311 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "common/algorithm.h"
+#include "common/file.h"
+#include "common/stream.h"
+#include "common/textconsole.h"
+
+#include "harold/bcb/bcb.h"
+
+namespace BCB {
+
+namespace {
+
+struct PeSection {
+ uint32 virtualAddress;
+ uint32 size;
+ uint32 rawOffset;
+ uint32 rawSize;
+};
+
+struct ExecutableDataProfile {
+ Common::Language language;
+ const char *executableName;
+ Common::CodePage encoding;
+ uint32 sceneChoiceTextTableVa;
+ uint32 sceneChoiceTextTableEndVa;
+ uint32 mapButtonTextPointerTableVa;
+ uint32 gameOverTextPointerTableVa;
+ uint32 saveSlotOccupiedTextFormatVa;
+ uint32 saveSlotEmptyTextFormatVa;
+ uint32 noSaveDataMessageVa;
+ uint32 sceneChoiceMoveTextVa;
+ uint32 loadSlotConfirmTextFormatVa;
+ uint32 saveSlotOverwriteConfirmTextFormatVa;
+ uint32 saveSlotEmptyConfirmTextFormatVa;
+ uint32 returnToTitleConfirmTextVa;
+ uint32 titleExitConfirmTextVa;
+};
+
+static const ExecutableDataProfile kJapaneseExecutableDataProfile = {
+ Common::JA_JPN,
+ "bcb.exe",
+ Common::kWindows932,
+ 0x00412000,
+ 0x00412d20,
+ 0x00412cd4,
+ 0x00413648,
+ 0x00416394,
+ 0x004163b4,
+ 0x004163bb,
+ 0x004163d0,
+ 0x004163d8,
+ 0x00416418,
+ 0x00416444,
+ 0x00416460,
+ 0x00416474};
+
+static const ExecutableDataProfile kKoreanExecutableDataProfile = {
+ Common::KO_KOR,
+ "bcb.exe",
+ Common::kWindows949,
+ 0x00421878,
+ 0x00422598,
+ 0x0041e404,
+ 0x0041f2a8,
+ 0x00423a10,
+ 0x004239f4,
+ 0x00423980,
+ 0x004239dc,
+ 0x004239a0,
+ 0x0042394c,
+ 0x00423934,
+ 0x004238fc,
+ 0x004238cc};
+
+static const ExecutableDataProfile *getExecutableDataProfile(Common::Language language) {
+ if (language == Common::KO_KOR)
+ return &kKoreanExecutableDataProfile;
+
+ return &kJapaneseExecutableDataProfile;
+}
+
+static const uint kSceneChoiceTextSlotsPerSet = 8;
+static const uint kMapButtonTextCount = 19;
+static const uint kGameOverTextCount = 10;
+
+static bool readExecutableBytes(Common::SeekableReadStream *stream, Common::Array<byte> &data) {
+ Common::ScopedPtr<Common::SeekableReadStream> executableStream(stream);
+ if (!executableStream)
+ return false;
+
+ const int32 size = executableStream->size();
+ if (size <= 0)
+ return false;
+
+ data.resize(size);
+ return executableStream->read(&data[0], size) == (uint32)size;
+}
+
+static uint16 readUint16LE(const Common::Array<byte> &data, uint offset) {
+ return data[offset] | (data[offset + 1] << 8);
+}
+
+static uint32 readUint32LE(const Common::Array<byte> &data, uint offset) {
+ return data[offset] | (data[offset + 1] << 8) | (data[offset + 2] << 16) | (data[offset + 3] << 24);
+}
+
+static bool virtualAddressToFileOffset(uint32 virtualAddress, uint32 imageBase, const Common::Array<PeSection> §ions, uint &fileOffset) {
+ const uint32 relativeAddress = virtualAddress - imageBase;
+ for (uint i = 0; i < sections.size(); ++i) {
+ const PeSection §ion = sections[i];
+ if (relativeAddress < section.virtualAddress || relativeAddress >= section.virtualAddress + section.size)
+ continue;
+
+ fileOffset = section.rawOffset + relativeAddress - section.virtualAddress;
+ return fileOffset < section.rawOffset + section.rawSize;
+ }
+
+ return false;
+}
+
+static bool readExecutableString(const Common::Array<byte> &executableData, uint32 virtualAddress, uint32 imageBase, const Common::Array<PeSection> §ions, Common::String &sjisText) {
+ uint textOffset = 0;
+ if (!virtualAddressToFileOffset(virtualAddress, imageBase, sections, textOffset))
+ return false;
+
+ uint textEnd = textOffset;
+ while (textEnd < executableData.size() && executableData[textEnd] != 0)
+ ++textEnd;
+
+ if (textEnd == textOffset)
+ return false;
+
+ sjisText = Common::String((const char *)&executableData[textOffset], textEnd - textOffset);
+ return true;
+}
+
+static bool readExecutableString(const Common::Array<byte> &executableData, uint32 virtualAddress, uint32 imageBase, const Common::Array<PeSection> §ions, Common::CodePage encoding, Common::U32String &text) {
+ Common::String sjisText;
+ if (!readExecutableString(executableData, virtualAddress, imageBase, sections, sjisText))
+ return false;
+
+ text = Common::U32String(sjisText.c_str(), sjisText.size(), encoding);
+ return true;
+}
+
+} // End of anonymous namespace
+
+bool BCBEngine::loadExecutableDataTables() {
+ const ExecutableDataProfile &profile = *getExecutableDataProfile(_gameDescription->desc.language);
+ _executableTextEncoding = profile.encoding;
+ Common::Array<byte> executableData;
+ if (!readExecutableBytes(createProgramReadStream(profile.executableName), executableData))
+ return false;
+
+ if (executableData.size() < 0x40 || executableData[0] != 'M' || executableData[1] != 'Z')
+ return false;
+
+ const uint32 peHeaderOffset = readUint32LE(executableData, 0x3c);
+ if (peHeaderOffset + 0x18 >= executableData.size() || executableData[peHeaderOffset] != 'P' || executableData[peHeaderOffset + 1] != 'E')
+ return false;
+
+ const uint sectionCount = readUint16LE(executableData, peHeaderOffset + 6);
+ const uint optionalHeaderSize = readUint16LE(executableData, peHeaderOffset + 20);
+ const uint optionalHeaderOffset = peHeaderOffset + 24;
+ const uint sectionTableOffset = optionalHeaderOffset + optionalHeaderSize;
+ if (sectionTableOffset + sectionCount * 40 > executableData.size())
+ return false;
+
+ const uint32 imageBase = readUint32LE(executableData, optionalHeaderOffset + 28);
+ Common::Array<PeSection> sections;
+ for (uint i = 0; i < sectionCount; ++i) {
+ const uint sectionOffset = sectionTableOffset + i * 40;
+ PeSection section;
+ const uint32 virtualSize = readUint32LE(executableData, sectionOffset + 8);
+ section.virtualAddress = readUint32LE(executableData, sectionOffset + 12);
+ section.rawSize = readUint32LE(executableData, sectionOffset + 16);
+ section.rawOffset = readUint32LE(executableData, sectionOffset + 20);
+ section.size = MAX<uint32>(virtualSize, section.rawSize);
+ sections.push_back(section);
+ }
+
+ uint tableOffset = 0;
+ uint tableEndOffset = 0;
+ if (!virtualAddressToFileOffset(profile.sceneChoiceTextTableVa, imageBase, sections, tableOffset))
+ return false;
+ if (!virtualAddressToFileOffset(profile.sceneChoiceTextTableEndVa, imageBase, sections, tableEndOffset))
+ return false;
+
+ const uint setCount = (tableEndOffset - tableOffset) / (kSceneChoiceTextSlotsPerSet * 4);
+ _sceneChoiceText.clear();
+ _sceneChoiceText.resize(setCount + 1);
+
+ for (uint setId = 1; setId <= setCount; ++setId) {
+ for (uint slot = 0; slot < kSceneChoiceTextSlotsPerSet; ++slot) {
+ const uint pointerOffset = tableOffset + ((setId - 1) * kSceneChoiceTextSlotsPerSet + slot) * 4;
+ const uint32 textVa = readUint32LE(executableData, pointerOffset);
+ uint textOffset = 0;
+ if (textVa == 0 || !virtualAddressToFileOffset(textVa, imageBase, sections, textOffset))
+ break;
+
+ uint textEnd = textOffset;
+ while (textEnd < executableData.size() && executableData[textEnd] != 0)
+ ++textEnd;
+ if (textEnd == textOffset)
+ break;
+
+ const char *textStart = (const char *)&executableData[textOffset];
+ _sceneChoiceText[setId].push_back(Common::U32String(textStart, textEnd - textOffset, profile.encoding));
+ }
+ }
+
+ if (!readExecutableString(executableData, profile.sceneChoiceMoveTextVa, imageBase, sections, _sceneChoiceMoveTextSjis))
+ return false;
+ _sceneChoiceMoveText = Common::U32String(_sceneChoiceMoveTextSjis.c_str(), _sceneChoiceMoveTextSjis.size(), profile.encoding);
+
+ uint mapTextTableOffset = 0;
+ if (!virtualAddressToFileOffset(profile.mapButtonTextPointerTableVa, imageBase, sections, mapTextTableOffset))
+ return false;
+
+ _mapButtonTextSjis.clear();
+ _mapButtonText.clear();
+ _mapButtonTextSjis.resize(kMapButtonTextCount);
+ _mapButtonText.resize(kMapButtonTextCount);
+ for (uint i = 0; i < kMapButtonTextCount; ++i) {
+ const uint pointerOffset = mapTextTableOffset + i * 4;
+ const uint32 textVa = readUint32LE(executableData, pointerOffset);
+ uint textOffset = 0;
+ if (textVa == 0 || !virtualAddressToFileOffset(textVa, imageBase, sections, textOffset))
+ continue;
+
+ uint textEnd = textOffset;
+ while (textEnd < executableData.size() && executableData[textEnd] != 0)
+ ++textEnd;
+
+ if (textEnd == textOffset)
+ continue;
+
+ const char *textStart = (const char *)&executableData[textOffset];
+ _mapButtonTextSjis[i] = Common::String(textStart, textEnd - textOffset);
+ _mapButtonText[i] = Common::U32String(textStart, textEnd - textOffset, profile.encoding);
+ }
+
+ uint gameOverTextTableOffset = 0;
+ if (!virtualAddressToFileOffset(profile.gameOverTextPointerTableVa, imageBase, sections, gameOverTextTableOffset))
+ return false;
+
+ _gameOverText.clear();
+ _gameOverText.resize(kGameOverTextCount);
+ for (uint i = 0; i < kGameOverTextCount; ++i) {
+ const uint pointerOffset = gameOverTextTableOffset + i * 4;
+ const uint32 textVa = readUint32LE(executableData, pointerOffset);
+ uint textOffset = 0;
+ if (textVa == 0 || !virtualAddressToFileOffset(textVa, imageBase, sections, textOffset))
+ continue;
+
+ uint textEnd = textOffset;
+ while (textEnd < executableData.size() && executableData[textEnd] != 0)
+ ++textEnd;
+
+ if (textEnd == textOffset)
+ continue;
+
+ const char *textStart = (const char *)&executableData[textOffset];
+ _gameOverText[i] = Common::U32String(textStart, textEnd - textOffset, profile.encoding);
+ }
+
+ if (!readExecutableString(executableData, profile.titleExitConfirmTextVa, imageBase, sections, profile.encoding, _titleExitConfirmText))
+ return false;
+ if (!readExecutableString(executableData, profile.saveSlotOccupiedTextFormatVa, imageBase, sections, _saveSlotOccupiedTextFormatSjis))
+ return false;
+ if (!readExecutableString(executableData, profile.saveSlotEmptyTextFormatVa, imageBase, sections, _saveSlotEmptyTextFormatSjis))
+ return false;
+ if (!readExecutableString(executableData, profile.loadSlotConfirmTextFormatVa, imageBase, sections, _loadSlotConfirmTextFormatSjis))
+ return false;
+ if (!readExecutableString(executableData, profile.saveSlotOverwriteConfirmTextFormatVa, imageBase, sections, _saveSlotOverwriteConfirmTextFormatSjis))
+ return false;
+ if (!readExecutableString(executableData, profile.saveSlotEmptyConfirmTextFormatVa, imageBase, sections, _saveSlotEmptyConfirmTextFormatSjis))
+ return false;
+ if (!readExecutableString(executableData, profile.noSaveDataMessageVa, imageBase, sections, _noSaveDataMessageSjis))
+ return false;
+ if (!readExecutableString(executableData, profile.returnToTitleConfirmTextVa, imageBase, sections, _returnToTitleConfirmTextSjis))
+ return false;
+
+ return true;
+}
+
+Common::U32String BCBEngine::decodeExecutableTextForDisplay(const Common::String &text) const {
+ return Common::U32String(text.c_str(), text.size(), _executableTextEncoding);
+}
+
+} // End of namespace BCB
diff --git a/engines/harold/module.mk b/engines/harold/module.mk
index 521827de166..18c8286067a 100644
--- a/engines/harold/module.mk
+++ b/engines/harold/module.mk
@@ -6,6 +6,7 @@ MODULE_OBJS := \
bcb/scene.o \
bcb/input.o \
bcb/script.o \
+ bcb/executable_data.o \
bcb/static_data.o \
metaengine.o
diff --git a/graphics/sjis.h b/graphics/sjis.h
index 03df49a3c02..c801c54be57 100644
--- a/graphics/sjis.h
+++ b/graphics/sjis.h
@@ -19,13 +19,13 @@
*
*/
-// The code in this file is currently used in KYRA, SCI, SCUMM, SAGA,
-// and AGOS. If none of those are enabled, we will skip compiling it.
+// The code in this file is currently used in BCB, KYRA, SCI, SCUMM,
+// SAGA, and AGOS. If none of those are enabled, we will skip compiling it.
// We also enable this code for ScummVM builds including support
// for dynamic engine plugins.
// If you plan to use this code in another engine, you will have
// to add the proper define check here.
-#if !(defined(ENABLE_KYRA) || defined(ENABLE_SCI) || defined(ENABLE_SCUMM) || defined(ENABLE_SAGA) || defined(ENABLE_AGOS) || defined(DYNAMIC_MODULES))
+#if !(defined(ENABLE_BCB) || defined(ENABLE_KYRA) || defined(ENABLE_SCI) || defined(ENABLE_SCUMM) || defined(ENABLE_SAGA) || defined(ENABLE_AGOS) || defined(DYNAMIC_MODULES))
// If neither of the above mentioned is enabled, do not include the SJIS code.
Commit: d3942269c2bd3851db943d738826f329b4deb000
https://github.com/scummvm/scummvm/commit/d3942269c2bd3851db943d738826f329b4deb000
Author: Scorp (scorp at mrs.mn)
Date: 2026-10-22T18:57:52+02:00
Commit Message:
HAROLD: BCB: Play interface sounds and background audio
Changed paths:
A engines/harold/bcb/video.cpp
engines/harold/bcb/bcb.cpp
engines/harold/bcb/bcb.h
engines/harold/module.mk
diff --git a/engines/harold/bcb/bcb.cpp b/engines/harold/bcb/bcb.cpp
index 17b363f21d4..98671bf39dd 100644
--- a/engines/harold/bcb/bcb.cpp
+++ b/engines/harold/bcb/bcb.cpp
@@ -81,10 +81,14 @@ BCBEngine::~BCBEngine() {
_chipBitmap->free();
delete _chipBitmap;
}
+ delete _activeMovieVideo;
+ delete _sceneIdleVideo;
+ delete _gameOverVideo;
delete _sjisFont;
delete _captionTextFont;
delete _gameOverTextFont;
delete _ownerDrawUiTextFont;
+ stopMenuBackgroundMusic();
}
Common::Error BCBEngine::run() {
@@ -125,6 +129,18 @@ void BCBEngine::mainLoop() {
if (!pollEvents())
return;
+ if (_menuBackgroundVideo) {
+ if (_menuBackgroundVideo->endOfVideo()) {
+ _menuBackgroundVideo->stop();
+ _menuBackgroundVideo->rewind();
+ _menuBackgroundVideo->decodeNextFrame();
+ _menuBackgroundVideo->start();
+ }
+
+ if (_menuBackgroundVideo->needsUpdate())
+ _menuBackgroundVideo->decodeNextFrame();
+ }
+
serviceCursorRefresh(screenUpdated);
delayWithEventPolling(10);
diff --git a/engines/harold/bcb/bcb.h b/engines/harold/bcb/bcb.h
index f1c23a52226..0e14c431e29 100644
--- a/engines/harold/bcb/bcb.h
+++ b/engines/harold/bcb/bcb.h
@@ -65,6 +65,13 @@ private:
bool initializeSearchPaths();
Common::SeekableReadStream *createMovieReadStream(int movieId) const;
bool switchActiveMediaDisc(int discNumber);
+ void playUiSound();
+ void startMenuBackgroundMusic();
+ void stopMenuBackgroundMusic();
+ void startGameOverMusic();
+ void stopGameOverMusic();
+ void startSceneIdleMovie();
+ void stopSceneIdleMovie();
void resetGameState();
bool loadScriptData();
bool loadExecutableDataTables();
@@ -124,17 +131,24 @@ private:
const Graphics::Font *_captionTextFont = nullptr;
const Graphics::Font *_gameOverTextFont = nullptr;
const Graphics::Font *_ownerDrawUiTextFont = nullptr;
+ Video::AVIDecoder *_menuBackgroundVideo = nullptr;
+ Video::AVIDecoder *_gameOverVideo = nullptr;
+ Video::AVIDecoder *_sceneIdleVideo = nullptr;
+ Video::AVIDecoder *_activeMovieVideo = nullptr;
Graphics::Surface *_uiBitmap = nullptr;
Common::String _uiBitmapName;
Graphics::Surface *_chipBitmap = nullptr;
+ Audio::SoundHandle _uiSoundHandle;
uint32 _gameState[0x100] = {};
uint32 _scriptValueStack[0x100] = {};
int _scriptStackDepth = 0;
bool _scriptStopFlag = false;
+ int _currentDisplayState = 0;
int _pendingStillElapsedTimerTicks = 0;
int _gameOverElapsedTimerTicks = 0;
int _pendingStillDurationIndex = 0;
int _pendingClockTickDelta = 0;
+ int _sceneIdleMovieTimerTicks = 0;
int _activeMediaDiscNumber = 1;
uint _timedCaptionCursorOffset = 0;
int _nextTimedCaptionThreshold = 99999;
diff --git a/engines/harold/bcb/video.cpp b/engines/harold/bcb/video.cpp
new file mode 100644
index 00000000000..42e008b066c
--- /dev/null
+++ b/engines/harold/bcb/video.cpp
@@ -0,0 +1,136 @@
+/* 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 "audio/audiostream.h"
+#include "audio/decoders/wave.h"
+#include "common/system.h"
+#include "common/textconsole.h"
+#include "graphics/surface.h"
+#include "harold/bcb/bcb.h"
+#include "harold/bcb/script.h"
+#include "harold/bcb/static_data.h"
+#include "video/avi_decoder.h"
+
+namespace BCB {
+
+void BCBEngine::playUiSound() {
+ Common::SeekableReadStream *stream = createProgramReadStream("se.wav");
+ if (!stream)
+ return;
+
+ Audio::SeekableAudioStream *wavStream = Audio::makeWAVStream(stream, DisposeAfterUse::YES);
+ if (!wavStream)
+ return;
+
+ _mixer->playStream(Audio::Mixer::kSFXSoundType, &_uiSoundHandle, wavStream, -1, Audio::Mixer::kMaxChannelVolume);
+}
+
+void BCBEngine::startMenuBackgroundMusic() {
+ stopMenuBackgroundMusic();
+
+ Common::SeekableReadStream *stream = createDiscReadStream(1, "bgm/system.avi");
+ if (!stream)
+ return;
+
+ _menuBackgroundVideo = new Video::AVIDecoder();
+ _menuBackgroundVideo->setSoundType(Audio::Mixer::kMusicSoundType);
+ if (!_menuBackgroundVideo->loadStream(stream)) {
+ delete _menuBackgroundVideo;
+ _menuBackgroundVideo = nullptr;
+ return;
+ }
+
+ _menuBackgroundVideo->decodeNextFrame();
+ _menuBackgroundVideo->start();
+}
+
+void BCBEngine::stopMenuBackgroundMusic() {
+ if (_menuBackgroundVideo) {
+ delete _menuBackgroundVideo;
+ _menuBackgroundVideo = nullptr;
+ }
+}
+
+void BCBEngine::startGameOverMusic() {
+ stopGameOverMusic();
+
+ Common::SeekableReadStream *stream = createActiveDiscReadStream("bgm/gameover.avi");
+ if (!stream) {
+ warning("BCBEngine::startGameOverMusic(): Missing gameover.avi for active media disc %d", _activeMediaDiscNumber);
+ return;
+ }
+
+ _gameOverVideo = new Video::AVIDecoder();
+ _gameOverVideo->setSoundType(Audio::Mixer::kMusicSoundType);
+ if (!_gameOverVideo->loadStream(stream)) {
+ warning("BCBEngine::startGameOverMusic(): Could not load gameover.avi");
+ delete _gameOverVideo;
+ _gameOverVideo = nullptr;
+ return;
+ }
+
+ _gameOverVideo->decodeNextFrame();
+ _gameOverVideo->start();
+}
+
+void BCBEngine::stopGameOverMusic() {
+ if (!_gameOverVideo)
+ return;
+
+ _gameOverVideo->stop();
+ delete _gameOverVideo;
+ _gameOverVideo = nullptr;
+}
+
+void BCBEngine::startSceneIdleMovie() {
+ if (_sceneIdleVideo || _activeMovieVideo || _currentDisplayState != 1)
+ return;
+
+ const char *idleMoviePath = stateDword(kStateMainSceneNightFlag) != 0 ? "bgm/night.avi" : "bgm/day.avi";
+ Common::SeekableReadStream *stream = nullptr;
+ for (int discNumber = 1; discNumber <= 5 && !stream; ++discNumber)
+ stream = createDiscReadStream(discNumber, idleMoviePath);
+ if (!stream)
+ return;
+
+ _sceneIdleVideo = new Video::AVIDecoder();
+ _sceneIdleVideo->setSoundType(Audio::Mixer::kMusicSoundType);
+ if (!_sceneIdleVideo->loadStream(stream)) {
+ delete _sceneIdleVideo;
+ _sceneIdleVideo = nullptr;
+ return;
+ }
+
+ _sceneIdleVideo->setOutputPixelFormat(g_system->getScreenFormat());
+ _sceneIdleVideo->start();
+ _sceneIdleMovieTimerTicks = 0;
+}
+
+void BCBEngine::stopSceneIdleMovie() {
+ if (!_sceneIdleVideo)
+ return;
+
+ _sceneIdleVideo->stop();
+ delete _sceneIdleVideo;
+ _sceneIdleVideo = nullptr;
+}
+
+} // End of namespace BCB
diff --git a/engines/harold/module.mk b/engines/harold/module.mk
index 18c8286067a..78f6202fe47 100644
--- a/engines/harold/module.mk
+++ b/engines/harold/module.mk
@@ -3,6 +3,7 @@ MODULE := engines/harold
MODULE_OBJS := \
bcb/bcb.o \
bcb/resources.o \
+ bcb/video.o \
bcb/scene.o \
bcb/input.o \
bcb/script.o \
Commit: aef791fa1043549711c473789c554606cf77c85d
https://github.com/scummvm/scummvm/commit/aef791fa1043549711c473789c554606cf77c85d
Author: Scorp (scorp at mrs.mn)
Date: 2026-10-22T18:57:52+02:00
Commit Message:
HAROLD: BCB: Display the logo and title menu
Changed paths:
A engines/harold/bcb/screens.cpp
engines/harold/bcb/bcb.cpp
engines/harold/bcb/bcb.h
engines/harold/bcb/input.cpp
engines/harold/module.mk
diff --git a/engines/harold/bcb/bcb.cpp b/engines/harold/bcb/bcb.cpp
index 98671bf39dd..f5ebe22f114 100644
--- a/engines/harold/bcb/bcb.cpp
+++ b/engines/harold/bcb/bcb.cpp
@@ -88,6 +88,10 @@ BCBEngine::~BCBEngine() {
delete _captionTextFont;
delete _gameOverTextFont;
delete _ownerDrawUiTextFont;
+ if (_titleBitmap) {
+ _titleBitmap->free();
+ delete _titleBitmap;
+ }
stopMenuBackgroundMusic();
}
@@ -118,6 +122,9 @@ Common::Error BCBEngine::run() {
resetGameState();
+ if (!showInitialLogoScreen())
+ return Common::kReadingFailed;
+
mainLoop();
return Common::kNoError;
}
@@ -129,6 +136,16 @@ void BCBEngine::mainLoop() {
if (!pollEvents())
return;
+ if (_currentDisplayState == 10) {
+ const uint32 now = g_system->getMillis();
+ while (now - _lastTimerTick >= 50 && _currentDisplayState == 10) {
+ _lastTimerTick += 50;
+ ++_initialLogoElapsedTimerTicks;
+ if (_initialLogoElapsedTimerTicks > 0x3c)
+ transitionFromLogoToTitle();
+ }
+ }
+
if (_menuBackgroundVideo) {
if (_menuBackgroundVideo->endOfVideo()) {
_menuBackgroundVideo->stop();
diff --git a/engines/harold/bcb/bcb.h b/engines/harold/bcb/bcb.h
index 0e14c431e29..3132d557e89 100644
--- a/engines/harold/bcb/bcb.h
+++ b/engines/harold/bcb/bcb.h
@@ -63,6 +63,9 @@ public:
private:
bool initializeSearchPaths();
+ bool showInitialLogoScreen();
+ bool showTitleScreen();
+ bool transitionFromLogoToTitle();
Common::SeekableReadStream *createMovieReadStream(int movieId) const;
bool switchActiveMediaDisc(int discNumber);
void playUiSound();
@@ -72,6 +75,7 @@ private:
void stopGameOverMusic();
void startSceneIdleMovie();
void stopSceneIdleMovie();
+ void restoreTitleScreenRegion(const Common::Rect &rect, bool present = true);
void resetGameState();
bool loadScriptData();
bool loadExecutableDataTables();
@@ -95,6 +99,8 @@ private:
Common::SeekableReadStream *createDiscReadStream(int discNumber, const char *relativePath) const;
Common::SeekableReadStream *createActiveDiscReadStream(const char *relativePath) const;
Common::SeekableReadStream *createActiveDiscHighColorReadStream(const char *relativePath) const;
+ void blitScreenWithLineReveal(const Graphics::Surface &surface, bool presentFinalFrame = true);
+ void drawTitleButton(uint buttonIndex, bool pressed);
void handleLeftButtonDown(const Common::Point &mousePosition);
bool pollEvents();
bool pollEvents(bool handleClicks);
@@ -135,6 +141,7 @@ private:
Video::AVIDecoder *_gameOverVideo = nullptr;
Video::AVIDecoder *_sceneIdleVideo = nullptr;
Video::AVIDecoder *_activeMovieVideo = nullptr;
+ Graphics::Surface *_titleBitmap = nullptr;
Graphics::Surface *_uiBitmap = nullptr;
Common::String _uiBitmapName;
Graphics::Surface *_chipBitmap = nullptr;
@@ -144,6 +151,7 @@ private:
int _scriptStackDepth = 0;
bool _scriptStopFlag = false;
int _currentDisplayState = 0;
+ int _initialLogoElapsedTimerTicks = 0;
int _pendingStillElapsedTimerTicks = 0;
int _gameOverElapsedTimerTicks = 0;
int _pendingStillDurationIndex = 0;
@@ -157,11 +165,15 @@ private:
int _sceneChoiceTerminatorButtonIndex = 0;
int _sceneChoiceEnabledButtonLimit = 0;
Common::U32String _timedCaptionText;
+ uint32 _lastTimerTick = 0;
int _lastLoggedGameClockTicks = -1;
uint32 _lastCursorRefreshMillis = 0;
Common::Point _mousePosition;
bool _sceneChoiceActive = false;
bool _suppressClickHandling = false;
+ bool _titleButtonsActive = false;
+ bool _systemButtonsVisible = false;
+ bool _titleLoadSlotsActive = false;
};
} // End of namespace BCB
diff --git a/engines/harold/bcb/input.cpp b/engines/harold/bcb/input.cpp
index a7595b18a0c..f2742d758a7 100644
--- a/engines/harold/bcb/input.cpp
+++ b/engines/harold/bcb/input.cpp
@@ -38,6 +38,30 @@ static const int kMovieControlStripSkipPressedSourceX = 0x98;
static const int kMovieControlStripPausePressedSourceX = 0x130;
void BCBEngine::handleLeftButtonDown(const Common::Point &mousePosition) {
+ if (_currentDisplayState == 10) {
+ transitionFromLogoToTitle();
+ return;
+ }
+
+ if (_currentDisplayState != 0 || !_titleButtonsActive)
+ return;
+
+ for (uint i = 1; i < ARRAYSIZE(kTitleButtonTable); ++i) {
+ const ButtonControlRecord &button = kTitleButtonTable[i];
+ const Common::Rect rect(button.x, button.y, button.x + button.width, button.y + button.height);
+ if (!rect.contains(mousePosition))
+ continue;
+
+ drawTitleButton(i, true);
+ delayWithEventPolling(80);
+
+ switch (button.controlId) {
+ default:
+ drawTitleButton(i, false);
+ break;
+ }
+ return;
+ }
}
bool BCBEngine::pollEvents() {
diff --git a/engines/harold/bcb/screens.cpp b/engines/harold/bcb/screens.cpp
new file mode 100644
index 00000000000..3f160a72806
--- /dev/null
+++ b/engines/harold/bcb/screens.cpp
@@ -0,0 +1,144 @@
+/* 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/system.h"
+#include "graphics/font.h"
+#include "graphics/surface.h"
+#include "harold/bcb/bcb.h"
+#include "harold/bcb/script.h"
+#include "harold/bcb/static_data.h"
+
+namespace BCB {
+
+static const int kGameOverTextLineGap = 2;
+
+bool BCBEngine::showInitialLogoScreen() {
+ Graphics::Surface *logo = loadBitmap("16mlogo.bmp");
+ if (!logo)
+ return false;
+
+ blitScreenWithLineReveal(*logo);
+
+ logo->free();
+ delete logo;
+
+ _currentDisplayState = 10;
+ _titleButtonsActive = false;
+ _systemButtonsVisible = false;
+ _initialLogoElapsedTimerTicks = 0;
+ _lastTimerTick = g_system->getMillis();
+ return true;
+}
+
+bool BCBEngine::showTitleScreen() {
+ stopGameOverMusic();
+ if (_titleBitmap && _titleBitmap->format != g_system->getScreenFormat()) {
+ _titleBitmap->free();
+ delete _titleBitmap;
+ _titleBitmap = nullptr;
+ }
+
+ if (!_titleBitmap)
+ _titleBitmap = loadBitmap("16mtitle.bmp");
+ if (!_titleBitmap)
+ return false;
+
+ blitScreenWithLineReveal(*_titleBitmap);
+
+ _currentDisplayState = 0;
+ _titleButtonsActive = true;
+ _systemButtonsVisible = false;
+ _titleLoadSlotsActive = false;
+ return true;
+}
+
+void BCBEngine::restoreTitleScreenRegion(const Common::Rect &rect, bool present) {
+ if (!_titleBitmap)
+ return;
+
+ const Common::Rect titleBounds(0, 0, _titleBitmap->w, _titleBitmap->h);
+ Common::Rect clippedRect = rect;
+ clippedRect.clip(titleBounds);
+ if (clippedRect.isEmpty())
+ return;
+
+ g_system->copyRectToScreen(_titleBitmap->getBasePtr(clippedRect.left, clippedRect.top), _titleBitmap->pitch,
+ clippedRect.left, clippedRect.top, clippedRect.width(), clippedRect.height());
+ if (present)
+ g_system->updateScreen();
+}
+
+bool BCBEngine::transitionFromLogoToTitle() {
+ if (!showTitleScreen())
+ return false;
+
+ startMenuBackgroundMusic();
+ return true;
+}
+
+void BCBEngine::drawTitleButton(uint buttonIndex, bool pressed) {
+ if (!_titleBitmap || buttonIndex == 0 || buttonIndex >= ARRAYSIZE(kTitleButtonTable))
+ return;
+
+ const ButtonControlRecord &button = kTitleButtonTable[buttonIndex];
+ const int sourceX = pressed ? 0xa2 : button.x;
+ const int sourceY = pressed ? (int)((buttonIndex + 0x16) * 0x20) : button.y;
+
+ if (sourceX + button.width > _titleBitmap->w || sourceY + button.height > _titleBitmap->h)
+ return;
+
+ g_system->copyRectToScreen(_titleBitmap->getBasePtr(sourceX, sourceY), _titleBitmap->pitch, button.x, button.y, button.width, button.height);
+ g_system->updateScreen();
+}
+
+void BCBEngine::blitScreenWithLineReveal(const Graphics::Surface &surface, bool presentFinalFrame) {
+ const int width = MIN<int>(surface.w, 640);
+ const int height = MIN<int>(surface.h, 480);
+ const int revealSteps = (height + 1) / 2;
+
+ const uint32 revealStartMillis = g_system->getMillis();
+ int revealedSteps = 0;
+ while (revealedSteps < revealSteps && !shouldQuit()) {
+ uint32 elapsedMillis = g_system->getMillis() - revealStartMillis;
+ int targetSteps = MIN<int>(elapsedMillis / 2 + 1, revealSteps);
+ if (targetSteps <= revealedSteps) {
+ pollEvents(false);
+ serviceCursorRefresh();
+ g_system->delayMillis(1);
+ continue;
+ }
+
+ for (; revealedSteps < targetSteps; ++revealedSteps) {
+ const int topY = revealedSteps * 2;
+ const int bottomY = height - 1 - revealedSteps * 2;
+ if (topY < height)
+ g_system->copyRectToScreen(surface.getBasePtr(0, topY), surface.pitch, 0, topY, width, 1);
+ if (bottomY != topY && bottomY >= 0)
+ g_system->copyRectToScreen(surface.getBasePtr(0, bottomY), surface.pitch, 0, bottomY, width, 1);
+ }
+
+ if (presentFinalFrame || revealedSteps < revealSteps)
+ g_system->updateScreen();
+ pollEvents(false);
+ }
+}
+
+} // End of namespace BCB
diff --git a/engines/harold/module.mk b/engines/harold/module.mk
index 78f6202fe47..83807ec72d9 100644
--- a/engines/harold/module.mk
+++ b/engines/harold/module.mk
@@ -5,6 +5,7 @@ MODULE_OBJS := \
bcb/resources.o \
bcb/video.o \
bcb/scene.o \
+ bcb/screens.o \
bcb/input.o \
bcb/script.o \
bcb/executable_data.o \
Commit: c8ea2ba02193cad441a9be91df86e746d493513e
https://github.com/scummvm/scummvm/commit/c8ea2ba02193cad441a9be91df86e746d493513e
Author: Scorp (scorp at mrs.mn)
Date: 2026-10-22T18:57:52+02:00
Commit Message:
HAROLD: BCB: Play numbered movies and dispatch completion
Changed paths:
engines/harold/bcb/bcb.cpp
engines/harold/bcb/bcb.h
engines/harold/bcb/input.cpp
engines/harold/bcb/scene.cpp
engines/harold/bcb/screens.cpp
engines/harold/bcb/video.cpp
diff --git a/engines/harold/bcb/bcb.cpp b/engines/harold/bcb/bcb.cpp
index f5ebe22f114..7e061644ced 100644
--- a/engines/harold/bcb/bcb.cpp
+++ b/engines/harold/bcb/bcb.cpp
@@ -158,10 +158,48 @@ void BCBEngine::mainLoop() {
_menuBackgroundVideo->decodeNextFrame();
}
+ if (_activeMovieVideo) {
+ bool movieUpdated = false;
+ if (_activeMovieVideo->needsUpdate()) {
+ const Graphics::Surface *frame = _activeMovieVideo->decodeNextFrame();
+ if (frame) {
+ const Common::Point moviePosition = getActiveMoviePosition();
+ g_system->copyRectToScreen(frame->getPixels(), frame->pitch, moviePosition.x, moviePosition.y, frame->w, frame->h);
+ movieUpdated = true;
+ }
+ }
+ if (movieUpdated) {
+ g_system->updateScreen();
+ screenUpdated = true;
+ }
+
+ if (_activeMovieVideo->endOfVideo()) {
+ const bool dispatchAfterEnd = _activeMovieDispatchOnFinish;
+ stopActiveMovie();
+ if (dispatchAfterEnd) {
+ _activeMovieDispatchOnFinish = false;
+ if (_currentDisplayState == 7 || _currentDisplayState == 8) {
+ processQueuedMovieSequence();
+ } else if (_currentDisplayState == 1) {
+ drawMainSceneScreen(false);
+ processGameStateDispatcher(true);
+ } else {
+ if (_currentDisplayState == 2)
+ drawClockAndChipIndicator();
+ processGameStateDispatcher(true);
+ }
+ }
+ }
+ }
+
serviceCursorRefresh(screenUpdated);
delayWithEventPolling(10);
}
}
+void BCBEngine::processGameStateDispatcher(bool runScriptBeforeDispatch) {
+ error("BCB: processGameStateDispatcher is not implemented");
+}
+
} // End of namespace BCB
diff --git a/engines/harold/bcb/bcb.h b/engines/harold/bcb/bcb.h
index 3132d557e89..30558d458b3 100644
--- a/engines/harold/bcb/bcb.h
+++ b/engines/harold/bcb/bcb.h
@@ -66,8 +66,11 @@ private:
bool showInitialLogoScreen();
bool showTitleScreen();
bool transitionFromLogoToTitle();
+ bool startNewGameFromTitle();
+ bool playNumberedMovie(int movieId, bool blankScreenMovie = false);
Common::SeekableReadStream *createMovieReadStream(int movieId) const;
bool switchActiveMediaDisc(int discNumber);
+ void stopActiveMovie();
void playUiSound();
void startMenuBackgroundMusic();
void stopMenuBackgroundMusic();
@@ -80,6 +83,7 @@ private:
bool loadScriptData();
bool loadExecutableDataTables();
bool loadByteFile(const char *fileName, Common::Array<byte> &data) const;
+ void processGameStateDispatcher(bool runScriptBeforeDispatch);
void executeCurrentScriptSequence();
uint32 executeScriptExpression(uint32 &scriptPc, int &branchTokenSeen);
bool pushScriptValue(uint32 value);
@@ -89,7 +93,13 @@ private:
uint32 &scriptOperandDword(uint32 encodedOperand);
uint32 &stateDword(uint byteOffset);
uint32 stateDword(uint byteOffset) const;
+ bool processPendingMovieRequest();
+ Common::Point getActiveMoviePosition() const;
+ void stopActiveMovieAndDispatch();
+ void processQueuedMovieSequence();
+ bool drawMainSceneScreen(bool lineReveal);
void notifyGameClockTicksChanged();
+ void drawClockAndChipIndicator(bool present = true);
Graphics::Surface *loadBitmap(const char *fileName);
const Graphics::Surface *getUiBitmap(const char *fileName);
Graphics::Surface *loadBitmapFromStream(Common::SeekableReadStream *stream, const char *debugName) const;
@@ -169,7 +179,11 @@ private:
int _lastLoggedGameClockTicks = -1;
uint32 _lastCursorRefreshMillis = 0;
Common::Point _mousePosition;
+ bool _activeMovieDispatchOnFinish = false;
+ bool _activeMovieOnBlankScreen = false;
+ bool _activeMoviePaused = false;
bool _sceneChoiceActive = false;
+ bool _mapControlsActive = false;
bool _suppressClickHandling = false;
bool _titleButtonsActive = false;
bool _systemButtonsVisible = false;
diff --git a/engines/harold/bcb/input.cpp b/engines/harold/bcb/input.cpp
index f2742d758a7..039cdde6fcb 100644
--- a/engines/harold/bcb/input.cpp
+++ b/engines/harold/bcb/input.cpp
@@ -38,6 +38,16 @@ static const int kMovieControlStripSkipPressedSourceX = 0x98;
static const int kMovieControlStripPausePressedSourceX = 0x130;
void BCBEngine::handleLeftButtonDown(const Common::Point &mousePosition) {
+ const bool activeMovieHit =
+ (_currentDisplayState == 0 && kTitleMciControlHitRect.contains(mousePosition)) ||
+ ((_currentDisplayState == 1 || _currentDisplayState == 7) && kMainSceneMciControlHitRect.contains(mousePosition)) ||
+ (_currentDisplayState == 8 && kMapSubmodeMciControlHitRect.contains(mousePosition));
+ if (_activeMovieVideo && activeMovieHit) {
+ delayWithEventPolling(160, false);
+ stopActiveMovieAndDispatch();
+ return;
+ }
+
if (_currentDisplayState == 10) {
transitionFromLogoToTitle();
return;
@@ -56,6 +66,9 @@ void BCBEngine::handleLeftButtonDown(const Common::Point &mousePosition) {
delayWithEventPolling(80);
switch (button.controlId) {
+ case 51:
+ startNewGameFromTitle();
+ break;
default:
drawTitleButton(i, false);
break;
diff --git a/engines/harold/bcb/scene.cpp b/engines/harold/bcb/scene.cpp
index 51238c7d389..0104a0899df 100644
--- a/engines/harold/bcb/scene.cpp
+++ b/engines/harold/bcb/scene.cpp
@@ -47,4 +47,12 @@ void BCBEngine::notifyGameClockTicksChanged() {
g_system->logMessage(LogMessageType::kInfo, message.c_str());
}
+void BCBEngine::drawClockAndChipIndicator(bool present) {
+ error("BCB: drawClockAndChipIndicator is not implemented");
+}
+
+bool BCBEngine::drawMainSceneScreen(bool lineReveal) {
+ error("BCB: drawMainSceneScreen is not implemented");
+}
+
} // End of namespace BCB
diff --git a/engines/harold/bcb/screens.cpp b/engines/harold/bcb/screens.cpp
index 3f160a72806..3d0418a68d5 100644
--- a/engines/harold/bcb/screens.cpp
+++ b/engines/harold/bcb/screens.cpp
@@ -94,6 +94,19 @@ bool BCBEngine::transitionFromLogoToTitle() {
return true;
}
+bool BCBEngine::startNewGameFromTitle() {
+ _titleButtonsActive = false;
+ _systemButtonsVisible = false;
+ stopMenuBackgroundMusic();
+ stopGameOverMusic();
+
+ g_system->fillScreen(g_system->getScreenFormat().RGBToColor(0, 0, 0));
+ g_system->updateScreen();
+
+ stateDword(kStateCurrentScriptSequenceId) = 1;
+ return playNumberedMovie(6, true);
+}
+
void BCBEngine::drawTitleButton(uint buttonIndex, bool pressed) {
if (!_titleBitmap || buttonIndex == 0 || buttonIndex >= ARRAYSIZE(kTitleButtonTable))
return;
diff --git a/engines/harold/bcb/video.cpp b/engines/harold/bcb/video.cpp
index 42e008b066c..eefa74a3722 100644
--- a/engines/harold/bcb/video.cpp
+++ b/engines/harold/bcb/video.cpp
@@ -31,6 +31,95 @@
namespace BCB {
+bool BCBEngine::playNumberedMovie(int movieId, bool blankScreenMovie) {
+ stopSceneIdleMovie();
+ stopActiveMovie();
+ _sceneChoiceActive = false;
+ _mapControlsActive = false;
+ _activeMovieOnBlankScreen = blankScreenMovie;
+
+ const Common::String movieName = Common::String::format("mov%04d.avi", movieId);
+ Common::SeekableReadStream *stream = createMovieReadStream(movieId);
+ if (!stream) {
+ warning("BCBEngine::playNumberedMovie(): Missing %s", movieName.c_str());
+ stream = createMovieReadStream(0);
+ }
+ if (!stream)
+ return false;
+
+ _activeMovieVideo = new Video::AVIDecoder();
+ _activeMovieVideo->setSoundType(Audio::Mixer::kSFXSoundType);
+ if (!_activeMovieVideo->loadStream(stream)) {
+ delete _activeMovieVideo;
+ _activeMovieVideo = nullptr;
+ return false;
+ }
+
+ _activeMovieVideo->setOutputPixelFormat(g_system->getScreenFormat());
+ _activeMovieVideo->start();
+ _activeMoviePaused = false;
+ _activeMovieDispatchOnFinish = true;
+ return true;
+}
+
+Common::Point BCBEngine::getActiveMoviePosition() const {
+ if (_currentDisplayState == 0)
+ return Common::Point(0x78, 0x64);
+ if (_currentDisplayState == 8)
+ return Common::Point(0x78, 0x38);
+ return Common::Point(0x10, 0x30);
+}
+
+void BCBEngine::stopActiveMovie() {
+ if (!_activeMovieVideo)
+ return;
+
+ _activeMovieVideo->stop();
+ delete _activeMovieVideo;
+ _activeMovieVideo = nullptr;
+ _activeMovieOnBlankScreen = false;
+ _activeMoviePaused = false;
+}
+
+void BCBEngine::stopActiveMovieAndDispatch() {
+ if (!_activeMovieVideo)
+ return;
+
+ const bool dispatchAfterStop = _activeMovieDispatchOnFinish;
+ stopActiveMovie();
+ playUiSound();
+ if (!dispatchAfterStop)
+ return;
+
+ _activeMovieDispatchOnFinish = false;
+ if (_currentDisplayState == 7 || _currentDisplayState == 8) {
+ processQueuedMovieSequence();
+ } else if (_currentDisplayState == 1) {
+ drawMainSceneScreen(false);
+ processGameStateDispatcher(true);
+ } else {
+ if (_currentDisplayState == 2)
+ drawClockAndChipIndicator();
+ processGameStateDispatcher(true);
+ }
+}
+
+bool BCBEngine::processPendingMovieRequest() {
+ const int movieId = stateDword(kStateMovieToPlayId);
+ if (movieId == 0)
+ return false;
+
+ playNumberedMovie(movieId);
+
+ uint32 &movieHistoryCount = stateDword(kStateMovieHistoryCount);
+ stateDword(kStateMovieHistory + MIN<uint32>(movieHistoryCount, 15) * 4) = movieId;
+ if (movieHistoryCount < 15)
+ ++movieHistoryCount;
+
+ stateDword(kStateMovieToPlayId) = 0;
+ return true;
+}
+
void BCBEngine::playUiSound() {
Common::SeekableReadStream *stream = createProgramReadStream("se.wav");
if (!stream)
@@ -133,4 +222,8 @@ void BCBEngine::stopSceneIdleMovie() {
_sceneIdleVideo = nullptr;
}
+void BCBEngine::processQueuedMovieSequence() {
+ error("BCB: processQueuedMovieSequence is not implemented");
+}
+
} // End of namespace BCB
Commit: 9ae51aa779de0837d2f9fe24e3d8b73d43bd7389
https://github.com/scummvm/scummvm/commit/9ae51aa779de0837d2f9fe24e3d8b73d43bd7389
Author: Scorp (scorp at mrs.mn)
Date: 2026-10-22T18:57:52+02:00
Commit Message:
HAROLD: BCB: Read timed captions from all.bin
Changed paths:
A engines/harold/bcb/captions.cpp
engines/harold/bcb/bcb.h
engines/harold/bcb/video.cpp
engines/harold/module.mk
diff --git a/engines/harold/bcb/bcb.h b/engines/harold/bcb/bcb.h
index 30558d458b3..5a57ee547ed 100644
--- a/engines/harold/bcb/bcb.h
+++ b/engines/harold/bcb/bcb.h
@@ -94,6 +94,10 @@ private:
uint32 &stateDword(uint byteOffset);
uint32 stateDword(uint byteOffset) const;
bool processPendingMovieRequest();
+ void loadTimedCaptionRecord(int movieId);
+ void loadNextTimedCaptionText();
+ bool readTimedCaptionLine(Common::String &line);
+ const byte *findAllBinRecordData(const Common::String &recordName, uint32 &recordSize) const;
Common::Point getActiveMoviePosition() const;
void stopActiveMovieAndDispatch();
void processQueuedMovieSequence();
@@ -175,6 +179,7 @@ private:
int _sceneChoiceTerminatorButtonIndex = 0;
int _sceneChoiceEnabledButtonLimit = 0;
Common::U32String _timedCaptionText;
+ Common::String _timedCaptionTextSjis;
uint32 _lastTimerTick = 0;
int _lastLoggedGameClockTicks = -1;
uint32 _lastCursorRefreshMillis = 0;
diff --git a/engines/harold/bcb/captions.cpp b/engines/harold/bcb/captions.cpp
new file mode 100644
index 00000000000..4878b5a9ed4
--- /dev/null
+++ b/engines/harold/bcb/captions.cpp
@@ -0,0 +1,181 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "common/algorithm.h"
+#include "common/system.h"
+#include "graphics/font.h"
+#include "graphics/sjis.h"
+#include "graphics/surface.h"
+#include "harold/bcb/bcb.h"
+#include "harold/bcb/script.h"
+#include "harold/bcb/static_data.h"
+#include "video/avi_decoder.h"
+
+namespace BCB {
+
+static const StaticRect kTitleCaptionTextRect = {120, 312, 520, 352};
+static const StaticRect kTitleCaptionBackingRect = {120, 312, 520, 362};
+static const StaticRect kMainSceneCaptionTextRect = {48, 260, 384, 300};
+static const StaticRect kMainSceneCaptionShadowRect = {50, 262, 384, 300};
+static const StaticRect kMapSubmodeCaptionTextRect = {152, 268, 488, 308};
+
+static uint32 readUint32BE(const byte *data) {
+ return ((uint32)data[0] << 24) | ((uint32)data[1] << 16) | ((uint32)data[2] << 8) | data[3];
+}
+
+const byte *BCBEngine::findAllBinRecordData(const Common::String &recordName, uint32 &recordSize) const {
+ recordSize = 0;
+ if (_allBinData.size() < 4)
+ return nullptr;
+
+ const uint32 entryCount = readUint32BE(&_allBinData[0]);
+ for (uint32 entryIndex = 0; entryIndex < entryCount; ++entryIndex) {
+ const uint entryOffset = 4 + entryIndex * 0x28;
+ if (entryOffset + 0x28 > _allBinData.size())
+ return nullptr;
+ if (readUint32BE(&_allBinData[entryOffset]) == 0xffffffff)
+ return nullptr;
+
+ uint nameLength = 0;
+ while (nameLength < 0x20 && _allBinData[entryOffset + 8 + nameLength] != 0)
+ ++nameLength;
+ const Common::String entryName((const char *)&_allBinData[entryOffset + 8], nameLength);
+ if (!recordName.equals(entryName))
+ continue;
+
+ const uint32 dataOffset = readUint32BE(&_allBinData[entryOffset]);
+ const uint32 dataSize = readUint32BE(&_allBinData[entryOffset + 4]);
+ if (dataOffset + dataSize > _allBinData.size())
+ return nullptr;
+
+ recordSize = dataSize;
+ return &_allBinData[dataOffset];
+ }
+
+ return nullptr;
+}
+
+void BCBEngine::loadTimedCaptionRecord(int movieId) {
+ _timedCaptionRecordData.clear();
+ _timedCaptionCursorOffset = 0;
+
+ uint32 recordSize = 0;
+ const Common::String recordName = Common::String::format("MES%04d.TXT", movieId);
+ const byte *recordData = findAllBinRecordData(recordName, recordSize);
+ if (recordData && recordSize) {
+ _timedCaptionRecordData.resize(recordSize);
+ Common::copy(recordData, recordData + recordSize, _timedCaptionRecordData.begin());
+ }
+
+ loadNextTimedCaptionText();
+}
+
+bool BCBEngine::readTimedCaptionLine(Common::String &line) {
+ line.clear();
+ if (_timedCaptionCursorOffset >= _timedCaptionRecordData.size())
+ return false;
+
+ int copiedCharCount = 0;
+ while (_timedCaptionCursorOffset + 1 < _timedCaptionRecordData.size()) {
+ if (_timedCaptionRecordData[_timedCaptionCursorOffset] == '\r' && _timedCaptionRecordData[_timedCaptionCursorOffset + 1] == '\n') {
+ _timedCaptionCursorOffset += 2;
+ return true;
+ }
+
+ line += (char)_timedCaptionRecordData[_timedCaptionCursorOffset++];
+ ++copiedCharCount;
+ if (copiedCharCount > 0x27)
+ return false;
+ }
+
+ return false;
+}
+
+static bool isCaptionEdgeSpace(byte c) {
+ return c == ' ' || c == '\t';
+}
+
+static void trimTimedCaptionLine(Common::String &line) {
+ const byte *text = (const byte *)line.c_str();
+ uint start = 0;
+ uint end = line.size();
+
+ while (start < end) {
+ if (isCaptionEdgeSpace(text[start])) {
+ ++start;
+ } else if (start + 1 < end && ((text[start] == 0x81 && text[start + 1] == 0x40) ||
+ (text[start] == 0xa1 && text[start + 1] == 0xa1))) {
+ start += 2;
+ } else {
+ break;
+ }
+ }
+
+ while (end > start) {
+ if (isCaptionEdgeSpace(text[end - 1])) {
+ --end;
+ } else if (end >= start + 2 && ((text[end - 2] == 0x81 && text[end - 1] == 0x40) ||
+ (text[end - 2] == 0xa1 && text[end - 1] == 0xa1))) {
+ end -= 2;
+ } else {
+ break;
+ }
+ }
+
+ if (start != 0 || end != line.size())
+ line = Common::String(line.c_str() + start, line.c_str() + end);
+}
+
+void BCBEngine::loadNextTimedCaptionText() {
+ if (_timedCaptionRecordData.empty() || _timedCaptionCursorOffset >= _timedCaptionRecordData.size()) {
+ _timedCaptionText.clear();
+ _timedCaptionTextSjis.clear();
+ _nextTimedCaptionThreshold = 99999;
+ return;
+ }
+
+ Common::String thresholdText;
+ Common::String firstLine;
+ Common::String secondLine;
+ if (!readTimedCaptionLine(thresholdText) || !readTimedCaptionLine(firstLine) || !readTimedCaptionLine(secondLine)) {
+ _timedCaptionText.clear();
+ _timedCaptionTextSjis.clear();
+ _nextTimedCaptionThreshold = 99999;
+ return;
+ }
+
+ trimTimedCaptionLine(firstLine);
+ trimTimedCaptionLine(secondLine);
+
+ _nextTimedCaptionThreshold = atoi(thresholdText.c_str());
+ _timedCaptionTextSjis = firstLine;
+ if (!secondLine.empty()) {
+ _timedCaptionTextSjis += '\n';
+ _timedCaptionTextSjis += secondLine;
+ }
+ _timedCaptionText = Common::U32String(firstLine.c_str(), firstLine.size(), _executableTextEncoding);
+ if (!secondLine.empty()) {
+ _timedCaptionText += U'\n';
+ _timedCaptionText += Common::U32String(secondLine.c_str(), secondLine.size(), _executableTextEncoding);
+ }
+}
+
+} // End of namespace BCB
diff --git a/engines/harold/bcb/video.cpp b/engines/harold/bcb/video.cpp
index eefa74a3722..4d9398e761f 100644
--- a/engines/harold/bcb/video.cpp
+++ b/engines/harold/bcb/video.cpp
@@ -37,6 +37,7 @@ bool BCBEngine::playNumberedMovie(int movieId, bool blankScreenMovie) {
_sceneChoiceActive = false;
_mapControlsActive = false;
_activeMovieOnBlankScreen = blankScreenMovie;
+ loadTimedCaptionRecord(movieId);
const Common::String movieName = Common::String::format("mov%04d.avi", movieId);
Common::SeekableReadStream *stream = createMovieReadStream(movieId);
diff --git a/engines/harold/module.mk b/engines/harold/module.mk
index 83807ec72d9..6b26e82f8ea 100644
--- a/engines/harold/module.mk
+++ b/engines/harold/module.mk
@@ -4,6 +4,7 @@ MODULE_OBJS := \
bcb/bcb.o \
bcb/resources.o \
bcb/video.o \
+ bcb/captions.o \
bcb/scene.o \
bcb/screens.o \
bcb/input.o \
Commit: 22dec7d6fa3c80665238db69e8d3d7b6838b48aa
https://github.com/scummvm/scummvm/commit/22dec7d6fa3c80665238db69e8d3d7b6838b48aa
Author: Scorp (scorp at mrs.mn)
Date: 2026-10-22T18:57:52+02:00
Commit Message:
HAROLD: BCB: Render captions and restore their backdrop
Changed paths:
engines/harold/bcb/bcb.cpp
engines/harold/bcb/bcb.h
engines/harold/bcb/captions.cpp
diff --git a/engines/harold/bcb/bcb.cpp b/engines/harold/bcb/bcb.cpp
index 7e061644ced..f8e058a0be6 100644
--- a/engines/harold/bcb/bcb.cpp
+++ b/engines/harold/bcb/bcb.cpp
@@ -168,6 +168,8 @@ void BCBEngine::mainLoop() {
movieUpdated = true;
}
}
+ if (drawTimedVideoTextOverlay())
+ movieUpdated = true;
if (movieUpdated) {
g_system->updateScreen();
screenUpdated = true;
diff --git a/engines/harold/bcb/bcb.h b/engines/harold/bcb/bcb.h
index 5a57ee547ed..b2efb006a2e 100644
--- a/engines/harold/bcb/bcb.h
+++ b/engines/harold/bcb/bcb.h
@@ -98,6 +98,11 @@ private:
void loadNextTimedCaptionText();
bool readTimedCaptionLine(Common::String &line);
const byte *findAllBinRecordData(const Common::String &recordName, uint32 &recordSize) const;
+ bool drawTimedVideoTextOverlay();
+ void restoreActiveCaptionBackingRect();
+ Common::Rect getCaptionBackingRect(const Common::String &sjisText, const Common::U32String &unicodeText, const Common::Rect &nativeRect) const;
+ void drawCaptionTextBlock(const Common::String &sjisText, const Common::U32String &unicodeText, const Common::Rect &rect, uint32 color);
+ void restoreCaptionBackingRect(const Common::Rect &rect);
Common::Point getActiveMoviePosition() const;
void stopActiveMovieAndDispatch();
void processQueuedMovieSequence();
@@ -180,6 +185,7 @@ private:
int _sceneChoiceEnabledButtonLimit = 0;
Common::U32String _timedCaptionText;
Common::String _timedCaptionTextSjis;
+ Common::Rect _activeCaptionBackingRect;
uint32 _lastTimerTick = 0;
int _lastLoggedGameClockTicks = -1;
uint32 _lastCursorRefreshMillis = 0;
diff --git a/engines/harold/bcb/captions.cpp b/engines/harold/bcb/captions.cpp
index 4878b5a9ed4..cd8ae11c738 100644
--- a/engines/harold/bcb/captions.cpp
+++ b/engines/harold/bcb/captions.cpp
@@ -178,4 +178,168 @@ void BCBEngine::loadNextTimedCaptionText() {
}
}
+void BCBEngine::restoreCaptionBackingRect(const Common::Rect &rect) {
+ if (_activeMovieOnBlankScreen) {
+ Graphics::Surface *screen = g_system->lockScreen();
+ if (screen) {
+ screen->fillRect(rect, screen->format.RGBToColor(0, 0, 0));
+ g_system->unlockScreen();
+ }
+ return;
+ }
+
+ const Graphics::Surface *backing = nullptr;
+ if (_currentDisplayState == 0 && _titleBitmap) {
+ backing = _titleBitmap;
+ } else if (_currentDisplayState == 1 || _currentDisplayState == 7) {
+ backing = getUiBitmap(stateDword(kStateMainSceneNightFlag) != 0 ? "16mmainn.bmp" : "16mmain.bmp");
+ } else if (_currentDisplayState == 8) {
+ Common::Rect restoreRect = rect;
+ restoreRect.clip(kMapSubmodeMovieBackdropRect);
+
+ const Graphics::Surface *map = getUiBitmap("16mmap.bmp");
+ if (map && !restoreRect.isEmpty()) {
+ const int sourceX = kMapSubmodeMovieBackdropSourceX + restoreRect.left - kMapSubmodeMovieBackdropRect.left;
+ const int sourceY = kMapSubmodeMovieBackdropSourceY + restoreRect.top - kMapSubmodeMovieBackdropRect.top;
+ if (sourceX >= 0 && sourceY >= 0 && sourceX + restoreRect.width() <= map->w && sourceY + restoreRect.height() <= map->h)
+ g_system->copyRectToScreen(map->getBasePtr(sourceX, sourceY), map->pitch, restoreRect.left, restoreRect.top, restoreRect.width(), restoreRect.height());
+ }
+
+ return;
+ }
+
+ if (backing)
+ g_system->copyRectToScreen(backing->getBasePtr(rect.left, rect.top), backing->pitch, rect.left, rect.top, rect.width(), rect.height());
+}
+
+Common::Rect BCBEngine::getCaptionBackingRect(const Common::String &sjisText, const Common::U32String &unicodeText, const Common::Rect &nativeRect) const {
+ int lineCount = 1;
+ int fontHeight = nativeRect.height();
+
+ if (_captionTextFont) {
+ lineCount = 0;
+ for (uint i = 0; i <= unicodeText.size(); ++i) {
+ if (i == unicodeText.size() || unicodeText[i] == U'\n')
+ ++lineCount;
+ }
+ fontHeight = _captionTextFont->getFontHeight();
+ } else if (_sjisFont) {
+ lineCount = 0;
+ for (uint i = 0; i <= sjisText.size(); ++i) {
+ if (i == sjisText.size() || sjisText[i] == '\n')
+ ++lineCount;
+ }
+ fontHeight = _sjisFont->getFontHeight();
+ }
+
+ Common::Rect backingRect = nativeRect;
+ backingRect.bottom = MAX<int>(backingRect.bottom, nativeRect.top + MAX<int>(lineCount, 1) * fontHeight);
+ backingRect.clip(Common::Rect(0, 0, 640, 480));
+ return backingRect;
+}
+
+static Common::U32String makeU32Substring(const Common::U32String &text, uint start, uint end) {
+ return Common::U32String(text.c_str() + start, end - start);
+}
+
+void BCBEngine::drawCaptionTextBlock(const Common::String &sjisText, const Common::U32String &unicodeText, const Common::Rect &rect, uint32 color) {
+ if (!_captionTextFont && !_sjisFont)
+ return;
+
+ Graphics::Surface *screen = g_system->lockScreen();
+ if (!screen)
+ return;
+
+ if (_captionTextFont) {
+ int y = rect.top;
+ for (uint lineStart = 0; lineStart <= unicodeText.size() && y < rect.bottom;) {
+ uint lineEnd = lineStart;
+ while (lineEnd < unicodeText.size() && unicodeText[lineEnd] != U'\n')
+ ++lineEnd;
+
+ _captionTextFont->drawString(screen, makeU32Substring(unicodeText, lineStart, lineEnd), rect.left, y, rect.width(), color, Graphics::kTextAlignCenter);
+ y += _captionTextFont->getFontHeight();
+ lineStart = lineEnd + 1;
+ }
+ } else {
+ int y = rect.top;
+ for (uint lineStart = 0; lineStart <= sjisText.size() && y < rect.bottom;) {
+ uint lineEnd = lineStart;
+ while (lineEnd < sjisText.size() && sjisText[lineEnd] != '\n')
+ ++lineEnd;
+
+ Common::Array<uint16> chars;
+ int textWidth = 0;
+ for (uint charOffset = lineStart; charOffset < lineEnd;) {
+ const byte firstByte = (byte)sjisText[charOffset++];
+ uint16 sjisChar = firstByte;
+ if (((firstByte >= 0x81 && firstByte <= 0x9f) || (firstByte >= 0xe0 && firstByte <= 0xef)) && charOffset < lineEnd)
+ sjisChar |= (uint16)((byte)sjisText[charOffset++]) << 8;
+ chars.push_back(sjisChar);
+ textWidth += _sjisFont->getCharWidth(sjisChar);
+ }
+
+ int lineX = rect.left + MAX<int>((rect.width() - textWidth) / 2, 0);
+ for (uint i = 0; i < chars.size(); ++i) {
+ if (lineX >= rect.right)
+ break;
+ _sjisFont->drawChar(*screen, chars[i], lineX, y, color, 0);
+ lineX += _sjisFont->getCharWidth(chars[i]);
+ }
+
+ y += _sjisFont->getFontHeight();
+ lineStart = lineEnd + 1;
+ }
+ }
+
+ g_system->unlockScreen();
+}
+
+bool BCBEngine::drawTimedVideoTextOverlay() {
+ if (!_activeMovieVideo || _nextTimedCaptionThreshold == 99999 || _activeMovieVideo->getCurFrame() < _nextTimedCaptionThreshold)
+ return false;
+
+ const Graphics::PixelFormat format = g_system->getScreenFormat();
+ const uint32 white = format.RGBToColor(255, 255, 255);
+ const uint32 black = format.RGBToColor(0, 0, 0);
+
+ restoreActiveCaptionBackingRect();
+ if (_timedCaptionTextSjis.empty() || _timedCaptionText.empty()) {
+ loadNextTimedCaptionText();
+ return true;
+ }
+
+ if (_currentDisplayState == 0) {
+ _activeCaptionBackingRect = getCaptionBackingRect(_timedCaptionTextSjis, _timedCaptionText, kTitleCaptionBackingRect);
+ drawCaptionTextBlock(_timedCaptionTextSjis, _timedCaptionText, kTitleCaptionTextRect, white);
+ } else if (_currentDisplayState == 1 || _currentDisplayState == 7) {
+ _activeCaptionBackingRect = getCaptionBackingRect(_timedCaptionTextSjis, _timedCaptionText, kMainSceneCaptionTextRect);
+ _activeCaptionBackingRect.extend(getCaptionBackingRect(_timedCaptionTextSjis, _timedCaptionText, kMainSceneCaptionShadowRect));
+ drawCaptionTextBlock(_timedCaptionTextSjis, _timedCaptionText, kMainSceneCaptionShadowRect, black);
+ drawCaptionTextBlock(_timedCaptionTextSjis, _timedCaptionText, kMainSceneCaptionTextRect, white);
+ } else if (_currentDisplayState == 8) {
+ _activeCaptionBackingRect = getCaptionBackingRect(_timedCaptionTextSjis, _timedCaptionText, kMapSubmodeCaptionTextRect);
+ drawCaptionTextBlock(_timedCaptionTextSjis, _timedCaptionText, kMapSubmodeCaptionTextRect, white);
+ }
+
+ loadNextTimedCaptionText();
+ return true;
+}
+
+void BCBEngine::restoreActiveCaptionBackingRect() {
+ if (!_activeCaptionBackingRect.isEmpty()) {
+ restoreCaptionBackingRect(_activeCaptionBackingRect);
+ _activeCaptionBackingRect = Common::Rect();
+ return;
+ }
+
+ if (_currentDisplayState == 0) {
+ restoreCaptionBackingRect(kTitleCaptionBackingRect);
+ } else if (_currentDisplayState == 1 || _currentDisplayState == 7) {
+ restoreCaptionBackingRect(kMainSceneCaptionTextRect);
+ } else if (_currentDisplayState == 8) {
+ restoreCaptionBackingRect(kMapSubmodeCaptionTextRect);
+ }
+}
+
} // End of namespace BCB
Commit: 07c82a3499c51b0784f4e752053b772ff5b75604
https://github.com/scummvm/scummvm/commit/07c82a3499c51b0784f4e752053b772ff5b75604
Author: Scorp (scorp at mrs.mn)
Date: 2026-10-22T18:57:52+02:00
Commit Message:
HAROLD: BCB: Add movie controls and queued replay
Changed paths:
A engines/harold/bcb/map.cpp
engines/harold/bcb/bcb.h
engines/harold/bcb/input.cpp
engines/harold/bcb/scene.cpp
engines/harold/bcb/video.cpp
engines/harold/module.mk
diff --git a/engines/harold/bcb/bcb.h b/engines/harold/bcb/bcb.h
index b2efb006a2e..9356a27840e 100644
--- a/engines/harold/bcb/bcb.h
+++ b/engines/harold/bcb/bcb.h
@@ -104,11 +104,18 @@ private:
void drawCaptionTextBlock(const Common::String &sjisText, const Common::U32String &unicodeText, const Common::Rect &rect, uint32 color);
void restoreCaptionBackingRect(const Common::Rect &rect);
Common::Point getActiveMoviePosition() const;
+ bool shouldDrawMovieControlStrip() const;
+ void drawMapSubmodeMovieBackdrop(bool present = true);
+ void drawSceneChoiceControlStrip(int sourceX = 0, bool present = true);
+ void drawMovieControlStrip(int sourceX = 0, bool present = true);
void stopActiveMovieAndDispatch();
void processQueuedMovieSequence();
+ void composeMapControlsToSurface(Graphics::Surface &surface, const Graphics::Surface &mapArt);
bool drawMainSceneScreen(bool lineReveal);
+ void applyPendingClockTickDelta();
void notifyGameClockTicksChanged();
void drawClockAndChipIndicator(bool present = true);
+ void renderClockAndChipToSurface(Graphics::Surface &surface, int baseHourOffset, int gameClockTicks);
Graphics::Surface *loadBitmap(const char *fileName);
const Graphics::Surface *getUiBitmap(const char *fileName);
Graphics::Surface *loadBitmapFromStream(Common::SeekableReadStream *stream, const char *debugName) const;
diff --git a/engines/harold/bcb/input.cpp b/engines/harold/bcb/input.cpp
index 039cdde6fcb..318c82849ae 100644
--- a/engines/harold/bcb/input.cpp
+++ b/engines/harold/bcb/input.cpp
@@ -38,11 +38,29 @@ static const int kMovieControlStripSkipPressedSourceX = 0x98;
static const int kMovieControlStripPausePressedSourceX = 0x130;
void BCBEngine::handleLeftButtonDown(const Common::Point &mousePosition) {
+ if (_activeMovieVideo && shouldDrawMovieControlStrip()) {
+ if (kSystemMenuControlHitRect.contains(mousePosition)) {
+ _activeMoviePaused = !_activeMoviePaused;
+ _activeMovieVideo->pauseVideo(_activeMoviePaused);
+ drawMovieControlStrip(_activeMoviePaused ? kMovieControlStripPausePressedSourceX : kMovieControlStripNormalSourceX);
+ return;
+ }
+
+ if (kMovieQueueControlHitRect.contains(mousePosition)) {
+ drawMovieControlStrip(kMovieControlStripSkipPressedSourceX);
+ delayWithEventPolling(160, false);
+ stopActiveMovieAndDispatch();
+ return;
+ }
+ }
+
const bool activeMovieHit =
(_currentDisplayState == 0 && kTitleMciControlHitRect.contains(mousePosition)) ||
((_currentDisplayState == 1 || _currentDisplayState == 7) && kMainSceneMciControlHitRect.contains(mousePosition)) ||
(_currentDisplayState == 8 && kMapSubmodeMciControlHitRect.contains(mousePosition));
if (_activeMovieVideo && activeMovieHit) {
+ if (shouldDrawMovieControlStrip())
+ drawMovieControlStrip(kMovieControlStripSkipPressedSourceX);
delayWithEventPolling(160, false);
stopActiveMovieAndDispatch();
return;
diff --git a/engines/harold/bcb/map.cpp b/engines/harold/bcb/map.cpp
new file mode 100644
index 00000000000..e22c93a29fd
--- /dev/null
+++ b/engines/harold/bcb/map.cpp
@@ -0,0 +1,43 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "common/algorithm.h"
+#include "common/fs.h"
+#include "common/ustr.h"
+#include "graphics/font.h"
+#include "graphics/sjis.h"
+#include "graphics/surface.h"
+#include "video/avi_decoder.h"
+
+#include "harold/bcb/bcb.h"
+#include "harold/bcb/script.h"
+#include "harold/bcb/static_data.h"
+
+#include "common/textconsole.h"
+
+namespace BCB {
+
+static const uint32 kScriptSecondaryStateByteOffset = 0x98;
+void BCBEngine::composeMapControlsToSurface(Graphics::Surface &surface, const Graphics::Surface &mapArt) {
+ error("BCB: composeMapControlsToSurface is not implemented");
+}
+
+} // End of namespace BCB
diff --git a/engines/harold/bcb/scene.cpp b/engines/harold/bcb/scene.cpp
index 0104a0899df..4765c019bff 100644
--- a/engines/harold/bcb/scene.cpp
+++ b/engines/harold/bcb/scene.cpp
@@ -47,6 +47,10 @@ void BCBEngine::notifyGameClockTicksChanged() {
g_system->logMessage(LogMessageType::kInfo, message.c_str());
}
+void BCBEngine::applyPendingClockTickDelta() {
+ error("BCB: applyPendingClockTickDelta is not implemented");
+}
+
void BCBEngine::drawClockAndChipIndicator(bool present) {
error("BCB: drawClockAndChipIndicator is not implemented");
}
@@ -55,4 +59,8 @@ bool BCBEngine::drawMainSceneScreen(bool lineReveal) {
error("BCB: drawMainSceneScreen is not implemented");
}
+void BCBEngine::renderClockAndChipToSurface(Graphics::Surface &surface, int baseHourOffset, int gameClockTicks) {
+ error("BCB: renderClockAndChipToSurface is not implemented");
+}
+
} // End of namespace BCB
diff --git a/engines/harold/bcb/video.cpp b/engines/harold/bcb/video.cpp
index 4d9398e761f..9b0c7f7d9a1 100644
--- a/engines/harold/bcb/video.cpp
+++ b/engines/harold/bcb/video.cpp
@@ -60,6 +60,7 @@ bool BCBEngine::playNumberedMovie(int movieId, bool blankScreenMovie) {
_activeMovieVideo->start();
_activeMoviePaused = false;
_activeMovieDispatchOnFinish = true;
+ drawMovieControlStrip();
return true;
}
@@ -71,6 +72,84 @@ Common::Point BCBEngine::getActiveMoviePosition() const {
return Common::Point(0x10, 0x30);
}
+bool BCBEngine::shouldDrawMovieControlStrip() const {
+ return !_activeMovieOnBlankScreen && (_currentDisplayState == 1 || _currentDisplayState == 7 || _currentDisplayState == 8);
+}
+
+void BCBEngine::drawMapSubmodeMovieBackdrop(bool present) {
+ const Graphics::Surface *map = getUiBitmap("16mmap.bmp");
+ if (map) {
+ const Common::Rect sourceRect(kMapSubmodeMovieBackdropSourceX, kMapSubmodeMovieBackdropSourceY,
+ kMapSubmodeMovieBackdropSourceX + 0x1a4, kMapSubmodeMovieBackdropSourceY + 0x11e);
+ if (sourceRect.right <= map->w && sourceRect.bottom <= map->h)
+ g_system->copyRectToScreen(map->getBasePtr(sourceRect.left, sourceRect.top), map->pitch,
+ kMapSubmodeMovieBackdropRect.left, kMapSubmodeMovieBackdropRect.top,
+ sourceRect.width(), sourceRect.height());
+ } else {
+ Graphics::Surface *screen = g_system->lockScreen();
+ if (!screen)
+ return;
+
+ const uint32 backingColor = screen->format.RGBToColor(0, 0, 0);
+ screen->fillRect(kMapSubmodeMciControlHitRect, backingColor);
+ g_system->unlockScreen();
+ }
+
+ if (present)
+ g_system->updateScreen();
+}
+
+void BCBEngine::drawMovieControlStrip(int sourceX, bool present) {
+ if (!shouldDrawMovieControlStrip())
+ return;
+
+ const Graphics::Surface *chip = getUiBitmap("16mchip.bmp");
+ if (chip) {
+ if (sourceX + 0x98 <= chip->w && 0xe0 + 0x30 <= chip->h)
+ g_system->copyRectToScreen(chip->getBasePtr(sourceX, 0xe0), chip->pitch, 0x104, 0x199, 0x98, 0x30);
+ if (present)
+ g_system->updateScreen();
+ }
+}
+
+void BCBEngine::drawSceneChoiceControlStrip(int sourceX, bool present) {
+ const Graphics::Surface *chip = getUiBitmap("16mchip.bmp");
+ if (chip) {
+ if (sourceX + 0x98 <= chip->w && 0xb0 + 0x30 <= chip->h)
+ g_system->copyRectToScreen(chip->getBasePtr(sourceX, 0xb0), chip->pitch, 0x104, 0x199, 0x98, 0x30);
+ if (present)
+ g_system->updateScreen();
+ }
+}
+
+void BCBEngine::processQueuedMovieSequence() {
+ const int movieHistoryCount = stateDword(kStateMovieHistoryCount);
+ if (_queuedMoviePlaybackIndex < movieHistoryCount) {
+ const int movieId = stateDword(kStateMovieHistory + _queuedMoviePlaybackIndex * 4);
+ playNumberedMovie(movieId);
+ ++_queuedMoviePlaybackIndex;
+ return;
+ }
+
+ if (_previousDisplayStateBeforeSystem == 1) {
+ drawSceneChoiceControlStrip();
+ } else if (_previousDisplayStateBeforeSystem == 2) {
+ Graphics::Surface *map = loadBitmap("16mmap.bmp");
+ if (map) {
+ applyPendingClockTickDelta();
+ renderClockAndChipToSurface(*map, 0, stateDword(kStateGameClockTicks));
+ composeMapControlsToSurface(*map, *map);
+ g_system->copyRectToScreen(map->getPixels(), map->pitch, 0, 0, MIN<int>(map->w, 640), MIN<int>(map->h, 480));
+ map->free();
+ delete map;
+ }
+ drawSceneChoiceControlStrip(0, false);
+ g_system->updateScreen();
+ }
+
+ processGameStateDispatcher(false);
+}
+
void BCBEngine::stopActiveMovie() {
if (!_activeMovieVideo)
return;
@@ -223,8 +302,4 @@ void BCBEngine::stopSceneIdleMovie() {
_sceneIdleVideo = nullptr;
}
-void BCBEngine::processQueuedMovieSequence() {
- error("BCB: processQueuedMovieSequence is not implemented");
-}
-
} // End of namespace BCB
diff --git a/engines/harold/module.mk b/engines/harold/module.mk
index 6b26e82f8ea..bf42103d2ce 100644
--- a/engines/harold/module.mk
+++ b/engines/harold/module.mk
@@ -8,6 +8,7 @@ MODULE_OBJS := \
bcb/scene.o \
bcb/screens.o \
bcb/input.o \
+ bcb/map.o \
bcb/script.o \
bcb/executable_data.o \
bcb/static_data.o \
Commit: ed1eb0c917349e76bac58ba52426fe522136b93f
https://github.com/scummvm/scummvm/commit/ed1eb0c917349e76bac58ba52426fe522136b93f
Author: Scorp (scorp at mrs.mn)
Date: 2026-10-22T18:57:52+02:00
Commit Message:
HAROLD: BCB: Draw the clock and apply elapsed game time
Changed paths:
engines/harold/bcb/bcb.h
engines/harold/bcb/scene.cpp
diff --git a/engines/harold/bcb/bcb.h b/engines/harold/bcb/bcb.h
index 9356a27840e..2ff09af23a6 100644
--- a/engines/harold/bcb/bcb.h
+++ b/engines/harold/bcb/bcb.h
@@ -115,6 +115,7 @@ private:
void applyPendingClockTickDelta();
void notifyGameClockTicksChanged();
void drawClockAndChipIndicator(bool present = true);
+ void drawAnalogClockAndChip(int baseHourOffset, int gameClockTicks, bool present = true);
void renderClockAndChipToSurface(Graphics::Surface &surface, int baseHourOffset, int gameClockTicks);
Graphics::Surface *loadBitmap(const char *fileName);
const Graphics::Surface *getUiBitmap(const char *fileName);
diff --git a/engines/harold/bcb/scene.cpp b/engines/harold/bcb/scene.cpp
index 4765c019bff..c9e1ddcf942 100644
--- a/engines/harold/bcb/scene.cpp
+++ b/engines/harold/bcb/scene.cpp
@@ -33,6 +33,12 @@
namespace BCB {
+void BCBEngine::applyPendingClockTickDelta() {
+ stateDword(kStateGameClockTicks) += _pendingClockTickDelta;
+ _pendingClockTickDelta = 0;
+ notifyGameClockTicksChanged();
+}
+
void BCBEngine::notifyGameClockTicksChanged() {
const int ticks = stateDword(kStateGameClockTicks);
if (_lastLoggedGameClockTicks == ticks)
@@ -47,20 +53,59 @@ void BCBEngine::notifyGameClockTicksChanged() {
g_system->logMessage(LogMessageType::kInfo, message.c_str());
}
-void BCBEngine::applyPendingClockTickDelta() {
- error("BCB: applyPendingClockTickDelta is not implemented");
+void BCBEngine::drawClockAndChipIndicator(bool present) {
+ applyPendingClockTickDelta();
+ drawAnalogClockAndChip(0, stateDword(kStateGameClockTicks), present);
}
-void BCBEngine::drawClockAndChipIndicator(bool present) {
- error("BCB: drawClockAndChipIndicator is not implemented");
+void BCBEngine::renderClockAndChipToSurface(Graphics::Surface &surface, int baseHourOffset, int gameClockTicks) {
+ static const double kClockDegreesToRadians = 0.017453292519943295769;
+ const int clockTicks = gameClockTicks % 1440;
+ const double hourAngle = (30.0 * (baseHourOffset % 24) + clockTicks / 2.0) * kClockDegreesToRadians;
+ const double minuteAngle = (clockTicks * 6.0) * kClockDegreesToRadians;
+ const int centerX = 0x55;
+ const int centerY = 0x198;
+
+ const uint32 handColor = surface.format.RGBToColor(0x50, 0x50, 0x50);
+ const int hourEndX = centerX + (int)floor(sin(hourAngle) * 25.0 + 0.5);
+ const int hourEndY = centerY - (int)floor(cos(hourAngle) * 25.0 + 0.5);
+ const int minuteEndX = centerX + (int)floor(sin(minuteAngle) * 32.0 + 0.5);
+ const int minuteEndY = centerY - (int)floor(cos(minuteAngle) * 32.0 + 0.5);
+
+ surface.drawThickLine(centerX, centerY, hourEndX, hourEndY, 4, 4, handColor);
+ surface.drawThickLine(centerX, centerY, minuteEndX, minuteEndY, 3, 3, handColor);
+
+ const Graphics::Surface *chip = getUiBitmap("16mchip.bmp");
+ if (chip) {
+ const int sourceX = CLIP<int>(stateDword(kStateRequiredDiscIndex), 0, 4) * 0x58;
+ if (sourceX + 0x58 <= chip->w && 0x32 <= chip->h)
+ surface.copyRectToSurface(*chip, 0x8c, 0x16c, Common::Rect(sourceX, 0, sourceX + 0x58, 0x32));
+ }
}
-bool BCBEngine::drawMainSceneScreen(bool lineReveal) {
- error("BCB: drawMainSceneScreen is not implemented");
+void BCBEngine::drawAnalogClockAndChip(int baseHourOffset, int gameClockTicks, bool present) {
+ const Graphics::Surface *clockBacking = nullptr;
+ if (_currentDisplayState == 2 || _currentDisplayState == 8) {
+ clockBacking = getUiBitmap("16mmap.bmp");
+ } else {
+ clockBacking = getUiBitmap(stateDword(kStateMainSceneNightFlag) != 0 ? "16mmainn.bmp" : "16mmain.bmp");
+ }
+ if (clockBacking) {
+ g_system->copyRectToScreen(clockBacking->getBasePtr(0x25, 0x167), clockBacking->pitch, 0x25, 0x167, 0x60, 0x60);
+ }
+
+ Graphics::Surface *screen = g_system->lockScreen();
+ if (screen) {
+ renderClockAndChipToSurface(*screen, baseHourOffset, gameClockTicks);
+ g_system->unlockScreen();
+ }
+
+ if (present)
+ g_system->updateScreen();
}
-void BCBEngine::renderClockAndChipToSurface(Graphics::Surface &surface, int baseHourOffset, int gameClockTicks) {
- error("BCB: renderClockAndChipToSurface is not implemented");
+bool BCBEngine::drawMainSceneScreen(bool lineReveal) {
+ error("BCB: drawMainSceneScreen is not implemented");
}
} // End of namespace BCB
Commit: b95075a0870044cbf1eeb68d609622c3e66352b1
https://github.com/scummvm/scummvm/commit/b95075a0870044cbf1eeb68d609622c3e66352b1
Author: Scorp (scorp at mrs.mn)
Date: 2026-10-22T18:57:52+02:00
Commit Message:
HAROLD: BCB: Compose conversation screens and backing
Changed paths:
engines/harold/bcb/bcb.cpp
engines/harold/bcb/bcb.h
engines/harold/bcb/scene.cpp
diff --git a/engines/harold/bcb/bcb.cpp b/engines/harold/bcb/bcb.cpp
index f8e058a0be6..309bdf7b0e6 100644
--- a/engines/harold/bcb/bcb.cpp
+++ b/engines/harold/bcb/bcb.cpp
@@ -84,6 +84,7 @@ BCBEngine::~BCBEngine() {
delete _activeMovieVideo;
delete _sceneIdleVideo;
delete _gameOverVideo;
+ freeSceneChoiceBacking();
delete _sjisFont;
delete _captionTextFont;
delete _gameOverTextFont;
@@ -158,6 +159,24 @@ void BCBEngine::mainLoop() {
_menuBackgroundVideo->decodeNextFrame();
}
+ if (_sceneIdleVideo) {
+ if (_sceneIdleVideo->endOfVideo()) {
+ _sceneIdleVideo->stop();
+ _sceneIdleVideo->rewind();
+ _sceneIdleVideo->decodeNextFrame();
+ _sceneIdleVideo->start();
+ }
+
+ if (_sceneIdleVideo->needsUpdate()) {
+ const Graphics::Surface *frame = _sceneIdleVideo->decodeNextFrame();
+ if (frame) {
+ g_system->copyRectToScreen(frame->getPixels(), frame->pitch, 0x10, 0x30, frame->w, frame->h);
+ g_system->updateScreen();
+ screenUpdated = true;
+ }
+ }
+ }
+
if (_activeMovieVideo) {
bool movieUpdated = false;
if (_activeMovieVideo->needsUpdate()) {
diff --git a/engines/harold/bcb/bcb.h b/engines/harold/bcb/bcb.h
index 2ff09af23a6..5f045715b08 100644
--- a/engines/harold/bcb/bcb.h
+++ b/engines/harold/bcb/bcb.h
@@ -111,7 +111,13 @@ private:
void stopActiveMovieAndDispatch();
void processQueuedMovieSequence();
void composeMapControlsToSurface(Graphics::Surface &surface, const Graphics::Surface &mapArt);
+ bool showMainSceneScreen();
bool drawMainSceneScreen(bool lineReveal);
+ void captureSceneChoiceBacking();
+ void restoreSceneChoiceBacking(bool present = true);
+ void freeSceneChoiceBacking();
+ void clearSceneChoiceAfterSelection();
+ bool isSceneChoiceButtonVisible(int buttonIndex) const;
void applyPendingClockTickDelta();
void notifyGameClockTicksChanged();
void drawClockAndChipIndicator(bool present = true);
@@ -172,6 +178,8 @@ private:
Graphics::Surface *_uiBitmap = nullptr;
Common::String _uiBitmapName;
Graphics::Surface *_chipBitmap = nullptr;
+ Graphics::Surface *_sceneChoiceBacking = nullptr;
+ Common::Rect _sceneChoiceBackingRect;
Audio::SoundHandle _uiSoundHandle;
uint32 _gameState[0x100] = {};
uint32 _scriptValueStack[0x100] = {};
diff --git a/engines/harold/bcb/scene.cpp b/engines/harold/bcb/scene.cpp
index c9e1ddcf942..127945c02bd 100644
--- a/engines/harold/bcb/scene.cpp
+++ b/engines/harold/bcb/scene.cpp
@@ -33,6 +33,103 @@
namespace BCB {
+bool BCBEngine::showMainSceneScreen() {
+ _sceneChoiceActive = false;
+ _mapControlsActive = false;
+ freeSceneChoiceBacking();
+ return drawMainSceneScreen(true);
+}
+
+bool BCBEngine::drawMainSceneScreen(bool lineReveal) {
+ const bool mainSceneNightFlag = stateDword(kStateMainSceneNightFlag) != 0;
+ Graphics::Surface *mainScene = loadBitmap(mainSceneNightFlag ? "16mmainn.bmp" : "16mmain.bmp");
+ if (!mainScene)
+ return false;
+
+ applyPendingClockTickDelta();
+ renderClockAndChipToSurface(*mainScene, 0, stateDword(kStateGameClockTicks));
+ if (lineReveal) {
+ blitScreenWithLineReveal(*mainScene, false);
+ } else {
+ g_system->copyRectToScreen(mainScene->getPixels(), mainScene->pitch, 0, 0, MIN<int>(mainScene->w, 640), MIN<int>(mainScene->h, 480));
+ }
+ mainScene->free();
+ delete mainScene;
+ _currentDisplayState = 1;
+ _sceneIdleMovieTimerTicks = 0;
+ _lastTimerTick = g_system->getMillis();
+ g_system->updateScreen();
+ return true;
+}
+
+void BCBEngine::captureSceneChoiceBacking() {
+ freeSceneChoiceBacking();
+ for (uint i = 1; i < ARRAYSIZE(kSceneChoiceButtonTable); ++i) {
+ const ButtonControlRecord &button = kSceneChoiceButtonTable[i];
+ const Common::Rect rect(button.x, button.y, button.x + button.width, button.y + button.height);
+ if (i == 1)
+ _sceneChoiceBackingRect = rect;
+ else
+ _sceneChoiceBackingRect.extend(rect);
+ }
+
+ Graphics::Surface *screen = g_system->lockScreen();
+ if (!screen)
+ return;
+
+ _sceneChoiceBacking = new Graphics::Surface();
+ _sceneChoiceBacking->create(_sceneChoiceBackingRect.width(), _sceneChoiceBackingRect.height(), screen->format);
+ _sceneChoiceBacking->copyRectToSurface(*screen, 0, 0, _sceneChoiceBackingRect);
+ g_system->unlockScreen();
+}
+
+void BCBEngine::restoreSceneChoiceBacking(bool present) {
+ if (!_sceneChoiceBacking)
+ return;
+
+ for (int buttonIndex = 1; buttonIndex <= _sceneChoiceEnabledButtonLimit && buttonIndex < (int)ARRAYSIZE(kSceneChoiceButtonTable); ++buttonIndex) {
+ if (!isSceneChoiceButtonVisible(buttonIndex))
+ continue;
+
+ const ButtonControlRecord &button = kSceneChoiceButtonTable[buttonIndex];
+ g_system->copyRectToScreen(_sceneChoiceBacking->getBasePtr(button.x - _sceneChoiceBackingRect.left, button.y - _sceneChoiceBackingRect.top), _sceneChoiceBacking->pitch, button.x, button.y, button.width, button.height);
+ }
+
+ if (present)
+ g_system->updateScreen();
+}
+
+void BCBEngine::freeSceneChoiceBacking() {
+ if (!_sceneChoiceBacking)
+ return;
+
+ _sceneChoiceBacking->free();
+ delete _sceneChoiceBacking;
+ _sceneChoiceBacking = nullptr;
+}
+
+void BCBEngine::clearSceneChoiceAfterSelection() {
+ Graphics::Surface *screen = g_system->lockScreen();
+ if (screen) {
+ screen->fillRect(Common::Rect(0x10, 0x30, 0x1a0, 0xf8), screen->format.RGBToColor(0, 0, 0));
+ g_system->unlockScreen();
+ }
+
+ freeSceneChoiceBacking();
+ g_system->updateScreen();
+}
+
+bool BCBEngine::isSceneChoiceButtonVisible(int buttonIndex) const {
+ if (buttonIndex <= 0 || buttonIndex >= (int)ARRAYSIZE(kSceneChoiceButtonTable))
+ return false;
+
+ const int sceneChoiceUiSetToShowId = stateDword(kStateSceneChoiceUiSetToShowId);
+ const int choiceCount = (sceneChoiceUiSetToShowId >= 0 && sceneChoiceUiSetToShowId < (int)_sceneChoiceText.size()) ? MIN<int>(_sceneChoiceText[sceneChoiceUiSetToShowId].size(), 8) : 0;
+
+ return buttonIndex <= choiceCount ||
+ (buttonIndex == _sceneChoiceTerminatorButtonIndex && stateDword(kStateSceneChoiceExtraButtonFlag) != 0);
+}
+
void BCBEngine::applyPendingClockTickDelta() {
stateDword(kStateGameClockTicks) += _pendingClockTickDelta;
_pendingClockTickDelta = 0;
@@ -104,8 +201,4 @@ void BCBEngine::drawAnalogClockAndChip(int baseHourOffset, int gameClockTicks, b
g_system->updateScreen();
}
-bool BCBEngine::drawMainSceneScreen(bool lineReveal) {
- error("BCB: drawMainSceneScreen is not implemented");
-}
-
} // End of namespace BCB
Commit: d965780f3b73ec0e8176b576013034e86e705023
https://github.com/scummvm/scummvm/commit/d965780f3b73ec0e8176b576013034e86e705023
Author: Scorp (scorp at mrs.mn)
Date: 2026-10-22T18:57:52+02:00
Commit Message:
HAROLD: BCB: Display and select conversation choices
Changed paths:
engines/harold/bcb/bcb.h
engines/harold/bcb/input.cpp
engines/harold/bcb/scene.cpp
diff --git a/engines/harold/bcb/bcb.h b/engines/harold/bcb/bcb.h
index 5f045715b08..a1f315098e5 100644
--- a/engines/harold/bcb/bcb.h
+++ b/engines/harold/bcb/bcb.h
@@ -113,11 +113,15 @@ private:
void composeMapControlsToSurface(Graphics::Surface &surface, const Graphics::Surface &mapArt);
bool showMainSceneScreen();
bool drawMainSceneScreen(bool lineReveal);
+ bool showActiveSceneChoiceControls();
void captureSceneChoiceBacking();
void restoreSceneChoiceBacking(bool present = true);
void freeSceneChoiceBacking();
void clearSceneChoiceAfterSelection();
bool isSceneChoiceButtonVisible(int buttonIndex) const;
+ void drawSceneChoiceButtons(int sceneChoiceUiSetToShowId, bool present = true, int pressedButtonIndex = -1);
+ void handleSceneChoiceClick(const Common::Point &mousePosition);
+ void handleSceneChoiceSelection(int buttonIndex);
void applyPendingClockTickDelta();
void notifyGameClockTicksChanged();
void drawClockAndChipIndicator(bool present = true);
@@ -135,6 +139,7 @@ private:
void blitScreenWithLineReveal(const Graphics::Surface &surface, bool presentFinalFrame = true);
void drawTitleButton(uint buttonIndex, bool pressed);
void handleLeftButtonDown(const Common::Point &mousePosition);
+ void handleSceneChoiceKeyboardSelection(int choiceNumber);
bool pollEvents();
bool pollEvents(bool handleClicks);
void serviceCursorRefresh(bool screenUpdated = false);
diff --git a/engines/harold/bcb/input.cpp b/engines/harold/bcb/input.cpp
index 318c82849ae..3ce39fdc093 100644
--- a/engines/harold/bcb/input.cpp
+++ b/engines/harold/bcb/input.cpp
@@ -71,6 +71,11 @@ void BCBEngine::handleLeftButtonDown(const Common::Point &mousePosition) {
return;
}
+ if (_currentDisplayState == 1 && _sceneChoiceActive) {
+ handleSceneChoiceClick(mousePosition);
+ return;
+ }
+
if (_currentDisplayState != 0 || !_titleButtonsActive)
return;
diff --git a/engines/harold/bcb/scene.cpp b/engines/harold/bcb/scene.cpp
index 127945c02bd..e80ec6a5a80 100644
--- a/engines/harold/bcb/scene.cpp
+++ b/engines/harold/bcb/scene.cpp
@@ -62,6 +62,43 @@ bool BCBEngine::drawMainSceneScreen(bool lineReveal) {
return true;
}
+bool BCBEngine::showActiveSceneChoiceControls() {
+ const int sceneChoiceUiSetToShowId = stateDword(kStateSceneChoiceUiSetToShowId);
+ if (_currentDisplayState != 1 && _currentDisplayState != 7)
+ showMainSceneScreen();
+ _currentDisplayState = 1;
+
+ if (sceneChoiceUiSetToShowId == 0)
+ return false;
+
+ if (sceneChoiceUiSetToShowId < 0 || sceneChoiceUiSetToShowId >= (int)_sceneChoiceText.size() || _sceneChoiceText[sceneChoiceUiSetToShowId].empty()) {
+ warning("BCBEngine::showActiveSceneChoiceControls(): Scene choice set %d has no parsed text", sceneChoiceUiSetToShowId);
+ return false;
+ }
+ uint32 &sceneChoiceExtraButtonFlag = stateDword(kStateSceneChoiceExtraButtonFlag);
+ const uint locationTableOffset = sceneChoiceUiSetToShowId * 2 - 1;
+ sceneChoiceExtraButtonFlag = locationTableOffset < _locationTableData.size() ? _locationTableData[locationTableOffset] : 0;
+
+ _sceneChoiceTerminatorButtonIndex = MIN<int>(_sceneChoiceText[sceneChoiceUiSetToShowId].size() + 1, 9);
+ _sceneChoiceEnabledButtonLimit = sceneChoiceExtraButtonFlag == 0 ? _sceneChoiceTerminatorButtonIndex - 1 : _sceneChoiceTerminatorButtonIndex;
+
+ const Common::String overlayPath = Common::String::format("bmp/select/16m/sel%03d.bmp", sceneChoiceUiSetToShowId);
+ Graphics::Surface *overlay = loadDiscBitmap(overlayPath.c_str());
+ if (overlay) {
+ g_system->copyRectToScreen(overlay->getPixels(), overlay->pitch, 16, 48, MIN<int>(overlay->w, 400), MIN<int>(overlay->h, 200));
+ overlay->free();
+ delete overlay;
+ }
+
+ drawSceneChoiceControlStrip(0, false);
+
+ captureSceneChoiceBacking();
+ drawSceneChoiceButtons(sceneChoiceUiSetToShowId);
+ _sceneChoiceActive = true;
+ _sceneIdleMovieTimerTicks = 0;
+ return true;
+}
+
void BCBEngine::captureSceneChoiceBacking() {
freeSceneChoiceBacking();
for (uint i = 1; i < ARRAYSIZE(kSceneChoiceButtonTable); ++i) {
@@ -130,6 +167,129 @@ bool BCBEngine::isSceneChoiceButtonVisible(int buttonIndex) const {
(buttonIndex == _sceneChoiceTerminatorButtonIndex && stateDword(kStateSceneChoiceExtraButtonFlag) != 0);
}
+void BCBEngine::drawSceneChoiceButtons(int sceneChoiceUiSetToShowId, bool present, int pressedButtonIndex) {
+ const Graphics::Font *font = _ownerDrawUiTextFont;
+ if (!font)
+ font = FontMan.getFontByUsage(Graphics::FontManager::kGUIFont);
+ if (!font)
+ font = FontMan.getFontByUsage(Graphics::FontManager::kConsoleFont);
+
+ if (pressedButtonIndex < 0) {
+ restoreSceneChoiceBacking(false);
+ } else if (_sceneChoiceBacking && pressedButtonIndex < (int)ARRAYSIZE(kSceneChoiceButtonTable)) {
+ const ButtonControlRecord &button = kSceneChoiceButtonTable[pressedButtonIndex];
+ g_system->copyRectToScreen(_sceneChoiceBacking->getBasePtr(button.x - _sceneChoiceBackingRect.left, button.y - _sceneChoiceBackingRect.top), _sceneChoiceBacking->pitch, button.x, button.y, button.width, button.height);
+ }
+
+ Graphics::Surface *screen = g_system->lockScreen();
+ if (!screen)
+ return;
+
+ const uint32 normalShadowColor = screen->format.RGBToColor(0xf6, 0xd3, 0x68);
+ const uint32 normalTextColor = screen->format.RGBToColor(0x73, 0x4b, 0x20);
+ const uint32 pressedShadowColor = screen->format.RGBToColor(0xff, 0xd3, 0x83);
+ const uint32 pressedTextColor = screen->format.RGBToColor(0x8c, 0x3c, 0x05);
+ const Common::Array<Common::U32String> &originalLabels = _sceneChoiceText[sceneChoiceUiSetToShowId];
+ const auto getChoiceLabel = [&](uint choiceIndex) -> Common::U32String {
+ return originalLabels[choiceIndex];
+ };
+ const auto drawChoiceLabel = [&](const Common::U32String &label, int buttonIndex) {
+ if (!font)
+ return;
+ const ButtonControlRecord &button = kSceneChoiceButtonTable[buttonIndex];
+ const bool pressed = buttonIndex == pressedButtonIndex;
+ const uint32 shadowColor = pressed ? pressedShadowColor : normalShadowColor;
+ const uint32 textColor = pressed ? pressedTextColor : normalTextColor;
+ const int textX = button.x + 10;
+ const int textY = button.y + (button.height - font->getFontHeight()) / 2 - (_gameDescription->desc.language == Common::KO_KOR ? 1 : 0);
+ const int textWidth = button.width - 10;
+ font->drawString(screen, label, textX + 1, textY + 1, textWidth, shadowColor, Graphics::kTextAlignLeft);
+ font->drawString(screen, label, textX, textY, textWidth, textColor, Graphics::kTextAlignLeft);
+ };
+
+ g_system->unlockScreen();
+
+ const Graphics::Surface *mainScene = getUiBitmap(stateDword(kStateMainSceneNightFlag) != 0 ? "16mmainn.bmp" : "16mmain.bmp");
+ if (mainScene) {
+ const int firstButtonIndex = pressedButtonIndex < 0 ? 1 : pressedButtonIndex;
+ const int lastButtonIndex = pressedButtonIndex < 0 ? _sceneChoiceEnabledButtonLimit : pressedButtonIndex;
+ for (int buttonIndex = firstButtonIndex; buttonIndex <= lastButtonIndex && buttonIndex < (int)ARRAYSIZE(kSceneChoiceButtonTable); ++buttonIndex) {
+ if (!isSceneChoiceButtonVisible(buttonIndex))
+ continue;
+
+ const ButtonControlRecord &button = kSceneChoiceButtonTable[buttonIndex];
+ if (button.width <= mainScene->w && 0x210 + button.height <= mainScene->h)
+ g_system->copyRectToScreen(mainScene->getBasePtr(0, 0x210), mainScene->pitch, button.x, button.y, button.width, button.height);
+ }
+ }
+
+ screen = g_system->lockScreen();
+ if (!screen)
+ return;
+
+ if (pressedButtonIndex < 0) {
+ for (uint i = 0; i < originalLabels.size() && (int)i < _sceneChoiceEnabledButtonLimit && i + 1 < ARRAYSIZE(kSceneChoiceButtonTable); ++i)
+ drawChoiceLabel(getChoiceLabel(i), i + 1);
+ } else if (pressedButtonIndex - 1 >= 0 && pressedButtonIndex - 1 < (int)originalLabels.size()) {
+ drawChoiceLabel(getChoiceLabel(pressedButtonIndex - 1), pressedButtonIndex);
+ }
+
+ if ((pressedButtonIndex < 0 || pressedButtonIndex == _sceneChoiceTerminatorButtonIndex) &&
+ stateDword(kStateSceneChoiceExtraButtonFlag) != 0 &&
+ _sceneChoiceTerminatorButtonIndex > 0 &&
+ _sceneChoiceTerminatorButtonIndex < (int)ARRAYSIZE(kSceneChoiceButtonTable)) {
+ drawChoiceLabel(_sceneChoiceMoveText, _sceneChoiceTerminatorButtonIndex);
+ }
+
+ g_system->unlockScreen();
+ if (present)
+ g_system->updateScreen();
+}
+
+void BCBEngine::handleSceneChoiceClick(const Common::Point &mousePosition) {
+ if (!_sceneChoiceActive)
+ return;
+
+ for (int buttonIndex = 1; buttonIndex <= _sceneChoiceEnabledButtonLimit && buttonIndex < (int)ARRAYSIZE(kSceneChoiceButtonTable); ++buttonIndex) {
+ if (!isSceneChoiceButtonVisible(buttonIndex))
+ continue;
+
+ const ButtonControlRecord &button = kSceneChoiceButtonTable[buttonIndex];
+ const Common::Rect rect(button.x, button.y, button.x + button.width, button.y + button.height);
+ if (!rect.contains(mousePosition))
+ continue;
+
+ handleSceneChoiceSelection(buttonIndex);
+ return;
+ }
+}
+
+void BCBEngine::handleSceneChoiceSelection(int buttonIndex) {
+ if (!_sceneChoiceActive || buttonIndex <= 0 || buttonIndex > _sceneChoiceEnabledButtonLimit ||
+ buttonIndex >= (int)ARRAYSIZE(kSceneChoiceButtonTable) || !isSceneChoiceButtonVisible(buttonIndex))
+ return;
+
+ const int sceneChoiceUiSetToShowId = stateDword(kStateSceneChoiceUiSetToShowId);
+ drawSceneChoiceButtons(sceneChoiceUiSetToShowId, true, buttonIndex);
+ delayWithEventPolling(80);
+
+ stopSceneIdleMovie();
+ _sceneChoiceActive = false;
+ stateDword(kStateMovieHistoryCount) = 0;
+
+ if (buttonIndex == _sceneChoiceTerminatorButtonIndex && stateDword(kStateSceneChoiceExtraButtonFlag) != 0) {
+ stateDword(kStateSelectedSceneChoiceValue) = 0xff;
+ } else {
+ stateDword(kStateSelectedSceneChoiceValue) = kSceneChoiceValueByButtonIndex[buttonIndex];
+ const uint timingOffset = (sceneChoiceUiSetToShowId - 1) * 8 + (buttonIndex - 1);
+ if (timingOffset < _locationTimingData.size())
+ _pendingClockTickDelta = (int8)_locationTimingData[timingOffset];
+ }
+
+ clearSceneChoiceAfterSelection();
+ processGameStateDispatcher(true);
+}
+
void BCBEngine::applyPendingClockTickDelta() {
stateDword(kStateGameClockTicks) += _pendingClockTickDelta;
_pendingClockTickDelta = 0;
@@ -201,4 +361,16 @@ void BCBEngine::drawAnalogClockAndChip(int baseHourOffset, int gameClockTicks, b
g_system->updateScreen();
}
+void BCBEngine::handleSceneChoiceKeyboardSelection(int choiceNumber) {
+ if (!_sceneChoiceActive || choiceNumber <= 0 || choiceNumber > 9)
+ return;
+
+ if (choiceNumber == 9 && stateDword(kStateSceneChoiceExtraButtonFlag) != 0) {
+ handleSceneChoiceSelection(_sceneChoiceTerminatorButtonIndex);
+ return;
+ }
+
+ handleSceneChoiceSelection(choiceNumber);
+}
+
} // End of namespace BCB
Commit: c3d8e70c725029d58e251896e6f6dae7d1733532
https://github.com/scummvm/scummvm/commit/c3d8e70c725029d58e251896e6f6dae7d1733532
Author: Scorp (scorp at mrs.mn)
Date: 2026-10-22T18:57:52+02:00
Commit Message:
HAROLD: BCB: Add map travel and gated destinations
Changed paths:
engines/harold/bcb/bcb.cpp
engines/harold/bcb/bcb.h
engines/harold/bcb/input.cpp
engines/harold/bcb/map.cpp
engines/harold/bcb/scene.cpp
engines/harold/bcb/video.cpp
diff --git a/engines/harold/bcb/bcb.cpp b/engines/harold/bcb/bcb.cpp
index 309bdf7b0e6..aab18cfa356 100644
--- a/engines/harold/bcb/bcb.cpp
+++ b/engines/harold/bcb/bcb.cpp
@@ -82,6 +82,7 @@ BCBEngine::~BCBEngine() {
delete _chipBitmap;
}
delete _activeMovieVideo;
+ delete _mapMoveVideo;
delete _sceneIdleVideo;
delete _gameOverVideo;
freeSceneChoiceBacking();
@@ -177,6 +178,18 @@ void BCBEngine::mainLoop() {
}
}
+ if (_mapMoveVideo) {
+ if (_mapMoveVideo->endOfVideo()) {
+ _mapMoveVideo->stop();
+ _mapMoveVideo->rewind();
+ _mapMoveVideo->decodeNextFrame();
+ _mapMoveVideo->start();
+ }
+
+ if (_mapMoveVideo->needsUpdate())
+ _mapMoveVideo->decodeNextFrame();
+ }
+
if (_activeMovieVideo) {
bool movieUpdated = false;
if (_activeMovieVideo->needsUpdate()) {
diff --git a/engines/harold/bcb/bcb.h b/engines/harold/bcb/bcb.h
index a1f315098e5..fd9b16f147f 100644
--- a/engines/harold/bcb/bcb.h
+++ b/engines/harold/bcb/bcb.h
@@ -79,11 +79,15 @@ private:
void startSceneIdleMovie();
void stopSceneIdleMovie();
void restoreTitleScreenRegion(const Common::Rect &rect, bool present = true);
+ bool openMapMoveMovie();
+ void startMapMoveMovie();
+ void stopMapMoveMovie();
void resetGameState();
bool loadScriptData();
bool loadExecutableDataTables();
bool loadByteFile(const char *fileName, Common::Array<byte> &data) const;
void processGameStateDispatcher(bool runScriptBeforeDispatch);
+ bool dispatchMapMoveControls();
void executeCurrentScriptSequence();
uint32 executeScriptExpression(uint32 &scriptPc, int &branchTokenSeen);
bool pushScriptValue(uint32 value);
@@ -110,9 +114,14 @@ private:
void drawMovieControlStrip(int sourceX = 0, bool present = true);
void stopActiveMovieAndDispatch();
void processQueuedMovieSequence();
+ bool isMapButtonEnabled(uint buttonIndex) const;
+ void drawMapButtonPressed(uint buttonIndex, bool present = true);
void composeMapControlsToSurface(Graphics::Surface &surface, const Graphics::Surface &mapArt);
+ void drawMapControls(bool present = true);
+ bool handleMapControlClick(const Common::Point &mousePosition);
bool showMainSceneScreen();
bool drawMainSceneScreen(bool lineReveal);
+ bool showMapScreen();
bool showActiveSceneChoiceControls();
void captureSceneChoiceBacking();
void restoreSceneChoiceBacking(bool present = true);
@@ -178,6 +187,7 @@ private:
Video::AVIDecoder *_menuBackgroundVideo = nullptr;
Video::AVIDecoder *_gameOverVideo = nullptr;
Video::AVIDecoder *_sceneIdleVideo = nullptr;
+ Video::AVIDecoder *_mapMoveVideo = nullptr;
Video::AVIDecoder *_activeMovieVideo = nullptr;
Graphics::Surface *_titleBitmap = nullptr;
Graphics::Surface *_uiBitmap = nullptr;
diff --git a/engines/harold/bcb/input.cpp b/engines/harold/bcb/input.cpp
index 3ce39fdc093..cc9943f926b 100644
--- a/engines/harold/bcb/input.cpp
+++ b/engines/harold/bcb/input.cpp
@@ -71,11 +71,36 @@ void BCBEngine::handleLeftButtonDown(const Common::Point &mousePosition) {
return;
}
+ if ((_currentDisplayState == 1 || _currentDisplayState == 2) && !_activeMovieVideo) {
+ if (kMovieQueueControlHitRect.contains(mousePosition)) {
+ playUiSound();
+ drawSceneChoiceControlStrip(kMovieControlStripSkipPressedSourceX);
+ delayWithEventPolling(160, false);
+ _previousDisplayStateBeforeSystem = _currentDisplayState;
+ _sceneChoiceActive = false;
+ _mapControlsActive = false;
+ if (_currentDisplayState == 1) {
+ stopSceneIdleMovie();
+ _currentDisplayState = 7;
+ } else {
+ stopMapMoveMovie();
+ drawMapSubmodeMovieBackdrop();
+ _currentDisplayState = 8;
+ }
+ _queuedMoviePlaybackIndex = 0;
+ processQueuedMovieSequence();
+ return;
+ }
+ }
+
if (_currentDisplayState == 1 && _sceneChoiceActive) {
handleSceneChoiceClick(mousePosition);
return;
}
+ if (handleMapControlClick(mousePosition))
+ return;
+
if (_currentDisplayState != 0 || !_titleButtonsActive)
return;
diff --git a/engines/harold/bcb/map.cpp b/engines/harold/bcb/map.cpp
index e22c93a29fd..c19dc9ffde3 100644
--- a/engines/harold/bcb/map.cpp
+++ b/engines/harold/bcb/map.cpp
@@ -31,13 +31,190 @@
#include "harold/bcb/script.h"
#include "harold/bcb/static_data.h"
-#include "common/textconsole.h"
-
namespace BCB {
static const uint32 kScriptSecondaryStateByteOffset = 0x98;
+bool BCBEngine::showMapScreen() {
+ Graphics::Surface *map = loadBitmap("16mmap.bmp");
+ if (!map)
+ return false;
+
+ applyPendingClockTickDelta();
+ renderClockAndChipToSurface(*map, 0, stateDword(kStateGameClockTicks));
+ if (_mapControlsActive)
+ composeMapControlsToSurface(*map, *map);
+ blitScreenWithLineReveal(*map, false);
+ map->free();
+ delete map;
+ _currentDisplayState = 2;
+ _lastTimerTick = g_system->getMillis();
+ g_system->updateScreen();
+ return true;
+}
+
+bool BCBEngine::openMapMoveMovie() {
+ if (_mapMoveVideo)
+ return true;
+
+ Common::SeekableReadStream *stream = createActiveDiscReadStream("bgm/move.avi");
+ if (!stream)
+ return false;
+
+ _mapMoveVideo = new Video::AVIDecoder();
+ _mapMoveVideo->setSoundType(Audio::Mixer::kMusicSoundType);
+ if (!_mapMoveVideo->loadStream(stream)) {
+ delete _mapMoveVideo;
+ _mapMoveVideo = nullptr;
+ return false;
+ }
+
+ _mapMoveVideo->setOutputPixelFormat(g_system->getScreenFormat());
+ return true;
+}
+
+void BCBEngine::startMapMoveMovie() {
+ if (!_mapMoveVideo && !openMapMoveMovie())
+ return;
+
+ _mapMoveVideo->start();
+}
+
+void BCBEngine::stopMapMoveMovie() {
+ if (!_mapMoveVideo)
+ return;
+
+ _mapMoveVideo->stop();
+ delete _mapMoveVideo;
+ _mapMoveVideo = nullptr;
+}
+
+bool BCBEngine::isMapButtonEnabled(uint buttonIndex) const {
+ if (buttonIndex == 0 || buttonIndex >= ARRAYSIZE(kMapButtonTable))
+ return false;
+
+ const int secondaryStateIndex = kMapButtonSecondaryStateIndexTable[buttonIndex];
+ return secondaryStateIndex == 0 || stateDword(kScriptSecondaryStateByteOffset + secondaryStateIndex * 4) != 0;
+}
+
+void BCBEngine::drawMapButtonPressed(uint buttonIndex, bool present) {
+ if (buttonIndex == 0 || buttonIndex >= ARRAYSIZE(kMapButtonTable))
+ return;
+
+ const Graphics::Surface *map = getUiBitmap("16mmap.bmp");
+ if (!map)
+ return;
+
+ const ButtonControlRecord &button = kMapButtonTable[buttonIndex];
+ if (0x1c8 + button.width <= map->w && 0x1e0 + button.height <= map->h)
+ g_system->copyRectToScreen(map->getBasePtr(0x1c8, 0x1e0), map->pitch, button.x, button.y, button.width, button.height);
+
+ if (present)
+ g_system->updateScreen();
+}
+
void BCBEngine::composeMapControlsToSurface(Graphics::Surface &surface, const Graphics::Surface &mapArt) {
- error("BCB: composeMapControlsToSurface is not implemented");
+ for (uint i = 1; i < ARRAYSIZE(kMapButtonTable); ++i) {
+ if (!isMapButtonEnabled(i))
+ continue;
+
+ const ButtonControlRecord &button = kMapButtonTable[i];
+ if (0x1d8 + button.width <= mapArt.w && 0x1e0 + button.height <= mapArt.h)
+ surface.copyRectToSurface(mapArt, button.x, button.y, Common::Rect(0x1d8, 0x1e0, 0x1d8 + button.width, 0x1e0 + button.height));
+ }
+
+ const uint32 shadowColor = surface.format.RGBToColor(0, 0, 0);
+ const uint32 textColor = surface.format.RGBToColor(255, 255, 255);
+ for (uint i = 1; i < ARRAYSIZE(kMapButtonTable); ++i) {
+ if (!isMapButtonEnabled(i))
+ continue;
+ if (i >= _mapButtonTextSjis.size() || _mapButtonTextSjis[i].empty())
+ continue;
+
+ const ButtonControlRecord &button = kMapButtonTable[i];
+ const int textX = button.x + 0x14;
+ const int textY = button.y - (_gameDescription->desc.language == Common::KO_KOR ? 5 : 4);
+ const int textWidth = MAX<int>(0, surface.w - textX - 2);
+ if (_gameDescription->desc.language == Common::JA_JPN && _sjisFont) {
+ int x = textX + 1;
+ for (const byte *text = (const byte *)_mapButtonTextSjis[i].c_str(); *text;) {
+ const byte firstByte = *text++;
+ uint16 sjisChar = firstByte;
+ if (((firstByte >= 0x81 && firstByte <= 0x9f) || (firstByte >= 0xe0 && firstByte <= 0xef)) && *text)
+ sjisChar |= (uint16)(*text++) << 8;
+ _sjisFont->drawChar(surface, sjisChar, x, textY + 1, shadowColor, 0);
+ x += _sjisFont->getCharWidth(sjisChar);
+ }
+ x = textX;
+ for (const byte *text = (const byte *)_mapButtonTextSjis[i].c_str(); *text;) {
+ const byte firstByte = *text++;
+ uint16 sjisChar = firstByte;
+ if (((firstByte >= 0x81 && firstByte <= 0x9f) || (firstByte >= 0xe0 && firstByte <= 0xef)) && *text)
+ sjisChar |= (uint16)(*text++) << 8;
+ _sjisFont->drawChar(surface, sjisChar, x, textY, textColor, 0);
+ x += _sjisFont->getCharWidth(sjisChar);
+ }
+ } else if (_ownerDrawUiTextFont && i < _mapButtonText.size()) {
+ _ownerDrawUiTextFont->drawString(&surface, _mapButtonText[i], textX + 1, textY + 1, textWidth, shadowColor, Graphics::kTextAlignLeft);
+ _ownerDrawUiTextFont->drawString(&surface, _mapButtonText[i], textX, textY, textWidth, textColor, Graphics::kTextAlignLeft);
+ }
+ }
+}
+
+void BCBEngine::drawMapControls(bool present) {
+ const Graphics::Surface *map = getUiBitmap("16mmap.bmp");
+ if (!map)
+ return;
+
+ Graphics::Surface *screen = g_system->lockScreen();
+ if (screen) {
+ composeMapControlsToSurface(*screen, *map);
+ g_system->unlockScreen();
+ }
+
+ if (present)
+ g_system->updateScreen();
+}
+
+bool BCBEngine::handleMapControlClick(const Common::Point &mousePosition) {
+ if (_currentDisplayState != 2 || !_mapControlsActive)
+ return false;
+
+ for (uint i = 1; i < ARRAYSIZE(kMapButtonTable); ++i) {
+ if (!isMapButtonEnabled(i))
+ continue;
+
+ const ButtonControlRecord &button = kMapButtonTable[i];
+ if (!Common::Rect(button.x, button.y, button.x + button.width, button.y + button.height).contains(mousePosition))
+ continue;
+
+ drawMapButtonPressed(i);
+ stateDword(kStateSelectedSceneChoiceValue) = (button.controlId - 0xc2) & 0xff;
+ _mapControlsActive = false;
+ stateDword(kStateMovieHistoryCount) = 0;
+ stopMapMoveMovie();
+ playUiSound();
+ showMainSceneScreen();
+ processGameStateDispatcher(true);
+ return true;
+ }
+
+ return false;
+}
+
+bool BCBEngine::dispatchMapMoveControls() {
+ freeSceneChoiceBacking();
+ _sceneChoiceActive = false;
+ stopSceneIdleMovie();
+ openMapMoveMovie();
+ _mapControlsActive = true;
+ const bool showNewMapScreen = (_currentDisplayState != 2 && _currentDisplayState != 8);
+ if (showNewMapScreen)
+ showMapScreen();
+ _currentDisplayState = 2;
+ if (!showNewMapScreen)
+ drawMapControls();
+ startMapMoveMovie();
+ return true;
}
} // End of namespace BCB
diff --git a/engines/harold/bcb/scene.cpp b/engines/harold/bcb/scene.cpp
index e80ec6a5a80..e6c719d9550 100644
--- a/engines/harold/bcb/scene.cpp
+++ b/engines/harold/bcb/scene.cpp
@@ -34,6 +34,7 @@
namespace BCB {
bool BCBEngine::showMainSceneScreen() {
+ stopMapMoveMovie();
_sceneChoiceActive = false;
_mapControlsActive = false;
freeSceneChoiceBacking();
diff --git a/engines/harold/bcb/video.cpp b/engines/harold/bcb/video.cpp
index 9b0c7f7d9a1..24d959a5901 100644
--- a/engines/harold/bcb/video.cpp
+++ b/engines/harold/bcb/video.cpp
@@ -33,6 +33,7 @@ namespace BCB {
bool BCBEngine::playNumberedMovie(int movieId, bool blankScreenMovie) {
stopSceneIdleMovie();
+ stopMapMoveMovie();
stopActiveMovie();
_sceneChoiceActive = false;
_mapControlsActive = false;
Commit: a193a2927d0532b44fedd5fa5d22fe55361a9ff8
https://github.com/scummvm/scummvm/commit/a193a2927d0532b44fedd5fa5d22fe55361a9ff8
Author: Scorp (scorp at mrs.mn)
Date: 2026-10-22T18:57:52+02:00
Commit Message:
HAROLD: BCB: Display chapter stills and game over
Changed paths:
engines/harold/bcb/bcb.cpp
engines/harold/bcb/bcb.h
engines/harold/bcb/input.cpp
engines/harold/bcb/screens.cpp
diff --git a/engines/harold/bcb/bcb.cpp b/engines/harold/bcb/bcb.cpp
index aab18cfa356..665766846b9 100644
--- a/engines/harold/bcb/bcb.cpp
+++ b/engines/harold/bcb/bcb.cpp
@@ -146,6 +146,33 @@ void BCBEngine::mainLoop() {
if (_initialLogoElapsedTimerTicks > 0x3c)
transitionFromLogoToTitle();
}
+ } else if (_currentDisplayState == 1 && !_activeMovieVideo && !_sceneIdleVideo) {
+ const uint32 now = g_system->getMillis();
+ while (now - _lastTimerTick >= 50 && _currentDisplayState == 1 && !_activeMovieVideo && !_sceneIdleVideo) {
+ _lastTimerTick += 50;
+ ++_sceneIdleMovieTimerTicks;
+ if (_sceneIdleMovieTimerTicks > 200)
+ startSceneIdleMovie();
+ }
+ } else if (_currentDisplayState == 6) {
+ const uint32 now = g_system->getMillis();
+ while (now - _lastTimerTick >= 50 && _currentDisplayState == 6) {
+ _lastTimerTick += 50;
+ ++_pendingStillElapsedTimerTicks;
+ if (kStillImageDurationTicks[_pendingStillDurationIndex] < _pendingStillElapsedTimerTicks)
+ completePendingStillImageScreen();
+ }
+ } else if (_currentDisplayState == 9) {
+ const uint32 now = g_system->getMillis();
+ while (now - _lastTimerTick >= 50 && _currentDisplayState == 9) {
+ _lastTimerTick += 50;
+ ++_gameOverElapsedTimerTicks;
+ if (stateDword(kStateClearDisplayBeforeMediaCheckFlag) != 0 || _gameOverElapsedTimerTicks > 300) {
+ resetGameState();
+ showTitleScreen();
+ startMenuBackgroundMusic();
+ }
+ }
}
if (_menuBackgroundVideo) {
@@ -190,6 +217,13 @@ void BCBEngine::mainLoop() {
_mapMoveVideo->decodeNextFrame();
}
+ if (_gameOverVideo) {
+ if (_gameOverVideo->needsUpdate())
+ _gameOverVideo->decodeNextFrame();
+ if (_gameOverVideo->endOfVideo())
+ stopGameOverMusic();
+ }
+
if (_activeMovieVideo) {
bool movieUpdated = false;
if (_activeMovieVideo->needsUpdate()) {
diff --git a/engines/harold/bcb/bcb.h b/engines/harold/bcb/bcb.h
index fd9b16f147f..21aac781edb 100644
--- a/engines/harold/bcb/bcb.h
+++ b/engines/harold/bcb/bcb.h
@@ -88,6 +88,8 @@ private:
bool loadByteFile(const char *fileName, Common::Array<byte> &data) const;
void processGameStateDispatcher(bool runScriptBeforeDispatch);
bool dispatchMapMoveControls();
+ bool dispatchGameOverScreen();
+ bool dispatchEndingStillDisplayState();
void executeCurrentScriptSequence();
uint32 executeScriptExpression(uint32 &scriptPc, int &branchTokenSeen);
bool pushScriptValue(uint32 value);
@@ -119,6 +121,8 @@ private:
void composeMapControlsToSurface(Graphics::Surface &surface, const Graphics::Surface &mapArt);
void drawMapControls(bool present = true);
bool handleMapControlClick(const Common::Point &mousePosition);
+ bool showPendingStillImageScreen();
+ void completePendingStillImageScreen();
bool showMainSceneScreen();
bool drawMainSceneScreen(bool lineReveal);
bool showMapScreen();
diff --git a/engines/harold/bcb/input.cpp b/engines/harold/bcb/input.cpp
index cc9943f926b..1aff33f17e5 100644
--- a/engines/harold/bcb/input.cpp
+++ b/engines/harold/bcb/input.cpp
@@ -71,6 +71,12 @@ void BCBEngine::handleLeftButtonDown(const Common::Point &mousePosition) {
return;
}
+ if (_currentDisplayState == 6) {
+ playUiSound();
+ completePendingStillImageScreen();
+ return;
+ }
+
if ((_currentDisplayState == 1 || _currentDisplayState == 2) && !_activeMovieVideo) {
if (kMovieQueueControlHitRect.contains(mousePosition)) {
playUiSound();
diff --git a/engines/harold/bcb/screens.cpp b/engines/harold/bcb/screens.cpp
index 3d0418a68d5..2bd2dd08009 100644
--- a/engines/harold/bcb/screens.cpp
+++ b/engines/harold/bcb/screens.cpp
@@ -107,6 +107,107 @@ bool BCBEngine::startNewGameFromTitle() {
return playNumberedMovie(6, true);
}
+bool BCBEngine::showPendingStillImageScreen() {
+ const int stillImageToShowId = stateDword(kStateStillImageToShowId);
+ if (stillImageToShowId == 0)
+ return false;
+
+ const int stillIndex = stillImageToShowId - 0x34;
+ if (stillIndex < 0 || stillIndex >= ARRAYSIZE(kStillImageHighColorPaths)) {
+ warning("BCBEngine::showPendingStillImageScreen(): Unsupported still id %d", stillImageToShowId);
+ stateDword(kStateStillImageToShowId) = 0;
+ return false;
+ }
+
+ Graphics::Surface *still = loadDiscBitmap(kStillImageHighColorPaths[stillIndex]);
+ if (!still)
+ return false;
+
+ g_system->copyRectToScreen(still->getPixels(), still->pitch, 0, 0, MIN<int>(still->w, 640), MIN<int>(still->h, 480));
+ g_system->updateScreen();
+ still->free();
+ delete still;
+
+ _currentDisplayState = 6;
+ _pendingStillElapsedTimerTicks = 0;
+ _lastTimerTick = g_system->getMillis();
+ _pendingStillDurationIndex = stillIndex;
+ stateDword(kStateStillImageToShowId) = 0;
+ return true;
+}
+
+void BCBEngine::completePendingStillImageScreen() {
+ if (_currentDisplayState != 6)
+ return;
+
+ if (stateDword(kStateClearDisplayBeforeMediaCheckFlag) == 0)
+ showMainSceneScreen();
+ processGameStateDispatcher(true);
+}
+
+bool BCBEngine::dispatchGameOverScreen() {
+ stopSceneIdleMovie();
+ stopMapMoveMovie();
+ stopActiveMovie();
+ stopMenuBackgroundMusic();
+ _sceneChoiceActive = false;
+ _mapControlsActive = false;
+ freeSceneChoiceBacking();
+
+ Graphics::Surface *gameOver = loadDiscBitmapFromAnyDisc("bmp/still/16m/pover.bmp");
+ if (!gameOver)
+ return false;
+
+ const uint selectedSceneChoiceValue = stateDword(kStateSelectedSceneChoiceValue);
+ if (selectedSceneChoiceValue < _gameOverText.size() && !_gameOverText[selectedSceneChoiceValue].empty()) {
+ const uint32 white = gameOver->format.RGBToColor(255, 255, 255);
+ const Common::Rect textRect(100, 200, 540, 400);
+ if (_gameOverTextFont) {
+ const Common::U32String &text = _gameOverText[selectedSceneChoiceValue];
+ int y = textRect.top - (_gameDescription->desc.language == Common::KO_KOR ? 1 : 0);
+ for (uint lineStart = 0; lineStart < text.size() && y < textRect.bottom;) {
+ uint lineEnd = lineStart;
+ uint lastFit = lineStart;
+ while (lineEnd < text.size() && text[lineEnd] != U'\n') {
+ ++lineEnd;
+ const Common::U32String candidate(text.c_str() + lineStart, lineEnd - lineStart);
+ if (_gameOverTextFont->getStringWidth(candidate) <= textRect.width())
+ lastFit = lineEnd;
+ else
+ break;
+ }
+
+ if (lastFit == lineStart && lineEnd > lineStart)
+ lastFit = lineStart + 1;
+ if (lineEnd < text.size() && text[lineEnd] == U'\n')
+ lastFit = lineEnd;
+
+ _gameOverTextFont->drawString(gameOver, Common::U32String(text.c_str() + lineStart, lastFit - lineStart), textRect.left, y, textRect.width(), white, Graphics::kTextAlignLeft);
+ y += _gameOverTextFont->getFontHeight() + kGameOverTextLineGap;
+ lineStart = lastFit;
+ if (lineStart < text.size() && text[lineStart] == U'\n')
+ ++lineStart;
+ }
+ }
+ }
+
+ blitScreenWithLineReveal(*gameOver, false);
+ gameOver->free();
+ delete gameOver;
+
+ _currentDisplayState = 9;
+ _gameOverElapsedTimerTicks = 0;
+ _lastTimerTick = g_system->getMillis();
+ g_system->updateScreen();
+ startGameOverMusic();
+ return true;
+}
+
+bool BCBEngine::dispatchEndingStillDisplayState() {
+ _currentDisplayState = 9;
+ return true;
+}
+
void BCBEngine::drawTitleButton(uint buttonIndex, bool pressed) {
if (!_titleBitmap || buttonIndex == 0 || buttonIndex >= ARRAYSIZE(kTitleButtonTable))
return;
Commit: c3aded185bfe9fb5a96ee4cd13d89f0a17e58f7f
https://github.com/scummvm/scummvm/commit/c3aded185bfe9fb5a96ee4cd13d89f0a17e58f7f
Author: Scorp (scorp at mrs.mn)
Date: 2026-10-22T18:57:52+02:00
Commit Message:
HAROLD: BCB: Dispatch script requests to game screens
Changed paths:
A engines/harold/bcb/computer.cpp
engines/harold/bcb/bcb.cpp
engines/harold/bcb/bcb.h
engines/harold/bcb/resources.cpp
engines/harold/module.mk
diff --git a/engines/harold/bcb/bcb.cpp b/engines/harold/bcb/bcb.cpp
index 665766846b9..5029638844d 100644
--- a/engines/harold/bcb/bcb.cpp
+++ b/engines/harold/bcb/bcb.cpp
@@ -131,6 +131,81 @@ Common::Error BCBEngine::run() {
return Common::kNoError;
}
+void BCBEngine::clearDisplayAndResetState() {
+ _currentDisplayState = 0;
+ stateDword(kStateRequiredDiscIndex) = 0;
+ stopSceneIdleMovie();
+ stopMapMoveMovie();
+ restoreActiveCaptionBackingRect();
+ freeSceneChoiceBacking();
+ _sceneChoiceActive = false;
+ _mapControlsActive = false;
+
+ Graphics::Surface *screen = g_system->lockScreen();
+ if (screen) {
+ const uint32 black = screen->format.RGBToColor(0, 0, 0);
+ screen->fillRect(Common::Rect(0, 0, 640, 480), black);
+ g_system->unlockScreen();
+ }
+ g_system->updateScreen();
+}
+
+void BCBEngine::processGameStateDispatcher(bool runScriptBeforeDispatch) {
+ _scriptStopFlag = false;
+
+ while (!shouldQuit() && !_scriptStopFlag) {
+ checkCurrentMediaPathReady();
+ if (showPendingStillImageScreen())
+ return;
+
+ if (processPendingMovieRequest())
+ return;
+
+ if (runScriptBeforeDispatch)
+ executeCurrentScriptSequence();
+
+ if (stateDword(kStateDispatcherControlCode) > 6) {
+ warning("BCBEngine::processGameStateDispatcher(): Invalid dispatcher control code %u", stateDword(kStateDispatcherControlCode));
+ _scriptStopFlag = true;
+ return;
+ }
+
+ if (dispatchGameStateControlCode(stateDword(kStateDispatcherControlCode)))
+ return;
+ }
+}
+
+bool BCBEngine::dispatchGameStateControlCode(uint32 dispatcherControlCode) {
+ switch (dispatcherControlCode) {
+ case 0:
+ return dispatchGameStateNoOp();
+ case 1:
+ return dispatchSceneChoiceControls();
+ case 2:
+ return dispatchMapMoveControls();
+ case 3:
+ return dispatchComputerMainControls();
+ case 4:
+ return dispatchComputerDetailControls();
+ case 5:
+ return dispatchGameOverScreen();
+ case 6:
+ return dispatchEndingStillDisplayState();
+ default:
+ warning("BCBEngine::dispatchGameStateControlCode(): Unhandled dispatcher control code %u", dispatcherControlCode);
+ _scriptStopFlag = true;
+ return true;
+ }
+}
+
+bool BCBEngine::dispatchGameStateNoOp() {
+ return false;
+}
+
+bool BCBEngine::dispatchSceneChoiceControls() {
+ return showActiveSceneChoiceControls();
+}
+
void BCBEngine::mainLoop() {
while (!shouldQuit()) {
bool screenUpdated = false;
@@ -266,8 +341,4 @@ void BCBEngine::mainLoop() {
}
}
-void BCBEngine::processGameStateDispatcher(bool runScriptBeforeDispatch) {
- error("BCB: processGameStateDispatcher is not implemented");
-}
-
} // End of namespace BCB
diff --git a/engines/harold/bcb/bcb.h b/engines/harold/bcb/bcb.h
index 21aac781edb..ec3f1630a94 100644
--- a/engines/harold/bcb/bcb.h
+++ b/engines/harold/bcb/bcb.h
@@ -87,9 +87,16 @@ private:
bool loadExecutableDataTables();
bool loadByteFile(const char *fileName, Common::Array<byte> &data) const;
void processGameStateDispatcher(bool runScriptBeforeDispatch);
+ bool dispatchGameStateControlCode(uint32 dispatcherControlCode);
+ bool dispatchGameStateNoOp();
+ bool dispatchSceneChoiceControls();
bool dispatchMapMoveControls();
+ bool dispatchComputerMainControls();
+ bool dispatchComputerDetailControls();
bool dispatchGameOverScreen();
bool dispatchEndingStillDisplayState();
+ void checkCurrentMediaPathReady();
+ void clearDisplayAndResetState();
void executeCurrentScriptSequence();
uint32 executeScriptExpression(uint32 &scriptPc, int &branchTokenSeen);
bool pushScriptValue(uint32 value);
diff --git a/engines/harold/bcb/computer.cpp b/engines/harold/bcb/computer.cpp
new file mode 100644
index 00000000000..84372cd95a0
--- /dev/null
+++ b/engines/harold/bcb/computer.cpp
@@ -0,0 +1,51 @@
+/* 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 source distribution. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "common/algorithm.h"
+#include "common/endian.h"
+#include "common/events.h"
+#include "common/system.h"
+#include "graphics/surface.h"
+
+#include <math.h>
+
+#include "harold/bcb/bcb.h"
+#include "harold/bcb/script.h"
+#include "harold/bcb/static_data.h"
+
+#include "common/textconsole.h"
+
+namespace BCB {
+
+static const int kComputerCompositeTransitionLeft = 36;
+static const int kComputerCompositeTransitionTop = 36;
+static const int kComputerCompositeTransitionRight = 606;
+static const int kComputerCompositeTransitionBottom = 444;
+
+bool BCBEngine::dispatchComputerDetailControls() {
+ error("BCB: dispatchComputerDetailControls is not implemented");
+}
+
+bool BCBEngine::dispatchComputerMainControls() {
+ error("BCB: dispatchComputerMainControls is not implemented");
+}
+
+} // End of namespace BCB
diff --git a/engines/harold/bcb/resources.cpp b/engines/harold/bcb/resources.cpp
index 5d748dbc3f5..65da108503f 100644
--- a/engines/harold/bcb/resources.cpp
+++ b/engines/harold/bcb/resources.cpp
@@ -160,4 +160,51 @@ bool BCBEngine::switchActiveMediaDisc(int discNumber) {
return true;
}
+void BCBEngine::checkCurrentMediaPathReady() {
+ if (stateDword(kStateClearDisplayBeforeMediaCheckFlag) != 0)
+ clearDisplayAndResetState();
+
+ const int requiredDiscNumber = stateDword(kStateRequiredDiscIndex) + 1;
+ if (requiredDiscNumber == _activeMediaDiscNumber)
+ return;
+
+ if (requiredDiscNumber < 1 || requiredDiscNumber > 5) {
+ warning("BCBEngine::checkCurrentMediaPathReady(): Invalid required media index %d from script state %u",
+ requiredDiscNumber, stateDword(kStateRequiredDiscIndex));
+ _scriptStopFlag = true;
+ return;
+ }
+
+ Common::ArchiveMemberList requiredDiscMembers;
+ if (_discFiles[requiredDiscNumber - 1].listMembers(requiredDiscMembers) == 0) {
+ warning("BCBEngine::checkCurrentMediaPathReady(): Missing installed media directory bcb%d",
+ requiredDiscNumber);
+ debugC(1, kBCBDebugGeneral,
+ "BCB required media dump: active=bcb%d required=bcb%d scriptRequiredDiscIndex=%u",
+ _activeMediaDiscNumber, requiredDiscNumber, stateDword(kStateRequiredDiscIndex));
+ _scriptStopFlag = true;
+ return;
+ }
+
+ _currentDisplayState = 0;
+ stopSceneIdleMovie();
+ restoreActiveCaptionBackingRect();
+ freeSceneChoiceBacking();
+ _sceneChoiceActive = false;
+
+ Graphics::Surface blackScreen;
+ blackScreen.create(640, 480, g_system->getScreenFormat());
+ blackScreen.fillRect(Common::Rect(0, 0, 640, 480), blackScreen.format.RGBToColor(0, 0, 0));
+ blitScreenWithLineReveal(blackScreen);
+ blackScreen.free();
+
+ if (!switchActiveMediaDisc(requiredDiscNumber)) {
+ warning("BCBEngine::checkCurrentMediaPathReady(): Failed to switch to installed media directory bcb%d", requiredDiscNumber);
+ _scriptStopFlag = true;
+ return;
+ }
+
+ stateDword(kStateMovieHistoryCount) = 0;
+}
+
} // End of namespace BCB
diff --git a/engines/harold/module.mk b/engines/harold/module.mk
index bf42103d2ce..e7461a46f44 100644
--- a/engines/harold/module.mk
+++ b/engines/harold/module.mk
@@ -8,6 +8,7 @@ MODULE_OBJS := \
bcb/scene.o \
bcb/screens.o \
bcb/input.o \
+ bcb/computer.o \
bcb/map.o \
bcb/script.o \
bcb/executable_data.o \
Commit: b5b73fb8db0bce1ee58837e1218f2b9358322caf
https://github.com/scummvm/scummvm/commit/b5b73fb8db0bce1ee58837e1218f2b9358322caf
Author: Scorp (scorp at mrs.mn)
Date: 2026-10-22T18:57:52+02:00
Commit Message:
HAROLD: BCB: Add computer categories and evidence icons
Changed paths:
engines/harold/bcb/bcb.cpp
engines/harold/bcb/bcb.h
engines/harold/bcb/computer.cpp
diff --git a/engines/harold/bcb/bcb.cpp b/engines/harold/bcb/bcb.cpp
index 5029638844d..ca76a37b336 100644
--- a/engines/harold/bcb/bcb.cpp
+++ b/engines/harold/bcb/bcb.cpp
@@ -81,6 +81,10 @@ BCBEngine::~BCBEngine() {
_chipBitmap->free();
delete _chipBitmap;
}
+ if (_computerBitmap) {
+ _computerBitmap->free();
+ delete _computerBitmap;
+ }
delete _activeMovieVideo;
delete _mapMoveVideo;
delete _sceneIdleVideo;
diff --git a/engines/harold/bcb/bcb.h b/engines/harold/bcb/bcb.h
index ec3f1630a94..4e04675e640 100644
--- a/engines/harold/bcb/bcb.h
+++ b/engines/harold/bcb/bcb.h
@@ -95,6 +95,13 @@ private:
bool dispatchComputerDetailControls();
bool dispatchGameOverScreen();
bool dispatchEndingStillDisplayState();
+ const Graphics::Surface *getComputerBitmap();
+ bool showComputerScreen();
+ void drawComputerChoiceGroupControls();
+ void drawComputerConditionalChoiceControls(const ButtonControlRecord *choiceButtonRecords, int firstStateIndex, int stateCount);
+ void drawComputerButton(const ButtonControlRecord &sourceButton, const ButtonControlRecord &destButton);
+ void drawComputerButtonPressed(const ButtonControlRecord &button, int sourceX, int sourceY);
+ void drawComputerUnavailableMarker(const ButtonControlRecord &destButton);
void checkCurrentMediaPathReady();
void clearDisplayAndResetState();
void executeCurrentScriptSequence();
@@ -206,6 +213,7 @@ private:
Graphics::Surface *_chipBitmap = nullptr;
Graphics::Surface *_sceneChoiceBacking = nullptr;
Common::Rect _sceneChoiceBackingRect;
+ Graphics::Surface *_computerBitmap = nullptr;
Audio::SoundHandle _uiSoundHandle;
uint32 _gameState[0x100] = {};
uint32 _scriptValueStack[0x100] = {};
@@ -223,8 +231,13 @@ private:
int _nextTimedCaptionThreshold = 99999;
int _queuedMoviePlaybackIndex = 0;
int _previousDisplayStateBeforeSystem = 0;
+ int _computerChoiceGroupIndex = 0;
int _sceneChoiceTerminatorButtonIndex = 0;
int _sceneChoiceEnabledButtonLimit = 0;
+ bool _computerControlsActive = false;
+ bool _computerDetailControlsActive = false;
+ Common::Array<int> _computerActiveChoiceIds;
+ Common::Array<Common::Rect> _computerActiveChoiceRects;
Common::U32String _timedCaptionText;
Common::String _timedCaptionTextSjis;
Common::Rect _activeCaptionBackingRect;
diff --git a/engines/harold/bcb/computer.cpp b/engines/harold/bcb/computer.cpp
index 84372cd95a0..a343b3850cd 100644
--- a/engines/harold/bcb/computer.cpp
+++ b/engines/harold/bcb/computer.cpp
@@ -40,12 +40,152 @@ static const int kComputerCompositeTransitionTop = 36;
static const int kComputerCompositeTransitionRight = 606;
static const int kComputerCompositeTransitionBottom = 444;
-bool BCBEngine::dispatchComputerDetailControls() {
- error("BCB: dispatchComputerDetailControls is not implemented");
+const Graphics::Surface *BCBEngine::getComputerBitmap() {
+ if (!_computerBitmap) {
+ _computerBitmap = loadBitmap("256com.bmp");
+ }
+ return _computerBitmap;
}
bool BCBEngine::dispatchComputerMainControls() {
- error("BCB: dispatchComputerMainControls is not implemented");
+ if (!showComputerScreen())
+ return false;
+
+ _computerControlsActive = true;
+ _computerDetailControlsActive = false;
+ _computerActiveChoiceIds.clear();
+ _computerActiveChoiceRects.clear();
+ drawComputerChoiceGroupControls();
+ drawComputerButton(kComputerPrimaryButtonTable[1], kComputerPrimaryButtonTable[1]);
+
+ g_system->updateScreen();
+ return true;
+}
+
+bool BCBEngine::showComputerScreen() {
+ const Graphics::Surface *computer = getComputerBitmap();
+ if (!computer)
+ return false;
+
+ g_system->copyRectToScreen(computer->getPixels(), computer->pitch, 0, 0, MIN<int>(computer->w, 640), MIN<int>(computer->h, 480));
+ _currentDisplayState = 4;
+ return true;
+}
+
+void BCBEngine::drawComputerChoiceGroupControls() {
+ _computerActiveChoiceIds.clear();
+ _computerActiveChoiceRects.clear();
+
+ const Graphics::Surface *computer = getComputerBitmap();
+ if (!computer)
+ return;
+
+ Graphics::Surface *screen = g_system->lockScreen();
+ if (screen) {
+ if (_computerChoiceGroupIndex >= 0 && _computerChoiceGroupIndex <= 2) {
+ const int sourceY = _computerChoiceGroupIndex * 0x16 + 0x2a0;
+ if (sourceY + 0x16 <= computer->h)
+ screen->copyRectToSurface(*computer, 0x3c, 0x43, Common::Rect(0, sourceY, 0xe8, sourceY + 0x16));
+ }
+
+ for (uint i = 1; i < ARRAYSIZE(kComputerChoiceLayoutSlots); ++i) {
+ const ButtonControlRecord &slot = kComputerChoiceLayoutSlots[i];
+ if (slot.x + slot.width <= computer->w && slot.y + slot.height <= computer->h)
+ screen->copyRectToSurface(*computer, slot.x, slot.y, Common::Rect(slot.x, slot.y, slot.x + slot.width, slot.y + slot.height));
+ }
+ g_system->unlockScreen();
+ }
+
+ if (_computerChoiceGroupIndex == 0)
+ drawComputerConditionalChoiceControls(kComputerChoiceButtonTable + 21, 21, 6);
+ else if (_computerChoiceGroupIndex == 1)
+ drawComputerConditionalChoiceControls(kComputerChoiceButtonTable, 0, 21);
+ else if (_computerChoiceGroupIndex == 2)
+ drawComputerConditionalChoiceControls(kComputerChoiceButtonTable + 27, 27, 3);
+}
+
+void BCBEngine::drawComputerButton(const ButtonControlRecord &sourceButton, const ButtonControlRecord &destButton) {
+ const Graphics::Surface *computer = getComputerBitmap();
+ if (!computer)
+ return;
+
+ Graphics::Surface *screen = g_system->lockScreen();
+ if (screen && sourceButton.x + sourceButton.width <= computer->w && sourceButton.y + sourceButton.height <= computer->h)
+ screen->copyRectToSurface(*computer, destButton.x, destButton.y, Common::Rect(sourceButton.x, sourceButton.y, sourceButton.x + sourceButton.width, sourceButton.y + sourceButton.height));
+ if (screen)
+ g_system->unlockScreen();
+}
+
+void BCBEngine::drawComputerButtonPressed(const ButtonControlRecord &button, int sourceX, int sourceY) {
+ const Graphics::Surface *computer = getComputerBitmap();
+ if (!computer)
+ return;
+
+ if (sourceX + button.width <= computer->w && sourceY + button.height <= computer->h) {
+ g_system->copyRectToScreen(computer->getBasePtr(sourceX, sourceY), computer->pitch, button.x, button.y, button.width, button.height);
+ g_system->updateScreen();
+ }
+}
+
+void BCBEngine::drawComputerUnavailableMarker(const ButtonControlRecord &destButton) {
+ const Graphics::Surface *computer = getComputerBitmap();
+ if (!computer)
+ return;
+
+ const int markerSourceX = 0x240;
+ const int markerSourceY = 0x260;
+ const int markerWidth = 0x36;
+ const int markerHeight = 0x18;
+ if (markerSourceX + markerWidth <= computer->w && markerSourceY + markerHeight <= computer->h)
+ g_system->copyRectToScreen(computer->getBasePtr(markerSourceX, markerSourceY), computer->pitch, destButton.x + 5, destButton.y + 0x0c, markerWidth, markerHeight);
+}
+
+void BCBEngine::drawComputerConditionalChoiceControls(const ButtonControlRecord *choiceButtonRecords, int firstStateIndex, int stateCount) {
+ const Graphics::Surface *computer = getComputerBitmap();
+ if (!computer)
+ return;
+
+ Graphics::Surface *screen = g_system->lockScreen();
+ int visibleButtonSlot = 1;
+ for (int i = 0; screen && i < stateCount && visibleButtonSlot < (int)ARRAYSIZE(kComputerChoiceLayoutSlots); ++i) {
+ const int stateIndex = firstStateIndex + i;
+ const byte dependencyStateIndex = stateIndex >= 0 && stateIndex < (int)ARRAYSIZE(kComputerChoiceDependencyStateIndex) ? kComputerChoiceDependencyStateIndex[stateIndex] : 0;
+ const bool directlyAvailable = stateDword(kStateComputerChoiceStateByIndex + stateIndex * 4) != 0;
+ const bool dependencyOnlyAvailable =
+ dependencyStateIndex != 0 && stateDword(kStateComputerChoiceStateByIndex + dependencyStateIndex * 4) == 1;
+ const bool visible = directlyAvailable || dependencyOnlyAvailable;
+ if (!visible)
+ continue;
+
+ const ButtonControlRecord &source = choiceButtonRecords[i];
+ const ButtonControlRecord &dest = kComputerChoiceLayoutSlots[visibleButtonSlot++];
+ if (source.x + source.width <= computer->w && source.y + source.height <= computer->h)
+ screen->copyRectToSurface(*computer, dest.x, dest.y, Common::Rect(source.x, source.y, source.x + source.width, source.y + source.height));
+ if (directlyAvailable) {
+ _computerActiveChoiceIds.push_back(source.controlId);
+ _computerActiveChoiceRects.push_back(Common::Rect(dest.x, dest.y, dest.x + dest.width, dest.y + dest.height));
+ }
+ }
+ if (screen)
+ g_system->unlockScreen();
+
+ visibleButtonSlot = 1;
+ for (int i = 0; i < stateCount && visibleButtonSlot < (int)ARRAYSIZE(kComputerChoiceLayoutSlots); ++i) {
+ const int stateIndex = firstStateIndex + i;
+ const byte dependencyStateIndex = stateIndex >= 0 && stateIndex < (int)ARRAYSIZE(kComputerChoiceDependencyStateIndex) ? kComputerChoiceDependencyStateIndex[stateIndex] : 0;
+ const bool directlyAvailable = stateDword(kStateComputerChoiceStateByIndex + stateIndex * 4) != 0;
+ const bool dependencyOnlyAvailable =
+ dependencyStateIndex != 0 && stateDword(kStateComputerChoiceStateByIndex + dependencyStateIndex * 4) == 1;
+ if (!directlyAvailable && !dependencyOnlyAvailable)
+ continue;
+ const ButtonControlRecord &dest = kComputerChoiceLayoutSlots[visibleButtonSlot++];
+ if (!directlyAvailable && dependencyOnlyAvailable)
+ drawComputerUnavailableMarker(dest);
+ }
+}
+
+bool BCBEngine::dispatchComputerDetailControls() {
+ error("BCB: dispatchComputerDetailControls is not implemented");
}
} // End of namespace BCB
Commit: 35467ac424915868e28a871353df568ce8e67660
https://github.com/scummvm/scummvm/commit/35467ac424915868e28a871353df568ce8e67660
Author: Scorp (scorp at mrs.mn)
Date: 2026-10-22T18:57:52+02:00
Commit Message:
HAROLD: BCB: Display computer pages and transitions
Changed paths:
engines/harold/bcb/bcb.cpp
engines/harold/bcb/bcb.h
engines/harold/bcb/computer.cpp
diff --git a/engines/harold/bcb/bcb.cpp b/engines/harold/bcb/bcb.cpp
index ca76a37b336..b1086851277 100644
--- a/engines/harold/bcb/bcb.cpp
+++ b/engines/harold/bcb/bcb.cpp
@@ -90,6 +90,10 @@ BCBEngine::~BCBEngine() {
delete _sceneIdleVideo;
delete _gameOverVideo;
freeSceneChoiceBacking();
+ if (_computerDetailBacking) {
+ _computerDetailBacking->free();
+ delete _computerDetailBacking;
+ }
delete _sjisFont;
delete _captionTextFont;
delete _gameOverTextFont;
diff --git a/engines/harold/bcb/bcb.h b/engines/harold/bcb/bcb.h
index 4e04675e640..b1ce6fd0aef 100644
--- a/engines/harold/bcb/bcb.h
+++ b/engines/harold/bcb/bcb.h
@@ -101,7 +101,11 @@ private:
void drawComputerConditionalChoiceControls(const ButtonControlRecord *choiceButtonRecords, int firstStateIndex, int stateCount);
void drawComputerButton(const ButtonControlRecord &sourceButton, const ButtonControlRecord &destButton);
void drawComputerButtonPressed(const ButtonControlRecord &button, int sourceX, int sourceY);
+ void drawComputerDetailButtonNormal(const ButtonControlRecord &button);
void drawComputerUnavailableMarker(const ButtonControlRecord &destButton);
+ void drawXorRectangleBorder(const Common::Rect &rect);
+ void processXorRectangleBorderAnimation(bool expand, const Common::Rect &sourceRect, const Common::Rect &targetRect);
+ bool showComputerDetailScreen(int compositeIndex);
void checkCurrentMediaPathReady();
void clearDisplayAndResetState();
void executeCurrentScriptSequence();
@@ -214,6 +218,7 @@ private:
Graphics::Surface *_sceneChoiceBacking = nullptr;
Common::Rect _sceneChoiceBackingRect;
Graphics::Surface *_computerBitmap = nullptr;
+ Graphics::Surface *_computerDetailBacking = nullptr;
Audio::SoundHandle _uiSoundHandle;
uint32 _gameState[0x100] = {};
uint32 _scriptValueStack[0x100] = {};
@@ -232,10 +237,12 @@ private:
int _queuedMoviePlaybackIndex = 0;
int _previousDisplayStateBeforeSystem = 0;
int _computerChoiceGroupIndex = 0;
+ int _computerDetailVariantIndex = 0;
int _sceneChoiceTerminatorButtonIndex = 0;
int _sceneChoiceEnabledButtonLimit = 0;
bool _computerControlsActive = false;
bool _computerDetailControlsActive = false;
+ bool _computerDetailNextVariantEnabled = false;
Common::Array<int> _computerActiveChoiceIds;
Common::Array<Common::Rect> _computerActiveChoiceRects;
Common::U32String _timedCaptionText;
diff --git a/engines/harold/bcb/computer.cpp b/engines/harold/bcb/computer.cpp
index a343b3850cd..7b16e8d2cf2 100644
--- a/engines/harold/bcb/computer.cpp
+++ b/engines/harold/bcb/computer.cpp
@@ -31,8 +31,6 @@
#include "harold/bcb/script.h"
#include "harold/bcb/static_data.h"
-#include "common/textconsole.h"
-
namespace BCB {
static const int kComputerCompositeTransitionLeft = 36;
@@ -62,6 +60,15 @@ bool BCBEngine::dispatchComputerMainControls() {
return true;
}
+bool BCBEngine::dispatchComputerDetailControls() {
+ _computerDetailVariantIndex = 0;
+ _computerControlsActive = false;
+ _computerDetailControlsActive = true;
+ _computerActiveChoiceIds.clear();
+ _computerActiveChoiceRects.clear();
+ return showComputerDetailScreen(stateDword(kStateSelectedSceneChoiceValue));
+}
+
bool BCBEngine::showComputerScreen() {
const Graphics::Surface *computer = getComputerBitmap();
if (!computer)
@@ -127,6 +134,16 @@ void BCBEngine::drawComputerButtonPressed(const ButtonControlRecord &button, int
}
}
+void BCBEngine::drawComputerDetailButtonNormal(const ButtonControlRecord &button) {
+ if (!_computerDetailBacking)
+ return;
+
+ if (button.x + button.width <= _computerDetailBacking->w && button.y + button.height <= _computerDetailBacking->h) {
+ g_system->copyRectToScreen(_computerDetailBacking->getBasePtr(button.x, button.y), _computerDetailBacking->pitch, button.x, button.y, button.width, button.height);
+ g_system->updateScreen();
+ }
+}
+
void BCBEngine::drawComputerUnavailableMarker(const ButtonControlRecord &destButton) {
const Graphics::Surface *computer = getComputerBitmap();
if (!computer)
@@ -140,6 +157,87 @@ void BCBEngine::drawComputerUnavailableMarker(const ButtonControlRecord &destBut
g_system->copyRectToScreen(computer->getBasePtr(markerSourceX, markerSourceY), computer->pitch, destButton.x + 5, destButton.y + 0x0c, markerWidth, markerHeight);
}
+void BCBEngine::drawXorRectangleBorder(const Common::Rect &rect) {
+ Graphics::Surface *screen = g_system->lockScreen();
+ if (!screen)
+ return;
+
+ const Common::Rect clipped = rect.findIntersectingRect(Common::Rect(0, 0, screen->w, screen->h));
+ if (clipped.width() > 0 && clipped.height() > 0) {
+ const auto invertPixel = [&](int x, int y) {
+ byte *pixel = (byte *)screen->getBasePtr(x, y);
+ switch (screen->format.bytesPerPixel) {
+ case 1:
+ pixel[0] ^= 0xff;
+ break;
+ case 2:
+ WRITE_UINT16(pixel, READ_UINT16(pixel) ^ 0xffff);
+ break;
+ case 3:
+ pixel[0] ^= 0xff;
+ pixel[1] ^= 0xff;
+ pixel[2] ^= 0xff;
+ break;
+ case 4:
+ pixel[0] ^= 0xff;
+ pixel[1] ^= 0xff;
+ pixel[2] ^= 0xff;
+ break;
+ default:
+ break;
+ }
+ };
+
+ for (int x = clipped.left; x < clipped.right; ++x) {
+ invertPixel(x, clipped.top);
+ if (rect.bottom != rect.top && rect.bottom >= 0 && rect.bottom < screen->h)
+ invertPixel(x, rect.bottom);
+ }
+ for (int y = clipped.top; y < clipped.bottom; ++y) {
+ invertPixel(clipped.left, y);
+ if (rect.right != rect.left && rect.right >= 0 && rect.right < screen->w)
+ invertPixel(rect.right, y);
+ }
+ }
+
+ g_system->unlockScreen();
+ g_system->updateScreen();
+}
+
+void BCBEngine::processXorRectangleBorderAnimation(bool expand, const Common::Rect &sourceRect, const Common::Rect &targetRect) {
+ const double base = 0.7;
+ double drawScale = pow(base, 15.0);
+ double eraseScale = drawScale;
+ const double divisor = expand ? base : (1.0 / base);
+ if (!expand) {
+ drawScale = 1.0 - drawScale;
+ eraseScale = drawScale;
+ }
+
+ const auto transitionRect = [&](double scale) {
+ return Common::Rect(
+ sourceRect.left + (int16)(int)((targetRect.left - sourceRect.left) * scale),
+ sourceRect.top + (int16)(int)((targetRect.top - sourceRect.top) * scale),
+ sourceRect.right + (int16)(int)((targetRect.right - sourceRect.right) * scale),
+ sourceRect.bottom + (int16)(int)((targetRect.bottom - sourceRect.bottom) * scale));
+ };
+
+ for (int step = 0; step < 22; ++step) {
+ if (step < 14) {
+ drawScale /= divisor;
+ drawXorRectangleBorder(transitionRect(drawScale));
+ }
+
+ if (step > 7) {
+ eraseScale /= divisor;
+ drawXorRectangleBorder(transitionRect(eraseScale));
+ }
+
+ g_system->delayMillis(20);
+ pollEvents(false);
+ }
+}
+
void BCBEngine::drawComputerConditionalChoiceControls(const ButtonControlRecord *choiceButtonRecords, int firstStateIndex, int stateCount) {
const Graphics::Surface *computer = getComputerBitmap();
if (!computer)
@@ -184,8 +282,49 @@ void BCBEngine::drawComputerConditionalChoiceControls(const ButtonControlRecord
}
}
-bool BCBEngine::dispatchComputerDetailControls() {
- error("BCB: dispatchComputerDetailControls is not implemented");
+bool BCBEngine::showComputerDetailScreen(int compositeIndex) {
+ if (compositeIndex < 0 || compositeIndex >= (int)ARRAYSIZE(kComputerDetailBitmapVariantsBySelectedValue))
+ return false;
+
+ const CompositeBitmapVariantSet &variantSet = kComputerDetailBitmapVariantsBySelectedValue[compositeIndex];
+ if (variantSet.pathCount == 0 || _computerDetailVariantIndex < 0 || _computerDetailVariantIndex >= (int)variantSet.pathCount)
+ return false;
+ const int displayedVariantIndex = _computerDetailVariantIndex;
+ const char *const compositePath = variantSet.paths[_computerDetailVariantIndex];
+
+ const Graphics::Surface *computer = getComputerBitmap();
+ if (!computer)
+ return false;
+
+ Graphics::Surface *composite = loadDiscBitmap(compositePath);
+ if (!composite)
+ return false;
+
+ Graphics::Surface *screen = g_system->lockScreen();
+ if (screen) {
+ screen->copyRectToSurface(*computer, 0, 0, Common::Rect(0, 0, MIN<int>(computer->w, 640), MIN<int>(computer->h, 480)));
+ screen->copyRectToSurface(*composite, 0x24, 0x23, Common::Rect(0, 0, MIN<int>(composite->w, 640 - 0x24), MIN<int>(composite->h, 480 - 0x23)));
+ if (_computerDetailBacking) {
+ _computerDetailBacking->free();
+ delete _computerDetailBacking;
+ _computerDetailBacking = nullptr;
+ }
+ _computerDetailBacking = new Graphics::Surface();
+ _computerDetailBacking->copyFrom(*screen);
+ g_system->unlockScreen();
+ }
+
+ composite->free();
+ delete composite;
+
+ _computerDetailNextVariantEnabled = variantSet.pathCount > 1;
+
+ if (_computerDetailNextVariantEnabled && displayedVariantIndex + 1 >= (int)variantSet.pathCount)
+ _computerDetailVariantIndex = -1;
+
+ _currentDisplayState = 5;
+ g_system->updateScreen();
+ return true;
}
} // End of namespace BCB
Commit: 09f13dc11485d9c262d511d8718840c625a8dcac
https://github.com/scummvm/scummvm/commit/09f13dc11485d9c262d511d8718840c625a8dcac
Author: Scorp (scorp at mrs.mn)
Date: 2026-10-22T18:57:52+02:00
Commit Message:
HAROLD: BCB: Handle computer navigation and page input
Changed paths:
engines/harold/bcb/bcb.h
engines/harold/bcb/computer.cpp
engines/harold/bcb/input.cpp
diff --git a/engines/harold/bcb/bcb.h b/engines/harold/bcb/bcb.h
index b1ce6fd0aef..e42f723d422 100644
--- a/engines/harold/bcb/bcb.h
+++ b/engines/harold/bcb/bcb.h
@@ -153,6 +153,8 @@ private:
void drawSceneChoiceButtons(int sceneChoiceUiSetToShowId, bool present = true, int pressedButtonIndex = -1);
void handleSceneChoiceClick(const Common::Point &mousePosition);
void handleSceneChoiceSelection(int buttonIndex);
+ bool handleComputerClick(const Common::Point &mousePosition);
+ bool handleComputerKey(Common::KeyCode keycode);
void applyPendingClockTickDelta();
void notifyGameClockTicksChanged();
void drawClockAndChipIndicator(bool present = true);
@@ -245,6 +247,7 @@ private:
bool _computerDetailNextVariantEnabled = false;
Common::Array<int> _computerActiveChoiceIds;
Common::Array<Common::Rect> _computerActiveChoiceRects;
+ Common::Rect _computerSelectionXorRect;
Common::U32String _timedCaptionText;
Common::String _timedCaptionTextSjis;
Common::Rect _activeCaptionBackingRect;
diff --git a/engines/harold/bcb/computer.cpp b/engines/harold/bcb/computer.cpp
index 7b16e8d2cf2..fe33c6e87f4 100644
--- a/engines/harold/bcb/computer.cpp
+++ b/engines/harold/bcb/computer.cpp
@@ -327,4 +327,97 @@ bool BCBEngine::showComputerDetailScreen(int compositeIndex) {
return true;
}
+bool BCBEngine::handleComputerClick(const Common::Point &mousePosition) {
+ if (_currentDisplayState == 4 && _computerControlsActive) {
+ const ButtonControlRecord &cancelButton = kComputerPrimaryButtonTable[1];
+ if (Common::Rect(cancelButton.x, cancelButton.y, cancelButton.x + cancelButton.width, cancelButton.y + cancelButton.height).contains(mousePosition)) {
+ drawComputerButtonPressed(cancelButton, 0xf8, 0x2d0);
+ playUiSound();
+ _computerControlsActive = false;
+ _computerActiveChoiceIds.clear();
+ _computerActiveChoiceRects.clear();
+ stateDword(kStateSelectedSceneChoiceValue) = 0xff;
+ processGameStateDispatcher(true);
+ return true;
+ }
+
+ for (uint groupIndex = 0; groupIndex < ARRAYSIZE(kComputerChoiceGroupHitRects); ++groupIndex) {
+ if (!kComputerChoiceGroupHitRects[groupIndex].contains(mousePosition))
+ continue;
+
+ if (_computerChoiceGroupIndex != (int)groupIndex) {
+ playUiSound();
+ _computerChoiceGroupIndex = groupIndex;
+ drawComputerChoiceGroupControls();
+ g_system->updateScreen();
+ }
+ return true;
+ }
+
+ for (uint i = 0; i < _computerActiveChoiceRects.size(); ++i) {
+ if (!_computerActiveChoiceRects[i].contains(mousePosition))
+ continue;
+
+ playUiSound();
+ _computerSelectionXorRect = _computerActiveChoiceRects[i];
+ processXorRectangleBorderAnimation(true, _computerSelectionXorRect, Common::Rect(kComputerCompositeTransitionLeft, kComputerCompositeTransitionTop, kComputerCompositeTransitionRight, kComputerCompositeTransitionBottom));
+ _computerControlsActive = false;
+ stateDword(kStateSelectedSceneChoiceValue) = _computerActiveChoiceIds[i] - 700;
+ _computerActiveChoiceIds.clear();
+ _computerActiveChoiceRects.clear();
+ processGameStateDispatcher(true);
+ return true;
+ }
+ }
+
+ if (_currentDisplayState == 5 && _computerDetailControlsActive) {
+ const ButtonControlRecord &continueButton = kComputerDetailButtonTable[1];
+ if (Common::Rect(continueButton.x, continueButton.y, continueButton.x + continueButton.width, continueButton.y + continueButton.height).contains(mousePosition)) {
+ drawComputerButtonPressed(continueButton, 0x149, 0x2b8);
+ playUiSound();
+ if (!_computerSelectionXorRect.isEmpty())
+ processXorRectangleBorderAnimation(false, _computerSelectionXorRect, Common::Rect(kComputerCompositeTransitionLeft, kComputerCompositeTransitionTop, kComputerCompositeTransitionRight, kComputerCompositeTransitionBottom));
+ _computerDetailControlsActive = false;
+ processGameStateDispatcher(true);
+ return true;
+ }
+
+ const ButtonControlRecord &nextButton = kComputerDetailButtonTable[2];
+ if (_computerDetailNextVariantEnabled &&
+ Common::Rect(nextButton.x, nextButton.y, nextButton.x + nextButton.width, nextButton.y + nextButton.height).contains(mousePosition)) {
+ drawComputerButtonPressed(nextButton, _computerDetailVariantIndex >= 0 ? 0x188 : 0x228, 0x2a0);
+ playUiSound();
+ if (_computerDetailVariantIndex < 0) {
+ _computerDetailVariantIndex = 0;
+ } else {
+ ++_computerDetailVariantIndex;
+ }
+ showComputerDetailScreen(stateDword(kStateSelectedSceneChoiceValue));
+ return true;
+ }
+ }
+
+ return false;
+}
+
+bool BCBEngine::handleComputerKey(Common::KeyCode keycode) {
+ if (_currentDisplayState == 4 && _computerControlsActive && keycode == Common::KEYCODE_ESCAPE) {
+ const ButtonControlRecord &cancelButton = kComputerPrimaryButtonTable[1];
+ return handleComputerClick(Common::Point(cancelButton.x + 1, cancelButton.y + 1));
+ }
+
+ if (_currentDisplayState == 5 && _computerDetailControlsActive) {
+ if (keycode == Common::KEYCODE_ESCAPE) {
+ const ButtonControlRecord &continueButton = kComputerDetailButtonTable[1];
+ return handleComputerClick(Common::Point(continueButton.x + 1, continueButton.y + 1));
+ }
+
+ if (keycode == Common::KEYCODE_SPACE && _computerDetailNextVariantEnabled) {
+ const ButtonControlRecord &nextButton = kComputerDetailButtonTable[2];
+ return handleComputerClick(Common::Point(nextButton.x + 1, nextButton.y + 1));
+ }
+ }
+
+ return false;
+}
} // End of namespace BCB
diff --git a/engines/harold/bcb/input.cpp b/engines/harold/bcb/input.cpp
index 1aff33f17e5..a66200a6d84 100644
--- a/engines/harold/bcb/input.cpp
+++ b/engines/harold/bcb/input.cpp
@@ -104,6 +104,9 @@ void BCBEngine::handleLeftButtonDown(const Common::Point &mousePosition) {
return;
}
+ if (handleComputerClick(mousePosition))
+ return;
+
if (handleMapControlClick(mousePosition))
return;
Commit: 65e7fb2b8c2d8f98d636eef91da7ba57c4856feb
https://github.com/scummvm/scummvm/commit/65e7fb2b8c2d8f98d636eef91da7ba57c4856feb
Author: Scorp (scorp at mrs.mn)
Date: 2026-10-22T18:57:52+02:00
Commit Message:
HAROLD: BCB: Save and restore BCB game state
Changed paths:
A engines/harold/bcb/saveload.cpp
engines/harold/bcb/bcb.h
engines/harold/bcb/input.cpp
engines/harold/bcb/map.cpp
engines/harold/bcb/scene.cpp
engines/harold/module.mk
diff --git a/engines/harold/bcb/bcb.h b/engines/harold/bcb/bcb.h
index e42f723d422..fb44cfc184d 100644
--- a/engines/harold/bcb/bcb.h
+++ b/engines/harold/bcb/bcb.h
@@ -60,6 +60,12 @@ public:
~BCBEngine();
Common::Error run() override;
+ bool hasFeature(EngineFeature f) const override;
+ Common::Error loadGameState(int slot) override;
+ bool canLoadGameStateCurrently(Common::U32String *msg = nullptr) override;
+ Common::Error saveGameState(int slot, const Common::String &desc, bool isAutosave = false) override;
+ bool canSaveGameStateCurrently(Common::U32String *msg = nullptr) override;
+ bool canSaveAutosaveCurrently() override;
private:
bool initializeSearchPaths();
@@ -79,10 +85,12 @@ private:
void startSceneIdleMovie();
void stopSceneIdleMovie();
void restoreTitleScreenRegion(const Common::Rect &rect, bool present = true);
+ void writeContinueState();
bool openMapMoveMovie();
void startMapMoveMovie();
void stopMapMoveMovie();
void resetGameState();
+ void resetTransientRuntimeStateAfterLoad();
bool loadScriptData();
bool loadExecutableDataTables();
bool loadByteFile(const char *fileName, Common::Array<byte> &data) const;
@@ -179,6 +187,8 @@ private:
void delayWithEventPolling(uint32 millis);
void delayWithEventPolling(uint32 millis, bool handleClicks);
Common::U32String decodeExecutableTextForDisplay(const Common::String &text) const;
+ Common::U32String formatSystemTextForDisplay(const Common::String &originalFormat, int slot) const;
+ Common::U32String formatSystemClockTextForDisplay(const Common::String &originalFormat, int slot, int savedDay, int savedClockTicks) const;
void mainLoop();
const HaroldGameDescription *_gameDescription;
@@ -263,6 +273,7 @@ private:
bool _suppressClickHandling = false;
bool _titleButtonsActive = false;
bool _systemButtonsVisible = false;
+ int _systemSaveLoadSlotActionMode = 0;
bool _titleLoadSlotsActive = false;
};
diff --git a/engines/harold/bcb/input.cpp b/engines/harold/bcb/input.cpp
index a66200a6d84..15158da2c9d 100644
--- a/engines/harold/bcb/input.cpp
+++ b/engines/harold/bcb/input.cpp
@@ -126,6 +126,13 @@ void BCBEngine::handleLeftButtonDown(const Common::Point &mousePosition) {
case 51:
startNewGameFromTitle();
break;
+ case 52:
+ drawTitleButton(i, false);
+ _titleButtonsActive = false;
+ _titleLoadSlotsActive = false;
+ if (loadGameState(0).getCode() != Common::kNoError)
+ _titleButtonsActive = true;
+ break;
default:
drawTitleButton(i, false);
break;
diff --git a/engines/harold/bcb/map.cpp b/engines/harold/bcb/map.cpp
index c19dc9ffde3..a4d78397061 100644
--- a/engines/harold/bcb/map.cpp
+++ b/engines/harold/bcb/map.cpp
@@ -211,6 +211,7 @@ bool BCBEngine::dispatchMapMoveControls() {
if (showNewMapScreen)
showMapScreen();
_currentDisplayState = 2;
+ writeContinueState();
if (!showNewMapScreen)
drawMapControls();
startMapMoveMovie();
diff --git a/engines/harold/bcb/saveload.cpp b/engines/harold/bcb/saveload.cpp
new file mode 100644
index 00000000000..d3cbbb01599
--- /dev/null
+++ b/engines/harold/bcb/saveload.cpp
@@ -0,0 +1,226 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "common/algorithm.h"
+#include "common/config-manager.h"
+#include "common/endian.h"
+#include "common/events.h"
+#include "common/savefile.h"
+#include "common/stream.h"
+#include "common/system.h"
+#include "common/textconsole.h"
+#include "graphics/font.h"
+#include "graphics/fontman.h"
+#include "graphics/surface.h"
+
+#include "harold/bcb/bcb.h"
+#include "harold/bcb/script.h"
+#include "harold/bcb/static_data.h"
+
+namespace BCB {
+
+enum {
+ kSavegameMagic = MKTAG('B', 'C', 'B', 'S'),
+ kSavegameVersion = 1,
+ kManualSaveSlotCount = 8,
+ kNativeGameStateDwordCount = 0x100,
+ kNativeGameStateByteSize = kNativeGameStateDwordCount * 4
+};
+
+static const StaticRect kSystemWaveVolumeDownRect = {268, 407, 290, 439};
+static const StaticRect kSystemWaveVolumeUpRect = {444, 407, 466, 439};
+static const StaticRect kSystemWaveVolumeSeekRect = {294, 414, 442, 439};
+static const StaticRect kSystemWaveVolumeIndicatorRect = {294, 425, 442, 449};
+
+static const Common::Point kSystemSubmodeNormalSources[5] = {
+ Common::Point(0, 0),
+ Common::Point(0, 736),
+ Common::Point(0, 824),
+ Common::Point(240, 736),
+ Common::Point(274, 824)};
+
+static const Common::Point kSystemSubmodeSelectedSources[5] = {
+ Common::Point(0, 0),
+ Common::Point(0, 780),
+ Common::Point(0, 868),
+ Common::Point(240, 780),
+ Common::Point(368, 824)};
+
+static Common::String formatEnglishSaveDescription(int savedDay, int savedClockTicks) {
+ const int discIndex = CLIP<int>(savedDay, 0, 4);
+ const int dayOfMonth = 10 + discIndex;
+ const int clockTicks = savedClockTicks % (24 * 60);
+ const int hour24 = clockTicks / 60;
+ const int minute = clockTicks % 60;
+ const int hour12 = hour24 % 12 == 0 ? 12 : hour24 % 12;
+ const char *period = hour24 < 12 ? "AM" : "PM";
+
+ const Common::String chapterName = discIndex == 0 ? Common::String("Prologue") : Common::String::format("Chapter %d", discIndex);
+ return Common::String::format("%s: November %d, %d:%02d %s",
+ chapterName.c_str(), dayOfMonth, hour12, minute, period);
+}
+
+static bool isValidBCBSaveSlot(int slot) {
+ return slot >= 0 && slot <= kManualSaveSlotCount;
+}
+
+bool BCBEngine::hasFeature(EngineFeature f) const {
+ return f == kSupportsLoadingDuringRuntime ||
+ f == kSupportsSavingDuringRuntime ||
+ f == kSupportsReturnToLauncher;
+}
+
+bool BCBEngine::canLoadGameStateCurrently(Common::U32String *msg) {
+ return true;
+}
+
+bool BCBEngine::canSaveGameStateCurrently(Common::U32String *msg) {
+ return true;
+}
+
+bool BCBEngine::canSaveAutosaveCurrently() {
+ return false;
+}
+
+Common::Error BCBEngine::saveGameState(int slot, const Common::String &, bool isAutosave) {
+ if (!isValidBCBSaveSlot(slot))
+ return Common::kWritingFailed;
+
+ Common::OutSaveFile *saveFile = _saveFileMan->openForSaving(getSaveStateName(slot));
+ if (!saveFile)
+ return Common::kWritingFailed;
+
+ saveFile->writeUint32BE(kSavegameMagic);
+ saveFile->writeUint16LE(kSavegameVersion);
+ saveFile->writeUint16LE(kNativeGameStateByteSize);
+
+ for (uint i = 0; i < kNativeGameStateDwordCount; ++i)
+ saveFile->writeUint32LE(_gameState[i]);
+
+ Common::Error result = saveFile->err() ? Common::kWritingFailed : Common::kNoError;
+ if (result.getCode() == Common::kNoError) {
+ const Common::String saveDescription = formatEnglishSaveDescription(stateDword(kStateRequiredDiscIndex), stateDword(kStateGameClockTicks));
+ getMetaEngine()->appendExtendedSave(saveFile, getTotalPlayTime(), saveDescription, isAutosave);
+ }
+
+ delete saveFile;
+ return result;
+}
+
+Common::Error BCBEngine::loadGameState(int slot) {
+ if (!isValidBCBSaveSlot(slot))
+ return Common::kReadingFailed;
+
+ Common::InSaveFile *saveFile = _saveFileMan->openForLoading(getSaveStateName(slot));
+ if (!saveFile)
+ return Common::kReadingFailed;
+
+ bool validSave = false;
+ const uint32 magic = saveFile->readUint32BE();
+ if (magic == kSavegameMagic) {
+ const uint16 version = saveFile->readUint16LE();
+ const uint16 stateByteSize = saveFile->readUint16LE();
+ validSave = version == kSavegameVersion && stateByteSize == kNativeGameStateByteSize;
+ } else if (saveFile->size() == kNativeGameStateByteSize) {
+ saveFile->seek(0);
+ validSave = true;
+ }
+
+ if (validSave) {
+ for (uint i = 0; i < kNativeGameStateDwordCount; ++i)
+ _gameState[i] = saveFile->readUint32LE();
+ validSave = !saveFile->err();
+ }
+
+ delete saveFile;
+ if (!validSave)
+ return Common::kReadingFailed;
+
+ resetTransientRuntimeStateAfterLoad();
+ processGameStateDispatcher(false);
+ return Common::kNoError;
+}
+
+void BCBEngine::resetTransientRuntimeStateAfterLoad() {
+ stopActiveMovie();
+ stopMenuBackgroundMusic();
+ stopSceneIdleMovie();
+ stopMapMoveMovie();
+ stopGameOverMusic();
+ restoreActiveCaptionBackingRect();
+ freeSceneChoiceBacking();
+
+ _scriptStackDepth = 0;
+ _scriptStopFlag = false;
+ _timedCaptionRecordData.clear();
+ _timedCaptionCursorOffset = 0;
+ _nextTimedCaptionThreshold = 99999;
+ _timedCaptionText.clear();
+ _timedCaptionTextSjis.clear();
+ _activeCaptionBackingRect = Common::Rect();
+ _queuedMoviePlaybackIndex = 0;
+ _previousDisplayStateBeforeSystem = 0;
+ _computerChoiceGroupIndex = 0;
+ _computerDetailVariantIndex = 0;
+ _sceneChoiceTerminatorButtonIndex = 0;
+ _sceneChoiceEnabledButtonLimit = 0;
+ _computerControlsActive = false;
+ _computerDetailControlsActive = false;
+ _computerDetailNextVariantEnabled = false;
+ _computerActiveChoiceIds.clear();
+ _computerActiveChoiceRects.clear();
+ _sceneChoiceActive = false;
+ _mapControlsActive = false;
+ _suppressClickHandling = false;
+ _titleButtonsActive = false;
+ _systemButtonsVisible = false;
+ _titleLoadSlotsActive = false;
+ _activeMovieDispatchOnFinish = false;
+ _activeMovieOnBlankScreen = false;
+ _activeMoviePaused = false;
+ _pendingStillElapsedTimerTicks = 0;
+ _gameOverElapsedTimerTicks = 0;
+ _pendingStillDurationIndex = 0;
+ _pendingClockTickDelta = 0;
+ _sceneIdleMovieTimerTicks = 0;
+ _systemSaveLoadSlotActionMode = 0;
+ _lastTimerTick = g_system->getMillis();
+ _lastLoggedGameClockTicks = -1;
+ _currentDisplayState = 0;
+}
+
+void BCBEngine::writeContinueState() {
+ saveGameState(0, formatEnglishSaveDescription(stateDword(kStateRequiredDiscIndex), stateDword(kStateGameClockTicks)), true);
+}
+
+Common::U32String BCBEngine::formatSystemTextForDisplay(const Common::String &originalFormat, int slot) const {
+ const Common::String formatted = Common::String::format(originalFormat.c_str(), slot);
+ return decodeExecutableTextForDisplay(formatted);
+}
+
+Common::U32String BCBEngine::formatSystemClockTextForDisplay(const Common::String &originalFormat, int slot, int savedDay, int savedClockTicks) const {
+ const int hour = savedClockTicks / 60;
+ const int minute = savedClockTicks % 60;
+ const Common::String formatted = Common::String::format(originalFormat.c_str(), slot, savedDay, hour, minute);
+ return decodeExecutableTextForDisplay(formatted);
+}
+
+} // End of namespace BCB
diff --git a/engines/harold/bcb/scene.cpp b/engines/harold/bcb/scene.cpp
index e6c719d9550..35b22557865 100644
--- a/engines/harold/bcb/scene.cpp
+++ b/engines/harold/bcb/scene.cpp
@@ -97,6 +97,7 @@ bool BCBEngine::showActiveSceneChoiceControls() {
drawSceneChoiceButtons(sceneChoiceUiSetToShowId);
_sceneChoiceActive = true;
_sceneIdleMovieTimerTicks = 0;
+ writeContinueState();
return true;
}
diff --git a/engines/harold/module.mk b/engines/harold/module.mk
index e7461a46f44..39510a47034 100644
--- a/engines/harold/module.mk
+++ b/engines/harold/module.mk
@@ -9,6 +9,7 @@ MODULE_OBJS := \
bcb/screens.o \
bcb/input.o \
bcb/computer.o \
+ bcb/saveload.o \
bcb/map.o \
bcb/script.o \
bcb/executable_data.o \
Commit: 5e7eee3119ce1bf39d02705c182caacb46fa74d8
https://github.com/scummvm/scummvm/commit/5e7eee3119ce1bf39d02705c182caacb46fa74d8
Author: Scorp (scorp at mrs.mn)
Date: 2026-10-22T18:57:52+02:00
Commit Message:
HAROLD: BCB: Add the in-game system menu
Changed paths:
engines/harold/bcb/bcb.h
engines/harold/bcb/input.cpp
engines/harold/bcb/saveload.cpp
diff --git a/engines/harold/bcb/bcb.h b/engines/harold/bcb/bcb.h
index fb44cfc184d..c4901b515e6 100644
--- a/engines/harold/bcb/bcb.h
+++ b/engines/harold/bcb/bcb.h
@@ -84,8 +84,20 @@ private:
void stopGameOverMusic();
void startSceneIdleMovie();
void stopSceneIdleMovie();
+ void enterSystemMenu();
+ bool showSystemScreen();
void restoreTitleScreenRegion(const Common::Rect &rect, bool present = true);
+ void drawSystemSaveLoadSlots(bool present = true);
+ void drawSystemSubmodeButtons(bool present = true);
+ void drawSystemSubmodeButton(uint buttonIndex, bool selected, bool present = true);
+ void syncSystemWaveVolumeFromMixer();
+ bool handleSystemWaveVolumeClick(const Common::Point &mousePosition);
+ bool handleSystemClick(const Common::Point &mousePosition);
+ void activateSystemExitButton();
void writeContinueState();
+ bool showSystemConfirmationDialog(const Common::U32String &message, bool showCancelButton);
+ void returnFromSystemMenuToPreviousState();
+ bool handleSaveLoadSlotClick(const Common::Point &mousePosition);
bool openMapMoveMovie();
void startMapMoveMovie();
void stopMapMoveMovie();
@@ -274,6 +286,7 @@ private:
bool _titleButtonsActive = false;
bool _systemButtonsVisible = false;
int _systemSaveLoadSlotActionMode = 0;
+ int _systemWaveVolume = 0xffff;
bool _titleLoadSlotsActive = false;
};
diff --git a/engines/harold/bcb/input.cpp b/engines/harold/bcb/input.cpp
index 15158da2c9d..4d63eb404dc 100644
--- a/engines/harold/bcb/input.cpp
+++ b/engines/harold/bcb/input.cpp
@@ -78,6 +78,12 @@ void BCBEngine::handleLeftButtonDown(const Common::Point &mousePosition) {
}
if ((_currentDisplayState == 1 || _currentDisplayState == 2) && !_activeMovieVideo) {
+ if (kSystemMenuControlHitRect.contains(mousePosition)) {
+ drawSceneChoiceControlStrip(kMovieControlStripPausePressedSourceX);
+ enterSystemMenu();
+ return;
+ }
+
if (kMovieQueueControlHitRect.contains(mousePosition)) {
playUiSound();
drawSceneChoiceControlStrip(kMovieControlStripSkipPressedSourceX);
@@ -110,6 +116,9 @@ void BCBEngine::handleLeftButtonDown(const Common::Point &mousePosition) {
if (handleMapControlClick(mousePosition))
return;
+ if (handleSystemClick(mousePosition))
+ return;
+
if (_currentDisplayState != 0 || !_titleButtonsActive)
return;
diff --git a/engines/harold/bcb/saveload.cpp b/engines/harold/bcb/saveload.cpp
index d3cbbb01599..d67436722cf 100644
--- a/engines/harold/bcb/saveload.cpp
+++ b/engines/harold/bcb/saveload.cpp
@@ -207,6 +207,154 @@ void BCBEngine::resetTransientRuntimeStateAfterLoad() {
_currentDisplayState = 0;
}
+void BCBEngine::enterSystemMenu() {
+ _previousDisplayStateBeforeSystem = _currentDisplayState;
+ _systemSaveLoadSlotActionMode = 0;
+ _sceneChoiceActive = false;
+ _mapControlsActive = false;
+
+ if (_previousDisplayStateBeforeSystem == 1)
+ stopSceneIdleMovie();
+ else if (_previousDisplayStateBeforeSystem == 2)
+ stopMapMoveMovie();
+
+ stopActiveMovie();
+ playUiSound();
+ startMenuBackgroundMusic();
+ showSystemScreen();
+}
+
+bool BCBEngine::showSystemScreen() {
+ Graphics::Surface *system = loadBitmap("16msys.bmp");
+ if (!system)
+ return false;
+
+ applyPendingClockTickDelta();
+ syncSystemWaveVolumeFromMixer();
+ renderClockAndChipToSurface(*system, 0, stateDword(kStateGameClockTicks));
+ const int fillWidth = (_systemWaveVolume * kSystemWaveVolumeIndicatorRect.width()) / 0xffff;
+ if (fillWidth > 0) {
+ const Common::Rect fillRect(kSystemWaveVolumeIndicatorRect.left, kSystemWaveVolumeIndicatorRect.top,
+ kSystemWaveVolumeIndicatorRect.left + fillWidth, kSystemWaveVolumeIndicatorRect.bottom);
+ system->fillRect(fillRect, system->format.RGBToColor(255, 255, 255));
+ }
+ g_system->copyRectToScreen(system->getPixels(), system->pitch, 0, 0, MIN<int>(system->w, 640), MIN<int>(system->h, 480));
+ system->free();
+ delete system;
+
+ _currentDisplayState = 3;
+ drawSystemSubmodeButtons(false);
+ g_system->updateScreen();
+ return true;
+}
+
+void BCBEngine::drawSystemSubmodeButtons(bool present) {
+ const Graphics::Surface *system = getUiBitmap("16msys.bmp");
+ if (!system)
+ return;
+
+ for (uint i = 1; i < ARRAYSIZE(kSystemSubmodeButtonTable); ++i) {
+ const ButtonControlRecord &button = kSystemSubmodeButtonTable[i];
+ Common::Point source = kSystemSubmodeNormalSources[i];
+ if ((_systemSaveLoadSlotActionMode == 1 && i == 1) ||
+ (_systemSaveLoadSlotActionMode == 2 && i == 2))
+ source = kSystemSubmodeSelectedSources[i];
+ if (source.x + button.width <= system->w && source.y + button.height <= system->h)
+ g_system->copyRectToScreen(system->getBasePtr(source.x, source.y), system->pitch,
+ button.x, button.y, button.width, button.height);
+ }
+
+ if (present)
+ g_system->updateScreen();
+}
+
+void BCBEngine::drawSystemSubmodeButton(uint buttonIndex, bool selected, bool present) {
+ if (buttonIndex == 0 || buttonIndex >= ARRAYSIZE(kSystemSubmodeButtonTable))
+ return;
+
+ const Graphics::Surface *system = getUiBitmap("16msys.bmp");
+ if (!system)
+ return;
+
+ const ButtonControlRecord &button = kSystemSubmodeButtonTable[buttonIndex];
+ const Common::Point source = selected ? kSystemSubmodeSelectedSources[buttonIndex] : kSystemSubmodeNormalSources[buttonIndex];
+ if (source.x + button.width <= system->w && source.y + button.height <= system->h)
+ g_system->copyRectToScreen(system->getBasePtr(source.x, source.y), system->pitch, button.x, button.y, button.width, button.height);
+
+ if (present)
+ g_system->updateScreen();
+}
+
+bool BCBEngine::handleSystemClick(const Common::Point &mousePosition) {
+ if (_currentDisplayState == 0 && _titleLoadSlotsActive)
+ return handleSaveLoadSlotClick(mousePosition);
+
+ if (_currentDisplayState != 3)
+ return false;
+
+ const Common::Rect saveRect(kSystemSubmodeButtonTable[1].x, kSystemSubmodeButtonTable[1].y,
+ kSystemSubmodeButtonTable[1].x + kSystemSubmodeButtonTable[1].width,
+ kSystemSubmodeButtonTable[1].y + kSystemSubmodeButtonTable[1].height);
+ const Common::Rect loadRect(kSystemSubmodeButtonTable[2].x, kSystemSubmodeButtonTable[2].y,
+ kSystemSubmodeButtonTable[2].x + kSystemSubmodeButtonTable[2].width,
+ kSystemSubmodeButtonTable[2].y + kSystemSubmodeButtonTable[2].height);
+ const Common::Rect titleRect(kSystemSubmodeButtonTable[3].x, kSystemSubmodeButtonTable[3].y,
+ kSystemSubmodeButtonTable[3].x + kSystemSubmodeButtonTable[3].width,
+ kSystemSubmodeButtonTable[3].y + kSystemSubmodeButtonTable[3].height);
+ const Common::Rect exitRect(kSystemSubmodeButtonTable[4].x, kSystemSubmodeButtonTable[4].y,
+ kSystemSubmodeButtonTable[4].x + kSystemSubmodeButtonTable[4].width,
+ kSystemSubmodeButtonTable[4].y + kSystemSubmodeButtonTable[4].height);
+
+ if (handleSystemWaveVolumeClick(mousePosition))
+ return true;
+
+ if (saveRect.contains(mousePosition)) {
+ _systemSaveLoadSlotActionMode = 1;
+ drawSystemSubmodeButtons(false);
+ drawSystemSaveLoadSlots();
+ return true;
+ }
+
+ if (loadRect.contains(mousePosition)) {
+ _systemSaveLoadSlotActionMode = 2;
+ drawSystemSubmodeButtons(false);
+ drawSystemSaveLoadSlots();
+ return true;
+ }
+
+ if (titleRect.contains(mousePosition)) {
+ drawSystemSubmodeButton(3, true);
+ delayWithEventPolling(80, false);
+ playUiSound();
+ _systemSaveLoadSlotActionMode = 0;
+ showSystemScreen();
+ if (showSystemConfirmationDialog(decodeExecutableTextForDisplay(_returnToTitleConfirmTextSjis), true)) {
+ stopMenuBackgroundMusic();
+ resetGameState();
+ showTitleScreen();
+ startMenuBackgroundMusic();
+ }
+ return true;
+ }
+
+ if (exitRect.contains(mousePosition)) {
+ activateSystemExitButton();
+ return true;
+ }
+
+ if (handleSaveLoadSlotClick(mousePosition))
+ return true;
+
+ return false;
+}
+
+void BCBEngine::activateSystemExitButton() {
+ drawSystemSubmodeButton(4, true);
+ delayWithEventPolling(80, false);
+ playUiSound();
+ returnFromSystemMenuToPreviousState();
+}
+
void BCBEngine::writeContinueState() {
saveGameState(0, formatEnglishSaveDescription(stateDword(kStateRequiredDiscIndex), stateDword(kStateGameClockTicks)), true);
}
@@ -223,4 +371,28 @@ Common::U32String BCBEngine::formatSystemClockTextForDisplay(const Common::Strin
return decodeExecutableTextForDisplay(formatted);
}
+void BCBEngine::drawSystemSaveLoadSlots(bool present) {
+ error("BCB: drawSystemSaveLoadSlots is not implemented");
+}
+
+bool BCBEngine::handleSaveLoadSlotClick(const Common::Point &mousePosition) {
+ error("BCB: handleSaveLoadSlotClick is not implemented");
+}
+
+bool BCBEngine::handleSystemWaveVolumeClick(const Common::Point &mousePosition) {
+ error("BCB: handleSystemWaveVolumeClick is not implemented");
+}
+
+void BCBEngine::returnFromSystemMenuToPreviousState() {
+ error("BCB: returnFromSystemMenuToPreviousState is not implemented");
+}
+
+bool BCBEngine::showSystemConfirmationDialog(const Common::U32String &message, bool showCancelButton) {
+ error("BCB: showSystemConfirmationDialog is not implemented");
+}
+
+void BCBEngine::syncSystemWaveVolumeFromMixer() {
+ error("BCB: syncSystemWaveVolumeFromMixer is not implemented");
+}
+
} // End of namespace BCB
Commit: e70c430195b2179b09527caccb173b61e7c699b9
https://github.com/scummvm/scummvm/commit/e70c430195b2179b09527caccb173b61e7c699b9
Author: Scorp (scorp at mrs.mn)
Date: 2026-10-22T18:57:52+02:00
Commit Message:
HAROLD: BCB: Add persistent volume controls
Changed paths:
engines/harold/bcb/bcb.h
engines/harold/bcb/saveload.cpp
diff --git a/engines/harold/bcb/bcb.h b/engines/harold/bcb/bcb.h
index c4901b515e6..5e73b335f5b 100644
--- a/engines/harold/bcb/bcb.h
+++ b/engines/harold/bcb/bcb.h
@@ -90,7 +90,9 @@ private:
void drawSystemSaveLoadSlots(bool present = true);
void drawSystemSubmodeButtons(bool present = true);
void drawSystemSubmodeButton(uint buttonIndex, bool selected, bool present = true);
+ void drawSystemWaveVolumeIndicator(bool present = true);
void syncSystemWaveVolumeFromMixer();
+ void applySystemWaveVolume();
bool handleSystemWaveVolumeClick(const Common::Point &mousePosition);
bool handleSystemClick(const Common::Point &mousePosition);
void activateSystemExitButton();
diff --git a/engines/harold/bcb/saveload.cpp b/engines/harold/bcb/saveload.cpp
index d67436722cf..2d4a5b45cef 100644
--- a/engines/harold/bcb/saveload.cpp
+++ b/engines/harold/bcb/saveload.cpp
@@ -285,6 +285,104 @@ void BCBEngine::drawSystemSubmodeButton(uint buttonIndex, bool selected, bool pr
g_system->updateScreen();
}
+void BCBEngine::applySystemWaveVolume() {
+ const int mixerVolume = CLIP<int>((_systemWaveVolume * Audio::Mixer::kMaxMixerVolume + 0x7fff) / 0xffff, 0, Audio::Mixer::kMaxMixerVolume);
+ const int configVolume = CLIP<int>((_systemWaveVolume * Audio::Mixer::kMaxChannelVolume + 0x7fff) / 0xffff, 0, Audio::Mixer::kMaxChannelVolume);
+ _mixer->setVolumeForSoundType(Audio::Mixer::kMusicSoundType, mixerVolume);
+ _mixer->setVolumeForSoundType(Audio::Mixer::kSFXSoundType, mixerVolume);
+ ConfMan.setInt("music_volume", configVolume);
+ ConfMan.setInt("sfx_volume", configVolume);
+}
+
+void BCBEngine::syncSystemWaveVolumeFromMixer() {
+ const int configMusicVolume = ConfMan.hasKey("music_volume") ? ConfMan.getInt("music_volume") : Audio::Mixer::kMaxChannelVolume;
+ const int configSfxVolume = ConfMan.hasKey("sfx_volume") ? ConfMan.getInt("sfx_volume") : Audio::Mixer::kMaxChannelVolume;
+ const int configVolume = MAX<int>(configMusicVolume, configSfxVolume);
+ if (configVolume >= Audio::Mixer::kMaxChannelVolume) {
+ _systemWaveVolume = 0xffff;
+ return;
+ }
+
+ _systemWaveVolume = CLIP<int>((configVolume * 0xffff + Audio::Mixer::kMaxChannelVolume / 2) / Audio::Mixer::kMaxChannelVolume, 0, 0xffff);
+}
+
+void BCBEngine::drawSystemWaveVolumeIndicator(bool present) {
+ const Graphics::Surface *system = getUiBitmap("16msys.bmp");
+ if (!system)
+ return;
+
+ if (kSystemWaveVolumeIndicatorRect.right <= system->w && kSystemWaveVolumeIndicatorRect.bottom <= system->h) {
+ g_system->copyRectToScreen(system->getBasePtr(kSystemWaveVolumeIndicatorRect.left, kSystemWaveVolumeIndicatorRect.top),
+ system->pitch, kSystemWaveVolumeIndicatorRect.left, kSystemWaveVolumeIndicatorRect.top,
+ kSystemWaveVolumeIndicatorRect.width(), kSystemWaveVolumeIndicatorRect.height());
+ }
+
+ Graphics::Surface *screen = g_system->lockScreen();
+ if (screen) {
+ const int fillWidth = (_systemWaveVolume * kSystemWaveVolumeIndicatorRect.width()) / 0xffff;
+ if (fillWidth > 0) {
+ const Common::Rect fillRect(kSystemWaveVolumeIndicatorRect.left, kSystemWaveVolumeIndicatorRect.top,
+ kSystemWaveVolumeIndicatorRect.left + fillWidth, kSystemWaveVolumeIndicatorRect.bottom);
+ screen->fillRect(fillRect, screen->format.RGBToColor(255, 255, 255));
+ }
+ g_system->unlockScreen();
+ }
+
+ if (present)
+ g_system->updateScreen();
+}
+
+bool BCBEngine::handleSystemWaveVolumeClick(const Common::Point &mousePosition) {
+ if (kSystemWaveVolumeSeekRect.contains(mousePosition)) {
+ const int seekX = CLIP<int>(mousePosition.x - kSystemWaveVolumeIndicatorRect.left, 0, kSystemWaveVolumeIndicatorRect.width());
+ const int volume = CLIP<int>((seekX * 0xffff) / kSystemWaveVolumeIndicatorRect.width(), 0, 0xffff);
+ if (volume != _systemWaveVolume) {
+ _systemWaveVolume = volume;
+ applySystemWaveVolume();
+ drawSystemWaveVolumeIndicator();
+ }
+ ConfMan.flushToDisk();
+ return true;
+ }
+
+ int delta = 0;
+ if (kSystemWaveVolumeUpRect.contains(mousePosition))
+ delta = 0x500;
+ else if (kSystemWaveVolumeDownRect.contains(mousePosition))
+ delta = -0x500;
+ else
+ return false;
+
+ bool done = false;
+ while (!done && !shouldQuit()) {
+ const int volume = CLIP<int>(_systemWaveVolume + delta, 0, 0xffff);
+ if (volume != _systemWaveVolume) {
+ _systemWaveVolume = volume;
+ applySystemWaveVolume();
+ drawSystemWaveVolumeIndicator();
+ }
+
+ Common::Event event;
+ while (g_system->getEventManager()->pollEvent(event)) {
+ if (event.type == Common::EVENT_QUIT) {
+ quitGame();
+ done = true;
+ break;
+ }
+ if (event.type == Common::EVENT_MOUSEMOVE) {
+ _mousePosition = event.mouse;
+ } else if (event.type == Common::EVENT_LBUTTONUP) {
+ _mousePosition = event.mouse;
+ done = true;
+ }
+ }
+ serviceCursorRefresh();
+ g_system->delayMillis(1);
+ }
+ ConfMan.flushToDisk();
+ return true;
+}
+
bool BCBEngine::handleSystemClick(const Common::Point &mousePosition) {
if (_currentDisplayState == 0 && _titleLoadSlotsActive)
return handleSaveLoadSlotClick(mousePosition);
@@ -379,10 +477,6 @@ bool BCBEngine::handleSaveLoadSlotClick(const Common::Point &mousePosition) {
error("BCB: handleSaveLoadSlotClick is not implemented");
}
-bool BCBEngine::handleSystemWaveVolumeClick(const Common::Point &mousePosition) {
- error("BCB: handleSystemWaveVolumeClick is not implemented");
-}
-
void BCBEngine::returnFromSystemMenuToPreviousState() {
error("BCB: returnFromSystemMenuToPreviousState is not implemented");
}
@@ -391,8 +485,4 @@ bool BCBEngine::showSystemConfirmationDialog(const Common::U32String &message, b
error("BCB: showSystemConfirmationDialog is not implemented");
}
-void BCBEngine::syncSystemWaveVolumeFromMixer() {
- error("BCB: syncSystemWaveVolumeFromMixer is not implemented");
-}
-
} // End of namespace BCB
Commit: e317656c71531cbf45d82f7bfa7a109635e00e6d
https://github.com/scummvm/scummvm/commit/e317656c71531cbf45d82f7bfa7a109635e00e6d
Author: Scorp (scorp at mrs.mn)
Date: 2026-10-22T18:57:52+02:00
Commit Message:
HAROLD: BCB: Add save slot lists and confirmation dialogs
Changed paths:
engines/harold/bcb/bcb.h
engines/harold/bcb/input.cpp
engines/harold/bcb/saveload.cpp
diff --git a/engines/harold/bcb/bcb.h b/engines/harold/bcb/bcb.h
index 5e73b335f5b..9e3c0a7b2aa 100644
--- a/engines/harold/bcb/bcb.h
+++ b/engines/harold/bcb/bcb.h
@@ -50,6 +50,21 @@ enum {
kBCBDebugGeneral = 1
};
+enum BCBAction {
+ kBCBActionInteract,
+ kBCBActionEscape,
+ kBCBActionSkip,
+ kBCBActionChoice1,
+ kBCBActionChoice2,
+ kBCBActionChoice3,
+ kBCBActionChoice4,
+ kBCBActionChoice5,
+ kBCBActionChoice6,
+ kBCBActionChoice7,
+ kBCBActionChoice8,
+ kBCBActionChoice9
+};
+
class Debugger;
class BCBEngine : public Engine {
@@ -96,6 +111,8 @@ private:
bool handleSystemWaveVolumeClick(const Common::Point &mousePosition);
bool handleSystemClick(const Common::Point &mousePosition);
void activateSystemExitButton();
+ bool readSaveSlotHeader(int slot, int &savedDay, int &savedClockTicks);
+ bool isSaveSlotOccupied(int slot);
void writeContinueState();
bool showSystemConfirmationDialog(const Common::U32String &message, bool showCancelButton);
void returnFromSystemMenuToPreviousState();
diff --git a/engines/harold/bcb/input.cpp b/engines/harold/bcb/input.cpp
index 4d63eb404dc..cc03cc3296f 100644
--- a/engines/harold/bcb/input.cpp
+++ b/engines/harold/bcb/input.cpp
@@ -142,6 +142,23 @@ void BCBEngine::handleLeftButtonDown(const Common::Point &mousePosition) {
if (loadGameState(0).getCode() != Common::kNoError)
_titleButtonsActive = true;
break;
+ case 53:
+ _systemButtonsVisible = true;
+ _systemSaveLoadSlotActionMode = 2;
+ _titleLoadSlotsActive = true;
+ drawTitleButton(i, false);
+ drawSystemSaveLoadSlots();
+ break;
+ case 54:
+ if (_titleLoadSlotsActive) {
+ _titleLoadSlotsActive = false;
+ _systemSaveLoadSlotActionMode = 0;
+ restoreTitleScreenRegion(Common::Rect(0, 0, 640, 480));
+ }
+ drawTitleButton(i, false);
+ if (showSystemConfirmationDialog(_titleExitConfirmText, true))
+ quitGame();
+ break;
default:
drawTitleButton(i, false);
break;
diff --git a/engines/harold/bcb/saveload.cpp b/engines/harold/bcb/saveload.cpp
index 2d4a5b45cef..75546246ea1 100644
--- a/engines/harold/bcb/saveload.cpp
+++ b/engines/harold/bcb/saveload.cpp
@@ -383,6 +383,75 @@ bool BCBEngine::handleSystemWaveVolumeClick(const Common::Point &mousePosition)
return true;
}
+bool BCBEngine::readSaveSlotHeader(int slot, int &savedDay, int &savedClockTicks) {
+ Common::InSaveFile *saveFile = _saveFileMan->openForLoading(getSaveStateName(slot));
+ if (!saveFile)
+ return false;
+
+ bool validSave = false;
+ const uint32 magic = saveFile->readUint32BE();
+ if (magic == kSavegameMagic) {
+ const uint16 version = saveFile->readUint16LE();
+ const uint16 stateByteSize = saveFile->readUint16LE();
+ validSave = version == kSavegameVersion && stateByteSize == kNativeGameStateByteSize;
+ } else if (saveFile->size() == kNativeGameStateByteSize) {
+ saveFile->seek(0);
+ validSave = true;
+ }
+
+ if (!validSave) {
+ delete saveFile;
+ return false;
+ }
+
+ saveFile->seek(magic == kSavegameMagic ? 8 + kStateRequiredDiscIndex : kStateRequiredDiscIndex);
+ savedDay = saveFile->readUint32LE();
+ savedClockTicks = saveFile->readUint32LE();
+ validSave = !saveFile->err();
+ delete saveFile;
+ return validSave;
+}
+
+bool BCBEngine::isSaveSlotOccupied(int slot) {
+ int savedDay = 0;
+ int savedClockTicks = 0;
+ return readSaveSlotHeader(slot, savedDay, savedClockTicks);
+}
+
+void BCBEngine::drawSystemSaveLoadSlots(bool present) {
+ const Graphics::Surface *system = getUiBitmap("16msys.bmp");
+ if (!system)
+ return;
+
+ for (int slot = 1; slot <= kManualSaveSlotCount; ++slot) {
+ const ButtonControlRecord &button = kSystemButtonTable[slot];
+ const int sourceY = 480 + (slot - 1) * button.height;
+ if (sourceY + button.height <= system->h)
+ g_system->copyRectToScreen(system->getBasePtr(0, sourceY), system->pitch, button.x, button.y, button.width, button.height);
+
+ int savedDay = 0;
+ int savedClockTicks = 0;
+ Common::U32String slotText;
+ if (readSaveSlotHeader(slot, savedDay, savedClockTicks)) {
+ slotText = formatSystemClockTextForDisplay(_saveSlotOccupiedTextFormatSjis, slot, savedDay, savedClockTicks);
+ } else {
+ slotText = formatSystemTextForDisplay(_saveSlotEmptyTextFormatSjis, slot);
+ }
+
+ Graphics::Surface *screen = g_system->lockScreen();
+ if (screen) {
+ const uint32 textColor = screen->format.RGBToColor(0, 0, 0);
+ const Graphics::Font *font = _ownerDrawUiTextFont ? _ownerDrawUiTextFont : FontMan.getFontByUsage(Graphics::FontManager::kGUIFont);
+ const int textY = button.y + (button.height - font->getFontHeight()) / 2 - (_gameDescription->desc.language == Common::KO_KOR ? 1 : 0);
+ font->drawString(screen, slotText, button.x, textY, button.width, textColor);
+ g_system->unlockScreen();
+ }
+ }
+
+ if (present)
+ g_system->updateScreen();
+}
+
bool BCBEngine::handleSystemClick(const Common::Point &mousePosition) {
if (_currentDisplayState == 0 && _titleLoadSlotsActive)
return handleSaveLoadSlotClick(mousePosition);
@@ -453,10 +522,172 @@ void BCBEngine::activateSystemExitButton() {
returnFromSystemMenuToPreviousState();
}
+bool BCBEngine::handleSaveLoadSlotClick(const Common::Point &mousePosition) {
+ if (_systemSaveLoadSlotActionMode != 1 && _systemSaveLoadSlotActionMode != 2)
+ return false;
+
+ for (int slot = 1; slot <= kManualSaveSlotCount; ++slot) {
+ const ButtonControlRecord &button = kSystemButtonTable[slot];
+ const Common::Rect slotRect(button.x, button.y, button.x + button.width, button.y + button.height);
+ if (!slotRect.contains(mousePosition))
+ continue;
+
+ if (_systemSaveLoadSlotActionMode == 1) {
+ int savedDay = 0;
+ int savedClockTicks = 0;
+ Common::U32String message;
+ if (readSaveSlotHeader(slot, savedDay, savedClockTicks)) {
+ message = formatSystemClockTextForDisplay(_saveSlotOverwriteConfirmTextFormatSjis, slot, savedDay, savedClockTicks);
+ } else {
+ message = formatSystemTextForDisplay(_saveSlotEmptyConfirmTextFormatSjis, slot);
+ }
+
+ if (showSystemConfirmationDialog(message, true)) {
+ saveGameState(slot, formatEnglishSaveDescription(stateDword(kStateRequiredDiscIndex), stateDword(kStateGameClockTicks)));
+ drawSystemSaveLoadSlots();
+ }
+ return true;
+ }
+
+ if (_systemSaveLoadSlotActionMode == 2) {
+ int savedDay = 0;
+ int savedClockTicks = 0;
+ if (!readSaveSlotHeader(slot, savedDay, savedClockTicks)) {
+ showSystemConfirmationDialog(decodeExecutableTextForDisplay(_noSaveDataMessageSjis), false);
+ return true;
+ }
+ Common::U32String message = formatSystemClockTextForDisplay(_loadSlotConfirmTextFormatSjis, slot, savedDay, savedClockTicks);
+ if (showSystemConfirmationDialog(message, true)) {
+ _titleLoadSlotsActive = false;
+ loadGameState(slot);
+ }
+ return true;
+ }
+
+ return true;
+ }
+
+ return false;
+}
+
void BCBEngine::writeContinueState() {
saveGameState(0, formatEnglishSaveDescription(stateDword(kStateRequiredDiscIndex), stateDword(kStateGameClockTicks)), true);
}
+bool BCBEngine::showSystemConfirmationDialog(const Common::U32String &message, bool showCancelButton) {
+ const Graphics::Surface *chip = getUiBitmap("16mchip.bmp");
+ if (!chip)
+ return false;
+
+ const Common::Rect dialogRect(144, 192, 496, 288);
+ const Common::Rect okRect(showCancelButton ? 344 : 420, 254, showCancelButton ? 408 : 484, 278);
+ const Common::Rect cancelRect(420, 254, 484, 278);
+ const int dialogSourceY = 0x50;
+ const int buttonSourceY = 0x32;
+ Graphics::Surface dialogBacking;
+ dialogBacking.create(dialogRect.width(), dialogRect.height(), g_system->getScreenFormat());
+ Graphics::Surface *screenBacking = g_system->lockScreen();
+ if (screenBacking) {
+ dialogBacking.copyRectToSurface(*screenBacking, 0, 0, dialogRect);
+ g_system->unlockScreen();
+ }
+
+ auto drawDialogButton = [&](const Common::Rect &rect, int sourceX, bool present) {
+ if (sourceX + rect.width() <= chip->w && chip->h >= buttonSourceY + rect.height())
+ g_system->copyRectToScreen(chip->getBasePtr(sourceX, buttonSourceY), chip->pitch,
+ rect.left, rect.top, rect.width(), rect.height());
+ if (present)
+ g_system->updateScreen();
+ };
+
+ if (dialogRect.width() <= chip->w && chip->h >= dialogSourceY + dialogRect.height())
+ g_system->copyRectToScreen(chip->getBasePtr(0, dialogSourceY), chip->pitch, dialogRect.left, dialogRect.top, dialogRect.width(), dialogRect.height());
+
+ Graphics::Surface *screen = g_system->lockScreen();
+ if (screen) {
+ const Graphics::Font *font = _ownerDrawUiTextFont ? _ownerDrawUiTextFont : FontMan.getFontByUsage(Graphics::FontManager::kGUIFont);
+ const uint32 shadowColor = screen->format.RGBToColor(0, 0, 0);
+ const uint32 textColor = screen->format.RGBToColor(255, 255, 255);
+ const int textYOffset = _gameDescription->desc.language == Common::KO_KOR ? -1 : 0;
+ font->drawString(screen, message, dialogRect.left + 10, dialogRect.top + 20 + textYOffset, 340, shadowColor);
+ font->drawString(screen, message, dialogRect.left + 11, dialogRect.top + 21 + textYOffset, 339, textColor);
+ g_system->unlockScreen();
+ }
+
+ drawDialogButton(okRect, 0x17, false);
+ if (showCancelButton)
+ drawDialogButton(cancelRect, 0x97, false);
+ g_system->updateScreen();
+
+ bool result = false;
+ bool done = false;
+ auto handleDialogClick = [&]() {
+ if (okRect.contains(_mousePosition)) {
+ drawDialogButton(okRect, 0x57, true);
+ delayWithEventPolling(80, false);
+ result = true;
+ done = true;
+ } else if (showCancelButton && cancelRect.contains(_mousePosition)) {
+ drawDialogButton(cancelRect, 0xd7, true);
+ delayWithEventPolling(80, false);
+ done = true;
+ }
+ };
+ while (!done && !shouldQuit()) {
+ Common::Event event;
+ while (g_system->getEventManager()->pollEvent(event)) {
+ if (event.type == Common::EVENT_QUIT) {
+ quitGame();
+ done = true;
+ break;
+ }
+ if (event.type == Common::EVENT_MOUSEMOVE) {
+ _mousePosition = event.mouse;
+ } else if (event.type == Common::EVENT_LBUTTONDOWN) {
+ _mousePosition = event.mouse;
+ handleDialogClick();
+ } else if (event.type == Common::EVENT_KEYDOWN) {
+ if (event.kbd.keycode == Common::KEYCODE_RETURN) {
+ result = true;
+ done = true;
+ } else if (event.kbd.keycode == Common::KEYCODE_ESCAPE) {
+ done = true;
+ }
+ } else if (event.type == Common::EVENT_CUSTOM_ENGINE_ACTION_START) {
+ if (event.customType == kBCBActionEscape) {
+ done = true;
+ } else if (event.customType == kBCBActionInteract) {
+ handleDialogClick();
+ }
+ }
+ }
+ serviceCursorRefresh();
+ g_system->delayMillis(10);
+ }
+
+ g_system->copyRectToScreen(dialogBacking.getPixels(), dialogBacking.pitch, dialogRect.left, dialogRect.top,
+ dialogBacking.w, dialogBacking.h);
+ dialogBacking.free();
+ g_system->updateScreen();
+ return result;
+}
+
+void BCBEngine::returnFromSystemMenuToPreviousState() {
+ stopMenuBackgroundMusic();
+ _systemSaveLoadSlotActionMode = 0;
+
+ if (_previousDisplayStateBeforeSystem == 2) {
+ processGameStateDispatcher(false);
+ return;
+ } else {
+ showMainSceneScreen();
+ if (stateDword(kStateSceneChoiceUiSetToShowId) != 0)
+ showActiveSceneChoiceControls();
+ }
+
+ processGameStateDispatcher(false);
+}
+
Common::U32String BCBEngine::formatSystemTextForDisplay(const Common::String &originalFormat, int slot) const {
const Common::String formatted = Common::String::format(originalFormat.c_str(), slot);
return decodeExecutableTextForDisplay(formatted);
@@ -469,20 +700,4 @@ Common::U32String BCBEngine::formatSystemClockTextForDisplay(const Common::Strin
return decodeExecutableTextForDisplay(formatted);
}
-void BCBEngine::drawSystemSaveLoadSlots(bool present) {
- error("BCB: drawSystemSaveLoadSlots is not implemented");
-}
-
-bool BCBEngine::handleSaveLoadSlotClick(const Common::Point &mousePosition) {
- error("BCB: handleSaveLoadSlotClick is not implemented");
-}
-
-void BCBEngine::returnFromSystemMenuToPreviousState() {
- error("BCB: returnFromSystemMenuToPreviousState is not implemented");
-}
-
-bool BCBEngine::showSystemConfirmationDialog(const Common::U32String &message, bool showCancelButton) {
- error("BCB: showSystemConfirmationDialog is not implemented");
-}
-
} // End of namespace BCB
Commit: f38062681fd05f58f4fc6ab1df2abe4f5af85216
https://github.com/scummvm/scummvm/commit/f38062681fd05f58f4fc6ab1df2abe4f5af85216
Author: Scorp (scorp at mrs.mn)
Date: 2026-10-22T18:57:52+02:00
Commit Message:
HAROLD: BCB: Connect game input and screen timers
Changed paths:
engines/harold/bcb/bcb.h
engines/harold/bcb/input.cpp
diff --git a/engines/harold/bcb/bcb.h b/engines/harold/bcb/bcb.h
index 9e3c0a7b2aa..4244c4ba159 100644
--- a/engines/harold/bcb/bcb.h
+++ b/engines/harold/bcb/bcb.h
@@ -211,6 +211,9 @@ private:
void blitScreenWithLineReveal(const Graphics::Surface &surface, bool presentFinalFrame = true);
void drawTitleButton(uint buttonIndex, bool pressed);
void handleLeftButtonDown(const Common::Point &mousePosition);
+ void handleKeymapperAction(Common::CustomEventType action);
+ bool handleEscapeAction();
+ void handleSkipKey();
void handleSceneChoiceKeyboardSelection(int choiceNumber);
bool pollEvents();
bool pollEvents(bool handleClicks);
diff --git a/engines/harold/bcb/input.cpp b/engines/harold/bcb/input.cpp
index cc03cc3296f..187a45a2d97 100644
--- a/engines/harold/bcb/input.cpp
+++ b/engines/harold/bcb/input.cpp
@@ -188,6 +188,29 @@ bool BCBEngine::pollEvents(bool handleClicks) {
if (handleClicks && !_suppressClickHandling)
handleLeftButtonDown(_mousePosition);
break;
+ case Common::EVENT_KEYDOWN:
+ if (handleClicks && !_suppressClickHandling) {
+ if (event.kbd.keycode == Common::KEYCODE_ESCAPE && handleEscapeAction()) {
+ break;
+ } else if (handleComputerKey(event.kbd.keycode)) {
+ break;
+ } else if (event.kbd.keycode == Common::KEYCODE_SPACE) {
+ handleSkipKey();
+ } else if (_sceneChoiceActive &&
+ event.kbd.keycode >= Common::KEYCODE_1 &&
+ event.kbd.keycode <= Common::KEYCODE_9) {
+ handleSceneChoiceKeyboardSelection(event.kbd.keycode - Common::KEYCODE_1 + 1);
+ } else if (_sceneChoiceActive &&
+ event.kbd.keycode >= Common::KEYCODE_KP1 &&
+ event.kbd.keycode <= Common::KEYCODE_KP9) {
+ handleSceneChoiceKeyboardSelection(event.kbd.keycode - Common::KEYCODE_KP1 + 1);
+ }
+ }
+ break;
+ case Common::EVENT_CUSTOM_ENGINE_ACTION_START:
+ if (handleClicks && !_suppressClickHandling)
+ handleKeymapperAction(event.customType);
+ break;
default:
break;
}
@@ -196,6 +219,47 @@ bool BCBEngine::pollEvents(bool handleClicks) {
return !shouldQuit();
}
+void BCBEngine::handleKeymapperAction(Common::CustomEventType action) {
+ if (action == kBCBActionInteract) {
+ handleLeftButtonDown(_mousePosition);
+ } else if (action == kBCBActionEscape) {
+ handleEscapeAction();
+ } else if (action == kBCBActionSkip) {
+ handleSkipKey();
+ } else if (action >= kBCBActionChoice1 && action <= kBCBActionChoice9) {
+ handleSceneChoiceKeyboardSelection(action - kBCBActionChoice1 + 1);
+ }
+}
+
+bool BCBEngine::handleEscapeAction() {
+ if (_currentDisplayState == 3) {
+ activateSystemExitButton();
+ return true;
+ }
+
+ return handleComputerKey(Common::KEYCODE_ESCAPE);
+}
+
+void BCBEngine::handleSkipKey() {
+ if (_activeMovieVideo) {
+ if (shouldDrawMovieControlStrip())
+ drawMovieControlStrip(kMovieControlStripSkipPressedSourceX);
+ delayWithEventPolling(160, false);
+ stopActiveMovieAndDispatch();
+ return;
+ }
+
+ if (_currentDisplayState == 10) {
+ transitionFromLogoToTitle();
+ return;
+ }
+
+ if (_currentDisplayState == 6) {
+ playUiSound();
+ completePendingStillImageScreen();
+ }
+}
+
void BCBEngine::serviceCursorRefresh(bool screenUpdated) {
const uint32 now = g_system->getMillis();
Commit: d88edb28e2c7fd872aa4f1e576aff363ca35cc78
https://github.com/scummvm/scummvm/commit/d88edb28e2c7fd872aa4f1e576aff363ca35cc78
Author: Scorp (scorp at mrs.mn)
Date: 2026-10-22T18:57:52+02:00
Commit Message:
HAROLD: BCB: Register engine keymaps and debugger commands
Changed paths:
A engines/harold/bcb/debugger.cpp
A engines/harold/bcb/debugger.h
engines/harold/bcb/bcb.cpp
engines/harold/bcb/bcb.h
engines/harold/metaengine.cpp
engines/harold/module.mk
diff --git a/engines/harold/bcb/bcb.cpp b/engines/harold/bcb/bcb.cpp
index b1086851277..fb6adcee52a 100644
--- a/engines/harold/bcb/bcb.cpp
+++ b/engines/harold/bcb/bcb.cpp
@@ -29,6 +29,7 @@
#include "graphics/fonts/ttf.h"
#include "graphics/sjis.h"
#include "graphics/surface.h"
+#include "harold/bcb/debugger.h"
#include "harold/bcb/script.h"
#include "harold/bcb/static_data.h"
#include "video/avi_decoder.h"
@@ -108,6 +109,8 @@ BCBEngine::~BCBEngine() {
Common::Error BCBEngine::run() {
const Graphics::PixelFormat screenFormat = Graphics::PixelFormat(4, 8, 8, 8, 8, 16, 8, 0, 24);
+ setDebugger(new Debugger(this));
+
if (!initializeSearchPaths())
return Common::kPathDoesNotExist;
diff --git a/engines/harold/bcb/bcb.h b/engines/harold/bcb/bcb.h
index 4244c4ba159..962cb43960b 100644
--- a/engines/harold/bcb/bcb.h
+++ b/engines/harold/bcb/bcb.h
@@ -215,6 +215,9 @@ private:
bool handleEscapeAction();
void handleSkipKey();
void handleSceneChoiceKeyboardSelection(int choiceNumber);
+ bool parseCheatNumber(const Common::String &text, uint32 &value) const;
+ void addCheatTime(int minutes);
+ void jumpToCheatSequence(uint sequenceId);
bool pollEvents();
bool pollEvents(bool handleClicks);
void serviceCursorRefresh(bool screenUpdated = false);
diff --git a/engines/harold/bcb/debugger.cpp b/engines/harold/bcb/debugger.cpp
new file mode 100644
index 00000000000..ffcf8fe89eb
--- /dev/null
+++ b/engines/harold/bcb/debugger.cpp
@@ -0,0 +1,303 @@
+/* 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/str.h"
+
+#include "harold/bcb/bcb.h"
+#include "harold/bcb/debugger.h"
+#include "harold/bcb/script.h"
+
+namespace BCB {
+
+Debugger::Debugger(BCBEngine *vm) : GUI::Debugger(), _vm(vm) {
+ registerCmd("time", WRAP_METHOD(Debugger, cmdTime));
+ registerCmd("add_time", WRAP_METHOD(Debugger, cmdAddTime));
+ registerCmd("seq", WRAP_METHOD(Debugger, cmdSequence));
+ registerCmd("state", WRAP_METHOD(Debugger, cmdState));
+ registerCmd("flag", WRAP_METHOD(Debugger, cmdFlag));
+ registerCmd("choice", WRAP_METHOD(Debugger, cmdChoice));
+ registerCmd("gameover", WRAP_METHOD(Debugger, cmdGameOver));
+ registerCmd("clock_chip", WRAP_METHOD(Debugger, cmdClockChip));
+ registerCmd("night", WRAP_METHOD(Debugger, cmdNight));
+}
+
+bool Debugger::parseNumber(const char *text, uint32 &value) const {
+ return _vm->parseCheatNumber(Common::String(text), value);
+}
+
+Common::String Debugger::formatGameClock(uint32 gameClockTicks) const {
+ const uint clockTicks = gameClockTicks % 1440;
+ const uint dayOfMonth = 10 + CLIP<uint32>(_vm->stateDword(kStateRequiredDiscIndex), 0, 4);
+ const uint hour = clockTicks / 60;
+ const uint minute = clockTicks % 60;
+
+ return Common::String::format("11/%02u %02u:%02u", dayOfMonth, hour, minute);
+}
+
+bool Debugger::cmdTime(int argc, const char **argv) {
+ if (argc > 2) {
+ debugPrintf("Format: time [ticks]\n");
+ debugPrintf("Current: %s, ticks=%u / 0x%04x\n",
+ formatGameClock(_vm->stateDword(kStateGameClockTicks)).c_str(),
+ _vm->stateDword(kStateGameClockTicks),
+ _vm->stateDword(kStateGameClockTicks));
+ return true;
+ }
+
+ if (argc == 2) {
+ uint32 ticks = 0;
+ if (!parseNumber(argv[1], ticks)) {
+ debugPrintf("Invalid tick value: %s\n", argv[1]);
+ return true;
+ }
+ _vm->stateDword(kStateGameClockTicks) = ticks;
+ _vm->notifyGameClockTicksChanged();
+ if (_vm->_currentDisplayState == 1 || _vm->_currentDisplayState == 2 ||
+ _vm->_currentDisplayState == 7 || _vm->_currentDisplayState == 8)
+ _vm->drawClockAndChipIndicator();
+ }
+
+ debugPrintf("Current: %s, ticks=%u / 0x%04x\n",
+ formatGameClock(_vm->stateDword(kStateGameClockTicks)).c_str(),
+ _vm->stateDword(kStateGameClockTicks),
+ _vm->stateDword(kStateGameClockTicks));
+ return true;
+}
+
+bool Debugger::cmdAddTime(int argc, const char **argv) {
+ if (argc != 2) {
+ debugPrintf("Format: add_time <minutes>\n");
+ return true;
+ }
+
+ uint32 minutes = 0;
+ if (!parseNumber(argv[1], minutes)) {
+ debugPrintf("Invalid minute value: %s\n", argv[1]);
+ return true;
+ }
+
+ _vm->addCheatTime((int)minutes);
+ debugPrintf("Current: %s, ticks=%u / 0x%04x\n",
+ formatGameClock(_vm->stateDword(kStateGameClockTicks)).c_str(),
+ _vm->stateDword(kStateGameClockTicks),
+ _vm->stateDword(kStateGameClockTicks));
+ return true;
+}
+
+bool Debugger::cmdSequence(int argc, const char **argv) {
+ if (argc != 2) {
+ debugPrintf("Format: seq <sequence-id>\n");
+ return true;
+ }
+
+ uint32 sequenceId = 0;
+ if (!parseNumber(argv[1], sequenceId)) {
+ debugPrintf("Invalid sequence id: %s\n", argv[1]);
+ return true;
+ }
+
+ _vm->jumpToCheatSequence(sequenceId);
+ return false;
+}
+
+bool Debugger::cmdState(int argc, const char **argv) {
+ if (argc != 2 && argc != 3) {
+ debugPrintf("Format: state <byte-offset> [value]\n");
+ return true;
+ }
+
+ uint32 byteOffset = 0;
+ if (!parseNumber(argv[1], byteOffset)) {
+ debugPrintf("Invalid state offset: %s\n", argv[1]);
+ return true;
+ }
+
+ if (argc == 3) {
+ uint32 value = 0;
+ if (!parseNumber(argv[2], value)) {
+ debugPrintf("Invalid value: %s\n", argv[2]);
+ return true;
+ }
+ _vm->stateDword(byteOffset) = value;
+ }
+
+ debugPrintf("state[0x%04x] = %u / 0x%08x\n", byteOffset, _vm->stateDword(byteOffset), _vm->stateDword(byteOffset));
+ return true;
+}
+
+bool Debugger::cmdFlag(int argc, const char **argv) {
+ if (argc != 2 && argc != 3) {
+ debugPrintf("Format: flag <secondary-word-index> [value]\n");
+ return true;
+ }
+
+ uint32 flagIndex = 0;
+ if (!parseNumber(argv[1], flagIndex)) {
+ debugPrintf("Invalid flag index: %s\n", argv[1]);
+ return true;
+ }
+
+ const uint32 byteOffset = 0x98 + flagIndex * 4;
+ if (argc == 3) {
+ uint32 value = 0;
+ if (!parseNumber(argv[2], value)) {
+ debugPrintf("Invalid value: %s\n", argv[2]);
+ return true;
+ }
+ _vm->stateDword(byteOffset) = value;
+ }
+
+ debugPrintf("secondary[%u] state[0x%04x] = %u / 0x%08x\n",
+ flagIndex, byteOffset, _vm->stateDword(byteOffset), _vm->stateDword(byteOffset));
+ return true;
+}
+
+bool Debugger::cmdChoice(int argc, const char **argv) {
+ if (argc != 1 && argc != 2) {
+ debugPrintf("Format: choice [value]\n");
+ return true;
+ }
+
+ if (argc == 2) {
+ uint32 value = 0;
+ if (!parseNumber(argv[1], value)) {
+ debugPrintf("Invalid choice value: %s\n", argv[1]);
+ return true;
+ }
+ _vm->stateDword(kStateSelectedSceneChoiceValue) = value;
+ }
+
+ debugPrintf("selectedSceneChoiceValue = %u / 0x%02x\n",
+ _vm->stateDword(kStateSelectedSceneChoiceValue),
+ _vm->stateDword(kStateSelectedSceneChoiceValue));
+ return true;
+}
+
+bool Debugger::cmdGameOver(int argc, const char **argv) {
+ if (argc != 2) {
+ debugPrintf("Format: gameover <1..9>\n");
+ return true;
+ }
+
+ uint32 messageIndex = 0;
+ if (!parseNumber(argv[1], messageIndex) || messageIndex < 1 || messageIndex > 9) {
+ debugPrintf("Invalid game-over message index: %s\n", argv[1]);
+ return true;
+ }
+
+ _vm->stateDword(kStateSelectedSceneChoiceValue) = messageIndex;
+ if (!_vm->dispatchGameOverScreen())
+ debugPrintf("Could not display game-over screen %u\n", messageIndex);
+ return false;
+}
+
+bool Debugger::cmdClockChip(int argc, const char **argv) {
+ if (argc != 1 && argc != 2) {
+ debugPrintf("Format: clock_chip [0..4]\n");
+ return true;
+ }
+
+ if (argc == 2) {
+ uint32 chipIndex = 0;
+ if (!parseNumber(argv[1], chipIndex) || chipIndex > 4) {
+ debugPrintf("Invalid clock/chip index: %s\n", argv[1]);
+ return true;
+ }
+ _vm->stateDword(kStateRequiredDiscIndex) = chipIndex;
+ if (_vm->_currentDisplayState == 1 || _vm->_currentDisplayState == 2 ||
+ _vm->_currentDisplayState == 7 || _vm->_currentDisplayState == 8)
+ _vm->drawClockAndChipIndicator();
+ }
+
+ debugPrintf("clock/chip state index = %u (state[0x%04x])\n",
+ _vm->stateDword(kStateRequiredDiscIndex), kStateRequiredDiscIndex);
+ return true;
+}
+
+bool Debugger::cmdNight(int argc, const char **argv) {
+ if (argc != 1 && argc != 2) {
+ debugPrintf("Format: night [0|1]\n");
+ return true;
+ }
+
+ if (argc == 2) {
+ uint32 value = 0;
+ if (!parseNumber(argv[1], value)) {
+ debugPrintf("Invalid night flag: %s\n", argv[1]);
+ return true;
+ }
+ _vm->stateDword(kStateMainSceneNightFlag) = value != 0 ? 1 : 0;
+ }
+
+ debugPrintf("mainSceneNightFlag = %u\n", _vm->stateDword(kStateMainSceneNightFlag));
+ return true;
+}
+
+bool BCBEngine::parseCheatNumber(const Common::String &text, uint32 &value) const {
+ if (text.empty())
+ return false;
+
+ const char *cursor = text.c_str();
+ int base = 10;
+ if (text.size() > 2 && text[0] == '0' && (text[1] == 'x' || text[1] == 'X')) {
+ cursor += 2;
+ base = 16;
+ }
+
+ char *end = nullptr;
+ const unsigned long parsedValue = strtoul(cursor, &end, base);
+ if (!end || *end != 0)
+ return false;
+
+ value = (uint32)parsedValue;
+ return true;
+}
+
+void BCBEngine::addCheatTime(int minutes) {
+ stateDword(kStateGameClockTicks) += minutes;
+ notifyGameClockTicksChanged();
+
+ if (_currentDisplayState == 1 || _currentDisplayState == 2 || _currentDisplayState == 7 || _currentDisplayState == 8)
+ drawClockAndChipIndicator();
+}
+
+void BCBEngine::jumpToCheatSequence(uint sequenceId) {
+ if (sequenceId == 0 || sequenceId * 2 > _scriptData.size()) {
+ warning("BCBEngine::jumpToCheatSequence(): Invalid sequence id %u", sequenceId);
+ return;
+ }
+
+ stopActiveMovie();
+ stopSceneIdleMovie();
+ stopMapMoveMovie();
+ restoreActiveCaptionBackingRect();
+ freeSceneChoiceBacking();
+ _sceneChoiceActive = false;
+ _mapControlsActive = false;
+ _scriptStopFlag = false;
+ stateDword(kStateMovieToPlayId) = 0;
+ stateDword(kStateStillImageToShowId) = 0;
+ stateDword(kStateDispatcherControlCode) = 0;
+ stateDword(kStateCurrentScriptSequenceId) = sequenceId;
+ processGameStateDispatcher(true);
+}
+
+} // End of namespace BCB
diff --git a/engines/harold/bcb/debugger.h b/engines/harold/bcb/debugger.h
new file mode 100644
index 00000000000..a8181cff696
--- /dev/null
+++ b/engines/harold/bcb/debugger.h
@@ -0,0 +1,54 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#ifndef BCB_DEBUGGER_H
+#define BCB_DEBUGGER_H
+
+#include "gui/debugger.h"
+
+namespace BCB {
+
+class BCBEngine;
+
+class Debugger : public GUI::Debugger {
+public:
+ Debugger(BCBEngine *vm);
+
+private:
+ BCBEngine *_vm;
+
+ bool parseNumber(const char *text, uint32 &value) const;
+ Common::String formatGameClock(uint32 gameClockTicks) const;
+
+ bool cmdTime(int argc, const char **argv);
+ bool cmdAddTime(int argc, const char **argv);
+ bool cmdSequence(int argc, const char **argv);
+ bool cmdState(int argc, const char **argv);
+ bool cmdFlag(int argc, const char **argv);
+ bool cmdChoice(int argc, const char **argv);
+ bool cmdGameOver(int argc, const char **argv);
+ bool cmdClockChip(int argc, const char **argv);
+ bool cmdNight(int argc, const char **argv);
+};
+
+} // End of namespace BCB
+
+#endif // BCB_DEBUGGER_H
diff --git a/engines/harold/metaengine.cpp b/engines/harold/metaengine.cpp
index b2a5e28861c..1b5ffc18146 100644
--- a/engines/harold/metaengine.cpp
+++ b/engines/harold/metaengine.cpp
@@ -38,9 +38,63 @@ public:
}
int getMaximumSaveSlot() const override { return 8; }
+ Common::KeymapArray initKeymaps(const char *target) const override;
Common::Error createInstance(OSystem *syst, Engine **engine, const BCB::HaroldGameDescription *gd) const override;
};
+Common::KeymapArray HaroldMetaEngine::initKeymaps(const char *target) const {
+ using namespace Common;
+ using namespace BCB;
+
+ Keymap *engineKeyMap = new Keymap(Keymap::kKeymapTypeGame, "bcb-default", _("Blue Chicago Blues"));
+ Action *act;
+
+ act = new Action("INTERACT", _("Interact"));
+ act->setCustomEngineActionEvent(kBCBActionInteract);
+ act->addDefaultInputMapping("MOUSE_LEFT");
+ act->addDefaultInputMapping("JOY_A");
+ engineKeyMap->addAction(act);
+
+ act = new Action("ESCAPE", _("Escape"));
+ act->setCustomEngineActionEvent(kBCBActionEscape);
+ act->addDefaultInputMapping("ESCAPE");
+ act->addDefaultInputMapping("MOUSE_RIGHT");
+ act->addDefaultInputMapping("JOY_B");
+ engineKeyMap->addAction(act);
+
+ act = new Action("SKIP", _("Skip / Advance"));
+ act->setCustomEngineActionEvent(kBCBActionSkip);
+ act->addDefaultInputMapping("SPACE");
+ act->addDefaultInputMapping("JOY_X");
+ engineKeyMap->addAction(act);
+
+ act = new Action("MOVE_CHOICE", _("Move"));
+ act->setCustomEngineActionEvent(kBCBActionChoice9);
+ act->addDefaultInputMapping("9");
+ act->addDefaultInputMapping("KP9");
+ engineKeyMap->addAction(act);
+
+ static const char *const choiceActionIds[] = {
+ "CHOICE1", "CHOICE2", "CHOICE3", "CHOICE4",
+ "CHOICE5", "CHOICE6", "CHOICE7", "CHOICE8"
+ };
+ static const char *const choiceActionLabels[] = {
+ _s("Choice 1"), _s("Choice 2"), _s("Choice 3"),
+ _s("Choice 4"), _s("Choice 5"), _s("Choice 6"),
+ _s("Choice 7"), _s("Choice 8")
+ };
+
+ for (int choiceIndex = 1; choiceIndex <= 8; ++choiceIndex) {
+ act = new Action(choiceActionIds[choiceIndex - 1], _(choiceActionLabels[choiceIndex - 1]));
+ act->setCustomEngineActionEvent((Common::CustomEventType)(kBCBActionChoice1 + choiceIndex - 1));
+ act->addDefaultInputMapping(Common::String::format("%d", choiceIndex));
+ act->addDefaultInputMapping(Common::String::format("KP%d", choiceIndex));
+ engineKeyMap->addAction(act);
+ }
+
+ return Keymap::arrayOf(engineKeyMap);
+}
+
Common::Error HaroldMetaEngine::createInstance(OSystem *syst, Engine **engine, const BCB::HaroldGameDescription *gd) const {
switch (gd->gameType) {
case BCB::kHaroldGameBCB:
diff --git a/engines/harold/module.mk b/engines/harold/module.mk
index 39510a47034..276b4a8c8a1 100644
--- a/engines/harold/module.mk
+++ b/engines/harold/module.mk
@@ -9,6 +9,7 @@ MODULE_OBJS := \
bcb/screens.o \
bcb/input.o \
bcb/computer.o \
+ bcb/debugger.o \
bcb/saveload.o \
bcb/map.o \
bcb/script.o \
Commit: 3a257cfdd18e9f586c8bb86c0c6c6a8452e5d86f
https://github.com/scummvm/scummvm/commit/3a257cfdd18e9f586c8bb86c0c6c6a8452e5d86f
Author: Scorp (scorp at mrs.mn)
Date: 2026-10-22T18:57:52+02:00
Commit Message:
HAROLD: BCB: Reduce rendering, font and menu audio overhead
Changed paths:
engines/harold/bcb/bcb.cpp
engines/harold/bcb/bcb.h
engines/harold/bcb/captions.cpp
engines/harold/bcb/computer.cpp
engines/harold/bcb/input.cpp
engines/harold/bcb/map.cpp
engines/harold/bcb/saveload.cpp
engines/harold/bcb/scene.cpp
engines/harold/bcb/screens.cpp
engines/harold/bcb/video.cpp
diff --git a/engines/harold/bcb/bcb.cpp b/engines/harold/bcb/bcb.cpp
index fb6adcee52a..00b92856c69 100644
--- a/engines/harold/bcb/bcb.cpp
+++ b/engines/harold/bcb/bcb.cpp
@@ -20,6 +20,7 @@
*/
#include "harold/bcb/bcb.h"
+#include "common/compression/unzip.h"
#include "common/config-manager.h"
#include "common/system.h"
#include "common/textconsole.h"
@@ -27,6 +28,7 @@
#include "graphics/cursorman.h"
#include "graphics/font.h"
#include "graphics/fonts/ttf.h"
+#include "graphics/screen.h"
#include "graphics/sjis.h"
#include "graphics/surface.h"
#include "harold/bcb/debugger.h"
@@ -36,37 +38,71 @@
namespace BCB {
+#ifdef USE_FREETYPE2
static const int kCaptionTextNativeCharacterHeight = 18;
static const int kOwnerDrawUiTextNativeCharacterHeight = 16;
-static Graphics::Font *loadLocalizedCjkFont(Common::Language language, int size, Graphics::TTFSizeMode sizeMode) {
+static Graphics::Font *loadFont(Common::Archive &archive, const char *name, int size, Graphics::TTFSizeMode sizeMode) {
+ Common::SeekableReadStream *stream = archive.createReadStreamForMember(Common::Path(name));
+ if (!stream)
+ return nullptr;
+ Graphics::Font *font = Graphics::loadTTFFont(stream, DisposeAfterUse::YES, size, sizeMode, 0, 0, Graphics::kTTFRenderModeNormal);
+ if (!font)
+ delete stream;
+ return font;
+}
+
+static Graphics::Font *loadLocalizedCjkFont(Common::Archive &archive, Common::Language language, int size, Graphics::TTFSizeMode sizeMode) {
if (language == Common::KO_KOR) {
- Graphics::Font *font = Graphics::loadTTFFontFromArchive("NotoSansKR-Regular.otf", size, sizeMode, 0, 0, Graphics::kTTFRenderModeNormal);
+ Graphics::Font *font = loadFont(archive, "NotoSansKR-Regular.otf", size, sizeMode);
if (font)
return font;
- font = Graphics::loadTTFFontFromArchive("NotoSansJP-Regular.otf", size, sizeMode, 0, 0, Graphics::kTTFRenderModeNormal);
+ font = loadFont(archive, "NotoSansJP-Regular.otf", size, sizeMode);
if (font)
return font;
- return Graphics::loadTTFFontFromArchive("VL-Gothic-Regular.ttf", size, sizeMode, 0, 0, Graphics::kTTFRenderModeNormal);
+ return loadFont(archive, "VL-Gothic-Regular.ttf", size, sizeMode);
}
- Graphics::Font *font = Graphics::loadTTFFontFromArchive("VL-Gothic-Regular.ttf", size, sizeMode, 0, 0, Graphics::kTTFRenderModeNormal);
+ Graphics::Font *font = loadFont(archive, "VL-Gothic-Regular.ttf", size, sizeMode);
if (font)
return font;
- return Graphics::loadTTFFontFromArchive("NotoSansJP-Regular.otf", size, sizeMode, 0, 0, Graphics::kTTFRenderModeNormal);
+ return loadFont(archive, "NotoSansJP-Regular.otf", size, sizeMode);
}
-static Graphics::Font *loadLocalizedOwnerDrawFont(Common::Language language) {
+static Graphics::Font *loadLocalizedOwnerDrawFont(Common::Archive &archive, Common::Language language) {
if (language == Common::KO_KOR) {
- Graphics::Font *font = Graphics::loadTTFFontFromArchive("NotoSansKR-Regular.otf", kOwnerDrawUiTextNativeCharacterHeight, Graphics::kTTFSizeModeCharacter, 0, 0, Graphics::kTTFRenderModeNormal);
+ Graphics::Font *font = loadFont(archive, "NotoSansKR-Regular.otf", kOwnerDrawUiTextNativeCharacterHeight, Graphics::kTTFSizeModeCharacter);
if (font)
return font;
}
- Graphics::Font *font = Graphics::loadTTFFontFromArchive("NotoSansJP-Regular.otf", kOwnerDrawUiTextNativeCharacterHeight, Graphics::kTTFSizeModeCharacter, 0, 0, Graphics::kTTFRenderModeNormal);
+ Graphics::Font *font = loadFont(archive, "NotoSansJP-Regular.otf", kOwnerDrawUiTextNativeCharacterHeight, Graphics::kTTFSizeModeCharacter);
if (font)
return font;
- return Graphics::loadTTFFontFromArchive("VL-Gothic-Regular.ttf", kOwnerDrawUiTextNativeCharacterHeight, Graphics::kTTFSizeModeCharacter, 0, 0, Graphics::kTTFRenderModeNormal);
+ return loadFont(archive, "VL-Gothic-Regular.ttf", kOwnerDrawUiTextNativeCharacterHeight, Graphics::kTTFSizeModeCharacter);
+}
+#endif
+
+void BCBEngine::loadFonts() {
+#ifdef USE_FREETYPE2
+ Common::SearchSet fonts;
+ const char *archiveNames[] = {"fonts.dat", "fonts-cjk.dat"};
+ for (const char *name : archiveNames) {
+ Common::SeekableReadStream *stream = nullptr;
+ if (ConfMan.hasKey("extrapath")) {
+ Common::FSDirectory directory(ConfMan.getPath("extrapath"));
+ stream = directory.createReadStreamForMember(name);
+ }
+ if (!stream)
+ stream = SearchMan.createReadStreamForMember(name);
+ Common::Archive *archive = Common::makeZipArchive(stream);
+ if (archive)
+ fonts.add(name, archive);
+ }
+ _captionTextFont = loadLocalizedCjkFont(fonts, _gameDescription->desc.language, kCaptionTextNativeCharacterHeight, Graphics::kTTFSizeModeCharacter);
+ _gameOverTextFont = loadLocalizedCjkFont(fonts, _gameDescription->desc.language, 20, Graphics::kTTFSizeModeCell);
+ _ownerDrawUiTextFont = loadLocalizedOwnerDrawFont(fonts, _gameDescription->desc.language);
+#endif
}
BCBEngine::BCBEngine(OSystem *syst, const HaroldGameDescription *desc)
@@ -74,6 +110,7 @@ BCBEngine::BCBEngine(OSystem *syst, const HaroldGameDescription *desc)
}
BCBEngine::~BCBEngine() {
+ delete _screen;
if (_uiBitmap) {
_uiBitmap->free();
delete _uiBitmap;
@@ -115,15 +152,14 @@ Common::Error BCBEngine::run() {
return Common::kPathDoesNotExist;
initGraphics(640, 480, &screenFormat);
+ _screen = new Graphics::Screen(640, 480, g_system->getScreenFormat());
CursorMan.lock(false);
CursorMan.setDefaultArrowCursor();
CursorMan.showMouse(true);
- g_system->updateScreen();
+ _screen->update();
_lastCursorRefreshMillis = g_system->getMillis();
_sjisFont = Graphics::FontSJIS::createFont();
- _captionTextFont = loadLocalizedCjkFont(_gameDescription->desc.language, kCaptionTextNativeCharacterHeight, Graphics::kTTFSizeModeCharacter);
- _gameOverTextFont = loadLocalizedCjkFont(_gameDescription->desc.language, 20, Graphics::kTTFSizeModeCell);
- _ownerDrawUiTextFont = loadLocalizedOwnerDrawFont(_gameDescription->desc.language);
+ loadFonts();
if (!_sjisFont && !_captionTextFont && !_gameOverTextFont && !_ownerDrawUiTextFont)
warning("BCBEngine::run(): No CJK font available; captions and choice text will not render");
@@ -152,13 +188,13 @@ void BCBEngine::clearDisplayAndResetState() {
_sceneChoiceActive = false;
_mapControlsActive = false;
- Graphics::Surface *screen = g_system->lockScreen();
+ Graphics::Surface *screen = _screen->surfacePtr();
if (screen) {
const uint32 black = screen->format.RGBToColor(0, 0, 0);
screen->fillRect(Common::Rect(0, 0, 640, 480), black);
- g_system->unlockScreen();
+ _screen->makeAllDirty();
}
- g_system->updateScreen();
+ _screen->update();
}
void BCBEngine::processGameStateDispatcher(bool runScriptBeforeDispatch) {
@@ -261,17 +297,7 @@ void BCBEngine::mainLoop() {
}
}
- if (_menuBackgroundVideo) {
- if (_menuBackgroundVideo->endOfVideo()) {
- _menuBackgroundVideo->stop();
- _menuBackgroundVideo->rewind();
- _menuBackgroundVideo->decodeNextFrame();
- _menuBackgroundVideo->start();
- }
-
- if (_menuBackgroundVideo->needsUpdate())
- _menuBackgroundVideo->decodeNextFrame();
- }
+ updateMenuBackgroundMusic();
if (_sceneIdleVideo) {
if (_sceneIdleVideo->endOfVideo()) {
@@ -284,8 +310,8 @@ void BCBEngine::mainLoop() {
if (_sceneIdleVideo->needsUpdate()) {
const Graphics::Surface *frame = _sceneIdleVideo->decodeNextFrame();
if (frame) {
- g_system->copyRectToScreen(frame->getPixels(), frame->pitch, 0x10, 0x30, frame->w, frame->h);
- g_system->updateScreen();
+ _screen->copyRectToSurface(frame->getPixels(), frame->pitch, 0x10, 0x30, frame->w, frame->h);
+ _screen->update();
screenUpdated = true;
}
}
@@ -316,14 +342,14 @@ void BCBEngine::mainLoop() {
const Graphics::Surface *frame = _activeMovieVideo->decodeNextFrame();
if (frame) {
const Common::Point moviePosition = getActiveMoviePosition();
- g_system->copyRectToScreen(frame->getPixels(), frame->pitch, moviePosition.x, moviePosition.y, frame->w, frame->h);
+ _screen->copyRectToSurface(frame->getPixels(), frame->pitch, moviePosition.x, moviePosition.y, frame->w, frame->h);
movieUpdated = true;
}
}
if (drawTimedVideoTextOverlay())
movieUpdated = true;
if (movieUpdated) {
- g_system->updateScreen();
+ _screen->update();
screenUpdated = true;
}
diff --git a/engines/harold/bcb/bcb.h b/engines/harold/bcb/bcb.h
index 962cb43960b..d3a95d06502 100644
--- a/engines/harold/bcb/bcb.h
+++ b/engines/harold/bcb/bcb.h
@@ -31,14 +31,15 @@
#include "common/str-enc.h"
#include "common/ustr.h"
#include "engines/engine.h"
-#include "harold/detection.h"
#include "harold/bcb/ui.h"
+#include "harold/detection.h"
namespace Graphics {
class Font;
class FontSJIS;
+class Screen;
struct Surface;
-}
+} // namespace Graphics
namespace Video {
class AVIDecoder;
@@ -83,6 +84,7 @@ public:
bool canSaveAutosaveCurrently() override;
private:
+ void loadFonts();
bool initializeSearchPaths();
bool showInitialLogoScreen();
bool showTitleScreen();
@@ -94,6 +96,7 @@ private:
void stopActiveMovie();
void playUiSound();
void startMenuBackgroundMusic();
+ void updateMenuBackgroundMusic();
void stopMenuBackgroundMusic();
void startGameOverMusic();
void stopGameOverMusic();
@@ -229,6 +232,7 @@ private:
void mainLoop();
const HaroldGameDescription *_gameDescription;
+ Graphics::Screen *_screen = nullptr;
Common::SearchSet _programFiles;
Common::SearchSet _discFiles[5];
Common::Array<byte> _scriptData;
@@ -236,7 +240,7 @@ private:
Common::Array<byte> _locationTimingData;
Common::Array<byte> _allBinData;
Common::Array<byte> _timedCaptionRecordData;
- Common::Array<Common::Array<Common::U32String> > _sceneChoiceText;
+ Common::Array<Common::Array<Common::U32String>> _sceneChoiceText;
Common::CodePage _executableTextEncoding = Common::kWindows932;
Common::String _sceneChoiceMoveTextSjis;
Common::U32String _sceneChoiceMoveText;
diff --git a/engines/harold/bcb/captions.cpp b/engines/harold/bcb/captions.cpp
index cd8ae11c738..1b868c28681 100644
--- a/engines/harold/bcb/captions.cpp
+++ b/engines/harold/bcb/captions.cpp
@@ -22,6 +22,7 @@
#include "common/algorithm.h"
#include "common/system.h"
#include "graphics/font.h"
+#include "graphics/screen.h"
#include "graphics/sjis.h"
#include "graphics/surface.h"
#include "harold/bcb/bcb.h"
@@ -180,10 +181,9 @@ void BCBEngine::loadNextTimedCaptionText() {
void BCBEngine::restoreCaptionBackingRect(const Common::Rect &rect) {
if (_activeMovieOnBlankScreen) {
- Graphics::Surface *screen = g_system->lockScreen();
+ Graphics::Surface *screen = _screen->surfacePtr();
if (screen) {
- screen->fillRect(rect, screen->format.RGBToColor(0, 0, 0));
- g_system->unlockScreen();
+ _screen->fillRect(rect, screen->format.RGBToColor(0, 0, 0));
}
return;
}
@@ -202,14 +202,14 @@ void BCBEngine::restoreCaptionBackingRect(const Common::Rect &rect) {
const int sourceX = kMapSubmodeMovieBackdropSourceX + restoreRect.left - kMapSubmodeMovieBackdropRect.left;
const int sourceY = kMapSubmodeMovieBackdropSourceY + restoreRect.top - kMapSubmodeMovieBackdropRect.top;
if (sourceX >= 0 && sourceY >= 0 && sourceX + restoreRect.width() <= map->w && sourceY + restoreRect.height() <= map->h)
- g_system->copyRectToScreen(map->getBasePtr(sourceX, sourceY), map->pitch, restoreRect.left, restoreRect.top, restoreRect.width(), restoreRect.height());
+ _screen->copyRectToSurface(map->getBasePtr(sourceX, sourceY), map->pitch, restoreRect.left, restoreRect.top, restoreRect.width(), restoreRect.height());
}
return;
}
if (backing)
- g_system->copyRectToScreen(backing->getBasePtr(rect.left, rect.top), backing->pitch, rect.left, rect.top, rect.width(), rect.height());
+ _screen->copyRectToSurface(backing->getBasePtr(rect.left, rect.top), backing->pitch, rect.left, rect.top, rect.width(), rect.height());
}
Common::Rect BCBEngine::getCaptionBackingRect(const Common::String &sjisText, const Common::U32String &unicodeText, const Common::Rect &nativeRect) const {
@@ -246,7 +246,7 @@ void BCBEngine::drawCaptionTextBlock(const Common::String &sjisText, const Commo
if (!_captionTextFont && !_sjisFont)
return;
- Graphics::Surface *screen = g_system->lockScreen();
+ Graphics::Surface *screen = _screen->surfacePtr();
if (!screen)
return;
@@ -292,7 +292,7 @@ void BCBEngine::drawCaptionTextBlock(const Common::String &sjisText, const Commo
}
}
- g_system->unlockScreen();
+ _screen->addDirtyRect(getCaptionBackingRect(sjisText, unicodeText, rect));
}
bool BCBEngine::drawTimedVideoTextOverlay() {
diff --git a/engines/harold/bcb/computer.cpp b/engines/harold/bcb/computer.cpp
index fe33c6e87f4..4069614da11 100644
--- a/engines/harold/bcb/computer.cpp
+++ b/engines/harold/bcb/computer.cpp
@@ -23,6 +23,7 @@
#include "common/endian.h"
#include "common/events.h"
#include "common/system.h"
+#include "graphics/screen.h"
#include "graphics/surface.h"
#include <math.h>
@@ -56,7 +57,7 @@ bool BCBEngine::dispatchComputerMainControls() {
drawComputerChoiceGroupControls();
drawComputerButton(kComputerPrimaryButtonTable[1], kComputerPrimaryButtonTable[1]);
- g_system->updateScreen();
+ _screen->update();
return true;
}
@@ -74,7 +75,7 @@ bool BCBEngine::showComputerScreen() {
if (!computer)
return false;
- g_system->copyRectToScreen(computer->getPixels(), computer->pitch, 0, 0, MIN<int>(computer->w, 640), MIN<int>(computer->h, 480));
+ _screen->copyRectToSurface(computer->getPixels(), computer->pitch, 0, 0, MIN<int>(computer->w, 640), MIN<int>(computer->h, 480));
_currentDisplayState = 4;
return true;
}
@@ -87,20 +88,19 @@ void BCBEngine::drawComputerChoiceGroupControls() {
if (!computer)
return;
- Graphics::Surface *screen = g_system->lockScreen();
+ Graphics::Surface *screen = _screen->surfacePtr();
if (screen) {
if (_computerChoiceGroupIndex >= 0 && _computerChoiceGroupIndex <= 2) {
const int sourceY = _computerChoiceGroupIndex * 0x16 + 0x2a0;
if (sourceY + 0x16 <= computer->h)
- screen->copyRectToSurface(*computer, 0x3c, 0x43, Common::Rect(0, sourceY, 0xe8, sourceY + 0x16));
+ _screen->copyRectToSurface(*computer, 0x3c, 0x43, Common::Rect(0, sourceY, 0xe8, sourceY + 0x16));
}
for (uint i = 1; i < ARRAYSIZE(kComputerChoiceLayoutSlots); ++i) {
const ButtonControlRecord &slot = kComputerChoiceLayoutSlots[i];
if (slot.x + slot.width <= computer->w && slot.y + slot.height <= computer->h)
- screen->copyRectToSurface(*computer, slot.x, slot.y, Common::Rect(slot.x, slot.y, slot.x + slot.width, slot.y + slot.height));
+ _screen->copyRectToSurface(*computer, slot.x, slot.y, Common::Rect(slot.x, slot.y, slot.x + slot.width, slot.y + slot.height));
}
- g_system->unlockScreen();
}
if (_computerChoiceGroupIndex == 0)
@@ -116,11 +116,9 @@ void BCBEngine::drawComputerButton(const ButtonControlRecord &sourceButton, cons
if (!computer)
return;
- Graphics::Surface *screen = g_system->lockScreen();
+ Graphics::Surface *screen = _screen->surfacePtr();
if (screen && sourceButton.x + sourceButton.width <= computer->w && sourceButton.y + sourceButton.height <= computer->h)
- screen->copyRectToSurface(*computer, destButton.x, destButton.y, Common::Rect(sourceButton.x, sourceButton.y, sourceButton.x + sourceButton.width, sourceButton.y + sourceButton.height));
- if (screen)
- g_system->unlockScreen();
+ _screen->copyRectToSurface(*computer, destButton.x, destButton.y, Common::Rect(sourceButton.x, sourceButton.y, sourceButton.x + sourceButton.width, sourceButton.y + sourceButton.height));
}
void BCBEngine::drawComputerButtonPressed(const ButtonControlRecord &button, int sourceX, int sourceY) {
@@ -129,8 +127,8 @@ void BCBEngine::drawComputerButtonPressed(const ButtonControlRecord &button, int
return;
if (sourceX + button.width <= computer->w && sourceY + button.height <= computer->h) {
- g_system->copyRectToScreen(computer->getBasePtr(sourceX, sourceY), computer->pitch, button.x, button.y, button.width, button.height);
- g_system->updateScreen();
+ _screen->copyRectToSurface(computer->getBasePtr(sourceX, sourceY), computer->pitch, button.x, button.y, button.width, button.height);
+ _screen->update();
}
}
@@ -139,8 +137,8 @@ void BCBEngine::drawComputerDetailButtonNormal(const ButtonControlRecord &button
return;
if (button.x + button.width <= _computerDetailBacking->w && button.y + button.height <= _computerDetailBacking->h) {
- g_system->copyRectToScreen(_computerDetailBacking->getBasePtr(button.x, button.y), _computerDetailBacking->pitch, button.x, button.y, button.width, button.height);
- g_system->updateScreen();
+ _screen->copyRectToSurface(_computerDetailBacking->getBasePtr(button.x, button.y), _computerDetailBacking->pitch, button.x, button.y, button.width, button.height);
+ _screen->update();
}
}
@@ -154,11 +152,11 @@ void BCBEngine::drawComputerUnavailableMarker(const ButtonControlRecord &destBut
const int markerWidth = 0x36;
const int markerHeight = 0x18;
if (markerSourceX + markerWidth <= computer->w && markerSourceY + markerHeight <= computer->h)
- g_system->copyRectToScreen(computer->getBasePtr(markerSourceX, markerSourceY), computer->pitch, destButton.x + 5, destButton.y + 0x0c, markerWidth, markerHeight);
+ _screen->copyRectToSurface(computer->getBasePtr(markerSourceX, markerSourceY), computer->pitch, destButton.x + 5, destButton.y + 0x0c, markerWidth, markerHeight);
}
void BCBEngine::drawXorRectangleBorder(const Common::Rect &rect) {
- Graphics::Surface *screen = g_system->lockScreen();
+ Graphics::Surface *screen = _screen->surfacePtr();
if (!screen)
return;
@@ -200,8 +198,15 @@ void BCBEngine::drawXorRectangleBorder(const Common::Rect &rect) {
}
}
- g_system->unlockScreen();
- g_system->updateScreen();
+ if (!clipped.isEmpty()) {
+ _screen->addDirtyRect(Common::Rect(clipped.left, clipped.top, clipped.right, clipped.top + 1));
+ _screen->addDirtyRect(Common::Rect(clipped.left, clipped.top + 1, clipped.left + 1, clipped.bottom));
+ if (rect.bottom != rect.top && rect.bottom >= 0 && rect.bottom < screen->h)
+ _screen->addDirtyRect(Common::Rect(clipped.left, rect.bottom, clipped.right, rect.bottom + 1));
+ if (rect.right != rect.left && rect.right >= 0 && rect.right < screen->w)
+ _screen->addDirtyRect(Common::Rect(rect.right, clipped.top, rect.right + 1, clipped.bottom));
+ }
+ _screen->update();
}
void BCBEngine::processXorRectangleBorderAnimation(bool expand, const Common::Rect &sourceRect, const Common::Rect &targetRect) {
@@ -243,7 +248,7 @@ void BCBEngine::drawComputerConditionalChoiceControls(const ButtonControlRecord
if (!computer)
return;
- Graphics::Surface *screen = g_system->lockScreen();
+ Graphics::Surface *screen = _screen->surfacePtr();
int visibleButtonSlot = 1;
for (int i = 0; screen && i < stateCount && visibleButtonSlot < (int)ARRAYSIZE(kComputerChoiceLayoutSlots); ++i) {
const int stateIndex = firstStateIndex + i;
@@ -258,15 +263,12 @@ void BCBEngine::drawComputerConditionalChoiceControls(const ButtonControlRecord
const ButtonControlRecord &source = choiceButtonRecords[i];
const ButtonControlRecord &dest = kComputerChoiceLayoutSlots[visibleButtonSlot++];
if (source.x + source.width <= computer->w && source.y + source.height <= computer->h)
- screen->copyRectToSurface(*computer, dest.x, dest.y, Common::Rect(source.x, source.y, source.x + source.width, source.y + source.height));
+ _screen->copyRectToSurface(*computer, dest.x, dest.y, Common::Rect(source.x, source.y, source.x + source.width, source.y + source.height));
if (directlyAvailable) {
_computerActiveChoiceIds.push_back(source.controlId);
_computerActiveChoiceRects.push_back(Common::Rect(dest.x, dest.y, dest.x + dest.width, dest.y + dest.height));
}
}
- if (screen)
- g_system->unlockScreen();
-
visibleButtonSlot = 1;
for (int i = 0; i < stateCount && visibleButtonSlot < (int)ARRAYSIZE(kComputerChoiceLayoutSlots); ++i) {
const int stateIndex = firstStateIndex + i;
@@ -300,7 +302,7 @@ bool BCBEngine::showComputerDetailScreen(int compositeIndex) {
if (!composite)
return false;
- Graphics::Surface *screen = g_system->lockScreen();
+ Graphics::Surface *screen = _screen->surfacePtr();
if (screen) {
screen->copyRectToSurface(*computer, 0, 0, Common::Rect(0, 0, MIN<int>(computer->w, 640), MIN<int>(computer->h, 480)));
screen->copyRectToSurface(*composite, 0x24, 0x23, Common::Rect(0, 0, MIN<int>(composite->w, 640 - 0x24), MIN<int>(composite->h, 480 - 0x23)));
@@ -311,7 +313,7 @@ bool BCBEngine::showComputerDetailScreen(int compositeIndex) {
}
_computerDetailBacking = new Graphics::Surface();
_computerDetailBacking->copyFrom(*screen);
- g_system->unlockScreen();
+ _screen->makeAllDirty();
}
composite->free();
@@ -323,7 +325,7 @@ bool BCBEngine::showComputerDetailScreen(int compositeIndex) {
_computerDetailVariantIndex = -1;
_currentDisplayState = 5;
- g_system->updateScreen();
+ _screen->update();
return true;
}
@@ -349,7 +351,7 @@ bool BCBEngine::handleComputerClick(const Common::Point &mousePosition) {
playUiSound();
_computerChoiceGroupIndex = groupIndex;
drawComputerChoiceGroupControls();
- g_system->updateScreen();
+ _screen->update();
}
return true;
}
@@ -384,7 +386,7 @@ bool BCBEngine::handleComputerClick(const Common::Point &mousePosition) {
const ButtonControlRecord &nextButton = kComputerDetailButtonTable[2];
if (_computerDetailNextVariantEnabled &&
- Common::Rect(nextButton.x, nextButton.y, nextButton.x + nextButton.width, nextButton.y + nextButton.height).contains(mousePosition)) {
+ Common::Rect(nextButton.x, nextButton.y, nextButton.x + nextButton.width, nextButton.y + nextButton.height).contains(mousePosition)) {
drawComputerButtonPressed(nextButton, _computerDetailVariantIndex >= 0 ? 0x188 : 0x228, 0x2a0);
playUiSound();
if (_computerDetailVariantIndex < 0) {
diff --git a/engines/harold/bcb/input.cpp b/engines/harold/bcb/input.cpp
index 187a45a2d97..ec6ba1f6d4c 100644
--- a/engines/harold/bcb/input.cpp
+++ b/engines/harold/bcb/input.cpp
@@ -21,6 +21,7 @@
#include "common/events.h"
#include "common/system.h"
+#include "graphics/screen.h"
#include "harold/bcb/bcb.h"
#include "harold/bcb/script.h"
#include "harold/bcb/static_data.h"
@@ -270,7 +271,7 @@ void BCBEngine::serviceCursorRefresh(bool screenUpdated) {
if (now - _lastCursorRefreshMillis >= kCursorRefreshMillis) {
_lastCursorRefreshMillis = now;
- g_system->updateScreen();
+ _screen->update();
}
}
@@ -283,6 +284,7 @@ void BCBEngine::delayWithEventPolling(uint32 millis, bool handleClicks) {
while (!shouldQuit() && g_system->getMillis() - start < millis) {
pollEvents(handleClicks);
+ updateMenuBackgroundMusic();
serviceCursorRefresh();
const uint32 elapsed = g_system->getMillis() - start;
diff --git a/engines/harold/bcb/map.cpp b/engines/harold/bcb/map.cpp
index a4d78397061..3e7ffa61acd 100644
--- a/engines/harold/bcb/map.cpp
+++ b/engines/harold/bcb/map.cpp
@@ -23,6 +23,7 @@
#include "common/fs.h"
#include "common/ustr.h"
#include "graphics/font.h"
+#include "graphics/screen.h"
#include "graphics/sjis.h"
#include "graphics/surface.h"
#include "video/avi_decoder.h"
@@ -48,7 +49,7 @@ bool BCBEngine::showMapScreen() {
delete map;
_currentDisplayState = 2;
_lastTimerTick = g_system->getMillis();
- g_system->updateScreen();
+ _screen->update();
return true;
}
@@ -106,10 +107,10 @@ void BCBEngine::drawMapButtonPressed(uint buttonIndex, bool present) {
const ButtonControlRecord &button = kMapButtonTable[buttonIndex];
if (0x1c8 + button.width <= map->w && 0x1e0 + button.height <= map->h)
- g_system->copyRectToScreen(map->getBasePtr(0x1c8, 0x1e0), map->pitch, button.x, button.y, button.width, button.height);
+ _screen->copyRectToSurface(map->getBasePtr(0x1c8, 0x1e0), map->pitch, button.x, button.y, button.width, button.height);
if (present)
- g_system->updateScreen();
+ _screen->update();
}
void BCBEngine::composeMapControlsToSurface(Graphics::Surface &surface, const Graphics::Surface &mapArt) {
@@ -165,14 +166,14 @@ void BCBEngine::drawMapControls(bool present) {
if (!map)
return;
- Graphics::Surface *screen = g_system->lockScreen();
+ Graphics::Surface *screen = _screen->surfacePtr();
if (screen) {
composeMapControlsToSurface(*screen, *map);
- g_system->unlockScreen();
+ _screen->makeAllDirty();
}
if (present)
- g_system->updateScreen();
+ _screen->update();
}
bool BCBEngine::handleMapControlClick(const Common::Point &mousePosition) {
diff --git a/engines/harold/bcb/saveload.cpp b/engines/harold/bcb/saveload.cpp
index 75546246ea1..70b8c5ee21e 100644
--- a/engines/harold/bcb/saveload.cpp
+++ b/engines/harold/bcb/saveload.cpp
@@ -29,6 +29,7 @@
#include "common/textconsole.h"
#include "graphics/font.h"
#include "graphics/fontman.h"
+#include "graphics/screen.h"
#include "graphics/surface.h"
#include "harold/bcb/bcb.h"
@@ -238,13 +239,13 @@ bool BCBEngine::showSystemScreen() {
kSystemWaveVolumeIndicatorRect.left + fillWidth, kSystemWaveVolumeIndicatorRect.bottom);
system->fillRect(fillRect, system->format.RGBToColor(255, 255, 255));
}
- g_system->copyRectToScreen(system->getPixels(), system->pitch, 0, 0, MIN<int>(system->w, 640), MIN<int>(system->h, 480));
+ _screen->copyRectToSurface(system->getPixels(), system->pitch, 0, 0, MIN<int>(system->w, 640), MIN<int>(system->h, 480));
system->free();
delete system;
_currentDisplayState = 3;
drawSystemSubmodeButtons(false);
- g_system->updateScreen();
+ _screen->update();
return true;
}
@@ -260,12 +261,12 @@ void BCBEngine::drawSystemSubmodeButtons(bool present) {
(_systemSaveLoadSlotActionMode == 2 && i == 2))
source = kSystemSubmodeSelectedSources[i];
if (source.x + button.width <= system->w && source.y + button.height <= system->h)
- g_system->copyRectToScreen(system->getBasePtr(source.x, source.y), system->pitch,
+ _screen->copyRectToSurface(system->getBasePtr(source.x, source.y), system->pitch,
button.x, button.y, button.width, button.height);
}
if (present)
- g_system->updateScreen();
+ _screen->update();
}
void BCBEngine::drawSystemSubmodeButton(uint buttonIndex, bool selected, bool present) {
@@ -279,10 +280,10 @@ void BCBEngine::drawSystemSubmodeButton(uint buttonIndex, bool selected, bool pr
const ButtonControlRecord &button = kSystemSubmodeButtonTable[buttonIndex];
const Common::Point source = selected ? kSystemSubmodeSelectedSources[buttonIndex] : kSystemSubmodeNormalSources[buttonIndex];
if (source.x + button.width <= system->w && source.y + button.height <= system->h)
- g_system->copyRectToScreen(system->getBasePtr(source.x, source.y), system->pitch, button.x, button.y, button.width, button.height);
+ _screen->copyRectToSurface(system->getBasePtr(source.x, source.y), system->pitch, button.x, button.y, button.width, button.height);
if (present)
- g_system->updateScreen();
+ _screen->update();
}
void BCBEngine::applySystemWaveVolume() {
@@ -312,24 +313,23 @@ void BCBEngine::drawSystemWaveVolumeIndicator(bool present) {
return;
if (kSystemWaveVolumeIndicatorRect.right <= system->w && kSystemWaveVolumeIndicatorRect.bottom <= system->h) {
- g_system->copyRectToScreen(system->getBasePtr(kSystemWaveVolumeIndicatorRect.left, kSystemWaveVolumeIndicatorRect.top),
+ _screen->copyRectToSurface(system->getBasePtr(kSystemWaveVolumeIndicatorRect.left, kSystemWaveVolumeIndicatorRect.top),
system->pitch, kSystemWaveVolumeIndicatorRect.left, kSystemWaveVolumeIndicatorRect.top,
kSystemWaveVolumeIndicatorRect.width(), kSystemWaveVolumeIndicatorRect.height());
}
- Graphics::Surface *screen = g_system->lockScreen();
+ Graphics::Surface *screen = _screen->surfacePtr();
if (screen) {
const int fillWidth = (_systemWaveVolume * kSystemWaveVolumeIndicatorRect.width()) / 0xffff;
if (fillWidth > 0) {
const Common::Rect fillRect(kSystemWaveVolumeIndicatorRect.left, kSystemWaveVolumeIndicatorRect.top,
kSystemWaveVolumeIndicatorRect.left + fillWidth, kSystemWaveVolumeIndicatorRect.bottom);
- screen->fillRect(fillRect, screen->format.RGBToColor(255, 255, 255));
+ _screen->fillRect(fillRect, screen->format.RGBToColor(255, 255, 255));
}
- g_system->unlockScreen();
}
if (present)
- g_system->updateScreen();
+ _screen->update();
}
bool BCBEngine::handleSystemWaveVolumeClick(const Common::Point &mousePosition) {
@@ -377,6 +377,7 @@ bool BCBEngine::handleSystemWaveVolumeClick(const Common::Point &mousePosition)
}
}
serviceCursorRefresh();
+ updateMenuBackgroundMusic();
g_system->delayMillis(1);
}
ConfMan.flushToDisk();
@@ -427,7 +428,7 @@ void BCBEngine::drawSystemSaveLoadSlots(bool present) {
const ButtonControlRecord &button = kSystemButtonTable[slot];
const int sourceY = 480 + (slot - 1) * button.height;
if (sourceY + button.height <= system->h)
- g_system->copyRectToScreen(system->getBasePtr(0, sourceY), system->pitch, button.x, button.y, button.width, button.height);
+ _screen->copyRectToSurface(system->getBasePtr(0, sourceY), system->pitch, button.x, button.y, button.width, button.height);
int savedDay = 0;
int savedClockTicks = 0;
@@ -438,18 +439,18 @@ void BCBEngine::drawSystemSaveLoadSlots(bool present) {
slotText = formatSystemTextForDisplay(_saveSlotEmptyTextFormatSjis, slot);
}
- Graphics::Surface *screen = g_system->lockScreen();
+ Graphics::Surface *screen = _screen->surfacePtr();
if (screen) {
const uint32 textColor = screen->format.RGBToColor(0, 0, 0);
const Graphics::Font *font = _ownerDrawUiTextFont ? _ownerDrawUiTextFont : FontMan.getFontByUsage(Graphics::FontManager::kGUIFont);
const int textY = button.y + (button.height - font->getFontHeight()) / 2 - (_gameDescription->desc.language == Common::KO_KOR ? 1 : 0);
font->drawString(screen, slotText, button.x, textY, button.width, textColor);
- g_system->unlockScreen();
+ _screen->addDirtyRect(Common::Rect(button.x, textY, button.x + button.width, textY + font->getFontHeight()));
}
}
if (present)
- g_system->updateScreen();
+ _screen->update();
}
bool BCBEngine::handleSystemClick(const Common::Point &mousePosition) {
@@ -586,24 +587,23 @@ bool BCBEngine::showSystemConfirmationDialog(const Common::U32String &message, b
const int buttonSourceY = 0x32;
Graphics::Surface dialogBacking;
dialogBacking.create(dialogRect.width(), dialogRect.height(), g_system->getScreenFormat());
- Graphics::Surface *screenBacking = g_system->lockScreen();
+ Graphics::Surface *screenBacking = _screen->surfacePtr();
if (screenBacking) {
dialogBacking.copyRectToSurface(*screenBacking, 0, 0, dialogRect);
- g_system->unlockScreen();
}
auto drawDialogButton = [&](const Common::Rect &rect, int sourceX, bool present) {
if (sourceX + rect.width() <= chip->w && chip->h >= buttonSourceY + rect.height())
- g_system->copyRectToScreen(chip->getBasePtr(sourceX, buttonSourceY), chip->pitch,
+ _screen->copyRectToSurface(chip->getBasePtr(sourceX, buttonSourceY), chip->pitch,
rect.left, rect.top, rect.width(), rect.height());
if (present)
- g_system->updateScreen();
+ _screen->update();
};
if (dialogRect.width() <= chip->w && chip->h >= dialogSourceY + dialogRect.height())
- g_system->copyRectToScreen(chip->getBasePtr(0, dialogSourceY), chip->pitch, dialogRect.left, dialogRect.top, dialogRect.width(), dialogRect.height());
+ _screen->copyRectToSurface(chip->getBasePtr(0, dialogSourceY), chip->pitch, dialogRect.left, dialogRect.top, dialogRect.width(), dialogRect.height());
- Graphics::Surface *screen = g_system->lockScreen();
+ Graphics::Surface *screen = _screen->surfacePtr();
if (screen) {
const Graphics::Font *font = _ownerDrawUiTextFont ? _ownerDrawUiTextFont : FontMan.getFontByUsage(Graphics::FontManager::kGUIFont);
const uint32 shadowColor = screen->format.RGBToColor(0, 0, 0);
@@ -611,13 +611,13 @@ bool BCBEngine::showSystemConfirmationDialog(const Common::U32String &message, b
const int textYOffset = _gameDescription->desc.language == Common::KO_KOR ? -1 : 0;
font->drawString(screen, message, dialogRect.left + 10, dialogRect.top + 20 + textYOffset, 340, shadowColor);
font->drawString(screen, message, dialogRect.left + 11, dialogRect.top + 21 + textYOffset, 339, textColor);
- g_system->unlockScreen();
+ _screen->addDirtyRect(dialogRect);
}
drawDialogButton(okRect, 0x17, false);
if (showCancelButton)
drawDialogButton(cancelRect, 0x97, false);
- g_system->updateScreen();
+ _screen->update();
bool result = false;
bool done = false;
@@ -662,13 +662,14 @@ bool BCBEngine::showSystemConfirmationDialog(const Common::U32String &message, b
}
}
serviceCursorRefresh();
+ updateMenuBackgroundMusic();
g_system->delayMillis(10);
}
- g_system->copyRectToScreen(dialogBacking.getPixels(), dialogBacking.pitch, dialogRect.left, dialogRect.top,
+ _screen->copyRectToSurface(dialogBacking.getPixels(), dialogBacking.pitch, dialogRect.left, dialogRect.top,
dialogBacking.w, dialogBacking.h);
dialogBacking.free();
- g_system->updateScreen();
+ _screen->update();
return result;
}
diff --git a/engines/harold/bcb/scene.cpp b/engines/harold/bcb/scene.cpp
index 35b22557865..e810225c2bb 100644
--- a/engines/harold/bcb/scene.cpp
+++ b/engines/harold/bcb/scene.cpp
@@ -23,6 +23,7 @@
#include "common/textconsole.h"
#include "graphics/font.h"
#include "graphics/fontman.h"
+#include "graphics/screen.h"
#include "graphics/surface.h"
#include "harold/bcb/bcb.h"
#include "harold/bcb/script.h"
@@ -52,14 +53,14 @@ bool BCBEngine::drawMainSceneScreen(bool lineReveal) {
if (lineReveal) {
blitScreenWithLineReveal(*mainScene, false);
} else {
- g_system->copyRectToScreen(mainScene->getPixels(), mainScene->pitch, 0, 0, MIN<int>(mainScene->w, 640), MIN<int>(mainScene->h, 480));
+ _screen->copyRectToSurface(mainScene->getPixels(), mainScene->pitch, 0, 0, MIN<int>(mainScene->w, 640), MIN<int>(mainScene->h, 480));
}
mainScene->free();
delete mainScene;
_currentDisplayState = 1;
_sceneIdleMovieTimerTicks = 0;
_lastTimerTick = g_system->getMillis();
- g_system->updateScreen();
+ _screen->update();
return true;
}
@@ -86,7 +87,7 @@ bool BCBEngine::showActiveSceneChoiceControls() {
const Common::String overlayPath = Common::String::format("bmp/select/16m/sel%03d.bmp", sceneChoiceUiSetToShowId);
Graphics::Surface *overlay = loadDiscBitmap(overlayPath.c_str());
if (overlay) {
- g_system->copyRectToScreen(overlay->getPixels(), overlay->pitch, 16, 48, MIN<int>(overlay->w, 400), MIN<int>(overlay->h, 200));
+ _screen->copyRectToSurface(overlay->getPixels(), overlay->pitch, 16, 48, MIN<int>(overlay->w, 400), MIN<int>(overlay->h, 200));
overlay->free();
delete overlay;
}
@@ -112,14 +113,13 @@ void BCBEngine::captureSceneChoiceBacking() {
_sceneChoiceBackingRect.extend(rect);
}
- Graphics::Surface *screen = g_system->lockScreen();
+ Graphics::Surface *screen = _screen->surfacePtr();
if (!screen)
return;
_sceneChoiceBacking = new Graphics::Surface();
_sceneChoiceBacking->create(_sceneChoiceBackingRect.width(), _sceneChoiceBackingRect.height(), screen->format);
_sceneChoiceBacking->copyRectToSurface(*screen, 0, 0, _sceneChoiceBackingRect);
- g_system->unlockScreen();
}
void BCBEngine::restoreSceneChoiceBacking(bool present) {
@@ -131,11 +131,11 @@ void BCBEngine::restoreSceneChoiceBacking(bool present) {
continue;
const ButtonControlRecord &button = kSceneChoiceButtonTable[buttonIndex];
- g_system->copyRectToScreen(_sceneChoiceBacking->getBasePtr(button.x - _sceneChoiceBackingRect.left, button.y - _sceneChoiceBackingRect.top), _sceneChoiceBacking->pitch, button.x, button.y, button.width, button.height);
+ _screen->copyRectToSurface(_sceneChoiceBacking->getBasePtr(button.x - _sceneChoiceBackingRect.left, button.y - _sceneChoiceBackingRect.top), _sceneChoiceBacking->pitch, button.x, button.y, button.width, button.height);
}
if (present)
- g_system->updateScreen();
+ _screen->update();
}
void BCBEngine::freeSceneChoiceBacking() {
@@ -148,14 +148,13 @@ void BCBEngine::freeSceneChoiceBacking() {
}
void BCBEngine::clearSceneChoiceAfterSelection() {
- Graphics::Surface *screen = g_system->lockScreen();
+ Graphics::Surface *screen = _screen->surfacePtr();
if (screen) {
- screen->fillRect(Common::Rect(0x10, 0x30, 0x1a0, 0xf8), screen->format.RGBToColor(0, 0, 0));
- g_system->unlockScreen();
+ _screen->fillRect(Common::Rect(0x10, 0x30, 0x1a0, 0xf8), screen->format.RGBToColor(0, 0, 0));
}
freeSceneChoiceBacking();
- g_system->updateScreen();
+ _screen->update();
}
bool BCBEngine::isSceneChoiceButtonVisible(int buttonIndex) const {
@@ -180,10 +179,10 @@ void BCBEngine::drawSceneChoiceButtons(int sceneChoiceUiSetToShowId, bool presen
restoreSceneChoiceBacking(false);
} else if (_sceneChoiceBacking && pressedButtonIndex < (int)ARRAYSIZE(kSceneChoiceButtonTable)) {
const ButtonControlRecord &button = kSceneChoiceButtonTable[pressedButtonIndex];
- g_system->copyRectToScreen(_sceneChoiceBacking->getBasePtr(button.x - _sceneChoiceBackingRect.left, button.y - _sceneChoiceBackingRect.top), _sceneChoiceBacking->pitch, button.x, button.y, button.width, button.height);
+ _screen->copyRectToSurface(_sceneChoiceBacking->getBasePtr(button.x - _sceneChoiceBackingRect.left, button.y - _sceneChoiceBackingRect.top), _sceneChoiceBacking->pitch, button.x, button.y, button.width, button.height);
}
- Graphics::Surface *screen = g_system->lockScreen();
+ Graphics::Surface *screen = _screen->surfacePtr();
if (!screen)
return;
@@ -207,10 +206,9 @@ void BCBEngine::drawSceneChoiceButtons(int sceneChoiceUiSetToShowId, bool presen
const int textWidth = button.width - 10;
font->drawString(screen, label, textX + 1, textY + 1, textWidth, shadowColor, Graphics::kTextAlignLeft);
font->drawString(screen, label, textX, textY, textWidth, textColor, Graphics::kTextAlignLeft);
+ _screen->addDirtyRect(Common::Rect(textX, textY, textX + textWidth + 1, textY + font->getFontHeight() + 1));
};
- g_system->unlockScreen();
-
const Graphics::Surface *mainScene = getUiBitmap(stateDword(kStateMainSceneNightFlag) != 0 ? "16mmainn.bmp" : "16mmain.bmp");
if (mainScene) {
const int firstButtonIndex = pressedButtonIndex < 0 ? 1 : pressedButtonIndex;
@@ -221,11 +219,11 @@ void BCBEngine::drawSceneChoiceButtons(int sceneChoiceUiSetToShowId, bool presen
const ButtonControlRecord &button = kSceneChoiceButtonTable[buttonIndex];
if (button.width <= mainScene->w && 0x210 + button.height <= mainScene->h)
- g_system->copyRectToScreen(mainScene->getBasePtr(0, 0x210), mainScene->pitch, button.x, button.y, button.width, button.height);
+ _screen->copyRectToSurface(mainScene->getBasePtr(0, 0x210), mainScene->pitch, button.x, button.y, button.width, button.height);
}
}
- screen = g_system->lockScreen();
+ screen = _screen->surfacePtr();
if (!screen)
return;
@@ -243,9 +241,8 @@ void BCBEngine::drawSceneChoiceButtons(int sceneChoiceUiSetToShowId, bool presen
drawChoiceLabel(_sceneChoiceMoveText, _sceneChoiceTerminatorButtonIndex);
}
- g_system->unlockScreen();
if (present)
- g_system->updateScreen();
+ _screen->update();
}
void BCBEngine::handleSceneChoiceClick(const Common::Point &mousePosition) {
@@ -350,17 +347,17 @@ void BCBEngine::drawAnalogClockAndChip(int baseHourOffset, int gameClockTicks, b
clockBacking = getUiBitmap(stateDword(kStateMainSceneNightFlag) != 0 ? "16mmainn.bmp" : "16mmain.bmp");
}
if (clockBacking) {
- g_system->copyRectToScreen(clockBacking->getBasePtr(0x25, 0x167), clockBacking->pitch, 0x25, 0x167, 0x60, 0x60);
+ _screen->copyRectToSurface(clockBacking->getBasePtr(0x25, 0x167), clockBacking->pitch, 0x25, 0x167, 0x60, 0x60);
}
- Graphics::Surface *screen = g_system->lockScreen();
+ Graphics::Surface *screen = _screen->surfacePtr();
if (screen) {
renderClockAndChipToSurface(*screen, baseHourOffset, gameClockTicks);
- g_system->unlockScreen();
+ _screen->addDirtyRect(Common::Rect(0x25, 0x167, 0xe4, 0x1c7));
}
if (present)
- g_system->updateScreen();
+ _screen->update();
}
void BCBEngine::handleSceneChoiceKeyboardSelection(int choiceNumber) {
diff --git a/engines/harold/bcb/screens.cpp b/engines/harold/bcb/screens.cpp
index 2bd2dd08009..657e973bbd6 100644
--- a/engines/harold/bcb/screens.cpp
+++ b/engines/harold/bcb/screens.cpp
@@ -21,6 +21,7 @@
#include "common/system.h"
#include "graphics/font.h"
+#include "graphics/screen.h"
#include "graphics/surface.h"
#include "harold/bcb/bcb.h"
#include "harold/bcb/script.h"
@@ -80,10 +81,10 @@ void BCBEngine::restoreTitleScreenRegion(const Common::Rect &rect, bool present)
if (clippedRect.isEmpty())
return;
- g_system->copyRectToScreen(_titleBitmap->getBasePtr(clippedRect.left, clippedRect.top), _titleBitmap->pitch,
+ _screen->copyRectToSurface(_titleBitmap->getBasePtr(clippedRect.left, clippedRect.top), _titleBitmap->pitch,
clippedRect.left, clippedRect.top, clippedRect.width(), clippedRect.height());
if (present)
- g_system->updateScreen();
+ _screen->update();
}
bool BCBEngine::transitionFromLogoToTitle() {
@@ -100,8 +101,8 @@ bool BCBEngine::startNewGameFromTitle() {
stopMenuBackgroundMusic();
stopGameOverMusic();
- g_system->fillScreen(g_system->getScreenFormat().RGBToColor(0, 0, 0));
- g_system->updateScreen();
+ _screen->clear(g_system->getScreenFormat().RGBToColor(0, 0, 0));
+ _screen->update();
stateDword(kStateCurrentScriptSequenceId) = 1;
return playNumberedMovie(6, true);
@@ -123,8 +124,8 @@ bool BCBEngine::showPendingStillImageScreen() {
if (!still)
return false;
- g_system->copyRectToScreen(still->getPixels(), still->pitch, 0, 0, MIN<int>(still->w, 640), MIN<int>(still->h, 480));
- g_system->updateScreen();
+ _screen->copyRectToSurface(still->getPixels(), still->pitch, 0, 0, MIN<int>(still->w, 640), MIN<int>(still->h, 480));
+ _screen->update();
still->free();
delete still;
@@ -198,7 +199,7 @@ bool BCBEngine::dispatchGameOverScreen() {
_currentDisplayState = 9;
_gameOverElapsedTimerTicks = 0;
_lastTimerTick = g_system->getMillis();
- g_system->updateScreen();
+ _screen->update();
startGameOverMusic();
return true;
}
@@ -219,8 +220,8 @@ void BCBEngine::drawTitleButton(uint buttonIndex, bool pressed) {
if (sourceX + button.width > _titleBitmap->w || sourceY + button.height > _titleBitmap->h)
return;
- g_system->copyRectToScreen(_titleBitmap->getBasePtr(sourceX, sourceY), _titleBitmap->pitch, button.x, button.y, button.width, button.height);
- g_system->updateScreen();
+ _screen->copyRectToSurface(_titleBitmap->getBasePtr(sourceX, sourceY), _titleBitmap->pitch, button.x, button.y, button.width, button.height);
+ _screen->update();
}
void BCBEngine::blitScreenWithLineReveal(const Graphics::Surface &surface, bool presentFinalFrame) {
@@ -244,13 +245,13 @@ void BCBEngine::blitScreenWithLineReveal(const Graphics::Surface &surface, bool
const int topY = revealedSteps * 2;
const int bottomY = height - 1 - revealedSteps * 2;
if (topY < height)
- g_system->copyRectToScreen(surface.getBasePtr(0, topY), surface.pitch, 0, topY, width, 1);
+ _screen->copyRectToSurface(surface.getBasePtr(0, topY), surface.pitch, 0, topY, width, 1);
if (bottomY != topY && bottomY >= 0)
- g_system->copyRectToScreen(surface.getBasePtr(0, bottomY), surface.pitch, 0, bottomY, width, 1);
+ _screen->copyRectToSurface(surface.getBasePtr(0, bottomY), surface.pitch, 0, bottomY, width, 1);
}
if (presentFinalFrame || revealedSteps < revealSteps)
- g_system->updateScreen();
+ _screen->update();
pollEvents(false);
}
}
diff --git a/engines/harold/bcb/video.cpp b/engines/harold/bcb/video.cpp
index 24d959a5901..ed8946dd0ee 100644
--- a/engines/harold/bcb/video.cpp
+++ b/engines/harold/bcb/video.cpp
@@ -23,6 +23,7 @@
#include "audio/decoders/wave.h"
#include "common/system.h"
#include "common/textconsole.h"
+#include "graphics/screen.h"
#include "graphics/surface.h"
#include "harold/bcb/bcb.h"
#include "harold/bcb/script.h"
@@ -83,21 +84,20 @@ void BCBEngine::drawMapSubmodeMovieBackdrop(bool present) {
const Common::Rect sourceRect(kMapSubmodeMovieBackdropSourceX, kMapSubmodeMovieBackdropSourceY,
kMapSubmodeMovieBackdropSourceX + 0x1a4, kMapSubmodeMovieBackdropSourceY + 0x11e);
if (sourceRect.right <= map->w && sourceRect.bottom <= map->h)
- g_system->copyRectToScreen(map->getBasePtr(sourceRect.left, sourceRect.top), map->pitch,
+ _screen->copyRectToSurface(map->getBasePtr(sourceRect.left, sourceRect.top), map->pitch,
kMapSubmodeMovieBackdropRect.left, kMapSubmodeMovieBackdropRect.top,
sourceRect.width(), sourceRect.height());
} else {
- Graphics::Surface *screen = g_system->lockScreen();
+ Graphics::Surface *screen = _screen->surfacePtr();
if (!screen)
return;
const uint32 backingColor = screen->format.RGBToColor(0, 0, 0);
- screen->fillRect(kMapSubmodeMciControlHitRect, backingColor);
- g_system->unlockScreen();
+ _screen->fillRect(kMapSubmodeMciControlHitRect, backingColor);
}
if (present)
- g_system->updateScreen();
+ _screen->update();
}
void BCBEngine::drawMovieControlStrip(int sourceX, bool present) {
@@ -107,9 +107,9 @@ void BCBEngine::drawMovieControlStrip(int sourceX, bool present) {
const Graphics::Surface *chip = getUiBitmap("16mchip.bmp");
if (chip) {
if (sourceX + 0x98 <= chip->w && 0xe0 + 0x30 <= chip->h)
- g_system->copyRectToScreen(chip->getBasePtr(sourceX, 0xe0), chip->pitch, 0x104, 0x199, 0x98, 0x30);
+ _screen->copyRectToSurface(chip->getBasePtr(sourceX, 0xe0), chip->pitch, 0x104, 0x199, 0x98, 0x30);
if (present)
- g_system->updateScreen();
+ _screen->update();
}
}
@@ -117,9 +117,9 @@ void BCBEngine::drawSceneChoiceControlStrip(int sourceX, bool present) {
const Graphics::Surface *chip = getUiBitmap("16mchip.bmp");
if (chip) {
if (sourceX + 0x98 <= chip->w && 0xb0 + 0x30 <= chip->h)
- g_system->copyRectToScreen(chip->getBasePtr(sourceX, 0xb0), chip->pitch, 0x104, 0x199, 0x98, 0x30);
+ _screen->copyRectToSurface(chip->getBasePtr(sourceX, 0xb0), chip->pitch, 0x104, 0x199, 0x98, 0x30);
if (present)
- g_system->updateScreen();
+ _screen->update();
}
}
@@ -140,12 +140,12 @@ void BCBEngine::processQueuedMovieSequence() {
applyPendingClockTickDelta();
renderClockAndChipToSurface(*map, 0, stateDword(kStateGameClockTicks));
composeMapControlsToSurface(*map, *map);
- g_system->copyRectToScreen(map->getPixels(), map->pitch, 0, 0, MIN<int>(map->w, 640), MIN<int>(map->h, 480));
+ _screen->copyRectToSurface(map->getPixels(), map->pitch, 0, 0, MIN<int>(map->w, 640), MIN<int>(map->h, 480));
map->free();
delete map;
}
drawSceneChoiceControlStrip(0, false);
- g_system->updateScreen();
+ _screen->update();
}
processGameStateDispatcher(false);
@@ -232,6 +232,19 @@ void BCBEngine::startMenuBackgroundMusic() {
_menuBackgroundVideo->start();
}
+void BCBEngine::updateMenuBackgroundMusic() {
+ if (!_menuBackgroundVideo)
+ return;
+ if (_menuBackgroundVideo->endOfVideo()) {
+ _menuBackgroundVideo->stop();
+ _menuBackgroundVideo->rewind();
+ _menuBackgroundVideo->decodeNextFrame();
+ _menuBackgroundVideo->start();
+ }
+ if (_menuBackgroundVideo->needsUpdate())
+ _menuBackgroundVideo->decodeNextFrame();
+}
+
void BCBEngine::stopMenuBackgroundMusic() {
if (_menuBackgroundVideo) {
delete _menuBackgroundVideo;
Commit: e7e04ce66dfd7e44c7b59c04b37516978b7cd58f
https://github.com/scummvm/scummvm/commit/e7e04ce66dfd7e44c7b59c04b37516978b7cd58f
Author: Scorp (scorp at mrs.mn)
Date: 2026-10-22T18:57:52+02:00
Commit Message:
HAROLD: BCB: Fix volume input and open System with mapped Escape
Changed paths:
engines/harold/bcb/input.cpp
engines/harold/bcb/saveload.cpp
diff --git a/engines/harold/bcb/input.cpp b/engines/harold/bcb/input.cpp
index ec6ba1f6d4c..7b3712cd32e 100644
--- a/engines/harold/bcb/input.cpp
+++ b/engines/harold/bcb/input.cpp
@@ -233,6 +233,11 @@ void BCBEngine::handleKeymapperAction(Common::CustomEventType action) {
}
bool BCBEngine::handleEscapeAction() {
+ if ((_currentDisplayState == 1 || _currentDisplayState == 2) && !_activeMovieVideo) {
+ handleLeftButtonDown(Common::Point(kSystemMenuControlHitRect.left, kSystemMenuControlHitRect.top));
+ return true;
+ }
+
if (_currentDisplayState == 3) {
activateSystemExitButton();
return true;
diff --git a/engines/harold/bcb/saveload.cpp b/engines/harold/bcb/saveload.cpp
index 70b8c5ee21e..89a76adaa92 100644
--- a/engines/harold/bcb/saveload.cpp
+++ b/engines/harold/bcb/saveload.cpp
@@ -48,7 +48,6 @@ enum {
static const StaticRect kSystemWaveVolumeDownRect = {268, 407, 290, 439};
static const StaticRect kSystemWaveVolumeUpRect = {444, 407, 466, 439};
-static const StaticRect kSystemWaveVolumeSeekRect = {294, 414, 442, 439};
static const StaticRect kSystemWaveVolumeIndicatorRect = {294, 425, 442, 449};
static const Common::Point kSystemSubmodeNormalSources[5] = {
@@ -333,7 +332,7 @@ void BCBEngine::drawSystemWaveVolumeIndicator(bool present) {
}
bool BCBEngine::handleSystemWaveVolumeClick(const Common::Point &mousePosition) {
- if (kSystemWaveVolumeSeekRect.contains(mousePosition)) {
+ if (kSystemWaveVolumeIndicatorRect.contains(mousePosition)) {
const int seekX = CLIP<int>(mousePosition.x - kSystemWaveVolumeIndicatorRect.left, 0, kSystemWaveVolumeIndicatorRect.width());
const int volume = CLIP<int>((seekX * 0xffff) / kSystemWaveVolumeIndicatorRect.width(), 0, 0xffff);
if (volume != _systemWaveVolume) {
@@ -374,6 +373,8 @@ bool BCBEngine::handleSystemWaveVolumeClick(const Common::Point &mousePosition)
} else if (event.type == Common::EVENT_LBUTTONUP) {
_mousePosition = event.mouse;
done = true;
+ } else if (event.type == Common::EVENT_CUSTOM_ENGINE_ACTION_END && event.customType == kBCBActionInteract) {
+ done = true;
}
}
serviceCursorRefresh();
Commit: ac076b0fa2b839b54382ecbc7c2d8d63233f3334
https://github.com/scummvm/scummvm/commit/ac076b0fa2b839b54382ecbc7c2d8d63233f3334
Author: Scorp (scorp at mrs.mn)
Date: 2026-10-22T18:57:52+02:00
Commit Message:
HAROLD: BCB: Rename game to match official naming
Changed paths:
engines/harold/bcb/captions.cpp
engines/harold/bcb/computer.cpp
engines/harold/bcb/executable_data.cpp
engines/harold/bcb/map.cpp
engines/harold/configure.engine
engines/harold/detection.cpp
engines/harold/detection_tables.h
engines/harold/metaengine.cpp
diff --git a/engines/harold/bcb/captions.cpp b/engines/harold/bcb/captions.cpp
index 1b868c28681..0866c348a0e 100644
--- a/engines/harold/bcb/captions.cpp
+++ b/engines/harold/bcb/captions.cpp
@@ -58,7 +58,7 @@ const byte *BCBEngine::findAllBinRecordData(const Common::String &recordName, ui
uint nameLength = 0;
while (nameLength < 0x20 && _allBinData[entryOffset + 8 + nameLength] != 0)
++nameLength;
- const Common::String entryName((const char *)&_allBinData[entryOffset + 8], nameLength);
+ const Common::String entryName(reinterpret_cast<const char *>(&_allBinData[entryOffset + 8]), nameLength);
if (!recordName.equals(entryName))
continue;
@@ -115,7 +115,7 @@ static bool isCaptionEdgeSpace(byte c) {
}
static void trimTimedCaptionLine(Common::String &line) {
- const byte *text = (const byte *)line.c_str();
+ const byte *text = reinterpret_cast<const byte *>(line.c_str());
uint start = 0;
uint end = line.size();
diff --git a/engines/harold/bcb/computer.cpp b/engines/harold/bcb/computer.cpp
index 4069614da11..91a01cb5a1d 100644
--- a/engines/harold/bcb/computer.cpp
+++ b/engines/harold/bcb/computer.cpp
@@ -163,7 +163,7 @@ void BCBEngine::drawXorRectangleBorder(const Common::Rect &rect) {
const Common::Rect clipped = rect.findIntersectingRect(Common::Rect(0, 0, screen->w, screen->h));
if (clipped.width() > 0 && clipped.height() > 0) {
const auto invertPixel = [&](int x, int y) {
- byte *pixel = (byte *)screen->getBasePtr(x, y);
+ byte *pixel = static_cast<byte *>(screen->getBasePtr(x, y));
switch (screen->format.bytesPerPixel) {
case 1:
pixel[0] ^= 0xff;
diff --git a/engines/harold/bcb/executable_data.cpp b/engines/harold/bcb/executable_data.cpp
index b5d37e61074..f40bd6be992 100644
--- a/engines/harold/bcb/executable_data.cpp
+++ b/engines/harold/bcb/executable_data.cpp
@@ -150,7 +150,7 @@ static bool readExecutableString(const Common::Array<byte> &executableData, uint
if (textEnd == textOffset)
return false;
- sjisText = Common::String((const char *)&executableData[textOffset], textEnd - textOffset);
+ sjisText = Common::String(reinterpret_cast<const char *>(&executableData[textOffset]), textEnd - textOffset);
return true;
}
@@ -224,7 +224,7 @@ bool BCBEngine::loadExecutableDataTables() {
if (textEnd == textOffset)
break;
- const char *textStart = (const char *)&executableData[textOffset];
+ const char *textStart = reinterpret_cast<const char *>(&executableData[textOffset]);
_sceneChoiceText[setId].push_back(Common::U32String(textStart, textEnd - textOffset, profile.encoding));
}
}
@@ -255,7 +255,7 @@ bool BCBEngine::loadExecutableDataTables() {
if (textEnd == textOffset)
continue;
- const char *textStart = (const char *)&executableData[textOffset];
+ const char *textStart = reinterpret_cast<const char *>(&executableData[textOffset]);
_mapButtonTextSjis[i] = Common::String(textStart, textEnd - textOffset);
_mapButtonText[i] = Common::U32String(textStart, textEnd - textOffset, profile.encoding);
}
@@ -280,7 +280,7 @@ bool BCBEngine::loadExecutableDataTables() {
if (textEnd == textOffset)
continue;
- const char *textStart = (const char *)&executableData[textOffset];
+ const char *textStart = reinterpret_cast<const char *>(&executableData[textOffset]);
_gameOverText[i] = Common::U32String(textStart, textEnd - textOffset, profile.encoding);
}
diff --git a/engines/harold/bcb/map.cpp b/engines/harold/bcb/map.cpp
index 3e7ffa61acd..51943408a13 100644
--- a/engines/harold/bcb/map.cpp
+++ b/engines/harold/bcb/map.cpp
@@ -137,7 +137,7 @@ void BCBEngine::composeMapControlsToSurface(Graphics::Surface &surface, const Gr
const int textWidth = MAX<int>(0, surface.w - textX - 2);
if (_gameDescription->desc.language == Common::JA_JPN && _sjisFont) {
int x = textX + 1;
- for (const byte *text = (const byte *)_mapButtonTextSjis[i].c_str(); *text;) {
+ for (const byte *text = reinterpret_cast<const byte *>(_mapButtonTextSjis[i].c_str()); *text;) {
const byte firstByte = *text++;
uint16 sjisChar = firstByte;
if (((firstByte >= 0x81 && firstByte <= 0x9f) || (firstByte >= 0xe0 && firstByte <= 0xef)) && *text)
@@ -146,7 +146,7 @@ void BCBEngine::composeMapControlsToSurface(Graphics::Surface &surface, const Gr
x += _sjisFont->getCharWidth(sjisChar);
}
x = textX;
- for (const byte *text = (const byte *)_mapButtonTextSjis[i].c_str(); *text;) {
+ for (const byte *text = reinterpret_cast<const byte *>(_mapButtonTextSjis[i].c_str()); *text;) {
const byte firstByte = *text++;
uint16 sjisChar = firstByte;
if (((firstByte >= 0x81 && firstByte <= 0x9f) || (firstByte >= 0xe0 && firstByte <= 0xef)) && *text)
diff --git a/engines/harold/configure.engine b/engines/harold/configure.engine
index 14c4908336b..2dd66cc47a6 100644
--- a/engines/harold/configure.engine
+++ b/engines/harold/configure.engine
@@ -1,4 +1,4 @@
# This file is included from the main "configure" script
# add_engine [name] [desc] [build-by-default] [subengines] [base games] [deps] [components]
add_engine harold "J.B. Harold series" no "bcb"
-add_engine bcb "Blue Chicago Blues" no "" "" "" "msmpeg4"
+add_engine bcb "J.B. Harold: Blue Chicago Blues" no "" "" "" "msmpeg4"
diff --git a/engines/harold/detection.cpp b/engines/harold/detection.cpp
index fbe08e10e6f..478587301ac 100644
--- a/engines/harold/detection.cpp
+++ b/engines/harold/detection.cpp
@@ -27,7 +27,7 @@
#include "harold/detection_tables.h"
static const PlainGameDescriptor haroldGames[] = {
- {"bcb", "Blue Chicago Blues"},
+ {"bcb", "J.B. Harold: Blue Chicago Blues"},
{nullptr, nullptr}};
static const DebugChannelDef debugFlagList[] = {
diff --git a/engines/harold/detection_tables.h b/engines/harold/detection_tables.h
index 2ec09feb116..483c0501661 100644
--- a/engines/harold/detection_tables.h
+++ b/engines/harold/detection_tables.h
@@ -41,6 +41,7 @@ static const HaroldGameDescription gameDescriptions[] = {
},
{
+ // Blue Chicago Blues Korean Windows release.
{
"bcb",
nullptr,
diff --git a/engines/harold/metaengine.cpp b/engines/harold/metaengine.cpp
index 1b5ffc18146..a30fcf0f31b 100644
--- a/engines/harold/metaengine.cpp
+++ b/engines/harold/metaengine.cpp
@@ -46,7 +46,7 @@ Common::KeymapArray HaroldMetaEngine::initKeymaps(const char *target) const {
using namespace Common;
using namespace BCB;
- Keymap *engineKeyMap = new Keymap(Keymap::kKeymapTypeGame, "bcb-default", _("Blue Chicago Blues"));
+ Keymap *engineKeyMap = new Keymap(Keymap::kKeymapTypeGame, "bcb-default", _("J.B. Harold: Blue Chicago Blues"));
Action *act;
act = new Action("INTERACT", _("Interact"));
@@ -76,13 +76,11 @@ Common::KeymapArray HaroldMetaEngine::initKeymaps(const char *target) const {
static const char *const choiceActionIds[] = {
"CHOICE1", "CHOICE2", "CHOICE3", "CHOICE4",
- "CHOICE5", "CHOICE6", "CHOICE7", "CHOICE8"
- };
+ "CHOICE5", "CHOICE6", "CHOICE7", "CHOICE8"};
static const char *const choiceActionLabels[] = {
_s("Choice 1"), _s("Choice 2"), _s("Choice 3"),
_s("Choice 4"), _s("Choice 5"), _s("Choice 6"),
- _s("Choice 7"), _s("Choice 8")
- };
+ _s("Choice 7"), _s("Choice 8")};
for (int choiceIndex = 1; choiceIndex <= 8; ++choiceIndex) {
act = new Action(choiceActionIds[choiceIndex - 1], _(choiceActionLabels[choiceIndex - 1]));
Commit: f0772668a7011da60b0f36d6e3738f72ef060ba4
https://github.com/scummvm/scummvm/commit/f0772668a7011da60b0f36d6e3738f72ef060ba4
Author: Scorp (scorp at mrs.mn)
Date: 2026-10-22T18:57:52+02:00
Commit Message:
HAROLD: BCB: Use shared font loading and address engine integration review
Changed paths:
A engines/harold/POTFILES
engines/harold/bcb/bcb.cpp
engines/harold/bcb/computer.cpp
engines/harold/bcb/scene.cpp
engines/harold/bcb/screens.cpp
engines/harold/configure.engine
engines/harold/metaengine.cpp
engines/harold/module.mk
diff --git a/engines/harold/POTFILES b/engines/harold/POTFILES
new file mode 100644
index 00000000000..10f9e1eb1e2
--- /dev/null
+++ b/engines/harold/POTFILES
@@ -0,0 +1 @@
+engines/harold/metaengine.cpp
diff --git a/engines/harold/bcb/bcb.cpp b/engines/harold/bcb/bcb.cpp
index 00b92856c69..36edbf08992 100644
--- a/engines/harold/bcb/bcb.cpp
+++ b/engines/harold/bcb/bcb.cpp
@@ -20,7 +20,6 @@
*/
#include "harold/bcb/bcb.h"
-#include "common/compression/unzip.h"
#include "common/config-manager.h"
#include "common/system.h"
#include "common/textconsole.h"
@@ -42,66 +41,42 @@ namespace BCB {
static const int kCaptionTextNativeCharacterHeight = 18;
static const int kOwnerDrawUiTextNativeCharacterHeight = 16;
-static Graphics::Font *loadFont(Common::Archive &archive, const char *name, int size, Graphics::TTFSizeMode sizeMode) {
- Common::SeekableReadStream *stream = archive.createReadStreamForMember(Common::Path(name));
- if (!stream)
- return nullptr;
- Graphics::Font *font = Graphics::loadTTFFont(stream, DisposeAfterUse::YES, size, sizeMode, 0, 0, Graphics::kTTFRenderModeNormal);
- if (!font)
- delete stream;
- return font;
-}
-
-static Graphics::Font *loadLocalizedCjkFont(Common::Archive &archive, Common::Language language, int size, Graphics::TTFSizeMode sizeMode) {
+static Graphics::Font *loadLocalizedCjkFont(Common::Language language, int size, Graphics::TTFSizeMode sizeMode) {
if (language == Common::KO_KOR) {
- Graphics::Font *font = loadFont(archive, "NotoSansKR-Regular.otf", size, sizeMode);
+ Graphics::Font *font = Graphics::loadTTFFontFromArchive("NotoSansKR-Regular.otf", size, sizeMode, 0, 0, Graphics::kTTFRenderModeNormal);
if (font)
return font;
- font = loadFont(archive, "NotoSansJP-Regular.otf", size, sizeMode);
+ font = Graphics::loadTTFFontFromArchive("NotoSansJP-Regular.otf", size, sizeMode, 0, 0, Graphics::kTTFRenderModeNormal);
if (font)
return font;
- return loadFont(archive, "VL-Gothic-Regular.ttf", size, sizeMode);
+ return Graphics::loadTTFFontFromArchive("VL-Gothic-Regular.ttf", size, sizeMode, 0, 0, Graphics::kTTFRenderModeNormal);
}
- Graphics::Font *font = loadFont(archive, "VL-Gothic-Regular.ttf", size, sizeMode);
+ Graphics::Font *font = Graphics::loadTTFFontFromArchive("VL-Gothic-Regular.ttf", size, sizeMode, 0, 0, Graphics::kTTFRenderModeNormal);
if (font)
return font;
- return loadFont(archive, "NotoSansJP-Regular.otf", size, sizeMode);
+ return Graphics::loadTTFFontFromArchive("NotoSansJP-Regular.otf", size, sizeMode, 0, 0, Graphics::kTTFRenderModeNormal);
}
-static Graphics::Font *loadLocalizedOwnerDrawFont(Common::Archive &archive, Common::Language language) {
+static Graphics::Font *loadLocalizedOwnerDrawFont(Common::Language language) {
if (language == Common::KO_KOR) {
- Graphics::Font *font = loadFont(archive, "NotoSansKR-Regular.otf", kOwnerDrawUiTextNativeCharacterHeight, Graphics::kTTFSizeModeCharacter);
+ Graphics::Font *font = Graphics::loadTTFFontFromArchive("NotoSansKR-Regular.otf", kOwnerDrawUiTextNativeCharacterHeight, Graphics::kTTFSizeModeCharacter, 0, 0, Graphics::kTTFRenderModeNormal);
if (font)
return font;
}
- Graphics::Font *font = loadFont(archive, "NotoSansJP-Regular.otf", kOwnerDrawUiTextNativeCharacterHeight, Graphics::kTTFSizeModeCharacter);
+ Graphics::Font *font = Graphics::loadTTFFontFromArchive("NotoSansJP-Regular.otf", kOwnerDrawUiTextNativeCharacterHeight, Graphics::kTTFSizeModeCharacter, 0, 0, Graphics::kTTFRenderModeNormal);
if (font)
return font;
- return loadFont(archive, "VL-Gothic-Regular.ttf", kOwnerDrawUiTextNativeCharacterHeight, Graphics::kTTFSizeModeCharacter);
+ return Graphics::loadTTFFontFromArchive("VL-Gothic-Regular.ttf", kOwnerDrawUiTextNativeCharacterHeight, Graphics::kTTFSizeModeCharacter, 0, 0, Graphics::kTTFRenderModeNormal);
}
#endif
void BCBEngine::loadFonts() {
#ifdef USE_FREETYPE2
- Common::SearchSet fonts;
- const char *archiveNames[] = {"fonts.dat", "fonts-cjk.dat"};
- for (const char *name : archiveNames) {
- Common::SeekableReadStream *stream = nullptr;
- if (ConfMan.hasKey("extrapath")) {
- Common::FSDirectory directory(ConfMan.getPath("extrapath"));
- stream = directory.createReadStreamForMember(name);
- }
- if (!stream)
- stream = SearchMan.createReadStreamForMember(name);
- Common::Archive *archive = Common::makeZipArchive(stream);
- if (archive)
- fonts.add(name, archive);
- }
- _captionTextFont = loadLocalizedCjkFont(fonts, _gameDescription->desc.language, kCaptionTextNativeCharacterHeight, Graphics::kTTFSizeModeCharacter);
- _gameOverTextFont = loadLocalizedCjkFont(fonts, _gameDescription->desc.language, 20, Graphics::kTTFSizeModeCell);
- _ownerDrawUiTextFont = loadLocalizedOwnerDrawFont(fonts, _gameDescription->desc.language);
+ _captionTextFont = loadLocalizedCjkFont(_gameDescription->desc.language, kCaptionTextNativeCharacterHeight, Graphics::kTTFSizeModeCharacter);
+ _gameOverTextFont = loadLocalizedCjkFont(_gameDescription->desc.language, 20, Graphics::kTTFSizeModeCell);
+ _ownerDrawUiTextFont = loadLocalizedOwnerDrawFont(_gameDescription->desc.language);
#endif
}
diff --git a/engines/harold/bcb/computer.cpp b/engines/harold/bcb/computer.cpp
index 91a01cb5a1d..0ca4f44a1eb 100644
--- a/engines/harold/bcb/computer.cpp
+++ b/engines/harold/bcb/computer.cpp
@@ -26,8 +26,6 @@
#include "graphics/screen.h"
#include "graphics/surface.h"
-#include <math.h>
-
#include "harold/bcb/bcb.h"
#include "harold/bcb/script.h"
#include "harold/bcb/static_data.h"
@@ -172,10 +170,6 @@ void BCBEngine::drawXorRectangleBorder(const Common::Rect &rect) {
WRITE_UINT16(pixel, READ_UINT16(pixel) ^ 0xffff);
break;
case 3:
- pixel[0] ^= 0xff;
- pixel[1] ^= 0xff;
- pixel[2] ^= 0xff;
- break;
case 4:
pixel[0] ^= 0xff;
pixel[1] ^= 0xff;
diff --git a/engines/harold/bcb/scene.cpp b/engines/harold/bcb/scene.cpp
index e810225c2bb..ec3f4b37850 100644
--- a/engines/harold/bcb/scene.cpp
+++ b/engines/harold/bcb/scene.cpp
@@ -30,8 +30,6 @@
#include "harold/bcb/static_data.h"
#include "video/avi_decoder.h"
-#include <math.h>
-
namespace BCB {
bool BCBEngine::showMainSceneScreen() {
diff --git a/engines/harold/bcb/screens.cpp b/engines/harold/bcb/screens.cpp
index 657e973bbd6..819d238faec 100644
--- a/engines/harold/bcb/screens.cpp
+++ b/engines/harold/bcb/screens.cpp
@@ -169,7 +169,7 @@ bool BCBEngine::dispatchGameOverScreen() {
for (uint lineStart = 0; lineStart < text.size() && y < textRect.bottom;) {
uint lineEnd = lineStart;
uint lastFit = lineStart;
- while (lineEnd < text.size() && text[lineEnd] != U'\n') {
+ while (lineEnd < text.size() && text[lineEnd] != '\n') {
++lineEnd;
const Common::U32String candidate(text.c_str() + lineStart, lineEnd - lineStart);
if (_gameOverTextFont->getStringWidth(candidate) <= textRect.width())
@@ -180,13 +180,13 @@ bool BCBEngine::dispatchGameOverScreen() {
if (lastFit == lineStart && lineEnd > lineStart)
lastFit = lineStart + 1;
- if (lineEnd < text.size() && text[lineEnd] == U'\n')
+ if (lineEnd < text.size() && text[lineEnd] == '\n')
lastFit = lineEnd;
_gameOverTextFont->drawString(gameOver, Common::U32String(text.c_str() + lineStart, lastFit - lineStart), textRect.left, y, textRect.width(), white, Graphics::kTextAlignLeft);
y += _gameOverTextFont->getFontHeight() + kGameOverTextLineGap;
lineStart = lastFit;
- if (lineStart < text.size() && text[lineStart] == U'\n')
+ if (lineStart < text.size() && text[lineStart] == '\n')
++lineStart;
}
}
diff --git a/engines/harold/configure.engine b/engines/harold/configure.engine
index 2dd66cc47a6..365da369e28 100644
--- a/engines/harold/configure.engine
+++ b/engines/harold/configure.engine
@@ -1,4 +1,4 @@
# This file is included from the main "configure" script
# add_engine [name] [desc] [build-by-default] [subengines] [base games] [deps] [components]
add_engine harold "J.B. Harold series" no "bcb"
-add_engine bcb "J.B. Harold: Blue Chicago Blues" no "" "" "" "msmpeg4"
+add_engine bcb "J.B. Harold: Blue Chicago Blues" no "" "" "highres 16bit" "msmpeg4"
diff --git a/engines/harold/metaengine.cpp b/engines/harold/metaengine.cpp
index a30fcf0f31b..eecf771f179 100644
--- a/engines/harold/metaengine.cpp
+++ b/engines/harold/metaengine.cpp
@@ -38,10 +38,15 @@ public:
}
int getMaximumSaveSlot() const override { return 8; }
+ bool hasFeature(MetaEngineFeature f) const override;
Common::KeymapArray initKeymaps(const char *target) const override;
Common::Error createInstance(OSystem *syst, Engine **engine, const BCB::HaroldGameDescription *gd) const override;
};
+bool HaroldMetaEngine::hasFeature(MetaEngineFeature f) const {
+ return checkExtendedSaves(f);
+}
+
Common::KeymapArray HaroldMetaEngine::initKeymaps(const char *target) const {
using namespace Common;
using namespace BCB;
diff --git a/engines/harold/module.mk b/engines/harold/module.mk
index 276b4a8c8a1..291366b581d 100644
--- a/engines/harold/module.mk
+++ b/engines/harold/module.mk
@@ -2,19 +2,19 @@ MODULE := engines/harold
MODULE_OBJS := \
bcb/bcb.o \
- bcb/resources.o \
- bcb/video.o \
bcb/captions.o \
- bcb/scene.o \
- bcb/screens.o \
- bcb/input.o \
bcb/computer.o \
bcb/debugger.o \
- bcb/saveload.o \
+ bcb/executable_data.o \
+ bcb/input.o \
bcb/map.o \
+ bcb/resources.o \
+ bcb/saveload.o \
+ bcb/scene.o \
+ bcb/screens.o \
bcb/script.o \
- bcb/executable_data.o \
bcb/static_data.o \
+ bcb/video.o \
metaengine.o
MODULE_DIRS += \
More information about the Scummvm-git-logs
mailing list