Einige Bug Fixes

This commit is contained in:
2026-08-16 09:28:55 +02:00
parent 47b2d43c50
commit e79f5b4755
29 changed files with 1030 additions and 348 deletions

View File

@@ -0,0 +1,375 @@
{
"status": "NEUTRAL",
"trader": false,
"dialogOptions": {
"begruessung": {
"id": "begruessung",
"requiresChapter": 0,
"textHero": {
"id": "dialog.bjoernson.begruessung.textmainchar"
},
"textNpc": {
"id": "dialog.bjoernson.begruessung.textnpc"
},
"heroSteps": [],
"npcSteps": [],
"abortsQuests": [],
"enablesTrade": false,
"onChosenTriggers": []
},
"wer_bist_du": {
"id": "wer_bist_du",
"requiresChapter": 0,
"textHero": {
"id": "dialog.bjoernson.wer_bist_du.textmainchar"
},
"textNpc": {
"id": "dialog.bjoernson.wer_bist_du.textnpc"
},
"heroSteps": [],
"npcSteps": [],
"abortsQuests": [],
"enablesTrade": false,
"onChosenTriggers": []
},
"aus_dem_norden": {
"id": "aus_dem_norden",
"requiresChapter": 0,
"textHero": {
"id": "dialog.bjoernson.aus_dem_norden.textmainchar"
},
"heroSteps": [],
"npcSteps": [
{
"text": { "id": "dialog.bjoernson.aus_dem_norden.textnpc" },
"audio": { "key": "dialog.bjoernson.aus_dem_norden.textnpc" }
},
{
"text": { "id": "dialog.bjoernson.aus_dem_norden.textnpc.1" },
"audio": { "key": "dialog.bjoernson.aus_dem_norden.textnpc.1" }
}
],
"abortsQuests": [],
"enablesTrade": false,
"onChosenTriggers": []
},
"bier_angebot": {
"id": "bier_angebot",
"requiresChapter": 0,
"textHero": {
"id": "dialog.bjoernson.bier_angebot.textmainchar"
},
"textNpc": {
"id": "dialog.bjoernson.bier_angebot.textnpc"
},
"heroSteps": [],
"npcSteps": [],
"abortsQuests": [],
"enablesTrade": false,
"onChosenTriggers": []
},
"abgemacht_bier": {
"id": "abgemacht_bier",
"requiresChapter": 0,
"textHero": {
"id": "dialog.bjoernson.abgemacht_bier.textmainchar"
},
"recievesQuest": {
"questId": "kuehles_bier"
},
"heroSteps": [],
"npcSteps": [],
"abortsQuests": [],
"enablesTrade": false,
"onChosenTriggers": []
},
"bier_geliefert": {
"id": "bier_geliefert",
"requiresChapter": 0,
"requiresQuestOpen": {
"questId": "kuehles_bier"
},
"textHero": {
"id": "dialog.bjoernson.bier_geliefert.textmainchar"
},
"textNpc": {
"id": "dialog.bjoernson.bier_geliefert.textnpc"
},
"fulfillsQuest": {
"questId": "kuehles_bier"
},
"heroSteps": [],
"npcSteps": [],
"abortsQuests": [],
"enablesTrade": false,
"onChosenTriggers": []
},
"schwert_annehmen": {
"id": "schwert_annehmen",
"requiresChapter": 1,
"textHero": {
"id": "dialog.bjoernson.schwert_annehmen.textmainchar"
},
"heroSteps": [],
"npcSteps": [
{
"text": { "id": "dialog.bjoernson.schwert_annehmen.textnpc" },
"audio": { "key": "dialog.bjoernson.schwert_annehmen.textnpc" }
},
{
"text": { "id": "dialog.bjoernson.schwert_annehmen.textnpc.1" },
"audio": { "key": "dialog.bjoernson.schwert_annehmen.textnpc.1" }
},
{
"text": { "id": "dialog.bjoernson.schwert_annehmen.textnpc.2" },
"audio": { "key": "dialog.bjoernson.schwert_annehmen.textnpc.2" }
},
{
"text": { "id": "dialog.bjoernson.schwert_annehmen.textnpc.3" },
"audio": { "key": "dialog.bjoernson.schwert_annehmen.textnpc.3" }
}
],
"abortsQuests": [],
"enablesTrade": false,
"onChosenTriggers": []
},
"ich_kuemmere_mich": {
"id": "ich_kuemmere_mich",
"requiresChapter": 0,
"textHero": {
"id": "dialog.bjoernson.ich_kuemmere_mich.textmainchar"
},
"textNpc": {
"id": "dialog.bjoernson.ich_kuemmere_mich.textnpc"
},
"recievesQuest": {
"questId": "blight_wuehler"
},
"heroSteps": [],
"npcSteps": [],
"abortsQuests": [],
"enablesTrade": false,
"onChosenTriggers": []
},
"erz_quest": {
"id": "erz_quest",
"requiresChapter": 0,
"textHero": {
"id": "dialog.bjoernson.erz_quest.textmainchar"
},
"recievesQuest": {
"questId": "magnetiterz"
},
"heroSteps": [],
"npcSteps": [],
"abortsQuests": [],
"enablesTrade": false,
"onChosenTriggers": []
},
"wie_schuerfern": {
"id": "wie_schuerfern",
"requiresChapter": 0,
"textHero": {
"id": "dialog.bjoernson.wie_schuerfern.textmainchar"
},
"heroSteps": [],
"npcSteps": [
{
"text": { "id": "dialog.bjoernson.wie_schuerfern.textnpc" },
"audio": { "key": "dialog.bjoernson.wie_schuerfern.textnpc" }
},
{
"text": { "id": "dialog.bjoernson.wie_schuerfern.textnpc.1" },
"audio": { "key": "dialog.bjoernson.wie_schuerfern.textnpc.1" }
}
],
"abortsQuests": [],
"enablesTrade": false,
"onChosenTriggers": []
},
"danke_bjoernson": {
"id": "danke_bjoernson",
"requiresChapter": 0,
"textHero": {
"id": "dialog.bjoernson.danke_bjoernson.textmainchar"
},
"textNpc": {
"id": "dialog.bjoernson.danke_bjoernson.textnpc"
},
"heroSteps": [],
"npcSteps": [],
"abortsQuests": [],
"enablesTrade": false,
"onChosenTriggers": []
},
"quests_erledigt": {
"id": "quests_erledigt",
"requiresChapter": 0,
"requiresQuestComplete": {
"questId": "blight_wuehler"
},
"textHero": {
"id": "dialog.bjoernson.quests_erledigt.textmainchar"
},
"textNpc": {
"id": "dialog.bjoernson.quests_erledigt.textnpc"
},
"heroSteps": [],
"npcSteps": [],
"abortsQuests": [],
"enablesTrade": false,
"onChosenTriggers": []
},
"ich_bin_zaeh": {
"id": "ich_bin_zaeh",
"requiresChapter": 0,
"textHero": {
"id": "dialog.bjoernson.ich_bin_zaeh.textmainchar"
},
"textNpc": {
"id": "dialog.bjoernson.ich_bin_zaeh.textnpc"
},
"heroSteps": [],
"npcSteps": [],
"abortsQuests": [],
"enablesTrade": false,
"onChosenTriggers": []
},
"wie_stehts_abmachung": {
"id": "wie_stehts_abmachung",
"requiresChapter": 0,
"textHero": {
"id": "dialog.bjoernson.wie_stehts_abmachung.textmainchar"
},
"textNpc": {
"id": "dialog.bjoernson.wie_stehts_abmachung.textnpc"
},
"heroSteps": [],
"npcSteps": [],
"abortsQuests": [],
"enablesTrade": false,
"onChosenTriggers": []
},
"nur_die_waffe": {
"id": "nur_die_waffe",
"requiresChapter": 0,
"textHero": {
"id": "dialog.bjoernson.nur_die_waffe.textmainchar"
},
"textNpc": {
"id": "dialog.bjoernson.nur_die_waffe.textnpc"
},
"heroSteps": [],
"npcSteps": [],
"abortsQuests": [],
"enablesTrade": false,
"onChosenTriggers": []
},
"selber_lernen": {
"id": "selber_lernen",
"requiresChapter": 0,
"textHero": {
"id": "dialog.bjoernson.selber_lernen.textmainchar"
},
"textNpc": {
"id": "dialog.bjoernson.selber_lernen.textnpc"
},
"heroSteps": [],
"npcSteps": [],
"abortsQuests": [],
"enablesTrade": true,
"onChosenTriggers": []
}
},
"dialogRoots": [
"begruessung",
"bier_geliefert",
"schwert_annehmen",
"quests_erledigt"
],
"dialogNext": {
"begruessung": [
"wer_bist_du",
"bier_angebot"
],
"wer_bist_du": [
"aus_dem_norden",
"bier_angebot"
],
"aus_dem_norden": [
"bier_angebot"
],
"bier_angebot": [
"abgemacht_bier"
],
"schwert_annehmen": [
"ich_kuemmere_mich"
],
"ich_kuemmere_mich": [
"erz_quest",
"wie_schuerfern",
"danke_bjoernson"
],
"erz_quest": [
"wie_schuerfern",
"danke_bjoernson"
],
"quests_erledigt": [
"ich_bin_zaeh"
],
"ich_bin_zaeh": [
"wie_stehts_abmachung"
],
"wie_stehts_abmachung": [
"nur_die_waffe",
"selber_lernen"
]
},
"dialogDisables": {
"abgemacht_bier": [
"begruessung",
"wer_bist_du",
"aus_dem_norden",
"bier_angebot"
],
"nur_die_waffe": [
"selber_lernen",
"quests_erledigt",
"ich_bin_zaeh",
"wie_stehts_abmachung"
],
"selber_lernen": [
"nur_die_waffe",
"quests_erledigt",
"ich_bin_zaeh",
"wie_stehts_abmachung"
]
},
"defaultMessages": {},
"routines": [
{
"name": "chapter0",
"blocks": [
{
"startHour": 0,
"endHour": 24,
"activity": {
"type": "STAND",
"position": {
"x": 0.0,
"y": 1.0,
"z": 0.0
}
}
}
]
}
],
"characterId": "bjoernson",
"name": {
"id": "bjoernson.name"
},
"modelPath": "Models/Chars/bjoernson.j3o",
"animSetPath": "human",
"type": "NPC"
}

View File

@@ -0,0 +1,14 @@
{
"xp": 150,
"questId": "blight_wuehler",
"text": {
"id": "quest.blight_wuehler.name"
},
"description": {
"id": "quest.blight_wuehler.description"
},
"successText": {
"id": "quest.blight_wuehler.success"
},
"type": "INTERACT"
}

