From 985c34759dc5b2c7066f8ed3db09936abb5d4abc Mon Sep 17 00:00:00 2001 From: Richard Zink Date: Thu, 5 Nov 2026 17:15:20 +0100 Subject: [PATCH] Editor texts (EN/DE), whole-campaign play and briefings for editor missions Co-Authored-By: Claude Opus 5.5 --- lang/de.lang | 315 ++++++++++++++++++++++++++++++++++++++++ lang/en.lang | 315 ++++++++++++++++++++++++++++++++++++++++ src/editor/mod.rs | 16 +- src/editor/templates.rs | 4 +- src/main.rs | 35 ++++- src/menu.rs | 34 ++++- 6 files changed, 705 insertions(+), 14 deletions(-) diff --git a/lang/de.lang b/lang/de.lang index a60d466..b240c67 100644 --- a/lang/de.lang +++ b/lang/de.lang @@ -1012,3 +1012,318 @@ ov.summary = {0} Kreaturen - Lohn {1} Gold pro Zahltag - Laune {2}% ov.none = Noch keine Kreaturen. Baue ein Quartier und einen Pilzgarten und nimm ein Portal ein. ov.hint = St., Laune, Lohn, Tätigkeit, Leben - Klick: hingehen set.voice = Krummfingers Stimme + +# ---- campaign editor, block scripts, demo campaign +menu.back_editor = Zurück zum Editor +ed.campaign = Kampagneneditor +ed.name = Name +ed.author = Autor +ed.language = Sprache +ed.play = Spielen +ed.new = Neu +ed.open = Öffnen +ed.save_file = Als Datei +ed.save_server = Online speichern +ed.close = Schließen +ed.published = Veröffentlicht +ed.add_mission = + Mission +ed.new_campaign = Neue Kampagne +ed.card_scripts = {0} Trigger +ed.card_problems = {0} Probleme +ed.graph_hint = Klick auf eine Karte bearbeitet sie. Karten ziehen verschiebt sie, vom Punkt auf eine Karte ziehen verbindet Missionen (gold: weiter, blau: Abzweig). Rechte Maustaste verschiebt die Ansicht. +ed.open_file = Datei öffnen +ed.open_demo = Demo öffnen +ed.template = Eine Mission der Kampagne als Kopie +ed.template_add = Hinzufügen +ed.own_campaigns = Meine Kampagnen (online) +ed.public_campaigns = Veröffentlichte Kampagnen +ed.none = keine +ed.cancel = Abbrechen +ed.back_graph = < Kampagne +ed.test = Testspiel +ed.tab_general = Allgemein +ed.tab_goals = Ziele +ed.tab_map = Karte +ed.tab_scripts = Skripte +ed.title = Titel +ed.briefing = Briefing +ed.gold = Startgold +ed.time_limit = Zeitlimit +ed.waves = Heldenwellen +ed.wave_limit = Wellen +ed.endless = endlos +ed.first_wave = Erste Welle +ed.goals = Ziele +ed.goals_hint = Die Mission ist gewonnen, wenn alle Ziele erreicht sind (oder ein Skript "Gewinnen" ruft). +ed.add_goal = + Ziel +ed.next_mission = Nächste Mission +ed.branches = Abzweige +ed.branches_hint = Wird zuerst ein anderes Ziel erreicht, geht die Kampagne mit einer anderen Mission weiter. +ed.add_branch = + Abzweig +ed.goal.killall = Alle Helden besiegen +ed.goal.shards = Splitter holen +ed.goal.heart = Tor zerstören +ed.goal.towers = Rittertürme schleifen +ed.goal.rivals = Rivalen besiegen +ed.goal.loot = Gold erbeuten +ed.goal.survive = Wellen überstehen +ed.goal.springs = Manaquellen nehmen +ed.goal.freed = Kreaturen befreien +ed.map_random = Zufallskarte +ed.map_fixed = Gemalte Karte +ed.map_is_random = Zufallskarte +ed.map_is_fixed = Gemalte Karte +ed.map_random_hint = Wird für jeden Durchgang der Kampagne neu gewürfelt und bleibt im Durchgang gleich. +ed.map_fixed_hint = Im Karteneditor gemalt; sie ändert sich nie. +ed.map_width = Breite +ed.map_height = Höhe +ed.map_seed = Seed +ed.rivers = Flüsse +ed.lava_rivers = Lavaflüsse +ed.forts = Ritterfestungen +ed.rivals = Rivalen-Meister +ed.difficulty = Schwierigkeit +ed.portals = Portale +ed.edit_map = Karte bearbeiten +ed.make_random = Zur Zufallskarte machen +ed.paint_map = Karte erzeugen und malen +ed.zones_list = Zonen ({0}): {1} +ed.special_scripts = Sonderskripte +ed.script_start = Start (einmal) +ed.script_victory = Siegprüfung (jede Sekunde) +ed.script_defeat = Niederlageprüfung (jede Sekunde) +ed.script_tick = Takt (jede Sekunde) +ed.blocks_count = {0} Blöcke +ed.blocks_problems = {0} Blöcke, {1} Probleme +ed.edit_blocks = Bearbeiten +ed.triggers = Trigger +ed.triggers_hint = Ein Trigger führt seine Blöcke aus, sobald seine Bedingung erfüllt ist. Zonen werden im Karteneditor gezeichnet. +ed.add_trigger = + Trigger +ed.once = einmal +ed.enabled = aktiv +ed.edit_trigger_blocks = Blöcke ({0}) +ed.pick_zone = - Zone - +ed.pick_var = - Variable - +ed.pick_trigger = - Trigger - +ed.any_kind = jede Art +ed.any_room = jeder Raum +ed.any_spell = jeder Zauber +ed.every = alle +ed.after = nach +ed.repeat = wiederholen +ed.unit_name = Name +ed.towers_dungeon = Türme des Kerkers +ed.towers_knights = Türme der Ritter +ed.no_settings = (nichts einzustellen) +ed.side.player = Spieler +ed.side.heroes = Helden +ed.side.mossgrim = Mossgrim +ed.side.nightwell = Lady Nightwell +ed.side.rival3 = Rivale 3 +ed.side.any = alle +ed.trig.zone_enter = Zone betreten +ed.trig.zone_leave = Zone verlassen +ed.trig.line_cross = Linie überquert +ed.trig.zone_claimed = Zone erobert +ed.trig.zone_dug = In Zone gegraben +ed.trig.zone_count = Einheiten in Zone +ed.trig.timer = Timer +ed.trig.unit_dies = Einheit fällt +ed.trig.tower_falls = Turm fällt +ed.trig.value = Wert erreicht +ed.trig.room_built = Raum gebaut +ed.trig.spell_cast = Zauber gewirkt +ed.trig.slap = Kreatur geschlagen +ed.trig.research = Forschung fertig +ed.value.gold = Gold +ed.value.mana = Mana +ed.value.creatures = Kreaturen +ed.value.heroes = Helden +ed.value.var = Variable +ed.map_done = Fertig +ed.cat.terrain = Boden +ed.cat.rooms = Räume +ed.cat.places = Orte +ed.cat.units = Einheiten +ed.cat.towers = Türme +ed.cat.traps = Fallen +ed.cat.forts = Festungen +ed.cat.zones = Zonen +ed.cat.erase = Löschen +ed.terrain.floor = Boden +ed.terrain.earth = Erde +ed.terrain.reinforced = Befestigte Wand +ed.terrain.rock = Fels +ed.terrain.water = Wasser +ed.terrain.lava = Lava +ed.terrain.bridge = Brücke +ed.terrain.lavabridge = Lavabrücke +ed.terrain.gold = Goldader +ed.terrain.silver = Silberader +ed.terrain.ruby = Rubinader +ed.terrain.diamond = Diamantader +ed.terrain.steel = Stahlwand +ed.place.core = Seelenkern +ed.place.portal = Portal +ed.place.gate = Heldentor +ed.place.spring = Manaquelle +ed.place.shard = Kernsplitter +ed.place.chest = Runentruhe +ed.place.chained = Angekettete Kreatur +ed.tower.arrow = Pfeilturm +ed.tower.cannon = Kanonenturm +ed.tower.magic = Magieturm +ed.tower.rune = Runenschmiedeturm +ed.trap.spikes = Stachelgrube +ed.trap.poison = Giftwolke +ed.trap.fire = Feuerstrahl +ed.trap.roller = Steinwalze +ed.trap.alarm = Alarm +ed.trap.mine = Runenmine +ed.trap.frost = Frostglut +ed.trap.wooddoor = Holztür +ed.trap.irondoor = Eisentür +ed.trap.magicdoor = Magietür +ed.fort = Ritterfestung +ed.fort_shard = Festung mit Splitter +ed.owner = Besitzer +ed.side = Seite +ed.unit_kind = Art +ed.level = Stufe +ed.zone_shape = Form einer neuen Zone +ed.zone_new_name = Name einer neuen Zone +ed.zones = Zonen +ed.shape.rect = Rechteck +ed.shape.circle = Kreis +ed.shape.tiles = Kacheln +ed.shape.line = Linie +ed.brush = Pinsel +ed.rect = Rechteck +ed.fill = Füllen +ed.mirror0 = Kein Spiegeln +ed.mirror1 = Spiegeln links/rechts +ed.mirror2 = Spiegeln oben/unten +ed.mirror3 = Spiegeln beides +ed.undo = Rückgängig +ed.redo = Wiederholen +ed.map_info = {0}x{1}, {2} Einheiten, {3} Türme +ed.map_hint = Links: malen/setzen, rechts: Ansicht schieben, Mitte: drehen, Rad: Zoom, WASD/QE, Strg+Z/Y +ed.back = < Zurück +ed.delete = Löschen +ed.paste = Einfügen +ed.copy = Kopieren +ed.search = Suchen... +ed.no_problems = Keine Probleme +ed.problems = {0} Probleme (rot) +ed.blocks_hint = Blöcke ins Skript ziehen. Am Griff verschieben, hierher zurückziehen löscht sie. +ed.empty_script = Leer: Blöcke von links hierher ziehen. +ed.drop_here = Blöcke hier ablegen +ed.trigger_script = Trigger: {0} +ed.saved_server = Online gespeichert. +ed.loaded = Kampagne geladen. +ed.no_server = Keine Verbindung zum Server. +ed.busy = Der Server ist noch beschäftigt, gleich noch einmal versuchen. +ed.save_first = Die Kampagne zuerst online speichern. +ed.debug_title = Skripte (Test) +blk.cat.say = Sprechen +blk.cat.units = Einheiten +blk.cat.world = Welt +blk.cat.logic = Logik +blk.cat.vars = Variablen +blk.cat.mission = Mission +blk.message = Nachricht +blk.objective = Auftrag +blk.hint = Krummfinger sagt +blk.spawn = Erscheinen +blk.wave = Heldenwelle +blk.kill = Entfernen +blk.terrain = Boden ändern +blk.reveal = Aufdecken +blk.camera = Zeigen +blk.gold = Gold + +blk.mana = Mana + +blk.if = Wenn +blk.repeat = Wiederhole +blk.trigger = Trigger schalten +blk.setvar = Setze +blk.addvar = Ändere +blk.win = Mission gewinnen +blk.lose = Mission verlieren +blk.else = sonst +blk.help.message = Zeigt eine Nachricht. {name} im Text zeigt den Wert einer Variable. +blk.help.objective = Setzt den Auftrag, den der Spieler sieht. +blk.help.hint = Krummfinger kommentiert etwas, mit Ton. +blk.help.spawn = Einheiten erscheinen: wie viele, welche Art, Stufe, Seite und wo (Kern, bei der auslösenden Einheit oder in einer Zone). Mit Namen kann ein "Einheit fällt"-Trigger genau auf diese Einheit warten. +blk.help.wave = Schickt sofort Heldenwellen. +blk.help.kill = Entfernt alle Einheiten einer Seite in einer Zone. +blk.help.terrain = Macht den Boden einer Zone zu Boden, Erde, Fels, Wasser, Lava oder Gold. +blk.help.reveal = Hebt den Nebel über einer Zone. +blk.help.camera = Bewegt die Kamera zu einer Zone. +blk.help.gold = Gibt (oder nimmt) Gold. +blk.help.mana = Gibt (oder nimmt) Mana. +blk.help.if = Führt die Blöcke darin nur aus, wenn die Bedingung gilt, sonst die unter "sonst". +blk.help.repeat = Führt die Blöcke darin mehrmals aus. +blk.help.trigger = Schaltet einen Trigger ein (er darf erneut auslösen) oder aus. +blk.help.setvar = Setzt eine Variable (eine Zahl, die sich die Mission merkt). +blk.help.addvar = Zählt eine Zahl zu einer Variable dazu (negativ: zieht ab). +blk.help.win = Die Mission ist gewonnen. +blk.help.lose = Die Mission ist verloren. +blk.v.num = Zahl +blk.v.var = Variable +blk.v.gold = Gold +blk.v.mana = Mana +blk.v.time = Gespielte Sekunden +blk.v.creatures = Anzahl +blk.v.inzone = Anzahl +blk.v.random = Zufall +blk.v.math = Rechnung +blk.v.cmp = Vergleich +blk.v.and = und +blk.v.or = oder +blk.v.not = nicht +blk.v.bool = Ja/Nein +blk.v.eventkind = Auslöser ist ein +blk.w_at = bei +blk.w_by = um +blk.w_in = in +blk.w_level = Stufe +blk.w_name = Name +blk.w_times = mal +blk.w_to = auf +blk.and = und +blk.or = oder +blk.not = nicht +blk.yes = ja +blk.no = nein +blk.on = an +blk.off = aus +blk.place_core = dem Kern +blk.place_event = der auslösenden Einheit +blk.place_zone = Zone {0} +demo.name = Demo: alle Trigger +demo.branch = Reich genug für einen Umweg +demo.m1.title = Die Probe der Trigger +demo.m1.story = Ein kleiner Ritterposten östlich deines Herzens, ein Tresor, eine Höhle, ein Tunnel und eine Grenzlinie.\n\nJeder Trigger des Editors löst hier irgendwo aus: Betritt die Höhle, grabe den Tunnel, erobere den Tresor, schlag eine Kreatur, wirk einen Zauber, bau einen Hort, erforsche etwas und brich den Posten. +demo.start = Willkommen zur Probe, Meister. Jeder deiner Schritte wird von einem Trigger beobachtet. Meistens auch von mir. +demo.objective = Schleife die Rittertürme - oder besiege acht Ritter. +demo.win = Acht Ritter erledigt. Die Probe ist bestanden! +demo.lose = Zehn Minuten ohne eine einzige Kreatur? Das ist kein Kerker, das ist ein Keller. +demo.tick = Zwei Minuten gespielt. Der Uhr-Trigger hat viermal gezählt. +demo.den = Die Höhle ist offen. Ein verschlafener Oger kriecht heraus, um zu sehen, wer stört. +demo.den_leave = Jemand hat die Höhle wieder verlassen. Der Oger bleibt. +demo.border = Ritter überqueren die Grenzlinie! +demo.vault = Der Tresor gehört dir: 500 Gold klimpern in die Schatzkammer. +demo.tunnel = Der Tunnel ist gegraben. Irgendetwas darunter seufzt erleichtert. +demo.crowd = Vier oder mehr Ritter sammeln sich auf dem Feld! +demo.wave = Verstärkung! Zwei Bogenschützinnen marschieren aufs Feld. +demo.kills = Besiegte Ritter: {kills} +demo.captain = Der Hauptmann ist gefallen. Sein Mana gehört dir. +demo.tower = Ein Ritterturm fällt - und lässt etwas Gold fallen. +demo.rich = 8000 Gold. Du darfst dich jetzt wohlhabend nennen. +demo.lair = Ein Hort! Kreaturen brauchen Betten, auch böse. +demo.slap = Autsch. Der Schlag-Trigger hat es bemerkt. Die Kreatur auch. +demo.research = Forschung fertig. Ein wenig Gold für die Gelehrten. +demo.m2.title = Neu gewürfelt +demo.m2.story = Diese Karte wird für jeden Durchgang der Kampagne neu gewürfelt. Besiege alle Helden. +demo.m2.start = Eine Zufallskarte. Selbst ich habe sie noch nie gesehen. +demo.m2.wave = Zwei Minuten sind um: Die Helden kommen. diff --git a/lang/en.lang b/lang/en.lang index 3ac6235..ef31be9 100644 --- a/lang/en.lang +++ b/lang/en.lang @@ -1013,3 +1013,318 @@ ov.summary = {0} creatures - wages {1} gold per payday - mood {2}% ov.none = No creatures yet. Build a lair and a mushroom garden and claim a portal. ov.hint = Lv, mood, wage, job, health - click: go there set.voice = Crookfinger's voice + +# ---- campaign editor, block scripts, demo campaign +menu.back_editor = Back to the editor +ed.campaign = Campaign editor +ed.name = Name +ed.author = Author +ed.language = Language +ed.play = Play +ed.new = New +ed.open = Open +ed.save_file = Save file +ed.save_server = Save online +ed.close = Close +ed.published = Published +ed.add_mission = + Mission +ed.new_campaign = New campaign +ed.card_scripts = {0} triggers +ed.card_problems = {0} problems +ed.graph_hint = Click a card to edit it. Drag a card to move it, drag from a dot to a card to link missions (gold: next, blue: branch). Right mouse button moves the view. +ed.open_file = Open a file +ed.open_demo = Open the demo +ed.template = A mission of the campaign as a copy +ed.template_add = Add +ed.own_campaigns = My campaigns (online) +ed.public_campaigns = Published campaigns +ed.none = none +ed.cancel = Cancel +ed.back_graph = < Campaign +ed.test = Test play +ed.tab_general = General +ed.tab_goals = Goals +ed.tab_map = Map +ed.tab_scripts = Scripts +ed.title = Title +ed.briefing = Briefing +ed.gold = Starting gold +ed.time_limit = Time limit +ed.waves = Hero waves +ed.wave_limit = Waves +ed.endless = endless +ed.first_wave = First wave +ed.goals = Goals +ed.goals_hint = The mission is won when all goals are reached (or a script calls Win). +ed.add_goal = + Goal +ed.next_mission = Next mission +ed.branches = Branches +ed.branches_hint = Reach another goal first and the campaign goes on with another mission. +ed.add_branch = + Branch +ed.goal.killall = Defeat all heroes +ed.goal.shards = Take the shards +ed.goal.heart = Destroy the gate +ed.goal.towers = Raze the knight towers +ed.goal.rivals = Defeat the rivals +ed.goal.loot = Loot gold +ed.goal.survive = Survive waves +ed.goal.springs = Claim mana springs +ed.goal.freed = Free creatures +ed.map_random = Random map +ed.map_fixed = Painted map +ed.map_is_random = Random map +ed.map_is_fixed = Painted map +ed.map_random_hint = Rolled anew for every run of the campaign, the same for the whole run. +ed.map_fixed_hint = Painted in the map editor; it never changes. +ed.map_width = Width +ed.map_height = Height +ed.map_seed = Seed +ed.rivers = Rivers +ed.lava_rivers = Lava rivers +ed.forts = Knight fortresses +ed.rivals = Rival masters +ed.difficulty = Difficulty +ed.portals = Portals +ed.edit_map = Edit the map +ed.make_random = Make it a random map +ed.paint_map = Generate and paint the map +ed.zones_list = Zones ({0}): {1} +ed.special_scripts = Special scripts +ed.script_start = Start (once) +ed.script_victory = Victory check (every second) +ed.script_defeat = Defeat check (every second) +ed.script_tick = Clock (every second) +ed.blocks_count = {0} blocks +ed.blocks_problems = {0} blocks, {1} problems +ed.edit_blocks = Edit +ed.triggers = Triggers +ed.triggers_hint = A trigger runs its blocks when its condition is met. Zones are drawn in the map editor. +ed.add_trigger = + Trigger +ed.once = once +ed.enabled = active +ed.edit_trigger_blocks = Blocks ({0}) +ed.pick_zone = - zone - +ed.pick_var = - variable - +ed.pick_trigger = - trigger - +ed.any_kind = any kind +ed.any_room = any room +ed.any_spell = any spell +ed.every = every +ed.after = after +ed.repeat = repeat +ed.unit_name = Name +ed.towers_dungeon = the dungeon's towers +ed.towers_knights = the knights' towers +ed.no_settings = (nothing to set) +ed.side.player = Player +ed.side.heroes = Heroes +ed.side.mossgrim = Mossgrim +ed.side.nightwell = Lady Nightwell +ed.side.rival3 = Rival 3 +ed.side.any = anyone +ed.trig.zone_enter = Zone entered +ed.trig.zone_leave = Zone left +ed.trig.line_cross = Line crossed +ed.trig.zone_claimed = Zone claimed +ed.trig.zone_dug = Dug in the zone +ed.trig.zone_count = Units in the zone +ed.trig.timer = Timer +ed.trig.unit_dies = A unit falls +ed.trig.tower_falls = A tower falls +ed.trig.value = A value is reached +ed.trig.room_built = Room built +ed.trig.spell_cast = Spell cast +ed.trig.slap = Creature slapped +ed.trig.research = Research done +ed.value.gold = Gold +ed.value.mana = Mana +ed.value.creatures = Creatures +ed.value.heroes = Heroes +ed.value.var = Variable +ed.map_done = Done +ed.cat.terrain = Ground +ed.cat.rooms = Rooms +ed.cat.places = Places +ed.cat.units = Units +ed.cat.towers = Towers +ed.cat.traps = Traps +ed.cat.forts = Forts +ed.cat.zones = Zones +ed.cat.erase = Erase +ed.terrain.floor = Floor +ed.terrain.earth = Earth +ed.terrain.reinforced = Reinforced wall +ed.terrain.rock = Rock +ed.terrain.water = Water +ed.terrain.lava = Lava +ed.terrain.bridge = Bridge +ed.terrain.lavabridge = Lava bridge +ed.terrain.gold = Gold vein +ed.terrain.silver = Silver vein +ed.terrain.ruby = Ruby vein +ed.terrain.diamond = Diamond vein +ed.terrain.steel = Steel wall +ed.place.core = Soul core +ed.place.portal = Portal +ed.place.gate = Hero gate +ed.place.spring = Mana spring +ed.place.shard = Core shard +ed.place.chest = Rune chest +ed.place.chained = Chained creature +ed.tower.arrow = Arrow tower +ed.tower.cannon = Cannon tower +ed.tower.magic = Magic tower +ed.tower.rune = Rune forge tower +ed.trap.spikes = Spike pit +ed.trap.poison = Poison cloud +ed.trap.fire = Fire jet +ed.trap.roller = Stone roller +ed.trap.alarm = Alarm +ed.trap.mine = Rune mine +ed.trap.frost = Frost glow +ed.trap.wooddoor = Wooden door +ed.trap.irondoor = Iron door +ed.trap.magicdoor = Magic door +ed.fort = Knight fortress +ed.fort_shard = Fortress with shard +ed.owner = Owner +ed.side = Side +ed.unit_kind = Kind +ed.level = Level +ed.zone_shape = Shape of a new zone +ed.zone_new_name = Name of a new zone +ed.zones = Zones +ed.shape.rect = Rectangle +ed.shape.circle = Circle +ed.shape.tiles = Tiles +ed.shape.line = Line +ed.brush = Brush +ed.rect = Rectangle +ed.fill = Fill +ed.mirror0 = No mirror +ed.mirror1 = Mirror left/right +ed.mirror2 = Mirror top/bottom +ed.mirror3 = Mirror both +ed.undo = Undo +ed.redo = Redo +ed.map_info = {0}x{1}, {2} units, {3} towers +ed.map_hint = Left: paint/place, right: move view, middle: turn, wheel: zoom, WASD/QE, Ctrl+Z/Y +ed.back = < Back +ed.delete = Delete +ed.paste = Paste +ed.copy = Copy +ed.search = Search... +ed.no_problems = No problems +ed.problems = {0} problems (red) +ed.blocks_hint = Drag blocks into the script. Move them by their grip, drop them here to delete them. +ed.empty_script = Empty: drag blocks from the left in here. +ed.drop_here = drop blocks here +ed.trigger_script = Trigger: {0} +ed.saved_server = Saved online. +ed.loaded = Campaign loaded. +ed.no_server = No connection to the server. +ed.busy = The server is still busy, try again in a moment. +ed.save_first = Save the campaign online first. +ed.debug_title = Scripts (test) +blk.cat.say = Speak +blk.cat.units = Units +blk.cat.world = World +blk.cat.logic = Logic +blk.cat.vars = Variables +blk.cat.mission = Mission +blk.message = Message +blk.objective = Objective +blk.hint = Crookfinger says +blk.spawn = Spawn +blk.wave = Hero wave +blk.kill = Remove +blk.terrain = Change ground +blk.reveal = Reveal +blk.camera = Show +blk.gold = Gold + +blk.mana = Mana + +blk.if = If +blk.repeat = Repeat +blk.trigger = Switch trigger +blk.setvar = Set +blk.addvar = Change +blk.win = Win the mission +blk.lose = Lose the mission +blk.else = else +blk.help.message = Shows a message. {name} in the text shows the value of a variable. +blk.help.objective = Sets the objective shown to the player. +blk.help.hint = Crookfinger comments on something, with a sound. +blk.help.spawn = Units appear: how many, which kind, level, side and where (core, where the trigger unit is, or in a zone). With a name, a "unit falls" trigger can wait for exactly this unit. +blk.help.wave = Sends hero waves at once. +blk.help.kill = Removes all units of a side in a zone. +blk.help.terrain = Turns the ground of a zone into floor, earth, rock, water, lava or gold. +blk.help.reveal = Lifts the fog of war over a zone. +blk.help.camera = Moves the camera to a zone. +blk.help.gold = Gives (or takes) gold. +blk.help.mana = Gives (or takes) mana. +blk.help.if = Runs the blocks inside only if the condition holds, otherwise the ones under "else". +blk.help.repeat = Runs the blocks inside several times. +blk.help.trigger = Switches a trigger on (it may fire again) or off. +blk.help.setvar = Sets a variable (a number the mission remembers). +blk.help.addvar = Adds a number to a variable (negative: subtracts). +blk.help.win = The mission is won. +blk.help.lose = The mission is lost. +blk.v.num = Number +blk.v.var = Variable +blk.v.gold = Gold +blk.v.mana = Mana +blk.v.time = Seconds played +blk.v.creatures = Count +blk.v.inzone = Count +blk.v.random = Random +blk.v.math = Calculation +blk.v.cmp = Comparison +blk.v.and = and +blk.v.or = or +blk.v.not = not +blk.v.bool = Yes/no +blk.v.eventkind = Trigger unit is a +blk.w_at = at +blk.w_by = by +blk.w_in = in +blk.w_level = level +blk.w_name = name +blk.w_times = times +blk.w_to = to +blk.and = and +blk.or = or +blk.not = not +blk.yes = yes +blk.no = no +blk.on = on +blk.off = off +blk.place_core = the core +blk.place_event = the trigger unit +blk.place_zone = zone {0} +demo.name = Demo: every trigger +demo.branch = Rich enough for a detour +demo.m1.title = The Trial of Triggers +demo.m1.story = A small knight outpost east of your heart, a vault, a den, a tunnel and a border line.\n\nEvery trigger of the editor fires somewhere here: walk into the den, dig the tunnel, claim the vault, slap a creature, cast a spell, build a lair, research something and break the outpost. +demo.start = Welcome to the trial, master. Every move of yours is watched by a trigger. Mostly by me, too. +demo.objective = Raze the knight towers - or defeat eight knights. +demo.win = Eight knights down. The trial is passed! +demo.lose = Ten minutes without a single creature? That is no dungeon, that is a cellar. +demo.tick = Two minutes of play. The clock trigger has counted four times. +demo.den = The den is open. A sleepy ogre crawls out to see who disturbs it. +demo.den_leave = Someone left the den again. The ogre stays. +demo.border = Knights cross the border line! +demo.vault = The vault is yours: 500 gold rattle into the treasury. +demo.tunnel = The tunnel is dug. Something below it sighs with relief. +demo.crowd = Four or more knights gather on the field! +demo.wave = Reinforcements! Two archers march onto the field. +demo.kills = Knights defeated: {kills} +demo.captain = The captain has fallen. His mana is yours. +demo.tower = A knight tower falls - and drops some gold. +demo.rich = 8000 gold. You may now call yourself well-off. +demo.lair = A lair! Creatures need beds, even evil ones. +demo.slap = Ouch. The slap trigger noticed. So did the creature. +demo.research = Research done. A little gold for the scholars. +demo.m2.title = Rolled Anew +demo.m2.story = This map is rolled anew for every run of the campaign. Defeat every hero. +demo.m2.start = A random map. Even I have never seen it before. +demo.m2.wave = Two minutes are over: the heroes come. diff --git a/src/editor/mod.rs b/src/editor/mod.rs index 5ae2784..d60d7ac 100644 --- a/src/editor/mod.rs +++ b/src/editor/mod.rs @@ -43,6 +43,7 @@ pub enum EdAction { None, Close, Test(usize), + Play, // the whole campaign from its first mission, following the branches SaveFile, OpenFile, ServerSave, @@ -280,31 +281,32 @@ impl Editor { self.doc.language = langs[k].0.clone(); } // buttons on the right - let labels = [tr("ed.new"), tr("ed.open"), tr("ed.save_file"), tr("ed.save_server"), tr("ed.close")]; + let labels = [tr("ed.play"), tr("ed.new"), tr("ed.open"), tr("ed.save_file"), tr("ed.save_server"), tr("ed.close")]; let bw = labels.iter().map(|l| gfx.measure(l, S())).fold(0.0, f32::max) + CW() * 2.0; let mut bx = w - CW() - bw; for k in (0..labels.len()).rev() { let r = Rect::new(bx, CH() * 0.6, bw, CH() * 1.7); if im.button(gfx, r, &labels[k]) { match k { - 0 => { + 0 => act = EdAction::Play, + 1 => { self.doc = CampaignDoc { name: tr("ed.new_campaign"), language: crate::i18n::language(), ..CampaignDoc::default() }; self.doc.add_mission(); self.server_id = None; } - 1 => { + 2 => { self.open_dialog = true; act = EdAction::ServerRefresh; } - 2 => act = EdAction::SaveFile, - 3 => act = EdAction::ServerSave, + 3 => act = EdAction::SaveFile, + 4 => act = EdAction::ServerSave, _ => act = EdAction::Close, } } bx -= bw + CW() * 0.5; } let mut publ = self.published; - if self.server_id.is_some() && im.toggle(gfx, w - CW() * 2.0 - bw * 3.0, CH() * 3.1, &tr("ed.published"), &mut publ) { + if self.server_id.is_some() && im.toggle(gfx, w - CW() * 2.0 - bw * 3.5, CH() * 3.1, &tr("ed.published"), &mut publ) { self.published = publ; act = EdAction::Publish(publ); } @@ -748,7 +750,7 @@ impl Editor { (tr("ed.lava_rivers"), m.gen.lava.to_string(), 4), (tr("ed.forts"), m.gen.forts.to_string(), 5), (tr("ed.rivals"), m.gen.rivals.to_string(), 6), - (tr("ed.difficulty"), tr(["diff.easy", "diff.medium", "diff.hard"][m.gen.difficulty.min(2) as usize]), 7), + (tr("ed.difficulty"), tr(["diff.0", "diff.1", "diff.2"][m.gen.difficulty.min(2) as usize]), 7), (tr("ed.portals"), m.portals.to_string(), 8), ]; if fixed { diff --git a/src/editor/templates.rs b/src/editor/templates.rs index 261fe45..bf5336c 100644 --- a/src/editor/templates.rs +++ b/src/editor/templates.rs @@ -88,7 +88,7 @@ pub fn demo_campaign() -> CampaignDoc { let mut m = MissionDoc { key: "demo1".into(), title: tr("demo.m1.title"), - story: tr("demo.m1.story"), + story: tr("demo.m1.story").replace("\\n", "\n"), size: (80, 64), seed: 4242, gold: 4000.0, @@ -181,7 +181,7 @@ pub fn demo_campaign() -> CampaignDoc { let mut m2 = MissionDoc { key: "demo2".into(), title: tr("demo.m2.title"), - story: tr("demo.m2.story"), + story: tr("demo.m2.story").replace("\\n", "\n"), size: (72, 64), seed: 77, gold: 3000.0, diff --git a/src/main.rs b/src/main.rs index 074ddb6..6bfd0e6 100644 --- a/src/main.rs +++ b/src/main.rs @@ -196,6 +196,12 @@ fn sanitize_campaign_key(value: &str) -> String { value.trim().chars().filter(|c| c.is_ascii_alphanumeric() || *c == '_' || *c == '-').take(48).collect() } +// the briefing of an editor mission (None: it has no text) +fn briefing_of(doc: &campaign_doc::CampaignDoc, i: usize) -> Option<(String, String)> { + let m = doc.missions.get(i)?; + (!m.story.trim().is_empty()).then(|| (m.title.clone(), m.story.clone())) +} + // a mission of an editor campaign (its map is painted, or rolled from the run's seed) fn custom_mission_game(doc: &campaign_doc::CampaignDoc, i: usize, run_seed: u32) -> (Game, Cam) { let g = campaign::start_editor_mission(&doc.missions[i], i, run_seed); @@ -438,9 +444,11 @@ struct App { frame_hist: [f32; FRAME_HIST], frame_hist_pos: usize, editor: Option, // the campaign editor (takes the whole frame while open) - editor_testing: bool, // a mission of the editor is being played + editor_testing: bool, // the editor waits while one of its missions is played + editor_test_only: bool, // only that mission (test play); else the campaign goes on editor_public_pending: bool, // the public campaign list still has to be fetched editor_loading: Option, // server id of the own campaign being loaded + pending_briefing: Option<(String, String)>, // an editor mission began: show its briefing custom_campaign: Option, custom_progress: Option, custom_mission_index: Option, @@ -652,7 +660,7 @@ async fn main() { let seed = (miniquad::date::now() * 1000.0) as u32 | 1; // behind the title menu: the endless bridge battle let (game, cam) = Game::new_demo(seed); - let mut app = App { game, cam, settings, seed, status: None, recorded: false, client: MatchClient::new(), ws_id: 0, match_mode: false, coop: false, fps: 0.0, renderer: platform::renderer(), frame_ms: 0.0, frame_hist: [0.0; FRAME_HIST], frame_hist_pos: 0, custom_campaign: None, custom_progress: None, custom_mission_index: None, custom_next_index: None, editor: None, editor_testing: false, editor_public_pending: false, editor_loading: None }; + let mut app = App { game, cam, settings, seed, status: None, recorded: false, client: MatchClient::new(), ws_id: 0, match_mode: false, coop: false, fps: 0.0, renderer: platform::renderer(), frame_ms: 0.0, frame_hist: [0.0; FRAME_HIST], frame_hist_pos: 0, custom_campaign: None, custom_progress: None, custom_mission_index: None, custom_next_index: None, editor: None, editor_testing: false, editor_test_only: false, editor_public_pending: false, editor_loading: None, pending_briefing: None }; menu.progress = platform::storage_get(PROGRESS_KEY).and_then(|s| serde_json::from_str(&s).ok()).unwrap_or_default(); apply_game_settings(&mut app.game, &app.settings); let mut inp = Input { ldown: None, rdown: None, rlast: (0.0, 0.0), rdrag: false, mlast: None, minimap_drag: false, tlast: None, touch_world: false, inspect: None }; @@ -684,6 +692,16 @@ async fn main() { app.editor = Some(editor::Editor::new(menu.campaign_doc.clone())); menu.page = Some(Page::Title); } + // an editor mission began: its briefing first + if let Some(b) = app.pending_briefing.take() { + menu.custom_briefing = b; + menu.page = Some(Page::CustomBriefing); + menu.scroll_lines(-1000); + } + // a played editor campaign is over: back into the editor + if menu.page == Some(Page::CampaignEditor) && app.editor_testing { + back_to_editor(&mut app, &mut menu, &mut r3d); + } if app.editor.is_some() && !app.editor_testing { editor_frame(&mut app, &mut menu, &mut cloud, &mut r3d, &gfx, &mut ui, dt, now); next_frame().await; @@ -1311,6 +1329,7 @@ async fn main() { let _ = mission_doc; let progress = campaign_doc::CampaignProgress::for_doc(&menu.campaign_doc); (app.game, app.cam) = custom_mission_game(&menu.campaign_doc, i, progress.run_seed); +app.pending_briefing = briefing_of(&menu.campaign_doc, i); apply_game_settings(&mut app.game, &app.settings); app.game.coop = false; app.coop = false; @@ -1391,7 +1410,7 @@ async fn main() { app.coop = false; } // end of a mission: next mission (won) or try again (lost) - UiAction::EndNext | UiAction::EndCampaign if app.editor_testing => back_to_editor(&mut app, &mut menu, &mut r3d), + UiAction::EndNext | UiAction::EndCampaign if app.editor_testing && app.editor_test_only => back_to_editor(&mut app, &mut menu, &mut r3d), UiAction::EndNext => { if app.custom_campaign.is_some() { let campaign = app.custom_campaign.clone(); @@ -1401,6 +1420,7 @@ async fn main() { if let Some(campaign) = campaign { if next < campaign.missions.len() { (app.game, app.cam) = custom_mission_game(&campaign, next, progress.as_ref().map_or(1, |p| p.run_seed)); +app.pending_briefing = briefing_of(&campaign, next); apply_game_settings(&mut app.game, &app.settings); app.game.coop = false; app.coop = false; @@ -1424,6 +1444,7 @@ async fn main() { } else if let (Some(campaign), Some(i)) = (campaign, app.custom_mission_index) { if i < campaign.missions.len() { (app.game, app.cam) = custom_mission_game(&campaign, i, progress.as_ref().map_or(1, |p| p.run_seed)); +app.pending_briefing = briefing_of(&campaign, i); apply_game_settings(&mut app.game, &app.settings); app.game.coop = false; app.coop = false; @@ -1841,9 +1862,14 @@ fn editor_frame(app: &mut App, menu: &mut Menu, cloud: &mut Cloud, r3d: &mut R3d menu.page = Some(Page::Title); return; // the editor is gone } - editor::EdAction::Test(i) => { + editor::EdAction::Test(_) | editor::EdAction::Play => { + let (i, only) = match act { + editor::EdAction::Test(i) => (i, true), + _ => (0, false), + }; let progress = campaign_doc::CampaignProgress::for_doc(&ed.doc); (app.game, app.cam) = custom_mission_game(&ed.doc, i, progress.run_seed); +app.pending_briefing = briefing_of(&ed.doc, i); apply_game_settings(&mut app.game, &app.settings); app.game.coop = false; app.coop = false; @@ -1853,6 +1879,7 @@ fn editor_frame(app: &mut App, menu: &mut Menu, cloud: &mut Cloud, r3d: &mut R3d app.custom_mission_index = Some(i); app.custom_next_index = None; app.editor_testing = true; + app.editor_test_only = only; menu.editor_test = true; menu.page = None; ui.log.clear(); diff --git a/src/menu.rs b/src/menu.rs index 3cbd33f..f8c4add 100644 --- a/src/menu.rs +++ b/src/menu.rs @@ -28,6 +28,7 @@ pub enum Page { Campaign, // list of the campaign missions CampaignEditor, // editable shareable .dccamp campaign file Briefing { mission: usize, ingame: bool }, // story and goals of a mission + CustomBriefing, // title and briefing of an editor mission Login, // login to the savegame service (before the title) Account, // password, logout, users (admin) Match { from_title: bool }, // multiplayer lobby and match status @@ -415,6 +416,7 @@ pub struct Menu { pub coop_mission: usize, // co-op campaign mission selected on the Match page pub in_match: bool, // a match is being played; the pause menu changes its shape pub editor_test: bool, // a mission of the editor is being tested: back to the editor + pub custom_briefing: (String, String), // title and text of an editor mission's briefing pub campaign_doc: CampaignDoc, pub campaign_selected: usize, pub campaign_pending: bool, @@ -451,6 +453,7 @@ impl Menu { coop_mission: crate::campaign::coop_mission_indexes().first().copied().unwrap_or(0), in_match: false, editor_test: false, + custom_briefing: (String::new(), String::new()), campaign_doc: CampaignDoc::default(), campaign_selected: 0, campaign_pending: false, @@ -489,6 +492,7 @@ impl Menu { Some(Page::Bestiary { from_title: false, .. }) => Some(Page::Pause), Some(Page::Briefing { ingame: true, .. }) => Some(Page::Pause), Some(Page::Briefing { ingame: false, .. }) => Some(Page::Campaign), + Some(Page::CustomBriefing) => None, Some(Page::Pause) => None, Some(Page::Title) => Some(Page::Title), None => Some(Page::Pause), @@ -865,6 +869,7 @@ impl Menu { Page::Account => self.draw_account(gfx, cloud), Page::Match { .. } => self.draw_match(gfx, cloud), Page::Briefing { mission, ingame } => self.draw_briefing(gfx, mission, ingame), + Page::CustomBriefing => self.draw_custom_briefing(gfx), Page::Bestiary { tab, sel, level, .. } => self.draw_bestiary(gfx, tab, sel, level), Page::KeyCapture { action, .. } => { self.draw_settings(gfx, settings, 5); @@ -1632,10 +1637,37 @@ impl Menu { } // scrolling on pages with lists (mouse wheel) + // the briefing of an editor mission: its text (scrolls) and a button to begin + fn draw_custom_briefing(&mut self, gfx: &Gfx) { + let (sw, sh) = (sw(), sh()); + let cols = ((sw / CW()) as i32 - 2).min(64); + let rows = ((sh / CH()) as i32 - 2).min(28); + let x = grid_center(sw, cols as f32 * CW()).max(0.0); + let y = grid_center_y(sh, rows as f32 * CH()).max(0.0); + let (title, story) = self.custom_briefing.clone(); + gfx.window_titled(x, y, cols, rows, S(), true, FRAME_HI, Color::new(0.05, 0.03, 0.04, 0.97), &title, FRAME_HI); + let maxc = (cols - 6) as usize; + let mut lines: Vec = Vec::new(); + for p in story.split('\n') { + lines.extend(wrap(p, maxc)); + if wrap(p, maxc).is_empty() { + lines.push(String::new()); + } + } + self.rows_visible = (rows as usize).saturating_sub(7).max(1); + self.scroll = self.scroll.min(lines.len().saturating_sub(self.rows_visible)); + for (k, l) in lines.iter().skip(self.scroll).take(self.rows_visible).enumerate() { + gfx.text(l, x + CW() * 3.0, y + CH() * (2.0 + k as f32), S(), TXT); + } + let label = tr("camp.start"); + let w = gfx.measure(&label, S()) + CW() * 6.0; + self.column(gfx, x + (cols as f32 * CW() - w) * 0.5, y + (rows - 4) as f32 * CH(), w, &[(Btn::Resume, label)]); + } + pub fn scrollable(&self) -> bool { matches!( self.page, - Some(Page::Saves { .. }) | Some(Page::Campaign) | Some(Page::CampaignEditor) | Some(Page::Briefing { .. }) | Some(Page::Settings { tab: 5, .. }) | Some(Page::Match { .. }) | Some(Page::Account) + Some(Page::Saves { .. }) | Some(Page::Campaign) | Some(Page::CampaignEditor) | Some(Page::Briefing { .. }) | Some(Page::CustomBriefing) | Some(Page::Settings { tab: 5, .. }) | Some(Page::Match { .. }) | Some(Page::Account) ) }