mirror of
https://github.com/DarkflameUniverse/DarkflameServer.git
synced 2026-10-02 10:53:44 +00:00
Converts the property messages to NetGameMsgs in PropertyMessages.{h,cpp}.
Sent: OpenPropertyVendor, OpenPropertyManagement, DownloadPropertyData
(replaces PropertyDataMessage, now with the client's PropertyData field
names), PropertyRentalResponse, PropertyEntranceBegin,
PropertySelectQuery (replaces PropertySelectQueryProperty with the
client's PropertyInfo), GetModelsOnProperty, PlaceModelResponse and
HandleUGCEquipPre/PostDeleteBasedOnEditMode. Received, registered in
GameMessageHandler's map: SetPropertyAccess,
UpdatePropertyOrModelForFilterCheck, QueryPropertyData,
PropertyEditorBegin/End, PropertyContentsFromClient,
ZonePropertyModelEquipped/Rotated, PlacePropertyModel,
UpdateModelFromClient, DeleteModelFromClient, ControlBehaviors,
PropertyEntranceSync, EnterProperty1, UpdatePropertyPerformanceCost,
ReportOffensiveModel/Property and GetHotPropertyData. The news screen's
NewsSendHotPropertiesInfoToClient moves here with the top properties
lookup; the dashboard's player reports now take the decoded text.
Handlers keep their logic and hand the work to PropertyManagementComponent,
PropertyVendorComponent, PropertyEntranceComponent and
MultiZoneEntranceComponent as before. Messages whose payload DLU ignored
(PropertyEditorBegin, PropertyContentsFromClient, ZonePropertyModel*)
now read it with the client's layout. The never-called
SendZonePropertyModelEquipped (which wrote no default flags) is gone.
No wire change and no change in recipients. Verified byte for byte
against a frozen verbatim copy of the old functions, PropertyDataMessage
and PropertySelectQueryProperty over an input grid (to one client and
broadcast), received messages compared with the old handlers' read
sequences with every truncated payload rejected, hand computed golden
bytes, round trips and a mutation check. Layouts confirmed against the
1.10.64 client in Ghidra. Known wire bug kept as is: PlaceModelResponse
writes response where the client reads a rotation quaternion
(PlaceModelResponse::Deserialize 0x00dc0170).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
106 lines
4.3 KiB
C++
106 lines
4.3 KiB
C++
#include "PlayerReports.h"
|
|
|
|
#include <ctime>
|
|
|
|
#include "Database.h"
|
|
#include "DashboardNotify.h"
|
|
#include "Entity.h"
|
|
#include "EntityManager.h"
|
|
#include "Character.h"
|
|
#include "User.h"
|
|
#include "Game.h"
|
|
#include "dServer.h"
|
|
#include "dZoneManager.h"
|
|
#include "Logger.h"
|
|
#include "GeneralUtils.h"
|
|
#include "PetComponent.h"
|
|
#include "PropertyManagementComponent.h"
|
|
#include "ePlayerReportKind.h"
|
|
#include "magic_enum.hpp"
|
|
|
|
namespace {
|
|
constexpr uint32_t MAX_BODY = 2000;
|
|
|
|
// Who sent it and where they are
|
|
IModeration::PlayerReport NewReport(Entity* reporter, ePlayerReportKind kind, const std::string& body) {
|
|
IModeration::PlayerReport report;
|
|
report.createdAt = static_cast<int64_t>(std::time(nullptr));
|
|
report.kind = std::string(magic_enum::enum_name(kind));
|
|
report.body = body.substr(0, MAX_BODY);
|
|
if (const auto* character = reporter ? reporter->GetCharacter() : nullptr) {
|
|
report.reporterId = character->GetID();
|
|
if (const auto* user = character->GetParentUser()) report.reporterAccountId = user->GetAccountID();
|
|
}
|
|
report.zoneId = Game::server->GetZoneID();
|
|
report.instanceId = Game::server->GetInstanceID();
|
|
report.cloneId = Game::zoneManager->GetZoneID().GetCloneID();
|
|
return report;
|
|
}
|
|
|
|
// The player a report is about, when the world knows
|
|
void SetTarget(IModeration::PlayerReport& report, LWOOBJID characterId) {
|
|
const auto info = characterId ? Database::Get()->GetCharacterInfo(characterId) : std::nullopt;
|
|
if (!info) return;
|
|
report.targetCharacterId = info->id;
|
|
report.targetAccountId = info->accountId;
|
|
}
|
|
|
|
PlayerReports::RateLimit g_RateLimit;
|
|
|
|
void Save(const IModeration::PlayerReport& report) {
|
|
// By account (a player can't get more by switching characters), or by character when the account is unknown
|
|
const uint64_t reporter = report.reporterAccountId != 0 ? report.reporterAccountId : static_cast<uint64_t>(report.reporterId) | (1ULL << 63);
|
|
if (!g_RateLimit.Allow(reporter, report.createdAt)) {
|
|
LOG("Dropped a player report (%s) from account %u / character %llu: over the report limit", report.kind.c_str(), report.reporterAccountId, report.reporterId);
|
|
return;
|
|
}
|
|
const auto id = Database::Get()->InsertPlayerReport(report);
|
|
LOG("Player report %llu (%s) from %llu about %llu", id, report.kind.c_str(), report.reporterId, report.targetCharacterId);
|
|
DashboardNotify::Changed("player_reports");
|
|
}
|
|
}
|
|
|
|
namespace PlayerReports {
|
|
void ReportPlayer(Entity* reporter, LWOOBJID reportedId, const std::string& body) {
|
|
auto report = NewReport(reporter, ePlayerReportKind::PLAYER, body);
|
|
report.objectId = reportedId;
|
|
// The client sends the player's object ID; when they're in this world, their character says which one it is
|
|
const auto* reported = Game::entityManager->GetEntity(reportedId);
|
|
const auto* character = reported ? reported->GetCharacter() : nullptr;
|
|
SetTarget(report, character ? character->GetID() : reportedId);
|
|
Save(report);
|
|
}
|
|
|
|
void ReportOffensiveModel(Entity* reporter, const std::u16string& text, LWOOBJID objectId) {
|
|
if (text.size() > MAX_BODY) return;
|
|
const auto description = GeneralUtils::UTF16ToWTF8(text);
|
|
auto report = NewReport(reporter, ePlayerReportKind::MODEL, description);
|
|
report.objectId = objectId;
|
|
auto* object = Game::entityManager->GetEntity(objectId);
|
|
if (object) report.objectLot = object->GetLOT();
|
|
// A model placed on this property belongs to the property's owner; a pet to its owner
|
|
auto* property = PropertyManagementComponent::Instance();
|
|
if (property && property->GetModels().contains(objectId)) {
|
|
report.propertyId = property->GetId();
|
|
SetTarget(report, property->GetOwnerId());
|
|
} else if (auto* pet = object ? object->GetComponent<PetComponent>() : nullptr) {
|
|
SetTarget(report, pet->GetOwnerId());
|
|
}
|
|
Save(report);
|
|
}
|
|
|
|
void ReportOffensiveProperty(Entity* reporter, const std::u16string& text, LWOOBJID plaqueId) {
|
|
if (text.size() > MAX_BODY) return;
|
|
const auto description = GeneralUtils::UTF16ToWTF8(text);
|
|
auto report = NewReport(reporter, ePlayerReportKind::PROPERTY, description);
|
|
report.objectId = plaqueId;
|
|
if (auto* plaque = Game::entityManager->GetEntity(plaqueId)) report.objectLot = plaque->GetLOT();
|
|
// The plaque stands on the property this world holds
|
|
if (auto* property = PropertyManagementComponent::Instance()) {
|
|
report.propertyId = property->GetId();
|
|
SetTarget(report, property->GetOwnerId());
|
|
}
|
|
Save(report);
|
|
}
|
|
}
|