mirror of
https://github.com/cliffe/BreakEscape.git
synced 2026-02-20 13:50:46 +00:00
feat: Improve NPC and player combat interactions with enhanced state management and safety checks
This commit is contained in:
@@ -26,11 +26,16 @@ export class NPCCombat {
|
||||
|
||||
const state = window.npcHostileSystem.getState(npcId);
|
||||
if (!state || !state.isHostile || state.isKO) {
|
||||
// If NPC is KO, make sure they're not stuck in attacking state
|
||||
if (state && state.isKO) {
|
||||
this.npcAttacking.delete(npcId);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// Don't attack if already attacking
|
||||
if (this.npcAttacking.has(npcId)) {
|
||||
console.log(`🥊 ${npcId} already attacking, skipping`);
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -92,6 +97,7 @@ export class NPCCombat {
|
||||
performAttack(npcId, npcSprite, state) {
|
||||
// Mark NPC as attacking
|
||||
this.npcAttacking.add(npcId);
|
||||
console.log(`🥊 ${npcId} starting attack sequence, added to attacking set (size: ${this.npcAttacking.size})`);
|
||||
|
||||
// Update attack timer
|
||||
this.npcAttackTimers.set(npcId, Date.now());
|
||||
@@ -119,6 +125,20 @@ export class NPCCombat {
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if NPC is still valid and not destroyed
|
||||
if (!npcSprite || npcSprite.destroyed) {
|
||||
console.log(`${npcId} sprite destroyed during attack`);
|
||||
this.npcAttacking.delete(npcId);
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if NPC became KO during windup
|
||||
if (window.npcHostileSystem && window.npcHostileSystem.isNPCKO(npcId)) {
|
||||
console.log(`${npcId} became KO during attack windup`);
|
||||
this.npcAttacking.delete(npcId);
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if player is still in range
|
||||
const distance = Phaser.Math.Distance.Between(
|
||||
npcSprite.x,
|
||||
@@ -144,10 +164,18 @@ export class NPCCombat {
|
||||
* @param {Object} state - NPC hostile state
|
||||
*/
|
||||
applyAttackDamage(npcId, npcSprite, state) {
|
||||
// Clear attacking flag
|
||||
// Clear attacking flag - must be first to ensure it's always cleared
|
||||
this.npcAttacking.delete(npcId);
|
||||
console.log(`🥊 ${npcId} applying attack damage, cleared attacking flag`);
|
||||
|
||||
if (!window.player || !window.playerHealth) {
|
||||
console.log(`⚠️ ${npcId} attack aborted - no player or health system`);
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if sprite is still valid
|
||||
if (!npcSprite || npcSprite.destroyed) {
|
||||
console.log(`⚠️ ${npcId} attack aborted - sprite invalid`);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -267,8 +295,22 @@ export class NPCCombat {
|
||||
npcSprite.play(attackAnimKey);
|
||||
console.log(`🥊 Playing NPC attack animation: ${attackAnimKey}`);
|
||||
|
||||
// Safety timeout to ensure flag is cleared even if animation doesn't complete
|
||||
const maxAttackDuration = 2000; // 2 seconds max
|
||||
const safetyTimeout = this.scene.time.delayedCall(maxAttackDuration, () => {
|
||||
if (this.npcAttacking.has(npcId)) {
|
||||
console.warn(`⚠️ Attack animation timeout for ${npcId}, clearing flag`);
|
||||
this.npcAttacking.delete(npcId);
|
||||
}
|
||||
});
|
||||
|
||||
// Apply damage when animation completes, then return to idle
|
||||
npcSprite.once('animationcomplete', (anim) => {
|
||||
// Cancel safety timeout if animation completes properly
|
||||
if (safetyTimeout) {
|
||||
safetyTimeout.remove();
|
||||
}
|
||||
|
||||
if (anim.key === attackAnimKey && !npcSprite.destroyed) {
|
||||
// Apply damage on animation complete
|
||||
this.applyAttackDamage(npcId, npcSprite, state);
|
||||
@@ -277,21 +319,39 @@ export class NPCCombat {
|
||||
if (npcSprite.scene.anims.exists(idleAnimKey)) {
|
||||
npcSprite.play(idleAnimKey);
|
||||
}
|
||||
} else if (this.npcAttacking.has(npcId)) {
|
||||
// Animation was different or sprite destroyed, clear flag
|
||||
this.npcAttacking.delete(npcId);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
console.warn(`⚠️ Attack animation not found: ${attackAnimKey}, using fallback`);
|
||||
|
||||
// Fallback: red tint + delayed damage
|
||||
console.log(`🥊 Using fallback attack for ${npcId}`);
|
||||
if (window.spriteEffects) {
|
||||
window.spriteEffects.applyAttackTint(npcSprite);
|
||||
}
|
||||
|
||||
// Safety timeout to ensure flag is cleared
|
||||
const maxAttackDuration = 2000;
|
||||
const safetyTimeout = this.scene.time.delayedCall(maxAttackDuration, () => {
|
||||
if (this.npcAttacking.has(npcId)) {
|
||||
console.warn(`⚠️ Fallback attack timeout for ${npcId}, clearing flag`);
|
||||
this.npcAttacking.delete(npcId);
|
||||
}
|
||||
});
|
||||
|
||||
// Apply damage and remove tint after animation duration
|
||||
this.scene.time.delayedCall(COMBAT_CONFIG.player.punchAnimationDuration, () => {
|
||||
// Cancel safety timeout
|
||||
if (safetyTimeout) {
|
||||
safetyTimeout.remove();
|
||||
}
|
||||
|
||||
this.applyAttackDamage(npcId, npcSprite, state);
|
||||
|
||||
if (window.spriteEffects) {
|
||||
if (window.spriteEffects && npcSprite && !npcSprite.destroyed) {
|
||||
window.spriteEffects.clearAttackTint(npcSprite);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -69,8 +69,13 @@ export class PlayerCombat {
|
||||
* Damage applies to ALL NPCs in punch range and facing direction
|
||||
*/
|
||||
punch() {
|
||||
if (this.isPunching || !this.canPunch()) {
|
||||
console.log('Punch on cooldown');
|
||||
if (this.isPunching) {
|
||||
console.log('🥊 Punch blocked - already punching');
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!this.canPunch()) {
|
||||
console.log('🥊 Punch blocked - on cooldown');
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -82,6 +87,8 @@ export class PlayerCombat {
|
||||
this.isPunching = true;
|
||||
this.lastPunchTime = Date.now();
|
||||
|
||||
console.log(`🥊 Player starting punch, isPunching set to true`);
|
||||
|
||||
// Play punch animation and wait for completion
|
||||
this.playPunchAnimation();
|
||||
|
||||
@@ -141,9 +148,27 @@ export class PlayerCombat {
|
||||
|
||||
if (animPlayed) {
|
||||
console.log(`🥊 Playing punch animation: ${animKey}`);
|
||||
|
||||
// Safety timeout to ensure flag is cleared even if animation is interrupted
|
||||
const maxAttackDuration = 2000; // 2 seconds max
|
||||
const safetyTimeout = this.scene.time.delayedCall(maxAttackDuration, () => {
|
||||
if (this.isPunching) {
|
||||
console.warn(`⚠️ Player punch animation timeout, clearing isPunching flag`);
|
||||
this.isPunching = false;
|
||||
}
|
||||
});
|
||||
|
||||
// Animation will complete naturally
|
||||
// Apply damage when animation completes, then return to idle
|
||||
player.once('animationcomplete', () => {
|
||||
player.once('animationcomplete', (anim) => {
|
||||
// Cancel safety timeout if animation completes properly
|
||||
if (safetyTimeout) {
|
||||
safetyTimeout.remove();
|
||||
}
|
||||
|
||||
// Check if the completed animation is a punch/jab animation
|
||||
if (anim.key.includes('punch') || anim.key.includes('jab')) {
|
||||
console.log(`🥊 Player punch animation completed: ${anim.key}`);
|
||||
// Apply damage on animation complete
|
||||
this.checkForHits();
|
||||
this.isPunching = false;
|
||||
@@ -152,6 +177,11 @@ export class PlayerCombat {
|
||||
if (player.anims && player.anims.exists && this.scene.anims.exists(idleKey)) {
|
||||
player.anims.play(idleKey, true);
|
||||
}
|
||||
} else if (this.isPunching) {
|
||||
// Animation was different (interrupted), just clear the flag
|
||||
console.warn(`⚠️ Player punch interrupted - animation changed to: ${anim.key}`);
|
||||
this.isPunching = false;
|
||||
}
|
||||
});
|
||||
} else {
|
||||
// Fallback: red tint + walk animation
|
||||
@@ -170,16 +200,33 @@ export class PlayerCombat {
|
||||
}
|
||||
}
|
||||
|
||||
// Safety timeout to ensure flag is cleared
|
||||
const maxAttackDuration = 2000;
|
||||
const safetyTimeout = this.scene.time.delayedCall(maxAttackDuration, () => {
|
||||
if (this.isPunching) {
|
||||
console.warn(`⚠️ Player fallback punch timeout, clearing isPunching flag`);
|
||||
this.isPunching = false;
|
||||
}
|
||||
});
|
||||
|
||||
// Apply damage and remove tint after animation duration (fallback)
|
||||
this.scene.time.delayedCall(COMBAT_CONFIG.player.punchAnimationDuration, () => {
|
||||
// Cancel safety timeout
|
||||
if (safetyTimeout) {
|
||||
safetyTimeout.remove();
|
||||
}
|
||||
|
||||
console.log(`🥊 Player fallback punch completed`);
|
||||
this.checkForHits();
|
||||
this.isPunching = false;
|
||||
|
||||
if (window.spriteEffects) {
|
||||
if (window.spriteEffects && player && !player.destroyed) {
|
||||
window.spriteEffects.clearAttackTint(player);
|
||||
}
|
||||
// Stop animation
|
||||
if (player && player.anims && !player.destroyed) {
|
||||
player.anims.stop();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user