Files
DarkflameServer/dGame/dComponents/CollectibleComponent.cpp
Aaron Kimbrell 8dd279cb45 fix: write a collectible's id on construction only
LWOCollectibleComponent::Deserialize (0x00c45ec0) reads the 16-bit id
on construction and nothing on an update. DLU wrote the id on every
serialization, so the client read 16 stray bits as the flag bits of the
components after it. The replica decoder follows.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-10 14:26:46 -06:00

81 lines
3.2 KiB
C++

#include "CollectibleComponent.h"
#include "MissionComponent.h"
#include "dServer.h"
#include "Amf3.h"
#include "CDClientManager.h"
#include "CDCollectibleComponentTable.h"
#include "CDMissionsTable.h"
#include "eMissionState.h"
#include "Entity.h"
#include "ObjectLoadState.h"
#include "DestroyableComponent.h"
CollectibleComponent::CollectibleComponent(Entity* parentEntity, const int32_t componentID, const int32_t collectibleId) :
Component(parentEntity, componentID), m_CollectibleId(collectibleId) {
RegisterMsg(&CollectibleComponent::MsgGetObjectReportInfo);
}
// The CollectibleComponent row
void CollectibleComponent::LoadDataFromTemplate() {
const auto* const row = CDClientManager::GetTable<CDCollectibleComponentTable>()->GetByID(m_ComponentID);
if (row) m_RequirementMission = row->requirementMission;
}
bool CollectibleComponent::CountsFor(const Entity& player) const {
if (m_RequirementMission <= 0) return true;
bool found = false;
const auto& mission = CDClientManager::GetTable<CDMissionsTable>()->GetByMissionID(m_RequirementMission, found);
// Not a mission (rows with 66666666), or an achievement: nothing to accept first
if (!found || !mission.isMission) return true;
const auto* const missionComponent = player.GetComponent<MissionComponent>();
if (!missionComponent) return false;
switch (missionComponent->GetMissionState(m_RequirementMission)) {
case eMissionState::ACTIVE:
case eMissionState::READY_TO_COMPLETE:
case eMissionState::COMPLETE_ACTIVE:
case eMissionState::COMPLETE_READY_TO_COMPLETE:
return true;
default:
return false;
}
}
// LWOCollectibleComponent::Deserialize (0x00c45ec0) reads the id on construction only
void CollectibleComponent::Serialize(RakNet::BitStream& outBitStream, bool isConstruction) {
if (isConstruction) outBitStream.Write(GetCollectibleId());
}
bool CollectibleComponent::MsgGetObjectReportInfo(GameMessages::GetObjectReportInfo& reportMsg) {
auto& cmptType = reportMsg.info->PushDebug("Collectible");
auto collectibleID = static_cast<uint32_t>(m_CollectibleId) + static_cast<uint32_t>(Game::server->GetZoneID() << 8);
cmptType.PushDebug<AMFIntValue>("Component ID") = GetComponentID();
cmptType.PushDebug<AMFIntValue>("Collectible ID") = GetCollectibleId();
cmptType.PushDebug<AMFIntValue>("Requirement mission") = m_RequirementMission;
cmptType.PushDebug<AMFIntValue>("Mission Tracking ID (for save data)") = collectibleID;
auto* localCharEntity = Game::entityManager->GetEntity(reportMsg.clientID);
bool collected = false;
if (localCharEntity) {
auto* missionComponent = localCharEntity->GetComponent<MissionComponent>();
if (m_CollectibleId != 0) {
collected = missionComponent->HasCollectible(collectibleID);
}
}
cmptType.PushDebug<AMFBoolValue>("Has been collected") = collected;
return true;
}
void CollectibleComponent::Allocator(Entity& entity, ObjectLoadState& state) {
if (state.GetComponentID(eReplicaComponentType::COLLECTIBLE) > 0) {
entity.AddComponent<CollectibleComponent>(state.GetComponentID(eReplicaComponentType::COLLECTIBLE), entity.GetVarAs<int32_t>(u"collectible_id"));
// The client makes a collectible's destroyable right after it when there is none
if (!entity.HasComponent(eReplicaComponentType::DESTROYABLE)) entity.AddComponent<DestroyableComponent>(-1);
}
}