Files
BreakEscape/planning_notes/npc/hostile/enhanced_combat_feedback.md
Z. Cliffe Schreuders 660105421a Add Hostile NPC mode
Fix NPC interaction and event handling issues

- Added a visual problem-solution summary for debugging NPC event handling.
- Resolved cooldown bug in NPCManager by implementing explicit null/undefined checks.
- Modified PersonChatMinigame to prioritize event parameters over state restoration.
- Updated security guard dialogue in Ink scenarios to improve interaction flow.
- Adjusted vault key parameters in npc-patrol-lockpick.json for consistency.
- Changed inventory stylesheet references to hud.css in test HTML files for better organization.

feat(combat): Integrate chair kicking with punch mechanic

Update chair interaction to use the punch system instead of direct kicking:

**Changes to interactions.js:**
- Modified swivel chair interaction to trigger player punch instead of
  directly applying kick velocity
- Simplified chair interaction handler to just call playerCombat.punch()

**Changes to player-combat.js:**
- Extended checkForHits() to detect chairs in punch range and direction
- Added kickChair() method that applies the same velocity calculation:
  - Calculates direction from player to chair
  - Applies 1200 px/s kick force in that direction
  - Triggers spin direction calculation for visual rotation
  - Adds visual feedback (flash chair, light screen shake)
- Chairs now respond to punch AOE damage like hostile NPCs

Now clicking a chair or pressing 'E' near it triggers a punch, and if the
chair is in punch range and facing direction, it gets kicked with the
original velocity physics. Multiple chairs can be kicked with one punch.

feat(combat): Implement hostile NPC behavior and final integration (Phase 6-7)

Complete hostile NPC combat system with chase behavior and integration:

**Phase 6: Hostile NPC Behavior**
- Modified npc-behavior.js determineState() to check hostile state from npcHostileSystem
- Implemented updateHostileBehavior() with chase and attack logic:
  - NPCs chase player when hostile and in aggro range
  - NPCs stop and attack when in attack range
  - NPCs use directional movement with proper animations
  - Integration with npcCombat system for attack attempts
- Added KO state check to prevent KO'd NPCs from acting

**Phase 7: Final Integration**
- Modified player.js to disable movement when player is KO
- Added visual KO effect (50% alpha) to NPC sprites in npc-hostile.js
- Connected all combat systems end-to-end:
  - Ink dialogue → hostile tag → hostile state → chase behavior → combat
  - Player interaction → punch → NPC damage → KO → visual feedback
  - NPC chase → attack → player damage → HP UI → game over

Full combat loop now functional: hostile NPCs chase and attack player,
player can punch hostile NPCs, complete visual/audio feedback, game over on KO.

feat(combat): Add feedback, UI, and combat mechanics (Phase 2-5)

Implement comprehensive combat feedback, UI, and mechanics:

**Phase 2: Enhanced Feedback Systems**
- damage-numbers.js: Floating damage numbers with object pooling
- screen-effects.js: Screen flash and shake for combat feedback
- sprite-effects.js: Sprite tinting, flashing, and visual effects
- attack-telegraph.js: Visual indicators for incoming NPC attacks

**Phase 3: UI Components**
- health-ui.js: Player health display as hearts (5 hearts, shows when damaged)
- npc-health-bars.js: Health bars above hostile NPCs with color coding
- game-over-screen.js: KO screen with restart/main menu options

**Phase 4-5: Combat Mechanics**
- player-combat.js: Player punch system with AOE directional damage
- npc-combat.js: NPC attack system with telegraph and cooldowns
- Modified interactions.js to trigger punch on hostile NPC interaction
- Integrated all systems into game.js create() and update() loops

Combat now functional with complete visual/audio feedback pipeline.
Player can punch hostile NPCs, NPCs can attack player, health tracking works.

feat(combat): Add hostile NPC system foundation (Phase 0-1)

Implement core hostile NPC combat system infrastructure:

