Compare commits
3 Commits
0a1c7cd835
...
94e0cac10d
| Author | SHA1 | Date | |
|---|---|---|---|
| 94e0cac10d | |||
| f457b897bd | |||
| 470c743848 |
BIN
assets/imported/animations/Crouched To Standing.fbx
Normal file
BIN
assets/imported/animations/Crouched To Standing.fbx
Normal file
Binary file not shown.
BIN
assets/imported/animations/Crouching Idle.fbx
Normal file
BIN
assets/imported/animations/Crouching Idle.fbx
Normal file
Binary file not shown.
BIN
assets/imported/animations/Falling To Roll.fbx
Normal file
BIN
assets/imported/animations/Falling To Roll.fbx
Normal file
Binary file not shown.
BIN
assets/imported/animations/Picking Up.fbx
Normal file
BIN
assets/imported/animations/Picking Up.fbx
Normal file
Binary file not shown.
Binary file not shown.
BIN
assets/imported/animations/Standing To Crouched.fbx
Normal file
BIN
assets/imported/animations/Standing To Crouched.fbx
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
blight-assets/src/main/resources/Characters/clips/Picking Up.j3o
Normal file
BIN
blight-assets/src/main/resources/Characters/clips/Picking Up.j3o
Normal file
Binary file not shown.
BIN
blight-assets/src/main/resources/Characters/clips/Roll.j3o
Normal file
BIN
blight-assets/src/main/resources/Characters/clips/Roll.j3o
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -24,7 +24,13 @@
|
||||
"Sword And Shield Block Walk Backwards",
|
||||
"Sword And Shield Strafe Left",
|
||||
"Sword And Shield Strafe Right",
|
||||
"Sword And Shield Heavy Attack"
|
||||
"Sword And Shield Heavy Attack",
|
||||
"Picking Up",
|
||||
"Roll",
|
||||
"Crouched To Standing",
|
||||
"Crouching Idle",
|
||||
"Sneaking Forward",
|
||||
"Standing To Crouched"
|
||||
],
|
||||
"actionMap": {
|
||||
"DEFAULT": "Idle",
|
||||
@@ -51,7 +57,13 @@
|
||||
"SWORD_BLOCK_MOVE_BACKWARD": "Sword And Shield Block Walk Backwards",
|
||||
"SWORD_BLOCK_STRAFE_LEFT": "Sword And Shield Strafe Left",
|
||||
"SWORD_BLOCK_STRAFE_RIGHT": "Sword And Shield Strafe Right",
|
||||
"SWORD_HEAVY_ATTACK": "Sword And Shield Heavy Attack"
|
||||
"SWORD_HEAVY_ATTACK": "Sword And Shield Heavy Attack",
|
||||
"PICK_UP": "Picking Up",
|
||||
"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": {
|
||||
@@ -111,6 +123,8 @@
|
||||
"Sword And Shield Block Walk Backwards",
|
||||
"Sword And Shield Strafe Left",
|
||||
"Sword And Shield Strafe Right",
|
||||
"Sword And Shield Heavy Attack"
|
||||
"Sword And Shield Heavy Attack",
|
||||
"Roll",
|
||||
"Sneaking Forward"
|
||||
]
|
||||
}
|
||||
@@ -14,7 +14,11 @@ public enum AnimationAction {
|
||||
SPRINT,
|
||||
JUMP,
|
||||
RUNNING_JUMP,
|
||||
ROLL,
|
||||
DUCK,
|
||||
CROUCH_IDLE,
|
||||
CROUCH_TO_STAND,
|
||||
SNEAK,
|
||||
PICK_UP,
|
||||
LIE_DOWN,
|
||||
LIE_UP,
|
||||
@@ -78,7 +82,11 @@ public enum AnimationAction {
|
||||
case SPRINT -> TextRegistry.resolve(null, key, "Sprint");
|
||||
case JUMP -> TextRegistry.resolve(null, key, "Jump");
|
||||
case RUNNING_JUMP -> TextRegistry.resolve(null, key, "Running Jump");
|
||||
case DUCK -> TextRegistry.resolve(null, key, "Duck");
|
||||
case ROLL -> TextRegistry.resolve(null, key, "Rolle");
|
||||
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)");
|
||||
|
||||
@@ -22,6 +22,8 @@ public class KeyBindings {
|
||||
public int drawMeleeWeapon = KeyInput.KEY_1;
|
||||
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 = {
|
||||
@@ -42,6 +44,8 @@ public class KeyBindings {
|
||||
{"drawMeleeWeapon", "Nahkampfwaffe ziehen"},
|
||||
{"drawRangedWeapon", "Fernkampfwaffe ziehen"},
|
||||
{"sheathWeapon", "Waffe wegstecken"},
|
||||
{"roll", "Rolle"},
|
||||
{"sneak", "Schleichen"},
|
||||
};
|
||||
|
||||
public int get(String fieldName) {
|
||||
|
||||
@@ -7,9 +7,16 @@ import com.jme3.anim.tween.action.BaseAction;
|
||||
import com.jme3.bullet.control.CharacterControl;
|
||||
import com.jme3.input.InputManager;
|
||||
import com.jme3.input.MouseInput;
|
||||
import com.jme3.input.RawInputListener;
|
||||
import com.jme3.input.controls.ActionListener;
|
||||
import com.jme3.input.controls.KeyTrigger;
|
||||
import com.jme3.input.controls.MouseButtonTrigger;
|
||||
import com.jme3.input.event.JoyAxisEvent;
|
||||
import com.jme3.input.event.JoyButtonEvent;
|
||||
import com.jme3.input.event.KeyInputEvent;
|
||||
import com.jme3.input.event.MouseButtonEvent;
|
||||
import com.jme3.input.event.MouseMotionEvent;
|
||||
import com.jme3.input.event.TouchEvent;
|
||||
import de.blight.common.model.ItemSubCategory;
|
||||
import de.blight.common.model.MainCharacter;
|
||||
import com.jme3.math.Quaternion;
|
||||
@@ -64,6 +71,8 @@ public class PlayerInputControl {
|
||||
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;
|
||||
private static final int STAMINA_ROLL_COST = 20;
|
||||
private static final float ROLL_AIRBORNE_THRESHOLD = 0.90f; // Hips-Y unter 90% Baseline → Luftphase
|
||||
/** 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
|
||||
@@ -93,10 +102,13 @@ public class PlayerInputControl {
|
||||
private static final String ACT_SHEATH = "SheathWeapon";
|
||||
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_DRAW_MELEE, ACT_DRAW_RANGED, ACT_SHEATH, ACT_COMBAT_ATK, ACT_COMBAT_ALT,
|
||||
ACT_ROLL, ACT_SNEAK
|
||||
};
|
||||
|
||||
private final InputManager inputManager;
|
||||
@@ -105,6 +117,8 @@ public class PlayerInputControl {
|
||||
private CharacterControl physicsChar;
|
||||
private Spatial visual;
|
||||
|
||||
private int sprintKey = -1;
|
||||
private RawInputListener sprintRawListener = null;
|
||||
private boolean forward, backward, left, right, sprint, walk;
|
||||
private boolean paused = false;
|
||||
|
||||
@@ -194,6 +208,8 @@ public class PlayerInputControl {
|
||||
|
||||
private com.jme3.anim.SkinningControl skinningControl = null;
|
||||
private com.jme3.anim.Armature armature = null;
|
||||
private com.jme3.anim.Joint hipsJoint = null;
|
||||
|
||||
|
||||
// ── Fußgeräusche ─────────────────────────────────────────────────────────
|
||||
// Bone-Namen nach Phase-0-Bone-Log eintragen:
|
||||
@@ -258,6 +274,21 @@ 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;
|
||||
private float rollInitRemaining = 0f;
|
||||
private boolean rollBaselineCaptured = false;
|
||||
private float rollHipsBaseY = 0f;
|
||||
private boolean rollAirborne = false;
|
||||
private Vector3f rollDir = null;
|
||||
|
||||
// Pre-allokierte Scratch-Objekte — keine Heap-Allokation pro Frame in update()
|
||||
private final Vector3f scratchCamDir = new Vector3f();
|
||||
private final Vector3f scratchCamLeft = new Vector3f();
|
||||
@@ -274,7 +305,7 @@ public class PlayerInputControl {
|
||||
case "Backward" -> backward = isPressed;
|
||||
case "Left" -> left = isPressed;
|
||||
case "Right" -> right = isPressed;
|
||||
case "Sprint" -> sprint = isPressed;
|
||||
case "Sprint" -> sprint = isPressed;
|
||||
case "Walk" -> walk = isPressed;
|
||||
case "Jump" -> { if (isPressed && physicsChar != null) { physicsChar.jump(); jumpFrames = 12; } }
|
||||
case ACT_DRAW_MELEE -> { if (isPressed) handleDrawMelee(); }
|
||||
@@ -283,19 +314,7 @@ public class PlayerInputControl {
|
||||
case ACT_COMBAT_ATK -> {
|
||||
if (isPressed) {
|
||||
if (!weaponDrawn || blockingAnimActive || drawAnimActive || isBlocking()) break;
|
||||
if (combatAnimActive) {
|
||||
if (comboWindowActive) {
|
||||
// Kombo-Follow während zweiter Hälfte: sofort fluid weiter
|
||||
combatAnimActive = false;
|
||||
combatAnimRemaining = 0f;
|
||||
combatAnimSkipComboWindow = false;
|
||||
heavyLungeVel = null;
|
||||
heavyLungeStartRemaining = 0f;
|
||||
heavyLungeEndRemaining = 0f;
|
||||
handleCombatAttack();
|
||||
}
|
||||
break;
|
||||
}
|
||||
// Immer Hold-Timer starten – auch mitten in einer Kombo
|
||||
attackHoldPending = true;
|
||||
attackHoldTimer = 0f;
|
||||
} else {
|
||||
@@ -303,16 +322,26 @@ public class PlayerInputControl {
|
||||
attackHoldPending = false;
|
||||
if (attackHoldTimer > QUICK_TAP_THRESHOLD) {
|
||||
handleHeavyAttack();
|
||||
} else {
|
||||
// Schneller Klick → schwere Anim abbrechen, normale Kombo starten
|
||||
combatAnimActive = false;
|
||||
combatAnimRemaining = 0f;
|
||||
} else if (combatAnimActive && comboWindowActive) {
|
||||
// Schneller Klick im Kombo-Fenster → Kombo fortsetzen
|
||||
combatAnimActive = false;
|
||||
combatAnimRemaining = 0f;
|
||||
combatAnimSkipComboWindow = false;
|
||||
heavyLungeVel = null;
|
||||
heavyLungeStartRemaining = 0f;
|
||||
heavyLungeEndRemaining = 0f;
|
||||
handleCombatAttack();
|
||||
} else if (!combatAnimActive) {
|
||||
// Schneller Klick außerhalb Kombo → normaler Angriff
|
||||
handleCombatAttack();
|
||||
}
|
||||
// Schneller Klick während Kombo aber außerhalb Fenster → ignorieren
|
||||
}
|
||||
}
|
||||
}
|
||||
case ACT_COMBAT_ALT -> handleCombatAlt(isPressed);
|
||||
case ACT_ROLL -> { if (isPressed) triggerRollFromInput(); }
|
||||
case ACT_SNEAK -> { if (!weaponDrawn) handleSneakKey(isPressed); }
|
||||
}
|
||||
};
|
||||
|
||||
@@ -355,6 +384,7 @@ public class PlayerInputControl {
|
||||
if (skinningControl != null) {
|
||||
armature = skinningControl.getArmature();
|
||||
logJointNames(armature);
|
||||
hipsJoint = armature.getJoint("mixamorig:Hips");
|
||||
jointRightArm = armature.getJoint("mixamorig:RightArm");
|
||||
jointRightForeArm = armature.getJoint("mixamorig:RightForeArm");
|
||||
jointLeftArm = armature.getJoint("mixamorig:LeftArm");
|
||||
@@ -668,12 +698,29 @@ public class PlayerInputControl {
|
||||
inputManager.addMapping("Right", new KeyTrigger(kb.right));
|
||||
inputManager.addMapping("Jump", new KeyTrigger(kb.jump));
|
||||
inputManager.addMapping("Sprint", new KeyTrigger(kb.sprint));
|
||||
if (sprintRawListener != null) inputManager.removeRawInputListener(sprintRawListener);
|
||||
sprintKey = kb.sprint;
|
||||
sprintRawListener = new RawInputListener() {
|
||||
public void onKeyEvent(KeyInputEvent e) {
|
||||
if (e.getKeyCode() == sprintKey) sprint = !e.isReleased();
|
||||
}
|
||||
public void beginInput() {}
|
||||
public void endInput() {}
|
||||
public void onJoyAxisEvent(JoyAxisEvent e) {}
|
||||
public void onJoyButtonEvent(JoyButtonEvent e) {}
|
||||
public void onMouseMotionEvent(MouseMotionEvent e) {}
|
||||
public void onMouseButtonEvent(MouseButtonEvent e) {}
|
||||
public void onTouchEvent(TouchEvent e) {}
|
||||
};
|
||||
inputManager.addRawInputListener(sprintRawListener);
|
||||
inputManager.addMapping("Walk", new KeyTrigger(kb.walk));
|
||||
inputManager.addMapping(ACT_DRAW_MELEE, new KeyTrigger(kb.drawMeleeWeapon));
|
||||
inputManager.addMapping(ACT_DRAW_RANGED, new KeyTrigger(kb.drawRangedWeapon));
|
||||
inputManager.addMapping(ACT_SHEATH, new KeyTrigger(kb.sheathWeapon));
|
||||
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);
|
||||
}
|
||||
|
||||
@@ -685,7 +732,7 @@ public class PlayerInputControl {
|
||||
runningClip = animPlayer.getCurrentClip();
|
||||
}
|
||||
|
||||
if (visual != null && animOffsetElapsed < AnimPlayer.BLEND_DURATION) {
|
||||
if (visual != null && animOffsetElapsed < AnimPlayer.BLEND_DURATION && !rollActive) {
|
||||
animOffsetElapsed += tpf;
|
||||
float t = Math.min(1f, animOffsetElapsed / AnimPlayer.BLEND_DURATION);
|
||||
animOffsetCurrent.set(animOffsetFrom);
|
||||
@@ -722,6 +769,51 @@ public class PlayerInputControl {
|
||||
}
|
||||
}
|
||||
|
||||
if (rollActive) {
|
||||
rollRemaining -= tpf;
|
||||
if (rollRemaining <= 0f) {
|
||||
rollActive = false;
|
||||
rollDir = null;
|
||||
currentAnim = null;
|
||||
if (physicsChar != null) physicsChar.setWalkDirection(Vector3f.ZERO);
|
||||
} else {
|
||||
boolean rollEndedEarly = false;
|
||||
if (rollInitRemaining > 0f) {
|
||||
rollInitRemaining -= tpf;
|
||||
if (physicsChar != null) physicsChar.setWalkDirection(Vector3f.ZERO);
|
||||
} else if (!rollBaselineCaptured && hipsJoint != null) {
|
||||
rollBaselineCaptured = true;
|
||||
rollHipsBaseY = hipsJoint.getModelTransform().getTranslation().y;
|
||||
rollAirborne = false;
|
||||
log.info("[ROLL] Baseline hipsY={}", rollHipsBaseY);
|
||||
if (physicsChar != null) physicsChar.setWalkDirection(Vector3f.ZERO);
|
||||
} else if (rollBaselineCaptured && hipsJoint != null && physicsChar != null) {
|
||||
float hipsY = hipsJoint.getModelTransform().getTranslation().y;
|
||||
boolean wasAirborne = rollAirborne;
|
||||
rollAirborne = hipsY < rollHipsBaseY * ROLL_AIRBORNE_THRESHOLD;
|
||||
if (rollAirborne != wasAirborne) {
|
||||
log.info("[ROLL] hipsY={} baseY={} → {}", hipsY, rollHipsBaseY,
|
||||
rollAirborne ? "AIRBORNE" : "GROUNDED");
|
||||
}
|
||||
// Gelandet + Bewegungstaste → Rolle beenden, direkt in Lauf-Logik fallen
|
||||
if (wasAirborne && !rollAirborne && (forward || backward || left || right)) {
|
||||
rollActive = false;
|
||||
rollDir = null;
|
||||
currentAnim = null;
|
||||
rollEndedEarly = true;
|
||||
physicsChar.setWalkDirection(Vector3f.ZERO);
|
||||
} else if (rollAirborne && rollDir != null) {
|
||||
physicsChar.setWalkDirection(scratchWalk.set(rollDir).multLocal(MOVE_SPEED));
|
||||
} else {
|
||||
physicsChar.setWalkDirection(Vector3f.ZERO);
|
||||
}
|
||||
} else {
|
||||
if (physicsChar != null) physicsChar.setWalkDirection(Vector3f.ZERO);
|
||||
}
|
||||
if (!rollEndedEarly) return;
|
||||
}
|
||||
}
|
||||
|
||||
// Drehung auf der Stelle (kein Vorwärtsbewegen)
|
||||
if (turnActive) {
|
||||
turnRemaining -= tpf;
|
||||
@@ -761,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);
|
||||
@@ -803,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));
|
||||
@@ -838,9 +952,20 @@ public class PlayerInputControl {
|
||||
// Hold-Timer muss jede Frame akkumulieren, unabhängig vom Anim-Status
|
||||
if (attackHoldPending) {
|
||||
attackHoldTimer += tpf;
|
||||
// Erst wenn klar ist dass es ein schwerer Angriff wird → Animation starten
|
||||
if (attackHoldTimer > QUICK_TAP_THRESHOLD && !combatAnimActive) {
|
||||
startHeavyCharge();
|
||||
if (attackHoldTimer > QUICK_TAP_THRESHOLD) {
|
||||
if (combatAnimActive && !combatAnimSkipComboWindow) {
|
||||
// Normale Kombo unterbrechen und schweren Angriff einleiten
|
||||
combatAnimActive = false;
|
||||
combatAnimRemaining = 0f;
|
||||
comboWindowActive = false;
|
||||
comboStep = 0;
|
||||
heavyLungeVel = null;
|
||||
heavyLungeStartRemaining = 0f;
|
||||
heavyLungeEndRemaining = 0f;
|
||||
startHeavyCharge(); // setzt combatAnimSkipComboWindow = true → verhindert Loop
|
||||
} else if (!combatAnimActive) {
|
||||
startHeavyCharge();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -944,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
|
||||
@@ -1271,6 +1400,62 @@ 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;
|
||||
cam.getDirection(scratchCamDir).setY(0).normalizeLocal();
|
||||
cam.getLeft(scratchCamLeft).setY(0).normalizeLocal();
|
||||
Vector3f rollDirVec;
|
||||
if (backward && !forward) rollDirVec = scratchCamDir.clone().negateLocal();
|
||||
else if (left && !right) rollDirVec = scratchCamLeft.clone();
|
||||
else if (right && !left) rollDirVec = scratchCamLeft.clone().negateLocal();
|
||||
else rollDirVec = scratchCamDir.clone();
|
||||
startRoll(rollDirVec);
|
||||
}
|
||||
|
||||
private void startRoll(Vector3f dir) {
|
||||
if (!spendStamina(STAMINA_ROLL_COST)) return;
|
||||
nextTransitionLength = 0.1f;
|
||||
rollActive = true;
|
||||
rollRemaining = resolveClipLength(AnimationAction.ROLL, 0.7f);
|
||||
rollInitRemaining = 0.12f;
|
||||
rollBaselineCaptured = false;
|
||||
rollAirborne = false;
|
||||
rollDir = dir;
|
||||
if (physicsChar != null) physicsChar.setWalkDirection(Vector3f.ZERO);
|
||||
if (visual != null) {
|
||||
scratchRot.lookAt(dir, Vector3f.UNIT_Y);
|
||||
visual.setLocalRotation(scratchRot);
|
||||
}
|
||||
playAction(AnimationAction.ROLL);
|
||||
currentAnim = AnimationAction.ROLL;
|
||||
}
|
||||
|
||||
private boolean hasShieldEquipped() {
|
||||
return mainCharacter != null && mainCharacter.getEquippedShield() != null;
|
||||
}
|
||||
@@ -1647,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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -409,7 +409,7 @@ public class WorldScene extends BaseAppState {
|
||||
WorldNpcsState npcsState = new WorldNpcsState(keyBindings, physicsChar, playerInput, mc);
|
||||
npcsState.setThirdPersonCamera(thirdPersonCam);
|
||||
app.getStateManager().attach(npcsState);
|
||||
app.getStateManager().attach(new InteractionHudState());
|
||||
app.getStateManager().attach(new InteractionHudState(keyBindings));
|
||||
inventoryState = new InventoryState(mc, keyBindings);
|
||||
inventoryState.setEnabled(false);
|
||||
app.getStateManager().attach(inventoryState);
|
||||
|
||||
@@ -10,6 +10,8 @@ import com.jme3.math.Vector3f;
|
||||
import com.jme3.renderer.Camera;
|
||||
import com.jme3.scene.Node;
|
||||
import com.jme3.scene.Spatial;
|
||||
import de.blight.game.config.KeyBindings;
|
||||
import de.blight.game.config.KeyNames;
|
||||
import de.blight.lang.TextResolver;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
@@ -33,6 +35,10 @@ public class InteractionHudState extends BaseAppState {
|
||||
private DialogHudState dialogHud;
|
||||
|
||||
private BitmapText labelText;
|
||||
private KeyBindings keyBindings;
|
||||
|
||||
public InteractionHudState() {}
|
||||
public InteractionHudState(KeyBindings keyBindings) { this.keyBindings = keyBindings; }
|
||||
|
||||
// ── Lifecycle ─────────────────────────────────────────────────────────────
|
||||
|
||||
@@ -134,6 +140,9 @@ public class InteractionHudState extends BaseAppState {
|
||||
}
|
||||
|
||||
String text = TextResolver.get().resolveId(labelKey);
|
||||
if (keyBindings != null) {
|
||||
text = text.replace("{interact}", KeyNames.of(keyBindings.interact));
|
||||
}
|
||||
labelText.setText(text);
|
||||
float textW = labelText.getLineWidth();
|
||||
labelText.setLocalTranslation(screenV.x - textW * 0.5f, screenV.y, 1f);
|
||||
|
||||
@@ -8,8 +8,6 @@ import com.jme3.bullet.BulletAppState;
|
||||
import com.jme3.bullet.control.CharacterControl;
|
||||
import com.jme3.bullet.control.RigidBodyControl;
|
||||
import com.jme3.bullet.util.CollisionShapeFactory;
|
||||
import com.jme3.font.BitmapFont;
|
||||
import com.jme3.font.BitmapText;
|
||||
import com.jme3.input.InputManager;
|
||||
import com.jme3.input.MouseInput;
|
||||
import com.jme3.input.controls.ActionListener;
|
||||
@@ -37,6 +35,7 @@ import de.blight.common.PlacedModel;
|
||||
import de.blight.common.PlacedModelIO;
|
||||
import de.blight.common.model.CombatDummy;
|
||||
import de.blight.common.model.CombatDummyIO;
|
||||
import de.blight.game.animation.AnimationAction;
|
||||
import de.blight.game.combat.DamageCalculator;
|
||||
import de.blight.game.combat.MeleeAttackCallback;
|
||||
import de.blight.game.config.KeyBindings;
|
||||
@@ -62,11 +61,12 @@ public class WorldDummyState extends BaseAppState implements MeleeAttackCallback
|
||||
// ── Spielwelt-Konstanten ──────────────────────────────────────────────────
|
||||
private static final float FOCUS_RANGE = 6f;
|
||||
private static final float RESTORE_DELAY = 60f;
|
||||
private static final float HP_BAR_SHOW_TIME = 5f;
|
||||
//private static final float HP_BAR_SHOW_TIME = 5f;
|
||||
private static final float STAND_UP_RANGE = 3f;
|
||||
private static final int DEFAULT_MAX_HP = 250;
|
||||
private static final float FALL_DURATION = 0.35f;
|
||||
private static final float MODEL_HALF_H = 0.75f;
|
||||
private static final int DEFAULT_MAX_HP = 10;
|
||||
private static final float MODEL_HALF_H = 0.75f;
|
||||
private static final float KNOCK_INITIAL_VEL = 0.4f; // rad/s initial push
|
||||
private static final float KNOCK_GRAVITY = 8f; // angular accel at upright (rad/s²)
|
||||
|
||||
// ── Welt-HP-Bar-Konstanten ────────────────────────────────────────────────
|
||||
private static final float WB_WIDTH = 0.8f;
|
||||
@@ -103,8 +103,6 @@ public class WorldDummyState extends BaseAppState implements MeleeAttackCallback
|
||||
// ── Feind-HUD ─────────────────────────────────────────────────────────────
|
||||
private Node enemyHudNode;
|
||||
private Geometry enemyHpFill;
|
||||
private BitmapText enemyNameText;
|
||||
private BitmapText enemyHpText;
|
||||
/** x-Koordinate des linken Randes der Feind-HP-Bar (Screen-Pixel). */
|
||||
private float ehbX;
|
||||
/** y-Koordinate des unteren Randes der Feind-HP-Bar (Screen-Pixel). */
|
||||
@@ -314,25 +312,6 @@ public class WorldDummyState extends BaseAppState implements MeleeAttackCallback
|
||||
enemyHpFill.setLocalTranslation(ehbX + EHB_BORDER, ehbY + EHB_BORDER, 2f);
|
||||
enemyHudNode.attachChild(enemyHpFill);
|
||||
|
||||
BitmapFont font = assetManager.loadFont("Interface/Fonts/Default.fnt");
|
||||
float renderedSize = font.getCharSet().getRenderedSize();
|
||||
|
||||
// Gegner-Name (über Bar)
|
||||
enemyNameText = new BitmapText(font);
|
||||
enemyNameText.setSize(renderedSize * 2f);
|
||||
enemyNameText.setLocalScale(0.5f);
|
||||
enemyNameText.setColor(new ColorRGBA(1f, 0.95f, 0.6f, 1f));
|
||||
enemyNameText.setQueueBucket(RenderQueue.Bucket.Gui);
|
||||
enemyHudNode.attachChild(enemyNameText);
|
||||
|
||||
// HP-Zahlen (in Bar)
|
||||
enemyHpText = new BitmapText(font);
|
||||
enemyHpText.setSize(renderedSize * 2f);
|
||||
enemyHpText.setLocalScale(0.5f);
|
||||
enemyHpText.setColor(ColorRGBA.White);
|
||||
enemyHpText.setQueueBucket(RenderQueue.Bucket.Gui);
|
||||
enemyHudNode.attachChild(enemyHpText);
|
||||
|
||||
guiNode.attachChild(enemyHudNode);
|
||||
enemyHudNode.setCullHint(Spatial.CullHint.Always);
|
||||
}
|
||||
@@ -375,23 +354,8 @@ public class WorldDummyState extends BaseAppState implements MeleeAttackCallback
|
||||
}
|
||||
|
||||
private void updateEnemyHudContent(DummyRuntime dr) {
|
||||
float sw = cam.getWidth();
|
||||
float ratio = dr.maxHp > 0 ? (float) dr.currentHp / dr.maxHp : 0f;
|
||||
applyEnemyHpRatio(ratio);
|
||||
|
||||
String name = "Kampfpuppe";
|
||||
enemyNameText.setText(name);
|
||||
float nameW = enemyNameText.getLineWidth() * 0.5f;
|
||||
enemyNameText.setLocalTranslation(
|
||||
sw * 0.5f - nameW,
|
||||
ehbY + EHB_H + EHB_BORDER * 2 + 6f, 3f);
|
||||
|
||||
String hpStr = dr.currentHp + " / " + dr.maxHp;
|
||||
enemyHpText.setText(hpStr);
|
||||
float hpW = enemyHpText.getLineWidth() * 0.5f;
|
||||
enemyHpText.setLocalTranslation(
|
||||
sw * 0.5f - hpW,
|
||||
ehbY + EHB_BORDER + 1f, 3f);
|
||||
}
|
||||
|
||||
// ── MeleeAttackCallback ───────────────────────────────────────────────────
|
||||
@@ -437,14 +401,19 @@ public class WorldDummyState extends BaseAppState implements MeleeAttackCallback
|
||||
}
|
||||
|
||||
private void knockout(DummyRuntime dr) {
|
||||
dr.fallen = true;
|
||||
dr.fallenLerp = 0f;
|
||||
dr.fallen = true;
|
||||
dr.fallenLerp = 0f;
|
||||
dr.knockAngle = 0f;
|
||||
dr.knockAngVel = KNOCK_INITIAL_VEL;
|
||||
dr.hpBarNode.setCullHint(Spatial.CullHint.Always);
|
||||
}
|
||||
|
||||
private void standUp(DummyRuntime dr) {
|
||||
playerInput.requestAnimation(AnimationAction.PICK_UP, 0f, null);
|
||||
dr.fallen = false;
|
||||
dr.fallenLerp = 0f;
|
||||
dr.knockAngle = 0f;
|
||||
dr.knockAngVel = 0f;
|
||||
dr.currentHp = dr.maxHp;
|
||||
dr.restoreTimer = 0f;
|
||||
dr.dummyNode.setLocalRotation(new Quaternion());
|
||||
@@ -476,13 +445,18 @@ public class WorldDummyState extends BaseAppState implements MeleeAttackCallback
|
||||
for (int i = 0; i < dummies.size(); i++) {
|
||||
DummyRuntime dr = dummies.get(i);
|
||||
|
||||
// Umfall-Animation
|
||||
// Umfall-Animation (physikalisch: Schwerkraft-Drehmoment nimmt mit cos(Winkel) ab)
|
||||
if (dr.fallen && dr.fallenLerp < 1f) {
|
||||
dr.fallenLerp = Math.min(1f, dr.fallenLerp + tpf / FALL_DURATION);
|
||||
Quaternion target = new Quaternion().fromAngles(0f, 0f, -FastMath.HALF_PI);
|
||||
Quaternion cur = new Quaternion();
|
||||
cur.slerp(Quaternion.IDENTITY, target, dr.fallenLerp);
|
||||
dr.dummyNode.setLocalRotation(cur);
|
||||
float gravAccel = KNOCK_GRAVITY * FastMath.cos(dr.knockAngle);
|
||||
dr.knockAngVel += gravAccel * tpf;
|
||||
dr.knockAngle += dr.knockAngVel * tpf;
|
||||
if (dr.knockAngle >= FastMath.HALF_PI) {
|
||||
dr.knockAngle = FastMath.HALF_PI;
|
||||
dr.knockAngVel = 0f;
|
||||
dr.fallenLerp = 1f;
|
||||
}
|
||||
dr.dummyNode.setLocalRotation(
|
||||
new Quaternion().fromAngles(-dr.knockAngle, 0f, 0f));
|
||||
}
|
||||
|
||||
// Welt-HP-Balken-Timer
|
||||
@@ -588,6 +562,8 @@ public class WorldDummyState extends BaseAppState implements MeleeAttackCallback
|
||||
float hpBarTimer = 0f;
|
||||
boolean fallen = false;
|
||||
float fallenLerp = 0f;
|
||||
float knockAngle = 0f; // aktueller Kippwinkel (0..PI/2)
|
||||
float knockAngVel = 0f; // Winkelgeschwindigkeit (rad/s)
|
||||
|
||||
DummyRuntime(String id, float wx, float wy, float wz,
|
||||
Node dummyNode, Node hpBarNode, Geometry hpBarFg, int maxHp,
|
||||
|
||||
@@ -7,7 +7,7 @@ dialog.speaker.player=Spieler
|
||||
# ── Interactables ─────────────────────────────────────────────────────────────
|
||||
interactable.bench.name=Bank
|
||||
interactable.bed.name=Bett
|
||||
dummy.stand_up=Aufstellen [F]
|
||||
dummy.stand_up=Aufstellen [{interact}]
|
||||
dummy.name=Kampfpuppe
|
||||
|
||||
# ── Items ────────────────────────────────────────────────────────────────────
|
||||
|
||||
@@ -7,7 +7,7 @@ dialog.speaker.player=Player
|
||||
# ── Interactables ─────────────────────────────────────────────────────────────
|
||||
interactable.bench.name=Bench
|
||||
interactable.bed.name=Bed
|
||||
dummy.stand_up=Stand Up [F]
|
||||
dummy.stand_up=Stand Up [{interact}]
|
||||
dummy.name=Training Dummy
|
||||
|
||||
# ── Items ────────────────────────────────────────────────────────────────────
|
||||
|
||||
Reference in New Issue
Block a user