mirror of
https://github.com/DarkflameUniverse/DarkflameServer.git
synced 2024-11-26 07:27:18 +00:00
CombatAI and Vendor
This commit is contained in:
parent
ec9278286b
commit
34cfd45d40
@ -1,8 +1,9 @@
|
||||
#include "eMissionTaskType.h"
|
||||
|
||||
#ifndef __ACHIEVEMENTCACHEKEY__H__
|
||||
#define __ACHIEVEMENTCACHEKEY__H__
|
||||
|
||||
#include "eMissionTaskType.h"
|
||||
#include "GeneralUtils.h"
|
||||
|
||||
class AchievementCacheKey {
|
||||
public:
|
||||
AchievementCacheKey() {
|
||||
|
@ -1,6 +1,10 @@
|
||||
#include "BaseCombatAIComponent.h"
|
||||
#include <BitStream.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <sstream>
|
||||
#include <vector>
|
||||
|
||||
#include "BitStream.h"
|
||||
#include "Entity.h"
|
||||
#include "EntityManager.h"
|
||||
#include "ControllablePhysicsComponent.h"
|
||||
@ -15,10 +19,6 @@
|
||||
#include "CDClientManager.h"
|
||||
#include "DestroyableComponent.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <sstream>
|
||||
#include <vector>
|
||||
|
||||
#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<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
|
||||
*/
|
||||
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<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);
|
||||
|
||||
/*
|
||||
* Add physics
|
||||
*/
|
||||
|
||||
// Add physics
|
||||
int32_t collisionGroup = (COLLISION_GROUP_DYNAMIC | COLLISION_GROUP_ENEMY);
|
||||
|
||||
CDComponentsRegistryTable* componentRegistryTable = CDClientManager::Instance().GetTable<CDComponentsRegistryTable>();
|
||||
auto componentID = componentRegistryTable->GetByIDAndType(parent->GetLOT(), eReplicaComponentType::CONTROLLABLE_PHYSICS);
|
||||
auto* componentRegistryTable = CDClientManager::Instance().GetTable<CDComponentsRegistryTable>();
|
||||
if (!componentRegistryTable) return;
|
||||
|
||||
CDPhysicsComponentTable* physicsComponentTable = CDClientManager::Instance().GetTable<CDPhysicsComponentTable>();
|
||||
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<CDPhysicsComponentTable>();
|
||||
|
||||
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<MovementAIComponent>();
|
||||
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<SkillComponent>();
|
||||
|
||||
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<LWOOBJID> 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<DestroyableComponent>();
|
||||
|
||||
if (destroyable == nullptr) {
|
||||
return false;
|
||||
}
|
||||
if (!destroyable) return false;
|
||||
|
||||
auto* referenceDestroyable = m_ParentEntity->GetComponent<DestroyableComponent>();
|
||||
|
||||
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<QuickBuildComponent>();
|
||||
|
||||
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);
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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<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;
|
||||
// Done with lootMatrix table
|
||||
@ -59,16 +61,16 @@ void VendorComponent::RefreshInventory(bool isCreation) {
|
||||
|
||||
for (const auto& lootMatrix : lootMatrices) {
|
||||
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) {
|
||||
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<int32_t>(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<int32_t>(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<int32_t>(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<CDVendorComponentTable>();
|
||||
std::vector<CDVendorComponent> 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;
|
||||
}
|
||||
|
||||
|
||||
|
@ -2,6 +2,7 @@
|
||||
#ifndef VENDORCOMPONENT_H
|
||||
#define VENDORCOMPONENT_H
|
||||
|
||||
#include <functional>
|
||||
#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<LOT, int>& GetInventory() {
|
||||
std::vector<SoldItem>& 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<LOT, int> m_Inventory;
|
||||
std::vector<SoldItem> 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
|
||||
|
@ -1286,7 +1286,7 @@ void GameMessages::SendVendorStatusUpdate(Entity* entity, const SystemAddress& s
|
||||
auto* vendor = entity->GetComponent<VendorComponent>();
|
||||
if (!vendor) return;
|
||||
|
||||
std::map<LOT, int> 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<uint32_t>(vendorItems.size()));
|
||||
|
||||
for (std::pair<LOT, int> item : vendorItems) {
|
||||
bitStream.Write(static_cast<int>(item.first));
|
||||
bitStream.Write(static_cast<int>(item.second));
|
||||
for (const auto&[lot, sortPriority] : vendorItems) {
|
||||
bitStream.Write<int32_t>(lot);
|
||||
bitStream.Write<int32_t>(sortPriority);
|
||||
}
|
||||
|
||||
if (sysAddr == UNASSIGNED_SYSTEM_ADDRESS) SEND_PACKET_BROADCAST
|
||||
|
Loading…
Reference in New Issue
Block a user