mirror of
https://github.com/DarkflameUniverse/DarkflameServer.git
synced 2025-08-09 20:24:16 +00:00
Grim LU
Wincent's attempt at making LU into something it isn't supposed to be, an ARPG.
This commit is contained in:
@@ -146,6 +146,12 @@ void BaseCombatAIComponent::Update(const float deltaTime) {
|
||||
//First, we need to process physics:
|
||||
if (!m_dpEntity) return;
|
||||
|
||||
if (m_State == AiState::spawn) {
|
||||
auto* destroyable = m_Parent->GetComponent<DestroyableComponent>();
|
||||
|
||||
destroyable->ComputeBaseStats(true);
|
||||
}
|
||||
|
||||
m_dpEntity->SetPosition(m_Parent->GetPosition()); //make sure our position is synced with our dpEntity
|
||||
m_dpEntityEnemy->SetPosition(m_Parent->GetPosition());
|
||||
|
||||
@@ -234,7 +240,7 @@ void BaseCombatAIComponent::CalculateCombat(const float deltaTime) {
|
||||
bool hasSkillToCast = false;
|
||||
for (auto& entry : m_SkillEntries) {
|
||||
if (entry.cooldown > 0.0f) {
|
||||
entry.cooldown -= deltaTime;
|
||||
entry.cooldown -= deltaTime * 2;
|
||||
} else {
|
||||
hasSkillToCast = true;
|
||||
}
|
||||
|
@@ -153,7 +153,7 @@ void BuffComponent::ApplyBuffEffect(int32_t id) {
|
||||
|
||||
if (destroyable == nullptr) return;
|
||||
|
||||
destroyable->SetMaxHealth(destroyable->GetMaxHealth() + maxHealth);
|
||||
destroyable->SetMaxHealth(destroyable->GetMaxHealth() + maxHealth * BASE_MULTIPLIER);
|
||||
} else if (parameter.name == "max_armor") {
|
||||
const auto maxArmor = parameter.value;
|
||||
|
||||
@@ -161,7 +161,7 @@ void BuffComponent::ApplyBuffEffect(int32_t id) {
|
||||
|
||||
if (destroyable == nullptr) return;
|
||||
|
||||
destroyable->SetMaxArmor(destroyable->GetMaxArmor() + maxArmor);
|
||||
destroyable->SetMaxArmor(destroyable->GetMaxArmor() + maxArmor * BASE_MULTIPLIER);
|
||||
} else if (parameter.name == "max_imagination") {
|
||||
const auto maxImagination = parameter.value;
|
||||
|
||||
@@ -169,7 +169,7 @@ void BuffComponent::ApplyBuffEffect(int32_t id) {
|
||||
|
||||
if (destroyable == nullptr) return;
|
||||
|
||||
destroyable->SetMaxImagination(destroyable->GetMaxImagination() + maxImagination);
|
||||
destroyable->SetMaxImagination(destroyable->GetMaxImagination() + maxImagination * BASE_MULTIPLIER);
|
||||
} else if (parameter.name == "speed") {
|
||||
auto* controllablePhysicsComponent = this->GetParent()->GetComponent<ControllablePhysicsComponent>();
|
||||
if (!controllablePhysicsComponent) return;
|
||||
@@ -189,7 +189,7 @@ void BuffComponent::RemoveBuffEffect(int32_t id) {
|
||||
|
||||
if (destroyable == nullptr) return;
|
||||
|
||||
destroyable->SetMaxHealth(destroyable->GetMaxHealth() - maxHealth);
|
||||
destroyable->SetMaxHealth(destroyable->GetMaxHealth() - maxHealth * BASE_MULTIPLIER);
|
||||
} else if (parameter.name == "max_armor") {
|
||||
const auto maxArmor = parameter.value;
|
||||
|
||||
@@ -197,7 +197,7 @@ void BuffComponent::RemoveBuffEffect(int32_t id) {
|
||||
|
||||
if (destroyable == nullptr) return;
|
||||
|
||||
destroyable->SetMaxArmor(destroyable->GetMaxArmor() - maxArmor);
|
||||
destroyable->SetMaxArmor(destroyable->GetMaxArmor() - maxArmor * BASE_MULTIPLIER);
|
||||
} else if (parameter.name == "max_imagination") {
|
||||
const auto maxImagination = parameter.value;
|
||||
|
||||
@@ -205,7 +205,7 @@ void BuffComponent::RemoveBuffEffect(int32_t id) {
|
||||
|
||||
if (destroyable == nullptr) return;
|
||||
|
||||
destroyable->SetMaxImagination(destroyable->GetMaxImagination() - maxImagination);
|
||||
destroyable->SetMaxImagination(destroyable->GetMaxImagination() - maxImagination * BASE_MULTIPLIER);
|
||||
} else if (parameter.name == "speed") {
|
||||
auto* controllablePhysicsComponent = this->GetParent()->GetComponent<ControllablePhysicsComponent>();
|
||||
if (!controllablePhysicsComponent) return;
|
||||
|
@@ -35,6 +35,9 @@
|
||||
#include "eMissionTaskType.h"
|
||||
#include "eStateChangeType.h"
|
||||
#include "eGameActivity.h"
|
||||
#include "LevelProgressionComponent.h"
|
||||
#include "ResistanceProfile.h"
|
||||
#include "DamageProfile.h"
|
||||
|
||||
#include "CDComponentsRegistryTable.h"
|
||||
|
||||
@@ -62,6 +65,7 @@ DestroyableComponent::DestroyableComponent(Entity* parent) : Component(parent) {
|
||||
m_MinCoins = 0;
|
||||
m_MaxCoins = 0;
|
||||
m_DamageReduction = 0;
|
||||
m_DirtyStats = true;
|
||||
|
||||
m_ImmuneToBasicAttackCount = 0;
|
||||
m_ImmuneToDamageOverTimeCount = 0;
|
||||
@@ -72,6 +76,8 @@ DestroyableComponent::DestroyableComponent(Entity* parent) : Component(parent) {
|
||||
m_ImmuneToImaginationLossCount = 0;
|
||||
m_ImmuneToQuickbuildInterruptCount = 0;
|
||||
m_ImmuneToPullToPointCount = 0;
|
||||
|
||||
m_ResistanceProfile = ResistanceProfile::FindResistanceProfile(m_Parent->GetLOT());
|
||||
}
|
||||
|
||||
DestroyableComponent::~DestroyableComponent() {
|
||||
@@ -95,6 +101,8 @@ void DestroyableComponent::Reinitialize(LOT templateID) {
|
||||
if (componentID > 0) {
|
||||
std::vector<CDDestructibleComponent> destCompData = destCompTable->Query([=](CDDestructibleComponent entry) { return (entry.id == componentID); });
|
||||
|
||||
m_Info = destCompData[0];
|
||||
|
||||
if (destCompData.size() > 0) {
|
||||
SetHealth(destCompData[0].life);
|
||||
SetImagination(destCompData[0].imagination);
|
||||
@@ -107,6 +115,11 @@ void DestroyableComponent::Reinitialize(LOT templateID) {
|
||||
SetIsSmashable(destCompData[0].isSmashable);
|
||||
}
|
||||
} else {
|
||||
m_Info = {};
|
||||
m_Info.life = 1;
|
||||
m_Info.imagination = 0;
|
||||
m_Info.armor = 0;
|
||||
|
||||
SetHealth(1);
|
||||
SetImagination(0);
|
||||
SetArmor(0);
|
||||
@@ -117,6 +130,15 @@ void DestroyableComponent::Reinitialize(LOT templateID) {
|
||||
|
||||
SetIsSmashable(true);
|
||||
}
|
||||
|
||||
ComputeBaseStats();
|
||||
|
||||
if (!m_Parent->IsPlayer())
|
||||
{
|
||||
SetHealth(GetMaxHealth());
|
||||
SetImagination(GetMaxImagination());
|
||||
SetArmor(GetMaxArmor());
|
||||
}
|
||||
}
|
||||
|
||||
void DestroyableComponent::Serialize(RakNet::BitStream* outBitStream, bool bIsInitialUpdate, uint32_t& flags) {
|
||||
@@ -810,6 +832,18 @@ void DestroyableComponent::Smash(const LWOOBJID source, const eKillType killType
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<Entity*> scripts = EntityManager::Instance()->GetEntitiesByComponent(eReplicaComponentType::SCRIPT);
|
||||
for (Entity* scriptEntity : scripts) {
|
||||
// Prevent double triggering
|
||||
if (scriptEntity->GetObjectID() == zoneControl->GetObjectID()) continue;
|
||||
if (std::find(scriptedActs.begin(), scriptedActs.end(), scriptEntity) != scriptedActs.end()) continue;
|
||||
if (scriptEntity->GetObjectID() != zoneControl->GetObjectID()) { // Don't want to trigger twice on instance worlds
|
||||
for (CppScripts::Script* script : CppScripts::GetEntityScripts(scriptEntity)) {
|
||||
script->OnPlayerDied(scriptEntity, m_Parent);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
m_Parent->Kill(owner);
|
||||
@@ -949,6 +983,8 @@ void DestroyableComponent::FixStats() {
|
||||
item->Equip();
|
||||
}
|
||||
|
||||
destroyableComponent->ComputeBaseStats();
|
||||
|
||||
// Fetch correct max stats after everything is done
|
||||
maxHealth = destroyableComponent->GetMaxHealth();
|
||||
maxArmor = destroyableComponent->GetMaxArmor();
|
||||
@@ -968,6 +1004,325 @@ void DestroyableComponent::FixStats() {
|
||||
EntityManager::Instance()->SerializeEntity(entity);
|
||||
}
|
||||
|
||||
void DestroyableComponent::AddStat(const StatProperty& stat) {
|
||||
m_DirtyStats = true;
|
||||
|
||||
const auto& typeIter = m_Stats.find(stat.type);
|
||||
|
||||
if (typeIter == m_Stats.end()) {
|
||||
m_Stats.emplace(stat.type, std::map<eStatModifier, float>());
|
||||
}
|
||||
|
||||
auto& typeMap = m_Stats.at(stat.type);
|
||||
|
||||
const auto& modifierIter = typeMap.find(stat.modifier);
|
||||
|
||||
if (modifierIter == typeMap.end()) {
|
||||
typeMap.emplace(stat.modifier, stat.value);
|
||||
return;
|
||||
}
|
||||
|
||||
typeMap.at(stat.modifier) += stat.value;
|
||||
}
|
||||
|
||||
void DestroyableComponent::RemoveStat(const StatProperty& stat) {
|
||||
m_DirtyStats = true;
|
||||
|
||||
const auto& typeIter = m_Stats.find(stat.type);
|
||||
|
||||
if (typeIter == m_Stats.end()) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto& typeMap = m_Stats.at(stat.type);
|
||||
|
||||
const auto& modifierIter = typeMap.find(stat.modifier);
|
||||
|
||||
if (modifierIter == typeMap.end()) {
|
||||
return;
|
||||
}
|
||||
|
||||
typeMap.at(stat.modifier) -= stat.value;
|
||||
|
||||
if (typeMap.at(stat.modifier) <= 0) {
|
||||
typeMap.erase(stat.modifier);
|
||||
}
|
||||
|
||||
if (typeMap.empty()) {
|
||||
m_Stats.erase(stat.type);
|
||||
}
|
||||
}
|
||||
|
||||
float DestroyableComponent::GetStat(eStatTypes statType, eStatModifier statModifier) const {
|
||||
const auto& typeIter = m_Stats.find(statType);
|
||||
|
||||
float baseResistance = 0;
|
||||
|
||||
if (statModifier == eStatModifier::DamageResistance) {
|
||||
if (m_ResistanceProfile != nullptr) {
|
||||
baseResistance = m_ResistanceProfile->GetResistanceProfile(statType);
|
||||
|
||||
Game::logger->Log("DestroyableComponent", "Base resistance: %f for %i, type %i", baseResistance, m_Parent->GetLOT(), statType);
|
||||
}
|
||||
else {
|
||||
Game::logger->Log("DestroyableComponent", "No resistance profile for %i", m_Parent->GetLOT());
|
||||
}
|
||||
}
|
||||
|
||||
if (typeIter == m_Stats.end()) {
|
||||
return baseResistance;
|
||||
}
|
||||
|
||||
const auto& typeMap = m_Stats.at(statType);
|
||||
|
||||
const auto& modifierIter = typeMap.find(statModifier);
|
||||
|
||||
if (modifierIter == typeMap.end()) {
|
||||
return baseResistance;
|
||||
}
|
||||
|
||||
return typeMap.at(statModifier);
|
||||
}
|
||||
|
||||
void DestroyableComponent::ComputeBaseStats(bool refill) {
|
||||
if (!m_Parent->IsPlayer())
|
||||
{
|
||||
auto* combatAI = m_Parent->GetComponent<BaseCombatAIComponent>();
|
||||
|
||||
if (combatAI == nullptr)
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Store the current health, armor and imagination
|
||||
const auto currentHealth = GetHealth();
|
||||
const auto currentArmor = GetArmor();
|
||||
|
||||
float maxHealth = 0.0f;
|
||||
float maxArmor = 0.0f;
|
||||
|
||||
int32_t level = (static_cast<int32_t>(m_Info.level) <= 1) ? 1 : m_Info.level;
|
||||
|
||||
if (m_Parent->IsPlayer())
|
||||
{
|
||||
level = m_Parent->GetComponent<LevelProgressionComponent>()->GetLevel();
|
||||
m_Info.life = 4;
|
||||
}
|
||||
|
||||
maxHealth += level * m_Info.life;
|
||||
|
||||
maxHealth *= 10;
|
||||
|
||||
maxHealth += GetStat(eStatTypes::Health, eStatModifier::Absolute);
|
||||
maxHealth *= 1.0f + GetStat(eStatTypes::Health, eStatModifier::Percent);
|
||||
|
||||
maxArmor = level * m_Info.armor;
|
||||
|
||||
maxArmor *= 10;
|
||||
|
||||
maxArmor += GetStat(eStatTypes::Armor, eStatModifier::Absolute);
|
||||
maxArmor *= 1.0f + GetStat(eStatTypes::Armor, eStatModifier::Percent);
|
||||
|
||||
// Set the base stats
|
||||
SetMaxHealth(maxHealth);
|
||||
SetMaxArmor(maxArmor);
|
||||
|
||||
// If any of the current stats are more than their max, set them to the max
|
||||
if (currentHealth > maxHealth || refill) SetHealth(maxHealth);
|
||||
else SetHealth(currentHealth);
|
||||
|
||||
if (currentArmor > maxArmor || refill) SetArmor(maxArmor);
|
||||
else SetArmor(currentArmor);
|
||||
|
||||
EntityManager::Instance()->SerializeEntity(m_Parent);
|
||||
}
|
||||
|
||||
std::map<eStatTypes, float> DestroyableComponent::ComputeDamage(uint32_t baseDamage, Entity* source, DamageProfile* damageProfile) {
|
||||
const bool debug = true;
|
||||
|
||||
auto* sourceDestroyable = source->GetComponent<DestroyableComponent>();
|
||||
|
||||
if (sourceDestroyable == nullptr) {
|
||||
Game::logger->Log("Damage", "Source entity has no destroyable componen %i", source->GetLOT());
|
||||
return {{eStatTypes::Physical, baseDamage}};
|
||||
}
|
||||
|
||||
std::stringstream ss;
|
||||
|
||||
std::map<eStatTypes, float> damageMap = {{eStatTypes::Physical, baseDamage}};
|
||||
|
||||
for (eStatTypes damageType = eStatTypes::Physical; damageType < eStatTypes::MAX;
|
||||
damageType = static_cast<eStatTypes>(static_cast<uint32_t>(damageType) + 1)) {
|
||||
// Get the damage for this damage type
|
||||
float damage = sourceDestroyable->GetStat(damageType, eStatModifier::DamageAbsolute);
|
||||
|
||||
damage *= 1.0f + sourceDestroyable->GetStat(damageType, eStatModifier::DamagePercent) + ::log(baseDamage);
|
||||
|
||||
if (damageProfile != nullptr)
|
||||
{
|
||||
damage += damageProfile->GetDamageProfile(damageType);
|
||||
}
|
||||
|
||||
/*
|
||||
Level scaling
|
||||
*/
|
||||
float ourLevel = (static_cast<int32_t>(m_Info.level) <= 1) ? 1 : m_Info.level;
|
||||
float sourceLevel;
|
||||
|
||||
if (source->IsPlayer())
|
||||
{
|
||||
auto* levelProgressionComponent = source->GetComponent<LevelProgressionComponent>();
|
||||
|
||||
if (levelProgressionComponent == nullptr)
|
||||
{
|
||||
Game::logger->Log("Damage", "Source entity is player but has no level progression component %i", source->GetLOT());
|
||||
return {{eStatTypes::Physical, baseDamage}};
|
||||
}
|
||||
|
||||
sourceLevel = levelProgressionComponent->GetLevel();
|
||||
|
||||
// Player max is level 45, scale it to between 1 and 10
|
||||
sourceLevel = static_cast<int32_t>(static_cast<float>(sourceLevel) / 4.5f);
|
||||
|
||||
// Make sure it's between 1 and 10
|
||||
sourceLevel = std::max(sourceLevel, 1.0f);
|
||||
sourceLevel = std::min(sourceLevel, 10.0f);
|
||||
|
||||
if (ourLevel < sourceLevel)
|
||||
{
|
||||
damage /= 1 + (sourceLevel - ourLevel);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
sourceLevel = (static_cast<int32_t>(sourceDestroyable->m_Info.level) <= 1) ? 1 : sourceDestroyable->m_Info.level;
|
||||
|
||||
if (m_Parent->IsPlayer())
|
||||
{
|
||||
auto* levelProgressionComponent = m_Parent->GetComponent<LevelProgressionComponent>();
|
||||
|
||||
if (levelProgressionComponent == nullptr)
|
||||
{
|
||||
Game::logger->Log("Damage", "Source entity is player but has no level progression component %i", m_Parent->GetLOT());
|
||||
return {{eStatTypes::Physical, baseDamage}};
|
||||
}
|
||||
|
||||
ourLevel = levelProgressionComponent->GetLevel();
|
||||
|
||||
// Player max is level 45, scale it to between 1 and 10
|
||||
ourLevel = static_cast<int32_t>(static_cast<float>(ourLevel) / 4.5f);
|
||||
|
||||
// Make sure it's between 1 and 10
|
||||
ourLevel = std::max(ourLevel, 1.0f);
|
||||
ourLevel = std::min(ourLevel, 10.0f);
|
||||
|
||||
if (ourLevel > sourceLevel)
|
||||
{
|
||||
damage *= 1 + (ourLevel - sourceLevel);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Idk
|
||||
|
||||
// If the damage is 0, skip this damage type
|
||||
if (damage == 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Calculate our resistance for this damage type
|
||||
const auto resistance = this->GetStat(damageType, eStatModifier::DamageResistance);
|
||||
|
||||
// Cap resistance at 80%
|
||||
const auto cappedResistance = std::min(resistance, 0.8f);
|
||||
|
||||
// Calculate the damage we take
|
||||
const auto damageTaken = damage * (1.0f - cappedResistance);
|
||||
|
||||
// Add the damage to our total damage
|
||||
const auto& it = damageMap.find(damageType);
|
||||
|
||||
if (it == damageMap.end()) {
|
||||
damageMap[damageType] = damageTaken;
|
||||
} else {
|
||||
damageMap[damageType] += damageTaken;
|
||||
}
|
||||
|
||||
// Log the calculation
|
||||
if (debug) {
|
||||
ss << "Damage type: " << static_cast<int32_t>(damageType) << std::endl;
|
||||
ss << "\tDamage: " << damage << std::endl;
|
||||
ss << "\tResistance: " << resistance << std::endl;
|
||||
ss << "\tCapped resistance: " << cappedResistance << std::endl;
|
||||
ss << "\tDamage taken: " << damageTaken << std::endl;
|
||||
}
|
||||
}
|
||||
|
||||
return damageMap;
|
||||
}
|
||||
|
||||
void DestroyableComponent::Damage(const std::map<eStatTypes, float>& damage, LWOOBJID source, uint32_t skillID, bool echo) {
|
||||
float totalDamage = 0;
|
||||
|
||||
for (const auto& [damageType, damageAmount] : damage) {
|
||||
totalDamage += damageAmount;
|
||||
}
|
||||
|
||||
// Find the greatest damage type
|
||||
eStatTypes greatestDamageType = eStatTypes::Physical;
|
||||
|
||||
for (const auto& [damageType, damageAmount] : damage) {
|
||||
if (damageAmount > damage.at(greatestDamageType)) {
|
||||
greatestDamageType = damageType;
|
||||
}
|
||||
}
|
||||
|
||||
const auto effectName = std::to_string(GeneralUtils::GenerateRandomNumber<size_t>(100, 10000));
|
||||
|
||||
const auto& objectId = m_Parent->GetObjectID();
|
||||
|
||||
const bool critical = GeneralUtils::GenerateRandomNumber<size_t>(0, 100) <= 10;
|
||||
|
||||
if (critical) {
|
||||
totalDamage *= 2;
|
||||
}
|
||||
|
||||
// Play an effect on us representing the damage
|
||||
switch (greatestDamageType) {
|
||||
case eStatTypes::Heat:
|
||||
if (!critical) GameMessages::SendPlayFXEffect(objectId, 50, u"onhit", effectName);
|
||||
else GameMessages::SendPlayFXEffect(objectId, 1578, u"onhit", effectName);
|
||||
break;
|
||||
case eStatTypes::Electric:
|
||||
if (!critical) GameMessages::SendPlayFXEffect(objectId, 4027, u"create", effectName);
|
||||
else GameMessages::SendPlayFXEffect(objectId, 953, u"onhit", effectName);
|
||||
break;
|
||||
case eStatTypes::Corruption:
|
||||
if (!critical) GameMessages::SendPlayFXEffect(objectId, 7, u"onhit", effectName);
|
||||
else GameMessages::SendPlayFXEffect(objectId, 1153, u"death", effectName);
|
||||
break;
|
||||
case eStatTypes::Physical:
|
||||
default:
|
||||
if (!critical) GameMessages::SendPlayFXEffect(objectId, 5039, u"on-anim", effectName);
|
||||
else GameMessages::SendPlayFXEffect(objectId, 4972, u"onhitObject", effectName);
|
||||
break;
|
||||
}
|
||||
|
||||
m_Parent->AddCallbackTimer(1.5f, [this, effectName]() {
|
||||
GameMessages::SendStopFXEffect(m_Parent, true, effectName);
|
||||
});
|
||||
|
||||
Damage(totalDamage, source, skillID, echo);
|
||||
}
|
||||
|
||||
void DestroyableComponent::Update(float deltaTime) {
|
||||
if (m_DirtyStats) {
|
||||
ComputeBaseStats();
|
||||
m_DirtyStats = false;
|
||||
}
|
||||
}
|
||||
|
||||
void DestroyableComponent::AddOnHitCallback(const std::function<void(Entity*)>& callback) {
|
||||
m_OnHitCallbacks.push_back(callback);
|
||||
}
|
||||
|
@@ -7,6 +7,8 @@
|
||||
#include "Entity.h"
|
||||
#include "Component.h"
|
||||
#include "eReplicaComponentType.h"
|
||||
#include "StatProperty.h"
|
||||
#include "CDDestructibleComponentTable.h"
|
||||
|
||||
namespace CppScripts {
|
||||
class Script;
|
||||
@@ -429,6 +431,46 @@ public:
|
||||
*/
|
||||
void FixStats();
|
||||
|
||||
void SetInfo(const CDDestructibleComponent& info) { m_Info = info; }
|
||||
|
||||
CDDestructibleComponent& GetInfo() { return m_Info; }
|
||||
|
||||
/**
|
||||
* Add a stat property to this entity
|
||||
*/
|
||||
void AddStat(const StatProperty& stat);
|
||||
|
||||
/**
|
||||
* Remove a stat property from this entity
|
||||
*/
|
||||
void RemoveStat(const StatProperty& stat);
|
||||
|
||||
/**
|
||||
* Get a stat property from this entity
|
||||
*/
|
||||
float GetStat(eStatTypes statType, eStatModifier statModifier) const;
|
||||
|
||||
/**
|
||||
* Compute the entity's health, armor, and imagination based on its stats
|
||||
*/
|
||||
void ComputeBaseStats(bool refill = false);
|
||||
|
||||
/**
|
||||
* Compute damage
|
||||
*/
|
||||
std::map<eStatTypes, float> ComputeDamage(uint32_t baseDamage, Entity* source, class DamageProfile* damageProfile);
|
||||
|
||||
/**
|
||||
* Damage this entity with a damage map
|
||||
*/
|
||||
void Damage(const std::map<eStatTypes, float>& damage, LWOOBJID source, uint32_t skillID = 0, bool echo = true);
|
||||
|
||||
/**
|
||||
* Updates the component in the game loop
|
||||
* @param deltaTime time passed since last update
|
||||
*/
|
||||
void Update(float deltaTime) override;
|
||||
|
||||
/**
|
||||
* Adds a callback that is called when this entity is hit by some other entity
|
||||
* @param callback the callback to add
|
||||
@@ -605,6 +647,26 @@ private:
|
||||
uint32_t m_ImmuneToImaginationLossCount;
|
||||
uint32_t m_ImmuneToQuickbuildInterruptCount;
|
||||
uint32_t m_ImmuneToPullToPointCount;
|
||||
|
||||
/**
|
||||
* Stats for the entity
|
||||
*/
|
||||
std::map<eStatTypes, std::map<eStatModifier, float>> m_Stats;
|
||||
|
||||
/**
|
||||
* Dirty flag for stats
|
||||
*/
|
||||
bool m_DirtyStats;
|
||||
|
||||
/**
|
||||
* The info for this component
|
||||
*/
|
||||
CDDestructibleComponent m_Info;
|
||||
|
||||
/**
|
||||
* Resistance profile for this entity
|
||||
*/
|
||||
class ResistanceProfile* m_ResistanceProfile;
|
||||
};
|
||||
|
||||
#endif // DESTROYABLECOMPONENT_H
|
||||
|
@@ -490,6 +490,11 @@ bool InventoryComponent::HasSpaceForLoot(const std::unordered_map<LOT, int32_t>&
|
||||
void InventoryComponent::LoadXml(tinyxml2::XMLDocument* document) {
|
||||
LoadPetXml(document);
|
||||
|
||||
EquipSkill(eSkillBar::Primary, BehaviorSlot::Primary, 1);
|
||||
EquipSkill(eSkillBar::Primary, BehaviorSlot::Head, 6);
|
||||
EquipSkill(eSkillBar::Primary, BehaviorSlot::Offhand, 7);
|
||||
EquipSkill(eSkillBar::Primary, BehaviorSlot::Neck, 8);
|
||||
|
||||
auto* inventoryElement = document->FirstChildElement("obj")->FirstChildElement("inv");
|
||||
|
||||
if (inventoryElement == nullptr) {
|
||||
@@ -577,14 +582,79 @@ void InventoryComponent::LoadXml(tinyxml2::XMLDocument* document) {
|
||||
auto* extraInfo = itemElement->FirstChildElement("x");
|
||||
|
||||
if (extraInfo) {
|
||||
std::string modInfo = extraInfo->Attribute("ma");
|
||||
// Check if has attribute "ma"
|
||||
if (extraInfo->Attribute("ma") != nullptr) {
|
||||
std::string modInfo = extraInfo->Attribute("ma");
|
||||
|
||||
LDFBaseData* moduleAssembly = new LDFData<std::u16string>(u"assemblyPartLOTs", GeneralUtils::ASCIIToUTF16(modInfo.substr(2, modInfo.size() - 1)));
|
||||
LDFBaseData* moduleAssembly = new LDFData<std::u16string>(u"assemblyPartLOTs", GeneralUtils::ASCIIToUTF16(modInfo.substr(2, modInfo.size() - 1)));
|
||||
|
||||
config.push_back(moduleAssembly);
|
||||
config.push_back(moduleAssembly);
|
||||
}
|
||||
}
|
||||
|
||||
const auto* item = new Item(id, lot, inventory, slot, count, bound, config, parent, subKey);
|
||||
auto* statInfo = itemElement->Attribute("st");
|
||||
|
||||
std::vector<StatProperty> stats;
|
||||
|
||||
if (statInfo != nullptr) {
|
||||
// type,modifier,value;...
|
||||
std::vector<std::string> statData = GeneralUtils::SplitString(statInfo, ';');
|
||||
|
||||
for (const auto& stat : statData) {
|
||||
if (stat.empty()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
std::vector<std::string> statParts = GeneralUtils::SplitString(stat, ',');
|
||||
|
||||
if (statParts.size() != 3) {
|
||||
continue;
|
||||
}
|
||||
|
||||
eStatTypes type = static_cast<eStatTypes>(std::stoi(statParts[0]));
|
||||
eStatModifier modifier = static_cast<eStatModifier>(std::stoi(statParts[1]));
|
||||
float value = std::stof(statParts[2]);
|
||||
|
||||
stats.push_back(StatProperty(type, modifier, value));
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<ItemModifierTemplate*> modifiers;
|
||||
|
||||
auto* modifierInfo = itemElement->Attribute("mo");
|
||||
|
||||
if (modifierInfo != nullptr) {
|
||||
// name;...
|
||||
std::vector<std::string> modifierData = GeneralUtils::SplitString(modifierInfo, ';');
|
||||
|
||||
for (const auto& modifier : modifierData) {
|
||||
if (modifier.empty()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
auto* modifierTemplate = ItemModifierTemplate::FindItemModifierTemplate(modifier);
|
||||
|
||||
if (modifierTemplate == nullptr) {
|
||||
Game::logger->Log("InventoryComponent", "Failed to find modifier template (%s)!", modifier.c_str());
|
||||
continue;
|
||||
}
|
||||
|
||||
modifiers.push_back(modifierTemplate);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
auto* item = new Item(id, lot, inventory, slot, count, bound, config, parent, subKey);
|
||||
|
||||
if (!stats.empty())
|
||||
{
|
||||
item->GetStats() = stats;
|
||||
}
|
||||
|
||||
if (!modifiers.empty())
|
||||
{
|
||||
item->GetModifiers() = modifiers;
|
||||
}
|
||||
|
||||
if (equipped) {
|
||||
const auto info = Inventory::FindItemComponent(lot);
|
||||
@@ -702,6 +772,24 @@ void InventoryComponent::UpdateXml(tinyxml2::XMLDocument* document) {
|
||||
itemElement->LinkEndChild(extraInfo);
|
||||
}
|
||||
|
||||
// St attribute
|
||||
std::stringstream ss;
|
||||
|
||||
for (const auto& stat : item->GetStats()) {
|
||||
ss << static_cast<uint32_t>(stat.type) << "," << static_cast<uint32_t>(stat.modifier) << "," << stat.value << ";";
|
||||
}
|
||||
|
||||
itemElement->SetAttribute("st", ss.str().c_str());
|
||||
|
||||
// Mo attribute
|
||||
ss.str("");
|
||||
|
||||
for (const auto& modifier : item->GetModifiers()) {
|
||||
ss << modifier->GetName() << ";";
|
||||
}
|
||||
|
||||
itemElement->SetAttribute("mo", ss.str().c_str());
|
||||
|
||||
bagElement->LinkEndChild(itemElement);
|
||||
}
|
||||
|
||||
@@ -1031,7 +1119,17 @@ void InventoryComponent::ApplyBuff(Item* item) const {
|
||||
const auto buffs = FindBuffs(item, true);
|
||||
|
||||
for (const auto buff : buffs) {
|
||||
SkillComponent::HandleUnmanaged(buff, m_Parent->GetObjectID());
|
||||
SkillComponent::HandleUnmanaged(buff, m_Parent->GetObjectID(), LWOOBJID_EMPTY, item->GetId());
|
||||
}
|
||||
|
||||
auto* destroyableComponent = m_Parent->GetComponent<DestroyableComponent>();
|
||||
|
||||
if (destroyableComponent == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (const auto& stat : item->GetStats()) {
|
||||
destroyableComponent->AddStat(stat);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1040,7 +1138,17 @@ void InventoryComponent::RemoveBuff(Item* item) const {
|
||||
const auto buffs = FindBuffs(item, false);
|
||||
|
||||
for (const auto buff : buffs) {
|
||||
SkillComponent::HandleUnCast(buff, m_Parent->GetObjectID());
|
||||
SkillComponent::HandleUnCast(buff, m_Parent->GetObjectID(), item->GetId());
|
||||
}
|
||||
|
||||
auto* destroyableComponent = m_Parent->GetComponent<DestroyableComponent>();
|
||||
|
||||
if (destroyableComponent == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (const auto& stat : item->GetStats()) {
|
||||
destroyableComponent->RemoveStat(stat);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1157,23 +1265,35 @@ void InventoryComponent::AddItemSkills(const LOT lot) {
|
||||
return;
|
||||
}
|
||||
|
||||
const auto index = m_Skills.find(slot);
|
||||
eSkillBar bar = eSkillBar::Gear;
|
||||
|
||||
switch (slot)
|
||||
{
|
||||
case BehaviorSlot::Primary:
|
||||
bar = eSkillBar::Primary;
|
||||
break;
|
||||
case BehaviorSlot::Consumable:
|
||||
bar = eSkillBar::Consumable;
|
||||
break;
|
||||
}
|
||||
|
||||
const auto skill = FindSkill(lot);
|
||||
|
||||
if (skill == 0) {
|
||||
UnequipSkill(bar, slot);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (index != m_Skills.end()) {
|
||||
const auto old = index->second;
|
||||
|
||||
GameMessages::SendRemoveSkill(m_Parent, old);
|
||||
if (slot == BehaviorSlot::Primary) {
|
||||
EquipSkill(eSkillBar::Primary, BehaviorSlot::Primary, skill);
|
||||
EquipSkill(eSkillBar::Gear, BehaviorSlot::Primary, skill);
|
||||
EquipSkill(eSkillBar::ClassPrimary, BehaviorSlot::Primary, skill);
|
||||
EquipSkill(eSkillBar::ClassSecondary, BehaviorSlot::Primary, skill);
|
||||
}
|
||||
else {
|
||||
EquipSkill(bar, slot, skill);
|
||||
}
|
||||
|
||||
GameMessages::SendAddSkill(m_Parent, skill, static_cast<int>(slot));
|
||||
|
||||
m_Skills.insert_or_assign(slot, skill);
|
||||
}
|
||||
|
||||
void InventoryComponent::RemoveItemSkills(const LOT lot) {
|
||||
@@ -1185,23 +1305,155 @@ void InventoryComponent::RemoveItemSkills(const LOT lot) {
|
||||
return;
|
||||
}
|
||||
|
||||
const auto index = m_Skills.find(slot);
|
||||
if (slot == BehaviorSlot::Primary) {
|
||||
EquipSkill(eSkillBar::Primary, BehaviorSlot::Primary, 1);
|
||||
EquipSkill(eSkillBar::Gear, BehaviorSlot::Primary, 1);
|
||||
EquipSkill(eSkillBar::ClassPrimary, BehaviorSlot::Primary, 1);
|
||||
EquipSkill(eSkillBar::ClassSecondary, BehaviorSlot::Primary, 1);
|
||||
|
||||
if (index == m_Skills.end()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const auto old = index->second;
|
||||
eSkillBar bar = eSkillBar::Gear;
|
||||
|
||||
GameMessages::SendRemoveSkill(m_Parent, old);
|
||||
|
||||
m_Skills.erase(slot);
|
||||
|
||||
if (slot == BehaviorSlot::Primary) {
|
||||
m_Skills.insert_or_assign(BehaviorSlot::Primary, 1);
|
||||
|
||||
GameMessages::SendAddSkill(m_Parent, 1, static_cast<int>(BehaviorSlot::Primary));
|
||||
switch (slot)
|
||||
{
|
||||
case BehaviorSlot::Primary:
|
||||
bar = eSkillBar::Primary;
|
||||
break;
|
||||
case BehaviorSlot::Consumable:
|
||||
bar = eSkillBar::Consumable;
|
||||
break;
|
||||
}
|
||||
|
||||
UnequipSkill(bar, slot);
|
||||
}
|
||||
|
||||
void InventoryComponent::EquipSkill(eSkillBar bar, BehaviorSlot slot, uint32_t skillID) {
|
||||
Game::logger->Log("InventoryComponent", "Equipping skill %i to slot %i on bar %i", skillID, slot, bar);
|
||||
|
||||
const auto& barIt = m_EquippedSkills.find(bar);
|
||||
|
||||
if (barIt == m_EquippedSkills.end()) {
|
||||
m_EquippedSkills.emplace(bar, std::map<BehaviorSlot, uint32_t>());
|
||||
}
|
||||
|
||||
auto& barMap = m_EquippedSkills.at(bar);
|
||||
|
||||
const auto& slotIt = barMap.find(slot);
|
||||
|
||||
barMap.insert_or_assign(slot, skillID);
|
||||
|
||||
UpdateSkills();
|
||||
}
|
||||
|
||||
void InventoryComponent::UnequipSkill(eSkillBar bar, BehaviorSlot slot) {
|
||||
Game::logger->Log("InventoryComponent", "Unequipping skill from slot %i on bar %i", slot, bar);
|
||||
|
||||
const auto& barIt = m_EquippedSkills.find(bar);
|
||||
|
||||
if (barIt == m_EquippedSkills.end()) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto& barMap = m_EquippedSkills.at(bar);
|
||||
|
||||
const auto& slotIt = barMap.find(slot);
|
||||
|
||||
if (slotIt == barMap.end()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const auto old = slotIt->second;
|
||||
|
||||
barMap.erase(slot);
|
||||
|
||||
if (bar == eSkillBar::Primary && slot == BehaviorSlot::Primary) {
|
||||
barMap.insert_or_assign(BehaviorSlot::Primary, 1);
|
||||
}
|
||||
|
||||
if (barMap.empty()) {
|
||||
m_EquippedSkills.erase(bar);
|
||||
}
|
||||
|
||||
UpdateSkills();
|
||||
}
|
||||
|
||||
uint32_t InventoryComponent::GetSkill(eSkillBar bar, BehaviorSlot slot) const {
|
||||
const auto& barIt = m_EquippedSkills.find(bar);
|
||||
|
||||
if (barIt == m_EquippedSkills.end()) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
const auto& barMap = barIt->second;
|
||||
|
||||
const auto& slotIt = barMap.find(slot);
|
||||
|
||||
if (slotIt == barMap.end()) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return slotIt->second;
|
||||
}
|
||||
|
||||
eSkillBar InventoryComponent::GetSelectedSkillBar() const {
|
||||
return m_SelectedSkillBar;
|
||||
}
|
||||
|
||||
void InventoryComponent::SetSelectedSkillBar(eSkillBar bar) {
|
||||
if (bar == m_SelectedSkillBar) {
|
||||
return;
|
||||
}
|
||||
|
||||
m_SelectedSkillBar = bar;
|
||||
|
||||
UpdateSkills();
|
||||
}
|
||||
|
||||
void InventoryComponent::UpdateSkills() {
|
||||
// The active skills are kept in m_Skills, compare what is there to what should be there
|
||||
// and add/remove skills as needed
|
||||
const auto& barIt = m_EquippedSkills.find(m_SelectedSkillBar);
|
||||
|
||||
if (barIt == m_EquippedSkills.end()) {
|
||||
// Unequip all skills
|
||||
for (auto& pair : m_Skills) {
|
||||
GameMessages::SendRemoveSkill(m_Parent, pair.second);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
const auto& barMap = barIt->second;
|
||||
|
||||
for (BehaviorSlot slot = BehaviorSlot::Primary; slot < BehaviorSlot::Consumable;) {
|
||||
const auto& slotIt = barMap.find(slot);
|
||||
|
||||
if (slotIt == barMap.end()) {
|
||||
// Unequip the skill
|
||||
const auto& skillIt = m_Skills.find(slot);
|
||||
|
||||
if (skillIt != m_Skills.end()) {
|
||||
GameMessages::SendRemoveSkill(m_Parent, skillIt->second);
|
||||
}
|
||||
}
|
||||
else {
|
||||
// Equip the skill
|
||||
const auto& skillIt = m_Skills.find(slot);
|
||||
|
||||
if (skillIt != m_Skills.end()) {
|
||||
GameMessages::SendRemoveSkill(m_Parent, skillIt->second);
|
||||
}
|
||||
|
||||
GameMessages::SendAddSkill(m_Parent, slotIt->second, static_cast<int32_t>(slot));
|
||||
}
|
||||
|
||||
slot = static_cast<BehaviorSlot>(static_cast<int32_t>(slot) + 1);
|
||||
}
|
||||
|
||||
// Update the active skills
|
||||
m_Skills = barMap;
|
||||
}
|
||||
|
||||
void InventoryComponent::TriggerPassiveAbility(PassiveAbilityTrigger trigger, Entity* target) {
|
||||
|
@@ -22,6 +22,7 @@
|
||||
#include "eInventoryType.h"
|
||||
#include "eReplicaComponentType.h"
|
||||
#include "eLootSourceType.h"
|
||||
#include "eSkillBar.h"
|
||||
|
||||
class Entity;
|
||||
class ItemSet;
|
||||
@@ -281,6 +282,36 @@ public:
|
||||
*/
|
||||
void RemoveItemSkills(LOT lot);
|
||||
|
||||
/**
|
||||
* Equip a skill to a bar and slot
|
||||
*/
|
||||
void EquipSkill(eSkillBar bar, BehaviorSlot slot, uint32_t skillID);
|
||||
|
||||
/**
|
||||
* Unequip a skill from a bar and slot
|
||||
*/
|
||||
void UnequipSkill(eSkillBar bar, BehaviorSlot slot);
|
||||
|
||||
/**
|
||||
* Get the skill in a bar and slot
|
||||
*/
|
||||
uint32_t GetSkill(eSkillBar bar, BehaviorSlot slot) const;
|
||||
|
||||
/**
|
||||
* Get the currently selected skill bar
|
||||
*/
|
||||
eSkillBar GetSelectedSkillBar() const;
|
||||
|
||||
/**
|
||||
* Set the currently selected skill bar
|
||||
*/
|
||||
void SetSelectedSkillBar(eSkillBar bar);
|
||||
|
||||
/**
|
||||
* Update skills on client
|
||||
*/
|
||||
void UpdateSkills();
|
||||
|
||||
/**
|
||||
* Triggers one of the passive abilities from the equipped item set
|
||||
* @param trigger the trigger to fire
|
||||
@@ -416,6 +447,16 @@ private:
|
||||
*/
|
||||
LOT m_Consumable;
|
||||
|
||||
/**
|
||||
* Equipped skills
|
||||
*/
|
||||
std::map<eSkillBar, std::map<BehaviorSlot, uint32_t>> m_EquippedSkills;
|
||||
|
||||
/**
|
||||
* The currently selected skill bar
|
||||
*/
|
||||
eSkillBar m_SelectedSkillBar = eSkillBar::Primary;
|
||||
|
||||
/**
|
||||
* Currently has a car equipped
|
||||
*/
|
||||
|
@@ -448,11 +448,12 @@ void SkillComponent::SyncProjectileCalculation(const ProjectileSyncEntry& entry)
|
||||
delete bitStream;
|
||||
}
|
||||
|
||||
void SkillComponent::HandleUnmanaged(const uint32_t behaviorId, const LWOOBJID target, LWOOBJID source) {
|
||||
void SkillComponent::HandleUnmanaged(const uint32_t behaviorId, const LWOOBJID target, LWOOBJID source, LWOOBJID itemID) {
|
||||
auto* context = new BehaviorContext(source);
|
||||
|
||||
context->unmanaged = true;
|
||||
context->caster = target;
|
||||
context->itemID = itemID;
|
||||
|
||||
auto* behavior = Behavior::CreateBehavior(behaviorId);
|
||||
|
||||
@@ -465,7 +466,7 @@ void SkillComponent::HandleUnmanaged(const uint32_t behaviorId, const LWOOBJID t
|
||||
delete context;
|
||||
}
|
||||
|
||||
void SkillComponent::HandleUnCast(const uint32_t behaviorId, const LWOOBJID target) {
|
||||
void SkillComponent::HandleUnCast(const uint32_t behaviorId, const LWOOBJID target, LWOOBJID itemID) {
|
||||
auto* context = new BehaviorContext(target);
|
||||
|
||||
context->caster = target;
|
||||
|
@@ -169,15 +169,17 @@ public:
|
||||
* @param behaviorId the root behavior ID of the skill
|
||||
* @param target the explicit target of the skill
|
||||
* @param source the explicit source of the skill
|
||||
* @param itemID the explicit item ID of the skill
|
||||
*/
|
||||
static void HandleUnmanaged(uint32_t behaviorId, LWOOBJID target, LWOOBJID source = LWOOBJID_EMPTY);
|
||||
static void HandleUnmanaged(uint32_t behaviorId, LWOOBJID target, LWOOBJID source = LWOOBJID_EMPTY, LWOOBJID itemID = LWOOBJID_EMPTY);
|
||||
|
||||
/**
|
||||
* Computes a server-side skill uncast calculation without an associated entity.
|
||||
* @param behaviorId the root behavior ID of the skill
|
||||
* @param target the explicit target of the skill
|
||||
* @param itemID the explicit item ID of the skill
|
||||
*/
|
||||
static void HandleUnCast(uint32_t behaviorId, LWOOBJID target);
|
||||
static void HandleUnCast(uint32_t behaviorId, LWOOBJID target, LWOOBJID itemID = LWOOBJID_EMPTY);
|
||||
|
||||
/**
|
||||
* @returns a unique ID for the next skill calculation
|
||||
|
@@ -20,7 +20,8 @@ VendorComponent::~VendorComponent() = default;
|
||||
void VendorComponent::Serialize(RakNet::BitStream* outBitStream, bool bIsInitialUpdate, unsigned int& flags) {
|
||||
outBitStream->Write1();
|
||||
outBitStream->Write1(); // Has standard items (Required for vendors with missions.)
|
||||
outBitStream->Write(HasCraftingStation()); // Has multi use items
|
||||
outBitStream->Write1();
|
||||
//outBitStream->Write(HasCraftingStation()); // Has multi use items
|
||||
}
|
||||
|
||||
void VendorComponent::OnUse(Entity* originator) {
|
||||
|
Reference in New Issue
Block a user