[Scummvm-git-logs] scummvm master -> eaf0c56182e1fd20b7fac7d12e6bf3d761646a1b

fracturehill noreply at scummvm.org
Sat Sep 23 18:59:54 UTC 2023


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

Summary:
5d7efacf87 NANCY: Add delays to puzzle button presses
693411b199 NANCY: Implement PlayRandomSound record
7ff5915118 NANCY: Simplify font drawing
120160b6e4 NANCY: Implement Autotext
eaf0c56182 NANCY: Implement PeepholePuzzle


Commit: 5d7efacf87cc2b73c4f5574df2762473840e29e4
    https://github.com/scummvm/scummvm/commit/5d7efacf87cc2b73c4f5574df2762473840e29e4
Author: Kaloyan Chehlarski (strahy at outlook.com)
Date: 2023-09-23T21:59:40+03:00

Commit Message:
NANCY: Add delays to puzzle button presses

Added code that waits for an sfx triggered by an action to
stop before the action can be triggered again. This way,
accidental double clicks will not be a problem, and edge
cases where the sfx is longer will not cause it to be skipped
on the second press,

Changed paths:
    engines/nancy/action/puzzle/bombpuzzle.cpp
    engines/nancy/action/puzzle/overridelockpuzzle.cpp
    engines/nancy/action/puzzle/rotatinglockpuzzle.cpp
    engines/nancy/action/puzzle/safedialpuzzle.cpp
    engines/nancy/action/puzzle/setplayerclock.cpp
    engines/nancy/action/puzzle/sliderpuzzle.cpp


