[Scummvm-git-logs] scummvm master -> 738808f2bcc306fbfd89694747e71544a925d528

sev- noreply at scummvm.org
Tue Jan 20 23:37:04 UTC 2026


This automated email contains information about 16 new commits which have been
pushed to the 'scummvm' repo located at https://api.github.com/repos/scummvm/scummvm .

Summary:
dbf051def9 GRAPHICS: MACGUI: make scrollbar visible only during mouse hold
8cddcb3c2d GRAPHICS: MACGUI: stop scrolling when cursor leaves scrollbar
62c5557c5a GRAPHICS: MACGUI: scrollbar hides when using mouse wheel to scroll.
b05c29c331 GRAPHICS: MACGUI: fix text flicker when hiding scrollbar
9f01925253 GRAPHICS: MACGUI: add scrollbar drag support for win95 games.
3d991420d5 GRAPHICS: MACGUI: fix scrolbar gap when at bottom arrow.
349bdd0bcf WAGE: fix missing quit text in The Sultan's Palace.
14e2c5e2c7 GRAPHICS: MACGUI: fix #16334 title box bug.
630609cc81 GRAPHICS: MACGUI: fix #16331 wrong size for dialog box.
7dca55e622 GRAPHICS: MACGUI: fix about dialog box.
41bc84efd5 GRAPHICS: MACGUI: force menu close before dialog box renders.
89765fe991 GRAPHICS: MACGUI: fix #16266 Wrong window priority.
34e111224d WAGE: add a debugger to render design step by step.
6c6ecc88e1 WAGE: fix black bars in design.
e0531c1d82 WAGE: add missing combat logic
738808f2bc WAGE: fix borders in map labels.


Commit: dbf051def9602a5fd7b7aa2de72c38717e326d03
    https://github.com/scummvm/scummvm/commit/dbf051def9602a5fd7b7aa2de72c38717e326d03
Author: dhruv (dhruvranger97 at gmail.com)
Date: 2026-01-21T02:36:55+03:00

Commit Message:
GRAPHICS: MACGUI: make scrollbar visible only during mouse hold

previously the scrollbar would stay regardless of if user is scrolling or not now it is fixed.

Changed paths:
    graphics/macgui/mactextwindow.cpp
    graphics/macgui/mactextwindow.h


