156 lines
5.6 KiB
Markdown
156 lines
5.6 KiB
Markdown
---
|
|
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
|
|
|
|
|