Tool und Oberflächen Konsolidierung angefangen

This commit is contained in:
2026-08-14 15:41:27 +02:00
parent 400a736804
commit 90599c9c7b
78 changed files with 2400 additions and 311 deletions

View File

@@ -327,6 +327,7 @@ public class WorldScene extends BaseAppState {
if (saveState != null) saveState.bind(mc, physicsChar::getPhysicsLocation);
app.getStateManager().attach(new LocationState(mc, character));
app.getStateManager().attach(new de.blight.game.state.AreaTriggerState(mc, character));
app.getStateManager().attach(
new WorldItemsState(keyBindings, physicsChar, mc, playerInput));
app.getStateManager().attach(

View File

@@ -0,0 +1,97 @@
package de.blight.game.state;
import com.jme3.app.Application;
import com.jme3.app.state.BaseAppState;
import com.jme3.math.Vector3f;
import com.jme3.scene.Node;
import de.blight.common.AreaIO;
import de.blight.common.PlacedArea;
import de.blight.common.model.MainCharacter;
import de.blight.common.model.trigger.Trigger;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
/**
* Prüft pro Frame ob der Spieler eine PlacedArea betreten hat und feuert
* ggf. die zugehörigen Trigger (Übergang outside → inside).
*
* Betretene Areas werden zusätzlich in {@code character.visitedZoneIds}
* eingetragen, damit die {@code FirstTimeCondition} funktioniert.
*/
public class AreaTriggerState extends BaseAppState {
private static final Logger log = LoggerFactory.getLogger(AreaTriggerState.class);
private final MainCharacter character;
private final Node playerNode;
private List<PlacedArea> areas;
private final Set<String> active = new HashSet<>();
public AreaTriggerState(MainCharacter character, Node playerNode) {
this.character = character;
this.playerNode = playerNode;
}
@Override
protected void initialize(Application app) {
try {
areas = AreaIO.load();
log.info("{} Area(s) für Trigger-Prüfung geladen.", areas.size());
} catch (Exception e) {
log.error("Areas nicht ladbar", e);
areas = List.of();
}
}
@Override
public void update(float tpf) {
if (areas.isEmpty() || playerNode == null) return;
Vector3f pos = playerNode.getWorldTranslation();
float px = pos.x, pz = pos.z;
for (PlacedArea area : areas) {
boolean inside = pointInPolygon(px, pz, area.pointsX(), area.pointsZ());
boolean wasInside = active.contains(area.areaId());
if (inside && !wasInside) {
active.add(area.areaId());
if (area.areaId() != null && !area.areaId().isBlank()) {
character.getVisitedZoneIds().add(area.areaId());
}
List<Trigger> triggers = area.triggers();
if (triggers != null && !triggers.isEmpty()) {
triggers.stream()
.filter(t -> t.isTriggarable(character))
.forEach(t -> t.fire(character));
log.debug("Area betreten: {} {} Trigger geprüft.", area.areaId(), triggers.size());
}
} else if (!inside) {
active.remove(area.areaId());
}
}
}
/** Ray-Casting-Algorithmus für Punkt-in-Polygon-Test (XZ-Ebene). */
private static boolean pointInPolygon(float px, float pz, float[] xs, float[] zs) {
int n = xs.length;
if (n < 3) return false;
boolean inside = false;
for (int i = 0, j = n - 1; i < n; j = i++) {
if (((zs[i] > pz) != (zs[j] > pz)) &&
(px < (xs[j] - xs[i]) * (pz - zs[i]) / (zs[j] - zs[i]) + xs[i])) {
inside = !inside;
}
}
return inside;
}
@Override protected void cleanup(Application app) {}
@Override protected void onEnable() {}
@Override protected void onDisable() {}
}

View File

@@ -222,6 +222,17 @@ public class DialogHudState extends BaseAppState {
Monologue pending = mainCharForMonologue.pollPendingMonologue();
if (pending != null) {
startMonologue(pending, mainCharForMonologue);
} else {
String pendingNpcId = mainCharForMonologue.pollPendingDialog();
if (pendingNpcId != null) {
WorldNpcsState npcs = getStateManager().getState(WorldNpcsState.class);
NPC npc = npcs != null ? npcs.findNpcById(pendingNpcId) : null;
if (npc != null) {
startDialog(npc, mainCharForMonologue, null, null);
} else {
log.warn("[DialogHud] Trigger-Dialog: NPC '{}' nicht gefunden.", pendingNpcId);
}
}
}
}
}
@@ -359,7 +370,14 @@ public class DialogHudState extends BaseAppState {
playSteps(ph, speaker, steps, idx + 1, after);
return;
}
String audioPath = step.getAudio() != null ? AudioResolver.get().resolve(step.getAudio()) : null;
String audioPath;
if (step.getAudio() != null) {
audioPath = AudioResolver.get().resolve(step.getAudio());
} else if (step.getText() != null && !step.getText().id().isBlank()) {
audioPath = AudioResolver.get().resolveKey(step.getText().id());
} else {
audioPath = null;
}
showText(ph, speaker, text, audioPath, () -> playSteps(ph, speaker, steps, idx + 1, after));
}

View File

@@ -632,6 +632,14 @@ public class WorldNpcsState extends BaseAppState {
// ── Accessoren ────────────────────────────────────────────────────────────
/** Liefert den NPC mit der gegebenen characterId, oder null wenn nicht gefunden. */
public NPC findNpcById(String characterId) {
if (characterId == null) return null;
return allNpcs.stream()
.filter(n -> characterId.equals(n.getCharacterId()))
.findFirst().orElse(null);
}
/** Liefert das Spatial des aktuellen Dialog-NPCs, oder null wenn kein Dialog aktiv. */
public com.jme3.scene.Spatial getDialogNpcSpatial() {
return dialogNpc != null ? dialogNpc.visual() : null;