diff --git a/blight-assets/src/main/resources/Characters/Models/hero.j3o b/blight-assets/src/main/resources/Characters/Models/hero.j3o index ba61881..4829c05 100644 Binary files a/blight-assets/src/main/resources/Characters/Models/hero.j3o and b/blight-assets/src/main/resources/Characters/Models/hero.j3o differ diff --git a/blight-assets/src/main/resources/Characters/hero.character b/blight-assets/src/main/resources/Characters/hero.character index 12c68ab..cf321b6 100644 --- a/blight-assets/src/main/resources/Characters/hero.character +++ b/blight-assets/src/main/resources/Characters/hero.character @@ -2,35 +2,27 @@ "chapter": 0, "level": 0, "xp": 0, - "currentHp": 100, - "currentStamina": 100, - "currentMana": 100, - "maxHp": 100, - "maxStamina": 100, - "maxMana": 100, + "currentHp": 0, + "currentStamina": 0, + "currentMana": 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 - }, + "playedMonologueIds": [], + "firedTriggerIds": [], + "visitedZoneIds": [], + "gameVariables": {}, + "memberFractionIds": [], "listeners": [], "characterId": "hero", "name": { "id": "hero.name" }, - "modelPath": "Models/Chars/mainchar.j3o", + "modelPath": "Characters/Models/hero.j3o", "animSetPath": "human", "type": "MAIN_CHARACTER" } \ No newline at end of file diff --git a/blight-editor/src/main/java/de/blight/editor/EditorApp.java b/blight-editor/src/main/java/de/blight/editor/EditorApp.java index 63c3fcd..7365b7b 100644 --- a/blight-editor/src/main/java/de/blight/editor/EditorApp.java +++ b/blight-editor/src/main/java/de/blight/editor/EditorApp.java @@ -11410,6 +11410,11 @@ public class EditorApp extends Application { saveCurrentAnimSet(animSetCurrentName, animSetCurrentDir); } + private void autoSaveCharacter() { + if (charIdField == null || charIdField.getText().trim().isBlank()) return; + saveCharacter(ASSET_ROOT.resolve("Characters")); + } + private void saveCurrentAnimSet(String setName, Path setDir) { if (animSetClipListView == null) { return; @@ -12037,16 +12042,6 @@ public class EditorApp extends Application { VBox inner = new VBox(8); inner.setPadding(new Insets(10)); - Button newCharBtn = new Button("Neuer Charakter"); - newCharBtn.setMaxWidth(Double.MAX_VALUE); - newCharBtn.setOnAction(e -> clearCharacterForm()); - - Button importCharModelBtn = new Button("Character-Modell importieren (FBX)…"); - importCharModelBtn.setMaxWidth(Double.MAX_VALUE); - importCharModelBtn.setStyle("-fx-font-weight: bold;"); - importCharModelBtn.setOnAction(e -> handleCharacterModelImport(importCharModelBtn.getScene().getWindow())); - - inner.getChildren().addAll(newCharBtn, importCharModelBtn); charEditorStatusLabel = new Label("Wähle einen Character im Baum oder erstelle einen neuen"); charEditorStatusLabel.setWrapText(true); @@ -12097,18 +12092,6 @@ public class EditorApp extends Application { refreshCharAnimSetCombo(); charAnimSetCombo.setOnAction(e -> updateCharActionCombosFromSet()); - 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( dl.apply("ID:"), charIdField, @@ -12202,8 +12185,7 @@ public class EditorApp extends Application { charEditContainer.getChildren().add(charNpcSection); charEditContainer.getChildren().addAll( dl.apply("Modell:"), charModelCombo, - dl.apply("Anim-Set:"), charAnimSetCombo, - stripClipsBtn + dl.apply("Anim-Set:"), charAnimSetCombo ); // ── Aktions-Zuweisung ───────────────────────────────────────────────── @@ -12214,13 +12196,20 @@ public class EditorApp extends Application { charActionLabelsBox.getChildren().add(actionHint); charEditContainer.getChildren().add(charActionLabelsBox); - // ── Speichern ───────────────────────────────────────────────────────── - charEditContainer.getChildren().add(new Separator()); - Button saveCharBtn = new Button("Charakter speichern"); - saveCharBtn.setMaxWidth(Double.MAX_VALUE); - saveCharBtn.setStyle("-fx-font-weight:bold;-fx-background-color:#3a7a3a;-fx-text-fill:white;"); - saveCharBtn.setOnAction(e -> saveCharacter(charDir)); - charEditContainer.getChildren().add(saveCharBtn); + // ── Auto-Save-Listener ──────────────────────────────────────────────── + charIdField.focusedProperty().addListener((obs, ov, nv) -> { if (!nv) autoSaveCharacter(); }); + charTypeCombo.valueProperty().addListener((obs, ov, nv) -> autoSaveCharacter()); + charModelCombo.valueProperty().addListener((obs, ov, nv) -> autoSaveCharacter()); + charAnimSetCombo.valueProperty().addListener((obs, ov, nv) -> autoSaveCharacter()); + charFractionCombo.valueProperty().addListener((obs, ov, nv) -> autoSaveCharacter()); + charStatusCombo.valueProperty().addListener((obs, ov, nv) -> autoSaveCharacter()); + charTraderCheck.selectedProperty().addListener((obs, ov, nv) -> autoSaveCharacter()); + charTraderItemsView.getItems().addListener( + (javafx.collections.ListChangeListener) c -> autoSaveCharacter()); + charMsgFriendly.focusedProperty().addListener((obs, ov, nv) -> { if (!nv) autoSaveCharacter(); }); + charMsgNeutral.focusedProperty().addListener((obs, ov, nv) -> { if (!nv) autoSaveCharacter(); }); + charMsgEnraged.focusedProperty().addListener((obs, ov, nv) -> { if (!nv) autoSaveCharacter(); }); + charMsgEnemy.focusedProperty().addListener((obs, ov, nv) -> { if (!nv) autoSaveCharacter(); }); inner.getChildren().add(charEditContainer); diff --git a/blight-game/src/main/java/de/blight/game/animation/AnimationLibrary.java b/blight-game/src/main/java/de/blight/game/animation/AnimationLibrary.java index 5673c72..44a78d6 100644 --- a/blight-game/src/main/java/de/blight/game/animation/AnimationLibrary.java +++ b/blight-game/src/main/java/de/blight/game/animation/AnimationLibrary.java @@ -336,21 +336,14 @@ public class AnimationLibrary extends BaseAppState { return null; } - // Clips mit Vorwärts-Root-Motion in Local-Y (rennen, gehen, springen): - // Y wird auf 0 eingefroren → kein Drift. Alle anderen Clips: Y bleibt frei (Hinsetzen usw.) - private static final java.util.Set LOCOMOTION_CLIPS = java.util.Set.of( - "running", "walking", "sprinting", "running_jump" - ); - /** * Entfernt Root-Motion aus dem flachsten Bone mit Translation-Track (typischerweise Hips). * Nur dieser eine Bone wird modifiziert — alle anderen Bones bleiben unverändert. * - * In diesen Mixamo-Exporten ist die Achsenbelegung des Hips-Bones: - * Local X → seitlich → wird auf 0 eingefroren (kein Seiten-Drift) - * Local Y → vorwärts → wird auf 0 eingefroren für Lauf-Clips (kein Vorwärts-Drift) - * bleibt frei für Sitz/Stand-Clips (leichte Neigungsbewegung) - * Local Z → Höhe → IMMER frei lassen (Charakter-Höhe und Setz-Bewegung erhalten) + * Achsenbelegung dieses Mixamo-Exports im Hips-Bone-Local-Space: + * Local X → JME3-X (seitlich) → wird auf 0 eingefroren + * Local Y → JME3-Y (Höhe oben) → IMMER frei lassen (Charakter-Höhe erhalten) + * Local Z → JME3-Z (vorwärts) → wird auf 0 eingefroren (alle Clips auf der Stelle) */ @SuppressWarnings("rawtypes") public static AnimClip snapRootBoneXZ(AnimClip clip, Armature armature) { @@ -367,8 +360,6 @@ public class AnimationLibrary extends BaseAppState { } if (minDepth == Integer.MAX_VALUE) return clip; - boolean isLocomotion = LOCOMOTION_CLIPS.contains(clip.getName().toLowerCase(java.util.Locale.ROOT)); - List> newTracks = new ArrayList<>(); boolean modified = false; for (AnimTrack track : clip.getTracks()) { @@ -383,11 +374,6 @@ public class AnimationLibrary extends BaseAppState { continue; } - // Y-Normalisierung für Nicht-Lauf-Clips: Frame-0 auf Bind-Pose-Y - float bindY = j.getInitialTransform().getTranslation().y; - float frame0Y = translations[0].y; - float yOffset = bindY - frame0Y; - // Diagnostik float yMin = Float.MAX_VALUE, yMax = -Float.MAX_VALUE; float xRange = 0, zRange = 0; @@ -397,21 +383,18 @@ public class AnimationLibrary extends BaseAppState { xRange = Math.max(xRange, Math.abs(t.x - translations[0].x)); zRange = Math.max(zRange, Math.abs(t.z - translations[0].z)); } - log.info("[AnimLib] snap '{}' root='{}' locomotion={} bindY={} frame0Y={} yOffset={} yRange={} xRange={} zRange={}", - clip.getName(), j.getName(), isLocomotion, - String.format("%.3f", bindY), String.format("%.3f", frame0Y), - String.format("%.3f", yOffset), String.format("%.3f", yMax - yMin), - String.format("%.3f", xRange), String.format("%.3f", zRange)); + log.info("[AnimLib] snap '{}' root='{}' xRange={} yRange={} zRange={}", + clip.getName(), j.getName(), + String.format("%.3f", xRange), + String.format("%.3f", yMax - yMin), + String.format("%.3f", zRange)); Vector3f[] snapped = new Vector3f[translations.length]; for (int i = 0; i < translations.length; i++) { - float newY = isLocomotion - ? 0f // Lauf-Clips: Y einfrieren (Vorwärts-Drift weg) - : (translations[i].y + yOffset); // Rest: Y normalisiert (Neigung erhalten) snapped[i] = new Vector3f( - 0f, // X immer 0 (Seiten-Drift weg) - newY, - translations[i].z // Z immer frei (Höhe und Setz-Bewegung erhalten) + 0f, // X = 0 (kein seitlicher Drift) + translations[i].y, // Y frei (Höhe; Y=oben in bone-local / JME3-Y) + 0f // Z = 0 (kein Vorwärts-Drift; Z=vorwärts in bone-local) ); } newTracks.add(new TransformTrack(j, tt.getTimes(), snapped, tt.getRotations(), tt.getScales())); diff --git a/blight-game/src/main/java/de/blight/game/control/PlayerInputControl.java b/blight-game/src/main/java/de/blight/game/control/PlayerInputControl.java index 2d9259c..3e13a8a 100644 --- a/blight-game/src/main/java/de/blight/game/control/PlayerInputControl.java +++ b/blight-game/src/main/java/de/blight/game/control/PlayerInputControl.java @@ -168,7 +168,7 @@ public class PlayerInputControl { visualBaseTranslation = visual.getLocalTranslation().clone(); } try { - AnimSet as = AnimSet.load(assetRoot.resolve("animations/sets"), animSetName); + AnimSet as = AnimSet.load(assetRoot.resolve("Characters").resolve("sets"), animSetName); animOffsets = as.getAnimOffsets(); log.info("[AnimCtx] {} Anim-Offset-Einträge geladen.", animOffsets.size()); } catch (Exception e) { @@ -629,6 +629,7 @@ public class PlayerInputControl { } String clip = AnimationLibrary.getClipForAction(assetRoot, animSetName, action); log.info("[Anim] {} → clip='{}' (set={})", action, clip, animSetName); + if (runningClip != null) nextTransitionLength = 0.15; if (clip != null && tryPlay(clip)) return; if (action != AnimationAction.DEFAULT) { String defClip = AnimationLibrary.getClipForAction(assetRoot, animSetName, AnimationAction.DEFAULT); diff --git a/blight-game/src/main/java/de/blight/game/navigation/CharacterNavigator.java b/blight-game/src/main/java/de/blight/game/navigation/CharacterNavigator.java index ae282d3..2067bb1 100644 --- a/blight-game/src/main/java/de/blight/game/navigation/CharacterNavigator.java +++ b/blight-game/src/main/java/de/blight/game/navigation/CharacterNavigator.java @@ -277,6 +277,12 @@ public class CharacterNavigator { } if (clip == null) return; if (!animLib.applyTo(clip, visual)) return; + if (currentAnim != null) { + com.jme3.anim.tween.action.Action nextAction = animComposer.action(clip); + if (nextAction instanceof com.jme3.anim.tween.action.BlendableAction ba) { + ba.setTransitionLength(0.15); + } + } com.jme3.anim.tween.action.Action act = animComposer.setCurrentAction(clip); if (act == null) return; if (skinningControl != null && !skinningControl.isEnabled()) {