- Add #hostile tag handler to chat-helpers.js for Ink integration
- Fix security-guard.ink to use proper hub pattern with -> hub instead of -> END
- Add #hostile:security_guard tags to hostile conversation paths
- Create combat configuration system (combat-config.js)
- Create combat event constants (combat-events.js)
- Implement player health tracking system with HP and KO state
- Implement NPC hostile state management with HP tracking
- Add combat debug utilities for testing
- Add error handling utilities for validation
- Integrate combat systems into game.js create() method
- Create test-hostile.ink for testing hostile tag system

This establishes the foundation for hostile NPC behavior, allowing NPCs to
become hostile through Ink dialogue and tracking health for both player and NPCs.

docs(npc): Apply codebase-verified corrections to hostile NPC plans

Apply critical corrections based on actual codebase verification:

CORRECTIONS.md (Updated):
-  Confirms #exit_conversation tag ALREADY IMPLEMENTED
  * Location: person-chat-minigame.js line 537
  * No handler needed in chat-helpers.js
-  Hostile tag still needs implementation in chat-helpers.js
- Provides exact code for hostile tag handler
- Clarifies tag format: #hostile:npcId or #hostile (uses current NPC)
- Updated action items to reflect what's already working

INTEGRATION_UPDATES.md (New):
- Comprehensive correction document
- Issue 1 Corrected: Exit conversation already works
- Issue 6 Corrected: Punch mechanics are interaction-based with AOE
- Details interaction-based punch targeting:
  * Player clicks hostile NPC OR presses 'E' nearby
  * Punch animation plays in facing direction
  * Damage applies to ALL NPCs in range + direction (AOE)
  * Can hit multiple enemies if grouped (strategic gameplay)
- Provides complete implementation examples
- Removes complexity of target selection systems
- Uses existing interaction patterns

quick_start.md (Updated):
- Removed exit_conversation handler (already exists)
- Updated hostile tag handler code
- Added punch mechanics design section
- Clarified interaction-based targeting
- Added troubleshooting for exit_conversation

Key Findings:
 Exit conversation tag works out of the box
 Punch targeting uses existing interaction system (simpler!)
 AOE punch adds strategic depth without complexity
 Only ONE critical task remains: Add hostile tag to chat-helpers.js

