mirror of
https://github.com/DarkflameUniverse/DarkflameServer.git
synced 2026-10-02 10:53:44 +00:00
The client reads the cooldowns still running from the charxml on load
(LWOSkillComponent::LoadFromSaveData 0x00c2a9c0): <skil sc="..."> split
on ';' and ':' (the delimiter string at 0x015b1714 is L";:") into a
cooldown group and the seconds left, each put in its cooldown map under
{hasCooldownGroup = 1, group}, the key it checks when the player casts a
skill of that group (0x00c11400). Live wrote <skil/> with nothing running
and e.g. <skil sc="17:15.6958;"/> otherwise (226 live charxmls; groups
8, 17 and 78 seen, the times below the groups' SkillBehavior cooldowns).
The claude-client-re-docs page described sc as "id;time" pairs; the
live saves and the delimiters show "group:time;".
DLU wrote no <skil>, so relogging or changing zones cleared every
cooldown (e.g. the 90 s imagination and faction skills).
- A player's successful cast (CastPlayerSkill) starts its
SkillBehavior.cooldowngroup's cooldown (groups 0 and up, cooldown > 0),
counted down in Update.
- Saved as live wrote it; loaded back when the player's SkillComponent
is created, so a cooldown survives several zone changes. Old saves
without <skil> load with none running.
Check in game: use a skill with a long cooldown (e.g. a faction kit's
special skill, a consumable with a cooldown), change zones or log out and
back in right away: its cooldown is still shown and counts down from
where it was (minus the loading time on the server).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
263 lines
8.8 KiB
C++
263 lines
8.8 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;
|
|
|
|
/**
|
|
* Loads the cooldown groups still running at the last save: <skil sc="group:seconds;group:seconds;"/>
|
|
*/
|
|
void LoadFromXml(const tinyxml2::XMLDocument& doc) override;
|
|
|
|
/**
|
|
* Saves the running cooldown groups the way live did and the client reads them (LWOSkillComponent::LoadFromSaveData
|
|
* 0x00c2a9c0 splits sc on ';' and ':' into a cooldown group and the seconds left)
|
|
*/
|
|
void UpdateXml(tinyxml2::XMLDocument& doc) override;
|
|
|
|
/**
|
|
* Starts the cooldown of a skill's cooldown group (SkillBehavior.cooldowngroup, cooldown) when the player casts it
|
|
*/
|
|
void StartCooldown(uint32_t skillID);
|
|
|
|
/**
|
|
* The cooldown groups running and the seconds left on each
|
|
*/
|
|
const std::map<int32_t, float>& GetCooldownGroups() const { return m_CooldownGroups; }
|
|
|
|
/**
|
|
* Computes server-side skill updates.
|
|
*/
|
|
void CalculateUpdate(float deltaTime);
|
|
|
|
/**
|
|
* Resets all skills, projectiles, and other calculations.
|
|
*/
|
|
void Reset();
|
|
|
|
/**
|
|
* Interrupts active skills.
|
|
*/
|
|
void Interrupt();
|
|
|
|
/**
|
|
* Ends a skill this entity is running: drops its behaviors (and their pending hits and projectiles) with that
|
|
* skill handle
|
|
* @param skillUid the skill handle
|
|
*/
|
|
void EndSkill(uint32_t skillUid);
|
|
|
|
/**
|
|
* @return whether this entity runs a skill with that handle
|
|
*/
|
|
bool HasSkill(uint32_t skillUid) const { return m_managedBehaviors.contains(skillUid); }
|
|
|
|
/**
|
|
* 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;
|
|
|
|
// Cooldown group -> seconds left, for the player's save
|
|
std::map<int32_t, float> m_CooldownGroups;
|
|
|
|
/**
|
|
* 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
|