diff --git a/engines/nancy/action/puzzle/bombpuzzle.cpp b/engines/nancy/action/puzzle/bombpuzzle.cpp
index f167c0df5a1..8f5fa89687c 100644
--- a/engines/nancy/action/puzzle/bombpuzzle.cpp
+++ b/engines/nancy/action/puzzle/bombpuzzle.cpp
@@ -257,12 +257,14 @@ void BombPuzzle::handleInput(NancyInput &input) {
 
 			if (input.input & NancyInput::kLeftMouseButtonUp) {
 				if (NancySceneState.getHeldItem() == _toolID) {
-					_playerOrder.push_back(i);
-					g_nancy->_sound->playSound(_snipSound);
-					Common::Rect dest = _wireDests[i];
-					dest.translate(-_screenPosition.left, -_screenPosition.top);
-					_drawSurface.blitFrom(_image, _wireSrcs[i], dest);
-					_needsRedraw = true;
+					if (!g_nancy->_sound->isSoundPlaying(_snipSound)) {
+						_playerOrder.push_back(i);
+						g_nancy->_sound->playSound(_snipSound);
+						Common::Rect dest = _wireDests[i];
+						dest.translate(-_screenPosition.left, -_screenPosition.top);
+						_drawSurface.blitFrom(_image, _wireSrcs[i], dest);
+						_needsRedraw = true;
+					}
 				} else {
 					g_nancy->_sound->playSound(_noToolSound);
 				}
diff --git a/engines/nancy/action/puzzle/overridelockpuzzle.cpp b/engines/nancy/action/puzzle/overridelockpuzzle.cpp
index 2e6182914bd..b4756c4da2d 100644
--- a/engines/nancy/action/puzzle/overridelockpuzzle.cpp
+++ b/engines/nancy/action/puzzle/overridelockpuzzle.cpp
@@ -193,7 +193,7 @@ void OverrideLockPuzzle::handleInput(NancyInput &input) {
 		if (NancySceneState.getViewport().convertViewportToScreen(_hotspots[i]).contains(input.mousePos)) {
 			g_nancy->_cursorManager->setCursorType(CursorManager::kHotspot);
 
-			if (input.input & NancyInput::kLeftMouseButtonUp) {
+			if (!g_nancy->_sound->isSoundPlaying(_buttonSound) && input.input & NancyInput::kLeftMouseButtonUp) {
 				drawButton(i, false);
 				_lastPushedButton = i;
 				_timeToPop = g_nancy->getTotalPlayTime() + _buttonPopTime;
diff --git a/engines/nancy/action/puzzle/rotatinglockpuzzle.cpp b/engines/nancy/action/puzzle/rotatinglockpuzzle.cpp
index 8e1c6c758aa..357ad6929eb 100644
--- a/engines/nancy/action/puzzle/rotatinglockpuzzle.cpp
+++ b/engines/nancy/action/puzzle/rotatinglockpuzzle.cpp
@@ -180,7 +180,7 @@ void RotatingLockPuzzle::handleInput(NancyInput &input) {
 		if (NancySceneState.getViewport().convertViewportToScreen(_upHotspots[i]).contains(input.mousePos)) {
 			g_nancy->_cursorManager->setCursorType(CursorManager::kHotspot);
 
-			if (input.input & NancyInput::kLeftMouseButtonUp) {
+			if (!g_nancy->_sound->isSoundPlaying(_clickSound) && input.input & NancyInput::kLeftMouseButtonUp) {
 				g_nancy->_sound->playSound(_clickSound);
 
 				_currentSequence[i] = ++_currentSequence[i] > 9 ? 0 : _currentSequence[i];
@@ -195,7 +195,7 @@ void RotatingLockPuzzle::handleInput(NancyInput &input) {
 		if (NancySceneState.getViewport().convertViewportToScreen(_downHotspots[i]).contains(input.mousePos)) {
 			g_nancy->_cursorManager->setCursorType(CursorManager::kHotspot);
 
-			if (input.input & NancyInput::kLeftMouseButtonUp) {
+			if (!g_nancy->_sound->isSoundPlaying(_clickSound) && input.input & NancyInput::kLeftMouseButtonUp) {
 				g_nancy->_sound->playSound(_clickSound);
 
 				int8 n = _currentSequence[i];
diff --git a/engines/nancy/action/puzzle/safedialpuzzle.cpp b/engines/nancy/action/puzzle/safedialpuzzle.cpp
index d72fc3a7224..1eb2d76a5a4 100644
--- a/engines/nancy/action/puzzle/safedialpuzzle.cpp
+++ b/engines/nancy/action/puzzle/safedialpuzzle.cpp
@@ -220,7 +220,7 @@ void SafeDialPuzzle::handleInput(NancyInput &input) {
 
 		g_nancy->_cursorManager->setCursorType(_useMoveArrows ? CursorManager::kMoveLeft : CursorManager::kRotateCCW);
 
-		if (input.input & NancyInput::kLeftMouseButtonUp && _nextAnim < g_nancy->getTotalPlayTime() &&
+		if (!g_nancy->_sound->isSoundPlaying(_spinSound) && input.input & NancyInput::kLeftMouseButtonUp && _nextAnim < g_nancy->getTotalPlayTime() &&
 				_animState != kReset && _animState != kResetAnim) {
 			if (_current == 0) {
 				_current = _dialSrcs.size() / (1 + _numInbetweens) - 1;
@@ -243,7 +243,7 @@ void SafeDialPuzzle::handleInput(NancyInput &input) {
 
 		g_nancy->_cursorManager->setCursorType(_useMoveArrows ? CursorManager::kMoveRight : CursorManager::kRotateCW);
 
-		if (input.input & NancyInput::kLeftMouseButtonUp && _nextAnim < g_nancy->getTotalPlayTime() &&
+		if (!g_nancy->_sound->isSoundPlaying(_spinSound) && input.input & NancyInput::kLeftMouseButtonUp && _nextAnim < g_nancy->getTotalPlayTime() &&
 				_animState != kReset && _animState != kResetAnim) {
 			drawDialFrame(_current * (1 + _numInbetweens) + 1);
 			_nextAnim = g_nancy->getTotalPlayTime() + (g_nancy->getGameType() == kGameTypeNancy3 ? 250 : 500); // hardcoded
@@ -268,7 +268,7 @@ void SafeDialPuzzle::handleInput(NancyInput &input) {
 	if (NancySceneState.getViewport().convertViewportToScreen(_arrowDest).contains(input.mousePos)) {
 		g_nancy->_cursorManager->setCursorType(CursorManager::kHotspot);
 
-		if (input.input & NancyInput::kLeftMouseButtonUp) {
+		if (!g_nancy->_sound->isSoundPlaying(_selectSound) && input.input & NancyInput::kLeftMouseButtonUp) {
 			g_nancy->_sound->playSound(_selectSound);
 			pushSequence(_current);
 			_drawSurface.blitFrom(_image1, _arrowSrc, _arrowDest);
@@ -281,9 +281,9 @@ void SafeDialPuzzle::handleInput(NancyInput &input) {
 	} else if (NancySceneState.getViewport().convertViewportToScreen(_resetDest).contains(input.mousePos)) {
 		g_nancy->_cursorManager->setCursorType(CursorManager::kHotspot);
 
-		if (input.input & NancyInput::kLeftMouseButtonUp) {
+		if (!g_nancy->_sound->isSoundPlaying(_resetSound) && input.input & NancyInput::kLeftMouseButtonUp) {
 			_drawSurface.blitFrom(_image1, _resetSrc, _resetDest);
-			g_nancy->_sound->playSound(_selectSound);
+			g_nancy->_sound->playSound(_resetSound);
 			_animState = kReset;
 			_nextAnim = g_nancy->getTotalPlayTime() + 500; // hardcoded
 			_current = 0;
diff --git a/engines/nancy/action/puzzle/setplayerclock.cpp b/engines/nancy/action/puzzle/setplayerclock.cpp
index 327507f861b..ea3c4a3c9c1 100644
--- a/engines/nancy/action/puzzle/setplayerclock.cpp
+++ b/engines/nancy/action/puzzle/setplayerclock.cpp
@@ -176,7 +176,7 @@ void SetPlayerClock::handleInput(NancyInput &input) {
 	if (NancySceneState.getViewport().convertViewportToScreen(_cancelButtonDest).contains(input.mousePos)) {
 		g_nancy->_cursorManager->setCursorType(CursorManager::kHotspot);
 
-		if (input.input & NancyInput::kLeftMouseButtonUp) {
+		if (!_clearButton && input.input & NancyInput::kLeftMouseButtonUp) {
 			// Cancel button pressed
 			_drawSurface.blitFrom(_image, _cancelButtonSrc, _cancelButtonDest);
 			_needsRedraw = true;
@@ -193,7 +193,7 @@ void SetPlayerClock::handleInput(NancyInput &input) {
 		if (NancySceneState.getViewport().convertViewportToScreen(_alarmButtonDest).contains(input.mousePos)) {
 			g_nancy->_cursorManager->setCursorType(CursorManager::kHotspot);
 
-			if (input.input & NancyInput::kLeftMouseButtonUp) {
+			if (!_clearButton && input.input & NancyInput::kLeftMouseButtonUp) {
 				// Alarm button pressed
 				_drawSurface.blitFrom(_image, _alarmButtonSrc, _alarmButtonDest);
 				_needsRedraw = true;
@@ -211,7 +211,7 @@ void SetPlayerClock::handleInput(NancyInput &input) {
 			// Time button is active only in alarm mode
 			g_nancy->_cursorManager->setCursorType(CursorManager::kHotspot);
 
-			if (input.input & NancyInput::kLeftMouseButtonUp) {
+			if (!_clearButton && input.input & NancyInput::kLeftMouseButtonUp) {
 				// Alarm button pressed
 				_drawSurface.blitFrom(_image, _timeButtonSrc, _timeButtonDest);
 				_needsRedraw = true;
@@ -226,7 +226,7 @@ void SetPlayerClock::handleInput(NancyInput &input) {
 			// Up button is active only in alarm mode
 			g_nancy->_cursorManager->setCursorType(CursorManager::kHotspot);
 
-			if (input.input & NancyInput::kLeftMouseButtonUp) {
+			if (!_clearButton && input.input & NancyInput::kLeftMouseButtonUp) {
 				// Up button pressed
 				_drawSurface.blitFrom(_image, _upButtonSrc, _upButtonDest);
 				_needsRedraw = true;
@@ -241,7 +241,7 @@ void SetPlayerClock::handleInput(NancyInput &input) {
 			// Down button is active only in alarm mode
 			g_nancy->_cursorManager->setCursorType(CursorManager::kHotspot);
 
-			if (input.input & NancyInput::kLeftMouseButtonUp) {
+			if (!_clearButton && input.input & NancyInput::kLeftMouseButtonUp) {
 				// Down button pressed
 				_drawSurface.blitFrom(_image, _downButtonSrc, _downButtonDest);
 				_needsRedraw = true;
@@ -256,7 +256,7 @@ void SetPlayerClock::handleInput(NancyInput &input) {
 			// Set button is active only in alarm mode
 			g_nancy->_cursorManager->setCursorType(CursorManager::kHotspot);
 
-			if (input.input & NancyInput::kLeftMouseButtonUp) {
+			if (!_clearButton && input.input & NancyInput::kLeftMouseButtonUp) {
 				// Down button pressed
 				_drawSurface.blitFrom(_image, _setButtonSrc, _setButtonDest);
 				_needsRedraw = true;
diff --git a/engines/nancy/action/puzzle/sliderpuzzle.cpp b/engines/nancy/action/puzzle/sliderpuzzle.cpp
index f4907207273..9eea3498898 100644
--- a/engines/nancy/action/puzzle/sliderpuzzle.cpp
+++ b/engines/nancy/action/puzzle/sliderpuzzle.cpp
@@ -229,7 +229,7 @@ void SliderPuzzle::handleInput(NancyInput &input) {
 	if (currentTileX != -1) {
 		g_nancy->_cursorManager->setCursorType(CursorManager::kHotspot);
 
-		if (input.input & NancyInput::kLeftMouseButtonUp) {
+		if (!g_nancy->_sound->isSoundPlaying(_clickSound) && input.input & NancyInput::kLeftMouseButtonUp) {
 			g_nancy->_sound->playSound(_clickSound);
 			switch (direction) {
 			case kUp: {


Commit: 693411b1997da4307ea181458868cb4d1c9c5f97
    https://github.com/scummvm/scummvm/commit/693411b1997da4307ea181458868cb4d1c9c5f97
Author: Kaloyan Chehlarski (strahy at outlook.com)
Date: 2023-09-23T21:59:40+03:00

Commit Message:
NANCY: Implement PlayRandomSound record

Implemented a record type responsible for playing a
random sound from a provided list. Introduced in nancy6.

Changed paths:
    engines/nancy/action/arfactory.cpp
    engines/nancy/action/soundrecords.cpp
    engines/nancy/action/soundrecords.h


diff --git a/engines/nancy/action/arfactory.cpp b/engines/nancy/action/arfactory.cpp
index 3a0eaa1772f..b9d86f99180 100644
--- a/engines/nancy/action/arfactory.cpp
+++ b/engines/nancy/action/arfactory.cpp
@@ -199,6 +199,8 @@ ActionRecord *ActionManager::createActionRecord(uint16 type) {
 		return new StopSound(); // StopAndUnloadSound, but we always unload
 	case 157:
 		return new PlayDigiSoundCC();
+	case 158:
+		return new PlayRandomSound();
 	case 160:
 		return new HintSystem();
 	case 170:
diff --git a/engines/nancy/action/soundrecords.cpp b/engines/nancy/action/soundrecords.cpp
index d480317b591..ec6ae23e42a 100644
--- a/engines/nancy/action/soundrecords.cpp
+++ b/engines/nancy/action/soundrecords.cpp
@@ -19,6 +19,8 @@
  *
  */
 
+#include "common/random.h"
+
 #include "engines/nancy/nancy.h"
 #include "engines/nancy/sound.h"
 #include "engines/nancy/util.h"
@@ -178,5 +180,21 @@ void StopSound::execute() {
 	_sceneChange.execute();
 }
 
+void PlayRandomSound::readData(Common::SeekableReadStream &stream) {
+	uint16 numSounds = stream.readUint16LE();
+	readFilenameArray(stream, _soundNames, numSounds - 1);
+
+	PlayDigiSound::readData(stream);
+	_soundNames.push_back(_sound.name);
+}
+
+void PlayRandomSound::execute() {
+	if (_state == kBegin) {
+		_sound.name = _soundNames[g_nancy->_randomSource->getRandomNumber(_soundNames.size() - 1)];
+	}
+
+	PlayDigiSound::execute();
+}
+
 } // End of namespace Action
 } // End of namespace Nancy
diff --git a/engines/nancy/action/soundrecords.h b/engines/nancy/action/soundrecords.h
index 747595107af..2fb4be28a83 100644
--- a/engines/nancy/action/soundrecords.h
+++ b/engines/nancy/action/soundrecords.h
@@ -93,6 +93,19 @@ protected:
 	Common::String getRecordTypeName() const override { return "StopSound"; }
 };
 
+class PlayRandomSound : public PlayDigiSound {
+public:
+	void readData(Common::SeekableReadStream &stream) override;
+	void execute() override;
+
+	Common::Array<Common::String> _soundNames;
+
+	uint _selectedSound = 0;
+
+protected:
+	Common::String getRecordTypeName() const override { return "PlayRandomSound"; }
+};
+
 } // End of namespace Action
 } // End of namespace Nancy
 


Commit: 7ff59151185c3d904834f8f5b964281f676d6c91
    https://github.com/scummvm/scummvm/commit/7ff59151185c3d904834f8f5b964281f676d6c91
Author: Kaloyan Chehlarski (strahy at outlook.com)
Date: 2023-09-23T21:59:40+03:00

Commit Message:
NANCY: Simplify font drawing

Removed the custom drawing code inside Font::drawChar(),
and replaced it with a ManagedSurface wrapper that can
handle all blitting by itself.

Changed paths:
    engines/nancy/font.cpp


diff --git a/engines/nancy/font.cpp b/engines/nancy/font.cpp
index 7e0addf76b2..d9e05faa0a8 100644
--- a/engines/nancy/font.cpp
+++ b/engines/nancy/font.cpp
@@ -37,6 +37,7 @@ void Font::read(Common::SeekableReadStream &stream) {
 	readFilename(stream, imageName);
 
 	g_nancy->_resource->loadImage(imageName, _image);
+	_image.setTransparentColor(g_nancy->_graphicsManager->getTransColor());
 
 	char desc[31];
 	stream.read(desc, 30);
@@ -133,41 +134,20 @@ void Font::drawChar(Graphics::Surface *dst, uint32 chr, int x, int y, uint32 col
 	}
 
 	uint vampireAdjust = g_nancy->getGameType() == kGameTypeVampire ? 1 : 0;
-	uint width = MAX<int>(srcRect.width() - vampireAdjust, 0);
+	srcRect.setWidth(MAX<int>(srcRect.width() - vampireAdjust, 0));
 	uint height = srcRect.height();
 	uint yOffset = getFontHeight() - height;
-	height = MAX<int>(height - vampireAdjust, 0);
+	srcRect.setHeight(MAX<int>(height - vampireAdjust, 0));
 
-	for (uint curY = 0; curY < height; ++curY) {
-		for (uint curX = 0; curX < width; ++curX) {
-			switch (g_nancy->_graphicsManager->getInputPixelFormat().bytesPerPixel) {
-			case 1: {
-				byte colorID = *(const byte *)_image.getBasePtr(srcRect.left + curX, srcRect.top + curY);
+	// Create a wrapper ManagedSurface to handle blitting/format differences
+	Graphics::ManagedSurface dest;
+	dest.w = dst->w;
+	dest.h = dst->h;
+	dest.pitch = dst->pitch;
+	dest.setPixels(dst->getPixels());
+	dest.format = dst->format;
 
-				if (colorID != _transColor) {
-					uint8 palette[1 * 3];
-					_image.grabPalette(palette, colorID, 1);
-					*(uint16 *)dst->getBasePtr(x + curX, y + yOffset + curY) = dst->format.RGBToColor(palette[0], palette[1], palette[2]);
-				}
-
-				break;
-			}
-			case 2: {
-				uint16 curColor = *(const uint16 *)_image.getBasePtr(srcRect.left + curX, srcRect.top + curY);
-
-				if (curColor != _transColor) {
-					uint8 r, g, b;
-					_image.format.colorToRGB(curColor, r, g, b);
-					*(uint16 *)dst->getBasePtr(x + curX, y + yOffset + curY) = dst->format.RGBToColor(r, g, b);
-				}
-
-				break;
-			}
-			default:
-				break;
-			}
-		}
-	}
+	dest.blitFrom(_image, srcRect, Common::Point(x, y + yOffset));
 }
 
 void Font::wordWrap(const Common::String &str, int maxWidth, Common::Array<Common::String> &lines, int initWidth) const {


Commit: 120160b6e41d8e1eb881c13c219714a498391e82
    https://github.com/scummvm/scummvm/commit/120160b6e41d8e1eb881c13c219714a498391e82
Author: Kaloyan Chehlarski (strahy at outlook.com)
Date: 2023-09-23T21:59:40+03:00

Commit Message:
NANCY: Implement Autotext

Implemented the Autotext record type, which is used for
text rendering (prior to its introduction in nancy6, all
in-game text was prerendered). Made relevant changes to
the Overlay and Hypertext classes to support it.

Changed paths:
  A engines/nancy/action/autotext.cpp
  A engines/nancy/action/autotext.h
    engines/nancy/action/arfactory.cpp
    engines/nancy/action/overlay.cpp
    engines/nancy/graphics.h
    engines/nancy/misc/hypertext.cpp
    engines/nancy/module.mk
    engines/nancy/puzzledata.cpp
    engines/nancy/puzzledata.h
    engines/nancy/ui/textbox.cpp


diff --git a/engines/nancy/action/arfactory.cpp b/engines/nancy/action/arfactory.cpp
index b9d86f99180..043885b794a 100644
--- a/engines/nancy/action/arfactory.cpp
+++ b/engines/nancy/action/arfactory.cpp
@@ -23,6 +23,7 @@
 #include "engines/nancy/action/soundrecords.h"
 #include "engines/nancy/action/miscrecords.h"
 
+#include "engines/nancy/action/autotext.h"
 #include "engines/nancy/action/conversation.h"
 #include "engines/nancy/action/overlay.h"
 #include "engines/nancy/action/secondaryvideo.h"
@@ -130,7 +131,12 @@ ActionRecord *ActionManager::createActionRecord(uint16 type) {
 			return new ConversationSoundT();
 		}
 	case 61:
-		return new MapCallHot1Fr();
+		if (g_nancy->getGameType() <= kGameTypeNancy5) {
+			// Only used in tvd and nancy1
+			return new MapCallHot1Fr();
+		} else {
+			return new Autotext();
+		}
 	case 62:
 		return new MapCallHotMultiframe();
 	case 75:
diff --git a/engines/nancy/action/autotext.cpp b/engines/nancy/action/autotext.cpp
new file mode 100644
index 00000000000..12f1d9c13c9
--- /dev/null
+++ b/engines/nancy/action/autotext.cpp
@@ -0,0 +1,137 @@
+/* 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 "engines/nancy/action/autotext.h"
+#include "engines/nancy/state/scene.h"
+
+#include "engines/nancy/util.h"
+#include "engines/nancy/graphics.h"
+#include "engines/nancy/nancy.h"
+#include "engines/nancy/resource.h"
+
+namespace Nancy {
+namespace Action {
+
+void Autotext::readData(Common::SeekableReadStream &stream) {
+	_transparency = stream.readUint16LE();
+	_surfaceID = stream.readUint16LE();
+	_fontID = stream.readUint16LE();
+	_textColor = stream.readUint16LE();
+	_offset.x = stream.readUint16LE();
+	_offset.y = stream.readUint16LE();
+	_surfWidth = stream.readUint16LE();
+	_surfHeight = stream.readUint16LE();
+
+	readFilename(stream, _imageName);
+
+	uint16 numImages = stream.readUint16LE();
+
+	_imageLineIDs.resize(numImages);
+	_imageSrcs.resize(numImages);
+	for (uint i = 0; i < numImages; ++i) {
+		_imageLineIDs[i] = stream.readUint16LE();
+		readRect(stream, _imageSrcs[i]);
+	}
+
+	stream.skip((5 - numImages) * (2 + 16));
+
+	_useAutotextChunk = stream.readByte();
+	readFilename(stream, _textKey);
+
+	uint sizeText = stream.readUint16LE();
+
+	if (sizeText) {
+		char *buf = new char[sizeText];
+		stream.read(buf, sizeText);
+		assembleTextLine(buf, _embeddedText, sizeText);
+		delete[] buf;
+	}
+}
+
+void Autotext::execute() {
+	g_nancy->_resource->loadImage(_imageName, _image);
+
+	if (_surfaceID > 2) {
+		// Surfaces 3+ are journal surfaces, and their text contents are saved. Texts MUST be in CONVO chunk,
+		// so we do not check _useAutotextChunk
+		Nancy::JournalData *journalData = (Nancy::JournalData *)NancySceneState.getPuzzleData(Nancy::JournalData::getTag());
+		assert(journalData);
+		const CVTX *autotext = (const CVTX *)g_nancy->getEngineData("AUTOTEXT");
+		assert(autotext);
+
+		Common::String stringToPush;
+		auto &entriesForSurface = journalData->journalEntries[_surfaceID];
+		bool foundThisKey = false;
+		for (auto &stringID : entriesForSurface) {
+			stringToPush += autotext->texts[stringID];
+			if (stringID == _textKey) {
+				foundThisKey = true;
+			}
+		}
+
+		if (!foundThisKey) {
+			// Key inside this Autotext instance wasn't found inside existing list, push it back and add it to string to draw
+			entriesForSurface.push_back(_textKey);
+			stringToPush += autotext->texts[_textKey];
+		}
+
+		addTextLine(stringToPush);
+	} else {
+		// Surfaces 0-2 have their contents cleared every scene (though we only bother doing so when we need to reuse)
+		if (_useAutotextChunk) {
+			// We have a key into the AUTOTEXT chunk
+			const CVTX *autotext = (const CVTX *)g_nancy->getEngineData("AUTOTEXT");
+			assert(autotext);
+
+			addTextLine(autotext->texts[_textKey]);
+		} else {
+			// We have text embedded inside this Autotext instance
+			addTextLine(_embeddedText);
+		}
+	}
+
+	// A height of zero means the surface doesn't need to be drawn
+	if (_surfHeight) {
+		// Guesstimate the height of the surface
+		uint surfHeight = _textLines[0].size() / 144 * _surfWidth;
+		surfHeight = MAX<uint>(surfHeight, _surfHeight + 20);
+		Graphics::ManagedSurface &surf = g_nancy->_graphicsManager->getAutotextSurface(_surfaceID);
+		surf.create(_surfWidth + 1, surfHeight, g_nancy->_graphicsManager->getInputPixelFormat());
+		if (_transparency) {
+			surf.clear(g_nancy->_graphicsManager->getTransColor());
+		}
+
+		_fullSurface.create(surf, surf.getBounds());
+		if(_transparency == kPlayOverlayTransparent) {
+			_fullSurface.setTransparentColor(g_nancy->_graphicsManager->getTransColor());
+		}
+
+		Common::Rect textBounds = surf.getBounds();
+		textBounds.left += _offset.x;
+		textBounds.top += _offset.y;
+		drawAllText(textBounds, _fontID, _fontID);
+	}
+
+	_isDone = true;
+}
+
+} // End of namespace Action
+} // End of namespace Nancy
diff --git a/engines/nancy/action/autotext.h b/engines/nancy/action/autotext.h
new file mode 100644
index 00000000000..5eb0c794eec
--- /dev/null
+++ b/engines/nancy/action/autotext.h
@@ -0,0 +1,74 @@
+/* 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 NANCY_ACTION_AUTOTEXT_H
+#define NANCY_ACTION_AUTOTEXT_H
+
+#include "engines/nancy/misc/hypertext.h"
+#include "engines/nancy/action/actionrecord.h"
+
+namespace Nancy {
+namespace Action {
+
+// Action record used for rendering text inside the game viewport
+// (before its introduction all text outside the textbox was prerendered)
+// Can be used in two ways: for single-use texts that get thrown away
+// after a scene change, or for permanent storage (used in nancy's journal)
+// Does not own or display any image data; it draws to a surface inside
+// GraphicsManager, which other ActionRecords (Overlay and PeepholePuzzle) can use.
+class Autotext : public ActionRecord, public Misc::HypertextParser {
+public:
+	Autotext() {}
+	virtual ~Autotext() {}
+
+	void readData(Common::SeekableReadStream &stream) override;
+	void execute() override;
+
+protected:
+	Common::String getRecordTypeName() const override { return "Autotext"; }
+
+	uint16 _transparency = kPlayOverlayPlain;
+	uint16 _surfaceID = 0;
+	uint16 _fontID = 0;
+	uint16 _textColor = 0;
+	Common::Point _offset;
+	uint16 _surfWidth = 0;
+	uint16 _surfHeight = 0;
+
+	// Data for displaying images inside text; not supported yet
+	Common::String _imageName;
+
+	Common::Array<uint16> _imageLineIDs;
+	Common::Array<Common::Rect> _imageSrcs;
+
+	bool _useAutotextChunk = false;
+
+	// Only one of these is valid
+	Common::String _textKey;
+	Common::String _embeddedText;
+
+	Graphics::ManagedSurface _image;
+};
+
+} // End of namespace Action
+} // End of namespace Nancy
+
+#endif // NANCY_ACTION_AUTOTEXT_H
diff --git a/engines/nancy/action/overlay.cpp b/engines/nancy/action/overlay.cpp
index f0aae790d70..a90a7078f81 100644
--- a/engines/nancy/action/overlay.cpp
+++ b/engines/nancy/action/overlay.cpp
@@ -25,6 +25,7 @@
 #include "engines/nancy/util.h"
 #include "engines/nancy/input.h"
 #include "engines/nancy/cursor.h"
+#include "engines/nancy/graphics.h"
 
 #include "engines/nancy/action/overlay.h"
 
@@ -36,7 +37,19 @@ namespace Nancy {
 namespace Action {
 
 void Overlay::init() {
-	g_nancy->_resource->loadImage(_imageName, _fullSurface);
+	// Check for special autotext strings, and use the requested surface as source
+	if (_imageName.hasPrefix("USE_AUTOTEXT")) {
+		uint surfID = _imageName[12] - '1';
+		Graphics::ManagedSurface &surf = g_nancy->_graphicsManager->getAutotextSurface(surfID);
+		_fullSurface.create(surf, surf.getBounds());
+	} else if (_imageName.hasPrefix("USE_AUTOJOURNAL")) {
+		uint surfID = _imageName.substr(15).asUint64() - 3;
+		Graphics::ManagedSurface &surf = g_nancy->_graphicsManager->getAutotextSurface(surfID);
+		_fullSurface.create(surf, surf.getBounds());
+	} else {
+		// No autotext, load image source
+		g_nancy->_resource->loadImage(_imageName, _fullSurface);
+	}
 
 	setFrame(_firstFrame);
 
diff --git a/engines/nancy/graphics.h b/engines/nancy/graphics.h
index 2c1d3e4630f..660c4b08658 100644
--- a/engines/nancy/graphics.h
+++ b/engines/nancy/graphics.h
@@ -56,6 +56,8 @@ public:
 	const Graphics::PixelFormat &getScreenPixelFormat();
 	uint32 getTransColor() { return _transColor; }
 
+	Graphics::ManagedSurface &getAutotextSurface(uint16 id) { return _autotextSurfaces.getOrCreateVal(id); }
+
 	void grabViewportObjects(Common::Array<RenderObject *> &inArray);
 	void screenshotViewport(Graphics::ManagedSurface &inSurf);
 	void screenshotScreen(Graphics::ManagedSurface &inSurf);
@@ -89,6 +91,8 @@ private:
 
 	Common::List<Common::Rect> _dirtyRects;
 
+	Common::HashMap<uint16, Graphics::ManagedSurface> _autotextSurfaces;
+
 	uint32 _transColor = 0;
 
 	bool _isSuppressed;
diff --git a/engines/nancy/misc/hypertext.cpp b/engines/nancy/misc/hypertext.cpp
index 92074cb2f40..eec44ac9cb0 100644
--- a/engines/nancy/misc/hypertext.cpp
+++ b/engines/nancy/misc/hypertext.cpp
@@ -28,9 +28,10 @@
 namespace Nancy {
 namespace Misc {
 
-struct ColorChange {
+struct ColorFontChange {
+	bool isFont;
 	uint numChars;
-	byte colorID;
+	byte colorOrFontID;
 };
 
 void HypertextParser::initSurfaces(uint width, uint height, const Graphics::PixelFormat &format, uint32 backgroundColor, uint32 highlightBackgroundColor) {
@@ -59,7 +60,7 @@ void HypertextParser::drawAllText(const Common::Rect &textBounds, uint fontID, u
 		Common::String currentLine;
 		bool hasHotspot = false;
 		Rect hotspot;
-		Common::Queue<ColorChange> colorChanges;
+		Common::Queue<ColorFontChange> colorTextChanges;
 		int curFontID = fontID;
 		uint numNonSpaceChars = 0;
 
@@ -105,17 +106,16 @@ void HypertextParser::drawAllText(const Common::Rect &textBounds, uint fontID, u
 						break;
 					}
 					
-					colorChanges.push({numNonSpaceChars, (byte)(curToken[1] - 48)});
+					colorTextChanges.push({false, numNonSpaceChars, (byte)(curToken[1] - 48)});
 					continue;
 				case 'f' :
 					// Font token
-					// This selects a specific font ID for the current line
+					// This selects a specific font ID for the following text
 					if (curToken.size() != 2) {
 						break;
 					}
 
-					curFontID = (int)Common::String(curToken[1]).asUint64();
-
+					colorTextChanges.push({true, numNonSpaceChars, (byte)(curToken[1] - 48)});
 					continue;
 				}
 			}
@@ -143,7 +143,7 @@ void HypertextParser::drawAllText(const Common::Rect &textBounds, uint fontID, u
 		// Setup most of the hotspot
 		if (hasHotspot) {
 			hotspot.left = textBounds.left;
-			hotspot.top = textBounds.top + ((_numDrawnLines - 1) * font->getFontHeight()) - 1;
+			hotspot.top = textBounds.top + (_numDrawnLines * font->getFontHeight()) - 1;
 			hotspot.setHeight(wrappedLines.size() * font->getFontHeight());
 			hotspot.setWidth(0);
 		}
@@ -172,19 +172,21 @@ void HypertextParser::drawAllText(const Common::Rect &textBounds, uint fontID, u
 			while (!line.empty()) {
 				Common::String subLine;
 
-				if (colorChanges.size()) {
-					// Text contains color part
-
-					if (totalCharsDrawn >= colorChanges.front().numChars) {
-						// Token is at begginning of (what's left of) the current line
-						colorID = colorChanges.pop().colorID;
+				while (colorTextChanges.size() && totalCharsDrawn >= colorTextChanges.front().numChars) {
+					// We have a color/font change token at begginning of (what's left of) the current line
+					ColorFontChange change = colorTextChanges.pop();
+					if (change.isFont) {
+						curFontID = change.colorOrFontID;
+						font = g_nancy->_graphicsManager->getFont(curFontID);
+					} else {
+						colorID = change.colorOrFontID;
 					}
+				}
 
-					if (totalCharsDrawn < colorChanges.front().numChars && colorChanges.front().numChars < (totalCharsDrawn + line.size())) {
-						// There's a token inside the current line, so split off the part before it
-						subLine = line.substr(0, colorChanges.front().numChars - totalCharsDrawn);
-						line = line.substr(subLine.size());
-					}
+				if (colorTextChanges.size() && totalCharsDrawn < colorTextChanges.front().numChars && colorTextChanges.front().numChars < (totalCharsDrawn + line.size())) {
+					// There's a token inside the current line, so split off the part before it
+					subLine = line.substr(0, colorTextChanges.front().numChars - totalCharsDrawn);
+					line = line.substr(subLine.size());
 				}
 
 				// Choose whether to draw the subLine, or the full line
@@ -194,7 +196,7 @@ void HypertextParser::drawAllText(const Common::Rect &textBounds, uint fontID, u
 				font->drawString(				&_fullSurface,
 												stringToDraw,
 												textBounds.left + horizontalOffset,
-												textBounds.top + (_numDrawnLines - 1) * font->getFontHeight(),
+												textBounds.top + _numDrawnLines * font->getFontHeight(),
 												textBounds.width(),
 												colorID);
 
@@ -203,7 +205,7 @@ void HypertextParser::drawAllText(const Common::Rect &textBounds, uint fontID, u
 					highlightFont->drawString(	&_textHighlightSurface,
 												stringToDraw,
 												textBounds.left + horizontalOffset,
-												textBounds.top + (_numDrawnLines - 1) * highlightFont->getFontHeight(),
+												textBounds.top + _numDrawnLines * highlightFont->getFontHeight(),
 												textBounds.width(),
 												colorID);
 				}
diff --git a/engines/nancy/module.mk b/engines/nancy/module.mk
index 32090cba661..aaa924b01cb 100644
--- a/engines/nancy/module.mk
+++ b/engines/nancy/module.mk
@@ -4,6 +4,7 @@ MODULE_OBJS = \
   action/actionmanager.o \
   action/actionrecord.o \
   action/arfactory.o \
+  action/autotext.o \
   action/navigationrecords.o \
   action/soundrecords.o \
   action/miscrecords.o \
diff --git a/engines/nancy/puzzledata.cpp b/engines/nancy/puzzledata.cpp
index 9b0baa117e7..722cc9e5f68 100644
--- a/engines/nancy/puzzledata.cpp
+++ b/engines/nancy/puzzledata.cpp
@@ -104,6 +104,36 @@ void SoundEqualizerPuzzleData::synchronize(Common::Serializer &ser) {
 	ser.syncArray(sliderValues.data(), 6, Common::Serializer::Byte);
 }
 
+void JournalData::synchronize(Common::Serializer &ser) {
+	uint16 numEntries = journalEntries.size();
+	ser.syncAsUint16LE(numEntries);
+
+	if (ser.isLoading()) {
+		for (uint i = 0; i < numEntries; ++i) {
+			uint16 id = 0;
+			ser.syncAsUint16LE(id);
+			uint16 numStrings = 0;
+			ser.syncAsUint16LE(numStrings);
+			auto &entry = journalEntries[id];
+			for (uint j = 0; j < numStrings; ++j) {
+				Common::String l;
+				ser.syncString(l);
+				entry.push_back(l);
+			}
+		}
+	} else {
+		for (auto &a : journalEntries) {
+			uint16 id = a._key;
+			ser.syncAsUint16LE(id);
+			uint16 numStrings = a._value.size();
+			ser.syncAsUint16LE(numStrings);
+			for (uint i = 0; i < numStrings; ++i) {
+				ser.syncString(a._value[i]);
+			}
+		}
+	}
+}
+
 PuzzleData *makePuzzleData(const uint32 tag) {
 	switch(tag) {
 	case SliderPuzzleData::getTag():
@@ -116,6 +146,8 @@ PuzzleData *makePuzzleData(const uint32 tag) {
 		return new RiddlePuzzleData();
 	case SoundEqualizerPuzzleData::getTag():
 		return new SoundEqualizerPuzzleData();
+	case JournalData::getTag():
+		return new JournalData();
 	default:
 		return nullptr;
 	}
diff --git a/engines/nancy/puzzledata.h b/engines/nancy/puzzledata.h
index abed79cf1d3..4d71012e082 100644
--- a/engines/nancy/puzzledata.h
+++ b/engines/nancy/puzzledata.h
@@ -21,6 +21,7 @@
 
 #include "common/serializer.h"
 #include "common/array.h"
+#include "common/hashmap.h"
 
 #ifndef NANCY_PUZZLEDATA_H
 #define NANCY_PUZZLEDATA_H
@@ -87,6 +88,16 @@ struct SoundEqualizerPuzzleData : public PuzzleData {
 	Common::Array<byte> sliderValues;
 };
 
+struct JournalData : public PuzzleData {
+	JournalData() {}
+	virtual ~JournalData() {}
+
+	static constexpr uint32 getTag() { return MKTAG('J', 'O', 'U', 'R'); }
+	virtual void synchronize(Common::Serializer &ser);
+
+	Common::HashMap<uint16, Common::Array<Common::String>> journalEntries;
+};
+
 PuzzleData *makePuzzleData(const uint32 tag);
 
 } // End of namespace Nancy
diff --git a/engines/nancy/ui/textbox.cpp b/engines/nancy/ui/textbox.cpp
index f05ad9f3fb3..13b3f93948e 100644
--- a/engines/nancy/ui/textbox.cpp
+++ b/engines/nancy/ui/textbox.cpp
@@ -144,6 +144,9 @@ void Textbox::drawTextbox() {
 	textBounds.left += tbox->leftOffset;
 	textBounds.right -= tbox->rightOffset;
 
+	const Font *font = g_nancy->_graphicsManager->getFont(_fontIDOverride != -1 ? _fontIDOverride : tbox->defaultFontID);
+	textBounds.top -= font->getFontHeight();
+
 	HypertextParser::drawAllText(	textBounds,															// bounds of text within full surface
 									_fontIDOverride != -1 ? _fontIDOverride : tbox->defaultFontID,	// font for basic text
 									tbox->highlightConversationFontID);									// font for highlight text


Commit: eaf0c56182e1fd20b7fac7d12e6bf3d761646a1b
    https://github.com/scummvm/scummvm/commit/eaf0c56182e1fd20b7fac7d12e6bf3d761646a1b
Author: Kaloyan Chehlarski (strahy at outlook.com)
Date: 2023-09-23T21:59:40+03:00

Commit Message:
NANCY: Implement PeepholePuzzle

Implemented the action record that, despite what its name
suggests, is actually responsible mostly for scrolling text
(e.g. Nancy's journal, the instructions to the Bul minigame).
Introduced in nancy6.

Changed paths:
  A engines/nancy/action/puzzle/peepholepuzzle.cpp
  A engines/nancy/action/puzzle/peepholepuzzle.h
    engines/nancy/action/arfactory.cpp
    engines/nancy/action/autotext.cpp
    engines/nancy/action/overlay.cpp
    engines/nancy/misc/hypertext.cpp
    engines/nancy/misc/hypertext.h
    engines/nancy/module.mk


diff --git a/engines/nancy/action/arfactory.cpp b/engines/nancy/action/arfactory.cpp
index 043885b794a..225f2345f4e 100644
--- a/engines/nancy/action/arfactory.cpp
+++ b/engines/nancy/action/arfactory.cpp
@@ -36,6 +36,7 @@
 #include "engines/nancy/action/puzzle/orderingpuzzle.h"
 #include "engines/nancy/action/puzzle/overridelockpuzzle.h"
 #include "engines/nancy/action/puzzle/passwordpuzzle.h"
+#include "engines/nancy/action/puzzle/peepholepuzzle.h"
 #include "engines/nancy/action/puzzle/raycastpuzzle.h"
 #include "engines/nancy/action/puzzle/riddlepuzzle.h"
 #include "engines/nancy/action/puzzle/rippedletterpuzzle.h"
@@ -243,6 +244,8 @@ ActionRecord *ActionManager::createActionRecord(uint16 type) {
 		return new OrderingPuzzle(OrderingPuzzle::PuzzleType::kKeypad);
 	case 215:
 		return new MazeChasePuzzle();
+	case 216:
+		return new PeepholePuzzle();
 	default:
 		return nullptr;
 	}
diff --git a/engines/nancy/action/autotext.cpp b/engines/nancy/action/autotext.cpp
index 12f1d9c13c9..c2a86d2c12f 100644
--- a/engines/nancy/action/autotext.cpp
+++ b/engines/nancy/action/autotext.cpp
@@ -34,7 +34,7 @@ void Autotext::readData(Common::SeekableReadStream &stream) {
 	_transparency = stream.readUint16LE();
 	_surfaceID = stream.readUint16LE();
 	_fontID = stream.readUint16LE();
-	_textColor = stream.readUint16LE();
+	_defaultTextColor = stream.readUint16LE();
 	_offset.x = stream.readUint16LE();
 	_offset.y = stream.readUint16LE();
 	_surfWidth = stream.readUint16LE();
diff --git a/engines/nancy/action/overlay.cpp b/engines/nancy/action/overlay.cpp
index a90a7078f81..c6915e66c71 100644
--- a/engines/nancy/action/overlay.cpp
+++ b/engines/nancy/action/overlay.cpp
@@ -43,7 +43,7 @@ void Overlay::init() {
 		Graphics::ManagedSurface &surf = g_nancy->_graphicsManager->getAutotextSurface(surfID);
 		_fullSurface.create(surf, surf.getBounds());
 	} else if (_imageName.hasPrefix("USE_AUTOJOURNAL")) {
-		uint surfID = _imageName.substr(15).asUint64() - 3;
+		uint surfID = _imageName.substr(15).asUint64() + 2;
 		Graphics::ManagedSurface &surf = g_nancy->_graphicsManager->getAutotextSurface(surfID);
 		_fullSurface.create(surf, surf.getBounds());
 	} else {
diff --git a/engines/nancy/action/puzzle/peepholepuzzle.cpp b/engines/nancy/action/puzzle/peepholepuzzle.cpp
new file mode 100644
index 00000000000..58ff2c5c330
--- /dev/null
+++ b/engines/nancy/action/puzzle/peepholepuzzle.cpp
@@ -0,0 +1,274 @@
+/* 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 "engines/nancy/graphics.h"
+#include "engines/nancy/nancy.h"
+#include "engines/nancy/sound.h"
+#include "engines/nancy/input.h"
+#include "engines/nancy/util.h"
+#include "engines/nancy/resource.h"
+
+#include "engines/nancy/action/puzzle/peepholepuzzle.h"
+
+#include "engines/nancy/state/scene.h"
+
+namespace Nancy {
+namespace Action {
+
+void PeepholePuzzle::init() {
+	Common::Rect screenBounds = NancySceneState.getViewport().getBounds();
+	_drawSurface.create(screenBounds.width(), screenBounds.height(), g_nancy->_graphicsManager->getInputPixelFormat());
+	moveTo(screenBounds);
+
+	// Check for special autotext strings, and use the requested surface as source
+	if (_innerImageName.hasPrefix("USE_AUTOTEXT")) {
+		uint surfID = _innerImageName[12] - '1';
+		Graphics::ManagedSurface &surf = g_nancy->_graphicsManager->getAutotextSurface(surfID);
+		_innerImage.create(surf, surf.getBounds());
+	} else if (_innerImageName.hasPrefix("USE_AUTOJOURNAL")) {
+		uint surfID = _innerImageName.substr(15).asUint64() + 2;
+		Graphics::ManagedSurface &surf = g_nancy->_graphicsManager->getAutotextSurface(surfID);
+		_innerImage.create(surf, surf.getBounds());
+	} else {
+		// No autotext, load image source
+		g_nancy->_resource->loadImage(_innerImageName, _innerImage);
+	}
+
+	if (!_buttonsImageName.size()) {
+		// Empty image name for buttons, use other image as source
+		_buttonsImage.create(_innerImage, _innerImage.getBounds());
+	} else {
+		g_nancy->_resource->loadImage(_buttonsImageName, _buttonsImage);
+	}
+
+	_currentSrc = _startSrc;
+
+	setTransparent(true);
+	_drawSurface.clear(_drawSurface.getTransparentColor());
+	setVisible(true);
+
+	drawInner();
+	checkButtons();
+}
+
+void PeepholePuzzle::readData(Common::SeekableReadStream &stream) {
+	readFilename(stream, _innerImageName);
+	readFilename(stream, _buttonsImageName);
+
+	_transparency = stream.readUint16LE();
+
+	readRect(stream, _innerBounds);
+	readRect(stream, _startSrc);
+	readRect(stream, _dest);
+
+	readRectArray(stream, _buttonDests, 4);
+	readRectArray(stream, _buttonSrcs, 4);
+	readRectArray(stream, _buttonDisabledSrcs, 4);
+
+	_pixelsToScroll = stream.readByte();
+
+	_exitScene.readData(stream);
+	readRect(stream, _exitHotspot);
+}
+
+void PeepholePuzzle::execute() {
+	switch (_state) {
+	case kBegin:
+		init();
+		registerGraphics();
+		_state = kRun;
+		// fall through
+	case kRun:
+		break;
+	case kActionTrigger:
+		
+		finishExecution();
+	}
+}
+
+void PeepholePuzzle::handleInput(NancyInput &input) {
+	if (_state != kRun) {
+		return;
+	}
+
+	bool justReleased = false;
+
+	if (NancySceneState.getViewport().convertViewportToScreen(_exitHotspot).contains(input.mousePos)) {
+		g_nancy->_cursorManager->setCursorType(g_nancy->_cursorManager->_puzzleExitCursor);
+
+		if (input.input & NancyInput::kLeftMouseButtonUp) {
+			_state = kActionTrigger;
+		}
+	}
+
+	if (_pressedButton != -1) {
+		if (input.input & NancyInput::kLeftMouseButtonHeld) {
+			// Player is still holding the left button, check if mouse has moved outside bounds
+			if (NancySceneState.getViewport().convertViewportToScreen(_buttonDests[_pressedButton]).contains(input.mousePos)) {
+				if (!_disabledButtons[_pressedButton]) {
+					// Do not show hover cursor on disabled button
+					g_nancy->_cursorManager->setCursorType(CursorManager::kHotspot);
+				}
+
+				if (_pressStart == 0) {
+					// Mouse was out of bounds but still held, and is now back in bounds, continue moving
+					_pressStart = g_nancy->getTotalPlayTime();
+				}
+			} else {
+				// Mouse is not in bounds, pause moving
+				_pressStart = 0;
+				justReleased = true;
+			}
+		} else {
+			// Player released mouse button
+
+			// Avoid single frame with non-highlighted cursor
+			if (NancySceneState.getViewport().convertViewportToScreen(_buttonDests[_pressedButton]).contains(input.mousePos) && !_disabledButtons[_pressedButton]) {
+				g_nancy->_cursorManager->setCursorType(CursorManager::kHotspot);
+			}
+
+			_pressedButton = -1;
+			_pressStart = 0;
+			justReleased = true;
+		}
+	} else {
+		// Mouse is not currently pressing button, check all buttons
+		for (uint i = 0; i < 4; ++i) {
+			if (!_disabledButtons[i]) {
+				if (NancySceneState.getViewport().convertViewportToScreen(_buttonDests[i]).contains(input.mousePos)) {
+					g_nancy->_cursorManager->setCursorType(CursorManager::kHotspot);
+
+					if (input.input & NancyInput::kLeftMouseButtonDown) {
+						if (_pressedButton == -1) {
+							// Just pressed
+							_pressedButton = i;
+							_pressStart = g_nancy->getTotalPlayTime();
+						}
+					}
+				}
+			}
+		}
+	}
+
+	// Perform movement
+	if (_pressedButton != -1 && _pressStart != 0) {
+		uint32 curTime = g_nancy->getTotalPlayTime();
+		uint pixelsToMove = 0;
+		if (curTime - _pressStart >= 1000 / _pixelsToScroll) {
+			pixelsToMove = (curTime - _pressStart) / (1000 / _pixelsToScroll);
+		}
+
+		switch (_pressedButton) {
+		case 0 :
+			// Up
+			_currentSrc.translate(0, -pixelsToMove);
+			if (_currentSrc.top < _innerBounds.top) {
+				_currentSrc.translate(0, _innerBounds.top - _currentSrc.top);
+			}
+			break;
+		case 1 :
+			// Down
+			_currentSrc.translate(0, pixelsToMove);
+			if (_currentSrc.bottom > _innerBounds.bottom) {
+				_currentSrc.translate(0, _innerBounds.top - _currentSrc.top);
+			}
+			break;
+		case 2 :
+			// Left
+			_currentSrc.translate(-pixelsToMove, 0);
+			if (_currentSrc.left < _innerBounds.left) {
+				_currentSrc.translate(_innerBounds.left - _currentSrc.left, 0);
+			}
+			break;
+		case 3:
+			// Right
+			_currentSrc.translate(pixelsToMove, 0);
+			if (_currentSrc.right > _innerBounds.right) {
+				_currentSrc.translate(_innerBounds.right - _currentSrc.right, 0);
+			}
+			break;
+		}
+
+		_pressStart = curTime;
+
+		checkButtons();
+		drawInner();
+	} else if (justReleased) {
+		checkButtons();
+	}
+}
+
+void PeepholePuzzle::drawInner() {
+	_drawSurface.blitFrom(_innerImage, _currentSrc, _dest);
+	_needsRedraw = true;
+}
+
+void PeepholePuzzle::checkButtons() {
+	for (int i = 0; i < 4; ++i) {
+		int16 *srcCoord = nullptr;
+		int16 *innerCoord = nullptr;
+
+		switch(i) {
+		case 0 :
+			srcCoord = &_currentSrc.top;
+			innerCoord = &_innerBounds.top;
+			break;
+		case 1 :
+			srcCoord = &_currentSrc.bottom;
+			innerCoord = &_innerBounds.bottom;
+			break;
+		case 2 :
+			srcCoord = &_currentSrc.left;
+			innerCoord = &_innerBounds.left;
+			break;
+		case 3 :
+			srcCoord = &_currentSrc.right;
+			innerCoord = &_innerBounds.right;
+			break;
+		}
+
+		if (!_buttonDests[i].isEmpty()) {
+			if (*srcCoord == *innerCoord) {
+				if (_disabledButtons[i] == false) {
+					_disabledButtons[i] = true;
+					if (!_buttonDisabledSrcs[i].isEmpty()) {
+						_drawSurface.blitFrom(_buttonsImage, _buttonDisabledSrcs[i], _buttonDests[i]);
+						_needsRedraw = true;
+					}
+				}
+			} else {
+				_disabledButtons[i] = false;
+				if (i == _pressedButton && _pressStart) {
+					_drawSurface.blitFrom(_buttonsImage, _buttonSrcs[i], _buttonDests[i]);
+					_needsRedraw = true;
+				} else {
+					_drawSurface.fillRect(_buttonDests[i], _drawSurface.getTransparentColor());
+					_needsRedraw = true;
+				}
+			}
+		} else {
+			_disabledButtons[i] = true;
+		}
+	}	
+}
+
+} // End of namespace Action
+} // End of namespace Nancy
diff --git a/engines/nancy/action/puzzle/peepholepuzzle.h b/engines/nancy/action/puzzle/peepholepuzzle.h
new file mode 100644
index 00000000000..c15702abb1c
--- /dev/null
+++ b/engines/nancy/action/puzzle/peepholepuzzle.h
@@ -0,0 +1,81 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#ifndef NANCY_ACTION_PEEPHOLEPUZZLE_H
+#define NANCY_ACTION_PEEPHOLEPUZZLE_H
+
+#include "engines/nancy/action/actionrecord.h"
+
+namespace Nancy {
+namespace Action {
+
+// Action record that, despite what its name suggests, is mostly used
+// to render Nancy's diary in nancy6 and up.
+class PeepholePuzzle : public RenderActionRecord {
+public:
+	PeepholePuzzle() : RenderActionRecord(7) {}
+	virtual ~PeepholePuzzle() {}
+
+	void init() override;
+
+	void readData(Common::SeekableReadStream &stream) override;
+	void execute() override;
+	void handleInput(NancyInput &input) override;
+
+protected:
+	Common::String getRecordTypeName() const override { return "PeepholePuzzle"; }
+	bool isViewportRelative() const override { return true; }
+
+	void drawInner();
+	void checkButtons();
+
+	Common::String _innerImageName;
+	Common::String _buttonsImageName;
+
+	uint16 _transparency = 0;
+
+	Common::Rect _innerBounds;
+	Common::Rect _startSrc;
+	Common::Rect _dest;
+
+	// Order: up, down, left, right
+	Common::Array<Common::Rect> _buttonDests;
+	Common::Array<Common::Rect> _buttonSrcs;
+	Common::Array<Common::Rect> _buttonDisabledSrcs;
+
+	byte _pixelsToScroll = 0;
+
+	SceneChangeWithFlag _exitScene;
+	Common::Rect _exitHotspot;
+
+	Graphics::ManagedSurface _innerImage;
+	Graphics::ManagedSurface _buttonsImage;
+
+	Common::Rect _currentSrc;
+	int _pressedButton = -1;
+	uint32 _pressStart = 0;
+	Common::Array<bool> _disabledButtons = Common::Array<bool>(4, false);
+};
+
+} // End of namespace Action
+} // End of namespace Nancy
+
+#endif // NANCY_ACTION_PEEPHOLEPUZZLE_H
diff --git a/engines/nancy/misc/hypertext.cpp b/engines/nancy/misc/hypertext.cpp
index eec44ac9cb0..4e55bc477cf 100644
--- a/engines/nancy/misc/hypertext.cpp
+++ b/engines/nancy/misc/hypertext.cpp
@@ -151,7 +151,7 @@ void HypertextParser::drawAllText(const Common::Rect &textBounds, uint fontID, u
 		// Go through the wrapped lines and draw them, making sure to
 		// respect color tokens
 		uint totalCharsDrawn = 0;
-		byte colorID = 0;
+		byte colorID = _defaultTextColor;
 		for (Common::String &line : wrappedLines) {
 			uint horizontalOffset = 0;
 
diff --git a/engines/nancy/misc/hypertext.h b/engines/nancy/misc/hypertext.h
index d54ed04509d..fd31e89fa57 100644
--- a/engines/nancy/misc/hypertext.h
+++ b/engines/nancy/misc/hypertext.h
@@ -34,7 +34,8 @@ public:
 	HypertextParser() :
 		_numDrawnLines(0),
 		_drawnTextHeight(0),
-		_needsTextRedraw(false) {}
+		_needsTextRedraw(false),
+		_defaultTextColor(0) {}
 	virtual ~HypertextParser() {};
 
 protected:
@@ -50,6 +51,7 @@ protected:
 
 	uint32 _backgroundColor;
 	uint32 _highlightBackgroundColor;
+	uint _defaultTextColor;
 
 	Common::Array<Common::String> _textLines;
 	Common::Array<Common::Rect> _hotspots;
diff --git a/engines/nancy/module.mk b/engines/nancy/module.mk
index aaa924b01cb..b6afef44619 100644
--- a/engines/nancy/module.mk
+++ b/engines/nancy/module.mk
@@ -19,6 +19,7 @@ MODULE_OBJS = \
   action/puzzle/orderingpuzzle.o \
   action/puzzle/overridelockpuzzle.o \
   action/puzzle/passwordpuzzle.o \
+  action/puzzle/peepholepuzzle.o \
   action/puzzle/raycastpuzzle.o \
   action/puzzle/riddlepuzzle.o \
   action/puzzle/rippedletterpuzzle.o \




More information about the Scummvm-git-logs mailing list