mirror of
https://github.com/DarkflameUniverse/DarkflameServer.git
synced 2026-10-02 02:43:44 +00:00
The client sends SetMissionTypeState (851) for the journal's mission types and subtypes (Missions.defined_type / defined_subtype) at load and after mission updates: 650 live packets, every one with the optional state left at NEW, the subtype written before the type. DLU dropped it and never wrote the states, so the client lost them on every load. - SetMissionTypeState message struct and handler: the state is stored on the player's MissionComponent (byte-sized as the client keeps it, LWOMissionComponent::msgSetMissionTypeState 0x00c90d00). - Saved as live wrote it (226 live charxmls): after <cur>, <ts><type v="Build"><st sub="" val="1"/></type>...</ts>, <ts/> when empty. Read back per <type>; old saves without <ts> load with none. The client's reader (0x00d171c0) reads v from <ts> rather than from each <type>, so it files every state under one type; the server keeps the types apart. - Mission::SetMissionTypeState (on accept) records NEW for the mission's type instead of doing nothing. Check in game: accept missions of a few kinds (a location mission, a battle achievement), open the passport/journal, then log out and back in and change zones: the journal tabs keep their "new" markers as before, nothing errors, and the character still loads. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
282 lines
9.5 KiB
C++
282 lines
9.5 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 "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;
|
|
|
|
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;
|
|
}
|
|
}
|