Weiter gearbeitet
This commit is contained in:
@@ -0,0 +1,4 @@
|
||||
package de.blight.common;
|
||||
|
||||
/** Einzeln platzierter Gras-Büschel. Y-Position wird zur Renderzeit aus dem Terrain berechnet. */
|
||||
public record GrassTuft(float x, float z, float height, int slot) {}
|
||||
@@ -0,0 +1,86 @@
|
||||
package de.blight.common;
|
||||
|
||||
import java.io.*;
|
||||
import java.nio.file.*;
|
||||
import java.util.*;
|
||||
import java.util.zip.*;
|
||||
|
||||
/**
|
||||
* Liest und schreibt individuell platzierte Gras-Büschel als komprimierte Binärdatei
|
||||
* ({@code blight_grass.blg}) neben der Kartendatei.
|
||||
*
|
||||
* Format v1:
|
||||
* int MAGIC 0x47525A53 ("GRZS")
|
||||
* int VERSION 1
|
||||
* int slotCount (8)
|
||||
* 8× UTF Texturpfad pro Slot (Slot 0 = Standardtextur)
|
||||
* int tuftCount
|
||||
* N× float x, float z, float height, byte slot
|
||||
*/
|
||||
public final class GrassTuftIO {
|
||||
|
||||
private static final int MAGIC = 0x47525A53;
|
||||
private static final int VERSION = 1;
|
||||
private static final int SLOT_COUNT = 8;
|
||||
|
||||
private GrassTuftIO() {}
|
||||
|
||||
public static Path getPath() {
|
||||
return MapIO.getMapPath().resolveSibling("blight_grass.blg");
|
||||
}
|
||||
|
||||
public record GrassData(String[] slotPaths, List<GrassTuft> tufts) {}
|
||||
|
||||
public static void save(GrassData data) throws IOException {
|
||||
Path p = getPath();
|
||||
Files.createDirectories(p.getParent());
|
||||
try (DataOutputStream out = new DataOutputStream(
|
||||
new BufferedOutputStream(new GZIPOutputStream(Files.newOutputStream(p))))) {
|
||||
out.writeInt(MAGIC);
|
||||
out.writeInt(VERSION);
|
||||
String[] paths = data.slotPaths() != null ? data.slotPaths() : new String[0];
|
||||
out.writeInt(SLOT_COUNT);
|
||||
for (int i = 0; i < SLOT_COUNT; i++) {
|
||||
String s = (i < paths.length && paths[i] != null) ? paths[i] : "";
|
||||
out.writeUTF(s);
|
||||
}
|
||||
List<GrassTuft> tufts = data.tufts() != null ? data.tufts() : List.of();
|
||||
out.writeInt(tufts.size());
|
||||
for (GrassTuft t : tufts) {
|
||||
out.writeFloat(t.x());
|
||||
out.writeFloat(t.z());
|
||||
out.writeFloat(t.height());
|
||||
out.writeByte(t.slot());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static GrassData load() throws IOException {
|
||||
Path p = getPath();
|
||||
if (!Files.exists(p)) return null;
|
||||
try (DataInputStream in = new DataInputStream(
|
||||
new BufferedInputStream(new GZIPInputStream(Files.newInputStream(p))))) {
|
||||
int magic = in.readInt();
|
||||
int version = in.readInt();
|
||||
if (magic != MAGIC) throw new IOException("Ungültige Grasdatei (Magic)");
|
||||
if (version > VERSION) throw new IOException("Unbekannte Gras-Version: " + version);
|
||||
int slotCount = in.readInt();
|
||||
String[] paths = new String[SLOT_COUNT];
|
||||
Arrays.fill(paths, "");
|
||||
for (int i = 0; i < slotCount; i++) {
|
||||
String s = in.readUTF();
|
||||
if (i < SLOT_COUNT) paths[i] = s;
|
||||
}
|
||||
int tuftCount = in.readInt();
|
||||
List<GrassTuft> tufts = new ArrayList<>(tuftCount);
|
||||
for (int i = 0; i < tuftCount; i++) {
|
||||
float x = in.readFloat();
|
||||
float z = in.readFloat();
|
||||
float h = in.readFloat();
|
||||
int slot = in.readUnsignedByte();
|
||||
tufts.add(new GrassTuft(x, z, h, slot));
|
||||
}
|
||||
return new GrassData(paths, tufts);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -70,6 +70,25 @@ public final class MapData {
|
||||
/** Gras-Dichte [SPLAT_SIZE²], Bytes 0–255 (0=kein Gras, 255=max Dichte). */
|
||||
public final byte[] grassDensity;
|
||||
|
||||
/**
|
||||
* Gras-Höhe pro Pixel [SPLAT_SIZE²].
|
||||
* 0 = nicht gesetzt → grassDefaultHeight verwenden.
|
||||
* 1–255 = 0.1 m bis 10.0 m (linear: h = 0.1 + (b-1) * 9.9/254).
|
||||
*/
|
||||
public final byte[] grassHeightMap;
|
||||
|
||||
/** Relativer Asset-Pfad der Gras-Textur (z. B. "Textures/grass.png"), "" = Standardfarbe. */
|
||||
public String grassTexturePath = "";
|
||||
|
||||
/** Standard-Blatt-Höhe für den Gras-Renderer (entspricht dem Grashöhe-Slider). */
|
||||
public float grassDefaultHeight = 1.5f;
|
||||
|
||||
/** Per-Pixel Textur-Slot-Index [SPLAT_SIZE²]. 0=grassTexturePath, 1–N=grassTextureSlots[slot-1]. */
|
||||
public final byte[] grassTextureMap;
|
||||
|
||||
/** Texturpfade für Gras-Slots 1..N. Leerstring = nicht belegt. */
|
||||
public String[] grassTextureSlots = new String[0];
|
||||
|
||||
/** Loch-Maske der oberen Schicht [UPPER_CELLS²], != 0 = Loch (Zelle ausgeblendet). */
|
||||
public final byte[] upperHole;
|
||||
|
||||
@@ -91,7 +110,9 @@ public final class MapData {
|
||||
upperSplatG = new byte [SPLAT_SIZE * SPLAT_SIZE];
|
||||
upperSplatB = new byte [SPLAT_SIZE * SPLAT_SIZE];
|
||||
upperSplatA = new byte [SPLAT_SIZE * SPLAT_SIZE];
|
||||
grassDensity = new byte [SPLAT_SIZE * SPLAT_SIZE];
|
||||
upperHole = new byte [UPPER_CELLS * UPPER_CELLS];
|
||||
grassDensity = new byte [SPLAT_SIZE * SPLAT_SIZE];
|
||||
grassHeightMap = new byte [SPLAT_SIZE * SPLAT_SIZE];
|
||||
grassTextureMap = new byte [SPLAT_SIZE * SPLAT_SIZE];
|
||||
upperHole = new byte [UPPER_CELLS * UPPER_CELLS];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,6 +19,9 @@ import java.util.zip.*;
|
||||
* 4 – wie 3 + Spawnpunkt (2× float)
|
||||
* 5 – wie 4 + Splatmap-Alpha + Texturpfade + Gebirge-Splatmap (RGBA + Pfade)
|
||||
* 6 – wie 5 ohne upperHole; upperTop/Bottom behalten (zukünftige Höhlen-Architektur)
|
||||
* 7 – wie 6 + Gras-Texturpfad (UTF-String) + Gras-Standardhöhe (float)
|
||||
* 8 – wie 7 + Gras-Höhen-Map (SPLAT_SIZE² Bytes, 0=ungesetzt 1-255=0.1-10m)
|
||||
* 9 – wie 8 + Gras-Textur-Map (SPLAT_SIZE² Bytes) + Slots (N×UTF)
|
||||
*/
|
||||
public final class MapIO {
|
||||
|
||||
@@ -50,7 +53,7 @@ public final class MapIO {
|
||||
}
|
||||
|
||||
private static final int MAGIC = 0x424C4947; // "BLIG"
|
||||
private static final int VERSION = 6;
|
||||
private static final int VERSION = 9;
|
||||
|
||||
private MapIO() {}
|
||||
|
||||
@@ -104,6 +107,19 @@ public final class MapIO {
|
||||
out.write(data.upperSplatB);
|
||||
out.write(data.upperSplatA);
|
||||
writeStrings(out, data.upperTextures);
|
||||
// v7: gras-textur + standardhöhe
|
||||
out.writeUTF(data.grassTexturePath != null ? data.grassTexturePath : "");
|
||||
out.writeFloat(data.grassDefaultHeight);
|
||||
// v8: gras-höhen-map
|
||||
out.write(data.grassHeightMap);
|
||||
// v9: gras-textur-slots + gras-textur-map
|
||||
String[] slots = data.grassTextureSlots != null ? data.grassTextureSlots : new String[0];
|
||||
// trim trailing empty entries
|
||||
int slotEnd = slots.length;
|
||||
while (slotEnd > 0 && (slots[slotEnd-1] == null || slots[slotEnd-1].isEmpty())) slotEnd--;
|
||||
out.writeInt(slotEnd);
|
||||
for (int i = 0; i < slotEnd; i++) out.writeUTF(slots[i] != null ? slots[i] : "");
|
||||
out.write(data.grassTextureMap);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -150,6 +166,19 @@ public final class MapIO {
|
||||
// Ältere Maps: upperSplatR auf 255 initialisieren (Tex1 immer sichtbar)
|
||||
java.util.Arrays.fill(data.upperSplatR, (byte) 255);
|
||||
}
|
||||
if (version >= 7) {
|
||||
data.grassTexturePath = in.readUTF();
|
||||
data.grassDefaultHeight = in.readFloat();
|
||||
}
|
||||
if (version >= 8) {
|
||||
in.readFully(data.grassHeightMap);
|
||||
}
|
||||
if (version >= 9) {
|
||||
int n = in.readInt();
|
||||
data.grassTextureSlots = new String[n];
|
||||
for (int i = 0; i < n; i++) data.grassTextureSlots[i] = in.readUTF();
|
||||
in.readFully(data.grassTextureMap);
|
||||
}
|
||||
}
|
||||
return data;
|
||||
}
|
||||
|
||||
@@ -11,5 +11,7 @@ public record PlacedModel(
|
||||
/** Relativer Asset-Pfad zur exportierten j3o-Datei des Custom Meshes; "" wenn nicht verwendet. */
|
||||
String meshFile,
|
||||
/** AnimationLibrary-Clip-Key (z. B. "walk/Run"); "" = keine Animation. */
|
||||
String animClip
|
||||
String animClip,
|
||||
boolean castShadow,
|
||||
boolean receiveShadow
|
||||
) {}
|
||||
|
||||
@@ -8,10 +8,10 @@ import java.util.*;
|
||||
* Liest und schreibt platzierte Modelle als tab-separierte Textdatei
|
||||
* ({@code blight_objects.blo}) neben der Kartendatei.
|
||||
*
|
||||
* Spalten (seit v2):
|
||||
* modelPath x y z rotY scale rotX rotZ solid texPath nmPath matPath meshFile
|
||||
* Spalten (seit v3):
|
||||
* modelPath x y z rotY scale rotX rotZ solid texPath nmPath matPath meshFile animClip castShadow receiveShadow
|
||||
*
|
||||
* Alte Dateien mit 6 Spalten (v1) werden gelesen; fehlende Felder erhalten Standardwerte.
|
||||
* Alte Dateien mit 6 Spalten (v1/v2) werden gelesen; fehlende Felder erhalten Standardwerte.
|
||||
*/
|
||||
public final class PlacedModelIO {
|
||||
|
||||
@@ -25,18 +25,19 @@ public final class PlacedModelIO {
|
||||
Path p = getPath();
|
||||
Files.createDirectories(p.getParent());
|
||||
try (BufferedWriter w = Files.newBufferedWriter(p)) {
|
||||
w.write("# modelPath\tx\ty\tz\trotY\tscale\trotX\trotZ\tsolid\ttexPath\tnmPath\tmatPath\tmeshFile\tanimClip");
|
||||
w.write("# modelPath\tx\ty\tz\trotY\tscale\trotX\trotZ\tsolid\ttexPath\tnmPath\tmatPath\tmeshFile\tanimClip\tcastShadow\treceiveShadow");
|
||||
w.newLine();
|
||||
for (PlacedModel m : models) {
|
||||
w.write(String.format(Locale.ROOT,
|
||||
"%s\t%.5f\t%.5f\t%.5f\t%.5f\t%.5f\t%.5f\t%.5f\t%b\t%s\t%s\t%s\t%s\t%s%n",
|
||||
"%s\t%.5f\t%.5f\t%.5f\t%.5f\t%.5f\t%.5f\t%.5f\t%b\t%s\t%s\t%s\t%s\t%s\t%b\t%b%n",
|
||||
m.modelPath(),
|
||||
m.x(), m.y(), m.z(),
|
||||
m.rotY(), m.scale(),
|
||||
m.rotX(), m.rotZ(),
|
||||
m.solid(),
|
||||
nvl(m.texturePath()), nvl(m.normalMapPath()), nvl(m.materialPath()),
|
||||
nvl(m.meshFile()), nvl(m.animClip())));
|
||||
nvl(m.meshFile()), nvl(m.animClip()),
|
||||
m.castShadow(), m.receiveShadow()));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -63,11 +64,14 @@ public final class PlacedModelIO {
|
||||
String texPath = f.length > 9 ? f[9] : "";
|
||||
String nmPath = f.length > 10 ? f[10] : "";
|
||||
String matPath = f.length > 11 ? f[11] : "";
|
||||
String meshFile = f.length > 12 ? f[12] : "";
|
||||
String animClip = f.length > 13 ? f[13] : "";
|
||||
String meshFile = f.length > 12 ? f[12] : "";
|
||||
String animClip = f.length > 13 ? f[13] : "";
|
||||
boolean castShadow = f.length > 14 ? Boolean.parseBoolean(f[14]) : true;
|
||||
boolean receiveShadow = f.length > 15 ? Boolean.parseBoolean(f[15]) : true;
|
||||
list.add(new PlacedModel(modelPath, x, y, z,
|
||||
rotY, rotX, rotZ, scale, solid,
|
||||
texPath, nmPath, matPath, meshFile, animClip));
|
||||
texPath, nmPath, matPath, meshFile, animClip,
|
||||
castShadow, receiveShadow));
|
||||
} catch (NumberFormatException ignored) {}
|
||||
}
|
||||
return list;
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
package de.blight.common;
|
||||
|
||||
/** Unveränderliche Daten einer platzierten Wasseroberfläche (Teich, See). */
|
||||
public record PlacedWater(
|
||||
float x, float y, float z,
|
||||
float width, float depth
|
||||
) {}
|
||||
/**
|
||||
* Platzierte Wasserfläche.
|
||||
* Die Form wird zur Laufzeit per Flood-Fill aus dem Geländenetz berechnet –
|
||||
* gespeichert werden nur Saatpunkt und Wasserstand.
|
||||
*/
|
||||
public record PlacedWater(float seedX, float seedZ, float waterHeight) {}
|
||||
|
||||
73
blight-common/src/main/java/de/blight/common/RiverIO.java
Normal file
73
blight-common/src/main/java/de/blight/common/RiverIO.java
Normal file
@@ -0,0 +1,73 @@
|
||||
package de.blight.common;
|
||||
|
||||
import java.io.*;
|
||||
import java.nio.file.*;
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* Liest und schreibt platzierte Flüsse als Textdatei
|
||||
* ({@code blight_rivers.blr}) neben der Kartendatei.
|
||||
*
|
||||
* Format: ein Fluss pro Zeile; Punkte getrennt durch {@code |};
|
||||
* jeder Punkt: {@code x,y,z,width,uvSpeed} (5 Komma-getrennte Floats).
|
||||
* Kommentarzeilen beginnen mit {@code #}.
|
||||
*/
|
||||
public final class RiverIO {
|
||||
|
||||
private RiverIO() {}
|
||||
|
||||
public static Path getPath() {
|
||||
return MapIO.getMapPath().resolveSibling("blight_rivers.blr");
|
||||
}
|
||||
|
||||
public static void save(List<List<RiverPoint>> rivers) throws IOException {
|
||||
Path p = getPath();
|
||||
Files.createDirectories(p.getParent());
|
||||
try (BufferedWriter w = Files.newBufferedWriter(p)) {
|
||||
w.write("# blight_rivers.blr – Flussdaten");
|
||||
w.newLine();
|
||||
w.write("# Format: x,y,z,width,uvSpeed | x,y,z,width,uvSpeed | ...");
|
||||
w.newLine();
|
||||
for (List<RiverPoint> river : rivers) {
|
||||
if (river == null || river.isEmpty()) continue;
|
||||
StringBuilder sb = new StringBuilder();
|
||||
for (int i = 0; i < river.size(); i++) {
|
||||
if (i > 0) sb.append('|');
|
||||
RiverPoint pt = river.get(i);
|
||||
sb.append(String.format(Locale.ROOT, "%.5f,%.5f,%.5f,%.5f,%.5f",
|
||||
pt.x(), pt.y(), pt.z(), pt.width(), pt.uvSpeed()));
|
||||
}
|
||||
w.write(sb.toString());
|
||||
w.newLine();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static List<List<RiverPoint>> load() throws IOException {
|
||||
Path p = getPath();
|
||||
if (!Files.exists(p)) return List.of();
|
||||
List<List<RiverPoint>> result = new ArrayList<>();
|
||||
for (String line : Files.readAllLines(p)) {
|
||||
line = line.strip();
|
||||
if (line.isEmpty() || line.startsWith("#")) continue;
|
||||
String[] parts = line.split("\\|", -1);
|
||||
List<RiverPoint> river = new ArrayList<>();
|
||||
for (String part : parts) {
|
||||
part = part.strip();
|
||||
if (part.isEmpty()) continue;
|
||||
String[] f = part.split(",", -1);
|
||||
if (f.length < 5) continue;
|
||||
try {
|
||||
river.add(new RiverPoint(
|
||||
Float.parseFloat(f[0]),
|
||||
Float.parseFloat(f[1]),
|
||||
Float.parseFloat(f[2]),
|
||||
Float.parseFloat(f[3]),
|
||||
Float.parseFloat(f[4])));
|
||||
} catch (NumberFormatException ignored) {}
|
||||
}
|
||||
if (!river.isEmpty()) result.add(river);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
package de.blight.common;
|
||||
|
||||
/** Kontrollpunkt eines Flusses in Weltkoordinaten. */
|
||||
public record RiverPoint(float x, float y, float z, float width, float uvSpeed) {
|
||||
public static final float DEFAULT_WIDTH = 4.0f;
|
||||
public static final float RIVER_SPEED = 0.4f;
|
||||
public static final float WATERFALL_SPEED = 3.0f;
|
||||
public boolean isWaterfall() { return uvSpeed >= 1.5f; }
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
package de.blight.common;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Catmull-Rom-Spline-Glättung für Flusskontrollpunkte.
|
||||
* Die Originalpunkte bleiben als Kontrollpunkte erhalten; subdivide()
|
||||
* gibt eine dichtere Punktliste nur für die Visualisierung zurück.
|
||||
*/
|
||||
public final class RiverSpline {
|
||||
|
||||
/** Schritte pro Segment zwischen zwei Kontrollpunkten. */
|
||||
private static final int STEPS = 8;
|
||||
|
||||
private RiverSpline() {}
|
||||
|
||||
/**
|
||||
* Gibt eine Catmull-Rom-interpolierte Kopie der Punktliste zurück.
|
||||
* Weniger als 2 Punkte werden unverändert zurückgegeben.
|
||||
*/
|
||||
public static List<RiverPoint> subdivide(List<RiverPoint> pts) {
|
||||
int n = pts.size();
|
||||
if (n < 2) return new ArrayList<>(pts);
|
||||
|
||||
List<RiverPoint> result = new ArrayList<>(n * STEPS);
|
||||
|
||||
for (int i = 0; i < n - 1; i++) {
|
||||
// Ghost-Punkte an den Enden: Spiegelung des Nachbarn
|
||||
RiverPoint p0 = (i > 0) ? pts.get(i - 1) : ghost(pts.get(i), pts.get(i + 1));
|
||||
RiverPoint p1 = pts.get(i);
|
||||
RiverPoint p2 = pts.get(i + 1);
|
||||
RiverPoint p3 = (i < n - 2) ? pts.get(i + 2) : ghost(pts.get(i + 1), pts.get(i));
|
||||
|
||||
for (int s = 0; s < STEPS; s++) {
|
||||
result.add(eval(p0, p1, p2, p3, (float) s / STEPS));
|
||||
}
|
||||
}
|
||||
result.add(pts.get(n - 1)); // letzten Originalpunkt immer anhängen
|
||||
return result;
|
||||
}
|
||||
|
||||
// ── Catmull-Rom-Formel ────────────────────────────────────────────────────
|
||||
|
||||
private static RiverPoint eval(RiverPoint p0, RiverPoint p1,
|
||||
RiverPoint p2, RiverPoint p3, float t) {
|
||||
float t2 = t * t, t3 = t2 * t;
|
||||
float b0 = -t3 + 2*t2 - t;
|
||||
float b1 = 3*t3 - 5*t2 + 2;
|
||||
float b2 = -3*t3 + 4*t2 + t;
|
||||
float b3 = t3 - t2;
|
||||
|
||||
float x = 0.5f * (b0*p0.x() + b1*p1.x() + b2*p2.x() + b3*p3.x());
|
||||
float y = 0.5f * (b0*p0.y() + b1*p1.y() + b2*p2.y() + b3*p3.y());
|
||||
float z = 0.5f * (b0*p0.z() + b1*p1.z() + b2*p2.z() + b3*p3.z());
|
||||
float w = p1.width() + t * (p2.width() - p1.width());
|
||||
float s = p1.uvSpeed() + t * (p2.uvSpeed() - p1.uvSpeed());
|
||||
return new RiverPoint(x, y, z, w, s);
|
||||
}
|
||||
|
||||
/** Spiegelung von 'other' durch 'anchor' als Ghost-Kontrollpunkt. */
|
||||
private static RiverPoint ghost(RiverPoint anchor, RiverPoint other) {
|
||||
return new RiverPoint(
|
||||
2*anchor.x() - other.x(),
|
||||
2*anchor.y() - other.y(),
|
||||
2*anchor.z() - other.z(),
|
||||
anchor.width(),
|
||||
anchor.uvSpeed()
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -5,10 +5,11 @@ import java.nio.file.*;
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* Liest und schreibt platzierte Wasseroberflächen als tab-separierte Textdatei
|
||||
* Liest und schreibt platzierte Wasserflächen als tab-separierte Textdatei
|
||||
* ({@code blight_water.blw}) neben der Kartendatei.
|
||||
*
|
||||
* Spalten: x y z width depth
|
||||
* Format: seedX seedZ waterHeight
|
||||
* Die Form des Wasserkörpers wird per Flood-Fill zur Laufzeit rekonstruiert.
|
||||
*/
|
||||
public final class WaterBodyIO {
|
||||
|
||||
@@ -22,12 +23,11 @@ public final class WaterBodyIO {
|
||||
Path p = getPath();
|
||||
Files.createDirectories(p.getParent());
|
||||
try (BufferedWriter w = Files.newBufferedWriter(p)) {
|
||||
w.write("# x\ty\tz\twidth\tdepth");
|
||||
w.write("# seedX\tseedZ\twaterHeight");
|
||||
w.newLine();
|
||||
for (PlacedWater b : bodies) {
|
||||
w.write(String.format(Locale.ROOT,
|
||||
"%.5f\t%.5f\t%.5f\t%.5f\t%.5f%n",
|
||||
b.x(), b.y(), b.z(), b.width(), b.depth()));
|
||||
w.write(String.format(Locale.ROOT, "%.5f\t%.5f\t%.5f%n",
|
||||
b.seedX(), b.seedZ(), b.waterHeight()));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -40,14 +40,12 @@ public final class WaterBodyIO {
|
||||
line = line.strip();
|
||||
if (line.isEmpty() || line.startsWith("#")) continue;
|
||||
String[] f = line.split("\t", -1);
|
||||
if (f.length < 5) continue;
|
||||
if (f.length < 3) continue;
|
||||
try {
|
||||
list.add(new PlacedWater(
|
||||
Float.parseFloat(f[0]),
|
||||
Float.parseFloat(f[1]),
|
||||
Float.parseFloat(f[2]),
|
||||
Float.parseFloat(f[3]),
|
||||
Float.parseFloat(f[4])));
|
||||
Float.parseFloat(f[2])));
|
||||
} catch (NumberFormatException ignored) {}
|
||||
}
|
||||
return list;
|
||||
|
||||
@@ -21,11 +21,12 @@ import java.util.stream.Stream;
|
||||
* "characterId": "hero",
|
||||
* "name": "Der Held",
|
||||
* "modelPath": "Models/hero.j3o",
|
||||
* "animSetPath": "animations/sets/hero.j3o",
|
||||
* "animSetPath": "human",
|
||||
* ... (subclass fields via Gson)
|
||||
* }
|
||||
* Die Aktions-Zuweisung (IDLE → Clip-Name usw.) ist im AnimSet gespeichert
|
||||
* (animSetPath.replaceAll(".j3o", "") + ".animset.json").
|
||||
* animSetPath ist der Set-Name (ohne Pfad/Extension).
|
||||
* Die Aktions-Zuweisung liegt in animations/sets/{animSetPath}.animset.json.
|
||||
* Die Clip-Dateien liegen in animations/clips/{clipName}.j3o.
|
||||
*/
|
||||
public final class CharacterIO {
|
||||
|
||||
|
||||
Reference in New Issue
Block a user