Files
DarkflameServer/dGame/dGameMessages/PropertyMessages.cpp
Aaron Kimbrell b9d6b739d5 fix(wire): PlaceModelResponse writes the model's rotation
The client's PlaceModelResponse::Deserialize (0x00dc0170) reads the rotation
as an optional w, x, y, z quaternion, but DLU wrote the 4-byte response
after the rotation flag. With any rotation other than identity the client
ran out of data. A live capture of a model turned 90 degrees shows the
server echoing the rotation the client placed it with. Bytes only change
when the rotation is not identity; a golden test pins the new layout.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-28 22:30:58 -05:00

749 lines
30 KiB
C++

#include "PropertyMessages.h"
#include "Amf3.h"
#include "AMFDeserialize.h"
#include "AmfSerialize.h"
#include "BitStreamUtils.h"
#include "CDClientManager.h"
#include "CDPropertyEntranceComponentTable.h"
#include "CDPropertyTemplateTable.h"
#include "Character.h"
#include "ControlBehaviors.h"
#include "Database.h"
#include "dZoneManager.h"
#include "eReplicaComponentType.h"
#include "Entity.h"
#include "EntityManager.h"
#include "Game.h"
#include "GeneralUtils.h"
#include "HotPropertySlots.h"
#include "Logger.h"
#include "MultiZoneEntranceComponent.h"
#include "PlayerManager.h"
#include "PlayerReports.h"
#include "PropertyEntranceComponent.h"
#include "PropertyManagementComponent.h"
#include "PropertyVendorComponent.h"
#include "User.h"
#include "UserManager.h"
#include <chrono>
namespace {
using HotPropertyInfo = GameMessages::NewsSendHotPropertiesInfoToClient::HotPropertyInfo;
// The news screen's slots and the property worlds they can show properties of, from the CDClient (see HotPropertySlots.h)
struct NewsWorlds {
std::vector<HotPropertySlots::Slot> slots;
std::vector<uint32_t> worlds;
};
const NewsWorlds& GetNewsWorlds() {
static const auto news = [] {
std::vector<HotPropertySlots::TemplateRow> templates;
for (const auto& row : CDClientManager::GetTable<CDPropertyTemplateTable>()->GetEntries()) templates.push_back({ row.id, row.mapID, row.spawnName });
std::vector<HotPropertySlots::EntranceRow> entrances;
for (const auto& row : CDClientManager::GetTable<CDPropertyEntranceComponentTable>()->GetEntries()) entrances.push_back({ row.mapID, row.propertyName });
return NewsWorlds{ HotPropertySlots::ResolveSlots(templates, entrances), HotPropertySlots::PropertyWorlds(templates, entrances) };
}();
return news;
}
HotPropertyInfo ToHotProperty(const IProperty::Info& info, const std::string& ownerName, uint32_t templateId) {
HotPropertyInfo hot;
hot.propertyId = info.id;
hot.ownerId = info.ownerId;
hot.ownerName = GeneralUtils::UTF8ToUTF16(ownerName);
hot.reputation = info.reputation;
hot.templateId = static_cast<int32_t>(templateId);
hot.name = GeneralUtils::UTF8ToUTF16(info.name); // empty: the client shows the template's name
hot.description = GeneralUtils::UTF8ToUTF16(info.description);
hot.performanceCost = info.performanceCost;
hot.lastPublished = info.lastUpdatedTime;
hot.cloneId = info.cloneId;
return hot;
}
// What each slot shows, as chosen on the dashboard (featured_properties, featured_properties_settings), resolved by
// HotPropertySlots::Resolve so no property is shown twice. Every entry carries its slot's template id, whatever
// world the property is on: that is what puts it in the slot. Empty slots are left out.
std::vector<HotPropertyInfo> LoadHotProperties() {
const auto& news = GetNewsWorlds();
const bool fullAuto = Database::Get()->GetFeaturedPropertiesSettings().fullAuto;
std::map<uint32_t, IFeaturedProperties::FeaturedSlot> chosen;
for (const auto& row : Database::Get()->GetFeaturedPropertySlots()) chosen[row.templateId] = row;
std::vector<HotPropertySlots::Choice> choices;
for (const auto& slot : news.slots) {
const auto it = chosen.find(slot.templateId);
HotPropertySlots::Choice choice{ HotPropertySlots::eMode::AUTO, slot.mapId };
if (it != chosen.end()) choice = { HotPropertySlots::ModeFromInt(it->second.mode), HotPropertySlots::Location(it->second.zoneId, slot, news.worlds), it->second.propertyId };
choices.push_back(choice);
}
// The candidates, with what to send for each
std::vector<HotPropertySlots::Candidate> candidates;
std::map<LWOOBJID, std::pair<IProperty::Info, std::string>> properties;
for (const auto world : HotPropertySlots::CandidateWorlds(choices, fullAuto)) {
IProperty::ShowcaseQuery query;
query.zoneId = world;
query.sort = IProperty::ShowcaseSort::REPUTATION;
query.length = HotPropertySlots::CANDIDATES_PER_WORLD;
for (const auto& entry : Database::Get()->GetShowcaseProperties(query).entries) {
candidates.push_back({ entry.info.id, entry.info.zoneId, entry.info.reputation });
properties.emplace(entry.info.id, std::make_pair(entry.info, entry.ownerName));
}
}
for (const auto& choice : choices) {
if (fullAuto || choice.mode != HotPropertySlots::eMode::PICKED || properties.contains(choice.propertyId)) continue;
const auto info = Database::Get()->GetPropertyInfo(choice.propertyId);
const auto owner = info ? Database::Get()->GetCharacterInfo(info->ownerId) : std::nullopt;
if (!info || !owner || !HotPropertySlots::Featurable(info->modApproved, info->privacyOption, info->zoneId, choice.mapId)) continue;
candidates.push_back({ info->id, info->zoneId, info->reputation });
properties.emplace(info->id, std::make_pair(*info, owner->name));
}
std::vector<HotPropertyInfo> hot;
const auto showing = HotPropertySlots::Resolve(choices, fullAuto, candidates);
for (size_t i = 0; i < showing.size(); i++) {
if (!showing[i].propertyId) continue;
const auto& [info, ownerName] = properties.at(*showing[i].propertyId);
hot.push_back(ToHotProperty(info, ownerName, news.slots[i].templateId));
}
return hot;
}
}
namespace GameMessages {
void DownloadPropertyData::Serialize(RakNet::BitStream& bitStream) const {
bitStream.Write(propertyId);
bitStream.Write(templateId);
bitStream.Write(mapId);
bitStream.Write(vendorMapId);
bitStream.Write(cloneId);
BitStreamUtils::WriteLengthPrefixed<uint32_t>(bitStream, name);
BitStreamUtils::WriteLengthPrefixed<uint32_t>(bitStream, description);
BitStreamUtils::WriteLengthPrefixed<uint32_t>(bitStream, ownerName);
bitStream.Write(ownerId);
bitStream.Write(propertyType);
bitStream.Write(zoneCode);
bitStream.Write(rent);
bitStream.Write(rentalPeriod);
bitStream.Write(expirationDate);
bitStream.Write(rentAmount);
bitStream.Write(reputation);
BitStreamUtils::WriteLengthPrefixed<uint32_t>(bitStream, spawnName);
BitStreamUtils::WriteLengthPrefixed<uint32_t>(bitStream, templateName);
BitStreamUtils::WriteLengthPrefixed<uint32_t>(bitStream, templateDescription);
bitStream.Write(rentDuration);
bitStream.Write(votes);
bitStream.Write(durationType);
bitStream.Write(renew);
bitStream.Write(ownerAccountID);
bitStream.Write(moderationStatus);
BitStreamUtils::WriteLengthPrefixed<uint32_t>(bitStream, rejectionReason);
bitStream.Write(lastLogoutTime);
bitStream.Write(dayOfMonthPlaqueWasBought);
bitStream.Write(repAchievementReq);
bitStream.Write(zonePosition.x);
bitStream.Write(zonePosition.y);
bitStream.Write(zonePosition.z);
bitStream.Write(maxBuildHeight);
bitStream.Write(rentalDate);
bitStream.Write(accessType);
bitStream.Write<uint32_t>(pathPositions.size());
for (const auto& path : pathPositions) {
bitStream.Write(path.x);
bitStream.Write(path.y);
bitStream.Write(path.z);
}
}
bool DownloadPropertyData::Deserialize(RakNet::BitStream& bitStream) {
VALIDATE_READ(bitStream.Read(propertyId));
VALIDATE_READ(bitStream.Read(templateId));
VALIDATE_READ(bitStream.Read(mapId));
VALIDATE_READ(bitStream.Read(vendorMapId));
VALIDATE_READ(bitStream.Read(cloneId));
VALIDATE_READ(BitStreamUtils::ReadLengthPrefixed<uint32_t>(bitStream, name));
VALIDATE_READ(BitStreamUtils::ReadLengthPrefixed<uint32_t>(bitStream, description));
VALIDATE_READ(BitStreamUtils::ReadLengthPrefixed<uint32_t>(bitStream, ownerName));
VALIDATE_READ(bitStream.Read(ownerId));
VALIDATE_READ(bitStream.Read(propertyType));
VALIDATE_READ(bitStream.Read(zoneCode));
VALIDATE_READ(bitStream.Read(rent));
VALIDATE_READ(bitStream.Read(rentalPeriod));
VALIDATE_READ(bitStream.Read(expirationDate));
VALIDATE_READ(bitStream.Read(rentAmount));
VALIDATE_READ(bitStream.Read(reputation));
VALIDATE_READ(BitStreamUtils::ReadLengthPrefixed<uint32_t>(bitStream, spawnName));
VALIDATE_READ(BitStreamUtils::ReadLengthPrefixed<uint32_t>(bitStream, templateName));
VALIDATE_READ(BitStreamUtils::ReadLengthPrefixed<uint32_t>(bitStream, templateDescription));
VALIDATE_READ(bitStream.Read(rentDuration));
VALIDATE_READ(bitStream.Read(votes));
VALIDATE_READ(bitStream.Read(durationType));
VALIDATE_READ(bitStream.Read(renew));
VALIDATE_READ(bitStream.Read(ownerAccountID));
VALIDATE_READ(bitStream.Read(moderationStatus));
VALIDATE_READ(BitStreamUtils::ReadLengthPrefixed<uint32_t>(bitStream, rejectionReason));
VALIDATE_READ(bitStream.Read(lastLogoutTime));
VALIDATE_READ(bitStream.Read(dayOfMonthPlaqueWasBought));
VALIDATE_READ(bitStream.Read(repAchievementReq));
VALIDATE_READ(bitStream.Read(zonePosition.x));
VALIDATE_READ(bitStream.Read(zonePosition.y));
VALIDATE_READ(bitStream.Read(zonePosition.z));
VALIDATE_READ(bitStream.Read(maxBuildHeight));
VALIDATE_READ(bitStream.Read(rentalDate));
VALIDATE_READ(bitStream.Read(accessType));
uint32_t pathCount{};
VALIDATE_READ(bitStream.Read(pathCount));
if (static_cast<uint64_t>(pathCount) * 96 > bitStream.GetNumberOfUnreadBits()) return false;
pathPositions.resize(pathCount);
for (auto& path : pathPositions) {
VALIDATE_READ(bitStream.Read(path.x));
VALIDATE_READ(bitStream.Read(path.y));
VALIDATE_READ(bitStream.Read(path.z));
}
return true;
}
void PropertyRentalResponse::Serialize(RakNet::BitStream& bitStream) const {
bitStream.Write(cloneid);
bitStream.Write(code);
bitStream.Write(propertyID);
bitStream.Write(rentdue);
}
bool PropertyRentalResponse::Deserialize(RakNet::BitStream& bitStream) {
VALIDATE_READ(bitStream.Read(cloneid));
VALIDATE_READ(bitStream.Read(code));
VALIDATE_READ(bitStream.Read(propertyID));
VALIDATE_READ(bitStream.Read(rentdue));
return true;
}
void PropertySelectQuery::Serialize(RakNet::BitStream& bitStream) const {
bitStream.Write(navOffset);
bitStream.Write(thereAreMore);
bitStream.Write(cloneId);
bitStream.Write(hasFeaturedProperty);
bitStream.Write(wasFriends);
bitStream.Write<uint32_t>(properties.size());
for (const auto& property : properties) {
bitStream.Write(property.cloneId);
BitStreamUtils::WriteLengthPrefixed<uint32_t>(bitStream, property.ownerName);
BitStreamUtils::WriteLengthPrefixed<uint32_t>(bitStream, property.name);
BitStreamUtils::WriteLengthPrefixed<uint32_t>(bitStream, property.description);
bitStream.Write(property.reputation);
bitStream.Write(property.isBff);
bitStream.Write(property.isFriend);
bitStream.Write(property.isModApproved);
bitStream.Write(property.isAlt);
bitStream.Write(property.isOwned);
bitStream.Write(property.accessType);
bitStream.Write(property.dateLastPublished);
bitStream.Write(property.performanceCost);
}
}
bool PropertySelectQuery::Deserialize(RakNet::BitStream& bitStream) {
VALIDATE_READ(bitStream.Read(navOffset));
VALIDATE_READ(bitStream.Read(thereAreMore));
VALIDATE_READ(bitStream.Read(cloneId));
VALIDATE_READ(bitStream.Read(hasFeaturedProperty));
VALIDATE_READ(bitStream.Read(wasFriends));
uint32_t count{};
VALIDATE_READ(bitStream.Read(count));
properties.clear();
for (uint32_t i = 0; i < count; i++) {
auto& property = properties.emplace_back();
VALIDATE_READ(bitStream.Read(property.cloneId));
VALIDATE_READ(BitStreamUtils::ReadLengthPrefixed<uint32_t>(bitStream, property.ownerName));
VALIDATE_READ(BitStreamUtils::ReadLengthPrefixed<uint32_t>(bitStream, property.name));
VALIDATE_READ(BitStreamUtils::ReadLengthPrefixed<uint32_t>(bitStream, property.description));
VALIDATE_READ(bitStream.Read(property.reputation));
VALIDATE_READ(bitStream.Read(property.isBff));
VALIDATE_READ(bitStream.Read(property.isFriend));
VALIDATE_READ(bitStream.Read(property.isModApproved));
VALIDATE_READ(bitStream.Read(property.isAlt));
VALIDATE_READ(bitStream.Read(property.isOwned));
VALIDATE_READ(bitStream.Read(property.accessType));
VALIDATE_READ(bitStream.Read(property.dateLastPublished));
VALIDATE_READ(bitStream.Read(property.performanceCost));
}
return true;
}
void GetModelsOnProperty::Serialize(RakNet::BitStream& bitStream) const {
bitStream.Write<uint32_t>(models.size());
for (const auto& [model, item] : models) {
bitStream.Write(model);
bitStream.Write(item);
}
}
bool GetModelsOnProperty::Deserialize(RakNet::BitStream& bitStream) {
uint32_t count{};
VALIDATE_READ(bitStream.Read(count));
if (static_cast<uint64_t>(count) * 128 > bitStream.GetNumberOfUnreadBits()) return false;
models.resize(count);
for (auto& [model, item] : models) {
VALIDATE_READ(bitStream.Read(model));
VALIDATE_READ(bitStream.Read(item));
}
return true;
}
void PlaceModelResponse::Serialize(RakNet::BitStream& bitStream) const {
BitStreamUtils::WriteOptional(bitStream, position, NiPoint3Constant::ZERO);
BitStreamUtils::WriteOptional(bitStream, propertyPlaqueID, LWOOBJID_EMPTY);
BitStreamUtils::WriteOptional(bitStream, response, 0);
BitStreamUtils::WriteOptional(bitStream, rotation, QuatUtils::IDENTITY);
}
bool PlaceModelResponse::Deserialize(RakNet::BitStream& bitStream) {
VALIDATE_READ(BitStreamUtils::ReadOptional(bitStream, position, NiPoint3Constant::ZERO));
VALIDATE_READ(BitStreamUtils::ReadOptional(bitStream, propertyPlaqueID, LWOOBJID_EMPTY));
VALIDATE_READ(BitStreamUtils::ReadOptional(bitStream, response, 0));
VALIDATE_READ(BitStreamUtils::ReadOptional(bitStream, rotation, QuatUtils::IDENTITY));
return true;
}
void HandleUGCEquipPreCreateBasedOnEditMode::Serialize(RakNet::BitStream& bitStream) const {
bitStream.Write(modelCount);
bitStream.Write(modelID);
}
bool HandleUGCEquipPreCreateBasedOnEditMode::Deserialize(RakNet::BitStream& bitStream) {
VALIDATE_READ(bitStream.Read(modelCount));
VALIDATE_READ(bitStream.Read(modelID));
return true;
}
void HandleUGCEquipPostDeleteBasedOnEditMode::Serialize(RakNet::BitStream& bitStream) const {
bitStream.Write(invItem);
BitStreamUtils::WriteOptional(bitStream, itemsTotal, 0);
}
bool HandleUGCEquipPostDeleteBasedOnEditMode::Deserialize(RakNet::BitStream& bitStream) {
VALIDATE_READ(bitStream.Read(invItem));
VALIDATE_READ(BitStreamUtils::ReadOptional(bitStream, itemsTotal, 0));
return true;
}
void SetPropertyAccess::Serialize(RakNet::BitStream& bitStream) const {
BitStreamUtils::WriteOptional<uint8_t>(bitStream, accessType, 0);
BitStreamUtils::WriteOptional(bitStream, renew, 0);
}
bool SetPropertyAccess::Deserialize(RakNet::BitStream& bitStream) {
VALIDATE_READ(BitStreamUtils::ReadOptional<uint8_t>(bitStream, accessType, 0));
VALIDATE_READ(BitStreamUtils::ReadOptional(bitStream, renew, 0));
return true;
}
void SetPropertyAccess::Handle(Entity& entity, const SystemAddress& sysAddr) {
LOG("Set privacy option to: %i", accessType);
if (PropertyManagementComponent::Instance() == nullptr) return;
PropertyManagementComponent::Instance()->SetPrivacyOption(static_cast<PropertyPrivacyOption>(accessType));
}
void UpdatePropertyOrModelForFilterCheck::Serialize(RakNet::BitStream& bitStream) const {
bitStream.Write(isProperty);
bitStream.Write(ugcId);
bitStream.Write(playerId);
bitStream.Write(worldId);
BitStreamUtils::WriteLengthPrefixed<uint32_t>(bitStream, newDescription);
BitStreamUtils::WriteLengthPrefixed<uint32_t>(bitStream, newName);
}
bool UpdatePropertyOrModelForFilterCheck::Deserialize(RakNet::BitStream& bitStream) {
VALIDATE_READ(bitStream.Read(isProperty));
VALIDATE_READ(bitStream.Read(ugcId));
VALIDATE_READ(bitStream.Read(playerId));
VALIDATE_READ(bitStream.Read(worldId));
VALIDATE_READ(BitStreamUtils::ReadLengthPrefixed<uint32_t>(bitStream, newDescription));
VALIDATE_READ(BitStreamUtils::ReadLengthPrefixed<uint32_t>(bitStream, newName));
return true;
}
void UpdatePropertyOrModelForFilterCheck::Handle(Entity& entity, const SystemAddress& sysAddr) {
PropertyManagementComponent::Instance()->UpdatePropertyDetails(GeneralUtils::UTF16ToWTF8(newName), GeneralUtils::UTF16ToWTF8(newDescription));
}
void QueryPropertyData::Handle(Entity& entity, const SystemAddress& sysAddr) {
LOG("Entity (%i) requesting data", entity.GetLOT());
auto* propertyVendorComponent = static_cast<PropertyVendorComponent*>(entity.GetComponent(eReplicaComponentType::PROPERTY_VENDOR));
if (propertyVendorComponent != nullptr) {
propertyVendorComponent->OnQueryPropertyData(&entity, sysAddr);
}
auto* propertyManagerComponent = static_cast<PropertyManagementComponent*>(entity.GetComponent(eReplicaComponentType::PROPERTY_MANAGEMENT));
if (propertyManagerComponent != nullptr) {
propertyManagerComponent->OnQueryPropertyData(&entity, sysAddr);
}
}
void PropertyEditorBegin::Serialize(RakNet::BitStream& bitStream) const {
BitStreamUtils::WriteOptional(bitStream, distanceType, 0);
BitStreamUtils::WriteOptional(bitStream, propertyObjectID, LWOOBJID_EMPTY);
BitStreamUtils::WriteOptional(bitStream, startMode, 1);
bitStream.Write(startPaused);
}
bool PropertyEditorBegin::Deserialize(RakNet::BitStream& bitStream) {
VALIDATE_READ(BitStreamUtils::ReadOptional(bitStream, distanceType, 0));
VALIDATE_READ(BitStreamUtils::ReadOptional(bitStream, propertyObjectID, LWOOBJID_EMPTY));
VALIDATE_READ(BitStreamUtils::ReadOptional(bitStream, startMode, 1));
VALIDATE_READ(bitStream.Read(startPaused));
return true;
}
void PropertyEditorBegin::Handle(Entity& entity, const SystemAddress& sysAddr) {
PropertyManagementComponent::Instance()->OnStartBuilding();
Game::zoneManager->GetZoneControlObject()->OnZonePropertyEditBegin();
}
void PropertyEditorEnd::Handle(Entity& entity, const SystemAddress& sysAddr) {
PropertyManagementComponent::Instance()->OnFinishBuilding();
Game::zoneManager->GetZoneControlObject()->OnZonePropertyEditEnd();
}
void PropertyContentsFromClient::Serialize(RakNet::BitStream& bitStream) const {
bitStream.Write(queryDB);
}
bool PropertyContentsFromClient::Deserialize(RakNet::BitStream& bitStream) {
VALIDATE_READ(bitStream.Read(queryDB));
return true;
}
void PropertyContentsFromClient::Handle(Entity& entity, const SystemAddress& sysAddr) {
User* user = UserManager::Instance()->GetUser(sysAddr);
Entity* player = Game::entityManager->GetEntity(user->GetLoggedInChar());
const auto& models = PropertyManagementComponent::Instance()->GetModels();
GetModelsOnProperty modelsOnProperty;
modelsOnProperty.target = player->GetObjectID();
modelsOnProperty.models = { models.begin(), models.end() };
LOG("Sending property models to (%llu) (%d)", modelsOnProperty.target, true);
modelsOnProperty.Send(UNASSIGNED_SYSTEM_ADDRESS);
}
void ZonePropertyModelEquipped::Serialize(RakNet::BitStream& bitStream) const {
BitStreamUtils::WriteOptional(bitStream, playerID, LWOOBJID_EMPTY);
BitStreamUtils::WriteOptional(bitStream, propertyID, LWOOBJID_EMPTY);
}
bool ZonePropertyModelEquipped::Deserialize(RakNet::BitStream& bitStream) {
VALIDATE_READ(BitStreamUtils::ReadOptional(bitStream, playerID, LWOOBJID_EMPTY));
VALIDATE_READ(BitStreamUtils::ReadOptional(bitStream, propertyID, LWOOBJID_EMPTY));
return true;
}
void ZonePropertyModelEquipped::Handle(Entity& entity, const SystemAddress& sysAddr) {
Game::zoneManager->GetZoneControlObject()->OnZonePropertyModelEquipped();
}
void ZonePropertyModelRotated::Serialize(RakNet::BitStream& bitStream) const {
BitStreamUtils::WriteOptional(bitStream, playerID, LWOOBJID_EMPTY);
BitStreamUtils::WriteOptional(bitStream, propertyID, LWOOBJID_EMPTY);
}
bool ZonePropertyModelRotated::Deserialize(RakNet::BitStream& bitStream) {
VALIDATE_READ(BitStreamUtils::ReadOptional(bitStream, playerID, LWOOBJID_EMPTY));
VALIDATE_READ(BitStreamUtils::ReadOptional(bitStream, propertyID, LWOOBJID_EMPTY));
return true;
}
void ZonePropertyModelRotated::Handle(Entity& entity, const SystemAddress& sysAddr) {
User* usr = UserManager::Instance()->GetUser(sysAddr);
Game::entityManager->GetZoneControlEntity()->OnZonePropertyModelRotated(usr->GetLastUsedChar()->GetEntity());
}
void PlacePropertyModel::Serialize(RakNet::BitStream& bitStream) const {
bitStream.Write(modelID);
}
bool PlacePropertyModel::Deserialize(RakNet::BitStream& bitStream) {
VALIDATE_READ(bitStream.Read(modelID));
return true;
}
void PlacePropertyModel::Handle(Entity& entity, const SystemAddress& sysAddr) {
PropertyManagementComponent::Instance()->UpdateModelPosition(modelID, NiPoint3Constant::ZERO, QuatUtils::IDENTITY);
}
void UpdateModelFromClient::Serialize(RakNet::BitStream& bitStream) const {
bitStream.Write(modelID);
bitStream.Write(position);
BitStreamUtils::WriteOptional(bitStream, rotation, QuatUtils::IDENTITY);
}
bool UpdateModelFromClient::Deserialize(RakNet::BitStream& bitStream) {
VALIDATE_READ(bitStream.Read(modelID));
VALIDATE_READ(bitStream.Read(position));
VALIDATE_READ(BitStreamUtils::ReadOptional(bitStream, rotation, QuatUtils::IDENTITY));
return true;
}
void UpdateModelFromClient::Handle(Entity& entity, const SystemAddress& sysAddr) {
PropertyManagementComponent::Instance()->UpdateModelPosition(modelID, position, rotation);
}
void DeleteModelFromClient::Serialize(RakNet::BitStream& bitStream) const {
BitStreamUtils::WriteOptional(bitStream, modelID, LWOOBJID_EMPTY);
BitStreamUtils::WriteOptional(bitStream, reason, 0);
}
bool DeleteModelFromClient::Deserialize(RakNet::BitStream& bitStream) {
VALIDATE_READ(BitStreamUtils::ReadOptional(bitStream, modelID, LWOOBJID_EMPTY));
VALIDATE_READ(BitStreamUtils::ReadOptional(bitStream, reason, 0));
return true;
}
void DeleteModelFromClient::Handle(Entity& entity, const SystemAddress& sysAddr) {
PropertyManagementComponent::Instance()->DeleteModel(modelID, reason);
}
void PropertyEntranceSync::Serialize(RakNet::BitStream& bitStream) const {
bitStream.Write(includeNullAddress);
bitStream.Write(includeNullDescription);
bitStream.Write(playersOwn);
bitStream.Write(updateUI);
bitStream.Write(numResults);
bitStream.Write(reputationTime);
bitStream.Write(sortMethod);
bitStream.Write(startIndex);
BitStreamUtils::WriteLengthPrefixed<uint32_t>(bitStream, filterText);
}
bool PropertyEntranceSync::Deserialize(RakNet::BitStream& bitStream) {
VALIDATE_READ(bitStream.Read(includeNullAddress));
VALIDATE_READ(bitStream.Read(includeNullDescription));
VALIDATE_READ(bitStream.Read(playersOwn));
VALIDATE_READ(bitStream.Read(updateUI));
VALIDATE_READ(bitStream.Read(numResults));
VALIDATE_READ(bitStream.Read(reputationTime));
VALIDATE_READ(bitStream.Read(sortMethod));
VALIDATE_READ(bitStream.Read(startIndex));
VALIDATE_READ(BitStreamUtils::ReadLengthPrefixed<uint32_t>(bitStream, filterText));
return true;
}
void PropertyEntranceSync::Handle(Entity& entity, const SystemAddress& sysAddr) {
auto* player = PlayerManager::GetPlayer(sysAddr);
auto* entranceComponent = entity.GetComponent<PropertyEntranceComponent>();
if (entranceComponent == nullptr) return;
entranceComponent->OnPropertyEntranceSync(player,
includeNullAddress,
includeNullDescription,
playersOwn,
updateUI,
numResults,
reputationTime,
sortMethod,
startIndex,
filterText,
sysAddr
);
}
void EnterProperty1::Serialize(RakNet::BitStream& bitStream) const {
bitStream.Write(index);
bitStream.Write(returnToZone);
}
bool EnterProperty1::Deserialize(RakNet::BitStream& bitStream) {
VALIDATE_READ(bitStream.Read(index));
VALIDATE_READ(bitStream.Read(returnToZone));
return true;
}
void EnterProperty1::Handle(Entity& entity, const SystemAddress& sysAddr) {
auto* player = PlayerManager::GetPlayer(sysAddr);
auto* entranceComponent = entity.GetComponent<PropertyEntranceComponent>();
if (entranceComponent != nullptr) {
entranceComponent->OnEnterProperty(player, static_cast<uint32_t>(index), returnToZone, sysAddr);
return;
}
auto multiZoneEntranceComponent = entity.GetComponent<MultiZoneEntranceComponent>();
if (multiZoneEntranceComponent != nullptr) {
multiZoneEntranceComponent->OnSelectWorld(player, static_cast<uint32_t>(index));
}
}
void UpdatePropertyPerformanceCost::Serialize(RakNet::BitStream& bitStream) const {
BitStreamUtils::WriteOptional(bitStream, performanceCost, 0.0f);
}
bool UpdatePropertyPerformanceCost::Deserialize(RakNet::BitStream& bitStream) {
VALIDATE_READ(BitStreamUtils::ReadOptional(bitStream, performanceCost, 0.0f));
return true;
}
void UpdatePropertyPerformanceCost::Handle(Entity& entity, const SystemAddress& sysAddr) {
if (performanceCost == 0.0f) return;
auto zone = Game::zoneManager->GetZone();
if (!zone) {
LOG("If you see this message, something is very wrong.");
return;
}
const auto* const propertyManagementComponent = entity.GetComponent<PropertyManagementComponent>();
const auto* const ownerEntity = propertyManagementComponent ? propertyManagementComponent->GetOwner() : nullptr;
const auto* const character = ownerEntity ? ownerEntity->GetCharacter() : nullptr;
const auto& zoneID = zone->GetZoneID();
if (character && character->GetPropertyCloneID() == zoneID.GetCloneID()) {
Database::Get()->UpdatePerformanceCost(zoneID, performanceCost);
}
}
void ReportOffensiveModel::Serialize(RakNet::BitStream& bitStream) const {
BitStreamUtils::WriteLengthPrefixed<uint32_t>(bitStream, description);
bitStream.Write(offensiveObjectID);
}
bool ReportOffensiveModel::Deserialize(RakNet::BitStream& bitStream) {
VALIDATE_READ(BitStreamUtils::ReadLengthPrefixed<uint32_t>(bitStream, description));
VALIDATE_READ(bitStream.Read(offensiveObjectID));
return true;
}
void ReportOffensiveModel::Handle(Entity& entity, const SystemAddress& sysAddr) {
PlayerReports::ReportOffensiveModel(&entity, description, offensiveObjectID);
}
void ReportOffensiveProperty::Serialize(RakNet::BitStream& bitStream) const {
BitStreamUtils::WriteLengthPrefixed<uint32_t>(bitStream, description);
bitStream.Write(propertyPlaqueObjectID);
}
bool ReportOffensiveProperty::Deserialize(RakNet::BitStream& bitStream) {
VALIDATE_READ(BitStreamUtils::ReadLengthPrefixed<uint32_t>(bitStream, description));
VALIDATE_READ(bitStream.Read(propertyPlaqueObjectID));
return true;
}
void ReportOffensiveProperty::Handle(Entity& entity, const SystemAddress& sysAddr) {
PlayerReports::ReportOffensiveProperty(&entity, description, propertyPlaqueObjectID);
}
ControlBehaviors::ControlBehaviors() : NetGameMsg(MessageType::Game::CONTROL_BEHAVIORS) {}
ControlBehaviors::~ControlBehaviors() = default;
void ControlBehaviors::Serialize(RakNet::BitStream& bitStream) const {
AMFArrayValue empty;
bitStream.Write<AMFBaseValue&>(args ? *args : empty);
BitStreamUtils::WriteLengthPrefixed<uint32_t>(bitStream, command);
}
bool ControlBehaviors::Deserialize(RakNet::BitStream& bitStream) {
AMFDeserialize reader;
try {
auto deserializedData = reader.Read(bitStream);
if (!deserializedData || deserializedData->GetValueType() != eAmf::Array) {
LOG("Failed to deserialize AMF data for control behaviors command: not an array");
return false;
}
args.reset(static_cast<AMFArrayValue*>(deserializedData.release()));
} catch (...) {
LOG("Failed to deserialize AMF data for control behaviors command");
return false;
}
VALIDATE_READ(BitStreamUtils::ReadLengthPrefixed<uint32_t>(bitStream, command)); // Prevent DoS via unbounded command buffer
return true;
}
void ControlBehaviors::Handle(Entity& entity, const SystemAddress& sysAddr) {
auto* const owner = PropertyManagementComponent::Instance()->GetOwner();
if (!owner) return;
::ControlBehaviors::Instance().ProcessCommand(&entity, *args, command, owner);
}
void GetHotPropertyData::Handle(Entity& entity, const SystemAddress& sysAddr) {
// The screen asks every time it opens; the answer is the same for everyone, so it is kept for a little while
static std::vector<HotPropertyInfo> cached;
static std::chrono::steady_clock::time_point loadedAt{};
static bool loaded = false;
const auto now = std::chrono::steady_clock::now();
if (!loaded || now - loadedAt > std::chrono::seconds(30)) {
try {
cached = LoadHotProperties();
} catch (const std::exception& ex) {
LOG("Failed to load the news screen's top properties: %s", ex.what());
cached.clear();
}
loadedAt = now;
loaded = true;
}
NewsSendHotPropertiesInfoToClient message;
message.target = entity.GetObjectID();
for (const auto index : HotPropertySlots::NewsOrder(cached.size())) message.properties.push_back(cached[index]);
message.Send(sysAddr);
}
void NewsSendHotPropertiesInfoToClient::Serialize(RakNet::BitStream& bitStream) const {
bitStream.Write<uint32_t>(properties.size());
for (const auto& info : properties) {
bitStream.Write(info.propertyId);
bitStream.Write(info.ownerId);
BitStreamUtils::WriteLengthPrefixed<uint32_t>(bitStream, info.ownerName);
bitStream.Write(info.reputation);
bitStream.Write(info.templateId);
BitStreamUtils::WriteLengthPrefixed<uint32_t>(bitStream, info.name);
BitStreamUtils::WriteLengthPrefixed<uint32_t>(bitStream, info.description);
bitStream.Write(info.performanceCost);
bitStream.Write(info.lastPublished);
bitStream.Write(info.cloneId);
}
}
bool NewsSendHotPropertiesInfoToClient::Deserialize(RakNet::BitStream& bitStream) {
uint32_t count{};
VALIDATE_READ(bitStream.Read(count));
properties.clear();
for (uint32_t i = 0; i < count; i++) {
auto& info = properties.emplace_back();
VALIDATE_READ(bitStream.Read(info.propertyId));
VALIDATE_READ(bitStream.Read(info.ownerId));
VALIDATE_READ(BitStreamUtils::ReadLengthPrefixed<uint32_t>(bitStream, info.ownerName));
VALIDATE_READ(bitStream.Read(info.reputation));
VALIDATE_READ(bitStream.Read(info.templateId));
VALIDATE_READ(BitStreamUtils::ReadLengthPrefixed<uint32_t>(bitStream, info.name));
VALIDATE_READ(BitStreamUtils::ReadLengthPrefixed<uint32_t>(bitStream, info.description));
VALIDATE_READ(bitStream.Read(info.performanceCost));
VALIDATE_READ(bitStream.Read(info.lastPublished));
VALIDATE_READ(bitStream.Read(info.cloneId));
}
return true;
}
void PlayBehaviorSound::Serialize(RakNet::BitStream& stream) const {
stream.Write(soundID != -1);
if (soundID != -1) stream.Write(soundID);
}
bool PlayBehaviorSound::Deserialize(RakNet::BitStream& stream) {
return BitStreamUtils::ReadOptional(stream, soundID, -1);
}
}