[Scummvm-git-logs] scummvm master -> e0b8712b63e2f97896bb92ce2a7520c0b9975b1a
mgerhardy
noreply at scummvm.org
Sun Jul 5 20:12:08 UTC 2026
This automated email contains information about 3 new commits which have been
pushed to the 'scummvm' repo located at https://api.github.com/repos/scummvm/scummvm .
Summary:
ede8e9237f MACS2: add binding for help menu
913b66dbec MACS2: helper methods
e0b8712b63 MACS2: implemented scumm verb ui hidden behind an enhancement
Commit: ede8e9237fae563cbce035910edd1e8a403a9157
https://github.com/scummvm/scummvm/commit/ede8e9237fae563cbce035910edd1e8a403a9157
Author: Martin Gerhardy (martin.gerhardy at gmail.com)
Date: 2026-07-05T22:11:43+02:00
Commit Message:
MACS2: add binding for help menu
Changed paths:
engines/macs2/detection.h
engines/macs2/metaengine.cpp
engines/macs2/view1.cpp
engines/macs2/view1.h
diff --git a/engines/macs2/detection.h b/engines/macs2/detection.h
index cf807d965a3..52f60774382 100644
--- a/engines/macs2/detection.h
+++ b/engines/macs2/detection.h
@@ -44,6 +44,7 @@ enum Macs2Action {
kMacs2ActionMenu,
kMacs2ActionGameSpeed,
kMacs2ActionOpenGMM,
+ kMacs2ActionHelp,
};
extern const PlainGameDescriptor macs2Games[];
diff --git a/engines/macs2/metaengine.cpp b/engines/macs2/metaengine.cpp
index 390c94e9780..3be0bda1883 100644
--- a/engines/macs2/metaengine.cpp
+++ b/engines/macs2/metaengine.cpp
@@ -82,6 +82,11 @@ Common::KeymapArray Macs2MetaEngine::initKeymaps(const char *target) const {
act->addDefaultInputMapping("F5");
engineKeyMap->addAction(act);
+ act = new Action("HELP", _("Help / map"));
+ act->setCustomEngineActionEvent(kMacs2ActionHelp);
+ act->addDefaultInputMapping("F1");
+ engineKeyMap->addAction(act);
+
return Keymap::arrayOf(engineKeyMap);
}
diff --git a/engines/macs2/view1.cpp b/engines/macs2/view1.cpp
index 280a8d6d5dc..0417d9962f4 100644
--- a/engines/macs2/view1.cpp
+++ b/engines/macs2/view1.cpp
@@ -263,7 +263,7 @@ int View1::findInventoryItem(const GameObject *item) {
return -1;
}
-Character *View1::getCharacterByIndex(uint16 index) {
+Character *View1::getCharacterByIndex(uint16 index) const {
if (index > 0 && index <= kMaxSceneObjects) {
Character *c = _characterByObjectIndex[index];
if (c != nullptr && c->_gameObject != nullptr && c->_gameObject->_index == index)
@@ -1824,6 +1824,13 @@ bool View1::msgAction(const ActionMessage &msg) {
g_engine->openMainMenuDialog();
updateCursor();
return true;
+ case Macs2::kMacs2ActionHelp:
+ if (!_helpButtonDisabled && _currentMode != ViewMode::VM_HELP &&
+ !g_engine->_scriptExecutor->isExecuting() &&
+ g_engine->_scriptExecutor->_cursorMode != Script::MouseMode::Disabled) {
+ enterMapMode();
+ }
+ return true;
default:
break;
}
diff --git a/engines/macs2/view1.h b/engines/macs2/view1.h
index 37faf599802..3ee9e392997 100644
--- a/engines/macs2/view1.h
+++ b/engines/macs2/view1.h
@@ -481,7 +481,7 @@ public:
void transferInventoryItem(GameObject *item, GameObject *targetContainer);
- Character *getCharacterByIndex(uint16 index);
+ Character *getCharacterByIndex(uint16 index) const;
void rebuildCharacterLookupTable() const;
int getCharacterArrayIndex(const Character *c) const;
Commit: 913b66dbec34842a0f56d97efad8cc1d82a33043
https://github.com/scummvm/scummvm/commit/913b66dbec34842a0f56d97efad8cc1d82a33043
Author: Martin Gerhardy (martin.gerhardy at gmail.com)
Date: 2026-07-05T22:11:43+02:00
Commit Message:
MACS2: helper methods
Changed paths:
engines/macs2/view1.cpp
engines/macs2/view1.h
diff --git a/engines/macs2/view1.cpp b/engines/macs2/view1.cpp
index 0417d9962f4..c40149c5c7d 100644
--- a/engines/macs2/view1.cpp
+++ b/engines/macs2/view1.cpp
@@ -510,6 +510,30 @@ void View1::renderString(const Common::Point pos, const Common::String &s) {
renderString(pos.x, pos.y, s);
}
+void View1::renderStringTo(uint16 x, uint16 y, const Common::String &s, Graphics::ManagedSurface &surf) {
+ uint16 currentX = x;
+ uint16 currentY = y;
+
+ uint16 widestGlyph = 1;
+ for (auto iter = s.begin(); iter != s.end(); iter++) {
+ GlyphData data;
+ if (g_engine->findGlyph(*iter, data)) {
+ widestGlyph = MAX(widestGlyph, data._width);
+ }
+ }
+
+ for (auto iter = s.begin(); iter != s.end(); iter++) {
+ GlyphData data;
+ const bool found = g_engine->findGlyph(*iter, data);
+ if (found) {
+ drawSprite(currentX, currentY, data, surf, false);
+ currentX += data._width + 1;
+ } else {
+ currentX += widestGlyph;
+ }
+ }
+}
+
int View1::measureStringWithFont(const Common::String &s, const GlyphData *glyphs, uint16 numGlyphs) {
int width = 0;
uint16 widestGlyph = 1;
@@ -533,6 +557,11 @@ int View1::measureStringWithFont(const Common::String &s, const GlyphData *glyph
void View1::renderStringWithFont(uint16 x, uint16 y, const Common::String &s, const GlyphData *glyphs, uint16 numGlyphs) {
Graphics::ManagedSurface surf = getSurface();
+ renderStringWithFontTo(x, y, s, glyphs, numGlyphs, surf);
+}
+
+void View1::renderStringWithFontTo(uint16 x, uint16 y, const Common::String &s, const GlyphData *glyphs,
+ uint16 numGlyphs, Graphics::ManagedSurface &surf) {
uint16 currentX = x;
uint16 widestGlyph = 1;
for (uint i = 0; i < numGlyphs; i++) {
@@ -2686,6 +2715,49 @@ void View1::drawSpriteClipped(uint16 x, uint16 y, Common::Rect &clippingRect, co
drawSpriteClipped(x, y, clippingRect, sprite._width, sprite._height, sprite._data.data(), s);
}
+void View1::drawSpriteFitted(const Common::Rect &bounds, const Sprite &sprite, Graphics::ManagedSurface &s, uint16 inset) {
+ if (sprite._width == 0 || sprite._height == 0 || sprite._data.empty())
+ return;
+
+ const Common::Rect inner(bounds.left + inset, bounds.top + inset,
+ bounds.right - inset, bounds.bottom - inset);
+ if (inner.width() <= 0 || inner.height() <= 0)
+ return;
+
+ const int destW = inner.width();
+ const int destH = inner.height();
+ const int scaleW = (destW * 256) / sprite._width;
+ const int scaleH = (destH * 256) / sprite._height;
+ const int scale = MIN(scaleW, scaleH);
+ if (scale <= 0)
+ return;
+
+ const int drawW = MAX(1, (sprite._width * scale) / 256);
+ const int drawH = MAX(1, (sprite._height * scale) / 256);
+ const int startX = inner.left + (destW - drawW) / 2;
+ const int startY = inner.top + (destH - drawH) / 2;
+
+ for (int dy = 0; dy < drawH; dy++) {
+ const int srcY = (dy * sprite._height) / drawH;
+ const int py = startY + dy;
+ if (py < inner.top || py >= inner.bottom)
+ continue;
+
+ for (int dx = 0; dx < drawW; dx++) {
+ const int srcX = (dx * sprite._width) / drawW;
+ const uint8 val = sprite._data[srcY * sprite._width + srcX];
+ if (val == 0)
+ continue;
+
+ const int px = startX + dx;
+ if (px < inner.left || px >= inner.right || px < 0 || px >= s.w || py < 0 || py >= s.h)
+ continue;
+
+ s.setPixel(px, py, val);
+ }
+ }
+}
+
static byte applyShadingTable(byte color, int shadingTableOffset) {
if (shadingTableOffset == 0)
return color;
diff --git a/engines/macs2/view1.h b/engines/macs2/view1.h
index 3ee9e392997..41538be1134 100644
--- a/engines/macs2/view1.h
+++ b/engines/macs2/view1.h
@@ -306,7 +306,10 @@ private:
void renderString(uint16 x, uint16 y, const Common::String &s);
void renderString(const Common::Point pos, const Common::String &s);
+ void renderStringTo(uint16 x, uint16 y, const Common::String &s, Graphics::ManagedSurface &surf);
void renderStringWithFont(uint16 x, uint16 y, const Common::String &s, const GlyphData *glyphs, uint16 numGlyphs);
+ void renderStringWithFontTo(uint16 x, uint16 y, const Common::String &s, const GlyphData *glyphs,
+ uint16 numGlyphs, Graphics::ManagedSurface &surf);
int measureStringWithFont(const Common::String &s, const GlyphData *glyphs, uint16 numGlyphs);
Common::Array<Common::Rect> _mainMenuButtonLocations;
@@ -322,6 +325,7 @@ private:
void drawSpriteClipped(uint16 x, uint16 y, Common::Rect &clippingRect, uint16 width, uint16 height, const byte *const data, Graphics::ManagedSurface &s);
void drawSpriteClipped(uint16 x, uint16 y, Common::Rect &clippingRect, const Sprite &sprite, Graphics::ManagedSurface &s);
+ void drawSpriteFitted(const Common::Rect &bounds, const Sprite &sprite, Graphics::ManagedSurface &s, uint16 inset = 6);
// Binary sortObjectListByY (1008:8cf2) + buildSortedObjectList (1008:8c5a):
// scan 512 objects, collect current-scene indices at 0xFAC, quicksort by Y.
Commit: e0b8712b63e2f97896bb92ce2a7520c0b9975b1a
https://github.com/scummvm/scummvm/commit/e0b8712b63e2f97896bb92ce2a7520c0b9975b1a
Author: Martin Gerhardy (martin.gerhardy at gmail.com)
Date: 2026-07-05T22:11:43+02:00
Commit Message:
MACS2: implemented scumm verb ui hidden behind an enhancement
Changed paths:
A engines/macs2/scummui.cpp
A engines/macs2/scummui.h
engines/macs2/macs2.cpp
engines/macs2/macs2.h
engines/macs2/macs2_constants.h
engines/macs2/module.mk
engines/macs2/saveload.cpp
engines/macs2/view1.cpp
engines/macs2/view1.h
diff --git a/engines/macs2/macs2.cpp b/engines/macs2/macs2.cpp
index c335eb739c4..bdfd2e59636 100644
--- a/engines/macs2/macs2.cpp
+++ b/engines/macs2/macs2.cpp
@@ -33,6 +33,7 @@
#include "common/types.h"
#include "common/util.h"
#include "engines/util.h"
+#include "engines/enhancements.h"
#include "gameobjects.h"
#include "graphics/cursorman.h"
#include "graphics/paletteman.h"
@@ -1119,6 +1120,23 @@ int Macs2Engine::computeMinCostToReachable(int nodeIndex, int prevNode, uint16 a
return result;
}
+void Macs2Engine::nextCursorMode() {
+ switch (_scriptExecutor->_cursorMode) {
+ case Script::MouseMode::Talk:
+ setCursorMode(Script::MouseMode::Look);
+ break;
+ case Script::MouseMode::Look:
+ setCursorMode(Script::MouseMode::Use);
+ break;
+ case Script::MouseMode::Use:
+ setCursorMode(Script::MouseMode::Walk);
+ break;
+ default:
+ setCursorMode(Script::MouseMode::Talk);
+ break;
+ }
+}
+
void Macs2Engine::setCursorMode(Script::MouseMode newMode) {
// setCursorMode (1008:3ea5): when the cursor image changes, keep the hotspot
// fixed on screen by compensating for the old/new image half-extents, clamp,
@@ -1135,26 +1153,42 @@ void Macs2Engine::setCursorMode(Script::MouseMode newMode) {
halfH = _imageResources[index]._height / 2;
};
+ auto isGameplayVerb = [](Script::MouseMode mode) {
+ return mode == Script::MouseMode::Talk || mode == Script::MouseMode::Look ||
+ mode == Script::MouseMode::Use || mode == Script::MouseMode::Walk;
+ };
+
+ View1 *view = (View1 *)findView("View1");
+ const bool scummVerbUI = view && view->hasScummVerbUI();
+
uint16 oldHalfW = 0, oldHalfH = 0, newHalfW = 0, newHalfH = 0;
cursorHalfSize(oldMode, oldHalfW, oldHalfH);
Common::Point mouse = g_system->getEventManager()->getMousePos();
- mouse.x += oldHalfW;
- mouse.y += oldHalfH;
+ const bool mouseInUiPanel = scummVerbUI && mouse.y >= kGameHeight;
_scriptExecutor->_cursorMode = newMode;
- cursorHalfSize(newMode, newHalfW, newHalfH);
- mouse.x -= newHalfW;
- mouse.y -= newHalfH;
+ // Keep the pointer on the verb/inventory panel when selecting verbs there, and
+ // skip hotspot compensation when the SCUMM UI shows the same walk cursor for all verbs.
+ if (!mouseInUiPanel && !(scummVerbUI && isGameplayVerb(oldMode) && isGameplayVerb(newMode))) {
+ mouse.x += oldHalfW;
+ mouse.y += oldHalfH;
- mouse.x = CLIP<int>(mouse.x, (int)newHalfW, kScreenWidthLast - (int)newHalfW);
- mouse.y = CLIP<int>(mouse.y, (int)newHalfH, kGameHeightLast - (int)newHalfH);
- g_system->warpMouse(mouse.x, mouse.y);
+ cursorHalfSize(newMode, newHalfW, newHalfH);
+ mouse.x -= newHalfW;
+ mouse.y -= newHalfH;
+
+ const int maxY = scummVerbUI ? (kScreenHeightLast - (int)newHalfH)
+ : (kGameHeightLast - (int)newHalfH);
+ mouse.x = CLIP<int>(mouse.x, (int)newHalfW, kScreenWidthLast - (int)newHalfW);
+ mouse.y = CLIP<int>(mouse.y, (int)newHalfH, maxY);
+ g_system->warpMouse(mouse.x, mouse.y);
+ }
_clipRectDirty = true;
- if (View1 *view = (View1 *)findView("View1"))
+ if (view)
view->updateCursor();
if (cursorVisible)
@@ -1760,8 +1794,8 @@ Common::Error Macs2Engine::run() {
loadTranslation();
}
- // Initialize graphics mode
- initGraphics(kScreenWidth, kGameHeight);
+ // Initialize graphics mode (taller framebuffer when SCUMM verb UI is enabled)
+ initGraphics(kScreenWidth, enhancementEnabled(kEnhUIUX) ? kScreenHeight : kGameHeight);
CursorMan.showMouse(false);
diff --git a/engines/macs2/macs2.h b/engines/macs2/macs2.h
index 0306562bcd6..f8831e33f6a 100644
--- a/engines/macs2/macs2.h
+++ b/engines/macs2/macs2.h
@@ -369,6 +369,7 @@ public:
Common::MemoryReadStream *_fileStream;
void setCursorMode(Script::MouseMode newMode);
+ void nextCursorMode();
Common::Array<uint16> _hotspotColorTable;
diff --git a/engines/macs2/macs2_constants.h b/engines/macs2/macs2_constants.h
index e2869b63520..bc707dc8051 100644
--- a/engines/macs2/macs2_constants.h
+++ b/engines/macs2/macs2_constants.h
@@ -30,6 +30,11 @@ static constexpr int kScreenWidthLast = kScreenWidth - 1;
static constexpr int kGameHeight = 200;
static constexpr int kGameHeightLast = kGameHeight - 1;
+// SCUMM-style verb/inventory strip (kEnhUIUX enhancement only).
+static constexpr int kUIHeight = 64;
+static constexpr int kScreenHeight = kGameHeight + kUIHeight;
+static constexpr int kScreenHeightLast = kScreenHeight - 1;
+
} // namespace Macs2
#endif // MACS2_CONSTANTS_H
diff --git a/engines/macs2/module.mk b/engines/macs2/module.mk
index 86740397e05..348110dc297 100644
--- a/engines/macs2/module.mk
+++ b/engines/macs2/module.mk
@@ -11,6 +11,7 @@ MODULE_OBJS = \
metaengine.o \
saveload.o \
scriptexecutor.o \
+ scummui.o \
view1.o
ifdef USE_IMGUI
diff --git a/engines/macs2/saveload.cpp b/engines/macs2/saveload.cpp
index 0a41cb43f4c..b7c52640911 100644
--- a/engines/macs2/saveload.cpp
+++ b/engines/macs2/saveload.cpp
@@ -841,7 +841,9 @@ Common::Error Macs2Engine::syncGame(Common::Serializer &s) {
// the per-object loop could populate their runtime walk/draw/dirty state.
// Do NOT recreate them here - that would discard the loaded fields.
view1->rebuildCharacterLookupTable();
- view1->setInventorySource(GameObjects::instance().getProtagonistObject());
+ view1->refreshProtagonistInventoryAfterLoad(actorIndex);
+ view1->_uiPanelState = View1::kUiPanelNone;
+ view1->ensureScummVerbUI();
// Restore UseInventory cursor image after load.
// The cursor slot is only populated when clicking an inventory item in the panel;
@@ -865,6 +867,7 @@ Common::Error Macs2Engine::syncGame(Common::Serializer &s) {
} else {
view1->startFadingWithSpeed(8);
}
+ view1->presentFrame();
}
return Common::kNoError;
diff --git a/engines/macs2/scummui.cpp b/engines/macs2/scummui.cpp
new file mode 100644
index 00000000000..3231ad39e8c
--- /dev/null
+++ b/engines/macs2/scummui.cpp
@@ -0,0 +1,414 @@
+/* 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 "macs2/scummui.h"
+
+#include "macs2/gameobjects.h"
+#include "macs2/macs2.h"
+#include "macs2/view1.h"
+
+namespace Macs2 {
+
+static Common::String getObjectDisplayName(const GameObject *obj) {
+ if (!obj)
+ return Common::String();
+
+ const GameObjects &objects = GameObjects::instance();
+ if (obj->_index < objects._objectNames.size() && !objects._objectNames[obj->_index].empty())
+ return objects._objectNames[obj->_index];
+
+ return Common::String();
+}
+
+const ScummUI::VerbDef ScummUI::kVerbs[4] = {
+ {"Walk", Script::MouseMode::Walk},
+ {"Look", Script::MouseMode::Look},
+ {"Use", Script::MouseMode::Use},
+ {"Talk", Script::MouseMode::Talk}
+};
+
+ScummUI::ScummUI(View1 *view)
+ : _view(view), _activeVerbIndex(0), _hoveredVerb(-1), _hoveredItemIndex(-1), _hoveredScrollButton(-1),
+ _inventoryScrollOffset(0) {
+}
+
+bool ScummUI::isPointInUI(const Common::Point &pos) const {
+ return pos.y >= kUITop;
+}
+
+void ScummUI::syncInventory() {
+ rebuildProtagonistItems();
+ const int maxOffset = MAX(0, (int)_protagonistItems.size() - kInvCols * kInvRows);
+ if (_inventoryScrollOffset > maxOffset)
+ _inventoryScrollOffset = maxOffset;
+ if (_inventoryScrollOffset < 0)
+ _inventoryScrollOffset = 0;
+}
+
+void ScummUI::rebuildProtagonistItems() {
+ _protagonistItems.clear();
+
+ if (_view->isInventorySourceProtagonist()) {
+ _protagonistItems = _view->_inventoryItems;
+ return;
+ }
+
+ const uint16 invScene = Scenes::instance()._currentActorIndex + 0x400;
+ for (GameObject *obj : GameObjects::instance()._objects) {
+ if (obj && obj->_sceneIndex == invScene)
+ _protagonistItems.push_back(obj);
+ }
+}
+
+void ScummUI::resetInventoryAfterLoad() {
+ _inventoryScrollOffset = 0;
+ _hoveredItemIndex = -1;
+ _hoveredScrollButton = -1;
+ _hoveredVerb = -1;
+ _sentenceObject.clear();
+ rebuildProtagonistItems();
+ syncActiveVerbFromCursorMode();
+
+ if (_view->_activeInventoryItem) {
+ bool inInventory = false;
+ for (GameObject *obj : _protagonistItems) {
+ if (obj == _view->_activeInventoryItem) {
+ inInventory = true;
+ break;
+ }
+ }
+ if (!inInventory) {
+ _view->_activeInventoryItem = nullptr;
+ g_engine->_scriptExecutor->_interactedInventoryItemId = 0;
+ }
+ }
+}
+
+void ScummUI::syncActiveVerbFromCursorMode() {
+ const Script::MouseMode mode = g_engine->_scriptExecutor->_cursorMode;
+ if (mode == Script::MouseMode::UseInventory) {
+ _activeVerbIndex = 2;
+ return;
+ }
+
+ for (int i = 0; i < 4; i++) {
+ if (kVerbs[i].mode == mode) {
+ _activeVerbIndex = i;
+ return;
+ }
+ }
+}
+
+void ScummUI::draw(Graphics::ManagedSurface &s) {
+ syncActiveVerbFromCursorMode();
+ rebuildProtagonistItems();
+ _view->drawBorderSide(Common::Point(0, kUITop), Common::Point(kScreenWidth, kUIHeight), s);
+ drawSentenceLine(s);
+ drawVerbBar(s);
+ drawInventoryStrip(s);
+}
+
+void ScummUI::drawUIButton(const Common::Rect &rect, bool pressed, Graphics::ManagedSurface &s) {
+ _view->drawBorderSide(Common::Point(rect.left, rect.top), Common::Point(rect.width(), rect.height()), s);
+ const View1::BorderStyle &style = pressed ? View1::kBorderPressed : View1::kBorderRaised;
+ _view->drawNinePatchBorder(Common::Point(rect.left, rect.top), Common::Point(rect.width(), rect.height()),
+ style, false, false, s);
+}
+
+void ScummUI::drawSentenceLine(Graphics::ManagedSurface &s) {
+ Common::String sentence;
+ const Script::MouseMode mode = g_engine->_scriptExecutor->_cursorMode;
+
+ if (mode == Script::MouseMode::UseInventory && _view->_activeInventoryItem) {
+ sentence = "Use";
+ const Common::String itemName = getObjectDisplayName(_view->_activeInventoryItem);
+ if (!itemName.empty())
+ sentence += " " + itemName;
+ } else if (_activeVerbIndex >= 0 && _activeVerbIndex < 4) {
+ sentence = kVerbs[_activeVerbIndex].label;
+ }
+ if (!_sentenceObject.empty()) {
+ if (!sentence.empty()) {
+ if (mode == Script::MouseMode::UseInventory)
+ sentence += " with ";
+ else
+ sentence += " ";
+ }
+ sentence += _sentenceObject;
+ }
+
+ if (sentence.empty())
+ return;
+
+ const bool usePanelFont = g_engine->numPanelGlyphs > 0;
+ const GlyphData *font = usePanelFont ? g_engine->_panelGlyphs : g_engine->_glyphs;
+ const uint16 fontCount = usePanelFont ? g_engine->numPanelGlyphs : g_engine->numGlyphs;
+ const int glyphH = usePanelFont ? g_engine->maxPanelGlyphHeight : g_engine->maxGlyphHeight;
+ if (usePanelFont)
+ sentence.toUppercase();
+ const int textY = kUITop + MAX(0, (kSentenceH - glyphH) / 2);
+ const int textX = MAX(0, (kScreenWidth - _view->measureStringWithFont(sentence, font, fontCount)) / 2);
+ _view->renderStringWithFontTo(textX, textY, sentence, font, fontCount, s);
+}
+
+void ScummUI::drawVerbBar(Graphics::ManagedSurface &s) {
+ for (int i = 0; i < 4; i++) {
+ const Common::Rect r = getVerbRect(i);
+ const bool isActive = (i == _activeVerbIndex);
+ const bool isHovered = (i == _hoveredVerb);
+
+ drawUIButton(r, isActive || isHovered, s);
+
+ const int textX = r.left + (r.width() - (int)strlen(kVerbs[i].label) * 6) / 2;
+ const int textY = r.top + (r.height() - (int)g_engine->maxGlyphHeight) / 2;
+ _view->renderStringTo(textX, textY, kVerbs[i].label, s);
+ }
+}
+
+int ScummUI::getScrollButtonWidth() const {
+ uint16 maxW = 0;
+ const Common::Array<uint16> &indices = g_engine->inventoryIconIndices;
+ for (int i = 2; i <= 3 && i < (int)indices.size(); i++) {
+ const int imgIdx = (int)indices[i] - 1;
+ if (imgIdx >= 0 && imgIdx < (int)g_engine->_imageResources.size())
+ maxW = MAX(maxW, g_engine->_imageResources[imgIdx]._width);
+ }
+ return MAX(22, (int)maxW + 6);
+}
+
+int ScummUI::getInvArrowX() const {
+ return kInvX;
+}
+
+void ScummUI::drawScrollButton(Graphics::ManagedSurface &s, const Common::Rect &rect,
+ int iconResourceIndex, bool hovered) {
+ drawUIButton(rect, hovered, s);
+
+ if (iconResourceIndex < 0 || iconResourceIndex >= (int)g_engine->_imageResources.size())
+ return;
+
+ const AnimFrame &frame = g_engine->_imageResources[iconResourceIndex];
+ if (frame._data.empty() || frame._width == 0 || frame._height == 0)
+ return;
+
+ int iconX = rect.left + (rect.width() - frame._width) / 2;
+ int iconY = rect.top + (rect.height() - frame._height) / 2;
+ if (hovered) {
+ iconX++;
+ iconY++;
+ }
+ _view->drawSprite(iconX, iconY, frame, s, false);
+}
+
+void ScummUI::drawInventoryStrip(Graphics::ManagedSurface &s) {
+ const Common::Array<uint16> &indices = g_engine->inventoryIconIndices;
+ const int upIconIdx = (indices.size() > 2) ? (int)indices[2] - 1 : -1;
+ const int downIconIdx = (indices.size() > 3) ? (int)indices[3] - 1 : -1;
+
+ drawScrollButton(s, getInvScrollLeftRect(), upIconIdx, _hoveredScrollButton == 0);
+ drawScrollButton(s, getInvScrollRightRect(), downIconIdx, _hoveredScrollButton == 1);
+
+ const Common::Array<GameObject *> items = getProtagonistItems();
+ const int maxVisible = kInvCols * kInvRows;
+ for (int i = 0; i < maxVisible; i++) {
+ const Common::Rect r = getInvItemRect(i);
+ drawUIButton(r, true, s);
+
+ const int itemIdx = _inventoryScrollOffset + i;
+ if (itemIdx >= (int)items.size())
+ continue;
+
+ const bool isHovered = (i == _hoveredItemIndex);
+ const bool isActive = (_view->_activeInventoryItem == items[itemIdx]);
+
+ AnimFrame *icon = _view->getInventoryIcon(items[itemIdx]);
+ if (icon && !icon->_data.empty()) {
+ _view->drawSpriteFitted(r, *icon, s, kInvIconInset);
+ }
+ delete icon;
+
+ if (isActive) {
+ _view->renderStringTo(r.left + 1, r.top, "*", s);
+ } else if (isHovered) {
+ _view->renderStringTo(r.left + 1, r.top, ".", s);
+ }
+ }
+}
+
+Common::Array<GameObject *> ScummUI::getProtagonistItems() const {
+ return _protagonistItems;
+}
+
+bool ScummUI::handleClick(const Common::Point &pos, bool scriptsRunning) {
+ for (int i = 0; i < 4; i++) {
+ if (getVerbRect(i).contains(pos)) {
+ _activeVerbIndex = i;
+ g_engine->setCursorMode(kVerbs[i].mode);
+ _view->_activeInventoryItem = nullptr;
+ g_engine->_scriptExecutor->_interactedInventoryItemId = 0;
+ _view->updateCursor();
+ return true;
+ }
+ }
+
+ if (getInvScrollLeftRect().contains(pos)) {
+ if (_inventoryScrollOffset > 0)
+ _inventoryScrollOffset -= kInvCols * kInvRows;
+ return true;
+ }
+ if (getInvScrollRightRect().contains(pos)) {
+ const int maxItems = (int)getProtagonistItems().size();
+ const int maxVisible = kInvCols * kInvRows;
+ if (_inventoryScrollOffset + maxVisible < maxItems)
+ _inventoryScrollOffset += maxVisible;
+ return true;
+ }
+
+ if (scriptsRunning)
+ return true;
+
+ const Common::Array<GameObject *> items = getProtagonistItems();
+ const int maxVisible = kInvCols * kInvRows;
+ for (int i = 0; i < maxVisible; i++) {
+ const int itemIdx = _inventoryScrollOffset + i;
+ if (itemIdx >= (int)items.size())
+ break;
+
+ if (!getInvItemRect(i).contains(pos))
+ continue;
+
+ GameObject *item = items[itemIdx];
+ const Script::MouseMode mode = g_engine->_scriptExecutor->_cursorMode;
+
+ if (mode == Script::MouseMode::Look) {
+ g_engine->_scriptExecutor->_interactedObjectID = 0x400 + item->_index;
+ g_engine->_scriptExecutor->_interactedInventoryItemId = 0;
+ _view->_pendingPanelRequest = View1::kPanelRequestInventory;
+ g_engine->runScriptExecutor(false);
+ _view->_pendingPanelRequest = View1::kPanelRequestNone;
+ } else if (mode == Script::MouseMode::Use) {
+ _view->_activeInventoryItem = item;
+ g_engine->_scriptExecutor->_interactedInventoryItemId = 0x400 + item->_index;
+ AnimFrame *icon = _view->getInventoryIcon(item);
+ if (icon != nullptr) {
+ const int cursorSlot = (int)Script::MouseMode::UseInventory - 1;
+ g_engine->_imageResources[cursorSlot] = *icon;
+ delete icon;
+ }
+ g_engine->setCursorMode(Script::MouseMode::UseInventory);
+ _view->updateCursor();
+ } else if (mode == Script::MouseMode::UseInventory && _view->_activeInventoryItem) {
+ g_engine->_scriptExecutor->_interactedObjectID = 0x400 + _view->_activeInventoryItem->_index;
+ g_engine->_scriptExecutor->_interactedInventoryItemId = 0x400 + item->_index;
+ _view->_pendingPanelRequest = View1::kPanelRequestInventory;
+ g_engine->runScriptExecutor(false);
+ _view->_pendingPanelRequest = View1::kPanelRequestNone;
+ _view->_activeInventoryItem = nullptr;
+ g_engine->setCursorMode(Script::MouseMode::Use);
+ _view->updateCursor();
+ syncInventory();
+ }
+ return true;
+ }
+
+ return true;
+}
+
+void ScummUI::handleMouseMove(const Common::Point &pos) {
+ const int oldHoveredVerb = _hoveredVerb;
+ const int oldHoveredItemIndex = _hoveredItemIndex;
+ const int oldHoveredScrollButton = _hoveredScrollButton;
+
+ _hoveredVerb = -1;
+ _hoveredItemIndex = -1;
+ _hoveredScrollButton = -1;
+ clearSentenceObject();
+
+ for (int i = 0; i < 4; i++) {
+ if (getVerbRect(i).contains(pos)) {
+ _hoveredVerb = i;
+ break;
+ }
+ }
+
+ if (_hoveredVerb < 0) {
+ if (getInvScrollLeftRect().contains(pos)) {
+ _hoveredScrollButton = 0;
+ } else if (getInvScrollRightRect().contains(pos)) {
+ _hoveredScrollButton = 1;
+ } else {
+ const Common::Array<GameObject *> items = getProtagonistItems();
+ const int maxVisible = kInvCols * kInvRows;
+ for (int i = 0; i < maxVisible; i++) {
+ const int itemIdx = _inventoryScrollOffset + i;
+ if (itemIdx >= (int)items.size())
+ break;
+ if (getInvItemRect(i).contains(pos)) {
+ _hoveredItemIndex = i;
+ break;
+ }
+ }
+ }
+ }
+
+ if (oldHoveredVerb != _hoveredVerb || oldHoveredItemIndex != _hoveredItemIndex ||
+ oldHoveredScrollButton != _hoveredScrollButton) {
+ _view->presentFrame();
+ }
+}
+
+void ScummUI::updateSentenceLine(const Common::String &objectName) {
+ _sentenceObject = objectName;
+}
+
+void ScummUI::clearSentenceObject() {
+ _sentenceObject.clear();
+}
+
+Common::Rect ScummUI::getVerbRect(int index) const {
+ const int col = index % kVerbCols;
+ const int row = index / kVerbCols;
+ const int x = col * kVerbW;
+ const int y = kVerbY + row * kVerbH;
+ return Common::Rect(x, y, x + kVerbW, y + kVerbH);
+}
+
+Common::Rect ScummUI::getInvItemRect(int index) const {
+ const int col = index % kInvCols;
+ const int row = index / kInvCols;
+ const int scrollW = getScrollButtonWidth();
+ const int x = getInvArrowX() + scrollW + col * kInvItemW;
+ const int y = kVerbY + row * kInvItemH;
+ return Common::Rect(x, y, x + kInvItemW, y + kInvItemH);
+}
+
+Common::Rect ScummUI::getInvScrollLeftRect() const {
+ const int scrollW = getScrollButtonWidth();
+ return Common::Rect(getInvArrowX(), kVerbY, getInvArrowX() + scrollW, kVerbY + kVerbH * kVerbRows);
+}
+
+Common::Rect ScummUI::getInvScrollRightRect() const {
+ const int scrollW = getScrollButtonWidth();
+ const int x = getInvArrowX() + scrollW + kInvCols * kInvItemW;
+ return Common::Rect(x, kVerbY, x + scrollW, kVerbY + kVerbH * kVerbRows);
+}
+
+} // namespace Macs2
diff --git a/engines/macs2/scummui.h b/engines/macs2/scummui.h
new file mode 100644
index 00000000000..694482f9212
--- /dev/null
+++ b/engines/macs2/scummui.h
@@ -0,0 +1,101 @@
+/* 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 MACS2_SCUMMUI_H
+#define MACS2_SCUMMUI_H
+
+#include "common/rect.h"
+#include "common/str.h"
+#include "graphics/managed_surface.h"
+#include "macs2/macs2_constants.h"
+#include "macs2/scriptexecutor.h"
+
+namespace Macs2 {
+
+class View1;
+class GameObject;
+
+class ScummUI {
+public:
+ ScummUI(View1 *view);
+
+ void draw(Graphics::ManagedSurface &s);
+ bool handleClick(const Common::Point &pos, bool scriptsRunning = false);
+ void handleMouseMove(const Common::Point &pos);
+ bool isPointInUI(const Common::Point &pos) const;
+ void updateSentenceLine(const Common::String &objectName);
+ void clearSentenceObject();
+ void syncInventory();
+ void syncActiveVerbFromCursorMode();
+ void resetInventoryAfterLoad();
+
+private:
+ static constexpr int kSentenceH = 14;
+ static constexpr int kUITop = kGameHeight;
+ static constexpr int kSentenceY = kGameHeight;
+ static constexpr int kVerbY = kGameHeight + kSentenceH;
+ static constexpr int kVerbW = 64;
+ static constexpr int kVerbH = 25;
+ static constexpr int kVerbCols = 2;
+ static constexpr int kVerbRows = 2;
+ static constexpr int kInvX = 128;
+ static constexpr int kInvItemW = 34;
+ static constexpr int kInvItemH = 25;
+ static constexpr int kInvIconInset = 1;
+ static constexpr int kInvCols = 4;
+ static constexpr int kInvRows = 2;
+
+ struct VerbDef {
+ const char *label;
+ Script::MouseMode mode;
+ };
+ static const VerbDef kVerbs[4];
+
+ void drawSentenceLine(Graphics::ManagedSurface &s);
+ void drawVerbBar(Graphics::ManagedSurface &s);
+ void drawInventoryStrip(Graphics::ManagedSurface &s);
+ void drawScrollButton(Graphics::ManagedSurface &s, const Common::Rect &rect, int iconResourceIndex, bool hovered);
+ void drawUIButton(const Common::Rect &rect, bool pressed, Graphics::ManagedSurface &s);
+ void rebuildProtagonistItems();
+
+ int getScrollButtonWidth() const;
+ int getInvArrowX() const;
+
+ Common::Array<GameObject *> getProtagonistItems() const;
+
+ Common::Rect getVerbRect(int index) const;
+ Common::Rect getInvItemRect(int index) const;
+ Common::Rect getInvScrollLeftRect() const;
+ Common::Rect getInvScrollRightRect() const;
+
+ View1 *_view;
+ int _activeVerbIndex;
+ int _hoveredVerb;
+ int _hoveredItemIndex;
+ int _hoveredScrollButton;
+ int _inventoryScrollOffset;
+ Common::Array<GameObject *> _protagonistItems;
+ Common::String _sentenceObject;
+};
+
+} // namespace Macs2
+
+#endif // MACS2_SCUMMUI_H
diff --git a/engines/macs2/view1.cpp b/engines/macs2/view1.cpp
index c40149c5c7d..7bda706c140 100644
--- a/engines/macs2/view1.cpp
+++ b/engines/macs2/view1.cpp
@@ -26,6 +26,7 @@
#include "common/debug.h"
#include "common/system.h"
#include "engines/enhancements.h"
+#include "engines/util.h"
#include "graphics/cursorman.h"
#include "graphics/paletteman.h"
#include "macs2/debugtools.h"
@@ -33,6 +34,7 @@
#include "macs2/gameobjects.h"
#include "macs2/macs2.h"
#include "macs2/music.h"
+#include "macs2/scummui.h"
namespace Macs2 {
namespace {
@@ -139,9 +141,57 @@ View1::View1() : UIElement("View1") {
rebuildCharacterLookupTable();
_inventorySource = protagonist->_gameObject;
_inventoryButtonLocations.resize(6);
+
+ if (hasScummVerbUI()) {
+ _scummUI = new ScummUI(this);
+ _bounds = Common::Rect(0, 0, kScreenWidth, kScreenHeight);
+ _innerBounds = _bounds;
+ setInventorySource(_inventorySource);
+ }
+}
+
+void View1::ensureScummVerbUI() {
+ if (!hasScummVerbUI())
+ return;
+ if (!_scummUI) {
+ _scummUI = new ScummUI(this);
+ if (_inventorySource)
+ setInventorySource(_inventorySource);
+ }
+ if (_bounds.height() != kScreenHeight) {
+ _bounds = Common::Rect(0, 0, kScreenWidth, kScreenHeight);
+ _innerBounds = _bounds;
+ ::initGraphics(kScreenWidth, kScreenHeight);
+ }
+}
+
+bool View1::hasScummVerbUI() const {
+ return g_engine->enhancementEnabled(kEnhUIUX);
+}
+
+bool View1::shouldShowScummVerbUI() const {
+ if (!hasScummVerbUI())
+ return false;
+ if (_currentMode == ViewMode::VM_HELP)
+ return false;
+ if (_isShowingTextBox || _isShowingDialoguePanel)
+ return false;
+ if (_uiPanelState == kUiPanelContainerInventory || _uiPanelState == kUiPanelSaveLoad)
+ return false;
+ if (g_engine->_scriptExecutor->_cursorMode == Script::MouseMode::Disabled)
+ return false;
+
+ // Use the actor object table directly; Character lookup can lag behind scene changes.
+ const GameObject *actor = GameObjects::getObjectByIndex(Scenes::instance()._currentActorIndex);
+ if (!actor)
+ return false;
+
+ const uint16 scene = (uint16)Scenes::instance()._currentSceneIndex;
+ return actor->_sceneIndex == scene;
}
View1::~View1() {
+ delete _scummUI;
for (Character *c : _characters) {
delete c;
}
@@ -182,6 +232,14 @@ void View1::openInventory(GameObject *newInventorySource) {
setInventorySource(newInventorySource);
_pendingPanelRequest = kPanelRequestNone; // Binary: g_wPendingPanelRequest = 0
+
+ // SCUMM verb UI: protagonist inventory is always visible in the strip.
+ if (hasScummVerbUI() && newInventorySource->_index == Scenes::instance()._currentActorIndex) {
+ if (_scummUI)
+ _scummUI->syncInventory();
+ return;
+ }
+
// Binary: g_wUiPanelState = 2 for protagonist, 3 for container
_uiPanelState = (newInventorySource->_index == Scenes::instance()._currentActorIndex)
? kUiPanelInventory
@@ -233,15 +291,49 @@ void View1::setInventorySource(GameObject *newInventorySource) {
_inventorySource = newInventorySource;
// Rebuild inventory list from all objects whose SceneIndex matches.
// Binary (syncInventoryObjectList at 1008:071e) checks: object.sceneIndex == actorIndex + 0x400.
- // The +0x400 offset encodes "inside this container/actor's inventory".
+ const uint16 inventorySceneId = (_inventorySource->_index == Scenes::instance()._currentActorIndex)
+ ? (uint16)(Scenes::instance()._currentActorIndex + 0x400)
+ : (uint16)(_inventorySource->_index + 0x400);
_inventoryItems.clear();
- const uint16 inventorySceneId = _inventorySource->_index + 0x400;
for (GameObject *currentObject : GameObjects::instance()._objects) {
if (currentObject != nullptr && currentObject->_sceneIndex == inventorySceneId) {
_inventoryItems.push_back(currentObject);
}
}
+ if (hasScummVerbUI() && _scummUI)
+ _scummUI->syncInventory();
+}
+
+void View1::refreshProtagonistInventoryAfterLoad(uint16 actorIndex) {
+ _inventorySource = GameObjects::instance().getProtagonistObject();
+ const uint16 invScene = actorIndex + 0x400;
+
+ Common::Array<GameObject *> validated;
+ for (GameObject *obj : _inventoryItems) {
+ if (obj && obj->_sceneIndex == invScene)
+ validated.push_back(obj);
+ }
+
+ for (GameObject *obj : GameObjects::instance()._objects) {
+ if (!obj || obj->_sceneIndex != invScene)
+ continue;
+
+ bool found = false;
+ for (GameObject *listed : validated) {
+ if (listed == obj) {
+ found = true;
+ break;
+ }
+ }
+ if (!found)
+ validated.push_back(obj);
+ }
+
+ _inventoryItems = validated;
+
+ if (hasScummVerbUI() && _scummUI)
+ _scummUI->resetInventoryAfterLoad();
}
bool View1::isInventorySourceProtagonist() const {
@@ -252,6 +344,8 @@ void View1::transferInventoryItem(GameObject *item, GameObject *targetContainer)
int index = findInventoryItem(item);
_inventoryItems.remove_at(index);
item->_sceneIndex = targetContainer->_index + 0x400;
+ if (hasScummVerbUI() && _scummUI)
+ _scummUI->syncInventory();
}
int View1::findInventoryItem(const GameObject *item) {
@@ -333,6 +427,22 @@ void View1::updateCursor(const byte *palette) {
// Original indexes cursor array as: base + mode * 16 - 16, i.e. 0-based index = mode - 1.
// The array has 33 entries (indices 0-32). Cursor modes 0x13-0x1A map to entries 18-25.
int mode = (int)g_engine->_scriptExecutor->_cursorMode - 1;
+
+ // SCUMM-style UI: gameplay verbs share the walk cursor; the sentence line shows the active verb.
+ if (hasScummVerbUI()) {
+ const Script::MouseMode cursorMode = g_engine->_scriptExecutor->_cursorMode;
+ switch (cursorMode) {
+ case Script::MouseMode::Talk:
+ case Script::MouseMode::Look:
+ case Script::MouseMode::Use:
+ case Script::MouseMode::Walk:
+ mode = (int)Script::MouseMode::Walk - 1;
+ break;
+ default:
+ break;
+ }
+ }
+
if (mode < 0 || mode >= kNumLoadedCursors) {
warning("Invalid cursor mode %d, falling back to Walk cursor", mode);
mode = (int)Script::MouseMode::Walk - 1;
@@ -714,6 +824,9 @@ void View1::layoutActionBarButtons() {
}
void View1::openMainMenu(Common::Point clickedPosition) {
+ if (hasScummVerbUI())
+ return;
+
// Binary handleInput: save cursor and set to PanelCursor (0x19)
_savedCursorMode = g_engine->_scriptExecutor->_cursorMode;
g_engine->setCursorMode(Script::MouseMode::PanelCursor);
@@ -934,6 +1047,9 @@ void View1::transferPickupTarget(GameObject *targetObject) {
}
}
+ if (hasScummVerbUI() && _scummUI)
+ _scummUI->syncInventory();
+
// Binary sets g_wNeedsRedraw and restores scene background over the panel area.
redraw();
}
@@ -1082,6 +1198,7 @@ void View1::startFadingWithSpeed(uint16 speed) {
_fadeMode = FadeMode::None;
_paletteDirty = false;
endFadeCursorSuppression(g_engine->_pal);
+ redraw();
}
void View1::beginFadeCursorSuppression() {
@@ -1325,6 +1442,8 @@ bool View1::handleContainerInventoryClick(const MouseDownMessage &msg) {
updateCursor();
g_engine->_scriptExecutor->_inventoryActionFlag = true;
setInventorySource(_inventorySource);
+ if (hasScummVerbUI() && _scummUI)
+ _scummUI->syncInventory();
}
break;
}
@@ -1364,6 +1483,8 @@ bool View1::handleContainerInventoryClick(const MouseDownMessage &msg) {
}
g_engine->setCursorMode(Script::MouseMode::UseInventory);
updateCursor();
+ if (hasScummVerbUI() && _scummUI)
+ _scummUI->syncInventory();
return true;
}
@@ -1490,6 +1611,14 @@ bool View1::handleInput(const MouseDownMessage &msg) {
return handleHelpClick(msg);
}
+ if (shouldShowScummVerbUI() && _scummUI && _scummUI->isPointInUI(msg._pos)) {
+ if (g_engine->_scriptExecutor->_cursorMode != Script::MouseMode::Disabled) {
+ _scummUI->handleClick(msg._pos, g_engine->_scriptExecutor->isExecuting());
+ presentFrame();
+ }
+ return true;
+ }
+
// Handle original save/load panel clicks
if (_uiPanelState == kUiPanelSaveLoad) {
handleOriginalSaveLoadClick(msg._pos);
@@ -1565,6 +1694,9 @@ bool View1::handleInput(const MouseDownMessage &msg) {
}
if (g_engine->_scriptExecutor->_cursorMode == Script::MouseMode::Walk) {
+ if (shouldShowScummVerbUI() && msg._pos.y >= kGameHeight)
+ return true;
+
Character *protagonist = getCharacterByIndex(Scenes::instance()._currentActorIndex);
if (protagonist == nullptr) {
debugC(kDebugScript, "Ignoring walk click without active actor character in the scene");
@@ -1605,6 +1737,9 @@ bool View1::handleInput(const MouseDownMessage &msg) {
}
// Check if we hit something
+ if (shouldShowScummVerbUI() && msg._pos.y >= kGameHeight)
+ return true;
+
// Original order: getHotspotAtPoint first, then drawCharactersAndHitTest overrides.
// Our order (objects first, fallback to background) produces the same result.
uint16 index = getHitObjectID(Common::Point(msg._pos.x, msg._pos.y));
@@ -1677,10 +1812,22 @@ bool View1::handleInput(const MouseDownMessage &msg) {
}
// From handleInput (1008:e8bf): right-click when not executing and cursor != Disabled
- // opens the action bar at the mouse position.
+ // opens the action bar at the mouse position (or cycles verbs with SCUMM UI).
if (g_engine->_scriptExecutor->_cursorMode == Script::MouseMode::Disabled) {
return true;
}
+ if (hasScummVerbUI()) {
+ if (shouldShowScummVerbUI()) {
+ g_engine->nextCursorMode();
+ _activeInventoryItem = nullptr;
+ g_engine->_scriptExecutor->_interactedInventoryItemId = 0;
+ if (_scummUI)
+ _scummUI->syncActiveVerbFromCursorMode();
+ updateCursor();
+ presentFrame();
+ }
+ return true;
+ }
if (_uiPanelState != kUiPanelActionBar) {
openMainMenu(msg._pos);
} else {
@@ -1817,6 +1964,26 @@ bool View1::msgMouseUp(const MouseUpMessage &msg) {
bool View1::msgMouseMove(const MouseMoveMessage &msg) {
_hoverAreaId = g_engine->_scriptExecutor->getAreaAtPoint(msg._pos.x, msg._pos.y);
_hoverHotspotId = g_engine->getHotspotAtPoint(msg._pos);
+
+ if (shouldShowScummVerbUI() && _scummUI) {
+ if (_scummUI->isPointInUI(msg._pos)) {
+ _scummUI->handleMouseMove(msg._pos);
+ } else if (msg._pos.y < kGameHeight) {
+ _scummUI->clearSentenceObject();
+ uint16 index = getHitObjectID(msg._pos);
+ if (index == 0)
+ index = g_engine->getHotspotAtPoint(msg._pos);
+ if (index != 0 && index >= 0x400) {
+ const uint16 objIndex = index - 0x400;
+ if (objIndex < GameObjects::instance()._objectNames.size()) {
+ const Common::String &name = GameObjects::instance()._objectNames[objIndex];
+ if (!name.empty())
+ _scummUI->updateSentenceLine(name);
+ }
+ }
+ }
+ }
+
return true;
}
@@ -1889,14 +2056,29 @@ bool View1::msgKeypress(const KeypressMessage &msg) {
// Binary (handleInput 1008:edff): UI panels only open when not executing and cursor != Disabled.
if (!g_engine->_scriptExecutor->isExecuting() && g_engine->_scriptExecutor->_cursorMode != Script::MouseMode::Disabled) {
if (msg.ascii == (uint16)'i') {
- if (_uiPanelState != kUiPanelInventory) {
+ if (hasScummVerbUI()) {
+ if (_uiPanelState == kUiPanelContainerInventory)
+ closeInventory();
+ } else if (_uiPanelState != kUiPanelInventory) {
openInventory(GameObjects::instance().getProtagonistObject());
} else {
closeInventory();
}
} else if (msg.ascii == 'n') {
- Common::Point mousePos = g_system->getEventManager()->getMousePos();
- openMainMenu(mousePos);
+ if (hasScummVerbUI()) {
+ if (shouldShowScummVerbUI()) {
+ g_engine->nextCursorMode();
+ _activeInventoryItem = nullptr;
+ g_engine->_scriptExecutor->_interactedInventoryItemId = 0;
+ if (_scummUI)
+ _scummUI->syncActiveVerbFromCursorMode();
+ updateCursor();
+ presentFrame();
+ }
+ } else {
+ Common::Point mousePos = g_system->getEventManager()->getMousePos();
+ openMainMenu(mousePos);
+ }
}
}
@@ -1978,7 +2160,8 @@ void View1::draw() {
// We keep the inventory on but don't draw it in case we display a string
// i.e. a description of an item
- if ((_uiPanelState == kUiPanelInventory || _uiPanelState == kUiPanelContainerInventory) && !_isShowingTextBox && !_isShowingDialoguePanel) {
+ const bool showProtagonistInventory = _uiPanelState == kUiPanelInventory && !hasScummVerbUI();
+ if ((showProtagonistInventory || _uiPanelState == kUiPanelContainerInventory) && !_isShowingTextBox && !_isShowingDialoguePanel) {
drawInventory(s);
}
@@ -2037,6 +2220,18 @@ void View1::draw() {
}
}
}
+
+ if (hasScummVerbUI()) {
+ ensureScummVerbUI();
+ if (_scummUI && !_isShowingTextBox && !_isShowingDialoguePanel) {
+ Graphics::ManagedSurface fullScreen(*g_events->getScreen(), Common::Rect(0, 0, kScreenWidth, kScreenHeight));
+ if (shouldShowScummVerbUI()) {
+ _scummUI->draw(fullScreen);
+ } else {
+ fullScreen.fillRect(Common::Rect(0, kGameHeight, kScreenWidth, kScreenHeight), 0);
+ }
+ }
+ }
}
void View1::drawSceneUpdate() {
@@ -2447,6 +2642,7 @@ void View1::drawAllCharacters(Graphics::ManagedSurface *surface, bool fullUpdate
const byte *pixelData = frame._data.data();
// drawAllCharacters @ 1008:9573-9754: drawAnimFrame / drawAnimFrameShaded / drawAnimFrameDepth
+ const bool clipGameArea = hasScummVerbUI();
if (obj->_hasScaling) {
drawSpriteTransparent(shadingTableOffset, depthThreshold, scalingFactor,
drawX, drawY, frame._width, frame._height, pixelData, *surface);
@@ -2455,7 +2651,7 @@ void View1::drawAllCharacters(Graphics::ManagedSurface *surface, bool fullUpdate
frame._width, frame._height, pixelData, *surface);
} else {
drawSprite(drawX, drawY, frame._width, frame._height,
- const_cast<byte *>(pixelData), *surface, false);
+ const_cast<byte *>(pixelData), *surface, false, false, 0, clipGameArea);
}
// drawAllCharacters @ 1008:9759: wLastDrawX/Y exclude per-frame offsetX/offsetY
@@ -2583,6 +2779,9 @@ void View1::drawInventory(Graphics::ManagedSurface &s) {
// Draw the buttons at the bottom
for (int i = 0; i < 6; i++) {
+ if (hasScummVerbUI() && !isInventorySourceProtagonist() && i == (int)InventoryButtonIndex::Drop)
+ continue;
+
uint16 index = iconIndices[i];
AnimFrame ¤tFrame = g_engine->_imageResources[index - 1];
drawNinePatchBorder(Common::Point(buttonX, buttonY), Common::Point(buttonW, buttonH), kBorderRaised, false, false, s);
@@ -2667,7 +2866,7 @@ GameObject *View1::getClickedInventoryItem(const Common::Point &p) {
return nullptr;
}
-void View1::drawSprite(int16 x, int16 y, uint16 width, uint16 height, byte *data, Graphics::ManagedSurface &s, bool mirrored, bool useDepth, uint8 depth) {
+void View1::drawSprite(int16 x, int16 y, uint16 width, uint16 height, byte *data, Graphics::ManagedSurface &s, bool mirrored, bool useDepth, uint8 depth, bool clipToGameArea) {
for (int currentX = 0; currentX < width; currentX++) {
int actualX = mirrored ? width - currentX - 1 : currentX;
for (int currentY = 0; currentY < height; currentY++) {
@@ -2676,6 +2875,8 @@ void View1::drawSprite(int16 x, int16 y, uint16 width, uint16 height, byte *data
int finalX = x + actualX;
int finalY = y + currentY;
if (finalX >= 0 && finalX < s.w && finalY >= 0 && finalY < s.h) {
+ if (clipToGameArea && finalY >= kGameHeight)
+ continue;
// Check for depth
uint8 bgDepth = g_engine->_depthMap.getPixel(finalX, finalY);
// Depth test: draw pixel only if depth map value < character depth
@@ -2689,12 +2890,12 @@ void View1::drawSprite(int16 x, int16 y, uint16 width, uint16 height, byte *data
}
}
-void View1::drawSprite(const Common::Point &pos, uint16 width, uint16 height, byte *data, Graphics::ManagedSurface &s, bool mirrored, bool useDepth, uint8 depth) {
- drawSprite(pos.x, pos.y, width, height, data, s, mirrored, useDepth, depth);
+void View1::drawSprite(const Common::Point &pos, uint16 width, uint16 height, byte *data, Graphics::ManagedSurface &s, bool mirrored, bool useDepth, uint8 depth, bool clipToGameArea) {
+ drawSprite(pos.x, pos.y, width, height, data, s, mirrored, useDepth, depth, clipToGameArea);
}
-void View1::drawSprite(int16 x, int16 y, const Sprite &sprite, Graphics::ManagedSurface &s, bool mirrored, bool useDepth, uint8 depth) {
- drawSprite(x, y, sprite._width, sprite._height, const_cast<byte *>(sprite._data.data()), s, mirrored, useDepth, depth);
+void View1::drawSprite(int16 x, int16 y, const Sprite &sprite, Graphics::ManagedSurface &s, bool mirrored, bool useDepth, uint8 depth, bool clipToGameArea) {
+ drawSprite(x, y, sprite._width, sprite._height, const_cast<byte *>(sprite._data.data()), s, mirrored, useDepth, depth, clipToGameArea);
}
void View1::drawSpriteClipped(uint16 x, uint16 y, Common::Rect &clippingRect, uint16 width, uint16 height, const byte *const data, Graphics::ManagedSurface &s) {
@@ -2702,10 +2903,10 @@ void View1::drawSpriteClipped(uint16 x, uint16 y, Common::Rect &clippingRect, ui
for (int currentY = 0; currentY < height; currentY++) {
uint8 val = data[currentY * width + currentX];
if (val != 0) {
- if (clippingRect.contains(x + currentX, y + currentY)) {
- if (x + currentX < kScreenWidth && y + currentY < kGameHeight)
- s.setPixel(x + currentX, y + currentY, val);
- }
+ const int px = x + currentX;
+ const int py = y + currentY;
+ if (clippingRect.contains(px, py) && px >= 0 && px < s.w && py >= 0 && py < s.h)
+ s.setPixel(px, py, val);
}
}
}
@@ -2774,7 +2975,7 @@ void View1::drawSpriteScaled(int shadingTableOffset, uint8 depthThreshold, int16
int srcRow = 0;
int remainingRows = srcHeight;
while (remainingRows > 0) {
- if (screenY >= 0 && screenY < s.h) {
+ if (screenY >= 0 && screenY < s.h && !(hasScummVerbUI() && screenY >= kGameHeight)) {
int screenX = drawX;
for (uint16 srcX = 0; srcX < srcWidth; srcX++) {
if (screenX >= 0 && screenX < s.w) {
@@ -2804,7 +3005,7 @@ void View1::drawSpriteTransparent(int shadingTableOffset, uint8 depthThreshold,
uint16 yScaleAccum = 0;
while (remainingRows > 0) {
- if (screenY >= 0 && screenY < s.h) {
+ if (screenY >= 0 && screenY < s.h && !(hasScummVerbUI() && screenY >= kGameHeight)) {
int screenX = drawX;
const byte *srcPtr = srcPixels + srcRowOffset;
int remainingSrcPixels = (int)srcWidth;
diff --git a/engines/macs2/view1.h b/engines/macs2/view1.h
index 41538be1134..2d3bd466e9a 100644
--- a/engines/macs2/view1.h
+++ b/engines/macs2/view1.h
@@ -27,6 +27,7 @@
namespace Macs2 {
+class ScummUI;
class GameObject;
enum class ViewMode {
@@ -187,6 +188,8 @@ struct ScalingValues {
};
class View1 : public UIElement {
+ friend class ScummUI;
+
private:
// drawSpriteTransparent @ 1010:0ed1 (drawAnimFrameDepth @ 1010:172c)
void drawSpriteTransparent(int shadingTableOffset, uint8 depthThreshold, uint16 scalingFactor,
@@ -200,6 +203,8 @@ private:
// Set by action bar map button on press; enterMapMode() runs on panel release.
bool _pendingMapOpen = false;
+ ScummUI *_scummUI = nullptr;
+
// Saved scene visuals for help screen restore (avoids changeScene on exit)
byte _savedPalVanilla[256 * 3] = {0};
Graphics::ManagedSurface _savedDepthMap;
@@ -319,9 +324,9 @@ private:
Common::Array<Common::Rect> _inventoryButtonLocations;
uint16 _inventoryScrollOffset = 0;
- void drawSprite(int16 x, int16 y, uint16 width, uint16 height, byte *data, Graphics::ManagedSurface &s, bool mirrored, bool useDepth = false, uint8 depth = 0);
- void drawSprite(int16 x, int16 y, const Sprite &sprite, Graphics::ManagedSurface &s, bool mirrored, bool useDepth = false, uint8 depth = 0);
- void drawSprite(const Common::Point &pos, uint16 width, uint16 height, byte *data, Graphics::ManagedSurface &s, bool mirrored, bool useDepth = false, uint8 depth = 0);
+ void drawSprite(int16 x, int16 y, uint16 width, uint16 height, byte *data, Graphics::ManagedSurface &s, bool mirrored, bool useDepth = false, uint8 depth = 0, bool clipToGameArea = false);
+ void drawSprite(int16 x, int16 y, const Sprite &sprite, Graphics::ManagedSurface &s, bool mirrored, bool useDepth = false, uint8 depth = 0, bool clipToGameArea = false);
+ void drawSprite(const Common::Point &pos, uint16 width, uint16 height, byte *data, Graphics::ManagedSurface &s, bool mirrored, bool useDepth = false, uint8 depth = 0, bool clipToGameArea = false);
void drawSpriteClipped(uint16 x, uint16 y, Common::Rect &clippingRect, uint16 width, uint16 height, const byte *const data, Graphics::ManagedSurface &s);
void drawSpriteClipped(uint16 x, uint16 y, Common::Rect &clippingRect, const Sprite &sprite, Graphics::ManagedSurface &s);
@@ -348,6 +353,10 @@ public:
View1();
virtual ~View1();
+ bool hasScummVerbUI() const;
+ bool shouldShowScummVerbUI() const;
+ void ensureScummVerbUI();
+
// g_wHelpButtonDisabled (1020:23B4): when non-zero, help/map button is disabled
// and script scene changes use applyScenePaletteEffect instead of palette fades.
// Map overlay mode is scene+0x61db (_currentMode == VM_HELP), not this flag.
@@ -477,6 +486,7 @@ public:
// Sets the source for the to-be-opened inventory and updats the array of inventory objects
void setInventorySource(GameObject *newInventorySource);
+ void refreshProtagonistInventoryAfterLoad(uint16 actorIndex);
void openInventory(GameObject *newInventorySource);
// Binary handleInput (1008:e8bf) close path for the inventory panels.
void closeInventory();
More information about the Scummvm-git-logs
mailing list