Schleichen hinzugefügt, Bugs bei den Sounds behoben

This commit is contained in:
2026-09-24 16:37:55 +02:00
parent f457b897bd
commit 94e0cac10d
12 changed files with 104 additions and 17 deletions

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@@ -26,7 +26,11 @@
"Sword And Shield Strafe Right",
"Sword And Shield Heavy Attack",
"Picking Up",
"Roll"
"Roll",
"Crouched To Standing",
"Crouching Idle",
"Sneaking Forward",
"Standing To Crouched"
],
"actionMap": {
"DEFAULT": "Idle",
@@ -55,7 +59,11 @@
"SWORD_BLOCK_STRAFE_RIGHT": "Sword And Shield Strafe Right",
"SWORD_HEAVY_ATTACK": "Sword And Shield Heavy Attack",
"PICK_UP": "Picking Up",
"ROLL": "Roll"
"ROLL": "Roll",
"DUCK": "Standing To Crouched",
"CROUCH_IDLE": "Crouching Idle",
"SNEAK": "Sneaking Forward",
"CROUCH_TO_STAND": "Crouched To Standing"
},
"previewModelPath": "Characters/Models/hero.j3o",
"animOffsets": {
@@ -116,6 +124,7 @@
"Sword And Shield Strafe Left",
"Sword And Shield Strafe Right",
"Sword And Shield Heavy Attack",
"Roll"
"Roll",
"Sneaking Forward"
]
}

View File

