diff --git a/planning_notes/npc/hostile/CORRECTIONS.md b/planning_notes/npc/hostile/CORRECTIONS.md index 761e272..85d3c28 100644 --- a/planning_notes/npc/hostile/CORRECTIONS.md +++ b/planning_notes/npc/hostile/CORRECTIONS.md @@ -35,6 +35,70 @@ The `#exit_conversation` tag tells the game engine to close the conversation UI, --- +## Exit Conversation Tag - Already Implemented ✅ + +**Good News**: The `#exit_conversation` tag is **already handled** in the codebase. + +**Location**: `/js/minigames/person-chat/person-chat-minigame.js` line 537: +```javascript +const shouldExit = result?.tags?.some(tag => tag.includes('exit_conversation')); +``` + +When this tag is detected, the minigame: +1. Shows the NPC's final response +2. Schedules the conversation to close +3. Saves the NPC conversation state +4. Exits the minigame + +**No additional handler needed** for `#exit_conversation` - it works out of the box. + +--- + +## Hostile Tag - Needs Implementation ❌ + +**Required**: The `#hostile` tag needs to be added to the tag processing system. + +**Location**: `/js/minigames/helpers/chat-helpers.js` + +**Where to Add**: In the `processGameActionTags()` function switch statement (around line 60), add: + +```javascript +case 'hostile': { + const npcId = param || window.currentConversationNPCId; + + if (!npcId) { + result.message = '⚠️ hostile tag missing NPC ID'; + console.warn(result.message); + break; + } + + console.log(`🔴 Processing hostile tag for NPC: ${npcId}`); + + // Set NPC to hostile state + if (window.npcHostileSystem) { + window.npcHostileSystem.setNPCHostile(npcId, true); + result.success = true; + result.message = `⚠️ ${npcId} is now hostile!`; + } else { + result.message = '⚠️ Hostile system not initialized'; + console.warn(result.message); + } + + // Emit event for other systems + if (window.eventDispatcher) { + window.eventDispatcher.emit('npc_became_hostile', { npcId }); + } + + break; +} +``` + +**Tag Format**: +- `#hostile:npcId` - Make specific NPC hostile +- `#hostile` - Make current conversation NPC hostile (uses `window.currentConversationNPCId`) + +--- + ## Files Needing Correction ### 1. implementation_plan.md @@ -85,25 +149,27 @@ Goodbye! ```ink === test_hostile === # speaker:test_npc -This will trigger hostile mode! +Triggering hostile state for security guard! +Watch out - they're coming for you! # hostile:security_guard # exit_conversation -> hub === test_exit === # speaker:test_npc -This will exit cleanly. +Exiting the conversation cleanly. +Goodbye, and good luck! # exit_conversation -> hub ``` -Note: The dialogue "You should now be in combat" and "Goodbye!" should come BEFORE the `#exit_conversation` tag, as the conversation closes when that tag is processed. +**Note**: The dialogue should come BEFORE the `#exit_conversation` tag, as the conversation closes when that tag is processed. Text after the tag won't be shown. --- ### 3. implementation_roadmap.md -**Phase 7.2 section** - May reference similar patterns. Should be reviewed for consistency. +**Phase 7.2 section** - References exit_conversation tag handler needing to be added. This should be removed since it already exists. --- @@ -248,19 +314,21 @@ Goodbye, and good luck! ## Summary of Corrections 1. **Never use `-> END`** - Always use `-> hub` -2. **Exit pattern**: `# exit_conversation` followed by `-> hub` +2. **Exit pattern**: `# exit_conversation` followed by `-> hub` (already works!) 3. **Hostile pattern**: `# hostile:npcId` + `# exit_conversation` + `-> hub` 4. **Hub pattern**: All conversation paths eventually return to hub 5. **Multiple exits**: A conversation can have multiple exit points, all using the same pattern +6. **Exit conversation already implemented**: No need to add handler, it already exists in person-chat-minigame.js --- ## Why This Pattern? -From analyzing `helper-npc.ink`: +From analyzing `helper-npc.ink` and `person-chat-minigame.js`: - The hub acts as a central conversation state - `#exit_conversation` is a **tag** that tells the game engine to close the UI +- This tag is **already detected** in person-chat-minigame.js - The Ink story still needs to resolve to a valid state (the hub) - Returning to hub after exit means the NPC state is properly saved - If player talks to NPC again, conversation starts at `start` knot, not hub @@ -275,11 +343,13 @@ When implementing: 1. ✅ Read this corrections document first 2. ✅ Never use `-> END` in any Ink file 3. ✅ Follow the corrected patterns above -4. ✅ Test each conversation path thoroughly -5. ✅ Verify `#exit_conversation` closes the UI -6. ✅ Verify returning to hub doesn't cause issues -7. ✅ Update security-guard.ink according to recommendations -8. ✅ Create test-hostile.ink with corrected pattern +4. ❌ **Don't add** exit_conversation handler - it already exists! +5. ✅ **Do add** hostile tag handler to chat-helpers.js +6. ✅ Test each conversation path thoroughly +7. ✅ Verify `#exit_conversation` closes the UI (should work already) +8. ✅ Verify returning to hub doesn't cause issues +9. ✅ Update security-guard.ink according to recommendations +10. ✅ Create test-hostile.ink with corrected pattern --- @@ -287,6 +357,8 @@ When implementing: - **Good Example**: `/scenarios/ink/helper-npc.ink` - Perfect hub pattern usage - **Needs Fixing**: `/scenarios/ink/security-guard.ink` - Has 8 `-> END` instances +- **Exit Tag Implementation**: `/js/minigames/person-chat/person-chat-minigame.js` line 537 +- **Tag Processing**: `/js/minigames/helpers/chat-helpers.js` - Add hostile case here - **Pattern Source**: Lines 68-71 of `helper-npc.ink`: ```ink + [Thanks, I'm good for now.] diff --git a/planning_notes/npc/hostile/review2/INTEGRATION_UPDATES.md b/planning_notes/npc/hostile/review2/INTEGRATION_UPDATES.md new file mode 100644 index 0000000..675f873 --- /dev/null +++ b/planning_notes/npc/hostile/review2/INTEGRATION_UPDATES.md @@ -0,0 +1,489 @@ +# Integration Review Updates - Critical Corrections + +## Date: 2025-11-14 + +This document contains critical corrections to the integration review based on codebase verification. + +--- + +## ✅ Issue 1 CORRECTION: Exit Conversation Tag Already Implemented + +**Original Assessment**: Missing tag handler for `#exit_conversation` + +**Actual State**: ✅ **ALREADY IMPLEMENTED** + +**Location**: `/js/minigames/person-chat/person-chat-minigame.js` line 537 + +**Implementation**: +```javascript +const shouldExit = result?.tags?.some(tag => tag.includes('exit_conversation')); +``` + +**What It Does**: +When `#exit_conversation` tag is detected in Ink story tags: +1. Shows the NPC's final response +2. Schedules the conversation to close after a delay +3. Saves the NPC conversation state +4. Exits the person-chat minigame + +**Impact on Planning**: +- ❌ **Don't** add exit_conversation handler to chat-helpers.js (not needed!) +- ✅ **Do** continue using `#exit_conversation` in Ink files (it works!) +- ✅ **Do** always follow `#exit_conversation` with `-> hub` in Ink + +**Revised Critical Issues**: +Only **ONE** critical issue remains: +1. ✅ Add hostile tag handler to chat-helpers.js + +--- + +## ✅ Issue 6 CORRECTION: Punch Mechanics Already Designed + +**Original Assessment**: Multiple hostile NPCs targeting logic not designed + +**Actual Design**: ✅ **INTERACTION-BASED WITH AOE DAMAGE** + +**How It Works**: + +### Step 1: Initiate Punch via Interaction +Player initiates punch by **interacting** with any hostile NPC: +- **Click** on hostile NPC sprite +- **Press 'E'** when near hostile NPC + +This interaction targets the specific NPC to initiate the punch action. + +### Step 2: Punch Animation Plays +- Player character plays punch animation (walk + red tint placeholder) +- Animation duration: 500ms (configurable) +- Player facing direction determines attack direction + +### Step 3: Damage Application (AOE) +When punch animation completes, damage applies to: +- **All NPCs** in punch range (default 60 pixels) +- **In the player's facing direction** (directional attack) + +This creates an **area-of-effect (AOE) punch** that can hit multiple enemies if they're grouped together. + +### Example Scenarios + +**Scenario A: Single Hostile NPC** +1. Player clicks on hostile NPC or presses 'E' nearby +2. Punch animation plays +3. If NPC still in range + direction when animation completes → takes damage +4. If NPC moved away → miss + +**Scenario B: Multiple Hostile NPCs Grouped** +1. Player clicks on one hostile NPC or presses 'E' +2. Punch animation plays in facing direction +3. All hostile NPCs within punch range AND in facing direction take damage +4. Potential to damage 2-3 NPCs with one punch if they're close together + +**Scenario C: NPC Behind Player** +1. Player has NPC in front and one behind +2. Player faces forward and clicks front NPC +3. Punch animation plays facing forward +4. Only front NPC takes damage (directional check) +5. NPC behind is not in facing direction → no damage + +### Implementation Details + +**In interactions.js**: +```javascript +function checkHostileNPCInteractions() { + // Find hostile NPCs player can interact with (click or 'E' key) + const nearbyHostileNPCs = getHostileNPCsInInteractionRange(); + + // Highlight/indicate which NPCs are interactable + for (const npc of nearbyHostileNPCs) { + // Show punch cursor or interaction indicator + showPunchIndicator(npc); + } +} + +// When player clicks NPC or presses 'E' +function onPlayerInteractWithHostileNPC(npc) { + if (window.playerCombat?.canPlayerPunch()) { + window.playerCombat.playerPunch(npc); + } +} +``` + +**In player-combat.js**: +```javascript +export async function playerPunch(targetNPC) { + if (!canPlayerPunch()) return; + + // Play punch animation in player's facing direction + const direction = getPlayerFacingDirection(); + await playPlayerPunchAnimation(scene, player, direction); + + // After animation, find ALL NPCs in range + direction + const npcsHit = getNPCsInPunchRange(direction); + + // Apply damage to all NPCs hit + for (const npc of npcsHit) { + window.npcHostileSystem.damageNPC(npc.id, COMBAT_CONFIG.player.punchDamage); + // Show feedback + window.damageNumbers?.show(npc.sprite.x, npc.sprite.y, damage); + flashSprite(npc.sprite); + } + + startPunchCooldown(); +} + +function getNPCsInPunchRange(facing Direction) { + const playerPos = { x: window.player.x, y: window.player.y }; + const punchRange = COMBAT_CONFIG.player.punchRange; + + return getHostileNPCsInRoom() + .filter(npc => { + // Check distance + const distance = Phaser.Math.Distance.Between( + playerPos.x, playerPos.y, + npc.sprite.x, npc.sprite.y + ); + if (distance > punchRange) return false; + + // Check direction (is NPC in front of player?) + return isInFacingDirection(playerPos, npc.sprite, facingDirection); + }); +} +``` + +**Benefits of This Design**: +1. **Intuitive**: Player targets specific NPC by clicking/interacting +2. **Strategic**: Can hit multiple enemies if positioned well +3. **Directional**: Can't hit enemies behind you +4. **Existing Pattern**: Uses existing interaction system (click or 'E' key) + +**Impact on Planning**: +- ❌ **Don't** need tab-cycling or closest-target selection +- ❌ **Don't** need complex targeting UI +- ✅ **Do** use existing interaction system (checkObjectInteractions) +- ✅ **Do** implement directional range check +- ✅ **Do** support multi-target damage (AOE punch) + +**No changes needed** to target selection plan - the design is solid and uses existing patterns! + +--- + +## Revised Critical Prerequisites + +### Before Phase 0: + +**Only ONE critical task**: +1. ✅ Add hostile tag handler to `/js/minigames/helpers/chat-helpers.js` + +**Already working** (no action needed): +- ✅ Exit conversation tag (already in person-chat-minigame.js) +- ✅ Interaction system for punch targeting (already exists) + +### Phase 0 Foundation: + +**Update chat-helpers.js**: +```javascript +// Add this case to the switch statement in processGameActionTags() +case 'hostile': { + const npcId = param || window.currentConversationNPCId; + + if (!npcId) { + result.message = '⚠️ hostile tag missing NPC ID'; + console.warn(result.message); + break; + } + + console.log(`🔴 Processing hostile tag for NPC: ${npcId}`); + + // Set NPC to hostile state + if (window.npcHostileSystem) { + window.npcHostileSystem.setNPCHostile(npcId, true); + result.success = true; + result.message = `⚠️ ${npcId} is now hostile!`; + } else { + result.message = '⚠️ Hostile system not initialized'; + console.warn(result.message); + } + + // Emit event + if (window.eventDispatcher) { + window.eventDispatcher.emit('npc_became_hostile', { npcId }); + } + + break; +} +``` + +**Test the hostile tag**: +1. Create test Ink file with `#hostile:security_guard` tag +2. Talk to test NPC in game +3. Choose option that triggers hostile tag +4. Verify in console: "🔴 Processing hostile tag for NPC: security_guard" +5. Verify conversation closes +6. Verify security guard becomes hostile (once hostile system implemented) + +--- + +## Revised Phase 5: Combat Mechanics + +### Player Combat - Interaction-Based AOE Punch + +**File**: `/js/systems/player-combat.js` + +**Key Implementation**: +```javascript +// Called when player interacts with hostile NPC (click or 'E' key) +export async function playerPunch(initiatingNPC) { + if (!canPlayerPunch()) return; + + // Get player facing direction + const direction = getPlayerFacingDirection(); + + // Play punch animation + await playPlayerPunchAnimation(scene, window.player, direction); + + // Find ALL NPCs in punch range + facing direction + const npcsInRange = getNPCsInPunchRange(direction); + + if (npcsInRange.length > 0) { + // HIT - damage all NPCs in range + for (const npc of npcsInRange) { + const damage = COMBAT_CONFIG.player.punchDamage; + + window.npcHostileSystem.damageNPC(npc.id, damage); + window.combatSounds?.playHit(); + + // Visual feedback per NPC + flashSprite(npc.sprite, 0xffffff, 100); + shakeSprite(npc.sprite, 5, 100); + window.damageNumbers?.show(npc.sprite.x, npc.sprite.y - 20, damage, false, false); + } + } else { + // MISS + window.combatSounds?.playMiss(); + window.damageNumbers?.show( + initiatingNPC.sprite.x, + initiatingNPC.sprite.y - 20, + 0, + false, + true // isMiss + ); + } + + startPunchCooldown(); +} + +function getNPCsInPunchRange(facingDirection) { + const playerPos = { x: window.player.x, y: window.player.y }; + const punchRange = COMBAT_CONFIG.player.punchRange; + + return getNPCsInRoom(window.currentRoom) + .filter(npc => { + // Only hostile, non-KO NPCs + if (!window.npcHostileSystem?.isNPCHostile(npc.id)) return false; + if (window.npcHostileSystem?.isNPCKO(npc.id)) return false; + + // Check distance + const distance = Phaser.Math.Distance.Between( + playerPos.x, playerPos.y, + npc.sprite.x, npc.sprite.y + ); + if (distance > punchRange) return false; + + // Check if NPC is in facing direction + return isInFacingDirection( + playerPos, + { x: npc.sprite.x, y: npc.sprite.y }, + facingDirection, + 90 // degrees tolerance (45° on each side) + ); + }); +} + +function isInFacingDirection(origin, target, direction, tolerance = 90) { + // Calculate angle from origin to target + const angle = Phaser.Math.Angle.Between( + origin.x, origin.y, + target.x, target.y + ); + + // Convert direction to angle + const directionAngles = { + 'down': Math.PI / 2, // 90 degrees + 'up': -Math.PI / 2, // -90 degrees + 'right': 0, // 0 degrees + 'left': Math.PI, // 180 degrees + 'down-right': Math.PI / 4, + 'down-left': 3 * Math.PI / 4, + 'up-right': -Math.PI / 4, + 'up-left': -3 * Math.PI / 4 + }; + + const expectedAngle = directionAngles[direction]; + const toleranceRad = (tolerance * Math.PI) / 180; + + // Check if angle is within tolerance + const angleDiff = Math.abs(Phaser.Math.Angle.Wrap(angle - expectedAngle)); + return angleDiff <= toleranceRad; +} +``` + +**Benefits**: +- Can hit multiple NPCs with one punch if grouped +- Directional attack feels natural +- Uses existing interaction system +- No complex targeting UI needed + +--- + +## Revised Phase 7: Integration Points + +### 7.4: Punch Interaction (Corrected) + +**File**: `/js/systems/interactions.js` + +**Integration**: +```javascript +// Extend existing checkObjectInteractions() to include hostile NPCs +function checkObjectInteractions() { + // ... existing code for objects and friendly NPCs ... + + // Check for hostile NPC interactions + checkHostileNPCInteractions(); +} + +function checkHostileNPCInteractions() { + if (!window.player || window.playerHealth?.isPlayerKO()) return; + + const playerPos = { x: window.player.x, y: window.player.y }; + const interactionRange = 64; // Existing interaction range + + // Get hostile NPCs in interaction range + const nearbyHostileNPCs = getNPCsInRoom(window.currentRoom) + .filter(npc => { + if (!window.npcHostileSystem?.isNPCHostile(npc.id)) return false; + if (window.npcHostileSystem?.isNPCKO(npc.id)) return false; + + const distance = Phaser.Math.Distance.Between( + playerPos.x, playerPos.y, + npc.sprite.x, npc.sprite.y + ); + + return distance <= interactionRange; + }); + + if (nearbyHostileNPCs.length > 0) { + // Show punch interaction indicator + for (const npc of nearbyHostileNPCs) { + // Could show fist icon above NPC, or change cursor, or highlight sprite + showPunchInteractionIndicator(npc); + } + + // Store for click/E key handling + window.currentHostileNPCTargets = nearbyHostileNPCs; + } else { + window.currentHostileNPCTargets = []; + } +} +``` + +**Click Handler** (in existing click handler): +```javascript +// When player clicks on hostile NPC +this.input.on('pointerdown', (pointer) => { + // Check if clicked on hostile NPC + const clickedNPC = window.currentHostileNPCTargets?.find(npc => + // Check if click is on NPC sprite bounds + isClickOnSprite(pointer, npc.sprite) + ); + + if (clickedNPC) { + // Initiate punch with this NPC + if (window.playerCombat?.canPlayerPunch()) { + window.playerCombat.playerPunch(clickedNPC); + } + return; // Don't process other click actions + } + + // ... existing click handling for movement, objects, etc. ... +}); +``` + +**'E' Key Handler** (add to keyboard input): +```javascript +// When player presses 'E' key +this.input.keyboard.on('keydown-E', () => { + // If near hostile NPC, punch instead of normal interaction + if (window.currentHostileNPCTargets?.length > 0) { + // Punch closest hostile NPC + const closestNPC = getClosestNPC(window.currentHostileNPCTargets); + if (window.playerCombat?.canPlayerPunch()) { + window.playerCombat.playerPunch(closestNPC); + } + return; + } + + // ... existing 'E' key handling for doors, objects, friendly NPCs ... +}); +``` + +**Visual Feedback**: +- Show fist cursor when hovering over hostile NPC in range +- Or: Red outline around punchable hostile NPCs +- Or: "Press E to Punch" text above hostile NPC + +--- + +## Summary of Corrections + +### What Changed: + +1. **Exit Conversation Tag**: ✅ Already implemented, no work needed +2. **Punch Targeting**: ✅ Uses existing interaction system (click or 'E') +3. **Punch Damage**: ✅ AOE damage to all NPCs in range + direction + +### What Stays the Same: + +1. **Hostile Tag**: ❌ Still needs to be added to chat-helpers.js +2. **Ink Pattern**: All docs still need `-> hub` not `-> END` +3. **All other systems**: Compatible as reviewed + +### Impact on Implementation: + +**Less work required**: +- Don't need to add exit_conversation handler +- Don't need to create complex targeting system +- Use existing interaction patterns + +**Simpler integration**: +- One critical task (hostile tag handler) +- Punch uses existing interaction system +- AOE damage is bonus feature, not complexity + +**Better gameplay**: +- Punch feels natural (click or 'E' to interact) +- Can strategically hit multiple enemies +- Directional attacks add tactical depth + +--- + +## Updated Quick Start - Phase -1 + +**Before implementing anything:** + +1. ✅ Add hostile tag handler to chat-helpers.js (see code above) +2. ✅ Fix Ink files to use `-> hub` not `-> END` +3. ✅ Test hostile tag with simple Ink file +4. ✅ Verify exit_conversation works (should already work!) + +**That's it!** These are the only critical prerequisites. + +Then proceed with Phase 0 as planned. + +--- + +## References + +- **Exit Tag**: `/js/minigames/person-chat/person-chat-minigame.js` line 537 +- **Tag Processing**: `/js/minigames/helpers/chat-helpers.js` - Add hostile case here +- **Interactions**: `/js/systems/interactions.js` - Extend for punch interaction +- **Player Combat**: New file - Implement punch with AOE damage diff --git a/planning_notes/npc/hostile/review2/quick_start.md b/planning_notes/npc/hostile/review2/quick_start.md index eac5526..f929f6f 100644 --- a/planning_notes/npc/hostile/review2/quick_start.md +++ b/planning_notes/npc/hostile/review2/quick_start.md @@ -12,27 +12,32 @@ Read these documents in order: ## Critical Prerequisites (Must Complete First) -### 1. Add Ink Tag Handlers +### 1. Add Hostile Tag Handler **File**: `/js/minigames/helpers/chat-helpers.js` -**Location**: In the `processGameActionTags()` function, add these cases to the switch statement (around line 60): +**Location**: In the `processGameActionTags()` function, add this case to the switch statement (around line 60): ```javascript case 'hostile': { - const parts = tag.split(':'); - const npcId = parts[1] || (ui && ui.npcId); + const npcId = param || window.currentConversationNPCId; if (!npcId) { - console.error('hostile tag missing NPC ID'); + result.message = '⚠️ hostile tag missing NPC ID'; + console.warn(result.message); break; } - console.log(`Processing hostile tag for NPC: ${npcId}`); + console.log(`🔴 Processing hostile tag for NPC: ${npcId}`); // Set NPC to hostile state if (window.npcHostileSystem) { window.npcHostileSystem.setNPCHostile(npcId, true); + result.success = true; + result.message = `⚠️ ${npcId} is now hostile!`; + } else { + result.message = '⚠️ Hostile system not initialized'; + console.warn(result.message); } // Emit event for other systems @@ -40,25 +45,12 @@ case 'hostile': { window.eventDispatcher.emit('npc_became_hostile', { npcId }); } - // Exit conversation after hostile trigger - if (ui && typeof ui.exitConversation === 'function') { - ui.exitConversation(); - } - - break; -} - -case 'exit_conversation': { - console.log('Processing exit_conversation tag'); - - if (ui && typeof ui.exitConversation === 'function') { - ui.exitConversation(); - } - break; } ``` +**Note on Exit Conversation**: ✅ The `#exit_conversation` tag is **already handled** in `/js/minigames/person-chat/person-chat-minigame.js` line 537. **No additional handler needed!** + **Test**: Verify with test Ink file before proceeding. --- @@ -515,13 +507,42 @@ Follow **implementation_roadmap.md** for detailed phase breakdowns. --- +## Punch Mechanics Design (For Reference) + +### How Punching Works + +**Initiation** (Interaction-Based): +- Player **clicks** on hostile NPC, OR +- Player presses **'E' key** when near hostile NPC +- Uses existing interaction system + +**Animation**: +- Player punch animation plays (walk + red tint, 500ms) +- Animation plays in player's facing direction + +**Damage Application** (AOE): +- After animation completes, check ALL hostile NPCs +- Damage applies to NPCs that are: + 1. Within punch range (60 pixels default) + 2. In player's facing direction (90° cone) + 3. Not already KO'd + +**Result**: +- Can hit multiple NPCs with one punch if grouped +- Directional attack (can't hit NPCs behind you) +- Miss if target moves out of range during animation + +**Implementation Note**: Phase 5 will implement this using existing interaction patterns from interactions.js + +--- + ## Common Issues and Solutions ### Issue: "Player health not initialized" **Solution**: Make sure initPlayerHealth() is called in game.js create() ### Issue: "hostile tag not working" -**Solution**: Check that tag handler added to chat-helpers.js and function exported +**Solution**: Check that tag handler added to chat-helpers.js with correct case statement ### Issue: "Events not firing" **Solution**: Verify window.eventDispatcher exists (should be created by NPC system) @@ -532,6 +553,9 @@ Follow **implementation_roadmap.md** for detailed phase breakdowns. ### Issue: "NPC not found" **Solution**: Verify NPC exists in scenario and npcManager is initialized +### Issue: "exit_conversation not working" +**Solution**: This should already work! Check /js/minigames/person-chat/person-chat-minigame.js line 537 + --- ## Critical Reminders