Beginn des Kampf Systems
This commit is contained in:
@@ -194,6 +194,10 @@ public class EditorApp extends Application {
|
||||
colorAbsPath -> { de.blight.editor.TextureMapData d = terrainController.loadTexMeta(colorAbsPath); return d.normalMap != null ? d.normalMap : ""; });
|
||||
terrainController.setGrassController(grassController);
|
||||
contentController = new de.blight.editor.toolset.content.ContentController(toolCtx);
|
||||
contentController.setOnEnterContentMode(() -> {
|
||||
if (assetController.isOverlayOpen()) assetController.closeOverlay();
|
||||
assetController.hideTabBtn();
|
||||
});
|
||||
tripoController = new de.blight.editor.toolset.tripo.TripoController(toolCtx);
|
||||
gameController = new de.blight.editor.toolset.game.GameController(toolCtx);
|
||||
worldPanelController = new de.blight.editor.toolset.world.WorldPanelController(toolCtx, this::scheduleAutoSave);
|
||||
|
||||
@@ -1167,6 +1167,18 @@ public class SharedInput {
|
||||
/** Gesetzt vom JME-Thread nach Raycast-Klick, damit JFX-Spinner aktualisiert werden. */
|
||||
public volatile boolean modelInteractablePosSetFromJme = false;
|
||||
|
||||
// ── Griff-Editor (Waffen-Attachment-Point) ────────────────────────────────
|
||||
/** true → ModelEditorState zeigt den Grip-Gizmo statt des Interactable-Pfeils. */
|
||||
public volatile boolean gripEditorActive = false;
|
||||
public volatile float gripEditorPosX = 0f;
|
||||
public volatile float gripEditorPosY = 0f;
|
||||
public volatile float gripEditorPosZ = 0f;
|
||||
public volatile float gripEditorRotX = 0f;
|
||||
public volatile float gripEditorRotY = 0f;
|
||||
public volatile float gripEditorRotZ = 0f;
|
||||
/** Gesetzt vom JFX-Thread wenn Spinner geändert → JME baut Gizmo neu. */
|
||||
public volatile boolean gripEditorGizmoDirty = 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<>();
|
||||
|
||||
@@ -99,6 +99,11 @@ public class ModelEditorState extends BaseAppState {
|
||||
private Node interactableArrowNode = null;
|
||||
private float interactableOffsetX = 0f;
|
||||
private float interactableOffsetY = 0.5f;
|
||||
|
||||
/** Grip-Gizmo: 15cm-Pfeil für Waffen-Attachment-Point. */
|
||||
private Node gripGizmoNode = null;
|
||||
private float gripPosX = 0f, gripPosY = 0f, gripPosZ = 0f;
|
||||
private float gripRotX = 0f, gripRotY = 0f, gripRotZ = 0f;
|
||||
private float interactableOffsetZ = 0f;
|
||||
private float interactableRotY = 0f;
|
||||
|
||||
@@ -232,6 +237,18 @@ public class ModelEditorState extends BaseAppState {
|
||||
loadModel(openPath);
|
||||
}
|
||||
|
||||
// Grip-Gizmo aktualisieren
|
||||
if (input.gripEditorActive && input.gripEditorGizmoDirty) {
|
||||
input.gripEditorGizmoDirty = false;
|
||||
gripPosX = input.gripEditorPosX;
|
||||
gripPosY = input.gripEditorPosY;
|
||||
gripPosZ = input.gripEditorPosZ;
|
||||
gripRotX = input.gripEditorRotX;
|
||||
gripRotY = input.gripEditorRotY;
|
||||
gripRotZ = input.gripEditorRotZ;
|
||||
rebuildGripGizmo();
|
||||
}
|
||||
|
||||
int removeLod = input.modelEditorRemoveEmbeddedLod;
|
||||
if (removeLod > 0) {
|
||||
input.modelEditorRemoveEmbeddedLod = -1;
|
||||
@@ -705,6 +722,17 @@ public class ModelEditorState extends BaseAppState {
|
||||
interactableOffsetZ = input.modelInteractableOffsetZ;
|
||||
interactableRotY = input.modelInteractableRotY;
|
||||
|
||||
gripGizmoNode = new Node("gripGizmo");
|
||||
gripGizmoNode.setCullHint(Spatial.CullHint.Always);
|
||||
previewRoot.attachChild(gripGizmoNode);
|
||||
|
||||
gripPosX = input.gripEditorPosX;
|
||||
gripPosY = input.gripEditorPosY;
|
||||
gripPosZ = input.gripEditorPosZ;
|
||||
gripRotX = input.gripEditorRotX;
|
||||
gripRotY = input.gripEditorRotY;
|
||||
gripRotZ = input.gripEditorRotZ;
|
||||
|
||||
orbitYaw = 30f;
|
||||
orbitPitch = 25f;
|
||||
}
|
||||
@@ -726,6 +754,8 @@ public class ModelEditorState extends BaseAppState {
|
||||
embeddedLodSpatials = null;
|
||||
originalSpatial = null;
|
||||
interactableArrowNode = null;
|
||||
gripGizmoNode = null;
|
||||
input.gripEditorActive = false;
|
||||
camLight = null;
|
||||
mainLight = null;
|
||||
treeLightMaterials.clear();
|
||||
@@ -1490,6 +1520,70 @@ public class ModelEditorState extends BaseAppState {
|
||||
input.modelInteractableActive ? Spatial.CullHint.Inherit : Spatial.CullHint.Always);
|
||||
}
|
||||
|
||||
/** Zeichnet / aktualisiert den 15cm-Grip-Gizmo (orangefarbener Pfeil). */
|
||||
public void rebuildGripGizmo() {
|
||||
if (gripGizmoNode == null) return;
|
||||
gripGizmoNode.detachAllChildren();
|
||||
|
||||
float shaftLen = 0.12f;
|
||||
float shaftRad = 0.008f;
|
||||
float headRad = 0.022f;
|
||||
float headLen = 0.03f; // 15cm gesamt: 12cm Schaft + 3cm Kopf
|
||||
|
||||
Material mat = new Material(app.getAssetManager(), "Common/MatDefs/Misc/Unshaded.j3md");
|
||||
mat.setColor("Color", new ColorRGBA(1f, 0.55f, 0f, 1f));
|
||||
Material matHead = new Material(app.getAssetManager(), "Common/MatDefs/Misc/Unshaded.j3md");
|
||||
matHead.setColor("Color", new ColorRGBA(1f, 0.3f, 0f, 1f));
|
||||
|
||||
Node group = new Node("gripGroup");
|
||||
group.setLocalTranslation(gripPosX, gripPosY, gripPosZ);
|
||||
|
||||
Quaternion rot = new Quaternion();
|
||||
rot.fromAngles(
|
||||
com.jme3.math.FastMath.DEG_TO_RAD * gripRotX,
|
||||
com.jme3.math.FastMath.DEG_TO_RAD * gripRotY,
|
||||
com.jme3.math.FastMath.DEG_TO_RAD * gripRotZ);
|
||||
group.setLocalRotation(rot);
|
||||
|
||||
// Kleiner Würfel am Griffpunkt
|
||||
Geometry marker = new Geometry("gMarker", new Box(0.015f, 0.015f, 0.015f));
|
||||
marker.setMaterial(mat);
|
||||
|
||||
// Schaft entlang +Z
|
||||
Geometry shaft = new Geometry("gShaft", new Cylinder(4, 8, shaftRad, shaftLen, true));
|
||||
shaft.setMaterial(mat);
|
||||
shaft.setLocalTranslation(0f, 0f, shaftLen * 0.5f);
|
||||
|
||||
// Kegelspitze: Dome zeigt per Default nach +Y → 90° um X → zeigt nach +Z
|
||||
Geometry head = new Geometry("gHead", new Dome(Vector3f.ZERO, 2, 8, headRad, false));
|
||||
head.setMaterial(matHead);
|
||||
head.setLocalTranslation(0f, 0f, shaftLen);
|
||||
Quaternion headRot = new Quaternion();
|
||||
headRot.fromAngleAxis(FastMath.HALF_PI, Vector3f.UNIT_X);
|
||||
head.setLocalRotation(headRot);
|
||||
|
||||
// Klingenflosse: dünnes flaches Rechteck senkrecht zum Schaft entlang +Y,
|
||||
// damit Rotation um Z (Roll) sichtbar ist. Breite 6cm, Dicke 2mm, entlang Schaft.
|
||||
Material matFin = new Material(app.getAssetManager(), "Common/MatDefs/Misc/Unshaded.j3md");
|
||||
matFin.setColor("Color", new ColorRGBA(1f, 0.8f, 0.1f, 1f));
|
||||
Geometry fin = new Geometry("gFin", new Box(0.001f, 0.03f, shaftLen * 0.5f));
|
||||
fin.setMaterial(matFin);
|
||||
fin.setLocalTranslation(0f, 0.03f, shaftLen * 0.5f);
|
||||
|
||||
group.attachChild(marker);
|
||||
group.attachChild(shaft);
|
||||
group.attachChild(fin);
|
||||
group.attachChild(head);
|
||||
gripGizmoNode.attachChild(group);
|
||||
gripGizmoNode.setCullHint(Spatial.CullHint.Inherit);
|
||||
}
|
||||
|
||||
/** Blendet den Grip-Gizmo ein oder aus. */
|
||||
public void setGripGizmoVisible(boolean visible) {
|
||||
if (gripGizmoNode != null)
|
||||
gripGizmoNode.setCullHint(visible ? Spatial.CullHint.Inherit : Spatial.CullHint.Always);
|
||||
}
|
||||
|
||||
/** Setzt den Pfeil sichtbar/unsichtbar. */
|
||||
public void setInteractableArrowVisible(boolean visible) {
|
||||
if (interactableArrowNode != null)
|
||||
|
||||
@@ -84,12 +84,18 @@ public class ContentController {
|
||||
|
||||
// ──────────────────────────────────────────────────────────────────────────
|
||||
|
||||
private Runnable onEnterContentMode;
|
||||
private GripEditorController gripEditorController;
|
||||
|
||||
public ContentController(ToolContext ctx) {
|
||||
this.ctx = ctx;
|
||||
this.gripEditorController = new GripEditorController(ctx);
|
||||
}
|
||||
|
||||
// ── Öffentliche API ────────────────────────────────────────────────────────
|
||||
|
||||
public void setOnEnterContentMode(Runnable r) { this.onEnterContentMode = r; }
|
||||
|
||||
public void setDualWindowMenuItem(CheckMenuItem item) {
|
||||
this.dualWindowMenuItem = item;
|
||||
}
|
||||
@@ -206,6 +212,7 @@ public class ContentController {
|
||||
|
||||
/** 1-Fenster-Content-Modus aktivieren. */
|
||||
public void switchToContentEditor() {
|
||||
if (onEnterContentMode != null) onEnterContentMode.run();
|
||||
ctx.setCurrentTool().accept("content");
|
||||
if (ctx.jmeApp() != null) ctx.jmeApp().setRenderThrottled(true);
|
||||
ctx.setMainToolbar().accept(buildSingleWindowContentToolBar());
|
||||
@@ -328,6 +335,7 @@ public class ContentController {
|
||||
if (contentItemBtn != null) contentItemBtn.setSelected(true);
|
||||
de.blight.editor.ui.ItemEditorView view =
|
||||
new de.blight.editor.ui.ItemEditorView(ctx.assetRoot().resolve("items"));
|
||||
wireItemView(view);
|
||||
setContentCenter(view);
|
||||
setContentRight(null);
|
||||
if (selectItemId != null) view.selectItem(selectItemId);
|
||||
@@ -338,6 +346,7 @@ public class ContentController {
|
||||
ctx.setMainToolbar().accept(tb);
|
||||
de.blight.editor.ui.ItemEditorView view =
|
||||
new de.blight.editor.ui.ItemEditorView(ctx.assetRoot().resolve("items"));
|
||||
wireItemView(view);
|
||||
ctx.setCenterView().accept(view);
|
||||
ctx.setRightPanel().accept(null);
|
||||
if (selectItemId != null) view.selectItem(selectItemId);
|
||||
@@ -949,9 +958,26 @@ public class ContentController {
|
||||
if (contentFractionNode != null) contentFractionNode.setExpanded(true);
|
||||
}
|
||||
|
||||
private void wireItemView(de.blight.editor.ui.ItemEditorView view) {
|
||||
view.setOnIdChanged(this::refreshContentItemTree);
|
||||
view.setGripEditorLauncher((modelPath, currentGrip, onSave) -> {
|
||||
String itemId = view.getCurrentItemId();
|
||||
gripEditorController.setOnReturn(() -> {
|
||||
if (dualWindowMode) {
|
||||
buildItemViewInContent();
|
||||
if (activeItemView != null) activeItemView.selectItem(itemId);
|
||||
} else {
|
||||
switchToItemEditor(itemId);
|
||||
}
|
||||
});
|
||||
gripEditorController.open(modelPath, currentGrip, onSave);
|
||||
});
|
||||
}
|
||||
|
||||
private void buildItemViewInContent() {
|
||||
collapseAllContentSections();
|
||||
activeItemView = new de.blight.editor.ui.ItemEditorView(ctx.assetRoot().resolve("items"));
|
||||
wireItemView(activeItemView);
|
||||
setContentCenter(activeItemView);
|
||||
setContentRight(null);
|
||||
setContentBottom(buildItemLegend());
|
||||
|
||||
@@ -0,0 +1,220 @@
|
||||
package de.blight.editor.toolset.content;
|
||||
|
||||
import de.blight.common.model.ItemGrip;
|
||||
import de.blight.editor.SharedInput;
|
||||
import de.blight.editor.ToolContext;
|
||||
import de.blight.editor.state.ModelEditorState;
|
||||
import de.blight.editor.ui.ItemEditorView;
|
||||
import javafx.geometry.Insets;
|
||||
import javafx.geometry.Orientation;
|
||||
import javafx.geometry.Pos;
|
||||
import javafx.scene.control.*;
|
||||
import javafx.scene.layout.*;
|
||||
|
||||
import java.util.function.Consumer;
|
||||
|
||||
/**
|
||||
* Öffnet den Modell-Editor-Viewport mit einem 15cm-Griff-Gizmo (orangefarbener Pfeil).
|
||||
* Position und Rotation des Griffes werden über 6 Spinner eingestellt.
|
||||
*/
|
||||
public class GripEditorController implements ItemEditorView.GripEditorLauncher {
|
||||
|
||||
private final ToolContext ctx;
|
||||
|
||||
private Spinner<Double> posXSpin, posYSpin, posZSpin;
|
||||
private Spinner<Double> rotXSpin, rotYSpin, rotZSpin;
|
||||
|
||||
private Consumer<ItemGrip> pendingOnSave;
|
||||
private Runnable onReturn;
|
||||
private boolean suppressSpinnerEvents = false;
|
||||
|
||||
public void setOnReturn(Runnable r) { this.onReturn = r; }
|
||||
|
||||
public GripEditorController(ToolContext ctx) {
|
||||
this.ctx = ctx;
|
||||
}
|
||||
|
||||
// ── GripEditorLauncher ────────────────────────────────────────────────────
|
||||
|
||||
@Override
|
||||
public void open(String modelPath, ItemGrip currentGrip, Consumer<ItemGrip> onSave) {
|
||||
pendingOnSave = onSave;
|
||||
|
||||
SharedInput input = ctx.input();
|
||||
input.gripEditorActive = true;
|
||||
|
||||
// Grip-Werte aus Item übernehmen (oder Defaults)
|
||||
ItemGrip g = currentGrip != null ? currentGrip : new ItemGrip();
|
||||
input.gripEditorPosX = g.getPosX();
|
||||
input.gripEditorPosY = g.getPosY();
|
||||
input.gripEditorPosZ = g.getPosZ();
|
||||
input.gripEditorRotX = g.getRotX();
|
||||
input.gripEditorRotY = g.getRotY();
|
||||
input.gripEditorRotZ = g.getRotZ();
|
||||
input.gripEditorGizmoDirty = true;
|
||||
|
||||
// Waffenmodell im Modell-Editor laden
|
||||
if (modelPath != null && !modelPath.isBlank()) {
|
||||
input.modelEditorOpenPath = modelPath;
|
||||
}
|
||||
input.activeLayer = SharedInput.LAYER_MODEL_EDITOR;
|
||||
|
||||
// Hauptfenster: JME-Viewport + Grip-Panel
|
||||
ctx.setCenterView().accept(ctx.viewportSupplier().get());
|
||||
ctx.setCurrentTool().accept("gripEditor");
|
||||
ctx.setMainToolbar().accept(buildToolbar());
|
||||
ctx.setRightPanel().accept(buildPanel(g));
|
||||
ctx.setLeftPanel().accept(null);
|
||||
ctx.setBottomPanel().accept(null);
|
||||
}
|
||||
|
||||
// ── Toolbar ───────────────────────────────────────────────────────────────
|
||||
|
||||
private ToolBar buildToolbar() {
|
||||
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;");
|
||||
ToolBar tb = new ToolBar();
|
||||
tb.getItems().addAll(cancelBtn, new Separator(Orientation.VERTICAL), title);
|
||||
return tb;
|
||||
}
|
||||
|
||||
// ── Panel ─────────────────────────────────────────────────────────────────
|
||||
|
||||
private VBox buildPanel(ItemGrip g) {
|
||||
posXSpin = doubleSpin(g.getPosX(), -2.0, 2.0, 0.01, "m");
|
||||
posYSpin = doubleSpin(g.getPosY(), -2.0, 2.0, 0.01, "m");
|
||||
posZSpin = doubleSpin(g.getPosZ(), -2.0, 2.0, 0.01, "m");
|
||||
rotXSpin = doubleSpin(g.getRotX(), -180.0, 180.0, 1.0, "°");
|
||||
rotYSpin = doubleSpin(g.getRotY(), -180.0, 180.0, 1.0, "°");
|
||||
rotZSpin = doubleSpin(g.getRotZ(), -180.0, 180.0, 1.0, "°");
|
||||
|
||||
posXSpin.valueProperty().addListener((obs, o, n) -> onSpinnerChanged());
|
||||
posYSpin.valueProperty().addListener((obs, o, n) -> onSpinnerChanged());
|
||||
posZSpin.valueProperty().addListener((obs, o, n) -> onSpinnerChanged());
|
||||
rotXSpin.valueProperty().addListener((obs, o, n) -> onSpinnerChanged());
|
||||
rotYSpin.valueProperty().addListener((obs, o, n) -> onSpinnerChanged());
|
||||
rotZSpin.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 -> onSave());
|
||||
|
||||
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());
|
||||
|
||||
VBox panel = new VBox(8);
|
||||
panel.setPadding(new Insets(12));
|
||||
panel.setStyle("-fx-background-color: #252535;");
|
||||
panel.setPrefWidth(220);
|
||||
|
||||
panel.getChildren().addAll(
|
||||
styledTitle("Position (m)"),
|
||||
row("X:", posXSpin),
|
||||
row("Y:", posYSpin),
|
||||
row("Z:", posZSpin),
|
||||
new Separator(),
|
||||
styledTitle("Rotation (°)"),
|
||||
row("X:", rotXSpin),
|
||||
row("Y:", rotYSpin),
|
||||
row("Z:", rotZSpin),
|
||||
new Separator(),
|
||||
styledHint("Pfeil zeigt Klingenrichtung.\n15cm Länge = Maßstab."),
|
||||
new Separator(),
|
||||
saveBtn,
|
||||
cancelBtn
|
||||
);
|
||||
return panel;
|
||||
}
|
||||
|
||||
// ── Spinner-Events ────────────────────────────────────────────────────────
|
||||
|
||||
private void onSpinnerChanged() {
|
||||
if (suppressSpinnerEvents) return;
|
||||
SharedInput input = ctx.input();
|
||||
input.gripEditorPosX = posXSpin.getValue().floatValue();
|
||||
input.gripEditorPosY = posYSpin.getValue().floatValue();
|
||||
input.gripEditorPosZ = posZSpin.getValue().floatValue();
|
||||
input.gripEditorRotX = rotXSpin.getValue().floatValue();
|
||||
input.gripEditorRotY = rotYSpin.getValue().floatValue();
|
||||
input.gripEditorRotZ = rotZSpin.getValue().floatValue();
|
||||
input.gripEditorGizmoDirty = true;
|
||||
}
|
||||
|
||||
// ── Save / Cancel ─────────────────────────────────────────────────────────
|
||||
|
||||
private void onSave() {
|
||||
ItemGrip grip = new ItemGrip();
|
||||
grip.setPosX(posXSpin.getValue().floatValue());
|
||||
grip.setPosY(posYSpin.getValue().floatValue());
|
||||
grip.setPosZ(posZSpin.getValue().floatValue());
|
||||
grip.setRotX(rotXSpin.getValue().floatValue());
|
||||
grip.setRotY(rotYSpin.getValue().floatValue());
|
||||
grip.setRotZ(rotZSpin.getValue().floatValue());
|
||||
|
||||
ctx.input().gripEditorActive = false;
|
||||
|
||||
Consumer<ItemGrip> save = pendingOnSave;
|
||||
Runnable ret = onReturn;
|
||||
pendingOnSave = null;
|
||||
onReturn = null;
|
||||
|
||||
if (save != null) save.accept(grip);
|
||||
if (ret != null) ret.run(); else ctx.switchToWorld().run();
|
||||
}
|
||||
|
||||
private void onCancel() {
|
||||
ctx.input().gripEditorActive = false;
|
||||
|
||||
Runnable ret = onReturn;
|
||||
pendingOnSave = null;
|
||||
onReturn = 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("%.2f", 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;");
|
||||
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(20);
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -4,6 +4,7 @@ import de.blight.common.model.CharacterStat;
|
||||
import de.blight.common.model.ConsumableEffect;
|
||||
import de.blight.common.model.Item;
|
||||
import de.blight.common.model.ItemCategory;
|
||||
import de.blight.common.model.ItemGrip;
|
||||
import de.blight.common.model.ItemCategoryManager;
|
||||
import de.blight.common.model.ItemIO;
|
||||
import de.blight.common.model.ItemSubCategory;
|
||||
@@ -42,14 +43,23 @@ public class ItemEditorView extends BorderPane {
|
||||
private TextField idField;
|
||||
private ComboBox<ItemCategory> catCombo;
|
||||
private ComboBox<ItemSubCategory> subCatCombo;
|
||||
private TextField nameField;
|
||||
private TextField descField;
|
||||
private Spinner<Integer> goldSpinner;
|
||||
private TextField modelRefField;
|
||||
private VBox formContainer;
|
||||
private CheckBox consumableCheck;
|
||||
private VBox consumablesSection;
|
||||
private VBox effectsRows;
|
||||
private Label nameLabel;
|
||||
private Label descLabel;
|
||||
private Spinner<Integer> goldSpinner;
|
||||
private Spinner<Integer> damageSpinner;
|
||||
private VBox weaponSection;
|
||||
private TextField modelRefField;
|
||||
private VBox formContainer;
|
||||
private CheckBox consumableCheck;
|
||||
private VBox consumablesSection;
|
||||
private VBox effectsRows;
|
||||
|
||||
public interface GripEditorLauncher {
|
||||
void open(String modelPath, ItemGrip currentGrip, java.util.function.Consumer<ItemGrip> onSave);
|
||||
}
|
||||
|
||||
private Runnable onIdChanged;
|
||||
private GripEditorLauncher gripEditorLauncher;
|
||||
|
||||
private final PauseTransition savePause = new PauseTransition(Duration.millis(600));
|
||||
|
||||
@@ -59,7 +69,7 @@ public class ItemEditorView extends BorderPane {
|
||||
|
||||
public ItemEditorView(Path itemDir) {
|
||||
this.itemDir = itemDir;
|
||||
this.assetRoot = itemDir.getParent(); // items/ ist direkt unter assetRoot
|
||||
this.assetRoot = itemDir.getParent();
|
||||
setStyle("-fx-background-color: #1e1e2e;");
|
||||
reload();
|
||||
|
||||
@@ -77,6 +87,10 @@ public class ItemEditorView extends BorderPane {
|
||||
|
||||
// ── Öffentliche API ───────────────────────────────────────────────────────
|
||||
|
||||
public void setOnIdChanged(Runnable r) { this.onIdChanged = r; }
|
||||
public void setGripEditorLauncher(GripEditorLauncher l) { this.gripEditorLauncher = l; }
|
||||
public String getCurrentItemId() { return current != null ? current.getItemId() : null; }
|
||||
|
||||
public void selectItem(String itemId) {
|
||||
if (itemId == null) return;
|
||||
items.stream()
|
||||
@@ -129,21 +143,6 @@ public class ItemEditorView extends BorderPane {
|
||||
};
|
||||
}
|
||||
|
||||
// ── Legend ────────────────────────────────────────────────────────────────
|
||||
|
||||
private HBox buildLegend() {
|
||||
HBox box = new HBox(8);
|
||||
box.setAlignment(Pos.CENTER_LEFT);
|
||||
for (ItemCategory cat : ItemCategory.values()) {
|
||||
Label dot = new Label("■");
|
||||
dot.setStyle("-fx-text-fill: " + categoryColor(cat) + "; -fx-font-size: 10;");
|
||||
Label lbl = new Label(cat.name());
|
||||
lbl.setStyle("-fx-text-fill: #888; -fx-font-size: 10;");
|
||||
box.getChildren().addAll(dot, lbl);
|
||||
}
|
||||
return box;
|
||||
}
|
||||
|
||||
// ── Form construction ─────────────────────────────────────────────────────
|
||||
|
||||
private VBox buildForm() {
|
||||
@@ -196,7 +195,6 @@ public class ItemEditorView extends BorderPane {
|
||||
}
|
||||
});
|
||||
|
||||
// Wenn Kategorie wechselt: SubKategorie-Liste aktualisieren
|
||||
catCombo.valueProperty().addListener((obs, oldCat, newCat) -> {
|
||||
ItemSubCategory prevSub = subCatCombo.getValue();
|
||||
subCatCombo.getItems().setAll(
|
||||
@@ -207,21 +205,47 @@ public class ItemEditorView extends BorderPane {
|
||||
} else {
|
||||
subCatCombo.setValue(null);
|
||||
}
|
||||
updateWeaponSectionVisibility();
|
||||
scheduleSave();
|
||||
});
|
||||
subCatCombo.valueProperty().addListener((obs, o, n) -> {
|
||||
updateWeaponSectionVisibility();
|
||||
scheduleSave();
|
||||
});
|
||||
subCatCombo.valueProperty().addListener((obs, o, n) -> scheduleSave());
|
||||
|
||||
nameField = new TextField();
|
||||
nameField.setPromptText("TextReference-Schlüssel");
|
||||
nameField.textProperty().addListener((obs, o, n) -> scheduleSave());
|
||||
descField = new TextField();
|
||||
descField.setPromptText("TextReference-Schlüssel");
|
||||
descField.textProperty().addListener((obs, o, n) -> scheduleSave());
|
||||
// Name und Beschreibung: read-only (auto-generierte TextReference-Schlüssel)
|
||||
nameLabel = new Label();
|
||||
nameLabel.setStyle("-fx-text-fill: #777; -fx-font-style: italic;");
|
||||
nameLabel.setMaxWidth(Double.MAX_VALUE);
|
||||
descLabel = new Label();
|
||||
descLabel.setStyle("-fx-text-fill: #777; -fx-font-style: italic;");
|
||||
descLabel.setMaxWidth(Double.MAX_VALUE);
|
||||
|
||||
goldSpinner = new Spinner<>(0, 999999, 0);
|
||||
goldSpinner.setEditable(true);
|
||||
goldSpinner.setMaxWidth(Double.MAX_VALUE);
|
||||
goldSpinner.valueProperty().addListener((obs, o, n) -> scheduleSave());
|
||||
|
||||
// ── Waffen-Sektion ────────────────────────────────────────────────────
|
||||
damageSpinner = new Spinner<>(0, 99999, 0);
|
||||
damageSpinner.setEditable(true);
|
||||
damageSpinner.setMaxWidth(Double.MAX_VALUE);
|
||||
damageSpinner.valueProperty().addListener((obs, o, n) -> scheduleSave());
|
||||
|
||||
Button gripBtn = new Button("Griff definieren...");
|
||||
gripBtn.setMaxWidth(Double.MAX_VALUE);
|
||||
gripBtn.setStyle("-fx-background-color: #3a3a5a; -fx-text-fill: #ddddff; -fx-cursor: hand;");
|
||||
gripBtn.setOnAction(e -> openGripEditor());
|
||||
|
||||
weaponSection = new VBox(6,
|
||||
new Separator(),
|
||||
sectionTitle("Kampfwerte"),
|
||||
row("Schaden:", damageSpinner),
|
||||
gripBtn
|
||||
);
|
||||
weaponSection.setVisible(false);
|
||||
weaponSection.setManaged(false);
|
||||
|
||||
modelRefField = new TextField();
|
||||
modelRefField.setPromptText("Modell-Pfad (z.B. Models/Items/sword.j3o)");
|
||||
modelRefField.setEditable(false);
|
||||
@@ -231,10 +255,6 @@ public class ItemEditorView extends BorderPane {
|
||||
pickModelBtn.setTooltip(new Tooltip("Modell auswählen"));
|
||||
pickModelBtn.setOnAction(e -> pickModel());
|
||||
|
||||
HBox modelRow = new HBox(4, modelRefField, pickModelBtn);
|
||||
HBox.setHgrow(modelRefField, Priority.ALWAYS);
|
||||
modelRow.setAlignment(Pos.CENTER_LEFT);
|
||||
|
||||
Button clearModelBtn = new Button("✕");
|
||||
clearModelBtn.setStyle("-fx-background-color: transparent; -fx-text-fill: #888; -fx-cursor: hand;");
|
||||
clearModelBtn.setTooltip(new Tooltip("Modell-Referenz entfernen"));
|
||||
@@ -273,12 +293,13 @@ public class ItemEditorView extends BorderPane {
|
||||
row("SubKategorie:", subCatCombo),
|
||||
new Separator(),
|
||||
sectionTitle("Texte"),
|
||||
row("Name:", nameField),
|
||||
row("Beschreibung:", descField),
|
||||
row("Name:", nameLabel),
|
||||
row("Beschreibung:", descLabel),
|
||||
new Separator(),
|
||||
sectionTitle("Werte"),
|
||||
row("Wert (Gold):", goldSpinner),
|
||||
row("Modell:", modelFullRow),
|
||||
weaponSection,
|
||||
new Separator(),
|
||||
consumableCheck,
|
||||
consumablesSection
|
||||
@@ -286,6 +307,24 @@ public class ItemEditorView extends BorderPane {
|
||||
return form;
|
||||
}
|
||||
|
||||
private void openGripEditor() {
|
||||
if (current == null || gripEditorLauncher == null) return;
|
||||
String modelPath = modelRefField.getText().trim();
|
||||
gripEditorLauncher.open(modelPath.isBlank() ? null : modelPath, current.getGrip(), grip -> {
|
||||
current.setGrip(grip);
|
||||
persistCurrent();
|
||||
});
|
||||
}
|
||||
|
||||
private void updateWeaponSectionVisibility() {
|
||||
ItemCategory cat = catCombo.getValue();
|
||||
ItemSubCategory sub = subCatCombo.getValue();
|
||||
boolean isWeapon = cat == ItemCategory.WEAPON
|
||||
&& sub != ItemSubCategory.SHIELD;
|
||||
weaponSection.setVisible(isWeapon);
|
||||
weaponSection.setManaged(isWeapon);
|
||||
}
|
||||
|
||||
// ── Modell-Auswahl ────────────────────────────────────────────────────────
|
||||
|
||||
private void pickModel() {
|
||||
@@ -296,8 +335,10 @@ public class ItemEditorView extends BorderPane {
|
||||
private void onIdCommitted() {
|
||||
String id = idField.getText().trim();
|
||||
if (id.isBlank()) return;
|
||||
if (nameField.getText().isBlank()) nameField.setText(id + ".name");
|
||||
if (descField.getText().isBlank()) descField.setText(id + ".description");
|
||||
nameLabel.setText(id + ".name");
|
||||
descLabel.setText(id + ".description");
|
||||
scheduleSave();
|
||||
if (onIdChanged != null) onIdChanged.run();
|
||||
}
|
||||
|
||||
// ── Auto-save ─────────────────────────────────────────────────────────────
|
||||
@@ -338,11 +379,13 @@ public class ItemEditorView extends BorderPane {
|
||||
|
||||
private void loadFormFromItem(Item item) {
|
||||
idField.setText(safe(item.getItemId()));
|
||||
catCombo.setValue(item.getCategory()); // löst Listener aus → subCatCombo befüllt
|
||||
catCombo.setValue(item.getCategory());
|
||||
subCatCombo.setValue(item.getSubCategory());
|
||||
nameField.setText(item.getName() != null ? item.getName().id() : "");
|
||||
descField.setText(item.getDescription() != null ? item.getDescription().id() : "");
|
||||
String id = safe(item.getItemId());
|
||||
nameLabel.setText(item.getName() != null ? item.getName().id() : (id.isBlank() ? "" : id + ".name"));
|
||||
descLabel.setText(item.getDescription() != null ? item.getDescription().id() : (id.isBlank() ? "" : id + ".description"));
|
||||
goldSpinner.getValueFactory().setValue(item.getWorthGold());
|
||||
damageSpinner.getValueFactory().setValue(item.getDamage());
|
||||
ObjectReference ref = item.getModelRef();
|
||||
modelRefField.setText(ref != null && ref.getPath() != null ? ref.getPath() : "");
|
||||
consumableCheck.setSelected(item.isConsumable());
|
||||
@@ -352,15 +395,19 @@ public class ItemEditorView extends BorderPane {
|
||||
addEffectRow(effect.getStat(), effect.getValue());
|
||||
}
|
||||
}
|
||||
updateWeaponSectionVisibility();
|
||||
}
|
||||
|
||||
private void saveFormToItem(Item item) {
|
||||
item.setItemId(idField.getText().trim());
|
||||
item.setCategory(catCombo.getValue());
|
||||
item.setSubCategory(subCatCombo.getValue());
|
||||
item.setName(ref(nameField));
|
||||
item.setDescription(ref(descField));
|
||||
String nameKey = nameLabel.getText().trim();
|
||||
String descKey = descLabel.getText().trim();
|
||||
item.setName(nameKey.isBlank() ? null : new TextReference(nameKey));
|
||||
item.setDescription(descKey.isBlank() ? null : new TextReference(descKey));
|
||||
item.setWorthGold(goldSpinner.getValue());
|
||||
item.setDamage(damageSpinner.getValue());
|
||||
String mr = modelRefField.getText().trim();
|
||||
item.setModelRef(mr.isBlank() ? null : new ObjectReference(mr));
|
||||
item.setConsumable(consumableCheck.isSelected());
|
||||
@@ -378,14 +425,16 @@ public class ItemEditorView extends BorderPane {
|
||||
|
||||
private void clearForm() {
|
||||
idField.clear();
|
||||
catCombo.setValue(null); // setzt subCatCombo via Listener zurück
|
||||
catCombo.setValue(null);
|
||||
subCatCombo.setValue(null);
|
||||
nameField.clear();
|
||||
descField.clear();
|
||||
nameLabel.setText("");
|
||||
descLabel.setText("");
|
||||
goldSpinner.getValueFactory().setValue(0);
|
||||
damageSpinner.getValueFactory().setValue(0);
|
||||
modelRefField.clear();
|
||||
consumableCheck.setSelected(false);
|
||||
effectsRows.getChildren().clear();
|
||||
updateWeaponSectionVisibility();
|
||||
}
|
||||
|
||||
// ── List operations ───────────────────────────────────────────────────────
|
||||
@@ -453,9 +502,4 @@ public class ItemEditorView extends BorderPane {
|
||||
}
|
||||
|
||||
private static String safe(String s) { return s != null ? s : ""; }
|
||||
|
||||
private static TextReference ref(TextField f) {
|
||||
String s = f.getText().trim();
|
||||
return s.isBlank() ? null : new TextReference(s);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user