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:
Aaron Kimbrell
2026-09-27 04:18:51 -05:00
parent 347a0a7286
commit e44eaf1862
13 changed files with 494 additions and 353 deletions

View File

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

View File

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

View File

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