Impact:
- Less work required (don't need exit_conversation handler)
- Simpler implementation (use existing interaction patterns)
- Better gameplay (AOE punches, directional attacks)
- Clear path forward with exact code examples

docs(npc): Add critical corrections and codebase integration review

Add comprehensive review of hostile NPC plans against actual codebase:

CORRECTIONS.md:
- Identifies critical Ink pattern error (-> END vs -> hub)
- Documents correct hub-based conversation pattern
- Provides corrected examples for all Ink files
- Explains why -> hub is required after #exit_conversation

FORMAT_REVIEW.md:
- Validates JSON scenario format against existing scenarios
- Reviews NPC object structure and required fields
- Documents correct Ink hub pattern from helper-npc.ink
- Proposes hostile configuration object for NPC customization
- Provides complete format reference and checklists

review2/integration_review.md:
- Comprehensive codebase analysis by Explore agent
- Identifies 2 critical blockers requiring immediate attention:
  * Missing tag handlers for #hostile and #exit_conversation
  * Incorrect Ink pattern (-> END) in planning documents
- Documents 4 important integration differences:
  * Initialization in game.js not main.js
  * Event dispatcher already exists (window.eventDispatcher)
  * Room transition behavior needs design decision
  * Multi-hostile NPC targeting needs design decision
- Confirms 8 systems are fully compatible with plan
- Provides existing code patterns to follow
- Corrects integration sequence

review2/quick_start.md:
- Step-by-step guide for Phase 0-1 implementation
- Includes complete code examples for critical systems
- Browser console test procedures
- Common issues and solutions
- Success criteria checklist

Key Findings:
 90% compatible with existing codebase
 Must add tag handlers to chat-helpers.js before implementation
 Must fix all Ink examples to use -> hub not -> END
⚠️ Should follow game.js initialization pattern not main.js
⚠️ Should use existing window.eventDispatcher
⚠️ Need design decisions on room transitions and multi-targeting

All critical issues documented with solutions ready.
Implementation can proceed with high confidence after corrections applied.

docs(npc): Add comprehensive planning documents for hostile NPC system

Add detailed implementation plans for hostile NPC feature including:
- Complete implementation plan with phase-by-phase breakdown
- Architecture overview with system diagrams and data flows
- Detailed TODO list with 200+ actionable tasks
- Phase 0 foundation with design decisions and base components
- Enhanced combat feedback implementation guide
- Implementation roadmap with 6-day schedule

Add comprehensive review documents:
- Implementation review with risk assessment and recommendations
- Technical review analyzing code patterns and best practices
- UX review covering player experience and game feel

Key features planned:
- NPC hostile state triggered via Ink tags
- Player health system with heart-based UI
- NPC health bars and combat mechanics
- Punch combat for both player and NPCs
- Strong visual/audio feedback for combat
- Game over system and KO states
- Attack telegraphing for fairness
- Enhanced NPC chase behavior with LOS
- Debug utilities and error handling
- Comprehensive testing strategy
2025-11-14 19:47:54 +00:00

17 KiB

Enhanced Combat Feedback Implementation

Overview

This document details the implementation of strong visual and audio feedback for combat actions, addressing the primary UX concern of clarity and responsiveness.

Visual Feedback System

1. Damage Numbers

File: /js/systems/damage-numbers.js (NEW)

Floating damage numbers that appear when entities take damage:

import { COMBAT_CONFIG } from '../config/combat-config.js';

class DamageNumberPool {
  constructor(scene, poolSize = 20) {
    this.scene = scene;
    this.pool = [];
    this.active = [];

    // Pre-create pool
    for (let i = 0; i < poolSize; i++) {
      this.pool.push(this.createDamageNumber());
    }
  }

  createDamageNumber() {
    const text = this.scene.add.text(0, 0, '', {
      fontSize: '24px',
      fontFamily: 'Arial Black, Arial',
      color: '#ffffff',
      stroke: '#000000',
      strokeThickness: 4
    });
    text.setVisible(false);
    text.setDepth(1000); // Above everything
    return text;
  }

  show(x, y, damage, isCritical = false, isMiss = false) {
    // Get from pool or create new
    let text = this.pool.pop() || this.createDamageNumber();

    if (isMiss) {
      // Miss display
      text.setText('MISS');
      text.setColor('#888888');
      text.setScale(1);
    } else {
      // Damage number
      text.setText(`-${Math.floor(damage)}`);
      text.setColor(isCritical ? '#ff0000' : '#ffffff');
      text.setScale(isCritical ? 1.5 : 1);
    }

    text.setPosition(x - text.width / 2, y);
    text.setVisible(true);
    text.setAlpha(1);

    this.active.push(text);

    // Animate up and fade
    this.scene.tweens.add({
      targets: text,
      y: y - COMBAT_CONFIG.ui.damageNumberRise,
      alpha: 0,
      duration: COMBAT_CONFIG.ui.damageNumberDuration,
      ease: 'Cubic.easeOut',
      onComplete: () => {
        this.recycle(text);
      }
    });
  }

  recycle(text) {
    text.setVisible(false);
    const index = this.active.indexOf(text);
    if (index > -1) {
      this.active.splice(index, 1);
    }
    if (this.pool.length < 20) { // Max pool size
      this.pool.push(text);
    } else {
      text.destroy(); // Pool full, destroy excess
    }
  }

  destroy() {
    [...this.pool, ...this.active].forEach(text => text.destroy());
    this.pool = [];
    this.active = [];
  }
}

// Initialize
export function initDamageNumbers(scene) {
  const pool = new DamageNumberPool(scene);

  // Add to window for global access
  window.damageNumbers = {
    show: (x, y, damage, isCritical, isMiss) => {
      pool.show(x, y, damage, isCritical, isMiss);
    },
    destroy: () => pool.destroy()
  };

  return pool;
}

Usage:

// When damage applied
window.damageNumbers?.show(npc.sprite.x, npc.sprite.y, 20, false, false);

// When attack misses
window.damageNumbers?.show(npc.sprite.x, npc.sprite.y, 0, false, true);

2. Screen Flash Effect

File: /js/systems/screen-effects.js (NEW)

Screen flash for player damage feedback:

export function initScreenEffects(scene) {
  // Create overlay for flashes
  const overlay = scene.add.rectangle(
    0, 0,
    scene.cameras.main.width,
    scene.cameras.main.height,
    0xff0000, // Red
    0 // Initially invisible
  );
  overlay.setOrigin(0, 0);
  overlay.setDepth(999); // Below damage numbers, above game
  overlay.setScrollFactor(0); // Fixed to camera

  window.screenEffects = {
    flashDamage() {
      if (!COMBAT_CONFIG.feedback.enableScreenFlash) return;

      overlay.setAlpha(0.3);
      scene.tweens.add({
        targets: overlay,
        alpha: 0,
        duration: COMBAT_CONFIG.ui.screenFlashDuration,
        ease: 'Cubic.easeOut'
      });
    },

    flashHeal() {
      overlay.fillColor = 0x00ff00; // Green
      overlay.setAlpha(0.2);
      scene.tweens.add({
        targets: overlay,
        alpha: 0,
        duration: 300,
        ease: 'Cubic.easeOut',
        onComplete: () => {
          overlay.fillColor = 0xff0000; // Back to red
        }
      });
    },

    flashWarning() {
      overlay.fillColor = 0xffaa00; // Orange
      overlay.setAlpha(0.2);
      scene.tweens.add({
        targets: overlay,
        alpha: 0,
        duration: 200,
        ease: 'Cubic.easeOut',
        onComplete: () => {
          overlay.fillColor = 0xff0000; // Back to red
        }
      });
    }
  };

  return window.screenEffects;
}

Usage:

// When player takes damage
window.screenEffects?.flashDamage();

// When player heals
window.screenEffects?.flashHeal();

// When hostile NPC attacks (wind-up)
window.screenEffects?.flashWarning();

3. Screen Shake Effect

File: /js/systems/screen-effects.js (ADD TO ABOVE)

Add to the same file:

// Add to window.screenEffects object
window.screenEffects.shake = function(intensity = null) {
  if (!COMBAT_CONFIG.feedback.enableScreenShake) return;

  const shakeAmount = intensity || COMBAT_CONFIG.ui.screenShakeIntensity;

  scene.cameras.main.shake(100, shakeAmount / 1000); // Duration ms, intensity 0-1
};

// Shake with different intensities
window.screenEffects.shakeLight = function() {
  this.shake(2);
};

window.screenEffects.shakeMedium = function() {
  this.shake(4);
};

window.screenEffects.shakeHeavy = function() {
  this.shake(6);
};

Usage:

// Light damage
window.screenEffects?.shakeLight();

// Medium damage
window.screenEffects?.shakeMedium();

// Heavy damage / KO
window.screenEffects?.shakeHeavy();

4. Sprite Flash Effects

File: /js/systems/sprite-effects.js (NEW)

Reusable sprite visual effects:

export function flashSprite(sprite, color = 0xffffff, duration = 100) {
  if (!sprite) return;

  const originalTint = sprite.tintTopLeft;

  sprite.setTint(color);

  sprite.scene.time.delayedCall(duration, () => {
    sprite.clearTint();
    if (originalTint !== 0xffffff) {
      sprite.setTint(originalTint);
    }
  });
}

export function flashSpriteRepeat(sprite, color = 0xff0000, times = 3, duration = 100) {
  if (!sprite) return;

  let count = 0;
  const interval = sprite.scene.time.addEvent({
    delay: duration * 2,
    callback: () => {
      flashSprite(sprite, color, duration);
      count++;
      if (count >= times) {
        interval.destroy();
      }
    },
    repeat: times - 1
  });
}

export function shakeSprite(sprite, intensity = 5, duration = 100) {
  if (!sprite) return;

  const originalX = sprite.x;
  const originalY = sprite.y;

  sprite.scene.tweens.add({
    targets: sprite,
    x: originalX + Phaser.Math.Between(-intensity, intensity),
    y: originalY + Phaser.Math.Between(-intensity, intensity),
    duration: duration / 4,
    yoyo: true,
    repeat: 3,
    onComplete: () => {
      sprite.setPosition(originalX, originalY);
    }
  });
}

Usage:

import { flashSprite, shakeSprite } from './sprite-effects.js';

// When NPC hit
flashSprite(npc.sprite, 0xffffff, 100);

// When player hit
flashSprite(window.player, 0xff0000, 300);

// When NPC KO'd
flashSpriteRepeat(npc.sprite, 0x666666, 3, 150);

5. Attack Telegraph Visuals

File: /js/systems/attack-telegraph.js (NEW)

Visual indicators for NPC attacks:

export class AttackTelegraph {
  constructor(scene, npc) {
    this.scene = scene;
    this.npc = npc;

    // Create exclamation mark
    this.icon = scene.add.text(0, 0, '!', {
      fontSize: '32px',
      fontFamily: 'Arial Black',
      color: '#ff0000',
      stroke: '#ffffff',
      strokeThickness: 3
    });
    this.icon.setOrigin(0.5, 1);
    this.icon.setVisible(false);
    this.icon.setDepth(100);

    // Create attack range indicator
    this.rangeCircle = scene.add.circle(0, 0, 50, 0xff0000, 0.2);
    this.rangeCircle.setStrokeStyle(2, 0xff0000, 0.8);
    this.rangeCircle.setVisible(false);
    this.rangeCircle.setDepth(1);
  }

  show() {
    this.updatePosition();
    this.icon.setVisible(true);
    this.rangeCircle.setVisible(true);

    // Pulse animation
    this.scene.tweens.add({
      targets: this.icon,
      scaleX: 1.2,
      scaleY: 1.2,
      duration: 200,
      yoyo: true,
      repeat: -1 // Infinite while showing
    });

    // Range circle expand
    this.scene.tweens.add({
      targets: this.rangeCircle,
      scaleX: 1.2,
      scaleY: 1.2,
      alpha: 0.4,
      duration: 250,
      yoyo: true,
      repeat: -1
    });
  }

  hide() {
    this.icon.setVisible(false);
    this.rangeCircle.setVisible(false);
    this.scene.tweens.killTweensOf(this.icon);
    this.scene.tweens.killTweensOf(this.rangeCircle);
    this.icon.setScale(1);
    this.rangeCircle.setScale(1);
  }

  updatePosition() {
    if (this.npc.sprite) {
      const x = this.npc.sprite.x;
      const y = this.npc.sprite.y - 50; // Above NPC

      this.icon.setPosition(x, y);
      this.rangeCircle.setPosition(this.npc.sprite.x, this.npc.sprite.y);
    }
  }

  destroy() {
    this.icon.destroy();
    this.rangeCircle.destroy();
  }
}

// Create for NPC
export function createAttackTelegraph(scene, npc) {
  return new AttackTelegraph(scene, npc);
}

Usage:

// In NPC hostile state initialization
npc.attackTelegraph = createAttackTelegraph(scene, npc);

// When NPC begins attack wind-up
npc.attackTelegraph.show();

// During wind-up, update position
npc.attackTelegraph.updatePosition();

// When attack executes or cancelled
npc.attackTelegraph.hide();

Audio Feedback System

6. Sound Effect Manager

File: /js/systems/combat-sounds.js (NEW)

Manage combat sound effects:

export class CombatSounds {
  constructor(scene) {
    this.scene = scene;
    this.enabled = COMBAT_CONFIG.feedback.enableSounds;

    // Preload sound effects (assuming they're loaded in scene preload)
    this.sounds = {
      playerPunch: null,
      npcPunch: null,
      hit: null,
      miss: null,
      playerHurt: null,
      playerKO: null,
      npcKO: null,
      warning: null
    };
  }

  init() {
    // Load or reference sounds
    // Assuming sounds are already loaded in scene
    try {
      this.sounds.playerPunch = this.scene.sound.add('punch');
      this.sounds.npcPunch = this.scene.sound.add('punch');
      this.sounds.hit = this.scene.sound.add('hit');
      this.sounds.miss = this.scene.sound.add('whoosh');
      this.sounds.playerHurt = this.scene.sound.add('hurt');
      this.sounds.playerKO = this.scene.sound.add('ko');
      this.sounds.npcKO = this.scene.sound.add('ko');
      this.sounds.warning = this.scene.sound.add('warning');
    } catch (e) {
      console.warn('Some combat sounds not loaded:', e);
    }
  }

  playPlayerPunch() {
    if (this.enabled && this.sounds.playerPunch) {
      this.sounds.playerPunch.play({ volume: 0.5 });
    }
  }

  playNPCPunch() {
    if (this.enabled && this.sounds.npcPunch) {
      this.sounds.npcPunch.play({ volume: 0.5 });
    }
  }

  playHit() {
    if (this.enabled && this.sounds.hit) {
      this.sounds.hit.play({ volume: 0.6 });
    }
  }

  playMiss() {
    if (this.enabled && this.sounds.miss) {
      this.sounds.miss.play({ volume: 0.3 });
    }
  }

  playPlayerHurt() {
    if (this.enabled && this.sounds.playerHurt) {
      this.sounds.playerHurt.play({ volume: 0.7 });
    }
  }

  playPlayerKO() {
    if (this.enabled && this.sounds.playerKO) {
      this.sounds.playerKO.play({ volume: 0.8 });
    }
  }

  playNPCKO() {
    if (this.enabled && this.sounds.npcKO) {
      this.sounds.npcKO.play({ volume: 0.6 });
    }
  }

  playWarning() {
    if (this.enabled && this.sounds.warning) {
      this.sounds.warning.play({ volume: 0.5 });
    }
  }

  setEnabled(enabled) {
    this.enabled = enabled;
  }
}

export function initCombatSounds(scene) {
  const sounds = new CombatSounds(scene);
  sounds.init();

  window.combatSounds = sounds;

  return sounds;
}

Sound Asset Loading (in scene preload):

// Add to scene preload() method
preload() {
  // Placeholder sounds (replace with actual assets)
  // You can use free sound effects or generate placeholder audio
  this.load.audio('punch', 'assets/sounds/punch.mp3');
  this.load.audio('hit', 'assets/sounds/hit.mp3');
  this.load.audio('whoosh', 'assets/sounds/whoosh.mp3');
  this.load.audio('hurt', 'assets/sounds/hurt.mp3');
  this.load.audio('ko', 'assets/sounds/ko.mp3');
  this.load.audio('warning', 'assets/sounds/warning.mp3');
}

Note: For MVP, sound effects can be skipped or use placeholder sounds. The system is built to gracefully handle missing sounds.


Integration into Combat Systems

7. Enhanced Player Combat

Update: /js/systems/player-combat.js

Add feedback to player punching:

export async function playerPunch(targetNPC) {
  if (!canPlayerPunch()) return;

  // Play punch sound
  window.combatSounds?.playPlayerPunch();

  // Get direction
  const direction = getPlayerFacingDirection();

  // Play punch animation
  await playPlayerPunchAnimation(scene, player, direction);

  // Check if NPC still in range
  const distance = Phaser.Math.Distance.Between(
    window.player.x, window.player.y,
    targetNPC.sprite.x, targetNPC.sprite.y
  );

  if (distance <= COMBAT_CONFIG.player.punchRange) {
    // HIT
    const damage = COMBAT_CONFIG.player.punchDamage;

    window.combatSounds?.playHit();
    window.npcHostileSystem.damageNPC(targetNPC.id, damage);

    // Visual feedback
    flashSprite(targetNPC.sprite, 0xffffff, 100);
    shakeSprite(targetNPC.sprite, 5, 100);
    window.damageNumbers?.show(
      targetNPC.sprite.x,
      targetNPC.sprite.y - 20,
      damage,
      false,
      false
    );
  } else {
    // MISS
    window.combatSounds?.playMiss();
    window.damageNumbers?.show(
      targetNPC.sprite.x,
      targetNPC.sprite.y - 20,
      0,
      false,
      true
    );
  }

  // Start cooldown
  startPunchCooldown();
}

8. Enhanced NPC Combat

Update: /js/systems/npc-combat.js

Add feedback to NPC attacking:

export async function npcAttack(npcId, npc) {
  const state = window.npcHostileSystem.getNPCHostileState(npcId);
  if (!state) return;

  // Show attack telegraph
  if (npc.attackTelegraph) {
    npc.attackTelegraph.show();
  }

  // Play warning
  window.combatSounds?.playWarning();
  window.screenEffects?.flashWarning();

  // Wind-up delay (gives player time to react)
  await new Promise(resolve =>
    setTimeout(resolve, COMBAT_CONFIG.npc.attackWindupDuration)
  );

  // Hide telegraph
  if (npc.attackTelegraph) {
    npc.attackTelegraph.hide();
  }

  // Play attack sound
  window.combatSounds?.playNPCPunch();

  // Play attack animation
  const direction = getNPCFacingDirection(npc);
  await playNPCPunchAnimation(scene, npc, direction);

  // Check if player still in range
  const playerPos = { x: window.player.x, y: window.player.y };
  const distance = Phaser.Math.Distance.Between(
    npc.sprite.x, npc.sprite.y,
    playerPos.x, playerPos.y
  );

  if (distance <= state.attackRange) {
    // HIT
    window.combatSounds?.playHit();
    window.combatSounds?.playPlayerHurt();

    window.playerHealth.damagePlayer(state.attackDamage);

    // Strong feedback for player damage
    window.screenEffects?.flashDamage();
    window.screenEffects?.shakeMedium();
    flashSprite(window.player, 0xff0000, 300);

    window.damageNumbers?.show(
      window.player.x,
      window.player.y - 30,
      state.attackDamage,
      false,
      false
    );
  } else {
    // MISS
    window.combatSounds?.playMiss();
  }

  // Update cooldown
  state.lastAttackTime = Date.now();
}

Feedback Integration Checklist

When integrating feedback systems:

  • Create damage numbers pool
  • Create screen flash overlay
  • Add screen shake support
  • Create sprite flash functions
  • Create attack telegraph graphics
  • Load sound effects (or skip for MVP)
  • Add feedback calls to player punch
  • Add feedback calls to NPC attack
  • Add feedback to damage functions
  • Test all feedback types
  • Add accessibility toggles for effects
  • Verify performance impact acceptable

Accessibility Settings

Add to game settings:

const feedbackSettings = {
  screenFlash: true,
  screenShake: true,
  damageNumbers: true,
  sounds: true,
  attackTelegraphs: true
};

// Apply settings
COMBAT_CONFIG.feedback.enableScreenFlash = feedbackSettings.screenFlash;
COMBAT_CONFIG.feedback.enableScreenShake = feedbackSettings.screenShake;
COMBAT_CONFIG.feedback.enableDamageNumbers = feedbackSettings.damageNumbers;
COMBAT_CONFIG.feedback.enableSounds = feedbackSettings.sounds;

// Settings UI
/*
Combat Feedback Settings
[ ] Screen Flash Effects
[ ] Screen Shake
[ ] Damage Numbers
[ ] Sound Effects
[ ] Attack Warnings
*/

Summary

Enhanced feedback makes combat feel responsive and clear. Priority order:

  1. Damage numbers - Critical for understanding combat
  2. Screen flash - Clear player damage feedback
  3. Sprite flash - Visual hit confirmation
  4. Attack telegraph - Fairness (player can react)
  5. Sound effects - Polish (can be added later)
  6. Screen shake - Polish (optional)

Implement in this order for best ROI on development time.