mirror of
https://github.com/DarkflameUniverse/DarkflameServer.git
synced 2026-10-02 10:53:44 +00:00
refactor: item set passive abilities come from the CDClient and item scripts
The server hardcoded each item set's passives by set ID. They now come from data: - On-kill bonuses (Paradox imagination, Sentinel armor repair, Bat Lord heal) are the set's DarkInspiration skills in ItemSetSkills. The client turns those into a status effect that casts the behavior's action when the wearer kills something of a faction in faction_list; the server now runs the same behavior on the smashed enemy, so the amounts, faction check and extra effects (e.g. rank 3 Sorcerer team imagination) follow the data. A behavior repeated in a higher tier does not stack. - Low imagination / low armor skills are what the live equipmenttriggers item scripts do. Items link to those scripts through their ScriptComponent; the script vars (skill, items required, set, cooldown) only exist in the scripts, so they are mirrored in a small table keyed by script name. The Sentinel scripts have no cooldown (was 11s). - Knockback immunity while quickbuilding comes from the Immunity behavior's immune_quickbuild_interrupts (the 5 item Assembly rank 2/3 set skill) instead of a set ID list; ImmunityBehavior now pops its immunities when an equip skill is uncast. Removes eItemSetPassiveAbilityID and InventoryComponent::HasAnyPassive (unused now). Refs #691 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -18,8 +18,6 @@ ItemSet::ItemSet(const uint32_t id, InventoryComponent* inventoryComponent) {
|
||||
this->m_ID = id;
|
||||
this->m_InventoryComponent = inventoryComponent;
|
||||
|
||||
this->m_PassiveAbilities = ItemSetPassiveAbility::FindAbilities(id, m_InventoryComponent->GetParent(), this);
|
||||
|
||||
auto query = CDClientDatabase::CreatePreppedStmt(
|
||||
"SELECT skillSetWith2, skillSetWith3, skillSetWith4, skillSetWith5, skillSetWith6, itemIDs FROM ItemSets WHERE setID = ?;");
|
||||
query.bind(1, static_cast<int>(id));
|
||||
@@ -44,7 +42,7 @@ ItemSet::ItemSet(const uint32_t id, InventoryComponent* inventoryComponent) {
|
||||
auto skillResult = skillQuery.execQuery();
|
||||
|
||||
if (skillResult.eof()) {
|
||||
return;
|
||||
continue;
|
||||
}
|
||||
|
||||
while (!skillResult.eof()) {
|
||||
@@ -96,6 +94,8 @@ ItemSet::ItemSet(const uint32_t id, InventoryComponent* inventoryComponent) {
|
||||
if (validToken) m_Items.push_back(validToken.value());
|
||||
}
|
||||
|
||||
m_PassiveAbilities = ItemSetPassiveAbility::FindAbilities(id, m_Items, { m_SkillsWith2, m_SkillsWith3, m_SkillsWith4, m_SkillsWith5, m_SkillsWith6 }, m_InventoryComponent->GetParent(), this);
|
||||
|
||||
m_Equipped = {};
|
||||
|
||||
for (const auto item : m_Items) {
|
||||
|
||||
@@ -1,9 +1,68 @@
|
||||
#include "ItemSetPassiveAbility.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cctype>
|
||||
#include <string>
|
||||
|
||||
#include "DestroyableComponent.h"
|
||||
#include "SkillComponent.h"
|
||||
#include "ItemSet.h"
|
||||
#include "eItemSetPassiveAbilityID.h"
|
||||
#include "EntityManager.h"
|
||||
#include "CDClientManager.h"
|
||||
#include "CDComponentsRegistryTable.h"
|
||||
#include "CDScriptComponentTable.h"
|
||||
#include "CDSkillBehaviorTable.h"
|
||||
#include "Behavior.h"
|
||||
#include "eReplicaComponentType.h"
|
||||
|
||||
namespace {
|
||||
struct ScriptStatTrigger {
|
||||
std::string_view script;
|
||||
ItemSetStatTrigger trigger;
|
||||
};
|
||||
|
||||
constexpr auto LowImagination = PassiveAbilityTrigger::AssemblyImagination;
|
||||
constexpr auto LowArmor = PassiveAbilityTrigger::SentinelArmor;
|
||||
|
||||
// The CDClient links set items to these scripts through their ScriptComponent, but what each
|
||||
// script does only exists in the scripts themselves (res/scripts/equipmenttriggers/<name>.lua,
|
||||
// which all run skillSetTriggerTemplate.lua with these vars), so they are mirrored here like dScripts.
|
||||
// Paradox and Venture have scripts of the same kind (paradox1-3, venture*) but no item uses them.
|
||||
constexpr ScriptStatTrigger StatTriggerScripts[] = {
|
||||
// Low Imagination, IMAGINATION LESS 1
|
||||
{ "assembly1.lua", { LowImagination, 394, 4, 2, 0.0f } },
|
||||
{ "assembly2.lua", { LowImagination, 581, 4, 3, 0.0f } },
|
||||
{ "assembly3.lua", { LowImagination, 582, 4, 4, 0.0f } },
|
||||
{ "assemblyengineer1.lua", { LowImagination, 394, 4, 2, 11.0f } },
|
||||
{ "assemblyengineer2.lua", { LowImagination, 581, 4, 3, 11.0f } },
|
||||
{ "assemblyengineer3.lua", { LowImagination, 582, 4, 4, 11.0f } },
|
||||
{ "assemblyinventor1.lua", { LowImagination, 394, 4, 25, 11.0f } },
|
||||
{ "assemblyinventor2.lua", { LowImagination, 581, 4, 26, 11.0f } },
|
||||
{ "assemblyinventor3.lua", { LowImagination, 582, 4, 27, 11.0f } },
|
||||
{ "assemblysummoner1.lua", { LowImagination, 394, 4, 28, 11.0f } },
|
||||
{ "assemblysummoner2.lua", { LowImagination, 581, 4, 29, 11.0f } },
|
||||
{ "assemblysummoner3.lua", { LowImagination, 582, 4, 30, 11.0f } },
|
||||
// Low Armor, ARMOR LESS 1
|
||||
{ "sentinelknight1.lua", { LowArmor, 559, 4, 7, 0.0f } },
|
||||
{ "sentinelknight2.lua", { LowArmor, 560, 4, 8, 0.0f } },
|
||||
{ "sentinelknight3.lua", { LowArmor, 561, 4, 9, 0.0f } },
|
||||
{ "sentinelspaceranger1.lua", { LowArmor, 1101, 4, 10, 0.0f } },
|
||||
{ "sentinelspaceranger2.lua", { LowArmor, 1102, 4, 11, 0.0f } },
|
||||
{ "sentinelspaceranger3.lua", { LowArmor, 1103, 4, 12, 0.0f } },
|
||||
{ "sentinelsamurai1.lua", { LowArmor, 562, 4, 13, 0.0f } },
|
||||
{ "sentinelsamurai2.lua", { LowArmor, 563, 4, 14, 0.0f } },
|
||||
{ "sentinelsamurai3.lua", { LowArmor, 564, 4, 15, 0.0f } },
|
||||
};
|
||||
|
||||
std::string ScriptFileName(std::string_view scriptName) {
|
||||
const auto slash = scriptName.find_last_of("/\\");
|
||||
if (slash != std::string_view::npos) scriptName.remove_prefix(slash + 1);
|
||||
|
||||
std::string fileName{ scriptName };
|
||||
std::transform(fileName.begin(), fileName.end(), fileName.begin(), [](unsigned char c) { return std::tolower(c); });
|
||||
return fileName;
|
||||
}
|
||||
}
|
||||
|
||||
ItemSetPassiveAbility::ItemSetPassiveAbility(PassiveAbilityTrigger trigger, Entity* parent, ItemSet* itemSet) {
|
||||
m_Trigger = trigger;
|
||||
@@ -13,9 +72,6 @@ ItemSetPassiveAbility::ItemSetPassiveAbility(PassiveAbilityTrigger trigger, Enti
|
||||
m_Cooldown = 0.0f;
|
||||
}
|
||||
|
||||
ItemSetPassiveAbility::~ItemSetPassiveAbility() {
|
||||
}
|
||||
|
||||
void ItemSetPassiveAbility::Trigger(PassiveAbilityTrigger trigger, Entity* target) {
|
||||
if (m_Trigger != trigger || m_Cooldown > 0.0f) {
|
||||
return;
|
||||
@@ -32,7 +88,7 @@ void ItemSetPassiveAbility::Update(float deltaTime) {
|
||||
|
||||
void ItemSetPassiveAbility::Activate(Entity* target) {
|
||||
if (m_Trigger == PassiveAbilityTrigger::EnemySmashed) {
|
||||
OnEnemySmshed(target);
|
||||
OnEnemySmashed(target);
|
||||
|
||||
return;
|
||||
}
|
||||
@@ -46,156 +102,91 @@ void ItemSetPassiveAbility::Activate(Entity* target) {
|
||||
|
||||
Game::entityManager->SerializeEntity(m_Parent);
|
||||
|
||||
const auto id = static_cast<eItemSetPassiveAbilityID>(m_ItemSet->GetID());
|
||||
const auto parentID = m_Parent->GetObjectID();
|
||||
const auto equippedCount = m_ItemSet->GetEquippedCount();
|
||||
if (m_ItemSet->GetEquippedCount() < m_StatTrigger.itemsRequired) return;
|
||||
|
||||
switch (id) {
|
||||
// Assembly
|
||||
case eItemSetPassiveAbilityID::InventorRank1:
|
||||
case eItemSetPassiveAbilityID::SummonerRank1:
|
||||
case eItemSetPassiveAbilityID::EngineerRank1: {
|
||||
if (equippedCount < 4) return;
|
||||
m_Cooldown = 11.0f;
|
||||
skillComponent->CalculateBehavior(394, 4401, parentID);
|
||||
break;
|
||||
}
|
||||
case eItemSetPassiveAbilityID::InventorRank2:
|
||||
case eItemSetPassiveAbilityID::SummonerRank2:
|
||||
case eItemSetPassiveAbilityID::EngineerRank2: {
|
||||
if (equippedCount < 4) return;
|
||||
m_Cooldown = 11.0f;
|
||||
skillComponent->CalculateBehavior(581, 9433, parentID);
|
||||
break;
|
||||
}
|
||||
case eItemSetPassiveAbilityID::InventorRank3:
|
||||
case eItemSetPassiveAbilityID::SummonerRank3:
|
||||
case eItemSetPassiveAbilityID::EngineerRank3: {
|
||||
if (equippedCount < 4) return;
|
||||
m_Cooldown = 11.0f;
|
||||
skillComponent->CalculateBehavior(582, 9435, parentID);
|
||||
break;
|
||||
}
|
||||
|
||||
// Sentinel
|
||||
case eItemSetPassiveAbilityID::KnightRank1: {
|
||||
if (equippedCount < 4) return;
|
||||
m_Cooldown = 11.0f;
|
||||
skillComponent->CalculateBehavior(559, 8884, parentID);
|
||||
break;
|
||||
}
|
||||
case eItemSetPassiveAbilityID::KnightRank2: {
|
||||
if (equippedCount < 4) return;
|
||||
m_Cooldown = 11.0f;
|
||||
skillComponent->CalculateBehavior(560, 8885, parentID);
|
||||
break;
|
||||
}
|
||||
case eItemSetPassiveAbilityID::KnightRank3: {
|
||||
if (equippedCount < 4) return;
|
||||
m_Cooldown = 11.0f;
|
||||
skillComponent->CalculateBehavior(561, 8890, parentID);
|
||||
break;
|
||||
}
|
||||
|
||||
case eItemSetPassiveAbilityID::SpaceRangerRank1: {
|
||||
if (equippedCount < 4) return;
|
||||
m_Cooldown = 11.0f;
|
||||
skillComponent->CalculateBehavior(1101, 24612, parentID);
|
||||
break;
|
||||
}
|
||||
case eItemSetPassiveAbilityID::SpaceRangerRank2: {
|
||||
if (equippedCount < 4) return;
|
||||
m_Cooldown = 11.0f;
|
||||
skillComponent->CalculateBehavior(1102, 24617, parentID);
|
||||
break;
|
||||
}
|
||||
case eItemSetPassiveAbilityID::SpaceRangerRank3: {
|
||||
if (equippedCount < 4) return;
|
||||
m_Cooldown = 11.0f;
|
||||
skillComponent->CalculateBehavior(1103, 24622, parentID);
|
||||
break;
|
||||
}
|
||||
|
||||
case eItemSetPassiveAbilityID::SamuraiRank1: {
|
||||
if (equippedCount < 4) return;
|
||||
m_Cooldown = 11.0f;
|
||||
skillComponent->CalculateBehavior(562, 8899, parentID);
|
||||
break;
|
||||
}
|
||||
case eItemSetPassiveAbilityID::SamuraiRank2: {
|
||||
if (equippedCount < 4) return;
|
||||
m_Cooldown = 11.0f;
|
||||
skillComponent->CalculateBehavior(563, 8904, parentID);
|
||||
break;
|
||||
}
|
||||
case eItemSetPassiveAbilityID::SamuraiRank3: {
|
||||
if (equippedCount < 4) return;
|
||||
m_Cooldown = 11.0f;
|
||||
skillComponent->CalculateBehavior(564, 8909, parentID);
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
m_Cooldown = m_StatTrigger.cooldown;
|
||||
skillComponent->CastSkill(m_StatTrigger.skillID, m_Parent->GetObjectID());
|
||||
}
|
||||
|
||||
std::vector<ItemSetPassiveAbility> ItemSetPassiveAbility::FindAbilities(uint32_t itemSetID, Entity* parent, ItemSet* itemSet) {
|
||||
const ItemSetStatTrigger* ItemSetPassiveAbility::GetStatTriggerForScript(std::string_view scriptName) {
|
||||
const auto fileName = ScriptFileName(scriptName);
|
||||
for (const auto& entry : StatTriggerScripts) {
|
||||
if (entry.script == fileName) return &entry.trigger;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
std::vector<ItemSetStatTrigger> ItemSetPassiveAbility::FindStatTriggers(uint32_t itemSetID, const std::vector<LOT>& items) {
|
||||
std::vector<ItemSetStatTrigger> triggers;
|
||||
|
||||
auto* registryTable = CDClientManager::GetTable<CDComponentsRegistryTable>();
|
||||
auto* scriptTable = CDClientManager::GetTable<CDScriptComponentTable>();
|
||||
|
||||
for (const auto lot : items) {
|
||||
const auto scriptComponentID = registryTable->GetByIDAndType(lot, eReplicaComponentType::SCRIPT, -1);
|
||||
if (scriptComponentID < 0) continue;
|
||||
|
||||
const auto* trigger = GetStatTriggerForScript(scriptTable->GetByID(scriptComponentID).script_name);
|
||||
|
||||
// Like the script, only fire for the set it was written for
|
||||
if (!trigger || trigger->setID != itemSetID) continue;
|
||||
|
||||
// Every item of a set carries the same script, but only one of them casts the skill
|
||||
const auto alreadyFound = std::any_of(triggers.begin(), triggers.end(), [trigger](const ItemSetStatTrigger& found) {
|
||||
return found.trigger == trigger->trigger && found.skillID == trigger->skillID;
|
||||
});
|
||||
if (!alreadyFound) triggers.push_back(*trigger);
|
||||
}
|
||||
|
||||
return triggers;
|
||||
}
|
||||
|
||||
std::vector<ItemSetOnKillSkill> ItemSetPassiveAbility::FindOnKillSkills(const std::array<std::vector<uint32_t>, 5>& skillSets) {
|
||||
std::vector<ItemSetOnKillSkill> onKillSkills;
|
||||
|
||||
auto* skillTable = CDClientManager::GetTable<CDSkillBehaviorTable>();
|
||||
|
||||
for (size_t tier = 0; tier < skillSets.size(); ++tier) {
|
||||
for (const auto skillID : skillSets[tier]) {
|
||||
const auto behaviorID = skillTable->GetSkillByID(skillID).behaviorID;
|
||||
if (behaviorID == 0) continue;
|
||||
|
||||
if (Behavior::GetBehaviorTemplate(behaviorID) != BehaviorTemplate::DARK_INSPIRATION) continue;
|
||||
|
||||
// The client keys the status effect by behavior ID, so a behavior repeated in a higher tier
|
||||
// (e.g. the Sentinel rank 3 armor repair at 5 and 6 items) does not stack. Keep the lowest tier.
|
||||
const auto alreadyFound = std::any_of(onKillSkills.begin(), onKillSkills.end(), [behaviorID](const ItemSetOnKillSkill& found) {
|
||||
return found.behaviorID == behaviorID;
|
||||
});
|
||||
if (alreadyFound) continue;
|
||||
|
||||
onKillSkills.push_back({ static_cast<uint32_t>(tier + 2), skillID, behaviorID });
|
||||
}
|
||||
}
|
||||
|
||||
return onKillSkills;
|
||||
}
|
||||
|
||||
std::vector<ItemSetPassiveAbility> ItemSetPassiveAbility::FindAbilities(uint32_t itemSetID, const std::vector<LOT>& items, const std::array<std::vector<uint32_t>, 5>& skillSets, Entity* parent, ItemSet* itemSet) {
|
||||
std::vector<ItemSetPassiveAbility> abilities;
|
||||
|
||||
switch (static_cast<eItemSetPassiveAbilityID>(itemSetID)) {
|
||||
// Assembly
|
||||
case eItemSetPassiveAbilityID::SummonerRank1:
|
||||
case eItemSetPassiveAbilityID::SummonerRank2:
|
||||
case eItemSetPassiveAbilityID::SummonerRank3:
|
||||
case eItemSetPassiveAbilityID::InventorRank1:
|
||||
case eItemSetPassiveAbilityID::InventorRank2:
|
||||
case eItemSetPassiveAbilityID::InventorRank3:
|
||||
case eItemSetPassiveAbilityID::EngineerRank1:
|
||||
case eItemSetPassiveAbilityID::EngineerRank2:
|
||||
case eItemSetPassiveAbilityID::EngineerRank3: {
|
||||
abilities.emplace_back(PassiveAbilityTrigger::AssemblyImagination, parent, itemSet);
|
||||
|
||||
break;
|
||||
for (const auto& statTrigger : FindStatTriggers(itemSetID, items)) {
|
||||
auto& ability = abilities.emplace_back(statTrigger.trigger, parent, itemSet);
|
||||
ability.m_StatTrigger = statTrigger;
|
||||
}
|
||||
// Sentinel
|
||||
case eItemSetPassiveAbilityID::KnightRank1:
|
||||
case eItemSetPassiveAbilityID::KnightRank2:
|
||||
case eItemSetPassiveAbilityID::KnightRank3:
|
||||
case eItemSetPassiveAbilityID::SpaceRangerRank1:
|
||||
case eItemSetPassiveAbilityID::SpaceRangerRank2:
|
||||
case eItemSetPassiveAbilityID::SpaceRangerRank3:
|
||||
case eItemSetPassiveAbilityID::SamuraiRank1:
|
||||
case eItemSetPassiveAbilityID::SamuraiRank2:
|
||||
case eItemSetPassiveAbilityID::SamuraiRank3: {
|
||||
abilities.emplace_back(PassiveAbilityTrigger::SentinelArmor, parent, itemSet);
|
||||
abilities.emplace_back(PassiveAbilityTrigger::EnemySmashed, parent, itemSet);
|
||||
|
||||
break;
|
||||
}
|
||||
// Paradox
|
||||
case eItemSetPassiveAbilityID::BatLord:
|
||||
case eItemSetPassiveAbilityID::SpaceMarauderRank1:
|
||||
case eItemSetPassiveAbilityID::SpaceMarauderRank2:
|
||||
case eItemSetPassiveAbilityID::SpaceMarauderRank3:
|
||||
case eItemSetPassiveAbilityID::SorcererRank1:
|
||||
case eItemSetPassiveAbilityID::SorcererRank2:
|
||||
case eItemSetPassiveAbilityID::SorcererRank3:
|
||||
case eItemSetPassiveAbilityID::ShinobiRank1:
|
||||
case eItemSetPassiveAbilityID::ShinobiRank2:
|
||||
case eItemSetPassiveAbilityID::ShinobiRank3: {
|
||||
abilities.emplace_back(PassiveAbilityTrigger::EnemySmashed, parent, itemSet);
|
||||
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
auto onKillSkills = FindOnKillSkills(skillSets);
|
||||
if (!onKillSkills.empty()) {
|
||||
auto& ability = abilities.emplace_back(PassiveAbilityTrigger::EnemySmashed, parent, itemSet);
|
||||
ability.m_OnKillSkills = std::move(onKillSkills);
|
||||
}
|
||||
|
||||
return abilities;
|
||||
}
|
||||
|
||||
void ItemSetPassiveAbility::OnEnemySmshed(Entity* target) {
|
||||
void ItemSetPassiveAbility::OnEnemySmashed(Entity* target) {
|
||||
if (!target) return;
|
||||
|
||||
auto* destroyableComponent = m_Parent->GetComponent<DestroyableComponent>();
|
||||
auto* skillComponent = m_Parent->GetComponent<SkillComponent>();
|
||||
|
||||
@@ -203,118 +194,16 @@ void ItemSetPassiveAbility::OnEnemySmshed(Entity* target) {
|
||||
return;
|
||||
}
|
||||
|
||||
Game::entityManager->SerializeEntity(m_Parent);
|
||||
|
||||
const auto id = static_cast<eItemSetPassiveAbilityID>(m_ItemSet->GetID());
|
||||
const auto parentID = m_Parent->GetObjectID();
|
||||
const auto equippedCount = m_ItemSet->GetEquippedCount();
|
||||
|
||||
switch (id) {
|
||||
// Bat Lord
|
||||
case eItemSetPassiveAbilityID::BatLord: {
|
||||
if (equippedCount < 5) return;
|
||||
destroyableComponent->Heal(3);
|
||||
break;
|
||||
}
|
||||
// Sentinel
|
||||
case eItemSetPassiveAbilityID::KnightRank1: {
|
||||
if (equippedCount < 5) return;
|
||||
destroyableComponent->Repair(1);
|
||||
break;
|
||||
}
|
||||
case eItemSetPassiveAbilityID::KnightRank2: {
|
||||
if (equippedCount < 5) return;
|
||||
destroyableComponent->Repair(1);
|
||||
break;
|
||||
}
|
||||
case eItemSetPassiveAbilityID::KnightRank3: {
|
||||
if (equippedCount < 5) return;
|
||||
destroyableComponent->Repair(1);
|
||||
break;
|
||||
// The client adds a status effect for each DarkInspiration set skill when it is cast on equip,
|
||||
// which casts the behavior's action when the wearer kills something of a faction in its faction_list.
|
||||
// A tier's skills stay cast while at least that many items are equipped.
|
||||
for (const auto& onKillSkill : m_OnKillSkills) {
|
||||
if (equippedCount < onKillSkill.itemsRequired) continue;
|
||||
|
||||
SkillComponent::CalculateUnmanaged(onKillSkill.behaviorID, m_Parent->GetObjectID(), target->GetObjectID());
|
||||
}
|
||||
|
||||
case eItemSetPassiveAbilityID::SpaceRangerRank1: {
|
||||
if (equippedCount < 5) return;
|
||||
destroyableComponent->Repair(1);
|
||||
break;
|
||||
}
|
||||
case eItemSetPassiveAbilityID::SpaceRangerRank2: {
|
||||
if (equippedCount < 5) return;
|
||||
destroyableComponent->Repair(1);
|
||||
break;
|
||||
}
|
||||
case eItemSetPassiveAbilityID::SpaceRangerRank3: {
|
||||
if (equippedCount < 5) return;
|
||||
destroyableComponent->Repair(1);
|
||||
break;
|
||||
}
|
||||
|
||||
case eItemSetPassiveAbilityID::SamuraiRank1: {
|
||||
if (equippedCount < 5) return;
|
||||
destroyableComponent->Repair(1);
|
||||
break;
|
||||
}
|
||||
case eItemSetPassiveAbilityID::SamuraiRank2: {
|
||||
if (equippedCount < 5) return;
|
||||
destroyableComponent->Repair(1);
|
||||
break;
|
||||
}
|
||||
case eItemSetPassiveAbilityID::SamuraiRank3: {
|
||||
if (equippedCount < 5) return;
|
||||
destroyableComponent->Repair(1);
|
||||
break;
|
||||
}
|
||||
|
||||
// Paradox
|
||||
case eItemSetPassiveAbilityID::SpaceMarauderRank1: {
|
||||
if (equippedCount < 4) return;
|
||||
destroyableComponent->Imagine(1);
|
||||
break;
|
||||
}
|
||||
case eItemSetPassiveAbilityID::SpaceMarauderRank2: {
|
||||
if (equippedCount < 4) return;
|
||||
destroyableComponent->Imagine(2);
|
||||
break;
|
||||
}
|
||||
case eItemSetPassiveAbilityID::SpaceMarauderRank3: {
|
||||
if (equippedCount < 4) return;
|
||||
destroyableComponent->Imagine(3);
|
||||
break;
|
||||
}
|
||||
|
||||
case eItemSetPassiveAbilityID::ShinobiRank1: {
|
||||
if (equippedCount < 4) return;
|
||||
destroyableComponent->Imagine(1);
|
||||
break;
|
||||
}
|
||||
case eItemSetPassiveAbilityID::ShinobiRank2: {
|
||||
if (equippedCount < 4) return;
|
||||
destroyableComponent->Imagine(2);
|
||||
break;
|
||||
}
|
||||
case eItemSetPassiveAbilityID::ShinobiRank3: {
|
||||
if (equippedCount < 4) return;
|
||||
destroyableComponent->Imagine(3);
|
||||
break;
|
||||
}
|
||||
|
||||
case eItemSetPassiveAbilityID::SorcererRank1: {
|
||||
if (equippedCount < 4) return;
|
||||
destroyableComponent->Imagine(1);
|
||||
break;
|
||||
}
|
||||
case eItemSetPassiveAbilityID::SorcererRank2: {
|
||||
if (equippedCount < 4) return;
|
||||
destroyableComponent->Imagine(2);
|
||||
break;
|
||||
}
|
||||
case eItemSetPassiveAbilityID::SorcererRank3: {
|
||||
if (equippedCount < 4) return;
|
||||
destroyableComponent->Imagine(3);
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
Game::entityManager->SerializeEntity(m_Parent);
|
||||
}
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
#include <string_view>
|
||||
#include <vector>
|
||||
|
||||
#include "dCommonVars.h"
|
||||
@@ -16,6 +18,28 @@ enum class PassiveAbilityTrigger
|
||||
EnemySmashed, // Enemy is smashed
|
||||
};
|
||||
|
||||
/**
|
||||
* What a stat-triggered set ability does, as set up by one of the live
|
||||
* server's res/scripts/equipmenttriggers item scripts.
|
||||
*/
|
||||
struct ItemSetStatTrigger {
|
||||
PassiveAbilityTrigger trigger; // the Lua "trigger" var (stat + threshold)
|
||||
uint32_t skillID; // the Lua "skillID" var
|
||||
uint32_t itemsRequired; // the Lua "itemsRequired" var
|
||||
uint32_t setID; // the Lua "skillSet" var: the ItemSets row the script belongs to
|
||||
float cooldown; // the Lua "factionCooldownTime" var; 0 for scripts without "isFactionSkill"
|
||||
};
|
||||
|
||||
/**
|
||||
* One set skill that runs when the wearer smashes something: a skill in the set's
|
||||
* ItemSetSkills whose root behavior is DarkInspiration (the client's "OnKillFactionBehavior").
|
||||
*/
|
||||
struct ItemSetOnKillSkill {
|
||||
uint32_t itemsRequired; // the skillSetWithN tier the skill comes from
|
||||
uint32_t skillID;
|
||||
uint32_t behaviorID;
|
||||
};
|
||||
|
||||
/**
|
||||
* Passive abilities that belong to an item set, activated when a PassiveAbilityTrigger condition is met
|
||||
*/
|
||||
@@ -23,7 +47,6 @@ class ItemSetPassiveAbility
|
||||
{
|
||||
public:
|
||||
ItemSetPassiveAbility(PassiveAbilityTrigger trigger, Entity* parent, ItemSet* itemSet);
|
||||
~ItemSetPassiveAbility();
|
||||
void Update(float deltaTime);
|
||||
|
||||
/**
|
||||
@@ -38,22 +61,51 @@ public:
|
||||
void Activate(Entity* target = nullptr);
|
||||
|
||||
/**
|
||||
* Finds all the passive abilities associated with a certain item set
|
||||
* Finds all the passive abilities of an item set, from the CDClient and the item scripts on its items
|
||||
* @param itemSetID the item set to find abilities for
|
||||
* @param items the LOTs in the set (ItemSets.itemIDs)
|
||||
* @param skillSets the skills of each skillSetWithN tier, index 0 being skillSetWith2
|
||||
* @param parent the parent to add to the passive abilities
|
||||
* @param itemSet the item set to add to the passive abilities
|
||||
* @return the passive abilities for the provided item set
|
||||
*/
|
||||
static std::vector<ItemSetPassiveAbility> FindAbilities(uint32_t itemSetID, Entity* parent, ItemSet* itemSet);
|
||||
static std::vector<ItemSetPassiveAbility> FindAbilities(uint32_t itemSetID, const std::vector<LOT>& items, const std::array<std::vector<uint32_t>, 5>& skillSets, Entity* parent, ItemSet* itemSet);
|
||||
|
||||
/**
|
||||
* The stat triggers the set's items carry. The live server set these up in the equipmenttriggers
|
||||
* item scripts, which are linked to the items through their ScriptComponent.
|
||||
*/
|
||||
static std::vector<ItemSetStatTrigger> FindStatTriggers(uint32_t itemSetID, const std::vector<LOT>& items);
|
||||
|
||||
/**
|
||||
* The on-kill skills among the set's skills: those whose root behavior is DarkInspiration.
|
||||
*/
|
||||
static std::vector<ItemSetOnKillSkill> FindOnKillSkills(const std::array<std::vector<uint32_t>, 5>& skillSets);
|
||||
|
||||
/**
|
||||
* The trigger a live equipmenttriggers script sets up, by the script's file name (any path, any case).
|
||||
* @return nullptr when the script is not a set stat trigger
|
||||
*/
|
||||
static const ItemSetStatTrigger* GetStatTriggerForScript(std::string_view scriptName);
|
||||
|
||||
private:
|
||||
void OnEnemySmshed(Entity* target = nullptr);
|
||||
void OnEnemySmashed(Entity* target);
|
||||
|
||||
/**
|
||||
* The means of triggering this ability
|
||||
*/
|
||||
PassiveAbilityTrigger m_Trigger;
|
||||
|
||||
/**
|
||||
* What a stat trigger does, unused for EnemySmashed
|
||||
*/
|
||||
ItemSetStatTrigger m_StatTrigger{};
|
||||
|
||||
/**
|
||||
* The skills an EnemySmashed ability runs
|
||||
*/
|
||||
std::vector<ItemSetOnKillSkill> m_OnKillSkills;
|
||||
|
||||
/**
|
||||
* The owner of this ability
|
||||
*/
|
||||
|
||||
Reference in New Issue
Block a user