diff --git a/graphics/macgui/mactextwindow.cpp b/graphics/macgui/mactextwindow.cpp
index 815d0d9677c..c25cf05f839 100644
--- a/graphics/macgui/mactextwindow.cpp
+++ b/graphics/macgui/mactextwindow.cpp
@@ -69,7 +69,11 @@ void MacTextWindow::init() {
 	_selectable = true;
 
 	_textColorRGB = 0;
-	
+
+	_scrollDirection = kBorderNone;
+	_nextScrollTime = 0;
+	_scrollDelay = 50;
+
 	// Disable autoselect on activation
 	_mactext->setAutoSelect(false);
 
@@ -198,6 +202,20 @@ const MacFont *MacTextWindow::getTextWindowFont() {
 }
 
 bool MacTextWindow::draw(bool forceRedraw) {
+
+	if (_scrollDirection != kBorderNone) {
+		uint32 now = g_system->getMillis();
+		if (now >= _nextScrollTime) {
+			if (_scrollDirection == kBorderScrollUp) {
+				_mactext->scroll(-1);
+			} else if (_scrollDirection == kBorderScrollDown) {
+				_mactext->scroll(1);
+			}
+			calcScrollBar();
+			_nextScrollTime = now + _scrollDelay;
+		}
+	}
+
 	if (!_borderIsDirty && !_contentIsDirty && !_mactext->needsRedraw() && !_inputIsDirty && !forceRedraw)
 		return false;
 
@@ -345,23 +363,15 @@ bool MacTextWindow::processEvent(Common::Event &event) {
 	if (click == kBorderScrollUp || click == kBorderScrollDown) {
 		if (event.type == Common::EVENT_LBUTTONDOWN) {
 			setHighlight(click);
-			_mactext->scroll(0);
+			_scrollDirection = click;
 			calcScrollBar();
 			return true;
 		} else if (event.type == Common::EVENT_LBUTTONUP) {
-			switch (click) {
-			case kBorderScrollUp:
-				setHighlight(kBorderNone);
-				_mactext->scroll(-1);
-				break;
-			case kBorderScrollDown:
-				setHighlight(kBorderNone);
-				_mactext->scroll(1);
-				break;
-			default:
-				return false;
-			}
-
+			// reset scrolling state
+			_scrollDirection = kBorderNone;
+			setHighlight(kBorderNone);
+			// hide the scroll bar
+			setScroll(0, 0);
 			return true;
 		}
 
diff --git a/graphics/macgui/mactextwindow.h b/graphics/macgui/mactextwindow.h
index 1d1285115d7..5e745094574 100644
--- a/graphics/macgui/mactextwindow.h
+++ b/graphics/macgui/mactextwindow.h
@@ -118,6 +118,10 @@ private:
 	uint _inputTextHeight;
 	bool _inputIsDirty;
 
+	WindowClick _scrollDirection;
+	uint32 _nextScrollTime;
+	uint32 _scrollDelay;
+
 	MacMenu *_menu;
 
 	int _bgcolor;


Commit: 8cddcb3c2d67e34da3993e6d11bf1b8b6212e28b
    https://github.com/scummvm/scummvm/commit/8cddcb3c2d67e34da3993e6d11bf1b8b6212e28b
Author: dhruv (dhruvranger97 at gmail.com)
Date: 2026-01-21T02:36:55+03:00

Commit Message:
GRAPHICS: MACGUI: stop scrolling when cursor leaves scrollbar

previously if we were scrolling continously by holding down the mouse button and moved the cursor away while having mouse button down the scrolling did not stop, this commit fixes that behaviour.

Changed paths:
    graphics/macgui/mactextwindow.cpp
    graphics/macgui/mactextwindow.h


diff --git a/graphics/macgui/mactextwindow.cpp b/graphics/macgui/mactextwindow.cpp
index c25cf05f839..60c6b731849 100644
--- a/graphics/macgui/mactextwindow.cpp
+++ b/graphics/macgui/mactextwindow.cpp
@@ -71,6 +71,7 @@ void MacTextWindow::init() {
 	_textColorRGB = 0;
 
 	_scrollDirection = kBorderNone;
+	_clickedScrollPart = kBorderNone;
 	_nextScrollTime = 0;
 	_scrollDelay = 50;
 
@@ -298,6 +299,28 @@ void MacTextWindow::calcScrollBar() {
 bool MacTextWindow::processEvent(Common::Event &event) {
 	WindowClick click = isInBorder(event.mouse.x, event.mouse.y);
 
+	if (event.type == Common::EVENT_MOUSEMOVE) {
+		if (_clickedScrollPart == kBorderScrollUp || _clickedScrollPart == kBorderScrollDown) {
+			if (click == _clickedScrollPart) {
+				// we are on the bar, so keep scrolling 
+				if (_scrollDirection != _clickedScrollPart) {
+					_scrollDirection = _clickedScrollPart;
+					setHighlight(_clickedScrollPart);
+					calcScrollBar();
+				}
+			} else {
+				// we moved away from the bar, pause scrolling
+				if (_scrollDirection != kBorderNone) {
+					_scrollDirection = kBorderNone;
+					setHighlight(kBorderNone);
+					calcScrollBar();
+					setScroll(0, 0);
+				}
+			}
+			return true;
+		}
+	}
+
 	if (event.type == Common::EVENT_KEYDOWN) {
 		if (!_editable)
 			return false;
@@ -364,11 +387,13 @@ bool MacTextWindow::processEvent(Common::Event &event) {
 		if (event.type == Common::EVENT_LBUTTONDOWN) {
 			setHighlight(click);
 			_scrollDirection = click;
+			_clickedScrollPart = click;
 			calcScrollBar();
 			return true;
 		} else if (event.type == Common::EVENT_LBUTTONUP) {
 			// reset scrolling state
 			_scrollDirection = kBorderNone;
+			_clickedScrollPart = kBorderNone;
 			setHighlight(kBorderNone);
 			// hide the scroll bar
 			setScroll(0, 0);
@@ -378,6 +403,14 @@ bool MacTextWindow::processEvent(Common::Event &event) {
 		return false;
 	}
 
+	if (event.type == Common::EVENT_LBUTTONUP && _clickedScrollPart != kBorderNone) {
+		_scrollDirection = kBorderNone;
+		_clickedScrollPart = kBorderNone;
+		setHighlight(kBorderNone);
+		setScroll(0, 0);
+		return true;
+	}
+
 	if (click == kBorderInner) {
 		// Call callback for processing any events
 		if (_callback)
diff --git a/graphics/macgui/mactextwindow.h b/graphics/macgui/mactextwindow.h
index 5e745094574..04ca260a343 100644
--- a/graphics/macgui/mactextwindow.h
+++ b/graphics/macgui/mactextwindow.h
@@ -121,6 +121,7 @@ private:
 	WindowClick _scrollDirection;
 	uint32 _nextScrollTime;
 	uint32 _scrollDelay;
+	WindowClick _clickedScrollPart;
 
 	MacMenu *_menu;
 


Commit: 62c5557c5a7219d031e6ba7ef95db7a0ae4d23fd
    https://github.com/scummvm/scummvm/commit/62c5557c5a7219d031e6ba7ef95db7a0ae4d23fd
Author: dhruv (dhruvranger97 at gmail.com)
Date: 2026-01-21T02:36:55+03:00

Commit Message:
GRAPHICS: MACGUI: scrollbar hides when using mouse wheel to scroll.

previously the scrollbar used to stay on the screen if we used mouse wheel to scroll, this commit makes sure scroll bar hides it self when we stop scrolling.

Changed paths:
    graphics/macgui/mactextwindow.cpp
    graphics/macgui/mactextwindow.h


diff --git a/graphics/macgui/mactextwindow.cpp b/graphics/macgui/mactextwindow.cpp
index 60c6b731849..85e25b65bca 100644
--- a/graphics/macgui/mactextwindow.cpp
+++ b/graphics/macgui/mactextwindow.cpp
@@ -27,6 +27,8 @@
 #include "graphics/macgui/mactextwindow.h"
 #include "graphics/macgui/macmenu.h"
 
+#define SCROLLBAR_DELAY 300
+
 namespace Graphics {
 
 enum {
@@ -73,6 +75,7 @@ void MacTextWindow::init() {
 	_scrollDirection = kBorderNone;
 	_clickedScrollPart = kBorderNone;
 	_nextScrollTime = 0;
+	_nextWheelEventTime = 0;
 	_scrollDelay = 50;
 
 	// Disable autoselect on activation
@@ -204,8 +207,16 @@ const MacFont *MacTextWindow::getTextWindowFont() {
 
 bool MacTextWindow::draw(bool forceRedraw) {
 
+	uint32 now = g_system->getMillis();
+
+	if (_nextWheelEventTime != 0 && now >= _nextWheelEventTime) {
+		if (_scrollDirection == kBorderNone && _clickedScrollPart == kBorderNone) {
+			setScroll(0, 0);         // hide the scrollbar
+			_nextWheelEventTime = 0; // reset timer
+		}
+	}
+
 	if (_scrollDirection != kBorderNone) {
-		uint32 now = g_system->getMillis();
 		if (now >= _nextScrollTime) {
 			if (_scrollDirection == kBorderScrollUp) {
 				_mactext->scroll(-1);
@@ -373,6 +384,8 @@ bool MacTextWindow::processEvent(Common::Event &event) {
 		//setHighlight(kBorderScrollUp);
 		_mactext->scroll(-2);
 		calcScrollBar();
+
+		_nextWheelEventTime = g_system->getMillis() + SCROLLBAR_DELAY; // hide the bar after 300ms from now
 		return true;
 	}
 
@@ -380,6 +393,8 @@ bool MacTextWindow::processEvent(Common::Event &event) {
 		//setHighlight(kBorderScrollDown);
 		_mactext->scroll(2);
 		calcScrollBar();
+
+		_nextWheelEventTime = g_system->getMillis() + SCROLLBAR_DELAY; // hide the bar after 300ms from now
 		return true;
 	}
 
diff --git a/graphics/macgui/mactextwindow.h b/graphics/macgui/mactextwindow.h
index 04ca260a343..27baeade051 100644
--- a/graphics/macgui/mactextwindow.h
+++ b/graphics/macgui/mactextwindow.h
@@ -121,6 +121,7 @@ private:
 	WindowClick _scrollDirection;
 	uint32 _nextScrollTime;
 	uint32 _scrollDelay;
+	uint32 _nextWheelEventTime;
 	WindowClick _clickedScrollPart;
 
 	MacMenu *_menu;


Commit: b05c29c331e8d97a7adc222bb7d5b0cf81cfa7d0
    https://github.com/scummvm/scummvm/commit/b05c29c331e8d97a7adc222bb7d5b0cf81cfa7d0
Author: dhruv (dhruvranger97 at gmail.com)
Date: 2026-01-21T02:36:55+03:00

Commit Message:
GRAPHICS: MACGUI: fix text flicker when hiding scrollbar

previously hiding the scroll bar triggered a redraw which redrew the text window and causes flickering it was fixed by decoupling border redrawing from content redrawing.

Changed paths:
    graphics/macgui/mactextwindow.cpp


diff --git a/graphics/macgui/mactextwindow.cpp b/graphics/macgui/mactextwindow.cpp
index 85e25b65bca..ec6b93b3d38 100644
--- a/graphics/macgui/mactextwindow.cpp
+++ b/graphics/macgui/mactextwindow.cpp
@@ -208,7 +208,7 @@ const MacFont *MacTextWindow::getTextWindowFont() {
 bool MacTextWindow::draw(bool forceRedraw) {
 
 	uint32 now = g_system->getMillis();
-
+	// check if we need to hide the scroll bar
 	if (_nextWheelEventTime != 0 && now >= _nextWheelEventTime) {
 		if (_scrollDirection == kBorderNone && _clickedScrollPart == kBorderNone) {
 			setScroll(0, 0);         // hide the scrollbar
@@ -216,6 +216,7 @@ bool MacTextWindow::draw(bool forceRedraw) {
 		}
 	}
 
+	// handle mouse button scrolling
 	if (_scrollDirection != kBorderNone) {
 		if (now >= _nextScrollTime) {
 			if (_scrollDirection == kBorderScrollUp) {
@@ -228,29 +229,32 @@ bool MacTextWindow::draw(bool forceRedraw) {
 		}
 	}
 
-	if (!_borderIsDirty && !_contentIsDirty && !_mactext->needsRedraw() && !_inputIsDirty && !forceRedraw)
+	bool needsContentRedraw = _contentIsDirty || _inputIsDirty || _mactext->needsRedraw() || forceRedraw;
+
+	if (!_borderIsDirty && !needsContentRedraw)
 		return false;
 
-	if (_borderIsDirty || forceRedraw) {
+	if (_borderIsDirty || forceRedraw)
 		drawBorder();
 
+	if (_inputIsDirty || forceRedraw) {
+		drawInput();
+		_inputIsDirty = false;
+		needsContentRedraw = true; // input update needs a redraw
+	}
+
+	if (needsContentRedraw) {
+		// only clear the surface if we are actually going to redraw the text
 		if (_wm->_mode & kWMModeWin95) {
 			_composeSurface->clear(_bgcolor);
 		} else {
 			_composeSurface->clear(_wm->_colorWhite);
 		}
-	}
 
-	if (_inputIsDirty || forceRedraw) {
-		drawInput();
-		_inputIsDirty = false;
+		_contentIsDirty = false;
+		_mactext->draw(_composeSurface, forceRedraw);
 	}
 
-	_contentIsDirty = false;
-
-	// Compose
-	_mactext->draw(_composeSurface, _inputIsDirty || forceRedraw);
-
 	return true;
 }
 


Commit: 9f01925253ea70f4f58a2918bcf0b821015b7271
    https://github.com/scummvm/scummvm/commit/9f01925253ea70f4f58a2918bcf0b821015b7271
Author: dhruv (dhruvranger97 at gmail.com)
Date: 2026-01-21T02:36:55+03:00

Commit Message:
GRAPHICS: MACGUI: add scrollbar drag support for win95 games.

previously scrolling for win95 games did not had dragging support, this
commit adds that and fixes a constructor mismatch bug.

Changed paths:
    graphics/macgui/mactextwindow.cpp
    graphics/macgui/mactextwindow.h


diff --git a/graphics/macgui/mactextwindow.cpp b/graphics/macgui/mactextwindow.cpp
index ec6b93b3d38..af1adee9a64 100644
--- a/graphics/macgui/mactextwindow.cpp
+++ b/graphics/macgui/mactextwindow.cpp
@@ -55,8 +55,7 @@ MacTextWindow::MacTextWindow(MacWindowManager *wm, const Font *font, int fgcolor
 		MacWindow(wm->getLastId(), true, true, true, wm), _bgcolor(bgcolor), _maxWidth(maxWidth), _menu(menu) {
 
 	_font = nullptr;
-	_mactext = new MacText(Common::U32String(""), _wm, font, fgcolor, bgcolor, maxWidth, textAlignment, 0, padding);
-
+	_mactext = new MacText(Common::U32String(""), _wm, font, fgcolor, bgcolor, maxWidth, textAlignment, 0, true);
 	_fontRef = font;
 
 	init();
@@ -78,6 +77,10 @@ void MacTextWindow::init() {
 	_nextWheelEventTime = 0;
 	_scrollDelay = 50;
 
+	_isDragging = false;
+	_dragStartY = 0;
+	_dragStartScrollPos = 0;
+
 	// Disable autoselect on activation
 	_mactext->setAutoSelect(false);
 
@@ -98,7 +101,7 @@ void MacTextWindow::resize(int w, int h) {
 	MacWindow::resize(w, h);
 
 	_maxWidth = getInnerDimensions().width();
-	_mactext->resize(_maxWidth, h);
+	_mactext->resize(_maxWidth, getInnerDimensions().height());
 }
 
 void MacTextWindow::setDimensions(const Common::Rect &r) {
@@ -209,16 +212,27 @@ bool MacTextWindow::draw(bool forceRedraw) {
 
 	uint32 now = g_system->getMillis();
 	// check if we need to hide the scroll bar
-	if (_nextWheelEventTime != 0 && now >= _nextWheelEventTime) {
-		if (_scrollDirection == kBorderNone && _clickedScrollPart == kBorderNone) {
-			setScroll(0, 0);         // hide the scrollbar
-			_nextWheelEventTime = 0; // reset timer
+	if (!(_wm->_mode & kWMModeWin95)) {
+		if (_nextWheelEventTime != 0 && now >= _nextWheelEventTime) {
+			if (_scrollDirection == kBorderNone && _clickedScrollPart == kBorderNone) {
+				setScroll(0, 0);         // hide the scrollbar
+				_nextWheelEventTime = 0; // reset timer
+			}
 		}
 	}
 
 	// handle mouse button scrolling
 	if (_scrollDirection != kBorderNone) {
-		if (now >= _nextScrollTime) {
+		Common::Point mousePos = g_system->getEventManager()->getMousePos();
+		if (isInBorder(mousePos.x, mousePos.y) != _clickedScrollPart) {
+			_scrollDirection = kBorderNone;
+			setHighlight(kBorderNone);
+			calcScrollBar();
+			if (!(_wm->_mode & kWMModeWin95)) {
+				setScroll(0, 0);
+			}
+		}
+		if (_scrollDirection != kBorderNone && now >= _nextScrollTime) {
 			if (_scrollDirection == kBorderScrollUp) {
 				_mactext->scroll(-1);
 			} else if (_scrollDirection == kBorderScrollDown) {
@@ -311,27 +325,133 @@ void MacTextWindow::calcScrollBar() {
 	setScroll(scrollPos, scrollSize);
 }
 
+void MacTextWindow::calcWin95Scroll(int &scrollAreaTop, int &scrollAreaHeight, int &barY, int &barHeight) {
+	const BorderOffsets &offsets = getBorderOffsets();
+
+	scrollAreaTop = offsets.upperScrollHeight;
+	scrollAreaHeight = _dims.height() - offsets.upperScrollHeight - offsets.lowerScrollHeight;
+
+	int contentHeight = _mactext->getTextHeight();
+	int winHeight = getInnerDimensions().height();
+
+	// if content fits in the current window
+	if (contentHeight <= winHeight || contentHeight == 0) {
+		barHeight = scrollAreaHeight;
+		barY = scrollAreaTop;
+		return;
+	}
+
+	float winRatio = (float)winHeight / (float)contentHeight;
+	barHeight = MAX<int>(8, (int)(scrollAreaHeight * winRatio)); // 8 the min height of scrollBar
+
+	int maxTextScroll = contentHeight - winHeight;
+	int maxBarScroll = scrollAreaHeight - barHeight;
+
+	float scrollAmount = (float)_mactext->_scrollPos / (float)maxTextScroll;
+
+	barY = scrollAreaTop + (int)(maxBarScroll * scrollAmount);
+}
+
 bool MacTextWindow::processEvent(Common::Event &event) {
 	WindowClick click = isInBorder(event.mouse.x, event.mouse.y);
 
+	if (!(g_system->getEventManager()->getButtonState() & Common::EventManager::LBUTTON)) {
+		if (_isDragging) {
+			_isDragging = false;
+		}
+		if (_clickedScrollPart != kBorderNone || _scrollDirection != kBorderNone) {
+			_scrollDirection = kBorderNone;
+			_clickedScrollPart = kBorderNone;
+			setHighlight(kBorderNone);
+
+			if (!(_wm->_mode & kWMModeWin95)) {
+				setScroll(0, 0);
+			}
+			return true;
+		}
+	}
+
 	if (event.type == Common::EVENT_MOUSEMOVE) {
-		if (_clickedScrollPart == kBorderScrollUp || _clickedScrollPart == kBorderScrollDown) {
-			if (click == _clickedScrollPart) {
-				// we are on the bar, so keep scrolling 
-				if (_scrollDirection != _clickedScrollPart) {
-					_scrollDirection = _clickedScrollPart;
-					setHighlight(_clickedScrollPart);
-					calcScrollBar();
+		if (!(_wm->_mode & kWMModeWin95)) {
+			if (_clickedScrollPart == kBorderScrollUp || _clickedScrollPart == kBorderScrollDown) {
+				if (click == kBorderScrollUp) {
+					if (_scrollDirection != kBorderScrollUp) {
+						_scrollDirection = kBorderScrollUp;
+						_clickedScrollPart = kBorderScrollUp;
+						setHighlight(kBorderScrollUp);
+						calcScrollBar();
+					}
+				} else if (click == kBorderScrollDown) {
+					if (_scrollDirection != kBorderScrollDown) {
+						_scrollDirection = kBorderScrollDown;
+						_clickedScrollPart = kBorderScrollDown;
+						setHighlight(kBorderScrollDown);
+						calcScrollBar();
+					}
+				} else {
+					if (_scrollDirection != kBorderNone) {
+						_scrollDirection = kBorderNone;
+						setHighlight(kBorderNone);
+						calcScrollBar();
+						setScroll(0, 0);
+					}
 				}
-			} else {
-				// we moved away from the bar, pause scrolling
-				if (_scrollDirection != kBorderNone) {
-					_scrollDirection = kBorderNone;
-					setHighlight(kBorderNone);
-					calcScrollBar();
-					setScroll(0, 0);
+				return true;
+			}
+		} else if ((_wm->_mode & kWMModeWin95) && _isDragging) {
+			int scrollAreaTop, scrollAreaHeight, barY, barHeight;
+			calcWin95Scroll(scrollAreaTop, scrollAreaHeight, barY, barHeight);
+
+			int relMouseY = event.mouse.y - _dims.top;
+			int diffY = relMouseY - _dragStartY;
+
+			if (diffY != 0) {
+				int maxTextScroll = _mactext->getTextHeight() - getInnerDimensions().height();
+				int maxBarScroll = scrollAreaHeight - barHeight;
+
+				if (maxBarScroll > 0) {
+					float pixelsPerUnit = (float)maxTextScroll / (float)maxBarScroll;
+					int newScrollPos = _dragStartScrollPos + (int)(diffY * pixelsPerUnit);
+
+					newScrollPos = CLIP<int>(newScrollPos, 0, maxTextScroll);
+
+					if (newScrollPos != _mactext->_scrollPos) {
+						_mactext->_scrollPos = newScrollPos;
+						_mactext->setDirty(true);
+						_contentIsDirty = true;
+						_borderIsDirty = true;
+						calcScrollBar();
+					}
+				}
+			}
+			return true;
+		}
+	}
+
+	if (event.type == Common::EVENT_LBUTTONDOWN) {
+		if ((_wm->_mode & kWMModeWin95) && (click == kBorderScrollUp || click == kBorderScrollDown)) {
+			const BorderOffsets &offsets = getBorderOffsets();
+			int scrollBarWidth = offsets.right;
+
+			if (event.mouse.x >= _dims.right - scrollBarWidth && event.mouse.x < _dims.right) {
+				int scrollAreaTop, scrollAreaHeight, barY, barHeight;
+				calcWin95Scroll(scrollAreaTop, scrollAreaHeight, barY, barHeight);
+
+				int relMouseY = event.mouse.y - _dims.top;
+
+				if (relMouseY >= barY && relMouseY < barY + barHeight) {
+					_isDragging = true;
+					_dragStartY = relMouseY; // store window relative Y
+					_dragStartScrollPos = _mactext->_scrollPos;
+					return true;
 				}
 			}
+		}
+	}
+
+	if (event.type == Common::EVENT_LBUTTONUP) {
+		if (_isDragging) {
+			_isDragging = false;
 			return true;
 		}
 	}
@@ -387,6 +507,7 @@ bool MacTextWindow::processEvent(Common::Event &event) {
 	if (event.type == Common::EVENT_WHEELUP) {
 		//setHighlight(kBorderScrollUp);
 		_mactext->scroll(-2);
+		_contentIsDirty = true;
 		calcScrollBar();
 
 		_nextWheelEventTime = g_system->getMillis() + SCROLLBAR_DELAY; // hide the bar after 300ms from now
@@ -396,6 +517,7 @@ bool MacTextWindow::processEvent(Common::Event &event) {
 	if (event.type == Common::EVENT_WHEELDOWN) {
 		//setHighlight(kBorderScrollDown);
 		_mactext->scroll(2);
+		_contentIsDirty = true;
 		calcScrollBar();
 
 		_nextWheelEventTime = g_system->getMillis() + SCROLLBAR_DELAY; // hide the bar after 300ms from now
@@ -415,7 +537,9 @@ bool MacTextWindow::processEvent(Common::Event &event) {
 			_clickedScrollPart = kBorderNone;
 			setHighlight(kBorderNone);
 			// hide the scroll bar
-			setScroll(0, 0);
+			if (!(_wm->_mode & kWMModeWin95)) {
+				setScroll(0, 0);
+			}
 			return true;
 		}
 
@@ -426,7 +550,9 @@ bool MacTextWindow::processEvent(Common::Event &event) {
 		_scrollDirection = kBorderNone;
 		_clickedScrollPart = kBorderNone;
 		setHighlight(kBorderNone);
-		setScroll(0, 0);
+		if (!(_wm->_mode & kWMModeWin95)) {
+			setScroll(0, 0);
+		}
 		return true;
 	}
 
diff --git a/graphics/macgui/mactextwindow.h b/graphics/macgui/mactextwindow.h
index 27baeade051..51e0d8975d8 100644
--- a/graphics/macgui/mactextwindow.h
+++ b/graphics/macgui/mactextwindow.h
@@ -104,6 +104,8 @@ private:
 	void drawInput();
 	void drawSelection();
 
+	void calcWin95Scroll(int &scrollAreaTop, int &scrollAreaHeight, int &barY, int &barHeight);
+
 public:
 	bool _editable;
 	bool _selectable;
@@ -124,6 +126,11 @@ private:
 	uint32 _nextWheelEventTime;
 	WindowClick _clickedScrollPart;
 
+	// dragging bar for win95
+	bool _isDragging;
+	int _dragStartY;
+	int _dragStartScrollPos;
+
 	MacMenu *_menu;
 
 	int _bgcolor;


Commit: 3d991420d56a735689f78fef645e25defb922428
    https://github.com/scummvm/scummvm/commit/3d991420d56a735689f78fef645e25defb922428
Author: dhruv (dhruvranger97 at gmail.com)
Date: 2026-01-21T02:36:55+03:00

Commit Message:
GRAPHICS: MACGUI: fix scrolbar gap when at bottom arrow.

previously, the scrollbar did not cover some pixels when at the bottom of the scroll area which left a gap which was inconsistent with the top arrow, this commit fixes that by changing calculation logic of scrollPos.

Changed paths:
    graphics/macgui/mactextwindow.cpp


diff --git a/graphics/macgui/mactextwindow.cpp b/graphics/macgui/mactextwindow.cpp
index af1adee9a64..70b2ca3a70b 100644
--- a/graphics/macgui/mactextwindow.cpp
+++ b/graphics/macgui/mactextwindow.cpp
@@ -308,20 +308,26 @@ void MacTextWindow::calcScrollBar() {
 	int maxText = 0, maxScrollbar = 0, displayHeight = 0;
 
 	displayHeight = getInnerDimensions().height();
-
 	maxScrollbar = getDimensions().height() - getBorderOffsets().upperScrollHeight - getBorderOffsets().lowerScrollHeight;
+	maxText = _mactext->getTextHeight();
 
 	// if we enable the win95 mode but the text height is smaller than window height, then we don't draw the scrollbar
-	if (_wm->_mode & kWMModeWin95 && displayHeight > _mactext->getTextHeight() && !_editable)
+	if (_wm->_mode & kWMModeWin95 && displayHeight > maxText && !_editable)
 		return;
 
+	int maxScroll = 0;
+	// identical to MacText scroll() logic
 	if (_editable)
-		maxText = _mactext->getTextHeight() + getInnerDimensions().height();
+		maxScroll = maxText - kConScrollStep;
 	else
-		maxText = MAX<int>(_mactext->getTextHeight(), displayHeight);
+		maxScroll = maxText - displayHeight;
+
+	float contentHeight = (float)(maxText + displayHeight);
+	float scrollSize = (float)(maxScrollbar * displayHeight) / contentHeight;
+	int range = maxScrollbar - (int)scrollSize - 1;
 
-	float scrollSize = (float)maxScrollbar * (float)displayHeight / (float)maxText;
-	float scrollPos = (float)_mactext->_scrollPos * (float)maxScrollbar / (float)maxText;
+	float ratio = CLIP<float>((float)_mactext->_scrollPos / (float)maxScroll, 0.0f, 1.0f);
+	float scrollPos = ratio * (float)range;
 	setScroll(scrollPos, scrollSize);
 }
 


Commit: 349bdd0bcf411be57feaeb014764473ae4fa3ac5
    https://github.com/scummvm/scummvm/commit/349bdd0bcf411be57feaeb014764473ae4fa3ac5
Author: dhruv (dhruvranger97 at gmail.com)
Date: 2026-01-21T02:36:55+03:00

Commit Message:
WAGE: fix missing quit text in The Sultan's Palace.

previously the quit text was empty for the sultan's palace fixed it by explicitly checking if the resource string is empty, if it is then we use the default message.

Changed paths:
    engines/wage/world.cpp


diff --git a/engines/wage/world.cpp b/engines/wage/world.cpp
index 0b4373b62b6..291ce84ba8e 100644
--- a/engines/wage/world.cpp
+++ b/engines/wage/world.cpp
@@ -184,7 +184,7 @@ bool World::loadWorld(Common::MacResManager *resMan) {
 	} else {
 		_saveBeforeQuitMessage = new Common::String("Save changes before quiting?");
 	}
-	if ((message = loadStringFromDITL(resMan, 2490, 3)) != NULL) {
+	if ((message = loadStringFromDITL(resMan, 2490, 3)) != NULL && !message->empty()) {
 		message->trim();
 		debug(2, "_saveBeforeCloseMessage: %s", message->c_str());
 		_saveBeforeCloseMessage = message;


Commit: 14e2c5e2c7c976f0026d70e453834dfacfec4ad2
    https://github.com/scummvm/scummvm/commit/14e2c5e2c7c976f0026d70e453834dfacfec4ad2
Author: dhruv (dhruvranger97 at gmail.com)
Date: 2026-01-21T02:36:55+03:00

Commit Message:
GRAPHICS: MACGUI: fix #16334 title box bug.

the box for the window title in WAGE games are not always the correct size, this was due to the max width of the box being clamped to less than what the title width max had which was an offset of 8 added that to max width for the box as well.

Changed paths:
    engines/wage/guiborders.cpp
    graphics/macgui/datafiles.cpp
    graphics/macgui/macwindowborder.cpp
    graphics/macgui/macwindowborder.h


diff --git a/engines/wage/guiborders.cpp b/engines/wage/guiborders.cpp
index 1ffa6545ec4..efac50ee2d2 100644
--- a/engines/wage/guiborders.cpp
+++ b/engines/wage/guiborders.cpp
@@ -295,6 +295,7 @@ void Gui::loadBorder(Graphics::MacWindow *target, const char *border[], uint hei
 	offsets.upperScrollHeight = 16;
 	offsets.lowerScrollHeight = 16;
 	offsets.titlePos = titlePos;
+	offsets.titlePadding = 8;
 	target->setBorder(surface, flags, offsets);
 }
 
diff --git a/graphics/macgui/datafiles.cpp b/graphics/macgui/datafiles.cpp
index 48fad7f7d0c..718f26087c2 100644
--- a/graphics/macgui/datafiles.cpp
+++ b/graphics/macgui/datafiles.cpp
@@ -43,27 +43,27 @@ struct BorderName {
 };
 
 static const BorderName borders[] = {
-	{0x00, 					"StandardClose",			{ 1,  2, 19,  2,		 2,  2,		false,	25,  15, 8, 11, -1, 0, 0, 0}},
-	{0x01, 					"ThickNoTitle",		 		{ 5,  5,  5,  5,		-1, -1,		false,	0,  -1, -1, 0,  -1, 0, 0, 0}},
-	{0x02, 					"ThinNoTitle",		 		{ 1,  1,  1,  1,		-1, -1,		false,	0,  -1, -1, 0,  -1, 0, 0, 0}},
-	{0x03, 					"ThinNoTitleShadow",		{ 1,  3,  1,  3,		-1, -1,		false,	0,  -1, -1, 0,  -1, 0, 0, 0}},
-	{0x04, 					"StandardClose",			{ 1,  2, 19,  2,		 2,  2,		false,	25,  15, 8, 11, -1, 0, 0, 0}},
-	{0x05, 					"Thick",					{ 5,  5, 20,  5,		 2,  3,		false,	13, -1, -1, 0,  -1, 0, 0, 0}},
-	{0x06, 					"ThinNoTitle",		 		{ 1,  1,  1,  1,		-1, -1,		false,	0,  -1, -1, 0,  -1, 0, 0, 0}},
-	{0x07, 					"ThinNoTitleShadow",		{ 1,  3,  1,  3,		-1, -1, 	false,	0,  -1, -1, 0,  -1, 0, 0, 0}},
-	{0x08, 					"StandardCloseZoom",		{ 1,  2, 19,  2,		 2,  2,		false,	25,  15, 8, 11, -1, 0, 0, 0}},
-	{0x09, 					"ThickZoom",				{ 5,  5, 20,  5,		 2,  3,		false,	13, -1, -1, 0,  -1, 0, 0, 0}},
-	{0x0A, 					"ThinNoTitle",		 		{ 1,  1,  1,  1,		-1, -1,		false,	0,  -1, -1, 0,  -1, 0, 0, 0}},
-	{0x0B, 					"ThinNoTitleShadow",  		{ 1,  3,  1,  3,		-1, -1,		false,	0,  -1, -1, 0,  -1, 0, 0, 0}},
-	{0x0C, 					"StandardCloseZoom",		{ 1,  2, 19,  2,		 2,  2,		false,	25,  15, 8, 11, -1, 0, 0, 0}},
-	{0x0D, 					"ThickZoom",				{ 5,  5, 20,  5,		 2,  3,		false,	13, -1, -1, 0,  -1, 0, 0, 0}},
-	{0x0E, 					"ThinNoTitle",		 		{ 1,  1,  1,  1,		-1, -1,		false,	0,  -1, -1, 0,  -1, 0, 0, 0}},
-	{0x0F, 					"ThinNoTitleShadow",  		{ 1,  3,  1,  3,		-1, -1,		false,	0,  -1, -1, 0,  -1, 0, 0, 0}},
-	{0x10, 					"RoundClose",		 		{ 1,  1, 19,  6,		 1,  1,		true,	25,  15, 8, 11, -1, 0, 0, 0}},
-	{kBorderScroll + 0x00,	"Win95BorderScrollbar",		{ 1,  17, 1,  1,		 1,  1,		true,	25, -1, -1, 0,  -1, 0, 15, 17}},
-	{kBorderScroll + 0x01, 	"Win95NoBorderScrollbar",	{ 1,  17, 1,  1,		 1,  1,		true,	25, -1, -1, 0,  -1, 0, 15, 17}},
-	{kBorderScroll + 0x02, 	"MacOSNoBorderScrollbar",	{ 1,  17, 1,  1,		 1,  1,		true,	25, -1, -1, 0,  -1, 0, 17, 17}},
-	{0xFF, 					"No type",			 		{-1, -1, -1, -1,		-1, -1,		false,	0,  -1, -1, 0,  -1, 0, 0, 0}}
+	{0x00, 					"StandardClose",			{ 1,  2, 19,  2,		 2,  2,		false,	25,  15, 8, 11, -1, 0, 0, 0, 0}},
+	{0x01, 					"ThickNoTitle",		 		{ 5,  5,  5,  5,		-1, -1,		false,	0,  -1, -1, 0,  -1, 0, 0, 0, 0}},
+	{0x02, 					"ThinNoTitle",		 		{ 1,  1,  1,  1,		-1, -1,		false,	0,  -1, -1, 0,  -1, 0, 0, 0, 0}},
+	{0x03, 					"ThinNoTitleShadow",		{ 1,  3,  1,  3,		-1, -1,		false,	0,  -1, -1, 0,  -1, 0, 0, 0, 0}},
+	{0x04, 					"StandardClose",			{ 1,  2, 19,  2,		 2,  2,		false,	25,  15, 8, 11, -1, 0, 0, 0, 0}},
+	{0x05, 					"Thick",					{ 5,  5, 20,  5,		 2,  3,		false,	13, -1, -1, 0,  -1, 0, 0, 0, 0}},
+	{0x06, 					"ThinNoTitle",		 		{ 1,  1,  1,  1,		-1, -1,		false,	0,  -1, -1, 0,  -1, 0, 0, 0, 0}},
+	{0x07, 					"ThinNoTitleShadow",		{ 1,  3,  1,  3,		-1, -1, 	false,	0,  -1, -1, 0,  -1, 0, 0, 0, 0}},
+	{0x08, 					"StandardCloseZoom",		{ 1,  2, 19,  2,		 2,  2,		false,	25,  15, 8, 11, -1, 0, 0, 0, 0}},
+	{0x09, 					"ThickZoom",				{ 5,  5, 20,  5,		 2,  3,		false,	13, -1, -1, 0,  -1, 0, 0, 0, 0}},
+	{0x0A, 					"ThinNoTitle",		 		{ 1,  1,  1,  1,		-1, -1,		false,	0,  -1, -1, 0,  -1, 0, 0, 0, 0}},
+	{0x0B, 					"ThinNoTitleShadow",  		{ 1,  3,  1,  3,		-1, -1,		false,	0,  -1, -1, 0,  -1, 0, 0, 0, 0}},
+	{0x0C, 					"StandardCloseZoom",		{ 1,  2, 19,  2,		 2,  2,		false,	25,  15, 8, 11, -1, 0, 0, 0, 0}},
+	{0x0D, 					"ThickZoom",				{ 5,  5, 20,  5,		 2,  3,		false,	13, -1, -1, 0,  -1, 0, 0, 0, 0}},
+	{0x0E, 					"ThinNoTitle",		 		{ 1,  1,  1,  1,		-1, -1,		false,	0,  -1, -1, 0,  -1, 0, 0, 0, 0}},
+	{0x0F, 					"ThinNoTitleShadow",  		{ 1,  3,  1,  3,		-1, -1,		false,	0,  -1, -1, 0,  -1, 0, 0, 0, 0}},
+	{0x10, 					"RoundClose",		 		{ 1,  1, 19,  6,		 1,  1,		true,	25,  15, 8, 11, -1, 0, 0, 0, 0}},
+	{kBorderScroll + 0x00,	"Win95BorderScrollbar",		{ 1,  17, 1,  1,		 1,  1,		true,	25, -1, -1, 0,  -1, 0, 15, 17, 8}},
+	{kBorderScroll + 0x01, 	"Win95NoBorderScrollbar",	{ 1,  17, 1,  1,		 1,  1,		true,	25, -1, -1, 0,  -1, 0, 15, 17, 8}},
+	{kBorderScroll + 0x02, 	"MacOSNoBorderScrollbar",	{ 1,  17, 1,  1,		 1,  1,		true,	25, -1, -1, 0,  -1, 0, 17, 17, 8}},
+	{0xFF, 					"No type",			 		{-1, -1, -1, -1,		-1, -1,		false,	0,  -1, -1, 0,  -1, 0, 0, 0, 0}}
 };
 
 Common::String windowTypeName(uint32 windowType) {
diff --git a/graphics/macgui/macwindowborder.cpp b/graphics/macgui/macwindowborder.cpp
index 7dfe226cdb7..8048b7abbdd 100644
--- a/graphics/macgui/macwindowborder.cpp
+++ b/graphics/macgui/macwindowborder.cpp
@@ -164,13 +164,13 @@ int MacWindowBorder::getMinHeight(uint32 flags) const {
 void MacWindowBorder::setTitle(const Common::String& title, int width) {
 	_title = title;
 	const Graphics::Font *font = _wm->_fontMan->getFont(Graphics::MacFont(kMacFontSystem, 12));
-	int titleWidth = font->getStringWidth(_title) + 8;
+	int titleWidth = font->getStringWidth(_title) + getOffset().titlePadding;
 
 	// if titleWidth is changed, then we modify it
 	// here, we change all the border that has title
 	for (uint32 i = 0; i < kWindowBorderMaxFlag; i++) {
 		if ((_border[i] != nullptr) && (i & kWindowBorderTitle)) {
-			int maxWidth = MAX<int>(width - _border[i]->getMinWidth() - 7, 0);
+			int maxWidth = MAX<int>(width - _border[i]->getMinWidth() + getOffset().titlePadding, 0);
 			if (titleWidth > maxWidth)
 				titleWidth = maxWidth;
 			_border[i]->modifyTitleWidth(titleWidth);
diff --git a/graphics/macgui/macwindowborder.h b/graphics/macgui/macwindowborder.h
index 587e96e15d7..2f9bc3bd07d 100644
--- a/graphics/macgui/macwindowborder.h
+++ b/graphics/macgui/macwindowborder.h
@@ -70,6 +70,7 @@ struct BorderOffsets {
 	int resizeButtonHeight;
 	int upperScrollHeight;
 	int lowerScrollHeight;
+	int titlePadding;
 };
 
 /**


Commit: 630609cc81327fcf066883259fd1bf71413de388
    https://github.com/scummvm/scummvm/commit/630609cc81327fcf066883259fd1bf71413de388
Author: dhruv (dhruvranger97 at gmail.com)
Date: 2026-01-21T02:36:55+03:00

Commit Message:
GRAPHICS: MACGUI: fix #16331 wrong size for dialog box.

previously, the dialog box was way bigger than what was required for the quit text and the text was also misaligned, this commit fixes that issue.

Changed paths:
    engines/wage/gui.cpp
    graphics/macgui/macdialog.cpp


diff --git a/engines/wage/gui.cpp b/engines/wage/gui.cpp
index 4ea3ac76761..002625463e0 100644
--- a/engines/wage/gui.cpp
+++ b/engines/wage/gui.cpp
@@ -619,11 +619,11 @@ bool Gui::saveDialog() {
 	Graphics::MacFont font;
 
 	Graphics::MacText saveBeforeCloseMessage(*_engine->_world->_saveBeforeCloseMessage, _wm, &font, Graphics::kColorBlack,
-									  Graphics::kColorWhite, 291, Graphics::kTextAlignCenter);
+									  Graphics::kColorWhite, 250, Graphics::kTextAlignCenter);
 
 	_engine->sayText(*_engine->_world->_saveBeforeCloseMessage);
 
-	Graphics::MacDialog save(&_screen, _wm, 291, &saveBeforeCloseMessage, 291, &buttons, 1);
+	Graphics::MacDialog save(&_screen, _wm, 291, &saveBeforeCloseMessage, 250, &buttons, 1);
 
 	int button = save.run();
 
diff --git a/graphics/macgui/macdialog.cpp b/graphics/macgui/macdialog.cpp
index 205037b5b84..968b90ae034 100644
--- a/graphics/macgui/macdialog.cpp
+++ b/graphics/macgui/macdialog.cpp
@@ -59,13 +59,26 @@
 namespace Graphics {
 
 enum {
-	kDialogHeight = 113
+	kDialogHeight = 113,
+	kDialogBottomPadding = 15
 };
 
 MacDialog::MacDialog(ManagedSurface *screen, MacWindowManager *wm, int width, MacText *mactext, int maxTextWidth, MacDialogButtonArray *buttons, uint defaultButton) :
 	_screen(screen), _wm(wm), _mactext(mactext), _maxTextWidth(maxTextWidth), _buttons(buttons), _defaultButton(defaultButton) {
+	// if we have buttons the height of the dialog box should resize accordingly
+	int buttonBottomPos = 0;
+	if (_buttons) {
+		for (uint i = 0; i < _buttons->size(); i++) {
+			if ((*_buttons)[i]->bounds.bottom > buttonBottomPos)
+				buttonBottomPos = (*_buttons)[i]->bounds.bottom;
+		}
+	}
 
-	int height = kDialogHeight + _mactext->getTextHeight();
+	int height;
+	if (buttonBottomPos > 0)
+		height = buttonBottomPos + kDialogBottomPadding;
+	else 
+		height = kDialogHeight + _mactext->getTextHeight();
 
 	_font = getDialogFont();
 


Commit: 7dca55e6221247f8faa7ab7fa3b836b43d2fabc5
    https://github.com/scummvm/scummvm/commit/7dca55e6221247f8faa7ab7fa3b836b43d2fabc5
Author: dhruv (dhruvranger97 at gmail.com)
Date: 2026-01-21T02:36:55+03:00

Commit Message:
GRAPHICS: MACGUI: fix about dialog box.

the volume slider was overlapping the OK button in the about section of all games fixed that by adding a dummy button to push the volume slider position down.

Changed paths:
    engines/wage/gui.cpp


diff --git a/engines/wage/gui.cpp b/engines/wage/gui.cpp
index 002625463e0..2c54fa45587 100644
--- a/engines/wage/gui.cpp
+++ b/engines/wage/gui.cpp
@@ -575,9 +575,15 @@ void Gui::aboutDialog() {
 	Graphics::MacDialogButtonArray buttons;
 
 	buttons.push_back(new Graphics::MacDialogButton("OK", 191, aboutMessage.getTextHeight() + 30, 68, 28));
+	// add a dummy button to push volume slider position down
+	// to avoid the overlapping of volume slider with OK button in the about section
+	buttons.push_back(new Graphics::MacDialogButton("", 0, aboutMessage.getTextHeight() + 100, 0, 0));
 
 	AboutDialog about(&_screen, _wm, 450, &aboutMessage, 400, &buttons, 0);
 
+	delete buttons.back();
+	buttons.pop_back();
+
 	int button = about.run();
 
 	if (button == Graphics::kMacDialogQuitRequested)


Commit: 41bc84efd56398ca8d5066795cd812b02cd7d823
    https://github.com/scummvm/scummvm/commit/41bc84efd56398ca8d5066795cd812b02cd7d823
Author: dhruv (dhruvranger97 at gmail.com)
Date: 2026-01-21T02:36:55+03:00

Commit Message:
GRAPHICS: MACGUI: force menu close before dialog box renders.

Changed paths:
    engines/wage/gui.cpp
    graphics/macgui/macdialog.cpp


diff --git a/engines/wage/gui.cpp b/engines/wage/gui.cpp
index 2c54fa45587..f20f9091f10 100644
--- a/engines/wage/gui.cpp
+++ b/engines/wage/gui.cpp
@@ -583,6 +583,9 @@ void Gui::aboutDialog() {
 
 	delete buttons.back();
 	buttons.pop_back();
+	// close the menu before calling run because it blocks execution
+	if (_menu)
+		_menu->closeMenu();
 
 	int button = about.run();
 
diff --git a/graphics/macgui/macdialog.cpp b/graphics/macgui/macdialog.cpp
index 968b90ae034..51d6df1e1ba 100644
--- a/graphics/macgui/macdialog.cpp
+++ b/graphics/macgui/macdialog.cpp
@@ -177,6 +177,12 @@ int MacDialog::run() {
 	bool shouldQuitEngine = false;
 	bool shouldQuit = false;
 	Common::Rect r(_bbox);
+	// we set _fullRefresh to true inside closeMenu() but it does not update the screen
+	// to ensure we capture the background without the menu we must force a draw
+	// draw() checks _fullRefresh flag which is set to true by closeMenu()
+	// so draw() will draw the screen again without the menu pixels
+	// if we don't call draw() then the background captured in the next line has the pixels of the menu.
+	_wm->draw();
 
 	_tempSurface->copyRectToSurface(_screen->getBasePtr(_bbox.left, _bbox.top), _screen->pitch, 0, 0, _bbox.width() + 1, _bbox.height() + 1);
 	_wm->pushCursor(kMacCursorArrow, nullptr);


Commit: 89765fe991b43eaea9308ec183b77139c6b22f9b
    https://github.com/scummvm/scummvm/commit/89765fe991b43eaea9308ec183b77139c6b22f9b
Author: dhruv (dhruvranger97 at gmail.com)
Date: 2026-01-21T02:36:55+03:00

Commit Message:
GRAPHICS: MACGUI: fix #16266 Wrong window priority.

Changed paths:
    engines/wage/gui.cpp
    graphics/macgui/macwindow.cpp
    graphics/macgui/macwindow.h


diff --git a/engines/wage/gui.cpp b/engines/wage/gui.cpp
index f20f9091f10..ff1492436ad 100644
--- a/engines/wage/gui.cpp
+++ b/engines/wage/gui.cpp
@@ -167,10 +167,29 @@ void Gui::draw() {
 
 		_scene = _engine->_world->_player->_currentScene;
 
-		_sceneWindow->setTitle(_scene->_name);
-		_sceneWindow->setDimensions(*_scene->_designBounds);
+		Common::Rect sceneBounds = *_scene->_designBounds;
+		const Graphics::BorderOffsets &offsets = _sceneWindow->getBorderOffsets();
+
+		int maxTitleWidth = sceneBounds.width() - (kWindowMinWidth - offsets.right);
+		Common::String displayTitle = _scene->_name;
+
+		if (maxTitleWidth > 0) {
+			const Graphics::Font *titleFont = getTitleFont();
+			if (titleFont) {
+				// keep deleting the last character untill the title fits
+				while (displayTitle.size() > 0 && titleFont->getStringWidth(displayTitle) > maxTitleWidth) {
+					displayTitle.deleteLastChar();
+				}
+			}
+		} else {
+			displayTitle.clear();
+		}
+
+		_sceneWindow->setTitle(displayTitle);
+		_sceneWindow->setDimensions(sceneBounds);
 		_consoleWindow->setDimensions(*_scene->_textBounds);
 
+		_wm->setActiveWindow(_consoleWindow->getId());
 		_wm->setFullRefresh(true);
 	}
 
@@ -410,6 +429,10 @@ const Graphics::Font *Gui::getConsoleFont() {
 	return _wm->_fontMan->getFont(*getConsoleMacFont());
 }
 
+const Graphics::Font *Gui::getTitleFont() {
+	return _wm->_fontMan->getFont(Graphics::MacFont(Graphics::kMacFontSystem, 12));
+}
+
 void Gui::appendText(const char *s) {
 	_consoleWindow->appendText(s, getConsoleMacFont());
 }
diff --git a/graphics/macgui/macwindow.cpp b/graphics/macgui/macwindow.cpp
index 7225b67b22c..60d4008fd19 100644
--- a/graphics/macgui/macwindow.cpp
+++ b/graphics/macgui/macwindow.cpp
@@ -138,6 +138,8 @@ void MacWindow::setActive(bool active) {
 bool MacWindow::isActive() const { return _active; }
 
 void MacWindow::resize(int w, int h) {
+	w = MAX(w, (int)kWindowMinWidth);
+	h = MAX(h, (int)kWindowMinHeight);
 	if (_composeSurface->w == w && _composeSurface->h == h)
 		return;
 
diff --git a/graphics/macgui/macwindow.h b/graphics/macgui/macwindow.h
index 04a7af6757d..07391f94dab 100644
--- a/graphics/macgui/macwindow.h
+++ b/graphics/macgui/macwindow.h
@@ -45,7 +45,9 @@ enum WindowType {
 };
 
 enum {
-	kBorderWidth = 17
+	kBorderWidth = 17,
+	kWindowMinWidth = 70,
+	kWindowMinHeight = 70
 };
 
 enum WindowClick {
@@ -398,6 +400,7 @@ public:
 
 	void setMode(uint32 mode) { _mode = mode; }
 	void setBorderOffsets(BorderOffsets &offsets) { _macBorder.setOffsets(offsets); }
+	const BorderOffsets &getBorderOffsets() const { return _macBorder.getOffset(); }
 
 	void updateInnerDims();
 
@@ -417,7 +420,6 @@ private:
 protected:
 	void drawBorder();
 	WindowClick isInBorder(int x, int y) const;
-	const BorderOffsets &getBorderOffsets() const { return _macBorder.getOffset(); }
 
 protected:
 	ManagedSurface _borderSurface;


Commit: 34e111224d5c272cf243a6142c98d8d4c1e3f60a
    https://github.com/scummvm/scummvm/commit/34e111224d5c272cf243a6142c98d8d4c1e3f60a
Author: dhruv (dhruvranger97 at gmail.com)
Date: 2026-01-21T02:36:55+03:00

Commit Message:
WAGE: add a debugger to render design step by step.

we can now view each step of drawing a design in imgui with the opcodes that were called in script.

Changed paths:
    engines/wage/debugtools.cpp
    engines/wage/design.cpp
    engines/wage/design.h
    engines/wage/dt-internal.h


diff --git a/engines/wage/debugtools.cpp b/engines/wage/debugtools.cpp
index e95bd358b8c..a3535e386c0 100644
--- a/engines/wage/debugtools.cpp
+++ b/engines/wage/debugtools.cpp
@@ -42,8 +42,34 @@ namespace Wage {
 
 ImGuiState *_state = nullptr;
 
-ImGuiImage getImageID(Designed *d, const char *type) {
-	Common::String key = Common::String::format("%s:%s", d->_name.c_str(), type);
+ImGuiImage getPatternImage(int fillType) {
+	int index = fillType - 1;
+	if (index >= 0 && index < (int)_state->_patternTextures.size()) {
+		return _state->_patternTextures[index];
+	}
+	return {0, 0, 0};
+}
+
+void drawThicknessIcon(int thickness) {
+	ImVec2 pos = ImGui::GetCursorScreenPos();
+	ImDrawList *dl = ImGui::GetWindowDrawList();
+
+	// draw Border
+	dl->AddRect(pos, pos + ImVec2(16, 16), IM_COL32(255, 255, 255, 255));
+
+	float half = thickness * 0.5f;
+	// draw Bar
+	dl->AddRectFilled(
+		ImVec2(pos.x, pos.y + 8 - half),
+		ImVec2(pos.x + 16, pos.y + 8 + half),
+		IM_COL32(255, 255, 255, 255));
+
+	ImGui::Dummy(ImVec2(16, 16));
+	ImGui::SetItemTooltip("Line thickness: %d", thickness);
+}
+
+ImGuiImage getImageID(Designed *d, const char *type, int steps = -1) {
+	Common::String key = Common::String::format("%s:%s:%d", d->_name.c_str(), type, steps);
 
 	if (_state->_images.contains(key))
 		return _state->_images[key];
@@ -64,7 +90,7 @@ ImGuiImage getImageID(Designed *d, const char *type) {
 	surface->create(sx, sy, Graphics::PixelFormat::createFormatCLUT8());
 	surface->fillRect(Common::Rect(0, 0, sx, sy), 4); // white background
 
-	d->_design->paint(surface, *g_wage->_world->_patterns, 0, 0);
+	d->_design->paint(surface, *g_wage->_world->_patterns, 0, 0, steps);
 
 	if (surface->surfacePtr()) {
 		_state->_images[key] = { (ImTextureID)g_system->getImGuiTexture(*surface->surfacePtr(), g_wage->_gui->_wm->getPalette(), g_wage->_gui->_wm->getPaletteSize()), sx, sy };
@@ -80,12 +106,137 @@ void showImage(const ImGuiImage &image, float scale) {
 
 	ImGui::BeginGroup();
 	ImVec2 screenPos = ImGui::GetCursorScreenPos();
-	ImGui::GetWindowDrawList()->AddRect(screenPos, screenPos + ImVec2(size.x, size.y), 0xFFFFFFFF);
+    ImGui::Image(image.id, size);
 
-	ImGui::Image(image.id, size);
+	ImGui::GetWindowDrawList()->AddRect(screenPos, screenPos + ImVec2(size.x, size.y), 0xFFFFFFFF);
 	ImGui::EndGroup();
 }
 
+void showDesignScriptWindow(Design *d) {
+	if (!_state->_showScriptWindow)
+		return;
+
+	ImGui::SetNextWindowSize(ImVec2(520, 600), ImGuiCond_FirstUseEver);
+
+	if (ImGui::Begin("Design Script", &_state->_showScriptWindow)) {
+
+		for (uint i = 0; i < d->_drawOps.size(); i++) {
+			const DrawOp &op = d->_drawOps[i];
+			// check if this is the current step
+			bool isCurrent = (_state->_currentStep == i + 1);
+
+			if (isCurrent) {
+				ImVec2 pos = ImGui::GetCursorScreenPos();
+				float width = ImGui::GetContentRegionAvail().x;
+				ImDrawList *dl = ImGui::GetWindowDrawList();
+
+				ImU32 green = IM_COL32(0, 255, 0, 255);
+				ImU32 greenTransparent = IM_COL32(0, 255, 0, 50);
+
+				// draw Arrow 
+				dl->AddText(pos, green, ICON_MS_ARROW_RIGHT_ALT);
+
+				// draw highlight
+				dl->AddRectFilled(
+					ImVec2(pos.x + 16.f, pos.y),
+					ImVec2(pos.x + width, pos.y + 16.f),
+					greenTransparent,
+					0.4f);
+			}
+
+			// offset text to the right so it doesn't overlap the arrow
+			ImGui::SetCursorPosX(ImGui::GetCursorPosX() + 16.0f);
+
+			// opcode
+			ImGui::TextColored(ImVec4(0.5f, 0.5f, 0.5f, 1.0f), "[%5d]", op.offset);
+			ImGui::SameLine();
+
+			// draw current foreground fill pattern
+			if (op.fillType > 0) {
+				ImGuiImage patImg = getPatternImage(op.fillType);
+				if (patImg.id) {
+					showImage(patImg, 1.0f);
+					ImGui::SetItemTooltip("Foreground pattern: %d", op.fillType);
+					ImGui::SameLine();
+				}
+			}
+
+			// draw current border fill pattern
+			if (op.borderFillType > 0) {
+				ImGuiImage patImg = getPatternImage(op.borderFillType);
+				if (patImg.id) {
+					showImage(patImg, 1.0f);
+					ImGui::SetItemTooltip("Border pattern: %d", op.borderFillType);
+					ImGui::SameLine();
+				}
+			}
+
+			// draw line thickness icon
+			if (op.borderThickness > 0) {
+				drawThicknessIcon(op.borderThickness);
+				ImGui::SameLine();
+			}
+
+			Common::String label = Common::String::format("%s##%d", op.opcode.c_str(), i);
+			if (ImGui::Selectable(label.c_str(), isCurrent)) {
+				_state->_currentStep = i + 1;
+			}
+			if (isCurrent) {
+				ImGui::SetItemDefaultFocus();
+			}
+		}
+	}
+	ImGui::End();
+}
+
+void showDesignViewer(Designed *item, const char *typeStr) {
+	if (!item)
+		return;
+
+	Design *d = item->_design;
+	if (d) {
+		if (d->_drawOps.empty()) {
+			// force render to populate ops
+			Graphics::ManagedSurface tmp;
+			tmp.create(1, 1, Graphics::PixelFormat::createFormatCLUT8());
+			d->paint(&tmp, *g_wage->_world->_patterns, 0, 0, -1);
+			tmp.free();
+		}
+
+		uint totalSteps = d->_drawOps.size();
+
+		if (ImGui::Button(ICON_MS_SKIP_PREVIOUS)) {
+			if (_state->_currentStep > 1)
+				_state->_currentStep--;
+			else
+				_state->_currentStep = totalSteps;
+		}
+		ImGui::SameLine();
+
+		if (ImGui::Button(ICON_MS_SKIP_NEXT)) {
+			if (_state->_currentStep < totalSteps)
+				_state->_currentStep++;
+			else
+				_state->_currentStep = 1;
+		}
+		ImGui::SameLine();
+
+		if (ImGui::Button("Script")) {
+			_state->_showScriptWindow = !_state->_showScriptWindow;
+		}
+		ImGui::SameLine();
+		ImGui::Text("Step: %d / %d", (_state->_currentStep), totalSteps);
+
+		if (d->getBounds()) {
+			ImGuiImage imgID = getImageID(item, typeStr, _state->_currentStep);
+			showImage(imgID, 1.0);
+		}
+		showDesignScriptWindow(d);
+	} else {
+		ImGui::Text("No Design Data");
+	}
+}
+
 static void showWorld() {
 	if (!_state->_showWorld)
 		return;
@@ -122,8 +273,10 @@ static void showWorld() {
 						for (int n = 0; n < (int)g_wage->_world->_orderedScenes.size(); n++) {
 							const bool is_selected = (_state->_selectedScene == n);
 							Common::String label = Common::String::format("%s##%d", g_wage->_world->_orderedScenes[n]->_name.c_str(), n);
-							if (ImGui::Selectable(label.c_str(), is_selected))
+							if (ImGui::Selectable(label.c_str(), is_selected)) {
 								_state->_selectedScene = n;
+								_state->_currentStep = 1;
+							}
 
 							// Set the initial focus when opening the combo (scrolling + keyboard navigation focus)
 							if (is_selected)
@@ -160,9 +313,8 @@ static void showWorld() {
 						}
 
 						if (ImGui::BeginTabItem("Design")) {
-							ImGuiImage imgID = getImageID(g_wage->_world->_orderedScenes[_state->_selectedScene], "obj");
-
-							showImage(imgID, 1.0);
+							Designed *d = g_wage->_world->_orderedScenes[_state->_selectedScene];
+							showDesignViewer(d, "scene");
 							ImGui::EndTabItem();
 						}
 
@@ -201,9 +353,7 @@ static void showWorld() {
 				{ // Right pane
 					ImGui::BeginChild("ChildR", ImVec2(ImGui::GetContentRegionAvail().x, ImGui::GetContentRegionAvail().y), ImGuiChildFlags_Borders);
 
-					ImGuiImage imgID = getImageID(g_wage->_world->_orderedObjs[_state->_selectedObj], "obj");
-
-					showImage(imgID, 1.0);
+					showDesignViewer(g_wage->_world->_orderedObjs[_state->_selectedObj], "obj");
 
 					ImGui::EndChild();
 				}
@@ -315,6 +465,25 @@ void onImGuiInit() {
 	ImGui::addTTFFontFromArchive("MaterialSymbolsSharp.ttf", 16.f, &icons_config, icons_ranges);
 
 	_state = new ImGuiState();
+
+	// pre-load patterns into array
+	Graphics::MacPatterns &patterns = *g_wage->_world->_patterns;
+	for (uint i = 0; i < patterns.size(); ++i) {
+		Graphics::ManagedSurface surface(16, 16, Graphics::PixelFormat::createFormatCLUT8());
+		Common::Rect rect(16, 16);
+		Design::drawFilledRect(&surface, rect, kColorBlack, patterns, i + 1);
+
+		if (surface.surfacePtr()) {
+			ImTextureID tex = (ImTextureID)g_system->getImGuiTexture(
+				*surface.surfacePtr(),
+				g_wage->_gui->_wm->getPalette(),
+				g_wage->_gui->_wm->getPaletteSize());
+
+			_state->_patternTextures.push_back({tex, 16, 16});
+		} else {
+			_state->_patternTextures.push_back({0, 0, 0});
+		}
+	}
 }
 
 void onImGuiRender() {
diff --git a/engines/wage/design.cpp b/engines/wage/design.cpp
index cf66cdb40a2..dcba32b2009 100644
--- a/engines/wage/design.cpp
+++ b/engines/wage/design.cpp
@@ -75,6 +75,8 @@ Design::Design(Common::SeekableReadStream *data) {
 	_maskImage = nullptr;
 
 	_boundsCalculationMode = false;
+	_renderedSteps = 0;
+	_lastOpString = "";
 }
 
 Design::~Design() {
@@ -86,13 +88,13 @@ Design::~Design() {
 	delete _maskImage;
 }
 
-void Design::paint(Graphics::ManagedSurface *surface, Graphics::MacPatterns &patterns, int x, int y) {
+void Design::paint(Graphics::ManagedSurface *surface, Graphics::MacPatterns &patterns, int x, int y, int steps) {
 	bool needRender = false;
 
 	if (_surface == NULL) {
 		_boundsCalculationMode = true;
 		_bounds->debugPrint(4, "Internal bounds:");
-		render(patterns);
+		render(patterns, -1);
 		_boundsCalculationMode = false;
 		if (_bounds->right == -10000) {
 			_bounds->left = _bounds->top = _bounds->right = _bounds->bottom = 0;
@@ -130,9 +132,16 @@ void Design::paint(Graphics::ManagedSurface *surface, Graphics::MacPatterns &pat
 	return;
 #endif
 
-	if (needRender)
+	if (_drawOps.empty())
 		render(patterns);
 
+	// only re-render if the requested step count changed
+	if (needRender || _renderedSteps != steps) {
+		_surface->clear(kColorGreen);
+		render(patterns, steps);
+		_renderedSteps = steps;
+	}
+
 	if (_bounds->width() && _bounds->height()) {
 		const int padding = 14;
 //		Common::Rect from(padding, padding, _bounds->width() - 2 * padding, _bounds->height() - 2 * padding);
@@ -144,11 +153,13 @@ void Design::paint(Graphics::ManagedSurface *surface, Graphics::MacPatterns &pat
 	}
 }
 
-void Design::render(Graphics::MacPatterns &patterns) {
+void Design::render(Graphics::MacPatterns &patterns, int steps) {
 	Common::MemoryReadStream in(_data, _len);
 	bool needRender = true;
+	int currentStep = 0;
 
 	while (needRender) {
+		uint32 opOffset = in.pos();
 		byte fillType = in.readByte();
 		byte borderThickness = in.readByte();
 		byte borderFillType = in.readByte();
@@ -158,6 +169,7 @@ void Design::render(Graphics::MacPatterns &patterns) {
 			break;
 
 		debug(8, "fill: %d borderFill: %d border: %d type: %d", fillType, borderFillType, borderThickness, type);
+
 		switch (type) {
 		case 4:
 			drawRect(_surface, in, patterns, fillType, borderThickness, borderFillType);
@@ -180,6 +192,22 @@ void Design::render(Graphics::MacPatterns &patterns) {
 			break;
 		}
 
+		if (_boundsCalculationMode && !_lastOpString.empty()) {
+			DrawOp op;
+			op.offset = opOffset;
+			op.fillType = fillType;
+			op.borderThickness = borderThickness;
+			op.borderFillType = borderFillType;
+			op.opcode = _lastOpString;
+			_drawOps.push_back(op);
+			_lastOpString = "";
+		}
+		currentStep++;
+
+		if (steps > 0 && currentStep >= steps) {
+			needRender = false;
+			break;
+		}
 		//g_system->copyRectToScreen(_surface->getPixels(), _surface->pitch, 0, 0, _surface->w, _surface->h);
 		//((WageEngine *)g_engine)->processEvents();
 		//g_system->updateScreen();
@@ -337,6 +365,9 @@ void Design::drawRect(Graphics::ManagedSurface *surface, Common::ReadStream &in,
 	if (y1 > y2)
 		SWAP(y1, y2);
 
+	if (_boundsCalculationMode)
+		_lastOpString = Common::String::format("rect %d, %d, %d, %d", x1, y1, x2, y2);
+
 	if (_boundsCalculationMode) {
 		_bounds->top = MIN(y1, _bounds->top);
 		_bounds->left = MIN(x1, _bounds->left);
@@ -390,6 +421,9 @@ void Design::drawRoundRect(Graphics::ManagedSurface *surface, Common::ReadStream
 	if (y1 > y2)
 		SWAP(y1, y2);
 
+	if (_boundsCalculationMode) 
+		_lastOpString = Common::String::format("roundRect %d, %d, %d, %d", x1, y1, x2, y2);
+
 	if (_surface) {
 		if (!_maskImage) {
 			_maskImage = new Graphics::ManagedSurface(_surface->w, _surface->h);
@@ -479,6 +513,16 @@ void Design::drawPolygon(Graphics::ManagedSurface *surface, Common::ReadStream &
 	xcoords.push_back(x1);
 	ycoords.push_back(y1);
 
+	if (_boundsCalculationMode) {
+		Common::String vertices;
+		for (uint i = 0; i < xcoords.size(); ++i) {
+			vertices += Common::String::format("(%d,%d)", xcoords[i], ycoords[i]);
+			if (i < xcoords.size() - 1)
+				vertices += ", ";
+		}
+		_lastOpString = Common::String::format("polygon %d, %d, %d, %d, [%s]", bx1, by1, bx2, by2, vertices.c_str());
+	}
+
 	if (borderThickness > 1) {
 		for (uint i = 0; i < xcoords.size(); ++i) {
 			xcoords[i] += borderThickness / 2;
@@ -521,6 +565,9 @@ void Design::drawOval(Graphics::ManagedSurface *surface, Common::ReadStream &in,
 	PlotData pd(surface, &patterns, fillType, 1, this);
 	PlotDataPrimitives primitives;
 
+	if (_boundsCalculationMode)
+		_lastOpString = Common::String::format("oval %d, %d, %d, %d", x1, y1, x2, y2);
+
 	if (_surface) {
 		if (!_maskImage) {
 			_maskImage = new Graphics::ManagedSurface(_surface->w, _surface->h);
@@ -567,6 +614,9 @@ void Design::drawBitmap(Graphics::ManagedSurface *surface, Common::SeekableReadS
 	int w = x2 - x1;
 	int h = y2 - y1;
 
+	if (_boundsCalculationMode)
+		_lastOpString = Common::String::format("bitmap %d, %d, %d, %d [%d bytes]", x1, y1, x2, y2, numBytes);
+
 	if (_surface) {
 		if (!_maskImage) {
 			_maskImage = new Graphics::ManagedSurface(_surface->w, _surface->h);
diff --git a/engines/wage/design.h b/engines/wage/design.h
index 36ec9903bd1..a45823aa7b0 100644
--- a/engines/wage/design.h
+++ b/engines/wage/design.h
@@ -57,11 +57,23 @@ namespace Wage {
 
 using namespace Graphics::MacGUIConstants;
 
+// struct to hold design operation data
+struct DrawOp {
+	uint32 offset;
+	Common::String opcode;
+	int fillType;
+	int borderThickness;
+	int borderFillType;
+	int lineSize;
+};
+
 class Design {
 public:
 	Design(Common::SeekableReadStream *data);
 	~Design();
 
+	Common::Array<DrawOp> _drawOps;
+
 	void setBounds(Common::Rect *bounds) {
 		_bounds = bounds;
 	}
@@ -70,7 +82,7 @@ public:
 		return _bounds;
 	}
 
-	void paint(Graphics::ManagedSurface *canvas, Graphics::MacPatterns &patterns, int x, int y);
+	void paint(Graphics::ManagedSurface *canvas, Graphics::MacPatterns &patterns, int x, int y, int steps = -1);
 	bool isInBounds(int x, int y);
 	static void drawRect(Graphics::ManagedSurface *surface, const Common::Rect &rect, int thickness, int color, Graphics::MacPatterns &patterns, byte fillType);
 	static void drawRect(Graphics::ManagedSurface *surface, int x1, int y1, int x2, int y2, int thickness, int color, Graphics::MacPatterns &patterns, byte fillType);
@@ -85,13 +97,15 @@ public:
 private:
 	byte *_data;
 	int _len;
+	int _renderedSteps;
+	Common::String _lastOpString;
 	Common::Rect *_bounds;
 	Graphics::ManagedSurface *_surface;
 	bool _boundsCalculationMode;
 	Graphics::ManagedSurface *_maskImage;
 
 private:
-	void render(Graphics::MacPatterns &patterns);
+	void render(Graphics::MacPatterns &patterns, int steps = -1);
 	void drawRect(Graphics::ManagedSurface *surface, Common::ReadStream &in,
 		Graphics::MacPatterns &patterns, byte fillType, byte borderThickness, byte borderFillType);
 	void drawRoundRect(Graphics::ManagedSurface *surface, Common::ReadStream &in,
diff --git a/engines/wage/dt-internal.h b/engines/wage/dt-internal.h
index b76902e3ab0..ff9a6759a92 100644
--- a/engines/wage/dt-internal.h
+++ b/engines/wage/dt-internal.h
@@ -34,9 +34,11 @@ typedef struct ImGuiState {
 	bool _showWorld = false;
 
 	Common::HashMap<Common::String, ImGuiImage> _images;
+	Common::Array<ImGuiImage> _patternTextures;
 
 	ImGuiTextFilter _nameFilter;
-
+	int _currentStep = 1;
+	bool _showScriptWindow = false;
 	int _selectedScene = 0;
 	int _selectedObj = 0;
 	int _selectedChr = 0;


Commit: 6c6ecc88e175dac98d32b0a183d6b72fdba9eae1
    https://github.com/scummvm/scummvm/commit/6c6ecc88e175dac98d32b0a183d6b72fdba9eae1
Author: dhruv (dhruvranger97 at gmail.com)
Date: 2026-01-21T02:36:55+03:00

Commit Message:
WAGE: fix black bars in design.

Changed paths:
    engines/wage/design.cpp


diff --git a/engines/wage/design.cpp b/engines/wage/design.cpp
index dcba32b2009..a8483d73577 100644
--- a/engines/wage/design.cpp
+++ b/engines/wage/design.cpp
@@ -706,7 +706,7 @@ void Design::drawBitmap(Graphics::ManagedSurface *surface, Common::SeekableReadS
 			if (x1 < 0)
 				x = -x1;
 
-			byte *src = (byte *)tmp.getBasePtr(0, y);
+			byte *src = (byte *)tmp.getBasePtr(x, y);
 			byte *dst = (byte *)surface->getBasePtr(x1 + x, y1 + y);
 			byte *mask = (byte *)_maskImage->getBasePtr(x1 + x, y1 + y);
 


Commit: e0531c1d8233986ec3d0fa6a174e4fc2ccecb1db
    https://github.com/scummvm/scummvm/commit/e0531c1d8233986ec3d0fa6a174e4fc2ccecb1db
Author: dhruv (dhruvranger97 at gmail.com)
Date: 2026-01-21T02:36:55+03:00

Commit Message:
WAGE: add missing combat logic

previously, there were a lot of missing combat features regarding running, spiritual attack, weapon accuracy etc., this commit has been made by checking the logic in the original and trying to mimic the same.

Changed paths:
    engines/wage/combat.cpp
    engines/wage/entities.cpp
    engines/wage/entities.h
    engines/wage/wage.cpp


diff --git a/engines/wage/combat.cpp b/engines/wage/combat.cpp
index 73aa96dc13d..c19bff567b1 100644
--- a/engines/wage/combat.cpp
+++ b/engines/wage/combat.cpp
@@ -98,8 +98,15 @@ void WageEngine::encounter(Chr *player, Chr *chr) {
 }
 
 void WageEngine::performCombatAction(Chr *npc, Chr *player) {
-	if (npc->_context._frozen)
-		return;
+	if (npc->_context._frozen) {
+		npc->_context._freezeTimer--;
+		if (npc->_context._freezeTimer <= 0) {
+			npc->_context._frozen = false;
+			npc->_context._freezeTimer = 0;
+		} else {
+			return;
+		}
+	}
 
 	RandomHat hat(_rnd);
 
@@ -107,8 +114,8 @@ void WageEngine::performCombatAction(Chr *npc, Chr *player) {
 	int validMoves = getValidMoveDirections(npc);
 	ObjArray *weapons = npc->getWeapons(false);
 	ObjArray *magics = npc->getMagicalObjects();
-	// TODO: Figure out under what circumstances we need to add +1
-	// for the chance (e.g. only when all values were set to 0?).
+	// if all values are zero we add +1 so that an action has some chance of occuring even if
+	// all stats have value zero, preventing the AI from getting stuck doing nothing
 	if (winning) {
 		if (!_world->_weaponMenuDisabled) {
 			if (!weapons->empty())
@@ -133,6 +140,11 @@ void WageEngine::performCombatAction(Chr *npc, Chr *player) {
 			hat.addTokens(kTokOffer, npc->_losingOffer + 1);
 	}
 
+	// if an enemy has no weapons or magics then it should have a chance to stay idle
+	// if we do not add this then an unarmed enemy will always keep running from room to room
+	if (weapons->empty() && magics->empty())
+		hat.addTokens(kTokNone, 1);
+
 	ObjList *objs = &npc->_currentScene->_objs;
 	if (npc->_inventory.size() < npc->_maximumCarriedObjects) {
 		int cnt = 0;
@@ -147,11 +159,9 @@ void WageEngine::performCombatAction(Chr *npc, Chr *player) {
 	int token = hat.drawToken();
 	switch (token) {
 	case kTokWeapons:
-		// TODO: I think the monster should choose the "best" weapon.
 		performAttack(npc, player, weapons->operator[](_rnd->getRandomNumber(weapons->size() - 1)));
 		break;
 	case kTokMagic:
-		// TODO: I think the monster should choose the "best" magic.
 		performMagic(npc, player, magics->operator[](_rnd->getRandomNumber(magics->size() - 1)));
 		break;
 	case kTokRun:
@@ -184,7 +194,6 @@ void WageEngine::performAttack(Chr *attacker, Chr *victim, Obj *weapon) {
 	if (_world->_weaponMenuDisabled)
 		return;
 
-	// TODO: verify that a player not aiming will always target the chest??
 	int targetIndex = -1;
 	char buf[256];
 
@@ -220,8 +229,9 @@ void WageEngine::performAttack(Chr *attacker, Chr *victim, Obj *weapon) {
 
 	bool usesDecremented = false;
 	int chance = _rnd->getRandomNumber(255);
-	// TODO: what about obj accuracy
-	if (chance < attacker->_physicalAccuracy) {
+	// average of character skill + weapon accuracy
+	int hitAccuracy = (attacker->_physicalAccuracy + weapon->_accuracy) / 2;
+	if (chance < hitAccuracy) {
 		usesDecremented = attackHit(attacker, victim, weapon, targetIndex);
 	} else if (weapon->_type != Obj::MAGICAL_OBJECT) {
 		appendText("A miss!");
@@ -257,22 +267,59 @@ void WageEngine::decrementUses(Obj *obj) {
 bool WageEngine::attackHit(Chr *attacker, Chr *victim, Obj *weapon, int targetIndex) {
 	bool receivedHitTextPrinted = false;
 	char buf[512];
+	int damage = 0;
+	int defense = 0;
 
-	if (targetIndex != -1) {
-		Obj *armor = victim->_armor[targetIndex];
-		if (armor != NULL) {
-			// TODO: Absorb some damage.
-			snprintf(buf, 512, "%s%s's %s weakens the impact of %s%s's %s.",
-				victim->getDefiniteArticle(true), victim->_name.c_str(),
-				victim->_armor[targetIndex]->_name.c_str(),
-				attacker->getDefiniteArticle(false), attacker->_name.c_str(),
-				weapon->_name.c_str());
-			appendText(buf);
-			decrementUses(armor);
+	if (weapon->_type == Obj::MAGICAL_OBJECT) {
+		// Damage = (Spell Power + Spiritual Strength) - 1
+		damage = (weapon->_damage + attacker->_spiritualStength) - 1;
+
+		// if victim has 0 or less Spiritual HP, any spell power kills them instantly
+		// we override defense to ensure the damage goes through
+		if (victim->_context._statVariables[SPIR_HIT_CUR] <= 0 && damage > 0) {
+			damage = 9999; // massive damage to force kill
+			defense = 0;
 		} else {
-			snprintf(buf, 512, "A hit to the %s!", targets[targetIndex]);
-			appendText(buf);
+			// Defense = Resistance + Spiritual Armor
+			defense = victim->_resistanceToMagic;
+
+			// check all equipped items for spiritual armor
+			for (int i = 0; i < Chr::NUMBER_OF_ARMOR_TYPES; i++) {
+				Obj *arm = victim->_armor[i];
+				if (arm != NULL && arm->_type == Obj::SPIRITUAL_ARMOR) {
+					defense += arm->_damage;
+				}
+			}
+		}
+
+		appendText(weapon->_useMessage.c_str());
+		appendText("The spell is effective!");
+
+	} else {
+		// Damage = (Weapon Power + Phys Strength) - 1
+		damage = (weapon->_damage + attacker->_physicalStrength) - 1;
+
+		// Defense = Natural Armor + Item Armor
+		defense = victim->_naturalArmor;
+
+		if (targetIndex != -1) {
+			Obj *armor = victim->_armor[targetIndex];
+			if (armor != NULL) {
+				defense += armor->_damage;
+
+				snprintf(buf, 512, "%s%s's %s weakens the impact of %s%s's %s.",
+						 victim->getDefiniteArticle(true), victim->_name.c_str(),
+						 victim->_armor[targetIndex]->_name.c_str(),
+						 attacker->getDefiniteArticle(false), attacker->_name.c_str(),
+						 weapon->_name.c_str());
+				appendText(buf);
+				decrementUses(armor);
+			} else {
+				snprintf(buf, 512, "A hit to the %s!", targets[targetIndex]);
+				appendText(buf);
+			}
 		}
+
 		debugC(1, kDebugSound, "** Attacker hit sound: %s", attacker->_scoresHitSound.c_str());
 		playSound(attacker->_scoresHitSound);
 		appendText(attacker->_scoresHitComment.c_str());
@@ -280,11 +327,12 @@ bool WageEngine::attackHit(Chr *attacker, Chr *victim, Obj *weapon, int targetIn
 		playSound(victim->_receivesHitSound);
 		appendText(victim->_receivesHitComment.c_str());
 		receivedHitTextPrinted = true;
-	} else if (weapon->_type == Obj::MAGICAL_OBJECT) {
-		appendText(weapon->_useMessage.c_str());
-		appendText("The spell is effective!");
 	}
 
+	if (damage < 0)
+		damage = 0;
+
+	// apply damage
 	bool causesPhysicalDamage = true;
 	bool causesSpiritualDamage = false;
 	bool freezesOpponent = false;
@@ -299,52 +347,63 @@ bool WageEngine::attackHit(Chr *attacker, Chr *victim, Obj *weapon, int targetIn
 		freezesOpponent = (type == Obj::FREEZES_OPPONENT);
 	}
 
-	if (causesPhysicalDamage) {
-		victim->_context._statVariables[PHYS_HIT_CUR] -= weapon->_damage;
-
-		/* Do it here to get the right order of messages in case of death. */
-		decrementUses(weapon);
-		usesDecremented = true;
-
-		if (victim->_context._statVariables[PHYS_HIT_CUR] < 0) {
-			debugC(1, kDebugSound, "** Victim dying sound: %s", victim->_dyingSound.c_str());
-			playSound(victim->_dyingSound);
-			appendText(victim->_dyingWords.c_str());
-			snprintf(buf, 512, "%s%s is dead!", victim->getDefiniteArticle(true), victim->_name.c_str());
-			appendText(buf);
-
-			attacker->_context._kills++;
-			attacker->_context._experience += victim->_context._statVariables[SPIR_HIT_CUR] + victim->_context._statVariables[PHYS_HIT_CUR];
+	// calculate net damage
+	int damageTaken = damage - defense;
+	if (damageTaken < 0)
+		damageTaken = 0;
+
+	// apply to hit Points
+	if (causesPhysicalDamage)
+		victim->_context._statVariables[PHYS_HIT_CUR] -= damageTaken;
+	if (causesSpiritualDamage)
+		victim->_context._statVariables[SPIR_HIT_CUR] -= damageTaken;
+
+	decrementUses(weapon);
+	usesDecremented = true;
+
+	// death check
+	// if either physical or spiritual HP drops < 0, the character dies.
+	if (victim->_context._statVariables[PHYS_HIT_CUR] < 0 ||
+		victim->_context._statVariables[SPIR_HIT_CUR] < 0) {
+
+		debugC(1, kDebugSound, "** Victim dying sound: %s", victim->_dyingSound.c_str());
+		playSound(victim->_dyingSound);
+		appendText(victim->_dyingWords.c_str());
+		snprintf(buf, 512, "%s%s is dead!", victim->getDefiniteArticle(true), victim->_name.c_str());
+		appendText(buf);
 
-			if (!victim->_playerCharacter && !victim->_inventory.empty()) {
-				Scene *currentScene = victim->_currentScene;
+		attacker->_context._kills++;
+		attacker->_context._experience += victim->_context._statVariables[SPIR_HIT_BAS] + victim->_context._statVariables[PHYS_HIT_BAS];
 
-				for (int i = victim->_inventory.size() - 1; i >= 0; i--) {
-					if (i < (int)victim->_inventory.size())
-						_world->move(victim->_inventory[i], currentScene);
-				}
-				Common::String *s = getGroundItemsList(currentScene);
-				appendText(s->c_str());
-				delete s;
+		// drop items if not player
+		if (!victim->_playerCharacter && !victim->_inventory.empty()) {
+			Scene *currentScene = victim->_currentScene;
+			for (int i = victim->_inventory.size() - 1; i >= 0; i--) {
+				if (i < (int)victim->_inventory.size())
+					_world->move(victim->_inventory[i], currentScene);
 			}
-			_world->move(victim, _world->_storageScene);
-		} else if (attacker->_playerCharacter && !receivedHitTextPrinted) {
-			double physicalPercent = (double)victim->_context._statVariables[SPIR_HIT_CUR] /
-					victim->_context._statVariables[SPIR_HIT_BAS];
-			snprintf(buf, 512, "%s%s's condition appears to be %s.",
-				victim->getDefiniteArticle(true), victim->_name.c_str(),
-				getPercentMessage(physicalPercent));
-			appendText(buf);
+			Common::String *s = getGroundItemsList(currentScene);
+			appendText(s->c_str());
+			delete s;
 		}
+		_world->move(victim, _world->_storageScene);
+	} else if (attacker->_playerCharacter && !receivedHitTextPrinted && weapon->_type != Obj::MAGICAL_OBJECT) {
+		double physicalPercent = (double)victim->_context._statVariables[PHYS_HIT_CUR] /
+								 victim->_context._statVariables[PHYS_HIT_BAS];
+		snprintf(buf, 512, "%s%s's condition appears to be %s.",
+				 victim->getDefiniteArticle(true), victim->_name.c_str(),
+				 getPercentMessage(physicalPercent));
+		appendText(buf);
 	}
 
-	if (causesSpiritualDamage) {
-		/* TODO */
-		warning("TODO: Spiritual damage");
-	}
-
+	// freeze Logic (No. Of Turns = Power / 50)
 	if (freezesOpponent) {
 		victim->_context._frozen = true;
+		int duration = 0;
+		if (weapon->_damage > 0)
+			duration = weapon->_damage / 50;
+
+		victim->_context._freezeTimer = duration;
 	}
 
 	return usesDecremented;
@@ -358,9 +417,34 @@ void WageEngine::performMagic(Chr *attacker, Chr *victim, Obj *magicalObject) {
 		performHealingMagic(attacker, magicalObject);
 		break;
 	default:
-		performAttack(attacker, victim, magicalObject);
 		break;
 	}
+
+	char buf[256];
+	if (!attacker->_playerCharacter) {
+		snprintf(buf, 256, "%s%s %ss %s%s at %s%s.",
+				 attacker->getDefiniteArticle(true), attacker->_name.c_str(),
+				 magicalObject->_operativeVerb.c_str(),
+				 getIndefiniteArticle(magicalObject->_name), magicalObject->_name.c_str(),
+				 victim->getDefiniteArticle(true), victim->_name.c_str());
+		appendText(buf);
+	}
+
+	debugC(1, kDebugSound, "** Magic sound: %s", magicalObject->_sound.c_str());
+	playSound(magicalObject->_sound);
+
+	int magicAcc = (attacker->_spiritualAccuracy + magicalObject->_accuracy) / 2;
+	int chance = _rnd->getRandomNumber(255);
+
+	// pass -1 to indicate a non physical hit
+	if (chance < magicAcc) 
+		attackHit(attacker, victim, magicalObject, -1);
+	else if (attacker->_playerCharacter) 
+		appendText("The spell has no effect.");
+	else 
+		appendText("A miss!");
+
+	decrementUses(magicalObject);
 }
 
 void WageEngine::performHealingMagic(Chr *chr, Obj *magicalObject) {
@@ -377,18 +461,38 @@ void WageEngine::performHealingMagic(Chr *chr, Obj *magicalObject) {
 	uint chance = _rnd->getRandomNumber(255);
 	if (chance < magicalObject->_accuracy) {
 		int type = magicalObject->_attackType;
+		int power = magicalObject->_damage;
+
+		if (type == Obj::HEALS_PHYSICAL_DAMAGE || type == Obj::HEALS_PHYSICAL_AND_SPIRITUAL_DAMAGE) {
+			int current = chr->_context._statVariables[PHYS_HIT_CUR];
+			int max = chr->_context._statVariables[PHYS_HIT_BAS];
+
+			// do not exceed base HP
+			if (current < max) {
+				current += power;
+				if (current > max)
+					current = max;
+				chr->_context._statVariables[PHYS_HIT_CUR] = current;
+			}
+		}
 
-		if (type == Obj::HEALS_PHYSICAL_DAMAGE || type == Obj::HEALS_PHYSICAL_AND_SPIRITUAL_DAMAGE)
-			chr->_context._statVariables[PHYS_HIT_CUR] += magicalObject->_damage;
+		if (type == Obj::HEALS_SPIRITUAL_DAMAGE || type == Obj::HEALS_PHYSICAL_AND_SPIRITUAL_DAMAGE) {
+			int current = chr->_context._statVariables[SPIR_HIT_CUR];
+			int max = chr->_context._statVariables[SPIR_HIT_BAS];
 
-		if (type == Obj::HEALS_SPIRITUAL_DAMAGE || type == Obj::HEALS_PHYSICAL_AND_SPIRITUAL_DAMAGE)
-			chr->_context._statVariables[SPIR_HIT_CUR] += magicalObject->_damage;
+			// do not exceed base HP
+			if (current < max) {
+				current += power;
+				if (current > max)
+					current = max;
+				chr->_context._statVariables[SPIR_HIT_CUR] = current;
+			}
+		}
 
 		debugC(1, kDebugSound, "** Magical object sound: %s", magicalObject->_sound.c_str());
 		playSound(magicalObject->_sound);
 		appendText(magicalObject->_useMessage.c_str());
 
-		// TODO: what if enemy heals himself?
 		if (chr->_playerCharacter) {
 			double physicalPercent = (double)chr->_context._statVariables[PHYS_HIT_CUR] / chr->_context._statVariables[PHYS_HIT_BAS];
 			double spiritualPercent = (double)chr->_context._statVariables[SPIR_HIT_CUR] / chr->_context._statVariables[SPIR_HIT_BAS];
@@ -408,19 +512,22 @@ static const int directionsY[] = { -1, 1, 0, 0 };
 static const char *const directionsS[] = { "north", "south", "east", "west" };
 
 void WageEngine::performMove(Chr *chr, int validMoves) {
-	// count how many valid moves we have
-	int numValidMoves = 0;
+	// if there are no open exits (validMoves is 0), the enemy is trapped.
+	// they cannot run, and therefore cannot escape.
+	if (validMoves == 0) {
+		return;
+	}
 
+	// we only choose from directions that are not blocked.
+	int numValidMoves = 0;
 	for (int i = 0; i < 4; i++)
 		if ((validMoves & (1 << i)) != 0)
 			numValidMoves++;
 
-	// Now pick random dir
 	int dirNum = _rnd->getRandomNumber(numValidMoves - 1);
-	int dir = 0;
+	int dir = -1;
 
-	// And get it
-	for (int i = 0; i < 4; i++)
+	for (int i = 0; i < 4; i++) {
 		if ((validMoves & (1 << i)) != 0) {
 			if (dirNum == 0) {
 				dir = i;
@@ -428,22 +535,50 @@ void WageEngine::performMove(Chr *chr, int validMoves) {
 			}
 			dirNum--;
 		}
+	}
 
-	char buf[256];
-	snprintf(buf, 256, "%s%s runs %s.", chr->getDefiniteArticle(true), chr->_name.c_str(), directionsS[dir]);
-	appendText(buf);
-
-	_running = chr;
+	// The enemy always runs to the next room first.
 	Scene *currentScene = chr->_currentScene;
 	int destX = currentScene->_worldX + directionsX[dir];
 	int destY = currentScene->_worldY + directionsY[dir];
+	Scene *destScene = _world->getSceneAt(destX, destY);
+
+	if (destScene != NULL) {
+		char buf[256];
+		// prints: "The Wuggly Ump runs West."
+		snprintf(buf, 256, "%s%s runs %s.", chr->getDefiniteArticle(true), chr->_name.c_str(), directionsS[dir]);
+		appendText(buf);
+
+		_running = chr;
+
+		// the enemy is in the new room.
+		_world->move(chr, destScene);
+	}
+
+	// now that they are in the new room, we check if they can "Escape".
+	if (chr->_runningSpeed > 0) {
+		int playerSpeed = _world->_player->_runningSpeed;
+		int enemySpeed = chr->_runningSpeed;
+		int totalSpeed = playerSpeed + enemySpeed;
 
-	_world->move(chr, _world->getSceneAt(destX, destY));
+		// Stop Chance = Player Speed / (Player Speed + Enemy Speed)
+		int stopChance = 0;
+		if (totalSpeed > 0) {
+			stopChance = (playerSpeed * 100) / totalSpeed;
+		}
+
+		// if rand(0, 99) >= stopChance, player failed to stop them.
+		// result: enemy vanishes from the room they just entered (escapes).
+		if (_rnd->getRandomNumber(99) >= stopChance) {
+			_world->move(chr, _world->_storageScene);
+		}
+	}
 }
 
 void WageEngine::performOffer(Chr *attacker, Chr *victim) {
-	/* TODO: choose in a smarter way? */
-	Obj *obj = attacker->_inventory[0];
+	// pick a random object from inventory
+	int r = _rnd->getRandomNumber(attacker->_inventory.size() - 1);
+	Obj *obj = attacker->_inventory[r];
 	char buf[512];
 
 	snprintf(buf, 512, "%s%s offers %s%s.", attacker->getDefiniteArticle(true), attacker->_name.c_str(),
@@ -619,7 +754,7 @@ bool WageEngine::handleInventoryCommand() {
 static const char *const armorMessages[] = {
 	"Head protection:",
 	"Chest protection:",
-	"Shield protection:", // TODO: check message
+	"Side protection:",
 	"Magical protection:"
 };
 
diff --git a/engines/wage/entities.cpp b/engines/wage/entities.cpp
index f897a156da2..e7a9e5d270e 100644
--- a/engines/wage/entities.cpp
+++ b/engines/wage/entities.cpp
@@ -71,6 +71,7 @@ Context::Context() {
 	_kills = 0;
 	_experience = 0;
 	_frozen = false;
+	_freezeTimer = 0;
 
 	for (int i = 0; i < 26 * 9; i++)
 		 _userVariables[i] = 0;
diff --git a/engines/wage/entities.h b/engines/wage/entities.h
index 77a713b5dd5..f4c100ff08a 100644
--- a/engines/wage/entities.h
+++ b/engines/wage/entities.h
@@ -104,6 +104,7 @@ public:
 	int16 _kills;  // Number of characters killed
 	int16 _experience;
 	bool _frozen;
+	int16 _freezeTimer;
 	int16 _userVariables[26 * 9];
 	int16 _statVariables[18];
 };
diff --git a/engines/wage/wage.cpp b/engines/wage/wage.cpp
index b57581e04f5..499298d4225 100644
--- a/engines/wage/wage.cpp
+++ b/engines/wage/wage.cpp
@@ -71,7 +71,7 @@ WageEngine *g_wage = nullptr;
 WageEngine::WageEngine(OSystem *syst, const ADGameDescription *desc) : Engine(syst), _gameDescription(desc) {
 	_rnd = new Common::RandomSource("wage");
 
-	_aim = Chr::CHEST;
+	_aim = Chr::SIDE;
 	_opponentAim = -1;
 	_temporarilyHidden = false;
 	_isGameOver = false;
@@ -450,25 +450,28 @@ void WageEngine::onMove(Designed *what, Designed *from, Designed *to) {
 
 	if (what != player && what->_classType == CHR) {
 		Chr *chr = (Chr *)what;
-		if (to == _world->_storageScene) {
-			int returnTo = chr->_returnTo;
-			if (returnTo != Chr::RETURN_TO_STORAGE) {
-				Common::String returnToSceneName;
-				if (returnTo == Chr::RETURN_TO_INITIAL_SCENE) {
-					returnToSceneName = chr->_initialScene;
-					returnToSceneName.toLowercase();
-				} else {
-					returnToSceneName = "random@";
-				}
-				Scene *scene = getSceneByName(returnToSceneName);
-				if (scene != NULL && scene != _world->_storageScene) {
-					_world->move(chr, scene);
-					// To avoid sleeping twice, return if the above move command would cause a sleep.
-					if (scene == currentScene)
-						return;
-				}
-			}
-		} else if (to == player->_currentScene) {
+		// the original code below forced enemies to immediately respawn if moved to storage.
+		// this broke the "Escape" mechanic, comment it out so they stay in storage.
+		//if (to == _world->_storageScene) {
+		//	int returnTo = chr->_returnTo;
+		//	if (returnTo != Chr::RETURN_TO_STORAGE) {
+		//		Common::String returnToSceneName;
+		//		if (returnTo == Chr::RETURN_TO_INITIAL_SCENE) {
+		//			returnToSceneName = chr->_initialScene;
+		//			returnToSceneName.toLowercase();
+		//		} else {
+		//			returnToSceneName = "random@";
+		//		}
+		//		Scene *scene = getSceneByName(returnToSceneName);
+		//		if (scene != NULL && scene != _world->_storageScene) {
+		//			_world->move(chr, scene);
+		//			// To avoid sleeping twice, return if the above move command would cause a sleep.
+		//			if (scene == currentScene)
+		//				return;
+		//		}
+		//	}
+		//}
+		if (to == player->_currentScene) {
 			if (getMonster() == NULL) {
 				_monster = chr;
 				encounter(player, chr);
@@ -574,7 +577,8 @@ void WageEngine::processTurnInternal(Common::String *textInput, Designed *clickI
 void WageEngine::processTurn(Common::String *textInput, Designed *clickInput) {
 	_commandWasQuick = false;
 	Scene *prevScene = _world->_player->_currentScene;
-	Chr *prevMonster = getMonster();
+	Chr *prevMonster = _monster;
+	Chr *runner = _running;
 	Common::String input;
 
 	if (textInput)
@@ -582,17 +586,61 @@ void WageEngine::processTurn(Common::String *textInput, Designed *clickInput) {
 
 	input.toLowercase();
 
-	processTurnInternal(&input, clickInput);
+	// if the player is frozen, we loop automatically to process enemy turns
+	// without waiting for user input
+	while (_world->_player->_context._frozen) {
+		// decrement Timer
+		_world->_player->_context._freezeTimer--;
+
+		if (_world->_player->_context._freezeTimer <= 0) {
+			_world->_player->_context._frozen = false;
+			_world->_player->_context._freezeTimer = 0;
+			// we break the loop. The player regains control for the next input
+			break;
+		}
+
+		// enemy gets a free attack
+		if (getMonster() != NULL)
+			performCombatAction(getMonster(), _world->_player);
+
+		// since we are inside a while loop, we must
+		// force a screen update or the text will not appear until the end
+		if (_gui) _gui->draw();
+		g_system->updateScreen();
+
+		// if player died during freeze, return
+		if (_isGameOver || _world->_player->_currentScene == _world->_storageScene)
+			return;
+	}
+
+	// only process the player's input if they are not frozen
+	if (!_world->_player->_context._frozen)
+		processTurnInternal(&input, clickInput);
+
 	Scene *playerScene = _world->_player->_currentScene;
 
 	if (prevScene != playerScene && playerScene != _world->_storageScene) {
 		if (prevMonster != NULL) {
 			bool followed = false;
-			if (getMonster() == NULL) {
+			bool monsterEscaped = false;
+
+			// check if the previous monster followed us to the new room
+			if (prevMonster->_currentScene != playerScene) {
+				// monster is gone did it escape?
+				// if the monster we were fighting was running, and is now in storage that means it escaped
+				if (prevMonster == runner && prevMonster->_currentScene == _world->_storageScene) {
+					char buf[512];
+					snprintf(buf, 512, "%s%s escapes!", prevMonster->getDefiniteArticle(true), prevMonster->_name.c_str());
+					appendText(buf);
+					monsterEscaped = true;
+				}
 				// TODO: adjacent scenes doesn't contain up/down etc... verify that monsters can't follow these...
-				if (_world->scenesAreConnected(playerScene, prevMonster->_currentScene)) {
-					int chance = _rnd->getRandomNumber(255);
-					followed = (chance < prevMonster->_followsOpponent);
+				// only check follow logic if monster did not just escape to storage
+				if (!monsterEscaped) {
+					if (_world->scenesAreConnected(playerScene, prevMonster->_currentScene)) {
+						int chance = _rnd->getRandomNumber(255);
+						followed = (chance < prevMonster->_followsOpponent);
+					}
 				}
 			}
 
@@ -603,7 +651,8 @@ void WageEngine::processTurn(Common::String *textInput, Designed *clickInput) {
 				appendText(buf);
 
 				_world->move(prevMonster, playerScene);
-			} else {
+			} else if (!monsterEscaped) {
+				// only say "You escape" if the monster did not already "Escape"
 				snprintf(buf, 512, "You escape %s%s.", prevMonster->getDefiniteArticle(false), prevMonster->_name.c_str());
 				appendText(buf);
 			}


Commit: 738808f2bcc306fbfd89694747e71544a925d528
    https://github.com/scummvm/scummvm/commit/738808f2bcc306fbfd89694747e71544a925d528
Author: dhruv (dhruvranger97 at gmail.com)
Date: 2026-01-21T02:36:55+03:00

Commit Message:
WAGE: fix borders in map labels.

in Escape from inaka, labels of some locations in the map had incomplete borders, this was because the white background was being calculated one pixel less than what it is supposed to be, this commit fixes that.

Changed paths:
    engines/wage/design.cpp


diff --git a/engines/wage/design.cpp b/engines/wage/design.cpp
index a8483d73577..347a274f5b9 100644
--- a/engines/wage/design.cpp
+++ b/engines/wage/design.cpp
@@ -357,8 +357,8 @@ void Design::drawRect(Graphics::ManagedSurface *surface, Common::ReadStream &in,
 				Graphics::MacPatterns &patterns, byte fillType, byte borderThickness, byte borderFillType) {
 	int16 y1 = in.readSint16BE();
 	int16 x1 = in.readSint16BE();
-	int16 y2 = in.readSint16BE() - 1;
-	int16 x2 = in.readSint16BE() - 1;
+	int16 y2 = in.readSint16BE();
+	int16 x2 = in.readSint16BE();
 
 	if (x1 > x2)
 		SWAP(x1, x2);
@@ -401,10 +401,10 @@ void Design::drawRect(Graphics::ManagedSurface *surface, Common::ReadStream &in,
 	}
 
 	if (borderThickness > 0 && borderFillType <= patterns.size()) {
-		primitives.drawLine(x1, y1, x2, y1, kColorBlack, &pd);
-		primitives.drawLine(x2, y1, x2, y2, kColorBlack, &pd);
-		primitives.drawLine(x2, y2, x1, y2, kColorBlack, &pd);
-		primitives.drawLine(x1, y2, x1, y1, kColorBlack, &pd);
+		primitives.drawLine(x1, y1, x2 - 1, y1, kColorBlack, &pd);
+		primitives.drawLine(x2 - 1, y1, x2 - 1, y2 - 1, kColorBlack, &pd);
+		primitives.drawLine(x2 - 1, y2 - 1, x1, y2 - 1, kColorBlack, &pd);
+		primitives.drawLine(x1, y2 - 1, x1, y1, kColorBlack, &pd);
 	}
 }
 




More information about the Scummvm-git-logs mailing list