From c9dafb16064ef3c1ce01258294dcfc5b66affab3 Mon Sep 17 00:00:00 2001 From: Richard Zink Date: Fri, 6 Nov 2026 07:47:21 +0100 Subject: [PATCH] Guard and attack markers (shields and swords, up to eight each, right click removes); the world and the interface are copied opaque so glows keep their brightness Co-Authored-By: Claude Opus 5.5 --- lang/cs.lang | 9 +- lang/da.lang | 9 +- lang/de.lang | 9 +- lang/el.lang | 9 +- lang/en.lang | 9 +- lang/es.lang | 9 +- lang/fi.lang | 9 +- lang/hr.lang | 9 +- lang/hu.lang | 9 +- lang/it.lang | 9 +- lang/nl.lang | 9 +- lang/no.lang | 9 +- lang/pt.lang | 9 +- lang/ro.lang | 9 +- lang/ru.lang | 9 +- lang/sk.lang | 9 +- lang/sr.lang | 9 +- lang/sv.lang | 9 +- src/commands.rs | 6 ++ src/game.rs | 36 +++++++- src/main.rs | 2 +- src/orders.rs | 218 ++++++++++++++++++++++++++++++++++++++++++++++-- src/render3d.rs | 82 +++++++++++++++++- src/ui.rs | 10 +++ 24 files changed, 470 insertions(+), 46 deletions(-) diff --git a/lang/cs.lang b/lang/cs.lang index d912a56..067e760 100644 --- a/lang/cs.lang +++ b/lang/cs.lang @@ -325,6 +325,7 @@ hint.tower_mana = Tvým magickým věžím došla mana. job.repair = Opravuje věž job.mend_bridge = Opravuje most job.raise_wall = Staví zeď +job.march = Pochoduje k meči job.attack_tower = Útočí na věž unit.jailer = Žalářník @@ -1092,15 +1093,19 @@ best.catapult.text = Dřevěné rameno, vědro kamení a rytíř s příliš mno # orders tool.order_attack.label = Útok tool.order_attack.name = Útok! -tool.order_attack.desc = Klikni na nepřítele nebo nepřátelskou věž: všichni bojeschopní tvorové do 18 políček se na něj vrhnou. Pravé kliknutí nástrojem rozkazu zruší rozkazy stráže. +tool.order_attack.desc = Klikni na nepřítele nebo nepřátelskou věž: všechny bytosti připravené k boji v okruhu 18 polí zaútočí. Klikni na volnou zem: značka meče – připravené bytosti se rozdělí mezi všechny meče a bojují tam. Pravý klik na značku ji odstraní. tool.order_guard.label = Hlídat tool.order_guard.name = Hlídat místnost -tool.order_guard.desc = Klikni na místnost: až tři tvorové poblíž tam na dvě minuty zaujmou pozice a zaútočí na vše, co se objeví na dohled. Pravé kliknutí ruší. +tool.order_guard.desc = Klikni na svou zem: značka štítu, kterou drží a brání trojice bytostí. Nejvýše osm štítů; pravý klik na štít ho odstraní. hint.order_attack = Klikni na nepřítele nebo nepřátelskou věž. hint.order_guard = Klikni na jednu ze svých místností (nebo na vlastní podlahu). hint.order_nobody = Žádný tvor nemá na tento rozkaz čas. msg.order_attack = Útočí tvorů: {0}! msg.order_guard = Pozice zaujímá tvorů: {0}. +msg.mark_attack = Meč označuje místo: tvé připravené bytosti tam pochodují. +msg.mark_guard = Štít označuje místo: tři bytosti ho budou držet. +msg.mark_removed = Značka odstraněna – její bytosti jsou opět volné. +hint.marks_full = Osm značek tohoto druhu je maximum. Jednu odstraň pravým klikem. msg.orders_cancelled = Rozkazy stráže zrušeny. # creature overview diff --git a/lang/da.lang b/lang/da.lang index 7cbb252..2236d7e 100644 --- a/lang/da.lang +++ b/lang/da.lang @@ -325,6 +325,7 @@ hint.tower_mana = Dine magitårne er løbet tør for mana. job.repair = Reparerer et tårn job.mend_bridge = Reparerer en bro job.raise_wall = Rejser en mur +job.march = Marcherer mod et sværd job.attack_tower = Angriber et tårn unit.jailer = Fangevogter @@ -1091,15 +1092,19 @@ best.catapult.text = En træarm, en spand sten og en ridder med for meget fritid # orders tool.order_attack.label = Angrib tool.order_attack.name = Angrib! -tool.order_attack.desc = Klik på en fjende eller et fjendtligt tårn: alle kæmpende væsner inden for 18 felter går efter det. Højreklik med et ordreværktøj annullerer vagtordrerne. +tool.order_attack.desc = Klik på en fjende eller et fjendtligt tårn: alle kampklare væsner inden for 18 felter går til angreb. Klik på fri grund: en sværdmarkør – de kampklare væsner fordeler sig på alle sværd og kæmper der. Højreklik på en markør fjerner den. tool.order_guard.label = Bevogt tool.order_guard.name = Bevogt dette rum -tool.order_guard.desc = Klik på et rum: op til tre væsner i nærheden indtager stillinger der i to minutter og angriber alt, der kommer inden for synsvidde. Højreklik annullerer. +tool.order_guard.desc = Klik på din egen grund: en skjoldmarkør, som tre væsner holder og forsvarer. Op til otte skjolde; højreklik på et skjold fjerner det. hint.order_attack = Klik på en fjende eller et fjendtligt tårn. hint.order_guard = Klik på et af dine rum (eller dit eget gulv). hint.order_nobody = Intet væsen er ledigt til den ordre. msg.order_attack = {0} væsner angriber! msg.order_guard = {0} væsner indtager deres stillinger. +msg.mark_attack = Et sværd markerer stedet: dine kampklare væsner marcherer derhen. +msg.mark_guard = Et skjold markerer stedet: tre væsner holder det. +msg.mark_removed = Markør fjernet – dens væsner er fri igen. +hint.marks_full = Otte markører af den slags er det højeste. Fjern en med højreklik. msg.orders_cancelled = Vagtordrer annulleret. # creature overview diff --git a/lang/de.lang b/lang/de.lang index 79fe0b3..857bcbe 100644 --- a/lang/de.lang +++ b/lang/de.lang @@ -324,6 +324,7 @@ hint.tower_mana = Deinen Magietürmen ist das Mana ausgegangen. job.repair = Repariert einen Turm job.mend_bridge = Flickt eine Brücke job.raise_wall = Mauert eine Wand +job.march = Marschiert zu einem Schwert job.attack_tower = Greift einen Turm an unit.jailer = Kerkermeister @@ -1091,15 +1092,19 @@ best.catapult.text = Ein Holzarm, ein Eimer Steine und ein Ritter mit zu viel Ze # orders tool.order_attack.label = Angriff tool.order_attack.name = Angriff! -tool.order_attack.desc = Klicke einen Feind oder einen feindlichen Turm an: Alle kampfbereiten Kreaturen im Umkreis von 18 Feldern gehen darauf los. Rechtsklick mit einem Befehlswerkzeug hebt die Wachbefehle auf. +tool.order_attack.desc = Klicke einen Feind oder einen feindlichen Turm an: Alle kampfbereiten Kreaturen im Umkreis von 18 Feldern gehen darauf los. Klicke freien Boden an: ein Schwert-Marker – die kampfbereiten Kreaturen verteilen sich auf alle Schwerter und kämpfen dort. Rechtsklick auf einen Marker entfernt ihn. tool.order_guard.label = Bewachen tool.order_guard.name = Raum bewachen -tool.order_guard.desc = Klicke einen Raum an: Bis zu drei Kreaturen in der Nähe beziehen dort für zwei Minuten Stellung und greifen alles an, was in Sicht kommt. Rechtsklick hebt auf. +tool.order_guard.desc = Klicke deinen eigenen Boden an: ein Schild-Marker, den drei Kreaturen halten und verteidigen. Bis zu acht Schilde; Rechtsklick auf einen Schild entfernt ihn. hint.order_attack = Klicke einen Feind oder einen feindlichen Turm an. hint.order_guard = Klicke einen deiner Räume (oder deinen Boden) an. hint.order_nobody = Keine Kreatur ist für diesen Befehl frei. msg.order_attack = {0} Kreaturen greifen an! msg.order_guard = {0} Kreaturen beziehen Stellung. +msg.mark_attack = Ein Schwert markiert die Stelle: Deine kampfbereiten Kreaturen marschieren hin. +msg.mark_guard = Ein Schild markiert die Stelle: Drei Kreaturen halten sie. +msg.mark_removed = Marker entfernt – seine Kreaturen sind wieder frei. +hint.marks_full = Mehr als acht Marker dieser Art gehen nicht. Entferne einen mit Rechtsklick. msg.orders_cancelled = Wachbefehle aufgehoben. # creature overview diff --git a/lang/el.lang b/lang/el.lang index b7ce770..386746f 100644 --- a/lang/el.lang +++ b/lang/el.lang @@ -325,6 +325,7 @@ hint.tower_mana = Οι πύργοι μαγείας σου ξέμειναν απ job.repair = Επισκευάζει πύργο job.mend_bridge = Επισκευάζει γέφυρα job.raise_wall = Υψώνει τοίχο +job.march = Βαδίζει προς ένα σπαθί job.attack_tower = Επιτίθεται σε πύργο unit.jailer = Δεσμοφύλακας @@ -1092,15 +1093,19 @@ best.catapult.text = Ένας ξύλινος βραχίονας, ένας κου # orders tool.order_attack.label = Επίθεση tool.order_attack.name = Επίθεση! -tool.order_attack.desc = Κάνε κλικ σε εχθρό ή εχθρικό πύργο: όλα τα μάχιμα πλάσματα σε ακτίνα 18 πλακιδίων ορμούν πάνω του. Το δεξί κλικ με εργαλείο εντολής ακυρώνει τις εντολές φρουράς. +tool.order_attack.desc = Κάνε κλικ σε εχθρό ή εχθρικό πύργο: όλα τα έτοιμα για μάχη πλάσματα σε ακτίνα 18 πλακιδίων επιτίθενται. Κάνε κλικ σε ελεύθερο έδαφος: σημάδι-σπαθί – τα έτοιμα πλάσματα μοιράζονται σε όλα τα σπαθιά και πολεμούν εκεί. Δεξί κλικ σε σημάδι το αφαιρεί. tool.order_guard.label = Φρουρά tool.order_guard.name = Φύλαξε αυτό το δωμάτιο -tool.order_guard.desc = Κάνε κλικ σε δωμάτιο: έως τρία κοντινά πλάσματα παίρνουν θέσεις εκεί για δύο λεπτά και επιτίθενται σε ό,τι εμφανιστεί. Το δεξί κλικ ακυρώνει. +tool.order_guard.desc = Κάνε κλικ στο δικό σου έδαφος: σημάδι-ασπίδα που κρατούν και υπερασπίζονται τρία πλάσματα. Έως οκτώ ασπίδες· δεξί κλικ σε ασπίδα την αφαιρεί. hint.order_attack = Κάνε κλικ σε εχθρό ή εχθρικό πύργο. hint.order_guard = Κάνε κλικ σε ένα από τα δωμάτιά σου (ή στο δικό σου δάπεδο). hint.order_nobody = Κανένα πλάσμα δεν είναι διαθέσιμο για αυτή την εντολή. msg.order_attack = {0} πλάσματα επιτίθενται! msg.order_guard = {0} πλάσματα παίρνουν θέσεις. +msg.mark_attack = Ένα σπαθί σημαδεύει το σημείο: τα έτοιμα πλάσματά σου βαδίζουν εκεί. +msg.mark_guard = Μια ασπίδα σημαδεύει το σημείο: τρία πλάσματα θα το κρατήσουν. +msg.mark_removed = Το σημάδι αφαιρέθηκε – τα πλάσματά του είναι πάλι ελεύθερα. +hint.marks_full = Οκτώ σημάδια αυτού του είδους είναι το μέγιστο. Αφαίρεσε ένα με δεξί κλικ. msg.orders_cancelled = Οι εντολές φρουράς ακυρώθηκαν. # creature overview diff --git a/lang/en.lang b/lang/en.lang index a7225f0..3f6297c 100644 --- a/lang/en.lang +++ b/lang/en.lang @@ -325,6 +325,7 @@ hint.tower_mana = Your magic towers have run out of mana. job.repair = Repairing a tower job.mend_bridge = Mending a bridge job.raise_wall = Raising a wall +job.march = Marching to a sword job.attack_tower = Attacking a tower unit.jailer = Jailer @@ -1092,15 +1093,19 @@ best.catapult.text = A wooden arm, a bucket of rocks and a knight with too much # orders tool.order_attack.label = Attack tool.order_attack.name = Attack! -tool.order_attack.desc = Click an enemy or an enemy tower: all fighting creatures within 18 tiles go for it. Right click with an order tool cancels the guard orders. +tool.order_attack.desc = Click an enemy or an enemy tower: all fighting creatures within 18 tiles go for it. Click open ground: a sword marker - the ready creatures share out over all swords and fight there. Right click on a marker removes it. tool.order_guard.label = Guard tool.order_guard.name = Guard this room -tool.order_guard.desc = Click a room: up to three creatures nearby take positions there for two minutes and attack whatever comes into view. Right click cancels. +tool.order_guard.desc = Click your own ground: a shield marker that three creatures hold and defend. Up to eight shields; right click on a shield removes it. hint.order_attack = Click an enemy or an enemy tower. hint.order_guard = Click one of your rooms (or your own floor). hint.order_nobody = No creature is free for that order. msg.order_attack = {0} creatures attack! msg.order_guard = {0} creatures take their positions. +msg.mark_attack = A sword marks the spot: your ready creatures march there. +msg.mark_guard = A shield marks the spot: three creatures will hold it. +msg.mark_removed = Marker removed - its creatures are free again. +hint.marks_full = Eight markers of that kind are the most. Remove one with a right click. msg.orders_cancelled = Guard orders cancelled. # creature overview diff --git a/lang/es.lang b/lang/es.lang index b3cf572..764901b 100644 --- a/lang/es.lang +++ b/lang/es.lang @@ -325,6 +325,7 @@ hint.tower_mana = Tus torres mágicas se han quedado sin maná. job.repair = Reparando una torre job.mend_bridge = Repara un puente job.raise_wall = Levanta un muro +job.march = Marcha hacia una espada job.attack_tower = Atacando una torre unit.jailer = Carcelero @@ -1084,15 +1085,19 @@ best.catapult.text = Un brazo de madera, un cubo de piedras y un caballero con d # orders tool.order_attack.label = Atacar tool.order_attack.name = ¡Al ataque! -tool.order_attack.desc = Haz clic en un enemigo o una torre enemiga: todas las criaturas combatientes a menos de 18 casillas van a por él. Clic derecho con una herramienta de órdenes cancela las órdenes de guardia. +tool.order_attack.desc = Haz clic en un enemigo o una torre enemiga: todas las criaturas listas para luchar en 18 casillas van a por él. Haz clic en suelo libre: un marcador de espada; las criaturas listas se reparten entre todas las espadas y luchan allí. Clic derecho en un marcador lo quita. tool.order_guard.label = Guardia tool.order_guard.name = Vigilar esta sala -tool.order_guard.desc = Haz clic en una sala: hasta tres criaturas cercanas toman posiciones allí durante dos minutos y atacan todo lo que aparezca a la vista. Clic derecho cancela. +tool.order_guard.desc = Haz clic en tu propio suelo: un marcador de escudo que tres criaturas mantienen y defienden. Hasta ocho escudos; clic derecho en un escudo lo quita. hint.order_attack = Haz clic en un enemigo o una torre enemiga. hint.order_guard = Haz clic en una de tus salas (o en tu propio suelo). hint.order_nobody = Ninguna criatura está libre para esa orden. msg.order_attack = ¡{0} criaturas atacan! msg.order_guard = {0} criaturas toman posiciones. +msg.mark_attack = Una espada marca el sitio: tus criaturas listas marchan allí. +msg.mark_guard = Un escudo marca el sitio: tres criaturas lo mantendrán. +msg.mark_removed = Marcador quitado: sus criaturas vuelven a estar libres. +hint.marks_full = Ocho marcadores de ese tipo es el máximo. Quita uno con clic derecho. msg.orders_cancelled = Órdenes de guardia canceladas. # creature overview diff --git a/lang/fi.lang b/lang/fi.lang index 1b4c85f..58605b7 100644 --- a/lang/fi.lang +++ b/lang/fi.lang @@ -325,6 +325,7 @@ hint.tower_mana = Taikatorneiltasi on loppunut mana. job.repair = Korjaa tornia job.mend_bridge = Korjaa siltaa job.raise_wall = Pystyttää muuria +job.march = Marssii miekalle job.attack_tower = Hyökkää tornia vastaan unit.jailer = Vanginvartija @@ -1092,15 +1093,19 @@ best.catapult.text = Puinen varsi, ämpäri kiviä ja ritari, jolla on liikaa va # orders tool.order_attack.label = Hyökkää tool.order_attack.name = Hyökkää! -tool.order_attack.desc = Klikkaa vihollista tai vihollisen tornia: kaikki taistelevat olennot 18 ruudun säteellä käyvät sen kimppuun. Oikea klikkaus käskytyökalulla peruu vartiokäskyt. +tool.order_attack.desc = Napsauta vihollista tai vihollistornia: kaikki taisteluvalmiit olennot 18 ruudun säteellä hyökkäävät. Napsauta vapaata maata: miekkamerkki – taisteluvalmiit olennot jakautuvat kaikille miekoille ja taistelevat siellä. Oikea napsautus merkkiin poistaa sen. tool.order_guard.label = Vartioi tool.order_guard.name = Vartioi huonetta -tool.order_guard.desc = Klikkaa huonetta: enintään kolme lähellä olevaa olentoa asettuu sinne kahdeksi minuutiksi ja hyökkää kaiken näkyviin tulevan kimppuun. Oikea klikkaus peruu. +tool.order_guard.desc = Napsauta omaa maatasi: kilpimerkki, jota kolme olentoa pitää ja puolustaa. Enintään kahdeksan kilpeä; oikea napsautus kilpeen poistaa sen. hint.order_attack = Klikkaa vihollista tai vihollisen tornia. hint.order_guard = Klikkaa yhtä huoneistasi (tai omaa lattiaasi). hint.order_nobody = Yksikään olento ei ole vapaana tähän käskyyn. msg.order_attack = {0} olentoa hyökkää! msg.order_guard = {0} olentoa asettuu asemiinsa. +msg.mark_attack = Miekka merkitsee paikan: taisteluvalmiit olentosi marssivat sinne. +msg.mark_guard = Kilpi merkitsee paikan: kolme olentoa pitää sen. +msg.mark_removed = Merkki poistettu – sen olennot ovat taas vapaita. +hint.marks_full = Kahdeksan tämän lajin merkkiä on enimmäismäärä. Poista yksi oikealla napsautuksella. msg.orders_cancelled = Vartiokäskyt peruttu. # creature overview diff --git a/lang/hr.lang b/lang/hr.lang index 6b7f068..dfe2b02 100644 --- a/lang/hr.lang +++ b/lang/hr.lang @@ -325,6 +325,7 @@ hint.tower_mana = Tvojim čarobnim tornjevima ponestalo je mane. job.repair = Popravlja toranj job.mend_bridge = Popravlja most job.raise_wall = Podiže zid +job.march = Maršira prema maču job.attack_tower = Napada toranj unit.jailer = Tamničar @@ -1092,15 +1093,19 @@ best.catapult.text = Drvena ruka, kanta kamenja i vitez s previše slobodnog vre # orders tool.order_attack.label = Napad tool.order_attack.name = Napad! -tool.order_attack.desc = Klikni neprijatelja ili neprijateljski toranj: sva borbena stvorenja unutar 18 polja kreću na njega. Desni klik alatom za naredbe poništava naredbe čuvanja. +tool.order_attack.desc = Klikni neprijatelja ili neprijateljski toranj: sva stvorenja spremna za borbu u krugu od 18 polja napadaju. Klikni slobodno tlo: oznaka mača – spremna stvorenja raspoređuju se na sve mačeve i bore se ondje. Desni klik na oznaku uklanja je. tool.order_guard.label = Čuvaj tool.order_guard.name = Čuvaj ovu prostoriju -tool.order_guard.desc = Klikni prostoriju: do tri obližnja stvorenja zauzimaju ondje položaj na dvije minute i napadaju sve što im dođe u vidokrug. Desni klik poništava. +tool.order_guard.desc = Klikni svoje tlo: oznaka štita koju tri stvorenja drže i brane. Do osam štitova; desni klik na štit uklanja ga. hint.order_attack = Klikni neprijatelja ili neprijateljski toranj. hint.order_guard = Klikni jednu od svojih prostorija (ili svoj pod). hint.order_nobody = Nijedno stvorenje nije slobodno za tu naredbu. msg.order_attack = Stvorenja napadaju: {0}! msg.order_guard = Stvorenja zauzimaju položaje: {0}. +msg.mark_attack = Mač označava mjesto: tvoja spremna stvorenja marširaju onamo. +msg.mark_guard = Štit označava mjesto: tri stvorenja će ga držati. +msg.mark_removed = Oznaka uklonjena – njezina su stvorenja opet slobodna. +hint.marks_full = Osam oznaka te vrste je najviše. Ukloni jednu desnim klikom. msg.orders_cancelled = Naredbe čuvanja poništene. # creature overview diff --git a/lang/hu.lang b/lang/hu.lang index 3941b7b..9024cfa 100644 --- a/lang/hu.lang +++ b/lang/hu.lang @@ -325,6 +325,7 @@ hint.tower_mana = A mágiatornyaidból kifogyott a mana. job.repair = Tornyot javít job.mend_bridge = Hidat javít job.raise_wall = Falat emel +job.march = Egy kardhoz vonul job.attack_tower = Tornyot támad unit.jailer = Börtönőr @@ -1092,15 +1093,19 @@ best.catapult.text = Egy fakar, egy vödör kő és egy lovag túl sok szabadid # orders tool.order_attack.label = Támadás tool.order_attack.name = Támadás! -tool.order_attack.desc = Kattints egy ellenségre vagy ellenséges toronyra: 18 mezőn belül minden harcoló lény rárohan. Jobb kattintás egy parancseszközzel visszavonja az őrzési parancsokat. +tool.order_attack.desc = Kattints egy ellenségre vagy ellenséges toronyra: minden harcra kész lény 18 mezőn belül rátámad. Kattints szabad talajra: kardjelölő – a harcra kész lények elosztódnak a kardok között, és ott harcolnak. Jobb kattintás egy jelölőre eltávolítja. tool.order_guard.label = Őrzés tool.order_guard.name = Őrizd ezt a szobát -tool.order_guard.desc = Kattints egy szobára: legfeljebb három közeli lény két percre ott foglal állást, és megtámad mindent, ami a látóterébe kerül. Jobb kattintás: visszavonás. +tool.order_guard.desc = Kattints a saját talajodra: pajzsjelölő, amelyet három lény tart és véd. Legfeljebb nyolc pajzs; jobb kattintás egy pajzsra eltávolítja. hint.order_attack = Kattints egy ellenségre vagy ellenséges toronyra. hint.order_guard = Kattints az egyik szobádra (vagy a saját padlódra). hint.order_nobody = Egy lény sem ér rá erre a parancsra. msg.order_attack = {0} lény támad! msg.order_guard = {0} lény foglalja el az állását. +msg.mark_attack = Egy kard jelöli a helyet: harcra kész lényeid odavonulnak. +msg.mark_guard = Egy pajzs jelöli a helyet: három lény tartja. +msg.mark_removed = Jelölő eltávolítva – lényei újra szabadok. +hint.marks_full = Ebből a fajtából legfeljebb nyolc jelölő lehet. Távolíts el egyet jobb kattintással. msg.orders_cancelled = Őrzési parancsok visszavonva. # creature overview diff --git a/lang/it.lang b/lang/it.lang index 5e0a5b7..65b47fc 100644 --- a/lang/it.lang +++ b/lang/it.lang @@ -325,6 +325,7 @@ hint.tower_mana = Le tue torri magiche sono rimaste senza mana. job.repair = Ripara una torre job.mend_bridge = Ripara un ponte job.raise_wall = Alza un muro +job.march = Marcia verso una spada job.attack_tower = Attacca una torre unit.jailer = Carceriere @@ -1084,15 +1085,19 @@ best.catapult.text = Un braccio di legno, un secchio di sassi e un cavaliere con # orders tool.order_attack.label = Attacca tool.order_attack.name = All'attacco! -tool.order_attack.desc = Clicca un nemico o una torre nemica: tutte le creature combattenti entro 18 caselle ci si lanciano. Il clic destro con uno strumento ordine annulla gli ordini di guardia. +tool.order_attack.desc = Clicca un nemico o una torre nemica: tutte le creature pronte entro 18 caselle lo attaccano. Clicca terreno libero: un segnalino spada; le creature pronte si dividono tra tutte le spade e combattono lì. Clic destro su un segnalino lo rimuove. tool.order_guard.label = Guardia tool.order_guard.name = Sorveglia questa stanza -tool.order_guard.desc = Clicca una stanza: fino a tre creature vicine prendono posizione lì per due minuti e attaccano tutto ciò che entra in vista. Il clic destro annulla. +tool.order_guard.desc = Clicca il tuo terreno: un segnalino scudo che tre creature tengono e difendono. Fino a otto scudi; clic destro su uno scudo lo rimuove. hint.order_attack = Clicca un nemico o una torre nemica. hint.order_guard = Clicca una delle tue stanze (o il tuo pavimento). hint.order_nobody = Nessuna creatura è libera per quell'ordine. msg.order_attack = {0} creature all'attacco! msg.order_guard = {0} creature prendono posizione. +msg.mark_attack = Una spada segna il punto: le tue creature pronte marciano lì. +msg.mark_guard = Uno scudo segna il punto: tre creature lo terranno. +msg.mark_removed = Segnalino rimosso: le sue creature sono di nuovo libere. +hint.marks_full = Otto segnalini di quel tipo sono il massimo. Rimuovine uno con il clic destro. msg.orders_cancelled = Ordini di guardia annullati. # creature overview diff --git a/lang/nl.lang b/lang/nl.lang index 69f8ca1..a1b7717 100644 --- a/lang/nl.lang +++ b/lang/nl.lang @@ -325,6 +325,7 @@ hint.tower_mana = Je magietorens hebben geen mana meer. job.repair = Repareert een toren job.mend_bridge = Herstelt een brug job.raise_wall = Trekt een muur op +job.march = Marcheert naar een zwaard job.attack_tower = Valt een toren aan unit.jailer = Cipier @@ -1092,15 +1093,19 @@ best.catapult.text = Een houten arm, een emmer stenen en een ridder met te veel # orders tool.order_attack.label = Aanval tool.order_attack.name = Aanval! -tool.order_attack.desc = Klik op een vijand of een vijandelijke toren: alle strijdende wezens binnen 18 vakken gaan erop af. Rechtsklik met een bevelgereedschap annuleert de wachtbevelen. +tool.order_attack.desc = Klik een vijand of vijandelijke toren aan: alle strijdklare wezens binnen 18 vakken vallen aan. Klik vrije grond aan: een zwaardmarkering – de strijdklare wezens verdelen zich over alle zwaarden en vechten daar. Rechtsklik op een markering verwijdert haar. tool.order_guard.label = Bewaken tool.order_guard.name = Deze kamer bewaken -tool.order_guard.desc = Klik op een kamer: tot drie wezens in de buurt nemen daar twee minuten positie in en vallen alles aan wat in zicht komt. Rechtsklik annuleert. +tool.order_guard.desc = Klik je eigen grond aan: een schildmarkering die drie wezens bezet houden en verdedigen. Tot acht schilden; rechtsklik op een schild verwijdert het. hint.order_attack = Klik op een vijand of een vijandelijke toren. hint.order_guard = Klik op een van je kamers (of je eigen vloer). hint.order_nobody = Geen enkel wezen is vrij voor dat bevel. msg.order_attack = {0} wezens vallen aan! msg.order_guard = {0} wezens nemen hun posities in. +msg.mark_attack = Een zwaard markeert de plek: je strijdklare wezens marcheren erheen. +msg.mark_guard = Een schild markeert de plek: drie wezens houden haar bezet. +msg.mark_removed = Markering verwijderd – haar wezens zijn weer vrij. +hint.marks_full = Acht markeringen van die soort is het maximum. Verwijder er één met rechtsklik. msg.orders_cancelled = Wachtbevelen geannuleerd. # creature overview diff --git a/lang/no.lang b/lang/no.lang index 4755627..6b388d0 100644 --- a/lang/no.lang +++ b/lang/no.lang @@ -325,6 +325,7 @@ hint.tower_mana = Magitårnene dine har gått tom for mana. job.repair = Reparerer et tårn job.mend_bridge = Reparerer en bro job.raise_wall = Reiser en mur +job.march = Marsjerer mot et sverd job.attack_tower = Angriper et tårn unit.jailer = Fangevokter @@ -1091,15 +1092,19 @@ best.catapult.text = En trearm, en bøtte stein og en ridder med for mye fritid. # orders tool.order_attack.label = Angrip tool.order_attack.name = Angrip! -tool.order_attack.desc = Klikk på en fiende eller et fiendtlig tårn: alle kjempende skapninger innen 18 ruter går løs på det. Høyreklikk med et ordreverktøy avbryter vaktordrene. +tool.order_attack.desc = Klikk på en fiende eller et fiendtlig tårn: alle kampklare skapninger innen 18 ruter angriper. Klikk på fri grunn: en sverdmarkør – de kampklare skapningene fordeler seg på alle sverd og kjemper der. Høyreklikk på en markør fjerner den. tool.order_guard.label = Vokt tool.order_guard.name = Vokt dette rommet -tool.order_guard.desc = Klikk på et rom: opptil tre skapninger i nærheten inntar stillinger der i to minutter og angriper alt som kommer innen synsvidde. Høyreklikk avbryter. +tool.order_guard.desc = Klikk på din egen grunn: en skjoldmarkør som tre skapninger holder og forsvarer. Opptil åtte skjold; høyreklikk på et skjold fjerner det. hint.order_attack = Klikk på en fiende eller et fiendtlig tårn. hint.order_guard = Klikk på et av rommene dine (eller ditt eget gulv). hint.order_nobody = Ingen skapning er ledig for den ordren. msg.order_attack = {0} skapninger angriper! msg.order_guard = {0} skapninger inntar stillingene sine. +msg.mark_attack = Et sverd markerer stedet: de kampklare skapningene dine marsjerer dit. +msg.mark_guard = Et skjold markerer stedet: tre skapninger holder det. +msg.mark_removed = Markør fjernet – skapningene er frie igjen. +hint.marks_full = Åtte markører av den sorten er det meste. Fjern en med høyreklikk. msg.orders_cancelled = Vaktordrer avbrutt. # creature overview diff --git a/lang/pt.lang b/lang/pt.lang index d4a1c4f..d004ddf 100644 --- a/lang/pt.lang +++ b/lang/pt.lang @@ -325,6 +325,7 @@ hint.tower_mana = As tuas torres mágicas ficaram sem mana. job.repair = A reparar uma torre job.mend_bridge = Repara uma ponte job.raise_wall = Ergue um muro +job.march = Marcha para uma espada job.attack_tower = A atacar uma torre unit.jailer = Carcereiro @@ -1092,15 +1093,19 @@ best.catapult.text = Um braço de madeira, um balde de pedras e um cavaleiro com # orders tool.order_attack.label = Atacar tool.order_attack.name = Atacar! -tool.order_attack.desc = Clica num inimigo ou numa torre inimiga: todas as criaturas combatentes num raio de 18 casas vão atrás dele. O clique direito com uma ferramenta de ordens cancela as ordens de guarda. +tool.order_attack.desc = Clica num inimigo ou numa torre inimiga: todas as criaturas prontas num raio de 18 casas atacam. Clica em chão livre: um marcador de espada – as criaturas prontas dividem-se por todas as espadas e lutam lá. Clique direito num marcador remove-o. tool.order_guard.label = Guardar tool.order_guard.name = Guardar esta sala -tool.order_guard.desc = Clica numa sala: até três criaturas próximas tomam posição ali durante dois minutos e atacam o que aparecer. O clique direito cancela. +tool.order_guard.desc = Clica no teu próprio chão: um marcador de escudo que três criaturas seguram e defendem. Até oito escudos; clique direito num escudo remove-o. hint.order_attack = Clica num inimigo ou numa torre inimiga. hint.order_guard = Clica numa das tuas salas (ou no teu próprio chão). hint.order_nobody = Nenhuma criatura está livre para essa ordem. msg.order_attack = {0} criaturas atacam! msg.order_guard = {0} criaturas tomam posição. +msg.mark_attack = Uma espada marca o local: as tuas criaturas prontas marcham para lá. +msg.mark_guard = Um escudo marca o local: três criaturas vão segurá-lo. +msg.mark_removed = Marcador removido – as suas criaturas estão livres de novo. +hint.marks_full = Oito marcadores desse tipo é o máximo. Remove um com clique direito. msg.orders_cancelled = Ordens de guarda canceladas. # creature overview diff --git a/lang/ro.lang b/lang/ro.lang index 05885b5..3d3eeca 100644 --- a/lang/ro.lang +++ b/lang/ro.lang @@ -325,6 +325,7 @@ hint.tower_mana = Turnurile tale magice au rămas fără mana. job.repair = Repară un turn job.mend_bridge = Repară un pod job.raise_wall = Ridică un zid +job.march = Mărșăluiește spre o sabie job.attack_tower = Atacă un turn unit.jailer = Temnicer @@ -1092,15 +1093,19 @@ best.catapult.text = Un braț de lemn, o găleată de pietre și un cavaler cu p # orders tool.order_attack.label = Atac tool.order_attack.name = Atacați! -tool.order_attack.desc = Clic pe un dușman sau pe un turn dușman: toate creaturile luptătoare pe o rază de 18 dale se năpustesc asupra lui. Clic dreapta cu o unealtă de ordin anulează ordinele de pază. +tool.order_attack.desc = Dă clic pe un dușman sau pe un turn inamic: toate creaturile gata de luptă pe o rază de 18 pătrate atacă. Dă clic pe teren liber: un marcaj-sabie – creaturile pregătite se împart pe toate săbiile și luptă acolo. Clic dreapta pe un marcaj îl elimină. tool.order_guard.label = Pază tool.order_guard.name = Păziți camera -tool.order_guard.desc = Clic pe o cameră: până la trei creaturi din apropiere iau poziții acolo timp de două minute și atacă orice le intră în raza vizuală. Clic dreapta anulează. +tool.order_guard.desc = Dă clic pe propriul teren: un marcaj-scut pe care trei creaturi îl țin și îl apără. Până la opt scuturi; clic dreapta pe un scut îl elimină. hint.order_attack = Clic pe un dușman sau pe un turn dușman. hint.order_guard = Clic pe una dintre camerele tale (sau pe podeaua ta). hint.order_nobody = Nicio creatură nu e liberă pentru acest ordin. msg.order_attack = {0} creaturi atacă! msg.order_guard = {0} creaturi își iau pozițiile. +msg.mark_attack = O sabie marchează locul: creaturile tale pregătite mărșăluiesc acolo. +msg.mark_guard = Un scut marchează locul: trei creaturi îl vor ține. +msg.mark_removed = Marcaj eliminat – creaturile lui sunt din nou libere. +hint.marks_full = Opt marcaje de acest fel sunt maximum. Elimină unul cu clic dreapta. msg.orders_cancelled = Ordinele de pază au fost anulate. # creature overview diff --git a/lang/ru.lang b/lang/ru.lang index bf5cd22..5baae99 100644 --- a/lang/ru.lang +++ b/lang/ru.lang @@ -325,6 +325,7 @@ hint.tower_mana = У магических башен кончилась мана job.repair = Чинит башню job.mend_bridge = Чинит мост job.raise_wall = Возводит стену +job.march = Идёт к мечу job.attack_tower = Атакует башню unit.jailer = Тюремщик @@ -1084,15 +1085,19 @@ best.catapult.text = Деревянный рычаг, ведро камней и # orders tool.order_attack.label = Атака tool.order_attack.name = В атаку! -tool.order_attack.desc = Кликни врага или вражескую башню: все боевые существа в радиусе 18 клеток идут на неё. Правый клик инструментом приказа отменяет приказы охраны. +tool.order_attack.desc = Щёлкни по врагу или вражеской башне: все готовые к бою существа в радиусе 18 клеток атакуют. Щёлкни по свободной земле: метка-меч — готовые существа делятся между всеми мечами и сражаются там. Правый щелчок по метке убирает её. tool.order_guard.label = Охрана tool.order_guard.name = Охранять комнату -tool.order_guard.desc = Кликни комнату: до трёх существ поблизости занимают там позиции на две минуты и атакуют всё, что увидят. Правый клик - отмена. +tool.order_guard.desc = Щёлкни по своей земле: метка-щит, которую держат и защищают три существа. До восьми щитов; правый щелчок по щиту убирает его. hint.order_attack = Кликни врага или вражескую башню. hint.order_guard = Кликни одну из своих комнат (или свой пол). hint.order_nobody = Нет свободных существ для этого приказа. msg.order_attack = Существ в атаке: {0}! msg.order_guard = Существ на позициях: {0}. +msg.mark_attack = Меч отмечает место: твои готовые существа идут туда. +msg.mark_guard = Щит отмечает место: три существа будут его держать. +msg.mark_removed = Метка убрана — её существа снова свободны. +hint.marks_full = Восемь меток этого вида — максимум. Убери одну правым щелчком. msg.orders_cancelled = Приказы охраны отменены. # creature overview diff --git a/lang/sk.lang b/lang/sk.lang index 88d166d..bf88863 100644 --- a/lang/sk.lang +++ b/lang/sk.lang @@ -325,6 +325,7 @@ hint.tower_mana = Tvojim magickým vežiam došla mana. job.repair = Opravuje vežu job.mend_bridge = Opravuje most job.raise_wall = Stavia múr +job.march = Pochoduje k meču job.attack_tower = Útočí na vežu unit.jailer = Žalárnik @@ -1092,15 +1093,19 @@ best.catapult.text = Drevené rameno, vedro kameňov a rytier s priveľa voľný # orders tool.order_attack.label = Útok tool.order_attack.name = Útok! -tool.order_attack.desc = Klikni na nepriateľa alebo nepriateľskú vežu: všetky bojaschopné tvory do 18 políčok sa naň vrhnú. Pravé kliknutie nástrojom rozkazu zruší rozkazy stráže. +tool.order_attack.desc = Klikni na nepriateľa alebo nepriateľskú vežu: všetky bytosti pripravené na boj v okruhu 18 polí zaútočia. Klikni na voľnú zem: značka meča – pripravené bytosti sa rozdelia medzi všetky meče a bojujú tam. Pravý klik na značku ju odstráni. tool.order_guard.label = Strážiť tool.order_guard.name = Strážiť miestnosť -tool.order_guard.desc = Klikni na miestnosť: až tri tvory nablízku tam na dve minúty zaujmú pozície a zaútočia na všetko, čo sa objaví na dohľad. Pravé kliknutie ruší. +tool.order_guard.desc = Klikni na svoju zem: značka štítu, ktorú drží a bráni trojica bytostí. Najviac osem štítov; pravý klik na štít ho odstráni. hint.order_attack = Klikni na nepriateľa alebo nepriateľskú vežu. hint.order_guard = Klikni na jednu zo svojich miestností (alebo na vlastnú podlahu). hint.order_nobody = Žiadny tvor nemá na tento rozkaz čas. msg.order_attack = Útočiace tvory: {0}! msg.order_guard = Pozície zaujíma tvorov: {0}. +msg.mark_attack = Meč označuje miesto: tvoje pripravené bytosti tam pochodujú. +msg.mark_guard = Štít označuje miesto: tri bytosti ho budú držať. +msg.mark_removed = Značka odstránená – jej bytosti sú opäť voľné. +hint.marks_full = Osem značiek tohto druhu je maximum. Jednu odstráň pravým klikom. msg.orders_cancelled = Rozkazy stráže zrušené. # creature overview diff --git a/lang/sr.lang b/lang/sr.lang index 020c7b7..9f2b9bf 100644 --- a/lang/sr.lang +++ b/lang/sr.lang @@ -325,6 +325,7 @@ hint.tower_mana = Твојим магијским кулама је понест job.repair = Поправља кулу job.mend_bridge = Поправља мост job.raise_wall = Подиже зид +job.march = Маршира ка мачу job.attack_tower = Напада кулу unit.jailer = Тамничар @@ -1092,15 +1093,19 @@ best.catapult.text = Дрвена рука, канта камења и вите # orders tool.order_attack.label = Напад tool.order_attack.name = Напад! -tool.order_attack.desc = Кликни непријатеља или непријатељску кулу: сва борбена створења унутар 18 поља крећу на њега. Десни клик алатком за наредбе поништава наредбе чувања. +tool.order_attack.desc = Кликни непријатеља или непријатељску кулу: сва створења спремна за борбу у кругу од 18 поља нападају. Кликни слободно тло: ознака мача – спремна створења распоређују се на све мачеве и боре се тамо. Десни клик на ознаку уклања је. tool.order_guard.label = Чувај tool.order_guard.name = Чувај ову просторију -tool.order_guard.desc = Кликни просторију: до три оближња створења заузимају тамо положај на два минута и нападају све што им дође у видокруг. Десни клик поништава. +tool.order_guard.desc = Кликни своје тло: ознака штита коју три створења држе и бране. До осам штитова; десни клик на штит уклања га. hint.order_attack = Кликни непријатеља или непријатељску кулу. hint.order_guard = Кликни једну од својих просторија (или свој под). hint.order_nobody = Ниједно створење није слободно за ту наредбу. msg.order_attack = Створења нападају: {0}! msg.order_guard = Створења заузимају положаје: {0}. +msg.mark_attack = Мач означава место: твоја спремна створења марширају тамо. +msg.mark_guard = Штит означава место: три створења ће га држати. +msg.mark_removed = Ознака уклоњена – њена створења су опет слободна. +hint.marks_full = Осам ознака те врсте је највише. Уклони једну десним кликом. msg.orders_cancelled = Наредбе чувања поништене. # creature overview diff --git a/lang/sv.lang b/lang/sv.lang index 264c60d..0a09623 100644 --- a/lang/sv.lang +++ b/lang/sv.lang @@ -325,6 +325,7 @@ hint.tower_mana = Dina magitorn har slut på mana. job.repair = Reparerar ett torn job.mend_bridge = Lagar en bro job.raise_wall = Reser en mur +job.march = Marscherar mot ett svärd job.attack_tower = Anfaller ett torn unit.jailer = Fångvaktare @@ -1092,15 +1093,19 @@ best.catapult.text = En träarm, en hink stenar och en riddare med för mycket f # orders tool.order_attack.label = Anfall tool.order_attack.name = Anfall! -tool.order_attack.desc = Klicka på en fiende eller ett fiendetorn: alla stridande varelser inom 18 rutor går på det. Högerklick med ett orderverktyg avbryter vaktordrarna. +tool.order_attack.desc = Klicka på en fiende eller ett fientligt torn: alla stridsberedda varelser inom 18 rutor anfaller. Klicka på fri mark: en svärdsmarkör – de stridsberedda varelserna fördelar sig på alla svärd och strider där. Högerklick på en markör tar bort den. tool.order_guard.label = Vakta tool.order_guard.name = Vakta detta rum -tool.order_guard.desc = Klicka på ett rum: upp till tre varelser i närheten tar position där i två minuter och anfaller allt som kommer inom synhåll. Högerklick avbryter. +tool.order_guard.desc = Klicka på din egen mark: en sköldmarkör som tre varelser håller och försvarar. Upp till åtta sköldar; högerklick på en sköld tar bort den. hint.order_attack = Klicka på en fiende eller ett fiendetorn. hint.order_guard = Klicka på ett av dina rum (eller ditt eget golv). hint.order_nobody = Ingen varelse är ledig för den ordern. msg.order_attack = {0} varelser anfaller! msg.order_guard = {0} varelser intar sina positioner. +msg.mark_attack = Ett svärd markerar platsen: dina stridsberedda varelser marscherar dit. +msg.mark_guard = En sköld markerar platsen: tre varelser håller den. +msg.mark_removed = Markör borttagen – dess varelser är fria igen. +hint.marks_full = Åtta markörer av det slaget är max. Ta bort en med högerklick. msg.orders_cancelled = Vaktordrar avbrutna. # creature overview diff --git a/src/commands.rs b/src/commands.rs index 0dca862..6cd3012 100644 --- a/src/commands.rs +++ b/src/commands.rs @@ -92,6 +92,10 @@ pub enum Act { y: i32, }, CancelOrders, + Unmark { + x: f32, + y: f32, + }, // right click with an order tool: removes the marker there (or cancels the guard orders) // gold for an allied master of a match (`crate::netplay::GOLD_ALL`: everything) SendGold { to: u8, @@ -172,6 +176,7 @@ impl Game { }, Act::Guard { x, y } => (point(x as f32, y as f32), MARK_GUARD, 10.0), Act::CancelOrders => (point(self.map.core.0 as f32, self.map.core.1 as f32), MARK_CANCEL, 4.0), + Act::Unmark { x, y } => (point(x, y), MARK_CANCEL, 3.0), }; let fixed = matches!(act, Act::Guard { .. } | Act::Attack { .. } | Act::Flag { .. }); let dur = match act { @@ -220,6 +225,7 @@ impl Game { } Act::Guard { x, y } => self.order_guard(x, y), Act::CancelOrders => self.cancel_orders(), + Act::Unmark { x, y } => self.unmark(x, y), Act::SendGold { to, amount } => self.send_gold(to, amount), } } diff --git a/src/game.rs b/src/game.rs index 944d030..9d9694a 100644 --- a/src/game.rs +++ b/src/game.rs @@ -221,6 +221,7 @@ pub enum Job { Spar(usize), // sparring in the arena MendBridge(usize), // imp mends a worn bridge tile RaiseWall(usize), // imp raises a planned wall on own floor + March(usize), // creature follows an attack marker (sword) at this tile } #[derive(Clone, Serialize, Deserialize)] @@ -499,6 +500,8 @@ pub struct MasterState { pub room_level: Vec, pub gear_stock: Vec, pub gear_work: f32, + pub guard_marks: Vec, + pub attack_marks: Vec, pub springs: usize, pub known: Vec, pub researchable: Vec, @@ -624,6 +627,12 @@ pub struct Game { #[serde(default)] pub happenings: Option, // random events (free games and versus), see events.rs #[serde(default)] + pub guard_marks: Vec, // guard markers (shields) of the master being processed (orders.rs) + #[serde(default)] + pub attack_marks: Vec, // attack markers (swords) + #[serde(default)] + pub marks_t: f32, + #[serde(default)] pub stats: crate::stats::GameStats, // statistics of the master at this computer (see stats.rs) #[serde(default)] pub wall_plans: Vec<(usize, u8)>, // walls to raise: tile, master side (see walls.rs) @@ -967,6 +976,9 @@ impl Game { bridgework: Default::default(), trading: Default::default(), happenings: None, + guard_marks: Vec::new(), + attack_marks: Vec::new(), + marks_t: 0.0, stats: Default::default(), wall_plans: Vec::new(), wood: crate::wood::WOOD_START, @@ -1476,6 +1488,8 @@ impl Game { room_level: self.room_level.clone(), gear_stock: self.gear_stock.clone(), gear_work: self.gear_work, + guard_marks: std::mem::take(&mut self.guard_marks), + attack_marks: std::mem::take(&mut self.attack_marks), springs: self.springs, known: std::mem::take(&mut self.known), researchable: std::mem::take(&mut self.researchable), @@ -1509,6 +1523,8 @@ impl Game { self.room_level = m.room_level; self.gear_stock = m.gear_stock; self.gear_work = m.gear_work; + self.guard_marks = m.guard_marks; + self.attack_marks = m.attack_marks; self.springs = m.springs; self.known = m.known; self.researchable = m.researchable; @@ -1715,6 +1731,7 @@ impl Game { } self.update_forge(dt); self.update_research(); + self.update_marks(dt); } fn after_economy(&mut self, dt: f32) { @@ -3776,7 +3793,7 @@ impl Game { // a new creature builds its own bed in the lair soon after its arrival let bed_kind = if flies(e.kind) { Kind::Aerie } else { Kind::Lair }; let beds = if flies(e.kind) { self.counts[7] } else { self.counts[1] }; - if e.bed.is_none() && beds > 0 && !matches!(e.job, Job::Attack(_) | Job::Leave | Job::Rally) && self.rng.f() < 0.15 { + if e.bed.is_none() && beds > 0 && !matches!(e.job, Job::Attack(_) | Job::Leave | Job::Rally | Job::March(_)) && self.rng.f() < 0.15 { let cur = e.tile(); let free = |i: usize| self.map.t[i].kind == bed_kind && self.map.t[i].bed == 0; let found = if flies(e.kind) { self.map.search(cur, self.map.fly_cost(), free, true) } else { self.map.search(cur, self.map.walk_cost(), free, true) }; @@ -3797,7 +3814,7 @@ impl Game { } } if !matches!(e.job, Job::Attack(_) | Job::Leave | Job::Rest(_) | Job::Brawl(_)) { - let radius = if e.job == Job::Rally { 8.5 } else if matches!(e.job, Job::Guard(_)) { 10.0 } else if flies(e.kind) { 9.0 } else { 7.0 }; + let radius = if matches!(e.job, Job::Rally | Job::March(_)) { 8.5 } else if matches!(e.job, Job::Guard(_)) { 10.0 } else if flies(e.kind) { 9.0 } else { 7.0 }; if let Some(id) = self.nearest_enemy(e, radius) { self.unreserve(me); e.job = Job::Attack(id); @@ -3893,6 +3910,15 @@ impl Game { self.rally(e, dt); return; } + if let Job::March(f) = e.job { + if self.attack_marks.contains(&f) { + self.rally_to(e, f, dt); + } else { + e.job = Job::None; + e.path.clear(); + } + return; + } if self.step_path(e, dt) { return; } @@ -4082,6 +4108,11 @@ impl Game { e.path.clear(); return; }; + self.rally_to(e, f, dt); + } + + // march to tile f and fight there (the attack flag, or an attack marker) + pub(crate) fn rally_to(&mut self, e: &mut Ent, f: usize, dt: f32) { if e.hp < e.max_hp * 0.3 || e.energy < 0.1 || e.hunger > 2.2 { // exhausted or badly hurt: retreat e.job = Job::None; @@ -5781,6 +5812,7 @@ pub fn job_name(e: &Ent) -> &'static str { Job::Repair(_) => "job.repair", Job::MendBridge(_) => "job.mend_bridge", Job::RaiseWall(_) => "job.raise_wall", + Job::March(_) => "job.march", Job::Carry(_) => "job.carry", Job::Pray(_) => "job.pray", Job::HealCore => "job.heal_core", diff --git a/src/main.rs b/src/main.rs index 2266792..39beb49 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1373,7 +1373,7 @@ async fn main() { if inp.ldown.is_some() { inp.ldown = None; } else if app.game.hand.is_empty() && matches!(ui.tool, Tool::Order(_)) { - app.game.queue(Act::CancelOrders); + app.game.queue(Act::Unmark { x: wx, y: wy }); } else { app.game.queue(Act::Drop { x: wx, y: wy }); } diff --git a/src/orders.rs b/src/orders.rs index 23d6ccd..49cc2d0 100644 --- a/src/orders.rs +++ b/src/orders.rs @@ -1,11 +1,14 @@ -// Orders from the hand: "attack" sends the fighting creatures nearby at an enemy unit or tower, -// "guard" posts a few creatures in a room. Right click with an order tool cancels the orders. +// Orders: "attack" on an enemy unit or tower sends the fighting creatures nearby at it; on open +// ground it sets an attack marker (a sword) - the ready creatures share out over all swords and +// fight where they stand. "Guard" sets a guard marker (a shield) that three creatures hold. Up to +// eight of each; a right click on a marker removes it (elsewhere: the guards stand down). use crate::game::*; use crate::map::*; const ORDER_RADIUS: f32 = 18.0; const GUARDS: usize = 3; +pub const MAX_MARKS: usize = 8; fn ready(e: &Ent) -> bool { e.alive && !e.held && e.creature() && !matches!(e.job, Job::Leave | Job::Register | Job::MakeBed(_)) && e.hp > e.max_hp * 0.25 @@ -26,8 +29,8 @@ impl Game { (Some(j), _) => (self.ents[j].x, self.ents[j].y), (None, Some((_, p))) => p, _ => { - self.hint(31, "hint.order_attack"); - self.sfx_ui(Sfx::Error); + // open ground: an attack marker + self.place_mark(wx.floor() as i32, wy.floor() as i32, true); return; } }; @@ -59,8 +62,168 @@ impl Game { self.msg("msg.order_attack", vec![n.to_string()]); } - // guard: a few creatures take positions in the room at (x, y) + // guard: a guard marker at (x, y) (three creatures hold it, see update_marks) pub fn order_guard(&mut self, x: i32, y: i32) { + self.place_mark(x, y, false); + } + + // a sword (attack) or shield (guard) marker on tile (x, y) + pub fn place_mark(&mut self, x: i32, y: i32, attack: bool) { + if !inb(x, y) { + return; + } + let i = idx(x, y); + let t = self.map.t[i]; + // a guard marker stands on own ground, an attack marker anywhere one can walk + let ok = if attack { t.walkable() || t.bridge() } else { t.owner == self.me && t.walkable() }; + if !ok { + self.hint(31, if attack { "hint.order_attack" } else { "hint.order_guard" }); + self.sfx_ui(Sfx::Error); + return; + } + let marks = if attack { &self.attack_marks } else { &self.guard_marks }; + if marks.iter().any(|&m| { + let (mx, my) = xy(m); + (mx - x).abs() <= 1 && (my - y).abs() <= 1 + }) { + return; // there is one already + } + if marks.len() >= MAX_MARKS { + self.hint(31, "hint.marks_full"); + self.sfx_ui(Sfx::Error); + return; + } + if attack { + self.attack_marks.push(i); + } else { + self.guard_marks.push(i); + } + self.marks_t = 0.0; // creatures are sent at once + self.fx(7, x as f32 + 0.5, y as f32 + 0.5, 0.0, 0.0, 0.6, if attack { 0xff3040ff } else { 0xff40d0ff }); + self.sfx_ui(Sfx::FlagSet); + self.msg(if attack { "msg.mark_attack" } else { "msg.mark_guard" }, vec![]); + } + + // a right click with an order tool: the marker there goes (its creatures are free again); + // without a marker there the guards stand down + pub fn unmark(&mut self, wx: f32, wy: f32) { + let near = |m: &usize| { + let (mx, my) = xy(*m); + (mx as f32 + 0.5 - wx).powi(2) + (my as f32 + 0.5 - wy).powi(2) < 1.6 * 1.6 + }; + if let Some(k) = self.attack_marks.iter().position(near) { + let m = self.attack_marks.remove(k); + for e in self.ents.iter_mut().filter(|e| e.job == Job::March(m)) { + e.job = Job::None; + e.path.clear(); + } + } else if let Some(k) = self.guard_marks.iter().position(near) { + let m = self.guard_marks.remove(k); + for j in 0..self.ents.len() { + if let Job::Guard(spot) = self.ents[j].job { + if guard_of(m, spot) && self.ents[j].creature() { + let me = self.ents[j].id + 1; + self.unreserve(me); + self.ents[j].job = Job::None; + } + } + } + } else { + self.cancel_orders(); + return; + } + self.msg("msg.mark_removed", vec![]); + self.sfx_ui(Sfx::Click); + } + + // every second: three creatures for every shield, the ready ones shared out over the swords + pub(crate) fn update_marks(&mut self, dt: f32) { + self.marks_t -= dt; + if self.marks_t > 0.0 || (self.guard_marks.is_empty() && self.attack_marks.is_empty()) { + return; + } + self.marks_t = 1.0; + // markers on ground that is gone (dug in, lost) go + let me = self.me; + let map = &self.map; + self.guard_marks.retain(|&m| map.t[m].owner == me && map.t[m].walkable()); + self.attack_marks.retain(|&m| map.t[m].walkable() || map.t[m].bridge()); + // shields + for m in self.guard_marks.clone() { + let have = self.ents.iter().filter(|e| e.alive && e.creature() && matches!(e.job, Job::Guard(s) if guard_of(m, s))).count(); + for _ in have..GUARDS { + if !self.send_guard(m) { + break; + } + } + } + // swords: every ready creature that is free goes to the nearest sword with room + if self.attack_marks.is_empty() { + return; + } + let free: Vec = (0..self.ents.len()) + .filter(|&j| { + let e = &self.ents[j]; + ready(e) && e.hp > e.max_hp * 0.5 && e.energy > 0.15 && matches!(e.job, Job::None | Job::Wander | Job::Train(_) | Job::Study(_) | Job::March(_)) + }) + .collect(); + let cap = free.len().div_ceil(self.attack_marks.len()).max(1); + let mut count: Vec = self.attack_marks.iter().map(|&m| self.ents.iter().filter(|e| e.alive && e.job == Job::March(m)).count()).collect(); + for j in free { + if matches!(self.ents[j].job, Job::March(_)) { + continue; + } + let (ex, ey) = (self.ents[j].x, self.ents[j].y); + let best = (0..self.attack_marks.len()).filter(|&k| count[k] < cap).min_by(|&a, &b| { + let d = |k: usize| { + let (mx, my) = xy(self.attack_marks[k]); + (mx as f32 + 0.5 - ex).powi(2) + (my as f32 + 0.5 - ey).powi(2) + }; + d(a).partial_cmp(&d(b)).unwrap_or(std::cmp::Ordering::Equal) + }); + let Some(k) = best else { break }; + count[k] += 1; + let mark = self.attack_marks[k]; + let me = self.ents[j].id + 1; + self.unreserve(me); + let e = &mut self.ents[j]; + e.job = Job::March(mark); + e.path.clear(); + e.repath = 0.0; + } + } + + // the nearest free creature takes a free spot next to shield m + fn send_guard(&mut self, m: usize) -> bool { + let (x, y) = xy(m); + let spot = (-2..=2).flat_map(|dy| (-2..=2).map(move |dx| (dx, dy))).filter(|&(dx, dy)| inb(x + dx, y + dy)).map(|(dx, dy)| idx(x + dx, y + dy)).filter(|&j| { + let t = &self.map.t[j]; + t.walkable() && t.owner == self.me && t.reserved == 0 && !self.ents.iter().any(|e| e.job == Job::Guard(j)) + }).min_by_key(|&j| { + let (sx, sy) = xy(j); + (sx - x).pow(2) + (sy - y).pow(2) + }); + let Some(spot) = spot else { return false }; + let (fx, fy) = (x as f32 + 0.5, y as f32 + 0.5); + let cand = (0..self.ents.len()).filter(|&j| ready(&self.ents[j]) && !matches!(self.ents[j].job, Job::Attack(_) | Job::Guard(_) | Job::March(_) | Job::Rally)).min_by(|&a, &b| { + let d = |j: usize| (self.ents[j].x - fx).powi(2) + (self.ents[j].y - fy).powi(2); + d(a).partial_cmp(&d(b)).unwrap_or(std::cmp::Ordering::Equal) + }); + let Some(j) = cand else { return false }; + let Some(p) = self.map.creature_path(flies(self.ents[j].kind), self.ents[j].tile(), spot) else { return false }; + let me = self.ents[j].id + 1; + self.unreserve(me); + self.map.t[spot].reserved = me; + let e = &mut self.ents[j]; + e.job = Job::Guard(spot); + e.path = p; + e.timer = 1.0e9; // until the marker goes + true + } + + // the old one-off guard order in a room (kept for scripts and tests) + #[allow(dead_code)] + pub fn order_guard_room(&mut self, x: i32, y: i32) { if !inb(x, y) { return; } @@ -134,6 +297,13 @@ impl Game { } } +// is guard spot s next to shield m? +fn guard_of(m: usize, s: usize) -> bool { + let (mx, my) = xy(m); + let (sx, sy) = xy(s); + (mx - sx).abs() <= 2 && (my - sy).abs() <= 2 +} + #[cfg(test)] mod tests { use super::*; @@ -152,10 +322,46 @@ mod tests { let j = g.find(id).unwrap(); g.ents[j].job = Job::None; } - g.order_guard(cx - 1, cy - 1); + g.order_guard_room(cx - 1, cy - 1); let guards = g.ents.iter().filter(|e| matches!(e.job, Job::Guard(_))).count(); assert_eq!(guards, 3); g.cancel_orders(); assert_eq!(g.ents.iter().filter(|e| matches!(e.job, Job::Guard(_))).count(), 0); } + + #[test] + fn shields_are_held_and_swords_share_the_creatures() { + let mut g = Game::new(14, 64, 64); + let (cx, cy) = g.map.core; + for y in cy - 6..=cy + 8 { + for x in cx - 6..=cx + 10 { + let t = &mut g.map.t[idx(x, y)]; + if t.kind != Kind::Core { + t.kind = Kind::Floor; + t.owner = Owner::Player; + } + } + } + let ids: Vec = (0..6).map(|k| g.spawn(EK::Ogre, false, cx as f32 - 2.5, cy as f32 + 0.5 + k as f32 * 0.3, 2)).collect(); + for &id in &ids { + let j = g.find(id).unwrap(); + g.ents[j].settled = true; + g.ents[j].job = Job::None; + } + g.order_guard(cx + 5, cy + 5); + g.update_marks(1.0); + let guards = g.ents.iter().filter(|e| matches!(e.job, Job::Guard(_))).count(); + assert_eq!(guards, 3, "three hold the shield"); + g.place_mark(cx + 8, cy - 4, true); + g.place_mark(cx - 4, cy - 5, true); + g.update_marks(1.0); + let a = g.ents.iter().filter(|e| e.job == Job::March(g.attack_marks[0])).count(); + let b = g.ents.iter().filter(|e| e.job == Job::March(g.attack_marks[1])).count(); + assert!(a + b == 3 && a.abs_diff(b) <= 1, "{} and {}", a, b); + // a right click on a sword frees its creatures + let (sx, sy) = xy(g.attack_marks[0]); + g.unmark(sx as f32 + 0.5, sy as f32 + 0.5); + assert_eq!(g.attack_marks.len(), 1); + assert!(!g.ents.iter().any(|e| e.job == Job::March(idx(sx, sy)))); + } } diff --git a/src/render3d.rs b/src/render3d.rs index ef226eb..834290c 100644 --- a/src/render3d.rs +++ b/src/render3d.rs @@ -312,6 +312,35 @@ fn lit_material() -> Material { } // Unlit, alpha-blended material for glows, shadows and effects +// the world target onto the virtual screen: fully opaque. Translucent glows (flames, the heart) +// leave the target's alpha below one; blended, the black behind would shine through and dim them. +pub(crate) fn opaque_material() -> Material { + const V: &str = r#"#version 100 +attribute vec3 position; +attribute vec2 texcoord; +attribute vec4 color0; +varying lowp vec2 uv; +varying lowp vec4 color; +uniform mat4 Model; +uniform mat4 Projection; +void main() { + gl_Position = Projection * Model * vec4(position, 1.0); + color = color0 / 255.0; + uv = texcoord; +} +"#; + const F: &str = r#"#version 100 +precision mediump float; +varying lowp vec4 color; +varying lowp vec2 uv; +uniform sampler2D Texture; +void main() { + gl_FragColor = vec4(color.rgb * texture2D(Texture, uv).rgb, 1.0); +} +"#; + load_material(ShaderSource::Glsl { vertex: V, fragment: F }, MaterialParams { pipeline_params: PipelineParams::default(), uniforms: vec![], textures: vec![] }).expect("opaque material") +} + fn glow_material() -> Material { const V: &str = r#"#version 100 attribute vec3 position; @@ -1122,6 +1151,7 @@ pub struct R3d { pub outlines: bool, // tile outlines on/off wall_sub: usize, // wall face subdivisions (detail) pub max_lights: usize, + opaque: Material, // the world target onto the screen without blending pub pixel_rows: u32, // pixel look: the world is rendered this many rows high (0: off) target: Option<(RenderTarget, u32, u32)>, // low resolution render target lava: Vec, // lava light per tile (see lava_field) @@ -1174,7 +1204,7 @@ fn chunk_sig(map: &Map, cx: i32, cz: i32) -> u64 { impl R3d { pub fn new() -> R3d { - R3d { lit: lit_material(), glow: glow_material(), chunks: Vec::new(), ncx: 0, ncz: 0, outlines: true, wall_sub: 3, max_lights: MAX_LIGHTS, pixel_rows: 360, target: None, lava: Vec::new() } + R3d { lit: lit_material(), glow: glow_material(), opaque: opaque_material(), chunks: Vec::new(), ncx: 0, ncz: 0, outlines: true, wall_sub: 3, max_lights: MAX_LIGHTS, pixel_rows: 360, target: None, lava: Vec::new() } } // Changing the wall detail rebuilds all chunks @@ -1505,6 +1535,12 @@ impl R3d { if let Some(f) = g.flag { draw_flag(f, t); } + for &m in &g.guard_marks { + draw_shield_mark(m, t); + } + for &m in &g.attack_marks { + draw_sword_mark(m, t); + } draw_marks(g, t); draw_overlay(&g.map, ov, self.wall_sub); draw_entry_warnings(g, t, self.wall_sub); @@ -1523,7 +1559,9 @@ impl R3d { crate::ui::begin_ui(); if let (Some((_, _, k)), Some((rt, tw, th))) = (pix, self.target.as_ref()) { // the world onto the virtual screen, every pixel k x k (scaled to the device at the end) + gl_use_material(&self.opaque); draw_texture_ex(&rt.texture, 0.0, 0.0, WHITE, DrawTextureParams { dest_size: Some(vec2(*tw as f32 * k, *th as f32 * k)), flip_y: true, ..Default::default() }); + gl_use_default_material(); } // ---- 2D overlay in device pixels: health bars, sleep marks, levels, gate HP @@ -2569,6 +2607,48 @@ fn draw_flag(f: usize, t: f32) { draw_mesh(&Mesh { vertices: v, indices: vec![0, 1, 2], texture: None }); } +// a flat polygon in a vertical plane (x along `right`, y up) around `base` +fn draw_plate(base: Vec3, right: Vec3, pts: &[(f32, f32)], c: [u8; 4]) { + let v: Vec = pts.iter().map(|&(x, y)| Vertex { position: base + right * x + Vec3::Y * y, uv: Vec2::ZERO, color: c, normal: Vec4::Y }).collect(); + let mut idx = Vec::new(); + for k in 1..pts.len() as u16 - 1 { + idx.extend([0, k, k + 1]); + } + draw_mesh(&Mesh { vertices: v, indices: idx, texture: None }); +} + +// guard marker: a shield on a short pole, gently bobbing +fn draw_shield_mark(m: usize, t: f32) { + let (x, z) = xy(m); + let base = vec3(x as f32 + 0.5, 0.0, z as f32 + 0.5); + draw_cube(base + vec3(0.0, 0.45, 0.0), vec3(0.05, 0.9, 0.05), None, Color::from_rgba(120, 90, 60, 255)); + let bob = (t * 2.0 + m as f32).sin() * 0.03; + let c = base + vec3(0.0, 0.95 + bob, 0.0); + let right = vec3(0.7071, 0.0, -0.7071); + let outline: [(f32, f32); 7] = [(-0.26, 0.24), (0.26, 0.24), (0.26, 0.02), (0.18, -0.16), (0.0, -0.3), (-0.18, -0.16), (-0.26, 0.02)]; + // the rim, the face and a cross (both sides, so it shows from every angle) + for (r, off) in [(right, 0.0f32), (-right, 0.01)] { + let at = c + vec3(0.0, 0.0, 0.0) + right.cross(Vec3::Y) * off; + draw_plate(at, r, &outline, [230, 190, 70, 255]); + let inner: Vec<(f32, f32)> = outline.iter().map(|&(a, b)| (a * 0.8, b * 0.8 + 0.01)).collect(); + draw_plate(at + right.cross(Vec3::Y) * 0.004, r, &inner, [50, 80, 170, 255]); + draw_plate(at + right.cross(Vec3::Y) * 0.008, r, &[(-0.03, 0.18), (0.03, 0.18), (0.03, -0.2), (-0.03, -0.2)], [230, 190, 70, 255]); + draw_plate(at + right.cross(Vec3::Y) * 0.008, r, &[(-0.16, 0.06), (0.16, 0.06), (0.16, 0.0), (-0.16, 0.0)], [230, 190, 70, 255]); + } +} + +// attack marker: a sword stuck in the ground, its blade glinting +fn draw_sword_mark(m: usize, t: f32) { + let (x, z) = xy(m); + let base = vec3(x as f32 + 0.5, 0.0, z as f32 + 0.5); + let glint = 200 + ((t * 3.0 + m as f32).sin() * 40.0) as i32; + let blade = Color::from_rgba(glint.clamp(0, 255) as u8, glint.clamp(0, 255) as u8, (glint + 15).clamp(0, 255) as u8, 255); + draw_cube(base + vec3(0.0, 0.4, 0.0), vec3(0.07, 0.8, 0.02), None, blade); + draw_cube(base + vec3(0.0, 0.82, 0.0), vec3(0.34, 0.05, 0.06), None, Color::from_rgba(150, 120, 50, 255)); + draw_cube(base + vec3(0.0, 0.98, 0.0), vec3(0.05, 0.26, 0.05), None, Color::from_rgba(170, 30, 30, 255)); + draw_cube(base + vec3(0.0, 1.13, 0.0), vec3(0.08, 0.06, 0.08), None, Color::from_rgba(230, 190, 70, 255)); +} + fn mark_color(kind: u8) -> Color { match kind { MARK_DIG => Color::new(0.45, 0.95, 0.55, 1.0), diff --git a/src/ui.rs b/src/ui.rs index 55645c8..1622057 100644 --- a/src/ui.rs +++ b/src/ui.rs @@ -24,6 +24,7 @@ thread_local! { static UI_TARGET: std::cell::RefCell> = std::cell::RefCell::new(None); static UP_TARGET: std::cell::RefCell> = std::cell::RefCell::new(None); static FRAME_STARTED: std::cell::Cell = std::cell::Cell::new(false); + static OPAQUE: std::cell::RefCell> = std::cell::RefCell::new(None); } // the rows of the virtual screen for this frame (0: device pixels). Set once per frame before @@ -188,7 +189,16 @@ pub fn end_frame() { c.render_target = Some(up.clone()); set_camera(&c); clear_background(Color::new(0.0, 0.0, 0.0, 1.0)); + // opaque: translucent parts must not let the black below shine through + OPAQUE.with(|m| { + let mut m = m.borrow_mut(); + if m.is_none() { + *m = Some(crate::render3d::opaque_material()); + } + gl_use_material(m.as_ref().unwrap()); + }); draw_texture_ex(&rt.texture, 0.0, 0.0, WHITE, DrawTextureParams { dest_size: Some(vec2(uw as f32, uh as f32)), flip_y: true, ..Default::default() }); + gl_use_default_material(); set_default_camera(); draw_texture_ex(&up.texture, 0.0, 0.0, WHITE, DrawTextureParams { dest_size: Some(vec2(screen_width(), screen_height())), flip_y: true, ..Default::default() }); }