[Scummvm-git-logs] scummvm master -> 84973d3462cce954eed5f79ebceb4d8327ef93c4

alexbevi noreply at scummvm.org
Thu Oct 15 23:06:34 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:
a5b7ebd7ff HARVESTER: add European release check and improve dialogue response handling
2d1848a124 HARVESTER: add European release handling for Hank's dialogue responses
f165744577 HARVESTER: reset topic buffer and index for Dwayne, Johnson, and Wasp Woman dialogues
9825de956a HARVESTER: refine spyhole topic handling
197997f27a HARVESTER: reset topic buffer and improve evidence handling in dialogue
83aa14ee1a HARVESTER: reset butcher topic buffer and enhance evidence discussion handling
81aa0f5fbb HARVESTER: enhance Fireman2 dialogue handling with additional state checks
bb8fc393b7 HARVESTER: enhance Mcknight dialogue handling with additional evidence discussion and response flow
7cd299daeb HARVESTER: enhance Mom dialogue with additional lines for improved interaction
1ea0767a8d HARVESTER: enhance Moynahan dialogue handling with improved intro sequence and error checks
5855aa834c HARVESTER: enhance Mr. Potts dialogue handling
2f3ec0e67d HARVESTER: enhance Parsons dialogue handling
f3cd18ecd2 HARVESTER: enhance Phelps dialogue handling
73bf3fc385 HARVESTER: picking up items should make them usable right away
cb3c416db7 HARVESTER: bitmap targeting and failed-use responses
84973d3462 HARVESTER: fix test for bitmap overlap on entity 0x15 hotspots


Commit: a5b7ebd7ffcd01a66cf89ff22c5a6b53226319d8
    https://github.com/scummvm/scummvm/commit/a5b7ebd7ffcd01a66cf89ff22c5a6b53226319d8
Author: Alex Bevilacqua (alex at alexbevi.com)
Date: 2026-10-15T19:06:10-04:00

Commit Message:
HARVESTER: add European release check and improve dialogue response handling

Assisted-by: Codex:gpt-6.1-sol

Changed paths:
    engines/harvester/harvester.cpp
    engines/harvester/harvester.h
    engines/harvester/text.cpp
    engines/harvester/text.h


diff --git a/engines/harvester/harvester.cpp b/engines/harvester/harvester.cpp
index 9896cfeecaa..0f2e1ed503b 100644
--- a/engines/harvester/harvester.cpp
+++ b/engines/harvester/harvester.cpp
@@ -68,6 +68,10 @@ bool HarvesterEngine::isDemo() const {
 	return (_gameDescription->flags & ADGF_DEMO) != 0;
 }
 
+bool HarvesterEngine::isEuropeanRelease() const {
+	return _gameDescription->extra && Common::String(_gameDescription->extra) == "European Release";
+}
+
 bool HarvesterEngine::canLoadGameStateCurrently(Common::U32String *) {
 	return _script != nullptr;
 }
diff --git a/engines/harvester/harvester.h b/engines/harvester/harvester.h
index 805a718994a..b2d9c066a33 100644
--- a/engines/harvester/harvester.h
+++ b/engines/harvester/harvester.h
@@ -55,6 +55,7 @@ public:
 	Common::String getGameId() const;
 	Common::Language getLanguage() const;
 	bool isDemo() const;
