fix: check the mission state before completing it from a dialogue

MissionDialogueOK accepted or completed the mission for whichever
object the message named as the responder. The responder now has to be
the player that sent the message, the object the dialogue belongs to
has to give or take the mission (on its own or through a random
mission's pool), a mission is only accepted when the player can accept
it and only completed when it is ready to complete. The object's
script runs after these checks.

Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
This commit is contained in:
Aaron Kimbrell
2026-11-10 12:59:29 -06:00
parent c0b7a8f643
commit fe4a1e5e98
3 changed files with 52 additions and 6 deletions

View File

@@ -136,6 +136,21 @@ std::vector<uint32_t> MissionOfferComponent::ParseRandomPool(const std::string&
return pool;
}
bool MissionOfferComponent::HasMission(const uint32_t missionId) const {
for (const auto& offeredMission : this->offeredMissions) {
if (offeredMission.GetMissionId() == missionId) return true;
// A random mission stands for the missions in its pool
CDMissions info{};
if (!Mission::IsValidMission(offeredMission.GetMissionId(), info) || !info.isRandom) continue;
const auto randomMissionPool = ParseRandomPool(info.randomPool);
if (std::ranges::find(randomMissionPool, missionId) != randomMissionPool.end()) return true;
}
return false;
}
const OfferedMission* MissionOfferComponent::GetOfferedMission(const uint32_t missionID) const {
const auto offered = std::ranges::find_if(offeredMissions, [missionID](const OfferedMission& other) { return other.GetMissionId() == missionID; });
return offered == offeredMissions.end() ? nullptr : &*offered;

View File

@@ -96,6 +96,12 @@ public:
*/
void OfferMissions(Entity* entity, uint32_t specifiedMissionId = 0);
/**
* Returns whether this entity offers or accepts a mission, on its own or as one of a random mission's pool
* @param missionId the mission to look for
*/
bool HasMission(uint32_t missionId) const;
// RequestLinkedMission after a mission window: offers the next mission of this giver, if there is a new one
void OfferLinkedMission(Entity* entity, uint32_t missionID);

View File

@@ -143,13 +143,12 @@ namespace GameMessages {
void MissionDialogueOK::Handle(Entity& entityRef, const SystemAddress& sysAddr) {
Entity* entity = &entityRef;
Entity* player = Game::entityManager->GetEntity(responder);
if (!player) {
LOG("MissionDialogueOK for mission %i from unknown responder %llu", missionID, responder);
// A player answers a mission dialogue for themselves
if (!player || !player->IsPlayer() || player->GetSystemAddress() != sysAddr) {
LOG("Responder %llu of MissionDialogueOK for mission %i is not the player that sent it", responder, missionID);
return;
}
entity->GetScript()->OnMissionDialogueOK(entity, player, missionID, iMissionState);
// Get the player's mission component
MissionComponent* missionComponent = static_cast<MissionComponent*>(player->GetComponent(eReplicaComponentType::MISSION));
if (!missionComponent) {
@@ -157,9 +156,35 @@ namespace GameMessages {
return;
}
if (iMissionState == eMissionState::AVAILABLE || iMissionState == eMissionState::COMPLETE_AVAILABLE) {
// The mission has to be one the object the dialogue belongs to gives or takes
const auto* const missionOfferComponent = entity->GetComponent<MissionOfferComponent>();
if (!missionOfferComponent || !missionOfferComponent->HasMission(missionID)) {
LOG("Object %llu (LOT %i) has no mission %i for player %llu to answer", entity->GetObjectID(), entity->GetLOT(), missionID, player->GetObjectID());
return;
}
const bool isAccepting = iMissionState == eMissionState::AVAILABLE || iMissionState == eMissionState::COMPLETE_AVAILABLE;
const bool isCompleting = iMissionState == eMissionState::READY_TO_COMPLETE || iMissionState == eMissionState::COMPLETE_READY_TO_COMPLETE;
if (isAccepting && !missionComponent->CanAccept(missionID)) {
LOG("Player %llu tried to accept mission %i, which they can not accept", player->GetObjectID(), missionID);
return;
}
if (isCompleting) {
// Only a mission with all of its tasks done can be handed in
const auto* const readyMission = missionComponent->GetMission(missionID);
if (!readyMission || !readyMission->IsReadyToComplete()) {
LOG("Player %llu tried to hand in mission %i, which is not ready to complete", player->GetObjectID(), missionID);
return;
}
}
entity->GetScript()->OnMissionDialogueOK(entity, player, missionID, iMissionState);
if (isAccepting) {
missionComponent->AcceptMission(missionID);
} else if (iMissionState == eMissionState::READY_TO_COMPLETE || iMissionState == eMissionState::COMPLETE_READY_TO_COMPLETE) {
} else if (isCompleting) {
// LWOMissionComponent::MissionWindowOK (0x00d501f0) only turns a mission in when every reward fits
// (CanReceiveAllRewards, with the reward the player picked)
const auto* const mission = missionComponent->GetMission(missionID);