From 34cfd45d40943409769f2dd3c8262fa0ad921d4b Mon Sep 17 00:00:00 2001 From: David Markowitz Date: Mon, 26 Jun 2023 00:15:25 -0700 Subject: [PATCH] CombatAI and Vendor --- dGame/dComponents/AchievementCacheKey.h | 5 +- dGame/dComponents/BaseCombatAIComponent.cpp | 213 +++++++------------- dGame/dComponents/BaseCombatAIComponent.h | 48 +++-- dGame/dComponents/ComponentHeirachy.md | 2 +- dGame/dComponents/VendorComponent.cpp | 61 +++--- dGame/dComponents/VendorComponent.h | 54 ++--- dGame/dGameMessages/GameMessages.cpp | 8 +- 7 files changed, 173 insertions(+), 218 deletions(-) diff --git a/dGame/dComponents/AchievementCacheKey.h b/dGame/dComponents/AchievementCacheKey.h index 398e0231..2e0b3628 100644 --- a/dGame/dComponents/AchievementCacheKey.h +++ b/dGame/dComponents/AchievementCacheKey.h @@ -1,8 +1,9 @@ -#include "eMissionTaskType.h" - #ifndef __ACHIEVEMENTCACHEKEY__H__ #define __ACHIEVEMENTCACHEKEY__H__ +#include "eMissionTaskType.h" +#include "GeneralUtils.h" + class AchievementCacheKey { public: AchievementCacheKey() { diff --git a/dGame/dComponents/BaseCombatAIComponent.cpp b/dGame/dComponents/BaseCombatAIComponent.cpp index 316ffab8..33772459 100644 --- a/dGame/dComponents/BaseCombatAIComponent.cpp +++ b/dGame/dComponents/BaseCombatAIComponent.cpp @@ -1,6 +1,10 @@ #include "BaseCombatAIComponent.h" -#include +#include +#include +#include + +#include "BitStream.h" #include "Entity.h" #include "EntityManager.h" #include "ControllablePhysicsComponent.h" @@ -15,10 +19,6 @@ #include "CDClientManager.h" #include "DestroyableComponent.h" -#include -#include -#include - #include "SkillComponent.h" #include "QuickBuildComponent.h" #include "DestroyableComponent.h" @@ -26,21 +26,25 @@ #include "CDComponentsRegistryTable.h" #include "CDPhysicsComponentTable.h" -BaseCombatAIComponent::BaseCombatAIComponent(Entity* parent, const uint32_t id): Component(parent) { +BaseCombatAIComponent::BaseCombatAIComponent(Entity* parent, const uint32_t componentId) : Component(parent) { m_Target = LWOOBJID_EMPTY; - SetAiState(AiState::spawn); + m_ComponentId = componentId; + SetAiState(AiState::Spawn); m_Timer = 1.0f; m_StartPosition = parent->GetPosition(); - m_MovementAI = nullptr; m_Disabled = false; m_SkillEntries = {}; m_MovementAI = nullptr; m_SoftTimer = 5.0f; + m_dpEntity = nullptr; + m_dpEntityEnemy = nullptr; +} +void BaseCombatAIComponent::LoadTemplateData() { //Grab the aggro information from BaseCombatAI: auto componentQuery = CDClientDatabase::CreatePreppedStmt( "SELECT aggroRadius, tetherSpeed, pursuitSpeed, softTetherRadius, hardTetherRadius FROM BaseCombatAIComponent WHERE id = ?;"); - componentQuery.bind(1, (int)id); + componentQuery.bind(1, m_ComponentId); auto componentResult = componentQuery.execQuery(); @@ -63,21 +67,12 @@ BaseCombatAIComponent::BaseCombatAIComponent(Entity* parent, const uint32_t id): componentResult.finalize(); - // Get aggro and tether radius from settings and use this if it is present. Only overwrite the - // radii if it is greater than the one in the database. - if (m_ParentEntity) { - auto aggroRadius = m_ParentEntity->GetVar(u"aggroRadius"); - m_AggroRadius = aggroRadius != 0 ? aggroRadius : m_AggroRadius; - auto tetherRadius = m_ParentEntity->GetVar(u"tetherRadius"); - m_HardTetherRadius = tetherRadius != 0 ? tetherRadius : m_HardTetherRadius; - } - /* * Find skills */ auto skillQuery = CDClientDatabase::CreatePreppedStmt( "SELECT skillID, cooldown, behaviorID FROM SkillBehavior WHERE skillID IN (SELECT skillID FROM ObjectSkills WHERE objectTemplate = ?);"); - skillQuery.bind(1, (int)parent->GetLOT()); + skillQuery.bind(1, m_ParentEntity->GetLOT()); auto result = skillQuery.execQuery(); @@ -90,35 +85,42 @@ BaseCombatAIComponent::BaseCombatAIComponent(Entity* parent, const uint32_t id): auto* behavior = Behavior::CreateBehavior(behaviorId); - std::stringstream behaviorQuery; - - AiSkillEntry entry = { skillId, 0, abilityCooldown, behavior }; - - m_SkillEntries.push_back(entry); + m_SkillEntries.push_back(AiSkillEntry(skillId, 0, abilityCooldown, behavior)); result.nextRow(); } +} +void BaseCombatAIComponent::LoadConfigData() { + // Get aggro and tether radius from settings and use this if it is present. Only overwrite the + // radii if it is greater than the one in the database. + if (m_ParentEntity) { + auto aggroRadius = m_ParentEntity->GetVar(u"aggroRadius"); + m_AggroRadius = aggroRadius != 0 ? aggroRadius : m_AggroRadius; + auto tetherRadius = m_ParentEntity->GetVar(u"tetherRadius"); + m_HardTetherRadius = tetherRadius != 0 ? tetherRadius : m_HardTetherRadius; + } +} + +void BaseCombatAIComponent::Startup() { Stun(1.0f); - /* - * Add physics - */ - + // Add physics int32_t collisionGroup = (COLLISION_GROUP_DYNAMIC | COLLISION_GROUP_ENEMY); - CDComponentsRegistryTable* componentRegistryTable = CDClientManager::Instance().GetTable(); - auto componentID = componentRegistryTable->GetByIDAndType(parent->GetLOT(), eReplicaComponentType::CONTROLLABLE_PHYSICS); + auto* componentRegistryTable = CDClientManager::Instance().GetTable(); + if (!componentRegistryTable) return; - CDPhysicsComponentTable* physicsComponentTable = CDClientManager::Instance().GetTable(); + auto componentID = componentRegistryTable->GetByIDAndType(m_ParentEntity->GetLOT(), eReplicaComponentType::CONTROLLABLE_PHYSICS); - if (physicsComponentTable != nullptr) { - auto* info = physicsComponentTable->GetByID(componentID); - if (info != nullptr) { - collisionGroup = info->bStatic ? COLLISION_GROUP_NEUTRAL : info->collisionGroup; - } - } + auto* physicsComponentTable = CDClientManager::Instance().GetTable(); + if (!physicsComponentTable) return; + + auto* info = physicsComponentTable->GetByID(componentID); + if (info) collisionGroup = info->bStatic ? COLLISION_GROUP_NEUTRAL : info->collisionGroup; + + // Why are these new'd here and then deleted by the dpworld?? //Create a phantom physics volume so we can detect when we're aggro'd. m_dpEntity = new dpEntity(m_ParentEntity->GetObjectID(), m_AggroRadius); m_dpEntityEnemy = new dpEntity(m_ParentEntity->GetObjectID(), m_AggroRadius, false); @@ -135,11 +137,12 @@ BaseCombatAIComponent::BaseCombatAIComponent(Entity* parent, const uint32_t id): } BaseCombatAIComponent::~BaseCombatAIComponent() { - if (m_dpEntity) - dpWorld::Instance().RemoveEntity(m_dpEntity); + if (m_dpEntity) dpWorld::Instance().RemoveEntity(m_dpEntity); - if (m_dpEntityEnemy) - dpWorld::Instance().RemoveEntity(m_dpEntityEnemy); + if (m_dpEntityEnemy) dpWorld::Instance().RemoveEntity(m_dpEntityEnemy); + m_MovementAI = nullptr; + m_dpEntity = nullptr; + m_dpEntityEnemy = nullptr; } void BaseCombatAIComponent::Update(const float deltaTime) { @@ -174,14 +177,12 @@ void BaseCombatAIComponent::Update(const float deltaTime) { if (m_SoftTimer <= 0.0f) { EntityManager::Instance()->SerializeEntity(m_ParentEntity); - m_SoftTimer = 5.0f; } else { m_SoftTimer -= deltaTime; } - if (m_Disabled || m_ParentEntity->IsDead()) - return; + if (m_Disabled || m_ParentEntity->IsDead()) return; bool stunnedThisFrame = m_Stunned; CalculateCombat(deltaTime); // Putting this here for now @@ -189,16 +190,13 @@ void BaseCombatAIComponent::Update(const float deltaTime) { m_StartPosition = m_ParentEntity->GetPosition(); } - if (m_MovementAI == nullptr) { + if (!m_MovementAI) { m_MovementAI = m_ParentEntity->GetComponent(); - if (m_MovementAI == nullptr) { - return; - } + if (!m_MovementAI) return; } if (stunnedThisFrame) { m_MovementAI->Stop(); - return; } @@ -208,24 +206,25 @@ void BaseCombatAIComponent::Update(const float deltaTime) { } switch (m_State) { - case AiState::spawn: + case AiState::Spawn: Stun(2.0f); - SetAiState(AiState::idle); + SetAiState(AiState::Idle); break; - case AiState::idle: + case AiState::Idle: Wander(); break; - case AiState::aggro: + case AiState::Aggro: OnAggro(); break; - case AiState::tether: + case AiState::Tether: OnTether(); break; default: + Game::logger->Log("BaseCombatAIComponent", "Entity %i is in an invalid state %i", m_ParentEntity->GetLOT(), m_State); break; } } @@ -256,9 +255,7 @@ void BaseCombatAIComponent::CalculateCombat(const float deltaTime) { auto* skillComponent = m_ParentEntity->GetComponent(); - if (skillComponent == nullptr) { - return; - } + if (!skillComponent) return; skillComponent->CalculateUpdate(deltaTime); @@ -330,19 +327,19 @@ void BaseCombatAIComponent::CalculateCombat(const float deltaTime) { SetTarget(newTarget); if (m_Target != LWOOBJID_EMPTY) { - if (m_State == AiState::idle) { + if (m_State == AiState::Idle) { m_Timer = 0; } - SetAiState(AiState::aggro); + SetAiState(AiState::Aggro); } else { - SetAiState(AiState::idle); + SetAiState(AiState::Idle); } if (!hasSkillToCast) return; if (m_Target == LWOOBJID_EMPTY) { - SetAiState(AiState::idle); + SetAiState(AiState::Idle); return; } @@ -367,7 +364,7 @@ void BaseCombatAIComponent::CalculateCombat(const float deltaTime) { m_MovementAI->Stop(); } - SetAiState(AiState::aggro); + SetAiState(AiState::Aggro); m_Timer = 0; @@ -383,8 +380,6 @@ void BaseCombatAIComponent::CalculateCombat(const float deltaTime) { } LWOOBJID BaseCombatAIComponent::FindTarget() { - //const auto reference = m_MovementAI == nullptr ? m_StartPosition : m_MovementAI->ApproximateLocation(); - NiPoint3 reference = m_StartPosition; if (m_MovementAI) reference = m_MovementAI->ApproximateLocation(); @@ -510,23 +505,10 @@ std::vector BaseCombatAIComponent::GetTargetWithinAggroRange() const { return targets; } -bool BaseCombatAIComponent::IsMech() { - switch (m_ParentEntity->GetLOT()) { - case 6253: - return true; - - default: - return false; - } - - return false; -} - - void BaseCombatAIComponent::Serialize(RakNet::BitStream* outBitStream, bool bIsInitialUpdate, unsigned int& flags) { outBitStream->Write(m_DirtyStateOrTarget || bIsInitialUpdate); if (m_DirtyStateOrTarget || bIsInitialUpdate) { - outBitStream->Write(uint32_t(m_State)); + outBitStream->Write(m_State); outBitStream->Write(m_Target); m_DirtyStateOrTarget = false; } @@ -542,7 +524,7 @@ void BaseCombatAIComponent::SetAiState(AiState newState) { bool BaseCombatAIComponent::IsEnemy(LWOOBJID target) const { auto* entity = EntityManager::Instance()->GetEntity(target); - if (entity == nullptr) { + if (!entity) { Game::logger->Log("BaseCombatAIComponent", "Invalid entity for checking validity (%llu)!", target); return false; @@ -550,13 +532,11 @@ bool BaseCombatAIComponent::IsEnemy(LWOOBJID target) const { auto* destroyable = entity->GetComponent(); - if (destroyable == nullptr) { - return false; - } + if (!destroyable) return false; auto* referenceDestroyable = m_ParentEntity->GetComponent(); - if (referenceDestroyable == nullptr) { + if (!referenceDestroyable) { Game::logger->Log("BaseCombatAIComponent", "Invalid reference destroyable component on (%llu)!", m_ParentEntity->GetObjectID()); return false; @@ -564,7 +544,7 @@ bool BaseCombatAIComponent::IsEnemy(LWOOBJID target) const { auto* quickbuild = entity->GetComponent(); - if (quickbuild != nullptr) { + if (quickbuild) { const auto state = quickbuild->GetState(); if (state != eRebuildState::COMPLETED) { @@ -576,7 +556,7 @@ bool BaseCombatAIComponent::IsEnemy(LWOOBJID target) const { auto candidateList = destroyable->GetFactionIDs(); - for (auto value : candidateList) { + for (const auto value : candidateList) { if (std::find(enemyList.begin(), enemyList.end(), value) != enemyList.end()) { return true; } @@ -598,8 +578,7 @@ Entity* BaseCombatAIComponent::GetTargetEntity() const { void BaseCombatAIComponent::Taunt(LWOOBJID offender, float threat) { // Can't taunt self - if (offender == m_ParentEntity->GetObjectID()) - return; + if (offender == m_ParentEntity->GetObjectID()) return; m_ThreatEntries[offender] += threat; m_DirtyThreat = true; @@ -634,9 +613,7 @@ void BaseCombatAIComponent::ClearThreat() { } void BaseCombatAIComponent::Wander() { - if (!m_MovementAI->AtFinalWaypoint()) { - return; - } + if (!m_MovementAI->AtFinalWaypoint()) return; m_MovementAI->SetHaltDistance(0); @@ -679,9 +656,7 @@ void BaseCombatAIComponent::OnAggro() { auto* target = GetTargetEntity(); - if (target == nullptr) { - return; - } + if (!target) return; m_MovementAI->SetHaltDistance(m_AttackRadius); @@ -704,7 +679,7 @@ void BaseCombatAIComponent::OnAggro() { m_MovementAI->SetDestination(targetPos); - SetAiState(AiState::tether); + SetAiState(AiState::Tether); } m_Timer += 0.5f; @@ -730,7 +705,7 @@ void BaseCombatAIComponent::OnTether() { m_MovementAI->SetDestination(m_StartPosition); - SetAiState(AiState::aggro); + SetAiState(AiState::Aggro); } else { if (IsMech() && Vector3::DistanceSquared(targetPos, currentPos) > m_AttackRadius * m_AttackRadius * 3 * 3) return; @@ -742,64 +717,20 @@ void BaseCombatAIComponent::OnTether() { m_Timer += 0.5f; } -bool BaseCombatAIComponent::GetStunned() const { - return m_Stunned; -} - -void BaseCombatAIComponent::SetStunned(const bool value) { - m_Stunned = value; -} - -bool BaseCombatAIComponent::GetStunImmune() const { - return m_StunImmune; -} - -void BaseCombatAIComponent::SetStunImmune(bool value) { - m_StunImmune = value; -} - -float BaseCombatAIComponent::GetTetherSpeed() const { - return m_TetherSpeed; -} - -void BaseCombatAIComponent::SetTetherSpeed(float value) { - m_TetherSpeed = value; -} - void BaseCombatAIComponent::Stun(const float time) { - if (m_StunImmune || m_StunTime > time) { - return; - } + if (m_StunImmune || m_StunTime > time) return; m_StunTime = time; m_Stunned = true; } -float BaseCombatAIComponent::GetAggroRadius() const { - return m_AggroRadius; -} - -void BaseCombatAIComponent::SetAggroRadius(const float value) { - m_AggroRadius = value; -} - void BaseCombatAIComponent::LookAt(const NiPoint3& point) { - if (m_Stunned) { - return; - } + if (m_Stunned) return; m_ParentEntity->SetRotation(NiQuaternion::LookAt(m_ParentEntity->GetPosition(), point)); } -void BaseCombatAIComponent::SetDisabled(bool value) { - m_Disabled = value; -} - -bool BaseCombatAIComponent::GetDistabled() const { - return m_Disabled; -} - void BaseCombatAIComponent::Sleep() { m_dpEntity->SetSleeping(true); m_dpEntityEnemy->SetSleeping(true); diff --git a/dGame/dComponents/BaseCombatAIComponent.h b/dGame/dComponents/BaseCombatAIComponent.h index 346bc340..78116b9d 100644 --- a/dGame/dComponents/BaseCombatAIComponent.h +++ b/dGame/dComponents/BaseCombatAIComponent.h @@ -20,20 +20,25 @@ class Entity; /** * The current state of the AI */ -enum class AiState : int { - idle = 0, // Doing nothing - aggro, // Waiting for an enemy to cross / running back to spawn - tether, // Chasing an enemy - spawn, // Spawning into the world - dead // Killed +enum class AiState : int32_t { + Idle = 0, // Doing nothing + Aggro, // Waiting for an enemy to cross / running back to spawn + Tether, // Chasing an enemy + Spawn, // Spawning into the world + Dead // Killed }; /** * Represents a skill that can be cast by this enemy, including its cooldowns, which determines how often the skill * may be cast. */ -struct AiSkillEntry -{ +struct AiSkillEntry { + AiSkillEntry(uint32_t skillId, float cooldown, float abilityCooldown, Behavior* behavior) { + this->skillId = skillId; + this->cooldown = cooldown; + this->abilityCooldown = abilityCooldown; + this->behavior = behavior; + } uint32_t skillId; float cooldown; @@ -53,6 +58,9 @@ public: BaseCombatAIComponent(Entity* parentEntity, uint32_t id); ~BaseCombatAIComponent() override; + void LoadTemplateData() override; + void LoadConfigData() override; + void Startup() override; void Update(float deltaTime) override; void Serialize(RakNet::BitStream* outBitStream, bool bIsInitialUpdate, unsigned int& flags); @@ -147,37 +155,37 @@ public: * Gets whether or not the entity is currently stunned * @return whether the entity is currently stunned */ - bool GetStunned() const; + bool GetStunned() const { return m_Stunned; } /** * (un)stuns the entity, determining whether it'll be able to attack other entities * @param value whether the enemy is stunned */ - void SetStunned(bool value); + void SetStunned(bool value) { m_Stunned = value; } /** * Gets if this entity may be stunned * @return if this entity may be stunned */ - bool GetStunImmune() const; + bool GetStunImmune() const { return m_StunImmune; } /** * Set the stun immune value, determining if the entity may be stunned * @param value */ - void SetStunImmune(bool value); + void SetStunImmune(bool value) { m_StunImmune = value; } /** * Gets the current speed at which an entity runs when tethering * @return the current speed at which an entity runs when tethering */ - float GetTetherSpeed() const; + float GetTetherSpeed() const { return m_TetherSpeed; } /** * Sets the speed at which an entity will tether * @param value the new tether speed */ - void SetTetherSpeed(float value); + void SetTetherSpeed(float value) { m_TetherSpeed = value; } /** * Stuns the entity for a certain amount of time, will not work if the entity is stun immune @@ -189,13 +197,13 @@ public: * Gets the radius that will cause this entity to get aggro'd, causing a target chase * @return the aggro radius of the entity */ - float GetAggroRadius() const; + float GetAggroRadius() const { return m_AggroRadius; } /** * Sets the aggro radius, causing the entity to start chasing enemies in this range * @param value the aggro radius to set */ - void SetAggroRadius(float value); + void SetAggroRadius(float value) { m_AggroRadius = value; } /** * Makes the entity look at a certain point in space @@ -207,13 +215,13 @@ public: * (dis)ables the AI, causing it to stop/start attacking enemies * @param value */ - void SetDisabled(bool value); + void SetDisabled(bool value) { m_Disabled = value; } /** * Gets the current state of the AI, whether or not it's looking for enemies to attack * @return */ - bool GetDistabled() const; + bool GetDistabled() const { return m_Disabled; } /** * Turns the entity asleep, stopping updates to its physics volumes @@ -387,7 +395,9 @@ private: * Whether the current entity is a mech enemy, needed as mechs tether radius works differently * @return whether this entity is a mech */ - bool IsMech(); + bool IsMech() const { return m_ParentEntity->GetLOT() == 6253; }; + + int32_t m_ComponentId; }; #endif // BASECOMBATAICOMPONENT_H diff --git a/dGame/dComponents/ComponentHeirachy.md b/dGame/dComponents/ComponentHeirachy.md index 3a316988..e135b6ad 100644 --- a/dGame/dComponents/ComponentHeirachy.md +++ b/dGame/dComponents/ComponentHeirachy.md @@ -128,7 +128,7 @@ LWOSpawnComponent LWOSpringpadComponent LWOSwitchComponent LWOTriggerComponent -LocalPlayer (not a component) +LocalPlayer - This is a function call in the client which, if the generated Entity is a player, the below components are added. This **must** be done for all players. ├~~ LWOInteractionManagerComponent ├~~ LWOUserControlComponent ├~~ LWOFriendsListComponent diff --git a/dGame/dComponents/VendorComponent.cpp b/dGame/dComponents/VendorComponent.cpp index 7645ee21..73f8363d 100644 --- a/dGame/dComponents/VendorComponent.cpp +++ b/dGame/dComponents/VendorComponent.cpp @@ -14,6 +14,9 @@ VendorComponent::VendorComponent(Entity* parent) : Component(parent) { m_HasStandardCostItems = false; m_HasMultiCostItems = false; +} + +void VendorComponent::Startup() { SetupConstants(); RefreshInventory(true); } @@ -31,24 +34,23 @@ void VendorComponent::OnUse(Entity* originator) { GameMessages::SendVendorStatusUpdate(m_ParentEntity, originator->GetSystemAddress()); } - void VendorComponent::RefreshInventory(bool isCreation) { SetHasStandardCostItems(false); SetHasMultiCostItems(false); - //Custom code for Max vanity NPC + // Custom code for Max vanity NPC if (m_ParentEntity->GetLOT() == 9749 && Game::server->GetZoneID() == 1201) { if (!isCreation) return; SetHasStandardCostItems(true); - m_Inventory.insert({ 11909, 0 }); // Top hat w frog - m_Inventory.insert({ 7785, 0 }); // Flash bulb - m_Inventory.insert({ 12764, 0 }); // Big fountain soda - m_Inventory.insert({ 12241, 0 }); // Hot cocoa (from fb) + m_Inventory.push_back(SoldItem(11909, 0)); // Top hat w frog + m_Inventory.push_back(SoldItem(7785, 0)); // Flash bulb + m_Inventory.push_back(SoldItem(12764, 0)); // Big fountain soda + m_Inventory.push_back(SoldItem(12241, 0)); // Hot cocoa (from fb) return; } m_Inventory.clear(); auto* lootMatrixTable = CDClientManager::Instance().GetTable(); - std::vector lootMatrices = lootMatrixTable->Query([=](CDLootMatrix entry) { return (entry.LootMatrixIndex == m_LootMatrixID); }); + const auto lootMatrices = lootMatrixTable->Query([=](CDLootMatrix entry) { return (entry.LootMatrixIndex == m_LootMatrixID); }); if (lootMatrices.empty()) return; // Done with lootMatrix table @@ -59,16 +61,16 @@ void VendorComponent::RefreshInventory(bool isCreation) { for (const auto& lootMatrix : lootMatrices) { int lootTableID = lootMatrix.LootTableIndex; - std::vector vendorItems = lootTableTable->Query([=](CDLootTable entry) { return (entry.LootTableIndex == lootTableID); }); + auto vendorItems = lootTableTable->Query([=](CDLootTable entry) { return (entry.LootTableIndex == lootTableID); }); if (lootMatrix.maxToDrop == 0 || lootMatrix.minToDrop == 0) { - for (CDLootTable item : vendorItems) { - if (!m_HasStandardCostItems || !m_HasMultiCostItems){ + for (const auto& item : vendorItems) { + if (!m_HasStandardCostItems || !m_HasMultiCostItems) { auto itemComponentID = compRegistryTable->GetByIDAndType(item.itemid, eReplicaComponentType::ITEM); auto itemComponent = itemComponentTable->GetItemComponentByID(itemComponentID); if (!m_HasStandardCostItems && itemComponent.baseValue != -1) SetHasStandardCostItems(true); if (!m_HasMultiCostItems && !itemComponent.currencyCosts.empty()) SetHasMultiCostItems(true); } - m_Inventory.insert({ item.itemid, item.sortPriority }); + m_Inventory.push_back(SoldItem(item.itemid, item.sortPriority)); } } else { auto randomCount = GeneralUtils::GenerateRandomNumber(lootMatrix.minToDrop, lootMatrix.maxToDrop); @@ -76,43 +78,47 @@ void VendorComponent::RefreshInventory(bool isCreation) { for (size_t i = 0; i < randomCount; i++) { if (vendorItems.empty()) break; auto randomItemIndex = GeneralUtils::GenerateRandomNumber(0, vendorItems.size() - 1); - const auto& randomItem = vendorItems[randomItemIndex]; + const auto& randomItem = vendorItems.at(randomItemIndex); vendorItems.erase(vendorItems.begin() + randomItemIndex); - if (!m_HasStandardCostItems || !m_HasMultiCostItems){ - auto itemComponentID = compRegistryTable->GetByIDAndType(randomItem.itemid, eReplicaComponentType::ITEM); + if (!m_HasStandardCostItems || !m_HasMultiCostItems) { + auto itemComponentID = compRegistryTable->GetByIDAndType(randomItem.itemid, eReplicaComponentType::ITEM, -1); + if (itemComponentID == -1) { + Game::logger->Log("VendorComponent", "Attempted to add item %i with ItemComponent ID -1 to vendor %i inventory. Not adding item!", itemComponentID, m_ParentEntity->GetLOT()); + continue; + } auto itemComponent = itemComponentTable->GetItemComponentByID(itemComponentID); if (!m_HasStandardCostItems && itemComponent.baseValue != -1) SetHasStandardCostItems(true); if (!m_HasMultiCostItems && !itemComponent.currencyCosts.empty()) SetHasMultiCostItems(true); } - m_Inventory.insert({ randomItem.itemid, randomItem.sortPriority }); + m_Inventory.push_back(SoldItem(randomItem.itemid, randomItem.sortPriority)); } } } - //Because I (Max) want a vendor to sell these cameras + // Because I (Max) want a vendor to sell these cameras if (m_ParentEntity->GetLOT() == 13569) { auto randomCamera = GeneralUtils::GenerateRandomNumber(0, 2); + LOT camera = 0; + DluAssert(randomCamera >= 0 && randomCamera <= 2); switch (randomCamera) { case 0: - m_Inventory.insert({ 16253, 0 }); //Grungagroid + camera = 16253; // Grungagroid break; case 1: - m_Inventory.insert({ 16254, 0 }); //Hipstabrick + camera = 16254; // Hipstabrick break; case 2: - m_Inventory.insert({ 16204, 0 }); //Megabrixel snapshot - break; - default: + camera = 16204; // Megabrixel snapshot break; } + m_Inventory.push_back(SoldItem(camera, 0)); //Megabrixel snapshot } // Callback timer to refresh this inventory. m_ParentEntity->AddCallbackTimer(m_RefreshTimeSeconds, [this]() { RefreshInventory(); - } - ); + }); EntityManager::Instance()->SerializeEntity(m_ParentEntity); GameMessages::SendVendorStatusUpdate(m_ParentEntity, UNASSIGNED_SYSTEM_ADDRESS); } @@ -124,10 +130,11 @@ void VendorComponent::SetupConstants() { auto* vendorComponentTable = CDClientManager::Instance().GetTable(); std::vector vendorComps = vendorComponentTable->Query([=](CDVendorComponent entry) { return (entry.id == componentID); }); if (vendorComps.empty()) return; - m_BuyScalar = vendorComps[0].buyScalar; - m_SellScalar = vendorComps[0].sellScalar; - m_RefreshTimeSeconds = vendorComps[0].refreshTimeSeconds; - m_LootMatrixID = vendorComps[0].LootMatrixIndex; + auto vendorData = vendorComps.at(0); + m_BuyScalar = vendorData.buyScalar; + m_SellScalar = vendorData.sellScalar; + m_RefreshTimeSeconds = vendorData.refreshTimeSeconds; + m_LootMatrixID = vendorData.LootMatrixIndex; } diff --git a/dGame/dComponents/VendorComponent.h b/dGame/dComponents/VendorComponent.h index 5ef992af..70f748a2 100644 --- a/dGame/dComponents/VendorComponent.h +++ b/dGame/dComponents/VendorComponent.h @@ -2,6 +2,7 @@ #ifndef VENDORCOMPONENT_H #define VENDORCOMPONENT_H +#include #include "CDClientManager.h" #include "Component.h" #include "Entity.h" @@ -9,6 +10,15 @@ #include "RakNetTypes.h" #include "eReplicaComponentType.h" +struct SoldItem { + SoldItem(const LOT lot, const int32_t sortPriority) { + this->lot = lot; + this->sortPriority = sortPriority; + }; + LOT lot = 0; + int32_t sortPriority = 0; +}; + /** * A component for vendor NPCs. A vendor sells items to the player. */ @@ -17,37 +27,31 @@ public: inline static const eReplicaComponentType ComponentType = eReplicaComponentType::VENDOR; VendorComponent(Entity* parent); + void Startup() override; void Serialize(RakNet::BitStream* outBitStream, bool bIsInitialUpdate, unsigned int& flags); void OnUse(Entity* originator) override; - float GetBuyScalar() const { - return m_BuyScalar; - } + float GetBuyScalar() const { return m_BuyScalar; } - float GetSellScalar() const { - return m_SellScalar; - } + float GetSellScalar() const { return m_SellScalar; } - void SetBuyScalar(float value) { - m_BuyScalar = value; - } + void SetBuyScalar(const float value) { m_BuyScalar = value; } - void SetSellScalar(float value) { - m_SellScalar = value; - } + void SetSellScalar(const float value) { m_SellScalar = value; } - std::map& GetInventory() { + std::vector& GetInventory() { return m_Inventory; } - void SetHasMultiCostItems(bool hasMultiCostItems) { + void SetHasMultiCostItems(const bool hasMultiCostItems) { if (m_HasMultiCostItems == hasMultiCostItems) return; m_HasMultiCostItems = hasMultiCostItems; m_DirtyVendor = true; } - void SetHasStandardCostItems(bool hasStandardCostItems) { + + void SetHasStandardCostItems(const bool hasStandardCostItems) { if (m_HasStandardCostItems == hasStandardCostItems) return; m_HasStandardCostItems = hasStandardCostItems; m_DirtyVendor = true; @@ -64,37 +68,39 @@ public: void SetupConstants(); bool SellsItem(const LOT item) const { - return m_Inventory.find(item) != m_Inventory.end(); + return std::count_if(m_Inventory.begin(), m_Inventory.end(), [item](const SoldItem& lhs) { + return lhs.lot == item; + }) > 0; } private: /** * The buy scalar. */ - float m_BuyScalar; + float m_BuyScalar = 0.0f; /** * The sell scalar. */ - float m_SellScalar; + float m_SellScalar = 0.0f; /** * The refresh time of this vendors' inventory. */ - float m_RefreshTimeSeconds; + float m_RefreshTimeSeconds = 0.0f; /** * Loot matrix id of this vendor. */ - uint32_t m_LootMatrixID; + uint32_t m_LootMatrixID = 0; /** * The list of items the vendor sells. */ - std::map m_Inventory; + std::vector m_Inventory; - bool m_DirtyVendor; - bool m_HasStandardCostItems; - bool m_HasMultiCostItems; + bool m_DirtyVendor = false; + bool m_HasStandardCostItems = false; + bool m_HasMultiCostItems = false; }; #endif // VENDORCOMPONENT_H diff --git a/dGame/dGameMessages/GameMessages.cpp b/dGame/dGameMessages/GameMessages.cpp index ea7e60c0..4666227e 100644 --- a/dGame/dGameMessages/GameMessages.cpp +++ b/dGame/dGameMessages/GameMessages.cpp @@ -1286,7 +1286,7 @@ void GameMessages::SendVendorStatusUpdate(Entity* entity, const SystemAddress& s auto* vendor = entity->GetComponent(); if (!vendor) return; - std::map vendorItems = vendor->GetInventory(); + auto vendorItems = vendor->GetInventory(); bitStream.Write(entity->GetObjectID()); bitStream.Write(eGameMessageType::VENDOR_STATUS_UPDATE); @@ -1294,9 +1294,9 @@ void GameMessages::SendVendorStatusUpdate(Entity* entity, const SystemAddress& s bitStream.Write(bUpdateOnly); bitStream.Write(static_cast(vendorItems.size())); - for (std::pair item : vendorItems) { - bitStream.Write(static_cast(item.first)); - bitStream.Write(static_cast(item.second)); + for (const auto&[lot, sortPriority] : vendorItems) { + bitStream.Write(lot); + bitStream.Write(sortPriority); } if (sysAddr == UNASSIGNED_SYSTEM_ADDRESS) SEND_PACKET_BROADCAST