@reldens/skills Architecture
Overview of the @reldens/skills package: skill types, levels and class paths, server and client communication, damage calculation and extension points.
Overview
@reldens/skills provides the skills, experience and leveling system used by Reldens. It can be attached to any owner entity and includes:
- Level sets with experience tracking, automatic level up and level modifiers.
- Class paths with skill trees (skills unlocked by level) and labels by level.
- Skill types: base skills, attacks with damage calculation, effects (buffs / debuffs) and physics-based attacks and effects.
- Combat mechanics: critical hits, aim vs dodge, attack vs defense, range validation, cooldowns and cast time.
- Server to client synchronization through a Sender (server) and a Receiver (client).
- Events for every step, namespaced by owner.
npm install @reldens/skills
Dependencies: @reldens/modifiers (Modifier, Condition, PropertyManager, Calculator) and @reldens/utils (Shortcuts, Logger, EventsManagerSingleton, InteractionArea).
const {
Skill, Attack, Effect, PhysicalAttack, PhysicalEffect,
Level, LevelsSet, ClassPath,
Receiver, SkillsEvents, SkillConst
} = require('@reldens/skills');
// the server wrapper is required from its own file:
const SkillsServer = require('@reldens/skills/lib/server');
Owner requirements
- getPosition() returning {x, y} - required by skills, level sets, class paths and the server wrapper.
- An ID property, id by default (configurable with ownerIdProperty).
- Optional eventsPrefix - used as the event namespace for the owner (recommended, see the event system).
- Optional eventUniqueKey() - used to build unique listener keys.
- executePhysicalSkill(target, skill) - required for physical skills.
Package Structure
- lib/skill.js - base Skill class.
- lib/level.js - single Level.
- lib/levels-set.js - level progression and experience.
- lib/class-path.js - class path with skill tree.
- lib/server.js - SkillsServer wrapper.
- lib/constants.js - skill types, states, actions and behaviors.
- lib/skills-events.js - event names.
- lib/server/sender.js - server to client messages.
- lib/client/receiver.js - client message handler.
- lib/types/ - attack.js, effect.js, physical-attack.js, physical-effect.js, physical-skill-runner.js, physical-properties-validator.js.
Core Classes
- Skill (base class)
- Attack - damage calculation
- PhysicalAttack - executed on collision
- Effect - modifiers applied to the target
- PhysicalEffect - executed on collision
- Attack - damage calculation
- LevelsSet - experience and progression
- ClassPath - adds the skill tree and labels by level
- Level - data container: key, label, required experience and modifiers.
- SkillsServer - wraps a ClassPath (property classPath) and a Sender (property client).
- Receiver - client-side message processor.
See Skills Class Hierarchy for the properties and methods of each class.
Data Flow
Skill execution
- The game validates the skill with skill.validate(): cooldown, owner casting state, owner conditions and uses limit. A successful validation starts the cooldown timer.
- skill.execute(target) fires the before execute event, sets the target, runs onExecuteConditions() and the automatic range validation (when enabled).
- Owner effects are applied to the owner.
- With a cast time, the owner is marked as casting and the logic runs when the timer ends; otherwise it runs immediately.
- runSkillLogic() runs the type-specific behavior: damage for attacks, target modifiers for effects, the physics call for physical skills.
- The uses counter increases, onExecuteRewards() runs and the after execute event is fired.
- When a SkillsServer is used, the Sender sends the cast and damage messages to the clients.
See Skills Execution Flow for every step and event.
Level progression
- addExperience(amount) adds the experience to the current total.
- When increaseLevelsWithExperience is enabled (default) and the total reaches the next level requirement, levelUp() runs for every level reached.
- Each level up increases the level, applies the new level modifiers to the owner and fires the level up event. A ClassPath also adds the skills and label of the new level.
- The experience added event is fired with the new total.
- When a SkillsServer is used, the Sender sends the level up and experience messages to the owner client.
See Skills Level Progression for the details.
Class path initialization
- new ClassPath({owner}) stores the owner and the events manager.
- await classPath.init(props) runs the LevelsSet initialization (levels, current level and experience).
- Sets the key (required), label, labels by level and current label.
- Sets the skills by level and fills the current skills with every skill up to the current level.
- Fires the class path init end event.
Event System
All events are defined in SkillsEvents with the reldens.skills. prefix and are fired through the shared EventsManager, namespaced by owner: {ownerEventKey}.reldens.skills.{eventName}, where the owner event key is owner.eventsPrefix when defined.
Event categories:
- Validation: validate before / success / fail.
- Execution: before / after execute, before / after run logic, apply owner effects, range checks.
- Cast: before / after cast.
- Combat: attack apply damage, effect target modifiers, physical attack / effect hit.
- Progression: level set init, set levels, generated levels, level up / down, level apply modifiers, experience added.
- Class path: init end, set skills, add / remove skills before and after.
See Skills Event System for the full catalog, parameters and listener rules.
Property Paths
Every property name used by the package (affected property, attack / defense / aim / dodge properties, range properties, modifier and condition property keys) is a @reldens/modifiers path with / as separator:
- hp - owner.hp
- stats/hp - owner.stats.hp
- stats/power/magical - owner.stats.power.magical
Reading a property that does not exist throws an error, so make sure the owner and target objects contain every configured path.
Modifier Integration
- Level modifiers - applied to the owner on level up and reverted on level down.
- ownerConditions - Condition instances validated against the owner in validate().
- ownerEffects - modifiers applied to the owner when the skill executes (for example, a mana cost), without critical.
- targetEffects - Effect skill modifiers applied to the target, with critical.
const { Modifier, ModifierConst } = require('@reldens/modifiers');
let manaCost = new Modifier({
key: 'fireball-mana-cost',
propertyKey: 'stats/mp',
operation: ModifierConst.OPS.DEC,
value: 10,
minValue: 0
});
let atkBuff = new Modifier({
key: 'atk-boost',
propertyKey: 'stats/atk',
operation: ModifierConst.OPS.INC,
value: 5
});
Network Communication
Sender (server)
Created by SkillsServer when a client is provided. It listens to the ClassPath events and calls the client send() or broadcast() with messages shaped as {act, owner, data}:
- Class path init end - send - level, label, experience, skill keys, next level experience and the level label.
- Level up - send - level, label, new skill keys and next level experience.
- Experience added - send - current experience.
- Skill before cast - broadcast - skill key and the owner position.
- Attack apply damage - broadcast - damage.
The cast and damage messages also include extraData when the owner implements getSkillExtraData({skill, target}).
{
act: 'rski.Lu',
owner: 'player-123',
data: {
lvl: 5,
lab: 'Warrior',
skl: ['sword', 'shield'],
ne: 2000
}
}
Receiver (client)
Processes messages whose action starts with rski. and calls the mapped handler method, for example onLevelUp(message). Extend it and implement the handlers you need; see Skills Event System for the action to method mapping.
Damage Calculation
Attack skills calculate the damage when the logic runs:
- Totals - the owner aim and the target dodge are the sum of their configured properties (or combined with the operators in propertiesTotalOperators).
- Full dodge - when dodgeFullEnabled (default true) and dodge > aim * dodgeOverAimSuccess, the attack is dodged and no damage is applied.
- Target check - without allowEffectBelowZero, no damage is applied when the affected property is already 0 or lower.
- Base damage - hitDamage as is when applyDirectDamage is true; otherwise it is adjusted by the attack vs defense difference:
- Attack higher than defense: the damage increases by the difference as a percentage of the defense (capped at 99%).
- Defense higher than attack: the damage decreases by the difference as a percentage of the attack (capped at 99%).
- With damageAffected and dodge higher than aim, the damage is reduced by the dodge over aim proportion.
- Critical - the critical bonus is calculated on the damage; with criticalAffected and dodge higher than aim, the bonus is reduced by the same proportion. The bonus is added to the damage.
- Apply - the damage is subtracted from the affected property (clamped to 0 unless allowEffectBelowZero) and the attack apply damage event is fired with (skill, target, damage, newValue).
Design Patterns
- Template method - Skill.execute() defines the workflow and calls the hooks onExecuteConditions(), runSkillLogic() and onExecuteRewards().
- Strategy - each skill type implements its own runSkillLogic().
- Observer - every step fires events through the shared EventsManager, so features can react without coupling (persistence, chat messages, animations).
Extension Points
Custom skill types
const { Skill } = require('@reldens/skills');
class MyCustomSkill extends Skill
{
constructor(props)
{
super(props);
this.customProperty = props.customProperty;
}
onExecuteConditions()
{
// return false to cancel the execution
return true;
}
async runSkillLogic()
{
// this.owner and this.target are available here
return true;
}
async onExecuteRewards()
{
// optional rewards after a successful execution
}
}
Custom level progression
const { ClassPath } = require('@reldens/skills');
class BonusExperienceClassPath extends ClassPath
{
async addExperience(number)
{
return await super.addExperience(Math.floor(number * 1.2));
}
}
Event hooks
skill.listenEvent(
SkillsEvents.SKILL_AFTER_EXECUTE,
async (skill, target) => {
// award achievements, update statistics, etc.
},
skill.getOwnerUniqueEventKey('achievementsListener'),
skill.getOwnerEventKey()
);
Performance Considerations
- Remove the owner listeners when the owner is destroyed (for example, by master key with the EventsManager).
- Clear the timers you no longer need: skill.skillActivationTimer (cooldown) and owner.castingTimer (cast time).
- Keep the property paths short and the number of active modifiers per entity limited; every property read navigates the full path.
- Reuse skill instances per owner instead of creating new ones for every execution.
Related Documentation
- Skills Class Hierarchy - Properties, methods and relationships of every class.
- Skills Execution Flow - Step by step skill execution.
- Skills Event System - Events, namespacing and listeners.
- Skills Level Progression - Levels, experience and class paths.
- Skills Testing Guide - Running and writing the package tests.
- Battle System - How Reldens uses skills in combat.
- Skill Entity and Skill Types - Skills configuration.
- @reldens/modifiers Architecture - Modifiers and conditions.
reldens