Ausdauer System implementiert

This commit is contained in:
2026-09-23 20:58:33 +02:00
parent ed0d622bfd
commit 0a1c7cd835
2 changed files with 94 additions and 1 deletions

View File

@@ -56,6 +56,14 @@ public class PlayerInputControl {
private static final float LUNGE_FRAC_END = 0.55f;
/** AnimComposer-Layer-Name für die prozedurale Ziehen/Wegstecken-Animation (Oberarm-Overlay). */
private static final String DRAW_LAYER = "__draw_weapon__";
// ── Ausdauer ──────────────────────────────────────────────────────────────
private static final float STAMINA_REGEN_PER_SEC = 5f;
private static final float STAMINA_SPRINT_PER_SEC = 10f;
private static final int STAMINA_ATTACK_COST = 10;
private static final int STAMINA_ATTACK5_COST = 20;
private static final int STAMINA_HEAVY_COST = 30;
private static final int STAMINA_BLOCK_HIT_COST = 30;
/** Dauer der Ziehen/Wegstecken-Animation in Sekunden. */
private static final float DRAW_DURATION = 0.85f;
// Ziel-Quaternions für die Ziehen-Animation: aus Heavy-Attack-Wind-Up bei t=0.933s gelesen
@@ -147,6 +155,8 @@ public class PlayerInputControl {
private static final float ATTACK5_SOUND_MIN_PROGRESS = 0.35f;
private boolean attackSoundFired = false;
private int currentComboStep = 0;
private float staminaAccum = 0f; // Fließkomma-Übertrag für kontinuierliche Ausdauer-Änderungen
private boolean staminaExhausted = false; // Sprint gesperrt bis 25% Ausdauer wiederhergestellt
private final Vector3f lastGripPos = new Vector3f();
private final Vector3f scratchGripVel = new Vector3f();
// Draw/Sheath Animation System
@@ -793,10 +803,17 @@ public class PlayerInputControl {
// Schon ab WAITING Gehgeschwindigkeit erzwingen, damit beim Blockstart kein Sprint-Frame entsteht
boolean inBlockStance = isBlocking() || blockPhase == BlockPhase.WAITING;
// Ausdauer: Regen + Sprint-Verbrauch
boolean isSprinting = sprint && !weaponDrawn && moving && !staminaExhausted;
tickStamina(tpf, isSprinting);
if (moving) {
scratchMoveDir.normalizeLocal();
boolean canSprint = sprint && !weaponDrawn && !staminaExhausted
&& (mainCharacter == null || mainCharacter.getMaxStamina() <= 0
|| mainCharacter.getCurrentStamina() > 0);
float speed = (inDeepWater || walk || inBlockStance) ? MOVE_SPEED * WALK_MULT
: (sprint && !weaponDrawn) ? MOVE_SPEED * SPRINT_MULT
: canSprint ? MOVE_SPEED * SPRINT_MULT
: MOVE_SPEED;
physicsChar.setWalkDirection(scratchWalk.set(scratchMoveDir).multLocal(speed));
@@ -1041,6 +1058,8 @@ public class PlayerInputControl {
if (isMeleeType(drawnWeaponType)) {
int step = comboStep;
boolean isComboFollow = comboWindowActive; // war Kombo-Fenster offen?
int staminaCost = (step == 4) ? STAMINA_ATTACK5_COST : STAMINA_ATTACK_COST;
if (!spendStamina(staminaCost)) return;
AnimationAction atk = getMeleeComboAction(drawnWeaponType, step);
lastAttackCritical = (step == 4);
currentAttackId = ++attackIdCounter;
@@ -1197,6 +1216,61 @@ public class PlayerInputControl {
|| blockPhase == BlockPhase.EXITING;
}
/** 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 (mainCharacter != null && mainCharacter.getMaxStamina() > 0) {
if (mainCharacter.getCurrentStamina() < STAMINA_BLOCK_HIT_COST) {
mainCharacter.setCurrentStamina(0);
startBlockExit();
return;
}
mainCharacter.setCurrentStamina(mainCharacter.getCurrentStamina() - STAMINA_BLOCK_HIT_COST);
}
AnimationAction impact = getMeleeBlockImpactAction(drawnWeaponType);
float dur = resolveClipLength(impact, 0.8f);
hitWindowActive = false;
attackSoundFired = true;
combatAnimActive = true;
combatAnimRemaining = Math.max(0.1f, dur - COMBAT_BLEND);
combatAnimDuration = dur;
combatAnimCallback = null;
combatAnimSkipComboWindow = true;
nextTransitionLength = COMBAT_BLEND;
playAction(impact);
currentAnim = impact;
}
// ── Ausdauer-Hilfsmethoden ────────────────────────────────────────────────
private void tickStamina(float tpf, boolean isSprinting) {
if (mainCharacter == null || mainCharacter.getMaxStamina() <= 0) return;
float delta = STAMINA_REGEN_PER_SEC * tpf;
if (isSprinting) delta -= STAMINA_SPRINT_PER_SEC * tpf;
staminaAccum += delta;
int intPart = (int) staminaAccum;
if (intPart != 0) {
staminaAccum -= intPart;
int cur = mainCharacter.getCurrentStamina() + intPart;
mainCharacter.setCurrentStamina(Math.max(0, Math.min(mainCharacter.getMaxStamina(), cur)));
}
int stamina = mainCharacter.getCurrentStamina();
int max = mainCharacter.getMaxStamina();
if (!staminaExhausted && stamina == 0) {
staminaExhausted = true;
} else if (staminaExhausted && stamina >= max / 4) {
staminaExhausted = false;
}
}
/** Zieht {@code cost} Ausdauer ab. Gibt false zurück (Aktion verweigert) wenn nicht genug vorhanden. */
private boolean spendStamina(int cost) {
if (mainCharacter == null || mainCharacter.getMaxStamina() <= 0) return true;
if (mainCharacter.getCurrentStamina() < cost) return false;
mainCharacter.setCurrentStamina(mainCharacter.getCurrentStamina() - cost);
return true;
}
private boolean hasShieldEquipped() {
return mainCharacter != null && mainCharacter.getEquippedShield() != null;
}
@@ -1394,6 +1468,7 @@ public class PlayerInputControl {
if (drawnWeaponType == null || !isMeleeType(drawnWeaponType)) {
return;
}
if (!spendStamina(STAMINA_HEAVY_COST)) return;
comboStep = 0;
comboWindowActive = false;
AnimationAction action = getMeleeHeavyAttackAction(drawnWeaponType);

View File

@@ -365,6 +365,24 @@ public class WorldScene extends BaseAppState {
MainCharacter mc = findMainCharacter();
if (mc != null) {
if (mc.getMaxHp() <= 0) {
mc.setMaxHp(100);
mc.setCurrentHp(100);
} else if (mc.getCurrentHp() <= 0) {
mc.setCurrentHp(mc.getMaxHp());
}
if (mc.getMaxMana() <= 0) {
mc.setMaxMana(100);
mc.setCurrentMana(100);
} else if (mc.getCurrentMana() <= 0) {
mc.setCurrentMana(mc.getMaxMana());
}
if (mc.getMaxStamina() <= 0) {
mc.setMaxStamina(100);
mc.setCurrentStamina(100);
} else if (mc.getCurrentStamina() <= 0) {
mc.setCurrentStamina(mc.getMaxStamina());
}
playerInput.setMainCharacter(mc);
var wac = new de.blight.game.control.WeaponAttachmentControl(assetManager, playerInput, mc);
character.addControl(wac);