Files
DarkflameServer/dGame/dGameMessages/MissionMessages.cpp
Aaron Kimbrell 01cc349e76 feat(collectibles): gate collectibles on CollectibleComponent.requirement_mission
CollectibleComponent (79 rows: id, requirement_mission) is only in the
CDClient for the server: the 1.10.64 client has no string for it. DLU
never read it.

What the column holds (1.10.64 CDClient, joined with ComponentsRegistry,
Missions and MissionTasks): for 63 rows it is the mission the collectible
belongs to. Mostly that is the achievement whose collection task
(taskType 3) targets the collectible's LOT (the flags, imagination bricks,
Johnny Thunder collectibles). For a few it is a mission to accept from an
NPC that does not collect the item itself: the four Ninjago dragon relics
(16483-16485) are collected by the hidden achievements 2064-2067 but
their requirement is 2040 (accepted from LOT 13789, "complete 2064-2067").
Other values: -1 and 66666666 (no such mission) on test rows.

So a collectible whose requirement_mission is a mission to accept
(Missions.isMission) now only counts (HasBeenCollected progresses
nothing) while the player has that mission accepted and not handed in
(ACTIVE, READY_TO_COMPLETE or their repeat states). Achievements, missing
missions and rows without one are unchanged. Without this, a player who
had not accepted 2040 could collect the relics early and have 2040
complete as soon as it was accepted. The gate itself is inferred from the
data: the captures show collections but not the live server's check.

The collectible's object report shows the requirement mission.

Check in game: in Ninjago Monastery, before accepting the dragon relic
mission (2040), touch a dragon relic: it does not count; accept 2040 and
collect them: each counts and 2040 completes after the fourth. Flags,
imagination bricks and Johnny Thunder collectibles still count as before.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-29 03:44:07 -05:00

287 lines
9.8 KiB
C++

