Beginn des Kampf Systems

This commit is contained in:
2026-09-15 17:19:21 +02:00
parent 1d6f43f9ce
commit bdaeed056c
34 changed files with 1448 additions and 93 deletions

View File

@@ -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);

View File

@@ -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<>();

View File

@@ -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)

View File

@@ -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());

View File

@@ -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;
}
}

View File

@@ -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);
}
}