Kampfsystem: Hier Position des Zweihänders und neues feature zum editieren des offsets
This commit is contained in:
@@ -1237,6 +1237,27 @@ public class SharedInput {
|
||||
/** Klingenrichtung im Modellraum: -1 = Klinge bei -Z, +1 = Klinge bei +Z. Wird von guessGripPosition gesetzt. */
|
||||
public volatile float gripEditorBladeDirZ = -1f;
|
||||
|
||||
// ── Charakter-Preview im Grip-Editor ─────────────────────────────────────
|
||||
/** JFX→JME: Charakter-Preview ein- (true) oder ausschalten (false). */
|
||||
public volatile boolean gripEditorCharPreview = false;
|
||||
/** JFX→JME: ItemSubCategory-Name der aktuell editierten Waffe. */
|
||||
public volatile String gripEditorSubCategory = null;
|
||||
/** JME→JFX: Charakter-Preview ist bereit / aktiv. */
|
||||
public volatile boolean gripEditorCharPreviewReady = false;
|
||||
/** JFX→JME: Kategorie-Offsets (rechte Hand) für Live-Update im Preview. */
|
||||
public volatile float gripEditorCatHandOffX = 0.1f;
|
||||
public volatile float gripEditorCatHandOffY = 0f;
|
||||
public volatile float gripEditorCatHandOffZ = 0.05f;
|
||||
/** JFX→JME: Kategorie-Offsets (linke Hand, nur Zweihänder). */
|
||||
public volatile float gripEditorCatHand2OffX = 0f;
|
||||
public volatile float gripEditorCatHand2OffY = 0f;
|
||||
public volatile float gripEditorCatHand2OffZ = 0f;
|
||||
/** JFX→JME: true wenn Kategorie-Offsets geändert wurden. */
|
||||
public volatile boolean gripEditorCatOffsetsDirty = false;
|
||||
|
||||
// ── Attachment-Editor (eigenständige Ansicht für Hand-Anker pro Kategorie) ─
|
||||
public volatile boolean attachmentEditorActive = 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<SculptEdit> sculptEditQueue = new ConcurrentLinkedQueue<>();
|
||||
|
||||
@@ -34,6 +34,12 @@ import com.jme3.effect.ParticleMesh;
|
||||
import com.jme3.material.RenderState;
|
||||
import com.jme3.texture.Texture;
|
||||
import com.jme3.util.BufferUtils;
|
||||
import com.jme3.anim.AnimComposer;
|
||||
import com.jme3.math.Matrix4f;
|
||||
import de.blight.common.model.ItemGrip;
|
||||
import de.blight.common.model.ItemSubCategory;
|
||||
import de.blight.common.model.WeaponAttachmentIO;
|
||||
import de.blight.common.model.WeaponCategoryAttachment;
|
||||
import de.blight.editor.SharedInput;
|
||||
import de.blight.editor.util.ImpostorUtil;
|
||||
import de.blight.editor.util.ModelExportUtil;
|
||||
@@ -107,6 +113,16 @@ public class ModelEditorState extends BaseAppState {
|
||||
private float gripPosX = 0f, gripPosY = 0f, gripPosZ = 0f;
|
||||
private float gripRotX = 0f, gripRotY = 0f, gripRotZ = 0f;
|
||||
private float grip2PosX = 0f, grip2PosY = 0f, grip2PosZ = 0f;
|
||||
|
||||
// ── Charakter-Preview ────────────────────────────────────────────────────
|
||||
private Node charPreviewNode = null;
|
||||
private Spatial charWeaponSpatial = null;
|
||||
private Node charHandNode = null; // RightHand-Attachment
|
||||
private Node charLeftHandNode = null; // LeftHand-Attachment
|
||||
private Node charPalmNode = null; // RightHandMiddle1-Attachment
|
||||
private Quaternion charHandBindRot = null;
|
||||
private Vector3f charPalmOffset = null;
|
||||
private int charPalmDelayFrames = 0;
|
||||
private float interactableOffsetZ = 0f;
|
||||
private float interactableRotY = 0f;
|
||||
|
||||
@@ -244,6 +260,29 @@ public class ModelEditorState extends BaseAppState {
|
||||
guessGripPosition();
|
||||
}
|
||||
|
||||
// Charakter-Preview ein-/ausschalten
|
||||
boolean wantsPreview = (input.gripEditorCharPreview && input.gripEditorActive)
|
||||
|| input.attachmentEditorActive;
|
||||
if (wantsPreview && charPreviewNode == null) {
|
||||
enableCharPreview();
|
||||
} else if (!wantsPreview && charPreviewNode != null) {
|
||||
disableCharPreview();
|
||||
}
|
||||
|
||||
// Palm-Offset nach 2 Frames berechnen
|
||||
if (charPalmDelayFrames > 0 && charPalmNode != null && charHandNode != null) {
|
||||
if (--charPalmDelayFrames == 0) {
|
||||
Vector3f delta = charPalmNode.getWorldTranslation()
|
||||
.subtract(charHandNode.getWorldTranslation());
|
||||
charPalmOffset = charHandNode.getWorldRotation().inverse().mult(delta);
|
||||
}
|
||||
}
|
||||
|
||||
// Waffe im Charakter-Preview jedes Frame neu positionieren
|
||||
if (charPreviewNode != null && charWeaponSpatial != null && charHandNode != null) {
|
||||
updateCharPreviewWeapon();
|
||||
}
|
||||
|
||||
if (input.modelEditorEmbedLodsRequest) {
|
||||
input.modelEditorEmbedLodsRequest = false;
|
||||
embedLodsAndSave(input.modelEditorEmbedLodsPath);
|
||||
@@ -260,8 +299,14 @@ public class ModelEditorState extends BaseAppState {
|
||||
loadModel(openPath);
|
||||
}
|
||||
|
||||
// Grip-Gizmo aktualisieren
|
||||
if (input.gripEditorActive && input.gripEditorGizmoDirty) {
|
||||
// Invariante: wenn Charakter-Preview aktiv, standalone-Modell immer ausblenden
|
||||
if (charPreviewNode != null) {
|
||||
if (modelWrapper != null) modelWrapper.setCullHint(Spatial.CullHint.Always);
|
||||
if (gripGizmoNode != null) gripGizmoNode.setCullHint(Spatial.CullHint.Always);
|
||||
}
|
||||
|
||||
// Grip-Gizmo aktualisieren (nicht im reinen Attachment-Editor-Modus)
|
||||
if (input.gripEditorActive && !input.attachmentEditorActive && input.gripEditorGizmoDirty) {
|
||||
input.gripEditorGizmoDirty = false;
|
||||
gripPosX = input.gripEditorPosX;
|
||||
gripPosY = input.gripEditorPosY;
|
||||
@@ -799,6 +844,7 @@ public class ModelEditorState extends BaseAppState {
|
||||
originalSpatial = null;
|
||||
interactableArrowNode = null;
|
||||
gripGizmoNode = null;
|
||||
disableCharPreview();
|
||||
input.gripEditorActive = false;
|
||||
camLight = null;
|
||||
mainLight = null;
|
||||
@@ -2024,4 +2070,236 @@ public class ModelEditorState extends BaseAppState {
|
||||
float dz = -cosEle * (float) Math.cos(azi);
|
||||
return new Vector3f(dx, dy, dz).normalizeLocal();
|
||||
}
|
||||
|
||||
// ── Charakter-Preview ────────────────────────────────────────────────────
|
||||
|
||||
private void enableCharPreview() {
|
||||
if (previewRoot == null || currentPath == null) return;
|
||||
try {
|
||||
Spatial hero = app.getAssetManager().loadModel("Characters/Models/hero.j3o");
|
||||
charPreviewNode = new Node("charPreview");
|
||||
charPreviewNode.attachChild(hero);
|
||||
previewRoot.attachChild(charPreviewNode);
|
||||
|
||||
SkinningControl skin = findSkinCtrl(hero);
|
||||
if (skin != null) {
|
||||
charHandBindRot = extractJointBindRot(skin, "mixamorig:RightHand");
|
||||
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);
|
||||
loadClipToComposer(anim, skin, clipName);
|
||||
}
|
||||
}
|
||||
|
||||
charWeaponSpatial = app.getAssetManager().loadModel(currentPath);
|
||||
charPreviewNode.attachChild(charWeaponSpatial);
|
||||
|
||||
// Standalone-Waffe ausblenden
|
||||
if (modelWrapper != null) modelWrapper.setCullHint(Spatial.CullHint.Always);
|
||||
if (gripGizmoNode != null) gripGizmoNode.setCullHint(Spatial.CullHint.Always);
|
||||
|
||||
orbitDist = 2.5f;
|
||||
orbitYaw = 135f;
|
||||
orbitPitch = 15f;
|
||||
input.gripEditorCharPreviewReady = true;
|
||||
log.info("[CharPreview] Aktiviert");
|
||||
} catch (Exception e) {
|
||||
log.warn("[CharPreview] Fehler beim Laden: {}", e.getMessage());
|
||||
disableCharPreview();
|
||||
}
|
||||
}
|
||||
|
||||
private void disableCharPreview() {
|
||||
if (charPreviewNode != null) {
|
||||
charPreviewNode.removeFromParent();
|
||||
charPreviewNode = null;
|
||||
charWeaponSpatial = null;
|
||||
charHandNode = null;
|
||||
charLeftHandNode = null;
|
||||
charPalmNode = null;
|
||||
charHandBindRot = null;
|
||||
charPalmOffset = null;
|
||||
}
|
||||
if (modelWrapper != null) modelWrapper.setCullHint(Spatial.CullHint.Inherit);
|
||||
if (gripGizmoNode != null) gripGizmoNode.setCullHint(Spatial.CullHint.Always);
|
||||
input.gripEditorCharPreviewReady = false;
|
||||
}
|
||||
|
||||
private void updateCharPreviewWeapon() {
|
||||
// Kategorie-Offsets aus SharedInput lesen (live-update)
|
||||
WeaponCategoryAttachment cfg = new WeaponCategoryAttachment();
|
||||
cfg.setHandOffX(input.gripEditorCatHandOffX);
|
||||
cfg.setHandOffY(input.gripEditorCatHandOffY);
|
||||
cfg.setHandOffZ(input.gripEditorCatHandOffZ);
|
||||
cfg.setHand2OffX(input.gripEditorCatHand2OffX);
|
||||
cfg.setHand2OffY(input.gripEditorCatHand2OffY);
|
||||
cfg.setHand2OffZ(input.gripEditorCatHand2OffZ);
|
||||
|
||||
// Grip-Werte aus SharedInput (Spinner = display-Werte, JSON-Werte = + HAND_GRIP_OFFSET)
|
||||
float g1x = input.gripEditorPosX + ItemGrip.HAND_GRIP_OFFSET_X;
|
||||
float g1y = input.gripEditorPosY + ItemGrip.HAND_GRIP_OFFSET_Y;
|
||||
float g1z = input.gripEditorPosZ + ItemGrip.HAND_GRIP_OFFSET_Z;
|
||||
float g2x = input.gripEditorGrip2PosX;
|
||||
float g2y = input.gripEditorGrip2PosY;
|
||||
float g2z = input.gripEditorGrip2PosZ;
|
||||
boolean bladeAtMinusZ = g2z > g1z;
|
||||
boolean isTwoHand = bladeAtMinusZ && (g2x != 0f || g2y != 0f || g2z != 0f);
|
||||
|
||||
Quaternion charWorldRot = charPreviewNode.getWorldRotation();
|
||||
Quaternion charWorldRotInv = charWorldRot.inverse();
|
||||
Vector3f charWorldPos = charPreviewNode.getWorldTranslation();
|
||||
|
||||
Vector3f boneInChar = charWorldRotInv.mult(charHandNode.getWorldTranslation().subtract(charWorldPos));
|
||||
Quaternion boneRotInChar = charWorldRotInv.mult(charHandNode.getWorldRotation());
|
||||
Vector3f palmOff = charPalmOffset;
|
||||
Vector3f attachInChar = (palmOff != null)
|
||||
? boneInChar.add(boneRotInChar.mult(palmOff.mult(0.5f)))
|
||||
: boneInChar;
|
||||
|
||||
float DEG_TO_RAD = (float) Math.PI / 180f;
|
||||
Quaternion gripRot = new Quaternion().fromAngles(
|
||||
DEG_TO_RAD * input.gripEditorRotX,
|
||||
DEG_TO_RAD * input.gripEditorRotY,
|
||||
DEG_TO_RAD * input.gripEditorRotZ);
|
||||
Quaternion animDelta = (charHandBindRot != null)
|
||||
? boneRotInChar.mult(charHandBindRot.inverse())
|
||||
: boneRotInChar;
|
||||
Quaternion bladeFlip = bladeAtMinusZ
|
||||
? new Quaternion().fromAngles(com.jme3.math.FastMath.PI, 0f, 0f)
|
||||
: new Quaternion();
|
||||
Quaternion weaponRot = animDelta.mult(bladeFlip).mult(gripRot.inverse());
|
||||
|
||||
Vector3f handOff = bladeAtMinusZ
|
||||
? new Vector3f(cfg.getHandOffX(), cfg.getHandOffY(), -cfg.getHandOffZ())
|
||||
: new Vector3f(cfg.getHandOffX(), cfg.getHandOffY(), cfg.getHandOffZ());
|
||||
|
||||
Quaternion finalRot;
|
||||
Vector3f finalPos;
|
||||
|
||||
if (isTwoHand && charLeftHandNode != null) {
|
||||
Vector3f hand2Off = bladeAtMinusZ
|
||||
? new Vector3f(cfg.getHand2OffX(), cfg.getHand2OffY(), -cfg.getHand2OffZ())
|
||||
: new Vector3f(cfg.getHand2OffX(), cfg.getHand2OffY(), cfg.getHand2OffZ());
|
||||
Vector3f g1 = new Vector3f(g1x, g1y, g1z);
|
||||
Vector3f g2 = new Vector3f(g2x, g2y, g2z);
|
||||
Vector3f g1eff = g1.subtract(handOff);
|
||||
Vector3f g2eff = g2.add(hand2Off);
|
||||
|
||||
Vector3f leftHandInChar = charWorldRotInv.mult(
|
||||
charLeftHandNode.getWorldTranslation().subtract(charWorldPos));
|
||||
Vector3f currentGripDir = weaponRot.mult(g2eff.subtract(g1eff).normalizeLocal());
|
||||
Vector3f targetDir = leftHandInChar.subtract(attachInChar).normalizeLocal();
|
||||
finalRot = swingFromTo(currentGripDir, targetDir).mult(weaponRot);
|
||||
finalPos = attachInChar.subtract(finalRot.mult(g1eff));
|
||||
} else {
|
||||
Vector3f gripPos = new Vector3f(g1x, g1y, g1z);
|
||||
finalRot = weaponRot;
|
||||
finalPos = attachInChar.subtract(finalRot.mult(gripPos)).addLocal(finalRot.mult(handOff));
|
||||
}
|
||||
|
||||
charWeaponSpatial.setLocalRotation(finalRot);
|
||||
charWeaponSpatial.setLocalTranslation(finalPos);
|
||||
}
|
||||
|
||||
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";
|
||||
default -> "Idle";
|
||||
};
|
||||
}
|
||||
|
||||
private void loadClipToComposer(AnimComposer ac, SkinningControl dstSC, String clipName) {
|
||||
if (ac.getAnimClipsNames().contains(clipName)) {
|
||||
ac.setCurrentAction(clipName);
|
||||
return;
|
||||
}
|
||||
try {
|
||||
String assetKey = "Characters/clips/" + clipName + ".j3o";
|
||||
Spatial clipModel = app.getAssetManager().loadModel(assetKey);
|
||||
AnimComposer clipAC = findAnimComposer(clipModel);
|
||||
SkinningControl clipSC = findSkinCtrl(clipModel);
|
||||
if (clipAC == null || clipAC.getAnimClipsNames().isEmpty()) {
|
||||
log.warn("[CharPreview] Clip-Datei leer oder kein AnimComposer: {}", assetKey);
|
||||
return;
|
||||
}
|
||||
com.jme3.anim.Armature srcArm = clipSC != null ? clipSC.getArmature() : null;
|
||||
com.jme3.anim.Armature dstArm = dstSC != null ? dstSC.getArmature() : null;
|
||||
String firstName = clipAC.getAnimClipsNames().iterator().next();
|
||||
com.jme3.anim.AnimClip src = clipAC.getAnimClip(firstName);
|
||||
com.jme3.anim.AnimClip target;
|
||||
if (srcArm != null && dstArm != null) {
|
||||
target = de.blight.game.animation.RetargetingSystem.retarget(src, srcArm, dstArm);
|
||||
if (target == null) target = src;
|
||||
} else {
|
||||
target = src;
|
||||
}
|
||||
if (!clipName.equals(target.getName())) {
|
||||
com.jme3.anim.AnimClip renamed = new com.jme3.anim.AnimClip(clipName);
|
||||
renamed.setTracks(target.getTracks());
|
||||
target = renamed;
|
||||
}
|
||||
ac.addAnimClip(target);
|
||||
ac.setCurrentAction(clipName);
|
||||
log.info("[CharPreview] Clip '{}' geladen und gestartet", clipName);
|
||||
} catch (Exception e) {
|
||||
log.warn("[CharPreview] Clip '{}' konnte nicht geladen werden: {}", clipName, e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
private static SkinningControl findSkinCtrl(Spatial s) {
|
||||
SkinningControl sc = s.getControl(SkinningControl.class);
|
||||
if (sc != null) return sc;
|
||||
if (s instanceof Node n) {
|
||||
for (Spatial c : n.getChildren()) {
|
||||
sc = findSkinCtrl(c);
|
||||
if (sc != null) return sc;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private static AnimComposer findAnimComposer(Spatial s) {
|
||||
AnimComposer ac = s.getControl(AnimComposer.class);
|
||||
if (ac != null) return ac;
|
||||
if (s instanceof Node n) {
|
||||
for (Spatial c : n.getChildren()) {
|
||||
ac = findAnimComposer(c);
|
||||
if (ac != null) return ac;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private static Quaternion extractJointBindRot(SkinningControl sc, String jointName) {
|
||||
com.jme3.anim.Armature arm = sc.getArmature();
|
||||
if (arm == null) return null;
|
||||
com.jme3.anim.Joint joint = arm.getJoint(jointName);
|
||||
if (joint == null) return null;
|
||||
Matrix4f invBind = joint.getInverseModelBindMatrix();
|
||||
if (invBind == null) return null;
|
||||
Matrix4f bindMat = invBind.invert();
|
||||
if (bindMat == null) return null;
|
||||
return bindMat.toRotationQuat().normalizeLocal();
|
||||
}
|
||||
|
||||
private static Quaternion swingFromTo(Vector3f from, Vector3f to) {
|
||||
float dot = com.jme3.math.FastMath.clamp(from.dot(to), -1f, 1f);
|
||||
Vector3f axis = from.cross(to);
|
||||
float len = axis.length();
|
||||
if (len < 0.001f) {
|
||||
if (dot > 0f) return new Quaternion();
|
||||
Vector3f perp = Math.abs(from.x) <= Math.abs(from.y)
|
||||
? from.cross(Vector3f.UNIT_X).normalizeLocal()
|
||||
: from.cross(Vector3f.UNIT_Y).normalizeLocal();
|
||||
return new Quaternion().fromAngleNormalAxis(com.jme3.math.FastMath.PI, perp);
|
||||
}
|
||||
return new Quaternion().fromAngleNormalAxis(com.jme3.math.FastMath.acos(dot),
|
||||
axis.divideLocal(len));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,279 @@
|
||||
package de.blight.editor.toolset.content;
|
||||
|
||||
import de.blight.common.model.ItemGrip;
|
||||
import de.blight.common.model.ItemSubCategory;
|
||||
import de.blight.common.model.WeaponAttachmentIO;
|
||||
import de.blight.common.model.WeaponCategoryAttachment;
|
||||
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 per-Kategorie Hand-Anker-Offsets.
|
||||
* Zeigt den Helden direkt mit Waffe in der Idle-Animation – keine Griff-Gizmos.
|
||||
*/
|
||||
public class AttachmentEditorController {
|
||||
|
||||
private final ToolContext ctx;
|
||||
|
||||
private Spinner<Double> handOffXSpin, handOffYSpin, handOffZSpin;
|
||||
private Spinner<Double> hand2OffXSpin, hand2OffYSpin, hand2OffZSpin;
|
||||
|
||||
private boolean suppressEvents = false;
|
||||
private ItemSubCategory itemSubCategory = null;
|
||||
private Runnable onReturn = null;
|
||||
|
||||
public AttachmentEditorController(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();
|
||||
// Grip-Editor-Modus nicht aktiv – reiner Attachment-Preview
|
||||
input.gripEditorActive = false;
|
||||
input.gripEditorIsShield = false;
|
||||
input.gripEditorCharPreview = false;
|
||||
input.attachmentEditorActive = true;
|
||||
|
||||
// Subkategorie + aktuelle Config in SharedInput laden
|
||||
input.gripEditorSubCategory = subCategory != null ? subCategory.name() : null;
|
||||
WeaponCategoryAttachment cfg = WeaponAttachmentIO.get(subCategory);
|
||||
input.gripEditorCatHandOffX = cfg.getHandOffX();
|
||||
input.gripEditorCatHandOffY = cfg.getHandOffY();
|
||||
input.gripEditorCatHandOffZ = cfg.getHandOffZ();
|
||||
input.gripEditorCatHand2OffX = cfg.getHand2OffX();
|
||||
input.gripEditorCatHand2OffY = cfg.getHand2OffY();
|
||||
input.gripEditorCatHand2OffZ = cfg.getHand2OffZ();
|
||||
input.gripEditorCatOffsetsDirty = false;
|
||||
|
||||
// Grip-Werte des Items setzen, damit updateCharPreviewWeapon g1/g2 kennt
|
||||
ItemGrip g = grip != null ? grip : new ItemGrip();
|
||||
input.gripEditorPosX = g.getPosX() - ItemGrip.HAND_GRIP_OFFSET_X;
|
||||
input.gripEditorPosY = g.getPosY() - ItemGrip.HAND_GRIP_OFFSET_Y;
|
||||
input.gripEditorPosZ = g.getPosZ() - ItemGrip.HAND_GRIP_OFFSET_Z;
|
||||
input.gripEditorRotX = g.getRotX();
|
||||
input.gripEditorRotY = g.getRotY();
|
||||
input.gripEditorRotZ = g.getRotZ();
|
||||
input.gripEditorGrip2PosX = g.getGrip2PosX();
|
||||
input.gripEditorGrip2PosY = g.getGrip2PosY();
|
||||
input.gripEditorGrip2PosZ = g.getGrip2PosZ();
|
||||
input.gripEditorGizmoDirty = false;
|
||||
|
||||
// Waffenmodell laden
|
||||
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("attachmentEditor");
|
||||
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("Hand-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(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.valueProperty().addListener((obs, o, n) -> onSpinnerChanged());
|
||||
handOffYSpin.valueProperty().addListener((obs, o, n) -> onSpinnerChanged());
|
||||
handOffZSpin.valueProperty().addListener((obs, o, n) -> onSpinnerChanged());
|
||||
hand2OffXSpin.valueProperty().addListener((obs, o, n) -> onSpinnerChanged());
|
||||
hand2OffYSpin.valueProperty().addListener((obs, o, n) -> onSpinnerChanged());
|
||||
hand2OffZSpin.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("Offsets gelten für alle Waffen dieser Kategorie.\nDer Held zeigt die Waffe in der Idle-Animation."),
|
||||
new Separator(),
|
||||
styledTitle("Rechte Hand – Anker-Offset (m)"),
|
||||
styledHint("Verschiebt den Befestigungspunkt der rechten Hand\nim Knochenraum des Charakters."),
|
||||
row("X:", handOffXSpin),
|
||||
row("Y:", handOffYSpin),
|
||||
row("Z:", handOffZSpin),
|
||||
new Separator(),
|
||||
styledTitle("Linke Hand – Anker-Offset (m)"),
|
||||
styledHint("Zweiter Griffpunkt (linke Hand) – nur relevant bei\nZweihänder-Waffen."),
|
||||
row("X:", hand2OffXSpin),
|
||||
row("Y:", hand2OffYSpin),
|
||||
row("Z:", hand2OffZSpin),
|
||||
new Separator(),
|
||||
saveBtn,
|
||||
cancelBtn
|
||||
);
|
||||
return panel;
|
||||
}
|
||||
|
||||
// ── Spinner-Events ────────────────────────────────────────────────────────
|
||||
|
||||
private void onSpinnerChanged() {
|
||||
if (suppressEvents) return;
|
||||
SharedInput input = ctx.input();
|
||||
input.gripEditorCatHandOffX = handOffXSpin.getValue().floatValue();
|
||||
input.gripEditorCatHandOffY = handOffYSpin.getValue().floatValue();
|
||||
input.gripEditorCatHandOffZ = handOffZSpin.getValue().floatValue();
|
||||
input.gripEditorCatHand2OffX = hand2OffXSpin.getValue().floatValue();
|
||||
input.gripEditorCatHand2OffY = hand2OffYSpin.getValue().floatValue();
|
||||
input.gripEditorCatHand2OffZ = hand2OffZSpin.getValue().floatValue();
|
||||
input.gripEditorCatOffsetsDirty = true;
|
||||
}
|
||||
|
||||
// ── Speichern / Abbrechen ─────────────────────────────────────────────────
|
||||
|
||||
private void saveAndReturn() {
|
||||
if (itemSubCategory != null) {
|
||||
WeaponCategoryAttachment cfg = new WeaponCategoryAttachment();
|
||||
cfg.setHandOffX(handOffXSpin.getValue().floatValue());
|
||||
cfg.setHandOffY(handOffYSpin.getValue().floatValue());
|
||||
cfg.setHandOffZ(handOffZSpin.getValue().floatValue());
|
||||
cfg.setHand2OffX(hand2OffXSpin.getValue().floatValue());
|
||||
cfg.setHand2OffY(hand2OffYSpin.getValue().floatValue());
|
||||
cfg.setHand2OffZ(hand2OffZSpin.getValue().floatValue());
|
||||
WeaponAttachmentIO.set(itemSubCategory, cfg);
|
||||
java.nio.file.Path dest = ctx.assetRoot().resolve("weapon_attachment.json");
|
||||
WeaponAttachmentIO.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 mirrorRoot = projectRoot.resolve(root);
|
||||
if (!java.nio.file.Files.isDirectory(mirrorRoot)) continue;
|
||||
java.nio.file.Path mirror = mirrorRoot.resolve("weapon_attachment.json");
|
||||
try {
|
||||
java.nio.file.Files.copy(savedFile, mirror,
|
||||
java.nio.file.StandardCopyOption.REPLACE_EXISTING);
|
||||
} catch (Exception e) {
|
||||
// Mirror nicht vorhanden oder nicht beschreibbar – ignorieren
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void onCancel() {
|
||||
closeEditor();
|
||||
}
|
||||
|
||||
private void closeEditor() {
|
||||
SharedInput input = ctx.input();
|
||||
input.attachmentEditorActive = false;
|
||||
input.gripEditorCharPreview = false;
|
||||
input.gripEditorCatOffsetsDirty = false;
|
||||
|
||||
Runnable ret = onReturn;
|
||||
onReturn = null;
|
||||
itemSubCategory = null;
|
||||
|
||||
if (ret != null) ret.run(); else ctx.switchToWorld().run();
|
||||
}
|
||||
|
||||
// ── Helpers ───────────────────────────────────────────────────────────────
|
||||
|
||||
private static Spinner<Double> 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<Double> 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<Double> 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;
|
||||
}
|
||||
}
|
||||
@@ -88,11 +88,13 @@ public class ContentController {
|
||||
// ──────────────────────────────────────────────────────────────────────────
|
||||
|
||||
private Runnable onEnterContentMode;
|
||||
private GripEditorController gripEditorController;
|
||||
private GripEditorController gripEditorController;
|
||||
private AttachmentEditorController attachmentEditorController;
|
||||
|
||||
public ContentController(ToolContext ctx) {
|
||||
this.ctx = ctx;
|
||||
this.gripEditorController = new GripEditorController(ctx);
|
||||
this.gripEditorController = new GripEditorController(ctx);
|
||||
this.attachmentEditorController = new AttachmentEditorController(ctx);
|
||||
}
|
||||
|
||||
// ── Öffentliche API ────────────────────────────────────────────────────────
|
||||
@@ -372,7 +374,10 @@ public class ContentController {
|
||||
wireItemView(activeItemView);
|
||||
ctx.setCenterView().accept(activeItemView);
|
||||
ctx.setRightPanel().accept(null);
|
||||
if (selectItemId != null) activeItemView.selectItem(selectItemId);
|
||||
if (selectItemId != null) {
|
||||
activeItemView.selectItem(selectItemId);
|
||||
selectItemInTree(selectItemId);
|
||||
}
|
||||
}
|
||||
|
||||
/** Prefs-Restore: stellt Dual-Window-Position und Content-Section wieder her. */
|
||||
@@ -995,7 +1000,7 @@ public class ContentController {
|
||||
|
||||
private void wireItemView(de.blight.editor.ui.ItemEditorView view) {
|
||||
view.setOnIdChanged(this::refreshContentItemTree);
|
||||
view.setGripEditorLauncher((modelPath, currentGrip, isShield, onSave) -> {
|
||||
view.setGripEditorLauncher((modelPath, currentGrip, isShield, subCategory, onSave) -> {
|
||||
String itemId = view.getCurrentItemId();
|
||||
gripEditorController.setOnReturn(() -> {
|
||||
if (dualWindowMode) {
|
||||
@@ -1005,7 +1010,19 @@ public class ContentController {
|
||||
switchToItemEditor(itemId);
|
||||
}
|
||||
});
|
||||
gripEditorController.open(modelPath, currentGrip, isShield, onSave);
|
||||
gripEditorController.open(modelPath, currentGrip, isShield, subCategory, onSave);
|
||||
});
|
||||
view.setAttachmentEditorLauncher((modelPath, subCategory, grip) -> {
|
||||
String itemId = view.getCurrentItemId();
|
||||
attachmentEditorController.setOnReturn(() -> {
|
||||
if (dualWindowMode) {
|
||||
buildItemViewInContent();
|
||||
if (activeItemView != null) activeItemView.selectItem(itemId);
|
||||
} else {
|
||||
switchToItemEditor(itemId);
|
||||
}
|
||||
});
|
||||
attachmentEditorController.open(modelPath, subCategory, grip);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1693,6 +1710,25 @@ public class ContentController {
|
||||
}
|
||||
}
|
||||
|
||||
private void selectItemInTree(String itemId) {
|
||||
if (contentItemNode == null || contentTreeView == null || itemId == null) return;
|
||||
contentItemNode.setExpanded(true);
|
||||
for (TreeItem<String> leaf : contentItemNode.getChildren()) {
|
||||
String val = leaf.getValue();
|
||||
String leafId = val != null && val.contains(":") ? val.substring(val.indexOf(':') + 1) : val;
|
||||
if (itemId.equals(leafId)) {
|
||||
contentTreeUpdating = true;
|
||||
try {
|
||||
contentTreeView.getSelectionModel().select(leaf);
|
||||
contentTreeView.scrollTo(contentTreeView.getRow(leaf));
|
||||
} finally {
|
||||
contentTreeUpdating = false;
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void refreshContentFractionTree() {
|
||||
if (contentFractionNode == null) return;
|
||||
contentTreeUpdating = true;
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package de.blight.editor.toolset.content;
|
||||
|
||||
import de.blight.common.model.ItemGrip;
|
||||
import de.blight.common.model.ItemSubCategory;
|
||||
import de.blight.editor.SharedInput;
|
||||
import de.blight.editor.ToolContext;
|
||||
import de.blight.editor.state.ModelEditorState;
|
||||
@@ -30,6 +31,7 @@ public class GripEditorController implements ItemEditorView.GripEditorLauncher {
|
||||
private Runnable onReturn;
|
||||
private boolean suppressSpinnerEvents = false;
|
||||
private boolean shieldMode = false;
|
||||
private ItemSubCategory itemSubCategory = null;
|
||||
|
||||
public void setOnReturn(Runnable r) { this.onReturn = r; }
|
||||
|
||||
@@ -47,12 +49,13 @@ public class GripEditorController implements ItemEditorView.GripEditorLauncher {
|
||||
rotXSpin.getValueFactory().setValue(0.0);
|
||||
rotYSpin.getValueFactory().setValue(0.0);
|
||||
rotZSpin.getValueFactory().setValue(0.0);
|
||||
// 2. Griff (linke Hand): 20cm Richtung Pommel vom 1. Griff (Zweihänder-Grifflänge).
|
||||
// Grip2-Spinner = Modell-Koordinaten direkt (kein HAND_GRIP_OFFSET), deshalb JSON-Pos von Grip1 verwenden.
|
||||
float grip1JsonZ = posZ + ItemGrip.HAND_GRIP_OFFSET_Z;
|
||||
grip2XSpin.getValueFactory().setValue((double) gX);
|
||||
grip2YSpin.getValueFactory().setValue((double) gY);
|
||||
grip2ZSpin.getValueFactory().setValue((double)(grip1JsonZ - bladeDirZ * 0.20f));
|
||||
// 2. Griff (linke Hand) nur für Zweihänder setzen
|
||||
if (itemSubCategory == ItemSubCategory.TWO_HANDED_SWORD) {
|
||||
float grip1JsonZ = posZ + ItemGrip.HAND_GRIP_OFFSET_Z;
|
||||
grip2XSpin.getValueFactory().setValue((double) gX);
|
||||
grip2YSpin.getValueFactory().setValue((double) gY);
|
||||
grip2ZSpin.getValueFactory().setValue((double)(grip1JsonZ - bladeDirZ * 0.20f));
|
||||
}
|
||||
suppressSpinnerEvents = false;
|
||||
onSpinnerChanged();
|
||||
}
|
||||
@@ -64,23 +67,29 @@ public class GripEditorController implements ItemEditorView.GripEditorLauncher {
|
||||
// ── GripEditorLauncher ────────────────────────────────────────────────────
|
||||
|
||||
@Override
|
||||
public void open(String modelPath, ItemGrip currentGrip, boolean isShield, Consumer<ItemGrip> onSave) {
|
||||
pendingOnSave = onSave;
|
||||
shieldMode = isShield;
|
||||
public void open(String modelPath, ItemGrip currentGrip, boolean isShield,
|
||||
ItemSubCategory subCategory, Consumer<ItemGrip> onSave) {
|
||||
pendingOnSave = onSave;
|
||||
shieldMode = isShield;
|
||||
itemSubCategory = subCategory;
|
||||
|
||||
SharedInput input = ctx.input();
|
||||
input.gripEditorActive = true;
|
||||
input.gripEditorIsShield = isShield;
|
||||
input.gripEditorActive = true;
|
||||
input.gripEditorIsShield = isShield;
|
||||
input.gripEditorSubCategory = subCategory != null ? subCategory.name() : null;
|
||||
input.gripEditorCharPreview = false;
|
||||
input.gripEditorCharPreviewReady = false;
|
||||
input.attachmentEditorActive = false;
|
||||
|
||||
// Grip-Werte aus Item übernehmen (oder Defaults)
|
||||
// Spinner/Gizmo zeigen den Handflächen-Kontaktpunkt (= JSON-pos minus HAND_GRIP_OFFSET)
|
||||
ItemGrip g = currentGrip != null ? currentGrip : new ItemGrip();
|
||||
input.gripEditorPosX = g.getPosX() - ItemGrip.HAND_GRIP_OFFSET_X;
|
||||
input.gripEditorPosY = g.getPosY() - ItemGrip.HAND_GRIP_OFFSET_Y;
|
||||
input.gripEditorPosZ = g.getPosZ() - ItemGrip.HAND_GRIP_OFFSET_Z;
|
||||
input.gripEditorRotX = g.getRotX();
|
||||
input.gripEditorRotY = g.getRotY();
|
||||
input.gripEditorRotZ = g.getRotZ();
|
||||
input.gripEditorPosX = g.getPosX() - ItemGrip.HAND_GRIP_OFFSET_X;
|
||||
input.gripEditorPosY = g.getPosY() - ItemGrip.HAND_GRIP_OFFSET_Y;
|
||||
input.gripEditorPosZ = g.getPosZ() - ItemGrip.HAND_GRIP_OFFSET_Z;
|
||||
input.gripEditorRotX = g.getRotX();
|
||||
input.gripEditorRotY = g.getRotY();
|
||||
input.gripEditorRotZ = g.getRotZ();
|
||||
input.gripEditorGrip2PosX = g.getGrip2PosX();
|
||||
input.gripEditorGrip2PosY = g.getGrip2PosY();
|
||||
input.gripEditorGrip2PosZ = g.getGrip2PosZ();
|
||||
@@ -125,7 +134,7 @@ public class GripEditorController implements ItemEditorView.GripEditorLauncher {
|
||||
// ── Toolbar ───────────────────────────────────────────────────────────────
|
||||
|
||||
private ToolBar buildToolbar() {
|
||||
Button cancelBtn = new Button("← Zurück");
|
||||
Button cancelBtn = new Button("<- Zurück");
|
||||
cancelBtn.setOnAction(e -> onCancel());
|
||||
Label title = new Label("Griff-Editor");
|
||||
title.setStyle("-fx-font-weight: bold; -fx-font-size: 13;");
|
||||
@@ -284,8 +293,9 @@ public class GripEditorController implements ItemEditorView.GripEditorLauncher {
|
||||
grip.setHandOffsetZ(handOffZSpin.getValue().floatValue());
|
||||
|
||||
SharedInput input = ctx.input();
|
||||
input.gripEditorActive = false;
|
||||
input.gripEditorIsShield = false;
|
||||
input.gripEditorActive = false;
|
||||
input.gripEditorIsShield = false;
|
||||
input.gripEditorCharPreview = false;
|
||||
|
||||
Consumer<ItemGrip> save = pendingOnSave;
|
||||
Runnable ret = onReturn;
|
||||
@@ -297,8 +307,10 @@ public class GripEditorController implements ItemEditorView.GripEditorLauncher {
|
||||
}
|
||||
|
||||
private void onCancel() {
|
||||
ctx.input().gripEditorActive = false;
|
||||
ctx.input().gripEditorIsShield = false;
|
||||
SharedInput input = ctx.input();
|
||||
input.gripEditorActive = false;
|
||||
input.gripEditorIsShield = false;
|
||||
input.gripEditorCharPreview = false;
|
||||
|
||||
Runnable ret = onReturn;
|
||||
pendingOnSave = null;
|
||||
@@ -313,7 +325,7 @@ public class GripEditorController implements ItemEditorView.GripEditorLauncher {
|
||||
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("%.2f", v) + " " + unit; }
|
||||
@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(); }
|
||||
@@ -340,7 +352,7 @@ public class GripEditorController implements ItemEditorView.GripEditorLauncher {
|
||||
|
||||
private static HBox row(String labelText, Spinner<Double> spinner) {
|
||||
Label lbl = new Label(labelText);
|
||||
lbl.setMinWidth(20);
|
||||
lbl.setMinWidth(30);
|
||||
lbl.setStyle("-fx-text-fill: #aaa;");
|
||||
HBox.setHgrow(spinner, Priority.ALWAYS);
|
||||
HBox box = new HBox(6, lbl, spinner);
|
||||
|
||||
@@ -56,11 +56,16 @@ public class ItemEditorView extends BorderPane {
|
||||
private VBox effectsRows;
|
||||
|
||||
public interface GripEditorLauncher {
|
||||
void open(String modelPath, ItemGrip currentGrip, boolean isShield, java.util.function.Consumer<ItemGrip> onSave);
|
||||
void open(String modelPath, ItemGrip currentGrip, boolean isShield, ItemSubCategory subCategory, java.util.function.Consumer<ItemGrip> onSave);
|
||||
}
|
||||
|
||||
public interface AttachmentEditorLauncher {
|
||||
void open(String modelPath, ItemSubCategory subCategory, ItemGrip grip);
|
||||
}
|
||||
|
||||
private Runnable onIdChanged;
|
||||
private GripEditorLauncher gripEditorLauncher;
|
||||
private GripEditorLauncher gripEditorLauncher;
|
||||
private AttachmentEditorLauncher attachmentEditorLauncher;
|
||||
|
||||
private final PauseTransition savePause = new PauseTransition(Duration.millis(600));
|
||||
|
||||
@@ -90,6 +95,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 String getCurrentItemId() { return current != null ? current.getItemId() : null; }
|
||||
|
||||
public void selectItem(String itemId) {
|
||||
@@ -237,11 +243,17 @@ public class ItemEditorView extends BorderPane {
|
||||
gripBtn.setStyle("-fx-background-color: #3a3a5a; -fx-text-fill: #ddddff; -fx-cursor: hand;");
|
||||
gripBtn.setOnAction(e -> openGripEditor());
|
||||
|
||||
Button attachmentBtn = new Button("Hand-Anker bearbeiten...");
|
||||
attachmentBtn.setMaxWidth(Double.MAX_VALUE);
|
||||
attachmentBtn.setStyle("-fx-background-color: #1a3a3a; -fx-text-fill: #88ffcc; -fx-cursor: hand;");
|
||||
attachmentBtn.setOnAction(e -> openAttachmentEditor());
|
||||
|
||||
weaponSection = new VBox(6,
|
||||
new Separator(),
|
||||
sectionTitle("Kampfwerte"),
|
||||
row("Schaden:", damageSpinner),
|
||||
gripBtn
|
||||
gripBtn,
|
||||
attachmentBtn
|
||||
);
|
||||
weaponSection.setVisible(false);
|
||||
weaponSection.setManaged(false);
|
||||
@@ -325,16 +337,22 @@ public class ItemEditorView extends BorderPane {
|
||||
private void openGripEditor() {
|
||||
if (current == null || gripEditorLauncher == null) return;
|
||||
String modelPath = modelRefField.getText().trim();
|
||||
gripEditorLauncher.open(modelPath.isBlank() ? null : modelPath, current.getGrip(), false, grip -> {
|
||||
gripEditorLauncher.open(modelPath.isBlank() ? null : modelPath, current.getGrip(), false, current.getSubCategory(), grip -> {
|
||||
current.setGrip(grip);
|
||||
persistCurrent();
|
||||
});
|
||||
}
|
||||
|
||||
private void openAttachmentEditor() {
|
||||
if (current == null || attachmentEditorLauncher == null) return;
|
||||
String modelPath = modelRefField.getText().trim();
|
||||
attachmentEditorLauncher.open(modelPath.isBlank() ? null : modelPath, current.getSubCategory(), current.getGrip());
|
||||
}
|
||||
|
||||
private void openShieldGripEditor() {
|
||||
if (current == null || gripEditorLauncher == null) return;
|
||||
String modelPath = modelRefField.getText().trim();
|
||||
gripEditorLauncher.open(modelPath.isBlank() ? null : modelPath, current.getGrip(), true, grip -> {
|
||||
gripEditorLauncher.open(modelPath.isBlank() ? null : modelPath, current.getGrip(), true, current.getSubCategory(), grip -> {
|
||||
current.setGrip(grip);
|
||||
persistCurrent();
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user