Files
DarkflameServer/dGame/dGameMessages/MissionMessages.cpp
Aaron Kimbrell 529bacd077 feat(missions): keep the viewed flag of missions in progress, as the client
The client keeps a viewed flag on each mission in progress, sets it with
SetMissionViewed (LWOMissionComponent::msgSetMissionViewed 0x00bec7a0, only
for missions in progress) and reads it as v on the mission's <cur> entry
(LWOMissionComponent::LoadFromSaveData 0x00d171c0, a number or "true", false
when missing). The server now handles SetMissionViewed, saves v="1" for a
viewed mission in progress and loads it back; saves without v load as not
viewed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 15:48:43 -05:00

303 lines
10 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->OfferLinkedMission(player, missionID);
}
}
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 SetMissionViewed::Serialize(RakNet::BitStream& bitStream) const {
bitStream.Write(missionID);
bitStream.Write(viewed);
}
bool SetMissionViewed::Deserialize(RakNet::BitStream& bitStream) {
VALIDATE_READ(bitStream.Read(missionID));
VALIDATE_READ(bitStream.Read(viewed));
return true;
}
void SetMissionViewed::Handle(Entity& entity, const SystemAddress& sysAddr) {
auto* const missionComponent = entity.GetComponent<MissionComponent>();
if (missionComponent) missionComponent->SetMissionViewed(missionID, viewed);
}
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;
}
}