5.6 KiB
5.6 KiB
| description | globs | alwaysApply |
|---|---|---|
| Combat skill usage workflow - selection, targeting, and execution of active skills | src/systems/SkillTargetingSystem.js, src/systems/SkillTargetingSystem.ts, src/core/GameLoop.js | false |
Combat Skill Usage Rule
This rule defines the workflow for selecting, targeting, and executing Active Skills during the Combat Phase.
1. The Interaction Flow
The process follows a strict 3-step sequence:
- Selection (UI): Player clicks a skill button in the HUD
- Targeting (Grid): Game enters
TARGETING_MODE. Player moves cursor to select a target. Valid targets are highlighted - Execution (Engine): Player confirms selection. Costs are paid, and effects are applied
2. State Machine Updates
We need to expand the CombatState in GameLoop to handle targeting.
| State | Description | Input Behavior |
|---|---|---|
| IDLE / SELECTING_MOVE | Standard state. Cursor highlights movement range. | Click Unit = Select. Click Empty = Move. |
| TARGETING_SKILL | Player has selected a skill. Cursor highlights Skill Range. | Hover: Update AoE Reticle. Click: Execute Skill. Cancel (B/Esc): Return to IDLE. |
| EXECUTING_SKILL | Animation playing. Input locked. | None. |
3. The Skill Targeting System
We need a helper system (src/systems/SkillTargetingSystem.js) to handle the complex math of "Can I hit this?" without cluttering the GameLoop.
Core Logic: isValidTarget(source, targetTile, skillDef)
- Range Check: Manhattan distance between Source and Target <=
skill.range - Line of Sight (LOS): Raycast from Source head height to Target center. Must not hit
isSolidvoxels (unless skill hasignore_cover) - Content Check:
- If
target_typeis ENEMY: Tile must contain a unit ANDunit.team != source.team - If
target_typeis ALLY: Tile must contain a unit ANDunit.team == source.team - If
target_typeis EMPTY: Tile must be empty
- If
Visual Logic: getAffectedTiles(targetTile, skillDef)
Calculates the Area of Effect (AoE) to highlight in Red.
- SINGLE: Just the target tile
- CIRCLE (Radius R): All tiles within distance R of target
- LINE (Length L): Raycast L tiles in the cardinal direction from Source to Target
- CONE: A triangle pattern originating from Source
4. Integration Steps
Step 1: Create SkillTargetingSystem
This class encapsulates the math.
class SkillTargetingSystem {
constructor(grid, unitManager) { ... }
/** Returns { valid: boolean, reason: string } */
validateTarget(sourceUnit, targetPos, skillId) { ... }
/** Returns array of {x,y,z} for highlighting */
getAoETiles(sourcePos, cursorPos, skillId) { ... }
}
Step 2: Update GameLoop State
Add activeSkillId to track which skill is pending.
// In GameLoop.js
// 1. Handle UI Event
onSkillClicked(skillId) {
// Validate unit has AP
if (this.unit.currentAP < getSkillCost(skillId)) return;
this.combatState = 'TARGETING_SKILL';
this.activeSkillId = skillId;
// VISUALS: Clear Movement Blue Grid -> Show Attack Red Grid (Range)
const skill = this.unit.skills.get(skillId);
this.voxelManager.highlightRange(this.unit.pos, skill.range, 'RED_OUTLINE');
}
// 2. Handle Cursor Hover (InputManager event)
onCursorHover(pos) {
if (this.combatState === 'TARGETING_SKILL') {
const aoeTiles = this.targetingSystem.getAoETiles(this.unit.pos, pos, this.activeSkillId);
this.voxelManager.showReticle(aoeTiles); // Solid Red Highlight
}
}
Step 3: Execution Logic
When the player confirms the click.
// In GameLoop.js -> triggerSelection()
if (this.combatState === 'TARGETING_SKILL') {
const valid = this.targetingSystem.validateTarget(this.unit, cursor, this.activeSkillId);
if (valid) {
this.executeSkill(this.activeSkillId, cursor);
} else {
// Audio: Error Buzz
console.log("Invalid Target");
}
}
executeSkill(skillId, targetPos) {
this.combatState = 'EXECUTING_SKILL';
// 1. Deduct Costs (AP, Cooldown) via SkillManager
this.unit.skillManager.payCosts(skillId);
// 2. Get Targets (Units in AoE)
const targets = this.targetingSystem.getUnitsInAoE(targetPos, skillId);
// 3. Process Effects (Damage, Status) via EffectProcessor
const skillDef = this.registry.get(skillId);
skillDef.effects.forEach(eff => {
targets.forEach(t => this.effectProcessor.process(eff, this.unit, t));
});
// 4. Cleanup
this.combatState = 'IDLE';
this.activeSkillId = null;
this.voxelManager.clearHighlights();
}
5. Conditions of Acceptance (CoA)
CoA 1: Range Validation
- A skill with
range: 3must reject targets beyond 3 tiles (Manhattan distance)
CoA 2: Line of Sight
- A skill targeting an enemy behind a wall must fail the LOS check
CoA 3: Cost Payment
- Executing a skill must deduct AP and increment cooldown before effects are applied
CoA 4: State Cleanup
- After skill execution, the game must return to
IDLEstate and clear all targeting highlights