#include "MissionMessages.h"
#include "BitStreamUtils.h"
#include "Character.h"
#include "CppScripts.h"
#include "dConfig.h"
#include "eMissionLockState.h"
#include "eMissionState.h"
#include "eMissionTaskType.h"
#include "ePlayerFlag.h"
#include "eReplicaComponentType.h"
#include "Entity.h"
#include "EntityManager.h"
#include "Game.h"
#include "Logger.h"
#include "Mission.h"
#include "CollectibleComponent.h"
#include "MissionComponent.h"
#include "MissionOfferComponent.h"
#include "EffectsMessages.h"
namespace GameMessages {
void OfferMission::Serialize(RakNet::BitStream& bitStream) const {
bitStream.Write(missionID);
bitStream.Write(offerer);
}
bool OfferMission::Deserialize(RakNet::BitStream& bitStream) {
VALIDATE_READ(bitStream.Read(missionID));
VALIDATE_READ(bitStream.Read(offerer));
return true;
}
void RespondToMission::Serialize(RakNet::BitStream& bitStream) const {
bitStream.Write(missionID);
bitStream.Write(playerID);
bitStream.Write(receiver);
BitStreamUtils::WriteOptional(bitStream, rewardItem, LOT_NULL);
}
bool RespondToMission::Deserialize(RakNet::BitStream& bitStream) {
VALIDATE_READ(bitStream.Read(missionID));
VALIDATE_READ(bitStream.Read(playerID));
VALIDATE_READ(bitStream.Read(receiver));
VALIDATE_READ(BitStreamUtils::ReadOptional(bitStream, rewardItem, LOT_NULL));
return true;
}
void RespondToMission::Handle(Entity& entity, const SystemAddress& sysAddr) {
const auto reward = rewardItem;
MissionComponent* missionComponent = static_cast<MissionComponent*>(entity.GetComponent(eReplicaComponentType::MISSION));
if (!missionComponent) {
LOG("Unable to get mission component for entity %llu to handle RespondToMission", playerID);
return;
}
Mission* mission = missionComponent->GetMission(missionID);
if (mission) {
mission->SetReward(reward);
} else {
LOG("Unable to get mission %i for entity %llu to update reward in RespondToMission", missionID, playerID);
}
Entity* offerer = Game::entityManager->GetEntity(receiver);
if (offerer == nullptr) {
LOG("Unable to get receiver entity %llu for RespondToMission", receiver);
return;
}
offerer->GetScript()->OnRespondToMission(offerer, missionID, Game::entityManager->GetEntity(playerID), reward);
}
void NotifyMission::Serialize(RakNet::BitStream& bitStream) const {
bitStream.Write(missionID);
bitStream.Write(missionState);
bitStream.Write(sendingRewards);
}
bool NotifyMission::Deserialize(RakNet::BitStream& bitStream) {
VALIDATE_READ(bitStream.Read(missionID));
VALIDATE_READ(bitStream.Read(missionState));
VALIDATE_READ(bitStream.Read(sendingRewards));
return true;
}
void NotifyMissionTask::Serialize(RakNet::BitStream& bitStream) const {
bitStream.Write(missionID);
bitStream.Write(taskMask);
bitStream.Write<uint8_t>(updates.size());
for (const auto update : updates) bitStream.Write(update);
}
bool NotifyMissionTask::Deserialize(RakNet::BitStream& bitStream) {
VALIDATE_READ(bitStream.Read(missionID));
VALIDATE_READ(bitStream.Read(taskMask));
uint8_t count{};
VALIDATE_READ(bitStream.Read(count));
updates.resize(count);
for (auto& update : updates) VALIDATE_READ(bitStream.Read(update));
return true;
}
void ResetMissions::Serialize(RakNet::BitStream& bitStream) const {
BitStreamUtils::WriteOptional(bitStream, missionID, -1);
}
bool ResetMissions::Deserialize(RakNet::BitStream& bitStream) {
VALIDATE_READ(BitStreamUtils::ReadOptional(bitStream, missionID, -1));
return true;
}
void MissionDialogueOK::Serialize(RakNet::BitStream& bitStream) const {
bitStream.Write(bIsComplete);
bitStream.Write(iMissionState);
bitStream.Write(missionID);
bitStream.Write(responder);
}
bool MissionDialogueOK::Deserialize(RakNet::BitStream& bitStream) {
VALIDATE_READ(bitStream.Read(bIsComplete));
VALIDATE_READ(bitStream.Read(iMissionState));
VALIDATE_READ(bitStream.Read(missionID));
VALIDATE_READ(bitStream.Read(responder));
return true;
}
void MissionDialogueCancelled::Serialize(RakNet::BitStream& bitStream) const {
bitStream.Write(bIsComplete);
bitStream.Write(iMissionState);
bitStream.Write(missionID);
bitStream.Write(responder);
}
bool MissionDialogueCancelled::Deserialize(RakNet::BitStream& bitStream) {
VALIDATE_READ(bitStream.Read(bIsComplete));
VALIDATE_READ(bitStream.Read(iMissionState));
VALIDATE_READ(bitStream.Read(missionID));
VALIDATE_READ(bitStream.Read(responder));
return true;
}
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);
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) {
LOG("Unable to get mission component for entity %llu to handle MissionDialogueOK", player->GetObjectID());
return;
}
if (iMissionState == eMissionState::AVAILABLE || iMissionState == eMissionState::COMPLETE_AVAILABLE) {
missionComponent->AcceptMission(missionID);
} else if (iMissionState == eMissionState::READY_TO_COMPLETE || iMissionState == eMissionState::COMPLETE_READY_TO_COMPLETE) {
missionComponent->CompleteMission(missionID);
}
if (Game::config->GetValue("allow_players_to_skip_cinematics") != "1"
|| !player->GetCharacter()
|| !player->GetCharacter()->GetPlayerFlag(ePlayerFlag::DLU_SKIP_CINEMATICS)) return;
player->AddCallbackTimer(0.5f, [player]() {
if (!player) return;
GameMessages::EndCinematic endCinematic;
endCinematic.target = player->GetObjectID();
endCinematic.Send(player->GetSystemAddress());
});
}
void RequestLinkedMission::Serialize(RakNet::BitStream& bitStream) const {
bitStream.Write(playerID);
bitStream.Write(missionID);
bitStream.Write(bMissionOffered);
}
bool RequestLinkedMission::Deserialize(RakNet::BitStream& bitStream) {
VALIDATE_READ(bitStream.Read(playerID));
VALIDATE_READ(bitStream.Read(missionID));
VALIDATE_READ(bitStream.Read(bMissionOffered));
return true;
}
void RequestLinkedMission::Handle(Entity& entity, const SystemAddress& sysAddr) {
auto* player = Game::entityManager->GetEntity(playerID);
auto* missionOfferComponent = static_cast<MissionOfferComponent*>(entity.GetComponent(eReplicaComponentType::MISSION_OFFER));
if (missionOfferComponent != nullptr) {
missionOfferComponent->OfferMissions(player, 0);
}
}
void SetFlag::Serialize(RakNet::BitStream& bitStream) const {
bitStream.Write(bFlag);
bitStream.Write(iFlagID);
}
bool SetFlag::Deserialize(RakNet::BitStream& bitStream) {
VALIDATE_READ(bitStream.Read(bFlag));
VALIDATE_READ(bitStream.Read(iFlagID));
return true;
}
void SetFlag::Handle(Entity& entity, const SystemAddress& sysAddr) {
auto character = entity.GetCharacter();
if (character) character->SetPlayerFlag(iFlagID, bFlag);
// This is always set the first time a player loads into a world from character select
// and is used to know when to refresh the players inventory items so they show up.
if (iFlagID == ePlayerFlag::IS_NEWS_SCREEN_VISIBLE && bFlag) {
entity.SetVar<bool>(u"dlu_first_time_load", true);
}
}
void NotifyClientFlagChange::Serialize(RakNet::BitStream& bitStream) const {
bitStream.Write(bFlag);
bitStream.Write(iFlagID);
}
bool NotifyClientFlagChange::Deserialize(RakNet::BitStream& bitStream) {
VALIDATE_READ(bitStream.Read(bFlag));
VALIDATE_READ(bitStream.Read(iFlagID));
return true;
}
void HasBeenCollected::Serialize(RakNet::BitStream& bitStream) const {
bitStream.Write(playerID);
}
bool HasBeenCollected::Deserialize(RakNet::BitStream& bitStream) {
VALIDATE_READ(bitStream.Read(playerID));
return true;
}
void HasBeenCollected::Handle(Entity& entity, const SystemAddress& sysAddr) {
Entity* player = Game::entityManager->GetEntity(playerID);
if (!player || entity.GetCollectibleID() == 0) return;
// Collectibles of a mission to accept only count while the player has it
const auto* const collectible = entity.GetComponent<CollectibleComponent>();
if (collectible && !collectible->CountsFor(*player)) return;
MissionComponent* missionComponent = static_cast<MissionComponent*>(player->GetComponent(eReplicaComponentType::MISSION));
if (missionComponent) {
missionComponent->Progress(eMissionTaskType::COLLECTION, entity.GetLOT(), entity.GetObjectID());
}
}
SetMissionTypeState::SetMissionTypeState() : NetGameMsg(MessageType::Game::SET_MISSION_TYPE_STATE), state{ eMissionLockState::NEW } {}
void SetMissionTypeState::Serialize(RakNet::BitStream& bitStream) const {
BitStreamUtils::WriteOptional(bitStream, state, eMissionLockState::NEW);
BitStreamUtils::WriteLengthPrefixed(bitStream, subtype);
BitStreamUtils::WriteLengthPrefixed(bitStream, type);
}
bool SetMissionTypeState::Deserialize(RakNet::BitStream& bitStream) {
VALIDATE_READ(BitStreamUtils::ReadOptional(bitStream, state, eMissionLockState::NEW));
VALIDATE_READ(BitStreamUtils::ReadLengthPrefixed(bitStream, subtype));
VALIDATE_READ(BitStreamUtils::ReadLengthPrefixed(bitStream, type));
return true;
}
void SetMissionTypeState::Handle(Entity& entity, const SystemAddress& sysAddr) {
auto* const missionComponent = entity.GetComponent<MissionComponent>();
if (missionComponent) missionComponent->SetMissionTypeState(type, subtype, state);
}
void NotifyLevelRewards::Serialize(RakNet::BitStream& bitStream) const {
bitStream.Write(level);
bitStream.Write(sendingRewards);
}
bool NotifyLevelRewards::Deserialize(RakNet::BitStream& bitStream) {
VALIDATE_READ(bitStream.Read(level));
VALIDATE_READ(bitStream.Read(sendingRewards));
return true;
}
}