diff --git a/blight-assets/src/main/resources/.thumbnails/Models/waffen/schwerter/rostiges_kurzschwert.j3o.thumb.png b/blight-assets/src/main/resources/.thumbnails/Models/waffen/schwerter/rostiges_kurzschwert.j3o.thumb.png index 3db202a..b2f9e2d 100644 Binary files a/blight-assets/src/main/resources/.thumbnails/Models/waffen/schwerter/rostiges_kurzschwert.j3o.thumb.png and b/blight-assets/src/main/resources/.thumbnails/Models/waffen/schwerter/rostiges_kurzschwert.j3o.thumb.png differ diff --git a/blight-assets/src/main/resources/Characters/sets/human.animset.json b/blight-assets/src/main/resources/Characters/sets/human.animset.json index b241aca..bc77f6a 100644 --- a/blight-assets/src/main/resources/Characters/sets/human.animset.json +++ b/blight-assets/src/main/resources/Characters/sets/human.animset.json @@ -125,12 +125,12 @@ "TWO_HAND_BLOCK_STRAFE_LEFT": "Great Sword Strafe left", "TWO_HAND_BLOCK_STRAFE_RIGHT": "Great Sword Strafe Right", "TWO_HAND_JUMP": "Great Sword Jump", - "TWO_HAND_RUNNING_JUMP": "Great Sword Running Jump", "TWO_HAND_IDLE_BORED": "Great Sword Idle boring", "TWO_HAND_IDLE_BORED_2": "Great Sword Idle boring 2", "TWO_HAND_IDLE_BORED_3": "Great Sword Idle boring 3", "TWO_HAND_DEATH": "Sword And Shield Death", - "TWO_HAND_DEATH_2": "Sword And Shield Death 2" + "TWO_HAND_DEATH_2": "Sword And Shield Death 2", + "TWO_HAND_RUNNING_JUMP": "Great Sword Running Jump" }, "previewModelPath": "Characters/Models/hero.j3o", "animOffsets": { diff --git a/blight-assets/src/main/resources/Models/waffen/schwerter/rostiges_kurzschwert.j3o b/blight-assets/src/main/resources/Models/waffen/schwerter/rostiges_kurzschwert.j3o index 1c8a29f..9e2933c 100644 Binary files a/blight-assets/src/main/resources/Models/waffen/schwerter/rostiges_kurzschwert.j3o and b/blight-assets/src/main/resources/Models/waffen/schwerter/rostiges_kurzschwert.j3o differ diff --git a/blight-assets/src/main/resources/Models/waffen/schwerter/rostiges_kurzschwert.j3o.meta b/blight-assets/src/main/resources/Models/waffen/schwerter/rostiges_kurzschwert.j3o.meta index 4afbe2f..b861f0b 100644 --- a/blight-assets/src/main/resources/Models/waffen/schwerter/rostiges_kurzschwert.j3o.meta +++ b/blight-assets/src/main/resources/Models/waffen/schwerter/rostiges_kurzschwert.j3o.meta @@ -1,4 +1,4 @@ -#Sun Sep 20 15:31:32 CEST 2026 +#Thu Oct 15 15:05:26 CEST 2026 attachedEmitters.count=0 attachedLights.count=0 attachedSounds.count=0 diff --git a/blight-assets/src/main/resources/items/weapon/rostiges_kurzschwert.item b/blight-assets/src/main/resources/items/weapon/rostiges_kurzschwert.item index 4a9168b..87e80f0 100644 --- a/blight-assets/src/main/resources/items/weapon/rostiges_kurzschwert.item +++ b/blight-assets/src/main/resources/items/weapon/rostiges_kurzschwert.item @@ -11,9 +11,9 @@ "worthGold": 0, "damage": 0, "grip": { - "posX": 0.11149749, - "posY": 0.07795838, - "posZ": 0.36503196, + "posX": 0.1114975, + "posY": 0.07676052, + "posZ": -0.27503356, "rotX": 0.0, "rotY": 0.0, "rotZ": 0.0, diff --git a/blight-assets/src/main/resources/shield_attachment.json b/blight-assets/src/main/resources/shield_attachment.json new file mode 100644 index 0000000..1807d74 --- /dev/null +++ b/blight-assets/src/main/resources/shield_attachment.json @@ -0,0 +1,8 @@ +{ + "SHIELD": { + "rotX": -90.0, + "rotY": 0.0, + "rotZ": 0.0, + "elbowOffset": 0.25 + } +} diff --git a/blight-assets/src/main/resources/weapon_attachment.json b/blight-assets/src/main/resources/weapon_attachment.json index a9bf810..77fba56 100644 --- a/blight-assets/src/main/resources/weapon_attachment.json +++ b/blight-assets/src/main/resources/weapon_attachment.json @@ -1,8 +1,8 @@ { "SWORD": { - "handOffX": 0.1, - "handOffY": 0.0, - "handOffZ": 0.05, + "handOffX": 0.105, + "handOffY": 0.1, + "handOffZ": 0.5, "hand2OffX": 0.0, "hand2OffY": 0.0, "hand2OffZ": 0.0 diff --git a/blight-common/src/main/java/de/blight/common/model/ShieldAttachmentIO.java b/blight-common/src/main/java/de/blight/common/model/ShieldAttachmentIO.java new file mode 100644 index 0000000..117fb77 --- /dev/null +++ b/blight-common/src/main/java/de/blight/common/model/ShieldAttachmentIO.java @@ -0,0 +1,92 @@ +package de.blight.common.model; + +import com.google.gson.*; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.IOException; +import java.io.InputStream; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.HashMap; +import java.util.Map; + +/** + * Lädt und speichert per-Kategorie Schild-Attachment-Konfiguration (shield_attachment.json). + */ +public final class ShieldAttachmentIO { + + private static final Logger log = LoggerFactory.getLogger(ShieldAttachmentIO.class); + private static final Gson GSON = new GsonBuilder().setPrettyPrinting().create(); + private static final String CLASSPATH_FILE = "shield_attachment.json"; + + private static final Map CONFIGS = new HashMap<>(); + private static boolean loaded = false; + + private ShieldAttachmentIO() {} + + public static ShieldCategoryAttachment get(ItemSubCategory cat) { + ensureLoaded(); + if (cat == null) return new ShieldCategoryAttachment(); + return CONFIGS.getOrDefault(cat.name(), new ShieldCategoryAttachment()); + } + + public static void set(ItemSubCategory cat, ShieldCategoryAttachment cfg) { + if (cat != null) CONFIGS.put(cat.name(), cfg); + } + + public static synchronized void loadFromClasspath() { + try (InputStream is = ShieldAttachmentIO.class.getClassLoader() + .getResourceAsStream(CLASSPATH_FILE)) { + if (is == null) { + log.warn("[ShieldAttachmentIO] {} nicht auf Classpath – Defaults verwendet", CLASSPATH_FILE); + loaded = true; + return; + } + parse(new String(is.readAllBytes(), StandardCharsets.UTF_8)); + loaded = true; + } catch (IOException e) { + log.warn("[ShieldAttachmentIO] Ladefehler: {}", e.getMessage()); + loaded = true; + } + } + + public static void load(Path file) { + if (!Files.exists(file)) return; + try { + parse(Files.readString(file, StandardCharsets.UTF_8)); + loaded = true; + } catch (IOException e) { + log.warn("[ShieldAttachmentIO] Ladefehler: {}", e.getMessage()); + } + } + + public static void save(Path file) { + try { + Files.writeString(file, GSON.toJson(CONFIGS), StandardCharsets.UTF_8); + log.info("[ShieldAttachmentIO] Gespeichert: {}", file); + } catch (IOException e) { + log.warn("[ShieldAttachmentIO] Speicherfehler: {}", e.getMessage()); + } + } + + public static Map getAll() { + ensureLoaded(); + return new HashMap<>(CONFIGS); + } + + private static synchronized void ensureLoaded() { + if (!loaded) loadFromClasspath(); + } + + private static void parse(String json) { + CONFIGS.clear(); + JsonObject obj = JsonParser.parseString(json).getAsJsonObject(); + for (var entry : obj.entrySet()) { + CONFIGS.put(entry.getKey(), + GSON.fromJson(entry.getValue(), ShieldCategoryAttachment.class)); + } + log.info("[ShieldAttachmentIO] {} Kategorien geladen", CONFIGS.size()); + } +} diff --git a/blight-common/src/main/java/de/blight/common/model/ShieldCategoryAttachment.java b/blight-common/src/main/java/de/blight/common/model/ShieldCategoryAttachment.java new file mode 100644 index 0000000..a48c1a3 --- /dev/null +++ b/blight-common/src/main/java/de/blight/common/model/ShieldCategoryAttachment.java @@ -0,0 +1,14 @@ +package de.blight.common.model; + +import lombok.Getter; +import lombok.Setter; + +/** Attachment-Parameter für einen Schild-Typ (am linken Unterarm). */ +@Getter +@Setter +public class ShieldCategoryAttachment { + private float rotX = -90f; + private float rotY = 0f; + private float rotZ = 0f; + private float elbowOffset = 0.25f; +} diff --git a/blight-editor/src/main/java/de/blight/editor/SharedInput.java b/blight-editor/src/main/java/de/blight/editor/SharedInput.java index ca91484..6a887fc 100644 --- a/blight-editor/src/main/java/de/blight/editor/SharedInput.java +++ b/blight-editor/src/main/java/de/blight/editor/SharedInput.java @@ -1258,6 +1258,18 @@ public class SharedInput { // ── Attachment-Editor (eigenständige Ansicht für Hand-Anker pro Kategorie) ─ public volatile boolean attachmentEditorActive = false; + // ── Schild-Attachment-Editor ────────────────────────────────────────────── + public volatile boolean shieldEditorActive = false; + public volatile String shieldEditorSubCategory = null; + public volatile float shieldEditorRotX = -90f; + public volatile float shieldEditorRotY = 0f; + public volatile float shieldEditorRotZ = 0f; + public volatile float shieldEditorElbowOffset = 0.25f; + public volatile float shieldEditorGripX = 0f; + public volatile float shieldEditorGripY = 0f; + public volatile float shieldEditorGripZ = 0f; + public volatile boolean shieldEditorOffsetsDirty = false; + /** Klick/Drag im Viewport im Sculpt-Modus. action 0=links, 1=rechts. */ public record SculptEdit(float screenX, float screenY, int action) {} public final ConcurrentLinkedQueue sculptEditQueue = new ConcurrentLinkedQueue<>(); diff --git a/blight-editor/src/main/java/de/blight/editor/state/ModelEditorState.java b/blight-editor/src/main/java/de/blight/editor/state/ModelEditorState.java index c1f2e96..820644d 100644 --- a/blight-editor/src/main/java/de/blight/editor/state/ModelEditorState.java +++ b/blight-editor/src/main/java/de/blight/editor/state/ModelEditorState.java @@ -121,6 +121,7 @@ public class ModelEditorState extends BaseAppState { private Node charLeftHandNode = null; // LeftHand-Attachment private Node charPalmNode = null; // RightHandMiddle1-Attachment private Quaternion charHandBindRot = null; + private Quaternion charLeftHandBindRot = null; private Vector3f charPalmOffset = null; private int charPalmDelayFrames = 0; private float interactableOffsetZ = 0f; @@ -260,9 +261,21 @@ public class ModelEditorState extends BaseAppState { guessGripPosition(); } + // Modell laden – MUSS vor enableCharPreview stehen, damit currentPath aktuell ist + String openPath = input.modelEditorOpenPath; + if (openPath != null) { + input.modelEditorOpenPath = null; + mainModelPath = openPath; + input.modelEditorLodPreview = 0; + input.modelImportLod1PreviewNode.set(null); + input.modelImportLod2PreviewNode.set(null); + loadModel(openPath); + } + // Charakter-Preview ein-/ausschalten boolean wantsPreview = (input.gripEditorCharPreview && input.gripEditorActive) - || input.attachmentEditorActive; + || input.attachmentEditorActive + || input.shieldEditorActive; if (wantsPreview && charPreviewNode == null) { enableCharPreview(); } else if (!wantsPreview && charPreviewNode != null) { @@ -278,7 +291,7 @@ public class ModelEditorState extends BaseAppState { } } - // Waffe im Charakter-Preview jedes Frame neu positionieren + // Waffe/Schild im Charakter-Preview jedes Frame neu positionieren if (charPreviewNode != null && charWeaponSpatial != null && charHandNode != null) { updateCharPreviewWeapon(); } @@ -288,17 +301,6 @@ public class ModelEditorState extends BaseAppState { embedLodsAndSave(input.modelEditorEmbedLodsPath); } - // Modell laden - String openPath = input.modelEditorOpenPath; - if (openPath != null) { - input.modelEditorOpenPath = null; - mainModelPath = openPath; - input.modelEditorLodPreview = 0; - input.modelImportLod1PreviewNode.set(null); - input.modelImportLod2PreviewNode.set(null); - loadModel(openPath); - } - // Invariante: wenn Charakter-Preview aktiv, standalone-Modell immer ausblenden if (charPreviewNode != null) { if (modelWrapper != null) modelWrapper.setCullHint(Spatial.CullHint.Always); @@ -2083,14 +2085,17 @@ public class ModelEditorState extends BaseAppState { SkinningControl skin = findSkinCtrl(hero); if (skin != null) { - charHandBindRot = extractJointBindRot(skin, "mixamorig:RightHand"); + charHandBindRot = extractJointBindRot(skin, "mixamorig:RightHand"); + charLeftHandBindRot = extractJointBindRot(skin, "mixamorig:LeftHand"); try { charHandNode = skin.getAttachmentsNode("mixamorig:RightHand"); } catch (Exception e) { log.warn("[CharPreview] RightHand: {}", e.getMessage()); } try { charLeftHandNode = skin.getAttachmentsNode("mixamorig:LeftHand"); } catch (Exception e) { log.warn("[CharPreview] LeftHand: {}", e.getMessage()); } try { charPalmNode = skin.getAttachmentsNode("mixamorig:RightHandMiddle1"); charPalmDelayFrames = 2; } catch (Exception e) { log.warn("[CharPreview] Palm: {}", e.getMessage()); } AnimComposer anim = findAnimComposer(hero); if (anim != null) { - String clipName = previewAnimName(input.gripEditorSubCategory); + String subCat = input.shieldEditorActive + ? input.shieldEditorSubCategory : input.gripEditorSubCategory; + String clipName = previewAnimName(subCat); loadClipToComposer(anim, skin, clipName); } } @@ -2116,13 +2121,14 @@ public class ModelEditorState extends BaseAppState { private void disableCharPreview() { if (charPreviewNode != null) { charPreviewNode.removeFromParent(); - charPreviewNode = null; - charWeaponSpatial = null; - charHandNode = null; - charLeftHandNode = null; - charPalmNode = null; - charHandBindRot = null; - charPalmOffset = null; + charPreviewNode = null; + charWeaponSpatial = null; + charHandNode = null; + charLeftHandNode = null; + charPalmNode = null; + charHandBindRot = null; + charLeftHandBindRot = null; + charPalmOffset = null; } if (modelWrapper != null) modelWrapper.setCullHint(Spatial.CullHint.Inherit); if (gripGizmoNode != null) gripGizmoNode.setCullHint(Spatial.CullHint.Always); @@ -2130,6 +2136,10 @@ public class ModelEditorState extends BaseAppState { } private void updateCharPreviewWeapon() { + if (input.shieldEditorActive) { + updateCharPreviewShield(); + return; + } // Kategorie-Offsets aus SharedInput lesen (live-update) WeaponCategoryAttachment cfg = new WeaponCategoryAttachment(); cfg.setHandOffX(input.gripEditorCatHandOffX); @@ -2205,11 +2215,39 @@ public class ModelEditorState extends BaseAppState { charWeaponSpatial.setLocalTranslation(finalPos); } + private void updateCharPreviewShield() { + if (charLeftHandNode == null) return; + float DEG_TO_RAD = (float) Math.PI / 180f; + Quaternion charWorldRotInv = charPreviewNode.getWorldRotation().inverse(); + Vector3f charWorldPos = charPreviewNode.getWorldTranslation(); + + Vector3f leftBoneInChar = charWorldRotInv.mult( + charLeftHandNode.getWorldTranslation().subtract(charWorldPos)); + Quaternion leftBoneRotInChar = charWorldRotInv.mult(charLeftHandNode.getWorldRotation()); + + Quaternion animDelta = (charLeftHandBindRot != null) + ? leftBoneRotInChar.mult(charLeftHandBindRot.inverse()) + : leftBoneRotInChar; + Quaternion armRot = new Quaternion().fromAngles( + DEG_TO_RAD * input.shieldEditorRotX, + DEG_TO_RAD * input.shieldEditorRotY, + DEG_TO_RAD * input.shieldEditorRotZ); + Quaternion shieldRot = animDelta.mult(armRot); + + Vector3f anchor = leftBoneInChar.add( + leftBoneRotInChar.mult(new Vector3f(0f, -input.shieldEditorElbowOffset, 0f))); + Vector3f gripPos = new Vector3f( + input.shieldEditorGripX, input.shieldEditorGripY, input.shieldEditorGripZ); + + charWeaponSpatial.setLocalRotation(shieldRot); + charWeaponSpatial.setLocalTranslation(anchor.subtract(shieldRot.mult(gripPos))); + } + private static String previewAnimName(String subCategory) { if (subCategory == null) return "Idle"; return switch (subCategory) { case "TWO_HANDED_SWORD" -> "Great Sword Idle"; - case "SWORD" -> "Sword And Shield Idle"; + case "SWORD", "SHIELD" -> "Sword And Shield Idle"; default -> "Idle"; }; } diff --git a/blight-editor/src/main/java/de/blight/editor/toolset/content/AttachmentEditorController.java b/blight-editor/src/main/java/de/blight/editor/toolset/content/AttachmentEditorController.java index ce654df..635111c 100644 --- a/blight-editor/src/main/java/de/blight/editor/toolset/content/AttachmentEditorController.java +++ b/blight-editor/src/main/java/de/blight/editor/toolset/content/AttachmentEditorController.java @@ -114,12 +114,12 @@ public class AttachmentEditorController { // ── Panel ───────────────────────────────────────────────────────────────── private VBox buildPanel(WeaponCategoryAttachment cfg, ItemSubCategory subCategory) { - handOffXSpin = doubleSpin(cfg.getHandOffX(), -0.5, 0.5, 0.005, "m"); - handOffYSpin = doubleSpin(cfg.getHandOffY(), -0.5, 0.5, 0.005, "m"); - handOffZSpin = doubleSpin(cfg.getHandOffZ(), -0.5, 0.5, 0.005, "m"); - hand2OffXSpin = doubleSpin(cfg.getHand2OffX(), -0.5, 0.5, 0.005, "m"); - hand2OffYSpin = doubleSpin(cfg.getHand2OffY(), -0.5, 0.5, 0.005, "m"); - hand2OffZSpin = doubleSpin(cfg.getHand2OffZ(), -0.5, 0.5, 0.005, "m"); + handOffXSpin = doubleSpin(cfg.getHandOffX(), -2.0, 2.0, 0.005, "m"); + handOffYSpin = doubleSpin(cfg.getHandOffY(), -2.0, 2.0, 0.005, "m"); + handOffZSpin = doubleSpin(cfg.getHandOffZ(), -2.0, 2.0, 0.005, "m"); + hand2OffXSpin = doubleSpin(cfg.getHand2OffX(), -2.0, 2.0, 0.005, "m"); + hand2OffYSpin = doubleSpin(cfg.getHand2OffY(), -2.0, 2.0, 0.005, "m"); + hand2OffZSpin = doubleSpin(cfg.getHand2OffZ(), -2.0, 2.0, 0.005, "m"); handOffXSpin.valueProperty().addListener((obs, o, n) -> onSpinnerChanged()); handOffYSpin.valueProperty().addListener((obs, o, n) -> onSpinnerChanged()); diff --git a/blight-editor/src/main/java/de/blight/editor/toolset/content/ContentController.java b/blight-editor/src/main/java/de/blight/editor/toolset/content/ContentController.java index 9b47ca3..1abd25f 100644 --- a/blight-editor/src/main/java/de/blight/editor/toolset/content/ContentController.java +++ b/blight-editor/src/main/java/de/blight/editor/toolset/content/ContentController.java @@ -88,13 +88,15 @@ public class ContentController { // ────────────────────────────────────────────────────────────────────────── private Runnable onEnterContentMode; - private GripEditorController gripEditorController; - private AttachmentEditorController attachmentEditorController; + private GripEditorController gripEditorController; + private AttachmentEditorController attachmentEditorController; + private ShieldAttachmentEditorController shieldAttachmentEditorController; public ContentController(ToolContext ctx) { this.ctx = ctx; - this.gripEditorController = new GripEditorController(ctx); - this.attachmentEditorController = new AttachmentEditorController(ctx); + this.gripEditorController = new GripEditorController(ctx); + this.attachmentEditorController = new AttachmentEditorController(ctx); + this.shieldAttachmentEditorController = new ShieldAttachmentEditorController(ctx); } // ── Öffentliche API ──────────────────────────────────────────────────────── @@ -1024,6 +1026,18 @@ public class ContentController { }); attachmentEditorController.open(modelPath, subCategory, grip); }); + view.setShieldAttachmentEditorLauncher((modelPath, subCategory, grip) -> { + String itemId = view.getCurrentItemId(); + shieldAttachmentEditorController.setOnReturn(() -> { + if (dualWindowMode) { + buildItemViewInContent(); + if (activeItemView != null) activeItemView.selectItem(itemId); + } else { + switchToItemEditor(itemId); + } + }); + shieldAttachmentEditorController.open(modelPath, subCategory, grip); + }); } private void buildItemViewInContent() { diff --git a/blight-editor/src/main/java/de/blight/editor/toolset/content/ShieldAttachmentEditorController.java b/blight-editor/src/main/java/de/blight/editor/toolset/content/ShieldAttachmentEditorController.java new file mode 100644 index 0000000..3d1c9dc --- /dev/null +++ b/blight-editor/src/main/java/de/blight/editor/toolset/content/ShieldAttachmentEditorController.java @@ -0,0 +1,253 @@ +package de.blight.editor.toolset.content; + +import de.blight.common.model.ItemGrip; +import de.blight.common.model.ItemSubCategory; +import de.blight.common.model.ShieldAttachmentIO; +import de.blight.common.model.ShieldCategoryAttachment; +import de.blight.editor.SharedInput; +import de.blight.editor.ToolContext; +import javafx.geometry.Insets; +import javafx.geometry.Orientation; +import javafx.geometry.Pos; +import javafx.scene.control.*; +import javafx.scene.layout.*; + +/** + * Eigenständige Ansicht zum Bearbeiten der Schild-Arm-Attachment-Parameter pro Kategorie. + * Zeigt den Helden in Sword-And-Shield Idle mit Schild am linken Arm. + */ +public class ShieldAttachmentEditorController { + + private final ToolContext ctx; + + private Spinner rotXSpin, rotYSpin, rotZSpin, elbowOffsetSpin; + + private boolean suppressEvents = false; + private ItemSubCategory itemSubCategory = null; + private Runnable onReturn = null; + + public ShieldAttachmentEditorController(ToolContext ctx) { + this.ctx = ctx; + } + + public void setOnReturn(Runnable r) { this.onReturn = r; } + + // ── Öffnen ──────────────────────────────────────────────────────────────── + + public void open(String modelPath, ItemSubCategory subCategory, ItemGrip grip) { + itemSubCategory = subCategory; + + SharedInput input = ctx.input(); + input.gripEditorActive = false; + input.gripEditorCharPreview = false; + input.attachmentEditorActive = false; + input.shieldEditorActive = true; + + input.shieldEditorSubCategory = subCategory != null ? subCategory.name() : null; + ShieldCategoryAttachment cfg = ShieldAttachmentIO.get(subCategory); + input.shieldEditorRotX = cfg.getRotX(); + input.shieldEditorRotY = cfg.getRotY(); + input.shieldEditorRotZ = cfg.getRotZ(); + input.shieldEditorElbowOffset = cfg.getElbowOffset(); + input.shieldEditorOffsetsDirty = false; + + // Grip des Schilds für Preview-Positionierung + ItemGrip g = grip != null ? grip : new ItemGrip(); + input.shieldEditorGripX = g.getPosX(); + input.shieldEditorGripY = g.getPosY(); + input.shieldEditorGripZ = g.getPosZ(); + + if (modelPath != null && !modelPath.isBlank()) { + input.modelEditorOpenPath = modelPath; + } + input.activeLayer = SharedInput.LAYER_MODEL_EDITOR; + if (ctx.jmeApp() != null) ctx.jmeApp().setRenderThrottled(false); + + ctx.setCenterView().accept(ctx.viewportSupplier().get()); + ctx.setCurrentTool().accept("shieldAttachmentEditor"); + ctx.setMainToolbar().accept(buildToolbar()); + + VBox innerPanel = buildPanel(cfg, subCategory); + ScrollPane scroll = new ScrollPane(innerPanel); + scroll.setFitToWidth(true); + scroll.setHbarPolicy(ScrollPane.ScrollBarPolicy.NEVER); + scroll.setStyle("-fx-background-color:transparent;-fx-background:transparent;"); + scroll.skinProperty().addListener((obs, o, n) -> { + javafx.scene.Node vp = scroll.lookup(".viewport"); + if (vp != null) vp.setStyle("-fx-background-color: #252535;"); + }); + VBox outerPanel = new VBox(scroll); + VBox.setVgrow(scroll, Priority.ALWAYS); + outerPanel.setPrefWidth(220); + outerPanel.setMaxWidth(220); + outerPanel.setStyle("-fx-background-color: #252535;"); + ctx.setRightPanel().accept(outerPanel); + ctx.setLeftPanel().accept(null); + ctx.setBottomPanel().accept(null); + } + + // ── Toolbar ─────────────────────────────────────────────────────────────── + + private ToolBar buildToolbar() { + Button backBtn = new Button("<- Zurück"); + backBtn.setOnAction(e -> onCancel()); + Label title = new Label("Schild-Anker-Editor"); + title.setStyle("-fx-font-weight: bold; -fx-font-size: 13;"); + ToolBar tb = new ToolBar(); + tb.getItems().addAll(backBtn, new Separator(Orientation.VERTICAL), title); + return tb; + } + + // ── Panel ───────────────────────────────────────────────────────────────── + + private VBox buildPanel(ShieldCategoryAttachment cfg, ItemSubCategory subCategory) { + rotXSpin = doubleSpin(cfg.getRotX(), -360.0, 360.0, 1.0, "°"); + rotYSpin = doubleSpin(cfg.getRotY(), -360.0, 360.0, 1.0, "°"); + rotZSpin = doubleSpin(cfg.getRotZ(), -360.0, 360.0, 1.0, "°"); + elbowOffsetSpin = doubleSpin(cfg.getElbowOffset(), 0.0, 1.0, 0.005, "m"); + + rotXSpin.valueProperty().addListener((obs, o, n) -> onSpinnerChanged()); + rotYSpin.valueProperty().addListener((obs, o, n) -> onSpinnerChanged()); + rotZSpin.valueProperty().addListener((obs, o, n) -> onSpinnerChanged()); + elbowOffsetSpin.valueProperty().addListener((obs, o, n) -> onSpinnerChanged()); + + Button saveBtn = new Button("Speichern"); + saveBtn.setMaxWidth(Double.MAX_VALUE); + saveBtn.setStyle("-fx-background-color: #2a5a2a; -fx-text-fill: #aaffaa; -fx-cursor: hand; -fx-font-weight: bold;"); + saveBtn.setOnAction(e -> saveAndReturn()); + + Button cancelBtn = new Button("Abbrechen"); + cancelBtn.setMaxWidth(Double.MAX_VALUE); + cancelBtn.setStyle("-fx-background-color: #3a3a3a; -fx-text-fill: #cccccc; -fx-cursor: hand;"); + cancelBtn.setOnAction(e -> onCancel()); + + String catLabel = subCategory != null ? subCategory.name() : "–"; + + VBox panel = new VBox(8); + panel.setPadding(new Insets(12)); + panel.setStyle("-fx-background-color: #252535;"); + panel.setPrefWidth(220); + panel.getChildren().addAll( + styledTitle("Kategorie: " + catLabel), + styledHint("Steuert wie das Schild am linken Unterarm angebracht ist.\nDer Held zeigt die Pose in der Sword-And-Shield Idle-Animation."), + new Separator(), + styledTitle("Rotation am Arm (Grad)"), + styledHint("Dreht das Schild relativ zur Bind-Pose des LeftHand-Knochens."), + row("X:", rotXSpin), + row("Y:", rotYSpin), + row("Z:", rotZSpin), + new Separator(), + styledTitle("Ellbogen-Offset (m)"), + styledHint("Verschiebung in Richtung Ellbogen (lokale -Y des LeftHand-Knochens)."), + row("Off:", elbowOffsetSpin), + new Separator(), + saveBtn, + cancelBtn + ); + return panel; + } + + // ── Spinner-Events ──────────────────────────────────────────────────────── + + private void onSpinnerChanged() { + if (suppressEvents) return; + SharedInput input = ctx.input(); + input.shieldEditorRotX = rotXSpin.getValue().floatValue(); + input.shieldEditorRotY = rotYSpin.getValue().floatValue(); + input.shieldEditorRotZ = rotZSpin.getValue().floatValue(); + input.shieldEditorElbowOffset = elbowOffsetSpin.getValue().floatValue(); + input.shieldEditorOffsetsDirty = true; + } + + // ── Speichern / Abbrechen ───────────────────────────────────────────────── + + private void saveAndReturn() { + if (itemSubCategory != null) { + ShieldCategoryAttachment cfg = new ShieldCategoryAttachment(); + cfg.setRotX(rotXSpin.getValue().floatValue()); + cfg.setRotY(rotYSpin.getValue().floatValue()); + cfg.setRotZ(rotZSpin.getValue().floatValue()); + cfg.setElbowOffset(elbowOffsetSpin.getValue().floatValue()); + ShieldAttachmentIO.set(itemSubCategory, cfg); + java.nio.file.Path dest = ctx.assetRoot().resolve("shield_attachment.json"); + ShieldAttachmentIO.save(dest); + syncToMirrors(dest); + } + closeEditor(); + } + + private void syncToMirrors(java.nio.file.Path savedFile) { + java.nio.file.Path projectRoot = de.blight.editor.ProjectRoot.PATH; + String[] mirrorRoots = { + "blight-assets/bin/main", + "blight-assets/build/resources/main" + }; + for (String root : mirrorRoots) { + java.nio.file.Path mirror = projectRoot.resolve(root).resolve("shield_attachment.json"); + try { + java.nio.file.Files.copy(savedFile, mirror, + java.nio.file.StandardCopyOption.REPLACE_EXISTING); + } catch (Exception e) { + // Mirror nicht vorhanden – ignorieren + } + } + } + + private void onCancel() { + closeEditor(); + } + + private void closeEditor() { + SharedInput input = ctx.input(); + input.shieldEditorActive = false; + input.shieldEditorOffsetsDirty = false; + + Runnable ret = onReturn; + onReturn = null; + itemSubCategory = null; + + if (ret != null) ret.run(); else ctx.switchToWorld().run(); + } + + // ── Helpers ─────────────────────────────────────────────────────────────── + + private static Spinner doubleSpin(float initial, double min, double max, double step, String unit) { + SpinnerValueFactory.DoubleSpinnerValueFactory vf = + new SpinnerValueFactory.DoubleSpinnerValueFactory(min, max, initial, step); + vf.setConverter(new javafx.util.StringConverter<>() { + @Override public String toString(Double v) { return v == null ? "0" : String.format("%.3f", v) + " " + unit; } + @Override public Double fromString(String s) { + try { return Double.parseDouble(s.replace(unit, "").replace(",", ".").trim()); } + catch (NumberFormatException e) { return vf.getValue(); } + } + }); + Spinner sp = new Spinner<>(vf); + sp.setEditable(true); + sp.setMaxWidth(Double.MAX_VALUE); + return sp; + } + + private static Label styledTitle(String text) { + Label l = new Label(text); + l.setStyle("-fx-font-weight: bold; -fx-font-size: 11; -fx-text-fill: #88aacc;"); + l.setWrapText(true); + return l; + } + + private static Label styledHint(String text) { + Label l = new Label(text); + l.setWrapText(true); + l.setStyle("-fx-text-fill: #888; -fx-font-size: 10;"); + return l; + } + + private static HBox row(String labelText, Spinner spinner) { + Label lbl = new Label(labelText); + lbl.setMinWidth(30); + lbl.setStyle("-fx-text-fill: #aaa;"); + HBox.setHgrow(spinner, Priority.ALWAYS); + HBox box = new HBox(6, lbl, spinner); + box.setAlignment(Pos.CENTER_LEFT); + return box; + } +} diff --git a/blight-editor/src/main/java/de/blight/editor/ui/ItemEditorView.java b/blight-editor/src/main/java/de/blight/editor/ui/ItemEditorView.java index dd9d6bf..0a65791 100644 --- a/blight-editor/src/main/java/de/blight/editor/ui/ItemEditorView.java +++ b/blight-editor/src/main/java/de/blight/editor/ui/ItemEditorView.java @@ -63,9 +63,14 @@ public class ItemEditorView extends BorderPane { void open(String modelPath, ItemSubCategory subCategory, ItemGrip grip); } + public interface ShieldAttachmentEditorLauncher { + void open(String modelPath, ItemSubCategory subCategory, ItemGrip grip); + } + private Runnable onIdChanged; - private GripEditorLauncher gripEditorLauncher; - private AttachmentEditorLauncher attachmentEditorLauncher; + private GripEditorLauncher gripEditorLauncher; + private AttachmentEditorLauncher attachmentEditorLauncher; + private ShieldAttachmentEditorLauncher shieldAttachmentEditorLauncher; private final PauseTransition savePause = new PauseTransition(Duration.millis(600)); @@ -96,6 +101,7 @@ public class ItemEditorView extends BorderPane { public void setOnIdChanged(Runnable r) { this.onIdChanged = r; } public void setGripEditorLauncher(GripEditorLauncher l) { this.gripEditorLauncher = l; } public void setAttachmentEditorLauncher(AttachmentEditorLauncher l) { this.attachmentEditorLauncher = l; } + public void setShieldAttachmentEditorLauncher(ShieldAttachmentEditorLauncher l) { this.shieldAttachmentEditorLauncher = l; } public String getCurrentItemId() { return current != null ? current.getItemId() : null; } public void selectItem(String itemId) { @@ -264,10 +270,16 @@ public class ItemEditorView extends BorderPane { shieldGripBtn.setStyle("-fx-background-color: #2a4a5a; -fx-text-fill: #aaddff; -fx-cursor: hand;"); shieldGripBtn.setOnAction(e -> openShieldGripEditor()); + Button shieldAttachmentBtn = new Button("Arm-Anker bearbeiten..."); + shieldAttachmentBtn.setMaxWidth(Double.MAX_VALUE); + shieldAttachmentBtn.setStyle("-fx-background-color: #1a4a4a; -fx-text-fill: #aaffee; -fx-cursor: hand;"); + shieldAttachmentBtn.setOnAction(e -> openShieldAttachmentEditor()); + shieldSection = new VBox(6, new Separator(), sectionTitle("Schild-Griff"), - shieldGripBtn + shieldGripBtn, + shieldAttachmentBtn ); shieldSection.setVisible(false); shieldSection.setManaged(false); @@ -358,6 +370,13 @@ public class ItemEditorView extends BorderPane { }); } + private void openShieldAttachmentEditor() { + if (current == null || shieldAttachmentEditorLauncher == null) return; + String modelPath = modelRefField.getText().trim(); + shieldAttachmentEditorLauncher.open(modelPath.isBlank() ? null : modelPath, + current.getSubCategory(), current.getGrip()); + } + private void updateWeaponSectionVisibility() { ItemCategory cat = catCombo.getValue(); ItemSubCategory sub = subCatCombo.getValue(); diff --git a/blight-game/src/main/java/de/blight/game/control/NpcWeaponAttachmentControl.java b/blight-game/src/main/java/de/blight/game/control/NpcWeaponAttachmentControl.java index 5c7124a..eddfdfa 100644 --- a/blight-game/src/main/java/de/blight/game/control/NpcWeaponAttachmentControl.java +++ b/blight-game/src/main/java/de/blight/game/control/NpcWeaponAttachmentControl.java @@ -12,6 +12,8 @@ import com.jme3.scene.control.AbstractControl; import de.blight.common.model.Item; import de.blight.common.model.ItemGrip; import de.blight.common.model.ItemSubCategory; +import de.blight.common.model.ShieldAttachmentIO; +import de.blight.common.model.ShieldCategoryAttachment; import de.blight.common.model.WeaponAttachmentIO; import de.blight.common.model.WeaponCategoryAttachment; import de.blight.game.animation.RetargetingSystem; @@ -35,7 +37,8 @@ public class NpcWeaponAttachmentControl extends AbstractControl { private static final float DEG_TO_RAD = (float) Math.PI / 180f; private static final float HALF_PI = (float) Math.PI * 0.5f; - private static final Vector3f BACK_GRIP_OFFSET = new Vector3f(0.0f, 0.40f, -0.13f); + private static final Vector3f BACK_GRIP_OFFSET = new Vector3f(0.0f, 0.40f, -0.13f); + private static final Vector3f TWO_HAND_BACK_GRIP_OFFSET = new Vector3f(0.0f, 0.40f, -0.03f); private static final Vector3f HAND_GRIP_OFFSET = new Vector3f( ItemGrip.HAND_GRIP_OFFSET_X, ItemGrip.HAND_GRIP_OFFSET_Y, ItemGrip.HAND_GRIP_OFFSET_Z); private static final Vector3f SHIELD_BACK_OFFSET = new Vector3f(0.12f, -0.10f, -0.10f); @@ -157,8 +160,10 @@ public class NpcWeaponAttachmentControl extends AbstractControl { applyHandTransform(weaponSpatial, attachInChar, boneRotInChar, wGrip, cfg); } } else { + boolean twoHanded = weaponItem != null + && weaponItem.getSubCategory() == ItemSubCategory.TWO_HANDED_SWORD; applyBackTransform(weaponSpatial, boneInChar, boneRotInChar, - weaponItem != null ? weaponItem.getGrip() : null); + weaponItem != null ? weaponItem.getGrip() : null, twoHanded); } } } @@ -169,7 +174,9 @@ public class NpcWeaponAttachmentControl extends AbstractControl { if (drawn && leftForeArmNode != null) { Vector3f boneInChar = charWorldRotInv.mult(leftForeArmNode.getWorldTranslation().subtract(charWorldPos)); Quaternion boneRotInChar = charWorldRotInv.mult(leftForeArmNode.getWorldRotation()); - applyShieldArmTransform(shieldSpatial, boneInChar, boneRotInChar, grip); + ShieldCategoryAttachment shieldCfg = ShieldAttachmentIO.get( + shieldItem != null ? shieldItem.getSubCategory() : null); + applyShieldArmTransform(shieldSpatial, boneInChar, boneRotInChar, grip, shieldCfg); shieldSpatial.setCullHint(Spatial.CullHint.Inherit); } else if (!drawn && backNode != null) { Vector3f boneInChar = charWorldRotInv.mult(backNode.getWorldTranslation().subtract(charWorldPos)); @@ -275,7 +282,7 @@ public class NpcWeaponAttachmentControl extends AbstractControl { private static final Quaternion BACK_ROT_NORM = new Quaternion(0f, -(float) Math.sqrt(0.5), (float) Math.sqrt(0.5), 0f); private void applyBackTransform(Spatial s, Vector3f boneInChar, - Quaternion boneRotInChar, ItemGrip grip) { + Quaternion boneRotInChar, ItemGrip grip, boolean twoHanded) { Quaternion gripRot = (grip != null) ? new Quaternion().fromAngles(DEG_TO_RAD * grip.getRotX(), DEG_TO_RAD * grip.getRotY(), @@ -286,7 +293,8 @@ public class NpcWeaponAttachmentControl extends AbstractControl { ? new Quaternion().fromAngles(com.jme3.math.FastMath.PI, 0f, 0f) : new Quaternion(); Quaternion weaponRot = boneRotInChar.mult(BACK_ROT_NORM).mult(bladeFlip2).mult(gripRot.inverse()); - Vector3f target = boneInChar.add(boneRotInChar.mult(BACK_GRIP_OFFSET)); + Vector3f target = boneInChar.add(boneRotInChar.mult( + twoHanded ? TWO_HAND_BACK_GRIP_OFFSET : BACK_GRIP_OFFSET)); Vector3f gripPos = grip != null ? new Vector3f(grip.getPosX(), grip.getPosY(), grip.getPosZ()) : Vector3f.ZERO; Vector3f backPos = target.subtract(weaponRot.mult(gripPos)); @@ -298,15 +306,17 @@ public class NpcWeaponAttachmentControl extends AbstractControl { // ── Schild-Transforms (1:1 von WeaponAttachmentControl) ────────────────────── private void applyShieldArmTransform(Spatial s, Vector3f bonePosInChar, - Quaternion boneRotInChar, ItemGrip grip) { + Quaternion boneRotInChar, ItemGrip grip, + ShieldCategoryAttachment cfg) { Quaternion animDelta = (leftForeArmBindRotInChar != null) ? boneRotInChar.mult(leftForeArmBindRotInChar.inverse()) : boneRotInChar; - Quaternion armRot = new Quaternion().fromAngles(SHIELD_ARM_ROT_X, SHIELD_ARM_ROT_Y, SHIELD_ARM_ROT_Z); + Quaternion armRot = new Quaternion().fromAngles( + DEG_TO_RAD * cfg.getRotX(), DEG_TO_RAD * cfg.getRotY(), DEG_TO_RAD * cfg.getRotZ()); Quaternion shieldRot = animDelta.mult(armRot); Vector3f gripPos = (grip != null) ? new Vector3f(grip.getPosX(), grip.getPosY(), grip.getPosZ()) : Vector3f.ZERO; - Vector3f anchor = bonePosInChar.add(boneRotInChar.mult(new Vector3f(0f, -SHIELD_ARM_ELBOW_OFFSET, 0f))); + Vector3f anchor = bonePosInChar.add(boneRotInChar.mult(new Vector3f(0f, -cfg.getElbowOffset(), 0f))); s.setLocalRotation(shieldRot); s.setLocalTranslation(anchor.subtract(shieldRot.mult(gripPos))); } 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 5c93da4..8e7969e 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 @@ -6,6 +6,7 @@ import com.jme3.anim.tween.Tween; import com.jme3.anim.tween.action.BaseAction; import com.jme3.bullet.control.CharacterControl; import com.jme3.input.InputManager; +import com.jme3.input.KeyInput; import com.jme3.input.MouseInput; import com.jme3.input.RawInputListener; import com.jme3.input.controls.ActionListener; @@ -110,11 +111,14 @@ public class PlayerInputControl implements CombatStyleBase.AnimReceiver { 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 ACT_BORED_1 = "BoredAnim1"; + private static final String ACT_BORED_2 = "BoredAnim2"; + private static final String ACT_BORED_3 = "BoredAnim3"; 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_ROLL, ACT_SNEAK + ACT_ROLL, ACT_SNEAK, ACT_BORED_1, ACT_BORED_2, ACT_BORED_3 }; private final InputManager inputManager; @@ -381,8 +385,11 @@ public class PlayerInputControl implements CombatStyleBase.AnimReceiver { } } case ACT_COMBAT_ALT -> handleCombatAlt(isPressed); - case ACT_ROLL -> { if (isPressed) triggerRollFromInput(); } - case ACT_SNEAK -> { if (!weaponDrawn) handleSneakKey(isPressed); } + case ACT_ROLL -> { if (isPressed) triggerRollFromInput(); } + case ACT_SNEAK -> { if (!weaponDrawn) handleSneakKey(isPressed); } + case ACT_BORED_1 -> { if (isPressed) triggerBoredAnim(0); } + case ACT_BORED_2 -> { if (isPressed) triggerBoredAnim(1); } + case ACT_BORED_3 -> { if (isPressed) triggerBoredAnim(2); } } }; @@ -468,6 +475,7 @@ public class PlayerInputControl implements CombatStyleBase.AnimReceiver { public void setMainCharacter(MainCharacter mc) { this.mainCharacter = mc; } public boolean isWeaponDrawn() { return weaponDrawn; } + public boolean isDrawAnimActive() { return drawAnimActive; } public ItemSubCategory getDrawnWeaponType() { return drawnWeaponType; } public boolean isLastAttackCritical() { return lastAttackCritical; } public com.jme3.anim.SkinningControl getSkinningControl() { return skinningControl; } @@ -832,6 +840,9 @@ public class PlayerInputControl implements CombatStyleBase.AnimReceiver { 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.addMapping(ACT_BORED_1, new KeyTrigger(KeyInput.KEY_F1)); + inputManager.addMapping(ACT_BORED_2, new KeyTrigger(KeyInput.KEY_F2)); + inputManager.addMapping(ACT_BORED_3, new KeyTrigger(KeyInput.KEY_F3)); inputManager.addListener(actionListener, ACTION_NAMES); } @@ -1222,7 +1233,8 @@ public class PlayerInputControl implements CombatStyleBase.AnimReceiver { } else { boolean swordShield = drawnWeaponType == de.blight.common.model.ItemSubCategory.SWORD && hasShieldEquipped(); - if (swordShield) { + boolean twoHand = drawnWeaponType == ItemSubCategory.TWO_HANDED_SWORD; + if (swordShield || twoHand) { if (boredAnimRemaining > 0f) { boredAnimRemaining -= tpf; target = boredVariant; @@ -1231,9 +1243,15 @@ public class PlayerInputControl implements CombatStyleBase.AnimReceiver { if (boredIdleTimer >= BORED_IDLE_DELAY) { boredIdleTimer = 0f; int pick = playerRng.nextInt(3); - boredVariant = pick == 0 ? AnimationAction.SWORD_IDLE_BORED - : pick == 1 ? AnimationAction.SWORD_IDLE_BORED_2 - : AnimationAction.SWORD_IDLE_BORED_3; + if (swordShield) { + boredVariant = pick == 0 ? AnimationAction.SWORD_IDLE_BORED + : pick == 1 ? AnimationAction.SWORD_IDLE_BORED_2 + : AnimationAction.SWORD_IDLE_BORED_3; + } else { + boredVariant = pick == 0 ? AnimationAction.TWO_HAND_IDLE_BORED + : pick == 1 ? AnimationAction.TWO_HAND_IDLE_BORED_2 + : AnimationAction.TWO_HAND_IDLE_BORED_3; + } boredAnimRemaining = resolveClipLength(boredVariant, 3.0f); target = boredVariant; } else { @@ -1676,6 +1694,25 @@ public class PlayerInputControl implements CombatStyleBase.AnimReceiver { currentAnim = AnimationAction.CROUCH_TO_STAND; } + private void triggerBoredAnim(int variant) { + if (!weaponDrawn || forward || backward || left || right || combatAnimActive || drawAnimActive) return; + if (physicsChar != null && !physicsChar.onGround()) return; + boolean swordShield = drawnWeaponType == de.blight.common.model.ItemSubCategory.SWORD && hasShieldEquipped(); + boolean twoHand = drawnWeaponType == ItemSubCategory.TWO_HANDED_SWORD; + if (!swordShield && !twoHand) return; + if (swordShield) { + boredVariant = variant == 0 ? AnimationAction.SWORD_IDLE_BORED + : variant == 1 ? AnimationAction.SWORD_IDLE_BORED_2 + : AnimationAction.SWORD_IDLE_BORED_3; + } else { + boredVariant = variant == 0 ? AnimationAction.TWO_HAND_IDLE_BORED + : variant == 1 ? AnimationAction.TWO_HAND_IDLE_BORED_2 + : AnimationAction.TWO_HAND_IDLE_BORED_3; + } + boredAnimRemaining = resolveClipLength(boredVariant, 3.0f); + boredIdleTimer = 0f; + } + private void triggerRollFromInput() { if (!weaponDrawn) return; if (rollActive || pickupActive || blockingAnimActive || turnActive diff --git a/blight-game/src/main/java/de/blight/game/control/WeaponAttachmentControl.java b/blight-game/src/main/java/de/blight/game/control/WeaponAttachmentControl.java index 461ac47..101b3e0 100644 --- a/blight-game/src/main/java/de/blight/game/control/WeaponAttachmentControl.java +++ b/blight-game/src/main/java/de/blight/game/control/WeaponAttachmentControl.java @@ -13,6 +13,8 @@ import de.blight.common.model.Item; import de.blight.common.model.ItemGrip; import de.blight.common.model.ItemSubCategory; import de.blight.common.model.MainCharacter; +import de.blight.common.model.ShieldAttachmentIO; +import de.blight.common.model.ShieldCategoryAttachment; import de.blight.common.model.WeaponAttachmentIO; import de.blight.common.model.WeaponCategoryAttachment; import org.slf4j.Logger; @@ -38,7 +40,8 @@ public class WeaponAttachmentControl extends AbstractControl { private static final float DEG_TO_RAD = (float) Math.PI / 180f; private static final float HALF_PI = (float) Math.PI * 0.5f; - private static final Vector3f BACK_GRIP_OFFSET = new Vector3f(0.0f, 0.40f, -0.13f); + private static final Vector3f BACK_GRIP_OFFSET = new Vector3f(0.0f, 0.40f, -0.13f); + private static final Vector3f TWO_HAND_BACK_GRIP_OFFSET = new Vector3f(0.0f, 0.40f, -0.03f); private static final Vector3f HAND_GRIP_OFFSET = new Vector3f( ItemGrip.HAND_GRIP_OFFSET_X, ItemGrip.HAND_GRIP_OFFSET_Y, ItemGrip.HAND_GRIP_OFFSET_Z); @@ -196,14 +199,16 @@ public class WeaponAttachmentControl extends AbstractControl { : boneInChar; boolean usesTwoHand = isTwoHand && leftForeArmNode != null && grip != null - && (grip.getGrip2PosX() != 0f || grip.getGrip2PosY() != 0f || grip.getGrip2PosZ() != 0f); + && (grip.getGrip2PosX() != 0f || grip.getGrip2PosY() != 0f || grip.getGrip2PosZ() != 0f) + && !playerInput.isDrawAnimActive(); if (usesTwoHand) { applyTwoHandTransform(attachInChar, boneRotInChar, grip, charWorldRotInv, charWorldPos, cfg); } else { applyHandTransform(attachInChar, boneRotInChar, grip, cfg); } } else { - applyBackTransform(boneInChar, boneRotInChar, grip); + boolean twoHanded = item != null && item.getSubCategory() == ItemSubCategory.TWO_HANDED_SWORD; + applyBackTransform(boneInChar, boneRotInChar, grip, twoHanded); } } @@ -356,7 +361,8 @@ public class WeaponAttachmentControl extends AbstractControl { // Ergebnis für Kurzschwert (gripRot=fromAngles(0,-90°,90°)) identisch zur alten Formel. private static final Quaternion BACK_ROT_NORM = new Quaternion(0f, -(float) Math.sqrt(0.5), (float) Math.sqrt(0.5), 0f); - private void applyBackTransform(Vector3f bonePosInChar, Quaternion boneRotInChar, ItemGrip grip) { + private void applyBackTransform(Vector3f bonePosInChar, Quaternion boneRotInChar, ItemGrip grip, + boolean twoHanded) { Quaternion gripRot = (grip != null) ? new Quaternion().fromAngles(DEG_TO_RAD * grip.getRotX(), DEG_TO_RAD * grip.getRotY(), @@ -369,7 +375,8 @@ public class WeaponAttachmentControl extends AbstractControl { ? new Quaternion().fromAngles(com.jme3.math.FastMath.PI, 0f, 0f) : new Quaternion(); Quaternion weaponRot = boneRotInChar.mult(BACK_ROT_NORM).mult(bladeFlip).mult(gripRot.inverse()); - Vector3f gripTarget = bonePosInChar.add(boneRotInChar.mult(BACK_GRIP_OFFSET)); + Vector3f gripTarget = bonePosInChar.add(boneRotInChar.mult( + twoHanded ? TWO_HAND_BACK_GRIP_OFFSET : BACK_GRIP_OFFSET)); Vector3f gripPos = (grip != null) ? new Vector3f(grip.getPosX(), grip.getPosY(), grip.getPosZ()) : Vector3f.ZERO; @@ -406,7 +413,9 @@ public class WeaponAttachmentControl extends AbstractControl { Vector3f boneInChar = charWorldRotInv.mult( leftForeArmNode.getWorldTranslation().subtract(charWorldPos)); Quaternion boneRotInChar = charWorldRotInv.mult(leftForeArmNode.getWorldRotation()); - applyShieldArmTransform(boneInChar, boneRotInChar, grip); + ShieldCategoryAttachment shieldCfg = ShieldAttachmentIO.get( + shield != null ? shield.getSubCategory() : null); + applyShieldArmTransform(boneInChar, boneRotInChar, grip, shieldCfg); shieldSpatial.setCullHint(Spatial.CullHint.Inherit); } else if (!drawn && backNode != null) { Vector3f boneInChar = charWorldRotInv.mult( @@ -419,17 +428,18 @@ public class WeaponAttachmentControl extends AbstractControl { } } - private void applyShieldArmTransform(Vector3f bonePosInChar, Quaternion boneRotInChar, ItemGrip grip) { + private void applyShieldArmTransform(Vector3f bonePosInChar, Quaternion boneRotInChar, + ItemGrip grip, ShieldCategoryAttachment cfg) { Quaternion animDelta = (leftForeArmBindRotInChar != null) ? boneRotInChar.mult(leftForeArmBindRotInChar.inverse()) : boneRotInChar; - Quaternion armRot = new Quaternion().fromAngles(SHIELD_ARM_ROT_X, SHIELD_ARM_ROT_Y, SHIELD_ARM_ROT_Z); + Quaternion armRot = new Quaternion().fromAngles( + DEG_TO_RAD * cfg.getRotX(), DEG_TO_RAD * cfg.getRotY(), DEG_TO_RAD * cfg.getRotZ()); Quaternion shieldRot = animDelta.mult(armRot); Vector3f gripPos = (grip != null) ? new Vector3f(grip.getPosX(), grip.getPosY(), grip.getPosZ()) : Vector3f.ZERO; - // Ankerpunkt Richtung Ellbogen verschieben (lokale −Y des LeftHand-Bones) - Vector3f anchor = bonePosInChar.add(boneRotInChar.mult(new Vector3f(0f, -SHIELD_ARM_ELBOW_OFFSET, 0f))); + Vector3f anchor = bonePosInChar.add(boneRotInChar.mult(new Vector3f(0f, -cfg.getElbowOffset(), 0f))); shieldSpatial.setLocalRotation(shieldRot); shieldSpatial.setLocalTranslation(anchor.subtract(shieldRot.mult(gripPos))); }