mirror of
https://github.com/DarkflameUniverse/DarkflameServer.git
synced 2026-10-02 10:53:44 +00:00
Converts OfferMission, NotifyMission, NotifyMissionTask, ResetMissions,
NotifyClientFlagChange and NotifyLevelRewards (sent with SendToClient:
the old functions never broadcast) and the received RespondToMission,
MissionDialogueOK, RequestLinkedMission, SetFlag and HasBeenCollected
to NetGameMsgs in MissionMessages.{h,cpp}. Callers are switched,
OfferMission's send-it-twice behaviour moves to MissionOfferComponent,
the received messages are registered in GameMessageHandler's map and
the old functions are deleted. Handlers are copied verbatim. The
byte-equality helper can now compare SendToClient messages.
No wire change and no change in recipients. Verified byte for byte
against a frozen verbatim copy of the old functions over an input grid
(including OfferMission's two packets), received messages compared with
the old handlers' read sequences and every truncated payload rejected,
hand computed golden bytes and round trips. Layouts confirmed against
the 1.10.64 client in Ghidra.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
494 lines
12 KiB
C++
494 lines
12 KiB
C++
#include <sstream>
|
|
|
|
#include "MissionTask.h"
|
|
|
|
#include "Game.h"
|
|
#include "Logger.h"
|
|
#include "Mission.h"
|
|
#include "Character.h"
|
|
#include "dServer.h"
|
|
#include "EntityManager.h"
|
|
#include "ScriptedActivityComponent.h"
|
|
#include "GameMessages.h"
|
|
#include "MissionMessages.h"
|
|
#include "dZoneManager.h"
|
|
#include "InventoryComponent.h"
|
|
#include "MissionComponent.h"
|
|
#include "eMissionTaskType.h"
|
|
#include "eReplicaComponentType.h"
|
|
#include "StringifiedEnum.h"
|
|
|
|
MissionTask::MissionTask(Mission* mission, CDMissionTasks* info, uint32_t mask) {
|
|
this->info = info;
|
|
this->mission = mission;
|
|
this->mask = mask;
|
|
|
|
progress = 0;
|
|
|
|
std::istringstream stream(info->taskParam1);
|
|
std::string token;
|
|
|
|
while (std::getline(stream, token, ',')) {
|
|
const auto parameter = GeneralUtils::TryParse<uint32_t>(token);
|
|
if (parameter) parameters.push_back(parameter.value());
|
|
}
|
|
|
|
stream = std::istringstream(info->targetGroup);
|
|
|
|
while (std::getline(stream, token, ',')) {
|
|
const auto parameter = GeneralUtils::TryParse<uint32_t>(token);
|
|
if (parameter) targets.push_back(parameter.value());
|
|
}
|
|
}
|
|
|
|
|
|
eMissionTaskType MissionTask::GetType() const {
|
|
return static_cast<eMissionTaskType>(info->taskType);
|
|
}
|
|
|
|
|
|
uint32_t MissionTask::GetProgress() const {
|
|
return progress;
|
|
}
|
|
|
|
|
|
void MissionTask::SetProgress(const uint32_t value, const bool echo) {
|
|
if (progress == value) {
|
|
return;
|
|
}
|
|
|
|
progress = value;
|
|
|
|
if (!echo) {
|
|
return;
|
|
}
|
|
|
|
auto* entity = mission->GetAssociate();
|
|
|
|
if (entity == nullptr) {
|
|
return;
|
|
}
|
|
|
|
GameMessages::NotifyMissionTask notifyMissionTask;
|
|
notifyMissionTask.target = entity->GetObjectID();
|
|
notifyMissionTask.missionID = static_cast<int32_t>(info->id);
|
|
notifyMissionTask.taskMask = static_cast<int32_t>(1 << (mask + 1));
|
|
notifyMissionTask.updates.push_back(static_cast<float>(progress));
|
|
notifyMissionTask.SendToClient(entity->GetSystemAddress());
|
|
}
|
|
|
|
|
|
void MissionTask::SetUnique(const std::vector<uint32_t>& value) {
|
|
unique = value;
|
|
}
|
|
|
|
|
|
void MissionTask::AddProgress(int32_t value) {
|
|
value += progress;
|
|
|
|
if (value > info->targetValue) {
|
|
value = info->targetValue;
|
|
}
|
|
|
|
if (value < 0) {
|
|
value = 0;
|
|
}
|
|
|
|
SetProgress(value);
|
|
}
|
|
|
|
|
|
Mission* MissionTask::GetMission() const {
|
|
return mission;
|
|
}
|
|
|
|
|
|
uint32_t MissionTask::GetTarget() const {
|
|
if (mission->GetTestedMissions().contains(mission->GetMissionId())) LOG("Target: %i", info->targetValue);
|
|
return info->target;
|
|
}
|
|
|
|
|
|
const CDMissionTasks& MissionTask::GetClientInfo() const {
|
|
return *info;
|
|
}
|
|
|
|
|
|
uint32_t MissionTask::GetMask() const {
|
|
return mask;
|
|
}
|
|
|
|
|
|
const std::vector<uint32_t>& MissionTask::GetUnique() const {
|
|
return unique;
|
|
}
|
|
|
|
|
|
const std::vector<uint32_t>& MissionTask::GetTargets() const {
|
|
return targets;
|
|
}
|
|
|
|
|
|
const std::vector<uint32_t>& MissionTask::GetParameters() const {
|
|
return parameters;
|
|
}
|
|
|
|
|
|
std::vector<uint32_t> MissionTask::GetAllTargets() const {
|
|
auto targets = GetTargets();
|
|
|
|
targets.push_back(GetTarget());
|
|
|
|
return targets;
|
|
}
|
|
|
|
|
|
bool MissionTask::InTargets(const uint32_t value) const {
|
|
auto targets = GetTargets();
|
|
|
|
return std::find(targets.begin(), targets.end(), value) != targets.end();
|
|
}
|
|
|
|
|
|
bool MissionTask::InAllTargets(const uint32_t value) const {
|
|
auto targets = GetAllTargets();
|
|
|
|
return std::find(targets.begin(), targets.end(), value) != targets.end();
|
|
}
|
|
|
|
bool MissionTask::InParameters(const uint32_t value) const {
|
|
auto parameters = GetParameters();
|
|
|
|
return std::find(parameters.begin(), parameters.end(), value) != parameters.end();
|
|
}
|
|
|
|
|
|
bool MissionTask::IsComplete() const {
|
|
if (mission->GetTestedMissions().contains(mission->GetMissionId())) LOG("uid: %i target: %i", info->uid, info->targetValue);
|
|
// Mission 668 has task uid 984 which is a bit mask. Its completion value is 3.
|
|
if (info->uid == 984) {
|
|
return progress >= 3;
|
|
} else {
|
|
return progress >= info->targetValue;
|
|
}
|
|
}
|
|
|
|
|
|
void MissionTask::Complete() {
|
|
if (mission->GetTestedMissions().contains(mission->GetMissionId())) LOG("target: %i", info->targetValue);
|
|
SetProgress(info->targetValue);
|
|
}
|
|
|
|
|
|
void MissionTask::CheckCompletion() const {
|
|
if (IsComplete()) {
|
|
mission->CheckCompletion();
|
|
}
|
|
}
|
|
|
|
|
|
void MissionTask::Progress(int32_t value, LWOOBJID associate, const std::string& targets, int32_t count) {
|
|
if (mission->GetTestedMissions().contains(mission->GetMissionId())) LOG("Progressing mission %s %i", StringifiedEnum::ToString(GetType()).data(), value);
|
|
if (IsComplete() && count > 0) return;
|
|
|
|
const auto type = GetType();
|
|
|
|
if (count < 0) {
|
|
if (mission->IsMission() && type == eMissionTaskType::GATHER && InAllTargets(value)) {
|
|
if (parameters.size() > 0 && (parameters[0] & 4) != 0) {
|
|
return;
|
|
}
|
|
|
|
auto* inventoryComponent = mission->GetAssociate()->GetComponent<InventoryComponent>();
|
|
|
|
if (inventoryComponent != nullptr) {
|
|
int32_t itemCount = inventoryComponent->GetLotCountNonTransfer(value);
|
|
|
|
if (itemCount < info->targetValue) {
|
|
SetProgress(itemCount);
|
|
|
|
if (mission->IsReadyToComplete()) {
|
|
mission->MakeActive();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
Entity* entity;
|
|
ScriptedActivityComponent* activity;
|
|
uint32_t activityId;
|
|
uint32_t lot;
|
|
uint32_t collectionId;
|
|
|
|
switch (type) {
|
|
case eMissionTaskType::UNKNOWN:
|
|
break;
|
|
|
|
case eMissionTaskType::ACTIVITY:
|
|
{
|
|
if (InAllTargets(value)) {
|
|
AddProgress(count);
|
|
break;
|
|
}
|
|
|
|
entity = Game::entityManager->GetEntity(associate);
|
|
if (entity == nullptr) {
|
|
if (associate != LWOOBJID_EMPTY) {
|
|
if (mission->GetTestedMissions().contains(mission->GetMissionId())) LOG("Failed to find associated entity (%llu)!", associate);
|
|
}
|
|
break;
|
|
}
|
|
|
|
activity = static_cast<ScriptedActivityComponent*>(entity->GetComponent(eReplicaComponentType::QUICK_BUILD));
|
|
if (activity == nullptr) {
|
|
break;
|
|
}
|
|
|
|
activityId = activity->GetActivityID();
|
|
|
|
const auto activityIdOverride = entity->GetVar<int32_t>(u"activityID");
|
|
|
|
if (activityIdOverride != 0) {
|
|
activityId = activityIdOverride;
|
|
}
|
|
|
|
if (!InAllTargets(activityId)) break;
|
|
|
|
AddProgress(count);
|
|
|
|
break;
|
|
}
|
|
|
|
case eMissionTaskType::USE_ITEM:
|
|
case eMissionTaskType::TALK_TO_NPC:
|
|
{
|
|
if (GetTarget() != value) break;
|
|
|
|
AddProgress(count);
|
|
|
|
break;
|
|
}
|
|
|
|
case eMissionTaskType::EMOTE:
|
|
{
|
|
if (!InParameters(value)) break;
|
|
|
|
entity = Game::entityManager->GetEntity(associate);
|
|
|
|
if (entity == nullptr) {
|
|
LOG("Failed to find associated entity (%llu)!", associate);
|
|
|
|
break;
|
|
}
|
|
|
|
lot = static_cast<uint32_t>(entity->GetLOT());
|
|
|
|
if (GetTarget() != lot) break;
|
|
|
|
AddProgress(count);
|
|
|
|
break;
|
|
}
|
|
|
|
case eMissionTaskType::USE_SKILL:
|
|
{
|
|
// This is a complicated check because for some missions we need to check for the associate being in the parameters instead of the value being in the parameters.
|
|
if (associate == LWOOBJID_EMPTY && GetAllTargets().size() == 1 && GetAllTargets()[0] == -1) {
|
|
if (InParameters(value)) AddProgress(count);
|
|
} else {
|
|
if (InParameters(associate) && InAllTargets(value)) AddProgress(count);
|
|
}
|
|
break;
|
|
}
|
|
|
|
case eMissionTaskType::PERFORM_ACTIVITY:
|
|
{
|
|
auto* minigameManager = Game::entityManager->GetEntity(associate);
|
|
if (minigameManager == nullptr)
|
|
break;
|
|
|
|
int32_t gameID = minigameManager->GetLOT();
|
|
|
|
auto* sac = minigameManager->GetComponent<ScriptedActivityComponent>();
|
|
if (sac != nullptr) {
|
|
gameID = sac->GetActivityID();
|
|
}
|
|
|
|
if (info->target != gameID) {
|
|
break;
|
|
}
|
|
// This special case is for shooting gallery missions that want their
|
|
// progress value set to 1 instead of being set to the target value.
|
|
if (info->targetGroup == targets && value >= info->targetValue && GetMission()->IsMission() && info->target == 1864 && info->targetGroup == "performact_score") {
|
|
SetProgress(1);
|
|
break;
|
|
}
|
|
if (info->targetGroup == targets && value >= info->targetValue) {
|
|
SetProgress(info->targetValue);
|
|
break;
|
|
}
|
|
break;
|
|
}
|
|
|
|
case eMissionTaskType::VISIT_PROPERTY:
|
|
{
|
|
if (!InAllTargets(value)) break;
|
|
|
|
if (std::find(unique.begin(), unique.end(), static_cast<uint32_t>(associate)) != unique.end()) break;
|
|
|
|
AddProgress(count);
|
|
|
|
unique.push_back(associate);
|
|
|
|
break;
|
|
}
|
|
|
|
case eMissionTaskType::COLLECTION:
|
|
{
|
|
if (!InAllTargets(value)) break;
|
|
|
|
entity = Game::entityManager->GetEntity(associate);
|
|
|
|
if (entity == nullptr) {
|
|
LOG("Failed to find associated entity (%llu)!", associate);
|
|
|
|
break;
|
|
}
|
|
|
|
collectionId = entity->GetCollectibleID();
|
|
|
|
collectionId = static_cast<uint32_t>(collectionId) + static_cast<uint32_t>(Game::server->GetZoneID() << 8);
|
|
|
|
if (std::find(unique.begin(), unique.end(), collectionId) != unique.end()) break;
|
|
|
|
unique.push_back(collectionId);
|
|
|
|
SetProgress(unique.size());
|
|
|
|
auto* entity = mission->GetAssociate();
|
|
|
|
if (entity == nullptr) break;
|
|
|
|
auto* missionComponent = entity->GetComponent<MissionComponent>();
|
|
|
|
if (missionComponent == nullptr) break;
|
|
|
|
missionComponent->AddCollectible(collectionId);
|
|
|
|
break;
|
|
}
|
|
|
|
case eMissionTaskType::EXPLORE:
|
|
{
|
|
if (info->targetGroup != targets) break;
|
|
|
|
AddProgress(count);
|
|
|
|
break;
|
|
}
|
|
|
|
case eMissionTaskType::RACING:
|
|
{
|
|
// The meaning of associate can be found in eRacingTaskParam.h
|
|
if (parameters.empty()) break;
|
|
|
|
if (!InAllTargets(Game::zoneManager->GetZone()->GetWorldID()) && !(parameters[0] == 4 || parameters[0] == 5) && !InAllTargets(value)) break;
|
|
|
|
if (parameters[0] != associate) break;
|
|
|
|
if (associate == 1 || associate == 2 || associate == 3) {
|
|
if (value > info->targetValue) break;
|
|
|
|
AddProgress(info->targetValue);
|
|
}
|
|
// task 15 is a bit mask!
|
|
else if (associate == 15) {
|
|
if (!InAllTargets(value)) break;
|
|
|
|
auto tempProgress = GetProgress();
|
|
// If we won at Nimbus Station, set bit 0
|
|
if (value == 1203) SetProgress(tempProgress |= 1 << 0);
|
|
// If we won at Gnarled Forest, set bit 1
|
|
else if (value == 1303) SetProgress(tempProgress |= 1 << 1);
|
|
// If both bits are set, then the client sees the mission as complete.
|
|
} else if (associate == 10) {
|
|
// If the player did not crash during the race, progress this task by count.
|
|
if (value != 0) break;
|
|
|
|
AddProgress(count);
|
|
} else if (associate == 4 || associate == 5 || associate == 14) {
|
|
if (!InAllTargets(value)) break;
|
|
if (associate == 4) {
|
|
// Recount all completed target missions so this stays idempotent with
|
|
// Catchup, which sets the same value on Accept(). AddProgress would
|
|
// double-count when LookForAchievements runs Catchup then Progress.
|
|
auto* entity = mission->GetAssociate();
|
|
if (entity == nullptr) break;
|
|
auto* missionComponent = entity->GetComponent<MissionComponent>();
|
|
if (missionComponent == nullptr) break;
|
|
uint32_t completedCount = 0;
|
|
for (const auto& target : GetAllTargets()) {
|
|
if (target == 0) continue;
|
|
auto* targetMission = missionComponent->GetMission(target);
|
|
if (targetMission != nullptr && targetMission->IsComplete()) completedCount++;
|
|
}
|
|
SetProgress(completedCount);
|
|
} else {
|
|
AddProgress(count);
|
|
}
|
|
} else if (associate == 17) {
|
|
if (!InAllTargets(value)) break;
|
|
AddProgress(count);
|
|
} else {
|
|
AddProgress(count);
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
case eMissionTaskType::PET_TAMING:
|
|
case eMissionTaskType::SCRIPT:
|
|
case eMissionTaskType::INTERACT:
|
|
case eMissionTaskType::META:
|
|
case eMissionTaskType::POWERUP:
|
|
case eMissionTaskType::SMASH:
|
|
case eMissionTaskType::GATHER:
|
|
case eMissionTaskType::PLAYER_FLAG:
|
|
case eMissionTaskType::EARN_REPUTATION:
|
|
{
|
|
if (!InAllTargets(value)) break;
|
|
|
|
AddProgress(count);
|
|
|
|
break;
|
|
}
|
|
case eMissionTaskType::PLACE_MODEL:
|
|
[[fallthrough]];
|
|
case eMissionTaskType::ADD_BEHAVIOR:
|
|
{
|
|
AddProgress(count);
|
|
break;
|
|
}
|
|
case eMissionTaskType::DONATION:
|
|
AddProgress(count);
|
|
break;
|
|
default:
|
|
LOG("Invalid mission task type (%i)!", static_cast<int>(type));
|
|
return;
|
|
}
|
|
|
|
CheckCompletion();
|
|
}
|
|
|
|
|
|
MissionTask::~MissionTask() {
|
|
targets.clear();
|
|
|
|
parameters.clear();
|
|
|
|
unique.clear();
|
|
}
|