Zwischenstand

This commit is contained in:
2026-06-21 23:04:40 +02:00
parent 52c1fb1fe8
commit e669e29096
24 changed files with 97 additions and 10 deletions

Binary file not shown.

Binary file not shown.

View File

@@ -3,11 +3,28 @@
"level": 0, "level": 0,
"xp": 0, "xp": 0,
"currentHp": 0, "currentHp": 0,
"maxHp": 0,
"currentStamina": 0, "currentStamina": 0,
"maxStamina": 0,
"currentMana": 0, "currentMana": 0,
"myMana": 0, "maxHp": 0,
"maxStamina": 0,
"maxMana": 0,
"openHpRegeneration": 0,
"openManaRegeneration": 0,
"openStaminaRegeneration": 0,
"restorationValue": 10,
"abilities": {
"lvlMagic": 1,
"lvlStaffCombat": 1,
"lvlSwordsmanship": 1,
"lvlArchery": 1,
"lvlHeavyWeapons": 1,
"lvlCrossbow": 1,
"lvlThievery": 0,
"lvlAlchemy": 0,
"lvlEngineering": 0,
"lvlSmithery": 0,
"lvlEnchanting": 0
},
"listeners": [], "listeners": [],
"characterId": "hero", "characterId": "hero",
"name": { "name": {

View File

@@ -498,6 +498,12 @@ public class EditorApp extends Application {
if (charEditorStatusLabel != null) charEditorStatusLabel.setText(embedStatus); if (charEditorStatusLabel != null) charEditorStatusLabel.setText(embedStatus);
} }
String stripStatus = input.animStripStatus;
if (stripStatus != null) {
input.animStripStatus = null;
if (charEditorStatusLabel != null) charEditorStatusLabel.setText(stripStatus);
}
String rts = input.randomTreeStatus; String rts = input.randomTreeStatus;
if (randomTreeStatusLabel != null && rts != null) { if (randomTreeStatusLabel != null && rts != null) {
randomTreeStatusLabel.setText(rts); randomTreeStatusLabel.setText(rts);
@@ -9452,6 +9458,19 @@ public class EditorApp extends Application {
input.animEmbedRequest.set(new SharedInput.AnimEmbedRequest(modelPath, setName)); input.animEmbedRequest.set(new SharedInput.AnimEmbedRequest(modelPath, setName));
}); });
Button stripClipsBtn = new Button("Eingebettete Clips löschen");
stripClipsBtn.setMaxWidth(Double.MAX_VALUE);
stripClipsBtn.setDisable(true);
charModelCombo.valueProperty().addListener((obs, ov, nv) ->
stripClipsBtn.setDisable(nv == null || nv.isBlank()));
stripClipsBtn.setOnAction(e -> {
String modelPath = charModelCombo.getValue();
if (modelPath == null || modelPath.isBlank()) return;
if (charEditorStatusLabel != null)
charEditorStatusLabel.setText("Entferne eingebettete Clips…");
input.animStripClipsRequest.set(modelPath);
});
charEditContainer.getChildren().addAll( charEditContainer.getChildren().addAll(
new Label("ID:"), charIdField, new Label("ID:"), charIdField,
new Label("Name:"), charNameField, new Label("Name:"), charNameField,
@@ -9562,7 +9581,8 @@ public class EditorApp extends Application {
charEditContainer.getChildren().addAll( charEditContainer.getChildren().addAll(
new Label("Modell:"), charModelCombo, new Label("Modell:"), charModelCombo,
new Label("Anim-Set:"), charAnimSetCombo, new Label("Anim-Set:"), charAnimSetCombo,
embedAnimBtn embedAnimBtn,
stripClipsBtn
); );
// ── Aktions-Zuweisung ────────────────────────────────────────────────── // ── Aktions-Zuweisung ──────────────────────────────────────────────────

View File

@@ -594,10 +594,16 @@ public class SharedInput {
public final java.util.concurrent.atomic.AtomicReference<AnimEmbedRequest> public final java.util.concurrent.atomic.AtomicReference<AnimEmbedRequest>
animEmbedRequest = new java.util.concurrent.atomic.AtomicReference<>(); animEmbedRequest = new java.util.concurrent.atomic.AtomicReference<>();
/** JavaFX → JME3: Modell-Pfad, aus dem alle eingebetteten AnimClips entfernt werden sollen. */
public final java.util.concurrent.atomic.AtomicReference<String>
animStripClipsRequest = new java.util.concurrent.atomic.AtomicReference<>();
/** JME3 → JavaFX: Status-Meldung für Clip- und Set-Operationen. */ /** JME3 → JavaFX: Status-Meldung für Clip- und Set-Operationen. */
public volatile String animOpStatus = null; public volatile String animOpStatus = null;
/** JME3 → JavaFX: Status-Meldung für Einbett-Operationen (Character Editor). */ /** JME3 → JavaFX: Status-Meldung für Einbett-Operationen (Character Editor). */
public volatile String animEmbedStatus = null; public volatile String animEmbedStatus = null;
/** JME3 → JavaFX: Status-Meldung für Strip-Operationen (Clips aus Modell entfernen). */
public volatile String animStripStatus = null;
// ── Modell-Konvertierung ────────────────────────────────────────────────── // ── Modell-Konvertierung ──────────────────────────────────────────────────
/** /**

View File

@@ -178,6 +178,9 @@ public class AnimPreviewState extends BaseAppState {
SharedInput.AnimEmbedRequest embedReq = input.animEmbedRequest.getAndSet(null); SharedInput.AnimEmbedRequest embedReq = input.animEmbedRequest.getAndSet(null);
if (embedReq != null) executeAnimEmbed(embedReq); if (embedReq != null) executeAnimEmbed(embedReq);
String stripPath = input.animStripClipsRequest.getAndSet(null);
if (stripPath != null) executeStripClips(stripPath);
// Geschwindigkeit live anpassen // Geschwindigkeit live anpassen
if (currentAction != null) { if (currentAction != null) {
try { currentAction.setSpeed(input.animPreviewSpeed); } catch (Exception ignored) {} try { currentAction.setSpeed(input.animPreviewSpeed); } catch (Exception ignored) {}
@@ -258,6 +261,19 @@ public class AnimPreviewState extends BaseAppState {
currentModelPath = assetPath; currentModelPath = assetPath;
previewHolder.attachChild(model); previewHolder.attachChild(model);
// Alle Clips in-place snappen (verhindert Drift im Preview)
AnimComposer previewAC = findControl(model, AnimComposer.class);
SkinningControl previewSC = findControl(model, SkinningControl.class);
if (previewAC != null && previewSC != null) {
for (AnimClip c : new java.util.ArrayList<>(previewAC.getAnimClips())) {
AnimClip snapped = de.blight.game.animation.AnimationLibrary.snapRootBoneXZ(c, previewSC.getArmature());
if (snapped != c) {
previewAC.removeAnimClip(c);
previewAC.addAnimClip(snapped);
}
}
}
// Kamera auf Bounding Box ausrichten // Kamera auf Bounding Box ausrichten
model.updateGeometricState(); model.updateGeometricState();
if (model.getWorldBound() instanceof BoundingBox bb) { if (model.getWorldBound() instanceof BoundingBox bb) {
@@ -498,9 +514,12 @@ public class AnimPreviewState extends BaseAppState {
LOG.info("[AnimPreview] Clip '{}' als '{}' gespeichert (Dateiname-Alias)", name, saveName); LOG.info("[AnimPreview] Clip '{}' als '{}' gespeichert (Dateiname-Alias)", name, saveName);
} }
// XZ-Drift einfrieren bevor gespeichert wird Clip-Dateien bleiben immer sauber
com.jme3.anim.Armature snapArm = dstArm != null ? dstArm : srcArm;
toSave = de.blight.game.animation.AnimationLibrary.snapRootBoneXZ(toSave, snapArm);
// Direkt in die Clip-Bibliothek speichern das Modell wird nicht modifiziert // Direkt in die Clip-Bibliothek speichern das Modell wird nicht modifiziert
saveClipToFile(toSave, dstArm != null ? dstArm : srcArm, saveClipToFile(toSave, snapArm, clipsDir.resolve(saveName + ".j3o"));
clipsDir.resolve(saveName + ".j3o"));
// Für den aktuellen Preview-Session auch auf das Modell anwenden (wenn geladen) // Für den aktuellen Preview-Session auch auf das Modell anwenden (wenn geladen)
if (targetAC != null) targetAC.addAnimClip(toSave); if (targetAC != null) targetAC.addAnimClip(toSave);
saved++; saved++;
@@ -925,6 +944,29 @@ public class AnimPreviewState extends BaseAppState {
} }
} }
private void executeStripClips(String modelPath) {
try {
Spatial model = loadFresh(modelPath);
AnimComposer ac = findControl(model, AnimComposer.class);
if (ac == null) {
input.animStripStatus = "Fehler: kein AnimComposer in " + modelPath;
return;
}
int count = ac.getAnimClips().size();
for (AnimClip c : new java.util.ArrayList<>(ac.getAnimClips())) {
ac.removeAnimClip(c);
}
java.nio.file.Path file = ASSET_ROOT.resolve(modelPath.replace('/', java.io.File.separatorChar));
BinaryExporter.getInstance().save(model, file.toFile());
assets.deleteFromCache(new com.jme3.asset.ModelKey(modelPath));
input.animStripStatus = count + " eingebettete Clip(s) aus '" + modelPath + "' entfernt";
LOG.info("[AnimStrip] {} Clips aus '{}' entfernt und gespeichert", count, modelPath);
} catch (Exception e) {
input.animStripStatus = "Strip-Fehler: " + e.getMessage();
LOG.error("[AnimStrip] Fehler beim Strip von {}", modelPath, e);
}
}
private void saveClipToFile(AnimClip clip, com.jme3.anim.Armature armature, private void saveClipToFile(AnimClip clip, com.jme3.anim.Armature armature,
java.nio.file.Path outFile) throws Exception { java.nio.file.Path outFile) throws Exception {
Node holder = new Node("clip_" + clip.getName()); Node holder = new Node("clip_" + clip.getName());

View File

@@ -24,7 +24,8 @@ public enum AnimationAction {
SITTING, SITTING,
SIT_DOWN_FLOOR, SIT_DOWN_FLOOR,
SITTING_FLOOR, SITTING_FLOOR,
GET_UP_FLOOR; GET_UP_FLOOR,
REVIVE;
/** Lesbare Bezeichnung für UI-Anzeige, via TextRegistry aufgelöst. */ /** Lesbare Bezeichnung für UI-Anzeige, via TextRegistry aufgelöst. */
public String displayName() { public String displayName() {
@@ -48,6 +49,7 @@ public enum AnimationAction {
case SIT_DOWN_FLOOR -> TextRegistry.resolve(null, key, "Hinsetzen (Boden)"); case SIT_DOWN_FLOOR -> TextRegistry.resolve(null, key, "Hinsetzen (Boden)");
case SITTING_FLOOR -> TextRegistry.resolve(null, key, "Sitzen (Boden)"); case SITTING_FLOOR -> TextRegistry.resolve(null, key, "Sitzen (Boden)");
case GET_UP_FLOOR -> TextRegistry.resolve(null, key, "Aufstehen (Boden)"); case GET_UP_FLOOR -> TextRegistry.resolve(null, key, "Aufstehen (Boden)");
case REVIVE -> TextRegistry.resolve(null, key, "Wiederbeleben");
}; };
} }
} }

View File

@@ -270,7 +270,7 @@ public class AnimationLibrary extends BaseAppState {
* wird Hips gesnappt. So passt der Snap zu beiden Rig-Strukturen. * wird Hips gesnappt. So passt der Snap zu beiden Rig-Strukturen.
* Erstellt einen neuen in-memory-Clip; J3O-Dateien bleiben unverändert. * Erstellt einen neuen in-memory-Clip; J3O-Dateien bleiben unverändert.
*/ */
private static AnimClip snapRootBoneXZ(AnimClip clip, Armature armature) { public static AnimClip snapRootBoneXZ(AnimClip clip, Armature armature) {
if (clip == null || armature == null) return clip; if (clip == null || armature == null) return clip;
// Tiefe des flachsten Joints mit Translation-Track bestimmen // Tiefe des flachsten Joints mit Translation-Track bestimmen