+	bool isEuropeanRelease() const;
 
 	uint32 getRandomNumber(uint maxNum) {
 		return _randomSource.getRandomNumber(maxNum);
diff --git a/engines/harvester/text.cpp b/engines/harvester/text.cpp
index 1988b0048cd..d2a61cc6754 100644
--- a/engines/harvester/text.cpp
+++ b/engines/harvester/text.cpp
@@ -83,7 +83,7 @@ static bool hasNativeHankDialogueResponseLayout(const Common::Array<Common::Stri
 }
 
 static bool loadDialogueResponseLinesFromNode(const Common::FSNode &node,
-		Common::Array<Common::String> &lines) {
+		Common::Array<Common::String> &lines, bool &endsWithNewline) {
 	if (!node.exists() || node.isDirectory())
 		return false;
 
@@ -96,10 +96,12 @@ static bool loadDialogueResponseLinesFromNode(const Common::FSNode &node,
 	if (!responseData.empty())
 		stream->read(responseData.data(), responseData.size());
 	parseDialogueResponseLines(responseData, lines);
+	endsWithNewline = !responseData.empty() && responseData.back() == '\n';
 	return !lines.empty();
 }
 
-static bool tryLoadPreferredDialogueResponsesFromGamePath(Common::Array<Common::String> &lines) {
+static bool tryLoadPreferredDialogueResponsesFromGamePath(Common::Array<Common::String> &lines,
+		bool &endsWithNewline) {
 	if (!ConfMan.hasKey("path"))
 		return false;
 
@@ -110,9 +112,11 @@ static bool tryLoadPreferredDialogueResponsesFromGamePath(Common::Array<Common::
 	for (int depth = 0; depth < 4; ++depth) {
 		const Common::FSNode candidate = current.getChild(kDialogueResponsePath);
 		Common::Array<Common::String> candidateLines;
-		if (loadDialogueResponseLinesFromNode(candidate, candidateLines) &&
+		bool candidateEndsWithNewline = true;
+		if (loadDialogueResponseLinesFromNode(candidate, candidateLines, candidateEndsWithNewline) &&
 				hasNativeHankDialogueResponseLayout(candidateLines)) {
 			lines = Common::move(candidateLines);
+			endsWithNewline = candidateEndsWithNewline;
 			debug(1, "Harvester: using native-compatible DIALOG.RSP from '%s'",
 				candidate.getPath().toString(Common::Path::kNativeSeparator).c_str());
 			return true;
@@ -197,13 +201,14 @@ bool Text::loadDialogueResponses(ResourceManager &resources) {
 	}
 
 	parseDialogueResponseLines(responseData, _dialogueResponseLines);
+	_dialogueResponsesEndWithNewline = responseData.back() == '\n';
 	if (_dialogueResponseLines.empty())
 		return false;
 
-	if ((!g_engine || !g_engine->isDemo()) &&
+	if ((!g_engine || (!g_engine->isDemo() && !g_engine->isEuropeanRelease())) &&
 			!hasNativeHankDialogueResponseLayout(_dialogueResponseLines)) {
 		Common::Array<Common::String> preferredLines;
-		if (tryLoadPreferredDialogueResponsesFromGamePath(preferredLines))
+		if (tryLoadPreferredDialogueResponsesFromGamePath(preferredLines, _dialogueResponsesEndWithNewline))
 			_dialogueResponseLines = Common::move(preferredLines);
 	}
 
@@ -278,10 +283,16 @@ bool Text::resolveDialogueSubtitle(int wavId, Common::String &text) const {
 }
 
 Common::String Text::getDialogueResponseLine(int zeroBasedIndex) const {
-	if (zeroBasedIndex < 0 || (uint)zeroBasedIndex >= _dialogueResponseLines.size())
+	if (zeroBasedIndex < 0 || _dialogueResponseLines.empty())
 		return Common::String();
 
-	return _dialogueResponseLines[(uint)zeroBasedIndex];
+	// load_dialogue_response_line retains the last buffer at EOF and always
+	// removes its final byte, including when the file ends without a newline.
+	const uint index = MIN<uint>((uint)zeroBasedIndex, _dialogueResponseLines.size() - 1);
+	Common::String line = _dialogueResponseLines[index];
+	if (index == _dialogueResponseLines.size() - 1 && !_dialogueResponsesEndWithNewline && !line.empty())
+		line.deleteLastChar();
+	return line;
 }
 
 } // End of namespace Harvester
diff --git a/engines/harvester/text.h b/engines/harvester/text.h
index be8367c5def..c73d3b8086f 100644
--- a/engines/harvester/text.h
+++ b/engines/harvester/text.h
@@ -64,6 +64,7 @@ private:
 	Common::Array<byte> _dialogueData;
 	Common::Array<DialogueIndexEntry> _dialogueEntries;
 	Common::Array<Common::String> _dialogueResponseLines;
+	bool _dialogueResponsesEndWithNewline = true;
 	Common::Array<CftFontResource> _fonts;
 };
 


Commit: 2d1848a124f6aad1d665d29ef87b88b1fceaad2e
    https://github.com/scummvm/scummvm/commit/2d1848a124f6aad1d665d29ef87b88b1fceaad2e
Author: Alex Bevilacqua (alex at alexbevi.com)
Date: 2026-10-15T19:06:10-04:00

Commit Message:
HARVESTER: add European release handling for Hank's dialogue responses

Assisted-by: Codex:gpt-6.1-sol

Changed paths:
    engines/harvester/npc/hank_dialogue.cpp


diff --git a/engines/harvester/npc/hank_dialogue.cpp b/engines/harvester/npc/hank_dialogue.cpp
index 9a6ce05d072..eddf962cc83 100644
--- a/engines/harvester/npc/hank_dialogue.cpp
+++ b/engines/harvester/npc/hank_dialogue.cpp
@@ -22,6 +22,7 @@
 
 #include "harvester/npc/hank_dialogue.h"
 
+#include "harvester/harvester.h"
 #include "harvester/npc/dialogue_flags.h"
 #include "harvester/npc/dialogue_runtime.h"
 
@@ -183,10 +184,13 @@ bool HankDialogueHandler::matchesNpc(const Common::String &npcName) const {
 
 Common::Error HankDialogueHandler::handleDialogue(DialogueRuntime &runtime,
 		const Common::String &usedItemName, DialogueSharedState &sharedState) {
+	const bool europeanRelease = runtime.engine().isEuropeanRelease();
 	const int currentStoryDayIndex = runtime.startupScript().getCurrentStoryDayIndex();
 	HankRoomDialogueState &state = _state;
 	Common::String &hankTopicBuffer = state.currentTopicBuffer;
 	int &hankTopicBufferLineIndex = state.currentTopicBufferLineIndex;
+	state.currentTopicBuffer.clear();
+	state.currentTopicBufferLineIndex = -1;
 	auto assignHankTopicBuffer = [&](int responseLineIndex) {
 		runtime.assignTopicBuffer(hankTopicBuffer, hankTopicBufferLineIndex,
 			responseLineIndex, "Hank topic buffer");
@@ -254,8 +258,6 @@ Common::Error HankDialogueHandler::handleDialogue(DialogueRuntime &runtime,
 		state.hasTrackedDayState = true;
 		state.pendingSameDayFollowup = true;
 		state.pendingInitialConversation = false;
-		hankTopicBuffer.clear();
-		hankTopicBufferLineIndex = -1;
 		skipHankFollowupBranches = true;
 
 		Common::Error lineError = runtime.playDialogueLine(0x703, "HANK");
@@ -537,7 +539,30 @@ Common::Error HankDialogueHandler::handleDialogue(DialogueRuntime &runtime,
 
 		if (selectedTopic.empty())
 			return Common::kNoError;
-		if (runtime.matchesResponseLine(selectedTopic, 0xd2)) {
+		if (europeanRelease) {
+			if (runtime.matchesResponseLine(selectedTopic, 0xd2))
+				(void)runtime.startupScript().setRuntimeFlagValue("GOT_REMAINS_FOR_LODGE", true);
+			if (runtime.matchesResponseLine(selectedTopic, 0xd3)) {
+				(void)runtime.startupScript().setRuntimeFlagValue("GIVEN_PAPER_TODAY", true);
+				(void)runtime.startupScript().addRuntimeObjectToInventory("COMPLETED_LODGE_APPLICATION");
+			}
+			if (runtime.matchesResponseLine(selectedTopic, 0xd4)) {
+				(void)runtime.startupScript().setRuntimeFlagValue("GIVEN_PAPER_TODAY", true);
+				(void)runtime.startupScript().setRuntimeFlagValue("JUST_GOT_MANHOLE_KEY", true);
+				(void)runtime.startupScript().setRuntimeFlagValue("HAVE_COMPLETED_LODGE_APP", true);
+				sharedState.dialogueStateD2f08 = true;
+				sharedState.dialogueStateD2f00 = true;
+				(void)runtime.startupScript().setRuntimeFlagValue("QUEST_1", true);
+				(void)runtime.startupScript().setRuntimeFlagValue("DAY_1", false);
+				(void)runtime.startupScript().setRuntimeFlagValue("DAY_2", true);
+				(void)runtime.startupScript().setRuntimeFlagValue("DAY_FLAG", true);
+				(void)runtime.startupScript().addRuntimeObjectToInventory("MANKEY");
+				(void)runtime.startupScript().addRuntimeObjectToInventory("ORALUBE");
+				(void)runtime.startupScript().addRuntimeObjectToInventory("SHOVEL");
+			}
+		}
+
+		if (runtime.matchesResponseLine(selectedTopic, europeanRelease ? 0x30f : 0xd2)) {
 			Common::Error lineError = playSequence(
 				kHankMomTopicLines, ARRAYSIZE(kHankMomTopicLines));
 			if (lineError.getCode() != Common::kNoError)


Commit: f165744577607ce1420274b8f79aa1732d792fac
    https://github.com/scummvm/scummvm/commit/f165744577607ce1420274b8f79aa1732d792fac
Author: Alex Bevilacqua (alex at alexbevi.com)
Date: 2026-10-15T19:06:10-04:00

Commit Message:
HARVESTER: reset topic buffer and index for Dwayne, Johnson, and Wasp Woman dialogues

Assisted-by: Codex:gpt-6.1-sol

Changed paths:
    engines/harvester/npc/dwayne_dialogue.cpp
    engines/harvester/npc/johnson_dialogue.cpp
    engines/harvester/npc/wasp_woman_dialogue.cpp


diff --git a/engines/harvester/npc/dwayne_dialogue.cpp b/engines/harvester/npc/dwayne_dialogue.cpp
index 042a83e3b03..e2e465a3bbe 100644
--- a/engines/harvester/npc/dwayne_dialogue.cpp
+++ b/engines/harvester/npc/dwayne_dialogue.cpp
@@ -55,6 +55,8 @@ Common::Error DwayneDialogueHandler::handleDialogue(DialogueRuntime &runtime,
 	Script &startupScript = runtime.startupScript();
 	Common::String &dwayneTopicBuffer = state.currentTopicBuffer;
 	int &dwayneTopicBufferLineIndex = state.currentTopicBufferLineIndex;
+	state.currentTopicBuffer.clear();
+	state.currentTopicBufferLineIndex = -1;
 	auto assignDwayneTopicBuffer = [&](int responseLineIndex) {
 		runtime.assignTopicBuffer(dwayneTopicBuffer, dwayneTopicBufferLineIndex,
 			responseLineIndex, "Dwayne topic buffer");
diff --git a/engines/harvester/npc/johnson_dialogue.cpp b/engines/harvester/npc/johnson_dialogue.cpp
index 02829539650..e9d35d8fed2 100644
--- a/engines/harvester/npc/johnson_dialogue.cpp
+++ b/engines/harvester/npc/johnson_dialogue.cpp
@@ -101,6 +101,8 @@ Common::Error JohnsonDialogueHandler::handleDialogue(DialogueRuntime &runtime,
 	JohnsonRoomDialogueState &state = _state;
 	Common::String &johnsonTopicBuffer = state.currentTopicBuffer;
 	int &johnsonTopicBufferLineIndex = state.currentTopicBufferLineIndex;
+	state.currentTopicBuffer.clear();
+	state.currentTopicBufferLineIndex = -1;
 
 	auto assignJohnsonTopicBuffer = [&](int responseLineIndex) {
 		runtime.assignTopicBuffer(johnsonTopicBuffer, johnsonTopicBufferLineIndex,
@@ -274,9 +276,6 @@ Common::Error JohnsonDialogueHandler::handleDialogue(DialogueRuntime &runtime,
 			return lineError;
 	}
 
-	if (johnsonTopicBufferLineIndex < 0)
-		assignJohnsonTopicBuffer(kJohnsonInitialTopicBufferLine);
-
 	for (;;) {
 		Common::String selectedTopic;
 		Common::Error menuError = runtime.runKeywordMenu(
diff --git a/engines/harvester/npc/wasp_woman_dialogue.cpp b/engines/harvester/npc/wasp_woman_dialogue.cpp
index 02338da6d62..b6e6b8e5420 100644
--- a/engines/harvester/npc/wasp_woman_dialogue.cpp
+++ b/engines/harvester/npc/wasp_woman_dialogue.cpp
@@ -95,7 +95,7 @@ Common::Error WaspWomanDialogueHandler::handleDialogue(DialogueRuntime &runtime,
 		const Common::String &usedItemName, DialogueSharedState &sharedState) {
 	WaspWomanRoomDialogueState &state = _state;
 	Common::String waspWomanTopicBuffer;
-	int waspWomanTopicBufferLineIndex = state.topicBufferLineIndex;
+	int waspWomanTopicBufferLineIndex = -1;
 	auto assignWaspWomanTopicBuffer = [&](int responseLineIndex) {
 		state.topicBufferLineIndex = responseLineIndex;
 		runtime.assignTopicBuffer(waspWomanTopicBuffer, waspWomanTopicBufferLineIndex,
@@ -153,7 +153,6 @@ Common::Error WaspWomanDialogueHandler::handleDialogue(DialogueRuntime &runtime,
 		Common::Error lineError = playWaspWomanLine(0x4bee, 0);
 		if (lineError.getCode() != Common::kNoError)
 			return lineError;
-		assignWaspWomanTopicBuffer(state.topicBufferLineIndex);
 	}
 
 	for (;;) {


Commit: 9825de956a67518e91dd38990d0f1c962c670057
    https://github.com/scummvm/scummvm/commit/9825de956a67518e91dd38990d0f1c962c670057
Author: Alex Bevilacqua (alex at alexbevi.com)
Date: 2026-10-15T19:06:10-04:00

Commit Message:
HARVESTER: refine spyhole topic handling

Refs #17325

Assisted-by: Codex:gpt-6.1-sol

Changed paths:
    engines/harvester/npc/stephanie_dialogue.cpp


diff --git a/engines/harvester/npc/stephanie_dialogue.cpp b/engines/harvester/npc/stephanie_dialogue.cpp
index 081fd8d8b1b..4fe4b1f2ccc 100644
--- a/engines/harvester/npc/stephanie_dialogue.cpp
+++ b/engines/harvester/npc/stephanie_dialogue.cpp
@@ -37,7 +37,7 @@ static const char *const kDialogueC023AFstPath = "GRAPHIC/FST/C023A.FST";
 static const char *const kDialogueC025AFstPath = "GRAPHIC/FST/C025A.FST";
 static const char *const kDialogueC025BFstPath = "GRAPHIC/FST/C025B.FST";
 static const char *const kDialogueC007FstPath = "GRAPHIC/FST/C007.FST";
-static const int kStephanieSpyholeTopicLines[] = { 0x2f9, 0x2fa, 0x2fb, 0x2fc, 0x2fd };
+static const int kStephanieSpyholeTopicLines[] = { 0x2f9, 0x2fa, 0x2fb, 0x2fc };
 
 } // End of namespace
 
@@ -50,6 +50,8 @@ Common::Error StephanieDialogueHandler::handleDialogue(DialogueRuntime &runtime,
 	StephanieRoomDialogueState &state = _state;
 	Common::String &stephanieTopicBuffer = state.currentTopicBuffer;
 	int &stephanieTopicBufferLineIndex = state.currentTopicBufferLineIndex;
+	state.currentTopicBuffer.clear();
+	state.currentTopicBufferLineIndex = -1;
 
 	auto assignStephanieTopicBuffer = [&](int responseLineIndex) {
 		runtime.assignTopicBuffer(stephanieTopicBuffer, stephanieTopicBufferLineIndex,
@@ -669,9 +671,6 @@ Common::Error StephanieDialogueHandler::handleDialogue(DialogueRuntime &runtime,
 		}
 	}
 
-	if (stephanieTopicBufferLineIndex < 0)
-		assignStephanieTopicBuffer(0x2d0);
-
 	for (;;) {
 		Common::String selectedTopic;
 		Common::Error menuError = runtime.runKeywordMenu(
@@ -868,13 +867,8 @@ Common::Error StephanieDialogueHandler::handleDialogue(DialogueRuntime &runtime,
 			assignStephanieTopicBuffer(0x2f8);
 			continue;
 		}
-		// The spyhole continuation is a hidden typed-topic branch. Native only reaches
-		// it after the visible buffer has advanced to dialog.rsp[0x2f8], while the
-		// generic BYE path remains the immediate 0x46a4 exit.
-		if (stephanieTopicBufferLineIndex == 0x2f8 &&
-				runtime.lastKeywordSelectionWasTypedInput() &&
-				runtime.matchesAnyResponseLine(selectedTopic, kStephanieSpyholeTopicLines,
-					ARRAYSIZE(kStephanieSpyholeTopicLines))) {
+		if (runtime.matchesAnyResponseLine(selectedTopic, kStephanieSpyholeTopicLines,
+				ARRAYSIZE(kStephanieSpyholeTopicLines))) {
 			state.playedSpyholeBranch = true;
 			Common::Error lineError = runtime.playDialogueLine(0x4a05, "PC");
 			if (lineError.getCode() != Common::kNoError)


Commit: 197997f27aa9e61117a6b2e600cf5fb51ec867e5
    https://github.com/scummvm/scummvm/commit/197997f27aa9e61117a6b2e600cf5fb51ec867e5
Author: Alex Bevilacqua (alex at alexbevi.com)
Date: 2026-10-15T19:06:10-04:00

Commit Message:
HARVESTER: reset topic buffer and improve evidence handling in dialogue

Refs #17325

Assisted-by: Codex:gpt-6.1-sol

Changed paths:
    engines/harvester/npc/herrill_dialogue.cpp


diff --git a/engines/harvester/npc/herrill_dialogue.cpp b/engines/harvester/npc/herrill_dialogue.cpp
index 7ffcadd4c84..5ff80e517ed 100644
--- a/engines/harvester/npc/herrill_dialogue.cpp
+++ b/engines/harvester/npc/herrill_dialogue.cpp
@@ -65,6 +65,8 @@ Common::Error HerrillDialogueHandler::handleDialogue(DialogueRuntime &runtime,
 	HerrillRoomDialogueState &state = _state;
 	Common::String &herrillTopicBuffer = state.currentTopicBuffer;
 	int &herrillTopicBufferLineIndex = state.currentTopicBufferLineIndex;
+	state.currentTopicBuffer.clear();
+	state.currentTopicBufferLineIndex = -1;
 
 	auto assignHerrillTopicBuffer = [&](int responseLineIndex) {
 		runtime.assignTopicBuffer(herrillTopicBuffer, herrillTopicBufferLineIndex,
@@ -110,7 +112,7 @@ Common::Error HerrillDialogueHandler::handleDialogue(DialogueRuntime &runtime,
 			hasInventoryItem("TV_DEED_PHOTOCOPY");
 	};
 	auto shownWhaleyHerrillPhoto = [&]() {
-		return runtime.startupScript().getFlagValue(DialogueFlags::kShownPhotoOfWhaleyHerrill);
+		return sharedState.discussedWhaleyHerrillPhoto != 0;
 	};
 
 	if (runtime.startupScript().getFlagValue("HERRILL_IN_LODGE")) {
@@ -150,45 +152,87 @@ Common::Error HerrillDialogueHandler::handleDialogue(DialogueRuntime &runtime,
 	if (state.requiresEvidenceForDialogue && !hasRequiredEvidence())
 		return Common::kNoError;
 
+	bool showGascanResponse = false;
 	if (!usedItemName.empty()) {
 		if (usedItemName.equalsIgnoreCase("PHOTO_OF_WHALEY_HERRILL")) {
 			return playHerrillLine(0x309f, 2);
 		}
-		if (usedItemName.equalsIgnoreCase("CASKET_PHOTO") ||
-				usedItemName.equalsIgnoreCase("CASKET_PHOTOCOPY")) {
-			(void)runtime.startupScript().setRuntimeFlagValue(DialogueFlags::kShownPhotoOfCorpse, true);
-			return playHerrillLine(0x30a6);
-		}
-		if ((usedItemName.equalsIgnoreCase("LEDGER") ||
-					usedItemName.equalsIgnoreCase("LEDGER2")) &&
-				runtime.startupScript().getFlagValue("HAVE_BOTH_LEDGERS")) {
-			(void)runtime.startupScript().setRuntimeFlagValue(DialogueFlags::kShownLedgersToAnyone, true);
-			return playHerrillLine(0x30a6);
+		const bool isCasketPhoto = usedItemName.equalsIgnoreCase("CASKET_PHOTO") ||
+			usedItemName.equalsIgnoreCase("CASKET_PHOTOCOPY");
+		const bool isLedger = (usedItemName.equalsIgnoreCase("LEDGER") ||
+			usedItemName.equalsIgnoreCase("LEDGER2")) &&
+			runtime.startupScript().getFlagValue("HAVE_BOTH_LEDGERS");
+		if (isCasketPhoto || isLedger) {
+			if (isCasketPhoto) {
+				sharedState.discussedCasketPhotoEvidence = 1;
+				(void)runtime.startupScript().setRuntimeFlagValue(DialogueFlags::kShownPhotoOfCorpse, true);
+			} else {
+				sharedState.discussedLedgerEvidence = 1;
+				(void)runtime.startupScript().setRuntimeFlagValue(DialogueFlags::kShownLedgersToAnyone, true);
+			}
+			Common::Error lineError = playHerrillLine(0x30a6);
+			if (lineError.getCode() != Common::kNoError)
+				return lineError;
+			int responseIndex = 0;
+			Common::Error responseError = runtime.runResponseMenu(0x27f, responseIndex);
+			if (responseError.getCode() != Common::kNoError)
+				return responseError;
+			if (responseIndex == 1)
+				return playHerrillLine(0x30b1, 2);
+			if (responseIndex == 2)
+				return playHerrillLine(0x30b6, 2);
+			return Common::kNoError;
 		}
 		if (usedItemName.equalsIgnoreCase("GASCAN")) {
 			state.gascanPresented = true;
 		} else if (usedItemName.equalsIgnoreCase("TV_DEED") ||
 				usedItemName.equalsIgnoreCase("TV_DEED_PHOTOCOPY")) {
 			(void)runtime.startupScript().setRuntimeFlagValue(DialogueFlags::kShownEvidenceSheriffOwns, true);
-			return playHerrillLine(0x3188);
+			sharedState.discussedTvDeedEvidence = 1;
+			const DialogueLineEntry lines[] = {
+				{ 0x3188, kHerrillNpc, 0 },
+				{ 0x318c, kPcSpeaker, 0 },
+				{ 0x3191, kHerrillNpc, 2 }
+			};
+			Common::Error lineError = runtime.playDialogueEntrySequence(lines, ARRAYSIZE(lines));
+			if (lineError.getCode() != Common::kNoError)
+				return lineError;
+			sharedState.dialogueStateD2eec = true;
+			if (!state.dialogueStateD2ce4)
+				return Common::kNoError;
+			lineError = playHerrillLine(0x3198, 2);
+			if (lineError.getCode() != Common::kNoError)
+				return lineError;
+			return playHerrillLine(0x319d, 2);
 		} else if (usedItemName.equalsIgnoreCase("NOTE") ||
 				usedItemName.equalsIgnoreCase("NOTE_PHOTOCOPY") ||
 				usedItemName.equalsIgnoreCase("CHECKBOOK") ||
 				usedItemName.equalsIgnoreCase("CHECKBOOK_PHOTOCOPY")) {
 			(void)runtime.startupScript().setRuntimeFlagValue(DialogueFlags::kShownEvidenceOfBlackmail, true);
-			return playHerrillLine(0x30bf);
+			sharedState.discussedNoteCheckbookEvidence = 1;
+			const DialogueLineEntry lines[] = {
+				{ 0x30bf, kHerrillNpc, 0 },
+				{ 0x30c4, kPcSpeaker, 0 },
+				{ 0x30c8, kHerrillNpc, 0 }
+			};
+			Common::Error lineError = runtime.playDialogueEntrySequence(lines, ARRAYSIZE(lines));
+			if (lineError.getCode() != Common::kNoError)
+				return lineError;
+			showGascanResponse = true;
 		} else {
 			return playHerrillLine(0x3099);
 		}
 	}
 
-	if (state.gascanPresented) {
-		state.gascanPresented = false;
-
-		Common::Error lineError = runtime.playDialogueEntrySequence(
-			kHerrillGascanIntroLines, ARRAYSIZE(kHerrillGascanIntroLines));
-		if (lineError.getCode() != Common::kNoError)
-			return lineError;
+	if (state.gascanPresented || showGascanResponse) {
+		Common::Error lineError;
+		if (state.gascanPresented) {
+			lineError = runtime.playDialogueEntrySequence(
+				kHerrillGascanIntroLines, ARRAYSIZE(kHerrillGascanIntroLines));
+			if (lineError.getCode() != Common::kNoError)
+				return lineError;
+			state.gascanPresented = false;
+		}
 		if (!sharedState.dialogueStateD2e98)
 			return playPcLine(0x3123);
 
@@ -203,7 +247,7 @@ Common::Error HerrillDialogueHandler::handleDialogue(DialogueRuntime &runtime,
 			if (lineError.getCode() != Common::kNoError)
 				return lineError;
 			if (sharedState.dialogueStateD2eb0) {
-				lineError = playHerrillLine(0x30eb);
+				lineError = playPcLine(0x30eb);
 				if (lineError.getCode() != Common::kNoError)
 					return lineError;
 			}
@@ -303,11 +347,17 @@ Common::Error HerrillDialogueHandler::handleDialogue(DialogueRuntime &runtime,
 		Common::Error lineError = playHerrillLine(0x304c, 1);
 		if (lineError.getCode() != Common::kNoError)
 			return lineError;
+		lineError = shownWhaleyHerrillPhoto() ? playHerrillLine(0x3050, 2) : playHerrillLine(0x3057, 1);
+		if (lineError.getCode() != Common::kNoError)
+			return lineError;
 	}
 	if (runtime.startupScript().getFlagValue("KARIN_FOUND_DEAD") &&
 			!state.karinFoundDeadShown) {
 		state.karinFoundDeadShown = true;
 		Common::Error lineError = playHerrillLine(0x305e);
+		if (lineError.getCode() != Common::kNoError)
+			return lineError;
+		lineError = playPcLine(0x461b, 3);
 		if (lineError.getCode() != Common::kNoError)
 			return lineError;
 		if (runtime.startupScript().getFlagValue("STEPH_MIDGAME_PLAYED")) {
@@ -333,11 +383,26 @@ Common::Error HerrillDialogueHandler::handleDialogue(DialogueRuntime &runtime,
 		Common::Error lineError = playHerrillLine(0x31a6, 2);
 		if (lineError.getCode() != Common::kNoError)
 			return lineError;
+		int responseIndex = 0;
+		Common::Error responseError = runtime.runResponseMenu(0x282, responseIndex);
+		if (responseError.getCode() != Common::kNoError)
+			return responseError;
+		if (responseIndex == 1) {
+			lineError = playHerrillLine(0x31b1, 2);
+			if (lineError.getCode() != Common::kNoError)
+				return lineError;
+			state.dialogueSuppressed = true;
+			return Common::kNoError;
+		}
+		if (responseIndex == 2) {
+			lineError = playHerrillLine(0x31b9, 2);
+			if (lineError.getCode() != Common::kNoError)
+				return lineError;
+			state.requiresEvidenceForDialogue = true;
+			return Common::kNoError;
+		}
 	}
 
-	if (herrillTopicBufferLineIndex < 0)
-		assignHerrillTopicBuffer(kHerrillTopicBufferResponseLine);
-
 	for (;;) {
 		Common::String selectedTopic;
 		Common::Error menuError = runtime.runKeywordMenu(
@@ -350,53 +415,80 @@ Common::Error HerrillDialogueHandler::handleDialogue(DialogueRuntime &runtime,
 			return playHerrillLine(0x2ff4);
 		}
 		if (runtime.matchesResponseLine(selectedTopic, 0x284)) {
-			Common::Error lineError = playPcLine(0x2f7b, 2);
+			const DialogueLineEntry lines[] = {
+				{ 0x2f7b, kPcSpeaker, 2 },
+				{ 0x2f7f, kHerrillNpc, 0 },
+				{ 0x2f80, kHerrillNpc, 0 },
+				{ 0x2f81, kHerrillNpc, 0 }
+			};
+			Common::Error lineError = runtime.playDialogueEntrySequence(lines, ARRAYSIZE(lines));
 			if (lineError.getCode() != Common::kNoError)
 				return lineError;
+			assignHerrillTopicBuffer(0x285);
 			continue;
 		}
 		if (runtime.matchesResponseLine(selectedTopic, 0x286)) {
-			Common::Error lineError = playHerrillLine(0x2f8f);
+			const DialogueLineEntry lines[] = {
+				{ 0x2f8f, kHerrillNpc, 0 },
+				{ 0x2f90, kHerrillNpc, 0 },
+				{ 0x2f91, kHerrillNpc, 1 }
+			};
+			Common::Error lineError = runtime.playDialogueEntrySequence(lines, ARRAYSIZE(lines));
 			if (lineError.getCode() != Common::kNoError)
 				return lineError;
+			assignHerrillTopicBuffer(0x287);
 			continue;
 		}
 		if (runtime.matchesResponseLine(selectedTopic, 0x288)) {
-			Common::Error lineError = playHerrillLine(0x2f9e, 1);
+			const DialogueLineEntry lines[] = {
+				{ 0x2f9e, kHerrillNpc, 1 },
+				{ 0x2fa3, kPcSpeaker, 2 },
+				{ 0x2fa7, kHerrillNpc, 1 }
+			};
+			Common::Error lineError = runtime.playDialogueEntrySequence(lines, ARRAYSIZE(lines));
 			if (lineError.getCode() != Common::kNoError)
 				return lineError;
+			assignHerrillTopicBuffer(0x289);
 			continue;
 		}
 		if (runtime.matchesResponseLine(selectedTopic, 0x28a)) {
-			Common::Error lineError = playPcLine(0x2fb2);
+			const DialogueLineEntry lines[] = {
+				{ 0x2fb2, kPcSpeaker, 0 },
+				{ 0x2fb6, kHerrillNpc, 0 },
+				{ 0x2fbe, kHerrillNpc, 1 },
+				{ 0x2fc2, kHerrillNpc, 1 }
+			};
+			Common::Error lineError = runtime.playDialogueEntrySequence(lines, ARRAYSIZE(lines));
 			if (lineError.getCode() != Common::kNoError)
 				return lineError;
+			assignHerrillTopicBuffer(0x28b);
 			continue;
 		}
 		if (runtime.matchesResponseLine(selectedTopic, 0x28c)) {
-			Common::Error lineError = playPcLine(0x2fcd);
-			if (lineError.getCode() != Common::kNoError)
-				return lineError;
-			continue;
-		}
-		if (runtime.matchesResponseLine(selectedTopic, 0x28e)) {
-			sharedState.discussedLodgeTopic = true;
-			Common::Error lineError = playHerrillLine(0x2fdd);
+			const DialogueLineEntry lines[] = {
+				{ 0x2fcd, kPcSpeaker, 0 },
+				{ 0x2fd2, kHerrillNpc, 0 }
+			};
+			Common::Error lineError = runtime.playDialogueEntrySequence(lines, ARRAYSIZE(lines));
 			if (lineError.getCode() != Common::kNoError)
 				return lineError;
+			assignHerrillTopicBuffer(0x28d);
 			continue;
 		}
-		if (runtime.matchesResponseLine(selectedTopic, 0x28f)) {
+		if (runtime.matchesResponseLine(selectedTopic, 0x28e) ||
+				runtime.matchesResponseLine(selectedTopic, 0x28f)) {
 			sharedState.discussedLodgeTopic = true;
 			Common::Error lineError = playHerrillLine(0x2fdd);
 			if (lineError.getCode() != Common::kNoError)
 				return lineError;
+			assignHerrillTopicBuffer(0x290);
 			continue;
 		}
 		if (runtime.matchesResponseLine(selectedTopic, 0x291)) {
 			Common::Error lineError = playHerrillLine(0x2fe9, 1);
 			if (lineError.getCode() != Common::kNoError)
 				return lineError;
+			assignHerrillTopicBuffer(0x292);
 			continue;
 		}
 		if (runtime.matchesResponseLine(selectedTopic, 0x293))
@@ -405,7 +497,6 @@ Common::Error HerrillDialogueHandler::handleDialogue(DialogueRuntime &runtime,
 		Common::Error lineError = playHerrillLine(0x3099);
 		if (lineError.getCode() != Common::kNoError)
 			return lineError;
-		return playHerrillLine(0x2ff4);
 	}
 }
 


Commit: 83aa14ee1a05c5d45b285f8475cf50ffdcdd6bc9
    https://github.com/scummvm/scummvm/commit/83aa14ee1a05c5d45b285f8475cf50ffdcdd6bc9
Author: Alex Bevilacqua (alex at alexbevi.com)
Date: 2026-10-15T19:06:10-04:00

Commit Message:
HARVESTER: reset butcher topic buffer and enhance evidence discussion handling

Assisted-by: Codex:gpt-6.1-sol

Changed paths:
    engines/harvester/npc/butcher_dialogue.cpp


diff --git a/engines/harvester/npc/butcher_dialogue.cpp b/engines/harvester/npc/butcher_dialogue.cpp
index e1f76666d85..39521964311 100644
--- a/engines/harvester/npc/butcher_dialogue.cpp
+++ b/engines/harvester/npc/butcher_dialogue.cpp
@@ -89,6 +89,8 @@ Common::Error ButcherDialogueHandler::handleDialogue(DialogueRuntime &runtime,
 	ButcherRoomDialogueState &state = _state;
 	Common::String &butcherTopicBuffer = state.currentTopicBuffer;
 	int &butcherTopicBufferLineIndex = state.currentTopicBufferLineIndex;
+	state.currentTopicBuffer.clear();
+	state.currentTopicBufferLineIndex = -1;
 
 	auto assignButcherTopicBuffer = [&](int responseLineIndex) {
 		runtime.assignTopicBuffer(butcherTopicBuffer, butcherTopicBufferLineIndex,
@@ -114,12 +116,14 @@ Common::Error ButcherDialogueHandler::handleDialogue(DialogueRuntime &runtime,
 
 	if (!usedItemName.empty()) {
 		if (usedItemName.equalsIgnoreCase("PHOTO_OF_WHALEY_HERRILL")) {
+			sharedState.discussedWhaleyHerrillPhoto = 1;
 			(void)runtime.startupScript().setRuntimeFlagValue(
 				DialogueFlags::kShownPhotoOfWhaleyHerrill, true);
 			return playSequence(kButcherWhaleyPhotoLines, ARRAYSIZE(kButcherWhaleyPhotoLines));
 		}
 		if (usedItemName.equalsIgnoreCase("CASKET_PHOTO") ||
 				usedItemName.equalsIgnoreCase("CASKET_PHOTOCOPY")) {
+			sharedState.discussedCasketPhotoEvidence = 1;
 			(void)runtime.startupScript().setRuntimeFlagValue(
 				DialogueFlags::kShownPhotoOfCorpse, true);
 			return playSequence(kButcherCasketPhotoLines, ARRAYSIZE(kButcherCasketPhotoLines));
@@ -127,6 +131,7 @@ Common::Error ButcherDialogueHandler::handleDialogue(DialogueRuntime &runtime,
 		if ((usedItemName.equalsIgnoreCase("LEDGER") ||
 					usedItemName.equalsIgnoreCase("LEDGER2")) &&
 				runtime.startupScript().getFlagValue("HAVE_BOTH_LEDGERS")) {
+			sharedState.discussedLedgerEvidence = 1;
 			(void)runtime.startupScript().setRuntimeFlagValue(
 				DialogueFlags::kShownLedgersToAnyone, true);
 			return playSequence(kButcherLedgerLines, ARRAYSIZE(kButcherLedgerLines));
@@ -135,6 +140,7 @@ Common::Error ButcherDialogueHandler::handleDialogue(DialogueRuntime &runtime,
 				usedItemName.equalsIgnoreCase("NOTE_PHOTOCOPY") ||
 				usedItemName.equalsIgnoreCase("CHECKBOOK") ||
 				usedItemName.equalsIgnoreCase("CHECKBOOK_PHOTOCOPY")) {
+			sharedState.discussedNoteCheckbookEvidence = 1;
 			(void)runtime.startupScript().setRuntimeFlagValue(
 				DialogueFlags::kShownEvidenceOfBlackmail, true);
 			return playButcherLine(0x1a9e);
@@ -403,7 +409,21 @@ Common::Error ButcherDialogueHandler::handleDialogue(DialogueRuntime &runtime,
 			Common::Error lineError = playPcLine(0x1a24, 2);
 			if (lineError.getCode() != Common::kNoError)
 				return lineError;
-			return playButcherLine(0x1a29, 1);
+			lineError = playButcherLine(0x1a29, 1);
+			if (lineError.getCode() != Common::kNoError)
+				return lineError;
+			if (!sharedState.dialogueStateD2f04) {
+				sharedState.dialogueStateD2f04 = true;
+				lineError = playPcLine(0x1a2d);
+			} else {
+				lineError = playPcLine(0x1a31);
+				if (lineError.getCode() == Common::kNoError)
+					lineError = playButcherLine(0x1a36, 2);
+			}
+			if (lineError.getCode() != Common::kNoError)
+				return lineError;
+			assignButcherTopicBuffer(0x278);
+			continue;
 		}
 
 		if (runtime.matchesResponseLine(selectedTopic, 0x279))


Commit: 81aa0f5fbbb0f5d287374852d649c4bc83da79a1
    https://github.com/scummvm/scummvm/commit/81aa0f5fbbb0f5d287374852d649c4bc83da79a1
Author: Alex Bevilacqua (alex at alexbevi.com)
Date: 2026-10-15T19:06:10-04:00

Commit Message:
HARVESTER: enhance Fireman2 dialogue handling with additional state checks

Assisted-by: Codex:gpt-6.1-sol

Changed paths:
    engines/harvester/npc/fireman2_dialogue.cpp


diff --git a/engines/harvester/npc/fireman2_dialogue.cpp b/engines/harvester/npc/fireman2_dialogue.cpp
index ea4c588fae2..302d18a2864 100644
--- a/engines/harvester/npc/fireman2_dialogue.cpp
+++ b/engines/harvester/npc/fireman2_dialogue.cpp
@@ -38,7 +38,20 @@ Common::Error Fireman2DialogueHandler::handleDialogue(DialogueRuntime &runtime,
 		wavId = 0x5b9;
 	}
 
-	return runtime.playDialogueLineWithVariant(wavId, "FIREMAN2", 2);
+	Common::Error lineError = runtime.playDialogueLineWithVariant(wavId, "FIREMAN2", 2);
+	if (lineError.getCode() != Common::kNoError)
+		return lineError;
+	if (runtime.startupScript().getFlagValue("STEPH_MIDGAME_PLAYED") && !_state.dialogueStateD2de0) {
+		_state.dialogueStateD2de0 = 1;
+		lineError = runtime.playDialogueLineWithVariant(0x5c0, "FIREMAN2", 1);
+		if (lineError.getCode() != Common::kNoError)
+			return lineError;
+	}
+	if (runtime.startupScript().getFlagValue("BOLT_OF_CLOTH_TAKEN") && !_state.dialogueStateD2de4) {
+		_state.dialogueStateD2de4 = 1;
+		return runtime.playDialogueLineWithVariant(0x5cd, "FIREMAN2", 3);
+	}
+	return Common::kNoError;
 }
 
 } // End of namespace Harvester


Commit: bb8fc393b78758b349c1c68b00cf0f31c7476be5
    https://github.com/scummvm/scummvm/commit/bb8fc393b78758b349c1c68b00cf0f31c7476be5
Author: Alex Bevilacqua (alex at alexbevi.com)
Date: 2026-10-15T19:06:10-04:00

Commit Message:
HARVESTER: enhance Mcknight dialogue handling with additional evidence discussion and response flow

Assisted-by: Codex:gpt-6.1-sol

Changed paths:
    engines/harvester/npc/mcknight_dialogue.cpp


diff --git a/engines/harvester/npc/mcknight_dialogue.cpp b/engines/harvester/npc/mcknight_dialogue.cpp
index d42befe7ac7..8bebe53b3c7 100644
--- a/engines/harvester/npc/mcknight_dialogue.cpp
+++ b/engines/harvester/npc/mcknight_dialogue.cpp
@@ -63,26 +63,49 @@ Common::Error McknightDialogueHandler::handleDialogue(DialogueRuntime &runtime,
 	if (usedItemName.empty()) {
 		if (_state.talkStatePending) {
 			_state.talkStatePending = false;
-			sharedState.dialogueStateD2f04 = true;
 			const DialogueLineEntry lines[] = {
 				{ 0x1587, kMcknightNpc, 2 },
 				{ 0x158b, kPcSpeaker, 0 },
 				{ 0x158f, kMcknightNpc, 0 },
 				{ 0x1594, kPcSpeaker, 0 },
-				{ 0x1598, kMcknightNpc, 2 },
-				{ 0x159d, kPcSpeaker, 0 }
+				{ 0x1598, kMcknightNpc, 2 }
 			};
-			return playSequence(lines, ARRAYSIZE(lines));
+			Common::Error lineError = playSequence(lines, ARRAYSIZE(lines));
+			if (lineError.getCode() != Common::kNoError)
+				return lineError;
+			sharedState.dialogueStateD2f04 = true;
+			lineError = playPcLine(0x159d);
+			if (lineError.getCode() != Common::kNoError)
+				return lineError;
+			lineError = playMcknightLine(0x15a2, 1);
+			if (lineError.getCode() != Common::kNoError)
+				return lineError;
+			int responseIndex = 0;
+			Common::Error responseError = runtime.runResponseMenu(0x18a, responseIndex);
+			if (responseError.getCode() != Common::kNoError)
+				return responseError;
+			if (responseIndex == 1)
+				return playMcknightLine(0x15af);
+			if (responseIndex == 2) {
+				const DialogueLineEntry replyLines[] = {
+					{ 0x15b5, kMcknightNpc, 4 },
+					{ 0x15bd, kPcSpeaker, 0 },
+					{ 0x15c1, kMcknightNpc, 2 }
+				};
+				return playSequence(replyLines, ARRAYSIZE(replyLines));
+			}
 		}
 		return playMcknightLine(0x15e4);
 	}
 
 	if (usedItemName.equalsIgnoreCase("PHOTO_OF_WHALEY_HERRILL")) {
+		sharedState.discussedWhaleyHerrillPhoto = 1;
 		(void)runtime.startupScript().setRuntimeFlagValue(DialogueFlags::kShownPhotoOfWhaleyHerrill, true);
 		return playMcknightLine(0x15ea, 1);
 	}
 	if (usedItemName.equalsIgnoreCase("CASKET_PHOTO") ||
 			usedItemName.equalsIgnoreCase("CASKET_PHOTOCOPY")) {
+		sharedState.discussedCasketPhotoEvidence = 1;
 		(void)runtime.startupScript().setRuntimeFlagValue(DialogueFlags::kShownPhotoOfCorpse, true);
 		Common::Error lineError = playMcknightLine(0x15f2);
 		if (lineError.getCode() != Common::kNoError)
@@ -103,6 +126,7 @@ Common::Error McknightDialogueHandler::handleDialogue(DialogueRuntime &runtime,
 	if ((usedItemName.equalsIgnoreCase("LEDGER") ||
 				usedItemName.equalsIgnoreCase("LEDGER2")) &&
 			runtime.startupScript().getFlagValue("HAVE_BOTH_LEDGERS")) {
+		sharedState.discussedLedgerEvidence = 1;
 		(void)runtime.startupScript().setRuntimeFlagValue(DialogueFlags::kShownLedgersToAnyone, true);
 		return playMcknightLine(0x1611, 1);
 	}
@@ -110,6 +134,7 @@ Common::Error McknightDialogueHandler::handleDialogue(DialogueRuntime &runtime,
 			usedItemName.equalsIgnoreCase("NOTE_PHOTOCOPY") ||
 			usedItemName.equalsIgnoreCase("CHECKBOOK") ||
 			usedItemName.equalsIgnoreCase("CHECKBOOK_PHOTOCOPY")) {
+		sharedState.discussedNoteCheckbookEvidence = 1;
 		(void)runtime.startupScript().setRuntimeFlagValue(DialogueFlags::kShownEvidenceOfBlackmail, true);
 		const DialogueLineEntry lines[] = {
 			{ 0x161b, kMcknightNpc, 1 },


Commit: 7cd299daeb19f68f74d3f69fc64e13b9b037b0de
    https://github.com/scummvm/scummvm/commit/7cd299daeb19f68f74d3f69fc64e13b9b037b0de
Author: Alex Bevilacqua (alex at alexbevi.com)
Date: 2026-10-15T19:06:10-04:00

Commit Message:
HARVESTER: enhance Mom dialogue with additional lines for improved interaction

Assisted-by: Codex:gpt-6.1-sol

Changed paths:
    engines/harvester/npc/mom_dialogue.cpp


diff --git a/engines/harvester/npc/mom_dialogue.cpp b/engines/harvester/npc/mom_dialogue.cpp
index 2d33edcedf4..2bd2e8fa0aa 100644
--- a/engines/harvester/npc/mom_dialogue.cpp
+++ b/engines/harvester/npc/mom_dialogue.cpp
@@ -47,11 +47,16 @@ static const DialogueLineEntry kMomLedgerLines[] = {
 };
 
 static const DialogueLineEntry kMomCasketPhotoLines[] = {
-	{ 0x233b, "PC", 0 }
+	{ 0x233b, "PC", 0 },
+	{ 0x233f, "MOM", 1 },
+	{ 0x2344, "PC", 0 },
+	{ 0x2348, "MOM", 0 }
 };
 
 static const DialogueLineEntry kMomWhaleyPhotoLines[] = {
-	{ 0x239c, "MOM", 2 }
+	{ 0x239c, "MOM", 2 },
+	{ 0x23a0, "PC", 0 },
+	{ 0x23a5, "MOM", 0 }
 };
 
 static const DialogueLineEntry kMomIntroLines[] = {


Commit: 1ea0767a8db22d9b9ed3acd12f15a2aae53802e8
    https://github.com/scummvm/scummvm/commit/1ea0767a8db22d9b9ed3acd12f15a2aae53802e8
Author: Alex Bevilacqua (alex at alexbevi.com)
Date: 2026-10-15T19:06:10-04:00

Commit Message:
HARVESTER: enhance Moynahan dialogue handling with improved intro sequence and error checks

Assisted-by: Codex:gpt-6.1-sol

Changed paths:
    engines/harvester/npc/moynahan_dialogue.cpp


diff --git a/engines/harvester/npc/moynahan_dialogue.cpp b/engines/harvester/npc/moynahan_dialogue.cpp
index 30fa59d7479..e7c94b85c1b 100644
--- a/engines/harvester/npc/moynahan_dialogue.cpp
+++ b/engines/harvester/npc/moynahan_dialogue.cpp
@@ -416,15 +416,27 @@ Common::Error MoynahanDialogueHandler::handleDialogue(DialogueRuntime &runtime,
 		return playMoynahanLine(0x3e3f, 2);
 	}
 
+	Common::Error lineError;
 	if (state.introPending) {
 		state.introPending = false;
-		return playDialogueSequence(runtime, kMoynahanIntroLines, ARRAYSIZE(kMoynahanIntroLines));
+		lineError = playDialogueSequence(runtime, kMoynahanIntroLines, ARRAYSIZE(kMoynahanIntroLines));
+		if (lineError.getCode() != Common::kNoError)
+			return lineError;
+		sharedState.dialogueStateD2f04 = true;
+		const DialogueLineEntry tailLines[] = {
+			{ 0x3d50, kPcSpeaker, 0 },
+			{ 0x3d54, kMoynahanNpc, 0 }
+		};
+		lineError = playDialogueSequence(runtime, tailLines, ARRAYSIZE(tailLines));
+		if (lineError.getCode() != Common::kNoError)
+			return lineError;
+		assignTopicBuffer(runtime.currentRoomName().equalsIgnoreCase(kMoynahanEmbalmingRoom) ? 0x193 : 0x192);
+	} else {
+		lineError = playMoynahanLine(0x3fd1);
+		if (lineError.getCode() != Common::kNoError)
+			return lineError;
 	}
 
-	Common::Error lineError = playMoynahanLine(0x3fd1);
-	if (lineError.getCode() != Common::kNoError)
-		return lineError;
-
 	if (runtime.startupScript().getFlagValue("IF_SAW_THE_SLASHES_ON_THE_BODY") &&
 			!state.slashMarksIntroShown) {
 		lineError = playDialogueSequence(runtime, kMoynahanSlashMarksIntroLines,


Commit: 5855aa834c32b9fe5175d1bb4597434104a28110
    https://github.com/scummvm/scummvm/commit/5855aa834c32b9fe5175d1bb4597434104a28110
Author: Alex Bevilacqua (alex at alexbevi.com)
Date: 2026-10-15T19:06:10-04:00

Commit Message:
HARVESTER: enhance Mr. Potts dialogue handling

Assisted-by: Codex:gpt-6.1-sol

Changed paths:
    engines/harvester/npc/mr_potts_dialogue.cpp


diff --git a/engines/harvester/npc/mr_potts_dialogue.cpp b/engines/harvester/npc/mr_potts_dialogue.cpp
index 25727c75791..f1190d9290e 100644
--- a/engines/harvester/npc/mr_potts_dialogue.cpp
+++ b/engines/harvester/npc/mr_potts_dialogue.cpp
@@ -271,20 +271,14 @@ Common::Error MrPottsDialogueHandler::handleDialogue(DialogueRuntime &runtime,
 		const Common::String &usedItemName, DialogueSharedState &sharedState) {
 	MrPottsRoomDialogueState &state = _state;
 	Common::String mrPottsTopicBuffer;
-	int mrPottsTopicBufferLineIndex = state.topicBufferLineIndex;
+	int mrPottsTopicBufferLineIndex = -1;
+	state.topicBufferLineIndex = -1;
 
 	auto assignMrPottsTopicBuffer = [&](int responseLineIndex) {
 		runtime.assignTopicBuffer(mrPottsTopicBuffer, mrPottsTopicBufferLineIndex,
 			responseLineIndex, "Mr Potts topic buffer");
 		state.topicBufferLineIndex = mrPottsTopicBufferLineIndex;
 	};
-	auto restoreMrPottsTopicBuffer = [&]() {
-		if (state.topicBufferLineIndex < 0)
-			return;
-
-		runtime.assignTopicBuffer(mrPottsTopicBuffer, mrPottsTopicBufferLineIndex,
-			state.topicBufferLineIndex, "Mr Potts topic buffer");
-	};
 	auto playMrPottsLine = [&](int wavId, int headVariant) -> Common::Error {
 		return runtime.playDialogueLineWithVariant(wavId, "MR_POTTS", headVariant);
 	};
@@ -838,10 +832,6 @@ Common::Error MrPottsDialogueHandler::handleDialogue(DialogueRuntime &runtime,
 		}
 	}
 
-	restoreMrPottsTopicBuffer();
-	if (mrPottsTopicBufferLineIndex < 0)
-		return Common::kNoError;
-
 	Common::String selectedTopic;
 	for (;;) {
 		if (runtime.matchesResponseLine(selectedTopic, 0x1e9))
@@ -1056,10 +1046,13 @@ Common::Error MrPottsDialogueHandler::handleDialogue(DialogueRuntime &runtime,
 					lineError = playMrPottsLine(0x2d8e, 2);
 					if (lineError.getCode() != Common::kNoError)
 						return lineError;
-				} else if (responseIndex == 2) {
+				} else if (responseIndex == 2 && sharedState.discussedMrPottsTuesdayNightAlibi) {
 					lineError = playMrPottsLine(0x2d93, 2);
 					if (lineError.getCode() != Common::kNoError)
 						return lineError;
+					lineError = runtime.playDialogueLineWithVariant(0x2d9c, "MRS_POTTS", 1);
+					if (lineError.getCode() != Common::kNoError)
+						return lineError;
 				}
 			} else if (accusationResponseIndex == weddingMatchesResponseIndex) {
 				lineError = playSequence(kMrPottsWeddingMatchesAccusationLines,


Commit: 2f3ec0e67daf81b8873e389481eee9cd261aab3f
    https://github.com/scummvm/scummvm/commit/2f3ec0e67daf81b8873e389481eee9cd261aab3f
Author: Alex Bevilacqua (alex at alexbevi.com)
Date: 2026-10-15T19:06:11-04:00

Commit Message:
HARVESTER: enhance Parsons dialogue handling

Assisted-by: Codex:gpt-6.1-sol

Changed paths:
    engines/harvester/npc/parsons_dialogue.cpp


diff --git a/engines/harvester/npc/parsons_dialogue.cpp b/engines/harvester/npc/parsons_dialogue.cpp
index 6397cf60cae..c1c5fe94138 100644
--- a/engines/harvester/npc/parsons_dialogue.cpp
+++ b/engines/harvester/npc/parsons_dialogue.cpp
@@ -49,7 +49,6 @@ static const int kParsonsAliensFollowupTopicLine = 0x1d0;
 static const int kParsonsHiddenExitTopicLine = 0x1d1;
 
 static const int kParsonsAliensTopicResponseLines[] = {
-	kParsonsAliensTopicLine,
 	kParsonsPastorelliTopicLine,
 	kParsonsBarberTopicLine,
 	kParsonsMrPastorelliTopicLine
@@ -116,6 +115,7 @@ Common::Error ParsonsDialogueHandler::handleDialogue(DialogueRuntime &runtime,
 
 	if (!usedItemName.empty()) {
 		if (usedItemName.equalsIgnoreCase("PHOTO_OF_WHALEY_HERRILL")) {
+			sharedState.discussedWhaleyHerrillPhoto = 1;
 			(void)runtime.startupScript().setRuntimeFlagValue(
 				DialogueFlags::kShownPhotoOfWhaleyHerrill, true);
 			return playParsonsLine(0xe77, 1);
@@ -123,6 +123,7 @@ Common::Error ParsonsDialogueHandler::handleDialogue(DialogueRuntime &runtime,
 
 		if (usedItemName.equalsIgnoreCase("CASKET_PHOTO") ||
 				usedItemName.equalsIgnoreCase("CASKET_PHOTOCOPY")) {
+			sharedState.discussedCasketPhotoEvidence = 1;
 			(void)runtime.startupScript().setRuntimeFlagValue(
 				DialogueFlags::kShownPhotoOfCorpse, true);
 
@@ -141,6 +142,7 @@ Common::Error ParsonsDialogueHandler::handleDialogue(DialogueRuntime &runtime,
 		if ((usedItemName.equalsIgnoreCase("LEDGER") ||
 					usedItemName.equalsIgnoreCase("LEDGER2")) &&
 				runtime.startupScript().getFlagValue("HAVE_BOTH_LEDGERS")) {
+			sharedState.discussedLedgerEvidence = 1;
 			(void)runtime.startupScript().setRuntimeFlagValue(
 				DialogueFlags::kShownLedgersToAnyone, true);
 			return playParsonsLine(0xe8e, 0);
@@ -150,6 +152,7 @@ Common::Error ParsonsDialogueHandler::handleDialogue(DialogueRuntime &runtime,
 				usedItemName.equalsIgnoreCase("NOTE_PHOTOCOPY") ||
 				usedItemName.equalsIgnoreCase("CHECKBOOK") ||
 				usedItemName.equalsIgnoreCase("CHECKBOOK_PHOTOCOPY")) {
+			sharedState.discussedNoteCheckbookEvidence = 1;
 			(void)runtime.startupScript().setRuntimeFlagValue(
 				DialogueFlags::kShownEvidenceOfBlackmail, true);
 			return runtime.playDialogueEntrySequence(
@@ -158,6 +161,7 @@ Common::Error ParsonsDialogueHandler::handleDialogue(DialogueRuntime &runtime,
 
 		if (usedItemName.equalsIgnoreCase("TV_DEED") ||
 				usedItemName.equalsIgnoreCase("TV_DEED_PHOTOCOPY")) {
+			sharedState.discussedTvDeedEvidence = 1;
 			(void)runtime.startupScript().setRuntimeFlagValue(
 				DialogueFlags::kShownEvidenceSheriffOwns, true);
 			return playParsonsLine(0xea4, 1);
@@ -280,7 +284,15 @@ Common::Error ParsonsDialogueHandler::handleDialogue(DialogueRuntime &runtime,
 		}
 
 		if (runtime.matchesAnyResponseLine(selectedTopic, kParsonsAliensTopicResponseLines,
-					ARRAYSIZE(kParsonsAliensTopicResponseLines))) {
+				ARRAYSIZE(kParsonsAliensTopicResponseLines))) {
+			lineError = playParsonsLine(0xe54, 2);
+			if (lineError.getCode() != Common::kNoError)
+				return lineError;
+			assignParsonsTopicBuffer(topicBuffer, topicBufferLineIndex, kParsonsAliensFollowupTopicLine);
+			continue;
+		}
+
+		if (runtime.matchesResponseLine(selectedTopic, kParsonsAliensTopicLine)) {
 			lineError = playParsonsLine(0xe1c, 0);
 			if (lineError.getCode() != Common::kNoError)
 				return lineError;
@@ -308,7 +320,6 @@ Common::Error ParsonsDialogueHandler::handleDialogue(DialogueRuntime &runtime,
 			lineError = playParsonsLine(0xe4b, 0);
 			if (lineError.getCode() != Common::kNoError)
 				return lineError;
-			assignParsonsTopicBuffer(topicBuffer, topicBufferLineIndex, kParsonsAliensFollowupTopicLine);
 			continue;
 		}
 


Commit: f3cd18ecd22bfb92caa72d6d266d017bf0e7443c
    https://github.com/scummvm/scummvm/commit/f3cd18ecd22bfb92caa72d6d266d017bf0e7443c
Author: Alex Bevilacqua (alex at alexbevi.com)
Date: 2026-10-15T19:06:11-04:00

Commit Message:
HARVESTER: enhance Phelps dialogue handling

Assisted-by: Codex:gpt-6.1-sol

Changed paths:
    engines/harvester/npc/phelps_dialogue.cpp


diff --git a/engines/harvester/npc/phelps_dialogue.cpp b/engines/harvester/npc/phelps_dialogue.cpp
index eaaa0f70313..f0a8ae5538c 100644
--- a/engines/harvester/npc/phelps_dialogue.cpp
+++ b/engines/harvester/npc/phelps_dialogue.cpp
@@ -110,12 +110,29 @@ Common::Error PhelpsDialogueHandler::handleDialogue(DialogueRuntime &runtime,
 				return lineError;
 
 			sharedState.dialogueStateD2f04 = true;
-			return playPhelpsLine(0x1b5a, 0, "PC");
+			lineError = playPhelpsLine(0x1b5a, 0, "PC");
+			if (lineError.getCode() != Common::kNoError)
+				return lineError;
+			lineError = playPhelpsLine(0x1b5e);
+			if (lineError.getCode() != Common::kNoError)
+				return lineError;
+			if (sharedState.dialogueStateD2ed0) {
+				lineError = playPhelpsLine(0x1b70, 3, "PC");
+				if (lineError.getCode() == Common::kNoError)
+					lineError = playPhelpsLine(0x1b75, 1);
+			} else {
+				lineError = playPhelpsLine(runtime.startupScript().getFlagValue("HEARD_DAD_MOAN") ?
+					0x1b6b : 0x1b67, 0, "PC");
+			}
+			if (lineError.getCode() != Common::kNoError)
+				return lineError;
+		} else {
+			Common::Error lineError = playPhelpsLine(0x1bf4, 1);
+			if (lineError.getCode() != Common::kNoError)
+				return lineError;
 		}
 
-		Common::Error lineError = playPhelpsLine(0x1bf4, 1);
-		if (lineError.getCode() != Common::kNoError)
-			return lineError;
+		Common::Error lineError;
 
 		if (runtime.startupScript().getCurrentStoryDayIndex() < 5) {
 			lineError = playPhelpsLine(0x1b7b, 1);
@@ -256,17 +273,20 @@ Common::Error PhelpsDialogueHandler::handleDialogue(DialogueRuntime &runtime,
 	}
 
 	if (usedItemName.equalsIgnoreCase("PHOTO_OF_WHALEY_HERRILL")) {
+		sharedState.discussedWhaleyHerrillPhoto = 1;
 		(void)runtime.startupScript().setRuntimeFlagValue(DialogueFlags::kShownPhotoOfWhaleyHerrill, true);
 		return playSequence(kPhelpsWhaleyLines, ARRAYSIZE(kPhelpsWhaleyLines));
 	}
 	if (usedItemName.equalsIgnoreCase("CASKET_PHOTO") ||
 			usedItemName.equalsIgnoreCase("CASKET_PHOTOCOPY")) {
+		sharedState.discussedCasketPhotoEvidence = 1;
 		(void)runtime.startupScript().setRuntimeFlagValue(DialogueFlags::kShownPhotoOfCorpse, true);
 		return playSequence(kPhelpsCasketLines, ARRAYSIZE(kPhelpsCasketLines));
 	}
 	if ((usedItemName.equalsIgnoreCase("LEDGER") ||
 				usedItemName.equalsIgnoreCase("LEDGER2")) &&
 			runtime.startupScript().getFlagValue("HAVE_BOTH_LEDGERS")) {
+		sharedState.discussedLedgerEvidence = 1;
 		(void)runtime.startupScript().setRuntimeFlagValue(DialogueFlags::kShownLedgersToAnyone, true);
 		return playPhelpsLine(0x1c36);
 	}
@@ -274,6 +294,7 @@ Common::Error PhelpsDialogueHandler::handleDialogue(DialogueRuntime &runtime,
 			usedItemName.equalsIgnoreCase("NOTE_PHOTOCOPY") ||
 			usedItemName.equalsIgnoreCase("CHECKBOOK") ||
 			usedItemName.equalsIgnoreCase("CHECKBOOK_PHOTOCOPY")) {
+		sharedState.discussedNoteCheckbookEvidence = 1;
 		(void)runtime.startupScript().setRuntimeFlagValue(DialogueFlags::kShownEvidenceOfBlackmail, true);
 		return playPhelpsLine(0x1c3d, 1);
 	}


Commit: 73bf3fc38531829bdf9ab7d6677f9f920c98116c
    https://github.com/scummvm/scummvm/commit/73bf3fc38531829bdf9ab7d6677f9f920c98116c
Author: Alex Bevilacqua (alex at alexbevi.com)
Date: 2026-10-15T19:06:11-04:00

Commit Message:
HARVESTER: picking up items should make them usable right away

Fixes #17300

Assisted-by: Codex:gpt-6.1-sol

Changed paths:
    engines/harvester/dialogue.cpp
    engines/harvester/input.cpp
    engines/harvester/interaction.cpp
    engines/harvester/interaction.h
    engines/harvester/scene.cpp


diff --git a/engines/harvester/dialogue.cpp b/engines/harvester/dialogue.cpp
index 92e7c579581..f61d9768499 100644
--- a/engines/harvester/dialogue.cpp
+++ b/engines/harvester/dialogue.cpp
@@ -1513,6 +1513,11 @@ Common::Error DialogueSystem::runRoomNpcDialogue(const IndexedBitmap &backdrop,
 		return Common::kNoError;
 	}
 
+	if (!usedItemName.empty() && script->addRuntimeObjectToInventory(usedItemName)) {
+		debugC(2, kDebugDialogue, "Harvester: presented item moved to inventory object='%s' npc='%s'",
+			usedItemName.c_str(), npc.npcName.c_str());
+	}
+
 	RoomNpcDialogueSession session(_engine, _mousePos, flow, backdrop, palette, paletteBrightness, npc);
 	if (!session.isValid())
 		return Common::kReadingFailed;
diff --git a/engines/harvester/input.cpp b/engines/harvester/input.cpp
index c4a93f1e195..69a6a91c35f 100644
--- a/engines/harvester/input.cpp
+++ b/engines/harvester/input.cpp
@@ -379,7 +379,7 @@ InputResult Input::handleLeftButtonDown(bool &needsRedraw) {
 					_inventory.getSelectedItemName().c_str(),
 					inventoryHover->object.objectName.c_str());
 				Common::Error interactionError =
-					_roomItems.handleInventoryTargetInteraction(inventoryHover->object, _interactionProcessor, needsRedraw);
+					_roomItems.handleSelectedItemTargetInteraction(inventoryHover->object, _interactionProcessor, needsRedraw);
 				if (interactionError.getCode() != Common::kNoError)
 					return InputResult::leaveRoom(interactionError);
 			}
@@ -401,14 +401,14 @@ InputResult Input::handleLeftButtonDown(bool &needsRedraw) {
 		_engine, _scene.state, _scene.sceneObjects, _scene.state.roomNpcs, _scene.sceneRegions,
 		_flow._mousePos, _flow._menuTextConfig, &_flow._dialogue);
 	ObjectRecord *selectedRoomTarget = nullptr;
-	if (_inventory.hasSelection() && !hoverState.npc)
-		selectedRoomTarget = _roomItems.findSelectedInventoryRoomTarget(_flow._mousePos);
+	if (_roomItems.hasSelectedItem() && !hoverState.npc)
+		selectedRoomTarget = _roomItems.findSelectedItemRoomTarget(_flow._mousePos);
 	if (selectedRoomTarget &&
 			(!hoverState.object ||
 			 !selectedRoomTarget->objectName.equalsIgnoreCase(hoverState.object->objectName))) {
 		debugC(1, kDebugInventory,
 			"Harvester: inventory target remap selected='%s' hovered='%s' remapped='%s'",
-			_inventory.getSelectedItemName().c_str(),
+			_roomItems.getSelectedItemName().c_str(),
 			hoverState.object ? hoverState.object->objectName.c_str() : "",
 			selectedRoomTarget->objectName.c_str());
 	}
@@ -428,12 +428,13 @@ InputResult Input::handleLeftButtonDown(bool &needsRedraw) {
 			if (!_roomItems.stowCarriedRoomItemToInventory())
 				return InputResult::leaveRoom(Common::kReadingFailed);
 			needsRedraw = true;
+			return InputResult();
 		}
-		return InputResult();
 	}
-	if (_inventory.hasSelection()) {
-		const Common::String selectedItemName = _inventory.getSelectedItemName();
+	if (_roomItems.hasSelectedItem()) {
+		const Common::String selectedItemName = _roomItems.getSelectedItemName();
 		if (clickHoverState.npc) {
+			_roomItems.clearSelectedItem();
 			bool didTransition = false;
 			Common::Error dialogueError = _interactionProcessor.runScriptedDialogue(
 				clickHoverState.npc->npcName, selectedItemName, Common::String(), didTransition);
@@ -441,8 +442,7 @@ InputResult Input::handleLeftButtonDown(bool &needsRedraw) {
 				return InputResult::leaveRoom(dialogueError);
 			if (_flow.hasPendingMainMenuReturn())
 				return InputResult::leaveRoom(Common::kNoError);
-			if (_inventory.clearSelection())
-				needsRedraw = true;
+			needsRedraw = true;
 			return InputResult();
 		}
 
@@ -451,7 +451,7 @@ InputResult Input::handleLeftButtonDown(bool &needsRedraw) {
 			: nullptr;
 		if (roomTarget) {
 			Common::Error interactionError =
-				_roomItems.handleInventoryTargetInteraction(*roomTarget, _interactionProcessor, needsRedraw);
+				_roomItems.handleSelectedItemTargetInteraction(*roomTarget, _interactionProcessor, needsRedraw);
 			if (interactionError.getCode() != Common::kNoError)
 				return InputResult::leaveRoom(interactionError);
 			needsRedraw = true;
diff --git a/engines/harvester/interaction.cpp b/engines/harvester/interaction.cpp
index 8a6500efe65..77b37bfd8cb 100644
--- a/engines/harvester/interaction.cpp
+++ b/engines/harvester/interaction.cpp
@@ -84,6 +84,26 @@ bool ItemInteractions::hasCarriedRoomItem() const {
 	return !_carriedRoomItemName.empty();
 }
 
+bool ItemInteractions::hasSelectedItem() const {
+	return hasCarriedRoomItem() || _inventory.hasSelection();
+}
+
+Common::String ItemInteractions::getSelectedItemName() const {
+	return hasCarriedRoomItem() ? _carriedRoomItemName : _inventory.getSelectedItemName();
+}
+
+void ItemInteractions::clearSelectedItem() {
+	clearCarriedRoomItem();
+	_inventory.clearSelection();
+}
+
+void ItemInteractions::hideCarriedRoomItem() {
+	if (!hasCarriedRoomItem())
+		return;
+	hideSceneObject(_carriedRoomItemName, nullptr);
+	_sceneRuntime.removeSceneEntityByName(_carriedRoomItemName);
+}
+
 void ItemInteractions::clearCarriedRoomItem() {
 	_carriedRoomItemName.clear();
 	_carriedRoomItemLabel.clear();
@@ -128,8 +148,8 @@ bool ItemInteractions::shouldCancelDeniedPickup(const InteractionResult &interac
 
 Common::Error ItemInteractions::restoreDeniedPickup(Script &script, const ObjectRecord &savedObject, bool clearCarryState,
 		InteractionCallbacks &callbacks, bool &needsRedraw) {
-	if (!script.syncRuntimeObjectRecord(savedObject))
-		return Common::kReadingFailed;
+	// This returns whether fields changed, not whether the record was found.
+	(void)script.syncRuntimeObjectRecord(savedObject);
 	if (clearCarryState)
 		clearCarriedRoomItem();
 
@@ -198,9 +218,14 @@ Common::Error ItemInteractions::beginRoomItemCarry(const ObjectRecord &object,
 	if (!spritePath.empty())
 		loadBitmapResource(*resources, spritePath, _carriedRoomItemBitmap);
 
-	script->setRuntimeObjectVisible(object.currentOwnerOrRoom, object.objectName, false);
-	hideSceneObject(object.objectName, nullptr);
-	_sceneRuntime.removeSceneEntityByName(object.objectName);
+	// GOG run_harvester_main_loop (0x6dc70) increments currentX to select
+	// the alternate sprite, preserving the object record's owner and visibility.
+	ObjectRecord carriedObject = object;
+	++carriedObject.currentX;
+	(void)script->syncRuntimeObjectRecord(carriedObject);
+	hideCarriedRoomItem();
+	debugC(2, kDebugRoom, "Harvester: carrying room item object='%s' owner='%s'",
+		object.objectName.c_str(), object.currentOwnerOrRoom.c_str());
 
 	bool didTransition = false;
 	if (shouldDispatchPickupActionOnCarryStart(object)) {
@@ -244,12 +269,12 @@ Common::Error ItemInteractions::beginRoomItemCarry(const ObjectRecord &object,
 	return Common::kNoError;
 }
 
-Common::Error ItemInteractions::handleInventoryTargetInteraction(const ObjectRecord &target,
+Common::Error ItemInteractions::handleSelectedItemTargetInteraction(const ObjectRecord &target,
 		Interaction &interactionProcessor, bool &needsRedraw) {
-	if (!_inventory.hasSelection())
+	if (!hasSelectedItem())
 		return Common::kNoError;
 
-	const Common::String selectedItemName = _inventory.getSelectedItemName();
+	const Common::String selectedItemName = getSelectedItemName();
 	InteractionResult interaction;
 	const bool handled = _engine.getScript()->resolveUseItemInteraction(
 		selectedItemName, target, interaction, _scene.state.roomName);
@@ -261,23 +286,19 @@ Common::Error ItemInteractions::handleInventoryTargetInteraction(const ObjectRec
 		return Common::kNoError;
 	}
 
-	StartupRoomTransitionKind roomTransition = interaction.roomTransition;
-	if (roomTransition == kStartupRoomTransitionNone && !interaction.nextRoomName.empty())
-		roomTransition = kStartupRoomTransitionCloseup;
-
-	// Flow shares one inventory system across nested room loops. Clear the held item
-	// before entering a closeup so it is not still carried while the child room loop
-	// is active.
-	if (!interaction.nextRoomName.empty() &&
-			roomTransition != kStartupRoomTransitionChangeRoom)
-		_inventory.clearSelection();
+	// GOG handle_target_interaction (0x7ff50) accepts room-carried items and
+	// requests a scene rebuild after every USEITEM match. Clear the selection
+	// before callbacks can enter another room or stow a consumed item.
+	clearSelectedItem();
+	interaction.visualRuntimeStateChanged = true;
+	debugC(2, kDebugRoom, "Harvester: using item object='%s' target='%s'",
+		selectedItemName.c_str(), target.objectName.c_str());
 
 	bool didTransition = false;
 	Common::Error interactionError =
 		interactionProcessor.handleInteractionResult(interaction, didTransition, selectedItemName);
 	if (interactionError.getCode() != Common::kNoError)
 		return interactionError;
-	_inventory.clearSelection();
 	if (interactionProcessor.hasPendingMainMenuReturn())
 		return Common::kNoError;
 	if (!_inventory.refresh())
@@ -287,8 +308,8 @@ Common::Error ItemInteractions::handleInventoryTargetInteraction(const ObjectRec
 	return Common::kNoError;
 }
 
-ObjectRecord *ItemInteractions::findSelectedInventoryRoomTarget(const Common::Point &point) {
-	if (!_inventory.hasSelection())
+ObjectRecord *ItemInteractions::findSelectedItemRoomTarget(const Common::Point &point) {
+	if (!hasSelectedItem())
 		return nullptr;
 
 	EntityManager *entityManager = _engine.getRuntimeEntities();
@@ -296,7 +317,7 @@ ObjectRecord *ItemInteractions::findSelectedInventoryRoomTarget(const Common::Po
 	if (!entityManager || !script)
 		return nullptr;
 
-	const Common::String selectedItemName = _inventory.getSelectedItemName();
+	const Common::String selectedItemName = getSelectedItemName();
 	ObjectRecord *bestTarget = nullptr;
 	int bestDrawIndex = -1;
 	for (ObjectRecord &candidate : _scene.sceneObjects) {
diff --git a/engines/harvester/interaction.h b/engines/harvester/interaction.h
index 1e80ef1cb38..5cc8d48481f 100644
--- a/engines/harvester/interaction.h
+++ b/engines/harvester/interaction.h
@@ -66,10 +66,14 @@ public:
 	const Common::String &getCarriedRoomItemLabel() const { return _carriedRoomItemLabel; }
 	const IndexedBitmap &getCarriedRoomItemBitmap() const { return _carriedRoomItemBitmap; }
 	bool hasCarriedRoomItem() const;
+	bool hasSelectedItem() const;
+	Common::String getSelectedItemName() const;
+	void clearSelectedItem();
+	void hideCarriedRoomItem();
 	bool stowCarriedRoomItemToInventory();
-	Common::Error handleInventoryTargetInteraction(const ObjectRecord &target,
+	Common::Error handleSelectedItemTargetInteraction(const ObjectRecord &target,
 		Interaction &interactionProcessor, bool &needsRedraw);
-	ObjectRecord *findSelectedInventoryRoomTarget(const Common::Point &point);
+	ObjectRecord *findSelectedItemRoomTarget(const Common::Point &point);
 	Common::Error handleRoomItemPickup(const ObjectRecord &object, bool playerPresent,
 		Interaction &interactionProcessor, InteractionCallbacks &callbacks, bool &needsRedraw);
 
diff --git a/engines/harvester/scene.cpp b/engines/harvester/scene.cpp
index 7f92daf4227..6164f03a7d2 100644
--- a/engines/harvester/scene.cpp
+++ b/engines/harvester/scene.cpp
@@ -232,6 +232,7 @@ bool Scene::applyCurrentRoomRuntimeMutationsInPlace(bool preservePaletteState) {
 	_scene.sceneObjects = updatedScene.sceneObjects;
 	_scene.sceneAnimations = updatedScene.sceneAnimations;
 	_scene.sceneRegions = updatedScene.sceneRegions;
+	_roomItems.hideCarriedRoomItem();
 	const int playerCombatLoadout = script->getPlayerCombatLoadout();
 	if (_playerState.entity && _playerState.combatLoadout != playerCombatLoadout &&
 			!Player::syncCombatLoadoutVisual(
@@ -266,6 +267,7 @@ bool Scene::refreshCurrentScene(bool preservePlayerPlacement) {
 	if (!_flow.populateRoomSceneEntities(_scene.state, _scene.sceneObjects, _scene.sceneAnimations))
 		return false;
 	_sceneRuntime.restoreRoomMonsterAnimations();
+	_roomItems.hideCarriedRoomItem();
 	Player::rebindAfterSceneRefresh(_scene.state, _playerState, _entityManager, preservePlayerPlacement);
 	_playerState.combatLoadout =
 		_engine.getScript() ? _engine.getScript()->getPlayerCombatLoadout() : 0;
@@ -483,8 +485,8 @@ bool Scene::drawRoomFrame(bool &needsRedraw) {
 		? RoomHoverState()
 		: resolveRoomHoverState(_engine, _scene.state, _scene.sceneObjects, _scene.state.roomNpcs,
 			_scene.sceneRegions, _flow._mousePos, _flow._menuTextConfig, &_flow._dialogue);
-	if (!suppressHover && inventorySelectionActive && !hoverState.npc) {
-		if (ObjectRecord *selectedTarget = _roomItems.findSelectedInventoryRoomTarget(_flow._mousePos))
+	if (!suppressHover && activeCarry && !hoverState.npc) {
+		if (ObjectRecord *selectedTarget = _roomItems.findSelectedItemRoomTarget(_flow._mousePos))
 			hoverState.object = selectedTarget;
 	}
 	Common::String promptText;


Commit: cb3c416db7d03ee96493c54d052e7da3ae3e15db
    https://github.com/scummvm/scummvm/commit/cb3c416db7d03ee96493c54d052e7da3ae3e15db
Author: Alex Bevilacqua (alex at alexbevi.com)
Date: 2026-10-15T19:06:11-04:00

Commit Message:
HARVESTER: bitmap targeting and failed-use responses

Assisted-by: Codex:gpt-6.1-sol

Changed paths:
    engines/harvester/entity.cpp
    engines/harvester/entity.h
    engines/harvester/input.cpp
    engines/harvester/interaction.cpp
    engines/harvester/interaction.h
    engines/harvester/inventory.cpp
    engines/harvester/inventory.h
    engines/harvester/scene.cpp


diff --git a/engines/harvester/entity.cpp b/engines/harvester/entity.cpp
index bd471b1d939..903a7e1e136 100644
--- a/engines/harvester/entity.cpp
+++ b/engines/harvester/entity.cpp
@@ -580,6 +580,31 @@ bool Entity::hitTest(const Common::Point &point) const {
 	return isOpaqueAt(point);
 }
 
+bool Entity::overlapsBitmap(const IndexedBitmap &bitmap, const Common::Point &position, bool opaquePixels) const {
+	if (!_visible || !bitmap.isValid())
+		return false;
+	const Common::Rect bounds = getFrameRect();
+	const Common::Rect bitmapBounds(position.x, position.y,
+		position.x + bitmap.width, position.y + bitmap.height);
+	if (bounds.right < bitmapBounds.left || bitmapBounds.right < bounds.left ||
+			bounds.bottom < bitmapBounds.top || bitmapBounds.bottom < bounds.top)
+		return false;
+	if (!opaquePixels)
+		return true;
+	if (!hasOpaqueFrame() || bitmap.width > 640 || bitmap.height > 480 ||
+			bounds.width() > 640 || bounds.height() > 480)
+		return false;
+
+	for (uint y = 0; y < bitmap.height; ++y) {
+		for (uint x = 0; x < bitmap.width; x += 2) {
+			if (bitmap.pixels[y * bitmap.width + x] &&
+					isOpaqueAt(Common::Point(position.x + x, position.y + y)))
+				return true;
+		}
+	}
+	return false;
+}
+
 bool Entity::overlapsEntity(const Entity &other) const {
 	if (this == &other || !_visible || !other._visible)
 		return false;
@@ -1021,6 +1046,25 @@ const Entity *EntityManager::findTopSceneEntityAt(const Common::Point &point, in
 	return nullptr;
 }
 
+const Entity *EntityManager::findTopSceneEntityOverlappingBitmap(const IndexedBitmap &bitmap,
+		const Common::Point &position, const Common::String &excludedName, bool opaquePixels) const {
+	// GOG handle_target_interaction (0x7ff50) excludes STAT and classes above 0x15.
+	for (int i = (int)_sceneEntities.size() - 1; i >= 0; --i) {
+		const Entity *entity = _sceneEntities[(uint)i];
+		const int classId = entity->getClassId();
+		if (entity->getName().empty() || entity->getName().equalsIgnoreCase(excludedName) ||
+				entity->getName().equalsIgnoreCase("STAT") || classId == kEntityClassCursor ||
+				classId > kEntityClassRectHotspot)
+			continue;
+		if (!opaquePixels && classId != kEntityClassObject && classId != kEntityClassRectHotspot &&
+				classId != kEntityClassNpc && classId != kEntityClassPlayer)
+			continue;
+		if (entity->overlapsBitmap(bitmap, position, opaquePixels))
+			return entity;
+	}
+	return nullptr;
+}
+
 int EntityManager::findSceneEntityDrawIndexByName(const Common::String &name) const {
 	for (uint i = 0; i < _sceneEntities.size(); ++i) {
 		const Entity *entity = _sceneEntities[i];
diff --git a/engines/harvester/entity.h b/engines/harvester/entity.h
index 8fbff46d5de..b14011749a3 100644
--- a/engines/harvester/entity.h
+++ b/engines/harvester/entity.h
@@ -125,6 +125,7 @@ public:
 	void draw(Graphics::Screen &screen) const;
 	bool hitTest(const Common::Point &point) const;
 	bool overlapsEntity(const Entity &other) const;
+	bool overlapsBitmap(const IndexedBitmap &bitmap, const Common::Point &position, bool opaquePixels) const;
 	bool measureCurrentFrameTransparency(uint32 &framePixels, uint32 &transparentPixels,
 		uint32 &preservedPixels) const;
 
@@ -215,6 +216,8 @@ public:
 	void drawSceneEntities(Graphics::Screen &screen) const;
 	void drawCursor(Graphics::Screen &screen) const;
 	const Entity *findTopSceneEntityAt(const Common::Point &point, int classIdFilter = -1) const;
+	const Entity *findTopSceneEntityOverlappingBitmap(const IndexedBitmap &bitmap,
+		const Common::Point &position, const Common::String &excludedName, bool opaquePixels) const;
 	int findSceneEntityDrawIndexByName(const Common::String &name) const;
 	const Entity *findSceneEntityByName(const Common::String &name) const;
 	Entity *findSceneEntityByName(const Common::String &name);
diff --git a/engines/harvester/input.cpp b/engines/harvester/input.cpp
index 69a6a91c35f..28aebb316b5 100644
--- a/engines/harvester/input.cpp
+++ b/engines/harvester/input.cpp
@@ -400,21 +400,27 @@ InputResult Input::handleLeftButtonDown(bool &needsRedraw) {
 	const RoomHoverState hoverState = resolveRoomHoverState(
 		_engine, _scene.state, _scene.sceneObjects, _scene.state.roomNpcs, _scene.sceneRegions,
 		_flow._mousePos, _flow._menuTextConfig, &_flow._dialogue);
-	ObjectRecord *selectedRoomTarget = nullptr;
-	if (_roomItems.hasSelectedItem() && !hoverState.npc)
-		selectedRoomTarget = _roomItems.findSelectedItemRoomTarget(_flow._mousePos);
-	if (selectedRoomTarget &&
-			(!hoverState.object ||
-			 !selectedRoomTarget->objectName.equalsIgnoreCase(hoverState.object->objectName))) {
-		debugC(1, kDebugInventory,
-			"Harvester: inventory target remap selected='%s' hovered='%s' remapped='%s'",
-			_roomItems.getSelectedItemName().c_str(),
-			hoverState.object ? hoverState.object->objectName.c_str() : "",
-			selectedRoomTarget->objectName.c_str());
-	}
 	RoomHoverState clickHoverState = hoverState;
-	if (selectedRoomTarget)
-		clickHoverState.object = selectedRoomTarget;
+	const Entity *itemTarget = nullptr;
+	if (_roomItems.hasSelectedItem()) {
+		clickHoverState = RoomHoverState();
+		itemTarget = _roomItems.findSelectedItemTarget(_flow._mousePos);
+		if (!itemTarget)
+			return InputResult();
+		if (itemTarget == playerState.entity)
+			clickHoverState.playerEntity = itemTarget;
+		if (itemTarget->getClassId() == kEntityClassNpc &&
+				_flow._dialogue.hasRoomNpcHandler(itemTarget->getName()) &&
+				_engine.getScript()->isNamedNpcDeathTypeClear(itemTarget->getName())) {
+			for (const NpcRecord &npc : _scene.state.roomNpcs) {
+				if (npc.npcName.equalsIgnoreCase(itemTarget->getName())) {
+					clickHoverState.npc = &npc;
+					break;
+				}
+			}
+		}
+		clickHoverState.object = findSceneObjectByName(_scene.sceneObjects, itemTarget->getName());
+	}
 	debugC(1, kDebugRoom,
 		"Harvester: room click room='%s' mouse=(%d,%d) object='%s' npc='%s' region='%s' cursor_sequence=%d prompt='%s'",
 		_scene.state.roomName.c_str(), _flow._mousePos.x, _flow._mousePos.y,
@@ -455,6 +461,10 @@ InputResult Input::handleLeftButtonDown(bool &needsRedraw) {
 			if (interactionError.getCode() != Common::kNoError)
 				return InputResult::leaveRoom(interactionError);
 			needsRedraw = true;
+		} else if (itemTarget) {
+			Common::Error interactionError = _roomItems.handleSelectedItemMiss(_interactionProcessor, needsRedraw);
+			if (interactionError.getCode() != Common::kNoError)
+				return InputResult::leaveRoom(interactionError);
 		}
 		return InputResult();
 	}
diff --git a/engines/harvester/interaction.cpp b/engines/harvester/interaction.cpp
index 77b37bfd8cb..e8fbecf9737 100644
--- a/engines/harvester/interaction.cpp
+++ b/engines/harvester/interaction.cpp
@@ -218,8 +218,6 @@ Common::Error ItemInteractions::beginRoomItemCarry(const ObjectRecord &object,
 	if (!spritePath.empty())
 		loadBitmapResource(*resources, spritePath, _carriedRoomItemBitmap);
 
-	// GOG run_harvester_main_loop (0x6dc70) increments currentX to select
-	// the alternate sprite, preserving the object record's owner and visibility.
 	ObjectRecord carriedObject = object;
 	++carriedObject.currentX;
 	(void)script->syncRuntimeObjectRecord(carriedObject);
@@ -279,11 +277,9 @@ Common::Error ItemInteractions::handleSelectedItemTargetInteraction(const Object
 	const bool handled = _engine.getScript()->resolveUseItemInteraction(
 		selectedItemName, target, interaction, _scene.state.roomName);
 	if (!handled) {
-		debugC(1, kDebugInventory,
-			"Harvester: inventory target miss selected='%s' target='%s' owner='%s' action='%s'",
-			selectedItemName.c_str(), target.objectName.c_str(),
-			target.currentOwnerOrRoom.c_str(), target.actionTag.c_str());
-		return Common::kNoError;
+		debugC(3, kDebugRoom, "Harvester: item use miss selected='%s' target='%s'",
+			selectedItemName.c_str(), target.objectName.c_str());
+		return handleSelectedItemMiss(interactionProcessor, needsRedraw);
 	}
 
 	// GOG handle_target_interaction (0x7ff50) accepts room-carried items and
@@ -308,43 +304,29 @@ Common::Error ItemInteractions::handleSelectedItemTargetInteraction(const Object
 	return Common::kNoError;
 }
 
-ObjectRecord *ItemInteractions::findSelectedItemRoomTarget(const Common::Point &point) {
+Common::Error ItemInteractions::handleSelectedItemMiss(Interaction &interactionProcessor, bool &needsRedraw) {
+	static const char *const responseTags[] = {
+		"GEN_RESP_3", "GEN_RESP_2", "GEN_RESP_4", "GEN_RESP_1", "GEN_RESP_1"
+	};
+	InteractionResult interaction;
+	_engine.getScript()->executeActionTag(responseTags[_engine.getRandomNumber(4)],
+		interaction, true, _scene.state.roomName);
+	bool didTransition = false;
+	Common::Error interactionError =
+		interactionProcessor.handleInteractionResult(interaction, didTransition, getSelectedItemName());
+	needsRedraw = true;
+	return interactionError;
+}
+
+const Entity *ItemInteractions::findSelectedItemTarget(const Common::Point &point, bool opaquePixels) {
 	if (!hasSelectedItem())
 		return nullptr;
-
 	EntityManager *entityManager = _engine.getRuntimeEntities();
-	Script *script = _engine.getScript();
-	if (!entityManager || !script)
+	const IndexedBitmap *bitmap = hasCarriedRoomItem() ? &_carriedRoomItemBitmap : _inventory.getSelectedItemBitmap();
+	if (!entityManager || !bitmap || !bitmap->isValid())
 		return nullptr;
-
-	const Common::String selectedItemName = getSelectedItemName();
-	ObjectRecord *bestTarget = nullptr;
-	int bestDrawIndex = -1;
-	for (ObjectRecord &candidate : _scene.sceneObjects) {
-		if (candidate.objectName.empty() ||
-				!script->hasUseItemInteraction(selectedItemName, candidate)) {
-			continue;
-		}
-
-		const Entity *entity = entityManager->findSceneEntityByName(candidate.objectName);
-		if (!entity || !entity->hitTest(point))
-			continue;
-		if (entity->getClassId() == kEntityClassBackground ||
-				entity->getClassId() == kEntityClassPlayer ||
-				entity->getClassId() == kEntityClassRectHotspot19) {
-			continue;
-		}
-
-		const int drawIndex = entityManager->findSceneEntityDrawIndexByName(entity->getName());
-		if (drawIndex < 0)
-			continue;
-		if (!bestTarget || drawIndex > bestDrawIndex) {
-			bestTarget = &candidate;
-			bestDrawIndex = drawIndex;
-		}
-	}
-
-	return bestTarget;
+	const Common::Point position(point.x - (int)bitmap->width / 2, point.y - (int)bitmap->height / 2);
+	return entityManager->findTopSceneEntityOverlappingBitmap(*bitmap, position, getSelectedItemName(), opaquePixels);
 }
 
 Common::Error ItemInteractions::handleRoomItemPickup(const ObjectRecord &object, bool playerPresent,
diff --git a/engines/harvester/interaction.h b/engines/harvester/interaction.h
index 5cc8d48481f..419031cf7d8 100644
--- a/engines/harvester/interaction.h
+++ b/engines/harvester/interaction.h
@@ -73,7 +73,8 @@ public:
 	bool stowCarriedRoomItemToInventory();
 	Common::Error handleSelectedItemTargetInteraction(const ObjectRecord &target,
 		Interaction &interactionProcessor, bool &needsRedraw);
-	ObjectRecord *findSelectedItemRoomTarget(const Common::Point &point);
+	const Entity *findSelectedItemTarget(const Common::Point &point, bool opaquePixels = true);
+	Common::Error handleSelectedItemMiss(Interaction &interactionProcessor, bool &needsRedraw);
 	Common::Error handleRoomItemPickup(const ObjectRecord &object, bool playerPresent,
 		Interaction &interactionProcessor, InteractionCallbacks &callbacks, bool &needsRedraw);
 
diff --git a/engines/harvester/inventory.cpp b/engines/harvester/inventory.cpp
index 81a646b8640..3054f5e7705 100644
--- a/engines/harvester/inventory.cpp
+++ b/engines/harvester/inventory.cpp
@@ -505,6 +505,15 @@ Common::Rect InventorySystem::getPanelBounds() const {
 		kInventoryX + (int)bitmap.width, kInventoryY + (int)bitmap.height);
 }
 
+const IndexedBitmap *InventorySystem::getSelectedItemBitmap() const {
+	for (const InventoryVisual &item : _items) {
+		if (item.object.objectName.equalsIgnoreCase(_selectedItemName) &&
+				item.hasBitmap && item.bitmap.isValid())
+			return &item.bitmap;
+	}
+	return nullptr;
+}
+
 void InventorySystem::drawSelectedDragItem(Graphics::Screen &screen, const Common::Point &point) const {
 	if (_selectedItemName.empty())
 		return;
diff --git a/engines/harvester/inventory.h b/engines/harvester/inventory.h
index b15d33db022..b7e5f590b3a 100644
--- a/engines/harvester/inventory.h
+++ b/engines/harvester/inventory.h
@@ -63,6 +63,7 @@ public:
 	bool isOpen() const;
 	bool hasSelection() const;
 	const Common::String &getSelectedItemName() const;
+	const IndexedBitmap *getSelectedItemBitmap() const;
 	Common::String resolveSelectedLabel() const;
 	Common::String buildSelectedPrompt(const Common::String &targetLabel,
 		const MenuTextConfig &menuTextConfig) const;
diff --git a/engines/harvester/scene.cpp b/engines/harvester/scene.cpp
index 6164f03a7d2..c66b32a234b 100644
--- a/engines/harvester/scene.cpp
+++ b/engines/harvester/scene.cpp
@@ -485,19 +485,20 @@ bool Scene::drawRoomFrame(bool &needsRedraw) {
 		? RoomHoverState()
 		: resolveRoomHoverState(_engine, _scene.state, _scene.sceneObjects, _scene.state.roomNpcs,
 			_scene.sceneRegions, _flow._mousePos, _flow._menuTextConfig, &_flow._dialogue);
-	if (!suppressHover && activeCarry && !hoverState.npc) {
-		if (ObjectRecord *selectedTarget = _roomItems.findSelectedItemRoomTarget(_flow._mousePos))
-			hoverState.object = selectedTarget;
-	}
 	Common::String promptText;
 	Common::String inventoryTooltipText;
 	auto resolveCarryTargetLabel = [&]() {
-		if (hoverState.playerEntity && _playerState.entity && hoverState.playerEntity == _playerState.entity)
+		const Entity *target = suppressHover ? nullptr : _roomItems.findSelectedItemTarget(_flow._mousePos, false);
+		if (!target)
+			return Common::String();
+		if (target == _playerState.entity)
 			return Common::String(kPlayerInventoryLabel);
-		if (hoverState.object)
-			return _engine.getScript()->resolveObjectLabel(*hoverState.object);
-		if (hoverState.npc)
-			return resolveStartupNpcLabel(*hoverState.npc);
+		if (const ObjectRecord *object = findSceneObjectByName(_scene.sceneObjects, target->getName()))
+			return _engine.getScript()->resolveObjectLabel(*object);
+		for (const NpcRecord &npc : _scene.state.roomNpcs) {
+			if (npc.npcName.equalsIgnoreCase(target->getName()))
+				return resolveStartupNpcLabel(npc);
+		}
 		return Common::String();
 	};
 	if (activeCarry) {


Commit: 84973d3462cce954eed5f79ebceb4d8327ef93c4
    https://github.com/scummvm/scummvm/commit/84973d3462cce954eed5f79ebceb4d8327ef93c4
Author: Alex Bevilacqua (alex at alexbevi.com)
Date: 2026-10-15T19:06:11-04:00

Commit Message:
HARVESTER: fix test for bitmap overlap on entity 0x15 hotspots

Assisted-by: Codex:gpt-6.1-sol

Changed paths:
    engines/harvester/entity.cpp


diff --git a/engines/harvester/entity.cpp b/engines/harvester/entity.cpp
index 903a7e1e136..6b1367bb723 100644
--- a/engines/harvester/entity.cpp
+++ b/engines/harvester/entity.cpp
@@ -591,14 +591,16 @@ bool Entity::overlapsBitmap(const IndexedBitmap &bitmap, const Common::Point &po
 		return false;
 	if (!opaquePixels)
 		return true;
-	if (!hasOpaqueFrame() || bitmap.width > 640 || bitmap.height > 480 ||
+	const bool rectangleTarget = _classId == kEntityClassRectHotspot;
+	if ((!rectangleTarget && !hasOpaqueFrame()) || bitmap.width > 640 || bitmap.height > 480 ||
 			bounds.width() > 640 || bounds.height() > 480)
 		return false;
 
 	for (uint y = 0; y < bitmap.height; ++y) {
 		for (uint x = 0; x < bitmap.width; x += 2) {
+			const Common::Point point(position.x + x, position.y + y);
 			if (bitmap.pixels[y * bitmap.width + x] &&
-					isOpaqueAt(Common::Point(position.x + x, position.y + y)))
+					(rectangleTarget ? bounds.contains(point) : isOpaqueAt(point)))
 				return true;
 		}
 	}




More information about the Scummvm-git-logs mailing list