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
|
* 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
|
* @param input Gemeinsame JavaFX ↔ JME3 Datenbasis
|
||||||
* über diesen Record auf Infrastruktur zu, nicht auf EditorApp direkt.
|
* @param jmeApp JME3-Anwendung (für StateManager-Zugriff)
|
||||||
*
|
* @param stageSupplier Liefert die primäre Stage (für Dialoge)
|
||||||
* @param input Gemeinsame JavaFX ↔ JME3 Datenbasis
|
* @param setStatus Setzt den Text im Statusbalken
|
||||||
* @param jmeApp JME3-Anwendung (für StateManager-Zugriff)
|
* @param setCenterView Setzt den Center-Node des Haupt-BorderPane
|
||||||
* @param stageSupplier Liefert die primäre Stage (für Dialoge)
|
* @param setRightPanel Setzt den Right-Node des Haupt-BorderPane
|
||||||
* @param setStatus Setzt den Text im Statusbalken
|
* @param setMainToolbar Ersetzt die aktive Werkzeug-Toolbar in der TopBar
|
||||||
* @param setCenterView Setzt den Center-Node des Haupt-BorderPane
|
* @param viewportSupplier Liefert den JME3-Viewport-StackPane
|
||||||
* @param setRightPanel Setzt den Right-Node des Haupt-BorderPane
|
* @param assetRoot Wurzelpfad der blight-assets (für Dateidialoge / IO)
|
||||||
* @param setMainToolbar Ersetzt die aktive Werkzeug-Toolbar in der TopBar
|
* @param switchToWorld Wechselt zurück zum Welteneditor
|
||||||
* @param viewportSupplier Liefert den JME3-Viewport-StackPane
|
* @param registry ToolRegistry (damit Controller sich selbst aktivieren können)
|
||||||
* @param assetRoot Wurzelpfad der blight-assets (für Dateidialoge / IO)
|
* @param setLeftPanel Setzt den Left-Node des Haupt-BorderPane
|
||||||
* @param switchToWorld Wechselt zurück zum Welteneditor (für "← Welteneditor"-Buttons)
|
* @param setBottomPanel Setzt den Bottom-Node des Haupt-BorderPane
|
||||||
* @param registry ToolRegistry (damit Controller sich selbst aktivieren können)
|
* @param centerStackSupplier Liefert den persistenten 3D-Viewport-Wrapper
|
||||||
|
* @param setCurrentTool Setzt das aktive Tool-Label in EditorApp (für Keyboard-Routing)
|
||||||
*/
|
*/
|
||||||
public record ToolContext(
|
public record ToolContext(
|
||||||
SharedInput input,
|
SharedInput input,
|
||||||
@@ -38,5 +39,9 @@ public record ToolContext(
|
|||||||
Supplier<StackPane> viewportSupplier,
|
Supplier<StackPane> viewportSupplier,
|
||||||
Path assetRoot,
|
Path assetRoot,
|
||||||
Runnable switchToWorld,
|
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 LINE_OFFSET = 0.1f;
|
||||||
private static final float POLE_HEIGHT = 5.0f;
|
private static final float POLE_HEIGHT = 5.0f;
|
||||||
private static final float CAP_SIZE = 1.2f;
|
private static final float CAP_SIZE = 2.0f;
|
||||||
private static final float HANDLE_PICK_PX = 24f;
|
private static final float HANDLE_PICK_PX = 32f;
|
||||||
private static final float EDGE_SPLIT_THRESHOLD = 1.5f;
|
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);
|
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_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_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_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_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_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_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);
|
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<Geometry> handleGeos = new ArrayList<>();
|
||||||
private final List<Material> handleMats = new ArrayList<>();
|
private final List<Material> handleMats = new ArrayList<>();
|
||||||
private final List<Float> handleCapY = new ArrayList<>();
|
private final List<Float> handleCapY = new ArrayList<>();
|
||||||
private int hoveredHandle = -1;
|
private int hoveredHandle = -1;
|
||||||
private int dragHandle = -1;
|
private int dragHandle = -1;
|
||||||
|
private int selectedHandle = -1;
|
||||||
private boolean isDragging = false;
|
private boolean isDragging = false;
|
||||||
private float[] liveXs = new float[0];
|
private float[] liveXs = new float[0];
|
||||||
private float[] liveYs = new float[0];
|
private float[] liveYs = new float[0];
|
||||||
@@ -187,7 +189,11 @@ public class WaterBodyState extends BaseAppState {
|
|||||||
|
|
||||||
if (input.deleteWaterRequested) {
|
if (input.deleteWaterRequested) {
|
||||||
input.deleteWaterRequested = false;
|
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;
|
SharedInput.WaterClick click;
|
||||||
@@ -235,6 +241,7 @@ public class WaterBodyState extends BaseAppState {
|
|||||||
PlacedWater b = bodies.get(selectedIdx);
|
PlacedWater b = bodies.get(selectedIdx);
|
||||||
int h = findHandleAt(jmeX, jmeY, b.pointsX(), b.pointsZ());
|
int h = findHandleAt(jmeX, jmeY, b.pointsX(), b.pointsZ());
|
||||||
if (h >= 0) {
|
if (h >= 0) {
|
||||||
|
setSelectedHandle(h);
|
||||||
startDrag(h, b.pointsX(), b.pointsY(), b.pointsZ());
|
startDrag(h, b.pointsX(), b.pointsY(), b.pointsZ());
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -569,10 +576,50 @@ public class WaterBodyState extends BaseAppState {
|
|||||||
handleMats.clear();
|
handleMats.clear();
|
||||||
handleCapY.clear();
|
handleCapY.clear();
|
||||||
hideSplitIndicator();
|
hideSplitIndicator();
|
||||||
hoveredHandle = -1;
|
hoveredHandle = -1;
|
||||||
|
dragHandle = -1;
|
||||||
|
selectedHandle = -1;
|
||||||
splitHoverEdge = -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() {
|
private void updateHandleHover() {
|
||||||
if (selectedIdx < 0) return;
|
if (selectedIdx < 0) return;
|
||||||
PlacedWater b = bodies.get(selectedIdx);
|
PlacedWater b = bodies.get(selectedIdx);
|
||||||
@@ -585,7 +632,8 @@ public class WaterBodyState extends BaseAppState {
|
|||||||
|
|
||||||
if (h != hoveredHandle) {
|
if (h != hoveredHandle) {
|
||||||
if (hoveredHandle >= 0 && hoveredHandle < handleMats.size()) {
|
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);
|
handleMats.get(hoveredHandle).setColor("Color", c);
|
||||||
}
|
}
|
||||||
if (h >= 0 && h < handleMats.size()) {
|
if (h >= 0 && h < handleMats.size()) {
|
||||||
@@ -846,7 +894,7 @@ public class WaterBodyState extends BaseAppState {
|
|||||||
rebuildPoleMesh(mesh, x, z, waterH, terrH);
|
rebuildPoleMesh(mesh, x, z, waterH, terrH);
|
||||||
Material mat = new Material(assets, "Common/MatDefs/Misc/Unshaded.j3md");
|
Material mat = new Material(assets, "Common/MatDefs/Misc/Unshaded.j3md");
|
||||||
mat.setColor("Color", color.clone());
|
mat.setColor("Color", color.clone());
|
||||||
mat.getAdditionalRenderState().setLineWidth(2.5f);
|
mat.getAdditionalRenderState().setLineWidth(4f);
|
||||||
Geometry geo = new Geometry(name, mesh);
|
Geometry geo = new Geometry(name, mesh);
|
||||||
geo.setMaterial(mat);
|
geo.setMaterial(mat);
|
||||||
return geo;
|
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
|
# 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
|
# 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
|
# 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
|
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