[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