Objekte können jetzt Sounds haben (z.B. Lagerfeuer). Sound System optimiert

This commit is contained in:
2026-09-14 16:09:21 +02:00
parent 08c76a798d
commit 45f7ee1045
42 changed files with 1721 additions and 109 deletions

View File

@@ -28,6 +28,7 @@ public record ModelMeta(
float cullDistance,
List<AttachedLight> attachedLights,
List<AttachedEmitter> attachedEmitters,
List<AttachedSound> attachedSounds,
de.blight.common.model.InteractableType interactableType,
float interactableOffsetX,
float interactableOffsetY,
@@ -42,17 +43,51 @@ public record ModelMeta(
float r, float g, float b,
float intensity, float radius) {}
/** Partikel-Emitter relativ zum Modell-Ursprung. Preset: 0=Feuer, 1=Rauch, 2=Funken. */
/** Sound-Quelle relativ zum Modell-Ursprung. */
public record AttachedSound(
float offsetX, float offsetY, float offsetZ,
String soundPath,
float volume,
float radius) {}
/** Partikel-Emitter relativ zum Modell-Ursprung — vollständige Parameter wie PlacedEmitter. */
public record AttachedEmitter(
float offsetX, float offsetY, float offsetZ,
int preset) {}
String texturePath, int imagesX, int imagesY,
float startR, float startG, float startB, float startA,
float endR, float endG, float endB, float endA,
float startSize, float endSize,
float velX, float velY, float velZ, float velocityVariation,
float gravX, float gravY, float gravZ,
float lowLife, float highLife,
int maxParticles, float emitRate) {
public static AttachedEmitter fire(float ox, float oy, float oz) {
return new AttachedEmitter(ox, oy, oz,
"Effects/Explosion/flame.png", 2, 2,
1f, 0.7f, 0.1f, 1f, 1f, 0.1f, 0f, 0f,
0.5f, 1.2f, 0f, 2.5f, 0f, 0.5f, 0f, -0.1f, 0f, 1f, 3f, 60, 25f);
}
public static AttachedEmitter smoke(float ox, float oy, float oz) {
return new AttachedEmitter(ox, oy, oz,
"Effects/Smoke/Smoke.png", 1, 1,
0.5f, 0.5f, 0.5f, 0.4f, 0.2f, 0.2f, 0.2f, 0f,
0.8f, 2.5f, 0f, 1.2f, 0f, 0.3f, 0f, 0.05f, 0f, 3f, 6f, 24, 6f);
}
public static AttachedEmitter sparks(float ox, float oy, float oz) {
return new AttachedEmitter(ox, oy, oz,
"Effects/Explosion/spark.png", 1, 1,
1f, 0.9f, 0.2f, 1f, 1f, 0.3f, 0f, 0f,
0.05f, 0.02f, 0f, 4f, 0f, 1.0f, 0f, -5f, 0f, 0.5f, 2f, 50, 20f);
}
}
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,
List.of(), List.of(),
List.of(), List.of(), List.of(),
de.blight.common.model.InteractableType.NONE,
0f, 0.5f, 0f, 0f, "",
de.blight.common.ModelCategory.NONE);

View File

