--- description: Combat skill usage workflow - selection, targeting, and execution of active skills globs: src/systems/SkillTargetingSystem.js, src/systems/SkillTargetingSystem.ts, src/core/GameLoop.js alwaysApply: 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: 1. **Selection (UI):** Player clicks a skill button in the HUD 2. **Targeting (Grid):** Game enters `TARGETING_MODE`. Player moves cursor to select a target. Valid targets are highlighted 3. **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)** 1. **Range Check:** Manhattan distance between Source and Target <= `skill.range` 2. **Line of Sight (LOS):** Raycast from Source head height to Target center. Must not hit `isSolid` voxels (unless skill has `ignore_cover`) 3. **Content Check:** - If `target_type` is **ENEMY**: Tile must contain a unit AND `unit.team != source.team` - If `target_type` is **ALLY**: Tile must contain a unit AND `unit.team == source.team` - If `target_type` is **EMPTY**: Tile must be empty ### **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. ```js 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. ```js // 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. ```js // 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: 3` must 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 `IDLE` state and clear all targeting highlights