# **Combat Skill Usage Specification** This document 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 (How to Code It)** ### **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(); } ```