diff --git a/dGame/dComponents/MissionOfferComponent.cpp b/dGame/dComponents/MissionOfferComponent.cpp index 45fb32dcd..f754e52fb 100644 --- a/dGame/dComponents/MissionOfferComponent.cpp +++ b/dGame/dComponents/MissionOfferComponent.cpp @@ -136,6 +136,21 @@ std::vector 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; diff --git a/dGame/dComponents/MissionOfferComponent.h b/dGame/dComponents/MissionOfferComponent.h index b33ba47f8..fc6a76c50 100644 --- a/dGame/dComponents/MissionOfferComponent.h +++ b/dGame/dComponents/MissionOfferComponent.h @@ -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); diff --git a/dGame/dGameMessages/MissionMessages.cpp b/dGame/dGameMessages/MissionMessages.cpp index 23d047929..26aeeb4e0 100644 --- a/dGame/dGameMessages/MissionMessages.cpp +++ b/dGame/dGameMessages/MissionMessages.cpp @@ -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(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(); + 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);