CombatAI and Vendor

This commit is contained in:
David Markowitz 2023-06-26 00:15:25 -07:00
parent ec9278286b
commit 34cfd45d40
7 changed files with 173 additions and 218 deletions

View File

@ -1,8 +1,9 @@
#include "eMissionTaskType.h"
#ifndef __ACHIEVEMENTCACHEKEY__H__ #ifndef __ACHIEVEMENTCACHEKEY__H__
#define __ACHIEVEMENTCACHEKEY__H__ #define __ACHIEVEMENTCACHEKEY__H__
#include "eMissionTaskType.h"
#include "GeneralUtils.h"
class AchievementCacheKey { class AchievementCacheKey {
public: public:
AchievementCacheKey() { AchievementCacheKey() {

View File

@ -1,6 +1,10 @@
#include "BaseCombatAIComponent.h" #include "BaseCombatAIComponent.h"
#include <BitStream.h>
#include <algorithm>
#include <sstream>
#include <vector>
#include "BitStream.h"
#include "Entity.h" #include "Entity.h"
#include "EntityManager.h" #include "EntityManager.h"
#include "ControllablePhysicsComponent.h" #include "ControllablePhysicsComponent.h"
@ -15,10 +19,6 @@
#include "CDClientManager.h" #include "CDClientManager.h"
#include "DestroyableComponent.h" #include "DestroyableComponent.h"
#include <algorithm>
#include <sstream>
#include <vector>
#include "SkillComponent.h" #include "SkillComponent.h"
#include "QuickBuildComponent.h" #include "QuickBuildComponent.h"
#include "DestroyableComponent.h" #include "DestroyableComponent.h"
@ -26,21 +26,25 @@
#include "CDComponentsRegistryTable.h" #include "CDComponentsRegistryTable.h"
#include "CDPhysicsComponentTable.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; m_Target = LWOOBJID_EMPTY;
SetAiState(AiState::spawn); m_ComponentId = componentId;
SetAiState(AiState::Spawn);
m_Timer = 1.0f; m_Timer = 1.0f;
m_StartPosition = parent->GetPosition(); m_StartPosition = parent->GetPosition();
m_MovementAI = nullptr;
m_Disabled = false; m_Disabled = false;
m_SkillEntries = {}; m_SkillEntries = {};
m_MovementAI = nullptr; m_MovementAI = nullptr;
m_SoftTimer = 5.0f; m_SoftTimer = 5.0f;
m_dpEntity = nullptr;
m_dpEntityEnemy = nullptr;
}
void BaseCombatAIComponent::LoadTemplateData() {
//Grab the aggro information from BaseCombatAI: //Grab the aggro information from BaseCombatAI:
auto componentQuery = CDClientDatabase::CreatePreppedStmt( auto componentQuery = CDClientDatabase::CreatePreppedStmt(
"SELECT aggroRadius, tetherSpeed, pursuitSpeed, softTetherRadius, hardTetherRadius FROM BaseCombatAIComponent WHERE id = ?;"); "SELECT aggroRadius, tetherSpeed, pursuitSpeed, softTetherRadius, hardTetherRadius FROM BaseCombatAIComponent WHERE id = ?;");
componentQuery.bind(1, (int)id); componentQuery.bind(1, m_ComponentId);
auto componentResult = componentQuery.execQuery(); auto componentResult = componentQuery.execQuery();
@ -63,21 +67,12 @@ BaseCombatAIComponent::BaseCombatAIComponent(Entity* parent, const uint32_t id):
componentResult.finalize(); 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<float>(u"aggroRadius");
m_AggroRadius = aggroRadius != 0 ? aggroRadius : m_AggroRadius;
auto tetherRadius = m_ParentEntity->GetVar<float>(u"tetherRadius");
m_HardTetherRadius = tetherRadius != 0 ? tetherRadius : m_HardTetherRadius;
}
/* /*
* Find skills * Find skills
*/ */
auto skillQuery = CDClientDatabase::CreatePreppedStmt( auto skillQuery = CDClientDatabase::CreatePreppedStmt(
"SELECT skillID, cooldown, behaviorID FROM SkillBehavior WHERE skillID IN (SELECT skillID FROM ObjectSkills WHERE objectTemplate = ?);"); "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(); auto result = skillQuery.execQuery();
@ -90,35 +85,42 @@ BaseCombatAIComponent::BaseCombatAIComponent(Entity* parent, const uint32_t id):
auto* behavior = Behavior::CreateBehavior(behaviorId); auto* behavior = Behavior::CreateBehavior(behaviorId);
std::stringstream behaviorQuery; m_SkillEntries.push_back(AiSkillEntry(skillId, 0, abilityCooldown, behavior));
AiSkillEntry entry = { skillId, 0, abilityCooldown, behavior };
m_SkillEntries.push_back(entry);
result.nextRow(); 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<float>(u"aggroRadius");
m_AggroRadius = aggroRadius != 0 ? aggroRadius : m_AggroRadius;
auto tetherRadius = m_ParentEntity->GetVar<float>(u"tetherRadius");
m_HardTetherRadius = tetherRadius != 0 ? tetherRadius : m_HardTetherRadius;
}
}
void BaseCombatAIComponent::Startup() {
Stun(1.0f); Stun(1.0f);
/* // Add physics
* Add physics
*/
int32_t collisionGroup = (COLLISION_GROUP_DYNAMIC | COLLISION_GROUP_ENEMY); int32_t collisionGroup = (COLLISION_GROUP_DYNAMIC | COLLISION_GROUP_ENEMY);
CDComponentsRegistryTable* componentRegistryTable = CDClientManager::Instance().GetTable<CDComponentsRegistryTable>(); auto* componentRegistryTable = CDClientManager::Instance().GetTable<CDComponentsRegistryTable>();
auto componentID = componentRegistryTable->GetByIDAndType(parent->GetLOT(), eReplicaComponentType::CONTROLLABLE_PHYSICS); if (!componentRegistryTable) return;
CDPhysicsComponentTable* physicsComponentTable = CDClientManager::Instance().GetTable<CDPhysicsComponentTable>(); auto componentID = componentRegistryTable->GetByIDAndType(m_ParentEntity->GetLOT(), eReplicaComponentType::CONTROLLABLE_PHYSICS);
if (physicsComponentTable != nullptr) { auto* physicsComponentTable = CDClientManager::Instance().GetTable<CDPhysicsComponentTable>();
auto* info = physicsComponentTable->GetByID(componentID);
if (info != nullptr) {
collisionGroup = info->bStatic ? COLLISION_GROUP_NEUTRAL : info->collisionGroup;
}
}
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. //Create a phantom physics volume so we can detect when we're aggro'd.
m_dpEntity = new dpEntity(m_ParentEntity->GetObjectID(), m_AggroRadius); m_dpEntity = new dpEntity(m_ParentEntity->GetObjectID(), m_AggroRadius);
m_dpEntityEnemy = new dpEntity(m_ParentEntity->GetObjectID(), m_AggroRadius, false); m_dpEntityEnemy = new dpEntity(m_ParentEntity->GetObjectID(), m_AggroRadius, false);
@ -135,11 +137,12 @@ BaseCombatAIComponent::BaseCombatAIComponent(Entity* parent, const uint32_t id):
} }
BaseCombatAIComponent::~BaseCombatAIComponent() { BaseCombatAIComponent::~BaseCombatAIComponent() {
if (m_dpEntity) if (m_dpEntity) dpWorld::Instance().RemoveEntity(m_dpEntity);
dpWorld::Instance().RemoveEntity(m_dpEntity);
if (m_dpEntityEnemy) if (m_dpEntityEnemy) dpWorld::Instance().RemoveEntity(m_dpEntityEnemy);
dpWorld::Instance().RemoveEntity(m_dpEntityEnemy); m_MovementAI = nullptr;
m_dpEntity = nullptr;
m_dpEntityEnemy = nullptr;
} }
void BaseCombatAIComponent::Update(const float deltaTime) { void BaseCombatAIComponent::Update(const float deltaTime) {
@ -174,14 +177,12 @@ void BaseCombatAIComponent::Update(const float deltaTime) {
if (m_SoftTimer <= 0.0f) { if (m_SoftTimer <= 0.0f) {
EntityManager::Instance()->SerializeEntity(m_ParentEntity); EntityManager::Instance()->SerializeEntity(m_ParentEntity);
m_SoftTimer = 5.0f; m_SoftTimer = 5.0f;
} else { } else {
m_SoftTimer -= deltaTime; m_SoftTimer -= deltaTime;
} }
if (m_Disabled || m_ParentEntity->IsDead()) if (m_Disabled || m_ParentEntity->IsDead()) return;
return;
bool stunnedThisFrame = m_Stunned; bool stunnedThisFrame = m_Stunned;
CalculateCombat(deltaTime); // Putting this here for now CalculateCombat(deltaTime); // Putting this here for now
@ -189,16 +190,13 @@ void BaseCombatAIComponent::Update(const float deltaTime) {
m_StartPosition = m_ParentEntity->GetPosition(); m_StartPosition = m_ParentEntity->GetPosition();
} }
if (m_MovementAI == nullptr) { if (!m_MovementAI) {
m_MovementAI = m_ParentEntity->GetComponent<MovementAIComponent>(); m_MovementAI = m_ParentEntity->GetComponent<MovementAIComponent>();
if (m_MovementAI == nullptr) { if (!m_MovementAI) return;
return;
}
} }
if (stunnedThisFrame) { if (stunnedThisFrame) {
m_MovementAI->Stop(); m_MovementAI->Stop();
return; return;
} }
@ -208,24 +206,25 @@ void BaseCombatAIComponent::Update(const float deltaTime) {
} }
switch (m_State) { switch (m_State) {
case AiState::spawn: case AiState::Spawn:
Stun(2.0f); Stun(2.0f);
SetAiState(AiState::idle); SetAiState(AiState::Idle);
break; break;
case AiState::idle: case AiState::Idle:
Wander(); Wander();
break; break;
case AiState::aggro: case AiState::Aggro:
OnAggro(); OnAggro();
break; break;
case AiState::tether: case AiState::Tether:
OnTether(); OnTether();
break; break;
default: default:
Game::logger->Log("BaseCombatAIComponent", "Entity %i is in an invalid state %i", m_ParentEntity->GetLOT(), m_State);
break; break;
} }
} }
@ -256,9 +255,7 @@ void BaseCombatAIComponent::CalculateCombat(const float deltaTime) {
auto* skillComponent = m_ParentEntity->GetComponent<SkillComponent>(); auto* skillComponent = m_ParentEntity->GetComponent<SkillComponent>();
if (skillComponent == nullptr) { if (!skillComponent) return;
return;
}
skillComponent->CalculateUpdate(deltaTime); skillComponent->CalculateUpdate(deltaTime);
@ -330,19 +327,19 @@ void BaseCombatAIComponent::CalculateCombat(const float deltaTime) {
SetTarget(newTarget); SetTarget(newTarget);
if (m_Target != LWOOBJID_EMPTY) { if (m_Target != LWOOBJID_EMPTY) {
if (m_State == AiState::idle) { if (m_State == AiState::Idle) {
m_Timer = 0; m_Timer = 0;
} }
SetAiState(AiState::aggro); SetAiState(AiState::Aggro);
} else { } else {
SetAiState(AiState::idle); SetAiState(AiState::Idle);
} }
if (!hasSkillToCast) return; if (!hasSkillToCast) return;
if (m_Target == LWOOBJID_EMPTY) { if (m_Target == LWOOBJID_EMPTY) {
SetAiState(AiState::idle); SetAiState(AiState::Idle);
return; return;
} }
@ -367,7 +364,7 @@ void BaseCombatAIComponent::CalculateCombat(const float deltaTime) {
m_MovementAI->Stop(); m_MovementAI->Stop();
} }
SetAiState(AiState::aggro); SetAiState(AiState::Aggro);
m_Timer = 0; m_Timer = 0;
@ -383,8 +380,6 @@ void BaseCombatAIComponent::CalculateCombat(const float deltaTime) {
} }
LWOOBJID BaseCombatAIComponent::FindTarget() { LWOOBJID BaseCombatAIComponent::FindTarget() {
//const auto reference = m_MovementAI == nullptr ? m_StartPosition : m_MovementAI->ApproximateLocation();
NiPoint3 reference = m_StartPosition; NiPoint3 reference = m_StartPosition;
if (m_MovementAI) reference = m_MovementAI->ApproximateLocation(); if (m_MovementAI) reference = m_MovementAI->ApproximateLocation();
@ -510,23 +505,10 @@ std::vector<LWOOBJID> BaseCombatAIComponent::GetTargetWithinAggroRange() const {
return targets; 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) { void BaseCombatAIComponent::Serialize(RakNet::BitStream* outBitStream, bool bIsInitialUpdate, unsigned int& flags) {
outBitStream->Write(m_DirtyStateOrTarget || bIsInitialUpdate); outBitStream->Write(m_DirtyStateOrTarget || bIsInitialUpdate);
if (m_DirtyStateOrTarget || bIsInitialUpdate) { if (m_DirtyStateOrTarget || bIsInitialUpdate) {
outBitStream->Write(uint32_t(m_State)); outBitStream->Write(m_State);
outBitStream->Write(m_Target); outBitStream->Write(m_Target);
m_DirtyStateOrTarget = false; m_DirtyStateOrTarget = false;
} }
@ -542,7 +524,7 @@ void BaseCombatAIComponent::SetAiState(AiState newState) {
bool BaseCombatAIComponent::IsEnemy(LWOOBJID target) const { bool BaseCombatAIComponent::IsEnemy(LWOOBJID target) const {
auto* entity = EntityManager::Instance()->GetEntity(target); auto* entity = EntityManager::Instance()->GetEntity(target);
if (entity == nullptr) { if (!entity) {
Game::logger->Log("BaseCombatAIComponent", "Invalid entity for checking validity (%llu)!", target); Game::logger->Log("BaseCombatAIComponent", "Invalid entity for checking validity (%llu)!", target);
return false; return false;
@ -550,13 +532,11 @@ bool BaseCombatAIComponent::IsEnemy(LWOOBJID target) const {
auto* destroyable = entity->GetComponent<DestroyableComponent>(); auto* destroyable = entity->GetComponent<DestroyableComponent>();
if (destroyable == nullptr) { if (!destroyable) return false;
return false;
}
auto* referenceDestroyable = m_ParentEntity->GetComponent<DestroyableComponent>(); auto* referenceDestroyable = m_ParentEntity->GetComponent<DestroyableComponent>();
if (referenceDestroyable == nullptr) { if (!referenceDestroyable) {
Game::logger->Log("BaseCombatAIComponent", "Invalid reference destroyable component on (%llu)!", m_ParentEntity->GetObjectID()); Game::logger->Log("BaseCombatAIComponent", "Invalid reference destroyable component on (%llu)!", m_ParentEntity->GetObjectID());
return false; return false;
@ -564,7 +544,7 @@ bool BaseCombatAIComponent::IsEnemy(LWOOBJID target) const {
auto* quickbuild = entity->GetComponent<QuickBuildComponent>(); auto* quickbuild = entity->GetComponent<QuickBuildComponent>();
if (quickbuild != nullptr) { if (quickbuild) {
const auto state = quickbuild->GetState(); const auto state = quickbuild->GetState();
if (state != eRebuildState::COMPLETED) { if (state != eRebuildState::COMPLETED) {
@ -576,7 +556,7 @@ bool BaseCombatAIComponent::IsEnemy(LWOOBJID target) const {
auto candidateList = destroyable->GetFactionIDs(); auto candidateList = destroyable->GetFactionIDs();
for (auto value : candidateList) { for (const auto value : candidateList) {
if (std::find(enemyList.begin(), enemyList.end(), value) != enemyList.end()) { if (std::find(enemyList.begin(), enemyList.end(), value) != enemyList.end()) {
return true; return true;
} }
@ -598,8 +578,7 @@ Entity* BaseCombatAIComponent::GetTargetEntity() const {
void BaseCombatAIComponent::Taunt(LWOOBJID offender, float threat) { void BaseCombatAIComponent::Taunt(LWOOBJID offender, float threat) {
// Can't taunt self // Can't taunt self
if (offender == m_ParentEntity->GetObjectID()) if (offender == m_ParentEntity->GetObjectID()) return;
return;
m_ThreatEntries[offender] += threat; m_ThreatEntries[offender] += threat;
m_DirtyThreat = true; m_DirtyThreat = true;
@ -634,9 +613,7 @@ void BaseCombatAIComponent::ClearThreat() {
} }
void BaseCombatAIComponent::Wander() { void BaseCombatAIComponent::Wander() {
if (!m_MovementAI->AtFinalWaypoint()) { if (!m_MovementAI->AtFinalWaypoint()) return;
return;
}
m_MovementAI->SetHaltDistance(0); m_MovementAI->SetHaltDistance(0);
@ -679,9 +656,7 @@ void BaseCombatAIComponent::OnAggro() {
auto* target = GetTargetEntity(); auto* target = GetTargetEntity();
if (target == nullptr) { if (!target) return;
return;
}
m_MovementAI->SetHaltDistance(m_AttackRadius); m_MovementAI->SetHaltDistance(m_AttackRadius);
@ -704,7 +679,7 @@ void BaseCombatAIComponent::OnAggro() {
m_MovementAI->SetDestination(targetPos); m_MovementAI->SetDestination(targetPos);
SetAiState(AiState::tether); SetAiState(AiState::Tether);
} }
m_Timer += 0.5f; m_Timer += 0.5f;
@ -730,7 +705,7 @@ void BaseCombatAIComponent::OnTether() {
m_MovementAI->SetDestination(m_StartPosition); m_MovementAI->SetDestination(m_StartPosition);
SetAiState(AiState::aggro); SetAiState(AiState::Aggro);
} else { } else {
if (IsMech() && Vector3::DistanceSquared(targetPos, currentPos) > m_AttackRadius * m_AttackRadius * 3 * 3) return; if (IsMech() && Vector3::DistanceSquared(targetPos, currentPos) > m_AttackRadius * m_AttackRadius * 3 * 3) return;
@ -742,64 +717,20 @@ void BaseCombatAIComponent::OnTether() {
m_Timer += 0.5f; 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) { void BaseCombatAIComponent::Stun(const float time) {
if (m_StunImmune || m_StunTime > time) { if (m_StunImmune || m_StunTime > time) return;
return;
}
m_StunTime = time; m_StunTime = time;
m_Stunned = true; 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) { void BaseCombatAIComponent::LookAt(const NiPoint3& point) {
if (m_Stunned) { if (m_Stunned) return;
return;
}
m_ParentEntity->SetRotation(NiQuaternion::LookAt(m_ParentEntity->GetPosition(), point)); 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() { void BaseCombatAIComponent::Sleep() {
m_dpEntity->SetSleeping(true); m_dpEntity->SetSleeping(true);
m_dpEntityEnemy->SetSleeping(true); m_dpEntityEnemy->SetSleeping(true);

View File

@ -20,20 +20,25 @@ class Entity;
/** /**
* The current state of the AI * The current state of the AI
*/ */
enum class AiState : int { enum class AiState : int32_t {
idle = 0, // Doing nothing Idle = 0, // Doing nothing
aggro, // Waiting for an enemy to cross / running back to spawn Aggro, // Waiting for an enemy to cross / running back to spawn
tether, // Chasing an enemy Tether, // Chasing an enemy
spawn, // Spawning into the world Spawn, // Spawning into the world
dead // Killed Dead // Killed
}; };
/** /**
* Represents a skill that can be cast by this enemy, including its cooldowns, which determines how often the skill * Represents a skill that can be cast by this enemy, including its cooldowns, which determines how often the skill
* may be cast. * 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; uint32_t skillId;
float cooldown; float cooldown;
@ -53,6 +58,9 @@ public:
BaseCombatAIComponent(Entity* parentEntity, uint32_t id); BaseCombatAIComponent(Entity* parentEntity, uint32_t id);
~BaseCombatAIComponent() override; ~BaseCombatAIComponent() override;
void LoadTemplateData() override;
void LoadConfigData() override;
void Startup() override;
void Update(float deltaTime) override; void Update(float deltaTime) override;
void Serialize(RakNet::BitStream* outBitStream, bool bIsInitialUpdate, unsigned int& flags); void Serialize(RakNet::BitStream* outBitStream, bool bIsInitialUpdate, unsigned int& flags);
@ -147,37 +155,37 @@ public:
* Gets whether or not the entity is currently stunned * Gets whether or not the entity is currently stunned
* @return whether 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 * (un)stuns the entity, determining whether it'll be able to attack other entities
* @param value whether the enemy is stunned * @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 * Gets if this entity may be stunned
* @return 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 * Set the stun immune value, determining if the entity may be stunned
* @param value * @param value
*/ */
void SetStunImmune(bool value); void SetStunImmune(bool value) { m_StunImmune = value; }
/** /**
* Gets the current speed at which an entity runs when tethering * Gets the current speed at which an entity runs when tethering
* @return 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 * Sets the speed at which an entity will tether
* @param value the new tether speed * @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 * 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 * Gets the radius that will cause this entity to get aggro'd, causing a target chase
* @return the aggro radius of the entity * @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 * Sets the aggro radius, causing the entity to start chasing enemies in this range
* @param value the aggro radius to set * @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 * 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 * (dis)ables the AI, causing it to stop/start attacking enemies
* @param value * @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 * Gets the current state of the AI, whether or not it's looking for enemies to attack
* @return * @return
*/ */
bool GetDistabled() const; bool GetDistabled() const { return m_Disabled; }
/** /**
* Turns the entity asleep, stopping updates to its physics volumes * 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 * Whether the current entity is a mech enemy, needed as mechs tether radius works differently
* @return whether this entity is a mech * @return whether this entity is a mech
*/ */
bool IsMech(); bool IsMech() const { return m_ParentEntity->GetLOT() == 6253; };
int32_t m_ComponentId;
}; };
#endif // BASECOMBATAICOMPONENT_H #endif // BASECOMBATAICOMPONENT_H

View File

@ -128,7 +128,7 @@ LWOSpawnComponent
LWOSpringpadComponent LWOSpringpadComponent
LWOSwitchComponent LWOSwitchComponent
LWOTriggerComponent 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 ├~~ LWOInteractionManagerComponent
├~~ LWOUserControlComponent ├~~ LWOUserControlComponent
├~~ LWOFriendsListComponent ├~~ LWOFriendsListComponent

View File

@ -14,6 +14,9 @@
VendorComponent::VendorComponent(Entity* parent) : Component(parent) { VendorComponent::VendorComponent(Entity* parent) : Component(parent) {
m_HasStandardCostItems = false; m_HasStandardCostItems = false;
m_HasMultiCostItems = false; m_HasMultiCostItems = false;
}
void VendorComponent::Startup() {
SetupConstants(); SetupConstants();
RefreshInventory(true); RefreshInventory(true);
} }
@ -31,24 +34,23 @@ void VendorComponent::OnUse(Entity* originator) {
GameMessages::SendVendorStatusUpdate(m_ParentEntity, originator->GetSystemAddress()); GameMessages::SendVendorStatusUpdate(m_ParentEntity, originator->GetSystemAddress());
} }
void VendorComponent::RefreshInventory(bool isCreation) { void VendorComponent::RefreshInventory(bool isCreation) {
SetHasStandardCostItems(false); SetHasStandardCostItems(false);
SetHasMultiCostItems(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 (m_ParentEntity->GetLOT() == 9749 && Game::server->GetZoneID() == 1201) {
if (!isCreation) return; if (!isCreation) return;
SetHasStandardCostItems(true); SetHasStandardCostItems(true);
m_Inventory.insert({ 11909, 0 }); // Top hat w frog m_Inventory.push_back(SoldItem(11909, 0)); // Top hat w frog
m_Inventory.insert({ 7785, 0 }); // Flash bulb m_Inventory.push_back(SoldItem(7785, 0)); // Flash bulb
m_Inventory.insert({ 12764, 0 }); // Big fountain soda m_Inventory.push_back(SoldItem(12764, 0)); // Big fountain soda
m_Inventory.insert({ 12241, 0 }); // Hot cocoa (from fb) m_Inventory.push_back(SoldItem(12241, 0)); // Hot cocoa (from fb)
return; return;
} }
m_Inventory.clear(); m_Inventory.clear();
auto* lootMatrixTable = CDClientManager::Instance().GetTable<CDLootMatrixTable>(); auto* lootMatrixTable = CDClientManager::Instance().GetTable<CDLootMatrixTable>();
std::vector<CDLootMatrix> 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; if (lootMatrices.empty()) return;
// Done with lootMatrix table // Done with lootMatrix table
@ -59,16 +61,16 @@ void VendorComponent::RefreshInventory(bool isCreation) {
for (const auto& lootMatrix : lootMatrices) { for (const auto& lootMatrix : lootMatrices) {
int lootTableID = lootMatrix.LootTableIndex; int lootTableID = lootMatrix.LootTableIndex;
std::vector<CDLootTable> 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) { if (lootMatrix.maxToDrop == 0 || lootMatrix.minToDrop == 0) {
for (CDLootTable item : vendorItems) { for (const auto& item : vendorItems) {
if (!m_HasStandardCostItems || !m_HasMultiCostItems){ if (!m_HasStandardCostItems || !m_HasMultiCostItems) {
auto itemComponentID = compRegistryTable->GetByIDAndType(item.itemid, eReplicaComponentType::ITEM); auto itemComponentID = compRegistryTable->GetByIDAndType(item.itemid, eReplicaComponentType::ITEM);
auto itemComponent = itemComponentTable->GetItemComponentByID(itemComponentID); auto itemComponent = itemComponentTable->GetItemComponentByID(itemComponentID);
if (!m_HasStandardCostItems && itemComponent.baseValue != -1) SetHasStandardCostItems(true); if (!m_HasStandardCostItems && itemComponent.baseValue != -1) SetHasStandardCostItems(true);
if (!m_HasMultiCostItems && !itemComponent.currencyCosts.empty()) SetHasMultiCostItems(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 { } else {
auto randomCount = GeneralUtils::GenerateRandomNumber<int32_t>(lootMatrix.minToDrop, lootMatrix.maxToDrop); auto randomCount = GeneralUtils::GenerateRandomNumber<int32_t>(lootMatrix.minToDrop, lootMatrix.maxToDrop);
@ -76,43 +78,47 @@ void VendorComponent::RefreshInventory(bool isCreation) {
for (size_t i = 0; i < randomCount; i++) { for (size_t i = 0; i < randomCount; i++) {
if (vendorItems.empty()) break; if (vendorItems.empty()) break;
auto randomItemIndex = GeneralUtils::GenerateRandomNumber<int32_t>(0, vendorItems.size() - 1); auto randomItemIndex = GeneralUtils::GenerateRandomNumber<int32_t>(0, vendorItems.size() - 1);
const auto& randomItem = vendorItems[randomItemIndex]; const auto& randomItem = vendorItems.at(randomItemIndex);
vendorItems.erase(vendorItems.begin() + randomItemIndex); vendorItems.erase(vendorItems.begin() + randomItemIndex);
if (!m_HasStandardCostItems || !m_HasMultiCostItems){ if (!m_HasStandardCostItems || !m_HasMultiCostItems) {
auto itemComponentID = compRegistryTable->GetByIDAndType(randomItem.itemid, eReplicaComponentType::ITEM); 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); auto itemComponent = itemComponentTable->GetItemComponentByID(itemComponentID);
if (!m_HasStandardCostItems && itemComponent.baseValue != -1) SetHasStandardCostItems(true); if (!m_HasStandardCostItems && itemComponent.baseValue != -1) SetHasStandardCostItems(true);
if (!m_HasMultiCostItems && !itemComponent.currencyCosts.empty()) SetHasMultiCostItems(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) { if (m_ParentEntity->GetLOT() == 13569) {
auto randomCamera = GeneralUtils::GenerateRandomNumber<int32_t>(0, 2); auto randomCamera = GeneralUtils::GenerateRandomNumber<int32_t>(0, 2);
LOT camera = 0;
DluAssert(randomCamera >= 0 && randomCamera <= 2);
switch (randomCamera) { switch (randomCamera) {
case 0: case 0:
m_Inventory.insert({ 16253, 0 }); //Grungagroid camera = 16253; // Grungagroid
break; break;
case 1: case 1:
m_Inventory.insert({ 16254, 0 }); //Hipstabrick camera = 16254; // Hipstabrick
break; break;
case 2: case 2:
m_Inventory.insert({ 16204, 0 }); //Megabrixel snapshot camera = 16204; // Megabrixel snapshot
break;
default:
break; break;
} }
m_Inventory.push_back(SoldItem(camera, 0)); //Megabrixel snapshot
} }
// Callback timer to refresh this inventory. // Callback timer to refresh this inventory.
m_ParentEntity->AddCallbackTimer(m_RefreshTimeSeconds, [this]() { m_ParentEntity->AddCallbackTimer(m_RefreshTimeSeconds, [this]() {
RefreshInventory(); RefreshInventory();
} });
);
EntityManager::Instance()->SerializeEntity(m_ParentEntity); EntityManager::Instance()->SerializeEntity(m_ParentEntity);
GameMessages::SendVendorStatusUpdate(m_ParentEntity, UNASSIGNED_SYSTEM_ADDRESS); GameMessages::SendVendorStatusUpdate(m_ParentEntity, UNASSIGNED_SYSTEM_ADDRESS);
} }
@ -124,10 +130,11 @@ void VendorComponent::SetupConstants() {
auto* vendorComponentTable = CDClientManager::Instance().GetTable<CDVendorComponentTable>(); auto* vendorComponentTable = CDClientManager::Instance().GetTable<CDVendorComponentTable>();
std::vector<CDVendorComponent> vendorComps = vendorComponentTable->Query([=](CDVendorComponent entry) { return (entry.id == componentID); }); std::vector<CDVendorComponent> vendorComps = vendorComponentTable->Query([=](CDVendorComponent entry) { return (entry.id == componentID); });
if (vendorComps.empty()) return; if (vendorComps.empty()) return;
m_BuyScalar = vendorComps[0].buyScalar; auto vendorData = vendorComps.at(0);
m_SellScalar = vendorComps[0].sellScalar; m_BuyScalar = vendorData.buyScalar;
m_RefreshTimeSeconds = vendorComps[0].refreshTimeSeconds; m_SellScalar = vendorData.sellScalar;
m_LootMatrixID = vendorComps[0].LootMatrixIndex; m_RefreshTimeSeconds = vendorData.refreshTimeSeconds;
m_LootMatrixID = vendorData.LootMatrixIndex;
} }

View File

@ -2,6 +2,7 @@
#ifndef VENDORCOMPONENT_H #ifndef VENDORCOMPONENT_H
#define VENDORCOMPONENT_H #define VENDORCOMPONENT_H
#include <functional>
#include "CDClientManager.h" #include "CDClientManager.h"
#include "Component.h" #include "Component.h"
#include "Entity.h" #include "Entity.h"
@ -9,6 +10,15 @@
#include "RakNetTypes.h" #include "RakNetTypes.h"
#include "eReplicaComponentType.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. * A component for vendor NPCs. A vendor sells items to the player.
*/ */
@ -17,37 +27,31 @@ public:
inline static const eReplicaComponentType ComponentType = eReplicaComponentType::VENDOR; inline static const eReplicaComponentType ComponentType = eReplicaComponentType::VENDOR;
VendorComponent(Entity* parent); VendorComponent(Entity* parent);
void Startup() override;
void Serialize(RakNet::BitStream* outBitStream, bool bIsInitialUpdate, unsigned int& flags); void Serialize(RakNet::BitStream* outBitStream, bool bIsInitialUpdate, unsigned int& flags);
void OnUse(Entity* originator) override; void OnUse(Entity* originator) override;
float GetBuyScalar() const { float GetBuyScalar() const { return m_BuyScalar; }
return m_BuyScalar;
}
float GetSellScalar() const { float GetSellScalar() const { return m_SellScalar; }
return m_SellScalar;
}
void SetBuyScalar(float value) { void SetBuyScalar(const float value) { m_BuyScalar = value; }
m_BuyScalar = value;
}
void SetSellScalar(float value) { void SetSellScalar(const float value) { m_SellScalar = value; }
m_SellScalar = value;
}
std::map<LOT, int>& GetInventory() { std::vector<SoldItem>& GetInventory() {
return m_Inventory; return m_Inventory;
} }
void SetHasMultiCostItems(bool hasMultiCostItems) { void SetHasMultiCostItems(const bool hasMultiCostItems) {
if (m_HasMultiCostItems == hasMultiCostItems) return; if (m_HasMultiCostItems == hasMultiCostItems) return;
m_HasMultiCostItems = hasMultiCostItems; m_HasMultiCostItems = hasMultiCostItems;
m_DirtyVendor = true; m_DirtyVendor = true;
} }
void SetHasStandardCostItems(bool hasStandardCostItems) {
void SetHasStandardCostItems(const bool hasStandardCostItems) {
if (m_HasStandardCostItems == hasStandardCostItems) return; if (m_HasStandardCostItems == hasStandardCostItems) return;
m_HasStandardCostItems = hasStandardCostItems; m_HasStandardCostItems = hasStandardCostItems;
m_DirtyVendor = true; m_DirtyVendor = true;
@ -64,37 +68,39 @@ public:
void SetupConstants(); void SetupConstants();
bool SellsItem(const LOT item) const { 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: private:
/** /**
* The buy scalar. * The buy scalar.
*/ */
float m_BuyScalar; float m_BuyScalar = 0.0f;
/** /**
* The sell scalar. * The sell scalar.
*/ */
float m_SellScalar; float m_SellScalar = 0.0f;
/** /**
* The refresh time of this vendors' inventory. * The refresh time of this vendors' inventory.
*/ */
float m_RefreshTimeSeconds; float m_RefreshTimeSeconds = 0.0f;
/** /**
* Loot matrix id of this vendor. * Loot matrix id of this vendor.
*/ */
uint32_t m_LootMatrixID; uint32_t m_LootMatrixID = 0;
/** /**
* The list of items the vendor sells. * The list of items the vendor sells.
*/ */
std::map<LOT, int> m_Inventory; std::vector<SoldItem> m_Inventory;
bool m_DirtyVendor; bool m_DirtyVendor = false;
bool m_HasStandardCostItems; bool m_HasStandardCostItems = false;
bool m_HasMultiCostItems; bool m_HasMultiCostItems = false;
}; };
#endif // VENDORCOMPONENT_H #endif // VENDORCOMPONENT_H

View File

@ -1286,7 +1286,7 @@ void GameMessages::SendVendorStatusUpdate(Entity* entity, const SystemAddress& s
auto* vendor = entity->GetComponent<VendorComponent>(); auto* vendor = entity->GetComponent<VendorComponent>();
if (!vendor) return; if (!vendor) return;
std::map<LOT, int> vendorItems = vendor->GetInventory(); auto vendorItems = vendor->GetInventory();
bitStream.Write(entity->GetObjectID()); bitStream.Write(entity->GetObjectID());
bitStream.Write(eGameMessageType::VENDOR_STATUS_UPDATE); bitStream.Write(eGameMessageType::VENDOR_STATUS_UPDATE);
@ -1294,9 +1294,9 @@ void GameMessages::SendVendorStatusUpdate(Entity* entity, const SystemAddress& s
bitStream.Write(bUpdateOnly); bitStream.Write(bUpdateOnly);
bitStream.Write(static_cast<uint32_t>(vendorItems.size())); bitStream.Write(static_cast<uint32_t>(vendorItems.size()));
for (std::pair<LOT, int> item : vendorItems) { for (const auto&[lot, sortPriority] : vendorItems) {
bitStream.Write(static_cast<int>(item.first)); bitStream.Write<int32_t>(lot);
bitStream.Write(static_cast<int>(item.second)); bitStream.Write<int32_t>(sortPriority);
} }
if (sysAddr == UNASSIGNED_SYSTEM_ADDRESS) SEND_PACKET_BROADCAST if (sysAddr == UNASSIGNED_SYSTEM_ADDRESS) SEND_PACKET_BROADCAST