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 Block Walk Backwards",
|
||||||
"Sword And Shield Strafe Left",
|
"Sword And Shield Strafe Left",
|
||||||
"Sword And Shield Strafe Right",
|
"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": {
|
"actionMap": {
|
||||||
"DEFAULT": "Idle",
|
"DEFAULT": "Idle",
|
||||||
@@ -51,7 +57,13 @@
|
|||||||
"SWORD_BLOCK_MOVE_BACKWARD": "Sword And Shield Block Walk Backwards",
|
"SWORD_BLOCK_MOVE_BACKWARD": "Sword And Shield Block Walk Backwards",
|
||||||
"SWORD_BLOCK_STRAFE_LEFT": "Sword And Shield Strafe Left",
|
"SWORD_BLOCK_STRAFE_LEFT": "Sword And Shield Strafe Left",
|
||||||
"SWORD_BLOCK_STRAFE_RIGHT": "Sword And Shield Strafe Right",
|
"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",
|
"previewModelPath": "Characters/Models/hero.j3o",
|
||||||
"animOffsets": {
|
"animOffsets": {
|
||||||
@@ -111,6 +123,8 @@
|
|||||||
"Sword And Shield Block Walk Backwards",
|
"Sword And Shield Block Walk Backwards",
|
||||||
"Sword And Shield Strafe Left",
|
"Sword And Shield Strafe Left",
|
||||||
"Sword And Shield Strafe Right",
|
"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,
|
SPRINT,
|
||||||
JUMP,
|
JUMP,
|
||||||
RUNNING_JUMP,
|
RUNNING_JUMP,
|
||||||
|
ROLL,
|
||||||
DUCK,
|
DUCK,
|
||||||
|
CROUCH_IDLE,
|
||||||
|
CROUCH_TO_STAND,
|
||||||
|
SNEAK,
|
||||||
PICK_UP,
|
PICK_UP,
|
||||||
LIE_DOWN,
|
LIE_DOWN,
|
||||||
LIE_UP,
|
LIE_UP,
|
||||||
@@ -78,7 +82,11 @@ public enum AnimationAction {
|
|||||||
case SPRINT -> TextRegistry.resolve(null, key, "Sprint");
|
case SPRINT -> TextRegistry.resolve(null, key, "Sprint");
|
||||||
case JUMP -> TextRegistry.resolve(null, key, "Jump");
|
case JUMP -> TextRegistry.resolve(null, key, "Jump");
|
||||||
case RUNNING_JUMP -> TextRegistry.resolve(null, key, "Running 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 PICK_UP -> TextRegistry.resolve(null, key, "Pick up");
|
||||||
case LIE_DOWN -> TextRegistry.resolve(null, key, "Hinlegen");
|
case LIE_DOWN -> TextRegistry.resolve(null, key, "Hinlegen");
|
||||||
case LIE_UP -> TextRegistry.resolve(null, key, "Aufstehen (Bett)");
|
case LIE_UP -> TextRegistry.resolve(null, key, "Aufstehen (Bett)");
|
||||||
|
|||||||
@@ -22,6 +22,8 @@ public class KeyBindings {
|
|||||||
public int drawMeleeWeapon = KeyInput.KEY_1;
|
public int drawMeleeWeapon = KeyInput.KEY_1;
|
||||||
public int drawRangedWeapon = KeyInput.KEY_2;
|
public int drawRangedWeapon = KeyInput.KEY_2;
|
||||||
public int sheathWeapon = KeyInput.KEY_X;
|
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. */
|
/** Metadaten für die Config-UI: Feldname im Objekt + Anzeigename. */
|
||||||
public static final String[][] ENTRIES = {
|
public static final String[][] ENTRIES = {
|
||||||
@@ -42,6 +44,8 @@ public class KeyBindings {
|
|||||||
{"drawMeleeWeapon", "Nahkampfwaffe ziehen"},
|
{"drawMeleeWeapon", "Nahkampfwaffe ziehen"},
|
||||||
{"drawRangedWeapon", "Fernkampfwaffe ziehen"},
|
{"drawRangedWeapon", "Fernkampfwaffe ziehen"},
|
||||||
{"sheathWeapon", "Waffe wegstecken"},
|
{"sheathWeapon", "Waffe wegstecken"},
|
||||||
|
{"roll", "Rolle"},
|
||||||
|
{"sneak", "Schleichen"},
|
||||||
};
|
};
|
||||||
|
|
||||||
public int get(String fieldName) {
|
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.bullet.control.CharacterControl;
|
||||||
import com.jme3.input.InputManager;
|
import com.jme3.input.InputManager;
|
||||||
import com.jme3.input.MouseInput;
|
import com.jme3.input.MouseInput;
|
||||||
|
import com.jme3.input.RawInputListener;
|
||||||
import com.jme3.input.controls.ActionListener;
|
import com.jme3.input.controls.ActionListener;
|
||||||
import com.jme3.input.controls.KeyTrigger;
|
import com.jme3.input.controls.KeyTrigger;
|
||||||
import com.jme3.input.controls.MouseButtonTrigger;
|
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.ItemSubCategory;
|
||||||
import de.blight.common.model.MainCharacter;
|
import de.blight.common.model.MainCharacter;
|
||||||
import com.jme3.math.Quaternion;
|
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_ATTACK5_COST = 20;
|
||||||
private static final int STAMINA_HEAVY_COST = 30;
|
private static final int STAMINA_HEAVY_COST = 30;
|
||||||
private static final int STAMINA_BLOCK_HIT_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. */
|
/** Dauer der Ziehen/Wegstecken-Animation in Sekunden. */
|
||||||
private static final float DRAW_DURATION = 0.85f;
|
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
|
// 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_SHEATH = "SheathWeapon";
|
||||||
private static final String ACT_COMBAT_ATK = "CombatAttack";
|
private static final String ACT_COMBAT_ATK = "CombatAttack";
|
||||||
private static final String ACT_COMBAT_ALT = "CombatAltAction";
|
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 = {
|
private static final String[] ACTION_NAMES = {
|
||||||
"Forward", "Backward", "Left", "Right", "Jump", "Sprint", "Walk",
|
"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;
|
private final InputManager inputManager;
|
||||||
@@ -105,6 +117,8 @@ public class PlayerInputControl {
|
|||||||
private CharacterControl physicsChar;
|
private CharacterControl physicsChar;
|
||||||
private Spatial visual;
|
private Spatial visual;
|
||||||
|
|
||||||
|
private int sprintKey = -1;
|
||||||
|
private RawInputListener sprintRawListener = null;
|
||||||
private boolean forward, backward, left, right, sprint, walk;
|
private boolean forward, backward, left, right, sprint, walk;
|
||||||
private boolean paused = false;
|
private boolean paused = false;
|
||||||
|
|
||||||
@@ -194,6 +208,8 @@ public class PlayerInputControl {
|
|||||||
|
|
||||||
private com.jme3.anim.SkinningControl skinningControl = null;
|
private com.jme3.anim.SkinningControl skinningControl = null;
|
||||||
private com.jme3.anim.Armature armature = null;
|
private com.jme3.anim.Armature armature = null;
|
||||||
|
private com.jme3.anim.Joint hipsJoint = null;
|
||||||
|
|
||||||
|
|
||||||
// ── Fußgeräusche ─────────────────────────────────────────────────────────
|
// ── Fußgeräusche ─────────────────────────────────────────────────────────
|
||||||
// Bone-Namen nach Phase-0-Bone-Log eintragen:
|
// 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. */
|
/** Autopilot: wenn gesetzt, geht der Charakter automatisch in diese (normalisierte) Richtung. */
|
||||||
private Vector3f autopilotDir = null;
|
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()
|
// Pre-allokierte Scratch-Objekte — keine Heap-Allokation pro Frame in update()
|
||||||
private final Vector3f scratchCamDir = new Vector3f();
|
private final Vector3f scratchCamDir = new Vector3f();
|
||||||
private final Vector3f scratchCamLeft = new Vector3f();
|
private final Vector3f scratchCamLeft = new Vector3f();
|
||||||
@@ -274,7 +305,7 @@ public class PlayerInputControl {
|
|||||||
case "Backward" -> backward = isPressed;
|
case "Backward" -> backward = isPressed;
|
||||||
case "Left" -> left = isPressed;
|
case "Left" -> left = isPressed;
|
||||||
case "Right" -> right = isPressed;
|
case "Right" -> right = isPressed;
|
||||||
case "Sprint" -> sprint = isPressed;
|
case "Sprint" -> sprint = isPressed;
|
||||||
case "Walk" -> walk = isPressed;
|
case "Walk" -> walk = isPressed;
|
||||||
case "Jump" -> { if (isPressed && physicsChar != null) { physicsChar.jump(); jumpFrames = 12; } }
|
case "Jump" -> { if (isPressed && physicsChar != null) { physicsChar.jump(); jumpFrames = 12; } }
|
||||||
case ACT_DRAW_MELEE -> { if (isPressed) handleDrawMelee(); }
|
case ACT_DRAW_MELEE -> { if (isPressed) handleDrawMelee(); }
|
||||||
@@ -283,19 +314,7 @@ public class PlayerInputControl {
|
|||||||
case ACT_COMBAT_ATK -> {
|
case ACT_COMBAT_ATK -> {
|
||||||
if (isPressed) {
|
if (isPressed) {
|
||||||
if (!weaponDrawn || blockingAnimActive || drawAnimActive || isBlocking()) break;
|
if (!weaponDrawn || blockingAnimActive || drawAnimActive || isBlocking()) break;
|
||||||
if (combatAnimActive) {
|
// Immer Hold-Timer starten – auch mitten in einer Kombo
|
||||||
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;
|
|
||||||
}
|
|
||||||
attackHoldPending = true;
|
attackHoldPending = true;
|
||||||
attackHoldTimer = 0f;
|
attackHoldTimer = 0f;
|
||||||
} else {
|
} else {
|
||||||
@@ -303,16 +322,26 @@ public class PlayerInputControl {
|
|||||||
attackHoldPending = false;
|
attackHoldPending = false;
|
||||||
if (attackHoldTimer > QUICK_TAP_THRESHOLD) {
|
if (attackHoldTimer > QUICK_TAP_THRESHOLD) {
|
||||||
handleHeavyAttack();
|
handleHeavyAttack();
|
||||||
} else {
|
} else if (combatAnimActive && comboWindowActive) {
|
||||||
// Schneller Klick → schwere Anim abbrechen, normale Kombo starten
|
// Schneller Klick im Kombo-Fenster → Kombo fortsetzen
|
||||||
combatAnimActive = false;
|
combatAnimActive = false;
|
||||||
combatAnimRemaining = 0f;
|
combatAnimRemaining = 0f;
|
||||||
|
combatAnimSkipComboWindow = false;
|
||||||
|
heavyLungeVel = null;
|
||||||
|
heavyLungeStartRemaining = 0f;
|
||||||
|
heavyLungeEndRemaining = 0f;
|
||||||
|
handleCombatAttack();
|
||||||
|
} else if (!combatAnimActive) {
|
||||||
|
// Schneller Klick außerhalb Kombo → normaler Angriff
|
||||||
handleCombatAttack();
|
handleCombatAttack();
|
||||||
}
|
}
|
||||||
|
// Schneller Klick während Kombo aber außerhalb Fenster → ignorieren
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
case ACT_COMBAT_ALT -> handleCombatAlt(isPressed);
|
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) {
|
if (skinningControl != null) {
|
||||||
armature = skinningControl.getArmature();
|
armature = skinningControl.getArmature();
|
||||||
logJointNames(armature);
|
logJointNames(armature);
|
||||||
|
hipsJoint = armature.getJoint("mixamorig:Hips");
|
||||||
jointRightArm = armature.getJoint("mixamorig:RightArm");
|
jointRightArm = armature.getJoint("mixamorig:RightArm");
|
||||||
jointRightForeArm = armature.getJoint("mixamorig:RightForeArm");
|
jointRightForeArm = armature.getJoint("mixamorig:RightForeArm");
|
||||||
jointLeftArm = armature.getJoint("mixamorig:LeftArm");
|
jointLeftArm = armature.getJoint("mixamorig:LeftArm");
|
||||||
@@ -668,12 +698,29 @@ public class PlayerInputControl {
|
|||||||
inputManager.addMapping("Right", new KeyTrigger(kb.right));
|
inputManager.addMapping("Right", new KeyTrigger(kb.right));
|
||||||
inputManager.addMapping("Jump", new KeyTrigger(kb.jump));
|
inputManager.addMapping("Jump", new KeyTrigger(kb.jump));
|
||||||
inputManager.addMapping("Sprint", new KeyTrigger(kb.sprint));
|
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("Walk", new KeyTrigger(kb.walk));
|
||||||
inputManager.addMapping(ACT_DRAW_MELEE, new KeyTrigger(kb.drawMeleeWeapon));
|
inputManager.addMapping(ACT_DRAW_MELEE, new KeyTrigger(kb.drawMeleeWeapon));
|
||||||
inputManager.addMapping(ACT_DRAW_RANGED, new KeyTrigger(kb.drawRangedWeapon));
|
inputManager.addMapping(ACT_DRAW_RANGED, new KeyTrigger(kb.drawRangedWeapon));
|
||||||
inputManager.addMapping(ACT_SHEATH, new KeyTrigger(kb.sheathWeapon));
|
inputManager.addMapping(ACT_SHEATH, new KeyTrigger(kb.sheathWeapon));
|
||||||
inputManager.addMapping(ACT_COMBAT_ATK, new MouseButtonTrigger(MouseInput.BUTTON_LEFT));
|
inputManager.addMapping(ACT_COMBAT_ATK, new MouseButtonTrigger(MouseInput.BUTTON_LEFT));
|
||||||
inputManager.addMapping(ACT_COMBAT_ALT, new MouseButtonTrigger(MouseInput.BUTTON_RIGHT));
|
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);
|
inputManager.addListener(actionListener, ACTION_NAMES);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -685,7 +732,7 @@ public class PlayerInputControl {
|
|||||||
runningClip = animPlayer.getCurrentClip();
|
runningClip = animPlayer.getCurrentClip();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (visual != null && animOffsetElapsed < AnimPlayer.BLEND_DURATION) {
|
if (visual != null && animOffsetElapsed < AnimPlayer.BLEND_DURATION && !rollActive) {
|
||||||
animOffsetElapsed += tpf;
|
animOffsetElapsed += tpf;
|
||||||
float t = Math.min(1f, animOffsetElapsed / AnimPlayer.BLEND_DURATION);
|
float t = Math.min(1f, animOffsetElapsed / AnimPlayer.BLEND_DURATION);
|
||||||
animOffsetCurrent.set(animOffsetFrom);
|
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)
|
// Drehung auf der Stelle (kein Vorwärtsbewegen)
|
||||||
if (turnActive) {
|
if (turnActive) {
|
||||||
turnRemaining -= tpf;
|
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
|
// Ruhezustand (liegen / sitzen) – blockiert alle Bewegungseingaben
|
||||||
if (lockedInPlace) {
|
if (lockedInPlace) {
|
||||||
physicsChar.setWalkDirection(Vector3f.ZERO);
|
physicsChar.setWalkDirection(Vector3f.ZERO);
|
||||||
@@ -803,16 +914,19 @@ public class PlayerInputControl {
|
|||||||
// Schon ab WAITING Gehgeschwindigkeit erzwingen, damit beim Blockstart kein Sprint-Frame entsteht
|
// Schon ab WAITING Gehgeschwindigkeit erzwingen, damit beim Blockstart kein Sprint-Frame entsteht
|
||||||
boolean inBlockStance = isBlocking() || blockPhase == BlockPhase.WAITING;
|
boolean inBlockStance = isBlocking() || blockPhase == BlockPhase.WAITING;
|
||||||
|
|
||||||
|
boolean sneaking = sneakPhase != SneakPhase.NONE;
|
||||||
|
|
||||||
// Ausdauer: Regen + Sprint-Verbrauch
|
// Ausdauer: Regen + Sprint-Verbrauch
|
||||||
boolean isSprinting = sprint && !weaponDrawn && moving && !staminaExhausted;
|
boolean isSprinting = sprint && !weaponDrawn && moving && !staminaExhausted && !sneaking;
|
||||||
tickStamina(tpf, isSprinting);
|
tickStamina(tpf, isSprinting);
|
||||||
|
|
||||||
if (moving) {
|
if (moving) {
|
||||||
scratchMoveDir.normalizeLocal();
|
scratchMoveDir.normalizeLocal();
|
||||||
boolean canSprint = sprint && !weaponDrawn && !staminaExhausted
|
boolean canSprint = !sneaking && sprint && !weaponDrawn && !staminaExhausted
|
||||||
&& (mainCharacter == null || mainCharacter.getMaxStamina() <= 0
|
&& (mainCharacter == null || mainCharacter.getMaxStamina() <= 0
|
||||||
|| mainCharacter.getCurrentStamina() > 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
|
: canSprint ? MOVE_SPEED * SPRINT_MULT
|
||||||
: MOVE_SPEED;
|
: MOVE_SPEED;
|
||||||
physicsChar.setWalkDirection(scratchWalk.set(scratchMoveDir).multLocal(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
|
// Hold-Timer muss jede Frame akkumulieren, unabhängig vom Anim-Status
|
||||||
if (attackHoldPending) {
|
if (attackHoldPending) {
|
||||||
attackHoldTimer += tpf;
|
attackHoldTimer += tpf;
|
||||||
// Erst wenn klar ist dass es ein schwerer Angriff wird → Animation starten
|
if (attackHoldTimer > QUICK_TAP_THRESHOLD) {
|
||||||
if (attackHoldTimer > QUICK_TAP_THRESHOLD && !combatAnimActive) {
|
if (combatAnimActive && !combatAnimSkipComboWindow) {
|
||||||
startHeavyCharge();
|
// 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--;
|
if (groundGraceFrames > 0) groundGraceFrames--;
|
||||||
|
|
||||||
// Block-Phase verwaltet ihre eigenen Animationen.
|
// 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;
|
AnimationAction target;
|
||||||
if (jumpFrames > 0 || (!physicsChar.onGround() && groundGraceFrames <= 0)) {
|
if (jumpFrames > 0 || (!physicsChar.onGround() && groundGraceFrames <= 0)) {
|
||||||
target = moving ? AnimationAction.RUNNING_JUMP : AnimationAction.JUMP;
|
target = moving ? AnimationAction.RUNNING_JUMP : AnimationAction.JUMP;
|
||||||
|
} else if (sneaking) {
|
||||||
|
target = moving ? AnimationAction.SNEAK : AnimationAction.CROUCH_IDLE;
|
||||||
} else if (moving) {
|
} else if (moving) {
|
||||||
target = (inDeepWater || walk) ? AnimationAction.WALK
|
target = (inDeepWater || walk) ? AnimationAction.WALK
|
||||||
: sprint ? AnimationAction.SPRINT
|
: sprint ? AnimationAction.SPRINT
|
||||||
@@ -1271,6 +1400,62 @@ public class PlayerInputControl {
|
|||||||
return true;
|
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() {
|
private boolean hasShieldEquipped() {
|
||||||
return mainCharacter != null && mainCharacter.getEquippedShield() != null;
|
return mainCharacter != null && mainCharacter.getEquippedShield() != null;
|
||||||
}
|
}
|
||||||
@@ -1647,22 +1832,32 @@ public class PlayerInputControl {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private String currentAnimToGait(AnimationAction anim) {
|
private String currentAnimToGait(AnimationAction anim) {
|
||||||
if (anim == AnimationAction.WALK) return "walking";
|
if (anim == null) return null;
|
||||||
if (anim == AnimationAction.RUN) return "running";
|
return switch (anim) {
|
||||||
if (anim == AnimationAction.SPRINT) return "sprinting";
|
case WALK,
|
||||||
return null;
|
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) {
|
private void triggerStep(String gait) {
|
||||||
|
float vol = (sneakPhase == SneakPhase.ACTIVE) ? 0.25f : 1f;
|
||||||
if (currentWaterDepth > 0f) {
|
if (currentWaterDepth > 0f) {
|
||||||
// Im Wasser: Wassergeräusche aus footsteps/water/walking bzw. /running
|
|
||||||
float[] weights = new float[de.blight.game.audio.SurfaceType.values().length];
|
float[] weights = new float[de.blight.game.audio.SurfaceType.values().length];
|
||||||
weights[de.blight.game.audio.SurfaceType.WATER.ordinal()] = 1f;
|
weights[de.blight.game.audio.SurfaceType.WATER.ordinal()] = 1f;
|
||||||
footstepSystem.play(weights, gait);
|
footstepSystem.play(weights, gait, vol);
|
||||||
} else {
|
} else {
|
||||||
Vector3f pos = physicsChar.getPhysicsLocation();
|
Vector3f pos = physicsChar.getPhysicsLocation();
|
||||||
float[] weights = surfaceQuery.apply(pos.x, pos.z);
|
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);
|
WorldNpcsState npcsState = new WorldNpcsState(keyBindings, physicsChar, playerInput, mc);
|
||||||
npcsState.setThirdPersonCamera(thirdPersonCam);
|
npcsState.setThirdPersonCamera(thirdPersonCam);
|
||||||
app.getStateManager().attach(npcsState);
|
app.getStateManager().attach(npcsState);
|
||||||
app.getStateManager().attach(new InteractionHudState());
|
app.getStateManager().attach(new InteractionHudState(keyBindings));
|
||||||
inventoryState = new InventoryState(mc, keyBindings);
|
inventoryState = new InventoryState(mc, keyBindings);
|
||||||
inventoryState.setEnabled(false);
|
inventoryState.setEnabled(false);
|
||||||
app.getStateManager().attach(inventoryState);
|
app.getStateManager().attach(inventoryState);
|
||||||
|
|||||||
@@ -10,6 +10,8 @@ import com.jme3.math.Vector3f;
|
|||||||
import com.jme3.renderer.Camera;
|
import com.jme3.renderer.Camera;
|
||||||
import com.jme3.scene.Node;
|
import com.jme3.scene.Node;
|
||||||
import com.jme3.scene.Spatial;
|
import com.jme3.scene.Spatial;
|
||||||
|
import de.blight.game.config.KeyBindings;
|
||||||
|
import de.blight.game.config.KeyNames;
|
||||||
import de.blight.lang.TextResolver;
|
import de.blight.lang.TextResolver;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
@@ -33,6 +35,10 @@ public class InteractionHudState extends BaseAppState {
|
|||||||
private DialogHudState dialogHud;
|
private DialogHudState dialogHud;
|
||||||
|
|
||||||
private BitmapText labelText;
|
private BitmapText labelText;
|
||||||
|
private KeyBindings keyBindings;
|
||||||
|
|
||||||
|
public InteractionHudState() {}
|
||||||
|
public InteractionHudState(KeyBindings keyBindings) { this.keyBindings = keyBindings; }
|
||||||
|
|
||||||
// ── Lifecycle ─────────────────────────────────────────────────────────────
|
// ── Lifecycle ─────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
@@ -134,6 +140,9 @@ public class InteractionHudState extends BaseAppState {
|
|||||||
}
|
}
|
||||||
|
|
||||||
String text = TextResolver.get().resolveId(labelKey);
|
String text = TextResolver.get().resolveId(labelKey);
|
||||||
|
if (keyBindings != null) {
|
||||||
|
text = text.replace("{interact}", KeyNames.of(keyBindings.interact));
|
||||||
|
}
|
||||||
labelText.setText(text);
|
labelText.setText(text);
|
||||||
float textW = labelText.getLineWidth();
|
float textW = labelText.getLineWidth();
|
||||||
labelText.setLocalTranslation(screenV.x - textW * 0.5f, screenV.y, 1f);
|
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.CharacterControl;
|
||||||
import com.jme3.bullet.control.RigidBodyControl;
|
import com.jme3.bullet.control.RigidBodyControl;
|
||||||
import com.jme3.bullet.util.CollisionShapeFactory;
|
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.InputManager;
|
||||||
import com.jme3.input.MouseInput;
|
import com.jme3.input.MouseInput;
|
||||||
import com.jme3.input.controls.ActionListener;
|
import com.jme3.input.controls.ActionListener;
|
||||||
@@ -37,6 +35,7 @@ import de.blight.common.PlacedModel;
|
|||||||
import de.blight.common.PlacedModelIO;
|
import de.blight.common.PlacedModelIO;
|
||||||
import de.blight.common.model.CombatDummy;
|
import de.blight.common.model.CombatDummy;
|
||||||
import de.blight.common.model.CombatDummyIO;
|
import de.blight.common.model.CombatDummyIO;
|
||||||
|
import de.blight.game.animation.AnimationAction;
|
||||||
import de.blight.game.combat.DamageCalculator;
|
import de.blight.game.combat.DamageCalculator;
|
||||||
import de.blight.game.combat.MeleeAttackCallback;
|
import de.blight.game.combat.MeleeAttackCallback;
|
||||||
import de.blight.game.config.KeyBindings;
|
import de.blight.game.config.KeyBindings;
|
||||||
@@ -62,11 +61,12 @@ public class WorldDummyState extends BaseAppState implements MeleeAttackCallback
|
|||||||
// ── Spielwelt-Konstanten ──────────────────────────────────────────────────
|
// ── Spielwelt-Konstanten ──────────────────────────────────────────────────
|
||||||
private static final float FOCUS_RANGE = 6f;
|
private static final float FOCUS_RANGE = 6f;
|
||||||
private static final float RESTORE_DELAY = 60f;
|
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 float STAND_UP_RANGE = 3f;
|
||||||
private static final int DEFAULT_MAX_HP = 250;
|
private static final int DEFAULT_MAX_HP = 10;
|
||||||
private static final float FALL_DURATION = 0.35f;
|
private static final float MODEL_HALF_H = 0.75f;
|
||||||
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 ────────────────────────────────────────────────
|
// ── Welt-HP-Bar-Konstanten ────────────────────────────────────────────────
|
||||||
private static final float WB_WIDTH = 0.8f;
|
private static final float WB_WIDTH = 0.8f;
|
||||||
@@ -103,8 +103,6 @@ public class WorldDummyState extends BaseAppState implements MeleeAttackCallback
|
|||||||
// ── Feind-HUD ─────────────────────────────────────────────────────────────
|
// ── Feind-HUD ─────────────────────────────────────────────────────────────
|
||||||
private Node enemyHudNode;
|
private Node enemyHudNode;
|
||||||
private Geometry enemyHpFill;
|
private Geometry enemyHpFill;
|
||||||
private BitmapText enemyNameText;
|
|
||||||
private BitmapText enemyHpText;
|
|
||||||
/** x-Koordinate des linken Randes der Feind-HP-Bar (Screen-Pixel). */
|
/** x-Koordinate des linken Randes der Feind-HP-Bar (Screen-Pixel). */
|
||||||
private float ehbX;
|
private float ehbX;
|
||||||
/** y-Koordinate des unteren Randes der Feind-HP-Bar (Screen-Pixel). */
|
/** 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);
|
enemyHpFill.setLocalTranslation(ehbX + EHB_BORDER, ehbY + EHB_BORDER, 2f);
|
||||||
enemyHudNode.attachChild(enemyHpFill);
|
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);
|
guiNode.attachChild(enemyHudNode);
|
||||||
enemyHudNode.setCullHint(Spatial.CullHint.Always);
|
enemyHudNode.setCullHint(Spatial.CullHint.Always);
|
||||||
}
|
}
|
||||||
@@ -375,23 +354,8 @@ public class WorldDummyState extends BaseAppState implements MeleeAttackCallback
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void updateEnemyHudContent(DummyRuntime dr) {
|
private void updateEnemyHudContent(DummyRuntime dr) {
|
||||||
float sw = cam.getWidth();
|
|
||||||
float ratio = dr.maxHp > 0 ? (float) dr.currentHp / dr.maxHp : 0f;
|
float ratio = dr.maxHp > 0 ? (float) dr.currentHp / dr.maxHp : 0f;
|
||||||
applyEnemyHpRatio(ratio);
|
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 ───────────────────────────────────────────────────
|
// ── MeleeAttackCallback ───────────────────────────────────────────────────
|
||||||
@@ -437,14 +401,19 @@ public class WorldDummyState extends BaseAppState implements MeleeAttackCallback
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void knockout(DummyRuntime dr) {
|
private void knockout(DummyRuntime dr) {
|
||||||
dr.fallen = true;
|
dr.fallen = true;
|
||||||
dr.fallenLerp = 0f;
|
dr.fallenLerp = 0f;
|
||||||
|
dr.knockAngle = 0f;
|
||||||
|
dr.knockAngVel = KNOCK_INITIAL_VEL;
|
||||||
dr.hpBarNode.setCullHint(Spatial.CullHint.Always);
|
dr.hpBarNode.setCullHint(Spatial.CullHint.Always);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void standUp(DummyRuntime dr) {
|
private void standUp(DummyRuntime dr) {
|
||||||
|
playerInput.requestAnimation(AnimationAction.PICK_UP, 0f, null);
|
||||||
dr.fallen = false;
|
dr.fallen = false;
|
||||||
dr.fallenLerp = 0f;
|
dr.fallenLerp = 0f;
|
||||||
|
dr.knockAngle = 0f;
|
||||||
|
dr.knockAngVel = 0f;
|
||||||
dr.currentHp = dr.maxHp;
|
dr.currentHp = dr.maxHp;
|
||||||
dr.restoreTimer = 0f;
|
dr.restoreTimer = 0f;
|
||||||
dr.dummyNode.setLocalRotation(new Quaternion());
|
dr.dummyNode.setLocalRotation(new Quaternion());
|
||||||
@@ -476,13 +445,18 @@ public class WorldDummyState extends BaseAppState implements MeleeAttackCallback
|
|||||||
for (int i = 0; i < dummies.size(); i++) {
|
for (int i = 0; i < dummies.size(); i++) {
|
||||||
DummyRuntime dr = dummies.get(i);
|
DummyRuntime dr = dummies.get(i);
|
||||||
|
|
||||||
// Umfall-Animation
|
// Umfall-Animation (physikalisch: Schwerkraft-Drehmoment nimmt mit cos(Winkel) ab)
|
||||||
if (dr.fallen && dr.fallenLerp < 1f) {
|
if (dr.fallen && dr.fallenLerp < 1f) {
|
||||||
dr.fallenLerp = Math.min(1f, dr.fallenLerp + tpf / FALL_DURATION);
|
float gravAccel = KNOCK_GRAVITY * FastMath.cos(dr.knockAngle);
|
||||||
Quaternion target = new Quaternion().fromAngles(0f, 0f, -FastMath.HALF_PI);
|
dr.knockAngVel += gravAccel * tpf;
|
||||||
Quaternion cur = new Quaternion();
|
dr.knockAngle += dr.knockAngVel * tpf;
|
||||||
cur.slerp(Quaternion.IDENTITY, target, dr.fallenLerp);
|
if (dr.knockAngle >= FastMath.HALF_PI) {
|
||||||
dr.dummyNode.setLocalRotation(cur);
|
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
|
// Welt-HP-Balken-Timer
|
||||||
@@ -588,6 +562,8 @@ public class WorldDummyState extends BaseAppState implements MeleeAttackCallback
|
|||||||
float hpBarTimer = 0f;
|
float hpBarTimer = 0f;
|
||||||
boolean fallen = false;
|
boolean fallen = false;
|
||||||
float fallenLerp = 0f;
|
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,
|
DummyRuntime(String id, float wx, float wy, float wz,
|
||||||
Node dummyNode, Node hpBarNode, Geometry hpBarFg, int maxHp,
|
Node dummyNode, Node hpBarNode, Geometry hpBarFg, int maxHp,
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ dialog.speaker.player=Spieler
|
|||||||
# ── Interactables ─────────────────────────────────────────────────────────────
|
# ── Interactables ─────────────────────────────────────────────────────────────
|
||||||
interactable.bench.name=Bank
|
interactable.bench.name=Bank
|
||||||
interactable.bed.name=Bett
|
interactable.bed.name=Bett
|
||||||
dummy.stand_up=Aufstellen [F]
|
dummy.stand_up=Aufstellen [{interact}]
|
||||||
dummy.name=Kampfpuppe
|
dummy.name=Kampfpuppe
|
||||||
|
|
||||||
# ── Items ────────────────────────────────────────────────────────────────────
|
# ── Items ────────────────────────────────────────────────────────────────────
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ dialog.speaker.player=Player
|
|||||||
# ── Interactables ─────────────────────────────────────────────────────────────
|
# ── Interactables ─────────────────────────────────────────────────────────────
|
||||||
interactable.bench.name=Bench
|
interactable.bench.name=Bench
|
||||||
interactable.bed.name=Bed
|
interactable.bed.name=Bed
|
||||||
dummy.stand_up=Stand Up [F]
|
dummy.stand_up=Stand Up [{interact}]
|
||||||
dummy.name=Training Dummy
|
dummy.name=Training Dummy
|
||||||
|
|
||||||
# ── Items ────────────────────────────────────────────────────────────────────
|
# ── Items ────────────────────────────────────────────────────────────────────
|
||||||
|
|||||||
Reference in New Issue
Block a user