Weiter gearbeitet

This commit is contained in:
2026-06-16 07:03:08 +02:00
parent a80269e681
commit a369647e9c
531 changed files with 8327 additions and 1381 deletions

View File

@@ -71,6 +71,33 @@ public final class MapData {
/** Normal-Map-Pfade für Gebirge (4 Slots, "" = keine Normal-Map). */
public final String[] upperNormalMaps = new String[]{"", "", "", ""};
/** Splatmap Rot-Kanal Dritte Gruppe (Slots 9-12, AlphaMap_2) [SPLAT_SIZE²]. */
public final byte[] thirdSplatR;
/** Splatmap Grün-Kanal Dritte Gruppe [SPLAT_SIZE²]. */
public final byte[] thirdSplatG;
/** Splatmap Blau-Kanal Dritte Gruppe [SPLAT_SIZE²]. */
public final byte[] thirdSplatB;
/** Splatmap Alpha-Kanal Dritte Gruppe [SPLAT_SIZE²]. */
public final byte[] thirdSplatA;
/** Texturpfade für dritte Gruppe (4 Slots, "" = Standard-Textur). */
public final String[] thirdTextures = new String[]{"", "", "", ""};
/** Normal-Map-Pfade für dritte Gruppe (4 Slots, "" = keine Normal-Map). */
public final String[] thirdNormalMaps = new String[]{"", "", "", ""};
/** Normal-Map-Priorisierungsreihenfolge (Slot-Index 0-11, absteigend nach Priorität). */
public int[] normalMapOrder = {0,1,2,3,4,5,6,7,8,9,10,11};
/** Displacement-Map-Pfade für Basis-Terrain (4 Slots, "" = keine). */
public String[] terrainDisplacementMaps = {"","","",""};
/** Displacement-Map-Pfade für Gebirge (4 Slots, "" = keine). */
public String[] upperDisplacementMaps = {"","","",""};
/** Displacement-Map-Pfade für dritte Gruppe (4 Slots, "" = keine). */
public String[] thirdDisplacementMaps = {"","","",""};
/** UV-Scale pro Slot in Welteinheiten (Meter pro Textur-Tile), 12 Werte für Slots 1-12. */
public float[] diffuseScales = {8f,8f,8f,8f, 8f,8f,8f,8f, 8f,8f,8f,8f};
/** Gras-Dichte [SPLAT_SIZE²], Bytes 0255 (0=kein Gras, 255=max Dichte). */
public final byte[] grassDensity;
@@ -102,6 +129,13 @@ public final class MapData {
/** Spawnpunkt Z-Koordinate in Welteinheiten (default: 0 = Kartenmitte). */
public float spawnZ = 0f;
/** Terrain-Slot (0-7) für flache Voxel-Flächen (TexFlat), -1 = nicht gesetzt. */
public int voxelFlatSlot = -1;
/** Terrain-Slot (0-7) für steile Wände (TexSteep), -1 = nicht gesetzt. */
public int voxelSteepSlot = -1;
/** Terrain-Slot (0-7) für Decken/Unterseiten (TexCeil), -1 = nicht gesetzt. */
public int voxelCeilSlot = -1;
public MapData() {
terrainHeight = new float[TERRAIN_VERTS * TERRAIN_VERTS];
upperTop = new float[UPPER_VERTS * UPPER_VERTS];
@@ -114,6 +148,10 @@ 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];
thirdSplatR = new byte [SPLAT_SIZE * SPLAT_SIZE];
thirdSplatG = new byte [SPLAT_SIZE * SPLAT_SIZE];
thirdSplatB = new byte [SPLAT_SIZE * SPLAT_SIZE];
thirdSplatA = new byte [SPLAT_SIZE * SPLAT_SIZE];
grassDensity = new byte [SPLAT_SIZE * SPLAT_SIZE];
grassHeightMap = new byte [SPLAT_SIZE * SPLAT_SIZE];
grassTextureMap = new byte [SPLAT_SIZE * SPLAT_SIZE];

View File

@@ -1,5 +1,8 @@
package de.blight.common;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.*;
import java.nio.*;
import java.nio.file.*;
@@ -27,6 +30,8 @@ import java.util.zip.*;
*/
public final class MapIO {
private static final Logger log = LoggerFactory.getLogger(MapIO.class);
/** System-Property, das vom Editor beim Spielstart auf eine temporäre Session-Kopie gesetzt wird. */
public static final String PROP_SESSION_MAP = "blight.session.map.path";
@@ -58,7 +63,7 @@ public final class MapIO {
}
private static final int MAGIC = 0x424C4947; // "BLIG"
private static final int VERSION = 11;
private static final int VERSION = 14;
// Größen älterer Saves (v≤9) für Migrations-Upsampling
private static final int OLD_TERRAIN_VERTS = 4097;
@@ -70,17 +75,17 @@ public final class MapIO {
public static boolean exists() {
Path p = getMapPath();
System.out.println("[MapIO] Suche Karte: " + p.toAbsolutePath());
log.info("[MapIO] Suche Karte: {}", p.toAbsolutePath());
if (Files.exists(p)) return true;
// Einmalige Migration vom alten Speicherort (world/blight_map.blm) nur im Nicht-Session-Modus
if (System.getProperty(PROP_SESSION_MAP) == null && Files.exists(MAP_PATH_OLD)) {
try {
Files.createDirectories(MAP_PATH.getParent());
Files.move(MAP_PATH_OLD, MAP_PATH);
System.out.println("[MapIO] Karte migriert: " + MAP_PATH_OLD + "" + MAP_PATH);
log.info("[MapIO] Karte migriert: {} → {}", MAP_PATH_OLD, MAP_PATH);
return true;
} catch (IOException e) {
System.err.println("[MapIO] Migration fehlgeschlagen: " + e.getMessage());
log.error("[MapIO] Migration fehlgeschlagen: {}", e.getMessage());
}
}
return false;
@@ -143,6 +148,22 @@ public final class MapIO {
// v11: normal-map-pfade
writeStrings(out, data.terrainNormalMaps);
writeStrings(out, data.upperNormalMaps);
// v12: voxel-slot-indizes
out.writeInt(data.voxelFlatSlot);
out.writeInt(data.voxelSteepSlot);
out.writeInt(data.voxelCeilSlot);
// v13: dritte splatmap + texturpfade
out.write(data.thirdSplatR);
out.write(data.thirdSplatG);
out.write(data.thirdSplatB);
out.write(data.thirdSplatA);
writeStrings(out, data.thirdTextures);
writeStrings(out, data.thirdNormalMaps);
// v14: displacement-maps + uv-scales
writeStrings(out, data.terrainDisplacementMaps);
writeStrings(out, data.upperDisplacementMaps);
writeStrings(out, data.thirdDisplacementMaps);
writeFloats(out, data.diffuseScales);
}
// Atomares Umbenennen: erst wenn die Datei vollständig ist ersetzen wir die alte.
// ATOMIC_MOVE schlägt auf manchen Systemen cross-device fehl → Fallback auf REPLACE_EXISTING.
@@ -266,6 +287,23 @@ public final class MapIO {
readStrings(in, data.terrainNormalMaps);
readStrings(in, data.upperNormalMaps);
}
if (version >= 12) {
data.voxelFlatSlot = in.readInt();
data.voxelSteepSlot = in.readInt();
data.voxelCeilSlot = in.readInt();
}
if (version >= 13) {
in.readFully(data.thirdSplatR); in.readFully(data.thirdSplatG);
in.readFully(data.thirdSplatB); in.readFully(data.thirdSplatA);
readStrings(in, data.thirdTextures);
readStrings(in, data.thirdNormalMaps);
}
if (version >= 14) {
readStrings(in, data.terrainDisplacementMaps);
readStrings(in, data.upperDisplacementMaps);
readStrings(in, data.thirdDisplacementMaps);
readFloats(in, data.diffuseScales);
}
}
return data;
}

View File

@@ -1,5 +1,7 @@
package de.blight.common;
import java.util.List;
/**
* Metadaten einer Model-Asset-Datei (.j3o.meta neben dem .j3o).
* Skalierung ist per-Achse; uniformScale=true → X/Y/Z werden gemeinsam angepasst.
@@ -19,16 +21,30 @@ public record ModelMeta(
boolean receiveShadow,
float randomScaleMin,
float randomScaleMax,
String lod1Path, // relativer Asset-Pfad; "" = nicht gesetzt
String lod1Path,
String lod2Path,
float lod1Distance, // ab dieser Distanz LOD1 anzeigen
float lod2Distance, // ab dieser Distanz LOD2 anzeigen
float cullDistance // ab dieser Distanz ausblenden
float lod1Distance,
float lod2Distance,
float cullDistance,
List<AttachedLight> attachedLights,
List<AttachedEmitter> attachedEmitters
) {
/** Lichtquelle relativ zum Modell-Ursprung. */
public record AttachedLight(
float offsetX, float offsetY, float offsetZ,
float r, float g, float b,
float intensity, float radius) {}
/** Partikel-Emitter relativ zum Modell-Ursprung. Preset: 0=Feuer, 1=Rauch, 2=Funken. */
public record AttachedEmitter(
float offsetX, float offsetY, float offsetZ,
int preset) {}
public static ModelMeta defaults(String j3oFileName) {
String name = j3oFileName.replaceFirst("\\.j3o$", "");
return new ModelMeta(name, "", "", 1f, 1f, 1f, true, 0f, 0f,
false, true, true, 1f, 1f,
"", "", 30f, 80f, 120f);
"", "", 30f, 80f, 120f,
List.of(), List.of());
}
}

View File

@@ -2,7 +2,7 @@ package de.blight.common;
import java.io.*;
import java.nio.file.*;
import java.util.Properties;
import java.util.*;
/** Liest und schreibt {@code .j3o.meta}-Dateien neben dem jeweiligen {@code .j3o}. */
public final class ModelMetaIO {
@@ -34,6 +34,28 @@ public final class ModelMetaIO {
p.setProperty("lod1Distance", String.valueOf(m.lod1Distance()));
p.setProperty("lod2Distance", String.valueOf(m.lod2Distance()));
p.setProperty("cullDistance", String.valueOf(m.cullDistance()));
// Anhänge: Lichter
List<ModelMeta.AttachedLight> lights = m.attachedLights();
p.setProperty("attachedLights.count", String.valueOf(lights.size()));
for (int i = 0; i < lights.size(); i++) {
ModelMeta.AttachedLight l = lights.get(i);
p.setProperty("attachedLight." + i, String.format(Locale.ROOT,
"%.5f|%.5f|%.5f|%.5f|%.5f|%.5f|%.5f|%.5f",
l.offsetX(), l.offsetY(), l.offsetZ(),
l.r(), l.g(), l.b(), l.intensity(), l.radius()));
}
// Anhänge: Emitter
List<ModelMeta.AttachedEmitter> emitters = m.attachedEmitters();
p.setProperty("attachedEmitters.count", String.valueOf(emitters.size()));
for (int i = 0; i < emitters.size(); i++) {
ModelMeta.AttachedEmitter e = emitters.get(i);
p.setProperty("attachedEmitter." + i, String.format(Locale.ROOT,
"%.5f|%.5f|%.5f|%d",
e.offsetX(), e.offsetY(), e.offsetZ(), e.preset()));
}
try (Writer w = Files.newBufferedWriter(metaPath(j3oPath))) {
p.store(w, null);
}
@@ -48,6 +70,42 @@ public final class ModelMetaIO {
} catch (IOException ignored) {}
}
String defName = j3oPath.getFileName().toString().replaceFirst("\\.j3o$", "");
// Anhänge: Lichter
int lightCount = parseInt(p, "attachedLights.count", 0);
List<ModelMeta.AttachedLight> lights = new ArrayList<>(lightCount);
for (int i = 0; i < lightCount; i++) {
String v = p.getProperty("attachedLight." + i, "");
if (!v.isEmpty()) {
String[] f = v.split("\\|", -1);
if (f.length >= 8) {
try {
lights.add(new ModelMeta.AttachedLight(
Float.parseFloat(f[0]), Float.parseFloat(f[1]), Float.parseFloat(f[2]),
Float.parseFloat(f[3]), Float.parseFloat(f[4]), Float.parseFloat(f[5]),
Float.parseFloat(f[6]), Float.parseFloat(f[7])));
} catch (NumberFormatException ignored) {}
}
}
}
// Anhänge: Emitter
int emitterCount = parseInt(p, "attachedEmitters.count", 0);
List<ModelMeta.AttachedEmitter> emitters = new ArrayList<>(emitterCount);
for (int i = 0; i < emitterCount; i++) {
String v = p.getProperty("attachedEmitter." + i, "");
if (!v.isEmpty()) {
String[] f = v.split("\\|", -1);
if (f.length >= 4) {
try {
emitters.add(new ModelMeta.AttachedEmitter(
Float.parseFloat(f[0]), Float.parseFloat(f[1]), Float.parseFloat(f[2]),
Integer.parseInt(f[3])));
} catch (NumberFormatException ignored) {}
}
}
}
return new ModelMeta(
p.getProperty("name", defName),
p.getProperty("category", ""),
@@ -67,7 +125,9 @@ public final class ModelMetaIO {
p.getProperty("lod2Path", ""),
parseFloat(p, "lod1Distance", 30f),
parseFloat(p, "lod2Distance", 80f),
parseFloat(p, "cullDistance", 120f)
parseFloat(p, "cullDistance", 120f),
Collections.unmodifiableList(lights),
Collections.unmodifiableList(emitters)
);
}
@@ -75,4 +135,9 @@ public final class ModelMetaIO {
try { return Float.parseFloat(p.getProperty(key, String.valueOf(def))); }
catch (NumberFormatException e) { return def; }
}
private static int parseInt(Properties p, String key, int def) {
try { return Integer.parseInt(p.getProperty(key, String.valueOf(def))); }
catch (NumberFormatException e) { return def; }
}
}

View File

@@ -42,6 +42,18 @@ public final class VoxelChunkIO {
return VoxelChunk.deserialize(Files.readAllBytes(getPath(cx, cy, cz)), cx, cy, cz);
}
/** Pfad der gebackenen J3O-Datei für einen LOD-Level. */
public static Path getBakedPath(int cx, int cy, int cz, int lod) {
String cyStr = cy < 0 ? "m" + (-cy) : String.valueOf(cy);
return ChunkTerrainIO.chunksDir()
.resolve(String.format("voxel_%02d_%s_%02d_baked_lod%d.j3o", cx, cyStr, cz, lod));
}
/** true wenn für (cx,cy,cz) ein gebackenes LOD0-Mesh vorliegt. */
public static boolean bakedExists(int cx, int cy, int cz) {
return Files.exists(getBakedPath(cx, cy, cz, 0));
}
/**
* Liest alle vorhandenen VoxelChunks aus dem Chunks-Verzeichnis.
* Gibt leere Liste zurück wenn kein Chunks-Verzeichnis existiert.