Merge branch 'main' into rotation-behaviors

This commit is contained in:
David Markowitz
2025-10-19 22:28:30 -07:00
255 changed files with 5051 additions and 1825 deletions

View File

@@ -9,7 +9,7 @@
#include "UserManager.h"
#include "CDMissionsTable.h"
AchievementVendorComponent::AchievementVendorComponent(Entity* parent) : VendorComponent(parent) {
AchievementVendorComponent::AchievementVendorComponent(Entity* parent, const int32_t componentID) : VendorComponent(parent, componentID) {
RefreshInventory(true);
};

View File

@@ -11,7 +11,7 @@ class Entity;
class AchievementVendorComponent final : public VendorComponent {
public:
static constexpr eReplicaComponentType ComponentType = eReplicaComponentType::ACHIEVEMENT_VENDOR;
AchievementVendorComponent(Entity* parent);
AchievementVendorComponent(Entity* parent, const int32_t componentID);
void RefreshInventory(bool isCreation = false) override;
bool SellsItem(Entity* buyer, const LOT lot);

View File

@@ -30,7 +30,7 @@
#include "CharacterComponent.h"
#include "Amf3.h"
ActivityComponent::ActivityComponent(Entity* parent, int32_t activityID) : Component(parent) {
ActivityComponent::ActivityComponent(Entity* parent, int32_t componentID) : Component(parent, componentID) {
using namespace GameMessages;
RegisterMsg<GetObjectReportInfo>(this, &ActivityComponent::OnGetObjectReportInfo);
/*
@@ -39,39 +39,12 @@ ActivityComponent::ActivityComponent(Entity* parent, int32_t activityID) : Compo
* if activityID is specified and if that column exists in the activities table, update the activity info with that data.
*/
m_ActivityID = activityID;
LoadActivityData(activityID);
m_ActivityID = componentID;
LoadActivityData(componentID);
if (m_Parent->HasVar(u"activityID")) {
m_ActivityID = parent->GetVar<int32_t>(u"activityID");
LoadActivityData(m_ActivityID);
}
auto* destroyableComponent = m_Parent->GetComponent<DestroyableComponent>();
if (destroyableComponent) {
// First lookup the loot matrix id for this component id.
CDActivityRewardsTable* activityRewardsTable = CDClientManager::GetTable<CDActivityRewardsTable>();
std::vector<CDActivityRewards> activityRewards = activityRewardsTable->Query([=](CDActivityRewards entry) {return (entry.LootMatrixIndex == destroyableComponent->GetLootMatrixID()); });
uint32_t startingLMI = 0;
// If we have one, set the starting loot matrix id to that.
if (activityRewards.size() > 0) {
startingLMI = activityRewards[0].LootMatrixIndex;
}
if (startingLMI > 0) {
// We may have more than 1 loot matrix index to use depending ont the size of the team that is looting the activity.
// So this logic will get the rest of the loot matrix indices for this activity.
std::vector<CDActivityRewards> objectTemplateActivities = activityRewardsTable->Query([=](CDActivityRewards entry) {return (activityRewards[0].objectTemplate == entry.objectTemplate); });
for (const auto& item : objectTemplateActivities) {
if (item.activityRating > 0 && item.activityRating < 5) {
m_ActivityLootMatrices.insert({ item.activityRating, item.LootMatrixIndex });
}
}
}
}
}
void ActivityComponent::LoadActivityData(const int32_t activityId) {
CDActivitiesTable* activitiesTable = CDClientManager::GetTable<CDActivitiesTable>();
@@ -698,10 +671,6 @@ bool ActivityComponent::OnGetObjectReportInfo(GameMessages::GameMsg& msg) {
}
}
auto& lootMatrices = activityInfo.PushDebug("Loot Matrices");
for (const auto& [activityRating, lootMatrixID] : m_ActivityLootMatrices) {
lootMatrices.PushDebug<AMFIntValue>("Loot Matrix " + std::to_string(activityRating)) = lootMatrixID;
}
activityInfo.PushDebug<AMFIntValue>("ActivityID") = m_ActivityID;
return true;
}

View File

@@ -341,19 +341,13 @@ public:
*/
void SetInstanceMapID(uint32_t mapID) { m_ActivityInfo.instanceMapID = mapID; };
/**
* Returns the LMI that this activity points to for a team size
* @param teamSize the team size to get the LMI for
* @return the LMI that this activity points to for a team size
*/
uint32_t GetLootMatrixForTeamSize(uint32_t teamSize) { return m_ActivityLootMatrices[teamSize]; }
private:
bool OnGetObjectReportInfo(GameMessages::GameMsg& msg);
/**
* The database information for this activity
*/
CDActivities m_ActivityInfo;
CDActivities m_ActivityInfo{};
/**
* All the active instances of this activity
@@ -370,11 +364,6 @@ private:
*/
std::vector<ActivityPlayer*> m_ActivityPlayers;
/**
* LMIs for team sizes
*/
std::unordered_map<uint32_t, uint32_t> m_ActivityLootMatrices;
/**
* The activity id
*/

View File

@@ -27,8 +27,13 @@
#include "CDComponentsRegistryTable.h"
#include "CDPhysicsComponentTable.h"
#include "dNavMesh.h"
#include "Amf3.h"
BaseCombatAIComponent::BaseCombatAIComponent(Entity* parent, const uint32_t id) : Component(parent) {
BaseCombatAIComponent::BaseCombatAIComponent(Entity* parent, const int32_t componentID) : Component(parent, componentID) {
{
using namespace GameMessages;
RegisterMsg<GetObjectReportInfo>(this, &BaseCombatAIComponent::MsgGetObjectReportInfo);
}
m_Target = LWOOBJID_EMPTY;
m_DirtyStateOrTarget = true;
m_State = AiState::spawn;
@@ -43,7 +48,7 @@ BaseCombatAIComponent::BaseCombatAIComponent(Entity* parent, const uint32_t id)
//Grab the aggro information from BaseCombatAI:
auto componentQuery = CDClientDatabase::CreatePreppedStmt(
"SELECT aggroRadius, tetherSpeed, pursuitSpeed, softTetherRadius, hardTetherRadius FROM BaseCombatAIComponent WHERE id = ?;");
componentQuery.bind(1, static_cast<int>(id));
componentQuery.bind(1, static_cast<int>(componentID));
auto componentResult = componentQuery.execQuery();
@@ -111,12 +116,12 @@ BaseCombatAIComponent::BaseCombatAIComponent(Entity* parent, const uint32_t id)
int32_t collisionGroup = (COLLISION_GROUP_DYNAMIC | COLLISION_GROUP_ENEMY);
CDComponentsRegistryTable* componentRegistryTable = CDClientManager::GetTable<CDComponentsRegistryTable>();
auto componentID = componentRegistryTable->GetByIDAndType(parent->GetLOT(), eReplicaComponentType::CONTROLLABLE_PHYSICS);
const auto controllablePhysicsID = componentRegistryTable->GetByIDAndType(parent->GetLOT(), eReplicaComponentType::CONTROLLABLE_PHYSICS);
CDPhysicsComponentTable* physicsComponentTable = CDClientManager::GetTable<CDPhysicsComponentTable>();
if (physicsComponentTable != nullptr) {
auto* info = physicsComponentTable->GetByID(componentID);
auto* info = physicsComponentTable->GetByID(controllablePhysicsID);
if (info != nullptr) {
collisionGroup = info->bStatic ? COLLISION_GROUP_NEUTRAL : info->collisionGroup;
}
@@ -839,3 +844,73 @@ void BaseCombatAIComponent::IgnoreThreat(const LWOOBJID threat, const float valu
SetThreat(threat, 0.0f);
m_Target = LWOOBJID_EMPTY;
}
bool BaseCombatAIComponent::MsgGetObjectReportInfo(GameMessages::GameMsg& msg) {
using enum AiState;
auto& reportMsg = static_cast<GameMessages::GetObjectReportInfo&>(msg);
auto& cmptType = reportMsg.info->PushDebug("Base Combat AI");
cmptType.PushDebug<AMFIntValue>("Component ID") = GetComponentID();
auto& targetInfo = cmptType.PushDebug("Current Target Info");
targetInfo.PushDebug<AMFStringValue>("Current Target ID") = std::to_string(m_Target);
// if (m_Target != LWOOBJID_EMPTY) {
// LWOGameMessages::ObjGetName nameMsg(m_CurrentTarget);
// SEND_GAMEOBJ_MSG(nameMsg);
// if (!nameMsg.msg.name.empty()) targetInfo.PushDebug("Name") = nameMsg.msg.name;
// }
auto& roundInfo = cmptType.PushDebug("Round Info");
// roundInfo.PushDebug<AMFDoubleValue>("Combat Round Time") = m_CombatRoundLength;
// roundInfo.PushDebug<AMFDoubleValue>("Minimum Time") = m_MinRoundLength;
// roundInfo.PushDebug<AMFDoubleValue>("Maximum Time") = m_MaxRoundLength;
// roundInfo.PushDebug<AMFDoubleValue>("Selected Time") = m_SelectedTime;
// roundInfo.PushDebug<AMFDoubleValue>("Combat Start Delay") = m_CombatStartDelay;
std::string curState;
switch (m_State) {
case idle: curState = "Idling"; break;
case aggro: curState = "Aggroed"; break;
case tether: curState = "Returning to Tether"; break;
case spawn: curState = "Spawn"; break;
case dead: curState = "Dead"; break;
default: curState = "Unknown or Undefined"; break;
}
cmptType.PushDebug<AMFStringValue>("Current Combat State") = curState;
//switch (m_CombatBehaviorType) {
// case 0: curState = "Passive"; break;
// case 1: curState = "Aggressive"; break;
// case 2: curState = "Passive (Turret)"; break;
// case 3: curState = "Aggressive (Turret)"; break;
// default: curState = "Unknown or Undefined"; break;
//}
//cmptType.PushDebug("Current Combat Behavior State") = curState;
//switch (m_CombatRole) {
// case 0: curState = "Melee"; break;
// case 1: curState = "Ranged"; break;
// case 2: curState = "Support"; break;
// default: curState = "Unknown or Undefined"; break;
//}
//cmptType.PushDebug("Current Combat Role") = curState;
auto& tetherPoint = cmptType.PushDebug("Tether Point");
tetherPoint.PushDebug<AMFDoubleValue>("X") = m_StartPosition.x;
tetherPoint.PushDebug<AMFDoubleValue>("Y") = m_StartPosition.y;
tetherPoint.PushDebug<AMFDoubleValue>("Z") = m_StartPosition.z;
cmptType.PushDebug<AMFDoubleValue>("Hard Tether Radius") = m_HardTetherRadius;
cmptType.PushDebug<AMFDoubleValue>("Soft Tether Radius") = m_SoftTetherRadius;
cmptType.PushDebug<AMFDoubleValue>("Aggro Radius") = m_AggroRadius;
cmptType.PushDebug<AMFDoubleValue>("Tether Speed") = m_TetherSpeed;
cmptType.PushDebug<AMFDoubleValue>("Aggro Speed") = m_TetherSpeed;
// cmptType.PushDebug<AMFDoubleValue>("Specified Min Range") = m_SpecificMinRange;
// cmptType.PushDebug<AMFDoubleValue>("Specified Max Range") = m_SpecificMaxRange;
auto& threats = cmptType.PushDebug("Target Threats");
for (const auto& [id, threat] : m_ThreatEntries) {
threats.PushDebug<AMFDoubleValue>(std::to_string(id)) = threat;
}
auto& ignoredThreats = cmptType.PushDebug("Temp Ignored Threats");
for (const auto& [id, threat] : m_ThreatEntries) {
ignoredThreats.PushDebug<AMFDoubleValue>(std::to_string(id) + " - Time") = threat;
}
return true;
}

View File

@@ -49,7 +49,7 @@ class BaseCombatAIComponent final : public Component {
public:
static constexpr eReplicaComponentType ComponentType = eReplicaComponentType::BASE_COMBAT_AI;
BaseCombatAIComponent(Entity* parentEntity, uint32_t id);
BaseCombatAIComponent(Entity* parentEntity, int32_t componentID);
~BaseCombatAIComponent() override;
void Update(float deltaTime) override;
@@ -234,6 +234,8 @@ public:
// Ignore a threat for a certain amount of time
void IgnoreThreat(const LWOOBJID target, const float time);
bool MsgGetObjectReportInfo(GameMessages::GameMsg& msg);
private:
/**
* Returns the current target or the target that currently is the largest threat to this entity

View File

@@ -8,15 +8,33 @@
#include "GameMessages.h"
#include "BitStream.h"
#include "eTriggerEventType.h"
#include "Amf3.h"
BouncerComponent::BouncerComponent(Entity* parent) : Component(parent) {
BouncerComponent::BouncerComponent(Entity* parent, const int32_t componentID) : Component(parent, componentID) {
m_PetEnabled = false;
m_PetBouncerEnabled = false;
m_PetSwitchLoaded = false;
m_Destination = GeneralUtils::TryParse<NiPoint3>(
GeneralUtils::SplitString(m_Parent->GetVarAsString(u"bouncer_destination"), '\x1f'))
.value_or(NiPoint3Constant::ZERO);
m_Speed = GeneralUtils::TryParse<float>(m_Parent->GetVarAsString(u"bouncer_speed")).value_or(-1.0f);
m_UsesHighArc = GeneralUtils::TryParse<bool>(m_Parent->GetVarAsString(u"bouncer_uses_high_arc")).value_or(false);
m_LockControls = GeneralUtils::TryParse<bool>(m_Parent->GetVarAsString(u"lock_controls")).value_or(false);
m_IgnoreCollision = !GeneralUtils::TryParse<bool>(m_Parent->GetVarAsString(u"ignore_collision")).value_or(true);
m_StickLanding = GeneralUtils::TryParse<bool>(m_Parent->GetVarAsString(u"stickLanding")).value_or(false);
m_UsesGroupName = GeneralUtils::TryParse<bool>(m_Parent->GetVarAsString(u"uses_group_name")).value_or(false);
m_GroupName = m_Parent->GetVarAsString(u"grp_name");
m_MinNumTargets = GeneralUtils::TryParse<int32_t>(m_Parent->GetVarAsString(u"num_targets_to_activate")).value_or(1);
m_CinematicPath = m_Parent->GetVarAsString(u"attached_cinematic_path");
if (parent->GetLOT() == 7625) {
LookupPetSwitch();
}
{
using namespace GameMessages;
RegisterMsg<GetObjectReportInfo>(this, &BouncerComponent::MsgGetObjectReportInfo);
}
}
BouncerComponent::~BouncerComponent() {
@@ -94,3 +112,54 @@ void BouncerComponent::LookupPetSwitch() {
});
}
}
bool BouncerComponent::MsgGetObjectReportInfo(GameMessages::GameMsg& msg) {
auto& reportMsg = static_cast<GameMessages::GetObjectReportInfo&>(msg);
auto& cmptType = reportMsg.info->PushDebug("Bouncer");
cmptType.PushDebug<AMFIntValue>("Component ID") = GetComponentID();
auto& destPos = cmptType.PushDebug("Destination Position");
if (m_Destination != NiPoint3Constant::ZERO) {
destPos.PushDebug(m_Destination);
} else {
destPos.PushDebug("<font color=\'#FF0000\'>WARNING:</font> Bouncer has no target position, is likely missing config data");
}
if (m_Speed == -1.0f) {
cmptType.PushDebug("<font color=\'#FF0000\'>WARNING:</font> Bouncer has no speed value, is likely missing config data");
} else {
cmptType.PushDebug<AMFDoubleValue>("Bounce Speed") = m_Speed;
}
cmptType.PushDebug<AMFStringValue>("Bounce trajectory arc") = m_UsesHighArc ? "High Arc" : "Low Arc";
cmptType.PushDebug<AMFBoolValue>("Collision Enabled") = m_IgnoreCollision;
cmptType.PushDebug<AMFBoolValue>("Stick Landing") = m_StickLanding;
cmptType.PushDebug<AMFBoolValue>("Locks character's controls") = m_LockControls;
if (!m_CinematicPath.empty()) cmptType.PushDebug<AMFStringValue>("Cinematic Camera Path (plays during bounce)") = m_CinematicPath;
auto* switchComponent = m_Parent->GetComponent<SwitchComponent>();
auto& respondsToFactions = cmptType.PushDebug("Responds to Factions");
if (!switchComponent || switchComponent->GetFactionsToRespondTo().empty()) respondsToFactions.PushDebug("Faction 1");
else {
for (const auto faction : switchComponent->GetFactionsToRespondTo()) {
respondsToFactions.PushDebug(("Faction " + std::to_string(faction)));
}
}
cmptType.PushDebug<AMFBoolValue>("Uses a group name for interactions") = m_UsesGroupName;
if (!m_UsesGroupName) {
if (m_MinNumTargets > 1) {
cmptType.PushDebug("<font color=\'#FF0000\'>WARNING:</font> Bouncer has a required number of objects to activate, but no group for interactions.");
}
if (!m_GroupName.empty()) {
cmptType.PushDebug("<font color=\'#FF0000\'>WARNING:</font> Has a group name for interactions , but is marked to not use that name.");
}
} else {
if (m_GroupName.empty()) {
cmptType.PushDebug("<font color=\'#FF0000\'>WARNING:</font> Set to use a group name for inter actions, but no group name is assigned");
}
cmptType.PushDebug<AMFIntValue>("Number of interactions to activate bouncer") = m_MinNumTargets;
}
return true;
}

View File

@@ -14,7 +14,7 @@ class BouncerComponent final : public Component {
public:
static constexpr eReplicaComponentType ComponentType = eReplicaComponentType::BOUNCER;
BouncerComponent(Entity* parentEntity);
BouncerComponent(Entity* parentEntity, const int32_t componentID);
~BouncerComponent() override;
void Serialize(RakNet::BitStream& outBitStream, bool bIsInitialUpdate) override;
@@ -51,6 +51,8 @@ public:
*/
void LookupPetSwitch();
bool MsgGetObjectReportInfo(GameMessages::GameMsg& msg);
private:
/**
* Whether this bouncer needs to be activated by a pet
@@ -66,6 +68,36 @@ private:
* Whether the pet switch for this bouncer has been located
*/
bool m_PetSwitchLoaded;
// The bouncer destination
NiPoint3 m_Destination;
// The speed at which the player is bounced
float m_Speed{};
// Whether to use a high arc for the bounce trajectory
bool m_UsesHighArc{};
// Lock controls when bouncing
bool m_LockControls{};
// Ignore collision when bouncing
bool m_IgnoreCollision{};
// Stick the landing afterwards or let the player slide
bool m_StickLanding{};
// Whether or not there is a group name
bool m_UsesGroupName{};
// The group name for targets
std::string m_GroupName{};
// The number of targets to activate the bouncer
int32_t m_MinNumTargets{};
// The cinematic path to play during the bounce
std::string m_CinematicPath{};
};
#endif // BOUNCERCOMPONENT_H

View File

@@ -26,7 +26,7 @@ namespace {
};
}
BuffComponent::BuffComponent(Entity* parent) : Component(parent) {
BuffComponent::BuffComponent(Entity* parent, const int32_t componentID) : Component(parent, componentID) {
}
BuffComponent::~BuffComponent() {

View File

@@ -51,7 +51,7 @@ class BuffComponent final : public Component {
public:
static constexpr eReplicaComponentType ComponentType = eReplicaComponentType::BUFF;
explicit BuffComponent(Entity* parent);
explicit BuffComponent(Entity* parent, const int32_t componentID);
~BuffComponent();

View File

@@ -9,7 +9,7 @@
#include "Item.h"
#include "PropertyManagementComponent.h"
BuildBorderComponent::BuildBorderComponent(Entity* parent) : Component(parent) {
BuildBorderComponent::BuildBorderComponent(Entity* parent, const int32_t componentID) : Component(parent, componentID) {
}
BuildBorderComponent::~BuildBorderComponent() {

View File

@@ -18,7 +18,7 @@ class BuildBorderComponent final : public Component {
public:
static constexpr eReplicaComponentType ComponentType = eReplicaComponentType::BUILD_BORDER;
BuildBorderComponent(Entity* parent);
BuildBorderComponent(Entity* parent, const int32_t componentID);
~BuildBorderComponent() override;
/**

View File

@@ -25,7 +25,7 @@
#include "MessageType/Game.h"
#include <ctime>
CharacterComponent::CharacterComponent(Entity* parent, Character* character, const SystemAddress& systemAddress) : Component(parent) {
CharacterComponent::CharacterComponent(Entity* parent, const int32_t componentID, Character* character, const SystemAddress& systemAddress) : Component(parent, componentID) {
m_Character = character;
m_IsRacing = false;
@@ -70,7 +70,7 @@ bool CharacterComponent::OnGetObjectReportInfo(GameMessages::GameMsg& msg) {
for (const auto zoneID : m_VisitedLevels) {
std::stringstream sstream;
sstream << "MapID: " << zoneID.GetMapID() << " CloneID: " << zoneID.GetCloneID();
vl.PushDebug<AMFStringValue>(sstream.str()) = "";
vl.PushDebug(sstream.str());
}
// visited locations
@@ -84,6 +84,30 @@ bool CharacterComponent::OnGetObjectReportInfo(GameMessages::GameMsg& msg) {
cmptType.PushDebug<AMFIntValue>("Current Activity Type") = GeneralUtils::ToUnderlying(m_CurrentActivity);
cmptType.PushDebug<AMFDoubleValue>("Property Clone ID") = m_Character->GetPropertyCloneID();
auto& flagCmptType = reportInfo.info->PushDebug("Player Flag");
auto& allFlags = flagCmptType.PushDebug("All flags");
for (const auto& [id, flagChunk] : m_Character->GetPlayerFlags()) {
const auto base = id * 64;
auto flagChunkCopy = flagChunk;
for (int i = 0; i < 64; i++) {
if (static_cast<bool>(flagChunkCopy & 1)) {
const int32_t flagId = base + i;
std::stringstream stream;
stream << "Flag: " << flagId;
allFlags.PushDebug(stream.str());
}
flagChunkCopy >>= 1;
}
}
auto& sessionFlags = flagCmptType.PushDebug("Session Only Flags");
for (const auto flagId : m_Character->GetSessionFlags()) {
std::stringstream stream;
stream << "Flag: " << flagId;
sessionFlags.PushDebug(stream.str());
}
return true;
}
@@ -859,7 +883,7 @@ void CharacterComponent::SendToZone(LWOMAPID zoneId, LWOCLONEID cloneId) const {
character->SetZoneID(zoneID);
character->SetZoneInstance(zoneInstance);
character->SetZoneClone(zoneClone);
characterComponent->SetLastRocketConfig(u"");
characterComponent->AddVisitedLevel(LWOZONEID(zoneID, LWOINSTANCEID_INVALID, zoneClone));

View File

@@ -70,7 +70,7 @@ class CharacterComponent final : public Component {
public:
static constexpr eReplicaComponentType ComponentType = eReplicaComponentType::CHARACTER;
CharacterComponent(Entity* parent, Character* character, const SystemAddress& systemAddress);
CharacterComponent(Entity* parent, const int32_t componentID, Character* character, const SystemAddress& systemAddress);
~CharacterComponent() override;
void LoadFromXml(const tinyxml2::XMLDocument& doc) override;

View File

@@ -1,5 +1,39 @@
#include "CollectibleComponent.h"
#include "MissionComponent.h"
#include "dServer.h"
#include "Amf3.h"
CollectibleComponent::CollectibleComponent(Entity* parentEntity, const int32_t componentID, const int32_t collectibleId) :
Component(parentEntity, componentID), m_CollectibleId(collectibleId) {
using namespace GameMessages;
RegisterMsg<GetObjectReportInfo>(this, &CollectibleComponent::MsgGetObjectReportInfo);
}
void CollectibleComponent::Serialize(RakNet::BitStream& outBitStream, bool isConstruction) {
outBitStream.Write(GetCollectibleId());
}
bool CollectibleComponent::MsgGetObjectReportInfo(GameMessages::GameMsg& msg) {
auto& reportMsg = static_cast<GameMessages::GetObjectReportInfo&>(msg);
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>("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;
}

View File

@@ -7,10 +7,12 @@
class CollectibleComponent final : public Component {
public:
static constexpr eReplicaComponentType ComponentType = eReplicaComponentType::COLLECTIBLE;
CollectibleComponent(Entity* parentEntity, int32_t collectibleId) : Component(parentEntity), m_CollectibleId(collectibleId) {}
CollectibleComponent(Entity* parentEntity, const int32_t componentID, const int32_t collectibleId);
int16_t GetCollectibleId() const { return m_CollectibleId; }
void Serialize(RakNet::BitStream& outBitStream, bool isConstruction) override;
bool MsgGetObjectReportInfo(GameMessages::GameMsg& msg);
private:
int16_t m_CollectibleId = 0;
};

View File

@@ -19,7 +19,7 @@ class Entity;
*/
class Component {
public:
Component(Entity* parent) : m_Parent{ parent } {}
Component(Entity* parent, const int32_t componentID) : m_Parent{ parent }, m_ComponentID{componentID} {}
virtual ~Component() = default;
/**
@@ -28,6 +28,8 @@ public:
*/
Entity* GetParent() const { return m_Parent; }
[[nodiscard]] int32_t GetComponentID() const noexcept { return m_ComponentID; }
/**
* Updates the component in the game loop
* @param deltaTime time passed since last update
@@ -70,4 +72,11 @@ protected:
* The entity that owns this component
*/
Entity* m_Parent;
// The component ID, this should never be changed after initialization
// This is used in various different ways
// 1. To identify which entry this component is in its corresponding table
// 2. To mark that an Entity should have the component with no database entry (it will be 0 in this case)
// 3. The component exists implicitly due to design (CollectibleComponent always has a DestructibleComponent accompanying it). In this case the ID will be -1.
const int32_t m_ComponentID;
};

View File

@@ -17,7 +17,7 @@
#include "StringifiedEnum.h"
#include "Amf3.h"
ControllablePhysicsComponent::ControllablePhysicsComponent(Entity* entity, int32_t componentId) : PhysicsComponent(entity, componentId) {
ControllablePhysicsComponent::ControllablePhysicsComponent(Entity* entity, const int32_t componentID) : PhysicsComponent(entity, componentID) {
RegisterMsg(MessageType::Game::GET_OBJECT_REPORT_INFO, this, &ControllablePhysicsComponent::OnGetObjectReportInfo);
m_Velocity = {};

View File

@@ -25,7 +25,7 @@ class ControllablePhysicsComponent : public PhysicsComponent {
public:
static constexpr eReplicaComponentType ComponentType = eReplicaComponentType::CONTROLLABLE_PHYSICS;
ControllablePhysicsComponent(Entity* entity, int32_t componentId);
ControllablePhysicsComponent(Entity* entity, const int32_t componentID);
~ControllablePhysicsComponent() override;
void Update(float deltaTime) override;

View File

@@ -3,6 +3,9 @@
#include "Logger.h"
#include "Game.h"
#include "dConfig.h"
#include "CDLootMatrixTable.h"
#include "CDLootTableTable.h"
#include "CDRarityTableTable.h"
#include "Amf3.h"
#include "AmfSerialize.h"
@@ -37,13 +40,14 @@
#include "eMissionTaskType.h"
#include "eStateChangeType.h"
#include "eGameActivity.h"
#include <ranges>
#include "CDComponentsRegistryTable.h"
Implementation<bool, const Entity*> DestroyableComponent::IsEnemyImplentation;
Implementation<bool, const Entity*> DestroyableComponent::IsFriendImplentation;
DestroyableComponent::DestroyableComponent(Entity* parent) : Component(parent) {
DestroyableComponent::DestroyableComponent(Entity* parent, const int32_t componentID) : Component(parent, componentID) {
using namespace GameMessages;
m_iArmor = 0;
m_fMaxArmor = 0.0f;
@@ -666,11 +670,6 @@ void DestroyableComponent::Damage(uint32_t damage, const LWOOBJID source, uint32
return;
}
//check if hardcore mode is enabled
if (Game::entityManager->GetHardcoreMode()) {
DoHardcoreModeDrops(source);
}
Smash(source, eKillType::VIOLENT, u"", skillID);
}
@@ -698,6 +697,13 @@ void DestroyableComponent::NotifySubscribers(Entity* attacker, uint32_t damage)
}
void DestroyableComponent::Smash(const LWOOBJID source, const eKillType killType, const std::u16string& deathType, uint32_t skillID) {
if (m_IsDead) return;
//check if hardcore mode is enabled
if (Game::entityManager->GetHardcoreMode()) {
DoHardcoreModeDrops(source);
}
if (m_iHealth > 0) {
SetArmor(0);
SetHealth(0);
@@ -705,6 +711,7 @@ void DestroyableComponent::Smash(const LWOOBJID source, const eKillType killType
Game::entityManager->SerializeEntity(m_Parent);
}
m_IsDead = true;
m_KillerID = source;
auto* owner = Game::entityManager->GetEntity(source);
@@ -752,40 +759,11 @@ void DestroyableComponent::Smash(const LWOOBJID source, const eKillType killType
//NANI?!
if (!isPlayer) {
if (owner != nullptr) {
auto* team = TeamManager::Instance()->GetTeam(owner->GetObjectID());
if (team != nullptr && m_Parent->GetComponent<BaseCombatAIComponent>() != nullptr) {
LWOOBJID specificOwner = LWOOBJID_EMPTY;
auto* scriptedActivityComponent = m_Parent->GetComponent<ScriptedActivityComponent>();
uint32_t teamSize = team->members.size();
uint32_t lootMatrixId = GetLootMatrixID();
if (scriptedActivityComponent) {
lootMatrixId = scriptedActivityComponent->GetLootMatrixForTeamSize(teamSize);
}
if (team->lootOption == 0) { // Round robin
specificOwner = TeamManager::Instance()->GetNextLootOwner(team);
auto* member = Game::entityManager->GetEntity(specificOwner);
if (member) Loot::DropLoot(member, m_Parent->GetObjectID(), lootMatrixId, GetMinCoins(), GetMaxCoins());
} else {
for (const auto memberId : team->members) { // Free for all
auto* member = Game::entityManager->GetEntity(memberId);
if (member == nullptr) continue;
Loot::DropLoot(member, m_Parent->GetObjectID(), lootMatrixId, GetMinCoins(), GetMaxCoins());
}
}
} else { // drop loot for non team user
Loot::DropLoot(owner, m_Parent->GetObjectID(), GetLootMatrixID(), GetMinCoins(), GetMaxCoins());
}
Loot::DropLoot(owner, m_Parent->GetObjectID(), GetLootMatrixID(), GetMinCoins(), GetMaxCoins());
}
} else {
//Check if this zone allows coin drops
if (Game::zoneManager->GetPlayerLoseCoinOnDeath()) {
if (Game::zoneManager->GetPlayerLoseCoinOnDeath() && !Game::entityManager->GetHardcoreMode()) {
auto* character = m_Parent->GetCharacter();
uint64_t coinsTotal = character->GetCoins();
const uint64_t minCoinsToLose = Game::zoneManager->GetWorldConfig().coinsLostOnDeathMin;
@@ -798,7 +776,15 @@ void DestroyableComponent::Smash(const LWOOBJID source, const eKillType killType
coinsTotal -= coinsToLose;
Loot::DropLoot(m_Parent, m_Parent->GetObjectID(), -1, coinsToLose, coinsToLose);
GameMessages::DropClientLoot lootMsg{};
lootMsg.target = m_Parent->GetObjectID();
lootMsg.ownerID = m_Parent->GetObjectID();
lootMsg.currency = coinsToLose;
lootMsg.spawnPos = m_Parent->GetPosition();
lootMsg.sourceID = source;
lootMsg.item = LOT_NULL;
lootMsg.Send();
lootMsg.Send(m_Parent->GetSystemAddress());
character->SetCoins(coinsTotal, eLootSourceType::PICKUP);
}
}
@@ -981,7 +967,8 @@ void DestroyableComponent::DoHardcoreModeDrops(const LWOOBJID source) {
auto* character = m_Parent->GetComponent<CharacterComponent>();
auto uscore = character->GetUScore();
auto uscoreToLose = uscore * (Game::entityManager->GetHardcoreLoseUscoreOnDeathPercent() / 100);
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);
GameMessages::SendModifyLEGOScore(m_Parent, m_Parent->GetSystemAddress(), -uscoreToLose, eLootSourceType::MISSION);
@@ -995,13 +982,18 @@ void DestroyableComponent::DoHardcoreModeDrops(const LWOOBJID source) {
if (items) {
auto itemMap = items->GetItems();
if (!itemMap.empty()) {
for (const auto& item : itemMap) {
//drop the item:
if (!item.second) continue;
// don't drop the thinkng cap
if (item.second->GetLot() == 6086) continue;
GameMessages::SendDropClientLoot(m_Parent, source, item.second->GetLot(), 0, m_Parent->GetPosition(), item.second->GetCount());
item.second->SetCount(0, false, false);
for (const auto item : itemMap | std::views::values) {
// Don't drop excluded items or null ones
if (!item || Game::entityManager->GetHardcoreExcludedItemDrops().contains(item->GetLot())) continue;
GameMessages::DropClientLoot lootMsg{};
lootMsg.target = m_Parent->GetObjectID();
lootMsg.ownerID = m_Parent->GetObjectID();
lootMsg.sourceID = m_Parent->GetObjectID();
lootMsg.item = item->GetLot();
lootMsg.count = 1;
lootMsg.spawnPos = m_Parent->GetPosition();
for (int i = 0; i < item->GetCount(); i++) Loot::DropItem(*m_Parent, lootMsg);
item->SetCount(0, false, false);
}
Game::entityManager->SerializeEntity(m_Parent);
}
@@ -1012,32 +1004,55 @@ void DestroyableComponent::DoHardcoreModeDrops(const LWOOBJID source) {
//get character:
auto* chars = m_Parent->GetCharacter();
if (chars) {
auto coins = chars->GetCoins();
auto oldCoins = chars->GetCoins();
// Floor this so there arent coins generated from rounding
auto coins = static_cast<uint64_t>(oldCoins * Game::entityManager->GetHardcoreCoinKeep());
auto coinsToDrop = oldCoins - coins;
LOG("Player had %llu coins, will lose %i coins to have %i", oldCoins, coinsToDrop, coins);
//lose all coins:
chars->SetCoins(0, eLootSourceType::NONE);
chars->SetCoins(coins, eLootSourceType::NONE);
//drop all coins:
GameMessages::SendDropClientLoot(m_Parent, source, LOT_NULL, coins, m_Parent->GetPosition());
constexpr auto MAX_TO_DROP_PER_GM = 100'000;
GameMessages::DropClientLoot lootMsg{};
lootMsg.target = m_Parent->GetObjectID();
lootMsg.ownerID = m_Parent->GetObjectID();
lootMsg.spawnPos = m_Parent->GetPosition();
lootMsg.sourceID = source;
lootMsg.item = LOT_NULL;
lootMsg.Send();
lootMsg.Send(m_Parent->GetSystemAddress());
while (coinsToDrop > MAX_TO_DROP_PER_GM) {
LOG("Dropping 100,000, %llu left", coinsToDrop);
lootMsg.currency = 100'000;
lootMsg.Send();
lootMsg.Send(m_Parent->GetSystemAddress());
coinsToDrop -= 100'000;
}
lootMsg.currency = coinsToDrop;
lootMsg.Send();
lootMsg.Send(m_Parent->GetSystemAddress());
}
// Reload the player since we can't normally reduce uscore from the server and we want the UI to update
// do this last so we don't get killed.... again
Game::entityManager->DestructEntity(m_Parent);
Game::entityManager->ConstructEntity(m_Parent);
return;
}
//award the player some u-score:
auto* player = Game::entityManager->GetEntity(source);
if (player && player->IsPlayer()) {
const auto lot = m_Parent->GetLOT();
auto* playerStats = player->GetComponent<CharacterComponent>();
if (playerStats) {
if (playerStats && GetMaxHealth() > 0 && !Game::entityManager->GetHardcoreUscoreExcludedEnemies().contains(lot)) {
//get the maximum health from this enemy:
auto maxHealth = GetMaxHealth();
const auto uscoreMultiplier = Game::entityManager->GetHardcoreUscoreEnemiesMultiplier();
const bool isUscoreReducedLot =
Game::entityManager->GetHardcoreUscoreReducedLots().contains(lot) ||
Game::entityManager->GetHardcoreUscoreReduced();
const auto uscoreReduction = isUscoreReducedLot ? Game::entityManager->GetHardcoreUscoreReduction() : 1.0f;
int uscore = maxHealth * Game::entityManager->GetHardcoreUscoreEnemiesMultiplier();
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);
GameMessages::SendModifyLEGOScore(player, player->GetSystemAddress(), uscore, eLootSourceType::MISSION);
@@ -1048,38 +1063,89 @@ void DestroyableComponent::DoHardcoreModeDrops(const LWOOBJID source) {
bool DestroyableComponent::OnGetObjectReportInfo(GameMessages::GameMsg& msg) {
auto& reportInfo = static_cast<GameMessages::GetObjectReportInfo&>(msg);
auto& destroyableInfo = reportInfo.info->PushDebug("Destroyable");
destroyableInfo.PushDebug<AMFIntValue>("Health") = m_iHealth;
destroyableInfo.PushDebug<AMFDoubleValue>("Max Health") = m_fMaxHealth;
destroyableInfo.PushDebug<AMFIntValue>("Armor") = m_iArmor;
destroyableInfo.PushDebug<AMFDoubleValue>("Max Armor") = m_fMaxArmor;
destroyableInfo.PushDebug<AMFIntValue>("Imagination") = m_iImagination;
destroyableInfo.PushDebug<AMFDoubleValue>("Max Imagination") = m_fMaxImagination;
destroyableInfo.PushDebug<AMFIntValue>("Damage To Absorb") = m_DamageToAbsorb;
destroyableInfo.PushDebug<AMFBoolValue>("Is GM Immune") = m_IsGMImmune;
destroyableInfo.PushDebug<AMFBoolValue>("Is Shielded") = m_IsShielded;
destroyableInfo.PushDebug<AMFIntValue>("DestructibleComponent DB Table Template ID") = m_ComponentID;
if (m_CurrencyIndex == -1) {
destroyableInfo.PushDebug<AMFBoolValue>("Has Loot Currency") = false;
} else {
destroyableInfo.PushDebug<AMFIntValue>("Loot Currency ID") = m_CurrencyIndex;
auto& detailedCoinInfo = destroyableInfo.PushDebug("Coin Info");
detailedCoinInfo.PushDebug<AMFIntValue>("Min Coins") = m_MinCoins;
detailedCoinInfo.PushDebug<AMFIntValue>("Max Coins") = m_MaxCoins;
}
if (m_LootMatrixID == -1 || m_LootMatrixID == 0) {
destroyableInfo.PushDebug<AMFBoolValue>("Has Loot Matrix") = false;
} else {
auto& lootInfo = destroyableInfo.PushDebug("Loot Info");
lootInfo.PushDebug<AMFIntValue>("Loot Matrix ID") = m_LootMatrixID;
auto* const componentsRegistryTable = CDClientManager::GetTable<CDComponentsRegistryTable>();
auto* const itemComponentTable = CDClientManager::GetTable<CDItemComponentTable>();
auto* const lootMatrixTable = CDClientManager::GetTable<CDLootMatrixTable>();
auto* const lootTableTable = CDClientManager::GetTable<CDLootTableTable>();
auto* const rarityTableTable = CDClientManager::GetTable<CDRarityTableTable>();
const auto& matrix = lootMatrixTable->GetMatrix(m_LootMatrixID);
for (const auto& entry : matrix) {
auto& thisEntry = lootInfo.PushDebug("Loot table Index - " + std::to_string(entry.LootTableIndex));
thisEntry.PushDebug<AMFDoubleValue>("Percent chance to drop") = entry.percent * 100.0f;
thisEntry.PushDebug<AMFDoubleValue>("Minimum amount to drop") = entry.minToDrop;
thisEntry.PushDebug<AMFDoubleValue>("Maximum amount to drop") = entry.maxToDrop;
const auto& lootTable = lootTableTable->GetTable(entry.LootTableIndex);
const auto& rarityTable = rarityTableTable->GetRarityTable(entry.RarityTableIndex);
auto& thisRarity = thisEntry.PushDebug("Rarity");
for (const auto& rarity : rarityTable) {
thisRarity.PushDebug<AMFDoubleValue>("Rarity " + std::to_string(rarity.rarity)) = rarity.randmax;
}
auto& thisItems = thisEntry.PushDebug("Drop(s) Info");
for (const auto& loot : lootTable) {
uint32_t itemComponentId = componentsRegistryTable->GetByIDAndType(loot.itemid, eReplicaComponentType::ITEM);
uint32_t rarity = itemComponentTable->GetItemComponentByID(itemComponentId).rarity;
auto title = "%[Objects_" + std::to_string(loot.itemid) + "_name] " + std::to_string(loot.itemid);
if (loot.MissionDrop) title += " - Mission Drop";
thisItems.PushDebug(title);
}
}
}
auto* const entity = Game::entityManager->GetEntity(reportInfo.clientID);
destroyableInfo.PushDebug<AMFBoolValue>("Is on your team") = entity ? IsFriend(entity) : false;
auto& stats = destroyableInfo.PushDebug("Statistics");
stats.PushDebug<AMFIntValue>("Health") = m_iHealth;
stats.PushDebug<AMFDoubleValue>("Maximum Health") = m_fMaxHealth;
stats.PushDebug<AMFIntValue>("Armor") = m_iArmor;
stats.PushDebug<AMFDoubleValue>("Maximum Armor") = m_fMaxArmor;
stats.PushDebug<AMFIntValue>("Imagination") = m_iImagination;
stats.PushDebug<AMFDoubleValue>("Maximum Imagination") = m_fMaxImagination;
stats.PushDebug<AMFIntValue>("Damage Absorption Points") = m_DamageToAbsorb;
stats.PushDebug<AMFBoolValue>("Is GM Immune") = m_IsGMImmune;
stats.PushDebug<AMFBoolValue>("Is Shielded") = m_IsShielded;
destroyableInfo.PushDebug<AMFIntValue>("Attacks To Block") = m_AttacksToBlock;
destroyableInfo.PushDebug<AMFIntValue>("Damage Reduction") = m_DamageReduction;
auto& factions = destroyableInfo.PushDebug("Factions");
size_t i = 0;
std::stringstream factionsStream;
for (const auto factionID : m_FactionIDs) {
factions.PushDebug<AMFStringValue>(std::to_string(i++) + " " + std::to_string(factionID)) = "";
factionsStream << factionID << " ";
}
auto& enemyFactions = destroyableInfo.PushDebug("Enemy Factions");
i = 0;
destroyableInfo.PushDebug<AMFStringValue>("Factions") = factionsStream.str();
factionsStream.str("");
for (const auto enemyFactionID : m_EnemyFactionIDs) {
enemyFactions.PushDebug<AMFStringValue>(std::to_string(i++) + " " + std::to_string(enemyFactionID)) = "";
factionsStream << enemyFactionID << " ";
}
destroyableInfo.PushDebug<AMFBoolValue>("Is Smashable") = m_IsSmashable;
destroyableInfo.PushDebug<AMFBoolValue>("Is Dead") = m_IsDead;
destroyableInfo.PushDebug<AMFStringValue>("Enemy Factions") = factionsStream.str();
destroyableInfo.PushDebug<AMFBoolValue>("Is A Smashable") = m_IsSmashable;
destroyableInfo.PushDebug<AMFBoolValue>("Is Smashed") = m_IsSmashed;
destroyableInfo.PushDebug<AMFBoolValue>("Is Module Assembly") = m_IsModuleAssembly;
destroyableInfo.PushDebug<AMFDoubleValue>("Explode Factor") = m_ExplodeFactor;
destroyableInfo.PushDebug<AMFBoolValue>("Has Threats") = m_HasThreats;
destroyableInfo.PushDebug<AMFIntValue>("Loot Matrix ID") = m_LootMatrixID;
destroyableInfo.PushDebug<AMFIntValue>("Min Coins") = m_MinCoins;
destroyableInfo.PushDebug<AMFIntValue>("Max Coins") = m_MaxCoins;
destroyableInfo.PushDebug<AMFStringValue>("Killer ID") = std::to_string(m_KillerID);
// "Scripts"; idk what to do about scripts yet
@@ -1094,7 +1160,25 @@ bool DestroyableComponent::OnGetObjectReportInfo(GameMessages::GameMsg& msg) {
immuneCounts.PushDebug<AMFIntValue>("Quickbuild Interrupt") = m_ImmuneToQuickbuildInterruptCount;
immuneCounts.PushDebug<AMFIntValue>("Pull To Point") = m_ImmuneToPullToPointCount;
destroyableInfo.PushDebug<AMFIntValue>("Death Behavior") = m_DeathBehavior;
auto& deathInfo = destroyableInfo.PushDebug("Death Info");
deathInfo.PushDebug<AMFBoolValue>("Is Dead") = m_IsDead;
switch (m_DeathBehavior) {
case 0:
deathInfo.PushDebug<AMFStringValue>("Death Behavior") = "Fade";
break;
case 1:
deathInfo.PushDebug<AMFStringValue>("Death Behavior") = "Stay";
break;
case 2:
deathInfo.PushDebug<AMFStringValue>("Death Behavior") = "Immediate";
break;
case -1:
deathInfo.PushDebug<AMFStringValue>("Death Behavior") = "Invulnerable";
break;
default:
deathInfo.PushDebug<AMFStringValue>("Death Behavior") = "Other";
break;
}
destroyableInfo.PushDebug<AMFDoubleValue>("Damage Cooldown Timer") = m_DamageCooldownTimer;
return true;

View File

@@ -26,7 +26,7 @@ class DestroyableComponent final : public Component {
public:
static constexpr eReplicaComponentType ComponentType = eReplicaComponentType::DESTROYABLE;
DestroyableComponent(Entity* parentEntity);
DestroyableComponent(Entity* parentEntity, const int32_t componentID);
~DestroyableComponent() override;
void Update(float deltaTime) override;
@@ -370,6 +370,8 @@ public:
*/
uint32_t GetLootMatrixID() const { return m_LootMatrixID; }
void SetCurrencyIndex(int32_t currencyIndex) { m_CurrencyIndex = currencyIndex; }
/**
* Returns the ID of the entity that killed this entity, if any
* @return the ID of the entity that killed this entity, if any
@@ -471,6 +473,8 @@ public:
bool OnGetObjectReportInfo(GameMessages::GameMsg& msg);
bool OnSetFaction(GameMessages::GameMsg& msg);
void SetIsDead(const bool value) { m_IsDead = value; }
static Implementation<bool, const Entity*> IsEnemyImplentation;
static Implementation<bool, const Entity*> IsFriendImplentation;
@@ -585,6 +589,9 @@ private:
*/
uint32_t m_LootMatrixID;
// The currency index to determine how much loot to drop
int32_t m_CurrencyIndex{ -1 };
/**
* The min amount of coins that will drop when this entity is smashed
*/

View File

@@ -1,7 +1,7 @@
#include "DonationVendorComponent.h"
#include "Database.h"
DonationVendorComponent::DonationVendorComponent(Entity* parent) : VendorComponent(parent) {
DonationVendorComponent::DonationVendorComponent(Entity* parent, const int32_t componentID) : VendorComponent(parent, componentID) {
//LoadConfigData
m_PercentComplete = 0.0;
m_TotalDonated = 0;

View File

@@ -9,7 +9,7 @@ class Entity;
class DonationVendorComponent final : public VendorComponent {
public:
static constexpr eReplicaComponentType ComponentType = eReplicaComponentType::DONATION_VENDOR;
DonationVendorComponent(Entity* parent);
DonationVendorComponent(Entity* parent, const int32_t componentID);
void Serialize(RakNet::BitStream& outBitStream, bool bIsInitialUpdate) override;
uint32_t GetActivityID() {return m_ActivityId;};
void SubmitDonation(uint32_t count);

View File

@@ -1,9 +1,14 @@
#include "GhostComponent.h"
GhostComponent::GhostComponent(Entity* parent) : Component(parent) {
#include "Amf3.h"
#include "GameMessages.h"
GhostComponent::GhostComponent(Entity* parent, const int32_t componentID) : Component(parent, componentID) {
m_GhostReferencePoint = NiPoint3Constant::ZERO;
m_GhostOverridePoint = NiPoint3Constant::ZERO;
m_GhostOverride = false;
RegisterMsg<GameMessages::GetObjectReportInfo>(this, &GhostComponent::MsgGetObjectReportInfo);
}
GhostComponent::~GhostComponent() {
@@ -55,3 +60,12 @@ bool GhostComponent::IsObserved(LWOOBJID id) {
void GhostComponent::GhostEntity(LWOOBJID id) {
m_ObservedEntities.erase(id);
}
bool GhostComponent::MsgGetObjectReportInfo(GameMessages::GameMsg& msg) {
auto& reportMsg = static_cast<GameMessages::GetObjectReportInfo&>(msg);
auto& cmptType = reportMsg.info->PushDebug("Ghost");
cmptType.PushDebug<AMFIntValue>("Component ID") = GetComponentID();
cmptType.PushDebug<AMFBoolValue>("Is GM Invis") = false;
return true;
}

View File

@@ -10,7 +10,7 @@ class NiPoint3;
class GhostComponent final : public Component {
public:
static inline const eReplicaComponentType ComponentType = eReplicaComponentType::GHOST;
GhostComponent(Entity* parent);
GhostComponent(Entity* parent, const int32_t componentID);
~GhostComponent() override;
void SetGhostOverride(bool value) { m_GhostOverride = value; };
@@ -39,6 +39,8 @@ public:
void GhostEntity(const LWOOBJID id);
bool MsgGetObjectReportInfo(GameMessages::GameMsg& msg);
private:
NiPoint3 m_GhostReferencePoint;

View File

@@ -2,7 +2,7 @@
#include "EntityManager.h"
#include "Amf3.h"
HavokVehiclePhysicsComponent::HavokVehiclePhysicsComponent(Entity* parent, int32_t componentId) : PhysicsComponent(parent, componentId) {
HavokVehiclePhysicsComponent::HavokVehiclePhysicsComponent(Entity* parent, const int32_t componentID) : PhysicsComponent(parent, componentID) {
RegisterMsg(MessageType::Game::GET_OBJECT_REPORT_INFO, this, &HavokVehiclePhysicsComponent::OnGetObjectReportInfo);
m_Velocity = NiPoint3Constant::ZERO;

View File

@@ -13,7 +13,7 @@ class HavokVehiclePhysicsComponent : public PhysicsComponent {
public:
static constexpr eReplicaComponentType ComponentType = eReplicaComponentType::HAVOK_VEHICLE_PHYSICS;
HavokVehiclePhysicsComponent(Entity* parentEntity, int32_t componentId);
HavokVehiclePhysicsComponent(Entity* parentEntity, const int32_t componentID);
void Serialize(RakNet::BitStream& outBitStream, bool bIsInitialUpdate) override;

View File

@@ -39,10 +39,13 @@
#include "CDObjectSkillsTable.h"
#include "CDSkillBehaviorTable.h"
#include "StringifiedEnum.h"
#include "Amf3.h"
#include <ranges>
InventoryComponent::InventoryComponent(Entity* parent) : Component(parent) {
InventoryComponent::InventoryComponent(Entity* parent, const int32_t componentID) : Component(parent, componentID) {
using namespace GameMessages;
RegisterMsg<GetObjectReportInfo>(this, &InventoryComponent::OnGetObjectReportInfo);
this->m_Dirty = true;
this->m_Equipped = {};
this->m_Pushed = {};
@@ -279,7 +282,14 @@ void InventoryComponent::AddItem(
case 1:
for (size_t i = 0; i < size; i++) {
GameMessages::SendDropClientLoot(this->m_Parent, this->m_Parent->GetObjectID(), lot, 0, this->m_Parent->GetPosition(), 1);
GameMessages::DropClientLoot lootMsg{};
lootMsg.target = m_Parent->GetObjectID();
lootMsg.ownerID = m_Parent->GetObjectID();
lootMsg.sourceID = m_Parent->GetObjectID();
lootMsg.item = lot;
lootMsg.count = 1;
lootMsg.spawnPos = m_Parent->GetPosition();
Loot::DropItem(*m_Parent, lootMsg);
}
break;
@@ -440,7 +450,7 @@ Item* InventoryComponent::FindItemBySubKey(LWOOBJID id, eInventoryType inventory
}
}
bool InventoryComponent::HasSpaceForLoot(const std::unordered_map<LOT, int32_t>& loot) {
bool InventoryComponent::HasSpaceForLoot(const Loot::Return& loot) {
std::unordered_map<eInventoryType, int32_t> spaceOffset{};
uint32_t slotsNeeded = 0;
@@ -626,7 +636,8 @@ void InventoryComponent::UpdateXml(tinyxml2::XMLDocument& document) {
for (const auto& pair : this->m_Inventories) {
auto* inventory = pair.second;
if (inventory->GetType() == VENDOR_BUYBACK || inventory->GetType() == eInventoryType::MODELS_IN_BBB) {
static const auto EXCLUDED_INVENTORIES = { VENDOR_BUYBACK, MODELS_IN_BBB, ITEM_SETS };
if (std::ranges::find(EXCLUDED_INVENTORIES, inventory->GetType()) != EXCLUDED_INVENTORIES.end()) {
continue;
}
@@ -1786,3 +1797,105 @@ void InventoryComponent::LoadGroupXml(const tinyxml2::XMLElement& groups) {
groupElement = groupElement->NextSiblingElement("grp");
}
}
void InventoryComponent::RegenerateItemIDs() {
for (auto* const inventory : m_Inventories | std::views::values) {
inventory->RegenerateItemIDs();
}
}
std::string DebugInvToString(const eInventoryType inv, bool verbose) {
switch (inv) {
case ITEMS:
return "Backpack";
case VAULT_ITEMS:
return "Bank";
case BRICKS:
return verbose ? "Bricks" : "Bricks (contents only shown in high-detail report)";
case MODELS_IN_BBB:
return "Models in BBB";
case TEMP_ITEMS:
return "Temp Equip";
case MODELS:
return verbose ? "Model" : "Model (contents only shown in high-detail report)";
case TEMP_MODELS:
return "Module";
case BEHAVIORS:
return "B3 Behavior";
case PROPERTY_DEEDS:
return "Property";
case BRICKS_IN_BBB:
return "Brick In BBB";
case VENDOR:
return "Vendor";
case VENDOR_BUYBACK:
return "BuyBack";
case QUEST:
return "Quest";
case DONATION:
return "Donation";
case VAULT_MODELS:
return "Bank Model";
case ITEM_SETS:
return "Bank Behavior";
case INVALID:
return "Invalid";
case ALL:
return "All";
}
return "";
}
bool InventoryComponent::OnGetObjectReportInfo(GameMessages::GameMsg& msg) {
auto& report = static_cast<GameMessages::GetObjectReportInfo&>(msg);
auto& cmpt = report.info->PushDebug("Inventory");
cmpt.PushDebug<AMFIntValue>("Component ID") = GetComponentID();
uint32_t numItems = 0;
for (auto* inventory : m_Inventories | std::views::values) numItems += inventory->GetItems().size();
cmpt.PushDebug<AMFIntValue>("Inventory Item Count") = numItems;
auto& itemsInBags = cmpt.PushDebug("Items in bags");
for (const auto& [id, inventoryMut] : m_Inventories) {
if (!inventoryMut) continue;
const auto* const inventory = inventoryMut;
auto& curInv = itemsInBags.PushDebug(DebugInvToString(id, report.bVerbose) + " - " + std::to_string(id));
for (uint32_t i = 0; i < inventory->GetSize(); i++) {
const auto* const item = inventory->FindItemBySlot(i);
if (!item) continue;
std::stringstream ss;
ss << "%[Objects_" << item->GetLot() << "_name] Slot " << item->GetSlot();
auto& slot = curInv.PushDebug(ss.str());
slot.PushDebug<AMFStringValue>("Object ID") = std::to_string(item->GetId());
slot.PushDebug<AMFIntValue>("LOT") = item->GetLot();
if (item->GetSubKey() != LWOOBJID_EMPTY) slot.PushDebug<AMFStringValue>("Subkey") = std::to_string(item->GetSubKey());
slot.PushDebug<AMFIntValue>("Count") = item->GetCount();
slot.PushDebug<AMFIntValue>("Slot") = item->GetSlot();
slot.PushDebug<AMFBoolValue>("Bind on pickup") = item->GetInfo().isBOP;
slot.PushDebug<AMFBoolValue>("Bind on equip") = item->GetInfo().isBOE;
slot.PushDebug<AMFBoolValue>("Is currently bound") = item->GetBound();
auto& extra = slot.PushDebug("Extra Info");
for (const auto* const setting : item->GetConfig()) {
if (setting) extra.PushDebug<AMFStringValue>(GeneralUtils::UTF16ToWTF8(setting->GetKey())) = setting->GetValueAsString();
}
}
}
auto& equipped = cmpt.PushDebug("Equipped Items");
for (const auto& [location, info] : GetEquippedItems()) {
std::stringstream ss;
ss << "%[Objects_" << info.lot << "_name]";
auto& equipSlot = equipped.PushDebug(ss.str());
equipSlot.PushDebug<AMFStringValue>("Location") = location;
equipSlot.PushDebug<AMFStringValue>("Object ID") = std::to_string(info.id);
equipSlot.PushDebug<AMFIntValue>("Slot") = info.slot;
equipSlot.PushDebug<AMFIntValue>("Count") = info.count;
auto& extra = equipSlot.PushDebug("Extra Info");
for (const auto* const setting : info.config) {
if (setting) extra.PushDebug<AMFStringValue>(GeneralUtils::UTF16ToWTF8(setting->GetKey())) = setting->GetValueAsString();
}
}
return true;
}

View File

@@ -22,6 +22,7 @@
#include "eInventoryType.h"
#include "eReplicaComponentType.h"
#include "eLootSourceType.h"
#include "Loot.h"
class Entity;
class ItemSet;
@@ -67,7 +68,7 @@ public:
static constexpr uint32_t MaximumGroupCount = 50;
static constexpr eReplicaComponentType ComponentType = eReplicaComponentType::INVENTORY;
InventoryComponent(Entity* parent);
InventoryComponent(Entity* parent, const int32_t componentID);
void Update(float deltaTime) override;
void Serialize(RakNet::BitStream& outBitStream, bool bIsInitialUpdate) override;
@@ -200,7 +201,7 @@ public:
* @param loot a map of items to add and how many to add
* @return whether the entity has enough space for all the items
*/
bool HasSpaceForLoot(const std::unordered_map<LOT, int32_t>& loot);
bool HasSpaceForLoot(const Loot::Return& loot);
/**
* Equips an item in the specified slot
@@ -402,10 +403,16 @@ public:
bool SetSkill(BehaviorSlot slot, uint32_t skillId);
void UpdateGroup(const GroupUpdate& groupUpdate);
void RemoveGroup(const std::string& groupId);
std::unordered_map<LWOOBJID, DatabasePet>& GetPetsMut() { return m_Pets; };
void FixInvisibleItems();
// Used to migrate a character version, no need to call outside of that context
void RegenerateItemIDs();
bool OnGetObjectReportInfo(GameMessages::GameMsg& msg);
~InventoryComponent() override;
private:

View File

@@ -8,7 +8,7 @@ class ItemComponent final : public Component {
public:
static constexpr eReplicaComponentType ComponentType = eReplicaComponentType::ITEM;
ItemComponent(Entity* entity) : Component(entity) {}
ItemComponent(Entity* entity, const int32_t componentID) : Component(entity, componentID) {}
void Serialize(RakNet::BitStream& bitStream, bool isConstruction) override;
};

View File

@@ -16,7 +16,7 @@ class LUPExhibitComponent final : public Component
public:
static constexpr eReplicaComponentType ComponentType = eReplicaComponentType::LUP_EXHIBIT;
LUPExhibitComponent(Entity* parent) : Component(parent) {};
LUPExhibitComponent(Entity* parent, const int32_t componentID) : Component(parent, componentID) {};
void Update(float deltaTime) override;
void Serialize(RakNet::BitStream& outBitStream, bool bIsInitialUpdate) override;
void NextLUPExhibit();

View File

@@ -6,7 +6,7 @@
#include "CDRewardsTable.h"
LevelProgressionComponent::LevelProgressionComponent(Entity* parent) : Component(parent) {
LevelProgressionComponent::LevelProgressionComponent(Entity* parent, const int32_t componentID) : Component(parent, componentID) {
m_Parent = parent;
m_Level = 1;
m_SpeedBase = 500.0f;

View File

@@ -19,7 +19,7 @@ public:
* Constructor for this component
* @param parent parent that contains this component
*/
LevelProgressionComponent(Entity* parent);
LevelProgressionComponent(Entity* parent, const int32_t componentID);
void Serialize(RakNet::BitStream& outBitStream, bool bIsInitialUpdate) override;

View File

@@ -8,7 +8,7 @@ class MiniGameControlComponent final : public Component {
public:
static constexpr eReplicaComponentType ComponentType = eReplicaComponentType::MINI_GAME_CONTROL;
MiniGameControlComponent(Entity* parent) : Component(parent) {}
MiniGameControlComponent(Entity* parent, const int32_t componentID) : Component(parent, componentID) {}
void Serialize(RakNet::BitStream& outBitStream, bool isConstruction);
};

View File

@@ -1,5 +0,0 @@
#include "MinigameComponent.h"
void MinigameComponent::Serialize(RakNet::BitStream& outBitStream, bool isConstruction) {
outBitStream.Write<uint32_t>(0x40000000);
}

View File

@@ -19,14 +19,20 @@
#include "MissionPrerequisites.h"
#include "AchievementCacheKey.h"
#include "eMissionState.h"
#include "StringifiedEnum.h"
// MARK: Mission Component
std::unordered_map<AchievementCacheKey, std::vector<uint32_t>> MissionComponent::m_AchievementCache = {};
//! Initializer
MissionComponent::MissionComponent(Entity* parent) : Component(parent) {
MissionComponent::MissionComponent(Entity* parent, const int32_t componentID) : Component(parent, componentID) {
using namespace GameMessages;
m_LastUsedMissionOrderUID = Game::zoneManager->GetUniqueMissionIdStartingValue();
RegisterMsg<GetObjectReportInfo>(this, &MissionComponent::OnGetObjectReportInfo);
RegisterMsg<GameMessages::GetMissionState>(this, &MissionComponent::OnGetMissionState);
RegisterMsg<GameMessages::MissionNeedsLot>(this, &MissionComponent::OnMissionNeedsLot);
}
//! Destructor
@@ -136,6 +142,7 @@ void MissionComponent::RemoveMission(uint32_t missionId) {
}
void MissionComponent::Progress(eMissionTaskType type, int32_t value, LWOOBJID associate, const std::string& targets, int32_t count, bool ignoreAchievements) {
LOG("Progressing missions %s %i %llu %s %s", StringifiedEnum::ToString(type).data(), value, associate, targets.c_str(), ignoreAchievements ? "(ignoring achievements)" : "");
std::vector<uint32_t> acceptedAchievements;
if (count > 0 && !ignoreAchievements) {
acceptedAchievements = LookForAchievements(type, value, true, associate, targets, count);
@@ -620,3 +627,123 @@ void MissionComponent::ResetMission(const int32_t missionId) {
m_Missions.erase(missionId);
GameMessages::SendResetMissions(m_Parent, m_Parent->GetSystemAddress(), missionId);
}
void PushMissions(const std::map<uint32_t, Mission*>& missions, AMFArrayValue& V, bool verbose) {
for (const auto& [id, mission] : missions) {
std::stringstream ss;
if (!mission) {
ss << "Mission ID: " << id;
V.PushDebug(ss.str());
} else if (!verbose) {
ss << "%[Missions_" << id << "_name]" << ", Mission ID";
V.PushDebug<AMFIntValue>(ss.str()) = id;
} else {
ss << "%[Missions_" << id << "_name]" << ", Mission ID: " << id;
auto& missionV = V.PushDebug(ss.str());
auto& missionInformation = missionV.PushDebug("Mission Information");
if (mission->IsComplete()) {
missionInformation.PushDebug<AMFStringValue>("Time mission last completed") = std::to_string(mission->GetTimestamp());
missionInformation.PushDebug<AMFIntValue>("Number of times completed") = mission->GetCompletions();
}
// Expensive to network this especially when its read from the client anyways
// missionInformation.PushDebug("Description").PushDebug("None");
// missionInformation.PushDebug("Text").PushDebug("None");
auto& statusInfo = missionInformation.PushDebug("Mission statuses for local player");
if (mission->IsAvalible()) statusInfo.PushDebug("Available");
if (mission->IsActive()) statusInfo.PushDebug("Active");
if (mission->IsReadyToComplete()) statusInfo.PushDebug("Ready To Complete");
if (mission->IsComplete()) statusInfo.PushDebug("Completed");
if (mission->IsFailed()) statusInfo.PushDebug("Failed");
const auto& clientInfo = mission->GetClientInfo();
statusInfo.PushDebug<AMFBoolValue>("Is an achievement mission") = mission->IsAchievement();
statusInfo.PushDebug<AMFBoolValue>("Is an timed mission") = clientInfo.time_limit > 0;
auto& taskInfo = statusInfo.PushDebug("Task Info");
taskInfo.PushDebug<AMFIntValue>("Number of tasks in this mission") = mission->GetTasks().size();
int32_t i = 0;
for (const auto* task : mission->GetTasks()) {
auto& thisTask = taskInfo.PushDebug("Task " + std::to_string(i));
// Expensive to network this especially when its read from the client anyways
// thisTask.PushDebug("Description").PushDebug("%[MissionTasks_" + taskUidStr + "_description]");
thisTask.PushDebug<AMFIntValue>("Number done") = std::min(task->GetProgress(), static_cast<uint32_t>(task->GetClientInfo().targetValue));
thisTask.PushDebug<AMFIntValue>("Number total needed") = task->GetClientInfo().targetValue;
thisTask.PushDebug<AMFIntValue>("Task Type") = task->GetClientInfo().taskType;
i++;
}
// auto& chatText = missionInformation.PushDebug("Chat Text for Mission States");
// Expensive to network this especially when its read from the client anyways
// chatText.PushDebug("Available Text").PushDebug("%[MissionText_" + idStr + "_chat_state_1]");
// chatText.PushDebug("Active Text").PushDebug("%[MissionText_" + idStr + "_chat_state_2]");
// chatText.PushDebug("Ready-to-Complete Text").PushDebug("%[MissionText_" + idStr + "_chat_state_3]");
// chatText.PushDebug("Complete Text").PushDebug("%[MissionText_" + idStr + "_chat_state_4]");
if (clientInfo.time_limit > 0) {
missionInformation.PushDebug<AMFIntValue>("Time Limit") = clientInfo.time_limit;
missionInformation.PushDebug<AMFDoubleValue>("Time Remaining") = 0;
}
if (clientInfo.offer_objectID != -1) {
missionInformation.PushDebug<AMFIntValue>("Offer Object LOT") = clientInfo.offer_objectID;
}
if (clientInfo.target_objectID != -1) {
missionInformation.PushDebug<AMFIntValue>("Complete Object LOT") = clientInfo.target_objectID;
}
if (!clientInfo.prereqMissionID.empty()) {
missionInformation.PushDebug<AMFStringValue>("Requirement Mission IDs") = clientInfo.prereqMissionID;
}
missionInformation.PushDebug<AMFBoolValue>("Is Repeatable") = clientInfo.repeatable;
const bool hasNoOfferer = clientInfo.offer_objectID == -1 || clientInfo.offer_objectID == 0;
const bool hasNoCompleter = clientInfo.target_objectID == -1 || clientInfo.target_objectID == 0;
missionInformation.PushDebug<AMFBoolValue>("Is Achievement") = hasNoOfferer && hasNoCompleter;
}
}
}
bool MissionComponent::OnGetObjectReportInfo(GameMessages::GameMsg& msg) {
auto& reportMsg = static_cast<GameMessages::GetObjectReportInfo&>(msg);
auto& missionInfo = reportMsg.info->PushDebug("Mission (Laggy)");
missionInfo.PushDebug<AMFIntValue>("Component ID") = GetComponentID();
// Sort the missions so they are easier to parse and present to the end user
std::map<uint32_t, Mission*> achievements;
std::map<uint32_t, Mission*> missions;
std::map<uint32_t, Mission*> doneMissions;
for (const auto [id, mission] : m_Missions) {
if (!mission) continue;
else if (mission->IsComplete()) doneMissions[id] = mission;
else if (mission->IsAchievement()) achievements[id] = mission;
else if (mission->IsMission()) missions[id] = mission;
}
// None of these should be empty, but if they are dont print the field
if (!achievements.empty() || !missions.empty()) {
auto& incompleteMissions = missionInfo.PushDebug("Incomplete Missions");
PushMissions(achievements, incompleteMissions, reportMsg.bVerbose);
PushMissions(missions, incompleteMissions, reportMsg.bVerbose);
}
if (!doneMissions.empty()) {
auto& completeMissions = missionInfo.PushDebug("Completed Missions");
PushMissions(doneMissions, completeMissions, reportMsg.bVerbose);
}
return true;
}
bool MissionComponent::OnGetMissionState(GameMessages::GameMsg& msg) {
auto misState = static_cast<GameMessages::GetMissionState&>(msg);
misState.missionState = GetMissionState(misState.missionID);
return true;
}
bool MissionComponent::OnMissionNeedsLot(GameMessages::GameMsg& msg) {
const auto& needMsg = static_cast<GameMessages::MissionNeedsLot&>(msg);
return RequiresItem(needMsg.item);
}

View File

@@ -28,7 +28,7 @@ class MissionComponent final : public Component {
public:
static constexpr eReplicaComponentType ComponentType = eReplicaComponentType::MISSION;
explicit MissionComponent(Entity* parent);
explicit MissionComponent(Entity* parent, const int32_t componentID);
~MissionComponent() override;
void Serialize(RakNet::BitStream& outBitStream, bool bIsInitialUpdate, unsigned int& flags);
void LoadFromXml(const tinyxml2::XMLDocument& doc) override;
@@ -171,6 +171,9 @@ public:
void ResetMission(const int32_t missionId);
private:
bool OnGetObjectReportInfo(GameMessages::GameMsg& msg);
bool OnGetMissionState(GameMessages::GameMsg& msg);
bool OnMissionNeedsLot(GameMessages::GameMsg& msg);
/**
* All the missions owned by this entity, mapped by mission ID
*/

View File

@@ -39,19 +39,13 @@ bool OfferedMission::GetAcceptsMission() const {
//------------------------ MissionOfferComponent below ------------------------
MissionOfferComponent::MissionOfferComponent(Entity* parent, const LOT parentLot) : Component(parent) {
auto* compRegistryTable = CDClientManager::GetTable<CDComponentsRegistryTable>();
auto value = compRegistryTable->GetByIDAndType(parentLot, eReplicaComponentType::MISSION_OFFER, -1);
if (value != -1) {
const uint32_t componentId = value;
MissionOfferComponent::MissionOfferComponent(Entity* parent, const int32_t componentID) : Component(parent, componentID) {
if (componentID != -1) {
// Now lookup the missions in the MissionNPCComponent table
auto* missionNpcComponentTable = CDClientManager::GetTable<CDMissionNPCComponentTable>();
auto missions = missionNpcComponentTable->Query([=](const CDMissionNPCComponent& entry) {
return entry.id == static_cast<unsigned>(componentId);
auto missions = missionNpcComponentTable->Query([componentID](const CDMissionNPCComponent& entry) {
return entry.id == static_cast<unsigned>(componentID);
});
for (auto& mission : missions) {

View File

@@ -63,7 +63,7 @@ class MissionOfferComponent final : public Component {
public:
static constexpr eReplicaComponentType ComponentType = eReplicaComponentType::MISSION_OFFER;
MissionOfferComponent(Entity* parent, LOT parentLot);
MissionOfferComponent(Entity* parent, const int32_t componentID);
/**
* Handles the OnUse event triggered by some entity, determines which missions to show based on what they may

View File

@@ -14,7 +14,7 @@
#include "Database.h"
#include "DluAssert.h"
ModelComponent::ModelComponent(Entity* parent) : Component(parent) {
ModelComponent::ModelComponent(Entity* parent, const int32_t componentID) : Component(parent, componentID) {
using namespace GameMessages;
m_OriginalPosition = m_Parent->GetDefaultPosition();
m_OriginalRotation = m_Parent->GetDefaultRotation();
@@ -25,6 +25,7 @@ ModelComponent::ModelComponent(Entity* parent) : Component(parent) {
m_userModelID = m_Parent->GetVarAs<LWOOBJID>(u"userModelID");
RegisterMsg<RequestUse>(this, &ModelComponent::OnRequestUse);
RegisterMsg<ResetModelToDefaults>(this, &ModelComponent::OnResetModelToDefaults);
RegisterMsg<GetObjectReportInfo>(this, &ModelComponent::OnGetObjectReportInfo);
}
bool ModelComponent::OnResetModelToDefaults(GameMessages::GameMsg& msg) {
@@ -375,3 +376,19 @@ void ModelComponent::RemoveAttack() {
set.Send();
}
}
bool ModelComponent::OnGetObjectReportInfo(GameMessages::GameMsg& msg) {
auto& reportMsg = static_cast<GameMessages::GetObjectReportInfo&>(msg);
if (!reportMsg.info) return false;
auto& cmptInfo = reportMsg.info->PushDebug("Model Behaviors (Mutable)");
cmptInfo.PushDebug<AMFIntValue>("Component ID") = GetComponentID();
cmptInfo.PushDebug<AMFStringValue>("Name") = "Objects_" + std::to_string(m_Parent->GetLOT()) + "_name";
cmptInfo.PushDebug<AMFBoolValue>("Has Unique Name") = false;
cmptInfo.PushDebug<AMFStringValue>("UGID (from item)") = std::to_string(m_userModelID);
cmptInfo.PushDebug<AMFStringValue>("UGID") = std::to_string(m_userModelID);
cmptInfo.PushDebug<AMFStringValue>("Description") = "";
cmptInfo.PushDebug<AMFIntValue>("Behavior Count") = m_Behaviors.size();
return true;
}

View File

@@ -27,13 +27,14 @@ class ModelComponent final : public Component {
public:
static constexpr eReplicaComponentType ComponentType = eReplicaComponentType::MODEL;
ModelComponent(Entity* parent);
ModelComponent(Entity* parent, const int32_t componentID);
void LoadBehaviors();
void Update(float deltaTime) override;
bool OnRequestUse(GameMessages::GameMsg& msg);
bool OnResetModelToDefaults(GameMessages::GameMsg& msg);
bool OnGetObjectReportInfo(GameMessages::GameMsg& msg);
void Serialize(RakNet::BitStream& outBitStream, bool bIsInitialUpdate) override;

View File

@@ -1,6 +1,6 @@
#include "ModuleAssemblyComponent.h"
ModuleAssemblyComponent::ModuleAssemblyComponent(Entity* parent) : Component(parent) {
ModuleAssemblyComponent::ModuleAssemblyComponent(Entity* parent, const int32_t componentID) : Component(parent, componentID) {
m_SubKey = LWOOBJID_EMPTY;
m_UseOptionalParts = false;
m_AssemblyPartsLOTs = u"";

View File

@@ -14,7 +14,7 @@ class ModuleAssemblyComponent final : public Component {
public:
static constexpr eReplicaComponentType ComponentType = eReplicaComponentType::MODULE_ASSEMBLY;
ModuleAssemblyComponent(Entity* parent);
ModuleAssemblyComponent(Entity* parent, const int32_t componentID);
~ModuleAssemblyComponent() override;
void Serialize(RakNet::BitStream& outBitStream, bool bIsInitialUpdate) override;

View File

@@ -26,7 +26,7 @@ namespace {
std::map<LOT, float> m_PhysicsSpeedCache;
}
MovementAIComponent::MovementAIComponent(Entity* parent, MovementAIInfo info) : Component(parent) {
MovementAIComponent::MovementAIComponent(Entity* parent, const int32_t componentID, MovementAIInfo info) : Component(parent, componentID) {
m_Info = info;
m_AtFinalWaypoint = true;

View File

@@ -62,7 +62,7 @@ class MovementAIComponent final : public Component {
public:
static constexpr eReplicaComponentType ComponentType = eReplicaComponentType::MOVEMENT_AI;
MovementAIComponent(Entity* parentEntity, MovementAIInfo info);
MovementAIComponent(Entity* parentEntity, const int32_t componentID, MovementAIInfo info);
void SetPath(const std::string pathName);

View File

@@ -55,7 +55,7 @@ void MoverSubComponent::Serialize(RakNet::BitStream& outBitStream, bool bIsIniti
//------------- MovingPlatformComponent below --------------
MovingPlatformComponent::MovingPlatformComponent(Entity* parent, const std::string& pathName) : Component(parent) {
MovingPlatformComponent::MovingPlatformComponent(Entity* parent, const int32_t componentID, const std::string& pathName) : Component(parent, componentID) {
m_MoverSubComponentType = eMoverSubComponentType::mover;
m_MoverSubComponent = new MoverSubComponent(m_Parent->GetDefaultPosition());
m_PathName = GeneralUtils::ASCIIToUTF16(pathName);

View File

@@ -108,7 +108,7 @@ class MovingPlatformComponent final : public Component {
public:
static constexpr eReplicaComponentType ComponentType = eReplicaComponentType::MOVING_PLATFORM;
MovingPlatformComponent(Entity* parent, const std::string& pathName);
MovingPlatformComponent(Entity* parent, const int32_t componentID, const std::string& pathName);
~MovingPlatformComponent() override;
void Serialize(RakNet::BitStream& outBitStream, bool bIsInitialUpdate) override;

View File

@@ -3,7 +3,7 @@
#include "InventoryComponent.h"
#include "CharacterComponent.h"
MultiZoneEntranceComponent::MultiZoneEntranceComponent(Entity* parent) : Component(parent) {
MultiZoneEntranceComponent::MultiZoneEntranceComponent(Entity* parent, const int32_t componentID) : Component(parent, componentID) {
m_Parent = parent;
std::string zoneString = GeneralUtils::UTF16ToWTF8(m_Parent->GetVar<std::u16string>(u"MultiZoneIDs"));
std::stringstream ss(zoneString);

View File

@@ -16,7 +16,7 @@ public:
* Constructor for this component, builds the m_LUPWorlds vector
* @param parent parent that contains this component
*/
MultiZoneEntranceComponent(Entity* parent);
MultiZoneEntranceComponent(Entity* parent, const int32_t componentID);
~MultiZoneEntranceComponent() override;
/**

View File

@@ -10,9 +10,11 @@
#include "InventoryComponent.h"
#include "Item.h"
#include "MissionComponent.h"
#include "User.h"
#include "SwitchComponent.h"
#include "DestroyableComponent.h"
#include "dpWorld.h"
#include "UserManager.h"
#include "PetDigServer.h"
#include "ObjectIDManager.h"
#include "eUnequippableActiveType.h"
@@ -21,6 +23,7 @@
#include "eUseItemResponse.h"
#include "ePlayerFlag.h"
#include "GeneralUtils.h"
#include "Game.h"
#include "dConfig.h"
#include "dChatFilter.h"
@@ -43,9 +46,8 @@ std::unordered_map<LWOOBJID, LWOOBJID> PetComponent::activePets{};
* while the faction ones could be checked using their respective missions.
*/
PetComponent::PetComponent(Entity* parentEntity, uint32_t componentId) : Component{ parentEntity } {
m_PetInfo = CDClientManager::GetTable<CDPetComponentTable>()->GetByID(componentId); // TODO: Make reference when safe
m_ComponentId = componentId;
PetComponent::PetComponent(Entity* parentEntity, const int32_t componentID) : Component{ parentEntity, componentID } {
m_PetInfo = CDClientManager::GetTable<CDPetComponentTable>()->GetByID(componentID); // TODO: Make reference when safe
m_Interaction = LWOOBJID_EMPTY;
m_Owner = LWOOBJID_EMPTY;
@@ -479,10 +481,19 @@ void PetComponent::NotifyTamingBuildSuccess(NiPoint3 position) {
return;
}
LWOOBJID petSubKey = ObjectIDManager::GenerateRandomObjectID();
LWOOBJID petSubKey = ObjectIDManager::GetPersistentID();
const uint32_t maxTries = 100;
uint32_t tries = 0;
while (Database::Get()->GetPetNameInfo(petSubKey) && tries < maxTries) {
tries++;
LOG("Found a duplicate pet %llu, getting a new subKey", petSubKey);
petSubKey = ObjectIDManager::GetPersistentID();
}
GeneralUtils::SetBit(petSubKey, eObjectBits::CHARACTER);
GeneralUtils::SetBit(petSubKey, eObjectBits::PERSISTENT);
if (tries >= maxTries) {
LOG("Failed to get a unique pet subKey after %i tries, aborting pet creation for player %i", maxTries, tamer->GetCharacter() ? tamer->GetCharacter()->GetID() : -1);
return;
}
m_DatabaseId = petSubKey;
@@ -528,7 +539,7 @@ void PetComponent::NotifyTamingBuildSuccess(NiPoint3 position) {
// Triggers the catch a pet missions
constexpr auto PET_FLAG_BASE = 800;
tamer->GetCharacter()->SetPlayerFlag(PET_FLAG_BASE + m_ComponentId, true);
tamer->GetCharacter()->SetPlayerFlag(PET_FLAG_BASE + m_ComponentID, true);
auto* missionComponent = tamer->GetComponent<MissionComponent>();
@@ -545,18 +556,29 @@ void PetComponent::NotifyTamingBuildSuccess(NiPoint3 position) {
}
void PetComponent::RequestSetPetName(std::u16string name) {
const bool autoRejectNames = UserManager::Instance()->GetMuteAutoRejectNames();
if (m_Tamer == LWOOBJID_EMPTY) {
if (m_Owner != LWOOBJID_EMPTY) {
auto* owner = GetOwner();
m_ModerationStatus = 1; // Pending
m_Name = "";
// If auto reject names is on, and the user is muted, force use of predefined 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) });
GameMessages::SendSetPetName(m_Owner, GeneralUtils::UTF8ToUTF16(m_Name), m_DatabaseId, owner->GetSystemAddress());
GameMessages::SendSetPetNameModerated(m_Owner, m_DatabaseId, m_ModerationStatus, owner->GetSystemAddress());
} else {
m_ModerationStatus = 1; // Pending
m_Name = "";
//Save our pet's new name to the db:
SetPetNameForModeration(GeneralUtils::UTF16ToWTF8(name));
//Save our pet's new name to the db:
SetPetNameForModeration(GeneralUtils::UTF16ToWTF8(name));
GameMessages::SendSetPetName(m_Owner, GeneralUtils::UTF8ToUTF16(m_Name), m_DatabaseId, owner->GetSystemAddress());
GameMessages::SendSetPetNameModerated(m_Owner, m_DatabaseId, m_ModerationStatus, owner->GetSystemAddress());
GameMessages::SendSetPetName(m_Owner, GeneralUtils::UTF8ToUTF16(m_Name), m_DatabaseId, owner->GetSystemAddress());
GameMessages::SendSetPetNameModerated(m_Owner, m_DatabaseId, m_ModerationStatus, owner->GetSystemAddress());
}
}
return;
@@ -578,11 +600,21 @@ void PetComponent::RequestSetPetName(std::u16string name) {
return;
}
m_ModerationStatus = 1; // Pending
m_Name = "";
// If auto reject names is on, and the user is muted, force use of predefined name ELSE proceed with normal name check
if (autoRejectNames && tamer->GetCharacter() && tamer->GetCharacter()->GetParentUser()->GetIsMuted()) {
m_ModerationStatus = 2; // Approved
m_Name = "";
std::string forcedName = "Pet";
//Save our pet's new name to the db:
SetPetNameForModeration(GeneralUtils::UTF16ToWTF8(name));
Database::Get()->SetPetNameModerationStatus(m_DatabaseId, IPetNames::Info{ forcedName, static_cast<int32_t>(m_ModerationStatus) });
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));
}
Game::entityManager->SerializeEntity(m_Parent);

View File

@@ -18,7 +18,7 @@ class PetComponent final : public Component
public:
static constexpr eReplicaComponentType ComponentType = eReplicaComponentType::PET;
explicit PetComponent(Entity* parentEntity, uint32_t componentId);
explicit PetComponent(Entity* parentEntity, const int32_t componentID);
~PetComponent() override;
void Serialize(RakNet::BitStream& outBitStream, bool bIsInitialUpdate) override;
@@ -250,11 +250,6 @@ private:
*/
static std::unordered_map<LWOOBJID, LWOOBJID> currentActivities;
/**
* The ID of the component in the pet component table
*/
uint32_t m_ComponentId;
/**
* The ID of the model that was built to complete the taming minigame for this pet
*/

View File

@@ -28,7 +28,7 @@
#include "dpShapeBox.h"
#include "dpShapeSphere.h"
PhantomPhysicsComponent::PhantomPhysicsComponent(Entity* parent, int32_t componentId) : PhysicsComponent(parent, componentId) {
PhantomPhysicsComponent::PhantomPhysicsComponent(Entity* parent, const int32_t componentID) : PhysicsComponent(parent, componentID) {
RegisterMsg(MessageType::Game::GET_OBJECT_REPORT_INFO, this, &PhantomPhysicsComponent::OnGetObjectReportInfo);
m_Position = m_Parent->GetDefaultPosition();

View File

@@ -28,7 +28,7 @@ class PhantomPhysicsComponent final : public PhysicsComponent {
public:
static constexpr eReplicaComponentType ComponentType = eReplicaComponentType::PHANTOM_PHYSICS;
PhantomPhysicsComponent(Entity* parent, int32_t componentId);
PhantomPhysicsComponent(Entity* parent, const int32_t componentID);
~PhantomPhysicsComponent() override;
void Update(float deltaTime) override;
void Serialize(RakNet::BitStream& outBitStream, bool bIsInitialUpdate) override;

View File

@@ -15,7 +15,7 @@
#include "EntityInfo.h"
#include "Amf3.h"
PhysicsComponent::PhysicsComponent(Entity* parent, int32_t componentId) : Component(parent) {
PhysicsComponent::PhysicsComponent(Entity* parent, const int32_t componentID) : Component(parent, componentID) {
m_Position = NiPoint3Constant::ZERO;
m_Rotation = QuatUtils::IDENTITY;
m_DirtyPosition = false;
@@ -23,7 +23,7 @@ PhysicsComponent::PhysicsComponent(Entity* parent, int32_t componentId) : Compon
CDPhysicsComponentTable* physicsComponentTable = CDClientManager::GetTable<CDPhysicsComponentTable>();
if (physicsComponentTable) {
auto* info = physicsComponentTable->GetByID(componentId);
auto* info = physicsComponentTable->GetByID(componentID);
if (info) {
m_CollisionGroup = info->collisionGroup;
}

View File

@@ -19,7 +19,7 @@ class dpEntity;
class PhysicsComponent : public Component {
public:
PhysicsComponent(Entity* parent, int32_t componentId);
PhysicsComponent(Entity* parent, const int32_t componentID);
virtual ~PhysicsComponent() = default;
void Serialize(RakNet::BitStream& outBitStream, bool bIsInitialUpdate) override;

View File

@@ -1,6 +1,6 @@
#include "PlayerForcedMovementComponent.h"
PlayerForcedMovementComponent::PlayerForcedMovementComponent(Entity* parent) : Component(parent) {
PlayerForcedMovementComponent::PlayerForcedMovementComponent(Entity* parent, const int32_t componentID) : Component(parent, componentID) {
m_Parent = parent;
}

View File

@@ -16,7 +16,7 @@ public:
* Constructor for this component
* @param parent parent that contains this component
*/
PlayerForcedMovementComponent(Entity* parent);
PlayerForcedMovementComponent(Entity* parent, const int32_t componentID);
~PlayerForcedMovementComponent() override;
void Serialize(RakNet::BitStream& outBitStream, bool bIsInitialUpdate) override;

View File

@@ -4,7 +4,7 @@
#include "Inventory.h"
#include "Item.h"
PossessableComponent::PossessableComponent(Entity* parent, uint32_t componentId) : Component(parent) {
PossessableComponent::PossessableComponent(Entity* parent, const int32_t componentID) : Component(parent, componentID) {
m_Possessor = LWOOBJID_EMPTY;
CDItemComponent item = Inventory::FindItemComponent(m_Parent->GetLOT());
m_AnimationFlag = static_cast<eAnimationFlags>(item.animationFlag);
@@ -12,7 +12,7 @@ PossessableComponent::PossessableComponent(Entity* parent, uint32_t componentId)
// Get the possession Type from the CDClient
auto query = CDClientDatabase::CreatePreppedStmt("SELECT possessionType, depossessOnHit FROM PossessableComponent WHERE id = ?;");
query.bind(1, static_cast<int>(componentId));
query.bind(1, static_cast<int>(componentID));
auto result = query.execQuery();

View File

@@ -16,15 +16,10 @@ class PossessableComponent final : public Component {
public:
static constexpr eReplicaComponentType ComponentType = eReplicaComponentType::POSSESSABLE;
PossessableComponent(Entity* parentEntity, uint32_t componentId);
PossessableComponent(Entity* parentEntity, const int32_t componentID);
void Serialize(RakNet::BitStream& outBitStream, bool bIsInitialUpdate) override;
/**
* @brief mounts the Entity
*/
void Mount();
/**
* @brief dismounts the Entity
*/

View File

@@ -7,7 +7,7 @@
#include "eControlScheme.h"
#include "eStateChangeType.h"
PossessorComponent::PossessorComponent(Entity* parent) : Component(parent) {
PossessorComponent::PossessorComponent(Entity* parent, const int32_t componentID) : Component(parent, componentID) {
m_Possessable = LWOOBJID_EMPTY;
}

View File

@@ -20,7 +20,7 @@ class PossessorComponent final : public Component {
public:
static constexpr eReplicaComponentType ComponentType = eReplicaComponentType::POSSESSOR;
PossessorComponent(Entity* parent);
PossessorComponent(Entity* parent, const int32_t componentID);
~PossessorComponent() override;
void Serialize(RakNet::BitStream& outBitStream, bool bIsInitialUpdate) override;

View File

@@ -16,7 +16,7 @@
class PropertyComponent final : public Component {
public:
static constexpr eReplicaComponentType ComponentType = eReplicaComponentType::PROPERTY;
explicit PropertyComponent(Entity* const parentEntity) noexcept : Component{ parentEntity } {}
explicit PropertyComponent(Entity* const parentEntity, const int32_t componentID) noexcept : Component{ parentEntity, componentID } {}
};
#endif // !PROPERTYCOMPONENT_H

View File

@@ -17,7 +17,7 @@
#include "ePropertySortType.h"
#include "User.h"
PropertyEntranceComponent::PropertyEntranceComponent(Entity* parent, uint32_t componentID) : Component(parent) {
PropertyEntranceComponent::PropertyEntranceComponent(Entity* parent, const int32_t componentID) : Component(parent, componentID) {
this->propertyQueries = {};
auto table = CDClientManager::GetTable<CDPropertyEntranceComponentTable>();
@@ -135,7 +135,7 @@ void PropertyEntranceComponent::OnPropertyEntranceSync(Entity* entity, bool incl
const auto owner = propertyEntry.ownerId;
const auto otherCharacter = Database::Get()->GetCharacterInfo(owner);
if (!otherCharacter.has_value()) {
LOG("Failed to find property owner name for %u!", owner);
LOG("Failed to find property owner name for %llu!", owner);
continue;
}
auto& entry = entries.emplace_back();

View File

@@ -13,7 +13,7 @@
*/
class PropertyEntranceComponent final : public Component {
public:
explicit PropertyEntranceComponent(Entity* parent, uint32_t componentID);
explicit PropertyEntranceComponent(Entity* parent, const int32_t componentID);
static constexpr eReplicaComponentType ComponentType = eReplicaComponentType::PROPERTY_ENTRANCE;
/**

View File

@@ -30,7 +30,7 @@
PropertyManagementComponent* PropertyManagementComponent::instance = nullptr;
PropertyManagementComponent::PropertyManagementComponent(Entity* parent) : Component(parent) {
PropertyManagementComponent::PropertyManagementComponent(Entity* parent, const int32_t componentID) : Component(parent, componentID) {
this->owner = LWOOBJID_EMPTY;
this->templateId = 0;
this->propertyId = LWOOBJID_EMPTY;
@@ -64,7 +64,6 @@ PropertyManagementComponent::PropertyManagementComponent(Entity* parent) : Compo
this->propertyId = propertyInfo->id;
this->owner = propertyInfo->ownerId;
GeneralUtils::SetBit(this->owner, eObjectBits::CHARACTER);
GeneralUtils::SetBit(this->owner, eObjectBits::PERSISTENT);
this->clone_Id = propertyInfo->cloneId;
this->propertyName = propertyInfo->name;
this->propertyDescription = propertyInfo->description;
@@ -171,7 +170,7 @@ void PropertyManagementComponent::UpdatePropertyDetails(std::string name, std::s
info.name = propertyName;
info.description = propertyDescription;
info.lastUpdatedTime = std::chrono::duration_cast<std::chrono::seconds>(std::chrono::system_clock::now().time_since_epoch()).count();
Database::Get()->UpdateLastSave(info);
Database::Get()->UpdatePropertyDetails(info);
@@ -204,14 +203,22 @@ bool PropertyManagementComponent::Claim(const LWOOBJID playerId) {
auto prop_path = zone->GetPath(m_Parent->GetVarAsString(u"propertyName"));
if (prop_path){
if (prop_path) {
if (!prop_path->property.displayName.empty()) name = prop_path->property.displayName;
description = prop_path->property.displayDesc;
}
SetOwnerId(playerId);
propertyId = ObjectIDManager::GenerateRandomObjectID();
// Due to legacy IDs being random
propertyId = ObjectIDManager::GetPersistentID();
const uint32_t maxTries = 100;
uint32_t tries = 0;
while (Database::Get()->GetPropertyInfo(propertyId) && tries < maxTries) {
tries++;
LOG("Found a duplicate property %llu, getting a new propertyId", propertyId);
propertyId = ObjectIDManager::GetPersistentID();
}
IProperty::Info info;
info.id = propertyId;
@@ -375,46 +382,45 @@ void PropertyManagementComponent::UpdateModelPosition(const LWOOBJID id, const N
node->position = position;
node->rotation = rotation;
ObjectIDManager::RequestPersistentID([this, node, modelLOT, entity, position, rotation, originalRotation](uint32_t persistentId) {
SpawnerInfo info{};
SpawnerInfo info{};
info.templateID = modelLOT;
info.nodes = { node };
info.templateScale = 1.0f;
info.activeOnLoad = true;
info.amountMaintained = 1;
info.respawnTime = 10;
info.templateID = modelLOT;
info.nodes = { node };
info.templateScale = 1.0f;
info.activeOnLoad = true;
info.amountMaintained = 1;
info.respawnTime = 10;
info.emulated = true;
info.emulator = Game::entityManager->GetZoneControlEntity()->GetObjectID();
info.emulated = true;
info.emulator = Game::entityManager->GetZoneControlEntity()->GetObjectID();
info.spawnerID = persistentId;
GeneralUtils::SetBit(info.spawnerID, eObjectBits::CLIENT);
info.spawnerID = ObjectIDManager::GetPersistentID();
GeneralUtils::SetBit(info.spawnerID, eObjectBits::CLIENT);
const auto spawnerId = Game::zoneManager->MakeSpawner(info);
const auto spawnerId = Game::zoneManager->MakeSpawner(info);
auto* spawner = Game::zoneManager->GetSpawner(spawnerId);
auto* spawner = Game::zoneManager->GetSpawner(spawnerId);
info.nodes[0]->config.push_back(new LDFData<LWOOBJID>(u"modelBehaviors", 0));
info.nodes[0]->config.push_back(new LDFData<LWOOBJID>(u"userModelID", info.spawnerID));
info.nodes[0]->config.push_back(new LDFData<int>(u"modelType", 2));
info.nodes[0]->config.push_back(new LDFData<bool>(u"propertyObjectID", true));
info.nodes[0]->config.push_back(new LDFData<int>(u"componentWhitelist", 1));
info.nodes[0]->config.push_back(new LDFData<LWOOBJID>(u"modelBehaviors", 0));
info.nodes[0]->config.push_back(new LDFData<LWOOBJID>(u"userModelID", info.spawnerID));
info.nodes[0]->config.push_back(new LDFData<int>(u"modelType", 2));
info.nodes[0]->config.push_back(new LDFData<bool>(u"propertyObjectID", true));
info.nodes[0]->config.push_back(new LDFData<int>(u"componentWhitelist", 1));
auto* model = spawner->Spawn();
auto* modelComponent = model->GetComponent<ModelComponent>();
if (modelComponent) modelComponent->Pause();
auto* model = spawner->Spawn();
auto* modelComponent = model->GetComponent<ModelComponent>();
if (modelComponent) modelComponent->Pause();
models.insert_or_assign(model->GetObjectID(), spawnerId);
models.insert_or_assign(model->GetObjectID(), spawnerId);
GameMessages::SendPlaceModelResponse(entity->GetObjectID(), entity->GetSystemAddress(), position, m_Parent->GetObjectID(), 14, originalRotation);
GameMessages::SendPlaceModelResponse(entity->GetObjectID(), entity->GetSystemAddress(), position, m_Parent->GetObjectID(), 14, originalRotation);
GameMessages::SendUGCEquipPreCreateBasedOnEditMode(entity->GetObjectID(), entity->GetSystemAddress(), 0, spawnerId);
GameMessages::SendUGCEquipPreCreateBasedOnEditMode(entity->GetObjectID(), entity->GetSystemAddress(), 0, spawnerId);
GameMessages::SendGetModelsOnProperty(entity->GetObjectID(), GetModels(), UNASSIGNED_SYSTEM_ADDRESS);
GameMessages::SendGetModelsOnProperty(entity->GetObjectID(), GetModels(), UNASSIGNED_SYSTEM_ADDRESS);
Game::entityManager->GetZoneControlEntity()->OnZonePropertyModelPlaced(entity);
Game::entityManager->GetZoneControlEntity()->OnZonePropertyModelPlaced(entity);
});
// Progress place model missions
auto missionComponent = entity->GetComponent<MissionComponent>();
if (missionComponent != nullptr) missionComponent->Progress(eMissionTaskType::PLACE_MODEL, 0);
@@ -622,8 +628,6 @@ void PropertyManagementComponent::Load() {
//BBB property models need to have extra stuff set for them:
if (databaseModel.lot == 14) {
LWOOBJID blueprintID = databaseModel.ugcId;
GeneralUtils::SetBit(blueprintID, eObjectBits::CHARACTER);
GeneralUtils::SetBit(blueprintID, eObjectBits::PERSISTENT);
settings.push_back(new LDFData<LWOOBJID>(u"blueprintid", blueprintID));
settings.push_back(new LDFData<int>(u"componentWhitelist", 1));
@@ -696,7 +700,7 @@ void PropertyManagementComponent::Save() {
// save the behaviors of the model
for (const auto& [behaviorId, behaviorStr] : modelBehaviors) {
if (behaviorStr.empty() || behaviorId == -1 || behaviorId == 0) continue;
IBehaviors::Info info {
IBehaviors::Info info{
.behaviorId = behaviorId,
.characterId = character->GetID(),
.behaviorInfo = behaviorStr
@@ -824,7 +828,7 @@ void PropertyManagementComponent::OnChatMessageReceived(const std::string& sMess
if (!model) continue;
auto* const modelComponent = model->GetComponent<ModelComponent>();
if (!modelComponent) continue;
modelComponent->OnChatMessageReceived(sMessage);
}
}

View File

@@ -31,7 +31,7 @@ enum class PropertyPrivacyOption {
class PropertyManagementComponent final : public Component {
public:
static constexpr eReplicaComponentType ComponentType = eReplicaComponentType::PROPERTY_MANAGEMENT;
PropertyManagementComponent(Entity* parent);
PropertyManagementComponent(Entity* parent, const int32_t componentID);
static PropertyManagementComponent* Instance();
/**

View File

@@ -10,7 +10,7 @@
#include "PropertyManagementComponent.h"
#include "UserManager.h"
PropertyVendorComponent::PropertyVendorComponent(Entity* parent) : Component(parent) {
PropertyVendorComponent::PropertyVendorComponent(Entity* parent, const int32_t componentID) : Component(parent, componentID) {
}
void PropertyVendorComponent::OnUse(Entity* originator) {

View File

@@ -10,7 +10,7 @@
class PropertyVendorComponent final : public Component {
public:
static constexpr eReplicaComponentType ComponentType = eReplicaComponentType::PROPERTY_VENDOR;
explicit PropertyVendorComponent(Entity* parent);
explicit PropertyVendorComponent(Entity* parent, const int32_t componentID);
/**
* Handles a use event from some entity, if the property is cleared this allows the entity to claim it

View File

@@ -7,7 +7,7 @@
const std::unordered_set<LWOOBJID> ProximityMonitorComponent::m_EmptyObjectSet = {};
ProximityMonitorComponent::ProximityMonitorComponent(Entity* parent, int radiusSmall, int radiusLarge) : Component(parent) {
ProximityMonitorComponent::ProximityMonitorComponent(Entity* parent, const int32_t componentID, int radiusSmall, int radiusLarge) : Component(parent, componentID) {
if (radiusSmall != -1 && radiusLarge != -1) {
SetProximityRadius(radiusSmall, "rocketSmall");
SetProximityRadius(radiusLarge, "rocketLarge");

View File

@@ -23,7 +23,7 @@ class ProximityMonitorComponent final : public Component {
public:
static constexpr eReplicaComponentType ComponentType = eReplicaComponentType::PROXIMITY_MONITOR;
ProximityMonitorComponent(Entity* parentEntity, int smallRadius = -1, int largeRadius = -1);
ProximityMonitorComponent(Entity* parentEntity, const int32_t componentID, int smallRadius = -1, int largeRadius = -1);
~ProximityMonitorComponent() override;
void Update(float deltaTime) override;

View File

@@ -23,7 +23,7 @@
#include "CppScripts.h"
QuickBuildComponent::QuickBuildComponent(Entity* const entity) : Component{ entity } {
QuickBuildComponent::QuickBuildComponent(Entity* const entity, const int32_t componentID) : Component{ entity, componentID } {
std::u16string checkPreconditions = entity->GetVar<std::u16string>(u"CheckPrecondition");
if (!checkPreconditions.empty()) {

View File

@@ -24,7 +24,7 @@ class QuickBuildComponent final : public Component {
public:
static constexpr eReplicaComponentType ComponentType = eReplicaComponentType::QUICK_BUILD;
QuickBuildComponent(Entity* const entity);
QuickBuildComponent(Entity* const entity, const int32_t componentID);
~QuickBuildComponent() override;
void Serialize(RakNet::BitStream& outBitStream, bool bIsInitialUpdate) override;

View File

@@ -32,8 +32,8 @@
#define M_PI 3.14159265358979323846264338327950288
#endif
RacingControlComponent::RacingControlComponent(Entity* parent)
: Component(parent) {
RacingControlComponent::RacingControlComponent(Entity* parent, const int32_t componentID)
: Component(parent, componentID) {
m_PathName = u"MainPath";
m_NumberOfLaps = 3;
m_RemainingLaps = m_NumberOfLaps;

View File

@@ -108,7 +108,7 @@ class RacingControlComponent final : public Component {
public:
static constexpr eReplicaComponentType ComponentType = eReplicaComponentType::RACING_CONTROL;
RacingControlComponent(Entity* parentEntity);
RacingControlComponent(Entity* parentEntity, const int32_t componentID);
~RacingControlComponent();
void Serialize(RakNet::BitStream& outBitStream, bool bIsInitialUpdate) override;

View File

@@ -9,7 +9,7 @@ class Entity;
class RacingSoundTriggerComponent : public SoundTriggerComponent {
public:
static constexpr eReplicaComponentType ComponentType = eReplicaComponentType::RACING_SOUND_TRIGGER;
RacingSoundTriggerComponent(Entity* parent) : SoundTriggerComponent(parent){};
RacingSoundTriggerComponent(Entity* parent, const int32_t componentID) : SoundTriggerComponent(parent, componentID){};
};
#endif //!__RACINGSOUNDTRIGGERCOMPONENT__H__

View File

@@ -8,7 +8,7 @@ class RacingStatsComponent final : public Component {
public:
static constexpr eReplicaComponentType ComponentType = eReplicaComponentType::RACING_STATS;
RacingStatsComponent(Entity* parent) : Component(parent) {}
RacingStatsComponent(Entity* parent, const int32_t componentID) : Component(parent, componentID) {}
};
#endif //!__RACINGSTATSCOMPONENT__H__

View File

@@ -11,9 +11,8 @@
#include "EntityManager.h"
#include "eStateChangeType.h"
RailActivatorComponent::RailActivatorComponent(Entity* parent, int32_t componentID) : Component(parent) {
m_ComponentID = componentID;
const auto tableData = CDClientManager::GetTable<CDRailActivatorComponentTable>()->GetEntryByID(componentID);;
RailActivatorComponent::RailActivatorComponent(Entity* parent, const int32_t componentID) : Component(parent, componentID) {
const auto tableData = CDClientManager::GetTable<CDRailActivatorComponentTable>()->GetEntryByID(componentID);
m_Path = parent->GetVar<std::u16string>(u"rail_path");
m_PathDirection = parent->GetVar<bool>(u"rail_path_direction");

View File

@@ -12,7 +12,7 @@
*/
class RailActivatorComponent final : public Component {
public:
explicit RailActivatorComponent(Entity* parent, int32_t componentID);
explicit RailActivatorComponent(Entity* parent, const int32_t componentID);
~RailActivatorComponent() override;
static constexpr eReplicaComponentType ComponentType = eReplicaComponentType::RAIL_ACTIVATOR;
@@ -37,12 +37,6 @@ public:
*/
void OnCancelRailMovement(Entity* originator);
private:
/**
* The ID of this component in the components database
*/
int32_t m_ComponentID;
/**
* The entities that are currently traversing the rail
*/

View File

@@ -16,12 +16,12 @@
std::unordered_map<int32_t, float> RenderComponent::m_DurationCache{};
RenderComponent::RenderComponent(Entity* const parentEntity, const int32_t componentId) : Component{ parentEntity } {
RenderComponent::RenderComponent(Entity* const parentEntity, const int32_t componentID) : Component{ parentEntity, componentID } {
m_LastAnimationName = "";
if (componentId == -1) return;
if (componentID == -1) return;
auto query = CDClientDatabase::CreatePreppedStmt("SELECT * FROM RenderComponent WHERE id = ?;");
query.bind(1, componentId);
query.bind(1, componentID);
auto result = query.execQuery();
if (!result.eof()) {

View File

@@ -63,7 +63,7 @@ class RenderComponent final : public Component {
public:
static constexpr eReplicaComponentType ComponentType = eReplicaComponentType::RENDER;
RenderComponent(Entity* const parentEntity, const int32_t componentId = -1);
RenderComponent(Entity* const parentEntity, const int32_t componentID);
void Serialize(RakNet::BitStream& outBitStream, bool bIsInitialUpdate) override;
void Update(float deltaTime) override;

View File

@@ -13,7 +13,7 @@
#include "EntityInfo.h"
#include "Amf3.h"
RigidbodyPhantomPhysicsComponent::RigidbodyPhantomPhysicsComponent(Entity* parent, int32_t componentId) : PhysicsComponent(parent, componentId) {
RigidbodyPhantomPhysicsComponent::RigidbodyPhantomPhysicsComponent(Entity* parent, const int32_t componentID) : PhysicsComponent(parent, componentID) {
RegisterMsg(MessageType::Game::GET_OBJECT_REPORT_INFO, this, &RigidbodyPhantomPhysicsComponent::OnGetObjectReportInfo);
m_Position = m_Parent->GetDefaultPosition();

View File

@@ -21,7 +21,7 @@ class RigidbodyPhantomPhysicsComponent : public PhysicsComponent {
public:
static constexpr eReplicaComponentType ComponentType = eReplicaComponentType::RIGID_BODY_PHANTOM_PHYSICS;
RigidbodyPhantomPhysicsComponent(Entity* parent, int32_t componentId);
RigidbodyPhantomPhysicsComponent(Entity* parent, const int32_t componentID);
void Update(const float deltaTime) override;

View File

@@ -20,10 +20,10 @@
#include "ServiceType.h"
#include "MessageType/Master.h"
RocketLaunchpadControlComponent::RocketLaunchpadControlComponent(Entity* parent, int rocketId) : Component(parent) {
RocketLaunchpadControlComponent::RocketLaunchpadControlComponent(Entity* parent, const int32_t componentID) : Component(parent, componentID) {
auto query = CDClientDatabase::CreatePreppedStmt(
"SELECT targetZone, defaultZoneID, targetScene, altLandingPrecondition, altLandingSpawnPointName FROM RocketLaunchpadControlComponent WHERE id = ?;");
query.bind(1, rocketId);
query.bind(1, componentID);
auto result = query.execQuery();

View File

@@ -20,7 +20,7 @@ class RocketLaunchpadControlComponent final : public Component {
public:
static constexpr eReplicaComponentType ComponentType = eReplicaComponentType::ROCKET_LAUNCH;
RocketLaunchpadControlComponent(Entity* parent, int rocketId);
RocketLaunchpadControlComponent(Entity* parent, const int32_t componentID);
~RocketLaunchpadControlComponent() override;
/**

View File

@@ -5,16 +5,17 @@
#include "Entity.h"
#include "ScriptComponent.h"
#include "GameMessages.h"
#include "Amf3.h"
ScriptComponent::ScriptComponent(Entity* parent, std::string scriptName, bool serialized, bool client) : Component(parent) {
ScriptComponent::ScriptComponent(Entity* parent, const int32_t componentID, const std::string& scriptName, bool serialized, bool client) : Component(parent, componentID) {
using namespace GameMessages;
m_Serialized = serialized;
m_Client = client;
m_ScriptName = scriptName;
SetScript(scriptName);
}
ScriptComponent::~ScriptComponent() {
RegisterMsg<GetObjectReportInfo>(this, &ScriptComponent::OnGetObjectReportInfo);
}
void ScriptComponent::Serialize(RakNet::BitStream& outBitStream, bool bIsInitialUpdate) {
@@ -50,3 +51,16 @@ void ScriptComponent::SetScript(const std::string& scriptName) {
// and they may also be used by other script components so DON'T delete them.
m_Script = CppScripts::GetScript(m_Parent, scriptName);
}
bool ScriptComponent::OnGetObjectReportInfo(GameMessages::GameMsg& msg) {
auto& infoMsg = static_cast<GameMessages::GetObjectReportInfo&>(msg);
auto& scriptInfo = infoMsg.info->PushDebug("Script");
scriptInfo.PushDebug<AMFStringValue>("Script Name") = m_ScriptName.empty() ? "None" : m_ScriptName;
auto& networkSettings = scriptInfo.PushDebug("Network Settings");
for (const auto* const setting : m_Parent->GetNetworkSettings()) {
networkSettings.PushDebug<AMFStringValue>(GeneralUtils::UTF16ToWTF8(setting->GetKey())) = setting->GetValueAsString();
}
return true;
}

View File

@@ -21,8 +21,7 @@ class ScriptComponent final : public Component {
public:
static constexpr eReplicaComponentType ComponentType = eReplicaComponentType::SCRIPT;
ScriptComponent(Entity* parent, std::string scriptName, bool serialized, bool client = false);
~ScriptComponent() override;
ScriptComponent(Entity* parent, const int32_t componentID, const std::string& scriptName, bool serialized, bool client = false);
void Serialize(RakNet::BitStream& outBitStream, bool bIsInitialUpdate) override;
@@ -43,6 +42,8 @@ public:
* @param scriptName the name of the script to find
*/
void SetScript(const std::string& scriptName);
bool OnGetObjectReportInfo(GameMessages::GameMsg& msg);
private:
@@ -60,6 +61,8 @@ private:
* Whether or not this script is a client script
*/
bool m_Client;
std::string m_ScriptName;
};
#endif // SCRIPTCOMPONENT_H

View File

@@ -9,7 +9,7 @@ class Entity;
class ScriptedActivityComponent final : public ActivityComponent {
public:
static constexpr eReplicaComponentType ComponentType = eReplicaComponentType::SCRIPTED_ACTIVITY;
ScriptedActivityComponent(Entity* parent, int activityID) : ActivityComponent(parent, activityID){};
ScriptedActivityComponent(Entity* parent, const int32_t componentID) : ActivityComponent(parent, componentID){};
};
#endif //!__SCRIPTEDACTIVITYCOMPONENT__H__

View File

@@ -2,7 +2,7 @@
#include "EntityManager.h"
#include "ScriptedActivityComponent.h"
ShootingGalleryComponent::ShootingGalleryComponent(Entity* parent, int32_t activityID) : ActivityComponent(parent, activityID) {
ShootingGalleryComponent::ShootingGalleryComponent(Entity* parent, const int32_t componentID) : ActivityComponent(parent, componentID) {
}
void ShootingGalleryComponent::SetStaticParams(const StaticShootingGalleryParams& params) {

View File

@@ -76,7 +76,7 @@ class ShootingGalleryComponent final : public ActivityComponent {
public:
static constexpr eReplicaComponentType ComponentType = eReplicaComponentType::SHOOTING_GALLERY;
explicit ShootingGalleryComponent(Entity* parent, int32_t activityID);
explicit ShootingGalleryComponent(Entity* parent, const int32_t componentID);
void Serialize(RakNet::BitStream& outBitStream, bool isInitialUpdate) override;
/**

View File

@@ -30,7 +30,7 @@ class SimplePhysicsComponent : public PhysicsComponent {
public:
static constexpr eReplicaComponentType ComponentType = eReplicaComponentType::SIMPLE_PHYSICS;
SimplePhysicsComponent(Entity* parent, int32_t componentID);
SimplePhysicsComponent(Entity* parent, const int32_t componentID);
~SimplePhysicsComponent() override;
void Update(const float deltaTime) override;

View File

@@ -489,7 +489,7 @@ void SkillComponent::HandleUnCast(const uint32_t behaviorId, const LWOOBJID targ
behavior->UnCast(&context, { target });
}
SkillComponent::SkillComponent(Entity* parent) : Component(parent) {
SkillComponent::SkillComponent(Entity* parent, const int32_t componentID) : Component(parent, componentID) {
this->m_skillUid = 0;
}

View File

@@ -63,7 +63,7 @@ class SkillComponent final : public Component {
public:
static constexpr eReplicaComponentType ComponentType = eReplicaComponentType::SKILL;
explicit SkillComponent(Entity* parent);
explicit SkillComponent(Entity* parent, const int32_t componentID);
~SkillComponent() override;
void Serialize(RakNet::BitStream& outBitStream, bool bIsInitialUpdate) override;

View File

@@ -25,7 +25,7 @@ void MixerProgram::Serialize(RakNet::BitStream& outBitStream){
outBitStream.Write(name.c_str(), name.size());
outBitStream.Write(result);
}
SoundTriggerComponent::SoundTriggerComponent(Entity* parent) : Component(parent) {
SoundTriggerComponent::SoundTriggerComponent(Entity* parent, const int32_t componentID) : Component(parent, componentID) {
const auto musicCueName = parent->GetVar<std::string>(u"NDAudioMusicCue_Name");
if (!musicCueName.empty()) {

View File

@@ -60,7 +60,7 @@ struct MixerProgram {
class SoundTriggerComponent : public Component {
public:
static constexpr eReplicaComponentType ComponentType = eReplicaComponentType::SOUND_TRIGGER;
explicit SoundTriggerComponent(Entity* parent);
explicit SoundTriggerComponent(Entity* parent, const int32_t componentID);
void Serialize(RakNet::BitStream& outBitStream, bool bIsInitialUpdate) override;
void ActivateMusicCue(const std::string& name, float bordemTime = -1.0);
void DeactivateMusicCue(const std::string& name);

View File

@@ -6,7 +6,7 @@
std::vector<SwitchComponent*> SwitchComponent::petSwitches;
SwitchComponent::SwitchComponent(Entity* parent) : Component(parent) {
SwitchComponent::SwitchComponent(Entity* parent, const int32_t componentID) : Component(parent, componentID) {
m_Active = false;
m_ResetTime = m_Parent->GetVarAs<int32_t>(u"switch_reset_time");

View File

@@ -18,7 +18,7 @@ class SwitchComponent final : public Component {
public:
static constexpr eReplicaComponentType ComponentType = eReplicaComponentType::SWITCH;
SwitchComponent(Entity* parent);
SwitchComponent(Entity* parent, const int32_t componentID);
~SwitchComponent() override;
void Update(float deltaTime) override;
@@ -67,6 +67,10 @@ public:
*/
static SwitchComponent* GetClosestSwitch(NiPoint3 position);
const std::vector<int32_t>& GetFactionsToRespondTo() const {
return m_FactionsToRespondTo;
}
private:
/**
* A list of all pet switches.

View File

@@ -19,7 +19,7 @@
#include <glm/gtc/quaternion.hpp>
TriggerComponent::TriggerComponent(Entity* parent, const std::string triggerInfo) : Component(parent) {
TriggerComponent::TriggerComponent(Entity* parent, const int32_t componentID, const std::string triggerInfo) : Component(parent, componentID) {
m_Parent = parent;
m_Trigger = nullptr;

View File

@@ -9,7 +9,7 @@ class TriggerComponent final : public Component {
public:
static constexpr eReplicaComponentType ComponentType = eReplicaComponentType::TRIGGER;
explicit TriggerComponent(Entity* parent, const std::string triggerInfo);
explicit TriggerComponent(Entity* parent, const int32_t componentID, const std::string triggerInfo);
void TriggerEvent(eTriggerEventType event, Entity* optionalTarget = nullptr);
LUTriggers::Trigger* GetTrigger() const { return m_Trigger; }

View File

@@ -14,7 +14,7 @@
#include "UserManager.h"
#include "CheatDetection.h"
VendorComponent::VendorComponent(Entity* parent) : Component(parent) {
VendorComponent::VendorComponent(Entity* parent, const int32_t componentID) : Component(parent, componentID) {
m_HasStandardCostItems = false;
m_HasMultiCostItems = false;
SetupConstants();

View File

@@ -21,7 +21,7 @@ struct SoldItem {
class VendorComponent : public Component {
public:
static constexpr eReplicaComponentType ComponentType = eReplicaComponentType::VENDOR;
VendorComponent(Entity* parent);
VendorComponent(Entity* parent, const int32_t componentID);
void Serialize(RakNet::BitStream& outBitStream, bool bIsInitialUpdate) override;