mirror of
				https://github.com/DarkflameUniverse/DarkflameServer.git
				synced 2025-11-04 06:32:00 +00:00 
			
		
		
		
	CombatAI and Vendor
This commit is contained in:
		@@ -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);
 | 
			
		||||
 | 
			
		||||
	auto* physicsComponentTable = CDClientManager::Instance().GetTable<CDPhysicsComponentTable>();
 | 
			
		||||
 | 
			
		||||
	if (!physicsComponentTable) return;
 | 
			
		||||
 | 
			
		||||
	if (physicsComponentTable != nullptr) {
 | 
			
		||||
	auto* info = physicsComponentTable->GetByID(componentID);
 | 
			
		||||
		if (info != nullptr) {
 | 
			
		||||
			collisionGroup = info->bStatic ? COLLISION_GROUP_NEUTRAL : info->collisionGroup;
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	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
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user