Weiter an den Dialogen gearbeitet
This commit is contained in:
@@ -11,12 +11,14 @@ import com.jme3.math.ColorRGBA;
|
||||
import com.jme3.math.Ray;
|
||||
import com.jme3.math.Vector2f;
|
||||
import com.jme3.math.Vector3f;
|
||||
import com.jme3.renderer.queue.RenderQueue;
|
||||
import com.jme3.scene.Geometry;
|
||||
import com.jme3.scene.Mesh;
|
||||
import com.jme3.scene.Node;
|
||||
import com.jme3.scene.Spatial;
|
||||
import com.jme3.scene.VertexBuffer;
|
||||
import com.jme3.terrain.geomipmap.TerrainQuad;
|
||||
import com.jme3.texture.Texture;
|
||||
import com.jme3.util.BufferUtils;
|
||||
import de.blight.common.GrassVertexBlade;
|
||||
import de.blight.common.GrassVertexIO;
|
||||
@@ -26,7 +28,9 @@ import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Random;
|
||||
|
||||
/**
|
||||
@@ -47,7 +51,7 @@ public class GrassVertexState extends BaseAppState {
|
||||
// ── Geometrie ─────────────────────────────────────────────────────────────
|
||||
static final int BLADES_PER_TUFT = 3; // Halme pro Büschel
|
||||
static final int SEGMENTS = 5; // Segmente pro Halm (5 → 6 Reihen)
|
||||
static final float WIDTH_FACTOR = 0.05f; // Basis-Halbbreite = Höhe × WIDTH_FACTOR
|
||||
static final float WIDTH_FACTOR = 0.10f; // Basis-Halbbreite = Höhe × WIDTH_FACTOR
|
||||
static final float BEND_FACTOR = 0.15f; // max. Krümmungsversatz an der Spitze
|
||||
|
||||
static final ColorRGBA ROOT_COLOR = new ColorRGBA(0.08f, 0.34f, 0.04f, 1f);
|
||||
@@ -63,6 +67,15 @@ public class GrassVertexState extends BaseAppState {
|
||||
private static final float CULL_DIST = 150f;
|
||||
private static final float CULL_DIST_SQ = CULL_DIST * CULL_DIST;
|
||||
|
||||
// ── Samen-Texturen ────────────────────────────────────────────────────────
|
||||
private static final String SEED_TEX_BASE = "Textures/internal/gras/seeds/seeds";
|
||||
private static final String SEED_TEX_EXT = ".png";
|
||||
|
||||
/** Samen-Größe relativ zur Halmhöhe */
|
||||
private static final float SEED_SIZE_FACTOR = 0.45f;
|
||||
/** Y-Position der Samen-Unterseite relativ zum Halmfuß (0–1) */
|
||||
private static final float SEED_Y_FACTOR = 0.78f;
|
||||
|
||||
// ── Zustand ───────────────────────────────────────────────────────────────
|
||||
private final SharedInput input;
|
||||
private AssetManager assetManager;
|
||||
@@ -70,6 +83,8 @@ public class GrassVertexState extends BaseAppState {
|
||||
private TerrainQuad terrain;
|
||||
private Node grassNode;
|
||||
private Material material;
|
||||
private Material[] seedMaterials = new Material[0];
|
||||
private Material seedStalkMaterial;
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private final List<GrassVertexBlade>[] chunkBlades = new List[CHUNK_COUNT];
|
||||
@@ -97,7 +112,9 @@ public class GrassVertexState extends BaseAppState {
|
||||
this.cam = app.getCamera();
|
||||
grassNode = new Node("grassVertexNode");
|
||||
((SimpleApplication) app).getRootNode().attachChild(grassNode);
|
||||
material = buildMaterial();
|
||||
material = buildMaterial();
|
||||
seedMaterials = loadSeedMaterials();
|
||||
seedStalkMaterial = buildSeedStalkMaterial();
|
||||
|
||||
try {
|
||||
for (GrassVertexBlade b : GrassVertexIO.load()) {
|
||||
@@ -109,6 +126,38 @@ public class GrassVertexState extends BaseAppState {
|
||||
}
|
||||
}
|
||||
|
||||
private Material buildSeedStalkMaterial() {
|
||||
Material mat = new Material(assetManager, "MatDefs/GrassVertex.j3md");
|
||||
mat.setFloat("WindSpeed", 1.0f);
|
||||
mat.setFloat("WindStrength", 0.15f);
|
||||
mat.setVector3("SunDir", new com.jme3.math.Vector3f(0.35f, 0.8f, 0.45f).normalizeLocal());
|
||||
mat.setColor("SunColor", new ColorRGBA(0.95f, 0.90f, 0.75f, 1.0f));
|
||||
mat.getAdditionalRenderState().setFaceCullMode(RenderState.FaceCullMode.Off);
|
||||
return mat;
|
||||
}
|
||||
|
||||
private Material[] loadSeedMaterials() {
|
||||
List<Material> mats = new ArrayList<>();
|
||||
for (int i = 1; i <= 99; i++) {
|
||||
String path = SEED_TEX_BASE + i + SEED_TEX_EXT;
|
||||
try {
|
||||
Texture tex = assetManager.loadTexture(path);
|
||||
Material mat = new Material(assetManager, "MatDefs/GrassSeed.j3md");
|
||||
mat.setTexture("ColorMap", tex);
|
||||
mat.setFloat("WindSpeed", 1.0f);
|
||||
mat.setFloat("WindStrength", 0.15f);
|
||||
mat.setVector3("SunDir", new com.jme3.math.Vector3f(0.35f, 0.8f, 0.45f).normalizeLocal());
|
||||
mat.setColor("SunColor", new ColorRGBA(0.95f, 0.90f, 0.75f, 1.0f));
|
||||
mat.getAdditionalRenderState().setFaceCullMode(RenderState.FaceCullMode.Off);
|
||||
mats.add(mat);
|
||||
log.info("[GrassVertexState] Samen-Textur geladen: {}", path);
|
||||
} catch (Exception e) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
return mats.toArray(new Material[0]);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void cleanup(Application app) {
|
||||
((SimpleApplication) app).getRootNode().detachChild(grassNode);
|
||||
@@ -201,7 +250,9 @@ public class GrassVertexState extends BaseAppState {
|
||||
float variation = (1f - uniformity) * 0.25f; // max ±25 % bei uniformity=0
|
||||
float radSq = radius * radius;
|
||||
|
||||
// Kleinere Rand-Halme im Pinselbereich durch größere ersetzen
|
||||
float seedPct = (float) input.grassVertexTool.seedDensity.getValue() / 100f;
|
||||
|
||||
// Kleinere Rand-Halme im Pinselbereich durch größere ersetzen (seedIdx erhalten)
|
||||
for (int ci = 0; ci < CHUNK_COUNT; ci++) {
|
||||
List<GrassVertexBlade> list = chunkBlades[ci];
|
||||
boolean changed = false;
|
||||
@@ -215,7 +266,7 @@ public class GrassVertexState extends BaseAppState {
|
||||
// Halm ist deutlich kleiner als die aktuelle Pinselposition erlaubt → ersetzen
|
||||
if (b.height() < idealH * 0.88f) {
|
||||
float newH = idealH * (1f + variation * (rng.nextFloat() * 2f - 1f));
|
||||
list.set(i, new GrassVertexBlade(b.x(), b.y(), b.z(), Math.max(0.05f, newH), b.dryness()));
|
||||
list.set(i, new GrassVertexBlade(b.x(), b.y(), b.z(), Math.max(0.05f, newH), b.dryness(), b.seedIdx()));
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
@@ -236,7 +287,11 @@ public class GrassVertexState extends BaseAppState {
|
||||
h = Math.max(0.05f, h);
|
||||
float bladeDryness = rng.nextFloat() < witherPct
|
||||
? 0.5f + rng.nextFloat() * 0.5f : 0f;
|
||||
GrassVertexBlade blade = new GrassVertexBlade(bx, by, bz, h, bladeDryness);
|
||||
int seedIdx = -1;
|
||||
if (seedMaterials.length > 0 && rng.nextFloat() < seedPct) {
|
||||
seedIdx = rng.nextInt(seedMaterials.length);
|
||||
}
|
||||
GrassVertexBlade blade = new GrassVertexBlade(bx, by, bz, h, bladeDryness, seedIdx);
|
||||
int ci = chunkIndex(bx, bz);
|
||||
if (ci >= 0) { chunkBlades[ci].add(blade); dirtyChunks[ci] = true; }
|
||||
}
|
||||
@@ -254,7 +309,7 @@ public class GrassVertexState extends BaseAppState {
|
||||
if (dx*dx + dz*dz > radSq) continue;
|
||||
float newY = terrain.getHeight(new Vector2f(b.x(), b.z()));
|
||||
if (!Float.isNaN(newY)) {
|
||||
list.set(i, new GrassVertexBlade(b.x(), newY, b.z(), b.height(), b.dryness()));
|
||||
list.set(i, new GrassVertexBlade(b.x(), newY, b.z(), b.height(), b.dryness(), b.seedIdx()));
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
@@ -333,10 +388,144 @@ public class GrassVertexState extends BaseAppState {
|
||||
|
||||
Node node = new Node("gvc_" + ci);
|
||||
node.attachChild(geo);
|
||||
|
||||
// ── Samen-Stiele + Kreuze ─────────────────────────────────────────────
|
||||
if (seedMaterials.length > 0) {
|
||||
List<GrassVertexBlade> seededAll = new ArrayList<>();
|
||||
Map<Integer, List<GrassVertexBlade>> byTex = new HashMap<>();
|
||||
for (GrassVertexBlade b : blades) {
|
||||
if (b.seedIdx() >= 0 && b.seedIdx() < seedMaterials.length) {
|
||||
seededAll.add(b);
|
||||
byTex.computeIfAbsent(b.seedIdx(), k -> new ArrayList<>()).add(b);
|
||||
}
|
||||
}
|
||||
if (!seededAll.isEmpty()) {
|
||||
Geometry stalkGeo = buildSeedStalkMesh("stalk_" + ci, seededAll);
|
||||
stalkGeo.setMaterial(seedStalkMaterial);
|
||||
node.attachChild(stalkGeo);
|
||||
}
|
||||
for (Map.Entry<Integer, List<GrassVertexBlade>> e : byTex.entrySet()) {
|
||||
Geometry seedGeo = buildSeedCrossMesh("seed_" + ci + "_" + e.getKey(), e.getValue());
|
||||
seedGeo.setMaterial(seedMaterials[e.getKey()]);
|
||||
node.attachChild(seedGeo);
|
||||
}
|
||||
}
|
||||
|
||||
chunkNodes[ci] = node;
|
||||
grassNode.attachChild(node);
|
||||
}
|
||||
|
||||
private static Geometry buildSeedStalkMesh(String name, List<GrassVertexBlade> blades) {
|
||||
final int SEG = 4;
|
||||
final float R = 0xbb / 255f, G = 0x90 / 255f, B = 0x59 / 255f;
|
||||
int n = blades.size();
|
||||
int vTotal = n * (SEG + 1) * 2;
|
||||
float[] pos = new float[vTotal * 3];
|
||||
float[] nrm = new float[vTotal * 3];
|
||||
float[] col = new float[vTotal * 4];
|
||||
float[] tex = new float[vTotal * 2];
|
||||
int[] idx = new int [n * SEG * 6];
|
||||
|
||||
int vi = 0, ii = 0;
|
||||
for (GrassVertexBlade blade : blades) {
|
||||
float x = blade.x(), y = blade.y(), z = blade.z(), h = blade.height();
|
||||
float hw = h * 0.018f;
|
||||
float ang = (float) (((x * 127.1f + z * 311.7f) % (Math.PI * 2) + Math.PI * 2) % (Math.PI * 2));
|
||||
float cA = (float) Math.cos(ang), sA = (float) Math.sin(ang);
|
||||
|
||||
// Normale senkrecht zur Halmbreite, 30 % Richtung Weltauf gekippt (wie Gras-Shader)
|
||||
float nx = -sA, ny = 0f, nz = cA;
|
||||
float blend = 0.30f;
|
||||
nx *= (1f - blend); ny = blend; nz *= (1f - blend);
|
||||
float nLen = (float) Math.sqrt(nx*nx + ny*ny + nz*nz);
|
||||
nx /= nLen; ny /= nLen; nz /= nLen;
|
||||
|
||||
for (int s = 0; s <= SEG; s++) {
|
||||
float t = (float) s / SEG;
|
||||
float curHW = hw * (float) Math.pow(1.0 - t, 1.4);
|
||||
float py = y + h * t;
|
||||
int sviL = vi + s * 2;
|
||||
int sviR = sviL + 1;
|
||||
|
||||
// Linkes Vertex
|
||||
pos[sviL*3] = x - cA*curHW; pos[sviL*3+1] = py; pos[sviL*3+2] = z - sA*curHW;
|
||||
nrm[sviL*3] = nx; nrm[sviL*3+1] = ny; nrm[sviL*3+2] = nz;
|
||||
col[sviL*4] = R; col[sviL*4+1] = G; col[sviL*4+2] = B; col[sviL*4+3] = 1f;
|
||||
tex[sviL*2] = t; tex[sviL*2+1] = 0f;
|
||||
|
||||
// Rechtes Vertex
|
||||
pos[sviR*3] = x + cA*curHW; pos[sviR*3+1] = py; pos[sviR*3+2] = z + sA*curHW;
|
||||
nrm[sviR*3] = nx; nrm[sviR*3+1] = ny; nrm[sviR*3+2] = nz;
|
||||
col[sviR*4] = R; col[sviR*4+1] = G; col[sviR*4+2] = B; col[sviR*4+3] = 1f;
|
||||
tex[sviR*2] = t; tex[sviR*2+1] = 0f;
|
||||
}
|
||||
for (int s = 0; s < SEG; s++) {
|
||||
int b0 = vi + s * 2;
|
||||
idx[ii] = b0; idx[ii+1] = b0+1; idx[ii+2] = b0+3;
|
||||
idx[ii+3] = b0; idx[ii+4] = b0+3; idx[ii+5] = b0+2;
|
||||
ii += 6;
|
||||
}
|
||||
vi += (SEG + 1) * 2;
|
||||
}
|
||||
|
||||
Mesh m = new Mesh();
|
||||
m.setBuffer(VertexBuffer.Type.Position, 3, BufferUtils.createFloatBuffer(pos));
|
||||
m.setBuffer(VertexBuffer.Type.Normal, 3, BufferUtils.createFloatBuffer(nrm));
|
||||
m.setBuffer(VertexBuffer.Type.Color, 4, BufferUtils.createFloatBuffer(col));
|
||||
m.setBuffer(VertexBuffer.Type.TexCoord, 2, BufferUtils.createFloatBuffer(tex));
|
||||
m.setBuffer(VertexBuffer.Type.Index, 3, BufferUtils.createIntBuffer(idx));
|
||||
m.updateBound();
|
||||
return new Geometry(name, m);
|
||||
}
|
||||
|
||||
private static Geometry buildSeedCrossMesh(String name, List<GrassVertexBlade> blades) {
|
||||
// Pro Halm: 2 Quads (X-Kreuz) × 4 Verts = 8 Verts, 2 × 6 Indices = 12
|
||||
int n = blades.size();
|
||||
float[] pos = new float[n * 8 * 3];
|
||||
float[] tex = new float[n * 8 * 2];
|
||||
int[] idx = new int [n * 12];
|
||||
|
||||
int vi = 0, ii = 0;
|
||||
for (GrassVertexBlade b : blades) {
|
||||
float x = b.x();
|
||||
float yBot = b.y() + b.height() * SEED_Y_FACTOR;
|
||||
float z = b.z();
|
||||
float size = b.height() * SEED_SIZE_FACTOR;
|
||||
float hw = size * 0.5f;
|
||||
|
||||
// Quad 1 – entlang Welt-X
|
||||
setSeedV(pos, tex, vi+0, x-hw, yBot, z, 0,0);
|
||||
setSeedV(pos, tex, vi+1, x+hw, yBot, z, 1,0);
|
||||
setSeedV(pos, tex, vi+2, x+hw, yBot+size, z, 1,1);
|
||||
setSeedV(pos, tex, vi+3, x-hw, yBot+size, z, 0,1);
|
||||
// Quad 2 – entlang Welt-Z
|
||||
setSeedV(pos, tex, vi+4, x, yBot, z-hw, 0,0);
|
||||
setSeedV(pos, tex, vi+5, x, yBot, z+hw, 1,0);
|
||||
setSeedV(pos, tex, vi+6, x, yBot+size, z+hw, 1,1);
|
||||
setSeedV(pos, tex, vi+7, x, yBot+size, z-hw, 0,1);
|
||||
|
||||
idx[ii] = vi; idx[ii+1] = vi+1; idx[ii+2] = vi+2;
|
||||
idx[ii+3] = vi; idx[ii+4] = vi+2; idx[ii+5] = vi+3;
|
||||
idx[ii+6] = vi+4; idx[ii+7] = vi+5; idx[ii+8] = vi+6;
|
||||
idx[ii+9] = vi+4; idx[ii+10] = vi+6; idx[ii+11] = vi+7;
|
||||
|
||||
vi += 8; ii += 12;
|
||||
}
|
||||
|
||||
Mesh m = new Mesh();
|
||||
m.setBuffer(VertexBuffer.Type.Position, 3, BufferUtils.createFloatBuffer(pos));
|
||||
m.setBuffer(VertexBuffer.Type.TexCoord, 2, BufferUtils.createFloatBuffer(tex));
|
||||
m.setBuffer(VertexBuffer.Type.Index, 3, BufferUtils.createIntBuffer(idx));
|
||||
m.updateBound();
|
||||
return new Geometry(name, m);
|
||||
}
|
||||
|
||||
private static void setSeedV(float[] pos, float[] tex, int vi,
|
||||
float x, float y, float z, float u, float v) {
|
||||
pos[vi*3] = x; pos[vi*3+1] = y; pos[vi*3+2] = z;
|
||||
tex[vi*2] = u; tex[vi*2+1] = v;
|
||||
}
|
||||
|
||||
// ── Mesh-Generierung (gemeinsame Logik, auch von GrassVertexRenderState genutzt) ──
|
||||
|
||||
static void buildTuft(float[] pos, float[] nrm, float[] col, float[] tex, int[] idx,
|
||||
|
||||
@@ -10,6 +10,8 @@ public class GrassVertexTool extends EditorTool {
|
||||
public final ToolParameter dryness = new ToolParameter("Vertrocknet %", 0.0, 0.0, 100.0);
|
||||
/** 1.0 = exakt gleiche Höhe, 0.0 = ±25 % Zufallsvariation */
|
||||
public final ToolParameter uniformity = new ToolParameter("Gleichmäßigkeit", 1.0, 0.0, 1.0);
|
||||
/** Anteil der Halme mit Samenstand (0 = keine, 100 = alle) */
|
||||
public final ToolParameter seedDensity = new ToolParameter("Samen %", 0.0, 0.0, 100.0);
|
||||
|
||||
@Override public String getName() { return "Gras (Vertices)"; }
|
||||
|
||||
@@ -17,5 +19,5 @@ public class GrassVertexTool extends EditorTool {
|
||||
public List<ChoiceToolParameter> getChoiceParameters() { return List.of(); }
|
||||
|
||||
@Override
|
||||
public List<ToolParameter> getParameters() { return List.of(brushRadius, bladeHeight, density, dryness, uniformity); }
|
||||
public List<ToolParameter> getParameters() { return List.of(brushRadius, bladeHeight, density, dryness, uniformity, seedDensity); }
|
||||
}
|
||||
|
||||
@@ -52,8 +52,8 @@ public class DialogEditorView extends BorderPane {
|
||||
|
||||
private TextField idField;
|
||||
private Label derivedLabelKey;
|
||||
private Label derivedHeroKey;
|
||||
private Label derivedNpcKey;
|
||||
private ListView<String> heroStepsView;
|
||||
private ListView<String> npcStepsView;
|
||||
private CheckBox rootCheck;
|
||||
private Spinner<Integer> chapterSpinner;
|
||||
private ComboBox<String> statusCombo;
|
||||
@@ -70,6 +70,7 @@ public class DialogEditorView extends BorderPane {
|
||||
private ListView<Trigger> triggersView;
|
||||
private ListView<String> nextOptionsView;
|
||||
private ListView<String> disablesOptionsView;
|
||||
private ComboBox<String> npcAnimCombo;
|
||||
|
||||
// ── Graph-mode ────────────────────────────────────────────────────────────
|
||||
|
||||
@@ -252,20 +253,16 @@ public class DialogEditorView extends BorderPane {
|
||||
idField.setPromptText("z. B. begruessung");
|
||||
|
||||
derivedLabelKey = derivedKeyLabel("—");
|
||||
derivedHeroKey = derivedKeyLabel("—");
|
||||
derivedNpcKey = derivedKeyLabel("—");
|
||||
|
||||
idField.textProperty().addListener((obs, oldVal, newVal) -> {
|
||||
String id = newVal.trim();
|
||||
if (id.isBlank()) {
|
||||
derivedLabelKey.setText("—");
|
||||
derivedHeroKey.setText("—");
|
||||
derivedNpcKey.setText("—");
|
||||
refreshStepKeys("");
|
||||
} else {
|
||||
String base = "dialog." + currentNpcId + "." + id;
|
||||
derivedLabelKey.setText(base + ".description");
|
||||
derivedHeroKey.setText(base + ".textmainchar");
|
||||
derivedNpcKey.setText(base + ".textnpc");
|
||||
refreshStepKeys(base);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -310,12 +307,31 @@ public class DialogEditorView extends BorderPane {
|
||||
new Separator()
|
||||
);
|
||||
|
||||
heroStepsView = stepsListView();
|
||||
npcStepsView = stepsListView();
|
||||
Button addHeroBtn = smallBtn("+");
|
||||
Button delHeroBtn = smallBtn("−");
|
||||
Button addNpcBtn = smallBtn("+");
|
||||
Button delNpcBtn = smallBtn("−");
|
||||
addHeroBtn.setOnAction(e -> addStep(heroStepsView, ".textmainchar"));
|
||||
delHeroBtn.setOnAction(e -> removeLastStep(heroStepsView));
|
||||
addNpcBtn.setOnAction(e -> addStep(npcStepsView, ".textnpc"));
|
||||
delNpcBtn.setOnAction(e -> removeLastStep(npcStepsView));
|
||||
|
||||
npcAnimCombo = new ComboBox<>();
|
||||
npcAnimCombo.getItems().addAll("— (keine)", "talk1", "talk2", "disappoint", "secret");
|
||||
npcAnimCombo.setValue("— (keine)");
|
||||
npcAnimCombo.setMaxWidth(Double.MAX_VALUE);
|
||||
|
||||
form.getChildren().addAll(
|
||||
sectionTitle("Texte"),
|
||||
row("Text Held:", derivedHeroKey),
|
||||
row("Text NPC:", derivedNpcKey),
|
||||
row("Audio Held:", placeholder("(wird später implementiert)")),
|
||||
row("Audio NPC:", placeholder("(wird später implementiert)")),
|
||||
sectionTitle("Held-Steps:"),
|
||||
heroStepsView,
|
||||
new HBox(4, addHeroBtn, delHeroBtn),
|
||||
sectionTitle("NPC-Steps:"),
|
||||
npcStepsView,
|
||||
new HBox(4, addNpcBtn, delNpcBtn),
|
||||
row("NPC-Animation:", npcAnimCombo),
|
||||
new Separator()
|
||||
);
|
||||
|
||||
@@ -469,7 +485,26 @@ public class DialogEditorView extends BorderPane {
|
||||
questCompleteField.setText(opt.getRequiresQuestComplete() != null
|
||||
? safe(opt.getRequiresQuestComplete().getQuestId()) : "");
|
||||
|
||||
// text keys are derived from ID and shown via derivedHeroKey / derivedNpcKey labels
|
||||
// Held- und NPC-Steps laden (mit Migration aus Legacy-Feldern)
|
||||
String base = (!id.isBlank()) ? "dialog." + currentNpcId + "." + id : "";
|
||||
heroStepsView.getItems().clear();
|
||||
java.util.List<de.blight.common.model.DialogStep> hs = opt.getHeroSteps();
|
||||
if (hs != null && !hs.isEmpty()) {
|
||||
for (int i = 0; i < hs.size(); i++) {
|
||||
heroStepsView.getItems().add(stepKey(base, ".textmainchar", i));
|
||||
}
|
||||
} else if (opt.getTextHero() != null && !opt.getTextHero().id().isBlank()) {
|
||||
heroStepsView.getItems().add(stepKey(base, ".textmainchar", 0));
|
||||
}
|
||||
npcStepsView.getItems().clear();
|
||||
java.util.List<de.blight.common.model.DialogStep> ns = opt.getNpcSteps();
|
||||
if (ns != null && !ns.isEmpty()) {
|
||||
for (int i = 0; i < ns.size(); i++) {
|
||||
npcStepsView.getItems().add(stepKey(base, ".textnpc", i));
|
||||
}
|
||||
} else if (opt.getTextNpc() != null && !opt.getTextNpc().id().isBlank()) {
|
||||
npcStepsView.getItems().add(stepKey(base, ".textnpc", 0));
|
||||
}
|
||||
|
||||
if (opt.getRequiredItem() != null && opt.getRequiredItem().getItem() != null) {
|
||||
reqItemIdField.setText(safe(opt.getRequiredItem().getItem().getItemId()));
|
||||
@@ -498,6 +533,9 @@ public class DialogEditorView extends BorderPane {
|
||||
.map(Quest::getQuestId)
|
||||
.forEach(abortsQuestsView.getItems()::add);
|
||||
|
||||
String anim = opt.getNpcAnimation();
|
||||
npcAnimCombo.setValue(anim != null && !anim.isBlank() ? anim : "— (keine)");
|
||||
|
||||
enablesTradeCheck.setSelected(opt.isEnablesTrade());
|
||||
|
||||
triggersView.getItems().clear();
|
||||
@@ -543,8 +581,24 @@ public class DialogEditorView extends BorderPane {
|
||||
if (!currentId.isBlank()) {
|
||||
String base = "dialog." + currentNpcId + "." + currentId;
|
||||
opt.setLabel(new TextReference(base + ".description"));
|
||||
opt.setTextHero(new TextReference(base + ".textmainchar"));
|
||||
opt.setTextNpc(new TextReference(base + ".textnpc"));
|
||||
|
||||
List<de.blight.common.model.DialogStep> heroList = new ArrayList<>();
|
||||
for (int i = 0; i < heroStepsView.getItems().size(); i++) {
|
||||
heroList.add(new de.blight.common.model.DialogStep(
|
||||
new TextReference(stepKey(base, ".textmainchar", i)), null));
|
||||
}
|
||||
opt.setHeroSteps(heroList);
|
||||
opt.setTextHero(heroList.isEmpty() ? null
|
||||
: new TextReference(stepKey(base, ".textmainchar", 0)));
|
||||
|
||||
List<de.blight.common.model.DialogStep> npcList = new ArrayList<>();
|
||||
for (int i = 0; i < npcStepsView.getItems().size(); i++) {
|
||||
npcList.add(new de.blight.common.model.DialogStep(
|
||||
new TextReference(stepKey(base, ".textnpc", i)), null));
|
||||
}
|
||||
opt.setNpcSteps(npcList);
|
||||
opt.setTextNpc(npcList.isEmpty() ? null
|
||||
: new TextReference(stepKey(base, ".textnpc", 0)));
|
||||
}
|
||||
|
||||
if (rootCheck.isSelected()) {
|
||||
@@ -578,6 +632,9 @@ public class DialogEditorView extends BorderPane {
|
||||
}
|
||||
opt.setAbortsQuests(aborts);
|
||||
|
||||
String animVal = npcAnimCombo.getValue();
|
||||
opt.setNpcAnimation(animVal == null || animVal.startsWith("—") ? null : animVal);
|
||||
|
||||
opt.setEnablesTrade(enablesTradeCheck.isSelected());
|
||||
opt.setOnChosenTriggers(new ArrayList<>(triggersView.getItems()));
|
||||
|
||||
@@ -594,8 +651,8 @@ public class DialogEditorView extends BorderPane {
|
||||
private void clearDetailForm() {
|
||||
if (idField != null) idField.clear();
|
||||
if (derivedLabelKey != null) derivedLabelKey.setText("—");
|
||||
if (derivedHeroKey != null) derivedHeroKey.setText("—");
|
||||
if (derivedNpcKey != null) derivedNpcKey.setText("—");
|
||||
if (heroStepsView != null) heroStepsView.getItems().clear();
|
||||
if (npcStepsView != null) npcStepsView.getItems().clear();
|
||||
if (rootCheck != null) rootCheck.setSelected(false);
|
||||
if (chapterSpinner != null) chapterSpinner.getValueFactory().setValue(0);
|
||||
if (statusCombo != null) statusCombo.setValue("— (keine)");
|
||||
@@ -608,6 +665,7 @@ public class DialogEditorView extends BorderPane {
|
||||
if (recvQuestField != null) recvQuestField.clear();
|
||||
if (fulfillsQuestField != null) fulfillsQuestField.clear();
|
||||
if (abortsQuestsView != null) abortsQuestsView.getItems().clear();
|
||||
if (npcAnimCombo != null) npcAnimCombo.setValue("— (keine)");
|
||||
if (enablesTradeCheck != null) enablesTradeCheck.setSelected(false);
|
||||
if (triggersView != null) triggersView.getItems().clear();
|
||||
if (nextOptionsView != null) nextOptionsView.getItems().clear();
|
||||
@@ -649,6 +707,10 @@ public class DialogEditorView extends BorderPane {
|
||||
opt.setLabel(new TextReference(base + ".description"));
|
||||
opt.setTextHero(new TextReference(base + ".textmainchar"));
|
||||
opt.setTextNpc(new TextReference(base + ".textnpc"));
|
||||
opt.setHeroSteps(new ArrayList<>(List.of(new de.blight.common.model.DialogStep(
|
||||
new TextReference(base + ".textmainchar"), null))));
|
||||
opt.setNpcSteps(new ArrayList<>(List.of(new de.blight.common.model.DialogStep(
|
||||
new TextReference(base + ".textnpc"), null))));
|
||||
allOptions.put(id, opt);
|
||||
if (asRoot) {
|
||||
rootIds.add(id);
|
||||
@@ -1145,4 +1207,56 @@ public class DialogEditorView extends BorderPane {
|
||||
});
|
||||
return dlg.showAndWait().orElse(null);
|
||||
}
|
||||
|
||||
// ── Steps helpers ─────────────────────────────────────────────────────────
|
||||
|
||||
private static ListView<String> stepsListView() {
|
||||
ListView<String> lv = new ListView<>();
|
||||
lv.setPrefHeight(72);
|
||||
lv.setStyle("-fx-background-color: #1e1e2e; -fx-control-inner-background: #1e1e2e;");
|
||||
lv.setCellFactory(v -> new ListCell<>() {
|
||||
@Override protected void updateItem(String s, boolean empty) {
|
||||
super.updateItem(s, empty);
|
||||
setText(empty || s == null ? null : s);
|
||||
setStyle("-fx-text-fill: #aaddaa;");
|
||||
}
|
||||
});
|
||||
return lv;
|
||||
}
|
||||
|
||||
private void addStep(ListView<String> lv, String suffix) {
|
||||
String id = idField.getText().trim();
|
||||
if (id.isBlank()) {
|
||||
return;
|
||||
}
|
||||
String base = "dialog." + currentNpcId + "." + id;
|
||||
int idx = lv.getItems().size();
|
||||
lv.getItems().add(stepKey(base, suffix, idx));
|
||||
}
|
||||
|
||||
private static void removeLastStep(ListView<String> lv) {
|
||||
if (!lv.getItems().isEmpty()) {
|
||||
lv.getItems().remove(lv.getItems().size() - 1);
|
||||
}
|
||||
}
|
||||
|
||||
private static String stepKey(String base, String suffix, int idx) {
|
||||
return idx == 0 ? base + suffix : base + suffix + "." + idx;
|
||||
}
|
||||
|
||||
private void refreshStepKeys(String base) {
|
||||
if (heroStepsView == null || npcStepsView == null) {
|
||||
return;
|
||||
}
|
||||
refreshListKeys(heroStepsView, base, ".textmainchar");
|
||||
refreshListKeys(npcStepsView, base, ".textnpc");
|
||||
}
|
||||
|
||||
private static void refreshListKeys(ListView<String> lv, String base, String suffix) {
|
||||
int count = lv.getItems().size();
|
||||
lv.getItems().clear();
|
||||
for (int i = 0; i < count; i++) {
|
||||
lv.getItems().add(stepKey(base, suffix, i));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user