diff --git a/blight-editor/src/main/java/de/blight/editor/ui/AreaEditorView.java b/blight-editor/src/main/java/de/blight/editor/ui/AreaEditorView.java index f2003af..aadf79b 100644 --- a/blight-editor/src/main/java/de/blight/editor/ui/AreaEditorView.java +++ b/blight-editor/src/main/java/de/blight/editor/ui/AreaEditorView.java @@ -14,7 +14,8 @@ import javafx.scene.layout.*; import javafx.util.Duration; import java.io.IOException; -import java.nio.file.Path; +import java.nio.charset.StandardCharsets; +import java.nio.file.*; public class AreaEditorView extends BorderPane { @@ -57,6 +58,7 @@ public class AreaEditorView extends BorderPane { current = saved; reloading = false; persist(); + writeDefaultLocalisationIfAbsent(saved.id()); } // ── Form panel ──────────────────────────────────────────────────────────── @@ -229,6 +231,53 @@ public class AreaEditorView extends BorderPane { catch (IOException e) { areas.clear(); } } + // ── Localisation defaults ────────────────────────────────────────────────── + + private void writeDefaultLocalisationIfAbsent(String fullId) { + if (fullId == null || fullId.isBlank()) return; + String shortId = fullId.startsWith(PREFIX) ? fullId.substring(PREFIX.length()) : fullId; + if (shortId.isBlank()) return; + String key = fullId + ".name"; + String defaultName = Character.toUpperCase(shortId.charAt(0)) + shortId.substring(1); + + Path langSrc = ProjectRoot.resolve("blight-lang", "src", "main", "resources", "lang"); + Path[] langMirrors = { + ProjectRoot.resolve("blight-lang", "bin", "main", "lang"), + ProjectRoot.resolve("blight-lang", "build", "resources", "main", "lang"), + }; + for (String locale : new String[]{"de", "en"}) { + Path src = langSrc.resolve("messages_" + locale + ".properties"); + try { + if (appendKeyIfAbsent(src, key, defaultName)) { + for (Path mirrorDir : langMirrors) { + Path mirror = mirrorDir.resolve("messages_" + locale + ".properties"); + if (Files.exists(mirror)) { + Files.copy(src, mirror, StandardCopyOption.REPLACE_EXISTING); + } + } + } + } catch (IOException e) { + // non-critical + } + } + } + + /** Returns true if the key was absent and has been appended. */ + private static boolean appendKeyIfAbsent(Path file, String key, String value) throws IOException { + if (!Files.exists(file)) return false; + String needle = key + "="; + String needleSpace = key + " ="; + for (String line : Files.readAllLines(file, StandardCharsets.UTF_8)) { + String s = line.stripLeading(); + if (s.startsWith(needle) || s.startsWith(needleSpace)) return false; + } + String existing = Files.readString(file, StandardCharsets.UTF_8); + String sep = existing.endsWith("\n") ? "" : "\n"; + Files.writeString(file, existing + sep + key + "=" + value + "\n", + StandardCharsets.UTF_8, StandardOpenOption.WRITE, StandardOpenOption.TRUNCATE_EXISTING); + return true; + } + // ── Helpers ─────────────────────────────────────────────────────────────── private static Label sectionTitle(String text) { diff --git a/blight-game/src/main/java/de/blight/game/state/MusicSystem.java b/blight-game/src/main/java/de/blight/game/state/MusicSystem.java index 74f14eb..6dfea0c 100644 --- a/blight-game/src/main/java/de/blight/game/state/MusicSystem.java +++ b/blight-game/src/main/java/de/blight/game/state/MusicSystem.java @@ -9,13 +9,17 @@ import com.jme3.audio.AudioNode; import com.jme3.audio.AudioSource; import com.jme3.font.BitmapFont; import com.jme3.font.BitmapText; +import com.jme3.material.Material; import com.jme3.math.ColorRGBA; import com.jme3.math.Vector3f; +import com.jme3.scene.Geometry; import com.jme3.scene.Node; +import com.jme3.scene.Spatial; import de.blight.common.AreaDefinition; import de.blight.common.AreaDefinitionIO; import de.blight.common.AreaIO; import de.blight.common.PlacedArea; +import de.blight.game.config.NinePatch; import de.blight.lang.TextResolver; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -67,7 +71,11 @@ public class MusicSystem extends BaseAppState { private boolean isDaytime = true; private BitmapText areaNameText; - private float areaNameTimer = 0f; + private Node areaNamePanel = null; + private Material areaNamePanelMat = null; + private float areaNameTimer = 0f; + private boolean areaNameFadingIn = false; + private float areaNameFadeInElapsed = 0f; // ── Lifecycle ───────────────────────────────────────────────────────────── @@ -97,7 +105,6 @@ public class MusicSystem extends BaseAppState { if (def == null) continue; AudioNode day = loadTrack(def.dayTrack()); AudioNode night = loadTrack(def.nightTrack()); - if (day == null && night == null) continue; String rawId = def.id(); String shortId = rawId.startsWith("area.") ? rawId.substring("area.".length()) : rawId; data.add(area); @@ -126,7 +133,8 @@ public class MusicSystem extends BaseAppState { phases.clear(); activeSlot.clear(); pendingSlot.clear(); - if (areaNameText != null) app.getGuiNode().detachChild(areaNameText); + if (areaNamePanel != null) { app.getGuiNode().detachChild(areaNamePanel); areaNamePanel = null; } + if (areaNameText != null) app.getGuiNode().detachChild(areaNameText); } @Override protected void onEnable() {} @@ -152,15 +160,22 @@ public class MusicSystem extends BaseAppState { @Override public void update(float tpf) { - // area name display timer - if (areaNameTimer > 0f) { + // area name display: fade-in → visible → fade-out + if (areaNameFadingIn) { + areaNameFadeInElapsed += tpf; + float alpha = Math.min(areaNameFadeInElapsed / AREA_NAME_FADE, 1f); + setAreaNameAlpha(alpha); + if (areaNameFadeInElapsed >= AREA_NAME_FADE) { + areaNameFadingIn = false; + setAreaNameAlpha(1f); + } + } else if (areaNameTimer > 0f) { areaNameTimer -= tpf; if (areaNameTimer <= 0f) { - areaNameText.setColor(new ColorRGBA(1f, 0.95f, 0.8f, 0f)); areaNameTimer = 0f; + setAreaNameAlpha(0f); } else if (areaNameTimer < AREA_NAME_FADE) { - float alpha = areaNameTimer / AREA_NAME_FADE; - areaNameText.setColor(new ColorRGBA(1f, 0.95f, 0.8f, alpha)); + setAreaNameAlpha(areaNameTimer / AREA_NAME_FADE); } } @@ -205,15 +220,18 @@ public class MusicSystem extends BaseAppState { private void startArea(int i) { int slot = desiredSlot(i); AudioNode n = nodes.get(i)[slot]; - if (n == null) return; - n.setVolume(0f); - n.setLooping(true); - rootNode.attachChild(n); - n.play(); - activeSlot.set(i, slot); - pendingSlot.set(i, -1); - phases.set(i, Phase.FADING_IN); - log.info("[MusicSystem] Area {} → slot {} gestartet", i, slot == SLOT_DAY ? "Tag" : "Nacht"); + if (n != null) { + n.setVolume(0f); + n.setLooping(true); + rootNode.attachChild(n); + n.play(); + activeSlot.set(i, slot); + pendingSlot.set(i, -1); + phases.set(i, Phase.FADING_IN); + log.info("[MusicSystem] Area {} → slot {} gestartet", i, slot == SLOT_DAY ? "Tag" : "Nacht"); + } else { + phases.set(i, Phase.ACTIVE); + } showAreaName(i); } @@ -286,19 +304,47 @@ public class MusicSystem extends BaseAppState { // ── Area-Name-Anzeige ───────────────────────────────────────────────────── private void showAreaName(int i) { - String key = i < areaNameKeys.size() ? areaNameKeys.get(i) : ""; - String shortId = i < areaShortIds.size() ? areaShortIds.get(i) : key; + String key = i < areaNameKeys.size() ? areaNameKeys.get(i) : ""; + String shortId = i < areaShortIds.size() ? areaShortIds.get(i) : key; String resolved = TextResolver.get().resolveOrId(key); - String name = resolved.equals(key) ? shortId : resolved; + String name = resolved.equals(key) ? shortId : resolved; if (name.isBlank()) return; areaNameText.setText(name); - float tw = areaNameText.getLineWidth() * 0.5f; - float sw = app.getCamera().getWidth(); - float sh = app.getCamera().getHeight(); - areaNameText.setLocalTranslation((sw - tw) / 2f, sh - 40f, 0f); - areaNameText.setColor(new ColorRGBA(1f, 0.95f, 0.8f, 1f)); - areaNameTimer = AREA_NAME_SHOW; + float tw = areaNameText.getLineWidth() * 0.5f; + float th = areaNameText.getLineHeight() * 0.5f; + float sw = app.getCamera().getWidth(); + float sh = app.getCamera().getHeight(); + + float padX = 20f, padY = 10f; + float textX = Math.round((sw - tw) / 2f); + float textY = sh - 40f; + float panelX = Math.round(textX - padX); + float panelY = Math.round(textY - th - padY); + float panelW = Math.round(tw + padX * 2f); + float panelH = Math.round(th + padY * 2f); + + areaNameText.setLocalTranslation(textX, textY, 0f); + + if (areaNamePanel != null) app.getGuiNode().detachChild(areaNamePanel); + areaNamePanel = NinePatch.panel(assets).build(panelX, panelY, panelW, panelH, -1f); + areaNamePanelMat = null; + for (Spatial s : areaNamePanel.getChildren()) { + if (s instanceof Geometry g) { areaNamePanelMat = g.getMaterial(); break; } + } + app.getGuiNode().attachChild(areaNamePanel); + + setAreaNameAlpha(0f); + areaNameFadingIn = true; + areaNameFadeInElapsed = 0f; + areaNameTimer = AREA_NAME_SHOW; + } + + private void setAreaNameAlpha(float alpha) { + areaNameText.setColor(new ColorRGBA(1f, 0.95f, 0.8f, alpha)); + if (areaNamePanelMat != null) { + areaNamePanelMat.setColor("Color", new ColorRGBA(1f, 1f, 1f, alpha)); + } } // ── Helpers ─────────────────────────────────────────────────────────────── diff --git a/blight-map/src/main/map/blight_area_definitions.bad b/blight-map/src/main/map/blight_area_definitions.bad new file mode 100644 index 0000000..6a2fd8f --- /dev/null +++ b/blight-map/src/main/map/blight_area_definitions.bad @@ -0,0 +1,2 @@ +# id dayTrack nightTrack combatTrack +area.strand diff --git a/blight-map/src/main/map/blight_areas.bla b/blight-map/src/main/map/blight_areas.bla index 0cbe9bd..f0938ab 100644 --- a/blight-map/src/main/map/blight_areas.bla +++ b/blight-map/src/main/map/blight_areas.bla @@ -1 +1,2 @@ # polygon areaId triggersJson +255.852,-828.388;281.186,-815.228;272.373,-782.936;237.423,-778.259;223.212,-812.720;227.232,-839.121 area.strand []