@@ -59,8 +59,27 @@ public final class ModelMetaIO {
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()));
"%.5f|%.5f|%.5f|%s|%d|%d|%.4f|%.4f|%.4f|%.4f|%.4f|%.4f|%.4f|%.4f|%.4f|%.4f|%.4f|%.4f|%.4f|%.4f|%.4f|%.4f|%.4f|%.4f|%.4f|%d|%.4f",
e.offsetX(), e.offsetY(), e.offsetZ(),
e.texturePath(), e.imagesX(), e.imagesY(),
e.startR(), e.startG(), e.startB(), e.startA(),
e.endR(), e.endG(), e.endB(), e.endA(),
e.startSize(), e.endSize(),
e.velX(), e.velY(), e.velZ(), e.velocityVariation(),
e.gravX(), e.gravY(), e.gravZ(),
e.lowLife(), e.highLife(),
e.maxParticles(), e.emitRate()));
}
// Anhänge: Sounds
List<ModelMeta.AttachedSound> sounds = m.attachedSounds();
p.setProperty("attachedSounds.count", String.valueOf(sounds.size()));
for (int i = 0; i < sounds.size(); i++) {
ModelMeta.AttachedSound s = sounds.get(i);
p.setProperty("attachedSound." + i, String.format(Locale.ROOT,
"%.5f|%.5f|%.5f|%s|%.4f|%.4f",
s.offsetX(), s.offsetY(), s.offsetZ(),
s.soundPath(), s.volume(), s.radius()));
}
try (Writer w = Files.newBufferedWriter(metaPath(j3oPath))) {
@@ -103,11 +122,48 @@ public final class ModelMetaIO {
String v = p.getProperty("attachedEmitter." + i, "");
if (!v.isEmpty()) {
String[] f = v.split("\\|", -1);
if (f.length >= 4) {
try {
try {
if (f.length >= 27) {
// Neues Format mit vollständigen Parametern
emitters.add(new ModelMeta.AttachedEmitter(
Float.parseFloat(f[0]), Float.parseFloat(f[1]), Float.parseFloat(f[2]),
Integer.parseInt(f[3])));
f[3], Integer.parseInt(f[4]), Integer.parseInt(f[5]),
Float.parseFloat(f[6]), Float.parseFloat(f[7]), Float.parseFloat(f[8]), Float.parseFloat(f[9]),
Float.parseFloat(f[10]), Float.parseFloat(f[11]), Float.parseFloat(f[12]), Float.parseFloat(f[13]),
Float.parseFloat(f[14]), Float.parseFloat(f[15]),
Float.parseFloat(f[16]), Float.parseFloat(f[17]), Float.parseFloat(f[18]), Float.parseFloat(f[19]),
Float.parseFloat(f[20]), Float.parseFloat(f[21]), Float.parseFloat(f[22]),
Float.parseFloat(f[23]), Float.parseFloat(f[24]),
Integer.parseInt(f[25]), Float.parseFloat(f[26])));
} else if (f.length >= 4) {
// Altes Format (offsetX|offsetY|offsetZ|preset) → Preset-Defaults übernehmen
float ox = Float.parseFloat(f[0]);
float oy = Float.parseFloat(f[1]);
float oz = Float.parseFloat(f[2]);
int preset = Integer.parseInt(f[3]);
emitters.add(switch (preset) {
case 1 -> ModelMeta.AttachedEmitter.smoke(ox, oy, oz);
case 2 -> ModelMeta.AttachedEmitter.sparks(ox, oy, oz);
default -> ModelMeta.AttachedEmitter.fire(ox, oy, oz);
});
}
} catch (NumberFormatException ignored) {}
}
}
// Anhänge: Sounds
int soundCount = parseInt(p, "attachedSounds.count", 0);
List<ModelMeta.AttachedSound> sounds = new ArrayList<>(soundCount);
for (int i = 0; i < soundCount; i++) {
String v = p.getProperty("attachedSound." + i, "");
if (!v.isEmpty()) {
String[] f = v.split("\\|", -1);
if (f.length >= 6) {
try {
sounds.add(new ModelMeta.AttachedSound(
Float.parseFloat(f[0]), Float.parseFloat(f[1]), Float.parseFloat(f[2]),
f[3],
Float.parseFloat(f[4]), Float.parseFloat(f[5])));
} catch (NumberFormatException ignored) {}
}
}
@@ -135,6 +191,7 @@ public final class ModelMetaIO {
parseFloat(p, "cullDistance", 120f),
Collections.unmodifiableList(lights),
Collections.unmodifiableList(emitters),
Collections.unmodifiableList(sounds),
de.blight.common.model.InteractableType.fromString(
p.getProperty("interactableType", "NONE")),
parseFloat(p, "interactableOffsetX", 0f),

View File

@@ -0,0 +1,8 @@
package de.blight.common;
public record PlacedSound(
float x, float y, float z,
String soundPath,
float volume,
float radius
) {}

View File

@@ -0,0 +1,58 @@
package de.blight.common;
import java.io.*;
import java.nio.file.*;
import java.util.*;
/**
* Liest und schreibt platzierte Sound-Quellen als tab-separierte Textdatei
* ({@code blight_sounds.bls}) neben der Kartendatei.
*
* Spalten: x y z soundPath volume radius
*/
public final class SoundIO {
private SoundIO() {}
public static Path getPath() {
return MapIO.getMapPath().resolveSibling("blight_sounds.bls");
}
public static void save(List<PlacedSound> sounds) throws IOException {
Path p = getPath();
Files.createDirectories(p.getParent());
try (BufferedWriter w = Files.newBufferedWriter(p)) {
w.write("# x\ty\tz\tsoundPath\tvolume\tradius");
w.newLine();
for (PlacedSound s : sounds) {
w.write(String.format(Locale.ROOT,
"%.5f\t%.5f\t%.5f\t%s\t%.4f\t%.4f%n",
s.x(), s.y(), s.z(),
s.soundPath(),
s.volume(), s.radius()));
}
}
}
public static List<PlacedSound> load() throws IOException {
Path p = getPath();
if (!Files.exists(p)) return List.of();
List<PlacedSound> list = new ArrayList<>();
for (String line : Files.readAllLines(p)) {
line = line.strip();
if (line.isEmpty() || line.startsWith("#")) continue;
String[] f = line.split("\t", -1);
if (f.length < 6) continue;
try {
list.add(new PlacedSound(
Float.parseFloat(f[0]),
Float.parseFloat(f[1]),
Float.parseFloat(f[2]),
f[3],
Float.parseFloat(f[4]),
Float.parseFloat(f[5])));
} catch (NumberFormatException ignored) {}
}
return list;
}
}