Abschluss des großen Editor Refactorings
This commit is contained in:
Binary file not shown.
|
After Width: | Height: | Size: 4.6 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 5.9 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 10 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 6.8 KiB |
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
File diff suppressed because it is too large
Load Diff
@@ -10,22 +10,23 @@ import java.util.function.Supplier;
|
||||
|
||||
/**
|
||||
* Shared-Dependencies-Container: wird einmalig in EditorApp erzeugt und
|
||||
* an alle {@link ToolController}-Implementierungen weitergegeben.
|
||||
* an alle Controller-Implementierungen weitergegeben.
|
||||
*
|
||||
* Ersetzt das implizite "EditorApp kennt alles" — Controller greifen nur
|
||||
* über diesen Record auf Infrastruktur zu, nicht auf EditorApp direkt.
|
||||
*
|
||||
* @param input Gemeinsame JavaFX ↔ JME3 Datenbasis
|
||||
* @param jmeApp JME3-Anwendung (für StateManager-Zugriff)
|
||||
* @param stageSupplier Liefert die primäre Stage (für Dialoge)
|
||||
* @param setStatus Setzt den Text im Statusbalken
|
||||
* @param setCenterView Setzt den Center-Node des Haupt-BorderPane
|
||||
* @param setRightPanel Setzt den Right-Node des Haupt-BorderPane
|
||||
* @param setMainToolbar Ersetzt die aktive Werkzeug-Toolbar in der TopBar
|
||||
* @param viewportSupplier Liefert den JME3-Viewport-StackPane
|
||||
* @param assetRoot Wurzelpfad der blight-assets (für Dateidialoge / IO)
|
||||
* @param switchToWorld Wechselt zurück zum Welteneditor (für "← Welteneditor"-Buttons)
|
||||
* @param registry ToolRegistry (damit Controller sich selbst aktivieren können)
|
||||
* @param input Gemeinsame JavaFX ↔ JME3 Datenbasis
|
||||
* @param jmeApp JME3-Anwendung (für StateManager-Zugriff)
|
||||
* @param stageSupplier Liefert die primäre Stage (für Dialoge)
|
||||
* @param setStatus Setzt den Text im Statusbalken
|
||||
* @param setCenterView Setzt den Center-Node des Haupt-BorderPane
|
||||
* @param setRightPanel Setzt den Right-Node des Haupt-BorderPane
|
||||
* @param setMainToolbar Ersetzt die aktive Werkzeug-Toolbar in der TopBar
|
||||
* @param viewportSupplier Liefert den JME3-Viewport-StackPane
|
||||
* @param assetRoot Wurzelpfad der blight-assets (für Dateidialoge / IO)
|
||||
* @param switchToWorld Wechselt zurück zum Welteneditor
|
||||
* @param registry ToolRegistry (damit Controller sich selbst aktivieren können)
|
||||
* @param setLeftPanel Setzt den Left-Node des Haupt-BorderPane
|
||||
* @param setBottomPanel Setzt den Bottom-Node des Haupt-BorderPane
|
||||
* @param centerStackSupplier Liefert den persistenten 3D-Viewport-Wrapper
|
||||
* @param setCurrentTool Setzt das aktive Tool-Label in EditorApp (für Keyboard-Routing)
|
||||
*/
|
||||
public record ToolContext(
|
||||
SharedInput input,
|
||||
@@ -38,5 +39,9 @@ public record ToolContext(
|
||||
Supplier<StackPane> viewportSupplier,
|
||||
Path assetRoot,
|
||||
Runnable switchToWorld,
|
||||
ToolRegistry registry
|
||||
ToolRegistry registry,
|
||||
Consumer<Node> setLeftPanel,
|
||||
Consumer<Node> setBottomPanel,
|
||||
Supplier<StackPane> centerStackSupplier,
|
||||
Consumer<String> setCurrentTool
|
||||
) {}
|
||||
|
||||
@@ -40,8 +40,8 @@ public class WaterBodyState extends BaseAppState {
|
||||
|
||||
private static final float LINE_OFFSET = 0.1f;
|
||||
private static final float POLE_HEIGHT = 5.0f;
|
||||
private static final float CAP_SIZE = 1.2f;
|
||||
private static final float HANDLE_PICK_PX = 24f;
|
||||
private static final float CAP_SIZE = 2.0f;
|
||||
private static final float HANDLE_PICK_PX = 32f;
|
||||
private static final float EDGE_SPLIT_THRESHOLD = 1.5f;
|
||||
|
||||
private static final ColorRGBA COLOR_WATER = new ColorRGBA(0.05f, 0.25f, 0.70f, 0.50f);
|
||||
@@ -53,8 +53,9 @@ public class WaterBodyState extends BaseAppState {
|
||||
private static final ColorRGBA COLOR_ARROW = new ColorRGBA(1f, 0.85f, 0f, 1f);
|
||||
private static final ColorRGBA COLOR_POLE_CORNER = new ColorRGBA(0f, 0.9f, 1.0f, 1f);
|
||||
private static final ColorRGBA COLOR_POLE_EDGE = new ColorRGBA(0f, 0.55f, 0.75f, 1f);
|
||||
private static final ColorRGBA COLOR_POLE_HOVER = new ColorRGBA(1f, 0.9f, 0f, 1f);
|
||||
private static final ColorRGBA COLOR_EDGE_SPLIT = new ColorRGBA(0.1f, 1.0f, 0.4f, 1f);
|
||||
private static final ColorRGBA COLOR_POLE_HOVER = new ColorRGBA(1f, 0.9f, 0f, 1f);
|
||||
private static final ColorRGBA COLOR_POLE_SELECTED = new ColorRGBA(1f, 0.35f, 0.05f, 1f);
|
||||
private static final ColorRGBA COLOR_EDGE_SPLIT = new ColorRGBA(0.1f, 1.0f, 0.4f, 1f);
|
||||
|
||||
private static final ColorRGBA PILLAR_SUB = new ColorRGBA(0.2f, 0.85f, 1.0f, 1f);
|
||||
private static final ColorRGBA PILLAR_DRY = new ColorRGBA(1.0f, 0.50f, 0.1f, 1f);
|
||||
@@ -90,8 +91,9 @@ public class WaterBodyState extends BaseAppState {
|
||||
private final List<Geometry> handleGeos = new ArrayList<>();
|
||||
private final List<Material> handleMats = new ArrayList<>();
|
||||
private final List<Float> handleCapY = new ArrayList<>();
|
||||
private int hoveredHandle = -1;
|
||||
private int dragHandle = -1;
|
||||
private int hoveredHandle = -1;
|
||||
private int dragHandle = -1;
|
||||
private int selectedHandle = -1;
|
||||
private boolean isDragging = false;
|
||||
private float[] liveXs = new float[0];
|
||||
private float[] liveYs = new float[0];
|
||||
@@ -187,7 +189,11 @@ public class WaterBodyState extends BaseAppState {
|
||||
|
||||
if (input.deleteWaterRequested) {
|
||||
input.deleteWaterRequested = false;
|
||||
if (selectedIdx >= 0) removeBody(selectedIdx);
|
||||
if (selectedHandle >= 0 && selectedIdx >= 0 && selectedHandle < liveXs.length) {
|
||||
deleteCornerPoint(selectedIdx, selectedHandle);
|
||||
} else if (selectedIdx >= 0) {
|
||||
removeBody(selectedIdx);
|
||||
}
|
||||
}
|
||||
|
||||
SharedInput.WaterClick click;
|
||||
@@ -235,6 +241,7 @@ public class WaterBodyState extends BaseAppState {
|
||||
PlacedWater b = bodies.get(selectedIdx);
|
||||
int h = findHandleAt(jmeX, jmeY, b.pointsX(), b.pointsZ());
|
||||
if (h >= 0) {
|
||||
setSelectedHandle(h);
|
||||
startDrag(h, b.pointsX(), b.pointsY(), b.pointsZ());
|
||||
return;
|
||||
}
|
||||
@@ -569,10 +576,50 @@ public class WaterBodyState extends BaseAppState {
|
||||
handleMats.clear();
|
||||
handleCapY.clear();
|
||||
hideSplitIndicator();
|
||||
hoveredHandle = -1;
|
||||
hoveredHandle = -1;
|
||||
dragHandle = -1;
|
||||
selectedHandle = -1;
|
||||
splitHoverEdge = -1;
|
||||
}
|
||||
|
||||
private void setSelectedHandle(int h) {
|
||||
if (selectedHandle >= 0 && selectedHandle < handleMats.size() && selectedHandle != hoveredHandle) {
|
||||
int n = liveXs.length;
|
||||
handleMats.get(selectedHandle).setColor("Color", selectedHandle < n ? COLOR_POLE_CORNER : COLOR_POLE_EDGE);
|
||||
}
|
||||
selectedHandle = h;
|
||||
if (h >= 0 && h < handleMats.size() && h != hoveredHandle) {
|
||||
handleMats.get(h).setColor("Color", COLOR_POLE_SELECTED);
|
||||
}
|
||||
}
|
||||
|
||||
private void deleteCornerPoint(int bodyIdx, int cornerIdx) {
|
||||
int n = liveXs.length;
|
||||
if (cornerIdx >= n) return; // Kantenmittelpunkte nicht löschbar
|
||||
if (n <= 3) {
|
||||
removeBody(bodyIdx);
|
||||
return;
|
||||
}
|
||||
float[] newXs = new float[n - 1];
|
||||
float[] newYs = new float[n - 1];
|
||||
float[] newZs = new float[n - 1];
|
||||
for (int i = 0, j = 0; i < n; i++) {
|
||||
if (i == cornerIdx) continue;
|
||||
newXs[j] = liveXs[i]; newYs[j] = liveYs[i]; newZs[j] = liveZs[i];
|
||||
j++;
|
||||
}
|
||||
PlacedWater updated = bodies.get(bodyIdx).withPoints(newXs, newYs, newZs);
|
||||
bodies.set(bodyIdx, updated);
|
||||
rebuildQuadVisual(bodyIdx, newXs, newYs, newZs);
|
||||
rebuildArrow(bodyIdx, updated);
|
||||
liveXs = newXs.clone();
|
||||
liveYs = newYs.clone();
|
||||
liveZs = newZs.clone();
|
||||
removeHandles();
|
||||
buildHandles(newXs, newYs, newZs);
|
||||
publishSelection(bodyIdx);
|
||||
}
|
||||
|
||||
private void updateHandleHover() {
|
||||
if (selectedIdx < 0) return;
|
||||
PlacedWater b = bodies.get(selectedIdx);
|
||||
@@ -585,7 +632,8 @@ public class WaterBodyState extends BaseAppState {
|
||||
|
||||
if (h != hoveredHandle) {
|
||||
if (hoveredHandle >= 0 && hoveredHandle < handleMats.size()) {
|
||||
ColorRGBA c = hoveredHandle < n ? COLOR_POLE_CORNER : COLOR_POLE_EDGE;
|
||||
ColorRGBA c = (hoveredHandle == selectedHandle) ? COLOR_POLE_SELECTED
|
||||
: (hoveredHandle < n ? COLOR_POLE_CORNER : COLOR_POLE_EDGE);
|
||||
handleMats.get(hoveredHandle).setColor("Color", c);
|
||||
}
|
||||
if (h >= 0 && h < handleMats.size()) {
|
||||
@@ -846,7 +894,7 @@ public class WaterBodyState extends BaseAppState {
|
||||
rebuildPoleMesh(mesh, x, z, waterH, terrH);
|
||||
Material mat = new Material(assets, "Common/MatDefs/Misc/Unshaded.j3md");
|
||||
mat.setColor("Color", color.clone());
|
||||
mat.getAdditionalRenderState().setLineWidth(2.5f);
|
||||
mat.getAdditionalRenderState().setLineWidth(4f);
|
||||
Geometry geo = new Geometry(name, mesh);
|
||||
geo.setMaterial(mat);
|
||||
return geo;
|
||||
|
||||
@@ -0,0 +1,91 @@
|
||||
package de.blight.editor.toolset;
|
||||
|
||||
import javafx.geometry.Insets;
|
||||
import javafx.scene.control.*;
|
||||
import javafx.scene.layout.*;
|
||||
|
||||
/** Shared static UI helpers used by multiple tool controllers. */
|
||||
public final class EditorUiHelper {
|
||||
|
||||
private EditorUiHelper() {}
|
||||
|
||||
public static Label bold(String text) {
|
||||
Label l = new Label(text);
|
||||
l.setStyle("-fx-font-weight: bold; -fx-text-fill: #111111;");
|
||||
return l;
|
||||
}
|
||||
|
||||
public static Label styledHint(String text) {
|
||||
Label l = new Label(text);
|
||||
l.setStyle("-fx-text-fill: #333333; -fx-font-size: 11;");
|
||||
return l;
|
||||
}
|
||||
|
||||
public static Label sectionTitle(String text) {
|
||||
Label l = new Label(text);
|
||||
l.setStyle("-fx-font-weight: bold; -fx-font-size: 12; -fx-text-fill: #1a3a6a;");
|
||||
VBox.setMargin(l, new Insets(8, 0, 0, 0));
|
||||
return l;
|
||||
}
|
||||
|
||||
public static HBox withField(Slider slider, String fmt) {
|
||||
slider.setMaxWidth(Double.MAX_VALUE);
|
||||
TextField tf = new TextField(String.format(fmt, slider.getValue()));
|
||||
tf.setPrefWidth(62);
|
||||
tf.setMaxWidth(62);
|
||||
tf.setStyle("-fx-font-size: 11;");
|
||||
slider.valueProperty().addListener((o, ov, nv) ->
|
||||
tf.setText(String.format(fmt, nv.doubleValue())));
|
||||
Runnable commit = () -> {
|
||||
try {
|
||||
double v = Math.max(slider.getMin(), Math.min(slider.getMax(),
|
||||
Double.parseDouble(tf.getText().replace(',', '.'))));
|
||||
slider.setValue(v);
|
||||
} catch (NumberFormatException ignored) {
|
||||
tf.setText(String.format(fmt, slider.getValue()));
|
||||
}
|
||||
};
|
||||
tf.setOnAction(e -> commit.run());
|
||||
tf.focusedProperty().addListener((o, ov, focused) -> { if (!focused) commit.run(); });
|
||||
HBox row = new HBox(5, slider, tf);
|
||||
HBox.setHgrow(slider, Priority.ALWAYS);
|
||||
return row;
|
||||
}
|
||||
|
||||
public static VBox paramSlider(String label, double min, double max, double init,
|
||||
java.util.function.Consumer<Float> setter) {
|
||||
Label name = new Label(label);
|
||||
name.setStyle("-fx-font-weight: bold; -fx-text-fill: #111111;");
|
||||
Slider slider = new Slider(min, max, init);
|
||||
slider.setShowTickMarks(true);
|
||||
slider.setShowTickLabels(true);
|
||||
slider.setMajorTickUnit((max - min) / 2.0);
|
||||
slider.valueProperty().addListener((o, a, b) -> setter.accept(b.floatValue()));
|
||||
return new VBox(3, name, withField(slider, "%.3f"));
|
||||
}
|
||||
|
||||
public static VBox ezFloat(String label, double min, double max, double init,
|
||||
java.util.function.Consumer<Float> setter) {
|
||||
Label name = new Label(label);
|
||||
name.setStyle("-fx-font-weight: bold; -fx-text-fill: #111111;");
|
||||
Slider slider = new Slider(min, max, Math.max(min, Math.min(max, init)));
|
||||
slider.setShowTickMarks(false);
|
||||
slider.valueProperty().addListener((o, a, b) -> setter.accept(b.floatValue()));
|
||||
return new VBox(2, name, withField(slider, "%.3f"));
|
||||
}
|
||||
|
||||
public static Spinner<Integer> intSpinner(int min, int max, int init) {
|
||||
Spinner<Integer> sp = new Spinner<>(min, max, Math.max(min, Math.min(max, init)));
|
||||
sp.setEditable(true);
|
||||
sp.setMaxWidth(Double.MAX_VALUE);
|
||||
return sp;
|
||||
}
|
||||
|
||||
public static String pathToName(String path, String[] paths, String[] names) {
|
||||
if (path == null) return names[0];
|
||||
for (int i = 0; i < paths.length; i++) {
|
||||
if (path.equals(paths[i])) return names[i];
|
||||
}
|
||||
return names[0];
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,387 @@
|
||||
package de.blight.editor.toolset.game;
|
||||
|
||||
import de.blight.editor.ProjectRoot;
|
||||
import de.blight.editor.SharedInput;
|
||||
import de.blight.editor.ToolContext;
|
||||
import de.blight.editor.toolset.EditorUiHelper;
|
||||
import javafx.application.Platform;
|
||||
import javafx.geometry.Insets;
|
||||
import javafx.scene.Scene;
|
||||
import javafx.scene.control.*;
|
||||
import javafx.scene.layout.*;
|
||||
import javafx.stage.Stage;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.ConcurrentLinkedQueue;
|
||||
|
||||
public class GameController {
|
||||
|
||||
private final ToolContext ctx;
|
||||
|
||||
// Game launch state
|
||||
private boolean launchGameAfterSave = false;
|
||||
private boolean pendingNewGame = false;
|
||||
private boolean pendingAutostart = true;
|
||||
|
||||
// Play panel UI
|
||||
private Button gamePlayBtn;
|
||||
private Button gameNewBtn;
|
||||
private Button gameFullBtn;
|
||||
private CheckBox debugConsoleCB;
|
||||
private Label tempSpawnCoordsLabel;
|
||||
private Label permSpawnCoordsLabel;
|
||||
|
||||
// Game console
|
||||
private Stage gameConsoleStage;
|
||||
private TextArea gameConsoleArea;
|
||||
private final ConcurrentLinkedQueue<String> consoleBuffer = new ConcurrentLinkedQueue<>();
|
||||
|
||||
public GameController(ToolContext ctx) {
|
||||
this.ctx = ctx;
|
||||
}
|
||||
|
||||
// ── Play-Tool-Panel ───────────────────────────────────────────────────────
|
||||
|
||||
public VBox buildPlayToolPanel() {
|
||||
VBox inner = new VBox(8);
|
||||
inner.setPadding(new Insets(10));
|
||||
|
||||
inner.getChildren().addAll(EditorUiHelper.sectionTitle("Spawnpunkte"), new Separator());
|
||||
|
||||
Button setTempBtn = new Button("Temp. Spawn hier setzen");
|
||||
setTempBtn.setMaxWidth(Double.MAX_VALUE);
|
||||
setTempBtn.setTooltip(new Tooltip(
|
||||
"Aktiviert Klick-Modus im Viewport zum Setzen des temporären Spawnpunkts"));
|
||||
setTempBtn.setOnAction(e -> {
|
||||
ctx.input().playToolMode = SharedInput.PlayToolMode.SET_TEMP;
|
||||
ctx.setStatus().accept("L-Klick im Viewport → temporären Spawnpunkt setzen");
|
||||
});
|
||||
|
||||
Button setPermBtn = new Button("Perm. Spawn hier setzen");
|
||||
setPermBtn.setMaxWidth(Double.MAX_VALUE);
|
||||
setPermBtn.setTooltip(new Tooltip(
|
||||
"Aktiviert Klick-Modus im Viewport zum Setzen des permanenten Spawnpunkts"));
|
||||
setPermBtn.setOnAction(e -> {
|
||||
ctx.input().playToolMode = SharedInput.PlayToolMode.SET_PERM;
|
||||
ctx.setStatus().accept("L-Klick im Viewport → permanenten Spawnpunkt setzen");
|
||||
});
|
||||
|
||||
inner.getChildren().addAll(setTempBtn, setPermBtn, new Separator());
|
||||
|
||||
tempSpawnCoordsLabel = new Label(tempSpawnCoordsText());
|
||||
tempSpawnCoordsLabel.setStyle("-fx-font-family: monospace; -fx-font-size: 11; -fx-text-fill: #333;");
|
||||
permSpawnCoordsLabel = new Label(permSpawnCoordsText());
|
||||
permSpawnCoordsLabel.setStyle("-fx-font-family: monospace; -fx-font-size: 11; -fx-text-fill: #333;");
|
||||
|
||||
Button clearTempBtn = new Button("X Temp. Spawn loeschen");
|
||||
clearTempBtn.setMaxWidth(Double.MAX_VALUE);
|
||||
clearTempBtn.setOnAction(e -> {
|
||||
ctx.input().tempSpawnX = Float.NaN;
|
||||
ctx.input().tempSpawnZ = Float.NaN;
|
||||
if (tempSpawnCoordsLabel != null) tempSpawnCoordsLabel.setText("(nicht gesetzt)");
|
||||
});
|
||||
|
||||
inner.getChildren().addAll(
|
||||
EditorUiHelper.bold("Temp. Spawn:"), tempSpawnCoordsLabel, clearTempBtn,
|
||||
EditorUiHelper.bold("Perm. Spawn:"), permSpawnCoordsLabel,
|
||||
new Separator());
|
||||
|
||||
inner.getChildren().addAll(EditorUiHelper.sectionTitle("Spiel starten"), new Separator());
|
||||
|
||||
Button playBtn = new Button("▶ Spielen");
|
||||
gamePlayBtn = playBtn;
|
||||
playBtn.setMaxWidth(Double.MAX_VALUE);
|
||||
playBtn.setStyle("-fx-background-color: #2d8a3e; -fx-text-fill: white; " +
|
||||
"-fx-font-weight: bold; -fx-padding: 6 12 6 12;");
|
||||
playBtn.setTooltip(new Tooltip(
|
||||
"Startet das Spiel mit dem temp. Spawnpunkt (bzw. gespeicherter Position)"));
|
||||
playBtn.setOnAction(e -> launchGame());
|
||||
|
||||
Button newGameBtn = new Button("Neues Spiel");
|
||||
gameNewBtn = newGameBtn;
|
||||
newGameBtn.setMaxWidth(Double.MAX_VALUE);
|
||||
newGameBtn.setStyle("-fx-background-color: #7b3ea4; -fx-text-fill: white; " +
|
||||
"-fx-font-weight: bold; -fx-padding: 6 12 6 12;");
|
||||
newGameBtn.setTooltip(new Tooltip(
|
||||
"Startet ein neues Spiel am perm. Spawnpunkt mit Intro-Sequenz"));
|
||||
newGameBtn.setOnAction(e -> launchNewGame());
|
||||
|
||||
Button fullStartBtn = new Button("▶ Vollst. Start");
|
||||
gameFullBtn = fullStartBtn;
|
||||
fullStartBtn.setMaxWidth(Double.MAX_VALUE);
|
||||
fullStartBtn.setStyle("-fx-background-color: #1a6080; -fx-text-fill: white; " +
|
||||
"-fx-font-weight: bold; -fx-padding: 6 12 6 12;");
|
||||
fullStartBtn.setTooltip(new Tooltip(
|
||||
"Startet das Spiel wie von außen: mit OAA-Logo, JME-Logo und Hauptmenü"));
|
||||
fullStartBtn.setOnAction(e -> launchFullGame());
|
||||
|
||||
debugConsoleCB = new CheckBox("Debug-Konsole anzeigen");
|
||||
debugConsoleCB.setTooltip(new Tooltip(
|
||||
"Öffnet bei Spielstart die Ausgabekonsole mit der Spielausgabe"));
|
||||
|
||||
inner.getChildren().addAll(playBtn, newGameBtn, fullStartBtn, debugConsoleCB);
|
||||
|
||||
ScrollPane scroll = new ScrollPane(inner);
|
||||
scroll.setFitToWidth(true);
|
||||
scroll.setHbarPolicy(ScrollPane.ScrollBarPolicy.NEVER);
|
||||
scroll.setStyle("-fx-background-color: transparent; -fx-background: transparent;");
|
||||
VBox panel = new VBox(scroll);
|
||||
VBox.setVgrow(scroll, Priority.ALWAYS);
|
||||
panel.setPrefWidth(270);
|
||||
panel.setStyle("-fx-background-color: #f0f0f0; -fx-border-color: #ccc; -fx-border-width: 0 0 0 1;");
|
||||
return panel;
|
||||
}
|
||||
|
||||
// ── Launch ────────────────────────────────────────────────────────────────
|
||||
|
||||
public void launchGame() {
|
||||
if (launchGameAfterSave) return;
|
||||
launchGameAfterSave = true;
|
||||
pendingNewGame = false;
|
||||
pendingAutostart = true;
|
||||
if (gamePlayBtn != null) { gamePlayBtn.setDisable(true); gamePlayBtn.setText("⏳ Startet…"); }
|
||||
if (gameNewBtn != null) gameNewBtn.setDisable(true);
|
||||
if (gameFullBtn != null) gameFullBtn.setDisable(true);
|
||||
ctx.input().saveRequested = true;
|
||||
ctx.setStatus().accept("Karte wird gespeichert, Spiel startet…");
|
||||
}
|
||||
|
||||
public void launchNewGame() {
|
||||
if (launchGameAfterSave) return;
|
||||
launchGameAfterSave = true;
|
||||
pendingNewGame = true;
|
||||
pendingAutostart = true;
|
||||
if (gameNewBtn != null) { gameNewBtn.setDisable(true); gameNewBtn.setText("⏳ Startet…"); }
|
||||
if (gamePlayBtn != null) gamePlayBtn.setDisable(true);
|
||||
if (gameFullBtn != null) gameFullBtn.setDisable(true);
|
||||
ctx.input().saveRequested = true;
|
||||
ctx.setStatus().accept("Karte wird gespeichert, Neues Spiel startet…");
|
||||
}
|
||||
|
||||
public void launchFullGame() {
|
||||
if (launchGameAfterSave) return;
|
||||
launchGameAfterSave = true;
|
||||
pendingNewGame = false;
|
||||
pendingAutostart = false;
|
||||
if (gameFullBtn != null) { gameFullBtn.setDisable(true); gameFullBtn.setText("⏳ Startet…"); }
|
||||
if (gamePlayBtn != null) gamePlayBtn.setDisable(true);
|
||||
if (gameNewBtn != null) gameNewBtn.setDisable(true);
|
||||
ctx.input().saveRequested = true;
|
||||
ctx.setStatus().accept("Karte wird gespeichert, vollständiger Spielstart…");
|
||||
}
|
||||
|
||||
/** Called from EditorApp AnimationTimer after save completes. */
|
||||
public void onSaveComplete() {
|
||||
if (!launchGameAfterSave) return;
|
||||
launchGameAfterSave = false;
|
||||
startGameProcess();
|
||||
}
|
||||
|
||||
// ── Console buffer drain — called from AnimationTimer ─────────────────────
|
||||
|
||||
public void flushConsoleBuffer() {
|
||||
if (consoleBuffer.isEmpty()) return;
|
||||
if (gameConsoleArea != null) {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
int count = 0;
|
||||
String ln;
|
||||
while (count < 200 && (ln = consoleBuffer.poll()) != null) {
|
||||
sb.append(ln).append('\n');
|
||||
count++;
|
||||
}
|
||||
gameConsoleArea.appendText(sb.toString());
|
||||
gameConsoleArea.setScrollTop(Double.MAX_VALUE);
|
||||
} else {
|
||||
consoleBuffer.clear();
|
||||
}
|
||||
}
|
||||
|
||||
// ── Spawn coords update — called from AnimationTimer ──────────────────────
|
||||
|
||||
public void updateSpawnFields(String info) {
|
||||
if (info == null) return;
|
||||
String[] p = info.split("\\|", -1);
|
||||
if (p.length < 2) return;
|
||||
if (tempSpawnCoordsLabel != null) tempSpawnCoordsLabel.setText(tempSpawnCoordsText());
|
||||
}
|
||||
|
||||
public void updatePermSpawnLabel() {
|
||||
if (permSpawnCoordsLabel != null) permSpawnCoordsLabel.setText(permSpawnCoordsText());
|
||||
}
|
||||
|
||||
// ── Internal ──────────────────────────────────────────────────────────────
|
||||
|
||||
private String tempSpawnCoordsText() {
|
||||
SharedInput in = ctx.input();
|
||||
if (Float.isNaN(in.tempSpawnX) || Float.isNaN(in.tempSpawnZ)) return "(nicht gesetzt)";
|
||||
return String.format("X=%.1f Z=%.1f Yaw=%.0f°", in.tempSpawnX, in.tempSpawnZ, in.tempSpawnYaw);
|
||||
}
|
||||
|
||||
private String permSpawnCoordsText() {
|
||||
SharedInput in = ctx.input();
|
||||
if (Float.isNaN(in.permSpawnX) || Float.isNaN(in.permSpawnZ)) return "(nicht gesetzt)";
|
||||
return String.format("X=%.1f Z=%.1f Yaw=%.0f°", in.permSpawnX, in.permSpawnZ, in.permSpawnYaw);
|
||||
}
|
||||
|
||||
private void startGameProcess() {
|
||||
final boolean isNewGame = pendingNewGame;
|
||||
final boolean isAutostart = pendingAutostart;
|
||||
pendingNewGame = false;
|
||||
pendingAutostart = true;
|
||||
Stage primary = ctx.stageSupplier().get();
|
||||
Platform.runLater(() -> { if (primary != null) primary.setIconified(true); });
|
||||
SharedInput in = ctx.input();
|
||||
new Thread(() -> {
|
||||
try {
|
||||
String javaExe = Paths.get(System.getProperty("java.home"), "bin", "java").toString();
|
||||
String classpath = System.getProperty("java.class.path");
|
||||
String libPath = System.getProperty("java.library.path", "");
|
||||
String projRoot = ProjectRoot.PATH.toString();
|
||||
|
||||
Path srcMapDir = ProjectRoot.PATH.resolve(
|
||||
Paths.get("blight-map", "src", "main", "map"));
|
||||
Path runDir = ProjectRoot.PATH.resolve("run");
|
||||
Path sessionDir = runDir.resolve("session-" + System.currentTimeMillis());
|
||||
copyDirectory(srcMapDir, sessionDir);
|
||||
|
||||
try {
|
||||
java.util.List<Path> sessions;
|
||||
try (java.util.stream.Stream<Path> s = Files.list(runDir)) {
|
||||
sessions = s.filter(p -> p.getFileName().toString().startsWith("session-"))
|
||||
.sorted(java.util.Comparator.comparing(p -> p.getFileName().toString()))
|
||||
.collect(java.util.stream.Collectors.toList());
|
||||
}
|
||||
int keep = 3;
|
||||
for (int i = 0; i < sessions.size() - keep; i++) {
|
||||
deleteDirectoryRecursive(sessions.get(i));
|
||||
}
|
||||
} catch (Exception e) {
|
||||
// non-fatal cleanup failure
|
||||
}
|
||||
String sessionMapPath = sessionDir.resolve("blight_map.blm").toString();
|
||||
|
||||
java.util.List<String> cmd = new java.util.ArrayList<>(List.of(
|
||||
javaExe,
|
||||
"--add-opens", "java.base/java.lang=ALL-UNNAMED",
|
||||
"--add-opens", "java.desktop/sun.awt=ALL-UNNAMED",
|
||||
"-Djava.library.path=" + libPath,
|
||||
"-Dblight.project.root=" + projRoot,
|
||||
"-D" + de.blight.common.MapIO.PROP_SESSION_MAP + "=" + sessionMapPath,
|
||||
"-Dblight.autostart=" + isAutostart));
|
||||
|
||||
if (isNewGame) {
|
||||
cmd.add("-Dblight.new.game=true");
|
||||
} else if (!Float.isNaN(in.tempSpawnX) && !Float.isNaN(in.tempSpawnZ)) {
|
||||
cmd.add("-Dblight.temp.spawn.x=" + in.tempSpawnX);
|
||||
cmd.add("-Dblight.temp.spawn.z=" + in.tempSpawnZ);
|
||||
cmd.add("-Dblight.temp.spawn.yaw=" + in.tempSpawnYaw);
|
||||
}
|
||||
|
||||
cmd.addAll(List.of("-cp", classpath, "de.blight.game.BlightGame"));
|
||||
|
||||
Process proc = new ProcessBuilder(cmd)
|
||||
.directory(ProjectRoot.PATH.toFile())
|
||||
.redirectErrorStream(true)
|
||||
.start();
|
||||
|
||||
Platform.runLater(() -> {
|
||||
ctx.setStatus().accept(isNewGame ? "Neues Spiel gestartet" : "Spiel gestartet");
|
||||
if (gamePlayBtn != null) gamePlayBtn.setText("Laeuft…");
|
||||
if (gameNewBtn != null) gameNewBtn.setText("Laeuft…");
|
||||
if (gameFullBtn != null) gameFullBtn.setText("Laeuft…");
|
||||
if (debugConsoleCB != null && debugConsoleCB.isSelected()) openGameConsole();
|
||||
});
|
||||
|
||||
try (java.io.BufferedReader br = new java.io.BufferedReader(
|
||||
new java.io.InputStreamReader(proc.getInputStream(),
|
||||
java.nio.charset.StandardCharsets.UTF_8))) {
|
||||
String line;
|
||||
while ((line = br.readLine()) != null) {
|
||||
consoleBuffer.offer(line);
|
||||
}
|
||||
}
|
||||
consoleBuffer.offer("--- Spiel beendet ---");
|
||||
Platform.runLater(() -> {
|
||||
if (primary != null) primary.setIconified(false);
|
||||
if (gamePlayBtn != null) { gamePlayBtn.setText("▶ Spielen"); gamePlayBtn.setDisable(false); }
|
||||
if (gameNewBtn != null) { gameNewBtn.setText("Neues Spiel"); gameNewBtn.setDisable(false); }
|
||||
if (gameFullBtn != null) { gameFullBtn.setText("▶ Vollst. Start"); gameFullBtn.setDisable(false); }
|
||||
});
|
||||
|
||||
} catch (IOException ex) {
|
||||
Platform.runLater(() -> {
|
||||
ctx.setStatus().accept("Spielstart fehlgeschlagen: " + ex.getMessage());
|
||||
if (gamePlayBtn != null) { gamePlayBtn.setText("▶ Spielen"); gamePlayBtn.setDisable(false); }
|
||||
if (gameNewBtn != null) { gameNewBtn.setText("Neues Spiel"); gameNewBtn.setDisable(false); }
|
||||
if (gameFullBtn != null) { gameFullBtn.setText("▶ Vollst. Start"); gameFullBtn.setDisable(false); }
|
||||
});
|
||||
}
|
||||
}, "game-launcher").start();
|
||||
}
|
||||
|
||||
public void openGameConsole() {
|
||||
Stage primary = ctx.stageSupplier().get();
|
||||
if (gameConsoleStage == null) {
|
||||
gameConsoleArea = new TextArea();
|
||||
gameConsoleArea.setEditable(false);
|
||||
gameConsoleArea.setStyle(
|
||||
"-fx-control-inner-background: #1e1e1e; -fx-text-fill: #d4d4d4;" +
|
||||
" -fx-font-family: monospace; -fx-font-size: 11;");
|
||||
|
||||
Button clearBtn = new Button("Löschen");
|
||||
clearBtn.setOnAction(e -> gameConsoleArea.clear());
|
||||
|
||||
HBox toolbar = new HBox(5, clearBtn);
|
||||
toolbar.setPadding(new Insets(4));
|
||||
|
||||
VBox layout = new VBox(toolbar, gameConsoleArea);
|
||||
VBox.setVgrow(gameConsoleArea, Priority.ALWAYS);
|
||||
|
||||
Scene scene = new Scene(layout, 800, 350);
|
||||
gameConsoleStage = new Stage();
|
||||
gameConsoleStage.setTitle("Spiel-Konsole");
|
||||
gameConsoleStage.setScene(scene);
|
||||
gameConsoleStage.initOwner(primary);
|
||||
var screens = javafx.stage.Screen.getScreens();
|
||||
if (screens.size() > 1) {
|
||||
javafx.geometry.Rectangle2D sb = screens.get(1).getVisualBounds();
|
||||
gameConsoleStage.setX(sb.getMinX());
|
||||
gameConsoleStage.setY(sb.getMinY());
|
||||
gameConsoleStage.setWidth(sb.getWidth());
|
||||
gameConsoleStage.setHeight(sb.getHeight());
|
||||
} else if (primary != null) {
|
||||
gameConsoleStage.setX(primary.getX());
|
||||
gameConsoleStage.setY(primary.getY() + primary.getHeight());
|
||||
}
|
||||
} else {
|
||||
gameConsoleArea.clear();
|
||||
}
|
||||
gameConsoleStage.show();
|
||||
gameConsoleStage.toFront();
|
||||
}
|
||||
|
||||
private static void copyDirectory(Path src, Path dst) throws IOException {
|
||||
try (java.util.stream.Stream<Path> walk = java.nio.file.Files.walk(src)) {
|
||||
for (Path s : (Iterable<Path>) walk::iterator) {
|
||||
Path d = dst.resolve(src.relativize(s));
|
||||
if (java.nio.file.Files.isDirectory(s)) {
|
||||
java.nio.file.Files.createDirectories(d);
|
||||
} else {
|
||||
java.nio.file.Files.copy(s, d, java.nio.file.StandardCopyOption.REPLACE_EXISTING);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void deleteDirectoryRecursive(Path dir) throws IOException {
|
||||
if (!Files.exists(dir)) return;
|
||||
try (java.util.stream.Stream<Path> walk = Files.walk(dir)) {
|
||||
walk.sorted(java.util.Comparator.reverseOrder())
|
||||
.forEach(p -> { try { Files.delete(p); } catch (IOException ignored) {} });
|
||||
}
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,335 @@
|
||||
package de.blight.editor.toolset.topbar;
|
||||
|
||||
import de.blight.editor.SharedInput;
|
||||
import de.blight.editor.ToolContext;
|
||||
import javafx.geometry.Orientation;
|
||||
import javafx.scene.control.*;
|
||||
import javafx.scene.layout.*;
|
||||
|
||||
public class TopBarController {
|
||||
|
||||
public record Deps(
|
||||
VBox toolPanel,
|
||||
de.blight.editor.toolset.model.ModelEditorController modelEditorController,
|
||||
de.blight.editor.toolset.asset.AssetTreeController assetController,
|
||||
de.blight.editor.toolset.anim.AnimSetController animSetController,
|
||||
de.blight.editor.toolset.content.ContentController contentController,
|
||||
de.blight.editor.toolset.world.WorldPanelController worldPanelController,
|
||||
de.blight.editor.toolset.voxel.VoxelController voxelController,
|
||||
de.blight.editor.toolset.grass.GrassController grassController,
|
||||
de.blight.editor.toolset.atmosphere.AtmosphereController atmosphereController,
|
||||
de.blight.editor.toolset.water.WaterController waterController,
|
||||
de.blight.editor.toolset.game.GameController gameController,
|
||||
de.blight.editor.toolset.terrain.TerrainPanelController terrainController,
|
||||
de.blight.editor.toolset.object.ObjectController objectController,
|
||||
Runnable switchToVegetationGenerator,
|
||||
Runnable switchToEzTree,
|
||||
Runnable switchToTripo,
|
||||
Runnable openCharacterEditor,
|
||||
Runnable switchToThumbnailManager,
|
||||
Runnable takeEditorScreenshot,
|
||||
Runnable openSettings
|
||||
) {}
|
||||
|
||||
private final SharedInput input;
|
||||
|
||||
// ToggleButtons – pre-created so getters work before buildTop() is called
|
||||
private final ToggleButton auswahlBtn = new ToggleButton("[k] Auswahl");
|
||||
private final ToggleButton baseBtn = new ToggleButton("▲▼ Terrain");
|
||||
private final ToggleButton voxelMergedBtn = new ToggleButton("◈ Voxel");
|
||||
private final ToggleButton textureBtn = new ToggleButton(": : Textur");
|
||||
private final ToggleButton grassBtn = new ToggleButton("// Gras");
|
||||
private final ToggleButton stoneBtn = new ToggleButton("o Steine");
|
||||
private final ToggleButton objPlaceBtn = new ToggleButton("+ Objekte");
|
||||
private final ToggleButton atmosphaereBtn = new ToggleButton("* Atmosphare");
|
||||
private final ToggleButton wasserMergedBtn = new ToggleButton("~ Wasser");
|
||||
private final ToggleButton wegeBtn = new ToggleButton("-- Wege");
|
||||
private final ToggleButton zonenBtn = new ToggleButton("[ ] Zonen");
|
||||
private final ToggleButton playToolBtn = new ToggleButton("▶ Spielen");
|
||||
|
||||
// Menu items – pre-created
|
||||
private final RadioMenuItem camOrbitItem = new RadioMenuItem("⊙ Orbit-Kamera (Alt+C)");
|
||||
private final RadioMenuItem camFreeItem = new RadioMenuItem("✈ FreeFly-Kamera (Alt+C)");
|
||||
private final CheckMenuItem dualWindowMenuItem = new CheckMenuItem("2-Fenster-Modus (Alt+W)");
|
||||
private final CheckMenuItem showGridItem = new CheckMenuItem("1m-Gitternetz (Alt+N)");
|
||||
private CheckMenuItem viewTopologyItem;
|
||||
private boolean wireframeActive = false;
|
||||
|
||||
private ToolBar worldToolBar;
|
||||
private Label worldHintLabel;
|
||||
|
||||
public TopBarController(SharedInput input) {
|
||||
this.input = input;
|
||||
|
||||
// Bold style
|
||||
final String BOLD = "-fx-font-weight:bold;";
|
||||
auswahlBtn.setStyle(BOLD);
|
||||
baseBtn.setStyle(BOLD);
|
||||
voxelMergedBtn.setStyle(BOLD);
|
||||
textureBtn.setStyle(BOLD);
|
||||
grassBtn.setStyle(BOLD);
|
||||
stoneBtn.setStyle(BOLD);
|
||||
objPlaceBtn.setStyle(BOLD);
|
||||
atmosphaereBtn.setStyle(BOLD);
|
||||
wasserMergedBtn.setStyle(BOLD);
|
||||
wegeBtn.setStyle(BOLD);
|
||||
zonenBtn.setStyle(BOLD);
|
||||
playToolBtn.setStyle(BOLD);
|
||||
|
||||
// Mutually exclusive toggle group
|
||||
ToggleGroup layerGroup = new ToggleGroup();
|
||||
auswahlBtn.setToggleGroup(layerGroup);
|
||||
baseBtn.setToggleGroup(layerGroup);
|
||||
voxelMergedBtn.setToggleGroup(layerGroup);
|
||||
textureBtn.setToggleGroup(layerGroup);
|
||||
grassBtn.setToggleGroup(layerGroup);
|
||||
stoneBtn.setToggleGroup(layerGroup);
|
||||
objPlaceBtn.setToggleGroup(layerGroup);
|
||||
atmosphaereBtn.setToggleGroup(layerGroup);
|
||||
wasserMergedBtn.setToggleGroup(layerGroup);
|
||||
wegeBtn.setToggleGroup(layerGroup);
|
||||
zonenBtn.setToggleGroup(layerGroup);
|
||||
playToolBtn.setToggleGroup(layerGroup);
|
||||
|
||||
// Cam-Menu toggle group
|
||||
ToggleGroup camMenuGroup = new ToggleGroup();
|
||||
camOrbitItem.setToggleGroup(camMenuGroup);
|
||||
camFreeItem.setToggleGroup(camMenuGroup);
|
||||
camOrbitItem.setSelected(true);
|
||||
|
||||
// Grid default
|
||||
showGridItem.setSelected(true);
|
||||
}
|
||||
|
||||
// ── Getters ──────────────────────────────────────────────────────────────
|
||||
|
||||
public ToggleButton getAuswahlBtn() { return auswahlBtn; }
|
||||
public ToggleButton getBaseBtn() { return baseBtn; }
|
||||
public ToggleButton getVoxelMergedBtn() { return voxelMergedBtn; }
|
||||
public ToggleButton getTextureBtn() { return textureBtn; }
|
||||
public ToggleButton getGrassBtn() { return grassBtn; }
|
||||
public ToggleButton getStoneBtn() { return stoneBtn; }
|
||||
public ToggleButton getObjPlaceBtn() { return objPlaceBtn; }
|
||||
public ToggleButton getAtmosphaereBtn() { return atmosphaereBtn; }
|
||||
public ToggleButton getWasserMergedBtn(){ return wasserMergedBtn; }
|
||||
public ToggleButton getWegeBtn() { return wegeBtn; }
|
||||
public ToggleButton getZonenBtn() { return zonenBtn; }
|
||||
public ToggleButton getPlayToolBtn() { return playToolBtn; }
|
||||
|
||||
public RadioMenuItem getCamOrbitItem() { return camOrbitItem; }
|
||||
public RadioMenuItem getCamFreeItem() { return camFreeItem; }
|
||||
public CheckMenuItem getDualWindowMenuItem() { return dualWindowMenuItem; }
|
||||
public CheckMenuItem getShowGridItem() { return showGridItem; }
|
||||
public CheckMenuItem getViewTopologyItem() { return viewTopologyItem; }
|
||||
|
||||
public ToolBar getWorldToolBar() { return worldToolBar; }
|
||||
public Label getWorldHintLabel(){ return worldHintLabel; }
|
||||
|
||||
public boolean toggleWireframe() {
|
||||
wireframeActive = !wireframeActive;
|
||||
return wireframeActive;
|
||||
}
|
||||
|
||||
public void updateCamHintLabel() {
|
||||
if (worldHintLabel != null) { worldHintLabel.setText(camHintText()); }
|
||||
}
|
||||
|
||||
public void applyShowGridPref(boolean value) {
|
||||
showGridItem.setSelected(value);
|
||||
}
|
||||
|
||||
private String camHintText() {
|
||||
String mode = (input.camMode == SharedInput.CAM_FREEFLY) ? "FreeFly" : "Orbit";
|
||||
return "WASD/QE: Kamera | Mitte-Drag: Drehen | Alt+C: Kamera wechseln | Kamera: " + mode;
|
||||
}
|
||||
|
||||
// ── Build ─────────────────────────────────────────────────────────────────
|
||||
|
||||
public VBox buildTop(ToolContext ctx, Deps deps) {
|
||||
MenuBar menuBar = new MenuBar();
|
||||
|
||||
// ── Datei-Menü ──────────────────────────────────────────────────────
|
||||
Menu fileMenu = new Menu("Datei");
|
||||
MenuItem newItem = new MenuItem("Neue Karte");
|
||||
MenuItem saveItem = new MenuItem("Speichern");
|
||||
saveItem.setAccelerator(javafx.scene.input.KeyCombination.keyCombination("Ctrl+S"));
|
||||
saveItem.setOnAction(e -> { input.saveRequested = true; ctx.setStatus().accept("Speichern…"); });
|
||||
MenuItem screenshotItem = new MenuItem("Screenshot");
|
||||
screenshotItem.setAccelerator(javafx.scene.input.KeyCombination.keyCombination("Ctrl+F12"));
|
||||
screenshotItem.setOnAction(e -> deps.takeEditorScreenshot().run());
|
||||
MenuItem settingsItem = new MenuItem("Einstellungen…");
|
||||
settingsItem.setOnAction(e -> deps.openSettings().run());
|
||||
MenuItem quitItem = new MenuItem("Beenden");
|
||||
quitItem.setOnAction(e -> javafx.application.Platform.exit());
|
||||
fileMenu.getItems().addAll(newItem, saveItem, new SeparatorMenuItem(),
|
||||
screenshotItem, new SeparatorMenuItem(), settingsItem,
|
||||
new SeparatorMenuItem(), quitItem);
|
||||
|
||||
// ── Import-Menü ─────────────────────────────────────────────────────
|
||||
Menu importMenu = new Menu("Import");
|
||||
MenuItem importModelLodItem = new MenuItem("Modell mit LOD-Editor…");
|
||||
MenuItem importTexItem = new MenuItem("Texturen…");
|
||||
MenuItem importTexZipItem = new MenuItem("Texturen-ZIP…");
|
||||
MenuItem importTexSetItem = new MenuItem("Textur-Set importieren…");
|
||||
MenuItem importAudioItem = new MenuItem("Audio…");
|
||||
MenuItem importAnimItem = new MenuItem("Animationen…");
|
||||
MenuItem importCharModelItem = new MenuItem("Character-Modell (FBX)…");
|
||||
importModelLodItem.setOnAction(e -> deps.modelEditorController().openModelImport(ctx.stageSupplier().get()));
|
||||
importTexItem.setOnAction(e -> deps.assetController().handleTextureImport(ctx.stageSupplier().get()));
|
||||
importTexZipItem.setOnAction(e -> deps.assetController().handleZipTextureImport(ctx.stageSupplier().get()));
|
||||
importTexSetItem.setOnAction(e -> deps.assetController().openTextureSetImport(ctx.stageSupplier().get()));
|
||||
importAudioItem.setOnAction(e -> deps.assetController().handleAudioImport(ctx.stageSupplier().get()));
|
||||
importAnimItem.setOnAction(e -> deps.animSetController().handleAnimationImport(ctx.stageSupplier().get()));
|
||||
importCharModelItem.setOnAction(e -> deps.animSetController().handleCharacterModelImport(ctx.stageSupplier().get()));
|
||||
importMenu.getItems().addAll(importModelLodItem, importTexItem, importTexZipItem,
|
||||
importTexSetItem, importAudioItem, importAnimItem,
|
||||
new SeparatorMenuItem(), importCharModelItem);
|
||||
|
||||
// ── Werkzeuge-Menü ──────────────────────────────────────────────────
|
||||
Menu toolsMenu = new Menu("Werkzeuge");
|
||||
MenuItem vegetationsItem = new MenuItem("Vegetations Generator");
|
||||
MenuItem ezTreeItem = new MenuItem("Baum Generator (EZ Tree)");
|
||||
MenuItem tripoItem = new MenuItem("AI Modell-Generator (Tripo3D)");
|
||||
MenuItem animPrevItem = new MenuItem("Animationset Editor");
|
||||
MenuItem objEditorItem = new MenuItem("Model Editor");
|
||||
MenuItem thumbItem = new MenuItem("Thumbnail-Verwaltung");
|
||||
vegetationsItem.setOnAction(e -> deps.switchToVegetationGenerator().run());
|
||||
ezTreeItem.setOnAction(e -> deps.switchToEzTree().run());
|
||||
tripoItem.setOnAction(e -> deps.switchToTripo().run());
|
||||
animPrevItem.setOnAction(e -> deps.openCharacterEditor().run());
|
||||
objEditorItem.setOnAction(e -> deps.modelEditorController().openModelEditor(null, null));
|
||||
thumbItem.setOnAction(e -> deps.switchToThumbnailManager().run());
|
||||
toolsMenu.getItems().addAll(vegetationsItem, ezTreeItem, tripoItem,
|
||||
animPrevItem, objEditorItem, new SeparatorMenuItem(), thumbItem);
|
||||
|
||||
// ── Ansicht-Menü ────────────────────────────────────────────────────
|
||||
Menu viewMenu = new Menu("Ansicht");
|
||||
MenuItem resetCam = new MenuItem("Kamera zurücksetzen (Alt+R)");
|
||||
resetCam.setOnAction(e -> input.resetCameraRequested.set(true));
|
||||
MenuItem viewTexture = new MenuItem("Textur (Alt+G)");
|
||||
MenuItem viewWireframe = new MenuItem("Drahtgitter (Alt+G)");
|
||||
viewTopologyItem = new CheckMenuItem("Topologie-Overlay (Alt+T)");
|
||||
viewTexture.setOnAction(e -> {
|
||||
wireframeActive = false;
|
||||
input.wireframeRequest = 2;
|
||||
});
|
||||
viewWireframe.setOnAction(e -> {
|
||||
wireframeActive = true;
|
||||
input.wireframeRequest = 1;
|
||||
});
|
||||
viewTopologyItem.setOnAction(e ->
|
||||
input.topologyRequest = viewTopologyItem.isSelected() ? 1 : 2);
|
||||
|
||||
camOrbitItem.setOnAction(e -> { input.camMode = SharedInput.CAM_ORBIT; updateCamHintLabel(); });
|
||||
camFreeItem.setOnAction(e -> { input.camMode = SharedInput.CAM_FREEFLY; updateCamHintLabel(); });
|
||||
|
||||
CheckMenuItem debugHeightItem = new CheckMenuItem("Höhen-Overlay (Alt+H)");
|
||||
debugHeightItem.setAccelerator(javafx.scene.input.KeyCombination.keyCombination("Alt+H"));
|
||||
debugHeightItem.setOnAction(e ->
|
||||
input.debugHeightOverlayEnabled = debugHeightItem.isSelected());
|
||||
|
||||
dualWindowMenuItem.setAccelerator(javafx.scene.input.KeyCombination.keyCombination("Alt+W"));
|
||||
dualWindowMenuItem.setOnAction(e -> {
|
||||
if (dualWindowMenuItem.isSelected()) { deps.contentController().enableDualWindowMode(); }
|
||||
else { deps.contentController().disableDualWindowMode(); }
|
||||
});
|
||||
deps.contentController().setDualWindowMenuItem(dualWindowMenuItem);
|
||||
|
||||
showGridItem.setAccelerator(javafx.scene.input.KeyCombination.keyCombination("Alt+N"));
|
||||
showGridItem.setOnAction(e -> input.showGrid = showGridItem.isSelected());
|
||||
|
||||
viewMenu.getItems().addAll(resetCam, new SeparatorMenuItem(), viewTexture, viewWireframe,
|
||||
new SeparatorMenuItem(), viewTopologyItem,
|
||||
new SeparatorMenuItem(), debugHeightItem,
|
||||
new SeparatorMenuItem(), showGridItem,
|
||||
new SeparatorMenuItem(), camOrbitItem, camFreeItem);
|
||||
|
||||
// ── Zeit-Menü ───────────────────────────────────────────────────────
|
||||
Menu zeitMenu = new Menu("Zeit");
|
||||
ToggleGroup zeitGroup = new ToggleGroup();
|
||||
int[] hours = {0, 3, 6, 9, 12, 15, 18, 21};
|
||||
for (int h : hours) {
|
||||
RadioMenuItem item = new RadioMenuItem(String.format("%02d:00", h));
|
||||
item.setToggleGroup(zeitGroup);
|
||||
if (h == 12) { item.setSelected(true); }
|
||||
final float t = h / 24f;
|
||||
item.setOnAction(e -> input.pendingTimeOfDay = t);
|
||||
zeitMenu.getItems().add(item);
|
||||
}
|
||||
menuBar.getMenus().addAll(fileMenu, toolsMenu, importMenu, viewMenu, zeitMenu);
|
||||
|
||||
// ── Toolbar-Buttons ─────────────────────────────────────────────────
|
||||
auswahlBtn.setSelected(true);
|
||||
input.activeLayer = SharedInput.LAYER_AUSWAHL;
|
||||
|
||||
auswahlBtn.setOnAction(e -> {
|
||||
input.activeLayer = SharedInput.LAYER_AUSWAHL;
|
||||
input.deselectAllOnAuswahl = true;
|
||||
ctx.setRightPanel().accept(deps.worldPanelController().buildAuswahlPanel());
|
||||
});
|
||||
baseBtn.setOnAction(e -> {
|
||||
input.heightTool.brushRadius.setValue(input.voxelTool.brushRadius.getValue());
|
||||
input.heightTool.brushStrength.setValue(input.voxelTool.brushStrength.getValue());
|
||||
input.heightTool.plateauHeight.setValue(input.voxelTool.plateauTarget.getValue());
|
||||
input.activeLayer = 0; input.activeTool = input.heightTool;
|
||||
ctx.setRightPanel().accept(deps.toolPanel());
|
||||
deps.terrainController().showToolParameters(deps.toolPanel(), input.activeTool);
|
||||
});
|
||||
voxelMergedBtn.setOnAction(e -> ctx.setRightPanel().accept(deps.voxelController().buildPanel()));
|
||||
textureBtn.setOnAction(e -> {
|
||||
input.activeLayer = 4; input.activeTool = input.textureTool;
|
||||
ctx.setRightPanel().accept(deps.toolPanel());
|
||||
deps.terrainController().showToolParameters(deps.toolPanel(), input.activeTool);
|
||||
});
|
||||
grassBtn.setOnAction(e -> ctx.setRightPanel().accept(deps.grassController().buildPanel()));
|
||||
stoneBtn.setOnAction(e -> {
|
||||
input.activeLayer = SharedInput.LAYER_STONE;
|
||||
input.activeTool = input.stoneTool;
|
||||
ctx.setRightPanel().accept(deps.toolPanel());
|
||||
deps.terrainController().showToolParameters(deps.toolPanel(), input.activeTool);
|
||||
});
|
||||
objPlaceBtn.setOnAction(e -> {
|
||||
deps.assetController().expandModelsNode();
|
||||
deps.assetController().openOverlay();
|
||||
deps.objectController().activatePlaceMode();
|
||||
});
|
||||
atmosphaereBtn.setOnAction(e -> ctx.setRightPanel().accept(deps.atmosphereController().buildPanel()));
|
||||
wasserMergedBtn.setOnAction(e -> ctx.setRightPanel().accept(deps.waterController().buildPanel()));
|
||||
wegeBtn.setOnAction(e -> {
|
||||
input.activeLayer = SharedInput.LAYER_PATH_NETWORK;
|
||||
ctx.setRightPanel().accept(deps.worldPanelController().buildWegePanel());
|
||||
});
|
||||
zonenBtn.setOnAction(e -> {
|
||||
input.activeLayer = SharedInput.LAYER_ZONEN;
|
||||
ctx.setRightPanel().accept(deps.worldPanelController().buildZonenPanel());
|
||||
});
|
||||
playToolBtn.setOnAction(e -> {
|
||||
input.activeLayer = SharedInput.LAYER_PLAY_TOOL;
|
||||
input.playToolMode = SharedInput.PlayToolMode.EDIT;
|
||||
ctx.setRightPanel().accept(deps.gameController().buildPlayToolPanel());
|
||||
});
|
||||
|
||||
worldHintLabel = new Label(camHintText());
|
||||
worldHintLabel.setStyle("-fx-text-fill: #555;");
|
||||
|
||||
Region spacer = new Region();
|
||||
HBox.setHgrow(spacer, Priority.ALWAYS);
|
||||
|
||||
ToolBar toolBar = new ToolBar();
|
||||
toolBar.getItems().addAll(
|
||||
auswahlBtn,
|
||||
new Separator(Orientation.VERTICAL), baseBtn, voxelMergedBtn,
|
||||
new Separator(Orientation.VERTICAL), textureBtn, grassBtn, stoneBtn,
|
||||
new Separator(Orientation.VERTICAL), objPlaceBtn,
|
||||
new Separator(Orientation.VERTICAL), atmosphaereBtn,
|
||||
new Separator(Orientation.VERTICAL), wasserMergedBtn, wegeBtn,
|
||||
new Separator(Orientation.VERTICAL), zonenBtn,
|
||||
new Separator(Orientation.VERTICAL), playToolBtn,
|
||||
new Separator(Orientation.VERTICAL), worldHintLabel,
|
||||
new Separator(Orientation.VERTICAL), spacer,
|
||||
deps.contentController().buildContentSwitchBtn());
|
||||
|
||||
worldToolBar = toolBar;
|
||||
return new VBox(menuBar, toolBar);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,282 @@
|
||||
package de.blight.editor.toolset.tripo;
|
||||
|
||||
import de.blight.editor.ToolContext;
|
||||
import de.blight.editor.toolset.EditorUiHelper;
|
||||
import de.blight.editor.SharedInput;
|
||||
import de.blight.editor.TripoGenerator;
|
||||
import javafx.application.Platform;
|
||||
import javafx.geometry.Insets;
|
||||
import javafx.geometry.Orientation;
|
||||
import javafx.scene.control.*;
|
||||
import javafx.scene.image.ImageView;
|
||||
import javafx.scene.layout.*;
|
||||
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
|
||||
public class TripoController {
|
||||
|
||||
private final ToolContext ctx;
|
||||
|
||||
private ImageView tripoPreviewView;
|
||||
private Label tripoStatusLabel;
|
||||
private ProgressBar tripoProgressBar;
|
||||
private Button tripoImportBtn;
|
||||
private Button tripoImportRigBtn;
|
||||
private boolean tripoGenerating = false;
|
||||
private String tripoLastModelUrl;
|
||||
private String tripoLastTaskId;
|
||||
|
||||
public TripoController(ToolContext ctx) {
|
||||
this.ctx = ctx;
|
||||
}
|
||||
|
||||
public void activate() {
|
||||
ctx.setCurrentTool().accept("tripo");
|
||||
|
||||
StackPane previewCenter = new StackPane();
|
||||
previewCenter.setStyle("-fx-background-color: #1a1a2e;");
|
||||
tripoPreviewView = new ImageView();
|
||||
tripoPreviewView.setPreserveRatio(true);
|
||||
tripoPreviewView.fitWidthProperty().bind(previewCenter.widthProperty());
|
||||
tripoPreviewView.fitHeightProperty().bind(previewCenter.heightProperty());
|
||||
Label placeholder = new Label("Vorschau erscheint hier nach der Generierung");
|
||||
placeholder.setStyle("-fx-text-fill: #888; -fx-font-size: 14;");
|
||||
previewCenter.getChildren().addAll(placeholder, tripoPreviewView);
|
||||
ctx.setCenterView().accept(previewCenter);
|
||||
ctx.setRightPanel().accept(buildTripoPanel());
|
||||
ctx.setMainToolbar().accept(buildTripoToolBar());
|
||||
}
|
||||
|
||||
private ToolBar buildTripoToolBar() {
|
||||
Button backBtn = new Button("← Welteneditor");
|
||||
backBtn.setOnAction(e -> ctx.switchToWorld().run());
|
||||
Label label = new Label("AI Modell-Generator · Tripo3D");
|
||||
label.setStyle("-fx-font-weight: bold; -fx-font-size: 13;");
|
||||
ToolBar tb = new ToolBar();
|
||||
tb.getItems().addAll(backBtn, new Separator(Orientation.VERTICAL), label);
|
||||
return tb;
|
||||
}
|
||||
|
||||
private VBox buildTripoPanel() {
|
||||
VBox inner = new VBox(8);
|
||||
inner.setPadding(new Insets(10));
|
||||
|
||||
inner.getChildren().addAll(EditorUiHelper.sectionTitle("Tripo3D API"), new Separator());
|
||||
Label apiHint = EditorUiHelper.styledHint("Schlüssel unter platform.tripo3d.ai → API Keys");
|
||||
apiHint.setWrapText(true);
|
||||
TextField apiKeyField = new TextField(loadTripoApiKey());
|
||||
apiKeyField.setPromptText("tsk_xxxx…");
|
||||
apiKeyField.setStyle("-fx-font-family: monospace; -fx-font-size: 11;");
|
||||
inner.getChildren().addAll(new Label("API-Schlüssel:"), apiKeyField, apiHint);
|
||||
|
||||
inner.getChildren().addAll(EditorUiHelper.sectionTitle("Modell beschreiben"), new Separator());
|
||||
TextArea promptArea = new TextArea();
|
||||
promptArea.setPromptText("z. B. \"Ein mittelalterlicher Ritter in Ruestung\"\n"
|
||||
+ "Tipp: Englische Prompts liefern meist bessere Ergebnisse.");
|
||||
promptArea.setPrefRowCount(5);
|
||||
promptArea.setWrapText(true);
|
||||
inner.getChildren().addAll(new Label("Prompt:"), promptArea);
|
||||
|
||||
TextField nameField = new TextField();
|
||||
nameField.setPromptText("dateiname (optional)");
|
||||
inner.getChildren().addAll(new Label("Dateiname:"), nameField);
|
||||
|
||||
inner.getChildren().add(new Separator());
|
||||
Button generateBtn = new Button("Modell generieren");
|
||||
generateBtn.setMaxWidth(Double.MAX_VALUE);
|
||||
generateBtn.setStyle("-fx-font-weight: bold;");
|
||||
generateBtn.setDisable(tripoGenerating);
|
||||
|
||||
tripoProgressBar = new ProgressBar(tripoGenerating ? ProgressBar.INDETERMINATE_PROGRESS : 0);
|
||||
tripoProgressBar.setMaxWidth(Double.MAX_VALUE);
|
||||
tripoStatusLabel = new Label(tripoGenerating ? "Generierung läuft…" : "Bereit");
|
||||
tripoStatusLabel.setWrapText(true);
|
||||
tripoStatusLabel.setStyle("-fx-font-size: 11; -fx-text-fill: #444;");
|
||||
inner.getChildren().addAll(generateBtn, tripoProgressBar, tripoStatusLabel);
|
||||
|
||||
inner.getChildren().addAll(EditorUiHelper.sectionTitle("Import"), new Separator());
|
||||
tripoImportBtn = new Button("Importieren (GLB → j3o)");
|
||||
tripoImportRigBtn = new Button("Importieren + Skelett (Humanoid)");
|
||||
tripoImportBtn.setMaxWidth(Double.MAX_VALUE);
|
||||
tripoImportRigBtn.setMaxWidth(Double.MAX_VALUE);
|
||||
boolean hasPrev = tripoLastModelUrl != null && !tripoGenerating;
|
||||
tripoImportBtn.setDisable(!hasPrev);
|
||||
tripoImportRigBtn.setDisable(!hasPrev || tripoLastTaskId == null);
|
||||
inner.getChildren().addAll(tripoImportBtn, tripoImportRigBtn);
|
||||
inner.getChildren().add(EditorUiHelper.styledHint("Das Skelett-Modell eignet sich für Humanoid-Animationen."));
|
||||
|
||||
generateBtn.setOnAction(e -> {
|
||||
if (tripoGenerating) return;
|
||||
String apiKey = apiKeyField.getText().trim();
|
||||
String prompt = promptArea.getText().trim();
|
||||
if (apiKey.isEmpty()) { ctx.setStatus().accept("Bitte API-Schlüssel eingeben"); return; }
|
||||
if (prompt.isEmpty()) { ctx.setStatus().accept("Bitte Prompt eingeben"); return; }
|
||||
|
||||
saveTripoApiKey(apiKey);
|
||||
tripoGenerating = true;
|
||||
generateBtn.setDisable(true);
|
||||
tripoImportBtn.setDisable(true);
|
||||
tripoImportRigBtn.setDisable(true);
|
||||
tripoProgressBar.setProgress(ProgressBar.INDETERMINATE_PROGRESS);
|
||||
tripoStatusLabel.setText("Erstelle Aufgabe…");
|
||||
if (tripoPreviewView != null) tripoPreviewView.setImage(null);
|
||||
|
||||
new Thread(() -> {
|
||||
try {
|
||||
String taskId = TripoGenerator.createTextToModelTask(apiKey, prompt);
|
||||
tripoLastTaskId = taskId;
|
||||
Platform.runLater(() -> tripoStatusLabel.setText("Generiere… (ID: " + taskId + ")"));
|
||||
|
||||
pollUntilDone(apiKey, taskId, result -> {
|
||||
tripoLastModelUrl = result.modelUrl();
|
||||
if (result.previewUrl() != null && tripoPreviewView != null) {
|
||||
tripoPreviewView.setImage(
|
||||
new javafx.scene.image.Image(result.previewUrl(), true));
|
||||
}
|
||||
tripoProgressBar.setProgress(1.0);
|
||||
tripoStatusLabel.setText("Fertig! Modell bereit zum Import.");
|
||||
generateBtn.setDisable(false);
|
||||
tripoImportBtn.setDisable(false);
|
||||
tripoImportRigBtn.setDisable(false);
|
||||
tripoGenerating = false;
|
||||
}, err -> {
|
||||
tripoProgressBar.setProgress(0);
|
||||
tripoStatusLabel.setText("Fehler: " + err);
|
||||
generateBtn.setDisable(false);
|
||||
tripoGenerating = false;
|
||||
});
|
||||
} catch (Exception ex) {
|
||||
Platform.runLater(() -> {
|
||||
tripoProgressBar.setProgress(0);
|
||||
tripoStatusLabel.setText("Fehler: " + ex.getMessage());
|
||||
generateBtn.setDisable(false);
|
||||
tripoGenerating = false;
|
||||
});
|
||||
}
|
||||
}).start();
|
||||
});
|
||||
|
||||
tripoImportBtn.setOnAction(e -> {
|
||||
if (tripoLastModelUrl == null) return;
|
||||
String name = nameField.getText().trim();
|
||||
if (name.isEmpty()) name = "tripo_" + System.currentTimeMillis();
|
||||
importTripoModel(tripoLastModelUrl, name);
|
||||
});
|
||||
|
||||
tripoImportRigBtn.setOnAction(e -> {
|
||||
if (tripoLastTaskId == null || tripoLastModelUrl == null) return;
|
||||
String apiKey = apiKeyField.getText().trim();
|
||||
String baseName = nameField.getText().trim();
|
||||
if (baseName.isEmpty()) baseName = "tripo_" + System.currentTimeMillis();
|
||||
final String rigName = baseName + "_rigged";
|
||||
|
||||
tripoImportRigBtn.setDisable(true);
|
||||
tripoProgressBar.setProgress(ProgressBar.INDETERMINATE_PROGRESS);
|
||||
tripoStatusLabel.setText("Erstelle Skelett…");
|
||||
|
||||
final String taskId = tripoLastTaskId;
|
||||
new Thread(() -> {
|
||||
try {
|
||||
String rigTaskId = TripoGenerator.createRigTask(apiKey, taskId);
|
||||
Platform.runLater(() -> tripoStatusLabel.setText(
|
||||
"Rigging… (ID: " + rigTaskId + ")"));
|
||||
|
||||
pollUntilDone(apiKey, rigTaskId, result -> {
|
||||
tripoProgressBar.setProgress(1.0);
|
||||
tripoStatusLabel.setText("Skelett fertig! Importiere…");
|
||||
tripoImportRigBtn.setDisable(false);
|
||||
importTripoModel(result.modelUrl(), rigName);
|
||||
}, err -> {
|
||||
tripoProgressBar.setProgress(0);
|
||||
tripoStatusLabel.setText("Skelett-Fehler: " + err);
|
||||
tripoImportRigBtn.setDisable(false);
|
||||
});
|
||||
} catch (Exception ex) {
|
||||
Platform.runLater(() -> {
|
||||
tripoProgressBar.setProgress(0);
|
||||
tripoStatusLabel.setText("Fehler: " + ex.getMessage());
|
||||
tripoImportRigBtn.setDisable(false);
|
||||
});
|
||||
}
|
||||
}).start();
|
||||
});
|
||||
|
||||
ScrollPane scroll = new ScrollPane(inner);
|
||||
scroll.setFitToWidth(true);
|
||||
scroll.setHbarPolicy(ScrollPane.ScrollBarPolicy.NEVER);
|
||||
scroll.setStyle("-fx-background-color: transparent; -fx-background: transparent;");
|
||||
VBox panel = new VBox(scroll);
|
||||
VBox.setVgrow(scroll, Priority.ALWAYS);
|
||||
panel.setPrefWidth(280);
|
||||
panel.setStyle("-fx-background-color: #f0f0f0; -fx-border-color: #ccc; -fx-border-width: 0 0 0 1;");
|
||||
return panel;
|
||||
}
|
||||
|
||||
private void pollUntilDone(String apiKey, String taskId,
|
||||
java.util.function.Consumer<TripoGenerator.TaskResult> onSuccess,
|
||||
java.util.function.Consumer<String> onError) {
|
||||
while (true) {
|
||||
try { Thread.sleep(3_000); } catch (InterruptedException ie) {
|
||||
Thread.currentThread().interrupt();
|
||||
Platform.runLater(() -> onError.accept("Unterbrochen"));
|
||||
return;
|
||||
}
|
||||
try {
|
||||
TripoGenerator.TaskResult r = TripoGenerator.pollTask(apiKey, taskId);
|
||||
switch (r.status()) {
|
||||
case "success" -> { Platform.runLater(() -> onSuccess.accept(r)); return; }
|
||||
case "failed", "cancelled" -> {
|
||||
Platform.runLater(() -> onError.accept(r.status())); return;
|
||||
}
|
||||
default -> {
|
||||
int p = r.progress();
|
||||
Platform.runLater(() -> {
|
||||
if (tripoProgressBar != null) tripoProgressBar.setProgress(p / 100.0);
|
||||
if (tripoStatusLabel != null) tripoStatusLabel.setText("Generiere… " + p + "%");
|
||||
});
|
||||
}
|
||||
}
|
||||
} catch (java.io.IOException ex) {
|
||||
Platform.runLater(() -> {
|
||||
if (tripoStatusLabel != null)
|
||||
tripoStatusLabel.setText("Prüfe… (" + ex.getMessage() + ")");
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void importTripoModel(String url, String name) {
|
||||
final String safeName = name.replaceAll("[^a-zA-Z0-9_\\-]", "_");
|
||||
new Thread(() -> {
|
||||
try {
|
||||
Platform.runLater(() -> ctx.setStatus().accept("Lade Modell herunter: " + safeName + "…"));
|
||||
Path destDir = ctx.assetRoot().resolve("Models");
|
||||
Files.createDirectories(destDir);
|
||||
Path destGlb = destDir.resolve(safeName + ".glb");
|
||||
TripoGenerator.downloadFile(url, destGlb);
|
||||
|
||||
String assetPath = "Models/" + safeName + ".glb";
|
||||
Path destJ3o = destDir.resolve(safeName + ".j3o");
|
||||
ctx.input().modelConvertQueue.offer(
|
||||
new SharedInput.ModelConvertRequest(assetPath, destJ3o, destGlb, true));
|
||||
|
||||
Platform.runLater(() -> ctx.setStatus().accept("Konvertiere " + safeName + ".glb → .j3o…"));
|
||||
} catch (java.io.IOException ex) {
|
||||
Platform.runLater(() -> ctx.setStatus().accept("Import-Fehler: " + ex.getMessage()));
|
||||
}
|
||||
}).start();
|
||||
}
|
||||
|
||||
private static String loadTripoApiKey() {
|
||||
return java.util.prefs.Preferences
|
||||
.userNodeForPackage(TripoController.class)
|
||||
.get("tripo_api_key", "");
|
||||
}
|
||||
|
||||
private static void saveTripoApiKey(String key) {
|
||||
java.util.prefs.Preferences
|
||||
.userNodeForPackage(TripoController.class)
|
||||
.put("tripo_api_key", key);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,422 @@
|
||||
package de.blight.editor.toolset.viewport;
|
||||
|
||||
import de.blight.editor.SharedInput;
|
||||
import de.blight.editor.ToolContext;
|
||||
import javafx.geometry.Insets;
|
||||
import javafx.scene.image.ImageView;
|
||||
import javafx.scene.image.WritableImage;
|
||||
import javafx.scene.input.MouseButton;
|
||||
import javafx.scene.layout.StackPane;
|
||||
|
||||
public class ViewportController {
|
||||
|
||||
private final SharedInput input;
|
||||
private final WritableImage initialImage;
|
||||
private final Runnable scheduleAutoSave;
|
||||
|
||||
private ImageView viewport;
|
||||
private javafx.scene.canvas.Canvas compassCanvas;
|
||||
|
||||
private double prevDragX, prevDragY;
|
||||
private double objDragPrevX, objDragPrevY;
|
||||
private boolean objDragging = false;
|
||||
private double editPressX, editPressY;
|
||||
private int editPressAction;
|
||||
private javafx.animation.Timeline editTimer;
|
||||
|
||||
public ViewportController(SharedInput input, WritableImage jfxImage, Runnable scheduleAutoSave) {
|
||||
this.input = input;
|
||||
this.initialImage = jfxImage;
|
||||
this.scheduleAutoSave = scheduleAutoSave;
|
||||
}
|
||||
|
||||
public ImageView getViewport() { return viewport; }
|
||||
|
||||
public void setImage(WritableImage img) {
|
||||
if (viewport != null) { viewport.setImage(img); }
|
||||
}
|
||||
|
||||
public StackPane buildViewport() {
|
||||
viewport = new ImageView(initialImage);
|
||||
viewport.setPreserveRatio(false);
|
||||
viewport.setFocusTraversable(true);
|
||||
|
||||
compassCanvas = new javafx.scene.canvas.Canvas(100, 100);
|
||||
compassCanvas.setMouseTransparent(true);
|
||||
StackPane.setAlignment(compassCanvas, javafx.geometry.Pos.BOTTOM_RIGHT);
|
||||
StackPane.setMargin(compassCanvas, new Insets(0, 10, 10, 0));
|
||||
drawCompass();
|
||||
|
||||
StackPane pane = new StackPane(viewport, compassCanvas);
|
||||
pane.setStyle("-fx-background-color: #1a1a2e;");
|
||||
|
||||
javafx.animation.PauseTransition resizeDebounce =
|
||||
new javafx.animation.PauseTransition(javafx.util.Duration.millis(150));
|
||||
resizeDebounce.setOnFinished(ev -> {
|
||||
int w = (int) pane.getWidth();
|
||||
int h = (int) pane.getHeight();
|
||||
if (w > 0 && h > 0) { input.resizeRequest.set(new int[]{w, h}); }
|
||||
});
|
||||
|
||||
pane.widthProperty().addListener((o, oldW, newW) -> {
|
||||
viewport.setFitWidth(newW.doubleValue());
|
||||
resizeDebounce.playFromStart();
|
||||
});
|
||||
pane.heightProperty().addListener((o, oldH, newH) -> {
|
||||
viewport.setFitHeight(newH.doubleValue());
|
||||
resizeDebounce.playFromStart();
|
||||
});
|
||||
|
||||
// Camera-Rotation: Filter am StackPane (Capture-Phase) → feuert immer,
|
||||
// auch wenn Overlay-Nodes (Tab-Button, Asset-Panel) das Event abfangen würden.
|
||||
pane.addEventFilter(javafx.scene.input.MouseEvent.MOUSE_PRESSED, e -> {
|
||||
if (e.getButton() == MouseButton.MIDDLE
|
||||
|| (e.isPrimaryButtonDown() && e.isSecondaryButtonDown())
|
||||
|| (e.getButton() == MouseButton.PRIMARY && e.isAltDown())) {
|
||||
prevDragX = e.getX(); prevDragY = e.getY();
|
||||
}
|
||||
});
|
||||
pane.addEventFilter(javafx.scene.input.MouseEvent.MOUSE_DRAGGED, e -> {
|
||||
boolean bothDown = e.isPrimaryButtonDown() && e.isSecondaryButtonDown();
|
||||
boolean altLeft = e.isPrimaryButtonDown() && e.isAltDown();
|
||||
if (e.isMiddleButtonDown() || bothDown || altLeft) {
|
||||
double dx = e.getX() - prevDragX;
|
||||
double dy = e.getY() - prevDragY;
|
||||
input.addMouseDelta((int) dx, (int) dy);
|
||||
prevDragX = e.getX(); prevDragY = e.getY();
|
||||
if (bothDown) { stopEditTimer(); }
|
||||
}
|
||||
});
|
||||
|
||||
viewport.setOnMousePressed(e -> {
|
||||
viewport.requestFocus();
|
||||
boolean bothDown = e.isPrimaryButtonDown() && e.isSecondaryButtonDown();
|
||||
|
||||
if (isObjectMode()) {
|
||||
if (e.getButton() == MouseButton.PRIMARY && !bothDown) {
|
||||
input.objectClickQueue.offer(
|
||||
new SharedInput.ObjectClick((float)e.getX(), (float)e.getY(), false, e.isShiftDown()));
|
||||
objDragPrevX = e.getX(); objDragPrevY = e.getY();
|
||||
objDragging = true;
|
||||
} else if (e.getButton() == MouseButton.SECONDARY && !bothDown) {
|
||||
input.objectClickQueue.offer(
|
||||
new SharedInput.ObjectClick((float)e.getX(), (float)e.getY(), true, false));
|
||||
objDragPrevX = e.getX(); objDragPrevY = e.getY();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (input.activeLayer == SharedInput.LAYER_ROUTINE_EDITOR) {
|
||||
if (e.getButton() == MouseButton.PRIMARY) {
|
||||
input.routinePointClickQueue.offer(
|
||||
new SharedInput.RoutinePointClick((float) e.getX(), (float) e.getY()));
|
||||
} else if (e.getButton() == MouseButton.SECONDARY) {
|
||||
input.routineYawDragQueue.offer(
|
||||
new SharedInput.RoutineYawDrag((float) e.getX(), (float) e.getY(), false));
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (input.activeLayer == SharedInput.LAYER_WATERFALL) {
|
||||
if (e.getButton() == MouseButton.PRIMARY) {
|
||||
input.waterfallClickQueue.offer(
|
||||
new SharedInput.WaterfallClick((float)e.getX(), (float)e.getY(), false));
|
||||
} else if (e.getButton() == MouseButton.SECONDARY) {
|
||||
input.waterfallClickQueue.offer(
|
||||
new SharedInput.WaterfallClick((float)e.getX(), (float)e.getY(), true));
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (input.activeLayer == SharedInput.LAYER_ITEMS) {
|
||||
if (e.getButton() == MouseButton.PRIMARY && !bothDown) {
|
||||
input.itemClickQueue.offer(
|
||||
new SharedInput.ObjectClick((float)e.getX(), (float)e.getY(), false, false));
|
||||
} else if (e.getButton() == MouseButton.SECONDARY) {
|
||||
input.itemClickQueue.offer(
|
||||
new SharedInput.ObjectClick((float)e.getX(), (float)e.getY(), true, false));
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (input.activeLayer == SharedInput.LAYER_PATH_NETWORK) {
|
||||
if (e.getButton() == MouseButton.PRIMARY && !bothDown) {
|
||||
input.pathNetworkClickQueue.offer(
|
||||
new SharedInput.PathNetworkClick((float)e.getX(), (float)e.getY(), false));
|
||||
} else if (e.getButton() == MouseButton.SECONDARY && !bothDown) {
|
||||
input.pathNetworkClickQueue.offer(
|
||||
new SharedInput.PathNetworkClick((float)e.getX(), (float)e.getY(), true));
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (input.activeLayer == SharedInput.LAYER_STONE
|
||||
&& input.stoneTool.brushRadius.getValue() == 1.0) {
|
||||
// Single-Stone-Modus: nur Rechtsklick, einmalig ohne Timer
|
||||
if (e.getButton() == MouseButton.SECONDARY && !bothDown) {
|
||||
input.stoneEditQueue.offer(
|
||||
new SharedInput.StoneEdit((float) e.getX(), (float) e.getY(), -1));
|
||||
}
|
||||
} else if (bothDown) {
|
||||
stopEditTimer();
|
||||
} else if (e.getButton() == MouseButton.PRIMARY && !e.isAltDown()) {
|
||||
boolean singleClickLayer = input.activeLayer == SharedInput.LAYER_PLAY_TOOL
|
||||
|| input.activeLayer == SharedInput.LAYER_ZONEN
|
||||
|| input.activeLayer == SharedInput.LAYER_SOUND_AREAS
|
||||
|| input.activeLayer == SharedInput.LAYER_AREAS
|
||||
|| input.activeLayer == SharedInput.LAYER_LOCATION_ZONES
|
||||
|| input.activeLayer == SharedInput.LAYER_WATER;
|
||||
if (input.activeLayer == SharedInput.LAYER_PLAY_TOOL) {
|
||||
// Einzel-Klick ohne Edit-Timer (verhindert Dauer-Spam)
|
||||
input.playToolClickQueue.offer(
|
||||
new SharedInput.PlayToolClick((float) e.getX(), (float) e.getY()));
|
||||
} else if (singleClickLayer) {
|
||||
submitEdit(e.getX(), e.getY(), +1);
|
||||
} else {
|
||||
editPressX = e.getX(); editPressY = e.getY(); editPressAction = +1;
|
||||
submitEdit(editPressX, editPressY, editPressAction);
|
||||
startEditTimer();
|
||||
}
|
||||
} else if (e.getButton() == MouseButton.SECONDARY) {
|
||||
boolean singleClickLayer = input.activeLayer == SharedInput.LAYER_ZONEN
|
||||
|| input.activeLayer == SharedInput.LAYER_SOUND_AREAS
|
||||
|| input.activeLayer == SharedInput.LAYER_AREAS
|
||||
|| input.activeLayer == SharedInput.LAYER_LOCATION_ZONES
|
||||
|| input.activeLayer == SharedInput.LAYER_WATER;
|
||||
editPressX = e.getX(); editPressY = e.getY(); editPressAction = -1;
|
||||
submitEdit(editPressX, editPressY, editPressAction);
|
||||
if (!singleClickLayer) { startEditTimer(); }
|
||||
}
|
||||
});
|
||||
|
||||
viewport.setOnMouseDragged(e -> {
|
||||
if (isObjectMode()) {
|
||||
if (objDragging && e.isPrimaryButtonDown()
|
||||
&& !e.isSecondaryButtonDown() && !e.isMiddleButtonDown() && !e.isAltDown()) {
|
||||
float dx = (float)(e.getX() - objDragPrevX);
|
||||
float dy = (float)(e.getY() - objDragPrevY);
|
||||
input.objectDragQueue.offer(new SharedInput.ObjectDrag(dx, dy));
|
||||
objDragPrevX = e.getX(); objDragPrevY = e.getY();
|
||||
} else if (e.isSecondaryButtonDown() && !e.isPrimaryButtonDown()
|
||||
&& !e.isMiddleButtonDown()
|
||||
&& input.activeLayer == SharedInput.LAYER_OBJECTS) {
|
||||
float dx = (float)(e.getX() - objDragPrevX);
|
||||
input.objectRotateDragQueue.offer(new SharedInput.ObjectRotateDrag(dx));
|
||||
objDragPrevX = e.getX(); objDragPrevY = e.getY();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (input.activeLayer == SharedInput.LAYER_WATER && e.isPrimaryButtonDown()) {
|
||||
input.waterDragQueue.offer(new SharedInput.WaterDrag((float) e.getX(), (float) e.getY()));
|
||||
input.mouseScreenX = (float) e.getX();
|
||||
input.mouseScreenY = (float) e.getY();
|
||||
return;
|
||||
}
|
||||
|
||||
if (input.activeLayer == SharedInput.LAYER_WATERFALL && e.isPrimaryButtonDown()) {
|
||||
input.waterfallDragQueue.offer(new SharedInput.WaterfallDrag((float) e.getX(), (float) e.getY()));
|
||||
return;
|
||||
}
|
||||
|
||||
if (input.activeLayer == SharedInput.LAYER_ROUTINE_EDITOR && e.isSecondaryButtonDown()) {
|
||||
input.routineYawDragQueue.offer(
|
||||
new SharedInput.RoutineYawDrag((float) e.getX(), (float) e.getY(), false));
|
||||
return;
|
||||
}
|
||||
|
||||
// Play-Tool EDIT: Drag-Events an JME3 weiterleiten
|
||||
if (input.activeLayer == SharedInput.LAYER_PLAY_TOOL
|
||||
&& input.playToolMode == SharedInput.PlayToolMode.EDIT
|
||||
&& e.isPrimaryButtonDown()) {
|
||||
input.playToolDragQueue.offer(
|
||||
new SharedInput.PlayToolDrag((float) e.getX(), (float) e.getY()));
|
||||
return;
|
||||
}
|
||||
|
||||
if (e.isPrimaryButtonDown() || e.isSecondaryButtonDown()) {
|
||||
editPressX = e.getX();
|
||||
editPressY = e.getY();
|
||||
input.mouseScreenX = (float) e.getX();
|
||||
input.mouseScreenY = (float) e.getY();
|
||||
}
|
||||
});
|
||||
|
||||
viewport.setOnMouseReleased(e -> {
|
||||
objDragging = false;
|
||||
if (!isObjectMode()) { stopEditTimer(); }
|
||||
if (input.activeLayer == SharedInput.LAYER_WATER) {
|
||||
input.waterDragEnd = true;
|
||||
}
|
||||
if (input.activeLayer == SharedInput.LAYER_WATERFALL) {
|
||||
input.waterfallDragEnd = true;
|
||||
}
|
||||
if (input.activeLayer == SharedInput.LAYER_ROUTINE_EDITOR) {
|
||||
input.routineYawDragQueue.offer(
|
||||
new SharedInput.RoutineYawDrag((float) e.getX(), (float) e.getY(), true));
|
||||
}
|
||||
if (input.activeLayer == SharedInput.LAYER_PLAY_TOOL) {
|
||||
input.playToolMouseUp = true;
|
||||
}
|
||||
if (input.vertexSnapEnabled
|
||||
&& input.objectSelectionMode == SharedInput.SEL_MODE_VERTEX) {
|
||||
input.vertexSnapTrigger = true;
|
||||
}
|
||||
});
|
||||
|
||||
pane.setOnMouseMoved(e -> {
|
||||
input.mouseScreenX = (float) e.getX();
|
||||
input.mouseScreenY = (float) e.getY();
|
||||
});
|
||||
pane.setOnMouseExited(e -> input.mouseScreenX = -1f);
|
||||
|
||||
viewport.setOnScroll(e -> {
|
||||
int steps = (int) Math.signum(e.getDeltaY());
|
||||
if (e.isControlDown()) { steps *= 10; }
|
||||
input.scrollAccum.addAndGet(steps);
|
||||
});
|
||||
|
||||
return pane;
|
||||
}
|
||||
|
||||
private boolean isObjectMode() {
|
||||
return input.activeLayer == SharedInput.LAYER_OBJECTS
|
||||
|| input.activeLayer == SharedInput.LAYER_OBJECTS_EDIT
|
||||
|| input.activeLayer == SharedInput.LAYER_AUSWAHL;
|
||||
}
|
||||
|
||||
private void startEditTimer() {
|
||||
stopEditTimer();
|
||||
editTimer = new javafx.animation.Timeline(
|
||||
new javafx.animation.KeyFrame(javafx.util.Duration.millis(50),
|
||||
ev -> submitEdit(editPressX, editPressY, editPressAction))
|
||||
);
|
||||
editTimer.setCycleCount(javafx.animation.Timeline.INDEFINITE);
|
||||
editTimer.play();
|
||||
}
|
||||
|
||||
private void stopEditTimer() {
|
||||
if (editTimer != null) { editTimer.stop(); editTimer = null; }
|
||||
}
|
||||
|
||||
private void submitEdit(double x, double y, int action) {
|
||||
switch (input.activeLayer) {
|
||||
case 0 -> input.editQueue.offer(new SharedInput.TerrainEdit((float) x, (float) y, action));
|
||||
case 3 -> input.grassEditQueue.offer(new SharedInput.GrassEdit((float) x, (float) y, action));
|
||||
case SharedInput.LAYER_GRASS_VERTEX -> {
|
||||
input.grassVertexEditQueue.offer(new SharedInput.GrassVertexEdit((float) x, (float) y, action));
|
||||
if (action < 0) { input.terrainGrassEditQueue.offer(new SharedInput.GrassVertexEdit((float) x, (float) y, action)); }
|
||||
}
|
||||
case SharedInput.LAYER_TERRAIN_GRASS -> {
|
||||
input.terrainGrassEditQueue.offer(new SharedInput.GrassVertexEdit((float) x, (float) y, action));
|
||||
if (action < 0) { input.grassVertexEditQueue.offer(new SharedInput.GrassVertexEdit((float) x, (float) y, action)); }
|
||||
}
|
||||
case 4 -> {
|
||||
SharedInput.TextureEdit te = new SharedInput.TextureEdit((float) x, (float) y, action);
|
||||
input.textureEditQueue.offer(te);
|
||||
input.voxelTextureEditQueue.offer(te);
|
||||
}
|
||||
case SharedInput.LAYER_LIGHTS ->
|
||||
input.lightClickQueue.offer(new SharedInput.LightClick((float) x, (float) y, action < 0));
|
||||
case SharedInput.LAYER_EMITTERS ->
|
||||
input.emitterClickQueue.offer(new SharedInput.EmitterClick((float) x, (float) y, action < 0));
|
||||
case SharedInput.LAYER_WATER ->
|
||||
input.waterClickQueue.offer(new SharedInput.WaterClick((float) x, (float) y, action < 0));
|
||||
case SharedInput.LAYER_SOUND_AREAS ->
|
||||
input.soundAreaClickQueue.offer(new SharedInput.SoundAreaClick((float) x, (float) y, action < 0));
|
||||
case SharedInput.LAYER_AREAS ->
|
||||
input.areaClickQueue.offer(new SharedInput.AreaClick((float) x, (float) y, action < 0));
|
||||
case SharedInput.LAYER_LOCATION_ZONES ->
|
||||
input.locationZoneClickQueue.offer(new SharedInput.LocationZoneClick((float) x, (float) y, action < 0));
|
||||
case SharedInput.LAYER_ZONEN -> {
|
||||
input.soundAreaClickQueue.offer(new SharedInput.SoundAreaClick((float) x, (float) y, action < 0));
|
||||
input.areaClickQueue.offer(new SharedInput.AreaClick((float) x, (float) y, action < 0));
|
||||
input.locationZoneClickQueue.offer(new SharedInput.LocationZoneClick((float) x, (float) y, action < 0));
|
||||
}
|
||||
case SharedInput.LAYER_VOXEL_CLIFF ->
|
||||
input.voxelCliffClickQueue.offer(new SharedInput.VoxelCliffClick((float) x, (float) y, action < 0));
|
||||
case SharedInput.LAYER_PLAY_TOOL -> {
|
||||
if (action > 0) {
|
||||
input.playToolClickQueue.offer(new SharedInput.PlayToolClick((float) x, (float) y));
|
||||
}
|
||||
}
|
||||
case SharedInput.LAYER_VOXEL ->
|
||||
input.voxelEditQueue.offer(new SharedInput.VoxelEdit((float) x, (float) y, action));
|
||||
case SharedInput.LAYER_STONE ->
|
||||
input.stoneEditQueue.offer(new SharedInput.StoneEdit((float) x, (float) y, action));
|
||||
case SharedInput.LAYER_MODEL_INTERACTABLE -> {
|
||||
if (action > 0) {
|
||||
input.modelInteractableClickQueue.offer(
|
||||
new SharedInput.ModelInteractableClick((float) x, (float) y));
|
||||
}
|
||||
}
|
||||
}
|
||||
scheduleAutoSave.run();
|
||||
}
|
||||
|
||||
public void drawCompass() {
|
||||
if (compassCanvas == null) { return; }
|
||||
final double S = compassCanvas.getWidth(); // 100
|
||||
final double cx = S / 2.0;
|
||||
final double cy = S / 2.0;
|
||||
final double R = cx - 3; // Radius bis zum Rand
|
||||
|
||||
javafx.scene.canvas.GraphicsContext gc = compassCanvas.getGraphicsContext2D();
|
||||
gc.clearRect(0, 0, S, S);
|
||||
|
||||
// Hintergrund: dunkler Kreis
|
||||
gc.setFill(javafx.scene.paint.Color.rgb(12, 12, 24, 0.82));
|
||||
gc.fillOval(2, 2, S - 4, S - 4);
|
||||
gc.setStroke(javafx.scene.paint.Color.rgb(100, 100, 160, 0.85));
|
||||
gc.setLineWidth(1.5);
|
||||
gc.strokeOval(2, 2, S - 4, S - 4);
|
||||
|
||||
// Rotierende Rose: -camYaw so dass die aktuelle Blickrichtung oben erscheint
|
||||
gc.save();
|
||||
gc.translate(cx, cy);
|
||||
gc.rotate(-input.camYaw);
|
||||
|
||||
// Tick-Striche an 8 Positionen (45°-Schritte)
|
||||
for (int i = 0; i < 8; i++) {
|
||||
double a = Math.toRadians(i * 45.0);
|
||||
double len = (i % 2 == 0) ? 9 : 5;
|
||||
gc.setStroke(javafx.scene.paint.Color.rgb(160, 160, 200, 0.65));
|
||||
gc.setLineWidth(i % 2 == 0 ? 1.5 : 1.0);
|
||||
gc.strokeLine(
|
||||
Math.sin(a) * (R - len), -Math.cos(a) * (R - len),
|
||||
Math.sin(a) * R, -Math.cos(a) * R);
|
||||
}
|
||||
|
||||
// Himmelsrichtungen (N/E/S/W), Text durch Gegenrotation immer lesbar
|
||||
String[] dirs = {"N", "E", "S", "W"};
|
||||
double[] angles = {0.0, 90.0, 180.0, 270.0};
|
||||
for (int i = 0; i < 4; i++) {
|
||||
double a = Math.toRadians(angles[i]);
|
||||
double tx = Math.sin(a) * (R - 16);
|
||||
double ty = -Math.cos(a) * (R - 16);
|
||||
gc.save();
|
||||
gc.translate(tx, ty);
|
||||
gc.rotate(input.camYaw); // Gegenrotation zur Rose → Buchstabe immer aufrecht
|
||||
gc.setFill(i == 0
|
||||
? javafx.scene.paint.Color.rgb(255, 80, 80)
|
||||
: javafx.scene.paint.Color.rgb(210, 210, 230));
|
||||
gc.setFont(javafx.scene.text.Font.font(
|
||||
"System", javafx.scene.text.FontWeight.BOLD, i == 0 ? 13 : 11));
|
||||
gc.fillText(dirs[i], -4.5, 5.0);
|
||||
gc.restore();
|
||||
}
|
||||
|
||||
gc.restore(); // Ende rotierende Rose
|
||||
|
||||
// Fixer Richtungszeiger oben (gelbes Dreieck, zeigt immer die Blickrichtung)
|
||||
gc.setFill(javafx.scene.paint.Color.rgb(255, 220, 60, 0.95));
|
||||
gc.fillPolygon(
|
||||
new double[]{cx, cx - 5, cx + 5},
|
||||
new double[]{cy - R + 5, cy - R + 16, cy - R + 16},
|
||||
3);
|
||||
|
||||
// Mittelpunkt
|
||||
gc.setFill(javafx.scene.paint.Color.rgb(200, 200, 240, 0.85));
|
||||
gc.fillOval(cx - 2.5, cy - 2.5, 5, 5);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,511 @@
|
||||
package de.blight.editor.toolset.world;
|
||||
|
||||
import de.blight.editor.ProjectRoot;
|
||||
import de.blight.editor.SharedInput;
|
||||
import de.blight.editor.ToolContext;
|
||||
import de.blight.editor.toolset.EditorUiHelper;
|
||||
import javafx.geometry.Insets;
|
||||
import javafx.scene.control.*;
|
||||
import javafx.scene.layout.*;
|
||||
|
||||
/**
|
||||
* Owns the world-zone side panels:
|
||||
* Auswahl, Wege, Zonen (unified), SoundArea, Area, LocationZone.
|
||||
*/
|
||||
public class WorldPanelController {
|
||||
|
||||
private final ToolContext ctx;
|
||||
private final Runnable scheduleAutoSave;
|
||||
|
||||
// Dynamic VBox areas rebuilt by update*Panel methods
|
||||
private VBox auswahlDynamicContent;
|
||||
private VBox zonenDynamicContent;
|
||||
private VBox soundAreaDynamicContent;
|
||||
private VBox areaDynamicContent;
|
||||
private VBox locationZoneDynamicContent;
|
||||
private Runnable zonenKindStyleUpdater;
|
||||
|
||||
public WorldPanelController(ToolContext ctx, Runnable scheduleAutoSave) {
|
||||
this.ctx = ctx;
|
||||
this.scheduleAutoSave = scheduleAutoSave;
|
||||
}
|
||||
|
||||
// ── Public update methods called from EditorApp AnimationTimer ─────────────
|
||||
|
||||
public void updateAuswahlItemPanel(String info) {
|
||||
if (auswahlDynamicContent == null) return;
|
||||
auswahlDynamicContent.getChildren().clear();
|
||||
if (info == null) {
|
||||
Label noSel = new Label("Kein Element ausgewählt");
|
||||
noSel.setStyle("-fx-text-fill: #888;");
|
||||
auswahlDynamicContent.getChildren().add(noSel);
|
||||
return;
|
||||
}
|
||||
String[] parts = info.split("\\|");
|
||||
String itemId = parts.length > 1 ? parts[1] : "?";
|
||||
Label idLabel = new Label("Item: " + itemId);
|
||||
idLabel.setStyle("-fx-font-weight: bold;");
|
||||
Button deleteBtn = new Button("Löschen");
|
||||
deleteBtn.setMaxWidth(Double.MAX_VALUE);
|
||||
deleteBtn.setStyle("-fx-background-color: #c0392b; -fx-text-fill: white;");
|
||||
deleteBtn.setOnAction(e -> ctx.input().deleteSelectedItemRequested = true);
|
||||
auswahlDynamicContent.getChildren().addAll(idLabel, deleteBtn);
|
||||
}
|
||||
|
||||
public void updateZonenPanel(String kind) {
|
||||
if (zonenDynamicContent == null) return;
|
||||
zonenDynamicContent.getChildren().clear();
|
||||
switch (kind != null ? kind : "") {
|
||||
case "area" -> {
|
||||
VBox saved = areaDynamicContent;
|
||||
areaDynamicContent = zonenDynamicContent;
|
||||
updateAreaPanel(ctx.input().selectedAreaInfo);
|
||||
areaDynamicContent = saved;
|
||||
}
|
||||
case "sound" -> {
|
||||
VBox saved = soundAreaDynamicContent;
|
||||
soundAreaDynamicContent = zonenDynamicContent;
|
||||
updateSoundAreaPanel(ctx.input().selectedSoundAreaInfo);
|
||||
soundAreaDynamicContent = saved;
|
||||
}
|
||||
case "location" -> {
|
||||
VBox saved = locationZoneDynamicContent;
|
||||
locationZoneDynamicContent = zonenDynamicContent;
|
||||
updateLocationZonePanel(ctx.input().selectedLocationZoneInfo);
|
||||
locationZoneDynamicContent = saved;
|
||||
}
|
||||
default -> {
|
||||
Label noSel = new Label("Keine Zone ausgewählt");
|
||||
noSel.setStyle("-fx-text-fill: #888;");
|
||||
zonenDynamicContent.getChildren().add(noSel);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void runZonenKindStyleUpdater() {
|
||||
if (zonenKindStyleUpdater != null) zonenKindStyleUpdater.run();
|
||||
}
|
||||
|
||||
public void updateSoundAreaPanel(String info) {
|
||||
if (soundAreaDynamicContent == null) return;
|
||||
soundAreaDynamicContent.getChildren().clear();
|
||||
if (info == null) {
|
||||
Label noSel = new Label("Kein Bereich ausgewählt");
|
||||
noSel.setStyle("-fx-text-fill: #888;");
|
||||
soundAreaDynamicContent.getChildren().add(noSel);
|
||||
return;
|
||||
}
|
||||
String[] p = info.split("\\|", -1);
|
||||
if (p.length < 4) return;
|
||||
try {
|
||||
int idx = Integer.parseInt(p[0]);
|
||||
float volume = Float.parseFloat(p[2]);
|
||||
boolean xfade = Boolean.parseBoolean(p[3]);
|
||||
final String[] soundPath = {p[1]};
|
||||
final float[] vol = {volume};
|
||||
final boolean[] cf = {xfade};
|
||||
|
||||
Label soundLabel = new Label("Sound: " + (soundPath[0].isEmpty() ? "(keine)" : soundPath[0]));
|
||||
soundLabel.setStyle("-fx-font-size: 11; -fx-text-fill: #555;");
|
||||
soundLabel.setWrapText(true);
|
||||
|
||||
Button browseBtn = new Button("Sound waehlen...");
|
||||
browseBtn.setMaxWidth(Double.MAX_VALUE);
|
||||
browseBtn.setOnAction(e -> {
|
||||
java.nio.file.Path assetRoot = ProjectRoot.resolve("blight-assets", "src", "main", "resources");
|
||||
new de.blight.editor.ui.SoundChooser(assetRoot, de.blight.editor.ui.SoundChooser.Mode.ALL)
|
||||
.showAndWait()
|
||||
.ifPresent(rel -> {
|
||||
soundPath[0] = rel;
|
||||
soundLabel.setText("Sound: " + rel);
|
||||
sendSoundAreaUpdate(idx, soundPath[0], vol[0], cf[0]);
|
||||
});
|
||||
});
|
||||
|
||||
Label volLbl = new Label("Lautstärke");
|
||||
volLbl.setStyle("-fx-font-size: 11;");
|
||||
Slider volSlider = new Slider(0, 1, vol[0]);
|
||||
volSlider.setShowTickLabels(false);
|
||||
volSlider.valueProperty().addListener((o, ov, nv) -> {
|
||||
vol[0] = nv.floatValue();
|
||||
sendSoundAreaUpdate(idx, soundPath[0], vol[0], cf[0]);
|
||||
});
|
||||
|
||||
CheckBox xfadeCB = new CheckBox("Loop-Crossfade");
|
||||
xfadeCB.setSelected(cf[0]);
|
||||
xfadeCB.setOnAction(e -> {
|
||||
cf[0] = xfadeCB.isSelected();
|
||||
sendSoundAreaUpdate(idx, soundPath[0], vol[0], cf[0]);
|
||||
});
|
||||
|
||||
Button delBtn = new Button("Löschen");
|
||||
delBtn.setMaxWidth(Double.MAX_VALUE);
|
||||
delBtn.setStyle("-fx-text-fill: #c0392b;");
|
||||
delBtn.setOnAction(e -> ctx.input().deleteSoundAreaRequested = true);
|
||||
|
||||
soundAreaDynamicContent.getChildren().addAll(
|
||||
soundLabel, browseBtn, new Separator(),
|
||||
volLbl, EditorUiHelper.withField(volSlider, "%.2f"), xfadeCB,
|
||||
new Separator(), delBtn);
|
||||
|
||||
} catch (NumberFormatException ignored) {}
|
||||
}
|
||||
|
||||
public void updateAreaPanel(String info) {
|
||||
if (areaDynamicContent == null) return;
|
||||
areaDynamicContent.getChildren().clear();
|
||||
if (info == null) {
|
||||
Label noSel = new Label("Keine Area ausgewählt");
|
||||
noSel.setStyle("-fx-text-fill: #888;");
|
||||
areaDynamicContent.getChildren().add(noSel);
|
||||
return;
|
||||
}
|
||||
String[] p = info.split("\\|", 3);
|
||||
if (p.length < 1) return;
|
||||
try {
|
||||
int idx = Integer.parseInt(p[0]);
|
||||
String currentAreaId = p.length > 1 ? p[1] : "";
|
||||
java.util.List<de.blight.common.model.trigger.Trigger> existingTriggers =
|
||||
p.length > 2
|
||||
? de.blight.common.model.trigger.TriggerIO.deserializeList(p[2])
|
||||
: new java.util.ArrayList<>();
|
||||
|
||||
java.util.List<String> areaIds = new java.util.ArrayList<>();
|
||||
areaIds.add("");
|
||||
try {
|
||||
de.blight.common.AreaDefinitionIO.load().stream()
|
||||
.map(de.blight.common.AreaDefinition::id)
|
||||
.filter(s -> !s.isBlank())
|
||||
.forEach(areaIds::add);
|
||||
} catch (Exception ignored) {}
|
||||
|
||||
Label areaLbl = new Label("Verknüpfte Area:");
|
||||
areaLbl.setStyle("-fx-font-size: 11; -fx-font-weight: bold;");
|
||||
|
||||
Label nameKeyLbl = new Label(currentAreaId.isBlank() ? "" : currentAreaId + ".name");
|
||||
nameKeyLbl.setStyle("-fx-font-size: 10; -fx-text-fill: #888; -fx-font-style: italic;");
|
||||
|
||||
de.blight.editor.ui.TriggerListEditor[] editorHolder = {null};
|
||||
|
||||
ComboBox<String> areaCombo = new ComboBox<>();
|
||||
areaCombo.getItems().setAll(areaIds);
|
||||
areaCombo.setValue(areaIds.contains(currentAreaId) ? currentAreaId : "");
|
||||
areaCombo.setMaxWidth(Double.MAX_VALUE);
|
||||
areaCombo.setPromptText("Area wählen…");
|
||||
areaCombo.valueProperty().addListener((obs, o, n) -> {
|
||||
String aid = n != null ? n : "";
|
||||
nameKeyLbl.setText(aid.isBlank() ? "" : aid + ".name");
|
||||
java.util.List<de.blight.common.model.trigger.Trigger> curTriggers =
|
||||
editorHolder[0] != null
|
||||
? new java.util.ArrayList<>(editorHolder[0].getTriggers())
|
||||
: existingTriggers;
|
||||
ctx.input().pendingArea.set(new de.blight.common.PlacedArea(
|
||||
new float[0], new float[0], aid, curTriggers));
|
||||
scheduleAutoSave.run();
|
||||
});
|
||||
|
||||
Label triggerLbl = new Label("Trigger:");
|
||||
triggerLbl.setStyle("-fx-font-size: 11; -fx-font-weight: bold;");
|
||||
|
||||
editorHolder[0] = new de.blight.editor.ui.TriggerListEditor(existingTriggers, () -> {
|
||||
String aid = areaCombo.getValue() != null ? areaCombo.getValue() : currentAreaId;
|
||||
ctx.input().pendingArea.set(new de.blight.common.PlacedArea(
|
||||
new float[0], new float[0], aid,
|
||||
new java.util.ArrayList<>(editorHolder[0].getTriggers())));
|
||||
scheduleAutoSave.run();
|
||||
});
|
||||
|
||||
Button delBtn = new Button("Löschen");
|
||||
delBtn.setMaxWidth(Double.MAX_VALUE);
|
||||
delBtn.setStyle("-fx-text-fill: #c0392b;");
|
||||
delBtn.setOnAction(e -> { ctx.input().deleteAreaRequested = true; scheduleAutoSave.run(); });
|
||||
|
||||
areaDynamicContent.getChildren().addAll(
|
||||
areaLbl, areaCombo, nameKeyLbl,
|
||||
new Separator(),
|
||||
triggerLbl, editorHolder[0],
|
||||
new Separator(),
|
||||
delBtn);
|
||||
|
||||
} catch (NumberFormatException ignored) {}
|
||||
}
|
||||
|
||||
public void updateLocationZonePanel(String info) {
|
||||
if (locationZoneDynamicContent == null) return;
|
||||
locationZoneDynamicContent.getChildren().clear();
|
||||
if (info == null) {
|
||||
Label noSel = new Label("Keine Location ausgewählt");
|
||||
noSel.setStyle("-fx-text-fill: #888;");
|
||||
locationZoneDynamicContent.getChildren().add(noSel);
|
||||
return;
|
||||
}
|
||||
String[] p = info.split("\\|", 3);
|
||||
if (p.length < 1) return;
|
||||
try {
|
||||
int idx = Integer.parseInt(p[0]);
|
||||
String nameId = p.length > 1 ? p[1] : "";
|
||||
java.util.List<de.blight.common.model.trigger.Trigger> existingTriggers =
|
||||
p.length > 2
|
||||
? de.blight.common.model.trigger.TriggerIO.deserializeList(p[2])
|
||||
: new java.util.ArrayList<>();
|
||||
|
||||
java.util.List<String> locationIds = new java.util.ArrayList<>();
|
||||
locationIds.add("");
|
||||
try {
|
||||
de.blight.common.LocationIO.load().stream()
|
||||
.map(de.blight.common.model.Location::getId)
|
||||
.filter(s -> !s.isBlank())
|
||||
.forEach(locationIds::add);
|
||||
} catch (Exception ignored) {}
|
||||
|
||||
Label nameLbl = new Label("Verknüpfte Location:");
|
||||
nameLbl.setStyle("-fx-font-size: 11; -fx-font-weight: bold;");
|
||||
|
||||
Label nameKeyLbl = new Label(nameId.isBlank() ? "" : nameId + ".name");
|
||||
nameKeyLbl.setStyle("-fx-font-size: 10; -fx-text-fill: #888; -fx-font-style: italic;");
|
||||
|
||||
ComboBox<String> locationCombo = new ComboBox<>();
|
||||
locationCombo.getItems().setAll(locationIds);
|
||||
locationCombo.setValue(locationIds.contains(nameId) ? nameId : "");
|
||||
locationCombo.setMaxWidth(Double.MAX_VALUE);
|
||||
locationCombo.setPromptText("Location wählen…");
|
||||
locationCombo.valueProperty().addListener((obs, o, n) -> {
|
||||
String lid = n != null ? n : "";
|
||||
nameKeyLbl.setText(lid.isBlank() ? "" : lid + ".name");
|
||||
ctx.input().pendingLocationZone.set(new de.blight.common.PlacedLocationZone(
|
||||
new float[0], new float[0], lid, existingTriggers));
|
||||
scheduleAutoSave.run();
|
||||
});
|
||||
|
||||
Button delBtn = new Button("Löschen");
|
||||
delBtn.setMaxWidth(Double.MAX_VALUE);
|
||||
delBtn.setStyle("-fx-text-fill: #c0392b;");
|
||||
delBtn.setOnAction(e -> { ctx.input().deleteLocationZoneRequested = true; scheduleAutoSave.run(); });
|
||||
|
||||
locationZoneDynamicContent.getChildren().addAll(
|
||||
nameLbl, locationCombo, nameKeyLbl,
|
||||
new Separator(),
|
||||
delBtn);
|
||||
|
||||
} catch (NumberFormatException ignored) {}
|
||||
}
|
||||
|
||||
// ── Panel builders ─────────────────────────────────────────────────────────
|
||||
|
||||
public VBox buildAuswahlPanel() {
|
||||
VBox panel = new VBox(10);
|
||||
panel.setPadding(new Insets(10));
|
||||
panel.setPrefWidth(260);
|
||||
panel.setStyle("-fx-background-color: #f0f0f0; -fx-border-color: #ccc; -fx-border-width: 0 0 0 1;");
|
||||
Label title = new Label("Auswahl");
|
||||
title.setStyle("-fx-font-weight: bold; -fx-font-size: 13; -fx-text-fill: #111;");
|
||||
panel.getChildren().addAll(title, new Separator());
|
||||
panel.getChildren().addAll(
|
||||
EditorUiHelper.styledHint("L-Klick: Element auswählen"),
|
||||
EditorUiHelper.styledHint(" → wechselt automatisch zum"),
|
||||
EditorUiHelper.styledHint(" passenden Werkzeug"),
|
||||
new Separator(),
|
||||
EditorUiHelper.styledHint("Selektierbar:"),
|
||||
EditorUiHelper.styledHint(" + Platzierte Objekte"),
|
||||
EditorUiHelper.styledHint(" * Lichter / Emitter"),
|
||||
EditorUiHelper.styledHint(" ~ Wasser / Wasserfall"),
|
||||
EditorUiHelper.styledHint(" -- Wege / Knoten"),
|
||||
EditorUiHelper.styledHint(" [ ] Zonen"),
|
||||
EditorUiHelper.styledHint(" o Spawnpunkte (Items)"),
|
||||
new Separator());
|
||||
auswahlDynamicContent = new VBox(6);
|
||||
Label noSel = new Label("Kein Element ausgewählt");
|
||||
noSel.setStyle("-fx-text-fill: #888;");
|
||||
auswahlDynamicContent.getChildren().add(noSel);
|
||||
panel.getChildren().add(auswahlDynamicContent);
|
||||
return panel;
|
||||
}
|
||||
|
||||
public VBox buildWegePanel() {
|
||||
VBox inner = new VBox(8);
|
||||
inner.setPadding(new Insets(10));
|
||||
inner.getChildren().addAll(
|
||||
EditorUiHelper.sectionTitle("Wegnetz"),
|
||||
new Separator(),
|
||||
EditorUiHelper.styledHint("L-Klick: neuen Knoten setzen"),
|
||||
EditorUiHelper.styledHint("L-Klick auf Knoten: auswählen"),
|
||||
EditorUiHelper.styledHint(" (2 gewählte → Kante erzeugen)"),
|
||||
EditorUiHelper.styledHint("L-Klick + gewählter Knoten:"),
|
||||
EditorUiHelper.styledHint(" neuen Knoten + Kante"),
|
||||
EditorUiHelper.styledHint("R-Klick auf Knoten: löschen"),
|
||||
EditorUiHelper.styledHint("R-Klick auf Terrain: abwählen"));
|
||||
ScrollPane scroll = new ScrollPane(inner);
|
||||
scroll.setFitToWidth(true);
|
||||
scroll.setHbarPolicy(ScrollPane.ScrollBarPolicy.NEVER);
|
||||
scroll.setStyle("-fx-background-color: transparent; -fx-background: transparent;");
|
||||
VBox panel = new VBox(scroll);
|
||||
VBox.setVgrow(scroll, Priority.ALWAYS);
|
||||
panel.setPrefWidth(260);
|
||||
panel.setStyle("-fx-background-color: #f0f0f0; -fx-border-color: #ccc; -fx-border-width: 0 0 0 1;");
|
||||
return panel;
|
||||
}
|
||||
|
||||
public VBox buildZonenPanel() {
|
||||
VBox inner = new VBox(8);
|
||||
inner.setPadding(new Insets(10));
|
||||
|
||||
final String STYLE_AREA_ON = "-fx-background-color: #e05050; -fx-text-fill: white; -fx-font-weight: bold; -fx-border-color: white; -fx-border-width: 2;";
|
||||
final String STYLE_AREA_OFF = "-fx-background-color: #b03030; -fx-text-fill: #ddd; -fx-font-weight: bold;";
|
||||
final String STYLE_SOUND_ON = "-fx-background-color: #e07818; -fx-text-fill: white; -fx-font-weight: bold; -fx-border-color: white; -fx-border-width: 2;";
|
||||
final String STYLE_SOUND_OFF = "-fx-background-color: #a05010; -fx-text-fill: #ddd; -fx-font-weight: bold;";
|
||||
final String STYLE_LOC_ON = "-fx-background-color: #c8a800; -fx-text-fill: white; -fx-font-weight: bold; -fx-border-color: white; -fx-border-width: 2;";
|
||||
final String STYLE_LOC_OFF = "-fx-background-color: #8a7200; -fx-text-fill: #ddd; -fx-font-weight: bold;";
|
||||
|
||||
Button areaKindBtn = new Button("+ Neue Area zeichnen");
|
||||
Button soundKindBtn = new Button("+ Neuen Sound zeichnen");
|
||||
Button locationKindBtn = new Button("+ Neue Location zeichnen");
|
||||
areaKindBtn.setMaxWidth(Double.MAX_VALUE);
|
||||
soundKindBtn.setMaxWidth(Double.MAX_VALUE);
|
||||
locationKindBtn.setMaxWidth(Double.MAX_VALUE);
|
||||
|
||||
Runnable updateKindStyles = () -> {
|
||||
String k = ctx.input().zonenNewKind;
|
||||
areaKindBtn.setStyle("area".equals(k) ? STYLE_AREA_ON : STYLE_AREA_OFF);
|
||||
soundKindBtn.setStyle("sound".equals(k) ? STYLE_SOUND_ON : STYLE_SOUND_OFF);
|
||||
locationKindBtn.setStyle("location".equals(k) ? STYLE_LOC_ON : STYLE_LOC_OFF);
|
||||
};
|
||||
updateKindStyles.run();
|
||||
zonenKindStyleUpdater = updateKindStyles;
|
||||
|
||||
areaKindBtn.setOnAction(e -> { ctx.input().zonenNewKind = "area"; updateKindStyles.run(); });
|
||||
soundKindBtn.setOnAction(e -> { ctx.input().zonenNewKind = "sound"; updateKindStyles.run(); });
|
||||
locationKindBtn.setOnAction(e -> { ctx.input().zonenNewKind = "location"; updateKindStyles.run(); });
|
||||
|
||||
areaKindBtn.setTooltip(new Tooltip(
|
||||
"Area: Musik-Zone mit Tag-, Nacht- und Kampf-Track.\nAreas dürfen sich nicht überschneiden."));
|
||||
soundKindBtn.setTooltip(new Tooltip(
|
||||
"Sound: Ambient-Soundbereich mit wählbarer Audio-Datei,\nLautstärke und Loop-Crossfade."));
|
||||
locationKindBtn.setTooltip(new Tooltip(
|
||||
"Location: Gameplay-Trigger-Zone, z.B. zum Starten\nvon Quests oder Dialogen beim Betreten."));
|
||||
|
||||
inner.getChildren().addAll(
|
||||
EditorUiHelper.sectionTitle("Neue Zone"),
|
||||
areaKindBtn, soundKindBtn, locationKindBtn,
|
||||
new Separator(),
|
||||
EditorUiHelper.styledHint("L-Klick → Polygon-Punkte setzen"),
|
||||
EditorUiHelper.styledHint("R-Klick (beim Zeichnen) → letzten Punkt rückgängig"),
|
||||
EditorUiHelper.styledHint("R-Klick (sonst) → Auswahl aufheben"),
|
||||
EditorUiHelper.styledHint("ESC → Zeichnen abbrechen"),
|
||||
EditorUiHelper.styledHint("Entf → Gewählte Zone löschen"),
|
||||
new Separator(),
|
||||
EditorUiHelper.sectionTitle("Gewählte Zone"),
|
||||
new Separator());
|
||||
|
||||
zonenDynamicContent = new VBox(6);
|
||||
Label noSel = new Label("Keine Zone ausgewählt");
|
||||
noSel.setStyle("-fx-text-fill: #888;");
|
||||
zonenDynamicContent.getChildren().add(noSel);
|
||||
inner.getChildren().add(zonenDynamicContent);
|
||||
|
||||
ScrollPane scroll = new ScrollPane(inner);
|
||||
scroll.setFitToWidth(true);
|
||||
scroll.setHbarPolicy(ScrollPane.ScrollBarPolicy.NEVER);
|
||||
scroll.setStyle("-fx-background-color: transparent; -fx-background: transparent;");
|
||||
VBox panel = new VBox(scroll);
|
||||
VBox.setVgrow(scroll, Priority.ALWAYS);
|
||||
panel.setPrefWidth(270);
|
||||
panel.setStyle("-fx-background-color: #f0f0f0; -fx-border-color: #ccc; -fx-border-width: 0 0 0 1;");
|
||||
return panel;
|
||||
}
|
||||
|
||||
public VBox buildSoundAreaPanel() {
|
||||
VBox inner = new VBox(8);
|
||||
inner.setPadding(new Insets(10));
|
||||
inner.getChildren().addAll(
|
||||
EditorUiHelper.sectionTitle("Sound-Bereiche"),
|
||||
EditorUiHelper.styledHint("L-Klick → Polygon-Punkte setzen"),
|
||||
EditorUiHelper.styledHint("R-Klick → Polygon schließen / Auswahl aufheben"),
|
||||
EditorUiHelper.styledHint("Entf → Bereich löschen"),
|
||||
new Separator(),
|
||||
EditorUiHelper.sectionTitle("Gewählter Bereich"),
|
||||
new Separator());
|
||||
soundAreaDynamicContent = new VBox(6);
|
||||
Label noSel = new Label("Kein Bereich ausgewählt");
|
||||
noSel.setStyle("-fx-text-fill: #888;");
|
||||
soundAreaDynamicContent.getChildren().add(noSel);
|
||||
inner.getChildren().add(soundAreaDynamicContent);
|
||||
ScrollPane scroll = new ScrollPane(inner);
|
||||
scroll.setFitToWidth(true);
|
||||
scroll.setHbarPolicy(ScrollPane.ScrollBarPolicy.NEVER);
|
||||
scroll.setStyle("-fx-background-color: transparent; -fx-background: transparent;");
|
||||
VBox panel = new VBox(scroll);
|
||||
VBox.setVgrow(scroll, Priority.ALWAYS);
|
||||
panel.setPrefWidth(270);
|
||||
panel.setStyle("-fx-background-color: #f0f0f0; -fx-border-color: #ccc; -fx-border-width: 0 0 0 1;");
|
||||
return panel;
|
||||
}
|
||||
|
||||
public VBox buildAreaPanel() {
|
||||
VBox inner = new VBox(8);
|
||||
inner.setPadding(new Insets(10));
|
||||
inner.getChildren().addAll(
|
||||
EditorUiHelper.sectionTitle("Areas"),
|
||||
EditorUiHelper.styledHint("L-Klick → Polygon-Punkte setzen"),
|
||||
EditorUiHelper.styledHint("R-Klick → Polygon schließen / Auswahl aufheben"),
|
||||
EditorUiHelper.styledHint("ESC → Zeichnen abbrechen"),
|
||||
EditorUiHelper.styledHint("Entf → Area löschen"),
|
||||
EditorUiHelper.styledHint("Areas dürfen sich nicht überschneiden"),
|
||||
new Separator(),
|
||||
EditorUiHelper.sectionTitle("Gewählte Area"),
|
||||
new Separator());
|
||||
areaDynamicContent = new VBox(6);
|
||||
Label noSel = new Label("Keine Area ausgewählt");
|
||||
noSel.setStyle("-fx-text-fill: #888;");
|
||||
areaDynamicContent.getChildren().add(noSel);
|
||||
inner.getChildren().add(areaDynamicContent);
|
||||
ScrollPane scroll = new ScrollPane(inner);
|
||||
scroll.setFitToWidth(true);
|
||||
scroll.setHbarPolicy(ScrollPane.ScrollBarPolicy.NEVER);
|
||||
scroll.setStyle("-fx-background-color: transparent; -fx-background: transparent;");
|
||||
VBox panel = new VBox(scroll);
|
||||
VBox.setVgrow(scroll, Priority.ALWAYS);
|
||||
panel.setPrefWidth(270);
|
||||
panel.setStyle("-fx-background-color: #f0f0f0; -fx-border-color: #ccc; -fx-border-width: 0 0 0 1;");
|
||||
return panel;
|
||||
}
|
||||
|
||||
public VBox buildLocationZonePanel() {
|
||||
VBox inner = new VBox(8);
|
||||
inner.setPadding(new Insets(10));
|
||||
inner.getChildren().addAll(
|
||||
EditorUiHelper.sectionTitle("Locations"),
|
||||
EditorUiHelper.styledHint("L-Klick → Polygon-Punkte setzen"),
|
||||
EditorUiHelper.styledHint("R-Klick → Polygon schließen / Auswahl aufheben"),
|
||||
EditorUiHelper.styledHint("ESC → Zeichnen abbrechen"),
|
||||
EditorUiHelper.styledHint("Entf → Location löschen"),
|
||||
EditorUiHelper.styledHint("Locations dürfen sich überschneiden"),
|
||||
new Separator(),
|
||||
EditorUiHelper.sectionTitle("Gewählte Location"),
|
||||
new Separator());
|
||||
locationZoneDynamicContent = new VBox(6);
|
||||
Label noSel = new Label("Keine Location ausgewählt");
|
||||
noSel.setStyle("-fx-text-fill: #888;");
|
||||
locationZoneDynamicContent.getChildren().add(noSel);
|
||||
inner.getChildren().add(locationZoneDynamicContent);
|
||||
ScrollPane scroll = new ScrollPane(inner);
|
||||
scroll.setFitToWidth(true);
|
||||
scroll.setHbarPolicy(ScrollPane.ScrollBarPolicy.NEVER);
|
||||
scroll.setStyle("-fx-background-color: transparent; -fx-background: transparent;");
|
||||
VBox panel = new VBox(scroll);
|
||||
VBox.setVgrow(scroll, Priority.ALWAYS);
|
||||
panel.setPrefWidth(270);
|
||||
panel.setStyle("-fx-background-color: #f0f0f0; -fx-border-color: #ccc; -fx-border-width: 0 0 0 1;");
|
||||
return panel;
|
||||
}
|
||||
|
||||
// ── Internal ──────────────────────────────────────────────────────────────
|
||||
|
||||
private void sendSoundAreaUpdate(int idx, String soundPath, float volume, boolean crossfade) {
|
||||
SharedInput in = ctx.input();
|
||||
if (in.selectedSoundAreaInfo == null) return;
|
||||
in.selectedSoundAreaInfo = idx + "|" + soundPath + "|" + volume + "|" + crossfade;
|
||||
in.pendingSoundArea.set(new de.blight.common.PlacedSoundArea(
|
||||
new float[0], new float[0], soundPath, volume, crossfade));
|
||||
scheduleAutoSave.run();
|
||||
}
|
||||
}
|
||||
@@ -1 +1,2 @@
|
||||
# x y z activationRadius texturePath imagesX imagesY startR startG startB startA endR endG endB endA startSize endSize velX velY velZ velVariation gravX gravY gravZ lowLife highLife maxParticles emitRate
|
||||
0.93604 2.50000 -3.56553 20.00000 Effects/Explosion/flame.png 2 2 1.0000 0.7000 0.1000 1.0000 1.0000 0.1000 0.0000 0.0000 0.5000 1.2000 0.0000 2.5000 0.0000 0.5000 0.0000 -0.1000 0.0000 1.0000 3.0000 60 25.0000
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -1 +1,3 @@
|
||||
# x y z r g b intensity radius
|
||||
-2.18687 3.00000 -5.43333 1.00000 1.00000 1.00000 1.00000 20.00000
|
||||
0.42349 3.00000 -10.67357 1.00000 1.00000 1.00000 1.00000 20.00000
|
||||
|
||||
Binary file not shown.
Binary file not shown.
@@ -1,2 +1,3 @@
|
||||
# polygon_points waterHeight flowDegrees speed waveScale waveAmplitude transparency waterR waterG waterB deepR deepG deepB
|
||||
155.13664,11.00000,-898.11578;120.48705,11.00000,-897.61938;107.05534,11.00000,-890.41797;96.71703,11.00000,-875.01575;98.34164,11.00000,-854.85681;182.64165,11.00000,-829.23749;195.95512,11.00000,-848.04095 11.00000 180.0 0.5000 0.008000 0.10000 0.15000 0.05000 0.25000 0.55000 0.02000 0.12000 0.30000
|
||||
219.67131,1.90000,-796.04559;197.17520,1.90000,-800.89313;192.60945,1.90000,-797.65149;190.13742,1.90000,-767.83209;221.88794,1.90000,-767.83209 1.90000 0.0 0.5000 0.008000 0.10000 0.15000 0.05000 0.25000 0.55000 0.02000 0.12000 0.30000
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
blight-map/src/main/map/chunks/voxel_16_0_08_baked_lod2.j3o
Normal file
BIN
blight-map/src/main/map/chunks/voxel_16_0_08_baked_lod2.j3o
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
blight-map/src/main/map/chunks/voxel_17_0_10.blvc.prebake
Normal file
BIN
blight-map/src/main/map/chunks/voxel_17_0_10.blvc.prebake
Normal file
Binary file not shown.
BIN
blight-map/src/main/map/chunks/voxel_17_0_10_baked_lod0.j3o
Normal file
BIN
blight-map/src/main/map/chunks/voxel_17_0_10_baked_lod0.j3o
Normal file
Binary file not shown.
BIN
blight-map/src/main/map/chunks/voxel_17_0_10_baked_lod1.j3o
Normal file
BIN
blight-map/src/main/map/chunks/voxel_17_0_10_baked_lod1.j3o
Normal file
Binary file not shown.
BIN
blight-map/src/main/map/chunks/voxel_17_0_10_baked_lod2.j3o
Normal file
BIN
blight-map/src/main/map/chunks/voxel_17_0_10_baked_lod2.j3o
Normal file
Binary file not shown.
BIN
blight-map/src/main/map/chunks/voxel_17_m1_10.blvc.prebake
Normal file
BIN
blight-map/src/main/map/chunks/voxel_17_m1_10.blvc.prebake
Normal file
Binary file not shown.
BIN
blight-map/src/main/map/chunks/voxel_17_m1_10_baked_lod0.j3o
Normal file
BIN
blight-map/src/main/map/chunks/voxel_17_m1_10_baked_lod0.j3o
Normal file
Binary file not shown.
BIN
blight-map/src/main/map/chunks/voxel_17_m1_10_baked_lod1.j3o
Normal file
BIN
blight-map/src/main/map/chunks/voxel_17_m1_10_baked_lod1.j3o
Normal file
Binary file not shown.
BIN
blight-map/src/main/map/chunks/voxel_17_m1_10_baked_lod2.j3o
Normal file
BIN
blight-map/src/main/map/chunks/voxel_17_m1_10_baked_lod2.j3o
Normal file
Binary file not shown.
Reference in New Issue
Block a user