@@ -16,6 +16,9 @@ public enum AnimationAction {
RUNNING_JUMP,
ROLL,
DUCK,
CROUCH_IDLE,
CROUCH_TO_STAND,
SNEAK,
PICK_UP,
LIE_DOWN,
LIE_UP,
@@ -80,7 +83,10 @@ public enum AnimationAction {
case JUMP -> TextRegistry.resolve(null, key, "Jump");
case RUNNING_JUMP -> TextRegistry.resolve(null, key, "Running Jump");
case ROLL -> TextRegistry.resolve(null, key, "Rolle");
case DUCK -> TextRegistry.resolve(null, key, "Duck");
case DUCK -> TextRegistry.resolve(null, key, "Hinducken");
case CROUCH_IDLE -> TextRegistry.resolve(null, key, "Ducken (Idle)");
case CROUCH_TO_STAND -> TextRegistry.resolve(null, key, "Aufstehen (Ducken)");
case SNEAK -> TextRegistry.resolve(null, key, "Schleichen");
case PICK_UP -> TextRegistry.resolve(null, key, "Pick up");
case LIE_DOWN -> TextRegistry.resolve(null, key, "Hinlegen");
case LIE_UP -> TextRegistry.resolve(null, key, "Aufstehen (Bett)");

View File

@@ -23,6 +23,7 @@ public class KeyBindings {
public int drawRangedWeapon = KeyInput.KEY_2;
public int sheathWeapon = KeyInput.KEY_X;
public int roll = KeyInput.KEY_LCONTROL;
public int sneak = KeyInput.KEY_X;
/** Metadaten für die Config-UI: Feldname im Objekt + Anzeigename. */
public static final String[][] ENTRIES = {
@@ -44,6 +45,7 @@ public class KeyBindings {
{"drawRangedWeapon", "Fernkampfwaffe ziehen"},
{"sheathWeapon", "Waffe wegstecken"},
{"roll", "Rolle"},
{"sneak", "Schleichen"},
};
public int get(String fieldName) {

View File

@@ -103,11 +103,12 @@ public class PlayerInputControl {
private static final String ACT_COMBAT_ATK = "CombatAttack";
private static final String ACT_COMBAT_ALT = "CombatAltAction";
private static final String ACT_ROLL = "Roll";
private static final String ACT_SNEAK = "Sneak";
private static final String[] ACTION_NAMES = {
"Forward", "Backward", "Left", "Right", "Jump", "Sprint", "Walk",
ACT_DRAW_MELEE, ACT_DRAW_RANGED, ACT_SHEATH, ACT_COMBAT_ATK, ACT_COMBAT_ALT,
ACT_ROLL
ACT_ROLL, ACT_SNEAK
};
private final InputManager inputManager;
@@ -273,6 +274,12 @@ public class PlayerInputControl {
/** Autopilot: wenn gesetzt, geht der Charakter automatisch in diese (normalisierte) Richtung. */
private Vector3f autopilotDir = null;
// ── Schleichen / Ducken ───────────────────────────────────────────────────
private enum SneakPhase { NONE, DOWN, ACTIVE, UP }
private SneakPhase sneakPhase = SneakPhase.NONE;
private float sneakTransitionRemaining = 0f;
private boolean sneakKeyHeld = false;
// ── Dodge Roll ────────────────────────────────────────────────────────────
private boolean rollActive = false;
private float rollRemaining = 0f;
@@ -333,7 +340,8 @@ public class PlayerInputControl {
}
}
case ACT_COMBAT_ALT -> handleCombatAlt(isPressed);
case ACT_ROLL -> { if (isPressed) triggerRollFromInput(); }
case ACT_ROLL -> { if (isPressed) triggerRollFromInput(); }
case ACT_SNEAK -> { if (!weaponDrawn) handleSneakKey(isPressed); }
}
};
@@ -712,6 +720,7 @@ public class PlayerInputControl {
inputManager.addMapping(ACT_COMBAT_ATK, new MouseButtonTrigger(MouseInput.BUTTON_LEFT));
inputManager.addMapping(ACT_COMBAT_ALT, new MouseButtonTrigger(MouseInput.BUTTON_RIGHT));
inputManager.addMapping(ACT_ROLL, new KeyTrigger(kb.roll));
inputManager.addMapping(ACT_SNEAK, new KeyTrigger(kb.sneak));
inputManager.addListener(actionListener, ACTION_NAMES);
}
@@ -844,6 +853,25 @@ public class PlayerInputControl {
}
}
// ── Sneak-Übergangs-Phasen: Timer ticken, Bewegung läuft weiter (walk speed) ──
if (sneakPhase == SneakPhase.DOWN) {
sneakTransitionRemaining -= tpf;
if (sneakTransitionRemaining <= 0f) {
if (sneakKeyHeld) {
sneakPhase = SneakPhase.ACTIVE;
currentAnim = null;
} else {
startSneakUp();
}
}
} else if (sneakPhase == SneakPhase.UP) {
sneakTransitionRemaining -= tpf;
if (sneakTransitionRemaining <= 0f) {
sneakPhase = SneakPhase.NONE;
currentAnim = null;
}
}
// Ruhezustand (liegen / sitzen) – blockiert alle Bewegungseingaben
if (lockedInPlace) {
physicsChar.setWalkDirection(Vector3f.ZERO);
@@ -886,16 +914,19 @@ public class PlayerInputControl {
// Schon ab WAITING Gehgeschwindigkeit erzwingen, damit beim Blockstart kein Sprint-Frame entsteht
boolean inBlockStance = isBlocking() || blockPhase == BlockPhase.WAITING;
boolean sneaking = sneakPhase != SneakPhase.NONE;
// Ausdauer: Regen + Sprint-Verbrauch
boolean isSprinting = sprint && !weaponDrawn && moving && !staminaExhausted;
boolean isSprinting = sprint && !weaponDrawn && moving && !staminaExhausted && !sneaking;
tickStamina(tpf, isSprinting);
if (moving) {
scratchMoveDir.normalizeLocal();
boolean canSprint = sprint && !weaponDrawn && !staminaExhausted
boolean canSprint = !sneaking && sprint && !weaponDrawn && !staminaExhausted
&& (mainCharacter == null || mainCharacter.getMaxStamina() <= 0
|| mainCharacter.getCurrentStamina() > 0);
float speed = (inDeepWater || walk || inBlockStance) ? MOVE_SPEED * WALK_MULT
float speed = sneaking ? MOVE_SPEED * WALK_MULT
: (inDeepWater || walk || inBlockStance) ? MOVE_SPEED * WALK_MULT
: canSprint ? MOVE_SPEED * SPRINT_MULT
: MOVE_SPEED;
physicsChar.setWalkDirection(scratchWalk.set(scratchMoveDir).multLocal(speed));
@@ -1038,10 +1069,14 @@ public class PlayerInputControl {
if (groundGraceFrames > 0) groundGraceFrames--;
// Block-Phase verwaltet ihre eigenen Animationen.
if (!combatAnimActive && !isBlocking()) {
// Während Sneak-Übergang (DOWN/UP) läuft die Transitions-Animation durch – nicht überschreiben.
if (!combatAnimActive && !isBlocking()
&& sneakPhase != SneakPhase.DOWN && sneakPhase != SneakPhase.UP) {
AnimationAction target;
if (jumpFrames > 0 || (!physicsChar.onGround() && groundGraceFrames <= 0)) {
target = moving ? AnimationAction.RUNNING_JUMP : AnimationAction.JUMP;
} else if (sneaking) {
target = moving ? AnimationAction.SNEAK : AnimationAction.CROUCH_IDLE;
} else if (moving) {
target = (inDeepWater || walk) ? AnimationAction.WALK
: sprint ? AnimationAction.SPRINT
@@ -1365,6 +1400,31 @@ public class PlayerInputControl {
return true;
}
private void handleSneakKey(boolean pressed) {
sneakKeyHeld = pressed;
if (pressed && sneakPhase == SneakPhase.NONE
&& !rollActive && !pickupActive && !blockingAnimActive
&& !turnActive && !lockedInPlace && !combatAnimActive) {
sneakPhase = SneakPhase.DOWN;
boolean moving = forward || backward || left || right;
sneakTransitionRemaining = resolveClipLength(AnimationAction.DUCK, 0.5f) * (moving ? 0.5f : 1f);
nextTransitionLength = 0.1f;
playAction(AnimationAction.DUCK);
currentAnim = AnimationAction.DUCK;
} else if (!pressed && sneakPhase == SneakPhase.ACTIVE) {
startSneakUp();
}
}
private void startSneakUp() {
sneakPhase = SneakPhase.UP;
boolean moving = forward || backward || left || right;
sneakTransitionRemaining = resolveClipLength(AnimationAction.CROUCH_TO_STAND, 0.5f) * (moving ? 0.5f : 1f);
nextTransitionLength = 0.1f;
playAction(AnimationAction.CROUCH_TO_STAND);
currentAnim = AnimationAction.CROUCH_TO_STAND;
}
private void triggerRollFromInput() {
if (rollActive || pickupActive || blockingAnimActive || turnActive
|| lockedInPlace || combatAnimActive || isBlocking()) return;
@@ -1772,22 +1832,32 @@ public class PlayerInputControl {
}
private String currentAnimToGait(AnimationAction anim) {
if (anim == AnimationAction.WALK) return "walking";
if (anim == AnimationAction.RUN) return "running";
if (anim == AnimationAction.SPRINT) return "sprinting";
return null;
if (anim == null) return null;
return switch (anim) {
case WALK,
SWORD_WALK, AXE_WALK, TWO_HAND_WALK,
BOW_WALK, CROSSBOW_WALK, CRYSTAL_WALK,
SWORD_BLOCK_MOVE, SWORD_BLOCK_MOVE_BACKWARD,
SWORD_BLOCK_STRAFE_LEFT, SWORD_BLOCK_STRAFE_RIGHT -> "walking";
case RUN,
SWORD_RUN, AXE_RUN, TWO_HAND_RUN,
BOW_RUN, CROSSBOW_RUN, CRYSTAL_RUN -> "running";
case SPRINT -> "sprinting";
case SNEAK -> "walking";
default -> null;
};
}
private void triggerStep(String gait) {
float vol = (sneakPhase == SneakPhase.ACTIVE) ? 0.25f : 1f;
if (currentWaterDepth > 0f) {
// Im Wasser: Wassergeräusche aus footsteps/water/walking bzw. /running
float[] weights = new float[de.blight.game.audio.SurfaceType.values().length];
weights[de.blight.game.audio.SurfaceType.WATER.ordinal()] = 1f;
footstepSystem.play(weights, gait);
footstepSystem.play(weights, gait, vol);
} else {
Vector3f pos = physicsChar.getPhysicsLocation();
float[] weights = surfaceQuery.apply(pos.x, pos.z);
footstepSystem.play(weights, gait);
footstepSystem.play(weights, gait, vol);
}
}