Character und Dialog System weiter gebaut
This commit is contained in:
@@ -30,5 +30,7 @@ public class SaveGame {
|
||||
public Set<String> pickedUpItems = new HashSet<>();
|
||||
/** IDs besiegter Gegner (für zukünftige Implementierung). */
|
||||
public Set<String> defeatedEnemies = new HashSet<>();
|
||||
/** NPC-ID → aktuell verfügbare Dialog-Option-IDs. */
|
||||
public Map<String, List<String>> npcDialogState = new HashMap<>();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,8 +31,9 @@ public final class SaveGameIO {
|
||||
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.pickedUpItems == null) sg.world.pickedUpItems = new java.util.HashSet<>();
|
||||
if (sg.world.defeatedEnemies == null) sg.world.defeatedEnemies = new java.util.HashSet<>();
|
||||
if (sg.world.npcDialogState == null) sg.world.npcDialogState = new java.util.HashMap<>();
|
||||
return sg;
|
||||
}
|
||||
|
||||
|
||||
@@ -32,7 +32,9 @@ public final class CharacterIO {
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(CharacterIO.class);
|
||||
private static final String EXTENSION = ".character";
|
||||
private static final Gson GSON = new GsonBuilder().setPrettyPrinting().create();
|
||||
private static final Gson GSON = de.blight.common.model.trigger.TriggerIO.registerAdapters(
|
||||
new GsonBuilder().setPrettyPrinting()
|
||||
).create();
|
||||
|
||||
private CharacterIO() {}
|
||||
|
||||
|
||||
@@ -2,8 +2,8 @@ package de.blight.common.model;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
import de.blight.common.model.trigger.Trigger;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
@@ -11,8 +11,8 @@ import lombok.Setter;
|
||||
@Setter
|
||||
public class DialogOption {
|
||||
|
||||
private String id = UUID.randomUUID().toString();
|
||||
private String label = "";
|
||||
private String id = "";
|
||||
private TextReference label;
|
||||
|
||||
private int requiresChapter;
|
||||
private QuestRef requiresQuestOpen;
|
||||
@@ -24,8 +24,8 @@ public class DialogOption {
|
||||
private TextReference textNpc;
|
||||
private AudioReference audioNpc;
|
||||
|
||||
private List<DialogOption> nextOptions;
|
||||
private List<DialogOption> disablesOptions;
|
||||
private transient List<DialogOption> nextOptions;
|
||||
private transient List<DialogOption> disablesOptions;
|
||||
|
||||
private RequiredItem requiredItem;
|
||||
private RecievesItem recievesItem;
|
||||
@@ -33,8 +33,10 @@ public class DialogOption {
|
||||
private QuestRef recievesQuest;
|
||||
private QuestRef fulfillsQuest;
|
||||
private List<QuestRef> abortsQuests = new ArrayList<>();
|
||||
|
||||
|
||||
private boolean enablesTrade;
|
||||
|
||||
private List<Trigger> onChosenTriggers = new ArrayList<>();
|
||||
|
||||
public Status getRequiredStatus() {
|
||||
if (requiresStatus == null) {
|
||||
|
||||
@@ -48,7 +48,10 @@ public class MainCharacter extends GameCharacter {
|
||||
if (option.getRecievesItem() != null) {
|
||||
getInventar().recieveItem(option.getRecievesItem());
|
||||
}
|
||||
if (option.getFulfillsQuest() != null ) {
|
||||
if (option.getRecievesQuest() != null) {
|
||||
startQuest(option.getRecievesQuest());
|
||||
}
|
||||
if (option.getFulfillsQuest() != null) {
|
||||
fullfillQuest(option.getFulfillsQuest());
|
||||
}
|
||||
option.getAbortsQuests().forEach(this::abortQuest);
|
||||
|
||||
@@ -2,8 +2,11 @@ package de.blight.common.model;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.EnumMap;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
@@ -23,10 +26,24 @@ public class NPC extends GameCharacter implements Interactable {
|
||||
|
||||
private List<Item> items;
|
||||
|
||||
private List<DialogOption> currentOptions;
|
||||
// ── Flache Dialog-Struktur (serialisiert) ─────────────────────────────────
|
||||
|
||||
/** Nur im Editor relevant: Optionen die nicht mit currentOptions verbunden sind, aber erhalten werden sollen. */
|
||||
private List<DialogOption> editorOnlyOptions;
|
||||
/** Alle Dialog-Optionen dieses NPCs, flach nach ID indiziert. */
|
||||
private Map<String, DialogOption> dialogOptions = new LinkedHashMap<>();
|
||||
|
||||
/** IDs der Wurzel-Optionen (am Dialog-Start verfügbar). */
|
||||
private List<String> dialogRoots = new ArrayList<>();
|
||||
|
||||
/** Baum: optionId → IDs der nachfolgenden Optionen. */
|
||||
private Map<String, List<String>> dialogNext = new LinkedHashMap<>();
|
||||
|
||||
/** Baum: optionId → IDs der Optionen, die dadurch deaktiviert werden. */
|
||||
private Map<String, List<String>> dialogDisables = new LinkedHashMap<>();
|
||||
|
||||
// ── Laufzeit-Zustand (transient, nicht serialisiert) ─────────────────────
|
||||
|
||||
/** Aktuell verfügbare Optionen – wird per {@link #initRuntimeTree} aufgebaut. */
|
||||
private transient List<DialogOption> currentOptions;
|
||||
|
||||
/**
|
||||
* Standard-Nachrichten je Status, die angezeigt werden wenn keine Dialog-Optionen
|
||||
@@ -98,12 +115,58 @@ public class NPC extends GameCharacter implements Interactable {
|
||||
return null;
|
||||
}
|
||||
|
||||
// ── Dialog-Initialisierung ────────────────────────────────────────────────
|
||||
|
||||
/**
|
||||
* Baut den Laufzeit-Dialog-Zustand auf.
|
||||
*
|
||||
* @param activeOptionIds die aktuell aktiven Option-IDs (aus dem Spielstand),
|
||||
* oder {@code null}/{@code empty} → {@link #dialogRoots} werden verwendet.
|
||||
*/
|
||||
public void initRuntimeTree(List<String> activeOptionIds) {
|
||||
if (dialogOptions == null) dialogOptions = new LinkedHashMap<>();
|
||||
|
||||
// Transiente nextOptions / disablesOptions je DialogOption setzen
|
||||
for (Map.Entry<String, List<String>> e : safe(dialogNext).entrySet()) {
|
||||
DialogOption opt = dialogOptions.get(e.getKey());
|
||||
if (opt == null) continue;
|
||||
opt.setNextOptions(e.getValue().stream()
|
||||
.map(dialogOptions::get).filter(Objects::nonNull)
|
||||
.collect(Collectors.toList()));
|
||||
}
|
||||
for (Map.Entry<String, List<String>> e : safe(dialogDisables).entrySet()) {
|
||||
DialogOption opt = dialogOptions.get(e.getKey());
|
||||
if (opt == null) continue;
|
||||
opt.setDisablesOptions(e.getValue().stream()
|
||||
.map(dialogOptions::get).filter(Objects::nonNull)
|
||||
.collect(Collectors.toList()));
|
||||
}
|
||||
|
||||
// currentOptions aus Spielstand oder Wurzeln
|
||||
List<String> ids = (activeOptionIds != null && !activeOptionIds.isEmpty())
|
||||
? activeOptionIds : safe(dialogRoots);
|
||||
this.currentOptions = ids.stream()
|
||||
.map(dialogOptions::get).filter(Objects::nonNull)
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
/** Gibt die aktuell aktiven Option-IDs zurück (für Spielstand-Persistenz). */
|
||||
public List<String> currentOptionIds() {
|
||||
if (currentOptions == null) return List.of();
|
||||
return currentOptions.stream()
|
||||
.map(DialogOption::getId).filter(Objects::nonNull)
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
private static <K, V> Map<K, V> safe(Map<K, V> m) { return m != null ? m : Map.of(); }
|
||||
private static <T> List<T> safe(List<T> l) { return l != null ? l : List.of(); }
|
||||
|
||||
// ── Dialog-Methoden ──────────────────────────────────────────────────────
|
||||
|
||||
public List<DialogOption> getAvailableOptions(MainCharacter character) {
|
||||
return currentOptions.stream().filter(option ->
|
||||
option.getRequiresChapter() < character.getChapter() &&
|
||||
option.getRequiresStatus().requirementFulfilled(status) &&
|
||||
option.getRequiresChapter() <= character.getChapter() &&
|
||||
(option.getRequiresStatus() == null || status.requirementFulfilled(option.getRequiresStatus())) &&
|
||||
(option.getRequiresQuestOpen() == null || character.getOpenQuests().contains(option.getRequiresQuestOpen())) &&
|
||||
(option.getRequiredItem() == null || character.getInventar().hasItem(option.getRequiredItem())) &&
|
||||
(option.getRequiresQuestComplete() == null || character.getCompletedQuests().contains(option.getRequiresQuestComplete()))).toList();
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
package de.blight.common.model;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.SortedSet;
|
||||
import java.util.TreeSet;
|
||||
|
||||
public final class TextKeyStore {
|
||||
|
||||
private static final LinkedHashSet<String> keys = new LinkedHashSet<>();
|
||||
private static Path keysFile = null;
|
||||
|
||||
private TextKeyStore() {}
|
||||
|
||||
/** Muss vom Editor beim Start aufgerufen werden. Lädt bestehende Keys aus der Datei. */
|
||||
public static void init(Path file) {
|
||||
keysFile = file;
|
||||
if (Files.exists(file)) {
|
||||
try {
|
||||
Files.lines(file, StandardCharsets.UTF_8)
|
||||
.map(String::trim)
|
||||
.filter(l -> !l.isBlank())
|
||||
.forEach(keys::add);
|
||||
} catch (IOException ignored) {}
|
||||
}
|
||||
}
|
||||
|
||||
/** Registriert einen Key. Im Spiel-Modus (keysFile == null) kein Schreiben. */
|
||||
public static void register(String id) {
|
||||
if (id == null || id.isBlank()) {
|
||||
return;
|
||||
}
|
||||
if (keys.add(id) && keysFile != null) {
|
||||
save();
|
||||
}
|
||||
}
|
||||
|
||||
/** Alphabetisch sortierte Kopie aller bekannten Keys. */
|
||||
public static SortedSet<String> getKeys() {
|
||||
return new TreeSet<>(keys);
|
||||
}
|
||||
|
||||
private static void save() {
|
||||
try {
|
||||
Files.write(keysFile, new TreeSet<>(keys), StandardCharsets.UTF_8);
|
||||
} catch (IOException ignored) {}
|
||||
}
|
||||
}
|
||||
@@ -1,3 +1,7 @@
|
||||
package de.blight.common.model;
|
||||
|
||||
public record TextReference(String id) {}
|
||||
public record TextReference(String id) {
|
||||
public TextReference {
|
||||
TextKeyStore.register(id);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,6 +3,8 @@ package de.blight.common.model;
|
||||
/** Weltposition (x, y, z in Metern). y = 0 bedeutet "Terrain-Höhe verwenden". */
|
||||
public class WorldPoint {
|
||||
public float x, y, z;
|
||||
/** Blickrichtung um Y-Achse in Radiant. 0 = Richtung +Z. */
|
||||
public float yaw = 0f;
|
||||
|
||||
public WorldPoint() {}
|
||||
|
||||
|
||||
@@ -35,6 +35,11 @@ public final class TriggerIO {
|
||||
|
||||
private TriggerIO() {}
|
||||
|
||||
/** Registriert den Trigger-Typ-Adapter an einem GsonBuilder (für andere Module). */
|
||||
public static GsonBuilder registerAdapters(GsonBuilder builder) {
|
||||
return builder.registerTypeHierarchyAdapter(Trigger.class, new TriggerAdapter());
|
||||
}
|
||||
|
||||
/** Serialisiert eine Trigger-Liste als kompaktes JSON (kein Zeilenumbruch). */
|
||||
public static String serializeList(List<Trigger> triggers) {
|
||||
if (triggers == null || triggers.isEmpty()) return "[]";
|
||||
|
||||
Reference in New Issue
Block a user