Commit vor Voxel Update für die Klippen
This commit is contained in:
16
blight-common/src/main/java/de/blight/common/BlightHome.java
Normal file
16
blight-common/src/main/java/de/blight/common/BlightHome.java
Normal file
@@ -0,0 +1,16 @@
|
||||
package de.blight.common;
|
||||
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
|
||||
/** Basisverzeichnis für alle benutzerspezifischen Laufzeitdaten: {@code ~/.blight/}. */
|
||||
public final class BlightHome {
|
||||
|
||||
public static final Path PATH = Paths.get(System.getProperty("user.home"), ".blight");
|
||||
|
||||
private BlightHome() {}
|
||||
|
||||
public static Path resolve(String first, String... more) {
|
||||
return PATH.resolve(Paths.get(first, more));
|
||||
}
|
||||
}
|
||||
@@ -27,6 +27,9 @@ import java.util.zip.*;
|
||||
*/
|
||||
public final class MapIO {
|
||||
|
||||
/** 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";
|
||||
|
||||
private static final Path PROJECT_ROOT = findProjectRoot();
|
||||
private static final Path MAP_PATH = PROJECT_ROOT.resolve(
|
||||
Paths.get("blight-map", "src", "main", "map", "blight_map.blm"));
|
||||
@@ -66,10 +69,11 @@ public final class MapIO {
|
||||
// ── Public API ────────────────────────────────────────────────────────────
|
||||
|
||||
public static boolean exists() {
|
||||
System.out.println("[MapIO] Suche Karte: " + MAP_PATH.toAbsolutePath());
|
||||
if (Files.exists(MAP_PATH)) return true;
|
||||
// Einmalige Migration vom alten Speicherort (world/blight_map.blm)
|
||||
if (Files.exists(MAP_PATH_OLD)) {
|
||||
Path p = getMapPath();
|
||||
System.out.println("[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);
|
||||
@@ -82,7 +86,16 @@ public final class MapIO {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gibt den Pfad zur aktiven Kartendatei zurück.
|
||||
* Im Spielmodus (gestartet vom Editor) wird {@value PROP_SESSION_MAP} bevorzugt
|
||||
* und zeigt auf eine temporäre Kopie, sodass die Editor-Karte unberührt bleibt.
|
||||
*/
|
||||
public static Path getProjectRoot() { return PROJECT_ROOT; }
|
||||
|
||||
public static Path getMapPath() {
|
||||
String session = System.getProperty(PROP_SESSION_MAP);
|
||||
if (session != null) return Paths.get(session).toAbsolutePath();
|
||||
return MAP_PATH.toAbsolutePath();
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,12 @@
|
||||
package de.blight.common;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
/** Ein Item-Pickup das auf der Spielwelt liegt (Kartendaten). */
|
||||
public record PlacedItem(String itemId, float x, float y, float z) {}
|
||||
public record PlacedItem(String uuid, String itemId, float x, float y, float z) {
|
||||
|
||||
/** Erzeugt ein neues PlacedItem mit automatisch generierter UUID. */
|
||||
public static PlacedItem create(String itemId, float x, float y, float z) {
|
||||
return new PlacedItem(UUID.randomUUID().toString(), itemId, x, y, z);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,13 +1,17 @@
|
||||
package de.blight.common;
|
||||
|
||||
import java.io.*;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.*;
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* Liest und schreibt auf der Karte liegende Items (Pickups).
|
||||
* Datei: {@code blight_placed_items.bpi} neben {@code blight_map.blm}.
|
||||
* Format: {@code itemId\tx\ty\tz}
|
||||
* Format: {@code uuid\titemId\tx\ty\tz}
|
||||
*
|
||||
* Migration: altes 4-Spalten-Format (itemId\tx\ty\tz) wird automatisch
|
||||
* erkannt und deterministisch mit einer UUID versehen.
|
||||
*/
|
||||
public final class PlacedItemIO {
|
||||
|
||||
@@ -21,11 +25,11 @@ public final class PlacedItemIO {
|
||||
Path p = getPath();
|
||||
Files.createDirectories(p.getParent());
|
||||
try (BufferedWriter w = Files.newBufferedWriter(p)) {
|
||||
w.write("# itemId\tx\ty\tz");
|
||||
w.write("# uuid\titemId\tx\ty\tz");
|
||||
w.newLine();
|
||||
for (PlacedItem it : items) {
|
||||
w.write(String.format(Locale.ROOT, "%s\t%.5f\t%.5f\t%.5f%n",
|
||||
it.itemId(), it.x(), it.y(), it.z()));
|
||||
w.write(String.format(Locale.ROOT, "%s\t%s\t%.5f\t%.5f\t%.5f%n",
|
||||
it.uuid(), it.itemId(), it.x(), it.y(), it.z()));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -34,16 +38,23 @@ public final class PlacedItemIO {
|
||||
Path p = getPath();
|
||||
if (!Files.exists(p)) return List.of();
|
||||
List<PlacedItem> list = new ArrayList<>();
|
||||
for (String line : Files.readAllLines(p)) {
|
||||
for (String line : Files.readAllLines(p, StandardCharsets.UTF_8)) {
|
||||
line = line.strip();
|
||||
if (line.isEmpty() || line.startsWith("#")) continue;
|
||||
String[] f = line.split("\t", -1);
|
||||
if (f.length < 4) continue;
|
||||
try {
|
||||
list.add(new PlacedItem(f[0],
|
||||
Float.parseFloat(f[1]),
|
||||
Float.parseFloat(f[2]),
|
||||
Float.parseFloat(f[3])));
|
||||
if (f.length >= 5) {
|
||||
// Aktuelles Format: uuid itemId x y z
|
||||
list.add(new PlacedItem(f[0], f[1],
|
||||
Float.parseFloat(f[2]), Float.parseFloat(f[3]), Float.parseFloat(f[4])));
|
||||
} else if (f.length == 4) {
|
||||
// Altes Format: itemId x y z → UUID deterministisch aus Inhalt ableiten
|
||||
String deterministicId = UUID.nameUUIDFromBytes(
|
||||
("legacy:" + f[0] + ":" + f[1] + ":" + f[2] + ":" + f[3])
|
||||
.getBytes(StandardCharsets.UTF_8)).toString();
|
||||
list.add(new PlacedItem(deterministicId, f[0],
|
||||
Float.parseFloat(f[1]), Float.parseFloat(f[2]), Float.parseFloat(f[3])));
|
||||
}
|
||||
} catch (NumberFormatException ignored) {}
|
||||
}
|
||||
return list;
|
||||
|
||||
34
blight-common/src/main/java/de/blight/common/SaveGame.java
Normal file
34
blight-common/src/main/java/de/blight/common/SaveGame.java
Normal file
@@ -0,0 +1,34 @@
|
||||
package de.blight.common;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* Spielstand-Modell (Delta-basiert).
|
||||
* Speichert nur Abweichungen vom Ausgangs-Kartenzustand:
|
||||
* aufgesammelte Items, besiegte Gegner und den Charakter-Zustand.
|
||||
*/
|
||||
public class SaveGame {
|
||||
|
||||
public static final int CURRENT_VERSION = 1;
|
||||
|
||||
public int version = CURRENT_VERSION;
|
||||
public String savedAt;
|
||||
|
||||
public CharacterSave character = new CharacterSave();
|
||||
public WorldSave world = new WorldSave();
|
||||
|
||||
public static class CharacterSave {
|
||||
/** true sobald mindestens einmal eine Position gespeichert wurde. */
|
||||
public boolean positionSaved = false;
|
||||
public float x, y, z;
|
||||
/** itemId → Anzahl */
|
||||
public Map<String, Integer> inventory = new LinkedHashMap<>();
|
||||
}
|
||||
|
||||
public static class WorldSave {
|
||||
/** UUIDs der PlacedItems, die der Spieler aufgesammelt hat. */
|
||||
public Set<String> pickedUpItems = new HashSet<>();
|
||||
/** IDs besiegter Gegner (für zukünftige Implementierung). */
|
||||
public Set<String> defeatedEnemies = new HashSet<>();
|
||||
}
|
||||
}
|
||||
47
blight-common/src/main/java/de/blight/common/SaveGameIO.java
Normal file
47
blight-common/src/main/java/de/blight/common/SaveGameIO.java
Normal file
@@ -0,0 +1,47 @@
|
||||
package de.blight.common;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.GsonBuilder;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.*;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
|
||||
/** Liest und schreibt {@link SaveGame}-Instanzen als JSON. */
|
||||
public final class SaveGameIO {
|
||||
|
||||
private static final Gson GSON = new GsonBuilder().setPrettyPrinting().create();
|
||||
|
||||
private SaveGameIO() {}
|
||||
|
||||
public static Path getSavePath() {
|
||||
return BlightHome.resolve("saves", "savegame.json");
|
||||
}
|
||||
|
||||
public static boolean exists() {
|
||||
return Files.exists(getSavePath());
|
||||
}
|
||||
|
||||
public static SaveGame load() throws IOException {
|
||||
SaveGame sg = GSON.fromJson(
|
||||
Files.readString(getSavePath(), StandardCharsets.UTF_8), SaveGame.class);
|
||||
// Gson setzt fehlende Collections auf null → sicher initialisieren
|
||||
if (sg.character == null) sg.character = new SaveGame.CharacterSave();
|
||||
if (sg.character.inventory == null) sg.character.inventory = new java.util.LinkedHashMap<>();
|
||||
if (sg.world == null) sg.world = new SaveGame.WorldSave();
|
||||
if (sg.world.pickedUpItems == null) sg.world.pickedUpItems = new java.util.HashSet<>();
|
||||
if (sg.world.defeatedEnemies == null) sg.world.defeatedEnemies = new java.util.HashSet<>();
|
||||
return sg;
|
||||
}
|
||||
|
||||
public static void save(SaveGame sg) throws IOException {
|
||||
sg.savedAt = LocalDateTime.now().format(DateTimeFormatter.ISO_LOCAL_DATE_TIME);
|
||||
Path p = getSavePath();
|
||||
Files.createDirectories(p.getParent());
|
||||
Path tmp = p.resolveSibling("savegame.tmp");
|
||||
Files.writeString(tmp, GSON.toJson(sg), StandardCharsets.UTF_8);
|
||||
Files.move(tmp, p, StandardCopyOption.REPLACE_EXISTING);
|
||||
}
|
||||
}
|
||||
164
blight-common/src/main/java/de/blight/common/VoxelChunk.java
Normal file
164
blight-common/src/main/java/de/blight/common/VoxelChunk.java
Normal file
@@ -0,0 +1,164 @@
|
||||
package de.blight.common;
|
||||
|
||||
import java.io.*;
|
||||
import java.util.Arrays;
|
||||
import java.util.zip.*;
|
||||
|
||||
/**
|
||||
* Voxel-Daten für einen 128×128×128m Welt-Bereich (1m/Voxel, passend zu CHUNK_VERTS).
|
||||
*
|
||||
* Koordinaten-Schema:
|
||||
* cx, cz – horizontaler Chunk-Index (identisch zu TerrainChunk)
|
||||
* cy – vertikaler Layer: 0 = Welt-Y 0..128m, -1 = -128..0m, usw.
|
||||
*
|
||||
* density[]: signed byte, < 0 = Luft, > 0 = Solid, 0 = Isosurface.
|
||||
* Standard: Byte.MIN_VALUE (= -128, vollständig Luft).
|
||||
* material[]: byte 0–3, welche der 4 Voxel-Texturen.
|
||||
* Lazy-initialisiert; null → alles Material 0.
|
||||
*/
|
||||
public final class VoxelChunk {
|
||||
|
||||
/** Sampling-Punkte pro Achse (= CHUNK_VERTS = 129). */
|
||||
public static final int SIZE = 129;
|
||||
/** Zellen pro Achse (SIZE - 1 = 128). */
|
||||
public static final int CELLS = SIZE - 1;
|
||||
|
||||
public final int cx, cy, cz;
|
||||
|
||||
/** Dichte-Feld, lazy: null = alles Luft (Byte.MIN_VALUE). */
|
||||
private byte[] density;
|
||||
/** Material-IDs, lazy: null = alles 0. */
|
||||
private byte[] material;
|
||||
|
||||
public volatile boolean dirty = false;
|
||||
|
||||
private static final int MAGIC = 0x424C5643; // "BLVC"
|
||||
private static final int VERSION = 1;
|
||||
|
||||
public VoxelChunk(int cx, int cy, int cz) {
|
||||
this.cx = cx; this.cy = cy; this.cz = cz;
|
||||
}
|
||||
|
||||
// ── Zugriff ────────────────────────────────────────────────────────────────
|
||||
|
||||
public int idx(int x, int y, int z) { return y * SIZE * SIZE + z * SIZE + x; }
|
||||
|
||||
public byte getDensity(int x, int y, int z) {
|
||||
return density == null ? Byte.MIN_VALUE : density[idx(x, y, z)];
|
||||
}
|
||||
|
||||
public void setDensity(int x, int y, int z, byte d) {
|
||||
if (density == null) { density = new byte[SIZE*SIZE*SIZE]; Arrays.fill(density, Byte.MIN_VALUE); }
|
||||
density[idx(x, y, z)] = d;
|
||||
dirty = true;
|
||||
}
|
||||
|
||||
public byte getMaterial(int x, int y, int z) {
|
||||
return material == null ? 0 : material[idx(x, y, z)];
|
||||
}
|
||||
|
||||
public void setMaterial(int x, int y, int z, byte m) {
|
||||
if (material == null) material = new byte[SIZE*SIZE*SIZE];
|
||||
material[idx(x, y, z)] = m;
|
||||
dirty = true;
|
||||
}
|
||||
|
||||
public boolean isSolid(int x, int y, int z) { return getDensity(x, y, z) > 0; }
|
||||
|
||||
public boolean isEmpty() { return density == null; }
|
||||
|
||||
// ── Kugelförmiger Pinsel ──────────────────────────────────────────────────
|
||||
|
||||
/**
|
||||
* Setzt alle Voxel innerhalb des Radius (in lokalen Einheiten) auf den gegebenen Wert.
|
||||
* localX/Y/Z sind Mittelpunkt-Koordinaten im lokalen Voxel-Raum (0..128).
|
||||
* densityVal: > 0 = solid hinzufügen, Byte.MIN_VALUE = Luft (entfernen).
|
||||
* matId: 0–3, wird nur gesetzt wenn densityVal > 0.
|
||||
*/
|
||||
public void applyBrush(float localX, float localY, float localZ,
|
||||
float radius, byte densityVal, byte matId) {
|
||||
int x0 = Math.max(0, (int)(localX - radius));
|
||||
int x1 = Math.min(SIZE-1, (int)Math.ceil(localX + radius));
|
||||
int y0 = Math.max(0, (int)(localY - radius));
|
||||
int y1 = Math.min(SIZE-1, (int)Math.ceil(localY + radius));
|
||||
int z0 = Math.max(0, (int)(localZ - radius));
|
||||
int z1 = Math.min(SIZE-1, (int)Math.ceil(localZ + radius));
|
||||
float r2 = radius * radius;
|
||||
for (int y = y0; y <= y1; y++) {
|
||||
float dy = y - localY;
|
||||
for (int z = z0; z <= z1; z++) {
|
||||
float dz = z - localZ;
|
||||
for (int x = x0; x <= x1; x++) {
|
||||
float dx = x - localX;
|
||||
if (dx*dx + dy*dy + dz*dz <= r2) {
|
||||
setDensity(x, y, z, densityVal);
|
||||
if (densityVal > 0) setMaterial(x, y, z, matId);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ── Serialisierung ────────────────────────────────────────────────────────
|
||||
|
||||
public byte[] serialize() throws IOException {
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream(4096);
|
||||
try (DataOutputStream out = new DataOutputStream(
|
||||
new BufferedOutputStream(new GZIPOutputStream(baos)))) {
|
||||
out.writeInt(MAGIC);
|
||||
out.writeInt(VERSION);
|
||||
out.writeInt(cx); out.writeInt(cy); out.writeInt(cz);
|
||||
boolean hasDensity = density != null;
|
||||
boolean hasMaterial = material != null;
|
||||
out.writeBoolean(hasDensity);
|
||||
if (hasDensity) out.write(density);
|
||||
out.writeBoolean(hasMaterial);
|
||||
if (hasMaterial) out.write(material);
|
||||
}
|
||||
return baos.toByteArray();
|
||||
}
|
||||
|
||||
public static VoxelChunk deserialize(byte[] data, int cx, int cy, int cz) throws IOException {
|
||||
VoxelChunk c = new VoxelChunk(cx, cy, cz);
|
||||
try (DataInputStream in = new DataInputStream(
|
||||
new BufferedInputStream(new GZIPInputStream(
|
||||
new ByteArrayInputStream(data))))) {
|
||||
if (in.readInt() != MAGIC) throw new IOException("Kein VoxelChunk-Magic");
|
||||
int ver = in.readInt();
|
||||
if (ver != VERSION) throw new IOException("Unbekannte Chunk-Version: " + ver);
|
||||
in.readInt(); in.readInt(); in.readInt(); // cx,cy,cz (aus Dateiname)
|
||||
if (in.readBoolean()) {
|
||||
c.density = new byte[SIZE*SIZE*SIZE];
|
||||
in.readFully(c.density);
|
||||
}
|
||||
if (in.readBoolean()) {
|
||||
c.material = new byte[SIZE*SIZE*SIZE];
|
||||
in.readFully(c.material);
|
||||
}
|
||||
}
|
||||
c.dirty = false;
|
||||
return c;
|
||||
}
|
||||
|
||||
// ── Koordinaten-Hilfsmethoden ─────────────────────────────────────────────
|
||||
|
||||
/** Welt-Y des Voxels mit localY, ausgehend von diesem cy-Layer. */
|
||||
public static float toWorldY(int cy, int localY) { return cy * CELLS + localY; }
|
||||
/** Welt-X des Voxels mit localX, ausgehend von cx. */
|
||||
public static float toWorldX(int cx, int localX) { return cx * CELLS - 2048f + localX; }
|
||||
/** Welt-Z des Voxels mit localZ, ausgehend von cz. */
|
||||
public static float toWorldZ(int cz, int localZ) { return cz * CELLS - 2048f + localZ; }
|
||||
|
||||
/** cy-Layer für Welt-Y. */
|
||||
public static int worldYToCy(float worldY) { return Math.floorDiv((int)worldY, CELLS); }
|
||||
/** Lokales Y innerhalb des cy-Layers. */
|
||||
public static float worldYToLocal(float worldY, int cy) { return worldY - cy * (float)CELLS; }
|
||||
/** cx für Welt-X. */
|
||||
public static int worldXToCx(float worldX) { return (int)((worldX + 2048f) / CELLS); }
|
||||
/** Lokales X im cx-Chunk. */
|
||||
public static float worldXToLocal(float worldX, int cx){ return worldX - (cx * CELLS - 2048f); }
|
||||
/** cz für Welt-Z. */
|
||||
public static int worldZToCz(float worldZ) { return (int)((worldZ + 2048f) / CELLS); }
|
||||
/** Lokales Z im cz-Chunk. */
|
||||
public static float worldZToLocal(float worldZ, int cz){ return worldZ - (cz * CELLS - 2048f); }
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
package de.blight.common;
|
||||
|
||||
import java.io.*;
|
||||
import java.nio.file.*;
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* Laden und Speichern von {@link VoxelChunk}s als {@code .blvc}-Dateien
|
||||
* im selben Verzeichnis wie die Terrain-Chunks.
|
||||
*
|
||||
* Dateiname: {@code voxel_CX_CY_CZ.blvc} (CY kann negativ sein → z.B. voxel_16_m1_16.blvc)
|
||||
*/
|
||||
public final class VoxelChunkIO {
|
||||
|
||||
private VoxelChunkIO() {}
|
||||
|
||||
public static Path getPath(int cx, int cy, int cz) {
|
||||
// Negative cy mit 'm' kodieren: voxel_16_m1_16.blvc
|
||||
String cyStr = cy < 0 ? "m" + (-cy) : String.valueOf(cy);
|
||||
return ChunkTerrainIO.chunksDir()
|
||||
.resolve(String.format("voxel_%02d_%s_%02d.blvc", cx, cyStr, cz));
|
||||
}
|
||||
|
||||
public static boolean exists(int cx, int cy, int cz) {
|
||||
return Files.exists(getPath(cx, cy, cz));
|
||||
}
|
||||
|
||||
public static void save(VoxelChunk chunk) throws IOException {
|
||||
Path p = getPath(chunk.cx, chunk.cy, chunk.cz);
|
||||
Path tmp = p.resolveSibling(p.getFileName() + ".tmp");
|
||||
Files.createDirectories(p.getParent());
|
||||
Files.write(tmp, chunk.serialize());
|
||||
try {
|
||||
Files.move(tmp, p, StandardCopyOption.REPLACE_EXISTING, StandardCopyOption.ATOMIC_MOVE);
|
||||
} catch (AtomicMoveNotSupportedException e) {
|
||||
Files.move(tmp, p, StandardCopyOption.REPLACE_EXISTING);
|
||||
}
|
||||
chunk.dirty = false;
|
||||
}
|
||||
|
||||
public static VoxelChunk load(int cx, int cy, int cz) throws IOException {
|
||||
return VoxelChunk.deserialize(Files.readAllBytes(getPath(cx, cy, cz)), cx, cy, cz);
|
||||
}
|
||||
|
||||
/**
|
||||
* Liest alle vorhandenen VoxelChunks aus dem Chunks-Verzeichnis.
|
||||
* Gibt leere Liste zurück wenn kein Chunks-Verzeichnis existiert.
|
||||
*/
|
||||
public static List<VoxelChunk> loadAll() {
|
||||
List<VoxelChunk> result = new ArrayList<>();
|
||||
Path dir = ChunkTerrainIO.chunksDir();
|
||||
if (!Files.isDirectory(dir)) return result;
|
||||
try (DirectoryStream<Path> ds = Files.newDirectoryStream(dir, "voxel_*.blvc")) {
|
||||
for (Path p : ds) {
|
||||
String name = p.getFileName().toString()
|
||||
.replace("voxel_", "").replace(".blvc", "");
|
||||
String[] parts = name.split("_");
|
||||
if (parts.length != 3) continue;
|
||||
try {
|
||||
int cx = Integer.parseInt(parts[0]);
|
||||
int cy = parts[1].startsWith("m")
|
||||
? -Integer.parseInt(parts[1].substring(1))
|
||||
: Integer.parseInt(parts[1]);
|
||||
int cz = Integer.parseInt(parts[2]);
|
||||
result.add(VoxelChunk.deserialize(Files.readAllBytes(p), cx, cy, cz));
|
||||
} catch (Exception ignored) {}
|
||||
}
|
||||
} catch (IOException ignored) {}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
package de.blight.common.model;
|
||||
|
||||
public enum CharacterStat {
|
||||
|
||||
CURRENT_HP ("HP (aktuell)"),
|
||||
CURRENT_STAMINA ("Stamina (aktuell)"),
|
||||
CURRENT_MANA ("Mana (aktuell)"),
|
||||
MAX_HP ("HP (maximal)"),
|
||||
MAX_STAMINA ("Stamina (maximal)"),
|
||||
MAX_MANA ("Mana (maximal)"),
|
||||
OPEN_HP_REGENERATION ("HP-Regeneration"),
|
||||
OPEN_MANA_REGENERATION ("Mana-Regeneration"),
|
||||
OPEN_STAMINA_REGENERATION("Stamina-Regeneration");
|
||||
|
||||
private final String displayName;
|
||||
|
||||
CharacterStat(String displayName) { this.displayName = displayName; }
|
||||
|
||||
public void apply(MainCharacter c, int value) {
|
||||
switch (this) {
|
||||
case CURRENT_HP -> c.heal(value);
|
||||
case CURRENT_STAMINA -> c.restoreStamina(value);
|
||||
case CURRENT_MANA -> c.restoreMana(value);
|
||||
case MAX_HP -> c.setMaxHp(c.getMaxHp() + value);
|
||||
case MAX_STAMINA -> c.setMaxStamina(c.getMaxStamina() + value);
|
||||
case MAX_MANA -> c.setMaxMana(c.getMaxMana() + value);
|
||||
case OPEN_HP_REGENERATION -> c.setOpenHpRegeneration(c.getOpenHpRegeneration() + value);
|
||||
case OPEN_MANA_REGENERATION -> c.setOpenManaRegeneration(c.getOpenManaRegeneration() + value);
|
||||
case OPEN_STAMINA_REGENERATION -> c.setOpenStaminaRegeneration(c.getOpenStaminaRegeneration() + value);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() { return displayName; }
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
package de.blight.common.model;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.Setter;
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class ConsumableEffect {
|
||||
private CharacterStat stat;
|
||||
private int value;
|
||||
}
|
||||
@@ -1,11 +1,18 @@
|
||||
package de.blight.common.model;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
|
||||
public class Inventar {
|
||||
|
||||
private HashMap<Item, Integer> items = new HashMap<Item, Integer>();
|
||||
|
||||
/** Gibt eine unveränderliche Sicht auf alle Items und ihre Anzahlen zurück. */
|
||||
public Map<Item, Integer> getItems() {
|
||||
return Collections.unmodifiableMap(items);
|
||||
}
|
||||
|
||||
public void collect(Item item) {
|
||||
add(item, 1);
|
||||
|
||||
@@ -3,19 +3,27 @@ package de.blight.common.model;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
public class Item implements Interactable {
|
||||
|
||||
private String itemId;
|
||||
private ItemCategory category;
|
||||
private ItemSubCategory subCategory;
|
||||
private TextReference name;
|
||||
private TextReference description;
|
||||
private int worthGold;
|
||||
private ObjectReference modelRef;
|
||||
|
||||
private boolean consumable;
|
||||
private List<ConsumableEffect> effects;
|
||||
|
||||
public void use(MainCharacter character) {
|
||||
|
||||
if (!consumable || effects == null) return;
|
||||
for (ConsumableEffect effect : effects) {
|
||||
if (effect.getStat() != null) effect.getStat().apply(character, effect.getValue());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -6,6 +6,6 @@ public enum ItemCategory {
|
||||
GEAR,
|
||||
CONSUMABLES,
|
||||
QUEST_ITEMS,
|
||||
USABLES,
|
||||
MISC;
|
||||
CRAFTING_ITEMS,
|
||||
MISC_ITEMS;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
package de.blight.common.model;
|
||||
|
||||
import static de.blight.common.model.ItemCategory.CONSUMABLES;
|
||||
import static de.blight.common.model.ItemCategory.CRAFTING_ITEMS;
|
||||
import static de.blight.common.model.ItemCategory.GEAR;
|
||||
import static de.blight.common.model.ItemCategory.MISC_ITEMS;
|
||||
import static de.blight.common.model.ItemCategory.QUEST_ITEMS;
|
||||
import static de.blight.common.model.ItemCategory.WEAPON;
|
||||
import static de.blight.common.model.ItemSubCategory.ARMOR;
|
||||
import static de.blight.common.model.ItemSubCategory.AXE;
|
||||
import static de.blight.common.model.ItemSubCategory.FOOD;
|
||||
import static de.blight.common.model.ItemSubCategory.HALBERD;
|
||||
import static de.blight.common.model.ItemSubCategory.HELM;
|
||||
import static de.blight.common.model.ItemSubCategory.MAGICAL;
|
||||
import static de.blight.common.model.ItemSubCategory.MAGICAL_ITEM;
|
||||
import static de.blight.common.model.ItemSubCategory.MISC;
|
||||
import static de.blight.common.model.ItemSubCategory.NECKLACE;
|
||||
import static de.blight.common.model.ItemSubCategory.PERMANENT_POTION;
|
||||
import static de.blight.common.model.ItemSubCategory.PLANT;
|
||||
import static de.blight.common.model.ItemSubCategory.POTION;
|
||||
import static de.blight.common.model.ItemSubCategory.QUEST_ITEM;
|
||||
import static de.blight.common.model.ItemSubCategory.RING;
|
||||
import static de.blight.common.model.ItemSubCategory.SHIELD;
|
||||
import static de.blight.common.model.ItemSubCategory.STAFF;
|
||||
import static de.blight.common.model.ItemSubCategory.SWORD;
|
||||
import static de.blight.common.model.ItemSubCategory.TECHNICAL;
|
||||
import static de.blight.common.model.ItemSubCategory.TWO_HANDED_SWORD;
|
||||
|
||||
import java.util.EnumMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
|
||||
public class ItemCategoryManager {
|
||||
|
||||
private static final Map<ItemCategory, List<ItemSubCategory>> mapping = new EnumMap<ItemCategory, List<ItemSubCategory>>(ItemCategory.class);
|
||||
|
||||
static {
|
||||
mapping.put(WEAPON, List.of(SWORD, TWO_HANDED_SWORD, STAFF, HALBERD, AXE));
|
||||
mapping.put(GEAR, List.of(RING, NECKLACE, ARMOR, HELM, SHIELD));
|
||||
mapping.put(CONSUMABLES, List.of(POTION, PERMANENT_POTION, FOOD, MAGICAL_ITEM));
|
||||
mapping.put(QUEST_ITEMS, List.of(QUEST_ITEM));
|
||||
mapping.put(CRAFTING_ITEMS, List.of(PLANT, TECHNICAL, MAGICAL));
|
||||
mapping.put(MISC_ITEMS, List.of(MISC));
|
||||
}
|
||||
|
||||
/** Gibt alle gültigen SubKategorien für eine Kategorie zurück (nie null, ggf. leer). */
|
||||
public static List<ItemSubCategory> getSubCategories(ItemCategory cat) {
|
||||
return mapping.getOrDefault(cat, List.of());
|
||||
}
|
||||
|
||||
public static boolean isValid(ItemCategory cat, ItemSubCategory sub) {
|
||||
return mapping.get(cat).contains(sub);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
package de.blight.common.model;
|
||||
|
||||
public enum ItemSubCategory {
|
||||
|
||||
SWORD, TWO_HANDED_SWORD, STAFF, HALBERD, AXE, //WEAPONS
|
||||
RING, NECKLACE, ARMOR, HELM, SHIELD, //GEAR
|
||||
POTION, PERMANENT_POTION, FOOD, MAGICAL_ITEM,
|
||||
QUEST_ITEM,
|
||||
PLANT, TECHNICAL, MAGICAL,
|
||||
MISC;
|
||||
}
|
||||
@@ -18,13 +18,18 @@ public class MainCharacter extends GameCharacter {
|
||||
private int xp;
|
||||
|
||||
private int currentHp;
|
||||
private int maxHp;
|
||||
|
||||
private int currentStamina;
|
||||
private int maxStamina;
|
||||
|
||||
private int currentMana;
|
||||
private int myMana;
|
||||
|
||||
private int maxHp;
|
||||
private int maxStamina;
|
||||
private int maxMana;
|
||||
|
||||
private int openHpRegeneration;
|
||||
private int openManaRegeneration;
|
||||
private int openStaminaRegeneration;
|
||||
|
||||
private int restorationValue = 10;
|
||||
|
||||
private List<Quest> openQuests;
|
||||
private List<Quest> completedQuests;
|
||||
@@ -80,6 +85,40 @@ public class MainCharacter extends GameCharacter {
|
||||
return !openQuests.contains(quest) && !completedQuests.contains(quest) && !abortedQuests.contains(quest);
|
||||
}
|
||||
|
||||
public void checkRegeneration() {
|
||||
if (openHpRegeneration > 0 && currentHp < maxHp) {
|
||||
heal(restorationValue);
|
||||
}
|
||||
if (openManaRegeneration > 0 && currentMana < maxMana) {
|
||||
restoreMana(restorationValue);
|
||||
}
|
||||
if (openStaminaRegeneration > 0 && currentStamina < maxStamina) {
|
||||
restoreStamina(restorationValue);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public void heal(int value) {
|
||||
currentHp += value;
|
||||
if (currentHp > maxHp) {
|
||||
currentHp = maxHp;
|
||||
}
|
||||
}
|
||||
|
||||
public void restoreMana(int value) {
|
||||
currentMana += value;
|
||||
if (currentMana > maxMana) {
|
||||
currentMana = maxMana;
|
||||
}
|
||||
}
|
||||
|
||||
public void restoreStamina(int value) {
|
||||
currentStamina += value;
|
||||
if (currentStamina > maxStamina) {
|
||||
currentStamina = maxStamina;
|
||||
}
|
||||
}
|
||||
|
||||
public void removeListener(CharacterListener listener) {
|
||||
listeners.remove(listener);
|
||||
}
|
||||
|
||||
@@ -16,4 +16,14 @@ public class ObjectReference {
|
||||
|
||||
/** Relativer Asset-Pfad (z. B. {@code Models/Items/sword.j3o}). */
|
||||
private String path;
|
||||
|
||||
/**
|
||||
* Relativer Asset-Pfad zum zugehörigen Thumbnail.
|
||||
* Format: {@code .thumbnails/<path>.thumb.png} – erzeugt vom ThumbnailRenderer.
|
||||
* Gibt {@code null} zurück wenn kein Pfad gesetzt ist.
|
||||
*/
|
||||
public String getThumbnailAssetPath() {
|
||||
if (path == null || path.isEmpty()) return null;
|
||||
return ".thumbnails/" + path + ".thumb.png";
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user