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

bluegr noreply at scummvm.org
Tue Jun 23 21:46:52 UTC 2026


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

Summary:
f99de04bb8 NANCY: Implement the AutotextTextBoxWrite AR for Nancy11


Commit: f99de04bb84ef10343fcba52327471da5267665b
    https://github.com/scummvm/scummvm/commit/f99de04bb84ef10343fcba52327471da5267665b
Author: Filippos Karapetis (bluegr at gmail.com)
Date: 2026-06-24T00:46:43+03:00

Commit Message:
NANCY: Implement the AutotextTextBoxWrite AR for Nancy11

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


diff --git a/engines/nancy/action/arfactory.cpp b/engines/nancy/action/arfactory.cpp
index f12443812a0..13f009f0b88 100644
--- a/engines/nancy/action/arfactory.cpp
+++ b/engines/nancy/action/arfactory.cpp
@@ -285,8 +285,7 @@ ActionRecord *ActionManager::createActionRecord(uint16 type, Common::SeekableRea
 	case 79:
 		return new ValueTest();
 	case 81:	// Nancy11
-		//warning("AutotextTextBoxWrite");	// TODO
-		return nullptr;
+		return new TextBoxWrite(true);
 	case 96:	// Nancy11
 		return new RandomizeEventFlags();
 	case 97:
diff --git a/engines/nancy/action/miscrecords.cpp b/engines/nancy/action/miscrecords.cpp
index bdb2ce9b121..e05029c5429 100644
--- a/engines/nancy/action/miscrecords.cpp
+++ b/engines/nancy/action/miscrecords.cpp
@@ -102,11 +102,14 @@ void LightningOn::execute() {
 	_isDone = true;
 }
 
-void TextBoxWrite::readData(Common::SeekableReadStream &stream) {
-	int16 size = stream.readSint16LE();
+// Reads a textbox caption: an int16 length, then either a CVTX (AUTOTEXT) key
+// to resolve (length == -1) or that many bytes of inline text. Shared by the
+// textbox-writing action records.
+static void readTextboxText(Common::SeekableReadStream &stream, Common::String &out) {
+	const int16 size = stream.readSint16LE();
 
 	if (size > 10000) {
-		error("Action Record atTextboxWrite has too many text box chars: %d", size);
+		error("Textbox write action record has too many text box chars: %d", size);
 	}
 
 	if (size == -1) {
@@ -116,24 +119,84 @@ void TextBoxWrite::readData(Common::SeekableReadStream &stream) {
 		const CVTX *autotext = (const CVTX *)g_nancy->getEngineData("AUTOTEXT");
 		assert(autotext);
 
-		_text = getTextFromCaseInsensitiveKey(autotext->texts, stringID);
-	} else {
+		out = getTextFromCaseInsensitiveKey(autotext->texts, stringID);
+	} else if (size > 0) {
 		char *buf = new char[size];
 		stream.read(buf, size);
 		buf[size - 1] = '\0';
 
-		assembleTextLine(buf, _text, size);
+		assembleTextLine(buf, out, size);
 
 		delete[] buf;
 	}
 }
 
+void TextBoxWrite::readData(Common::SeekableReadStream &stream) {
+	if (_isAutotext) {
+		// AR 81 prefixes the body with a wait header (runtime state at 0x00 is
+		// always 0 in the data, so skip it)
+		stream.skip(2);
+		_waitMode = stream.readSint16LE();
+		_soundChannel = stream.readUint16LE();
+		_waitTimeMs = stream.readUint32LE();
+	}
+
+	readTextboxText(stream, _text);
+
+	if (_isAutotext) {
+		// The original terminates the body with the "<e>" end-of-line hypertext tag
+		_text += "<e>";
+	}
+}
+
 void TextBoxWrite::execute() {
-	auto &tb = NancySceneState.getTextbox();
-	tb.clear();
-	tb.addTextLine(_text);
-	tb.setVisible(true);
-	finishExecution();
+	if (_state == kBegin) {
+		auto &tb = NancySceneState.getTextbox();
+		tb.clear();
+		if (!_text.empty()) {
+			tb.addTextLine(_text);
+		}
+		tb.setVisible(true);
+
+		if (!_isAutotext) {
+			// Plain TextBoxWrite completes immediately
+			finishExecution();
+			return;
+		}
+
+		if (_waitMode == kWaitForTimer) {
+			_endTime = g_nancy->getTotalPlayTime() + _waitTimeMs;
+		}
+
+		_state = kRun;
+		return;
+	}
+
+	// Nancy 11+ AR 81 waits for a sound or a timer before completing
+	switch (_state) {
+	case kRun:
+		switch (_waitMode) {
+		case kWaitForSound:
+			if (!g_nancy->_sound->isSoundPlaying(_soundChannel)) {
+				_state = kActionTrigger;
+			}
+			break;
+		case kWaitForTimer:
+			if (g_nancy->getTotalPlayTime() >= _endTime) {
+				_state = kActionTrigger;
+			}
+			break;
+		default:
+			_state = kActionTrigger;
+			break;
+		}
+		break;
+	case kActionTrigger:
+		finishExecution();
+		break;
+	default:
+		break;
+	}
 }
 
 void TextboxClear::readData(Common::SeekableReadStream &stream) {
@@ -146,29 +209,7 @@ void TextboxClear::execute() {
 }
 
 void FrameTextBox::readData(Common::SeekableReadStream &stream) {
-	const int16 size = stream.readSint16LE();
-
-	if (size > 10000)
-		error("FrameTextBox: too many text characters: %d", size);
-
-	if (size == -1) {
-		// CVTX-keyed lookup, same as the Nancy 6+ TextBoxWrite path.
-		Common::String stringID;
-		readFilename(stream, stringID);
-
-		const CVTX *autotext = (const CVTX *)g_nancy->getEngineData("AUTOTEXT");
-		assert(autotext);
-
-		_text = getTextFromCaseInsensitiveKey(autotext->texts, stringID);
-	} else if (size > 0) {
-		char *buf = new char[size];
-		stream.read(buf, size);
-		buf[size - 1] = '\0';
-
-		assembleTextLine(buf, _text, size);
-
-		delete[] buf;
-	}
+	readTextboxText(stream, _text);
 
 	// Trailing two int16 fields: meaning differs slightly between the
 	// three opcodes that reuse this layout, but are safe to capture as a
@@ -188,9 +229,8 @@ void FrameTextBox::execute() {
 	// textbox overlay that covers the taskbar buttons; the original arms a
 	// 15-second timer (DAT_005a7a7d = GetTickCount + 15000) that drops it
 	// back to the closed strip. Variant 75 (case 0x4b) is the legacy
-	// closed/strip path. Variant 81 first appears in Nancy 11; tentatively
-	// route it like 74 until its real semantics are confirmed.
-	if (_variant == kVariant74 || _variant == kVariant81) {
+	// closed/strip path.
+	if (_variant == kVariant74) {
 		tb.setFullMode(true);
 	} else {
 		tb.setFullMode(false);
diff --git a/engines/nancy/action/miscrecords.h b/engines/nancy/action/miscrecords.h
index ac589091ae9..d133518b151 100644
--- a/engines/nancy/action/miscrecords.h
+++ b/engines/nancy/action/miscrecords.h
@@ -88,16 +88,32 @@ protected:
 	Common::String getRecordTypeName() const override { return "SpecialEffect"; }
 };
 
-// Adds a caption to the textbox.
+// Adds a caption to the textbox. The Nancy 11+ "autotext" variant (AR 81),
+// carries an extra header that makes the record wait for a sound to finish
+// or a timer to elapse before it completes; in both variants the body is
+// either inline text or resolved from an AUTOTEXT key.
 class TextBoxWrite : public ActionRecord {
 public:
+	enum WaitMode { kWaitNone = 0, kWaitForSound = 1, kWaitForTimer = 2 };
+
+	TextBoxWrite(bool isAutotext = false) : _isAutotext(isAutotext) {}
+
 	void readData(Common::SeekableReadStream &stream) override;
 	void execute() override;
 
 	Common::String _text;
 
+	// Nancy 11+ AR 81 only
+	bool _isAutotext;
+	int16 _waitMode = 0;
+	uint16 _soundChannel = 0;
+	uint32 _waitTimeMs = 0;
+
 protected:
-	Common::String getRecordTypeName() const override { return "TextBoxWrite"; }
+	Common::String getRecordTypeName() const override { return _isAutotext ? "AutotextTextBoxWrite" : "TextBoxWrite"; }
+
+private:
+	uint32 _endTime = 0;
 };
 
 // Clears the textbox. Used very rarely.
@@ -116,8 +132,7 @@ class FrameTextBox : public ActionRecord {
 public:
 	enum Variant {
 		kVariant74 = 74,
-		kVariant75 = 75,
-		kVariant81 = 81
+		kVariant75 = 75
 	};
 
 	FrameTextBox(Variant variant) : _variant(variant), _flags(0), _slot(0) {}




More information about the Scummvm-git-logs mailing list