@reldens/skills
Version:
983 lines (796 loc) • 30.5 kB
Markdown
# CLASS HIERARCHY AND RELATIONSHIPS
## Complete Architecture Reference
## OVERVIEW
This document provides a comprehensive view of the @reldens/skills package architecture, including class inheritance, composition relationships, dependencies, and integration patterns.
## CLASS INHERITANCE HIERARCHY
```
EventsManagerSingleton (from @reldens/utils)
↓
├── Level (standalone)
│
├── LevelsSet
│ ├── init()
│ ├── levelUp()
│ ├── levelDown()
│ ├── addExperience()
│ └── applyLevelModifiers()
│ ↓
│ ClassPath extends LevelsSet
│ ├── (inherits all LevelsSet methods)
│ ├── setOwnerSkills()
│ ├── addSkills()
│ ├── removeSkills()
│ └── levelUp() [overridden]
│ ↓
│ SkillsServer extends ClassPath
│ ├── (inherits all ClassPath methods)
│ └── constructor() [creates Sender]
│
└── Skill
├── execute()
├── validate()
├── runSkillLogic() [abstract]
├── applyModifiers()
└── getCriticalDiff()
↓
Attack extends Skill
├── (inherits all Skill methods)
└── runSkillLogic() [damage calculation]
↓
PhysicalAttack extends Attack
├── (inherits all Attack methods)
└── runSkillLogic() [physics-based damage]
Effect extends Skill
├── (inherits all Skill methods)
└── runSkillLogic() [modifier application]
↓
PhysicalEffect extends Effect
├── (inherits all Effect methods)
└── runSkillLogic() [physics-based effects]
```
## CLASS DETAILS
### Level (lib/level.js)
**Type:** Data Container
**Extends:** None
**Dependencies:**
- @reldens/utils: `sc` (Shortcuts)
**Properties:**
```javascript
{
key: number, // Level number (integer)
label: string, // Display name
requiredExperience: number, // Total XP needed
modifiers: Array<Modifier> // @reldens/modifiers instances
}
```
**Purpose:** Represents a single level with associated modifiers and XP requirement.
**Relationships:**
- **Used by:** LevelsSet, ClassPath
- **Contains:** Modifier instances (from @reldens/modifiers)
### LevelsSet (lib/levels-set.js)
**Type:** Base Class
**Extends:** None (uses EventsManagerSingleton)
**Dependencies:**
- @reldens/utils: `sc`, `EventsManagerSingleton`
- @reldens/modifiers: (indirectly through Level modifiers)
**Properties:**
```javascript
{
owner: Entity, // Entity with getPosition()
key: string, // LevelsSet identifier
levels: Object<Level>, // Level instances by key
currentLevel: number, // Current level number
currentExp: number, // Current experience
autoFillRanges: boolean, // Auto-generate levels
increaseLevelsWithExperience: boolean, // Auto level-up
events: EventsManager, // Event system instance
previousLevel: Level // Last level for modifier reversion
}
```
**Methods:**
```javascript
// Initialization
async init(props)
// Level Management
async levelUp()
async levelDown()
async applyLevelModifiers()
// Experience Management
async addExperience(number)
getNextLevelExperience()
// Level Configuration
async setLevels(levels)
autoFillLevelRanges(levels)
// Event System
async fireEvent(eventName, ...args)
listenEvent(eventName, callback, removeKey, masterKey)
eventFullName(eventName)
getOwnerEventKey()
```
**Purpose:** Manages level progression, experience tracking, and modifier application.
**Relationships:**
- **Contains:** Level instances
- **Extended by:** ClassPath
- **Used by:** SkillsServer (via ClassPath)
### ClassPath (lib/class-path.js)
**Type:** Extended Class
**Extends:** LevelsSet
**Dependencies:**
- Same as LevelsSet
- Skill instances
**Additional Properties:**
```javascript
{
label: string, // Base class label
currentLabel: string, // Active label
labelsByLevel: Object<string>, // Level-specific labels
skillsByLevel: Object<Array<Skill>>, // Skills by level
skillsByLevelKeys: Object<Array<string>>, // Skill keys by level
currentSkills: Object<Skill>, // Available skills
affectedProperty: string // Property for skills
}
```
**Additional Methods:**
```javascript
// Skill Management
async setOwnerSkills(skills)
async addSkills(skills)
async removeSkills(skills)
// Label Management
updateLabelByLevel()
// Overridden
async levelUp() // Extended with skill unlocking
```
**Purpose:** Extends LevelsSet with skill trees and class-specific progression.
**Relationships:**
- **Extends:** LevelsSet
- **Contains:** Skill instances
- **Extended by:** SkillsServer
- **Uses:** Sender (in SkillsServer)
### SkillsServer (lib/server.js)
**Type:** Server Wrapper
**Extends:** ClassPath
**Dependencies:**
- Same as ClassPath
- Sender instance
**Additional Properties:**
```javascript
{
client: Client, // Client with send() and broadcast()
sender: Sender // Message sender instance
}
```
**Additional Methods:**
```javascript
constructor(props) // Creates and initializes ClassPath + Sender
```
**Purpose:** Server-side ClassPath wrapper with automatic client synchronization.
**Relationships:**
- **Extends:** ClassPath
- **Contains:** Sender instance
- **Requires:** Client object
### Skill (lib/skill.js)
**Type:** Base Class
**Extends:** None (uses EventsManagerSingleton)
**Dependencies:**
- @reldens/utils: `sc`, `EventsManagerSingleton`, `InteractionArea`
- @reldens/modifiers: `Condition`
**Properties:**
```javascript
{
key: string, // Unique identifier
owner: Entity, // Entity with getPosition()
target: Entity, // Target entity
range: number, // Skill range (0 = infinite)
rangeAutomaticValidation: boolean, // Auto-validate range
allowSelfTarget: boolean, // Allow self-targeting
usesLimit: number, // Max uses (0 = unlimited)
usesCount: number, // Current use count
ownerConditions: Array<Condition>, // Owner validation conditions
ownerEffects: Array<Modifier>, // Effects applied to owner
skillDelay: number, // Cooldown in ms
canActivate: boolean, // Cooldown state
castTime: number, // Cast time in ms
criticalChance: number, // Critical probability (0-1)
criticalMultiplier: number, // Critical damage multiplier
criticalFixedValue: number, // Fixed critical bonus
lastAppliedModifiers: Object, // Last modifier results
events: EventsManager // Event system instance
}
```
**Methods:**
```javascript
// Execution
async execute(target)
async validate()
async runSkillLogic() // Abstract - override in subclasses
// Critical System
applyCriticalValue(value)
getCriticalDiff(value)
canCriticalHit()
// Modifier Application
applyModifiers(modifiersObjectList, target, avoidCritical)
// Hooks
onExecuteConditions() // Override for custom validation
// Event System
async fireEvent(eventName, ...args)
listenEvent(eventName, callback, removeKey, masterKey)
eventFullName(eventName)
getOwnerEventKey()
```
**Purpose:** Base class for all skill types with validation, execution, and modifier systems.
**Relationships:**
- **Extended by:** Attack, Effect
- **Used by:** ClassPath (in skillsByLevel)
- **Requires:** Owner entity
- **Uses:** Condition, Modifier (from @reldens/modifiers)
### Attack (lib/types/attack.js)
**Type:** Skill Type
**Extends:** Skill
**Dependencies:** Same as Skill
**Additional Properties:**
```javascript
{
affectedProperty: string, // Target property to damage
hitDamage: number, // Base damage at 100%
applyDirectDamage: boolean, // Skip calculations
attackProperties: Array<string>, // Owner attack properties
defenseProperties: Array<string>, // Target defense properties
aimProperties: Array<string>, // Owner aim properties
dodgeProperties: Array<string>, // Target dodge properties
dodgeFullEnabled: boolean, // Enable complete dodge
dodgeOverAimSuccess: number, // Dodge threshold multiplier
damageAffected: boolean, // Apply dodge/aim to damage
criticalAffected: boolean, // Apply dodge/aim to critical
propertiesTotalOperators: Object, // Custom operators for totals
allowEffectBelowZero: boolean // Allow negative values
}
```
**Overridden Methods:**
```javascript
async runSkillLogic() // Damage calculation and application
```
**Purpose:** Damage-based skills with attack/defense and dodge/aim systems.
**Relationships:**
- **Extends:** Skill
- **Extended by:** PhysicalAttack
- **Used by:** ClassPath (as skill instance)
### Effect (lib/types/effect.js)
**Type:** Skill Type
**Extends:** Skill
**Dependencies:** Same as Skill
**Additional Properties:**
```javascript
{
targetEffects: Array<Modifier> // Modifiers applied to target
}
```
**Overridden Methods:**
```javascript
async runSkillLogic() // Modifier application to target
```
**Purpose:** Modifier-based skills (buffs/debuffs).
**Relationships:**
- **Extends:** Skill
- **Extended by:** PhysicalEffect
- **Used by:** ClassPath (as skill instance)
### PhysicalAttack (lib/types/physical-attack.js)
**Type:** Skill Type
**Extends:** Attack
**Dependencies:**
- Same as Attack
- PhysicalPropertiesValidator
- PhysicalSkillRunner
**Additional Properties:**
```javascript
{
magnitude: number, // Physics force
objectWidth: number, // Collision body width
objectHeight: number, // Collision body height
validateTargetOnHit: boolean // Verify target on collision
}
```
**Overridden Methods:**
```javascript
async runSkillLogic() // Physics-based attack execution
```
**Purpose:** Attack skills with physics/collision detection.
**Relationships:**
- **Extends:** Attack
- **Requires:** Owner implements `executePhysicalSkill()`
- **Uses:** PhysicalPropertiesValidator, PhysicalSkillRunner
### PhysicalEffect (lib/types/physical-effect.js)
**Type:** Skill Type
**Extends:** Effect
**Dependencies:**
- Same as Effect
- PhysicalPropertiesValidator
- PhysicalSkillRunner
**Additional Properties:**
```javascript
{
magnitude: number, // Physics force
objectWidth: number, // Collision body width
objectHeight: number, // Collision body height
validateTargetOnHit: boolean // Verify target on collision
}
```
**Overridden Methods:**
```javascript
async runSkillLogic() // Physics-based effect execution
```
**Purpose:** Effect skills with physics/collision detection.
**Relationships:**
- **Extends:** Effect
- **Requires:** Owner implements `executePhysicalSkill()`
- **Uses:** PhysicalPropertiesValidator, PhysicalSkillRunner
## COMPOSITION RELATIONSHIPS
### LevelsSet Composition
```
LevelsSet
├── contains: Level[]
│ └── Level
│ └── contains: Modifier[] (from @reldens/modifiers)
│
└── uses: EventsManagerSingleton (from @reldens/utils)
```
### ClassPath Composition
```
ClassPath (extends LevelsSet)
├── inherits: LevelsSet composition
│
├── contains: Skill[]
│ └── Skill (Attack, Effect, PhysicalAttack, PhysicalEffect)
│ ├── contains: Condition[] (from @reldens/modifiers)
│ └── contains: Modifier[] (from @reldens/modifiers)
│
└── skillsByLevel: {level: Skill[]}
```
### SkillsServer Composition
```
SkillsServer (extends ClassPath)
├── inherits: ClassPath composition
│
├── contains: Sender
│ ├── listens to: ClassPath events
│ └── uses: Client (send/broadcast)
│
└── uses: Client
```
## SERVER-CLIENT ARCHITECTURE
```
┌─────────────────────────────────────────────────────────────┐
│ SERVER SIDE │
├─────────────────────────────────────────────────────────────┤
│ │
│ ┌───────────────┐ ┌──────────────┐ │
│ │ SkillsServer │───────▶│ ClassPath │ │
│ │ (wrapper) │ │ (progression)│ │
│ └───────┬───────┘ └──────┬───────┘ │
│ │ │ │
│ │ creates │ fires events │
│ │ │ │
│ ▼ ▼ │
│ ┌───────────────┐ ┌──────────────┐ │
│ │ Sender │◀───────│ Events │ │
│ │ (broadcaster) │listens │ Manager │ │
│ └───────┬───────┘ └──────────────┘ │
│ │ │
│ │ sends messages │
│ │ │
└──────────┼──────────────────────────────────────────────────┘
│
│ Network (WebSocket, etc.)
│
┌──────────▼──────────────────────────────────────────────────┐
│ CLIENT SIDE │
├─────────────────────────────────────────────────────────────┤
│ │
│ ┌───────────────┐ ┌──────────────┐ │
│ │ Receiver │───────▶│ UI/Game │ │
│ │ (processor) │updates │ Client │ │
│ └───────▲───────┘ └──────────────┘ │
│ │ │
│ │ receives messages │
│ │ │
└──────────┴──────────────────────────────────────────────────┘
```
### Message Flow Example:
```
1. SERVER: Player gains XP
├─▶ SkillsServer.addExperience(500)
│ └─▶ ClassPath.addExperience(500)
│ └─▶ Fires LEVEL_EXPERIENCE_ADDED event
│
2. SERVER: Sender catches event
├─▶ Sender.sendExperienceData(classPath)
│ └─▶ client.send({act: 'rski.Ea', data: {exp: 500}})
│
3. NETWORK: Message transmitted
│
4. CLIENT: Receiver processes message
└─▶ Receiver.processMessage({act: 'rski.Ea', ...})
└─▶ Receiver.onLevelExperienceAdded(message)
└─▶ Update UI (XP bar, etc.)
```
## EXTERNAL DEPENDENCIES
### @reldens/utils
**Used by:** All classes
**Imports:**
- `sc` (Shortcuts): Property getters/setters, modifier application
- `EventsManagerSingleton`: Global event system
- `InteractionArea`: Range validation (used by Skill)
- `Logger`: Logging utility
**Key Methods Used:**
```javascript
// Shortcuts
sc.get(object, path, defaultValue)
sc.hasOwn(object, property)
sc.applyModifier(target, modifier)
sc.revertModifier(target, modifier)
// EventsManager
EventsManagerSingleton.on(eventName, callback)
EventsManagerSingleton.onWithKey(eventName, callback, removeKey, masterKey)
EventsManagerSingleton.emit(eventName, ...args)
EventsManagerSingleton.removeAllListeners()
// InteractionArea
InteractionArea.isValidInteraction(area1, area2, distance)
```
### @reldens/modifiers
**Used by:** Level, Skill, Attack, Effect
**Imports:**
- `Modifier`: Modifier instances for buffs/debuffs
- `Condition`: Validation conditions
- `PropertyManager`: Property management (indirect)
- `Calculator`: Modifier calculation (indirect)
**Key Patterns:**
```javascript
// Creating modifiers
let modifier = new Modifier({
key: 'hp-buff',
propertyKey: 'stats.hp',
operation: '+',
value: 50
});
// Using modifiers in skills
modifier.target = target;
let newValue = modifier.getModifiedValue(); // Returns modified value
modifier.setOwnerProperty(modifier.propertyKey, newValue);
// Creating conditions
let condition = new Condition({
key: 'has-mana',
propertyKey: 'stats.mp',
operation: '>=',
value: 50
});
// Validating conditions
let isValid = condition.isValid(owner);
```
## OWNER ENTITY REQUIREMENTS
### Minimum Requirements for All Classes
**For LevelsSet / ClassPath:**
```javascript
class MinimalOwner {
constructor() {
this.position = {x: 0, y: 0};
}
getPosition() {
return this.position;
}
// Required for event system
get eventsPrefix() {
return 'skills.ownerId.' + this.id;
}
}
```
### Requirements for Skills
**Basic Skills:**
```javascript
class SkillOwner extends MinimalOwner {
isInRange(skill, target, range) {
if(range === 0) return true;
let distance = this.getDistanceTo(target);
return distance <= range;
}
getDistanceTo(target) {
let dx = this.position.x - target.position.x;
let dy = this.position.y - target.position.y;
return Math.sqrt(dx * dx + dy * dy);
}
}
```
**Skills with Cast Time:**
```javascript
class CastingOwner extends SkillOwner {
constructor() {
super();
this.isCasting = false; // Required for cast time
}
}
```
**Physical Skills:**
```javascript
class PhysicalOwner extends CastingOwner {
async executePhysicalSkill(target, skill, executeOnHit) {
// Create physics body with skill.objectWidth, skill.objectHeight
let body = this.physicsEngine.createBody({
width: skill.objectWidth,
height: skill.objectHeight,
position: this.position
});
// Apply force/magnitude
body.applyForce(skill.magnitude, this.directionToTarget(target));
// Handle collision
body.onCollision((collidedEntity) => {
if(collidedEntity === target) {
executeOnHit(target);
}
});
return true;
}
}
```
### Requirements for Client
**SkillsServer Client:**
```javascript
class GameClient {
send(message) {
// Send message to this client only
this.connection.send(JSON.stringify(message));
}
broadcast(message) {
// Send message to all clients in room
this.room.broadcast(JSON.stringify(message));
}
}
```
## INTEGRATION PATTERNS
### Pattern #1: Basic Single-Class Setup
```javascript
import {ClassPath} from '@reldens/skills/lib/class-path.js';
import {Level} from '@reldens/skills/lib/level.js';
import {Skill} from '@reldens/skills/lib/skill.js';
// Create levels
let levels = {
1: new Level({key: 1, requiredExperience: 0}),
5: new Level({key: 5, requiredExperience: 1000})
};
// Create skills
let skill1 = new Skill({key: 'basic', owner: player});
let skill2 = new Skill({key: 'advanced', owner: player});
// Initialize class path
let classPath = new ClassPath();
await classPath.init({
owner: player,
key: 'warrior',
levels: levels,
skillsByLevel: {
1: [skill1],
5: [skill2]
}
});
```
### Pattern #2: Server-Side Multi-Class System
```javascript
import {SkillsServer} from '@reldens/skills/lib/server.js';
class PlayerClassManager {
constructor(player, client) {
this.player = player;
this.client = client;
this.classes = {};
}
async createClass(key, config) {
this.classes[key] = new SkillsServer({
owner: this.player,
client: this.client,
key: key,
label: config.label,
levels: config.levels,
skillsByLevel: config.skillsByLevel
});
return this.classes[key];
}
async addXP(classKey, amount) {
if(!this.classes[classKey]) {
throw new Error('Class not found: ' + classKey);
}
await this.classes[classKey].addExperience(amount);
}
getClass(classKey) {
return this.classes[classKey];
}
}
```
---
### Pattern #3: Client-Side Integration
```javascript
import {Receiver} from '@reldens/skills/lib/client/receiver.js';
class SkillsUI extends Receiver {
constructor(owner) {
super({owner});
this.xpBar = document.getElementById('xp-bar');
this.levelDisplay = document.getElementById('level');
this.skillsContainer = document.getElementById('skills');
}
onLevelUp(message) {
// Update UI on level up
this.levelDisplay.textContent = message.data.lvl;
this.updateSkillsDisplay(message.data.skl);
this.showLevelUpAnimation();
}
onLevelExperienceAdded(message) {
// Update XP bar
let percentage = (message.data.exp / message.data.ne) * 100;
this.xpBar.style.width = percentage + '%';
}
updateSkillsDisplay(skillKeys) {
this.skillsContainer.innerHTML = '';
skillKeys.forEach(key => {
let skillButton = this.createSkillButton(key);
this.skillsContainer.appendChild(skillButton);
});
}
}
// Initialize
let skillsUI = new SkillsUI(player);
gameClient.onMessage('*', (message) => {
skillsUI.processMessage(message);
});
```
### Pattern #4: Custom Skill Type
```javascript
import {Skill} from '@reldens/skills/lib/skill.js';
class HealSkill extends Skill {
constructor(props) {
super(props);
this.healAmount = props.healAmount || 50;
this.affectedProperty = props.affectedProperty || 'hp';
}
async runSkillLogic() {
// Validate target
if(!this.target) {
return {error: 'TARGET_NOT_AVAILABLE'};
}
// Calculate heal (with critical)
let healAmount = this.healAmount;
healAmount = healAmount + this.getCriticalDiff(healAmount);
// Apply heal
let currentValue = sc.get(this.target, this.affectedProperty, 0);
let maxValue = sc.get(this.target, 'stats.maxHp', 100);
let newValue = Math.min(currentValue + healAmount, maxValue);
sc.set(this.target, this.affectedProperty, newValue);
// Fire custom event
await this.fireEvent('SKILL_HEAL_APPLIED', this, this.target, healAmount, newValue);
return {
healed: healAmount,
[this.affectedProperty]: newValue
};
}
}
```
### Pattern #5: Persistent Storage Integration
```javascript
class PersistentClassPath extends ClassPath {
async init(props) {
// Load from database
let savedData = await database.loadClassPath(props.owner.id, props.key);
if(savedData) {
// Restore saved state
props.currentLevel = savedData.currentLevel;
props.currentExp = savedData.currentExp;
}
await super.init(props);
// Listen for changes to save
this.listenEvent(SkillsEvents.LEVEL_UP, async (cp) => {
await this.save();
}, 'persistent-level-up');
this.listenEvent(SkillsEvents.LEVEL_EXPERIENCE_ADDED, async (cp) => {
await this.save();
}, 'persistent-xp-added');
}
async save() {
await database.saveClassPath(this.owner.id, this.key, {
currentLevel: this.currentLevel,
currentExp: this.currentExp,
currentSkills: Object.keys(this.currentSkills)
});
}
}
```
## CLASS RESPONSIBILITY SUMMARY
| Class | Primary Responsibility | Key Methods | Events |
|-------|------------------------|-------------|--------|
| **Level** | Store level data | (constructor only) | None |
| **LevelsSet** | Manage level progression and XP | init, levelUp, addExperience | LEVEL_UP, LEVEL_EXPERIENCE_ADDED |
| **ClassPath** | Add skill trees to progression | setOwnerSkills, addSkills, removeSkills | ADD_SKILLS_*, REMOVE_SKILLS_* |
| **SkillsServer** | Server-client synchronization | (constructor) | (via Sender) |
| **Skill** | Base skill execution | execute, validate, applyModifiers | SKILL_BEFORE_EXECUTE, VALIDATE_* |
| **Attack** | Damage-based skills | runSkillLogic (damage calc) | SKILL_ATTACK_APPLY_DAMAGE |
| **Effect** | Modifier-based skills | runSkillLogic (modifiers) | SKILL_EFFECT_TARGET_MODIFIERS |
| **PhysicalAttack** | Physics-based attacks | runSkillLogic (physics) | SKILL_PHYSICAL_ATTACK_HIT |
| **PhysicalEffect** | Physics-based effects | runSkillLogic (physics) | SKILL_PHYSICAL_EFFECT_HIT |
| **Sender** | Broadcast server events to clients | register, send methods | (listens to ClassPath events) |
| **Receiver** | Process client messages | processMessage, on* methods | None |
## DEPENDENCY GRAPH
```
┌────────────────────────────────────────────────────────┐
│ EXTERNAL PACKAGES │
├────────────────────────────────────────────────────────┤
│ @reldens/utils @reldens/modifiers │
│ ├─ Shortcuts (sc) ├─ Modifier │
│ ├─ EventsManagerSingleton├─ Condition │
│ ├─ InteractionArea └─ Calculator │
│ └─ Logger │
└────────┬──────────────────────────┬────────────────────┘
│ │
▼ ▼
┌─────────────────┐ ┌───────────────────┐
│ Level │◀───────│ LevelsSet │
│ (standalone) │contains│ (progression) │
└─────────────────┘ └─────────┬─────────┘
│ extends
▼
┌───────────────────┐
│ ClassPath │
│ (skill trees) │
└─────────┬─────────┘
│ extends
▼
┌───────────────────┐ ┌──────────┐
│ SkillsServer │◀───────│ Sender │
│ (server) │creates │(broadcast)│
└───────────────────┘ └──────────┘
┌──────────┐ ┌────────────┐ ┌──────────────────┐
│ Skill │◀───────│ Attack │◀───────│ PhysicalAttack │
│ (base) │extends │ (damage) │extends │ (physics) │
└────┬─────┘ └────────────┘ └──────────────────┘
│ extends
▼
┌────────────┐ ┌──────────────────┐
│ Effect │◀───────│ PhysicalEffect │
│(modifiers) │extends │ (physics) │
└────────────┘ └──────────────────┘
┌──────────┐
│ Receiver │
│ (client) │
└──────────┘
```
## REFERENCES
- Level: `lib/level.js:9-33`
- LevelsSet: `lib/levels-set.js:11-298`
- ClassPath: `lib/class-path.js:11-315`
- SkillsServer: `lib/server.js:11-85`
- Skill: `lib/skill.js:12-348`
- Attack: `lib/types/attack.js:14-145`
- Effect: `lib/types/effect.js:14-70`
- PhysicalAttack: `lib/types/physical-attack.js:16-118`
- PhysicalEffect: `lib/types/physical-effect.js:16-116`
- Sender: `lib/server/sender.js:11-212`
- Receiver: `lib/client/receiver.js:10-73`