View File

@@ -0,0 +1,15 @@
{
"count": 1,
"xp": 75,
"questId": "kuehles_bier",
"text": {
"id": "quest.kuehles_bier.name"
},
"description": {
"id": "quest.kuehles_bier.description"
},
"successText": {
"id": "quest.kuehles_bier.success"
},
"type": "ITEM"
}

View File

@@ -0,0 +1,15 @@
{
"count": 5,
"xp": 100,
"questId": "magnetiterz",
"text": {
"id": "quest.magnetiterz.name"
},
"description": {
"id": "quest.magnetiterz.description"
},
"successText": {
"id": "quest.magnetiterz.success"
},
"type": "ITEM"
}

View File

@@ -282,6 +282,9 @@ public class EditorApp extends Application {
private VBox zonenDynamicContent;
private Runnable zonenKindStyleUpdater;
// Auswahl-Werkzeug dynamischer Bereich für selektierte Items
private VBox auswahlDynamicContent;
// Spiel-Starten-Werkzeug-Zustand
private TextField spawnXField;
private TextField spawnZField;
@@ -461,7 +464,7 @@ public class EditorApp extends Application {
centerStack = new StackPane(worldViewport);
setupAssetOverlay();
root.setCenter(centerStack);
root.setRight(toolPanel);
root.setRight(buildAuswahlPanel());
worldBottomBar = buildBottomBox();
root.setBottom(worldBottomBar);
@@ -752,6 +755,18 @@ public class EditorApp extends Application {
updateLocationZonePanel(input.selectedLocationZoneInfo);
}
if (input.auswahlZoneSelected) {
input.auswahlZoneSelected = false;
input.activeLayer = SharedInput.LAYER_ZONEN;
if (zonenBtn != null) zonenBtn.setSelected(true);
root.setRight(buildZonenPanel());
}
if (input.itemSelectionChanged) {
input.itemSelectionChanged = false;
updateAuswahlItemPanel(input.selectedItemInfo);
}
if (input.zonenSelectionChanged) {
input.zonenSelectionChanged = false;
updateZonenPanel(input.zonenSelectedKind);
@@ -969,7 +984,11 @@ public class EditorApp extends Application {
} else {
setCenterView(worldViewport);
}
if (input.activeLayer == SharedInput.LAYER_AUSWAHL) {
root.setRight(buildAuswahlPanel());
} else {
root.setRight(toolPanel);
}
root.setBottom(worldBottomBar);
if (wasObjEditor && (input.activeLayer == SharedInput.LAYER_OBJECTS
|| input.activeLayer == SharedInput.LAYER_OBJECTS_EDIT)) {
@@ -1590,9 +1609,11 @@ public class EditorApp extends Application {
zonenBtn.setToggleGroup(layerGroup);
playToolBtn.setToggleGroup(layerGroup);
auswahlBtn.setSelected(true);
input.activeLayer = SharedInput.LAYER_AUSWAHL;
auswahlBtn.setOnAction(e -> {
input.activeLayer = SharedInput.LAYER_AUSWAHL;
input.deselectAllOnAuswahl = true;
root.setRight(buildAuswahlPanel());
});
baseBtn.setOnAction(e -> {
@@ -1871,7 +1892,8 @@ public class EditorApp extends Application {
private boolean isObjectMode() {
return input.activeLayer == SharedInput.LAYER_OBJECTS
|| input.activeLayer == SharedInput.LAYER_OBJECTS_EDIT;
|| input.activeLayer == SharedInput.LAYER_OBJECTS_EDIT
|| input.activeLayer == SharedInput.LAYER_AUSWAHL;
}
private static Label styledHint(String text) {
@@ -3584,7 +3606,7 @@ public class EditorApp extends Application {
title.setStyle("-fx-font-weight: bold; -fx-font-size: 13; -fx-text-fill: #111;");
panel.getChildren().addAll(title, new Separator());
panel.getChildren().addAll(
styledHint("L-Klick: Objekt auswählen"),
styledHint("L-Klick: Element auswählen"),
styledHint(" → wechselt automatisch zum"),
styledHint(" passenden Werkzeug"),
new Separator(),
@@ -3593,10 +3615,37 @@ public class EditorApp extends Application {
styledHint(" * Lichter / Emitter"),
styledHint(" ~ Wasser / Wasserfall"),
styledHint(" -- Wege / Knoten"),
styledHint(" [ ] Zonen"));
styledHint(" [ ] Zonen"),
styledHint(" o Spawnpunkte (Items)"),
new Separator());
auswahlDynamicContent = new VBox(6);
Label noSel = new Label("Kein Element ausgewählt");
noSel.setStyle("-fx-text-fill: #888;");
auswahlDynamicContent.getChildren().add(noSel);
panel.getChildren().add(auswahlDynamicContent);
return panel;
}
private void updateAuswahlItemPanel(String info) {
if (auswahlDynamicContent == null) return;
auswahlDynamicContent.getChildren().clear();
if (info == null) {
Label noSel = new Label("Kein Element ausgewählt");
noSel.setStyle("-fx-text-fill: #888;");
auswahlDynamicContent.getChildren().add(noSel);
return;
}
String[] parts = info.split("\\|");
String itemId = parts.length > 1 ? parts[1] : "?";
Label idLabel = new Label("Item: " + itemId);
idLabel.setStyle("-fx-font-weight: bold;");
Button deleteBtn = new Button("Löschen");
deleteBtn.setMaxWidth(Double.MAX_VALUE);
deleteBtn.setStyle("-fx-background-color: #c0392b; -fx-text-fill: white;");
deleteBtn.setOnAction(e -> input.deleteSelectedItemRequested = true);
auswahlDynamicContent.getChildren().addAll(idLabel, deleteBtn);
}
private VBox buildAtmospherePanel() {
VBox panel = new VBox(10);
panel.setPadding(new Insets(10));
@@ -12022,6 +12071,7 @@ public class EditorApp extends Application {
private void switchToContentEditor() {
currentTool = "content";
if (jmeApp != null) jmeApp.setRenderThrottled(true);
root.setTop(topBar);
topBar.getChildren().set(1, buildSingleWindowContentToolBar());
root.setLeft(buildContentTreePanel());
root.setBottom(null);

View File

@@ -1114,6 +1114,18 @@ public class SharedInput {
/** activeLayer==33 → Auswahl-Modus: kein Terrain-Editing, Klick selektiert Objekte */
public static final int LAYER_AUSWAHL = 33;
/** JavaFX → JME3: Alle Selektionen aufheben wenn in Auswahl-Modus gewechselt wird. */
public volatile boolean deselectAllOnAuswahl = false;
/** JME3 → JavaFX: Zone wurde im Auswahl-Modus selektiert → Zonen-Werkzeug aktivieren. */
public volatile boolean auswahlZoneSelected = false;
/** JME3 → JavaFX: Item-Spawn im Auswahl-Modus selektiert. Format "idx|itemId|x|y|z" oder null. */
public volatile String selectedItemInfo = null;
public volatile boolean itemSelectionChanged = false;
/** JavaFX → JME3: Selektierten Item-Spawn löschen. */
public volatile boolean deleteSelectedItemRequested = false;
/** JME3-intern: Auswahl-Klick für ItemPlacementState (kein Objekt/keine Zone getroffen). */
public final ConcurrentLinkedQueue<ObjectClick> auswahlClickQueue = new ConcurrentLinkedQueue<>();
// ── Voxel-Textur-Malen ────────────────────────────────────────────────────
/** Parallel-Queue zu textureEditQueue wird von submitEdit(layer=4) mitbefüllt,
* damit VoxelEditorState unabhängig vom Terrain seine Splatmap beschreiben kann. */

View File

@@ -80,7 +80,8 @@ public class AreaState extends BaseAppState {
@Override
public void update(float tpf) {
if (input.activeLayer != SharedInput.LAYER_AREAS && input.activeLayer != SharedInput.LAYER_ZONEN) {
if (input.activeLayer != SharedInput.LAYER_AREAS && input.activeLayer != SharedInput.LAYER_ZONEN
&& input.activeLayer != SharedInput.LAYER_AUSWAHL) {
if (placing) cancelPoly();
return;
}
@@ -172,6 +173,7 @@ public class AreaState extends BaseAppState {
if (lzs != null && lzs.findZoneAt(hitX, hitZ) >= 0) return;
}
deselect();
if (input.activeLayer == SharedInput.LAYER_AUSWAHL) return;
if (input.activeLayer == SharedInput.LAYER_ZONEN && !"area".equals(input.zonenNewKind)) return;
placing = true;
currX.clear();
@@ -313,10 +315,13 @@ public class AreaState extends BaseAppState {
String triggersJson = TriggerIO.serializeList(a.triggers());
input.selectedAreaInfo = idx + "|" + a.areaId() + "|" + triggersJson;
input.areaSelectionChanged = true;
if (input.activeLayer == SharedInput.LAYER_ZONEN) {
if (input.activeLayer == SharedInput.LAYER_ZONEN || input.activeLayer == SharedInput.LAYER_AUSWAHL) {
input.zonenSelectedKind = "area";
input.zonenSelectionChanged = true;
}
if (input.activeLayer == SharedInput.LAYER_AUSWAHL) {
input.auswahlZoneSelected = true;
}
}
// ── Add / Remove / Apply ──────────────────────────────────────────────────

View File

@@ -46,6 +46,8 @@ public class ItemPlacementState extends BaseAppState {
private final Map<String, Item> itemDefs = new HashMap<>();
private Node itemRoot;
private Node previewNode;
private int selectedItemIdx = -1;
private Node selectedItemNode = null;
public ItemPlacementState(SharedInput input) {
this.input = input;
@@ -119,8 +121,15 @@ public class ItemPlacementState extends BaseAppState {
public void update(float tpf) {
if (input.reloadPlacedItems) {
input.reloadPlacedItems = false;
deselectItem();
reloadFromDisk();
}
// Highlight entfernen sobald wir nicht mehr im Auswahl-Modus sind
if (selectedItemIdx >= 0 && input.activeLayer != SharedInput.LAYER_AUSWAHL) {
deselectItem();
}
updatePreview();
SharedInput.ObjectClick click;
@@ -132,6 +141,19 @@ public class ItemPlacementState extends BaseAppState {
handlePlace(click);
}
}
SharedInput.ObjectClick auswahlClick;
while ((auswahlClick = input.auswahlClickQueue.poll()) != null) {
handleAuswahlClick(auswahlClick);
}
if (input.deleteSelectedItemRequested) {
input.deleteSelectedItemRequested = false;
if (selectedItemIdx >= 0) {
removeItem(selectedItemIdx);
deselectItem();
}
}
}
private void updatePreview() {
@@ -183,6 +205,68 @@ public class ItemPlacementState extends BaseAppState {
}
}
private void handleAuswahlClick(SharedInput.ObjectClick click) {
float jmeX = click.screenX() * (float) input.viewportScaleX;
float jmeY = cam.getHeight() - click.screenY() * (float) input.viewportScaleY;
Ray ray = screenToRay(jmeX, jmeY);
CollisionResults hits = new CollisionResults();
itemRoot.collideWith(ray, hits);
if (hits.size() > 0) {
Spatial geo = hits.getClosestCollision().getGeometry();
Node parent = geo.getParent();
while (parent != null && !nodes.contains(parent)) {
parent = parent.getParent();
}
if (parent != null) {
selectItem(nodes.indexOf(parent));
return;
}
}
deselectItem();
}
private void selectItem(int idx) {
deselectItem();
selectedItemIdx = idx;
selectedItemNode = nodes.get(idx);
Geometry highlight = new Geometry("item_sel",
new com.jme3.scene.shape.Box(0.28f, 0.28f, 0.28f));
Material mat = new Material(assets, "Common/MatDefs/Misc/Unshaded.j3md");
mat.setColor("Color", new com.jme3.math.ColorRGBA(1f, 0.6f, 0.1f, 1f));
mat.getAdditionalRenderState().setWireframe(true);
mat.getAdditionalRenderState().setDepthTest(false);
highlight.setMaterial(mat);
highlight.setQueueBucket(com.jme3.renderer.queue.RenderQueue.Bucket.Transparent);
selectedItemNode.attachChild(highlight);
PlacedItem pi = items.get(idx);
input.selectedItemInfo = idx + "|" + pi.itemId() + "|" + pi.x() + "|" + pi.y() + "|" + pi.z();
input.itemSelectionChanged = true;
}
private void deselectItem() {
if (selectedItemNode != null) {
Spatial h = selectedItemNode.getChild("item_sel");
if (h != null) h.removeFromParent();
selectedItemNode = null;
}
if (selectedItemIdx >= 0) {
selectedItemIdx = -1;
input.selectedItemInfo = null;
input.itemSelectionChanged = true;
}
}
private void removeItem(int idx) {
itemRoot.detachChild(nodes.get(idx));
items.remove(idx);
nodes.remove(idx);
try {
PlacedItemIO.save(items);
} catch (IOException e) {
log.warn("[ItemPlacement] Speichern fehlgeschlagen: {}", e.getMessage());
}
}
// ── Hilfsmethoden ────────────────────────────────────────────────────────
private Node buildItemNode(String itemId) {

View File

@@ -78,7 +78,8 @@ public class LocationZoneState extends BaseAppState {
@Override
public void update(float tpf) {
if (input.activeLayer != SharedInput.LAYER_LOCATION_ZONES && input.activeLayer != SharedInput.LAYER_ZONEN) {
if (input.activeLayer != SharedInput.LAYER_LOCATION_ZONES && input.activeLayer != SharedInput.LAYER_ZONEN
&& input.activeLayer != SharedInput.LAYER_AUSWAHL) {
if (placing) cancelPoly();
return;
}
@@ -170,6 +171,7 @@ public class LocationZoneState extends BaseAppState {
if (sas != null && sas.findZoneAt(hitX, hitZ) >= 0) return;
}
deselect();
if (input.activeLayer == SharedInput.LAYER_AUSWAHL) return;
if (input.activeLayer == SharedInput.LAYER_ZONEN && !"location".equals(input.zonenNewKind)) return;
placing = true;
currX.clear();
@@ -303,10 +305,13 @@ public class LocationZoneState extends BaseAppState {
String triggersJson = de.blight.common.model.trigger.TriggerIO.serializeList(z.triggers());
input.selectedLocationZoneInfo = idx + "|" + z.nameId() + "|" + triggersJson;
input.locationZoneSelectionChanged = true;
if (input.activeLayer == SharedInput.LAYER_ZONEN) {
if (input.activeLayer == SharedInput.LAYER_ZONEN || input.activeLayer == SharedInput.LAYER_AUSWAHL) {
input.zonenSelectedKind = "location";
input.zonenSelectionChanged = true;
}
if (input.activeLayer == SharedInput.LAYER_AUSWAHL) {
input.auswahlZoneSelected = true;
}
}
// ── Add / Remove / Apply ──────────────────────────────────────────────────

View File

@@ -399,6 +399,17 @@ public class SceneObjectState extends BaseAppState {
|| input.activeLayer == SharedInput.LAYER_AUSWAHL;
if (!isObjectLayer) return;
if (input.deselectAllOnAuswahl) {
input.deselectAllOnAuswahl = false;
deselectAll();
AreaState as = getStateManager().getState(AreaState.class);
if (as != null) as.deselectSilent();
SoundAreaState sas = getStateManager().getState(SoundAreaState.class);
if (sas != null) sas.deselectSilent();
LocationZoneState lzs = getStateManager().getState(LocationZoneState.class);
if (lzs != null) lzs.deselectSilent();
}
// Animation-Clip-Zuweisung von JavaFX
String animClipPending = input.pendingAnimClip;
if (animClipPending != null) {
@@ -696,8 +707,16 @@ public class SceneObjectState extends BaseAppState {
deselectAll();
return;
}
// Auswahl-Modus: kein Objekt angeklickt → nur deselektieren, Layer bleibt
if (input.activeLayer == SharedInput.LAYER_AUSWAHL) { deselectAll(); return; }
// Auswahl-Modus: kein Objekt angeklickt → deselektieren + Zonen/Items prüfen lassen
if (input.activeLayer == SharedInput.LAYER_AUSWAHL) {
deselectAll();
float sx = click.screenX(), sy = click.screenY();
input.areaClickQueue.offer(new SharedInput.AreaClick(sx, sy, false));
input.soundAreaClickQueue.offer(new SharedInput.SoundAreaClick(sx, sy, false));
input.locationZoneClickQueue.offer(new SharedInput.LocationZoneClick(sx, sy, false));
input.auswahlClickQueue.offer(click);
return;
}
if (input.activeLayer != SharedInput.LAYER_OBJECTS) { deselectAll(); return; }
String modelPath = input.pendingModelPath;

View File

@@ -82,7 +82,8 @@ public class SoundAreaState extends BaseAppState {
@Override
public void update(float tpf) {
if (input.activeLayer != SharedInput.LAYER_SOUND_AREAS && input.activeLayer != SharedInput.LAYER_ZONEN) {
if (input.activeLayer != SharedInput.LAYER_SOUND_AREAS && input.activeLayer != SharedInput.LAYER_ZONEN
&& input.activeLayer != SharedInput.LAYER_AUSWAHL) {
if (placing) cancelPoly();
return;
}
@@ -174,6 +175,7 @@ public class SoundAreaState extends BaseAppState {
if (lzs != null && lzs.findZoneAt(hitX, hitZ) >= 0) return;
}
deselect();
if (input.activeLayer == SharedInput.LAYER_AUSWAHL) return;
if (input.activeLayer == SharedInput.LAYER_ZONEN && !"sound".equals(input.zonenNewKind)) return;
placing = true;
currX.clear();
@@ -311,10 +313,13 @@ public class SoundAreaState extends BaseAppState {
PlacedSoundArea a = areas.get(idx);
input.selectedSoundAreaInfo = idx + "|" + a.soundPath() + "|" + a.volume() + "|" + a.crossfade();
input.soundAreaSelectionChanged = true;
if (input.activeLayer == SharedInput.LAYER_ZONEN) {
if (input.activeLayer == SharedInput.LAYER_ZONEN || input.activeLayer == SharedInput.LAYER_AUSWAHL) {
input.zonenSelectedKind = "sound";
input.zonenSelectionChanged = true;
}
if (input.activeLayer == SharedInput.LAYER_AUSWAHL) {
input.auswahlZoneSelected = true;
}
}
// ── Add / Remove / Apply ──────────────────────────────────────────────────

View File

@@ -179,7 +179,7 @@ public class DialogEditorView extends BorderPane {
skriptBtn = new Button("Skript");
skriptBtn.setStyle("-fx-background-color: #3a4a6a; -fx-text-fill: white;");
skriptBtn.setDisable(true);
skriptBtn.setOnAction(e -> { exportNpcScript(); exportHeroScript(); });
skriptBtn.setOnAction(e -> { exportDialogOdt(); exportHeroOdt(); });
ttsBtn = new Button("TTS");
ttsBtn.setStyle("-fx-background-color: #5a3a2a; -fx-text-fill: white;");
@@ -1278,7 +1278,7 @@ public class DialogEditorView extends BorderPane {
// ── Skript-Export ──────────────────────────────────────────────────────────
private void exportNpcScript() {
private void exportDialogOdt() {
saveCurrentForm();
if (currentNpcId.isBlank() || allOptions.isEmpty()) {
Dialogs.alert(Alert.AlertType.WARNING,
@@ -1286,39 +1286,91 @@ public class DialogEditorView extends BorderPane {
return;
}
Map<String, String> texts = loadTexts();
StringBuilder sb = new StringBuilder();
sb.append("=== NPC-Skript: ").append(currentNpcId).append(" ===\n");
sb.append("Exportiert: ").append(LocalDate.now()).append("\n\n");
OdtScriptExporter odt = new OdtScriptExporter();
odt.h1("Dialog-Skript: " + currentNpcId);
odt.normal("Exportiert: " + LocalDate.now());
odt.empty();
String npcLabel = currentNpcId.replace('_', ' ').toUpperCase();
List<String> ordered = new ArrayList<>(rootIds);
for (String id : allOptions.keySet()) {
if (!rootIds.contains(id)) ordered.add(id);
}
for (String optId : ordered) {
DialogOption opt = allOptions.get(optId);
if (opt == null) continue;
for (Map.Entry<String, DialogOption> entry : allOptions.entrySet()) {
DialogOption opt = entry.getValue();
String optId = entry.getKey();
String base = "dialog." + currentNpcId + "." + optId;
List<String> heroKeys = resolveStepKeys(opt.getHeroSteps(), opt.getTextHero(), base, ".textmainchar");
List<String> npcKeys = resolveStepKeys(opt.getNpcSteps(), opt.getTextNpc(), base, ".textnpc");
if (npcKeys.isEmpty()) continue;
if (heroKeys.isEmpty() && npcKeys.isEmpty()) continue;
sb.append("--- ").append(optId);
if (rootIds.contains(optId)) sb.append(" [ROOT]");
sb.append(" ---\n");
appendStepLines(sb, npcKeys, "NPC", texts);
sb.append("\n");
odt.h2(optId + (rootIds.contains(optId) ? " [ROOT]" : ""));
List<String> conds = conditions(opt);
if (!conds.isEmpty()) odt.meta("Bedingungen: " + String.join(" / ", conds));
if (!heroKeys.isEmpty()) {
odt.speakerHeld(heroKeys.size() > 1 ? "HELD (" + heroKeys.size() + " Varianten):" : "HELD:");
for (int i = 0; i < heroKeys.size(); i++) {
String key = heroKeys.get(i);
String textVal = texts.getOrDefault(key, "(nicht übersetzt)");
if (heroKeys.size() > 1) odt.meta("Variante " + (i + 1) + ":");
odt.speechHeld("" + textVal + "");
odt.meta("Key: " + key);
odt.meta("Audio: " + audioPathForKey(key));
}
}
writeScript(ProjectRoot.resolve("dialog_script_npc_" + currentNpcId + ".txt"), sb.toString());
if (!npcKeys.isEmpty()) {
odt.speakerNpc(npcLabel + ":");
for (int i = 0; i < npcKeys.size(); i++) {
String key = npcKeys.get(i);
String textVal = texts.getOrDefault(key, "(nicht übersetzt)");
if (npcKeys.size() > 1) odt.meta("Schritt " + (i + 1) + ":");
odt.speechNpc("" + textVal + "");
odt.meta("Key: " + key);
odt.meta("Audio: " + audioPathForKey(key));
}
}
private void exportHeroScript() {
if (opt.getNextOptions() != null && !opt.getNextOptions().isEmpty()) {
List<String> ids = opt.getNextOptions().stream()
.filter(o -> o != null && o.getId() != null && !o.getId().isBlank())
.map(DialogOption::getId).toList();
if (!ids.isEmpty()) odt.meta("→ Danach: " + String.join(", ", ids));
}
if (opt.getDisablesOptions() != null && !opt.getDisablesOptions().isEmpty()) {
List<String> ids = opt.getDisablesOptions().stream()
.filter(o -> o != null && o.getId() != null && !o.getId().isBlank())
.map(DialogOption::getId).toList();
if (!ids.isEmpty()) odt.meta("✗ Deaktiviert: " + String.join(", ", ids));
}
odt.empty();
}
writeOdt(ProjectRoot.resolve("dialog_script_" + currentNpcId + ".odt"), odt);
}
private void exportHeroOdt() {
Map<String, String> texts = loadTexts();
StringBuilder sb = new StringBuilder();
sb.append("=== Held-Skript ===\n");
sb.append("Exportiert: ").append(LocalDate.now()).append("\n\n");
OdtScriptExporter odt = new OdtScriptExporter();
odt.h1("Held-Skript");
odt.normal("Exportiert: " + LocalDate.now());
odt.empty();
Path charDir = ProjectRoot.resolve("blight-assets", "src", "main", "resources", "character");
for (GameCharacter gc : CharacterIO.loadAll(charDir)) {
if (!(gc instanceof NPC npc)) continue;
if (npc.getDialogOptions() == null || npc.getDialogOptions().isEmpty()) continue;
String npcLabel = (npc.getCharacterId() != null ? npc.getCharacterId() : "NPC")
.replace('_', ' ').toUpperCase();
boolean npcHeaderWritten = false;
for (Map.Entry<String, DialogOption> entry : npc.getDialogOptions().entrySet()) {
String optId = entry.getKey();
DialogOption opt = entry.getValue();
@@ -1327,31 +1379,73 @@ public class DialogEditorView extends BorderPane {
if (heroKeys.isEmpty()) continue;
if (!npcHeaderWritten) {
sb.append("=== NPC: ").append(npc.getCharacterId()).append(" ===\n\n");
odt.h1("NPC: " + npc.getCharacterId());
odt.empty();
npcHeaderWritten = true;
}
sb.append("--- ").append(optId).append(" ---\n");
appendStepLines(sb, heroKeys, "HELD", texts);
sb.append("\n");
odt.h2(optId);
List<String> npcKeys = resolveStepKeys(opt.getNpcSteps(), opt.getTextNpc(), base, ".textnpc");
if (!npcKeys.isEmpty()) {
odt.speakerNpc(npcLabel + " (Kontext):");
for (String key : npcKeys) {
odt.contextNpc("" + texts.getOrDefault(key, "(nicht übersetzt)") + "");
}
}
odt.speakerHeld("HELD:");
for (int i = 0; i < heroKeys.size(); i++) {
String key = heroKeys.get(i);
if (heroKeys.size() > 1) odt.meta("Variante " + (i + 1) + ":");
odt.speechHeld("" + texts.getOrDefault(key, "(nicht übersetzt)") + "");
odt.meta("Key: " + key);
odt.meta("Audio: " + audioPathForKey(key));
}
odt.empty();
}
}
List<Monologue> monologues = MonologueIO.load();
if (!monologues.isEmpty()) {
sb.append("=== Monologe ===\n\n");
odt.h1("Monologe");
odt.empty();
for (Monologue m : monologues) {
if (m.getHeroSteps() == null || m.getHeroSteps().isEmpty()) continue;
sb.append("--- ").append(m.getId()).append(" ---\n");
List<String> keys = m.getHeroSteps().stream()
.filter(s -> s.getText() != null && !s.getText().id().isBlank())
.map(s -> s.getText().id())
.toList();
appendStepLines(sb, keys, "HELD", texts);
sb.append("\n");
.map(s -> s.getText().id()).toList();
if (keys.isEmpty()) continue;
odt.h2(m.getId());
odt.speakerHeld("HELD:");
for (int i = 0; i < keys.size(); i++) {
String key = keys.get(i);
if (keys.size() > 1) odt.meta("Schritt " + (i + 1) + ":");
odt.speechHeld("" + texts.getOrDefault(key, "(nicht übersetzt)") + "");
odt.meta("Key: " + key);
odt.meta("Audio: " + audioPathForKey(key));
}
odt.empty();
}
}
writeScript(ProjectRoot.resolve("dialog_script_held.txt"), sb.toString());
writeOdt(ProjectRoot.resolve("dialog_script_held.odt"), odt);
}
private List<String> conditions(DialogOption opt) {
List<String> conds = new ArrayList<>();
if (opt.getRequiresChapter() > 0)
conds.add("Kapitel ≥ " + opt.getRequiresChapter());
if (opt.getRequiresStatus() != null)
conds.add("Status: " + opt.getRequiresStatus());
if (opt.getRequiresQuestOpen() != null
&& opt.getRequiresQuestOpen().getQuestId() != null
&& !opt.getRequiresQuestOpen().getQuestId().isBlank())
conds.add("Quest offen: " + opt.getRequiresQuestOpen().getQuestId());
if (opt.getRequiresQuestComplete() != null
&& opt.getRequiresQuestComplete().getQuestId() != null
&& !opt.getRequiresQuestComplete().getQuestId().isBlank())
conds.add("Quest erfüllt: " + opt.getRequiresQuestComplete().getQuestId());
return conds;
}
private Map<String, String> loadTexts() {
@@ -1363,20 +1457,9 @@ public class DialogEditorView extends BorderPane {
return new LinkedHashMap<>();
}
private static void appendStepLines(StringBuilder sb, List<String> keys,
String label, Map<String, String> texts) {
for (int i = 0; i < keys.size(); i++) {
String key = keys.get(i);
sb.append(keys.size() > 1 ? " " + label + " " + (i + 1) + ":\n" : " " + label + ":\n");
sb.append(" Key: ").append(key).append("\n");
sb.append(" Text: \"").append(texts.getOrDefault(key, "(nicht übersetzt)")).append("\"\n");
sb.append(" Audio: ").append(audioPathForKey(key)).append("\n");
}
}
private void writeScript(Path out, String content) {
private void writeOdt(Path out, OdtScriptExporter odt) {
try {
Files.writeString(out, content);
odt.write(out);
Dialogs.alert(Alert.AlertType.INFORMATION,
"Skript exportiert nach:\n" + out, ButtonType.OK).showAndWait();
} catch (IOException ex) {
@@ -1399,7 +1482,7 @@ public class DialogEditorView extends BorderPane {
return List.of(base + suffix);
}
private static String audioPathForKey(String key) {
static String audioPathForKey(String key) {
if (key == null) return "";
if (key.startsWith("dialog.")) {
String without = key.substring("dialog.".length());

View File

@@ -46,10 +46,20 @@ public class MonologueEditorView extends SplitPane {
// ── Liste ─────────────────────────────────────────────────────────────────
private VBox buildList() {
listView.setStyle("-fx-control-inner-background: #2a2a3a; -fx-text-fill: #ddd;"
+ " -fx-selection-bar: #3a5a8a; -fx-selection-bar-text: white;");
listView.setCellFactory(lv -> new ListCell<>() {
{ selectedProperty().addListener((obs, old, sel) -> applyStyle()); }
@Override protected void updateItem(Monologue m, boolean empty) {
super.updateItem(m, empty);
setText(empty || m == null ? null : (m.getId().isBlank() ? "(kein Name)" : m.getId()));
if (empty || m == null) { setText(null); setStyle(""); return; }
setText(m.getId().isBlank() ? "(kein Name)" : m.getId());
applyStyle();
}
private void applyStyle() {
if (getItem() == null || isEmpty()) { setStyle(""); return; }
String bg = isSelected() ? "#3a5a8a" : "#2a2a3a";
setStyle("-fx-background-color: " + bg + "; -fx-text-fill: #cccccc;");
}
});
listView.getSelectionModel().selectedItemProperty().addListener((obs, o, n) -> {
@@ -94,11 +104,14 @@ public class MonologueEditorView extends SplitPane {
playOnceCheck = new CheckBox("Nur einmal abspielen");
playOnceCheck.setSelected(true);
playOnceCheck.setStyle("-fx-text-fill: #ccc;");
// Schritte
stepsView = new ListView<>();
stepsView.setPrefHeight(120);
stepsView.setStyle("-fx-background-color: #1e1e2e; -fx-control-inner-background: #1e1e2e;");
stepsView.setCellFactory(javafx.scene.control.cell.TextFieldListCell.forListView());
stepsView.setEditable(true);
Button addStepBtn = new Button("+ Schritt");
addStepBtn.setOnAction(e -> {
@@ -111,22 +124,18 @@ public class MonologueEditorView extends SplitPane {
String sel = stepsView.getSelectionModel().getSelectedItem();
if (sel != null) stepsView.getItems().remove(sel);
});
stepsView.setEditable(true);
stepsView.setCellFactory(javafx.scene.control.cell.TextFieldListCell.forListView());
HBox stepBtns = new HBox(6, addStepBtn, delStepBtn);
// Quest-Folgen
recvQuestField = questField("Quest erhalten (ID)");
fulfillsQuestField = questField("Quest erfüllen (ID)");
recvQuestField = questField("Quest wählen…");
fulfillsQuestField = questField("Quest wählen…");
abortsQuestsView = new ListView<>();
abortsQuestsView.setPrefHeight(80);
abortsQuestsView.setStyle("-fx-background-color: #1e1e2e; -fx-control-inner-background: #1e1e2e;");
Button addAbortBtn = new Button("+ Quest abbrechen");
addAbortBtn.setOnAction(e -> {
String id = showQuestPicker();
String id = showQuestPickerDialog();
if (id != null && !abortsQuestsView.getItems().contains(id)) abortsQuestsView.getItems().add(id);
});
Button delAbortBtn = new Button("- Entfernen");
@@ -136,9 +145,6 @@ public class MonologueEditorView extends SplitPane {
});
HBox abortBtns = new HBox(6, addAbortBtn, delAbortBtn);
Label stepsLbl = sectionLabel("Schritte (Textschlüssel)");
Label questLbl = sectionLabel("Quest-Folgen");
VBox form = new VBox(10);
form.setPadding(new Insets(12));
form.setStyle("-fx-background-color: #1e1e2e;");
@@ -147,12 +153,13 @@ public class MonologueEditorView extends SplitPane {
row("Kapitel (mind.):", chapterSpinner),
playOnceCheck,
new Separator(),
stepsLbl, stepsView, stepBtns,
sectionLabel("Schritte (Textschlüssel)"),
stepsView, stepBtns,
new Separator(),
questLbl,
sectionLabel("Quest-Folgen"),
row("Erhält Quest:", recvQuestField),
row("Erfüllt Quest:", fulfillsQuestField),
new Label("Bricht Quests ab:"),
sectionLabel("Bricht Quests ab:"),
abortsQuestsView, abortBtns
);
@@ -179,10 +186,7 @@ public class MonologueEditorView extends SplitPane {
current = m;
loading = true;
try {
if (m == null) {
clearForm();
return;
}
if (m == null) { clearForm(); return; }
idField.setText(m.getId());
chapterSpinner.getValueFactory().setValue(m.getRequiresChapter());
playOnceCheck.setSelected(m.isPlayOnce());
@@ -215,10 +219,9 @@ public class MonologueEditorView extends SplitPane {
List<DialogStep> steps = new ArrayList<>();
for (String key : stepsView.getItems()) {
if (!key.isBlank()) {
if (!key.isBlank())
steps.add(new DialogStep(new TextReference(key), new AudioReference(key)));
}
}
current.setHeroSteps(steps);
current.setRecievesQuest(questRefFromText(recvQuestField.getText()));
@@ -283,13 +286,13 @@ public class MonologueEditorView extends SplitPane {
tf.setEditable(false);
tf.setCursor(Cursor.HAND);
tf.setOnMouseClicked(e -> {
String id = showQuestPicker();
String id = showQuestPickerDialog();
if (id != null) tf.setText(id);
});
return tf;
}
private String showQuestPicker() {
private String showQuestPickerDialog() {
Path questDir = ProjectRoot.resolve("blight-assets", "src", "main", "resources").resolve("quests");
List<Quest> quests = QuestIO.loadAll(questDir);
@@ -310,11 +313,17 @@ public class MonologueEditorView extends SplitPane {
}
});
chooser.getItems().setAll(quests);
chooser.setStyle("-fx-background-color: #1a1a2a; -fx-control-inner-background: #1a1a2a;");
chooser.setPrefSize(380, 280);
chooser.setStyle("-fx-background-color: #1a1a2a; -fx-control-inner-background: #1a1a2a;"
+ " -fx-selection-bar: #3a5a8a;");
chooser.setPrefSize(380, 300);
dlg.getDialogPane().setContent(quests.isEmpty()
? new Label("Keine Quests gefunden.") : chooser);
if (quests.isEmpty()) {
Label hint = new Label("Keine Quests gefunden.\nQuests im Quest-Editor anlegen.");
hint.setStyle("-fx-text-fill: #888; -fx-font-style: italic;");
dlg.getDialogPane().setContent(hint);
} else {
dlg.getDialogPane().setContent(chooser);
}
dlg.getDialogPane().getButtonTypes().addAll(ButtonType.OK, ButtonType.CANCEL);
dlg.getDialogPane().setStyle("-fx-background-color: #252535;");
@@ -342,13 +351,13 @@ public class MonologueEditorView extends SplitPane {
private static Label sectionLabel(String text) {
Label l = new Label(text);
l.setStyle("-fx-font-weight: bold; -fx-text-fill: #aaa; -fx-font-size: 11;");
l.setStyle("-fx-font-weight: bold; -fx-font-size: 12; -fx-text-fill: #88aacc;");
return l;
}
private static HBox row(String labelText, javafx.scene.Node control) {
Label lbl = new Label(labelText);
lbl.setMinWidth(130);
lbl.setMinWidth(150);
lbl.setStyle("-fx-text-fill: #aaa;");
HBox.setHgrow(control, Priority.ALWAYS);
HBox box = new HBox(8, lbl, control);

View File

@@ -0,0 +1,134 @@
package de.blight.editor.ui;
import java.io.*;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.zip.*;
/** Builds an .odt (Open Document Text) ZIP archive for dialog script exports. */
public class OdtScriptExporter {
private final StringBuilder body = new StringBuilder();
public void h1(String text) { para("P_H1", text); }
public void h2(String text) { para("P_H2", text); }
public void normal(String text) { para("P_normal", text); }
public void meta(String text) { para("P_meta", text); }
public void speakerHeld(String text) { para("P_speaker_held",text); }
public void speakerNpc(String text) { para("P_speaker_npc", text); }
public void contextNpc(String text) { para("P_context_npc", text); }
/** Hero speech text — italic + highlighted (yellow background). */
public void speechHeld(String text) {
body.append("<text:p text:style-name='P_speech_held'>")
.append("<text:span text:style-name='C_hi'>").append(esc(text)).append("</text:span>")
.append("</text:p>\n");
}
/** NPC speech text — bold + highlighted (yellow background). */
public void speechNpc(String text) {
body.append("<text:p text:style-name='P_speech_npc'>")
.append("<text:span text:style-name='C_hi'>").append(esc(text)).append("</text:span>")
.append("</text:p>\n");
}
public void empty() {
body.append("<text:p text:style-name='P_normal'/>\n");
}
private void para(String style, String text) {
body.append("<text:p text:style-name='").append(style).append("'>")
.append(esc(text)).append("</text:p>\n");
}
public void write(Path out) throws IOException {
byte[] content = buildContentXml().getBytes(StandardCharsets.UTF_8);
byte[] manifest = MANIFEST.getBytes(StandardCharsets.UTF_8);
byte[] mime = "application/vnd.oasis.opendocument.text".getBytes(StandardCharsets.US_ASCII);
try (ZipOutputStream zip = new ZipOutputStream(new BufferedOutputStream(Files.newOutputStream(out)))) {
// mimetype MUST be first entry and MUST use STORED (uncompressed)
var crc = new CRC32();
crc.update(mime);
ZipEntry mimeEntry = new ZipEntry("mimetype");
mimeEntry.setMethod(ZipEntry.STORED);
mimeEntry.setSize(mime.length);
mimeEntry.setCompressedSize(mime.length);
mimeEntry.setCrc(crc.getValue());
zip.putNextEntry(mimeEntry);
zip.write(mime);
zip.closeEntry();
zip.putNextEntry(new ZipEntry("META-INF/manifest.xml"));
zip.write(manifest);
zip.closeEntry();
zip.putNextEntry(new ZipEntry("content.xml"));
zip.write(content);
zip.closeEntry();
}
}
private String buildContentXml() {
return """
<?xml version='1.0' encoding='UTF-8'?>
<office:document-content
xmlns:office='urn:oasis:names:tc:opendocument:xmlns:office:1.0'
xmlns:text='urn:oasis:names:tc:opendocument:xmlns:text:1.0'
xmlns:style='urn:oasis:names:tc:opendocument:xmlns:style:1.0'
xmlns:fo='urn:oasis:names:tc:opendocument:xmlns:xsl-fo-compatible:1.0'
office:version='1.3'>
<office:automatic-styles>
""" + STYLES + """
</office:automatic-styles>
<office:body><office:text>
""" + body + """
</office:text></office:body>
</office:document-content>
""";
}
private static final String STYLES =
ps("P_H1", "fo:margin-top='0.5cm' fo:margin-bottom='0.2cm'",
"fo:font-size='16pt' fo:font-weight='bold' fo:color='#003366'")
+ ps("P_H2", "fo:margin-top='0.6cm' fo:margin-bottom='0.1cm'"
+ " fo:border-top='0.5pt solid #aaaaaa' fo:padding-top='0.15cm'",
"fo:font-size='12pt' fo:font-weight='bold' fo:color='#224466'")
+ ps("P_normal", "", "fo:font-size='10pt'")
+ ps("P_meta", "fo:margin-left='1.2cm'",
"fo:font-size='9pt' fo:color='#888888'")
+ ps("P_speaker_held","fo:margin-top='0.3cm' fo:margin-left='0.3cm'",
"fo:font-size='10pt' fo:font-weight='bold' fo:font-style='italic' fo:color='#445577'")
+ ps("P_speaker_npc", "fo:margin-top='0.3cm' fo:margin-left='0.3cm'",
"fo:font-size='10pt' fo:font-weight='bold' fo:color='#222222'")
+ ps("P_speech_held", "fo:margin-left='1.2cm' fo:margin-top='0.05cm' fo:margin-bottom='0.05cm'",
"fo:font-size='11pt' fo:font-style='italic' fo:color='#334466'")
+ ps("P_speech_npc", "fo:margin-left='1.2cm' fo:margin-top='0.05cm' fo:margin-bottom='0.05cm'",
"fo:font-size='11pt' fo:color='#111111'")
+ ps("P_context_npc", "fo:margin-left='1.2cm' fo:margin-top='0.02cm' fo:margin-bottom='0.02cm'",
"fo:font-size='10pt' fo:color='#999999'")
+ " <style:style style:name='C_hi' style:family='text'>\n"
+ " <style:text-properties fo:font-weight='bold' fo:background-color='#ffffcc'/>\n"
+ " </style:style>\n";
private static String ps(String name, String pProp, String tProp) {
String s = " <style:style style:name='" + name + "' style:family='paragraph'>\n";
if (!pProp.isBlank()) s += " <style:paragraph-properties " + pProp + "/>\n";
if (!tProp.isBlank()) s += " <style:text-properties " + tProp + "/>\n";
return s + " </style:style>\n";
}
private static final String MANIFEST = """
<?xml version='1.0' encoding='UTF-8'?>
<manifest:manifest xmlns:manifest='urn:oasis:names:tc:opendocument:xmlns:manifest:1.0' manifest:version='1.3'>
<manifest:file-entry manifest:full-path='/' manifest:media-type='application/vnd.oasis.opendocument.text'/>
<manifest:file-entry manifest:full-path='content.xml' manifest:media-type='text/xml'/>
</manifest:manifest>
""";
private static String esc(String s) {
if (s == null) return "";
return s.replace("&", "&amp;").replace("<", "&lt;").replace(">", "&gt;");
}
}

View File

@@ -168,3 +168,73 @@ dialog.silas.erstaunt_ruhe.textnpc=Vielleicht wirke ich so. Und vielleicht habe
dialog.silas.abschluss_letzte_worte.textmainchar=(Der Held macht sich auf den Weg)
dialog.silas.abschluss_letzte_worte.textnpc=Jetzt geh schon. Bevor ich es mir anders überlege und dich doch noch für die Steuerjagd anmelde.
# ── Björnson ─────────────────────────────────────────────────────────────────
bjoernson.name=Björnson
# Dialog: Begegnung 1 Der Sklavenring
dialog.bjoernson.begruessung.textmainchar=Silas hat mich geschickt. Ich muss diese Fesseln loswerden. Sie sind alt... und sie schmerzen.
dialog.bjoernson.begruessung.textnpc=Sklavenstahl. Mieses Zeug, gemacht zum Erniedrigen. Silas weiß, dass ich so ein Gerümpel in zwei Minuten von deinen Knochen meißeln kann. Normalerweise nehme ich für meine Zeit gutes Gold... aber von dir gibt's wohl nicht viel zu holen.
dialog.bjoernson.wer_bist_du.textmainchar=Wer bist du überhaupt?
dialog.bjoernson.wer_bist_du.textnpc=Ich bin Björnson. Früher war ich ein Freibeuter und lebte mit den anderen im Süden der Insel, aber der Ganze Trubel... Wir aus dem Norden sind eigentlich nicht gemacht für Lärm und Heiterkeit, aber meine Schwestern und Brüder scheinen das vergessen zu haben.
dialog.bjoernson.aus_dem_norden.textmainchar=Aus dem Norden?
dialog.bjoernson.aus_dem_norden.textnpc=Unsere Vorfahren kamen vor vielen Wintern hierher, und haben sich hier nieder gelassen. Weißt du überhaupt was Winter sind?
dialog.bjoernson.aus_dem_norden.textnpc.1=Weißt du... ich vermisse den Lärm der Freibeuter nicht. Ich vermisse nicht ihr Geschrei und nicht ihre blöden Gesichter. Aber was ich vermisse, ist ein vernünftiges, dunkles Gerstenbier aus dem Norden.
dialog.bjoernson.bier_angebot.textmainchar=Vielleicht können wir uns bezüglich der Fesseln ja einig werden, wenn ich dir ein Bier organisiere?
dialog.bjoernson.bier_angebot.textnpc=Schaff mir ein kühles Krüglein von diesem Bier hier hoch. Ohne dass es warm wird. Dann schlage ich dir das Eisen von den Handgelenken und wir sind quitt. Keine großen Reden, kein Gelaber. Nur Bier gegen Freiheit. Abgemacht?
dialog.bjoernson.abgemacht_bier.textmainchar=Abgemacht.
dialog.bjoernson.bier_geliefert.textmainchar=Kälte kenne ich nur von feuchten Zellennächten. Aber hier ist dein Bier!
dialog.bjoernson.bier_geliefert.textnpc=Nicht dasselbe... Aber egal. Dann widmen wir uns mal deinem Problem!
# Dialog: Begegnung 2 Ein echtes Schwert
dialog.bjoernson.schwert_annehmen.textmainchar=Silas meint, ich bin bereit für eine echte Waffe. Er schickt mich zu dir, damit wir gemeinsam ein Schwert schmieden.
dialog.bjoernson.schwert_annehmen.textnpc=Du schon wieder. Das Bier letztes Mal war brauchbar. Aber du kommst nicht für einen Plausch hoch, das sehe ich dir an. Was führt dich zu meinem Amboss?
dialog.bjoernson.schwert_annehmen.textnpc.1=Silas... der Kerl denkt auch, Schmieden sei wie Fische fangen. "Zusammen schmieden"? Ich bin Schmied, kein Lehrmeister für Strandgut!
dialog.bjoernson.schwert_annehmen.textnpc.2=Aber gut. Wer ein echtes Schwert führen will, muss wissen, was darin steckt. Für eine Klinge, die nicht beim ersten Schlag gegen eine Protektorats-Rüstung zersplittert, brauche ich vernünftiges Material. Nicht den Müll, den die Gezeiten anspülen.
dialog.bjoernson.schwert_annehmen.textnpc.3=Tief in den Bergen liegt ein alter Magnetit-Stollen. Die Ader dort liefert reinstes Eisen. Aber seit Monden treibt sich dort ein aggressiver Blight-Wühler rum ein Biesterzeug, das durch die Verderbnis der Erde durchgedreht ist. Es blockiert den Zugang.
dialog.bjoernson.ich_kuemmere_mich.textmainchar=Ich kümmere mich darum.
dialog.bjoernson.ich_kuemmere_mich.textnpc=Das wirst du müssen. Schlag das Vieh tot, räum den Weg frei und bring mir eine Ladung des reinen Magnetiterzes mit. Wenn du das überlebst… dann schauen wir weiter!
dialog.bjoernson.erz_quest.textmainchar=Alles klar!
dialog.bjoernson.wie_schuerfern.textmainchar=Eine Sache noch... Wie kann ich das Erz schürfen?
dialog.bjoernson.wie_schuerfern.textnpc=Das kann ich dir gerne beibringen, aber auch das wird dich etwas kosten was über einen Gerstensaft hinaus geht.
dialog.bjoernson.wie_schuerfern.textnpc.1=Und schau mal neben der Mine, dort solltest du auch das entsprechende Werkzeug für deinen Job finden.
dialog.bjoernson.danke_bjoernson.textmainchar=Danke Björnson.
dialog.bjoernson.danke_bjoernson.textnpc=Mhhhh...
# Dialog: Quest-Abschluss Schwert
dialog.bjoernson.quests_erledigt.textmainchar=Ich habe alles erledigt!
dialog.bjoernson.quests_erledigt.textnpc=Erstaunlich damit hatte ich nicht gerechnet.
dialog.bjoernson.ich_bin_zaeh.textmainchar=Ich bin zäher als ich aussehe.
dialog.bjoernson.ich_bin_zaeh.textnpc=In der Tat…
dialog.bjoernson.wie_stehts_abmachung.textmainchar=Wie steht es nun um unsere Abmachung? Du schmiedest mir das Schwert?
dialog.bjoernson.wie_stehts_abmachung.textnpc=Oder ich bringe dir bei, wie du es dir selber schmieden kannst, die Wahl liegt bei dir.
dialog.bjoernson.nur_die_waffe.textmainchar=Ich brauche nur die Waffe…
dialog.bjoernson.nur_die_waffe.textnpc=Alles klar…
dialog.bjoernson.selber_lernen.textmainchar=Ich würde es gerne selber lernen…
dialog.bjoernson.selber_lernen.textnpc=Alles klar…
# Quests
quest.kuehles_bier.name=Ein kühles Bier!
quest.kuehles_bier.description=Björnson will ein kühles Gerstenbier. Bring ihm eines, ohne dass es warm wird dann nimmt er dir die Fesseln ab.
quest.kuehles_bier.success=Björnson hat sein Bier bekommen und schlägt dir die Eisen von den Handgelenken.
quest.blight_wuehler.name=Der Blight-Wühler
quest.blight_wuehler.description=Ein aggressiver Blight-Wühler blockiert den Eingang zum Magnetit-Stollen in den Bergen. Besiege ihn und mach den Weg frei.
quest.blight_wuehler.success=Der Blight-Wühler ist besiegt. Der Stollen ist wieder zugänglich.
quest.magnetiterz.name=Magnetiterz
quest.magnetiterz.description=Schürfe 5 Einheiten reines Magnetiterz aus dem alten Stollen in den Bergen. Björnson braucht es für das Schwert.
quest.magnetiterz.success=Du hast genug Magnetiterz gesammelt. Bring es zu Björnson.

View File

@@ -1,5 +1,41 @@
bjoernson.name
blutagave.description
blutagave.name
dialog.bjoernson.abgemacht_bier.textmainchar
dialog.bjoernson.aus_dem_norden.textmainchar
dialog.bjoernson.aus_dem_norden.textnpc
dialog.bjoernson.aus_dem_norden.textnpc.1
dialog.bjoernson.begruessung.textmainchar
dialog.bjoernson.begruessung.textnpc
dialog.bjoernson.bier_angebot.textmainchar
dialog.bjoernson.bier_angebot.textnpc
dialog.bjoernson.bier_geliefert.textmainchar
dialog.bjoernson.bier_geliefert.textnpc
dialog.bjoernson.danke_bjoernson.textmainchar
dialog.bjoernson.danke_bjoernson.textnpc
dialog.bjoernson.erz_quest.textmainchar
dialog.bjoernson.ich_bin_zaeh.textmainchar
dialog.bjoernson.ich_bin_zaeh.textnpc
dialog.bjoernson.ich_kuemmere_mich.textmainchar
dialog.bjoernson.ich_kuemmere_mich.textnpc
dialog.bjoernson.nur_die_waffe.textmainchar
dialog.bjoernson.nur_die_waffe.textnpc
dialog.bjoernson.quests_erledigt.textmainchar
dialog.bjoernson.quests_erledigt.textnpc
dialog.bjoernson.schwert_annehmen.textmainchar
dialog.bjoernson.schwert_annehmen.textnpc
dialog.bjoernson.schwert_annehmen.textnpc.1
dialog.bjoernson.schwert_annehmen.textnpc.2
dialog.bjoernson.schwert_annehmen.textnpc.3
dialog.bjoernson.selber_lernen.textmainchar
dialog.bjoernson.selber_lernen.textnpc
dialog.bjoernson.wer_bist_du.textmainchar
dialog.bjoernson.wer_bist_du.textnpc
dialog.bjoernson.wie_schuerfern.textmainchar
dialog.bjoernson.wie_schuerfern.textnpc
dialog.bjoernson.wie_schuerfern.textnpc.1
dialog.bjoernson.wie_stehts_abmachung.textmainchar
dialog.bjoernson.wie_stehts_abmachung.textnpc
dialog.silas.0114af85-986f-4923-b77e-88529b868c0f.description
dialog.silas.0114af85-986f-4923-b77e-88529b868c0f.textmainchar
dialog.silas.0114af85-986f-4923-b77e-88529b868c0f.textnpc
@@ -97,6 +133,15 @@ location.neu_1786533928723
location.neu_1786546178407
location.neu_1786606437840
location.test
quest.blight_wuehler.description
quest.blight_wuehler.name
quest.blight_wuehler.success
quest.kuehles_bier.description
quest.kuehles_bier.name
quest.kuehles_bier.success
quest.magnetiterz.description
quest.magnetiterz.name
quest.magnetiterz.success
silas.name
strandgut.description
strandgut.name

View File

@@ -1,258 +0,0 @@
=== Dialog-Skript: silas ===
Exportiert: 2026-08-13
--- begruessung [ROOT] ---
HERO:
Key: dialog.silas.begruessung.textmainchar
Text: "Hi! Ich suche nur einen Weg weg von diesem Strand."
Audio: audio/dialog/silas/begruessung_textmainchar.ogg
NPC:
Key: dialog.silas.begruessung.textnpc
Text: "Woher kommst du?"
Audio: audio/dialog/silas/begruessung_textnpc.ogg
--- antwort_sklave ---
HERO:
Key: dialog.silas.antwort_sklave.textmainchar
Text: "Ich war Sklave des Protektorats unser Schiff ist an den steilen Klippen gestrandet."
Audio: audio/dialog/silas/antwort_sklave_textmainchar.ogg
NPC:
Key: dialog.silas.antwort_sklave.textnpc
Text: "Ein Sklave des Protektorats? Das Meer hat heute wohl einen Sinn für Ironie. Spült dich direkt vor meine Haustür. Immerhin bist du ehrlich… und sind wir nicht alle Sklaven des Protektorats? Ich jage ihr Wild, du hast ihre Steine geschleppt. Am Ende des Tages sitzen wir beide unter demselben Stiefel. Nur dass meiner schon etwas länger drückt."
Audio: audio/dialog/silas/antwort_sklave_textnpc.ogg
--- antwort_gedaechtnislos ---
HERO:
Key: dialog.silas.antwort_gedaechtnislos.textmainchar
Text: "Ich habe mein Gedächtnis verloren ich bin kürzlich an dem Strand dort unten aufgewacht."
Audio: audio/dialog/silas/antwort_gedaechtnislos_textmainchar.ogg
NPC:
Key: dialog.silas.antwort_gedaechtnislos.textnpc
Text: "Du siehst aus wie ein Sklave des Protektorats! Das Meer hat heute wohl einen Sinn für Ironie. Spült dich direkt vor meine Haustür. Aber sind wir nicht alle Sklaven des Protektorats."
Audio: audio/dialog/silas/antwort_gedaechtnislos_textnpc.ogg
--- antwort_gesandter ---
HERO:
Key: dialog.silas.antwort_gesandter.textmainchar
Text: "Ich bin Gesandter des Königs unser Schiff ist gekentert und ich bin der einzige Überlebende."
Audio: audio/dialog/silas/antwort_gesandter_textmainchar.ogg
NPC:
Key: dialog.silas.antwort_gesandter.textnpc
Text: "Ein Gesandter des Königs? Du siehst eher aus wie ein Sklave des Protektorats! Das Meer hat heute wohl einen Sinn für Ironie. Spült dich direkt vor meine Haustür. Aber sind wir nicht alle Sklaven des Protektorats."
Audio: audio/dialog/silas/antwort_gesandter_textnpc.ogg
--- wer_bist_du ---
HERO:
Key: dialog.silas.wer_bist_du.textmainchar
Text: "Wer bist du?"
Audio: audio/dialog/silas/wer_bist_du_textmainchar.ogg
NPC:
Key: dialog.silas.wer_bist_du.textnpc
Text: "Silas. Früher haben wir diesen Pass bewacht, damit die Guanchen und wir in Frieden tauschen konnten. Heute bin ich nur noch ein Geist, der hier oben wartet, dass die Fäule auch den letzten Rest der Welt frisst."
Audio: audio/dialog/silas/wer_bist_du_textnpc.ogg
--- wer_seid_ihr ---
HERO:
Key: dialog.silas.wer_seid_ihr.textmainchar
Text: "Und wer seid „Ihr“?"
Audio: audio/dialog/silas/wer_seid_ihr_textmainchar.ogg
NPC:
Key: dialog.silas.wer_seid_ihr.textnpc
Text: "Wer wir sind? Eine aussterbende Art, Junge. Ich bin einer der Jäger, die diesen Pass schon bewacht haben, als das Protektorat noch nichts weiter war als ein ferner Albtraum in den Köpfen gieriger Könige. Früher war das hier ein Geben und Nehmen: Wir haben das Wild reguliert, mit den Guanchen Fleisch gegen Kräuter getauscht und die Pfade sauber gehalten.
Dann kamen die Eroberer mit ihren Vermessungsgeräten und ihren Gesetzen. Wollten mir vorschreiben, wann ich einen Hirsch erlegen darf und wie viel Steuer ich für jedes Stück Fell an Puerto Valor abtreten soll. Die meisten meiner Brüder sind geflohen auf die Nachbarinseln oder zu den Piraten, nur um dem Joch zu entkommen. Aber ich? Ich bin zu alt, um vor ein paar aufgeblasenen Gockeln in Blechrüstungen wegzulaufen. Also sitze ich hier oben, lebe von dem, was der Wald noch hergibt, und sehe zu, wie die Fäule alles verschlingt, was mir mal etwas bedeutet hat.
Für das Protektorat bin ich ein Wilderer. Für die Piraten ein Narr. Und für die Guanchen… nun, vielleicht der letzte Außenweltler, dem sie nicht sofort einen Speer in die Kehle jagen. Such dir aus, was dir am besten gefällt."
Audio: audio/dialog/silas/wer_seid_ihr_textnpc.ogg
--- was_leuchten ---
HERO:
Key: dialog.silas.was_leuchten.textmainchar
Text: "Was ist das für ein Zeug am Strand? Das Leuchten?"
Audio: audio/dialog/silas/was_leuchten_textmainchar.ogg
NPC:
Key: dialog.silas.was_leuchten.textnpc
Text: "Das ist der Tod, Junge. Die Blight. Die arroganten Gecken in Puerto Valor haben den Vulkan angebohrt und jetzt blutet die Insel Gift. Wenn du klug bist, drehst du um und läufst zurück ins Wasser.
Nicht alles auf dieser Insel will dich korrumpieren, Junge. Manche wollen dich einfach nur fressen. Ein wilder Hund hat keine Kristalle, die ihn schützen, aber er hat Hunger und Zähne wie Dolche. Behalt ihn im Auge, weich seinen Sprüngen aus und zeig ihm, wer der neue Herr im Dorf ist."
Audio: audio/dialog/silas/was_leuchten_textnpc.ogg
--- ausruestung ---
HERO:
Key: dialog.silas.ausruestung.textmainchar
Text: "Ich brauche Ausrüstung. Waffen. Und Informationen."
Audio: audio/dialog/silas/ausruestung_textmainchar.ogg
NPC:
Key: dialog.silas.ausruestung.textnpc
Text: "Ausrüstung? Du siehst aus, als hätte dich das Meer einmal durch den Fleischwolf gedreht. Pass auf: Geh zurück zum Strand, dort wo du angespült wurdest. Bei diesem Sturm ist sicher nicht nur Sklavenfleisch am Riff hängen geblieben. Such im Treibgut. Mit etwas Glück findest du dort mehr als nur nasse Planken."
Audio: audio/dialog/silas/ausruestung_textnpc.ogg
--- gibts_was_rum ---
HERO:
Key: dialog.silas.gibts_was_rum.textmainchar
Text: "Und was ist mit dir? Hast du nichts übrig?"
Audio: audio/dialog/silas/gibts_was_rum_textmainchar.ogg
NPC:
Key: dialog.silas.gibts_was_rum.textnpc
Text: "Ich bin kein Wohlfahrtsverband, Junge. Aber … du bist mir sympathisch, einen Schluck Insel-Rum kriegst du. Das Zeug brennt die Korruption aus der Kehle, zumindest für fünf Minuten. Trink."
Audio: audio/dialog/silas/gibts_was_rum_textnpc.ogg
--- tipp_rum ---
HERO:
Key: dialog.silas.tipp_rum.textmainchar
Text: "Und was ist mit dir? Hast du nichts übrig?"
Audio: audio/dialog/silas/tipp_rum_textmainchar.ogg
NPC:
Key: dialog.silas.tipp_rum.textnpc
Text: "Ich bin kein Wohlfahrtsverband, Junge. Aber … einen Tipp gibt es umsonst: Insel-Rum ist das Beste, wenn du dich mal zu viel mit den wilden Tieren geprügelt hast."
Audio: audio/dialog/silas/tipp_rum_textnpc.ogg
--- welchen_weg ---
HERO:
Key: dialog.silas.welchen_weg.textmainchar
Text: "Welchen Weg soll ich nehmen, um zu den Lagern zu kommen?"
Audio: audio/dialog/silas/welchen_weg_textmainchar.ogg
NPC:
Key: dialog.silas.welchen_weg.textnpc
Text: "Egal welchen Weg du nimmst geh nicht so, wie du jetzt aussiehst. In diesen Lumpen erkennt dich jeder sofort als entflohenen Sklaven. Das Protektorat steckt dich direkt wieder in Ketten, und die Piraten… nun, die füttern die Haie mit Typen, die nichts wert sind.
Hör zu: Wenn du den Hang Richtung Strand wieder ein Stück runtergehst, zweigt ein Pfad zu einem alten Fischerdorf ab. Die Leute dort sind schon lange weg, als die Fäule kam, aber in den Hütten findest du vielleicht ein paar alte Lumpen, die weniger nach 'Gefängnis' stinken. Es ist besser als nichts."
Audio: audio/dialog/silas/welchen_weg_textnpc.ogg
--- abschluss_phase1 ---
HERO:
Key: dialog.silas.abschluss_phase1.textmainchar
Text: "Alles klar. Ich mache mich auf den Weg."
Audio: audio/dialog/silas/abschluss_phase1_textmainchar.ogg
NPC:
Key: dialog.silas.abschluss_phase1.textnpc
Text: "Also, hier ist der Plan: Such am Strand nach Brauchbarem, schau im Fischerdorf nach Kleidung und komm dann wieder zu mir. Wenn du dann nicht mehr aussiehst wie eine wandelnde Leiche, unterhalten wir uns darüber, wie du an den Monstern am Pass vorbeikommst. Jetzt beweg dich."
Audio: audio/dialog/silas/abschluss_phase1_textnpc.ogg
--- rueckkehr_beobachtet ---
HERO:
Key: dialog.silas.rueckkehr_beobachtet.textmainchar
Text: "(Ich kehre zu Silas zurück)"
Audio: audio/dialog/silas/rueckkehr_beobachtet_textmainchar.ogg
NPC:
Key: dialog.silas.rueckkehr_beobachtet.textnpc
Text: "Sieh mal einer an. Das Meer hat dich ausgespückt und die Insel hat dich nicht wieder verschlungen. Ich hätte meine letzte Flasche Rum darauf verwettet, dass dich die Klippenbeßer am Hang in handliche Stücke zerlegen, noch bevor du das Dorf erreichst."
Audio: audio/dialog/silas/rueckkehr_beobachtet_textnpc.ogg
--- ich_bin_zaeh ---
HERO:
Key: dialog.silas.ich_bin_zaeh.textmainchar
Text: "Ich bin zäher, als ich aussehe."
Audio: audio/dialog/silas/ich_bin_zaeh_textmainchar.ogg
NPC:
Key: dialog.silas.ich_bin_zaeh.textnpc
Text: "Das scheinst du wohl zu sein. Zumindest stinkst du jetzt weniger nach Sklavenpferch. Damit kommst du vielleicht an den Wachen der Festung vorbei, ohne dass sie direkt die Musketen laden."
Audio: audio/dialog/silas/ich_bin_zaeh_textnpc.ogg
--- weg_ins_innere ---
HERO:
Key: dialog.silas.weg_ins_innere.textmainchar
Text: "Du hast gesagt, wir unterhalten uns über den Weg ins Landesinnere, wenn ich bereit bin."
Audio: audio/dialog/silas/weg_ins_innere_textmainchar.ogg
NPC:
Key: dialog.silas.weg_ins_innere.textnpc
Text: "Richtig. Hör zu, Junge: Die Pfade hier draußen sind tückisch geworden. Mein wichtigster Rat? Bleib auf den Wegen. Die Blight hat die Kreaturen in den Wäldern wahnsinnig gemacht. Abseits der Pfade lauern Dinge, für die dein kleiner Roststängel da am Gürtel nur ein Zahnstocher ist. Geh nur in die tiefe Wildnis, wenn du Rüstung am Leib und Stahl in der Hand hast, der diesen Namen auch verdient."
Audio: audio/dialog/silas/weg_ins_innere_textnpc.ogg
--- was_ueber_insel ---
HERO:
Key: dialog.silas.was_ueber_insel.textmainchar
Text: "Diese Insel… sie wirkt so fremdartig. Was kannst du mir über diesen Ort sagen?"
Audio: audio/dialog/silas/was_ueber_insel_textmainchar.ogg
NPC:
Key: dialog.silas.was_ueber_insel.textnpc
Text: "Hör mir gut zu, Junge. Aeterna ist wie eine gespaltene Münze, und der große Vulkan in der Mitte der Monte Fuego ist der Amboss, auf dem alles geschmiedet wird. Der Westen ist ein einziges grünes Grab, erstickt von uralten Wäldern. Der Osten dagegen, hinter dem Berg, ist karg und trocken wie ein alter Knochen."
Audio: audio/dialog/silas/was_ueber_insel_textnpc.ogg
--- info_osten ---
HERO:
Key: dialog.silas.info_osten.textmainchar
Text: "Wer herrscht im Osten der Insel?"
Audio: audio/dialog/silas/info_osten_textmainchar.ogg
NPC:
Key: dialog.silas.info_osten.textnpc
Text: "Die Protektoren. Sie haben sich im Südosten in Puerto Valor breitgemacht. Sie haben Mauern, Ordnung und das beste Equipment aber sie haben auch einen Stock im Hintern und verlangen Gehorsam. Wenn du dort ihren Wein trinken willst, den sie in den Sonnental-Weinbergen anbauen, musst du nach ihrer Pfeife tanzen."
Audio: audio/dialog/silas/info_osten_textnpc.ogg
--- info_westen ---
HERO:
Key: dialog.silas.info_westen.textmainchar
Text: "Und was ist mit dem Westen?"
Audio: audio/dialog/silas/info_westen_textmainchar.ogg
NPC:
Key: dialog.silas.info_westen.textnpc
Text: "Dort regiert das Chaos oder die Natur. Im Südwesten liegt Shipwreck Bay, das Nest der Freibeuter. Da stellt keiner Fragen über deine Vergangenheit, solange du zupacken kannst. Aber pass auf deinen Geldbeutel auf und auf deinen Rücken. Tief im Nordwesten, im Schatten der Nebelgipfel, hausen die Guanchen in ihrer Siedlung Taganana. Du musst sie erst mal finden, aber wenn du Magie suchst, sind sie deine einzige Chance."
Audio: audio/dialog/silas/info_westen_textnpc.ogg
--- info_dorf ---
HERO:
Key: dialog.silas.info_dorf.textmainchar
Text: "Was hat es mit diesem Dorf hier auf sich?"
Audio: audio/dialog/silas/info_dorf_textmainchar.ogg
NPC:
Key: dialog.silas.info_dorf.textnpc
Text: "Das hier im Norden ist San Pedro. Früher ein reiches Fischerdorf, jetzt nur noch Schutt und Gestrüpp. Ein guter Ort, um den Kampf gegen Klippenbeßer zu üben. Ein wilder Hund hat keine Kristalle, die ihn schützen, aber er hat Hunger und Zähne wie Dolche. Weich ihren Sprüngen aus, dann gehört das Fleisch dir."
Audio: audio/dialog/silas/info_dorf_textnpc.ogg
--- info_pass ---
HERO:
Key: dialog.silas.info_pass.textmainchar
Text: "Und der Weg durch den Pass?"
Audio: audio/dialog/silas/info_pass_textmainchar.ogg
NPC:
Key: dialog.silas.info_pass.textnpc
Text: "Hinter meiner Hütte geht es steil bergauf. Oben am Kamm haben sich ein paar Corrupted eingenistet. Sie sind langsam, aber wenn sie dich einkesseln, war es das. Nutze den Platz, den du hast, und schlag erst zu, wenn du eine Lücke siehst. Sobald du an ihnen vorbei bist, liegt Aeterna vor dir."
Audio: audio/dialog/silas/info_pass_textnpc.ogg
--- ich_weiss_genug ---
HERO:
Key: dialog.silas.ich_weiss_genug.textmainchar
Text: "Ich glaub ich weiß alles, was ich brauche…"
Audio: audio/dialog/silas/ich_weiss_genug_textmainchar.ogg
NPC:
Key: dialog.silas.ich_weiss_genug.textnpc
Text: "Du willst also blind ins Verderben rennen? Hier, nimm das, damit deine Leiche wenigstens in der Nähe eines Pfades gefunden wird."
Audio: audio/dialog/silas/ich_weiss_genug_textnpc.ogg
--- karte_zeichnen ---
HERO:
Key: dialog.silas.karte_zeichnen.textmainchar
Text: "(Silas zeichnet die Karte)"
Audio: audio/dialog/silas/karte_zeichnen_textmainchar.ogg
NPC:
Key: dialog.silas.karte_zeichnen.textnpc
Text: "Du stellst verdammt viele Fragen für jemanden, der kaum eine Waffe halten kann. Aber Neugier hält einen hier oben am Leben.
Komm näher. Ich zeichne dir das Ganze auf: San Pedro hier oben, Monte Fuego in der Mitte, Puerto Valor im trockenen Südosten und Shipwreck Bay im Wald des Südwestens. Der Pfad führt direkt hinauf zum Schlund… aber sag nicht, ich hätte dich nicht gewarnt. Hier… nimm die Karte."
Audio: audio/dialog/silas/karte_zeichnen_textnpc.ogg
--- dankeschoen ---
HERO:
Key: dialog.silas.dankeschoen.textmainchar
Text: "Danke, Silas. Ich werde vorsichtig sein."
Audio: audio/dialog/silas/dankeschoen_textmainchar.ogg
NPC:
Key: dialog.silas.dankeschoen.textnpc
Text: "Das hoffe ich. Und hey… wenn du mal wieder in der Nähe bist und noch alle Gliedmaßen hast, schau auf einen Rum vorbei. Erzähl mir, was in der Welt da draußen vor sich geht. Ich will wissen, wer sich gerade gegenseitig an die Gurgel geht."
Audio: audio/dialog/silas/dankeschoen_textnpc.ogg
--- erstaunt_ruhe ---
HERO:
Key: dialog.silas.erstaunt_ruhe.textmainchar
Text: "Ich dachte, du willst hier oben deine Ruhe haben und hast die Welt da draußen längst abgeschrieben?"
Audio: audio/dialog/silas/erstaunt_ruhe_textmainchar.ogg
NPC:
Key: dialog.silas.erstaunt_ruhe.textnpc
Text: "Vielleicht wirke ich so. Und vielleicht habe ich den Glauben an die Menschen verloren… aber ich habe diese Insel noch nicht ganz aufgegeben. Und ich hab so ein Gefühl bei dir, Junge. Nenn es Instinkt oder den Rum, aber ich glaube, du wirst auf Aeterna noch für ordentlich Wirbel sorgen. Ich will nur sichergehen, dass ich in der ersten Reihe sitze, wenn der Vorhang fällt."
Audio: audio/dialog/silas/erstaunt_ruhe_textnpc.ogg
--- abschluss_letzte_worte ---
HERO:
Key: dialog.silas.abschluss_letzte_worte.textmainchar
Text: "(Der Held macht sich auf den Weg)"
Audio: audio/dialog/silas/abschluss_letzte_worte_textmainchar.ogg
NPC:
Key: dialog.silas.abschluss_letzte_worte.textnpc
Text: "Jetzt geh schon. Bevor ich es mir anders überlege und dich doch noch für die Steuerjagd anmelde."
Audio: audio/dialog/silas/abschluss_letzte_worte_textnpc.ogg

BIN
doc/Björnson.odt Normal file

Binary file not shown.