Wincent's attempt at making LU into something it isn't supposed to be, an ARPG.
This commit is contained in:
wincent
2023-07-15 10:54:41 +02:00
parent 0d6bd33f9e
commit 5973430720
54 changed files with 2634 additions and 76 deletions

View File

@@ -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;
}

View File

@@ -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;

View File

@@ -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);
}

View File

@@ -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

View File

@@ -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) {

View File

@@ -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
*/

View File

@@ -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;

View File

@@ -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

View File

@@ -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) {