mirror of
https://github.com/DarkflameUniverse/DarkflameServer.git
synced 2026-10-08 13:53:45 +00:00
feat: web dashboard and playground work
The NexusDashboard-parity dashboard (dDashboardServer) and everything built on it on the experimental branch: accounts, characters, properties and moderation tools, permissions shared with in-game slash commands, economy reports, World 3D and property 3D views with client scenery, scheduled events (features, vanity changes, live events, announcements, restarts), vanity files and events, the CDClient browser, the message inspector with saved captures, chat filter tools, community challenges, live ops, the AI moderator helper, and the server-side changes they need. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -17,6 +17,7 @@
|
||||
#include "eGameMasterLevel.h"
|
||||
#include "eGameActivity.h"
|
||||
#include "User.h"
|
||||
#include "EconomyLedger.h"
|
||||
#include "Database.h"
|
||||
#include "CDRewardCodesTable.h"
|
||||
#include "Mail.h"
|
||||
@@ -585,9 +586,9 @@ void CharacterComponent::TrackQuickBuildComplete() {
|
||||
}
|
||||
|
||||
void CharacterComponent::TrackRaceCompleted(bool won) {
|
||||
m_RacesFinished++;
|
||||
UpdatePlayerStatistic(RacesFinished);
|
||||
if (won)
|
||||
m_FirstPlaceRaceFinishes++;
|
||||
UpdatePlayerStatistic(FirstPlaceRaceFinishes);
|
||||
}
|
||||
|
||||
void CharacterComponent::TrackPositionUpdate(const NiPoint3& newPosition) {
|
||||
@@ -615,7 +616,9 @@ void CharacterComponent::HandleZoneStatisticsUpdate(LWOMAPID zoneID, const std::
|
||||
}
|
||||
}
|
||||
|
||||
void CharacterComponent::UpdatePlayerStatistic(StatisticID updateID, uint64_t updateValue) {
|
||||
void CharacterComponent::UpdatePlayerStatistic(StatisticID updateID, uint64_t updateValue, bool fromClient) {
|
||||
if (!fromClient) EconomyLedger::RecordStat(m_Parent, updateID, updateValue);
|
||||
|
||||
switch (updateID) {
|
||||
case CurrencyCollected:
|
||||
m_CurrencyCollected += updateValue;
|
||||
@@ -945,3 +948,8 @@ void CharacterComponent::LoadVisitedLevelsXml(const tinyxml2::XMLElement& vl) {
|
||||
}
|
||||
// </vl>
|
||||
}
|
||||
|
||||
void CharacterComponent::SetUScore(int64_t uscore, uint32_t source) {
|
||||
if (const auto* character = m_Parent->GetCharacter()) EconomyLedger::RecordUScore(character->GetID(), uscore - m_Uscore, source);
|
||||
m_Uscore = uscore;
|
||||
}
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
#include <array>
|
||||
#include <set>
|
||||
#include "Loot.h"
|
||||
#include "StatisticID.h"
|
||||
|
||||
enum class eGameActivity : uint32_t;
|
||||
|
||||
@@ -34,39 +35,6 @@ struct ZoneStatistics {
|
||||
bool operator==(const ZoneStatistics& rhs) const = default;
|
||||
};
|
||||
|
||||
/**
|
||||
* The IDs of each of the possible statistics
|
||||
*/
|
||||
enum StatisticID {
|
||||
CurrencyCollected = 1,
|
||||
BricksCollected,
|
||||
SmashablesSmashed,
|
||||
QuickBuildsCompleted,
|
||||
EnemiesSmashed,
|
||||
RocketsUsed,
|
||||
MissionsCompleted,
|
||||
PetsTamed,
|
||||
ImaginationPowerUpsCollected,
|
||||
LifePowerUpsCollected,
|
||||
ArmorPowerUpsCollected,
|
||||
MetersTraveled,
|
||||
TimesSmashed,
|
||||
TotalDamageTaken,
|
||||
TotalDamageHealed,
|
||||
TotalArmorRepaired,
|
||||
TotalImaginationRestored,
|
||||
TotalImaginationUsed,
|
||||
DistanceDriven,
|
||||
TimeAirborneInCar,
|
||||
RacingImaginationPowerUpsCollected,
|
||||
RacingImaginationCratesSmashed,
|
||||
RacingCarBoostsActivated,
|
||||
RacingTimesWrecked,
|
||||
RacingSmashablesSmashed,
|
||||
RacesFinished,
|
||||
FirstPlaceRaceFinishes,
|
||||
};
|
||||
|
||||
/**
|
||||
* Represents a character, including their rockets and stats
|
||||
*/
|
||||
@@ -117,8 +85,9 @@ public:
|
||||
/**
|
||||
* Sets the universe score for this entity
|
||||
* @param uscore the universe score to set
|
||||
* @param source why it changed, for the economy reports (see EconomyLedger for sources beyond eLootSourceType)
|
||||
*/
|
||||
void SetUScore(int64_t uscore) { m_Uscore = uscore; }
|
||||
void SetUScore(int64_t uscore, uint32_t source);
|
||||
|
||||
/**
|
||||
* Gets the current activity that the character is partaking in, see ScriptedActivityComponent for more details
|
||||
@@ -267,8 +236,9 @@ public:
|
||||
* Allows one to generically update a statistic
|
||||
* @param updateID the 1-indexed ID of the statistic in the order of definition below
|
||||
* @param updateValue the value to update the statistic with
|
||||
* @param fromClient whether the client reported it; those are not trusted for the dashboard's daily totals
|
||||
*/
|
||||
void UpdatePlayerStatistic(StatisticID updateID, uint64_t updateValue = 1);
|
||||
void UpdatePlayerStatistic(StatisticID updateID, uint64_t updateValue = 1, bool fromClient = false);
|
||||
|
||||
/**
|
||||
* Add a venture vision effect to the player minimap.
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
#include "DestroyableComponent.h"
|
||||
#include "EconomyLedger.h"
|
||||
#include "BitStream.h"
|
||||
#include "Logger.h"
|
||||
#include "Game.h"
|
||||
@@ -716,6 +717,11 @@ void DestroyableComponent::Smash(const LWOOBJID source, const eKillType killType
|
||||
|
||||
auto* owner = Game::entityManager->GetEntity(source);
|
||||
|
||||
// Where players die and what killed them (the killer itself, not whoever owns it; 0 when it is already gone)
|
||||
if (m_Parent->IsPlayer()) {
|
||||
EconomyLedger::RecordMapEvent(IEconomyLedger::eMapEvent::PLAYER_DEATHS, owner ? owner->GetLOT() : 0, m_Parent->GetPosition(), 1, m_Parent);
|
||||
}
|
||||
|
||||
if (owner != nullptr) {
|
||||
owner = owner->GetOwner(); // If the owner is overwritten, we collect that here
|
||||
|
||||
@@ -729,6 +735,12 @@ void DestroyableComponent::Smash(const LWOOBJID source, const eKillType killType
|
||||
inventoryComponent->TriggerPassiveAbility(PassiveAbilityTrigger::EnemySmashed, m_Parent);
|
||||
}
|
||||
|
||||
if (isEnemy && owner->IsPlayer()) {
|
||||
EconomyLedger::RecordMapEvent(IEconomyLedger::eMapEvent::ENEMY_KILLS, m_Parent->GetLOT(), m_Parent->GetPosition(), 1, owner);
|
||||
} else if (owner->IsPlayer() && !m_Parent->IsPlayer()) {
|
||||
EconomyLedger::RecordMapEvent(IEconomyLedger::eMapEvent::SMASHABLES_SMASHED, m_Parent->GetLOT(), m_Parent->GetPosition(), 1, owner);
|
||||
}
|
||||
|
||||
auto* missions = owner->GetComponent<MissionComponent>();
|
||||
|
||||
if (missions != nullptr) {
|
||||
@@ -785,6 +797,7 @@ void DestroyableComponent::Smash(const LWOOBJID source, const eKillType killType
|
||||
lootMsg.item = LOT_NULL;
|
||||
GameMessages::DeliverLocally(lootMsg);
|
||||
character->SetCoins(coinsTotal, eLootSourceType::DELETION);
|
||||
EconomyLedger::RecordMapEvent(IEconomyLedger::eMapEvent::PLAYER_COIN_DROPS, 0, m_Parent->GetPosition(), static_cast<int64_t>(coinsToLose), m_Parent);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -970,7 +983,7 @@ void DestroyableComponent::DoHardcoreModeDrops(const LWOOBJID source) {
|
||||
|
||||
auto uscoreToLose = static_cast<uint64_t>(uscore * (Game::entityManager->GetHardcoreLoseUscoreOnDeathPercent() / 100.0f));
|
||||
LOG("Player %llu has lost %llu uscore!", m_Parent->GetObjectID(), uscoreToLose);
|
||||
character->SetUScore(uscore - uscoreToLose);
|
||||
character->SetUScore(uscore - uscoreToLose, EconomyLedger::HARDCORE_DEATH_SOURCE);
|
||||
|
||||
GameMessages::SendModifyLEGOScore(m_Parent, m_Parent->GetSystemAddress(), -uscoreToLose, eLootSourceType::MISSION);
|
||||
|
||||
@@ -1013,6 +1026,7 @@ void DestroyableComponent::DoHardcoreModeDrops(const LWOOBJID source) {
|
||||
|
||||
//lose all coins:
|
||||
chars->SetCoins(coins, eLootSourceType::NONE);
|
||||
if (coinsToDrop > 0) EconomyLedger::RecordMapEvent(IEconomyLedger::eMapEvent::PLAYER_COIN_DROPS, 0, m_Parent->GetPosition(), static_cast<int64_t>(coinsToDrop), m_Parent);
|
||||
|
||||
//drop all coins:
|
||||
constexpr auto MAX_TO_DROP_PER_GM = 100'000;
|
||||
@@ -1054,7 +1068,7 @@ void DestroyableComponent::DoHardcoreModeDrops(const LWOOBJID source) {
|
||||
|
||||
int uscore = maxHealth * Game::entityManager->GetHardcoreUscoreEnemiesMultiplier() * uscoreReduction;
|
||||
LOG("Rewarding player %llu with %i uscore for killing enemy %i", player->GetObjectID(), uscore, lot);
|
||||
playerStats->SetUScore(playerStats->GetUScore() + uscore);
|
||||
playerStats->SetUScore(playerStats->GetUScore() + uscore, EconomyLedger::HARDCORE_KILL_SOURCE);
|
||||
GameMessages::SendModifyLEGOScore(player, player->GetSystemAddress(), uscore, eLootSourceType::MISSION);
|
||||
|
||||
Game::entityManager->SerializeEntity(m_Parent);
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
#include "InventoryComponent.h"
|
||||
#include "EconomyLedger.h"
|
||||
|
||||
#include <sstream>
|
||||
|
||||
#include "Entity.h"
|
||||
#include "Item.h"
|
||||
#include "Game.h"
|
||||
#include "dServer.h"
|
||||
#include "Logger.h"
|
||||
#include "CDClientManager.h"
|
||||
#include "ObjectIDManager.h"
|
||||
@@ -309,6 +311,58 @@ void InventoryComponent::AddItem(
|
||||
}
|
||||
}
|
||||
|
||||
ReceivedItem InventoryComponent::ReceiveItem(const LWOOBJID id, const LOT lot, const uint32_t count, eLootSourceType lootSourceType, const LwoNameValue& config, const LWOOBJID subKey, const bool bound, const ReceiveItemOptions& options) {
|
||||
if (count == 0 || !Inventory::IsValidItem(lot)) return {};
|
||||
|
||||
const auto inventoryType = options.inventory != INVALID ? options.inventory : Inventory::FindInventoryTypeForLot(lot);
|
||||
auto* inventory = GetInventory(inventoryType);
|
||||
if (!inventory) return {};
|
||||
|
||||
const bool unique = !config.values.empty() || subKey != LWOOBJID_EMPTY || bound || IsUniqueLot(lot);
|
||||
const bool keepId = id != LWOOBJID_EMPTY && FindItemById(id) == nullptr && (unique || inventory->FindItemByLot(lot) == nullptr);
|
||||
int32_t slot = -1;
|
||||
if (keepId) slot = options.preferredSlot != -1 && inventory->IsSlotEmpty(options.preferredSlot) ? options.preferredSlot : inventory->FindEmptySlot();
|
||||
if (slot != -1) {
|
||||
auto* item = new Item(id, lot, inventory, slot, count, bound || Inventory::FindItemComponent(lot).isBOP, config, LWOOBJID_EMPTY, subKey, lootSourceType);
|
||||
GameMessages::SendAddItemToInventoryClientSync(m_Parent, m_Parent->GetSystemAddress(), item, id, options.showFlyingLoot && !options.equip, static_cast<int>(count), subKey, lootSourceType);
|
||||
EconomyLedger::RecordItems(m_Parent, lot, count, static_cast<uint32_t>(lootSourceType));
|
||||
if (options.equip) {
|
||||
item->Equip();
|
||||
Game::entityManager->SerializeEntity(m_Parent);
|
||||
}
|
||||
|
||||
auto* missions = m_Parent->GetComponent<MissionComponent>();
|
||||
if (missions && !IsTransferInventory(inventoryType)) missions->Progress(eMissionTaskType::GATHER, lot, LWOOBJID_EMPTY, "", count, IsTransferInventory(options.sourceInventory));
|
||||
return { id, false };
|
||||
}
|
||||
|
||||
// Merge (or add a new stack) the normal way, then see which stack took the items
|
||||
std::map<LWOOBJID, uint32_t> before;
|
||||
for (const auto& [itemId, item] : inventory->GetItems()) {
|
||||
if (item->GetLot() == lot) before[itemId] = item->GetCount();
|
||||
}
|
||||
AddItem(lot, count, lootSourceType, inventoryType, config, LWOOBJID_EMPTY, options.showFlyingLoot, options.equip, subKey, options.sourceInventory, 0, bound, options.preferredSlot);
|
||||
|
||||
LWOOBJID receivedBy = LWOOBJID_EMPTY;
|
||||
int64_t largestGain = 0;
|
||||
for (const auto& [itemId, item] : inventory->GetItems()) {
|
||||
if (item->GetLot() != lot) continue;
|
||||
const auto previous = before.find(itemId);
|
||||
const int64_t gain = static_cast<int64_t>(item->GetCount()) - (previous == before.end() ? 0 : previous->second);
|
||||
if (gain > largestGain) {
|
||||
largestGain = gain;
|
||||
receivedBy = itemId;
|
||||
}
|
||||
}
|
||||
return { receivedBy, before.contains(receivedBy) };
|
||||
}
|
||||
|
||||
bool InventoryComponent::IsUniqueLot(const LOT lot) {
|
||||
const auto& info = Inventory::FindItemComponent(lot);
|
||||
const bool isBrick = Inventory::FindInventoryTypeForLot(lot) == eInventoryType::BRICKS || (info.stackSize == 0 && info.itemType == 1);
|
||||
return !isBrick && info.stackSize <= 1;
|
||||
}
|
||||
|
||||
bool InventoryComponent::RemoveItem(const LOT lot, const uint32_t count, eInventoryType inventoryType, const bool ignoreBound, const bool silent) {
|
||||
if (count == 0) {
|
||||
LOG("Attempted to remove 0 of item (%i) from the inventory!", lot);
|
||||
@@ -356,8 +410,28 @@ void InventoryComponent::MoveItemToInventory(Item* item, const eInventoryType in
|
||||
|
||||
const auto subkey = item->GetSubKey();
|
||||
|
||||
// Moving between a player's own inventories neither creates nor destroys anything, except for the vendor's
|
||||
// buyback inventory: selling takes the item out of the game until it is bought back
|
||||
EconomyLedger::ScopedItemTransfer moving;
|
||||
const auto originType = origin->GetType();
|
||||
uint32_t moved = 0;
|
||||
|
||||
// Each move gives the items a new object id in the other inventory (as upstream, and as live did), so the ledger
|
||||
// records it for item traces: the old id, the stack that took the items and whether that stack was already there
|
||||
auto* character = m_Parent->GetCharacter();
|
||||
const auto zone = Game::server ? Game::server->GetZoneID() : 0;
|
||||
const auto recordMove = [&](const LWOOBJID oldId, const ReceivedItem& received, const uint32_t moveCount) {
|
||||
if (!character || received.id == LWOOBJID_EMPTY || received.id == oldId) return;
|
||||
EconomyLedger::RecordTransfer({ .method = IEconomyLedger::eTransferMethod::INVENTORY_MOVE, .itemId = oldId, .newItemId = received.id,
|
||||
.lot = lot, .count = moveCount, .fromCharacter = character->GetID(), .toCharacter = character->GetID(), .zone = zone,
|
||||
.merged = received.merged });
|
||||
};
|
||||
const ReceiveItemOptions moveOptions{ .inventory = inventory, .showFlyingLoot = showFlyingLot, .preferredSlot = preferredSlot, .sourceInventory = originType };
|
||||
|
||||
// Items move the way upstream moves them (a new object in the other inventory); only the ledger bookkeeping is added
|
||||
if (subkey == LWOOBJID_EMPTY && item->GetConfig().values.empty() && (!item->GetBound() || (item->GetBound() && item->GetInfo().isBOP))) {
|
||||
auto left = std::min<uint32_t>(count, origin->GetLotCount(lot));
|
||||
moved = left;
|
||||
|
||||
while (left > 0) {
|
||||
if (item == nullptr) {
|
||||
@@ -372,9 +446,13 @@ void InventoryComponent::MoveItemToInventory(Item* item, const eInventoryType in
|
||||
|
||||
left -= delta;
|
||||
|
||||
AddItem(lot, delta, eLootSourceType::NONE, inventory, {}, LWOOBJID_EMPTY, showFlyingLot, isModMoveAndEquip, LWOOBJID_EMPTY, origin->GetType(), 0, false, preferredSlot);
|
||||
auto options = moveOptions;
|
||||
options.equip = isModMoveAndEquip;
|
||||
const auto oldId = item->GetId();
|
||||
const auto received = ReceiveItem(LWOOBJID_EMPTY, lot, delta, eLootSourceType::NONE, {}, LWOOBJID_EMPTY, false, options);
|
||||
|
||||
item->SetCount(item->GetCount() - delta, false, false);
|
||||
recordMove(oldId, received, delta);
|
||||
|
||||
isModMoveAndEquip = false;
|
||||
}
|
||||
@@ -382,10 +460,21 @@ void InventoryComponent::MoveItemToInventory(Item* item, const eInventoryType in
|
||||
const auto config = item->GetConfig();
|
||||
|
||||
const auto delta = std::min<uint32_t>(item->GetCount(), count);
|
||||
moved = delta;
|
||||
|
||||
AddItem(lot, delta, eLootSourceType::NONE, inventory, config, LWOOBJID_EMPTY, showFlyingLot, isModMoveAndEquip, subkey, origin->GetType(), 0, item->GetBound(), preferredSlot);
|
||||
auto options = moveOptions;
|
||||
options.equip = isModMoveAndEquip;
|
||||
const auto oldId = item->GetId();
|
||||
const auto received = ReceiveItem(LWOOBJID_EMPTY, lot, delta, eLootSourceType::NONE, config, subkey, item->GetBound(), options);
|
||||
|
||||
item->SetCount(item->GetCount() - delta, false, false);
|
||||
recordMove(oldId, received, delta);
|
||||
}
|
||||
|
||||
if (inventory == eInventoryType::VENDOR_BUYBACK && originType != eInventoryType::VENDOR_BUYBACK) {
|
||||
EconomyLedger::RecordItemsUnsuppressed(m_Parent, lot, -static_cast<int64_t>(moved), static_cast<uint32_t>(eLootSourceType::VENDOR));
|
||||
} else if (originType == eInventoryType::VENDOR_BUYBACK && inventory != eInventoryType::VENDOR_BUYBACK) {
|
||||
EconomyLedger::RecordItemsUnsuppressed(m_Parent, lot, moved, static_cast<uint32_t>(eLootSourceType::VENDOR));
|
||||
}
|
||||
|
||||
auto* missionComponent = m_Parent->GetComponent<MissionComponent>();
|
||||
|
||||
@@ -40,6 +40,21 @@ namespace GameMessages {
|
||||
* of different types, each type representing a different group of items, see `eInventoryType` for a list of
|
||||
* inventories.
|
||||
*/
|
||||
// Where and how ReceiveItem puts items; the defaults suit items arriving from someone else
|
||||
struct ReceiveItemOptions {
|
||||
eInventoryType inventory = INVALID; // INVALID: the item's usual inventory
|
||||
bool showFlyingLoot = true;
|
||||
bool equip = false;
|
||||
int32_t preferredSlot = -1;
|
||||
eInventoryType sourceInventory = INVALID; // set when moving between the player's own inventories
|
||||
};
|
||||
|
||||
// Which item or stack ReceiveItem put the items in
|
||||
struct ReceivedItem {
|
||||
LWOOBJID id = LWOOBJID_EMPTY; // LWOOBJID_EMPTY if nothing could be added
|
||||
bool merged = false; // joined a stack that was already there (and has a history of its own)
|
||||
};
|
||||
|
||||
class InventoryComponent final : public Component {
|
||||
public:
|
||||
struct Group {
|
||||
@@ -145,6 +160,19 @@ public:
|
||||
int32_t preferredSlot = -1
|
||||
);
|
||||
|
||||
/**
|
||||
* Receive items that already exist elsewhere (another player's inventory, the mail), keeping them traceable:
|
||||
* - a unique item (does not stack, or carries config/subkey) keeps its object id
|
||||
* - stackable items merge into this inventory's stack of the same LOT if there is one, taking that stack's id;
|
||||
* with nothing to merge into, a whole stack keeps its id and a partial one gets a new id
|
||||
* @param id the items' current object id, or LWOOBJID_EMPTY for part of a stack
|
||||
* @return the object id of the item or stack that received them, and whether that stack was already there
|
||||
*/
|
||||
ReceivedItem ReceiveItem(LWOOBJID id, LOT lot, uint32_t count, eLootSourceType lootSourceType, const LwoNameValue& config = {}, LWOOBJID subKey = LWOOBJID_EMPTY, bool bound = false, const ReceiveItemOptions& options = {});
|
||||
|
||||
// Whether items of this LOT are one of a kind rather than stacking
|
||||
static bool IsUniqueLot(LOT lot);
|
||||
|
||||
/**
|
||||
* Removes a LOT from the inventory
|
||||
* @param lot the lot to remove
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
#include "Logger.h"
|
||||
|
||||
#include "BehaviorStates.h"
|
||||
#include "PropertyBehaviorActions.h"
|
||||
#include "ControlBehaviorMsgs.h"
|
||||
#include "tinyxml2.h"
|
||||
#include "InventoryComponent.h"
|
||||
@@ -43,7 +44,7 @@ bool ModelComponent::OnResetModelToDefaults(GameMessages::ResetModelToDefaults&
|
||||
if (reset.bResetRot) m_Parent->SetRotation(m_OriginalRotation);
|
||||
m_Parent->SetVelocity(NiPoint3Constant::ZERO);
|
||||
|
||||
m_Speed = 3.0f;
|
||||
m_Speed = PropertyBehaviorActions::DEFAULT_SPEED;
|
||||
m_NumListeningInteract = 0;
|
||||
|
||||
m_NumActiveAttack = 0;
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
|
||||
#include "Action.h"
|
||||
#include "PropertyBehavior.h"
|
||||
#include "PropertyBehaviorActions.h"
|
||||
#include "StripUiPosition.h"
|
||||
|
||||
class AddMessage;
|
||||
@@ -209,7 +210,7 @@ private:
|
||||
LWOOBJID m_userModelID;
|
||||
|
||||
// The speed at which this model moves
|
||||
float m_Speed{ 3.0f };
|
||||
float m_Speed{ PropertyBehaviorActions::DEFAULT_SPEED };
|
||||
|
||||
// Whether or not to restart at the end of the frame.
|
||||
bool m_RestartAtEndOfFrame{ false };
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
#include "PetComponent.h"
|
||||
#include "DashboardNotify.h"
|
||||
#include "GameMessages.h"
|
||||
#include "BrickDatabase.h"
|
||||
#include "CDClientDatabase.h"
|
||||
@@ -566,7 +567,8 @@ void PetComponent::RequestSetPetName(std::u16string name) {
|
||||
if (autoRejectNames && owner && owner->GetCharacter() && owner->GetCharacter()->GetParentUser()->GetIsMuted()) {
|
||||
m_ModerationStatus = 2; // Approved
|
||||
std::string forcedName = "Pet";
|
||||
Database::Get()->SetPetNameModerationStatus(m_DatabaseId, IPetNames::Info{ forcedName, static_cast<int32_t>(m_ModerationStatus) });
|
||||
Database::Get()->SetPetNameModerationStatus(m_DatabaseId, IPetNames::Info{ forcedName, static_cast<int32_t>(m_ModerationStatus), m_Owner });
|
||||
DashboardNotify::Changed("pet_names", m_DatabaseId);
|
||||
GameMessages::SendSetPetName(m_Owner, GeneralUtils::UTF8ToUTF16(m_Name), m_DatabaseId, owner->GetSystemAddress());
|
||||
GameMessages::SendSetPetNameModerated(m_Owner, m_DatabaseId, m_ModerationStatus, owner->GetSystemAddress());
|
||||
} else {
|
||||
@@ -574,7 +576,7 @@ void PetComponent::RequestSetPetName(std::u16string name) {
|
||||
m_Name = "";
|
||||
|
||||
//Save our pet's new name to the db:
|
||||
SetPetNameForModeration(GeneralUtils::UTF16ToWTF8(name));
|
||||
SetPetNameForModeration(GeneralUtils::UTF16ToWTF8(name), m_Owner);
|
||||
|
||||
GameMessages::SendSetPetName(m_Owner, GeneralUtils::UTF8ToUTF16(m_Name), m_DatabaseId, owner->GetSystemAddress());
|
||||
GameMessages::SendSetPetNameModerated(m_Owner, m_DatabaseId, m_ModerationStatus, owner->GetSystemAddress());
|
||||
@@ -606,14 +608,15 @@ void PetComponent::RequestSetPetName(std::u16string name) {
|
||||
m_Name = "";
|
||||
std::string forcedName = "Pet";
|
||||
|
||||
Database::Get()->SetPetNameModerationStatus(m_DatabaseId, IPetNames::Info{ forcedName, static_cast<int32_t>(m_ModerationStatus) });
|
||||
Database::Get()->SetPetNameModerationStatus(m_DatabaseId, IPetNames::Info{ forcedName, static_cast<int32_t>(m_ModerationStatus), m_Tamer });
|
||||
DashboardNotify::Changed("pet_names", m_DatabaseId);
|
||||
LOG("AccountID: %i is muted, forcing use of predefined pet name", tamer->GetCharacter()->GetParentUser()->GetAccountID());
|
||||
} else {
|
||||
m_ModerationStatus = 1; // Pending
|
||||
m_Name = "";
|
||||
|
||||
//Save our pet's new name to the db:
|
||||
SetPetNameForModeration(GeneralUtils::UTF16ToWTF8(name));
|
||||
SetPetNameForModeration(GeneralUtils::UTF16ToWTF8(name), m_Tamer);
|
||||
}
|
||||
|
||||
Game::entityManager->SerializeEntity(m_Parent);
|
||||
@@ -1046,7 +1049,7 @@ PetComponent::~PetComponent() {
|
||||
m_Owner = LWOOBJID_EMPTY;
|
||||
}
|
||||
|
||||
void PetComponent::SetPetNameForModeration(const std::string& petName) {
|
||||
void PetComponent::SetPetNameForModeration(const std::string& petName, const LWOOBJID owner) {
|
||||
int approved = 1; //default, in mod
|
||||
|
||||
//Make sure that the name isn't already auto-approved:
|
||||
@@ -1055,7 +1058,8 @@ void PetComponent::SetPetNameForModeration(const std::string& petName) {
|
||||
}
|
||||
|
||||
//Save to db:
|
||||
Database::Get()->SetPetNameModerationStatus(m_DatabaseId, IPetNames::Info{ petName, approved });
|
||||
Database::Get()->SetPetNameModerationStatus(m_DatabaseId, IPetNames::Info{ petName, approved, owner });
|
||||
DashboardNotify::Changed("pet_names", m_DatabaseId);
|
||||
}
|
||||
|
||||
void PetComponent::LoadPetNameFromModeration() {
|
||||
@@ -1071,3 +1075,26 @@ void PetComponent::LoadPetNameFromModeration() {
|
||||
void PetComponent::SetPreconditions(const std::string& preconditions) {
|
||||
m_Preconditions = std::make_optional<PreconditionExpression>(preconditions);
|
||||
}
|
||||
|
||||
void PetComponent::ApplyNameModeration(const bool approved) {
|
||||
auto* owner = GetOwner();
|
||||
if (approved) {
|
||||
LoadPetNameFromModeration();
|
||||
} else {
|
||||
// Rejected names are removed; the pet goes back to having no name until a new one is chosen
|
||||
m_ModerationStatus = 0;
|
||||
m_Name = "";
|
||||
}
|
||||
if (owner) {
|
||||
if (auto* inventoryComponent = owner->GetComponent<InventoryComponent>()) {
|
||||
auto data = inventoryComponent->GetDatabasePet(m_DatabaseId);
|
||||
data.name = m_Name;
|
||||
data.moderationState = m_ModerationStatus;
|
||||
inventoryComponent->SetDatabasePet(m_DatabaseId, data);
|
||||
}
|
||||
GameMessages::SendSetPetName(m_Owner, GeneralUtils::UTF8ToUTF16(m_Name), m_DatabaseId, owner->GetSystemAddress());
|
||||
GameMessages::SendSetPetNameModerated(m_Owner, m_DatabaseId, m_ModerationStatus, owner->GetSystemAddress());
|
||||
}
|
||||
GameMessages::SendPetNameChanged(m_Parent->GetObjectID(), m_ModerationStatus, GeneralUtils::UTF8ToUTF16(m_Name), GeneralUtils::UTF8ToUTF16(m_OwnerName), UNASSIGNED_SYSTEM_ADDRESS);
|
||||
Game::entityManager->SerializeEntity(m_Parent);
|
||||
}
|
||||
|
||||
@@ -118,6 +118,9 @@ public:
|
||||
*/
|
||||
LWOOBJID GetDatabaseId() const;
|
||||
|
||||
// A moderator approved or rejected this pet's name (from the dashboard); updates it live for everyone
|
||||
void ApplyNameModeration(bool approved);
|
||||
|
||||
/**
|
||||
* Returns the ID of the object that the pet is currently interacting with, could be a treasure chest or a switch
|
||||
* @return the ID of the object that the pet is currently interacting with
|
||||
@@ -176,8 +179,9 @@ public:
|
||||
/**
|
||||
* Sets the name of the pet to be moderated
|
||||
* @param petName the name of the pet to set
|
||||
* @param owner the character that owns the pet, saved with the name
|
||||
*/
|
||||
void SetPetNameForModeration(const std::string& petName);
|
||||
void SetPetNameForModeration(const std::string& petName, const LWOOBJID owner);
|
||||
|
||||
/**
|
||||
* Loads the pet name up for moderation along with the moderation status from the database and sets them for this
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
#include "PropertyManagementComponent.h"
|
||||
#include "DashboardNotify.h"
|
||||
|
||||
#include <sstream>
|
||||
|
||||
@@ -147,6 +148,7 @@ void PropertyManagementComponent::SetPrivacyOption(PropertyPrivacyOption value)
|
||||
UpdateApprovedStatus(false, "Your property is empty. Please place a model to have a public property.");
|
||||
} else {
|
||||
Database::Get()->UpdatePropertyModerationInfo(info);
|
||||
DashboardNotify::Changed("properties", propertyId);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -165,6 +167,7 @@ void PropertyManagementComponent::UpdatePropertyDetails(std::string name, std::s
|
||||
|
||||
Database::Get()->UpdateLastSave(info);
|
||||
Database::Get()->UpdatePropertyDetails(info);
|
||||
DashboardNotify::Changed("properties", propertyId);
|
||||
|
||||
OnQueryPropertyData(GetOwner(), UNASSIGNED_SYSTEM_ADDRESS);
|
||||
}
|
||||
@@ -220,6 +223,7 @@ bool PropertyManagementComponent::Claim(const LWOOBJID playerId) {
|
||||
info.description = description;
|
||||
|
||||
Database::Get()->InsertNewProperty(info, templateId, worldId);
|
||||
DashboardNotify::Changed("properties", propertyId);
|
||||
|
||||
auto* zoneControlObject = Game::zoneManager->GetZoneControlObject();
|
||||
if (zoneControlObject) zoneControlObject->GetScript()->OnZonePropertyRented(zoneControlObject, entity);
|
||||
@@ -574,6 +578,7 @@ void PropertyManagementComponent::UpdateApprovedStatus(const bool value, const s
|
||||
info.rejectionReason = rejectionReason;
|
||||
|
||||
Database::Get()->UpdatePropertyModerationInfo(info);
|
||||
DashboardNotify::Changed("properties", propertyId);
|
||||
}
|
||||
|
||||
void PropertyManagementComponent::Load() {
|
||||
@@ -718,6 +723,7 @@ void PropertyManagementComponent::Save() {
|
||||
info.id = propertyId;
|
||||
info.lastUpdatedTime = std::chrono::duration_cast<std::chrono::seconds>(std::chrono::system_clock::now().time_since_epoch()).count();
|
||||
Database::Get()->UpdateLastSave(info);
|
||||
DashboardNotify::Changed("properties", propertyId);
|
||||
}
|
||||
|
||||
void PropertyManagementComponent::AddModel(LWOOBJID modelId, LWOOBJID spawnerId) {
|
||||
@@ -812,3 +818,10 @@ void PropertyManagementComponent::OnChatMessageReceived(const std::string& sMess
|
||||
modelComponent->OnChatMessageReceived(sMessage);
|
||||
}
|
||||
}
|
||||
|
||||
void PropertyManagementComponent::ApplyModeration(const bool approved, const std::string& reason) {
|
||||
rejectionReason = approved ? "" : reason;
|
||||
// The dashboard makes rejected properties private; don't let a later save here publish it again
|
||||
if (!approved) privacyOption = PropertyPrivacyOption::Private;
|
||||
OnQueryPropertyData(GetOwner(), UNASSIGNED_SYSTEM_ADDRESS);
|
||||
}
|
||||
|
||||
@@ -1,30 +1,11 @@
|
||||
#pragma once
|
||||
|
||||
#include "ePropertyPrivacyOption.h"
|
||||
#include <chrono>
|
||||
#include "Entity.h"
|
||||
#include "Component.h"
|
||||
#include "eReplicaComponentType.h"
|
||||
|
||||
/**
|
||||
* Information regarding which players may visit this property
|
||||
*/
|
||||
enum class PropertyPrivacyOption {
|
||||
/**
|
||||
* Default, only you can visit your property
|
||||
*/
|
||||
Private = 0,
|
||||
|
||||
/**
|
||||
* Your friends can visit your property
|
||||
*/
|
||||
Friends = 1,
|
||||
|
||||
/**
|
||||
* Requires Mythran approval, everyone can visit your property
|
||||
*/
|
||||
Public = 2
|
||||
};
|
||||
|
||||
/**
|
||||
* Main component that handles interactions with a property, generally the plaques you see on properties.
|
||||
*/
|
||||
@@ -137,6 +118,9 @@ public:
|
||||
*/
|
||||
void UpdateApprovedStatus(bool value, const std::string& rejectionReason = "");
|
||||
|
||||
// A moderator decided on this property from the dashboard (already saved): keep this world's copy in step
|
||||
void ApplyModeration(bool approved, const std::string& reason);
|
||||
|
||||
/**
|
||||
* Loads all the models on this property from the database
|
||||
*/
|
||||
|
||||
@@ -24,6 +24,7 @@
|
||||
#include "CppScripts.h"
|
||||
#include "StringifiedEnum.h"
|
||||
#include "Amf3.h"
|
||||
#include "EconomyLedger.h"
|
||||
|
||||
QuickBuildComponent::QuickBuildComponent(Entity* const entity, const int32_t componentID) : Component{ entity, componentID } {
|
||||
std::u16string checkPreconditions = entity->GetVar<std::u16string>(u"CheckPrecondition");
|
||||
@@ -415,6 +416,7 @@ void QuickBuildComponent::CompleteQuickBuild(Entity* const user) {
|
||||
if (characterComponent != nullptr) {
|
||||
characterComponent->SetCurrentActivity(eGameActivity::NONE);
|
||||
characterComponent->TrackQuickBuildComplete();
|
||||
EconomyLedger::RecordMapEvent(IEconomyLedger::eMapEvent::QUICKBUILDS_COMPLETED, m_Parent->GetLOT(), m_Parent->GetPosition(), 1, user);
|
||||
} else {
|
||||
LOG("Some user tried to finish the rebuild but they didn't have a character somehow.");
|
||||
return;
|
||||
|
||||
Reference in New Issue
Block a user