@reldens/skills
Version:
980 lines (788 loc) • 27.1 kB
Markdown
# SKILL EXECUTION FLOW
## Complete Step-by-Step Guide
## OVERVIEW
This document provides a detailed walkthrough of skill execution from the moment a skill is triggered until all effects are applied and events fired. Understanding this flow is critical for debugging, extending functionality, and implementing custom skills.
## ENTRY POINTS
### Method 1: Direct Execution
```javascript
await skill.execute(target);
```
### Method 2: With Owner Execution Hook
```javascript
// For physical skills, owner must implement this
await owner.executePhysicalSkill(target, skill);
```
## COMPLETE EXECUTION FLOW
### PHASE 1: PRE-VALIDATION
**File:** `lib/skill.js:106-125`
```
1. execute(target) called
2. Set this.target = target
3. Check if owner is casting (this.owner.isCasting)
→ If true: return {error: SKILL.CAN_NOT_ACTIVATE}
4. Fire SKILL_BEFORE_EXECUTE event
→ Event params: (this, target)
5. Continue to validation
```
**Events Fired:**
- `SkillsEvents.SKILL_BEFORE_EXECUTE`
**Possible Returns:**
- `{error: SKILL.CAN_NOT_ACTIVATE}` - Owner is casting another skill
### PHASE 2: VALIDATION
**File:** `lib/skill.js:147-187`
```
1. validate() called
2. Fire VALIDATE_BEFORE event
→ Event params: (this)
3. Check ownerConditions (if defined)
→ For each condition in this.ownerConditions:
→ condition.isValid(this.owner)
→ If any fails:
→ Fire VALIDATE_FAIL event with failedCondition
→ Return false
4. Check canActivate flag (cooldown/delay)
→ If false: Return false
5. Check range (if rangeAutomaticValidation enabled)
→ Call this.owner.isInRange(this, this.target, this.range)
→ Fire SKILL_BEFORE_IN_RANGE event
→ Fire SKILL_AFTER_IN_RANGE event with result
→ If out of range: Return false
6. Call onExecuteConditions() (hook for custom validation)
→ If returns false: Return false
7. Set canActivate = false (start cooldown)
8. Set skillActivationTimer for cooldown reset
9. Fire VALIDATE_SUCCESS event
10. Return true
```
**Events Fired:**
- `SkillsEvents.VALIDATE_BEFORE`
- `SkillsEvents.VALIDATE_FAIL` (if condition fails)
- `SkillsEvents.SKILL_BEFORE_IN_RANGE` (if range check enabled)
- `SkillsEvents.SKILL_AFTER_IN_RANGE` (if range check enabled)
- `SkillsEvents.VALIDATE_SUCCESS` (if all checks pass)
**Possible Returns:**
- `false` - Validation failed (condition, range, cooldown)
- `true` - Validation passed, ready to execute
**Hooks:**
- `onExecuteConditions()` - Override for custom validation logic
### PHASE 3: OWNER EFFECTS APPLICATION
**File:** `lib/skill.js:126-128`
```
1. Check if this.ownerEffects exists and has length > 0
2. If yes:
→ Fire SKILL_APPLY_OWNER_EFFECTS event
→ Event params: (this, target)
→ Call applyModifiers(this.ownerEffects, this.owner, true)
→ Note: avoidCritical = true (no crits on owner effects)
```
**Events Fired:**
- `SkillsEvents.SKILL_APPLY_OWNER_EFFECTS`
**What Happens:**
- Modifiers in ownerEffects are applied to the skill owner
- Examples: Buff owner's attack, consume mana, apply shield
- Critical damage is NOT applied to owner effects
### PHASE 4: CAST TIME HANDLING
**File:** `lib/skill.js:129-134`
```
1. Check if this.castTime > 0
2. If yes:
→ Fire SKILL_BEFORE_CAST event
→ Event params: (this, target)
→ Set this.owner.isCasting = true
→ await sleep(this.castTime)
→ Set this.owner.isCasting = false
→ Fire SKILL_AFTER_CAST event
→ Event params: (this, target)
3. Continue to skill logic
```
**Events Fired:**
- `SkillsEvents.SKILL_BEFORE_CAST` (if castTime > 0)
- `SkillsEvents.SKILL_AFTER_CAST` (if castTime > 0)
**State Changes:**
- `owner.isCasting` set to true during cast
- `owner.isCasting` set to false after cast completes
**Blocking:**
- If owner is already casting, execute() returns early (PHASE 1)
- Cast time blocks thread with await sleep()
### PHASE 5: SKILL LOGIC EXECUTION
**File:** `lib/skill.js:135-137`
```
1. Fire SKILL_BEFORE_RUN_LOGIC event
→ Event params: (this, target)
2. await this.runSkillLogic()
→ This is the main skill behavior method
→ Overridden by skill types (Attack, Effect, Physical, etc.)
3. Fire SKILL_AFTER_RUN_LOGIC event
→ Event params: (this, target)
```
**Events Fired:**
- `SkillsEvents.SKILL_BEFORE_RUN_LOGIC`
- `SkillsEvents.SKILL_AFTER_RUN_LOGIC`
**Hooks:**
- `runSkillLogic()` - MUST be overridden by skill types
### PHASE 6: POST-EXECUTION
**File:** `lib/skill.js:138-141`
```
1. Increment this.usesCount
2. Fire SKILL_AFTER_EXECUTE event
→ Event params: (this, target)
3. Return result (varies by skill type)
```
**Events Fired:**
- `SkillsEvents.SKILL_AFTER_EXECUTE`
**State Changes:**
- `usesCount` incremented
- If `usesLimit` reached, skill may become unavailable
## SKILL TYPE-SPECIFIC LOGIC
### BASE SKILL (lib/skill.js)
**runSkillLogic():**
```javascript
async runSkillLogic()
{
// No implementation in base class
// Must be overridden
return true;
}
```
### ATTACK SKILL (lib/types/attack.js:69-143)
**runSkillLogic() Flow:**
```
1. Check if this.applyDirectDamage is true
→ If true: Skip calculations, apply hitDamage directly
→ If false: Continue to damage calculation
2. DODGE CHECK:
→ Calculate owner aim total (sum of aimProperties)
→ Calculate target dodge total (sum of dodgeProperties)
→ If dodgeFullEnabled:
→ Check if dodge > (aim * dodgeOverAimSuccess)
→ If true: Return {error: SKILL.DODGED}
3. DAMAGE CALCULATION:
→ Calculate attack total (sum of attackProperties)
→ Calculate defense total (sum of defenseProperties)
→ Base damage = hitDamage modified by attack-defense difference
→ If damageAffected:
→ Apply dodge/aim ratio to damage
→ Calculate critical damage (if not avoided)
→ If criticalAffected:
→ Apply dodge/aim ratio to critical
4. DAMAGE APPLICATION:
→ Fire SKILL_ATTACK_APPLY_DAMAGE event
→ Event params: (this, target, damage, newValue)
→ Apply damage to target.stats[affectedProperty]
→ If !allowEffectBelowZero:
→ Clamp value to 0 minimum
→ Update target property
5. Return result with damage details
```
**Events Fired:**
- `SkillsEvents.SKILL_ATTACK_APPLY_DAMAGE`
**Return Format:**
```javascript
{
damages: {
[affectedProperty]: finalDamageValue
},
[affectedProperty]: target.stats[affectedProperty]
}
```
**Critical Calculation:**
Attack skills apply critical to the damage value BEFORE adding to current:
```javascript
// lib/types/attack.js:101
damage = damage + this.getCriticalDiff(damage);
```
### EFFECT SKILL (lib/types/effect.js:46-68)
**runSkillLogic() Flow:**
```
1. Check if target is defined
→ If not: Return {error: SKILL.TARGET_NOT_AVAILABLE}
2. Validate range (if rangeAutomaticValidation)
→ Check this.owner.isInRange(this, this.target, this.range)
→ If out of range: Return {error: SKILL.OUT_OF_RANGE}
3. Apply target effects:
→ Call applyModifiers(this.targetEffects, this.target)
→ Note: avoidCritical = false (crits can apply)
4. Fire SKILL_EFFECT_TARGET_MODIFIERS event
→ Event params: (this, this.target)
5. Return success result
```
**Events Fired:**
- `SkillsEvents.SKILL_EFFECT_TARGET_MODIFIERS`
**Return Format:**
```javascript
{
error: false
}
```
**Critical Calculation:**
Effect skills apply critical to modifier value BEFORE adding to current:
```javascript
// lib/skill.js:306-308
let newValue = modifier.getModifiedValue();
if(!avoidCritical){
newValue = newValue + this.getCriticalDiff(modifier.value);
}
```
### PHYSICAL ATTACK SKILL (lib/types/physical-attack.js:58-115)
**runSkillLogic() Flow:**
```
1. Validate physical properties (width, height, magnitude)
→ If invalid: Return {error: SKILL.PHYSICAL_SKILL_INVALID_TARGET}
2. Check if owner implements executePhysicalSkill()
→ If not: Return {error: SKILL.TARGET_NOT_AVAILABLE}
3. Set this.lastState = SKILL.PHYSICAL_SKILL_RUN_LOGIC
4. Define executeOnHit callback:
→ Validate target matches (if validateTargetOnHit)
→ Fire SKILL_PHYSICAL_ATTACK_HIT event
→ Event params: (this, target)
→ Call parent runSkillLogic() (Attack damage logic)
→ Return damage result
5. Call owner.executePhysicalSkill(this.target, this, executeOnHit)
→ Owner's physics system handles collision detection
→ When collision occurs, executeOnHit is called
6. Return {executed: true}
```
**Events Fired:**
- `SkillsEvents.SKILL_PHYSICAL_ATTACK_HIT` (on collision)
- `SkillsEvents.SKILL_ATTACK_APPLY_DAMAGE` (from parent logic)
**Owner Requirements:**
Owner must implement:
```javascript
async executePhysicalSkill(target, skill, executeOnHit)
{
// Create physics body with skill.objectWidth, skill.objectHeight
// Apply force with skill.magnitude
// On collision, call executeOnHit(collidedTarget)
}
```
**Return Format:**
```javascript
{
executed: true
}
```
### PHYSICAL EFFECT SKILL (lib/types/physical-effect.js:58-113)
**runSkillLogic() Flow:**
```
1. Validate physical properties (width, height, magnitude)
→ If invalid: Return {error: SKILL.PHYSICAL_SKILL_INVALID_TARGET}
2. Check if owner implements executePhysicalSkill()
→ If not: Return {error: SKILL.TARGET_NOT_AVAILABLE}
3. Set this.lastState = SKILL.PHYSICAL_SKILL_RUN_LOGIC
4. Define executeOnHit callback:
→ Validate target matches (if validateTargetOnHit)
→ Fire SKILL_PHYSICAL_EFFECT_HIT event
→ Event params: (this, target)
→ Call parent runSkillLogic() (Effect modifier logic)
→ Return effect result
5. Call owner.executePhysicalSkill(this.target, this, executeOnHit)
→ Owner's physics system handles collision detection
→ When collision occurs, executeOnHit is called
6. Return {executed: true}
```
**Events Fired:**
- `SkillsEvents.SKILL_PHYSICAL_EFFECT_HIT` (on collision)
- `SkillsEvents.SKILL_EFFECT_TARGET_MODIFIERS` (from parent logic)
**Owner Requirements:**
Same as PhysicalAttack - must implement executePhysicalSkill()
**Return Format:**
```javascript
{
executed: true
}
```
## MODIFIER APPLICATION FLOW
### applyModifiers() Method
**File:** `lib/skill.js:293-313`
```
1. Initialize this.lastAppliedModifiers = {}
2. For each modifier in modifiersObjectList:
→ Set modifier.target = target
→ Call modifier.getModifiedValue()
→ This uses @reldens/modifiers Calculator
→ Returns currentValue + modifierValue
→ If !avoidCritical:
→ Calculate critical bonus: getCriticalDiff(modifier.value)
→ Add critical bonus to newValue
→ Apply newValue to target property
→ Store in lastAppliedModifiers
```
**Critical Calculation:**
```javascript
// Critical applies ONLY to modifier.value, not to currentValue
getCriticalDiff(value)
{
let criticalValue = this.applyCriticalValue(value);
return criticalValue - value; // Returns the bonus amount
}
// Example:
// currentValue = 80, modifier.value = 10, critical = 2x
// getModifiedValue() returns 90 (80 + 10)
// getCriticalDiff(10) returns 10 (20 - 10)
// Final: 90 + 10 = 100
```
**Important:**
- Owner effects ALWAYS use avoidCritical = true
- Target effects use avoidCritical = false (can crit)
## COMPLETE SEQUENCE DIAGRAM
```
┌──────────────────────────────────────────────────────────────┐
│ skill.execute(target) CALLED │
└────────────────────┬─────────────────────────────────────────┘
│
▼
╔════════════════════════╗
║ PHASE 1: PRE-VALIDATION ║
╚════════════════════════╝
│
├─→ Check owner.isCasting
│ └─→ If true: Return {error: CAN_NOT_ACTIVATE}
│
├─→ Fire: SKILL_BEFORE_EXECUTE
│
▼
╔════════════════════════╗
║ PHASE 2: VALIDATION ║
╚════════════════════════╝
│
├─→ Fire: VALIDATE_BEFORE
│
├─→ Check ownerConditions
│ └─→ If fails: Fire VALIDATE_FAIL, Return false
│
├─→ Check canActivate (cooldown)
│ └─→ If false: Return false
│
├─→ Check range (if enabled)
│ ├─→ Fire: SKILL_BEFORE_IN_RANGE
│ ├─→ Fire: SKILL_AFTER_IN_RANGE
│ └─→ If out of range: Return false
│
├─→ Call onExecuteConditions()
│ └─→ If false: Return false
│
├─→ Set canActivate = false
├─→ Start skillActivationTimer
├─→ Fire: VALIDATE_SUCCESS
│
▼
╔════════════════════════╗
║ PHASE 3: OWNER EFFECTS ║
╚════════════════════════╝
│
├─→ If ownerEffects exist:
│ ├─→ Fire: SKILL_APPLY_OWNER_EFFECTS
│ └─→ applyModifiers(ownerEffects, owner, true)
│
▼
╔════════════════════════╗
║ PHASE 4: CAST TIME ║
╚════════════════════════╝
│
├─→ If castTime > 0:
│ ├─→ Fire: SKILL_BEFORE_CAST
│ ├─→ Set owner.isCasting = true
│ ├─→ await sleep(castTime)
│ ├─→ Set owner.isCasting = false
│ └─→ Fire: SKILL_AFTER_CAST
│
▼
╔════════════════════════╗
║ PHASE 5: SKILL LOGIC ║
╚════════════════════════╝
│
├─→ Fire: SKILL_BEFORE_RUN_LOGIC
│
├─→ await runSkillLogic()
│ │
│ ├─→ [BASE SKILL]
│ │ └─→ (no implementation)
│ │
│ ├─→ [ATTACK SKILL]
│ │ ├─→ Check dodge
│ │ ├─→ Calculate damage
│ │ ├─→ Apply critical
│ │ ├─→ Fire: SKILL_ATTACK_APPLY_DAMAGE
│ │ └─→ Apply to affectedProperty
│ │
│ ├─→ [EFFECT SKILL]
│ │ ├─→ Validate range
│ │ ├─→ applyModifiers(targetEffects, target)
│ │ └─→ Fire: SKILL_EFFECT_TARGET_MODIFIERS
│ │
│ ├─→ [PHYSICAL ATTACK]
│ │ ├─→ Validate properties
│ │ ├─→ owner.executePhysicalSkill()
│ │ ├─→ On collision:
│ │ │ ├─→ Fire: SKILL_PHYSICAL_ATTACK_HIT
│ │ │ └─→ Call parent Attack logic
│ │ └─→ Return {executed: true}
│ │
│ └─→ [PHYSICAL EFFECT]
│ ├─→ Validate properties
│ ├─→ owner.executePhysicalSkill()
│ ├─→ On collision:
│ │ ├─→ Fire: SKILL_PHYSICAL_EFFECT_HIT
│ │ └─→ Call parent Effect logic
│ └─→ Return {executed: true}
│
├─→ Fire: SKILL_AFTER_RUN_LOGIC
│
▼
╔════════════════════════╗
║ PHASE 6: POST-EXECUTE ║
╚════════════════════════╝
│
├─→ Increment usesCount
├─→ Fire: SKILL_AFTER_EXECUTE
│
▼
┌─────────────────────┐
│ Return result │
└─────────────────────┘
```
## EVENT SEQUENCE SUMMARY
### Standard Skill Execution (No Cast Time):
1. `SKILL_BEFORE_EXECUTE`
2. `VALIDATE_BEFORE`
3. `SKILL_BEFORE_IN_RANGE` (if range check enabled)
4. `SKILL_AFTER_IN_RANGE` (if range check enabled)
5. `VALIDATE_SUCCESS`
6. `SKILL_APPLY_OWNER_EFFECTS` (if ownerEffects exist)
7. `SKILL_BEFORE_RUN_LOGIC`
8. [Type-specific events]
9. `SKILL_AFTER_RUN_LOGIC`
10. `SKILL_AFTER_EXECUTE`
### With Cast Time:
(Same as above, but insert after owner effects):
- `SKILL_BEFORE_CAST`
- (wait castTime ms)
- `SKILL_AFTER_CAST`
### Attack Skill Additional Event:
- `SKILL_ATTACK_APPLY_DAMAGE` (during runSkillLogic)
### Effect Skill Additional Event:
- `SKILL_EFFECT_TARGET_MODIFIERS` (during runSkillLogic)
### Physical Attack Additional Event:
- `SKILL_PHYSICAL_ATTACK_HIT` (on collision)
### Physical Effect Additional Event:
- `SKILL_PHYSICAL_EFFECT_HIT` (on collision)
## COOLDOWN SYSTEM
### Timer Setup (lib/skill.js:178-184)
```javascript
this.canActivate = false;
this.skillActivationTimer = setTimeout(() => {
this.canActivate = true;
}, this.skillDelay);
```
### How It Works:
1. When validation starts, `canActivate` is set to false
2. Timer is created for `skillDelay` milliseconds
3. When timer fires, `canActivate` is set back to true
4. If skill is executed while `canActivate = false`, validation fails
### Important:
- `skillDelay` is in milliseconds
- Default is 0 (no cooldown)
- Timer must complete before skill can be used again
- Multiple skill instances can have independent timers
## CRITICAL HIT SYSTEM
### Components:
1. **criticalChance** (0-1): Probability of critical hit
2. **criticalMultiplier** (default 2): Damage/effect multiplier
3. **criticalFixedValue** (default null): Fixed bonus instead of multiplier
### Calculation Methods:
**applyCriticalValue(value)** - Apply critical to a value:
```javascript
// lib/skill.js:315-328
applyCriticalValue(value)
{
if(!this.canCriticalHit()){
return value;
}
if(this.criticalFixedValue){
return value + this.criticalFixedValue;
}
return value * this.criticalMultiplier;
}
```
**getCriticalDiff(value)** - Get critical bonus amount:
```javascript
// lib/skill.js:330-334
getCriticalDiff(value)
{
let criticalValue = this.applyCriticalValue(value);
return criticalValue - value;
}
```
**canCriticalHit()** - Roll for critical:
```javascript
// lib/skill.js:336-343
canCriticalHit()
{
if(!this.criticalChance){
return false;
}
let criticalRoll = Math.random();
return criticalRoll <= this.criticalChance;
}
```
### Critical Application:
**Attack Skills:**
```javascript
// Applies to damage value
damage = damage + this.getCriticalDiff(damage);
```
**Effect Skills:**
```javascript
// Applies to modifier value
let newValue = modifier.getModifiedValue();
if(!avoidCritical){
newValue = newValue + this.getCriticalDiff(modifier.value);
}
```
### Critical Affected by Dodge/Aim (Attack only):
If `criticalAffected` is enabled:
```javascript
let criticalDamage = this.getCriticalDiff(damage);
if(this.criticalAffected && totalDodge && totalAim){
criticalDamage = criticalDamage * (totalAim / totalDodge);
}
damage = damage + criticalDamage;
```
## RANGE VALIDATION
### Components:
1. **range**: Maximum distance (0 = infinite)
2. **rangeAutomaticValidation**: Enable auto-check before execution
3. **owner.isInRange(skill, target, range)**: Distance check method
### Validation Points:
**During Skill Validation (if rangeAutomaticValidation):**
```javascript
// lib/skill.js:164-171
if(this.rangeAutomaticValidation){
await this.fireEvent(SkillsEvents.SKILL_BEFORE_IN_RANGE, this);
let interactionResult = this.owner.isInRange(this, this.target, this.range);
await this.fireEvent(SkillsEvents.SKILL_AFTER_IN_RANGE, this, interactionResult);
if(!interactionResult){
return false;
}
}
```
**During Effect Skill Logic:**
```javascript
// lib/types/effect.js:52-55
if(this.rangeAutomaticValidation && !this.owner.isInRange(this, this.target, this.range)){
return {error: SKILL.OUT_OF_RANGE};
}
```
### Owner Requirements:
Owner must implement:
```javascript
isInRange(skill, target, range)
{
if(range === 0){
return true; // Infinite range
}
let distance = this.getDistanceTo(target);
return distance <= range;
}
```
## TARGET VALIDATION
### Target Types:
1. **Fixed Target**: Set in skill constructor
2. **Dynamic Target**: Passed to execute(target)
### Validation:
**Allow Self Target:**
```javascript
// If allowSelfTarget = false and target === owner
// Validation should fail (implement in onExecuteConditions)
```
**Physical Skill Target Validation:**
```javascript
// lib/types/physical-attack.js:74-80
if(this.validateTargetOnHit && target['key'] !== this.target['key']){
return {
executed: true,
error: SKILL.PHYSICAL_SKILL_INVALID_TARGET
};
}
```
## USES LIMIT SYSTEM
### Properties:
1. **usesLimit**: Maximum uses (0 = unlimited)
2. **usesCount**: Current usage count
### Tracking:
```javascript
// lib/skill.js:138
this.usesCount++;
```
### Implementation Note:
The current implementation does NOT enforce usesLimit. You must implement this check manually:
```javascript
onExecuteConditions()
{
if(this.usesLimit > 0 && this.usesCount >= this.usesLimit){
return false; // Skill exhausted
}
return true;
}
```
## DEBUGGING SKILL EXECUTION
### Add Logging to Track Flow:
```javascript
async execute(target)
{
console.log('[EXECUTE] Starting skill:', this.key);
this.target = target;
if(this.owner.isCasting){
console.log('[EXECUTE] Owner is casting, aborting');
return {error: SKILL.CAN_NOT_ACTIVATE};
}
console.log('[EXECUTE] Firing BEFORE_EXECUTE event');
await this.fireEvent(SkillsEvents.SKILL_BEFORE_EXECUTE, this, target);
console.log('[EXECUTE] Starting validation');
if(!this.validate()){
console.log('[EXECUTE] Validation failed');
return false;
}
console.log('[EXECUTE] Validation passed');
// ... rest of execution
}
```
### Check Event Registration:
```javascript
listenEvent(eventName, callback, removeKey, masterKey) {
let fullName = this.eventFullName(eventName);
console.log('[LISTEN]', fullName, 'removeKey:', removeKey);
return this.events.onWithKey(fullName, callback, removeKey, masterKey);
}
```
### Track Critical Hits:
```javascript
canCriticalHit()
{
if(!this.criticalChance){
return false;
}
let criticalRoll = Math.random();
let isCrit = criticalRoll <= this.criticalChance;
console.log('[CRITICAL] Roll:', criticalRoll, 'Chance:', this.criticalChance, 'Result:', isCrit);
return isCrit;
}
```
## COMMON PITFALLS
### ❌ PITFALL 1: Not Awaiting execute()
**BAD:**
```javascript
skill.execute(target); // Missing await
console.log('Skill done'); // Runs immediately
```
**GOOD:**
```javascript
await skill.execute(target); // Wait for completion
console.log('Skill done'); // Runs after skill finishes
```
### ❌ PITFALL 2: Forgetting to Override runSkillLogic()
**BAD:**
```javascript
class MySkill extends Skill {
// No runSkillLogic implementation
}
// Skill does nothing when executed
```
**GOOD:**
```javascript
class MySkill extends Skill {
async runSkillLogic() {
// Implement behavior
return true;
}
}
```
### ❌ PITFALL 3: Applying Critical to Wrong Value
**BAD:**
```javascript
// Applying critical to (currentValue + modifierValue)
let newValue = modifier.getModifiedValue(); // 80 + 10 = 90
newValue = newValue * 2; // 90 * 2 = 180 ❌
```
**GOOD:**
```javascript
// Applying critical to modifierValue only
let newValue = modifier.getModifiedValue(); // 80 + 10 = 90
newValue = newValue + this.getCriticalDiff(modifier.value); // 90 + 10 = 100 ✓
```
### ❌ PITFALL 4: Checking Range Without Validation Flag
**BAD:**
```javascript
// Range checked but rangeAutomaticValidation = false
let skill = new Skill({
owner: owner,
target: target,
range: 100
// rangeAutomaticValidation not set (defaults to false)
});
await skill.execute(target); // Range NOT checked
```
**GOOD:**
```javascript
let skill = new Skill({
owner: owner,
target: target,
range: 100,
rangeAutomaticValidation: true // Enable auto range check
});
await skill.execute(target); // Range checked
```
### ❌ PITFALL 5: Physical Skills Without Owner Implementation
**BAD:**
```javascript
let skill = new PhysicalAttack({
owner: owner, // owner does NOT implement executePhysicalSkill
objectWidth: 10,
objectHeight: 10
});
await skill.execute(target); // Returns TARGET_NOT_AVAILABLE error
```
**GOOD:**
```javascript
owner.executePhysicalSkill = async function(target, skill, executeOnHit) {
// Implement physics logic
};
let skill = new PhysicalAttack({
owner: owner,
objectWidth: 10,
objectHeight: 10
});
await skill.execute(target); // Works correctly
```
## REFERENCES
- Base Skill: `lib/skill.js:12-348`
- Attack Logic: `lib/types/attack.js:69-143`
- Effect Logic: `lib/types/effect.js:46-68`
- Physical Attack: `lib/types/physical-attack.js:58-115`
- Physical Effect: `lib/types/physical-effect.js:58-113`
- Event Names: `lib/skills-events.js`
- Constants: `lib/constants.js`