Commit vor Voxel Update für die Klippen

This commit is contained in:
2026-06-11 21:52:00 +02:00
parent fe5dfc19b1
commit a80269e681
143 changed files with 4340 additions and 342 deletions

View 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));
}
}

View File

@@ -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();
}

View File

@@ -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);
}
}

View File

@@ -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;

View 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<>();
}
}

View 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);
}
}

View 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 03, 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: 03, 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); }
}

View File

@@ -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;
}
}

View File

@@ -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; }
}

View File

@@ -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;
}

View File

@@ -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);

View File

@@ -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

View File

@@ -6,6 +6,6 @@ public enum ItemCategory {
GEAR,
CONSUMABLES,
QUEST_ITEMS,
USABLES,
MISC;
CRAFTING_ITEMS,
MISC_ITEMS;
}

View File

@@ -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);
}
}

View File

@@ -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;
}

View File

@@ -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);
}

View File

@@ -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";
}
}