Animationen jetzt heil und Keyframes eingebaut
This commit is contained in:
@@ -0,0 +1,27 @@
|
||||
package de.blight.game.animation;
|
||||
|
||||
/**
|
||||
* Ein Keyframe für manuellen Positions-/Rotations-Versatz während einer blockierenden Animation.
|
||||
*
|
||||
* TX/TZ: Versatz in Charakter-lokalem Raum (TX=seitlich, TZ=vorwärts relativ zur Blickrichtung).
|
||||
* TY: Versatz in Welt-Y (hoch/runter).
|
||||
* RX/RY/RZ: Additiver Rotations-Versatz in Grad (Euler XYZ, relativ zur Startrotation).
|
||||
*
|
||||
* Keyframes einer Aktion werden nach {@code time} sortiert und linear interpoliert.
|
||||
*/
|
||||
public class AnimKeyframe {
|
||||
|
||||
public float time; // Sekunden seit Animations-Start
|
||||
public float tx, ty, tz; // Positions-Versatz (Meter)
|
||||
public float rx, ry, rz; // Rotations-Versatz (Grad)
|
||||
|
||||
public AnimKeyframe() {}
|
||||
|
||||
public AnimKeyframe(float time,
|
||||
float tx, float ty, float tz,
|
||||
float rx, float ry, float rz) {
|
||||
this.time = time;
|
||||
this.tx = tx; this.ty = ty; this.tz = tz;
|
||||
this.rx = rx; this.ry = ry; this.rz = rz;
|
||||
}
|
||||
}
|
||||
@@ -26,15 +26,8 @@ public class AnimSet {
|
||||
private Map<String, String> actionMap = new LinkedHashMap<>();
|
||||
/** Zuletzt im Editor verwendeter Modell-Pfad (relativ zu Assets-Root). Wird beim Öffnen auto-geladen. */
|
||||
private String previewModelPath = null;
|
||||
/** Vertikaler Versatz des Visual-Nodes während der jeweiligen Animation (Root-Motion-Ersatz, Fallback). */
|
||||
private Map<String, Float> sinkMap = new LinkedHashMap<>();
|
||||
/**
|
||||
* Pro Aktion konfigurierbarer Anchor-Knochen (z. B. SIT_DOWN → "foot.l", PICK_UP → "hand.r").
|
||||
* Wenn für eine Aktion ein Eintrag vorhanden ist, wird Bone-Anchoring verwendet:
|
||||
* der Knochen bleibt auf seiner Welt-Y vor der Animation fixiert.
|
||||
* Überschreibt sinkMap für diese Aktion.
|
||||
*/
|
||||
private Map<String, String> anchorBoneMap = new LinkedHashMap<>();
|
||||
/** Manueller Positions-/Rotations-Versatz: Aktion → sortierte Keyframe-Liste. */
|
||||
private Map<String, List<AnimKeyframe>> motionKeyframes = new LinkedHashMap<>();
|
||||
|
||||
public List<String> getClips() { return clips; }
|
||||
public void setClips(List<String> clips) { this.clips = clips; }
|
||||
@@ -42,10 +35,12 @@ public class AnimSet {
|
||||
public void setActionMap(Map<String, String> actionMap) { this.actionMap = actionMap; }
|
||||
public String getPreviewModelPath() { return previewModelPath; }
|
||||
public void setPreviewModelPath(String previewModelPath) { this.previewModelPath = previewModelPath; }
|
||||
public Map<String, Float> getSinkMap() { return sinkMap != null ? sinkMap : new LinkedHashMap<>(); }
|
||||
public void setSinkMap(Map<String, Float> sinkMap) { this.sinkMap = sinkMap; }
|
||||
public Map<String, String> getAnchorBoneMap() { return anchorBoneMap != null ? anchorBoneMap : new LinkedHashMap<>(); }
|
||||
public void setAnchorBoneMap(Map<String, String> anchorBoneMap) { this.anchorBoneMap = anchorBoneMap; }
|
||||
public Map<String, List<AnimKeyframe>> getMotionKeyframes() {
|
||||
return motionKeyframes != null ? motionKeyframes : new LinkedHashMap<>();
|
||||
}
|
||||
public void setMotionKeyframes(Map<String, List<AnimKeyframe>> motionKeyframes) {
|
||||
this.motionKeyframes = motionKeyframes;
|
||||
}
|
||||
|
||||
/** Speichert dieses Set als {@code <setName>.animset.json} im Verzeichnis {@code setDir}. */
|
||||
public void save(Path setDir, String setName) throws IOException {
|
||||
|
||||
@@ -49,16 +49,8 @@ public class PlayerInputControl {
|
||||
|
||||
private AnimComposer animComposer;
|
||||
private String runningClip;
|
||||
private java.util.Map<String, Float> animSinkMap = java.util.Map.of();
|
||||
private java.util.Map<String, String> animAnchorBoneMap = java.util.Map.of();
|
||||
|
||||
/** Bone-Anchoring: SkinningControl + Referenz-Position vor der Animation (Model-Space, alle Achsen). */
|
||||
private com.jme3.anim.SkinningControl skinningControl = null;
|
||||
private Vector3f preAnimAnchorBoneModel = null;
|
||||
private Vector3f preAnimVisualTranslation = null;
|
||||
private String currentAnchorBone = null;
|
||||
private boolean boneAnchorWarnLogged = false;
|
||||
private int boneAnchorLogFrames = 0;
|
||||
private com.jme3.anim.SkinningControl skinningControl = null;
|
||||
private int jumpFrames = 0;
|
||||
private boolean pickupActive = false;
|
||||
private float pickupRemaining = 0f;
|
||||
@@ -69,14 +61,13 @@ public class PlayerInputControl {
|
||||
private float blockingAnimTotal = 0f;
|
||||
private Runnable blockingAnimCallback = null;
|
||||
|
||||
/**
|
||||
* Vertikaler Versatz des Visual-Nodes während einer blockierenden Animation
|
||||
* (Root-Motion-Ersatz: Körper senkt sich beim Setzen, hebt sich beim Aufstehen).
|
||||
* visualSinkCurrent wird pro Frame interpoliert.
|
||||
*/
|
||||
private float visualSinkStart = 0f;
|
||||
private float visualSinkTarget = 0f;
|
||||
private float visualSinkCurrent = 0f;
|
||||
/** Manueller Motion-Override: pro Aktion konfigurierbare Keyframe-Liste. */
|
||||
private java.util.Map<String, java.util.List<de.blight.game.animation.AnimKeyframe>>
|
||||
motionKeyframesMap = java.util.Map.of();
|
||||
private java.util.List<de.blight.game.animation.AnimKeyframe>
|
||||
currentMotionKfs = null;
|
||||
private Vector3f preMotionTranslation = null;
|
||||
private Quaternion preMotionRotation = null;
|
||||
|
||||
/** Drehung auf der Stelle (kein Vorwärtsbewegen, nur Rotation). */
|
||||
private boolean turnActive = false;
|
||||
@@ -125,21 +116,19 @@ public class PlayerInputControl {
|
||||
this.runningClip = null;
|
||||
this.animComposer = (visual != null) ? RetargetingSystem.findAnimComposer(visual) : null;
|
||||
log.info("[AnimCtx] AnimComposer gefunden: {}", animComposer != null);
|
||||
// SinkMap + AnchorBoneMap aus AnimSet laden
|
||||
skinningControl = findSkinningControl(visual);
|
||||
log.info("[AnimCtx] SkinningControl gefunden: {}", skinningControl != null);
|
||||
if (animSetName != null && assetRoot != null) {
|
||||
try {
|
||||
java.nio.file.Path setDir = assetRoot.resolve("animations").resolve("sets");
|
||||
de.blight.game.animation.AnimSet set = de.blight.game.animation.AnimSet.load(setDir, animSetName);
|
||||
animSinkMap = set.getSinkMap();
|
||||
animAnchorBoneMap = set.getAnchorBoneMap();
|
||||
motionKeyframesMap = set.getMotionKeyframes();
|
||||
log.info("[AnimCtx] MotionKeyframes geladen: {} Aktionen: {}",
|
||||
motionKeyframesMap.size(), motionKeyframesMap.keySet());
|
||||
} catch (Exception e) {
|
||||
animSinkMap = java.util.Map.of();
|
||||
animAnchorBoneMap = java.util.Map.of();
|
||||
motionKeyframesMap = java.util.Map.of();
|
||||
}
|
||||
}
|
||||
skinningControl = findSkinningControl(visual);
|
||||
log.info("[AnimCtx] SkinningControl gefunden: {}, AnchorBoneMap: {}",
|
||||
skinningControl != null, animAnchorBoneMap);
|
||||
if (animSetName != null) {
|
||||
String clip = AnimationLibrary.getClipForAction(assetRoot, animSetName, AnimationAction.IDLE);
|
||||
if (clip != null && tryPlay(clip)) {
|
||||
@@ -200,31 +189,25 @@ public class PlayerInputControl {
|
||||
if (duration <= 0f) {
|
||||
duration = resolveClipLength(action, 1.5f);
|
||||
}
|
||||
// Bone-Anchoring: pro Aktion konfigurierten Knochen laden und Referenz-Y einfrieren
|
||||
currentAnchorBone = animAnchorBoneMap.get(action.name());
|
||||
if (currentAnchorBone != null && !currentAnchorBone.isBlank()) {
|
||||
preAnimAnchorBoneModel = getBoneModelPos(currentAnchorBone);
|
||||
preAnimVisualTranslation = visual != null ? visual.getLocalTranslation().clone() : new Vector3f();
|
||||
boneAnchorWarnLogged = false;
|
||||
boneAnchorLogFrames = 0;
|
||||
log.info("[BoneAnchor] Aktion={} Knochen='{}' preModelY={} (null={})",
|
||||
action.name(), currentAnchorBone,
|
||||
preAnimAnchorBoneModel != null ? preAnimAnchorBoneModel.y : Float.NaN,
|
||||
preAnimAnchorBoneModel == null);
|
||||
String kfClip = AnimationLibrary.getClipForAction(assetRoot, animSetName, action);
|
||||
currentMotionKfs = kfClip != null ? motionKeyframesMap.get(kfClip) : null;
|
||||
if (currentMotionKfs != null && !currentMotionKfs.isEmpty() && visual != null) {
|
||||
// KF-Werte sind absolute Positionen im Charakter-Lokalraum (nicht additiv)
|
||||
preMotionTranslation = Vector3f.ZERO;
|
||||
preMotionRotation = visual.getLocalRotation().clone();
|
||||
} else {
|
||||
currentAnchorBone = null;
|
||||
preAnimAnchorBoneModel = null;
|
||||
preAnimVisualTranslation = null;
|
||||
// Fallback: manuellen Sink aus AnimSet-Konfiguration laden
|
||||
if (animSinkMap.containsKey(action.name())) {
|
||||
visualSinkTarget = animSinkMap.get(action.name());
|
||||
// Keine Keyframes: visuelle Verschiebung aus vorheriger Keyframe-Aktion zurücksetzen
|
||||
if (visual != null) {
|
||||
visual.setLocalTranslation(Vector3f.ZERO);
|
||||
}
|
||||
currentMotionKfs = null;
|
||||
preMotionTranslation = null;
|
||||
preMotionRotation = null;
|
||||
}
|
||||
blockingAnimActive = true;
|
||||
blockingAnimRemaining = duration;
|
||||
blockingAnimTotal = duration;
|
||||
blockingAnimCallback = onComplete;
|
||||
visualSinkStart = visualSinkCurrent;
|
||||
autopilotDir = null;
|
||||
forward = backward = left = right = false;
|
||||
if (physicsChar != null) physicsChar.setWalkDirection(Vector3f.ZERO);
|
||||
@@ -232,14 +215,6 @@ public class PlayerInputControl {
|
||||
currentAnim = action;
|
||||
}
|
||||
|
||||
/**
|
||||
* Überschreibt das Sink-Ziel für die nächste {@link #requestAnimation}-Animation manuell.
|
||||
* Hat Vorrang vor der AnimSet-Konfiguration wenn VOR requestAnimation aufgerufen.
|
||||
*/
|
||||
public void setNextAnimationSink(float targetY) {
|
||||
this.visualSinkTarget = targetY;
|
||||
}
|
||||
|
||||
/** Liefert die Länge des Clips für {@code action} in Sekunden, oder {@code fallback} wenn nicht ermittelbar. */
|
||||
private float resolveClipLength(AnimationAction action, float fallback) {
|
||||
if (animComposer == null || animLib == null || animSetName == null) {
|
||||
@@ -295,10 +270,27 @@ public class PlayerInputControl {
|
||||
/**
|
||||
* Spielt eine Animations-Aktion als Dauer-Loop während des Ruhezustands.
|
||||
* Nur sinnvoll nach {@link #lockInPlace()}.
|
||||
* Hat die Aktion Motion Keyframes, wird der erste Keyframe (time=0) als statischer
|
||||
* Versatz auf den Visual-Node angewendet.
|
||||
*/
|
||||
public void playLockedAnimation(AnimationAction action) {
|
||||
playAction(action);
|
||||
currentAnim = action;
|
||||
String lockedClip = AnimationLibrary.getClipForAction(assetRoot, animSetName, action);
|
||||
java.util.List<de.blight.game.animation.AnimKeyframe> kfs =
|
||||
lockedClip != null ? motionKeyframesMap.get(lockedClip) : null;
|
||||
log.info("[AnimKF] playLockedAnimation({}) clip='{}' → KFs gefunden: {}",
|
||||
action, lockedClip, kfs != null ? kfs.size() : "null");
|
||||
if (kfs != null && !kfs.isEmpty() && visual != null) {
|
||||
currentMotionKfs = kfs;
|
||||
preMotionTranslation = Vector3f.ZERO;
|
||||
preMotionRotation = visual.getLocalRotation().clone();
|
||||
applyMotionKeyframes(0f);
|
||||
log.info("[AnimKF] Offset angewendet: visual.localTranslation={}", visual.getLocalTranslation());
|
||||
currentMotionKfs = null;
|
||||
preMotionTranslation = null;
|
||||
preMotionRotation = null;
|
||||
}
|
||||
}
|
||||
|
||||
private void registerMappings(KeyBindings kb) {
|
||||
@@ -366,34 +358,22 @@ public class PlayerInputControl {
|
||||
if (blockingAnimActive) {
|
||||
blockingAnimRemaining -= tpf;
|
||||
physicsChar.setWalkDirection(Vector3f.ZERO);
|
||||
|
||||
// Visuellen Versatz anpassen: Foot-Anchoring hat Vorrang vor manuellem Sink
|
||||
if (visual != null) {
|
||||
if (currentAnchorBone != null && preAnimAnchorBoneModel != null) {
|
||||
// Bone-Anchoring: 3D-Delta im Model-Space messen und als Visual-Offset anwenden.
|
||||
// Model-Space ist unabhängig vom Visual-Shift → keine Rückkopplung.
|
||||
applyBoneAnchorOffset(currentAnchorBone);
|
||||
} else if (blockingAnimTotal > 0f) {
|
||||
// Fallback: manueller Sink interpoliert
|
||||
float t = Math.max(0f, Math.min(1f, 1f - blockingAnimRemaining / blockingAnimTotal));
|
||||
visualSinkCurrent = visualSinkStart + (visualSinkTarget - visualSinkStart) * t;
|
||||
applyVisualSink();
|
||||
}
|
||||
}
|
||||
float elapsed = blockingAnimTotal - blockingAnimRemaining;
|
||||
applyMotionKeyframes(elapsed);
|
||||
|
||||
if (blockingAnimRemaining <= 0f) {
|
||||
blockingAnimActive = false;
|
||||
if (currentAnchorBone != null && preAnimAnchorBoneModel != null) {
|
||||
// Bone-Anchoring: letzten Kompensationswert einrasten
|
||||
applyBoneAnchorOffset(currentAnchorBone);
|
||||
} else {
|
||||
// Fallback: Zielwert einrasten
|
||||
visualSinkCurrent = visualSinkTarget;
|
||||
applyVisualSink();
|
||||
}
|
||||
applyMotionKeyframes(blockingAnimTotal); // Endwert einrasten
|
||||
currentMotionKfs = null;
|
||||
preMotionTranslation = null;
|
||||
preMotionRotation = null;
|
||||
Runnable cb = blockingAnimCallback;
|
||||
blockingAnimCallback = null;
|
||||
if (cb != null) cb.run();
|
||||
// Kein Ruhezustand nach der Animation → visuellen Versatz zurücksetzen
|
||||
if (!lockedInPlace && visual != null) {
|
||||
visual.setLocalTranslation(Vector3f.ZERO);
|
||||
}
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
@@ -489,67 +469,6 @@ public class PlayerInputControl {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Liefert die aktuelle Welt-Y des angegebenen Joints, oder NaN wenn nicht ermittelbar.
|
||||
* Liest den Joint aus dem SkinningControl (nach AnimComposer-Update = aktueller Frame).
|
||||
*/
|
||||
/**
|
||||
* Gibt die Position des Joints im Model-Space des Armatures zurück.
|
||||
* Bewusst KEIN Welt-Transform: sonst entsteht eine Rückkopplung mit dem Visual-Offset,
|
||||
* weil der Visual-Node-Shift den Welt-Transform des Knochens beeinflusst.
|
||||
*/
|
||||
private Vector3f getBoneModelPos(String boneName) {
|
||||
if (skinningControl == null || boneName == null || boneName.isBlank()) {
|
||||
return null;
|
||||
}
|
||||
com.jme3.anim.Armature armature = skinningControl.getArmature();
|
||||
if (armature == null) {
|
||||
return null;
|
||||
}
|
||||
com.jme3.anim.Joint joint = armature.getJoint(boneName);
|
||||
if (joint == null) {
|
||||
return null;
|
||||
}
|
||||
return joint.getModelTransform().getTranslation().clone();
|
||||
}
|
||||
|
||||
/**
|
||||
* Berechnet den Y-Offset des Anchor-Knochens gegenüber seiner Startposition
|
||||
* (in Model-Space, keine Rückkopplung mit dem Visual-Shift) und setzt die
|
||||
* Local-Y des Visual-Nodes so, dass der Knochen vertikal fixiert bleibt.
|
||||
*
|
||||
* Nur Y wird kompensiert. X/Z-Drift im Model-Space liegt in einem anderen
|
||||
* Koordinatensystem als der Visual-Node (Blender-Export-Rotation) und würde
|
||||
* den Charakter horizontal verschieben — das ist falsch.
|
||||
*
|
||||
* Formel: visual.localY = preAnimVisualY + (preAnimBone.y - currentBone.y) * scale
|
||||
*/
|
||||
private void applyBoneAnchorOffset(String boneName) {
|
||||
if (visual == null || preAnimAnchorBoneModel == null || preAnimVisualTranslation == null) {
|
||||
if (!boneAnchorWarnLogged) {
|
||||
log.warn("[BoneAnchor] applyBoneAnchorOffset abgebrochen: visual={} preModel={} preVis={}",
|
||||
visual != null, preAnimAnchorBoneModel, preAnimVisualTranslation);
|
||||
boneAnchorWarnLogged = true;
|
||||
}
|
||||
return;
|
||||
}
|
||||
Vector3f current = getBoneModelPos(boneName);
|
||||
if (current == null) {
|
||||
if (!boneAnchorWarnLogged) {
|
||||
log.warn("[BoneAnchor] Knochen '{}' nicht im Armature gefunden (skinningControl={})",
|
||||
boneName, skinningControl != null);
|
||||
boneAnchorWarnLogged = true;
|
||||
}
|
||||
return;
|
||||
}
|
||||
float scale = skinningControl != null && skinningControl.getSpatial() != null
|
||||
? skinningControl.getSpatial().getWorldScale().y : 1f;
|
||||
float newY = preAnimVisualTranslation.y + (preAnimAnchorBoneModel.y - current.y) * scale;
|
||||
visualSinkCurrent = newY;
|
||||
com.jme3.math.Vector3f t = visual.getLocalTranslation();
|
||||
visual.setLocalTranslation(t.x, newY, t.z);
|
||||
}
|
||||
|
||||
/** Durchsucht den Szenegraphen rekursiv nach dem ersten SkinningControl. */
|
||||
private com.jme3.anim.SkinningControl findSkinningControl(Spatial s) {
|
||||
if (s == null) {
|
||||
@@ -570,14 +489,59 @@ public class PlayerInputControl {
|
||||
return null;
|
||||
}
|
||||
|
||||
private void applyVisualSink() {
|
||||
if (visual == null) {
|
||||
return;
|
||||
/**
|
||||
* Interpoliert die Motion-Keyframes der laufenden Aktion für den Zeitpunkt {@code time}
|
||||
* und setzt Translation + Rotation des Visual-Nodes.
|
||||
* TX/TZ werden im Charakter-lokalen Raum (Rotation zu Animations-Start) angewendet.
|
||||
* Dabei gilt: positive TX = rechts vom Charakter, positives TZ = vor dem Charakter
|
||||
* (= weg von der Bank), negatives TZ = hinter den Charakter (= zur Bank hin).
|
||||
* TY ist Welt-Y. RX/RY/RZ sind additiv zur Startrotation.
|
||||
*/
|
||||
private void applyMotionKeyframes(float time) {
|
||||
if (currentMotionKfs == null || currentMotionKfs.isEmpty()) return;
|
||||
if (preMotionRotation == null || visual == null) return;
|
||||
|
||||
de.blight.game.animation.AnimKeyframe before = null, after = null;
|
||||
for (de.blight.game.animation.AnimKeyframe kf : currentMotionKfs) {
|
||||
if (kf.time <= time) { before = kf; }
|
||||
else if (after == null) { after = kf; break; }
|
||||
}
|
||||
com.jme3.math.Vector3f t = visual.getLocalTranslation();
|
||||
visual.setLocalTranslation(t.x, visualSinkCurrent, t.z);
|
||||
|
||||
float tx, ty, tz, rx, ry, rz;
|
||||
if (before == null && after == null) { return; }
|
||||
else if (before == null) {
|
||||
tx = after.tx; ty = after.ty; tz = after.tz;
|
||||
rx = after.rx; ry = after.ry; rz = after.rz;
|
||||
} else if (after == null) {
|
||||
tx = before.tx; ty = before.ty; tz = before.tz;
|
||||
rx = before.rx; ry = before.ry; rz = before.rz;
|
||||
} else {
|
||||
float t = Math.max(0f, Math.min(1f,
|
||||
(time - before.time) / (after.time - before.time)));
|
||||
tx = lerp(before.tx, after.tx, t);
|
||||
ty = lerp(before.ty, after.ty, t);
|
||||
tz = lerp(before.tz, after.tz, t);
|
||||
rx = lerp(before.rx, after.rx, t);
|
||||
ry = lerp(before.ry, after.ry, t);
|
||||
rz = lerp(before.rz, after.rz, t);
|
||||
}
|
||||
|
||||
// TX/TZ im Charakter-lokalen Raum: preMotionRotation dreht den Offset in Welt-Raum.
|
||||
// Konvention: local -Z = forward (lookAt-Konvention), also tz negativ = hinter Charakter.
|
||||
Vector3f localXZ = preMotionRotation.mult(new Vector3f(tx, 0f, tz));
|
||||
visual.setLocalTranslation(localXZ.x, ty, localXZ.z);
|
||||
|
||||
// Rotation: additiv zur Startrotation via SLERP der Euler-Offsets
|
||||
Quaternion rotOffset = new Quaternion();
|
||||
rotOffset.fromAngles(
|
||||
rx * FastMath.DEG_TO_RAD,
|
||||
ry * FastMath.DEG_TO_RAD,
|
||||
rz * FastMath.DEG_TO_RAD);
|
||||
visual.setLocalRotation(preMotionRotation.mult(rotOffset));
|
||||
}
|
||||
|
||||
private static float lerp(float a, float b, float t) { return a + (b - a) * t; }
|
||||
|
||||
private boolean tryPlay(String clip) {
|
||||
if (animComposer == null || !animLib.applyTo(clip, visual)) {
|
||||
log.info("[Anim] tryPlay('{}') → applyTo FAILED", clip);
|
||||
|
||||
@@ -399,9 +399,8 @@ public class WorldInteractableState extends BaseAppState {
|
||||
AnimationAction idleAction = isBed ? AnimationAction.LYING : AnimationAction.SITTING;
|
||||
|
||||
// duration=0 → PlayerInputControl ermittelt die echte Clip-Länge automatisch
|
||||
// Sink-Wert kommt aus AnimSet-Konfiguration (Animationseditor)
|
||||
playerInput.requestAnimation(action, 0f, () -> {
|
||||
teleportToRestPos(entry);
|
||||
if (isBed) teleportToRestPos(entry);
|
||||
playerInput.lockInPlace();
|
||||
playerInput.playLockedAnimation(idleAction);
|
||||
phase = Phase.RESTING;
|
||||
@@ -432,19 +431,10 @@ public class WorldInteractableState extends BaseAppState {
|
||||
}
|
||||
|
||||
private void teleportToRestPos(InteractableEntry entry) {
|
||||
if (physicsChar == null) return;
|
||||
if (entry.type() == InteractableType.BED) {
|
||||
Bed bed = BedIO.load(entry.interactableId()).orElse(null);
|
||||
if (bed != null && bed.isLiegeSet())
|
||||
physicsChar.setPhysicsLocation(new Vector3f(bed.getLiegeX(), bed.getLiegeY(), bed.getLiegeZ()));
|
||||
} else if (entry.type() == InteractableType.BENCH) {
|
||||
// X/Z aus dem Sitzpunkt, Y bleibt bei der aktuellen Physik-Position (Charakter ist
|
||||
// bereits auf Bodenhöhe und durch Terrain geerdet — kein Sprung nach oben)
|
||||
Bench bench = BenchIO.load(entry.interactableId()).orElse(null);
|
||||
if (bench != null && bench.isSitzSet()) {
|
||||
float currentY = physicsChar.getPhysicsLocation().y;
|
||||
physicsChar.setPhysicsLocation(new Vector3f(bench.getSitzX(), currentY, bench.getSitzZ()));
|
||||
}
|
||||
if (physicsChar == null || entry.type() != InteractableType.BED) return;
|
||||
Bed bed = BedIO.load(entry.interactableId()).orElse(null);
|
||||
if (bed != null && bed.isLiegeSet()) {
|
||||
physicsChar.setPhysicsLocation(new Vector3f(bed.getLiegeX(), bed.getLiegeY(), bed.getLiegeZ()));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user