aether-shards/.cursor/rules/logic/CombatSkillUsage/RULE.md
Matthew Mone 2c86d674f4 Add mission debrief and procedural mission generation features
- Introduce the MissionDebrief component to display after-action reports, including XP, rewards, and squad status.
- Implement the MissionGenerator class to create procedural side missions, enhancing replayability and resource management.
- Update mission schema to include mission objects for INTERACT objectives, improving mission complexity.
- Enhance GameLoop and MissionManager to support new mission features and interactions.
- Add tests for MissionDebrief and MissionGenerator to ensure functionality and integration within the game architecture.
2026-01-01 16:08:54 -08:00

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:

  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.

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: 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