Files
DarkflameServer/dGame/dComponents/SkillComponent.h
Aaron Kimbrell e44eaf1862 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>
2026-09-28 22:30:58 -05:00

227 lines
7.5 KiB
C++

/*
* Darkflame Universe
* Copyright 2024
*/
#ifndef SKILLCOMPONENT_H
#define SKILLCOMPONENT_H
#include <map>
#include "BehaviorContext.h"
#include "BitStream.h"
#include "Component.h"
#include "Entity.h"
#include "Logger.h"
#include "eReplicaComponentType.h"
struct ProjectileSyncEntry {
LWOOBJID id = LWOOBJID_EMPTY;
bool calculation = false;
mutable float time = 0;
float maxTime = 0;
NiPoint3 startPosition{};
NiPoint3 lastPosition{};
NiPoint3 velocity{};
bool trackTarget = false;
float trackRadius = 0;
BehaviorContext* context = nullptr;
LOT lot = LOT_NULL;
BehaviorBranchContext branchContext{ 0, 0 };
int32_t skillId{ 0 };
explicit ProjectileSyncEntry();
};
struct SkillExecutionResult {
bool success;
float skillTime;
};
/**
* The SkillComponent of an entity. This manages both player and AI skills, such as attacks and consumables.
* There are two sets of skill methods: one for player skills and one for server-side calculations.
*
* Skills are a built up by a tree of behaviors. See dGame/dBehaviors/ for a list of behaviors.
*
* This system is very convoluted and still has a lot of unknowns.
*/
class SkillComponent final : public Component {
public:
static constexpr eReplicaComponentType ComponentType = eReplicaComponentType::SKILL;
explicit SkillComponent(Entity* parent, const int32_t componentID);
~SkillComponent() override;
void Serialize(RakNet::BitStream& outBitStream, bool bIsInitialUpdate) override;
/**
* Computes skill updates. Invokes CalculateUpdate.
*/
void Update(float deltaTime) override;
/**
* Computes server-side skill updates.
*/
void CalculateUpdate(float deltaTime);
/**
* Resets all skills, projectiles, and other calculations.
*/
void Reset();
/**
* Interrupts active skills.
*/
void Interrupt();
/**
* Starts a player skill. Should only be called when the server receives a start skill message from the client.
* @param behaviorId the root behavior ID of the skill
* @param skillUid the unique ID of the skill given by the client
* @param bitStream the bitSteam given by the client to determine the behavior path
* @param target the explicit target of the skill
*/
bool CastPlayerSkill(uint32_t behaviorId, uint32_t skillUid, RakNet::BitStream& bitStream, LWOOBJID target, uint32_t skillID = 0);
/**
* Continues a player skill. Should only be called when the server receives a sync message from the client.
* @param skillUid the unique ID of the skill given by the client
* @param syncId the unique sync ID of the skill given by the client
* @param bitStream the bitSteam given by the client to determine the behavior path
*/
void SyncPlayerSkill(uint32_t skillUid, uint32_t syncId, RakNet::BitStream& bitStream);
/**
* Continues a player projectile calculation. Should only be called when the server receives a projectile sync message from the client.
* @param projectileId the unique ID of the projectile given by the client
* @param bitStream the bitSteam given by the client to determine the behavior path
* @param target the explicit target of the target
*/
void SyncPlayerProjectile(LWOOBJID projectileId, RakNet::BitStream& bitStream, LWOOBJID target);
/**
* Registers a player projectile. Should only be called when the server is computing a player projectile.
* @param projectileId the unique ID of the projectile given by the client
* @param context the current behavior context of the active skill
* @param branch the current behavior branch context of the active skill
* @param lot the LOT of the projectile
*/
void RegisterPlayerProjectile(LWOOBJID projectileId, BehaviorContext* context, const BehaviorBranchContext& branch, LOT lot);
/**
* Wrapper for CalculateBehavior that mimics the call structure in scripts and helps reduce magic numbers
* @param skillId the skill to cast
* @param target the target of the skill
* @param optionalOriginatorID change the originator of the skill
* @return if the case succeeded
*/
bool CastSkill(const uint32_t skillId, LWOOBJID target = LWOOBJID_EMPTY, const LWOOBJID optionalOriginatorID = LWOOBJID_EMPTY, const int32_t castType = 0, const NiQuaternion rotationOverride = QuatUtils::IDENTITY);
/**
* Initializes a server-side skill calculation.
* @param skillId the skill ID
* @param behaviorId the root behavior ID of the skill
* @param target the explicit target of the skill
* @param ignoreTarget continue the skill calculation even if the target is invalid or no target is found
* @param clientInitalized indicates if the skill calculation was initiated by a client skill, ignores some checks
* @param originatorOverride an override for the originator of the skill calculation
* @return the result of the skill calculation
*/
SkillExecutionResult CalculateBehavior(uint32_t skillId, uint32_t behaviorId, LWOOBJID target, bool ignoreTarget = false, bool clientInitalized = false, LWOOBJID originatorOverride = LWOOBJID_EMPTY, const int32_t castType = 0, const NiQuaternion rotationOverride = QuatUtils::IDENTITY);
/**
* Register a server-side projectile.
* @param projectileId the unique ID of the projectile
* @param context the current behavior context of the active skill
* @param branch the current behavior branch context of the active skill
* @param lot the LOT of the projectile
* @param maxTime the maximum travel time of the projectile
* @param startPosition the start position of the projectile
* @param velocity the velocity of the projectile
* @param trackTarget whether the projectile should track the target
* @param trackRadius the radius of the tracking circle
*/
void RegisterCalculatedProjectile(
LWOOBJID projectileId,
BehaviorContext* context,
const BehaviorBranchContext& branch,
LOT lot,
const float maxTime,
const NiPoint3& startPosition,
const NiPoint3& velocity,
bool trackTarget,
float TrackRadius);
/**
* Computes a server-side skill calculation without an associated entity.
* @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
*/
static void HandleUnmanaged(uint32_t behaviorId, LWOOBJID target, LWOOBJID source = LWOOBJID_EMPTY);
/**
* Calculates a behavior on the server only, without syncing it to clients, for effects the
* clients already run on their own (e.g. item set DarkInspiration status effects).
* @param behaviorId the root behavior ID of the skill
* @param caster the entity the behavior is cast by
* @param target the entity the behavior targets
*/
static void CalculateUnmanaged(uint32_t behaviorId, LWOOBJID caster, LWOOBJID target);
/**
* 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
*/
static void HandleUnCast(uint32_t behaviorId, LWOOBJID target);
/**
* @returns a unique ID for the next skill calculation
*/
uint32_t GetUniqueSkillId();
private:
/**
* All of the active skills mapped by their unique ID.
*/
std::multimap<uint32_t, BehaviorContext*> m_managedBehaviors;
/**
* All active projectiles.
*/
std::vector<ProjectileSyncEntry> m_managedProjectiles;
/**
* Unique ID counter.
*/
uint32_t m_skillUid;
/**
* Cache for looking up a behavior id via a skill ID
*/
static std::unordered_map<uint32_t, uint32_t> m_skillBehaviorCache;
/**
* Sync a server-side projectile calculation.
* @param entry the projectile information
*/
void SyncProjectileCalculation(const ProjectileSyncEntry& entry) const;
};
#endif // SKILLCOMPONENT_H