From b7def4f2e7c7ccf55b56a9c3ca4b54339c22aeb5 Mon Sep 17 00:00:00 2001 From: Mario Date: Mon, 28 Sep 2026 21:56:56 +0200 Subject: [PATCH] Kampfsystem, hier: weitere Sounds, Interaktion mit NPCs weiter ausgebaut --- .../resources/audio/weapons/impact/stab1.ogg | Bin 0 -> 12175 bytes .../resources/audio/weapons/impact/stab2.ogg | Bin 0 -> 14920 bytes .../weapons/sword/counterparade/parade1.ogg | Bin 0 -> 14518 bytes .../weapons/sword/counterparade/parade2.ogg | Bin 0 -> 13452 bytes .../audio/weapons/sword/crit/stab3.ogg | Bin 0 -> 10997 bytes .../Sword Impact Hit 1.ogg | Bin .../Sword Impact Hit 2.ogg | Bin .../Sword Impact Hit 3.ogg | Bin .../game/animation/RetargetingSystem.java | 19 +- .../blight/game/combat/WeaponCombatState.java | 171 +++++++++ .../game/control/PlayerInputControl.java | 69 +++- .../de/blight/game/npc/NpcAggroBehavior.java | 8 + .../java/de/blight/game/scene/WorldScene.java | 1 + .../de/blight/game/state/WorldNpcsState.java | 341 +++++++++--------- .../resources/lang/messages_de.properties | 6 + .../resources/lang/messages_en.properties | 6 + 16 files changed, 424 insertions(+), 197 deletions(-) create mode 100644 blight-assets/src/main/resources/audio/weapons/impact/stab1.ogg create mode 100644 blight-assets/src/main/resources/audio/weapons/impact/stab2.ogg create mode 100644 blight-assets/src/main/resources/audio/weapons/sword/counterparade/parade1.ogg create mode 100644 blight-assets/src/main/resources/audio/weapons/sword/counterparade/parade2.ogg create mode 100644 blight-assets/src/main/resources/audio/weapons/sword/crit/stab3.ogg rename blight-assets/src/main/resources/audio/weapons/sword/{impact => impactblock}/Sword Impact Hit 1.ogg (100%) rename blight-assets/src/main/resources/audio/weapons/sword/{crit => impactblock}/Sword Impact Hit 2.ogg (100%) rename blight-assets/src/main/resources/audio/weapons/sword/{impact => impactblock}/Sword Impact Hit 3.ogg (100%) create mode 100644 blight-game/src/main/java/de/blight/game/combat/WeaponCombatState.java diff --git a/blight-assets/src/main/resources/audio/weapons/impact/stab1.ogg b/blight-assets/src/main/resources/audio/weapons/impact/stab1.ogg new file mode 100644 index 0000000000000000000000000000000000000000..2f0fd3777682d7e058da77bfcfed903c7d5f2854 GIT binary patch literal 12175 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 literal 0 HcmV?d00001 diff --git a/blight-assets/src/main/resources/audio/weapons/impact/stab2.ogg b/blight-assets/src/main/resources/audio/weapons/impact/stab2.ogg new file mode 100644 index 0000000000000000000000000000000000000000..44e7e9370b6fbc9b618573c215c011b081fab90a GIT binary patch literal 14920 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 literal 0 HcmV?d00001 diff --git a/blight-assets/src/main/resources/audio/weapons/sword/counterparade/parade1.ogg b/blight-assets/src/main/resources/audio/weapons/sword/counterparade/parade1.ogg new file mode 100644 index 0000000000000000000000000000000000000000..55429756ab54f9e74c4e1e662087206875ab9b17 GIT binary patch literal 14518 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 SP|#3?e>x@b`)%1jqyGcR7OxBd literal 0 HcmV?d00001 diff --git a/blight-assets/src/main/resources/audio/weapons/sword/counterparade/parade2.ogg b/blight-assets/src/main/resources/audio/weapons/sword/counterparade/parade2.ogg new file mode 100644 index 0000000000000000000000000000000000000000..b8c70a204e3c7418eeee74bde9eb8002770813cb GIT binary patch literal 13452 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∓rs{fPV!hJ{;3m1 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 nameMap, Armature src, Armature dst) { for (var e : nameMap.entrySet()) { if (!e.getKey().equals(e.getValue())) return false; - Joint srcJ = src.getJoint(e.getKey()); - Joint dstJ = dst.getJoint(e.getValue()); - if (srcJ == null || dstJ == null) continue; - Quaternion sb = srcJ.getInitialTransform() != null - ? srcJ.getInitialTransform().getRotation() : new Quaternion(); - Quaternion db = dstJ.getInitialTransform() != null - ? dstJ.getInitialTransform().getRotation() : new Quaternion(); - if (Math.abs(sb.dot(db)) < 0.999f) { - log.info("[isSameRig] MISMATCH '{}': src=({},{},{},{}) dst=({},{},{},{}) dot={}", - e.getKey(), - String.format("%.4f", sb.getX()), String.format("%.4f", sb.getY()), - String.format("%.4f", sb.getZ()), String.format("%.4f", sb.getW()), - String.format("%.4f", db.getX()), String.format("%.4f", db.getY()), - String.format("%.4f", db.getZ()), String.format("%.4f", db.getW()), - String.format("%.6f", Math.abs(sb.dot(db)))); - return false; - } } return true; } diff --git a/blight-game/src/main/java/de/blight/game/combat/WeaponCombatState.java b/blight-game/src/main/java/de/blight/game/combat/WeaponCombatState.java new file mode 100644 index 0000000..a99deb8 --- /dev/null +++ b/blight-game/src/main/java/de/blight/game/combat/WeaponCombatState.java @@ -0,0 +1,171 @@ +package de.blight.game.combat; + +/** + * Gemeinsamer Zustandsautomat für Waffenkampf. + * + * Wird von PlayerInputControl (Spieler) und WorldNpcsState/SpawnedNpc (NPCs) + * genutzt, damit Blocken, Angriff, Kombo, schwerer Angriff und Konterparade + * auf denselben Zeitkonstanten und Phasen basieren. + */ +public final class WeaponCombatState { + + // ── Phasen ──────────────────────────────────────────────────────────────── + + public enum Phase { + IDLE, // kein aktiver Kampfzustand + PARRY_WAITING, // Blocktaste gedrückt – Tap/Hold-Entscheidung läuft + ATTACKING, // Angriffsanimation + BLOCKING, // Haltephase des Blocks + BLOCK_IMPACT, // Treffer während Block + COUNTER_WINDOW, // nach erfolgreicher Parade → Konter bereit + COUNTERING, // Konterangriff-Animation + STAGGERED, // Trefferstagger + KNOCKED_OUT, // 0 HP + CHARGING, // NPC läuft auf Spieler zu + DODGING // NPC-Ausweichrolle + } + + // ── Gemeinsame Zeitkonstanten ────────────────────────────────────────────── + + /** Maximale Haltezeit für Kurz-Tap (→ Konterparade). Darüber: Block-Haltephase. */ + public static final float BLOCK_TAP_MAX = 0.20f; + /** Animationsfortschritt ab dem das Trefferfenster beginnt. */ + public static final float HIT_WIN_START = 0.35f; + /** Animationsfortschritt bis zu dem das Trefferfenster endet. */ + public static final float HIT_WIN_END = 0.65f; + /** Kombo-Verlängerungschance nach dem 1. Treffer. */ + public static final float COMBO_P1 = 0.70f; + /** Kombo-Verlängerungschance nach dem 2. Treffer. */ + public static final float COMBO_P2 = 0.30f; + + // ── Zustandsfelder ───────────────────────────────────────────────────────── + + public Phase phase = Phase.IDLE; + public int comboStep = 0; // 0=Angriff1, 1=Angriff2, 2=Angriff3/Schwer + public boolean isHeavy = false; + public float timer = 0f; // Phase-Timer (läuft runter bis 0) + public float clipDur = 0f; // Gesamtlänge des laufenden Clips + public float hitTimer = 0f; // Zeit innerhalb des laufenden Clips + public boolean hitFired = false; // Treffer dieser Animation bereits ausgelöst + public boolean parryArmed = false; // Tap erkannt, Parade noch nicht verbraucht + public float tapTime = 0f; // Haltezeit der Blocktaste (Tap-Erkennung) + + // ── Abgeleitete Werte ────────────────────────────────────────────────────── + + /** Fortschritt innerhalb des laufenden Clips (0.0–1.0). */ + public float progress() { + return clipDur > 0f ? Math.min(1f, hitTimer / clipDur) : 1f; + } + + /** True wenn der Angriff im Trefferfenster ist. */ + public boolean inHitWindow() { + float p = progress(); + return p >= HIT_WIN_START && p <= HIT_WIN_END; + } + + // ── Zustandsübergänge ────────────────────────────────────────────────────── + + /** Startet einen Angriffsschritt (Spieler oder NPC). */ + public void beginAttack(int step, boolean heavy, float duration) { + phase = Phase.ATTACKING; + comboStep = step; + isHeavy = heavy; + timer = duration; + clipDur = duration; + hitTimer = 0f; + hitFired = false; + } + + /** Startet einen Konterangriff. */ + public void beginCounter(float duration) { + phase = Phase.COUNTERING; + timer = duration; + clipDur = duration; + hitTimer = 0f; + hitFired = false; + } + + /** Rückt Angriffs- oder Konter-Timer weiter. Gibt true zurück wenn Animation beendet. */ + public boolean tickAttack(float tpf) { + timer -= tpf; + hitTimer += tpf; + return timer <= 0f; + } + + /** + * Entscheidet nach dem aktuellen Angriffsschritt ob die Kombo verlängert wird. + * Gibt den nächsten Schritt (1 oder 2) zurück, oder -1 wenn keine Verlängerung. + */ + public int nextComboStep(java.util.Random rng) { + if (phase != Phase.ATTACKING || isHeavy || comboStep >= 2) { return -1; } + float chance = comboStep == 0 ? COMBO_P1 : COMBO_P2; + return rng.nextFloat() < chance ? comboStep + 1 : -1; + } + + /** + * Blocktaste gedrückt. + * Gibt true zurück wenn Parry-Wartephase gestartet wurde. + */ + public boolean onBlockPress() { + if (phase == Phase.IDLE) { + phase = Phase.PARRY_WAITING; + tapTime = 0f; + parryArmed = false; // wird erst bei Tap-Release gesetzt, nicht schon auf Press + return true; + } + return false; + } + + /** + * Rückt den Tap-Timer weiter. + * Gibt true zurück wenn das Tap-Fenster abgelaufen ist (→ Haltephase starten). + * Löscht parryArmed wenn Fenster ohne Loslassen abläuft. + */ + public boolean tickParryWaiting(float tpf) { + if (phase != Phase.PARRY_WAITING) { return false; } + tapTime += tpf; + if (tapTime >= BLOCK_TAP_MAX) { + parryArmed = false; + phase = Phase.IDLE; // freigeben damit onBlockPress() beim nächsten Druck greift + return true; + } + return false; + } + + /** + * Blocktaste losgelassen. + * Gibt true zurück wenn ein gültiger Tap erkannt wurde → Parade-Animation abspielen. + */ + public boolean onBlockRelease() { + if (phase == Phase.PARRY_WAITING) { + phase = Phase.IDLE; + parryArmed = true; // Tap bestätigt → für consumeParryAttempt() im NPC-Update setzen + return true; + } + return false; + } + + /** + * Verbraucht einen Parade-Versuch (vom Gegner geprüft während dessen Trefferfenster). + * Gibt einmalig true zurück wenn Parade bereit war. + */ + public boolean consumeParryAttempt() { + if (!parryArmed) { return false; } + parryArmed = false; + return true; + } + + /** Versetzt in Stagger-Zustand. */ + public void stagger(float duration) { + phase = Phase.STAGGERED; + timer = duration; + hitFired = false; + } + + /** Rückt Stagger-Timer weiter. Gibt true zurück wenn Stagger beendet. */ + public boolean tickStagger(float tpf) { + if (phase != Phase.STAGGERED) { return false; } + timer -= tpf; + return timer <= 0f; + } +} diff --git a/blight-game/src/main/java/de/blight/game/control/PlayerInputControl.java b/blight-game/src/main/java/de/blight/game/control/PlayerInputControl.java index fd3f828..ff6e59c 100644 --- a/blight-game/src/main/java/de/blight/game/control/PlayerInputControl.java +++ b/blight-game/src/main/java/de/blight/game/control/PlayerInputControl.java @@ -31,6 +31,7 @@ import de.blight.game.animation.AnimationLibrary; import de.blight.game.animation.RetargetingSystem; import de.blight.game.config.KeyBindings; import de.blight.game.combat.MeleeAttackCallback; +import de.blight.game.combat.WeaponCombatState; import de.blight.game.navigation.CharacterNavigator; import de.blight.game.state.TerrainChunkState; @@ -56,7 +57,7 @@ public class PlayerInputControl { /** Zeit in Sekunden nach einem Angriff, in der der nächste Kombo-Schlag gedrückt werden kann. */ private static final float COMBO_WINDOW = 0.6f; /** Maximale Haltezeit für einen Kurz-Klick (Konterangriff); darüber = Block-Eingabe. */ - private static final float BLOCK_TAP_MAX = 0.20f; + private static final float BLOCK_TAP_MAX = WeaponCombatState.BLOCK_TAP_MAX; /** Maximale Haltezeit, die noch als "schneller Klick" (→ normale Kombo) gilt. */ private static final float QUICK_TAP_THRESHOLD = 0.30f; /** Animationsanteil (0–1), ab dem die Vorwärtsbewegung beim schweren Angriff beginnt. */ @@ -134,6 +135,8 @@ public class PlayerInputControl { private boolean rightMouseHeld = false; private BlockPhase blockPhase = BlockPhase.NONE; private float blockPhaseTimer = 0f; + /** Gemeinsamer Zustandsautomat (Tap/Hold-Erkennung, parryArmed). */ + private final WeaponCombatState weaponCombat = new WeaponCombatState(); private boolean combatAnimActive = false; private float combatAnimRemaining = 0f; private Runnable combatAnimCallback = null; @@ -264,6 +267,9 @@ public class PlayerInputControl { private float blockingAnimTotal = 0f; private Runnable blockingAnimCallback = null; + /** Gesetzt wenn Spieler gestorben ist – sperrt alle Eingaben. */ + private boolean playerDead = false; + /** Drehung auf der Stelle (kein Vorwärtsbewegen, nur Rotation). */ private boolean turnActive = false; private float turnRemaining = 0f; @@ -306,7 +312,7 @@ public class PlayerInputControl { private final Vector3f scratchTransl = new Vector3f(); private final ActionListener actionListener = (name, isPressed, tpf) -> { - if (paused) return; + if (paused || playerDead) return; switch (name) { case "Forward" -> forward = isPressed; case "Backward" -> backward = isPressed; @@ -464,8 +470,10 @@ public class PlayerInputControl { autopilotDir = null; if (physicsChar != null) physicsChar.setWalkDirection(Vector3f.ZERO); stopWading(); - rightMouseHeld = false; - blockPhase = BlockPhase.NONE; + rightMouseHeld = false; + blockPhase = BlockPhase.NONE; + weaponCombat.phase = WeaponCombatState.Phase.IDLE; + weaponCombat.parryArmed = false; combatAnimActive = false; combatAnimCallback = null; comboStep = 0; @@ -866,7 +874,7 @@ public class PlayerInputControl { blockingAnimRemaining -= tpf; physicsChar.setWalkDirection(Vector3f.ZERO); - if (blockingAnimRemaining <= 0f) { + if (blockingAnimRemaining <= 0f && !playerDead) { blockingAnimActive = false; Runnable cb = blockingAnimCallback; blockingAnimCallback = null; @@ -1207,7 +1215,9 @@ public class PlayerInputControl { rightMouseHeld = false; attackHoldPending = false; heavyLungeVel = null; - blockPhase = BlockPhase.NONE; + blockPhase = BlockPhase.NONE; + weaponCombat.phase = WeaponCombatState.Phase.IDLE; + weaponCombat.parryArmed = false; if (weaponSoundSystem != null) weaponSoundSystem.play("sheath"); boolean hasShield = mainCharacter != null && mainCharacter.getEquippedShield() != null; startDrawAnimation(hasShield, @@ -1290,14 +1300,18 @@ public class PlayerInputControl { if (isMeleeType(drawnWeaponType)) { if (isPressed) { if (blockPhase == BlockPhase.NONE) { - blockPhase = BlockPhase.WAITING; + blockPhase = BlockPhase.WAITING; blockPhaseTimer = 0f; + weaponCombat.onBlockPress(); } } else { switch (blockPhase) { case WAITING -> { + // Kurzer Tap: Parade-Animation abspielen (auch wenn NPC nicht im Trefferfenster) + if (weaponCombat.onBlockRelease()) { + playCounterAttack(); + } blockPhase = BlockPhase.NONE; - playCounterAttack(); } case ENTERING, HOLDING -> startBlockExit(); default -> {} @@ -1311,8 +1325,9 @@ public class PlayerInputControl { private void updateBlockPhase(float tpf) { switch (blockPhase) { case WAITING -> { - blockPhaseTimer += tpf; - if (blockPhaseTimer >= BLOCK_TAP_MAX) { + // Tap-Timer läuft in WeaponCombatState; gibt true zurück wenn Fenster abläuft + if (weaponCombat.tickParryWaiting(tpf)) { + // Tap-Fenster abgelaufen ohne Loslassen → Haltephase if (hasShieldEquipped()) { startBlockEnter(); } else { @@ -1365,6 +1380,7 @@ public class PlayerInputControl { private void playCounterAttack() { if (combatAnimActive) return; + if (weaponSoundSystem != null) { weaponSoundSystem.play("counterparade"); } AnimationAction counterAct = getMeleeCounterAttackAction(drawnWeaponType); float dur = resolveClipLength(counterAct, 0.7f); nextTransitionLength = COMBAT_BLEND; @@ -1376,6 +1392,38 @@ public class PlayerInputControl { currentAnim = counterAct; } + /** Liefert true (einmalig) wenn der Spieler die Block-Taste als Tap gedrückt hat. */ + public boolean consumeParryAttempt() { + return weaponCombat.consumeParryAttempt(); + } + + /** Spielt die Konter-Animations bei erfolgreicher Parade (nur wenn keine Combat-Animation aktiv). */ + public void triggerParrySuccess() { + playCounterAttack(); + } + + /** Spielt eine zufällige Sterbe-Animation und sperrt alle Eingaben dauerhaft. */ + public void playPlayerDeath() { + if (playerDead) { return; } + playerDead = true; + forward = backward = left = right = sprint = walk = false; + if (physicsChar != null) { physicsChar.setWalkDirection(com.jme3.math.Vector3f.ZERO); } + rightMouseHeld = false; + blockPhase = BlockPhase.NONE; + combatAnimActive = false; + comboWindowActive = false; + weaponCombat.phase = WeaponCombatState.Phase.IDLE; + weaponCombat.parryArmed = false; + AnimationAction deathAct = playerRng.nextBoolean() + ? AnimationAction.SWORD_DEATH : AnimationAction.SWORD_DEATH_2; + playAction(deathAct); + currentAnim = deathAct; + blockingAnimActive = true; + blockingAnimRemaining = 999f; + blockingAnimTotal = 999f; + blockingAnimCallback = null; + } + public boolean isBlocking() { return blockPhase == BlockPhase.ENTERING || blockPhase == BlockPhase.HOLDING @@ -1392,6 +1440,7 @@ public class PlayerInputControl { /** Wird aufgerufen wenn ein blockierter Treffer landet. Kostet 30 Ausdauer; unter 0 → Block bricht. */ public void triggerBlockImpact() { if (blockPhase != BlockPhase.HOLDING || drawnWeaponType == null) return; + if (weaponSoundSystem != null) { weaponSoundSystem.play("impactblock"); } if (mainCharacter != null && mainCharacter.getMaxStamina() > 0) { if (mainCharacter.getCurrentStamina() < STAMINA_BLOCK_HIT_COST) { mainCharacter.setCurrentStamina(0); diff --git a/blight-game/src/main/java/de/blight/game/npc/NpcAggroBehavior.java b/blight-game/src/main/java/de/blight/game/npc/NpcAggroBehavior.java index e19bf5f..a1b8dd8 100644 --- a/blight-game/src/main/java/de/blight/game/npc/NpcAggroBehavior.java +++ b/blight-game/src/main/java/de/blight/game/npc/NpcAggroBehavior.java @@ -63,6 +63,14 @@ public class NpcAggroBehavior { new TextReference("npc.aggro.break.5"), }; + public static final TextReference[] DEATH_TEXTS = { + new TextReference("npc.aggro.death.1"), + new TextReference("npc.aggro.death.2"), + new TextReference("npc.aggro.death.3"), + new TextReference("npc.aggro.death.4"), + new TextReference("npc.aggro.death.5"), + }; + /** * Audio path convention: audio/npc/{npcId}/aggro_{category}_{1-5}.ogg * category = "warn1" | "warn2" | "attack" diff --git a/blight-game/src/main/java/de/blight/game/scene/WorldScene.java b/blight-game/src/main/java/de/blight/game/scene/WorldScene.java index 8432b17..0376f17 100644 --- a/blight-game/src/main/java/de/blight/game/scene/WorldScene.java +++ b/blight-game/src/main/java/de/blight/game/scene/WorldScene.java @@ -408,6 +408,7 @@ public class WorldScene extends BaseAppState { app.getStateManager().attach(dialogHud); WorldNpcsState npcsState = new WorldNpcsState(keyBindings, physicsChar, playerInput, mc); npcsState.setThirdPersonCamera(thirdPersonCam); + npcsState.setWeaponSoundSystem(weaponSoundSystem); app.getStateManager().attach(npcsState); app.getStateManager().attach(new InteractionHudState(keyBindings)); inventoryState = new InventoryState(mc, keyBindings); diff --git a/blight-game/src/main/java/de/blight/game/state/WorldNpcsState.java b/blight-game/src/main/java/de/blight/game/state/WorldNpcsState.java index 1f0b002..d19b72d 100644 --- a/blight-game/src/main/java/de/blight/game/state/WorldNpcsState.java +++ b/blight-game/src/main/java/de/blight/game/state/WorldNpcsState.java @@ -49,6 +49,7 @@ import de.blight.game.animation.AnimationLibrary; import de.blight.game.config.KeyBindings; import de.blight.game.combat.DamageCalculator; import de.blight.game.combat.MeleeAttackCallback; +import de.blight.game.combat.WeaponCombatState; import de.blight.game.npc.AggroEvent; import de.blight.game.npc.NpcAggroBehavior; import de.blight.lang.TextResolver; @@ -96,6 +97,7 @@ public class WorldNpcsState extends BaseAppState implements MeleeAttackCallback private DayNightState dayNight; private TerrainChunkState terrainChunks; private AnimationLibrary animLib; + private de.blight.game.audio.WeaponSoundSystem weaponSoundSystem; // ── NPC-Daten ───────────────────────────────────────────────────────────── @@ -111,18 +113,6 @@ public class WorldNpcsState extends BaseAppState implements MeleeAttackCallback /** Aktuell in der Welt gespawnte NPCs. */ private final List spawned = new ArrayList<>(); - private enum NpcCombatPhase { - IDLE_COMBAT, // in Reichweite, Think-Timer läuft - CHARGING, // außer Reichweite, läuft ran - ATTACKING, // Angriffsanimation - BLOCKING, // Block-Pose, wartet auf Spieler-Treffer - COUNTER_WINDOW, // nach erfolgreichem Block → immer Konter - COUNTERING, // Konterangriff-Animation - DODGING, // Ausweichrolle - STAGGERED, // Trefferreaktion - KNOCKED_OUT // 0 HP, Umfall-Physik - } - private static class SpawnedNpc { final NPC npc; final Spatial visual; @@ -164,20 +154,15 @@ public class WorldNpcsState extends BaseAppState implements MeleeAttackCallback RigidBodyControl rbc = null; // ── Kampf-Zustand ────────────────────────────────────────────────────── - NpcCombatPhase combatPhase = NpcCombatPhase.IDLE_COMBAT; - float combatPhaseTimer = 0f; - float thinkTimer = 0f; - int comboHitsRemaining = 0; - int comboStep = 0; - float attackHitWinTimer = 0f; - float attackClipDuration = 0f; - boolean attackHitFired = false; - boolean knockedOut = false; - float knockAngle = 0f; - float knockAngVel = 0f; - float dodgeDirX = 0f; - float dodgeDirZ = 0f; - boolean npcHeavyAttack = false; + final WeaponCombatState combat = new WeaponCombatState(); + float thinkTimer = 0f; + boolean knockedOut = false; + float knockAngle = 0f; + float knockAngVel = 0f; + float dodgeDirX = 0f; + float dodgeDirZ = 0f; + float chargeTimer = 0f; + float chaseDelayTimer = 0f; de.blight.common.model.ItemSubCategory combatWeaponType = null; SpawnedNpc(NPC npc, Spatial visual, float x, float z) { @@ -272,7 +257,8 @@ public class WorldNpcsState extends BaseAppState implements MeleeAttackCallback private static final float NPC_MOVE_SPEED = CharacterNavigator.RUN_SPEED_MPS; private static final float DRAW_ANIM_DURATION = 0.9f; private static final float STEER_RAY_DIST = 1.8f; - private static final float NPC_ATTACK_RANGE = 1.5f; + private static final float NPC_ATTACK_RANGE = 2.0f; + private static final float NPC_STOP_DIST = 2.2f; // Abstand bei dem NPC stoppt und kämpft private static final float NPC_CLOSE_IN_DIST = 3.0f; private static final float NPC_CLOSE_IN_SPEED = CharacterNavigator.WALK_SPEED_MPS; @@ -285,10 +271,12 @@ public class WorldNpcsState extends BaseAppState implements MeleeAttackCallback private static final float NPC_BLOCK_CHANCE_NORMAL = 0.35f; private static final float NPC_BLOCK_CHANCE_LOW_HP = 0.15f; private static final float NPC_DODGE_CHANCE = 0.40f; - private static final float NPC_COMBO_EXTEND_P1 = 0.70f; - private static final float NPC_COMBO_EXTEND_P2 = 0.30f; - private static final float NPC_HIT_WIN_START = 0.35f; - private static final float NPC_HIT_WIN_END = 0.65f; + private static final float NPC_PARRY_CHANCE = 0.25f; + // Kombo/Trefferfenster-Konstanten aus WeaponCombatState (gemeinsam mit Spieler) + private static final float NPC_COMBO_EXTEND_P1 = WeaponCombatState.COMBO_P1; + private static final float NPC_COMBO_EXTEND_P2 = WeaponCombatState.COMBO_P2; + private static final float NPC_HIT_WIN_START = WeaponCombatState.HIT_WIN_START; + private static final float NPC_HIT_WIN_END = WeaponCombatState.HIT_WIN_END; private static final int NPC_UNARMED_DAMAGE = 8; private static final int NPC_COMBO3_DAMAGE_MULT = 2; private static final int NPC_BLOCK_STAMINA_BASE = 10; @@ -343,6 +331,10 @@ public class WorldNpcsState extends BaseAppState implements MeleeAttackCallback this.thirdPersonCam = cam; } + public void setWeaponSoundSystem(de.blight.game.audio.WeaponSoundSystem wss) { + this.weaponSoundSystem = wss; + } + // ── Lifecycle ───────────────────────────────────────────────────────────── @Override @@ -509,7 +501,6 @@ public class WorldNpcsState extends BaseAppState implements MeleeAttackCallback vis.setLocalTranslation(pos); npcsRoot.attachChild(vis); SpawnedNpc s = new SpawnedNpc(npc, vis, pos.x, pos.z); - attachNpcHpBar(s); attachNpcCapsule(s); de.blight.game.scene.WorldScene.collectPbrMaterials(vis, s.pbrMaterials); if (dayNight != null && !s.pbrMaterials.isEmpty()) { @@ -587,6 +578,7 @@ public class WorldNpcsState extends BaseAppState implements MeleeAttackCallback for (int i = 0; i < spawned.size(); i++) { SpawnedNpc s = spawned.get(i); + if (s.aggroBehavior.isAttacking()) continue; Vector3f pos = s.visual().getLocalTranslation(); float dx = pos.x - charPos.x; @@ -630,6 +622,7 @@ public class WorldNpcsState extends BaseAppState implements MeleeAttackCallback } private void startDialog(int idx) { + if (playerInput.isWeaponDrawn()) return; SpawnedNpc entry = spawned.get(idx); dialogNpc = entry; if (entry.currentAction == AnimationAction.SITTING) { @@ -1500,7 +1493,7 @@ public class WorldNpcsState extends BaseAppState implements MeleeAttackCallback } else { // Waffe gezückt: Kampf-KI initialisieren s.combatWeaponType = resolveNpcCombatWeaponType(s); - s.combatPhase = NpcCombatPhase.IDLE_COMBAT; + s.combat.phase = WeaponCombatState.Phase.IDLE; resetNpcThinkTimer(s); if (s.combatRunAnim != null) { playNpcAction(s, s.combatRunAnim); } } @@ -1556,16 +1549,37 @@ public class WorldNpcsState extends BaseAppState implements MeleeAttackCallback Vector3f npcPos = s.visual().getWorldTranslation(); float dist = dist2d(npcPos, playerPos); - if (dist <= NPC_ATTACK_RANGE) { + // Aktive Kampf-Phasen: Bewegungslogik komplett überspringen + if (s.combat.phase != WeaponCombatState.Phase.IDLE + && s.combat.phase != WeaponCombatState.Phase.CHARGING) { faceToward(s, playerPos); if (s.weaponDrawn) { updateNpcCombatPhase(s, tpf, playerPos); } continue; } - // Außerhalb Angriffsreichweite → CHARGING-Phase - if (s.combatPhase != NpcCombatPhase.CHARGING - && s.combatPhase != NpcCombatPhase.DODGING) { - s.combatPhase = NpcCombatPhase.CHARGING; + // Charging-Timer: nach 1.5s Stuck trotzdem angreifen + if (s.combat.phase == WeaponCombatState.Phase.CHARGING) { + s.chargeTimer += tpf; + } else { + s.chargeTimer = 0f; + } + + // In Kampf-Stopp-Distanz oder Stuck-Timeout → IDLE + Entscheidung + if (dist <= NPC_STOP_DIST || s.chargeTimer >= 1.5f) { + faceToward(s, playerPos); + s.chargeTimer = 0f; + if (s.combat.phase == WeaponCombatState.Phase.CHARGING) { + s.combat.phase = WeaponCombatState.Phase.IDLE; + resetNpcThinkTimer(s); + } + if (s.weaponDrawn) { updateNpcCombatPhase(s, tpf, playerPos); } + continue; + } + + // Außerhalb Angriffsreichweite → CHARGING + if (s.combat.phase == WeaponCombatState.Phase.IDLE) { + s.combat.phase = WeaponCombatState.Phase.CHARGING; + s.chargeTimer = 0f; } // Close-in: Spieler ≤ 3m und flieht nicht → NPC geht langsam ran @@ -1651,56 +1665,6 @@ public class WorldNpcsState extends BaseAppState implements MeleeAttackCallback return hitDist >= STEER_RAY_DIST; } - // ── NPC HP-Balken ───────────────────────────────────────────────────────── - - private void attachNpcHpBar(SpawnedNpc s) { - Node hpBarNode = buildWorldHpBar(); - hpBarNode.setLocalTranslation(0f, WB_Y_OFFSET, 0f); - hpBarNode.setCullHint(Spatial.CullHint.Always); - ((Node) s.visual()).attachChild(hpBarNode); - s.hpBarNode = hpBarNode; - s.hpBarFg = (Geometry) ((Node) hpBarNode.getChild(0)).getChild(1); - } - - private Node buildWorldHpBar() { - Node hpBarNode = new Node("npcHpBar"); - Node billChild = new Node("npcHpBillboard"); - billChild.addControl(new BillboardControl()); - - Geometry bg = new Geometry("npcHpBg", new Quad(WB_WIDTH, WB_HEIGHT)); - bg.setLocalTranslation(-WB_WIDTH * 0.5f, -WB_HEIGHT * 0.5f, 0f); - Material bgMat = new Material(assets, "Common/MatDefs/Misc/Unshaded.j3md"); - bgMat.setColor("Color", new ColorRGBA(0.15f, 0.15f, 0.15f, 0.85f)); - bgMat.getAdditionalRenderState().setBlendMode(RenderState.BlendMode.Alpha); - bg.setMaterial(bgMat); - bg.setQueueBucket(RenderQueue.Bucket.Transparent); - billChild.attachChild(bg); - - Geometry fg = new Geometry("npcHpFg", new Quad(WB_WIDTH, WB_HEIGHT - 0.01f)); - fg.setLocalTranslation(-WB_WIDTH * 0.5f, -WB_HEIGHT * 0.5f + 0.005f, 0.001f); - Material fgMat = new Material(assets, "Common/MatDefs/Misc/Unshaded.j3md"); - fgMat.setColor("Color", new ColorRGBA(0.2f, 0.8f, 0.2f, 1f)); - fg.setMaterial(fgMat); - fg.setQueueBucket(RenderQueue.Bucket.Transparent); - billChild.attachChild(fg); - - hpBarNode.attachChild(billChild); - return hpBarNode; - } - - private void refreshNpcHpBar(SpawnedNpc s) { - if (s.hpBarFg == null) { return; } - float frac = (float) s.currentHp / s.maxHp; - frac = Math.max(0f, frac); - ColorRGBA col; - if (frac > 0.5f) col = new ColorRGBA(0.2f, 0.8f, 0.2f, 1f); - else if (frac > 0.2f) col = new ColorRGBA(0.9f, 0.8f, 0.1f, 1f); - else col = new ColorRGBA(0.9f, 0.1f, 0.1f, 1f); - s.hpBarFg.getMaterial().setColor("Color", col); - s.hpBarFg.setLocalScale(Math.max(0.001f, frac), 1f, 1f); - if (s.hpBarNode != null) { s.hpBarNode.setCullHint(Spatial.CullHint.Inherit); } - } - // ── NPC Physik-Kapsel ───────────────────────────────────────────────────── private void attachNpcCapsule(SpawnedNpc s) { @@ -1752,7 +1716,24 @@ public class WorldNpcsState extends BaseAppState implements MeleeAttackCallback private void applyNpcDamage(SpawnedNpc s, int damage, boolean isCrit, boolean isHeavy, float critMultiplier) { if (s.knockedOut) { return; } - if (s.combatPhase == NpcCombatPhase.BLOCKING) { + // NPC Konter-Parade: reaktive Parade gegen normalen Angriff (nicht schwer, nicht bereits blockend) + if (!isHeavy + && (s.combat.phase == WeaponCombatState.Phase.IDLE + || s.combat.phase == WeaponCombatState.Phase.CHARGING) + && npcRng.nextFloat() < NPC_PARRY_CHANCE) { + AnimationAction blockImpact = resolveNpcBlockImpactAction(s); + float dur = resolveNpcClipLength(s, blockImpact, NPC_STAGGER_FALLBACK); + s.currentAction = null; + playNpcAction(s, blockImpact); + setNpcCurrentActionSpeed(s, 0.5f); + s.combat.phase = WeaponCombatState.Phase.COUNTER_WINDOW; + s.combat.timer = dur * 2f; + AnimationAction playerImpact = resolvePlayerImpactAction(); + if (playerImpact != null) playerInput.requestAnimation(playerImpact, NPC_STAGGER_FALLBACK, null); + return; + } + + if (s.combat.phase == WeaponCombatState.Phase.BLOCKING) { if (isHeavy) { // Schwerer Angriff: Block bricht, kein Schaden — NPC weicht ggf. aus if (npcRng.nextFloat() < NPC_DODGE_CHANCE) { @@ -1765,16 +1746,16 @@ public class WorldNpcsState extends BaseAppState implements MeleeAttackCallback int cost = Math.round(NPC_BLOCK_STAMINA_BASE * critMultiplier); s.currentStamina = Math.max(0, s.currentStamina - cost); if (s.currentStamina <= 0) { - // Stamina leer: Block bricht staggerNpc(s); } else { // Block hält: Impact-Animation + Konter AnimationAction impact = resolveNpcBlockImpactAction(s); float dur = resolveNpcClipLength(s, impact, NPC_CLIP_FALLBACK_BLOCK); + s.currentAction = null; playNpcAction(s, impact); playerInput.triggerBlockImpact(); - s.combatPhase = NpcCombatPhase.COUNTER_WINDOW; - s.combatPhaseTimer = dur; + s.combat.phase = WeaponCombatState.Phase.COUNTER_WINDOW; + s.combat.timer = dur; } } return; // kein HP-Verlust bei geblockt @@ -1782,7 +1763,7 @@ public class WorldNpcsState extends BaseAppState implements MeleeAttackCallback // Nicht blockend: Schaden anwenden s.currentHp = Math.max(0, s.currentHp - damage); - refreshNpcHpBar(s); + if (weaponSoundSystem != null) { weaponSoundSystem.play(isCrit ? "crit" : "impact"); } log.info("[NPC {}] -{} HP → {}/{}{}", s.npc().getCharacterId(), damage, s.currentHp, s.maxHp, s.currentHp <= 0 ? " K.O.!" : ""); if (s.currentHp <= 0) { @@ -1893,23 +1874,28 @@ public class WorldNpcsState extends BaseAppState implements MeleeAttackCallback // ── NPC Kampf-KI ───────────────────────────────────────────────────────── private void updateNpcCombatPhase(SpawnedNpc s, float tpf, Vector3f playerPos) { - switch (s.combatPhase) { + switch (s.combat.phase) { case KNOCKED_OUT -> { // DEATH-Animation läuft einmalig (DontLoop) und friert auf dem letzten Frame ein. } - case IDLE_COMBAT -> { + case IDLE -> { playNpcAction(s, resolveNpcIdleAction(s)); + Vector3f npcPos = s.visual().getWorldTranslation(); + float dist = dist2d(npcPos, playerPos); + if (dist > NPC_STOP_DIST) { + s.chaseDelayTimer += tpf; + if (s.chaseDelayTimer >= 0.5f) { + s.combat.phase = WeaponCombatState.Phase.CHARGING; + s.chaseDelayTimer = 0f; + } + return; + } + s.chaseDelayTimer = 0f; s.thinkTimer -= tpf; if (s.thinkTimer > 0f) { return; } // Think-Timer abgelaufen → Entscheidung - Vector3f npcPos = s.visual().getWorldTranslation(); - float dist = dist2d(npcPos, playerPos); - if (dist > NPC_ATTACK_RANGE) { - s.combatPhase = NpcCombatPhase.CHARGING; - return; - } boolean lowHp = (float) s.currentHp / s.maxHp < NPC_LOW_HP_THRESHOLD; float blockChance = lowHp ? NPC_BLOCK_CHANCE_LOW_HP : NPC_BLOCK_CHANCE_NORMAL; float heavyChance = heavyAttackChance(); @@ -1924,63 +1910,65 @@ public class WorldNpcsState extends BaseAppState implements MeleeAttackCallback case CHARGING -> { // Wird durch äußere Bewegungslogik in updateNpcCombat verwaltet. - // Wenn in Reichweite, wechselt updateNpcCombat zu IDLE_COMBAT + resetTimer. + // Wenn in Reichweite, wechselt updateNpcCombat zu IDLE + resetTimer. } case ATTACKING, COUNTERING -> { - s.combatPhaseTimer -= tpf; - s.attackHitWinTimer += tpf; - float progress = s.attackClipDuration > 0f - ? s.attackHitWinTimer / s.attackClipDuration : 1f; - if (!s.attackHitFired - && progress >= NPC_HIT_WIN_START - && progress <= NPC_HIT_WIN_END) { - s.attackHitFired = true; + boolean done = s.combat.tickAttack(tpf); + // Konter-Parade: Spieler tippt Block im Trefferfenster → Stagger (langsam), kein Schaden + if (!s.combat.isHeavy && !s.combat.hitFired + && s.combat.inHitWindow() + && playerInput.consumeParryAttempt()) { + staggerNpcSlow(s); + playerInput.triggerParrySuccess(); + return; + } + if (!s.combat.hitFired && s.combat.inHitWindow()) { + s.combat.hitFired = true; fireNpcAttack(s, playerPos); } - if (s.combatPhaseTimer <= 0f) { - if (s.combatPhase == NpcCombatPhase.ATTACKING - && s.comboHitsRemaining > 0) { + if (done) { + if (s.combat.phase == WeaponCombatState.Phase.ATTACKING) { + // Nach jedem Treffer: Chance auf nächsten Kombo-Hit (wie Spieler) + int next = s.combat.nextComboStep(npcRng); Vector3f npcPos = s.visual().getWorldTranslation(); - if (dist2d(npcPos, playerPos) <= NPC_ATTACK_RANGE + 0.5f) { - s.comboHitsRemaining--; - s.comboStep++; - startNpcAttackStep(s); + if (next >= 0 && dist2d(npcPos, playerPos) <= NPC_ATTACK_RANGE + 0.5f) { + startNpcAttackStep(s, next); return; } } - s.combatPhase = NpcCombatPhase.IDLE_COMBAT; + s.combat.phase = WeaponCombatState.Phase.IDLE; resetNpcThinkTimer(s); } } case BLOCKING -> { playNpcAction(s, resolveNpcBlockHoldAction(s)); - s.combatPhaseTimer -= tpf; - if (s.combatPhaseTimer <= 0f || s.currentStamina <= 0) { - s.combatPhase = NpcCombatPhase.IDLE_COMBAT; + s.combat.timer -= tpf; + if (s.combat.timer <= 0f || s.currentStamina <= 0) { + s.combat.phase = WeaponCombatState.Phase.IDLE; resetNpcThinkTimer(s); } } case COUNTER_WINDOW -> { // Immer sofort kontern - s.combatPhaseTimer -= tpf; - if (s.combatPhaseTimer <= 0f) { + s.combat.timer -= tpf; + if (s.combat.timer <= 0f) { startNpcCounter(s); } } case STAGGERED -> { - s.combatPhaseTimer -= tpf; - if (s.combatPhaseTimer <= 0f) { - s.combatPhase = NpcCombatPhase.IDLE_COMBAT; + s.combat.timer -= tpf; + if (s.combat.timer <= 0f) { + s.combat.phase = WeaponCombatState.Phase.IDLE; resetNpcThinkTimer(s); } } case DODGING -> { - s.combatPhaseTimer -= tpf; + s.combat.timer -= tpf; // Seitliche Ausweich-Bewegung (bereits in startNpcDodge gesetzt) if (s.dodgeDirX != 0f || s.dodgeDirZ != 0f) { Vector3f npcPos = s.visual().getWorldTranslation(); @@ -1989,47 +1977,44 @@ public class WorldNpcsState extends BaseAppState implements MeleeAttackCallback float ny = terrainChunks != null ? terrainChunks.getHeightAt(nx, nz) : npcPos.y; s.visual().setLocalTranslation(nx, ny, nz); } - if (s.combatPhaseTimer <= 0f) { + if (s.combat.timer <= 0f) { s.dodgeDirX = 0f; s.dodgeDirZ = 0f; - s.combatPhase = NpcCombatPhase.IDLE_COMBAT; + s.combat.phase = WeaponCombatState.Phase.IDLE; resetNpcThinkTimer(s); } } + + default -> {} } } /** Steigt nach 2 s dauerhaftem Block: +10 %/s, max. 60 %. */ private float heavyAttackChance() { float t = Math.max(0f, playerBlockTimer - 2f); - return Math.min(0.60f, t * 0.10f); + return Math.min(0.60f, 0.15f + t * 0.10f); } private void startNpcHeavyAttack(SpawnedNpc s) { - s.comboHitsRemaining = 0; - s.comboStep = 2; // ATTACK_3 animation - s.npcHeavyAttack = true; - startNpcAttackStep(s); + // Schwerer Angriff beginnt immer mit Schritt 0 (ATTACK_1 Animation); + // ATTACK_3 ist nur über Kombo (Schritt 0→1→2) erreichbar, wie beim Spieler. + startNpcAttackStep(s, 0, true); } private void startNpcAttackSequence(SpawnedNpc s) { - s.comboStep = 0; - int hits = 1; - if (npcRng.nextFloat() < NPC_COMBO_EXTEND_P1) { hits++; } - if (hits == 2 && npcRng.nextFloat() < NPC_COMBO_EXTEND_P2) { hits++; } - s.comboHitsRemaining = hits - 1; - startNpcAttackStep(s); + startNpcAttackStep(s, 0); } - private void startNpcAttackStep(SpawnedNpc s) { - AnimationAction atk = resolveNpcAttackAction(s, s.comboStep); + private void startNpcAttackStep(SpawnedNpc s, int step) { + startNpcAttackStep(s, step, false); + } + + private void startNpcAttackStep(SpawnedNpc s, int step, boolean heavy) { + AnimationAction atk = resolveNpcAttackAction(s, step); float dur = resolveNpcClipLength(s, atk, NPC_CLIP_FALLBACK_ATK); + s.currentAction = null; // Dedup umgehen damit Combo-Steps sich nicht blockieren playNpcAction(s, atk); - s.combatPhase = NpcCombatPhase.ATTACKING; - s.combatPhaseTimer = dur; - s.attackClipDuration = dur; - s.attackHitWinTimer = 0f; - s.attackHitFired = false; + s.combat.beginAttack(step, heavy, dur); } private void startNpcBlock(SpawnedNpc s) { @@ -2037,19 +2022,16 @@ public class WorldNpcsState extends BaseAppState implements MeleeAttackCallback float entryDur = resolveNpcClipLength(s, blockEntry, NPC_CLIP_FALLBACK_BLOCK); float holdTime = 0.5f + npcRng.nextFloat() * 1.5f; playNpcAction(s, blockEntry); - s.combatPhase = NpcCombatPhase.BLOCKING; - s.combatPhaseTimer = entryDur + holdTime; + s.combat.phase = WeaponCombatState.Phase.BLOCKING; + s.combat.timer = entryDur + holdTime; } private void startNpcCounter(SpawnedNpc s) { AnimationAction ctr = resolveNpcCounterAction(s); float dur = resolveNpcClipLength(s, ctr, NPC_CLIP_FALLBACK_CTR); + s.currentAction = null; playNpcAction(s, ctr); - s.combatPhase = NpcCombatPhase.COUNTERING; - s.combatPhaseTimer = dur; - s.attackClipDuration = dur; - s.attackHitWinTimer = 0f; - s.attackHitFired = false; + s.combat.beginCounter(dur); } private void startNpcDodge(SpawnedNpc s) { @@ -2065,23 +2047,37 @@ public class WorldNpcsState extends BaseAppState implements MeleeAttackCallback float sign = npcRng.nextBoolean() ? 1f : -1f; s.dodgeDirX = (len > 0.001f) ? sign * (dz / len) : 1f; s.dodgeDirZ = (len > 0.001f) ? sign * (-dx / len) : 0f; - s.combatPhase = NpcCombatPhase.DODGING; - s.combatPhaseTimer = dur; + s.combat.phase = WeaponCombatState.Phase.DODGING; + s.combat.timer = dur; } private void staggerNpc(SpawnedNpc s) { - s.attackHitFired = false; + AnimationAction stagger = resolveNpcStaggerAction(s); + float dur = resolveNpcClipLength(s, stagger, NPC_STAGGER_FALLBACK); + s.currentAction = null; + playNpcAction(s, stagger); + s.combat.stagger(dur); + } + + private void staggerNpcSlow(SpawnedNpc s) { + s.currentAction = null; AnimationAction stagger = resolveNpcStaggerAction(s); float dur = resolveNpcClipLength(s, stagger, NPC_STAGGER_FALLBACK); playNpcAction(s, stagger); - s.combatPhase = NpcCombatPhase.STAGGERED; - s.combatPhaseTimer = dur; + setNpcCurrentActionSpeed(s, 0.5f); + s.combat.stagger(dur * 2f); + } + + private void setNpcCurrentActionSpeed(SpawnedNpc s, float speed) { + AnimComposer ac = de.blight.game.animation.RetargetingSystem.findAnimComposer(s.visual()); + if (ac == null) { return; } + com.jme3.anim.tween.action.Action a = ac.getLayer(AnimComposer.DEFAULT_LAYER).getCurrentAction(); + if (a != null) { a.setSpeed(speed); } } private void knockoutNpc(SpawnedNpc s) { - s.knockedOut = true; - s.combatPhase = NpcCombatPhase.KNOCKED_OUT; - if (s.hpBarNode != null) { s.hpBarNode.setCullHint(Spatial.CullHint.Always); } + s.knockedOut = true; + s.combat.phase = WeaponCombatState.Phase.KNOCKED_OUT; s.aggroBehavior.forceCalm(); playNpcDeathAnim(s); log.info("[NPC {}] K.O.!", s.npc().getCharacterId()); @@ -2112,39 +2108,46 @@ public class WorldNpcsState extends BaseAppState implements MeleeAttackCallback if (dist2d(npcPos, playerPos) > NPC_ATTACK_RANGE + 0.3f) { return; } Item weapon = findItemById(s.npc().getEquippedWeaponId()); int base = (weapon != null && weapon.getDamage() > 0) ? weapon.getDamage() : NPC_UNARMED_DAMAGE; - boolean isHeavy = s.npcHeavyAttack; - s.npcHeavyAttack = false; // verbraucht + boolean isHeavy = s.combat.isHeavy; if (isHeavy) { if (playerInput.isBlocking()) { // Schwerer Angriff bricht Block — kein HP-Verlust, aber Stagger + if (weaponSoundSystem != null) { weaponSoundSystem.play("impact"); } playerInput.triggerBlockImpact(); AnimationAction impact = resolvePlayerImpactAction(); if (impact != null) { playerInput.requestAnimation(impact, NPC_STAGGER_FALLBACK, null); } } else { - applyPlayerDamage(Math.max(1, base * 3)); + if (weaponSoundSystem != null) { weaponSoundSystem.play("impact"); } + applyPlayerDamage(s, Math.max(1, base * 3)); AnimationAction impact = resolvePlayerImpactAction(); if (impact != null) { playerInput.requestAnimation(impact, NPC_STAGGER_FALLBACK, null); } } return; } - int damage = Math.max(1, base * (s.comboStep == 2 ? NPC_COMBO3_DAMAGE_MULT : 1)); + int damage = Math.max(1, base * (s.combat.comboStep == 2 ? NPC_COMBO3_DAMAGE_MULT : 1)); if (playerInput.isBlocking()) { + if (weaponSoundSystem != null) { weaponSoundSystem.play("impact"); } playerInput.triggerBlockImpact(); // Konter überspringt Block nicht — normaler Schlag absorbiert - if (s.combatPhase != NpcCombatPhase.COUNTERING) { return; } + if (s.combat.phase != WeaponCombatState.Phase.COUNTERING) { return; } } - applyPlayerDamage(damage); + if (weaponSoundSystem != null) { weaponSoundSystem.play("impact"); } + applyPlayerDamage(s, damage); AnimationAction impact = resolvePlayerImpactAction(); if (impact != null) { playerInput.requestAnimation(impact, NPC_STAGGER_FALLBACK, null); } } - private void applyPlayerDamage(int damage) { + private void applyPlayerDamage(SpawnedNpc killer, int damage) { if (mainCharacter == null) { return; } int hp = Math.max(0, mainCharacter.getCurrentHp() - damage); mainCharacter.setCurrentHp(hp); log.info("[NPC→Player] -{} HP → {}/{}", damage, hp, mainCharacter.getMaxHp()); + if (hp <= 0 && playerInput != null) { + playerInput.playPlayerDeath(); + if (killer != null) { showNpcSpeech(killer, NpcAggroBehavior.DEATH_TEXTS, "death"); } + } } private void resetNpcThinkTimer(SpawnedNpc s) { diff --git a/blight-lang/src/main/resources/lang/messages_de.properties b/blight-lang/src/main/resources/lang/messages_de.properties index 10c3aae..46b3c7d 100644 --- a/blight-lang/src/main/resources/lang/messages_de.properties +++ b/blight-lang/src/main/resources/lang/messages_de.properties @@ -276,6 +276,12 @@ npc.aggro.break.3=Lauf nur! Du kannst nicht für immer fliehen! npc.aggro.break.4=Beim nächsten Mal kommst du nicht so leicht davon! npc.aggro.break.5=Wir sehen uns wieder – darauf kannst du dich verlassen! +npc.aggro.death.1=Du wolltest es doch so. +npc.aggro.death.2=Hättest du lieber zu Hause geblieben. +npc.aggro.death.3=Deine Klinge hat dich nicht gerettet. +npc.aggro.death.4=Ein tapferer, aber dummer Tod. +npc.aggro.death.5=Das nächste Mal bleib, wo du hingehörst. + npc.bandit1.name=Bandit npc.bjoernson.name=Bjoernson npc.silas.name=Silas diff --git a/blight-lang/src/main/resources/lang/messages_en.properties b/blight-lang/src/main/resources/lang/messages_en.properties index 39e2c57..5b242ec 100644 --- a/blight-lang/src/main/resources/lang/messages_en.properties +++ b/blight-lang/src/main/resources/lang/messages_en.properties @@ -131,6 +131,12 @@ npc.aggro.break.3=Run! You can't hide forever! npc.aggro.break.4=Next time you won't get off so easily! npc.aggro.break.5=We'll meet again – count on it! +npc.aggro.death.1=You had this coming. +npc.aggro.death.2=Should have stayed home. +npc.aggro.death.3=Your blade couldn't save you. +npc.aggro.death.4=A brave, but foolish end. +npc.aggro.death.5=Next time, know your place. + npc.bandit1.name=Bandit npc.bjoernson.name=Bjoernson npc.silas.name=Silas