Statistics with a chart at the end of a game, 64 achievements kept locally and on the server
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
193b199875
commit
9931ca9b54
37 files changed
+1833
-4
No files matched your search
@@ -96,6 +96,10 @@ of your forge to break a fortress in the forest.
|
||||
<br><i>Left: the world up there. Too bright. Right: a drawbridge up, a drawbridge down, and lava that thinks it is a floor.</i>
|
||||
</p>
|
||||
|
||||
**🏆 Champions and trophies.** Every fifth wave a champion of the Order leads the knights - Sir Aldric, Dame
|
||||
Seraphine, Brother Halvard or Captain Rothgar, each with a trick of his own and loot for whoever brings him down.
|
||||
After every game a chart shows how it went, and 64 achievements wait - some of them secret, all of them petty.
|
||||
|
||||
**👑 Other masters want your realm, too.** Mossgrim the Mouldy (green, smells like a wet cellar) and Lady Nightwell
|
||||
(blue, writes threatening letters on scented paper) dig their own dungeons in free play and in the campaign.
|
||||
And the humans attack *everybody*. Let them bite each other first.
|
||||
|
||||
@@ -67,6 +67,14 @@ camera's surroundings lie above ground (4 s crossfade).
|
||||
|
||||
## 4. Additions
|
||||
|
||||
Status: creature abilities (personality.rs: an ability per creature at level 3-7, used by itself) and
|
||||
creature relationships (likes, hates, brawls) existed already. Done in this block so far:
|
||||
bosses (`src/bosses.rs`: four champions of the Order lead every fifth wave - Sir Aldric's shield wall,
|
||||
Dame Seraphine's radiant burst, Brother Halvard's call to arms, Captain Rothgar's charge - with a health bar
|
||||
and loot), statistics (`src/stats.rs`: counters and a chart at the end of a game) and achievements
|
||||
(`src/achievements.rs`: 64 of them, kept in the browser and merged with `/api/profile` on the server after a
|
||||
login; the menu page *Achievements*). Room levels 2 and 3 will be researched in the library (per room kind).
|
||||
|
||||
1. **Random events** – earthquake (tunnels cave in), lava eruption, flood, gold vein found. Rare
|
||||
(every 8–12 minutes), free play and multiplayer only, can be switched off in the menu; in
|
||||
campaigns only through new script blocks.
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 769 KiB |
@@ -1552,3 +1552,66 @@ coop.m20.goal = Porazte Mossgrima a všechny jeho tvory.
|
||||
coop.m20.boss = Mossgrim už není. Mech zase doroste. On ne.
|
||||
coop.m20.t1 = Ten drak na východě je Mossgrim. Ano, nechal se trochu jít.
|
||||
coop.m20.t2 = Každý poražený tvor je o jednoho méně, kdo bude tenhle příběh vyprávět špatně.
|
||||
|
||||
# achievements and statistics
|
||||
menu.achievements = Úspěchy
|
||||
ach.count = Odemčeno {0} z {1} úspěchů · {2} minut hraní
|
||||
ach.hidden = Tajemství. Křivoprst nic neprozradí.
|
||||
msg.achievement = Úspěch odemčen: {0}!
|
||||
end.stats2 = Hrdinové {0} · Ztracení tvorové {1} · Vykopaná pole {2} · Zlato {3} · Dřevo {4}
|
||||
ach.heroes.name = Postrach hrdinů {0}
|
||||
ach.heroes.desc = Poraz {0} hrdinů.
|
||||
ach.bosses.name = Lamač šampionů {0}
|
||||
ach.bosses.desc = Svrhni {0} šampionů řádu.
|
||||
ach.dug.name = Hlubinný kopáč {0}
|
||||
ach.dug.desc = Vykopej {0} polí.
|
||||
ach.gold.name = Hromaditel {0}
|
||||
ach.gold.desc = Vydělej {0} zlata.
|
||||
ach.wood.name = Pán dřeva {0}
|
||||
ach.wood.desc = Pokácej {0} dřeva.
|
||||
ach.walls.name = Zedník {0}
|
||||
ach.walls.desc = Postav {0} zdí.
|
||||
ach.rooms.name = Architekt stínů {0}
|
||||
ach.rooms.desc = Postav {0} polí místností.
|
||||
ach.spells.name = Temný mág {0}
|
||||
ach.spells.desc = Sešli {0} kouzel.
|
||||
ach.slaps.name = Pevná ruka {0}
|
||||
ach.slaps.desc = Dej svým tvorům {0} pohlavků. Milují to. Asi.
|
||||
ach.traps.name = Pastičkář {0}
|
||||
ach.traps.desc = Objednej {0} pastí a dveří v kovárně.
|
||||
ach.towers.name = Pán věží {0}
|
||||
ach.towers.desc = Postav {0} věží.
|
||||
ach.drowned.name = Kurz plavání {0}
|
||||
ach.drowned.desc = Nech {0} hrdinů spadnout do vody nebo lávy.
|
||||
ach.burnt.name = Žhář mostů {0}
|
||||
ach.burnt.desc = Spal {0} mostních prken.
|
||||
ach.siege.name = Mistr obléhání {0}
|
||||
ach.siege.desc = Postav {0} obléhacích strojů.
|
||||
ach.captured.name = Žalářník {0}
|
||||
ach.captured.desc = Zajmi {0} hrdinů.
|
||||
ach.drawbridge.name = Nahoru s ním {0}
|
||||
ach.drawbridge.desc = Vytáhni padací mosty {0}krát.
|
||||
ach.wins.name = Dobyvatel {0}
|
||||
ach.wins.desc = Vyhraj {0} volných her.
|
||||
ach.missions.name = Hledač střepů {0}
|
||||
ach.missions.desc = Vyhraj {0} misí kampaně.
|
||||
ach.coop.name = Člen ligy {0}
|
||||
ach.coop.desc = Vyhraj {0} kooperativních misí.
|
||||
ach.minutes.name = Noční sova {0}
|
||||
ach.minutes.desc = Hraj {0} minut.
|
||||
ach.flawless.name = Ani škrábnutí
|
||||
ach.flawless.desc = Vyhraj, aniž bys ztratil jediného tvora.
|
||||
ach.speedrun.name = Než čaj vystydne
|
||||
ach.speedrun.desc = Vyhraj do patnácti minut.
|
||||
ach.campaign.name = Jediný mistr
|
||||
ach.campaign.desc = Vyhraj poslední misi kampaně.
|
||||
ach.surface.name = Spálení od slunce
|
||||
ach.surface.desc = Vyhraj hru, která sahá až na povrch.
|
||||
ach.defeat.name = Čaj a soucit
|
||||
ach.defeat.desc = Prohraj hru. Křivoprst přistaví vodu na čaj.
|
||||
ach.legend.name = Živoucí legenda
|
||||
ach.legend.desc = Tvor dosáhne úrovně deset.
|
||||
ach.wyrm.name = Zde jsou draci
|
||||
ach.wyrm.desc = Žhavý drak se přidá k tvé kobce.
|
||||
ach.fullhand.name = Plné ruce práce
|
||||
ach.fullhand.desc = Drž v ruce najednou osm jednotek.
|
||||
@@ -1551,3 +1551,66 @@ coop.m20.goal = Besejr Mossgrim og alle hans væsner.
|
||||
coop.m20.boss = Mossgrim er ikke mere. Mosset gror ud igen. Det gør han ikke.
|
||||
coop.m20.t1 = Den lindorm i øst er Mossgrim. Ja, han har sluppet sig selv lidt løs.
|
||||
coop.m20.t2 = Hvert væsen, I besejrer, er ét mindre, der vil fortælle denne historie forkert.
|
||||
|
||||
# achievements and statistics
|
||||
menu.achievements = Præstationer
|
||||
ach.count = {0} af {1} præstationer låst op · {2} minutter spillet
|
||||
ach.hidden = En hemmelighed. Krumfinger siger ingenting.
|
||||
msg.achievement = Præstation låst op: {0}!
|
||||
end.stats2 = Helte {0} · Væsner tabt {1} · Felter gravet {2} · Guld {3} · Træ {4}
|
||||
ach.heroes.name = Heltenes Svøbe {0}
|
||||
ach.heroes.desc = Besejr {0} helte.
|
||||
ach.bosses.name = Kæmpeknuser {0}
|
||||
ach.bosses.desc = Fæld {0} af ordenens kæmper.
|
||||
ach.dug.name = Dybgraver {0}
|
||||
ach.dug.desc = Grav {0} felter ud.
|
||||
ach.gold.name = Skattesamler {0}
|
||||
ach.gold.desc = Tjen {0} guld.
|
||||
ach.wood.name = Tømmerfyrste {0}
|
||||
ach.wood.desc = Fæld {0} træ.
|
||||
ach.walls.name = Murer {0}
|
||||
ach.walls.desc = Rejs {0} mure.
|
||||
ach.rooms.name = Skyggernes Arkitekt {0}
|
||||
ach.rooms.desc = Byg {0} rumfelter.
|
||||
ach.spells.name = Mørk Troldmand {0}
|
||||
ach.spells.desc = Kast {0} besværgelser.
|
||||
ach.slaps.name = Fast Hånd {0}
|
||||
ach.slaps.desc = Giv dine væsner en lussing {0} gange. De elsker det. Formentlig.
|
||||
ach.traps.name = Fældesmed {0}
|
||||
ach.traps.desc = Bestil {0} fælder og døre i smedjen.
|
||||
ach.towers.name = Tårnherre {0}
|
||||
ach.towers.desc = Byg {0} tårne.
|
||||
ach.drowned.name = Svømmeundervisning {0}
|
||||
ach.drowned.desc = Lad {0} helte falde i vand eller lava.
|
||||
ach.burnt.name = Brobrænder {0}
|
||||
ach.burnt.desc = Brænd {0} broplanker ned.
|
||||
ach.siege.name = Belejringsmester {0}
|
||||
ach.siege.desc = Byg {0} belejringsmaskiner.
|
||||
ach.captured.name = Fangevogter {0}
|
||||
ach.captured.desc = Tag {0} helte til fange.
|
||||
ach.drawbridge.name = Op med Den {0}
|
||||
ach.drawbridge.desc = Hejs vindebroer {0} gange.
|
||||
ach.wins.name = Erobrer {0}
|
||||
ach.wins.desc = Vind {0} frie spil.
|
||||
ach.missions.name = Splintjæger {0}
|
||||
ach.missions.desc = Vind {0} kampagnemissioner.
|
||||
ach.coop.name = Forbundsfælle {0}
|
||||
ach.coop.desc = Vind {0} co-op-missioner.
|
||||
ach.minutes.name = Natteravn {0}
|
||||
ach.minutes.desc = Spil i {0} minutter.
|
||||
ach.flawless.name = Ikke en Skramme
|
||||
ach.flawless.desc = Vind uden at miste et eneste væsen.
|
||||
ach.speedrun.name = Før Teen Bliver Kold
|
||||
ach.speedrun.desc = Vind inden for femten minutter.
|
||||
ach.campaign.name = Den Ene Mester
|
||||
ach.campaign.desc = Vind kampagnens sidste mission.
|
||||
ach.surface.name = Solskoldning
|
||||
ach.surface.desc = Vind et spil, der når op til overfladen.
|
||||
ach.defeat.name = Te og Medfølelse
|
||||
ach.defeat.desc = Tab et spil. Krumfinger sætter kedlen over.
|
||||
ach.legend.name = Levende Legende
|
||||
ach.legend.desc = Et væsen når niveau ti.
|
||||
ach.wyrm.name = Her Er Drager
|
||||
ach.wyrm.desc = En glødelindorm slutter sig til dit dungeon.
|
||||
ach.fullhand.name = Hænderne Fulde
|
||||
ach.fullhand.desc = Hold otte enheder i hånden på én gang.
|
||||
@@ -1551,3 +1551,66 @@ coop.m20.goal = Besiegt Mossgrim und alle seine Kreaturen.
|
||||
coop.m20.boss = Mossgrim ist nicht mehr. Das Moos wächst nach. Er nicht.
|
||||
coop.m20.t1 = Der Wurm im Osten ist Mossgrim. Ja, er hat sich gehen lassen.
|
||||
coop.m20.t2 = Jede Kreatur, die ihr besiegt, kann diese Geschichte nicht mehr falsch erzählen.
|
||||
|
||||
# achievements and statistics
|
||||
menu.achievements = Erfolge
|
||||
ach.count = {0} von {1} Erfolgen freigeschaltet · {2} Minuten gespielt
|
||||
ach.hidden = Ein Geheimnis. Crookfinger verrät nichts.
|
||||
msg.achievement = Erfolg freigeschaltet: {0}!
|
||||
end.stats2 = Helden {0} · Kreaturen verloren {1} · Kacheln gegraben {2} · Gold {3} · Holz {4}
|
||||
ach.heroes.name = Heldenschreck {0}
|
||||
ach.heroes.desc = Besiege {0} Helden.
|
||||
ach.bosses.name = Championbrecher {0}
|
||||
ach.bosses.desc = Bring {0} Champions des Ordens zu Fall.
|
||||
ach.dug.name = Tiefgräber {0}
|
||||
ach.dug.desc = Grabe {0} Kacheln aus.
|
||||
ach.gold.name = Hortmeister {0}
|
||||
ach.gold.desc = Verdiene {0} Gold.
|
||||
ach.wood.name = Holzfürst {0}
|
||||
ach.wood.desc = Fälle {0} Holz.
|
||||
ach.walls.name = Maurer {0}
|
||||
ach.walls.desc = Richte {0} Wände auf.
|
||||
ach.rooms.name = Architekt der Schatten {0}
|
||||
ach.rooms.desc = Baue {0} Raumkacheln.
|
||||
ach.spells.name = Dunkler Zauberer {0}
|
||||
ach.spells.desc = Wirke {0} Zauber.
|
||||
ach.slaps.name = Strenge Hand {0}
|
||||
ach.slaps.desc = Gib deinen Kreaturen {0} Klapse. Sie lieben es. Vermutlich.
|
||||
ach.traps.name = Fallenschmied {0}
|
||||
ach.traps.desc = Gib {0} Fallen und Türen in der Schmiede in Auftrag.
|
||||
ach.towers.name = Turmherr {0}
|
||||
ach.towers.desc = Baue {0} Türme.
|
||||
ach.drowned.name = Schwimmkurs {0}
|
||||
ach.drowned.desc = Lass {0} Helden in Wasser oder Lava stürzen.
|
||||
ach.burnt.name = Brückenbrenner {0}
|
||||
ach.burnt.desc = Brenne {0} Brückenplanken nieder.
|
||||
ach.siege.name = Belagerungsmeister {0}
|
||||
ach.siege.desc = Baue {0} Belagerungsgeräte.
|
||||
ach.captured.name = Kerkermeister {0}
|
||||
ach.captured.desc = Nimm {0} Helden gefangen.
|
||||
ach.drawbridge.name = Hoch damit {0}
|
||||
ach.drawbridge.desc = Ziehe {0}-mal Zugbrücken hoch.
|
||||
ach.wins.name = Eroberer {0}
|
||||
ach.wins.desc = Gewinne {0} freie Spiele.
|
||||
ach.missions.name = Splittersucher {0}
|
||||
ach.missions.desc = Gewinne {0} Kampagnenmissionen.
|
||||
ach.coop.name = Bundesmitglied {0}
|
||||
ach.coop.desc = Gewinne {0} Koop-Missionen.
|
||||
ach.minutes.name = Nachteule {0}
|
||||
ach.minutes.desc = Spiele {0} Minuten lang.
|
||||
ach.flawless.name = Kein Kratzer
|
||||
ach.flawless.desc = Gewinne, ohne eine einzige Kreatur zu verlieren.
|
||||
ach.speedrun.name = Bevor der Tee kalt wird
|
||||
ach.speedrun.desc = Gewinne innerhalb von fünfzehn Minuten.
|
||||
ach.campaign.name = Der eine Meister
|
||||
ach.campaign.desc = Gewinne die letzte Mission der Kampagne.
|
||||
ach.surface.name = Sonnenbrand
|
||||
ach.surface.desc = Gewinne ein Spiel, das bis an die Oberfläche reicht.
|
||||
ach.defeat.name = Tee und Mitgefühl
|
||||
ach.defeat.desc = Verliere ein Spiel. Crookfinger setzt Wasser auf.
|
||||
ach.legend.name = Lebende Legende
|
||||
ach.legend.desc = Eine Kreatur erreicht Stufe zehn.
|
||||
ach.wyrm.name = Hier seien Drachen
|
||||
ach.wyrm.desc = Ein Glutwurm schließt sich deinem Kerker an.
|
||||
ach.fullhand.name = Alle Hände voll
|
||||
ach.fullhand.desc = Halte acht Einheiten gleichzeitig in der Hand.
|
||||
@@ -1552,3 +1552,66 @@ coop.m20.goal = Νικήστε τον Μόσγκριμ και όλα του τα
|
||||
coop.m20.boss = Ο Μόσγκριμ δεν υπάρχει πια. Τα βρύα θα ξαναφυτρώσουν. Αυτός όχι.
|
||||
coop.m20.t1 = Εκείνος ο δράκος στα ανατολικά είναι ο Μόσγκριμ. Ναι, παραμελήθηκε.
|
||||
coop.m20.t2 = Κάθε πλάσμα που νικάτε είναι ένα λιγότερο που θα πει αυτή την ιστορία λάθος.
|
||||
|
||||
# achievements and statistics
|
||||
menu.achievements = Επιτεύγματα
|
||||
ach.count = {0} από {1} επιτεύγματα ξεκλειδώθηκαν · {2} λεπτά παιχνιδιού
|
||||
ach.hidden = Μυστικό. Ο Στραβοδάχτυλος δεν λέει τίποτα.
|
||||
msg.achievement = Επίτευγμα ξεκλειδώθηκε: {0}!
|
||||
end.stats2 = Ήρωες {0} · Χαμένα πλάσματα {1} · Σκαμμένα πλακίδια {2} · Χρυσός {3} · Ξύλο {4}
|
||||
ach.heroes.name = Μάστιγα ηρώων {0}
|
||||
ach.heroes.desc = Νίκησε {0} ήρωες.
|
||||
ach.bosses.name = Συντρίπτης πρωταθλητών {0}
|
||||
ach.bosses.desc = Ρίξε {0} πρωταθλητές του Τάγματος.
|
||||
ach.dug.name = Βαθύς σκαφτιάς {0}
|
||||
ach.dug.desc = Σκάψε {0} πλακίδια.
|
||||
ach.gold.name = Θησαυριστής {0}
|
||||
ach.gold.desc = Κέρδισε {0} χρυσό.
|
||||
ach.wood.name = Άρχοντας του ξύλου {0}
|
||||
ach.wood.desc = Κόψε {0} ξύλο.
|
||||
ach.walls.name = Χτίστης {0}
|
||||
ach.walls.desc = Ύψωσε {0} τοίχους.
|
||||
ach.rooms.name = Αρχιτέκτονας των σκιών {0}
|
||||
ach.rooms.desc = Χτίσε {0} πλακίδια δωματίων.
|
||||
ach.spells.name = Σκοτεινός μάγος {0}
|
||||
ach.spells.desc = Ρίξε {0} ξόρκια.
|
||||
ach.slaps.name = Σταθερό χέρι {0}
|
||||
ach.slaps.desc = Χαστούκισε τα πλάσματά σου {0} φορές. Τους αρέσει. Μάλλον.
|
||||
ach.traps.name = Παγιδοποιός {0}
|
||||
ach.traps.desc = Παράγγειλε {0} παγίδες και πόρτες στο σιδηρουργείο.
|
||||
ach.towers.name = Άρχοντας των πύργων {0}
|
||||
ach.towers.desc = Χτίσε {0} πύργους.
|
||||
ach.drowned.name = Μαθήματα κολύμβησης {0}
|
||||
ach.drowned.desc = Ρίξε {0} ήρωες σε νερό ή λάβα.
|
||||
ach.burnt.name = Καψογέφυρος {0}
|
||||
ach.burnt.desc = Κάψε {0} σανίδες γεφυρών.
|
||||
ach.siege.name = Πολιορκητής {0}
|
||||
ach.siege.desc = Χτίσε {0} πολιορκητικές μηχανές.
|
||||
ach.captured.name = Δεσμοφύλακας {0}
|
||||
ach.captured.desc = Αιχμαλώτισε {0} ήρωες.
|
||||
ach.drawbridge.name = Και πάνω! {0}
|
||||
ach.drawbridge.desc = Σήκωσε κινητές γέφυρες {0} φορές.
|
||||
ach.wins.name = Κατακτητής {0}
|
||||
ach.wins.desc = Κέρδισε {0} ελεύθερα παιχνίδια.
|
||||
ach.missions.name = Κυνηγός θραυσμάτων {0}
|
||||
ach.missions.desc = Κέρδισε {0} αποστολές εκστρατείας.
|
||||
ach.coop.name = Μέλος της λίγκας {0}
|
||||
ach.coop.desc = Κέρδισε {0} συνεργατικές αποστολές.
|
||||
ach.minutes.name = Νυχτοπούλι {0}
|
||||
ach.minutes.desc = Παίξε για {0} λεπτά.
|
||||
ach.flawless.name = Ούτε γρατζουνιά
|
||||
ach.flawless.desc = Νίκησε χωρίς να χάσεις ούτε ένα πλάσμα.
|
||||
ach.speedrun.name = Πριν κρυώσει το τσάι
|
||||
ach.speedrun.desc = Νίκησε μέσα σε δεκαπέντε λεπτά.
|
||||
ach.campaign.name = Ο Ένας Άρχοντας
|
||||
ach.campaign.desc = Κέρδισε την τελευταία αποστολή της εκστρατείας.
|
||||
ach.surface.name = Ηλιοκαμένος
|
||||
ach.surface.desc = Κέρδισε ένα παιχνίδι που φτάνει ως την επιφάνεια.
|
||||
ach.defeat.name = Τσάι και συμπόνια
|
||||
ach.defeat.desc = Χάσε ένα παιχνίδι. Ο Στραβοδάχτυλος βάζει νερό να βράσει.
|
||||
ach.legend.name = Ζωντανός θρύλος
|
||||
ach.legend.desc = Ένα πλάσμα φτάνει στο επίπεδο δέκα.
|
||||
ach.wyrm.name = Εδώ υπάρχουν δράκοι
|
||||
ach.wyrm.desc = Ένας δράκος της θράκας μπαίνει στο μπουντρούμι σου.
|
||||
ach.fullhand.name = Γεμάτα χέρια
|
||||
ach.fullhand.desc = Κράτα οκτώ μονάδες στο χέρι σου ταυτόχρονα.
|
||||
@@ -1552,3 +1552,66 @@ coop.m20.goal = Defeat Mossgrim and all his creatures.
|
||||
coop.m20.boss = Mossgrim is no more. The moss will grow back. He will not.
|
||||
coop.m20.t1 = That wyrm in the east is Mossgrim. Yes, he let himself go.
|
||||
coop.m20.t2 = Every creature you defeat is one less that will tell this story wrong.
|
||||
|
||||
# achievements and statistics
|
||||
menu.achievements = Achievements
|
||||
ach.count = {0} of {1} achievements unlocked · {2} minutes played
|
||||
ach.hidden = A secret. Crookfinger is not telling.
|
||||
msg.achievement = Achievement unlocked: {0}!
|
||||
end.stats2 = Heroes {0} · Creatures lost {1} · Tiles dug {2} · Gold {3} · Wood {4}
|
||||
ach.heroes.name = Hero Bane {0}
|
||||
ach.heroes.desc = Defeat {0} heroes.
|
||||
ach.bosses.name = Champion Breaker {0}
|
||||
ach.bosses.desc = Bring down {0} champions of the Order.
|
||||
ach.dug.name = Deep Digger {0}
|
||||
ach.dug.desc = Dig out {0} tiles.
|
||||
ach.gold.name = Hoarder {0}
|
||||
ach.gold.desc = Earn {0} gold.
|
||||
ach.wood.name = Lumberlord {0}
|
||||
ach.wood.desc = Fell {0} wood.
|
||||
ach.walls.name = Mason {0}
|
||||
ach.walls.desc = Raise {0} walls.
|
||||
ach.rooms.name = Architect of Shadows {0}
|
||||
ach.rooms.desc = Build {0} room tiles.
|
||||
ach.spells.name = Dark Caster {0}
|
||||
ach.spells.desc = Cast {0} spells.
|
||||
ach.slaps.name = Firm Hand {0}
|
||||
ach.slaps.desc = Slap your creatures {0} times. They love it. Probably.
|
||||
ach.traps.name = Trapsmith {0}
|
||||
ach.traps.desc = Order {0} traps and doors in the forge.
|
||||
ach.towers.name = Tower Lord {0}
|
||||
ach.towers.desc = Build {0} towers.
|
||||
ach.drowned.name = Swimming Lessons {0}
|
||||
ach.drowned.desc = Let {0} heroes fall into water or lava.
|
||||
ach.burnt.name = Bridge Burner {0}
|
||||
ach.burnt.desc = Burn down {0} bridge planks.
|
||||
ach.siege.name = Siege Master {0}
|
||||
ach.siege.desc = Build {0} siege engines.
|
||||
ach.captured.name = Warden {0}
|
||||
ach.captured.desc = Capture {0} heroes.
|
||||
ach.drawbridge.name = Up She Goes {0}
|
||||
ach.drawbridge.desc = Raise drawbridges {0} times.
|
||||
ach.wins.name = Conqueror {0}
|
||||
ach.wins.desc = Win {0} free games.
|
||||
ach.missions.name = Shard Seeker {0}
|
||||
ach.missions.desc = Win {0} campaign missions.
|
||||
ach.coop.name = League Member {0}
|
||||
ach.coop.desc = Win {0} co-op missions.
|
||||
ach.minutes.name = Night Owl {0}
|
||||
ach.minutes.desc = Play for {0} minutes.
|
||||
ach.flawless.name = Not a Scratch
|
||||
ach.flawless.desc = Win without losing a single creature.
|
||||
ach.speedrun.name = Before the Tea Gets Cold
|
||||
ach.speedrun.desc = Win within fifteen minutes.
|
||||
ach.campaign.name = The One Master
|
||||
ach.campaign.desc = Win the last mission of the campaign.
|
||||
ach.surface.name = Sunburn
|
||||
ach.surface.desc = Win a game that reaches the surface.
|
||||
ach.defeat.name = Tea and Sympathy
|
||||
ach.defeat.desc = Lose a game. Crookfinger puts the kettle on.
|
||||
ach.legend.name = Living Legend
|
||||
ach.legend.desc = A creature reaches level ten.
|
||||
ach.wyrm.name = Here Be Dragons
|
||||
ach.wyrm.desc = An ember wyrm joins your dungeon.
|
||||
ach.fullhand.name = Hands Full
|
||||
ach.fullhand.desc = Hold eight units in your hand at once.
|
||||
@@ -1552,3 +1552,66 @@ coop.m20.goal = Derrotad a Mossgrim y a todas sus criaturas.
|
||||
coop.m20.boss = Mossgrim ya no existe. El musgo volverá a crecer. Él no.
|
||||
coop.m20.t1 = Ese dragón del este es Mossgrim. Sí, se ha descuidado bastante.
|
||||
coop.m20.t2 = Cada criatura que derrotáis es una menos que contará mal esta historia.
|
||||
|
||||
# achievements and statistics
|
||||
menu.achievements = Logros
|
||||
ach.count = {0} de {1} logros desbloqueados · {2} minutos jugados
|
||||
ach.hidden = Un secreto. Dedotuerto no suelta prenda.
|
||||
msg.achievement = Logro desbloqueado: ¡{0}!
|
||||
end.stats2 = Héroes {0} · Criaturas perdidas {1} · Casillas excavadas {2} · Oro {3} · Madera {4}
|
||||
ach.heroes.name = Azote de héroes {0}
|
||||
ach.heroes.desc = Derrota a {0} héroes.
|
||||
ach.bosses.name = Rompecampeones {0}
|
||||
ach.bosses.desc = Derriba a {0} campeones de la Orden.
|
||||
ach.dug.name = Excavador profundo {0}
|
||||
ach.dug.desc = Excava {0} casillas.
|
||||
ach.gold.name = Acaparador {0}
|
||||
ach.gold.desc = Gana {0} de oro.
|
||||
ach.wood.name = Señor de la madera {0}
|
||||
ach.wood.desc = Tala {0} de madera.
|
||||
ach.walls.name = Albañil {0}
|
||||
ach.walls.desc = Levanta {0} muros.
|
||||
ach.rooms.name = Arquitecto de las sombras {0}
|
||||
ach.rooms.desc = Construye {0} casillas de sala.
|
||||
ach.spells.name = Hechicero oscuro {0}
|
||||
ach.spells.desc = Lanza {0} hechizos.
|
||||
ach.slaps.name = Mano firme {0}
|
||||
ach.slaps.desc = Abofetea a tus criaturas {0} veces. Les encanta. Seguramente.
|
||||
ach.traps.name = Forjatrampas {0}
|
||||
ach.traps.desc = Encarga {0} trampas y puertas en la forja.
|
||||
ach.towers.name = Señor de las torres {0}
|
||||
ach.towers.desc = Construye {0} torres.
|
||||
ach.drowned.name = Clases de natación {0}
|
||||
ach.drowned.desc = Haz que {0} héroes caigan al agua o a la lava.
|
||||
ach.burnt.name = Quemapuentes {0}
|
||||
ach.burnt.desc = Quema {0} tablones de puente.
|
||||
ach.siege.name = Maestro de asedio {0}
|
||||
ach.siege.desc = Construye {0} máquinas de asedio.
|
||||
ach.captured.name = Carcelero {0}
|
||||
ach.captured.desc = Captura a {0} héroes.
|
||||
ach.drawbridge.name = ¡Arriba con él! {0}
|
||||
ach.drawbridge.desc = Alza puentes levadizos {0} veces.
|
||||
ach.wins.name = Conquistador {0}
|
||||
ach.wins.desc = Gana {0} partidas libres.
|
||||
ach.missions.name = Buscafragmentos {0}
|
||||
ach.missions.desc = Gana {0} misiones de campaña.
|
||||
ach.coop.name = Miembro de la liga {0}
|
||||
ach.coop.desc = Gana {0} misiones cooperativas.
|
||||
ach.minutes.name = Ave nocturna {0}
|
||||
ach.minutes.desc = Juega durante {0} minutos.
|
||||
ach.flawless.name = Ni un rasguño
|
||||
ach.flawless.desc = Gana sin perder una sola criatura.
|
||||
ach.speedrun.name = Antes de que se enfríe el té
|
||||
ach.speedrun.desc = Gana en menos de quince minutos.
|
||||
ach.campaign.name = El Único Amo
|
||||
ach.campaign.desc = Gana la última misión de la campaña.
|
||||
ach.surface.name = Quemadura solar
|
||||
ach.surface.desc = Gana una partida que llegue hasta la superficie.
|
||||
ach.defeat.name = Té y compasión
|
||||
ach.defeat.desc = Pierde una partida. Dedotuerto pone el agua a hervir.
|
||||
ach.legend.name = Leyenda viviente
|
||||
ach.legend.desc = Una criatura alcanza el nivel diez.
|
||||
ach.wyrm.name = Aquí hay dragones
|
||||
ach.wyrm.desc = Un dragón de brasas se une a tu mazmorra.
|
||||
ach.fullhand.name = Manos llenas
|
||||
ach.fullhand.desc = Sostén ocho unidades en la mano a la vez.
|
||||
@@ -1552,3 +1552,66 @@ coop.m20.goal = Kukistakaa Mossgrim ja kaikki hänen olentonsa.
|
||||
coop.m20.boss = Mossgrimia ei enää ole. Sammal kasvaa takaisin. Hän ei.
|
||||
coop.m20.t1 = Tuo lohikäärme idässä on Mossgrim. Niin, hän päästi itsensä rapistumaan.
|
||||
coop.m20.t2 = Jokainen kukistamanne olento on yksi vähemmän kertomassa tätä tarinaa väärin.
|
||||
|
||||
# achievements and statistics
|
||||
menu.achievements = Saavutukset
|
||||
ach.count = {0}/{1} saavutusta avattu · {2} minuuttia pelattu
|
||||
ach.hidden = Salaisuus. Crookfinger ei kerro.
|
||||
msg.achievement = Saavutus avattu: {0}!
|
||||
end.stats2 = Sankarit {0} · Menetetyt olennot {1} · Kaivetut ruudut {2} · Kulta {3} · Puu {4}
|
||||
ach.heroes.name = Sankarikauhu {0}
|
||||
ach.heroes.desc = Kukista {0} sankaria.
|
||||
ach.bosses.name = Mestarinmurskaaja {0}
|
||||
ach.bosses.desc = Kaada {0} ritarikunnan mestaria.
|
||||
ach.dug.name = Syväkaivaja {0}
|
||||
ach.dug.desc = Kaiva {0} ruutua.
|
||||
ach.gold.name = Aarteenkahmija {0}
|
||||
ach.gold.desc = Ansaitse {0} kultaa.
|
||||
ach.wood.name = Puuruhtinas {0}
|
||||
ach.wood.desc = Kaada {0} puuta.
|
||||
ach.walls.name = Muurari {0}
|
||||
ach.walls.desc = Pystytä {0} seinää.
|
||||
ach.rooms.name = Varjojen arkkitehti {0}
|
||||
ach.rooms.desc = Rakenna {0} huoneruutua.
|
||||
ach.spells.name = Pimeä loitsija {0}
|
||||
ach.spells.desc = Loitsi {0} loitsua.
|
||||
ach.slaps.name = Luja käsi {0}
|
||||
ach.slaps.desc = Läimäytä olentojasi {0} kertaa. Ne rakastavat sitä. Luultavasti.
|
||||
ach.traps.name = Ansaseppä {0}
|
||||
ach.traps.desc = Tilaa pajasta {0} ansaa ja ovea.
|
||||
ach.towers.name = Tornin herra {0}
|
||||
ach.towers.desc = Rakenna {0} tornia.
|
||||
ach.drowned.name = Uimakoulu {0}
|
||||
ach.drowned.desc = Anna {0} sankarin pudota veteen tai laavaan.
|
||||
ach.burnt.name = Siltojenpolttaja {0}
|
||||
ach.burnt.desc = Polta {0} siltalankkua.
|
||||
ach.siege.name = Piiritysmestari {0}
|
||||
ach.siege.desc = Rakenna {0} piiritysvälinettä.
|
||||
ach.captured.name = Vanginvartija {0}
|
||||
ach.captured.desc = Vangitse {0} sankaria.
|
||||
ach.drawbridge.name = Ylös vaan {0}
|
||||
ach.drawbridge.desc = Nosta laskusiltoja {0} kertaa.
|
||||
ach.wins.name = Valloittaja {0}
|
||||
ach.wins.desc = Voita {0} vapaata peliä.
|
||||
ach.missions.name = Sirpaleenetsijä {0}
|
||||
ach.missions.desc = Voita {0} kampanjatehtävää.
|
||||
ach.coop.name = Liiton jäsen {0}
|
||||
ach.coop.desc = Voita {0} yhteistyötehtävää.
|
||||
ach.minutes.name = Yökyöpeli {0}
|
||||
ach.minutes.desc = Pelaa {0} minuuttia.
|
||||
ach.flawless.name = Ei naarmuakaan
|
||||
ach.flawless.desc = Voita menettämättä yhtäkään olentoa.
|
||||
ach.speedrun.name = Ennen kuin tee jäähtyy
|
||||
ach.speedrun.desc = Voita viidessätoista minuutissa.
|
||||
ach.campaign.name = Se yksi mestari
|
||||
ach.campaign.desc = Voita kampanjan viimeinen tehtävä.
|
||||
ach.surface.name = Auringonpolttama
|
||||
ach.surface.desc = Voita peli, joka ulottuu maan pinnalle.
|
||||
ach.defeat.name = Teetä ja sympatiaa
|
||||
ach.defeat.desc = Häviä peli. Crookfinger laittaa teeveden kiehumaan.
|
||||
ach.legend.name = Elävä legenda
|
||||
ach.legend.desc = Olento saavuttaa tason kymmenen.
|
||||
ach.wyrm.name = Täällä on lohikäärmeitä
|
||||
ach.wyrm.desc = Hehkulohikäärme liittyy luolastoosi.
|
||||
ach.fullhand.name = Kädet täynnä
|
||||
ach.fullhand.desc = Pidä kahdeksaa yksikköä kädessäsi yhtä aikaa.
|
||||
@@ -1552,3 +1552,66 @@ coop.m20.goal = Porazite Mossgrima i sva njegova stvorenja.
|
||||
coop.m20.boss = Mossgrima više nema. Mahovina će ponovo izrasti. On neće.
|
||||
coop.m20.t1 = Onaj zmaj na istoku je Mossgrim. Da, zapustio se.
|
||||
coop.m20.t2 = Svako stvorenje koje porazite jedno je manje koje će ovu priču krivo ispričati.
|
||||
|
||||
# achievements and statistics
|
||||
menu.achievements = Postignuća
|
||||
ach.count = Otključano {0} od {1} postignuća · {2} minuta igre
|
||||
ach.hidden = Tajna. Krivoprst ne govori.
|
||||
msg.achievement = Postignuće otključano: {0}!
|
||||
end.stats2 = Junaci {0} · Izgubljena stvorenja {1} · Iskopana polja {2} · Zlato {3} · Drvo {4}
|
||||
ach.heroes.name = Strah junaka {0}
|
||||
ach.heroes.desc = Porazi {0} junaka.
|
||||
ach.bosses.name = Lomitelj prvaka {0}
|
||||
ach.bosses.desc = Obori {0} prvaka reda.
|
||||
ach.dug.name = Duboki kopač {0}
|
||||
ach.dug.desc = Iskopaj {0} polja.
|
||||
ach.gold.name = Gomilatelj {0}
|
||||
ach.gold.desc = Zaradi {0} zlata.
|
||||
ach.wood.name = Gospodar drva {0}
|
||||
ach.wood.desc = Posijeci {0} drva.
|
||||
ach.walls.name = Zidar {0}
|
||||
ach.walls.desc = Podigni {0} zidova.
|
||||
ach.rooms.name = Arhitekt sjena {0}
|
||||
ach.rooms.desc = Izgradi {0} polja soba.
|
||||
ach.spells.name = Mračni čarobnjak {0}
|
||||
ach.spells.desc = Baci {0} čarolija.
|
||||
ach.slaps.name = Čvrsta ruka {0}
|
||||
ach.slaps.desc = Pljusni svoja stvorenja {0} puta. Obožavaju to. Valjda.
|
||||
ach.traps.name = Kovač zamki {0}
|
||||
ach.traps.desc = Naruči {0} zamki i vrata u kovačnici.
|
||||
ach.towers.name = Gospodar tornjeva {0}
|
||||
ach.towers.desc = Izgradi {0} tornjeva.
|
||||
ach.drowned.name = Tečaj plivanja {0}
|
||||
ach.drowned.desc = Pusti {0} junaka da padnu u vodu ili lavu.
|
||||
ach.burnt.name = Palikuća mostova {0}
|
||||
ach.burnt.desc = Spali {0} dasaka mosta.
|
||||
ach.siege.name = Majstor opsade {0}
|
||||
ach.siege.desc = Izgradi {0} opsadnih sprava.
|
||||
ach.captured.name = Tamničar {0}
|
||||
ach.captured.desc = Zarobi {0} junaka.
|
||||
ach.drawbridge.name = Gore s njim {0}
|
||||
ach.drawbridge.desc = Podigni pokretne mostove {0} puta.
|
||||
ach.wins.name = Osvajač {0}
|
||||
ach.wins.desc = Pobijedi u {0} slobodnih igara.
|
||||
ach.missions.name = Tragač za krhotinama {0}
|
||||
ach.missions.desc = Pobijedi u {0} misija kampanje.
|
||||
ach.coop.name = Član saveza {0}
|
||||
ach.coop.desc = Pobijedi u {0} co-op misija.
|
||||
ach.minutes.name = Noćna ptica {0}
|
||||
ach.minutes.desc = Igraj {0} minuta.
|
||||
ach.flawless.name = Ni ogrebotine
|
||||
ach.flawless.desc = Pobijedi bez gubitka ijednog stvorenja.
|
||||
ach.speedrun.name = Prije nego se čaj ohladi
|
||||
ach.speedrun.desc = Pobijedi unutar petnaest minuta.
|
||||
ach.campaign.name = Jedini gospodar
|
||||
ach.campaign.desc = Pobijedi u posljednjoj misiji kampanje.
|
||||
ach.surface.name = Opeklina od sunca
|
||||
ach.surface.desc = Pobijedi u igri koja seže do površine.
|
||||
ach.defeat.name = Čaj i suosjećanje
|
||||
ach.defeat.desc = Izgubi igru. Krivoprst stavlja vodu za čaj.
|
||||
ach.legend.name = Živa legenda
|
||||
ach.legend.desc = Stvorenje dostiže razinu deset.
|
||||
ach.wyrm.name = Ovdje su zmajevi
|
||||
ach.wyrm.desc = Žarni zmaj pridružuje se tvojoj tamnici.
|
||||
ach.fullhand.name = Pune ruke posla
|
||||
ach.fullhand.desc = Drži osam jedinica u ruci odjednom.
|
||||
@@ -1552,3 +1552,66 @@ coop.m20.goal = Győzd le Mossgrimot és minden lényét.
|
||||
coop.m20.boss = Mossgrim nincs többé. A moha visszanő. Ő nem.
|
||||
coop.m20.t1 = Az a keleti sárkány Mossgrim. Igen, elhanyagolta magát.
|
||||
coop.m20.t2 = Minden legyőzött lény eggyel kevesebb, aki rosszul meséli majd el ezt a történetet.
|
||||
|
||||
# achievements and statistics
|
||||
menu.achievements = Teljesítmények
|
||||
ach.count = {0}/{1} teljesítmény feloldva · {2} perc játékidő
|
||||
ach.hidden = Titok. Crookfinger nem árulja el.
|
||||
msg.achievement = Teljesítmény feloldva: {0}!
|
||||
end.stats2 = Hősök {0} · Elvesztett lények {1} · Kiásott mezők {2} · Arany {3} · Fa {4}
|
||||
ach.heroes.name = Hősök réme {0}
|
||||
ach.heroes.desc = Győzz le {0} hőst.
|
||||
ach.bosses.name = Bajnoktörő {0}
|
||||
ach.bosses.desc = Dönts le {0} bajnokot a rendből.
|
||||
ach.dug.name = Mélyásó {0}
|
||||
ach.dug.desc = Áss ki {0} mezőt.
|
||||
ach.gold.name = Kincsgyűjtő {0}
|
||||
ach.gold.desc = Szerezz {0} aranyat.
|
||||
ach.wood.name = Faúr {0}
|
||||
ach.wood.desc = Vágj ki {0} fát.
|
||||
ach.walls.name = Kőműves {0}
|
||||
ach.walls.desc = Emelj {0} falat.
|
||||
ach.rooms.name = Az árnyak építésze {0}
|
||||
ach.rooms.desc = Építs {0} teremmezőt.
|
||||
ach.spells.name = Sötét varázsló {0}
|
||||
ach.spells.desc = Mondj el {0} varázslatot.
|
||||
ach.slaps.name = Kemény kéz {0}
|
||||
ach.slaps.desc = Pofozd fel a lényeidet {0} alkalommal. Imádják. Valószínűleg.
|
||||
ach.traps.name = Csapdakovács {0}
|
||||
ach.traps.desc = Rendelj {0} csapdát és ajtót a kovácsműhelyben.
|
||||
ach.towers.name = Toronyúr {0}
|
||||
ach.towers.desc = Építs {0} tornyot.
|
||||
ach.drowned.name = Úszólecke {0}
|
||||
ach.drowned.desc = Hagyd, hogy {0} hős vízbe vagy lávába zuhanjon.
|
||||
ach.burnt.name = Hídégető {0}
|
||||
ach.burnt.desc = Égess fel {0} hídpallót.
|
||||
ach.siege.name = Ostrommester {0}
|
||||
ach.siege.desc = Építs {0} ostromgépet.
|
||||
ach.captured.name = Börtönőr {0}
|
||||
ach.captured.desc = Ejts foglyul {0} hőst.
|
||||
ach.drawbridge.name = Fel vele {0}
|
||||
ach.drawbridge.desc = Húzz fel felvonóhidat {0} alkalommal.
|
||||
ach.wins.name = Hódító {0}
|
||||
ach.wins.desc = Nyerj meg {0} szabad játékot.
|
||||
ach.missions.name = Szilánkkereső {0}
|
||||
ach.missions.desc = Teljesíts {0} hadjárat-küldetést.
|
||||
ach.coop.name = Szövetségi tag {0}
|
||||
ach.coop.desc = Teljesíts {0} koop küldetést.
|
||||
ach.minutes.name = Éjjeli bagoly {0}
|
||||
ach.minutes.desc = Játssz {0} percet.
|
||||
ach.flawless.name = Egy karcolás sem
|
||||
ach.flawless.desc = Nyerj úgy, hogy egyetlen lényt sem veszítesz.
|
||||
ach.speedrun.name = Mielőtt kihűl a tea
|
||||
ach.speedrun.desc = Nyerj tizenöt percen belül.
|
||||
ach.campaign.name = Az egyetlen mester
|
||||
ach.campaign.desc = Nyerd meg a hadjárat utolsó küldetését.
|
||||
ach.surface.name = Leégés
|
||||
ach.surface.desc = Nyerj meg egy játékot, amely a felszínig ér.
|
||||
ach.defeat.name = Tea és együttérzés
|
||||
ach.defeat.desc = Veszíts el egy játékot. Crookfinger felteszi a teavizet.
|
||||
ach.legend.name = Élő legenda
|
||||
ach.legend.desc = Egy lény eléri a tizedik szintet.
|
||||
ach.wyrm.name = Itt sárkányok laknak
|
||||
ach.wyrm.desc = Egy parázssárkány csatlakozik a kazamatádhoz.
|
||||
ach.fullhand.name = Tele a kéz
|
||||
ach.fullhand.desc = Tarts egyszerre nyolc egységet a kezedben.
|
||||
@@ -1552,3 +1552,66 @@ coop.m20.goal = Sconfiggete Mossgrim e tutte le sue creature.
|
||||
coop.m20.boss = Mossgrim non c'è più. Il muschio ricrescerà. Lui no.
|
||||
coop.m20.t1 = Quel drago a est è Mossgrim. Sì, si è lasciato andare.
|
||||
coop.m20.t2 = Ogni creatura che sconfiggete è una in meno che racconterà male questa storia.
|
||||
|
||||
# achievements and statistics
|
||||
menu.achievements = Obiettivi
|
||||
ach.count = {0} di {1} obiettivi sbloccati · {2} minuti giocati
|
||||
ach.hidden = Un segreto. Ditastorte non parla.
|
||||
msg.achievement = Obiettivo sbloccato: {0}!
|
||||
end.stats2 = Eroi {0} · Creature perse {1} · Caselle scavate {2} · Oro {3} · Legno {4}
|
||||
ach.heroes.name = Flagello degli eroi {0}
|
||||
ach.heroes.desc = Sconfiggi {0} eroi.
|
||||
ach.bosses.name = Spezzacampioni {0}
|
||||
ach.bosses.desc = Abbatti {0} campioni dell'Ordine.
|
||||
ach.dug.name = Scavatore profondo {0}
|
||||
ach.dug.desc = Scava {0} caselle.
|
||||
ach.gold.name = Accumulatore {0}
|
||||
ach.gold.desc = Guadagna {0} oro.
|
||||
ach.wood.name = Signore del legno {0}
|
||||
ach.wood.desc = Abbatti {0} legno.
|
||||
ach.walls.name = Muratore {0}
|
||||
ach.walls.desc = Alza {0} muri.
|
||||
ach.rooms.name = Architetto delle ombre {0}
|
||||
ach.rooms.desc = Costruisci {0} caselle di stanza.
|
||||
ach.spells.name = Incantatore oscuro {0}
|
||||
ach.spells.desc = Lancia {0} magie.
|
||||
ach.slaps.name = Mano ferma {0}
|
||||
ach.slaps.desc = Dai {0} schiaffi alle tue creature. Lo adorano. Probabilmente.
|
||||
ach.traps.name = Fabbro di trappole {0}
|
||||
ach.traps.desc = Ordina {0} trappole e porte alla fucina.
|
||||
ach.towers.name = Signore delle torri {0}
|
||||
ach.towers.desc = Costruisci {0} torri.
|
||||
ach.drowned.name = Lezioni di nuoto {0}
|
||||
ach.drowned.desc = Fai cadere {0} eroi in acqua o nella lava.
|
||||
ach.burnt.name = Bruciaponti {0}
|
||||
ach.burnt.desc = Brucia {0} assi di ponte.
|
||||
ach.siege.name = Maestro d'assedio {0}
|
||||
ach.siege.desc = Costruisci {0} macchine d'assedio.
|
||||
ach.captured.name = Carceriere {0}
|
||||
ach.captured.desc = Cattura {0} eroi.
|
||||
ach.drawbridge.name = Su che si va! {0}
|
||||
ach.drawbridge.desc = Alza i ponti levatoi {0} volte.
|
||||
ach.wins.name = Conquistatore {0}
|
||||
ach.wins.desc = Vinci {0} partite libere.
|
||||
ach.missions.name = Cercaframmenti {0}
|
||||
ach.missions.desc = Vinci {0} missioni della campagna.
|
||||
ach.coop.name = Membro della lega {0}
|
||||
ach.coop.desc = Vinci {0} missioni co-op.
|
||||
ach.minutes.name = Nottambulo {0}
|
||||
ach.minutes.desc = Gioca per {0} minuti.
|
||||
ach.flawless.name = Neanche un graffio
|
||||
ach.flawless.desc = Vinci senza perdere nemmeno una creatura.
|
||||
ach.speedrun.name = Prima che il tè si raffreddi
|
||||
ach.speedrun.desc = Vinci entro quindici minuti.
|
||||
ach.campaign.name = L'Unico Maestro
|
||||
ach.campaign.desc = Vinci l'ultima missione della campagna.
|
||||
ach.surface.name = Scottatura
|
||||
ach.surface.desc = Vinci una partita che arriva fino in superficie.
|
||||
ach.defeat.name = Tè e compassione
|
||||
ach.defeat.desc = Perdi una partita. Ditastorte mette su il bollitore.
|
||||
ach.legend.name = Leggenda vivente
|
||||
ach.legend.desc = Una creatura raggiunge il livello dieci.
|
||||
ach.wyrm.name = Hic sunt dracones
|
||||
ach.wyrm.desc = Un drago di brace si unisce al tuo dungeon.
|
||||
ach.fullhand.name = Mani piene
|
||||
ach.fullhand.desc = Tieni in mano otto unità contemporaneamente.
|
||||
@@ -1552,3 +1552,66 @@ coop.m20.goal = Versla Mossgrim en al zijn wezens.
|
||||
coop.m20.boss = Mossgrim is niet meer. Het mos groeit wel terug. Hij niet.
|
||||
coop.m20.t1 = Die draak in het oosten is Mossgrim. Ja, hij heeft zich laten gaan.
|
||||
coop.m20.t2 = Elk wezen dat jullie verslaan, is er een minder dat dit verhaal verkeerd vertelt.
|
||||
|
||||
# achievements and statistics
|
||||
menu.achievements = Prestaties
|
||||
ach.count = {0} van {1} prestaties ontgrendeld · {2} minuten gespeeld
|
||||
ach.hidden = Een geheim. Krommevinger zegt niks.
|
||||
msg.achievement = Prestatie ontgrendeld: {0}!
|
||||
end.stats2 = Helden {0} · Wezens verloren {1} · Vakken gegraven {2} · Goud {3} · Hout {4}
|
||||
ach.heroes.name = Heldenplaag {0}
|
||||
ach.heroes.desc = Versla {0} helden.
|
||||
ach.bosses.name = Kampioenbreker {0}
|
||||
ach.bosses.desc = Breng {0} kampioenen van de Orde ten val.
|
||||
ach.dug.name = Diepgraver {0}
|
||||
ach.dug.desc = Graaf {0} vakken uit.
|
||||
ach.gold.name = Schatgraaier {0}
|
||||
ach.gold.desc = Verdien {0} goud.
|
||||
ach.wood.name = Houthakkersvorst {0}
|
||||
ach.wood.desc = Kap {0} hout.
|
||||
ach.walls.name = Metselaar {0}
|
||||
ach.walls.desc = Richt {0} muren op.
|
||||
ach.rooms.name = Architect der Schaduwen {0}
|
||||
ach.rooms.desc = Bouw {0} kamervakken.
|
||||
ach.spells.name = Duistere Magiër {0}
|
||||
ach.spells.desc = Spreek {0} spreuken uit.
|
||||
ach.slaps.name = Strenge Hand {0}
|
||||
ach.slaps.desc = Geef je wezens {0} keer een mep. Ze vinden het heerlijk. Waarschijnlijk.
|
||||
ach.traps.name = Vallensmid {0}
|
||||
ach.traps.desc = Bestel {0} vallen en deuren in de smederij.
|
||||
ach.towers.name = Torenheer {0}
|
||||
ach.towers.desc = Bouw {0} torens.
|
||||
ach.drowned.name = Zwemles {0}
|
||||
ach.drowned.desc = Laat {0} helden in water of lava vallen.
|
||||
ach.burnt.name = Bruggenbrander {0}
|
||||
ach.burnt.desc = Brand {0} brugplanken af.
|
||||
ach.siege.name = Belegeringsmeester {0}
|
||||
ach.siege.desc = Bouw {0} belegeringswerktuigen.
|
||||
ach.captured.name = Cipier {0}
|
||||
ach.captured.desc = Neem {0} helden gevangen.
|
||||
ach.drawbridge.name = Hup, omhoog {0}
|
||||
ach.drawbridge.desc = Haal {0} keer ophaalbruggen op.
|
||||
ach.wins.name = Veroveraar {0}
|
||||
ach.wins.desc = Win {0} vrije spellen.
|
||||
ach.missions.name = Scherfzoeker {0}
|
||||
ach.missions.desc = Win {0} veldtochtmissies.
|
||||
ach.coop.name = Bondgenoot {0}
|
||||
ach.coop.desc = Win {0} co-opmissies.
|
||||
ach.minutes.name = Nachtuil {0}
|
||||
ach.minutes.desc = Speel {0} minuten lang.
|
||||
ach.flawless.name = Geen Schrammetje
|
||||
ach.flawless.desc = Win zonder één enkel wezen te verliezen.
|
||||
ach.speedrun.name = Voor de Thee Koud Is
|
||||
ach.speedrun.desc = Win binnen vijftien minuten.
|
||||
ach.campaign.name = De Ene Meester
|
||||
ach.campaign.desc = Win de laatste missie van de veldtocht.
|
||||
ach.surface.name = Zonnebrand
|
||||
ach.surface.desc = Win een spel dat tot aan de oppervlakte reikt.
|
||||
ach.defeat.name = Thee en Medeleven
|
||||
ach.defeat.desc = Verlies een spel. Krommevinger zet water op.
|
||||
ach.legend.name = Levende Legende
|
||||
ach.legend.desc = Een wezen bereikt niveau tien.
|
||||
ach.wyrm.name = Hier Zijn Draken
|
||||
ach.wyrm.desc = Een gloeddraak sluit zich aan bij je kerker.
|
||||
ach.fullhand.name = Handen Vol
|
||||
ach.fullhand.desc = Houd acht units tegelijk in je hand.
|
||||
@@ -1551,3 +1551,66 @@ coop.m20.goal = Beseir Mossgrim og alle skapningene hans.
|
||||
coop.m20.boss = Mossgrim er ikke mer. Mosen gror tilbake. Det gjør ikke han.
|
||||
coop.m20.t1 = Den lindormen i øst er Mossgrim. Ja, han har sluppet seg litt løs.
|
||||
coop.m20.t2 = Hver skapning dere beseirer, er én mindre som kommer til å fortelle denne historien feil.
|
||||
|
||||
# achievements and statistics
|
||||
menu.achievements = Prestasjoner
|
||||
ach.count = {0} av {1} prestasjoner låst opp · {2} minutter spilt
|
||||
ach.hidden = En hemmelighet. Krokfinger sier ingenting.
|
||||
msg.achievement = Prestasjon låst opp: {0}!
|
||||
end.stats2 = Helter {0} · Skapninger tapt {1} · Ruter gravd {2} · Gull {3} · Tre {4}
|
||||
ach.heroes.name = Heltenes Svøpe {0}
|
||||
ach.heroes.desc = Beseire {0} helter.
|
||||
ach.bosses.name = Forkjemperknuser {0}
|
||||
ach.bosses.desc = Felle {0} av ordenens forkjempere.
|
||||
ach.dug.name = Dypgraver {0}
|
||||
ach.dug.desc = Grav ut {0} ruter.
|
||||
ach.gold.name = Skattesamler {0}
|
||||
ach.gold.desc = Tjen {0} gull.
|
||||
ach.wood.name = Tømmerfyrste {0}
|
||||
ach.wood.desc = Felle {0} tre.
|
||||
ach.walls.name = Murer {0}
|
||||
ach.walls.desc = Reis {0} murer.
|
||||
ach.rooms.name = Skyggenes Arkitekt {0}
|
||||
ach.rooms.desc = Bygg {0} romruter.
|
||||
ach.spells.name = Mørk Trollmann {0}
|
||||
ach.spells.desc = Kast {0} trollformler.
|
||||
ach.slaps.name = Fast Hånd {0}
|
||||
ach.slaps.desc = Gi skapningene dine en ørefik {0} ganger. De elsker det. Sannsynligvis.
|
||||
ach.traps.name = Fellesmed {0}
|
||||
ach.traps.desc = Bestill {0} feller og dører i smia.
|
||||
ach.towers.name = Tårnherre {0}
|
||||
ach.towers.desc = Bygg {0} tårn.
|
||||
ach.drowned.name = Svømmeopplæring {0}
|
||||
ach.drowned.desc = La {0} helter falle i vann eller lava.
|
||||
ach.burnt.name = Brobrenner {0}
|
||||
ach.burnt.desc = Brenn ned {0} broplanker.
|
||||
ach.siege.name = Beleiringsmester {0}
|
||||
ach.siege.desc = Bygg {0} beleiringsmaskiner.
|
||||
ach.captured.name = Fangevokter {0}
|
||||
ach.captured.desc = Ta {0} helter til fange.
|
||||
ach.drawbridge.name = Opp med Den {0}
|
||||
ach.drawbridge.desc = Heis vindebroer {0} ganger.
|
||||
ach.wins.name = Erobrer {0}
|
||||
ach.wins.desc = Vinn {0} frie spill.
|
||||
ach.missions.name = Skårjeger {0}
|
||||
ach.missions.desc = Vinn {0} kampanjeoppdrag.
|
||||
ach.coop.name = Forbundsfelle {0}
|
||||
ach.coop.desc = Vinn {0} co-op-oppdrag.
|
||||
ach.minutes.name = Nattugle {0}
|
||||
ach.minutes.desc = Spill i {0} minutter.
|
||||
ach.flawless.name = Ikke en Skramme
|
||||
ach.flawless.desc = Vinn uten å miste en eneste skapning.
|
||||
ach.speedrun.name = Før Teen Blir Kald
|
||||
ach.speedrun.desc = Vinn innen femten minutter.
|
||||
ach.campaign.name = Den Ene Mesteren
|
||||
ach.campaign.desc = Vinn kampanjens siste oppdrag.
|
||||
ach.surface.name = Solbrent
|
||||
ach.surface.desc = Vinn et spill som når opp til overflaten.
|
||||
ach.defeat.name = Te og Medfølelse
|
||||
ach.defeat.desc = Tap et spill. Krokfinger setter på kjelen.
|
||||
ach.legend.name = Levende Legende
|
||||
ach.legend.desc = En skapning når nivå ti.
|
||||
ach.wyrm.name = Her Være Drager
|
||||
ach.wyrm.desc = En glødlindorm slutter seg til dungeonet ditt.
|
||||
ach.fullhand.name = Hendene Fulle
|
||||
ach.fullhand.desc = Hold åtte enheter i hånden samtidig.
|
||||
@@ -1552,3 +1552,66 @@ coop.m20.goal = Derrotem o Mossgrim e todas as suas criaturas.
|
||||
coop.m20.boss = O Mossgrim já não existe. O musgo volta a crescer. Ele não.
|
||||
coop.m20.t1 = Aquela serpe a este é o Mossgrim. Sim, ele desleixou-se.
|
||||
coop.m20.t2 = Cada criatura que derrotarem é menos uma para contar mal esta história.
|
||||
|
||||
# achievements and statistics
|
||||
menu.achievements = Conquistas
|
||||
ach.count = {0} de {1} conquistas desbloqueadas · {2} minutos jogados
|
||||
ach.hidden = Um segredo. O Dedotorto não conta.
|
||||
msg.achievement = Conquista desbloqueada: {0}!
|
||||
end.stats2 = Heróis {0} · Criaturas perdidas {1} · Casas escavadas {2} · Ouro {3} · Madeira {4}
|
||||
ach.heroes.name = Flagelo dos Heróis {0}
|
||||
ach.heroes.desc = Derrota {0} heróis.
|
||||
ach.bosses.name = Quebra-Campeões {0}
|
||||
ach.bosses.desc = Derruba {0} campeões da Ordem.
|
||||
ach.dug.name = Escavador Profundo {0}
|
||||
ach.dug.desc = Escava {0} casas.
|
||||
ach.gold.name = Açambarcador {0}
|
||||
ach.gold.desc = Ganha {0} de ouro.
|
||||
ach.wood.name = Senhor da Lenha {0}
|
||||
ach.wood.desc = Abate {0} de madeira.
|
||||
ach.walls.name = Pedreiro {0}
|
||||
ach.walls.desc = Ergue {0} paredes.
|
||||
ach.rooms.name = Arquiteto das Sombras {0}
|
||||
ach.rooms.desc = Constrói {0} casas de salas.
|
||||
ach.spells.name = Feiticeiro Sombrio {0}
|
||||
ach.spells.desc = Lança {0} feitiços.
|
||||
ach.slaps.name = Mão Firme {0}
|
||||
ach.slaps.desc = Esbofeteia as tuas criaturas {0} vezes. Elas adoram. Provavelmente.
|
||||
ach.traps.name = Armadilheiro {0}
|
||||
ach.traps.desc = Encomenda {0} armadilhas e portas na forja.
|
||||
ach.towers.name = Senhor das Torres {0}
|
||||
ach.towers.desc = Constrói {0} torres.
|
||||
ach.drowned.name = Aulas de Natação {0}
|
||||
ach.drowned.desc = Deixa {0} heróis cair na água ou na lava.
|
||||
ach.burnt.name = Incendiário de Pontes {0}
|
||||
ach.burnt.desc = Queima {0} tábuas de ponte.
|
||||
ach.siege.name = Mestre de Cerco {0}
|
||||
ach.siege.desc = Constrói {0} máquinas de cerco.
|
||||
ach.captured.name = Carcereiro {0}
|
||||
ach.captured.desc = Captura {0} heróis.
|
||||
ach.drawbridge.name = Para Cima! {0}
|
||||
ach.drawbridge.desc = Levanta pontes levadiças {0} vezes.
|
||||
ach.wins.name = Conquistador {0}
|
||||
ach.wins.desc = Vence {0} jogos livres.
|
||||
ach.missions.name = Caçador de Fragmentos {0}
|
||||
ach.missions.desc = Vence {0} missões da campanha.
|
||||
ach.coop.name = Membro da Liga {0}
|
||||
ach.coop.desc = Vence {0} missões cooperativas.
|
||||
ach.minutes.name = Ave Noturna {0}
|
||||
ach.minutes.desc = Joga durante {0} minutos.
|
||||
ach.flawless.name = Nem um Arranhão
|
||||
ach.flawless.desc = Vence sem perder uma única criatura.
|
||||
ach.speedrun.name = Antes que o Chá Arrefeça
|
||||
ach.speedrun.desc = Vence em menos de quinze minutos.
|
||||
ach.campaign.name = O Mestre Único
|
||||
ach.campaign.desc = Vence a última missão da campanha.
|
||||
ach.surface.name = Escaldão
|
||||
ach.surface.desc = Vence um jogo que chega à superfície.
|
||||
ach.defeat.name = Chá e Compaixão
|
||||
ach.defeat.desc = Perde um jogo. O Dedotorto põe a chaleira ao lume.
|
||||
ach.legend.name = Lenda Viva
|
||||
ach.legend.desc = Uma criatura atinge o nível dez.
|
||||
ach.wyrm.name = Aqui Há Dragões
|
||||
ach.wyrm.desc = Uma serpe de brasa junta-se à tua masmorra.
|
||||
ach.fullhand.name = Mãos Cheias
|
||||
ach.fullhand.desc = Segura oito unidades na mão ao mesmo tempo.
|
||||
@@ -1552,3 +1552,66 @@ coop.m20.goal = Învingeți-l pe Mossgrim și toate creaturile lui.
|
||||
coop.m20.boss = Mossgrim nu mai e. Mușchiul va crește la loc. El nu.
|
||||
coop.m20.t1 = Balaurul de la est e Mossgrim. Da, s-a lăsat cam pe tânjală.
|
||||
coop.m20.t2 = Fiecare creatură pe care o învingeți e una mai puțin care va povesti greșit această istorie.
|
||||
|
||||
# achievements and statistics
|
||||
menu.achievements = Realizări
|
||||
ach.count = {0} din {1} realizări deblocate · {2} minute jucate
|
||||
ach.hidden = Un secret. Crookfinger nu spune nimic.
|
||||
msg.achievement = Realizare deblocată: {0}!
|
||||
end.stats2 = Eroi {0} · Creaturi pierdute {1} · Dale săpate {2} · Aur {3} · Lemn {4}
|
||||
ach.heroes.name = Groaza eroilor {0}
|
||||
ach.heroes.desc = Învinge {0} eroi.
|
||||
ach.bosses.name = Zdrobitor de campioni {0}
|
||||
ach.bosses.desc = Doboară {0} campioni ai Ordinului.
|
||||
ach.dug.name = Săpător adânc {0}
|
||||
ach.dug.desc = Sapă {0} dale.
|
||||
ach.gold.name = Strângător de comori {0}
|
||||
ach.gold.desc = Câștigă {0} aur.
|
||||
ach.wood.name = Stăpânul lemnului {0}
|
||||
ach.wood.desc = Taie {0} lemn.
|
||||
ach.walls.name = Zidar {0}
|
||||
ach.walls.desc = Ridică {0} ziduri.
|
||||
ach.rooms.name = Arhitectul umbrelor {0}
|
||||
ach.rooms.desc = Construiește {0} dale de cameră.
|
||||
ach.spells.name = Vrăjitor întunecat {0}
|
||||
ach.spells.desc = Aruncă {0} vrăji.
|
||||
ach.slaps.name = Mână fermă {0}
|
||||
ach.slaps.desc = Dă-le creaturilor tale {0} palme. Le place. Probabil.
|
||||
ach.traps.name = Făurar de capcane {0}
|
||||
ach.traps.desc = Comandă {0} capcane și uși la forjă.
|
||||
ach.towers.name = Stăpânul turnurilor {0}
|
||||
ach.towers.desc = Construiește {0} turnuri.
|
||||
ach.drowned.name = Lecții de înot {0}
|
||||
ach.drowned.desc = Lasă {0} eroi să cadă în apă sau în lavă.
|
||||
ach.burnt.name = Incendiator de poduri {0}
|
||||
ach.burnt.desc = Arde {0} scânduri de pod.
|
||||
ach.siege.name = Maestru al asediului {0}
|
||||
ach.siege.desc = Construiește {0} mașinării de asediu.
|
||||
ach.captured.name = Temnicer {0}
|
||||
ach.captured.desc = Capturează {0} eroi.
|
||||
ach.drawbridge.name = Sus cu el {0}
|
||||
ach.drawbridge.desc = Ridică podurile mobile de {0} ori.
|
||||
ach.wins.name = Cuceritor {0}
|
||||
ach.wins.desc = Câștigă {0} jocuri libere.
|
||||
ach.missions.name = Căutător de cioburi {0}
|
||||
ach.missions.desc = Câștigă {0} misiuni de campanie.
|
||||
ach.coop.name = Membru al Ligii {0}
|
||||
ach.coop.desc = Câștigă {0} misiuni co-op.
|
||||
ach.minutes.name = Bufniță de noapte {0}
|
||||
ach.minutes.desc = Joacă {0} minute.
|
||||
ach.flawless.name = Nicio zgârietură
|
||||
ach.flawless.desc = Câștigă fără să pierzi nicio creatură.
|
||||
ach.speedrun.name = Până nu se răcește ceaiul
|
||||
ach.speedrun.desc = Câștigă în cel mult cincisprezece minute.
|
||||
ach.campaign.name = Singurul Stăpân
|
||||
ach.campaign.desc = Câștigă ultima misiune a campaniei.
|
||||
ach.surface.name = Arsură de soare
|
||||
ach.surface.desc = Câștigă un joc care ajunge la suprafață.
|
||||
ach.defeat.name = Ceai și compasiune
|
||||
ach.defeat.desc = Pierde un joc. Crookfinger pune de un ceai.
|
||||
ach.legend.name = Legendă vie
|
||||
ach.legend.desc = O creatură ajunge la nivelul zece.
|
||||
ach.wyrm.name = Aici sunt dragoni
|
||||
ach.wyrm.desc = Un balaur de jar se alătură temniței tale.
|
||||
ach.fullhand.name = Mâinile pline
|
||||
ach.fullhand.desc = Ține opt unități în mână deodată.
|
||||
@@ -1552,3 +1552,66 @@ coop.m20.goal = Победите Моссгрима и всех его суще
|
||||
coop.m20.boss = Моссгрима больше нет. Мох отрастёт. Он - нет.
|
||||
coop.m20.t1 = Этот змий на востоке - Моссгрим. Да, он себя запустил.
|
||||
coop.m20.t2 = Каждое побеждённое существо - на одного меньше, кто перескажет эту историю неправильно.
|
||||
|
||||
# achievements and statistics
|
||||
menu.achievements = Достижения
|
||||
ach.count = Открыто достижений: {0} из {1} · Сыграно минут: {2}
|
||||
ach.hidden = Секрет. Кривопал не расскажет.
|
||||
msg.achievement = Достижение открыто: {0}!
|
||||
end.stats2 = Герои {0} · Потеряно существ {1} · Выкопано клеток {2} · Золото {3} · Дерево {4}
|
||||
ach.heroes.name = Гроза героев {0}
|
||||
ach.heroes.desc = Победи героев: {0}.
|
||||
ach.bosses.name = Сокрушитель чемпионов {0}
|
||||
ach.bosses.desc = Повергни чемпионов ордена: {0}.
|
||||
ach.dug.name = Глубокий копатель {0}
|
||||
ach.dug.desc = Выкопай клеток: {0}.
|
||||
ach.gold.name = Скопидом {0}
|
||||
ach.gold.desc = Заработай {0} золота.
|
||||
ach.wood.name = Лесной барон {0}
|
||||
ach.wood.desc = Сруби {0} дерева.
|
||||
ach.walls.name = Каменщик {0}
|
||||
ach.walls.desc = Возведи стен: {0}.
|
||||
ach.rooms.name = Зодчий теней {0}
|
||||
ach.rooms.desc = Построй клеток комнат: {0}.
|
||||
ach.spells.name = Тёмный чародей {0}
|
||||
ach.spells.desc = Сотвори чар: {0}.
|
||||
ach.slaps.name = Твёрдая рука {0}
|
||||
ach.slaps.desc = Отшлёпай своих существ {0} раз. Им нравится. Наверное.
|
||||
ach.traps.name = Мастер ловушек {0}
|
||||
ach.traps.desc = Закажи в кузнице ловушек и дверей: {0}.
|
||||
ach.towers.name = Владыка башен {0}
|
||||
ach.towers.desc = Построй башен: {0}.
|
||||
ach.drowned.name = Уроки плавания {0}
|
||||
ach.drowned.desc = Пусть героев упадёт в воду или лаву: {0}.
|
||||
ach.burnt.name = Поджигатель мостов {0}
|
||||
ach.burnt.desc = Сожги досок мостов: {0}.
|
||||
ach.siege.name = Мастер осады {0}
|
||||
ach.siege.desc = Построй осадных машин: {0}.
|
||||
ach.captured.name = Тюремщик {0}
|
||||
ach.captured.desc = Возьми героев в плен: {0}.
|
||||
ach.drawbridge.name = Поднять мост! {0}
|
||||
ach.drawbridge.desc = Подними подъёмные мосты {0} раз.
|
||||
ach.wins.name = Завоеватель {0}
|
||||
ach.wins.desc = Выиграй свободных игр: {0}.
|
||||
ach.missions.name = Искатель осколков {0}
|
||||
ach.missions.desc = Выиграй миссий кампании: {0}.
|
||||
ach.coop.name = Член лиги {0}
|
||||
ach.coop.desc = Выиграй кооп-миссий: {0}.
|
||||
ach.minutes.name = Полуночник {0}
|
||||
ach.minutes.desc = Сыграй {0} минут.
|
||||
ach.flawless.name = Ни царапины
|
||||
ach.flawless.desc = Победи, не потеряв ни одного существа.
|
||||
ach.speedrun.name = Пока чай не остыл
|
||||
ach.speedrun.desc = Победи за пятнадцать минут.
|
||||
ach.campaign.name = Единый Властелин
|
||||
ach.campaign.desc = Выиграй последнюю миссию кампании.
|
||||
ach.surface.name = Солнечный ожог
|
||||
ach.surface.desc = Выиграй игру, которая доходит до поверхности.
|
||||
ach.defeat.name = Чай и сочувствие
|
||||
ach.defeat.desc = Проиграй игру. Кривопал ставит чайник.
|
||||
ach.legend.name = Живая легенда
|
||||
ach.legend.desc = Существо достигает десятого уровня.
|
||||
ach.wyrm.name = Здесь водятся драконы
|
||||
ach.wyrm.desc = Огненный змий присоединяется к твоему подземелью.
|
||||
ach.fullhand.name = Полные руки
|
||||
ach.fullhand.desc = Держи в руке восемь существ одновременно.
|
||||
@@ -1552,3 +1552,66 @@ coop.m20.goal = Porazte Mossgrima a všetky jeho tvory.
|
||||
coop.m20.boss = Mossgrim už nie je. Mach zase dorastie. On nie.
|
||||
coop.m20.t1 = Ten šarkan na východe je Mossgrim. Áno, trochu sa zanedbal.
|
||||
coop.m20.t2 = Každý porazený tvor je o jedného menej, kto bude tento príbeh rozprávať zle.
|
||||
|
||||
# achievements and statistics
|
||||
menu.achievements = Úspechy
|
||||
ach.count = Odomknuté {0} z {1} úspechov · {2} minút hrania
|
||||
ach.hidden = Tajomstvo. Krivoprst nič neprezradí.
|
||||
msg.achievement = Úspech odomknutý: {0}!
|
||||
end.stats2 = Hrdinovia {0} · Stratené tvory {1} · Vykopané polia {2} · Zlato {3} · Drevo {4}
|
||||
ach.heroes.name = Postrach hrdinov {0}
|
||||
ach.heroes.desc = Poraz {0} hrdinov.
|
||||
ach.bosses.name = Lamač šampiónov {0}
|
||||
ach.bosses.desc = Zvrhni {0} šampiónov rádu.
|
||||
ach.dug.name = Hlbinný kopáč {0}
|
||||
ach.dug.desc = Vykop {0} polí.
|
||||
ach.gold.name = Hromadič {0}
|
||||
ach.gold.desc = Zarob {0} zlata.
|
||||
ach.wood.name = Drevorubačský pán {0}
|
||||
ach.wood.desc = Vyrúbaj {0} dreva.
|
||||
ach.walls.name = Murár {0}
|
||||
ach.walls.desc = Postav {0} múrov.
|
||||
ach.rooms.name = Architekt tieňov {0}
|
||||
ach.rooms.desc = Postav {0} polí miestností.
|
||||
ach.spells.name = Temný mág {0}
|
||||
ach.spells.desc = Zošli {0} kúziel.
|
||||
ach.slaps.name = Pevná ruka {0}
|
||||
ach.slaps.desc = Daj svojim tvorom {0} záuch. Milujú to. Asi.
|
||||
ach.traps.name = Pascový kováč {0}
|
||||
ach.traps.desc = Objednaj {0} pascí a dverí vo vyhni.
|
||||
ach.towers.name = Pán veží {0}
|
||||
ach.towers.desc = Postav {0} veží.
|
||||
ach.drowned.name = Kurz plávania {0}
|
||||
ach.drowned.desc = Nechaj {0} hrdinov spadnúť do vody alebo lávy.
|
||||
ach.burnt.name = Podpaľač mostov {0}
|
||||
ach.burnt.desc = Spáľ {0} mostových dosiek.
|
||||
ach.siege.name = Majster obliehania {0}
|
||||
ach.siege.desc = Postav {0} obliehacích strojov.
|
||||
ach.captured.name = Žalárnik {0}
|
||||
ach.captured.desc = Zajmi {0} hrdinov.
|
||||
ach.drawbridge.name = Hore s ním {0}
|
||||
ach.drawbridge.desc = Vytiahni padacie mosty {0}-krát.
|
||||
ach.wins.name = Dobyvateľ {0}
|
||||
ach.wins.desc = Vyhraj {0} voľných hier.
|
||||
ach.missions.name = Hľadač črepov {0}
|
||||
ach.missions.desc = Vyhraj {0} misií kampane.
|
||||
ach.coop.name = Člen ligy {0}
|
||||
ach.coop.desc = Vyhraj {0} kooperatívnych misií.
|
||||
ach.minutes.name = Nočná sova {0}
|
||||
ach.minutes.desc = Hraj {0} minút.
|
||||
ach.flawless.name = Ani škrabanec
|
||||
ach.flawless.desc = Vyhraj bez straty jediného tvora.
|
||||
ach.speedrun.name = Kým čaj nevychladne
|
||||
ach.speedrun.desc = Vyhraj do pätnástich minút.
|
||||
ach.campaign.name = Jediný majster
|
||||
ach.campaign.desc = Vyhraj poslednú misiu kampane.
|
||||
ach.surface.name = Úpal
|
||||
ach.surface.desc = Vyhraj hru, ktorá siaha až na povrch.
|
||||
ach.defeat.name = Čaj a súcit
|
||||
ach.defeat.desc = Prehraj hru. Krivoprst pristaví vodu na čaj.
|
||||
ach.legend.name = Živá legenda
|
||||
ach.legend.desc = Tvor dosiahne úroveň desať.
|
||||
ach.wyrm.name = Tu sú draci
|
||||
ach.wyrm.desc = Žeravý šarkan sa pridá k tvojej kobke.
|
||||
ach.fullhand.name = Plné ruky práce
|
||||
ach.fullhand.desc = Drž v ruke naraz osem jednotiek.
|
||||
@@ -1552,3 +1552,66 @@ coop.m20.goal = Поразите Мосгрима и сва његова ств
|
||||
coop.m20.boss = Мосгрима више нема. Маховина ће поново израсти. Он неће.
|
||||
coop.m20.t1 = Онај змај на истоку је Мосгрим. Да, запустио се.
|
||||
coop.m20.t2 = Свако створење које поразите једно је мање које ће ову причу погрешно испричати.
|
||||
|
||||
# achievements and statistics
|
||||
menu.achievements = Достигнућа
|
||||
ach.count = Откључано {0} од {1} достигнућа · {2} минута игре
|
||||
ach.hidden = Тајна. Кривопрст не говори.
|
||||
msg.achievement = Достигнуће откључано: {0}!
|
||||
end.stats2 = Јунаци {0} · Изгубљена створења {1} · Ископана поља {2} · Злато {3} · Дрво {4}
|
||||
ach.heroes.name = Страх јунака {0}
|
||||
ach.heroes.desc = Порази {0} јунака.
|
||||
ach.bosses.name = Ломитељ првака {0}
|
||||
ach.bosses.desc = Обори {0} првака реда.
|
||||
ach.dug.name = Дубоки копач {0}
|
||||
ach.dug.desc = Ископај {0} поља.
|
||||
ach.gold.name = Гомилач {0}
|
||||
ach.gold.desc = Заради {0} злата.
|
||||
ach.wood.name = Господар дрва {0}
|
||||
ach.wood.desc = Посеци {0} дрва.
|
||||
ach.walls.name = Зидар {0}
|
||||
ach.walls.desc = Подигни {0} зидова.
|
||||
ach.rooms.name = Архитекта сенки {0}
|
||||
ach.rooms.desc = Изгради {0} поља соба.
|
||||
ach.spells.name = Мрачни чаробњак {0}
|
||||
ach.spells.desc = Баци {0} чини.
|
||||
ach.slaps.name = Чврста рука {0}
|
||||
ach.slaps.desc = Ошамари своја створења {0} пута. Обожавају то. Ваљда.
|
||||
ach.traps.name = Ковач замки {0}
|
||||
ach.traps.desc = Наручи {0} замки и врата у ковачници.
|
||||
ach.towers.name = Господар кула {0}
|
||||
ach.towers.desc = Изгради {0} кула.
|
||||
ach.drowned.name = Курс пливања {0}
|
||||
ach.drowned.desc = Пусти {0} јунака да падну у воду или лаву.
|
||||
ach.burnt.name = Палитељ мостова {0}
|
||||
ach.burnt.desc = Спали {0} дасака моста.
|
||||
ach.siege.name = Мајстор опсаде {0}
|
||||
ach.siege.desc = Изгради {0} опсадних справа.
|
||||
ach.captured.name = Тамничар {0}
|
||||
ach.captured.desc = Зароби {0} јунака.
|
||||
ach.drawbridge.name = Горе с њим {0}
|
||||
ach.drawbridge.desc = Подигни покретне мостове {0} пута.
|
||||
ach.wins.name = Освајач {0}
|
||||
ach.wins.desc = Победи у {0} слободних игара.
|
||||
ach.missions.name = Трагач за крхотинама {0}
|
||||
ach.missions.desc = Победи у {0} мисија кампање.
|
||||
ach.coop.name = Члан савеза {0}
|
||||
ach.coop.desc = Победи у {0} ко-оп мисија.
|
||||
ach.minutes.name = Ноћна птица {0}
|
||||
ach.minutes.desc = Играј {0} минута.
|
||||
ach.flawless.name = Ни огреботине
|
||||
ach.flawless.desc = Победи без губитка иједног створења.
|
||||
ach.speedrun.name = Пре него што се чај охлади
|
||||
ach.speedrun.desc = Победи у року од петнаест минута.
|
||||
ach.campaign.name = Једини господар
|
||||
ach.campaign.desc = Победи у последњој мисији кампање.
|
||||
ach.surface.name = Опекотина од сунца
|
||||
ach.surface.desc = Победи у игри која сеже до површине.
|
||||
ach.defeat.name = Чај и саосећање
|
||||
ach.defeat.desc = Изгуби игру. Кривопрст ставља воду за чај.
|
||||
ach.legend.name = Жива легенда
|
||||
ach.legend.desc = Створење достиже ниво десет.
|
||||
ach.wyrm.name = Овде су змајеви
|
||||
ach.wyrm.desc = Жарки змај се придружује твојој тамници.
|
||||
ach.fullhand.name = Пуне руке посла
|
||||
ach.fullhand.desc = Држи осам јединица у руци одједном.
|
||||
@@ -1552,3 +1552,66 @@ coop.m20.goal = Besegra Mossgrim och alla hans varelser.
|
||||
coop.m20.boss = Mossgrim finns inte mer. Mossan växer tillbaka. Det gör inte han.
|
||||
coop.m20.t1 = Den där lindormen i öster är Mossgrim. Ja, han har släppt taget om sig själv.
|
||||
coop.m20.t2 = Varje varelse ni besegrar är en mindre som berättar den här historien fel.
|
||||
|
||||
# achievements and statistics
|
||||
menu.achievements = Prestationer
|
||||
ach.count = {0} av {1} prestationer upplåsta · {2} minuter spelade
|
||||
ach.hidden = En hemlighet. Crookfinger säger ingenting.
|
||||
msg.achievement = Prestation upplåst: {0}!
|
||||
end.stats2 = Hjältar {0} · Förlorade varelser {1} · Grävda rutor {2} · Guld {3} · Trä {4}
|
||||
ach.heroes.name = Hjälteskräck {0}
|
||||
ach.heroes.desc = Besegra {0} hjältar.
|
||||
ach.bosses.name = Kämpebrytare {0}
|
||||
ach.bosses.desc = Fäll {0} av ordens kämpar.
|
||||
ach.dug.name = Djupgrävare {0}
|
||||
ach.dug.desc = Gräv ut {0} rutor.
|
||||
ach.gold.name = Skattsamlare {0}
|
||||
ach.gold.desc = Tjäna {0} guld.
|
||||
ach.wood.name = Timmerfurste {0}
|
||||
ach.wood.desc = Fäll {0} trä.
|
||||
ach.walls.name = Murare {0}
|
||||
ach.walls.desc = Res {0} väggar.
|
||||
ach.rooms.name = Skuggornas arkitekt {0}
|
||||
ach.rooms.desc = Bygg {0} rumsrutor.
|
||||
ach.spells.name = Mörk besvärjare {0}
|
||||
ach.spells.desc = Kasta {0} trollformler.
|
||||
ach.slaps.name = Fast hand {0}
|
||||
ach.slaps.desc = Ge dina varelser {0} örfilar. De älskar det. Troligen.
|
||||
ach.traps.name = Fällsmed {0}
|
||||
ach.traps.desc = Beställ {0} fällor och dörrar i smedjan.
|
||||
ach.towers.name = Tornherre {0}
|
||||
ach.towers.desc = Bygg {0} torn.
|
||||
ach.drowned.name = Simskola {0}
|
||||
ach.drowned.desc = Låt {0} hjältar falla i vatten eller lava.
|
||||
ach.burnt.name = Brobrännare {0}
|
||||
ach.burnt.desc = Bränn ner {0} broplankor.
|
||||
ach.siege.name = Belägringsmästare {0}
|
||||
ach.siege.desc = Bygg {0} belägringsmaskiner.
|
||||
ach.captured.name = Fångvaktare {0}
|
||||
ach.captured.desc = Ta {0} hjältar till fånga.
|
||||
ach.drawbridge.name = Upp med den {0}
|
||||
ach.drawbridge.desc = Hissa upp vindbryggor {0} gånger.
|
||||
ach.wins.name = Erövrare {0}
|
||||
ach.wins.desc = Vinn {0} fria spel.
|
||||
ach.missions.name = Skärvsökare {0}
|
||||
ach.missions.desc = Vinn {0} kampanjuppdrag.
|
||||
ach.coop.name = Förbundsmedlem {0}
|
||||
ach.coop.desc = Vinn {0} co-op-uppdrag.
|
||||
ach.minutes.name = Nattuggla {0}
|
||||
ach.minutes.desc = Spela i {0} minuter.
|
||||
ach.flawless.name = Inte en skråma
|
||||
ach.flawless.desc = Vinn utan att förlora en enda varelse.
|
||||
ach.speedrun.name = Innan teet kallnar
|
||||
ach.speedrun.desc = Vinn inom femton minuter.
|
||||
ach.campaign.name = Den ende mästaren
|
||||
ach.campaign.desc = Vinn kampanjens sista uppdrag.
|
||||
ach.surface.name = Solbränna
|
||||
ach.surface.desc = Vinn ett spel som når upp till ytan.
|
||||
ach.defeat.name = Te och medkänsla
|
||||
ach.defeat.desc = Förlora ett spel. Crookfinger sätter på tevattnet.
|
||||
ach.legend.name = Levande legend
|
||||
ach.legend.desc = En varelse når nivå tio.
|
||||
ach.wyrm.name = Här finns drakar
|
||||
ach.wyrm.desc = En glödlindorm ansluter sig till din håla.
|
||||
ach.fullhand.name = Fullt upp
|
||||
ach.fullhand.desc = Håll åtta enheter i handen samtidigt.
|
||||
@@ -562,6 +562,43 @@ async fn list_campaigns(State(st): State<AppState>, headers: HeaderMap) -> Respo
|
||||
Json(campaign_infos(&st.campaign_dir(&u.name)).await).into_response()
|
||||
}
|
||||
|
||||
// the player's profile: lifetime statistics and achievements (an opaque JSON object; the client
|
||||
// merges it with what it kept locally)
|
||||
const MAX_PROFILE: usize = 64 * 1024;
|
||||
|
||||
async fn get_profile(State(st): State<AppState>, headers: HeaderMap) -> Response {
|
||||
let u = match st.auth(&headers).await {
|
||||
Ok(u) => u,
|
||||
Err(r) => return r,
|
||||
};
|
||||
match tokio::fs::read(st.user_dir(&u.name).join("profile.json")).await {
|
||||
Ok(b) => ([("content-type", "application/json")], b).into_response(),
|
||||
Err(_) => ([("content-type", "application/json")], b"{}".to_vec()).into_response(),
|
||||
}
|
||||
}
|
||||
|
||||
async fn put_profile(State(st): State<AppState>, headers: HeaderMap, body: Bytes) -> Response {
|
||||
let u = match st.auth(&headers).await {
|
||||
Ok(u) => u,
|
||||
Err(r) => return r,
|
||||
};
|
||||
if body.len() > MAX_PROFILE {
|
||||
return err(StatusCode::PAYLOAD_TOO_LARGE, "profile too large");
|
||||
}
|
||||
if limited(&st.writes, WRITES_PER_MINUTE).await {
|
||||
return err(StatusCode::TOO_MANY_REQUESTS, "too many requests");
|
||||
}
|
||||
if !serde_json::from_slice::<serde_json::Value>(&body).map_or(false, |v| v.is_object()) {
|
||||
return err(StatusCode::BAD_REQUEST, "not a JSON object");
|
||||
}
|
||||
let dir = st.user_dir(&u.name);
|
||||
let _ = tokio::fs::create_dir_all(&dir).await;
|
||||
match tokio::fs::write(dir.join("profile.json"), &body).await {
|
||||
Ok(_) => StatusCode::NO_CONTENT.into_response(),
|
||||
Err(_) => err(StatusCode::INTERNAL_SERVER_ERROR, "write failed"),
|
||||
}
|
||||
}
|
||||
|
||||
async fn get_campaign(State(st): State<AppState>, headers: HeaderMap, Path(id): Path<String>) -> Response {
|
||||
let u = match st.auth(&headers).await {
|
||||
Ok(u) => u,
|
||||
@@ -754,6 +791,7 @@ fn router(state: AppState) -> Router {
|
||||
.route("/api/users", get(list_users).post(create_user))
|
||||
.route("/api/users/{name}", delete(delete_user))
|
||||
.route("/api/netplay/ws", get(netplay::netplay_ws))
|
||||
.route("/api/profile", get(get_profile).put(put_profile))
|
||||
.route("/api/saves", get(list).post(create))
|
||||
.route("/api/saves/{id}", get(get_save).put(overwrite).delete(delete_save))
|
||||
.route("/api/campaigns", get(list_campaigns).post(create_campaign).layer(campaign_limit()))
|
||||
|
||||
@@ -0,0 +1,310 @@
|
||||
// Achievements: a lifetime profile of the player (totals of every game's statistics, wins,
|
||||
// minutes played) and 64 achievements - tiers of counters (bronze, silver, gold, ...) and a few
|
||||
// special, partly hidden ones. Kept in the browser; after a login merged with the server's copy
|
||||
// (the larger counter wins, unlocked stays unlocked) and stored there too.
|
||||
|
||||
use crate::game::*;
|
||||
use crate::stats as k;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
pub const PROFILE_KEY: &str = "dc.profile";
|
||||
|
||||
// counters of the profile: the game statistics (stats.rs) and a few of its own
|
||||
pub const STAT_NAMES: [&str; k::COUNTERS] = [
|
||||
"heroes", "bosses", "lost", "dug", "wood", "walls", "rooms", "spells", "slaps", "traps", "towers", "drowned", "burnt", "siege", "captured", "gold", "drawbridge",
|
||||
];
|
||||
|
||||
// tiered achievements: id (= counter), thresholds, icon cell
|
||||
pub const TIERS: [(&str, &[f64], u32); 20] = [
|
||||
("heroes", &[1.0, 50.0, 250.0, 1000.0], crate::gfx::I_ARROW),
|
||||
("bosses", &[1.0, 5.0, 20.0], crate::gfx::I_RUNE),
|
||||
("dug", &[100.0, 1000.0, 10000.0], crate::gfx::I_PICK),
|
||||
("gold", &[10000.0, 100000.0, 1000000.0], crate::gfx::I_SELL),
|
||||
("wood", &[50.0, 500.0, 2000.0], crate::gfx::I_BRIDGE),
|
||||
("walls", &[10.0, 100.0], crate::gfx::I_WALL),
|
||||
("rooms", &[50.0, 500.0, 2000.0], crate::gfx::I_STEEL),
|
||||
("spells", &[10.0, 100.0, 500.0], crate::gfx::I_BOLT),
|
||||
("slaps", &[10.0, 100.0, 1000.0], crate::gfx::I_HAND),
|
||||
("traps", &[5.0, 50.0, 200.0], crate::gfx::I_TRAPS),
|
||||
("towers", &[5.0, 50.0], crate::gfx::I_CANNON),
|
||||
("drowned", &[1.0, 25.0, 100.0], crate::gfx::I_FREEZE),
|
||||
("burnt", &[5.0, 50.0], crate::gfx::I_DRAWBRIDGE),
|
||||
("siege", &[1.0, 10.0], crate::gfx::I_CATAPULT),
|
||||
("captured", &[1.0, 25.0, 100.0], crate::gfx::I_SUMMON),
|
||||
("drawbridge", &[1.0, 50.0], crate::gfx::I_DRAWBRIDGE),
|
||||
("wins", &[1.0, 10.0, 50.0], crate::gfx::I_FLAG),
|
||||
("missions", &[1.0, 10.0, 30.0], crate::gfx::I_MAGIC),
|
||||
("coop", &[1.0, 10.0, 20.0], crate::gfx::I_HEAL),
|
||||
("minutes", &[60.0, 600.0, 3000.0], crate::gfx::I_SPELLS),
|
||||
];
|
||||
|
||||
// special achievements: id, hidden until unlocked, icon cell
|
||||
pub const SPECIAL: [(&str, bool, u32); 8] = [
|
||||
("flawless", false, crate::gfx::I_HEAL),
|
||||
("speedrun", false, crate::gfx::I_BOLT),
|
||||
("campaign", false, crate::gfx::I_RUNE),
|
||||
("surface", false, crate::gfx::I_WALL),
|
||||
("defeat", true, crate::gfx::I_SELL),
|
||||
("legend", false, crate::gfx::I_ARROW),
|
||||
("wyrm", true, crate::gfx::I_SUMMON),
|
||||
("fullhand", true, crate::gfx::I_HAND),
|
||||
];
|
||||
|
||||
// number of achievements in all
|
||||
#[cfg(test)]
|
||||
pub fn count() -> usize {
|
||||
TIERS.iter().map(|t| t.1.len()).sum::<usize>() + SPECIAL.len()
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq)]
|
||||
pub struct Profile {
|
||||
#[serde(default)]
|
||||
pub totals: BTreeMap<String, f64>,
|
||||
#[serde(default)]
|
||||
pub unlocked: BTreeMap<String, u64>, // achievement id ("heroes.2", "flawless") -> unix time
|
||||
}
|
||||
|
||||
impl Profile {
|
||||
pub fn total(&self, id: &str) -> f64 {
|
||||
self.totals.get(id).copied().unwrap_or(0.0)
|
||||
}
|
||||
|
||||
// the larger counter wins, unlocked stays unlocked (with the earlier date)
|
||||
pub fn merge(&mut self, other: &Profile) {
|
||||
for (k, v) in &other.totals {
|
||||
let e = self.totals.entry(k.clone()).or_insert(0.0);
|
||||
*e = e.max(*v);
|
||||
}
|
||||
for (k, t) in &other.unlocked {
|
||||
let e = self.unlocked.entry(k.clone()).or_insert(*t);
|
||||
*e = (*e).min(*t);
|
||||
}
|
||||
}
|
||||
|
||||
// unlocks what the totals have reached; returns the new ones
|
||||
pub fn check_tiers(&mut self, now: u64) -> Vec<String> {
|
||||
let mut new = Vec::new();
|
||||
for (id, steps, _) in TIERS.iter() {
|
||||
let v = self.total(id);
|
||||
for (t, &need) in steps.iter().enumerate() {
|
||||
let key = format!("{}.{}", id, t + 1);
|
||||
if v >= need && !self.unlocked.contains_key(&key) {
|
||||
self.unlocked.insert(key.clone(), now);
|
||||
new.push(key);
|
||||
}
|
||||
}
|
||||
}
|
||||
new
|
||||
}
|
||||
|
||||
pub fn unlock(&mut self, id: &str, now: u64) -> bool {
|
||||
if self.unlocked.contains_key(id) {
|
||||
return false;
|
||||
}
|
||||
self.unlocked.insert(id.to_string(), now);
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
// what the tracker needs to know about the running game besides the game itself
|
||||
pub struct Context {
|
||||
pub coop: bool,
|
||||
pub editor_test: bool, // test play from the editor counts nothing
|
||||
}
|
||||
|
||||
// Follows the running game: adds what happened to the profile every few seconds and unlocks
|
||||
pub struct Tracker {
|
||||
pub profile: Profile,
|
||||
flushed: Vec<f32>,
|
||||
last_time: f32,
|
||||
ended: bool,
|
||||
t: f32,
|
||||
pub dirty: bool, // changed since the last store
|
||||
pub upload: bool, // something new for the server
|
||||
}
|
||||
|
||||
impl Tracker {
|
||||
pub fn new(profile: Profile) -> Tracker {
|
||||
Tracker { profile, flushed: vec![0.0; k::COUNTERS], last_time: 0.0, ended: false, t: 0.0, dirty: false, upload: false }
|
||||
}
|
||||
|
||||
// a different game started (new, loaded): nothing of it has been counted yet
|
||||
fn restart(&mut self, g: &Game) {
|
||||
self.flushed = (0..k::COUNTERS).map(|i| g.stats.get(i)).collect();
|
||||
self.ended = g.state != 0;
|
||||
}
|
||||
|
||||
// called every frame; returns achievements unlocked just now
|
||||
pub fn update(&mut self, g: &Game, ctx: &Context, dt: f32, now: u64) -> Vec<String> {
|
||||
if g.demo.is_some() || ctx.editor_test {
|
||||
self.last_time = 0.0;
|
||||
return Vec::new();
|
||||
}
|
||||
// a loaded or new game: its old counters are not counted again
|
||||
if g.time + 0.5 < self.last_time || self.last_time == 0.0 {
|
||||
self.restart(g);
|
||||
}
|
||||
self.last_time = g.time.max(0.01);
|
||||
self.t += dt;
|
||||
let ended_now = g.state != 0 && !self.ended;
|
||||
if self.t < 5.0 && !ended_now {
|
||||
return Vec::new();
|
||||
}
|
||||
let played = self.t;
|
||||
self.t = 0.0;
|
||||
let mut new = Vec::new();
|
||||
// the counters of the game
|
||||
for i in 0..k::COUNTERS {
|
||||
let cur = g.stats.get(i);
|
||||
let d = cur - self.flushed[i];
|
||||
if d > 0.0 {
|
||||
*self.profile.totals.entry(STAT_NAMES[i].to_string()).or_insert(0.0) += d as f64;
|
||||
self.flushed[i] = cur;
|
||||
self.dirty = true;
|
||||
}
|
||||
}
|
||||
if g.state == 0 {
|
||||
*self.profile.totals.entry("minutes".into()).or_insert(0.0) += (played / 60.0) as f64;
|
||||
self.dirty = true;
|
||||
}
|
||||
// the special ones that can happen any time
|
||||
if g.ents.iter().any(|e| e.alive && e.creature() && e.level >= 10) && self.profile.unlock("legend", now) {
|
||||
new.push("legend".to_string());
|
||||
}
|
||||
if g.ents.iter().any(|e| e.alive && e.creature() && e.kind == EK::Wyrm) && self.profile.unlock("wyrm", now) {
|
||||
new.push("wyrm".to_string());
|
||||
}
|
||||
if g.hand.len() >= 8 && self.profile.unlock("fullhand", now) {
|
||||
new.push("fullhand".to_string());
|
||||
}
|
||||
// the end of the game
|
||||
if ended_now {
|
||||
self.ended = true;
|
||||
if g.state == 1 {
|
||||
let key = if ctx.coop {
|
||||
"coop"
|
||||
} else if g.mission.is_some() {
|
||||
"missions"
|
||||
} else {
|
||||
"wins"
|
||||
};
|
||||
*self.profile.totals.entry(key.into()).or_insert(0.0) += 1.0;
|
||||
if g.stats.get(k::LOST) == 0.0 && self.profile.unlock("flawless", now) {
|
||||
new.push("flawless".into());
|
||||
}
|
||||
if g.time < 15.0 * 60.0 && self.profile.unlock("speedrun", now) {
|
||||
new.push("speedrun".into());
|
||||
}
|
||||
let last = crate::campaign::missions().len().saturating_sub(1);
|
||||
if !ctx.coop && g.mission.as_ref().map_or(false, |m| m.index == last) && self.profile.unlock("campaign", now) {
|
||||
new.push("campaign".into());
|
||||
}
|
||||
if !g.map.sky.is_empty() && self.profile.unlock("surface", now) {
|
||||
new.push("surface".into());
|
||||
}
|
||||
} else if self.profile.unlock("defeat", now) {
|
||||
new.push("defeat".into());
|
||||
}
|
||||
self.dirty = true;
|
||||
}
|
||||
new.extend(self.profile.check_tiers(now));
|
||||
if !new.is_empty() {
|
||||
self.dirty = true;
|
||||
self.upload = true;
|
||||
}
|
||||
new
|
||||
}
|
||||
}
|
||||
|
||||
// the name and description of an achievement ("heroes.2" or "flawless") in the current language
|
||||
pub fn texts(id: &str) -> (String, String) {
|
||||
use crate::i18n::{tr, trf};
|
||||
match id.split_once('.') {
|
||||
Some((cat, tier)) => {
|
||||
let t: usize = tier.parse().unwrap_or(1);
|
||||
let need = TIERS.iter().find(|x| x.0 == cat).and_then(|x| x.1.get(t - 1)).copied().unwrap_or(0.0);
|
||||
let roman = ["I", "II", "III", "IV", "V"][(t - 1).min(4)];
|
||||
(trf(&format!("ach.{}.name", cat), &[roman.to_string()]), trf(&format!("ach.{}.desc", cat), &[fmt_count(need)]))
|
||||
}
|
||||
None => (tr(&format!("ach.{}.name", id)), tr(&format!("ach.{}.desc", id))),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn fmt_count(v: f64) -> String {
|
||||
let n = v.round() as i64;
|
||||
let s = n.to_string();
|
||||
let mut out = String::new();
|
||||
for (i, c) in s.chars().enumerate() {
|
||||
if i > 0 && (s.len() - i) % 3 == 0 {
|
||||
out.push('.');
|
||||
}
|
||||
out.push(c);
|
||||
}
|
||||
out
|
||||
}
|
||||
|
||||
// every achievement in display order: (id, hidden, icon, tier 0..)
|
||||
pub fn all() -> Vec<(String, bool, u32, usize)> {
|
||||
let mut v = Vec::new();
|
||||
for (id, steps, icon) in TIERS.iter() {
|
||||
for t in 0..steps.len() {
|
||||
v.push((format!("{}.{}", id, t + 1), false, *icon, t));
|
||||
}
|
||||
}
|
||||
for (id, hidden, icon) in SPECIAL.iter() {
|
||||
v.push((id.to_string(), *hidden, *icon, 9));
|
||||
}
|
||||
v
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn more_than_fifty_achievements() {
|
||||
assert!(count() >= 50, "{}", count());
|
||||
assert_eq!(all().len(), count());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn the_tracker_counts_a_game_and_unlocks() {
|
||||
let mut g = Game::new(3, 64, 64);
|
||||
let mut t = Tracker::new(Profile::default());
|
||||
let ctx = Context { coop: false, editor_test: false };
|
||||
g.time = 1.0;
|
||||
t.update(&g, &ctx, 0.1, 1);
|
||||
g.count(k::HEROES, 3.0);
|
||||
g.count(k::DUG, 150.0);
|
||||
g.time = 10.0;
|
||||
let new = t.update(&g, &ctx, 6.0, 2);
|
||||
assert!(new.contains(&"heroes.1".to_string()) && new.contains(&"dug.1".to_string()), "{:?}", new);
|
||||
assert_eq!(t.profile.total("heroes"), 3.0);
|
||||
// the same counters are not counted twice
|
||||
g.time = 20.0;
|
||||
t.update(&g, &ctx, 6.0, 3);
|
||||
assert_eq!(t.profile.total("heroes"), 3.0);
|
||||
// a won free game without losses
|
||||
g.state = 1;
|
||||
let new = t.update(&g, &ctx, 0.1, 4);
|
||||
assert!(new.contains(&"wins.1".to_string()) && new.contains(&"flawless".to_string()), "{:?}", new);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn profiles_merge() {
|
||||
let mut a = Profile::default();
|
||||
a.totals.insert("heroes".into(), 10.0);
|
||||
a.unlocked.insert("heroes.1".into(), 5);
|
||||
let mut b = Profile::default();
|
||||
b.totals.insert("heroes".into(), 7.0);
|
||||
b.totals.insert("dug".into(), 100.0);
|
||||
b.unlocked.insert("heroes.1".into(), 3);
|
||||
b.unlocked.insert("dug.1".into(), 4);
|
||||
a.merge(&b);
|
||||
assert_eq!(a.total("heroes"), 10.0);
|
||||
assert_eq!(a.total("dug"), 100.0);
|
||||
assert_eq!(a.unlocked["heroes.1"], 3);
|
||||
assert!(a.unlocked.contains_key("dug.1"));
|
||||
}
|
||||
}
|
||||
@@ -110,6 +110,7 @@ impl Game {
|
||||
return;
|
||||
}
|
||||
self.ents[j].boss = 0;
|
||||
self.count_local(crate::stats::BOSSES);
|
||||
let got = self.add_gold(BOSS_GOLD);
|
||||
self.rune_free += 1;
|
||||
let (x, y) = (self.ents[j].x, self.ents[j].y);
|
||||
|
||||
@@ -128,6 +128,7 @@ impl Game {
|
||||
}
|
||||
if t >= BURN_TIME {
|
||||
self.bridgework.burning.swap_remove(k);
|
||||
self.count_local(crate::stats::BURNT);
|
||||
self.bridge_falls(i);
|
||||
continue;
|
||||
}
|
||||
@@ -262,6 +263,7 @@ impl Game {
|
||||
t.res = (t.res & DB_LAVA) | state;
|
||||
}
|
||||
if raise {
|
||||
self.count(crate::stats::DRAWBRIDGE, 1.0);
|
||||
// whoever stands on it jumps to the bank (or falls)
|
||||
for &g in &group {
|
||||
self.ground_lost(g);
|
||||
|
||||
@@ -80,6 +80,8 @@ enum Req {
|
||||
CampaignPublish,
|
||||
PublicList,
|
||||
PublicLoad,
|
||||
ProfileLoad,
|
||||
ProfileSave,
|
||||
}
|
||||
|
||||
// the logged-in account
|
||||
@@ -124,6 +126,7 @@ pub struct Cloud {
|
||||
pub public_listed: bool,
|
||||
pub campaign_error: Option<&'static str>, // language key of the last failed campaign request
|
||||
loaded_campaign: Option<String>,
|
||||
pub profile_loaded: Option<String>, // the profile from the server, waiting to be merged
|
||||
saved_campaign: Option<CampaignInfo>,
|
||||
pending: Option<(u32, Req)>,
|
||||
save_name: String,
|
||||
@@ -176,6 +179,7 @@ impl Cloud {
|
||||
public_listed: false,
|
||||
campaign_error: None,
|
||||
loaded_campaign: None,
|
||||
profile_loaded: None,
|
||||
saved_campaign: None,
|
||||
pending: Some((id, Req::Health)),
|
||||
save_name: String::new(),
|
||||
@@ -330,6 +334,13 @@ impl Cloud {
|
||||
CloudEvent::Error(error_key(res.status))
|
||||
}
|
||||
}
|
||||
Req::ProfileLoad => {
|
||||
if ok {
|
||||
self.profile_loaded = Some(String::from_utf8_lossy(&res.body).into_owned());
|
||||
}
|
||||
CloudEvent::None
|
||||
}
|
||||
Req::ProfileSave => CloudEvent::None,
|
||||
Req::CreateUser | Req::DeleteUser => {
|
||||
if ok {
|
||||
self.load_users();
|
||||
@@ -470,6 +481,15 @@ impl Cloud {
|
||||
}
|
||||
|
||||
// JSON text of a campaign loaded by load_campaign() or load_public_campaign()
|
||||
// the profile (statistics and achievements) of the logged-in player
|
||||
pub fn load_profile(&mut self) -> bool {
|
||||
self.session.is_some() && self.start("GET", "profile", &[], Req::ProfileLoad)
|
||||
}
|
||||
|
||||
pub fn save_profile(&mut self, json: &str) -> bool {
|
||||
self.session.is_some() && self.start("PUT", "profile", json.as_bytes(), Req::ProfileSave)
|
||||
}
|
||||
|
||||
pub fn take_loaded_campaign(&mut self) -> Option<String> {
|
||||
self.loaded_campaign.take()
|
||||
}
|
||||
|
||||
+30
@@ -304,6 +304,11 @@ pub struct Ent {
|
||||
pub disp_y: f32,
|
||||
}
|
||||
|
||||
// units of the neutral dwarves count for nobody
|
||||
fn e_is_dwarf_side(side: u8) -> bool {
|
||||
side == crate::dwarves::DWARF_SIDE
|
||||
}
|
||||
|
||||
fn yes() -> bool {
|
||||
true
|
||||
}
|
||||
@@ -596,6 +601,8 @@ pub struct Game {
|
||||
#[serde(default)]
|
||||
pub bridgework: crate::bridges::BridgeWork, // burning and worn bridges
|
||||
#[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)
|
||||
#[serde(default)]
|
||||
pub wood: f32, // the second resource (see wood.rs)
|
||||
@@ -920,6 +927,7 @@ impl Game {
|
||||
vrng: crate::particles::vrng(),
|
||||
dwarf_caves: Vec::new(),
|
||||
bridgework: Default::default(),
|
||||
stats: Default::default(),
|
||||
wall_plans: Vec::new(),
|
||||
wood: crate::wood::WOOD_START,
|
||||
woodwork: Default::default(),
|
||||
@@ -1137,6 +1145,7 @@ impl Game {
|
||||
self.gold += acc;
|
||||
if acc > 0.0 {
|
||||
self.incoming.push((res, acc, at));
|
||||
self.count(crate::stats::GOLD, acc);
|
||||
}
|
||||
acc
|
||||
}
|
||||
@@ -1384,6 +1393,7 @@ impl Game {
|
||||
}
|
||||
let local = self.local_side;
|
||||
self.switch_to(local);
|
||||
self.sample_stats(STEP);
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------- masters
|
||||
@@ -2494,6 +2504,7 @@ impl Game {
|
||||
t.hp = 0.0;
|
||||
t.gold = 0;
|
||||
let (x, y) = xy(i);
|
||||
self.count(crate::stats::DUG, 1.0);
|
||||
self.fx(3, x as f32 + 0.5, y as f32 + 0.5, 0.0, 0.0, 0.6, 0xff304a66);
|
||||
self.sfx(Sfx::DigDone, x as f32 + 0.5, y as f32 + 0.5);
|
||||
}
|
||||
@@ -2759,6 +2770,8 @@ impl Game {
|
||||
e.patrol.clear();
|
||||
let b = st(kind).bounty as f32;
|
||||
let got = self.add_gold(b);
|
||||
self.count_local(crate::stats::HEROES);
|
||||
self.count_local(crate::stats::CAPTURED);
|
||||
self.fx(5, x, y, 0.0, 0.0, 1.0, 0xff30d0ff);
|
||||
self.sfx(Sfx::Capture, x, y);
|
||||
self.msg("msg.hero_captured", vec![format!("@{}", st(kind).name), (got as i32).to_string()]);
|
||||
@@ -2780,6 +2793,15 @@ impl Game {
|
||||
self.bury(x, y);
|
||||
}
|
||||
let s = st(kind);
|
||||
if hero {
|
||||
self.count_local(crate::stats::HEROES);
|
||||
} else if side == self.local_side && !e_is_dwarf_side(side) {
|
||||
let me = self.me_side;
|
||||
let local = self.local_side;
|
||||
self.switch_to(local);
|
||||
self.count(crate::stats::LOST, 1.0);
|
||||
self.switch_to(me);
|
||||
}
|
||||
if hero {
|
||||
let b = s.bounty as f32;
|
||||
let got = self.add_gold(b);
|
||||
@@ -2866,6 +2888,9 @@ impl Game {
|
||||
if !e.alive || e.held || e.tile() != i || flies(e.kind) {
|
||||
continue;
|
||||
}
|
||||
if e.hero {
|
||||
self.count_local(crate::stats::DROWNED);
|
||||
}
|
||||
if lava {
|
||||
self.burn(j);
|
||||
continue;
|
||||
@@ -4778,6 +4803,7 @@ impl Game {
|
||||
if built == 0 && !broke {
|
||||
self.hint(7, "hint.build_where");
|
||||
}
|
||||
self.count(crate::stats::ROOMS, built as f32);
|
||||
self.recount();
|
||||
}
|
||||
|
||||
@@ -4923,6 +4949,7 @@ impl Game {
|
||||
let tower = Tower { id, kind, tile: i, level: 1, hp: s.hp, cd: 1.0, aim: 0.0, flash: 0.0, hero: false, rune_final };
|
||||
let (px, py) = tower.pos();
|
||||
self.towers.push(tower);
|
||||
self.count(crate::stats::TOWERS, 1.0);
|
||||
// units standing on the tile step aside
|
||||
for e in self.ents.iter_mut() {
|
||||
if e.alive && e.tile() == i {
|
||||
@@ -5460,6 +5487,7 @@ impl Game {
|
||||
self.fx(2, x, y, 0.0, 0.0, 0.3, 0xffffffff);
|
||||
self.sfx(Sfx::Slap, x, y);
|
||||
self.script_event(crate::mscript::ScriptEvent::Slap);
|
||||
self.count(crate::stats::SLAPS, 1.0);
|
||||
return 1;
|
||||
}
|
||||
if self.ents[j].captive != 0 {
|
||||
@@ -5498,6 +5526,7 @@ impl Game {
|
||||
return;
|
||||
}
|
||||
self.mana -= COST;
|
||||
self.count(crate::stats::SPELLS, 1.0);
|
||||
self.sfx(Sfx::Lightning, wx, wy);
|
||||
self.fx(1, wx, wy, 0.0, 0.0, 0.5, 0xffffeeaa);
|
||||
self.ignite_at(wx, wy);
|
||||
@@ -5524,6 +5553,7 @@ impl Game {
|
||||
return;
|
||||
}
|
||||
self.mana -= COST;
|
||||
self.count(crate::stats::SPELLS, 1.0);
|
||||
self.sfx(Sfx::Heal, wx, wy);
|
||||
let mut fxs = Vec::new();
|
||||
for e in self.ents.iter_mut() {
|
||||
|
||||
+49
@@ -1,5 +1,6 @@
|
||||
// DungeonCraft – dungeon building & defence in the browser (Rust + macroquad, WASM).
|
||||
|
||||
mod achievements;
|
||||
mod audio;
|
||||
mod campaign;
|
||||
mod campaign_doc;
|
||||
@@ -41,6 +42,7 @@ mod traps;
|
||||
mod sound;
|
||||
mod surface;
|
||||
mod spells;
|
||||
mod stats;
|
||||
mod touch;
|
||||
mod ui;
|
||||
mod voice;
|
||||
@@ -847,6 +849,12 @@ async fn main() {
|
||||
let mut r3d = R3d::new();
|
||||
let mut sounds = SoundBank::new();
|
||||
let mut music = MusicDirector::new();
|
||||
// lifetime statistics and achievements (browser storage, merged with the server after a login)
|
||||
let stored_profile: achievements::Profile = platform::storage_get(achievements::PROFILE_KEY).and_then(|s| serde_json::from_str(&s).ok()).unwrap_or_default();
|
||||
let mut tracker = achievements::Tracker::new(stored_profile);
|
||||
let mut want_profile = false; // fetch the server's profile when the client is free
|
||||
let mut profile_store_t = 0.0f32;
|
||||
let mut profile_upload_t = 0.0f32;
|
||||
let mut ui = Ui::new();
|
||||
let mut menu = Menu::new();
|
||||
let mut cloud = Cloud::new(); // savegame service; files + browser autosave when unreachable
|
||||
@@ -992,6 +1000,7 @@ async fn main() {
|
||||
}
|
||||
}
|
||||
CloudEvent::LoggedIn => {
|
||||
want_profile = true;
|
||||
if menu.page == Some(Page::Login) {
|
||||
menu.page = Some(Page::Title);
|
||||
}
|
||||
@@ -1939,8 +1948,47 @@ app.pending_briefing = briefing_of(&campaign, i);
|
||||
| Some(Page::Account)
|
||||
| Some(Page::Bestiary { from_title: true, .. })
|
||||
| Some(Page::Match { from_title: true })
|
||||
| Some(Page::Achievements { from_title: true })
|
||||
);
|
||||
music.update(&app.game, on_title, (app.cam.rx, app.cam.rz));
|
||||
// ---------------- achievements: count the game, unlock, store and share the profile
|
||||
{
|
||||
let ctx = achievements::Context { coop: app.coop, editor_test: app.editor_testing };
|
||||
let unix = miniquad::date::now() as u64;
|
||||
let counting = !on_title && (playing || app.game.state != 0);
|
||||
for id in if counting { tracker.update(&app.game, &ctx, dt, unix) } else { Vec::new() } {
|
||||
let (name, _) = achievements::texts(&id);
|
||||
app.game.msgs.push(game::Msg { key: "msg.achievement", args: vec![name] });
|
||||
audio::play_sfx(game::Sfx::Victory.index(), 0.8, 0.0);
|
||||
}
|
||||
if let Some(json) = cloud.profile_loaded.take() {
|
||||
if let Ok(p) = serde_json::from_str::<achievements::Profile>(&json) {
|
||||
tracker.profile.merge(&p);
|
||||
tracker.profile.check_tiers(unix);
|
||||
tracker.dirty = true;
|
||||
tracker.upload = tracker.profile != p;
|
||||
}
|
||||
}
|
||||
if want_profile && cloud.session.is_some() && cloud.load_profile() {
|
||||
want_profile = false;
|
||||
}
|
||||
profile_store_t += dt;
|
||||
profile_upload_t += dt;
|
||||
if tracker.dirty && (profile_store_t > 10.0 || tracker.upload) {
|
||||
profile_store_t = 0.0;
|
||||
tracker.dirty = false;
|
||||
platform::storage_set(achievements::PROFILE_KEY, &serde_json::to_string(&tracker.profile).unwrap_or_default());
|
||||
}
|
||||
if tracker.upload && cloud.session.is_some() && profile_upload_t > 30.0 && !want_profile {
|
||||
if cloud.save_profile(&serde_json::to_string(&tracker.profile).unwrap_or_default()) {
|
||||
tracker.upload = false;
|
||||
profile_upload_t = 0.0;
|
||||
}
|
||||
}
|
||||
if matches!(menu.page, Some(Page::Achievements { .. })) {
|
||||
menu.profile = tracker.profile.clone();
|
||||
}
|
||||
}
|
||||
for m in app.game.msgs.drain(..) {
|
||||
if app.settings.voice && voice::speaks(m.key) {
|
||||
voice.say(&i18n::trf(m.key, &m.args), now, 12);
|
||||
@@ -2039,6 +2087,7 @@ fn draw_frame(app: &mut App, gfx: &Gfx, r3d: &mut R3d, ui: &mut Ui, menu: &mut M
|
||||
| Some(Page::Account)
|
||||
| Some(Page::Bestiary { from_title: true, .. })
|
||||
| Some(Page::Match { from_title: true })
|
||||
| Some(Page::Achievements { from_title: true })
|
||||
);
|
||||
if !title_screen {
|
||||
let info = if over_ui || (touch && inp.inspect.is_none()) {
|
||||
|
||||
+16
-1
@@ -14,6 +14,7 @@ use crate::settings::{all_actions, key_name, Action, Settings};
|
||||
use crate::ui::{grid_center, grid_center_y, mouse, sh, sw, tool_info, wrap, side_color, CH, CW, DIM, FRAME, FRAME_HI, GOLD, S, SMALL, TITLE, TOOLS, TXT};
|
||||
use macroquad::prelude::*;
|
||||
|
||||
mod achievements;
|
||||
mod bestiary;
|
||||
pub mod flags;
|
||||
|
||||
@@ -33,6 +34,7 @@ pub enum Page {
|
||||
Account, // password, logout, users (admin)
|
||||
Match { from_title: bool }, // multiplayer lobby and match status
|
||||
Bestiary { tab: usize, sel: usize, level: u32, from_title: bool }, // units, buildings, ranks
|
||||
Achievements { from_title: bool },
|
||||
}
|
||||
|
||||
pub const TABS: [&str; 6] = ["set.tab.sound", "set.tab.camera", "set.tab.graphics", "set.tab.game", "set.tab.debug", "set.tab.keys"];
|
||||
@@ -294,6 +296,7 @@ enum Btn {
|
||||
MatchLeave,
|
||||
MatchChat,
|
||||
Bestiary,
|
||||
Achievements,
|
||||
BestTab(usize),
|
||||
BestSel(i32),
|
||||
BestPick(usize),
|
||||
@@ -441,6 +444,7 @@ pub struct Menu {
|
||||
confirm: Option<Btn>, // overwrite / delete waiting for a second click
|
||||
scroll: usize,
|
||||
rows_visible: usize,
|
||||
pub profile: crate::achievements::Profile, // shown on the achievements page
|
||||
buttons: Vec<(Rect, Btn)>,
|
||||
lang_open: Option<Rect>, // the language dropdown is open below/above this field
|
||||
lang_scroll: usize, // first language shown in the open dropdown
|
||||
@@ -505,6 +509,7 @@ impl Menu {
|
||||
lang_rows: 0,
|
||||
cur_lang: String::new(),
|
||||
rows_visible: 1,
|
||||
profile: Default::default(),
|
||||
buttons: Vec::new(),
|
||||
}
|
||||
}
|
||||
@@ -530,6 +535,7 @@ impl Menu {
|
||||
Some(Page::Match { from_title }) => Some(if from_title { Page::Title } else { Page::Pause }),
|
||||
Some(Page::Bestiary { from_title: true, .. }) => Some(Page::Title),
|
||||
Some(Page::Bestiary { from_title: false, .. }) => Some(Page::Pause),
|
||||
Some(Page::Achievements { from_title }) => Some(if from_title { Page::Title } else { Page::Pause }),
|
||||
Some(Page::Briefing { ingame: true, .. }) => Some(Page::Pause),
|
||||
Some(Page::Briefing { ingame: false, .. }) => Some(Page::Campaign),
|
||||
Some(Page::CustomBriefing) => None,
|
||||
@@ -916,6 +922,11 @@ impl Menu {
|
||||
}
|
||||
MenuAction::Consumed
|
||||
}
|
||||
Some(Btn::Achievements) => {
|
||||
self.page = Some(Page::Achievements { from_title });
|
||||
self.scroll = 0;
|
||||
MenuAction::Consumed
|
||||
}
|
||||
Some(Btn::Bestiary) => {
|
||||
self.open_bestiary(from_title);
|
||||
MenuAction::Consumed
|
||||
@@ -954,6 +965,7 @@ impl Menu {
|
||||
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::Achievements { .. } => self.draw_achievements(gfx),
|
||||
Page::KeyCapture { action, .. } => {
|
||||
self.draw_settings(gfx, settings, 5);
|
||||
self.buttons.clear();
|
||||
@@ -1098,6 +1110,7 @@ impl Menu {
|
||||
}
|
||||
items.push((Btn::Standalone, tr("menu.standalone")));
|
||||
items.push((Btn::Bestiary, tr("menu.bestiary")));
|
||||
items.push((Btn::Achievements, tr("menu.achievements")));
|
||||
items.push((Btn::Help, tr("menu.help")));
|
||||
let w = 26.0 * CW();
|
||||
let y = ty + GH * big + CH() * 4.0;
|
||||
@@ -1215,6 +1228,7 @@ impl Menu {
|
||||
items.extend([
|
||||
(Btn::Settings, tr("menu.settings")),
|
||||
(Btn::Bestiary, tr("menu.bestiary")),
|
||||
(Btn::Achievements, tr("menu.achievements")),
|
||||
(Btn::Help, tr("menu.help")),
|
||||
(Btn::MainMenu, tr("net.leave")),
|
||||
]);
|
||||
@@ -1225,6 +1239,7 @@ impl Menu {
|
||||
(Btn::Match, tr("menu.multiplayer")),
|
||||
(Btn::Settings, tr("menu.settings")),
|
||||
(Btn::Bestiary, tr("menu.bestiary")),
|
||||
(Btn::Achievements, tr("menu.achievements")),
|
||||
(Btn::Help, tr("menu.help")),
|
||||
(Btn::MainMenu, tr("menu.main")),
|
||||
]);
|
||||
@@ -1780,7 +1795,7 @@ impl Menu {
|
||||
pub fn scrollable(&self) -> bool {
|
||||
matches!(
|
||||
self.page,
|
||||
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)
|
||||
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) | Some(Page::Achievements { .. })
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,70 @@
|
||||
// The achievements page: every achievement with its badge (bronze, silver, gold, ... in the
|
||||
// colour of its tier), name, description and the progress towards it. Hidden ones show "???"
|
||||
// until they are unlocked.
|
||||
|
||||
use super::*;
|
||||
use crate::achievements::{all, texts, TIERS};
|
||||
|
||||
const TIER_COL: [(f32, f32, f32); 5] = [(0.72, 0.45, 0.25), (0.75, 0.78, 0.85), (0.95, 0.78, 0.25), (0.55, 0.9, 1.0), (0.85, 0.5, 1.0)];
|
||||
|
||||
impl Menu {
|
||||
pub(super) fn draw_achievements(&mut self, gfx: &Gfx) {
|
||||
let sw = sw();
|
||||
let sh = sh();
|
||||
let cols = 70.min((sw / CW()) as i32 - 2);
|
||||
let rows = 40.min((sh / CH()) as i32 - 2);
|
||||
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);
|
||||
gfx.window_titled(x, y, cols, rows, S(), true, FRAME_HI, Color::new(0.05, 0.03, 0.04, 0.97), &tr("menu.achievements"), FRAME_HI);
|
||||
let list = all();
|
||||
let done = list.iter().filter(|a| self.profile.unlocked.contains_key(&a.0)).count();
|
||||
let head = trf("ach.count", &[done.to_string(), list.len().to_string(), crate::achievements::fmt_count(self.profile.total("minutes"))]);
|
||||
gfx.text(&head, x + CW() * 3.0, y + CH() * 1.6, S(), GOLD);
|
||||
let top = y + CH() * 3.5;
|
||||
let bottom = y + (rows - 4) as f32 * CH();
|
||||
let row_h = CH() * 3.2;
|
||||
self.rows_visible = (((bottom - top) / row_h).floor() as usize).max(1);
|
||||
self.scroll = self.scroll.min(list.len().saturating_sub(self.rows_visible));
|
||||
let ix = x + CW() * 3.0;
|
||||
let w = (cols - 6) as f32 * CW();
|
||||
let maxc = ((w - CW() * 6.0) / (GW * SMALL())) as usize;
|
||||
for (k, (id, hidden, icon, tier)) in list.iter().enumerate().skip(self.scroll).take(self.rows_visible) {
|
||||
let ry = top + (k - self.scroll) as f32 * row_h;
|
||||
let got = self.profile.unlocked.contains_key(id);
|
||||
// the badge: a frame in the tier's colour around the icon (dark while locked)
|
||||
let (r, g, b) = TIER_COL[(*tier).min(4)];
|
||||
let bc = if got { Color::new(r, g, b, 1.0) } else { Color::new(0.25, 0.22, 0.22, 1.0) };
|
||||
let pic = CH() * 2.6;
|
||||
draw_rectangle(ix, ry, pic, pic, Color::new(0.08, 0.05, 0.05, 1.0));
|
||||
draw_rectangle_lines(ix, ry, pic, pic, 3.0, bc);
|
||||
let tint = if got { WHITE } else { Color::new(0.3, 0.3, 0.3, 1.0) };
|
||||
draw_texture_ex(&gfx.atlas, ix + 4.0, ry + 4.0, tint, DrawTextureParams { source: Some(crate::gfx::Gfx::cell(*icon)), dest_size: Some(vec2(pic - 8.0, pic - 8.0)), ..Default::default() });
|
||||
let tx = ix + pic + CW();
|
||||
let (name, desc) = if *hidden && !got { ("???".to_string(), tr("ach.hidden")) } else { texts(id) };
|
||||
gfx.text(&name, tx, ry + CH() * 0.1, S(), if got { GOLD } else { TXT });
|
||||
let d: String = desc.chars().take(maxc).collect();
|
||||
gfx.text(&d, tx, ry + CH() * 1.3, SMALL(), DIM);
|
||||
// progress towards a tier
|
||||
if let Some((cat, t)) = id.split_once('.') {
|
||||
if !got {
|
||||
let need = TIERS.iter().find(|x| x.0 == cat).and_then(|x| x.1.get(t.parse::<usize>().unwrap_or(1) - 1)).copied().unwrap_or(1.0);
|
||||
let have = self.profile.total(cat);
|
||||
let bar_x = x + (cols - 20) as f32 * CW();
|
||||
crate::ui::ascii_bar(gfx, bar_x, ry + CH() * 0.1, 10, (have / need).min(1.0) as f32, FRAME_HI, SMALL());
|
||||
let p = format!("{}/{}", crate::achievements::fmt_count(have.floor()), crate::achievements::fmt_count(need));
|
||||
gfx.text(&p, bar_x, ry + CH() * 1.3, SMALL(), DIM);
|
||||
}
|
||||
}
|
||||
}
|
||||
// scrolling and back
|
||||
let by = y + (rows - 3) as f32 * CH();
|
||||
let (mx, my) = mouse();
|
||||
let bw = 14.0 * CW();
|
||||
let items = [(Btn::Scroll(-1), "▲".to_string()), (Btn::Scroll(1), "▼".to_string()), (Btn::Back, tr("menu.back"))];
|
||||
for (k, (b, label)) in items.iter().enumerate() {
|
||||
let r = Rect::new(ix + k as f32 * (bw + CW()), by, bw, CH() * 2.0);
|
||||
button(gfx, r, label, r.contains(vec2(mx, my)), FRAME);
|
||||
self.buttons.push((r, *b));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -48,6 +48,7 @@ impl Game {
|
||||
return;
|
||||
}
|
||||
self.gold -= gold;
|
||||
self.count(crate::stats::SIEGE, 1.0);
|
||||
let id = self.spawn(kind, false, x as f32 + 0.5, y as f32 + 0.5, 1);
|
||||
if let Some(e) = self.ents.iter_mut().find(|e| e.id == id) {
|
||||
e.side = self.me_side;
|
||||
|
||||
@@ -263,6 +263,7 @@ impl Game {
|
||||
}
|
||||
}
|
||||
self.mana -= cost;
|
||||
self.count(crate::stats::SPELLS, 1.0);
|
||||
}
|
||||
|
||||
// running spell effects on a unit (called every frame)
|
||||
|
||||
+108
@@ -0,0 +1,108 @@
|
||||
// Statistics of a game for the master at this computer: counters (heroes slain, tiles dug, wood
|
||||
// felled, ...) and a sample of gold, creatures and army strength every few seconds for the charts
|
||||
// at the end. They are local (not part of the shared match state) and feed the achievements.
|
||||
|
||||
use crate::game::*;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
// counters (index into GameStats::c)
|
||||
pub const HEROES: usize = 0; // heroes slain or captured
|
||||
pub const BOSSES: usize = 1; // champions of the Order brought down
|
||||
pub const LOST: usize = 2; // own creatures lost
|
||||
pub const DUG: usize = 3; // tiles dug out
|
||||
pub const WOOD: usize = 4; // wood felled
|
||||
pub const WALLS: usize = 5; // walls raised
|
||||
pub const ROOMS: usize = 6; // room tiles built
|
||||
pub const SPELLS: usize = 7; // spells cast (incl. lightning and healing)
|
||||
pub const SLAPS: usize = 8; // creatures slapped
|
||||
pub const TRAPS: usize = 9; // traps and doors ordered
|
||||
pub const TOWERS: usize = 10; // towers built
|
||||
pub const DROWNED: usize = 11; // heroes who fell into water or lava
|
||||
pub const BURNT: usize = 12; // bridge tiles that burnt down
|
||||
pub const SIEGE: usize = 13; // siege engines built
|
||||
pub const CAPTURED: usize = 14; // heroes knocked out and carried off
|
||||
pub const GOLD: usize = 15; // gold earned (treasure delivered, bounties, loot)
|
||||
pub const DRAWBRIDGE: usize = 16; // drawbridges raised
|
||||
pub const COUNTERS: usize = 17;
|
||||
|
||||
// one sample every SAMPLE_EVERY seconds: time, gold, creatures, army
|
||||
pub const SAMPLE_EVERY: f32 = 10.0;
|
||||
const MAX_SAMPLES: usize = 720;
|
||||
|
||||
#[derive(Clone, Debug, Default, Serialize, Deserialize)]
|
||||
pub struct GameStats {
|
||||
#[serde(default)]
|
||||
pub c: Vec<f32>,
|
||||
#[serde(default)]
|
||||
pub samples: Vec<(f32, f32, u16, f32)>,
|
||||
#[serde(default)]
|
||||
pub sample_t: f32,
|
||||
}
|
||||
|
||||
impl GameStats {
|
||||
pub fn get(&self, k: usize) -> f32 {
|
||||
self.c.get(k).copied().unwrap_or(0.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl Game {
|
||||
// counts for the master at this computer (another master's deeds count on his computer)
|
||||
pub(crate) fn count(&mut self, k: usize, n: f32) {
|
||||
if self.me_side != self.local_side || self.demo.is_some() {
|
||||
return;
|
||||
}
|
||||
if self.stats.c.len() < COUNTERS {
|
||||
self.stats.c.resize(COUNTERS, 0.0);
|
||||
}
|
||||
self.stats.c[k] += n;
|
||||
}
|
||||
|
||||
// a hero fell or was captured: the master at this computer counts it in single player and
|
||||
// co-op (the heroes are everybody's enemy there)
|
||||
pub(crate) fn count_local(&mut self, k: usize) {
|
||||
let me = self.me_side;
|
||||
let local = self.local_side;
|
||||
self.switch_to(local);
|
||||
self.count(k, 1.0);
|
||||
self.switch_to(me);
|
||||
}
|
||||
|
||||
// every few seconds: how the local master stands
|
||||
pub(crate) fn sample_stats(&mut self, dt: f32) {
|
||||
if self.demo.is_some() || self.state != 0 {
|
||||
return;
|
||||
}
|
||||
self.stats.sample_t -= dt;
|
||||
if self.stats.sample_t > 0.0 {
|
||||
return;
|
||||
}
|
||||
self.stats.sample_t = SAMPLE_EVERY;
|
||||
let s = (self.time, self.gold, self.creature_count() as u16, self.army_value());
|
||||
self.stats.samples.push(s);
|
||||
if self.stats.samples.len() > MAX_SAMPLES {
|
||||
// keep the whole game in view: every second sample goes
|
||||
let v: Vec<_> = self.stats.samples.iter().copied().step_by(2).collect();
|
||||
self.stats.samples = v;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn a_game_keeps_its_statistics() {
|
||||
let mut g = Game::new(3, 64, 64);
|
||||
for _ in 0..(35 * 20) {
|
||||
g.update(0.05);
|
||||
}
|
||||
assert!(g.stats.samples.len() >= 3, "{} samples", g.stats.samples.len());
|
||||
g.count(DUG, 2.0);
|
||||
assert_eq!(g.stats.get(DUG), 2.0);
|
||||
// saved with the game
|
||||
let json = serde_json::to_string(&g.stats).unwrap();
|
||||
let back: GameStats = serde_json::from_str(&json).unwrap();
|
||||
assert_eq!(back.get(DUG), 2.0);
|
||||
}
|
||||
}
|
||||
@@ -186,6 +186,7 @@ impl Game {
|
||||
}
|
||||
let id = self.next_id;
|
||||
self.next_id += 1;
|
||||
self.count(crate::stats::TRAPS, 1.0);
|
||||
self.traps.push(Trap { id, kind, tile: i, stage: ORDERED, crate_tile: 0, progress: 0.0, cd: 0.0, hp: kind.hp(), locked: false, carrier: 0, fired: 99.0, aim: 0.0, revealed: false, sprung: false });
|
||||
self.fx(8, x as f32 + 0.5, y as f32 + 0.5, 0.0, 0.0, 0.4, 0xff40d0ff);
|
||||
self.sfx_ui(Sfx::FlagSet);
|
||||
|
||||
@@ -1334,7 +1334,10 @@ impl Ui {
|
||||
}
|
||||
lines.extend(wrap(para, tw));
|
||||
}
|
||||
let rows = 9 + lines.len() as i32;
|
||||
// the chart of the game (gold, creatures, army) when there are a few samples
|
||||
let chart = g.stats.samples.len() >= 3 && sh > CH() * (22.0 + lines.len() as f32);
|
||||
let chart_rows = if chart { 10 } else { 0 };
|
||||
let rows = 9 + lines.len() as i32 + chart_rows;
|
||||
let x = grid_center(sw, cols as f32 * CW());
|
||||
let y = grid_center_y(sh, rows as f32 * CH()).max(0.0);
|
||||
let col = if win { GOLD } else { RED };
|
||||
@@ -1347,6 +1350,38 @@ impl Ui {
|
||||
}
|
||||
let st = trf("end.stats", &[mmss(g.time), g.wave.to_string(), (g.army_value() as i32).to_string()]);
|
||||
gfx.text(&st, grid_center(sw, gfx.measure(&st, S())), y + CH() * (4.5 + lines.len() as f32), S(), DIM);
|
||||
if chart {
|
||||
use crate::stats as k;
|
||||
let cy = y + CH() * (6.0 + lines.len() as f32);
|
||||
let c = &g.stats;
|
||||
let line2 = trf("end.stats2", &[(c.get(k::HEROES) as i32).to_string(), (c.get(k::LOST) as i32).to_string(), (c.get(k::DUG) as i32).to_string(), (c.get(k::GOLD) as i32).to_string(), (c.get(k::WOOD) as i32).to_string()]);
|
||||
gfx.text(&line2, grid_center(sw, gfx.measure(&line2, SMALL())), cy, SMALL(), DIM);
|
||||
// three lines, each scaled to its own maximum
|
||||
let (px, py, pw, ph) = (x + CW() * 3.0, cy + CH() * 1.2, (cols - 6) as f32 * CW(), CH() * 6.0);
|
||||
draw_rectangle(px, py, pw, ph, Color::new(0.0, 0.0, 0.0, 0.35));
|
||||
draw_rectangle_lines(px, py, pw, ph, 1.0, Color::new(0.4, 0.3, 0.2, 1.0));
|
||||
let n = c.samples.len();
|
||||
let t_end = c.samples[n - 1].0.max(1.0);
|
||||
let series: [(Box<dyn Fn(&(f32, f32, u16, f32)) -> f32>, Color); 3] = [(Box::new(|s| s.1), GOLD), (Box::new(|s| s.2 as f32), GREEN), (Box::new(|s| s.3), RED)];
|
||||
for (f, col) in series.iter() {
|
||||
let max = c.samples.iter().map(|s| f(s)).fold(1.0f32, f32::max);
|
||||
for w in c.samples.windows(2) {
|
||||
let (a, b) = (&w[0], &w[1]);
|
||||
let p = |s: &(f32, f32, u16, f32)| (px + pw * (s.0 / t_end), py + ph - ph * 0.92 * (f(s) / max));
|
||||
let (x0, y0) = p(a);
|
||||
let (x1, y1) = p(b);
|
||||
draw_line(x0, y0, x1, y1, 2.0 * K(), *col);
|
||||
}
|
||||
}
|
||||
let legend = format!("{} {} {}", tr("hud.gold"), tr("hud.creatures"), tr("hud.army"));
|
||||
let lx = px + CW();
|
||||
gfx.text(&tr("hud.gold"), lx, py + ph + CH() * 0.3, SMALL(), GOLD);
|
||||
let w1 = gfx.measure(&format!("{} ", tr("hud.gold")), SMALL());
|
||||
gfx.text(&tr("hud.creatures"), lx + w1, py + ph + CH() * 0.3, SMALL(), GREEN);
|
||||
let w2 = gfx.measure(&format!("{} {} ", tr("hud.gold"), tr("hud.creatures")), SMALL());
|
||||
gfx.text(&tr("hud.army"), lx + w2, py + ph + CH() * 0.3, SMALL(), RED);
|
||||
let _ = legend;
|
||||
}
|
||||
// buttons: free game: new game; mission: next / again and back to the campaign
|
||||
let labels: Vec<(String, u8)> = match (mission, self.coop_end) {
|
||||
// an editor campaign in co-op: the host goes on for everybody, the others wait or leave
|
||||
@@ -1359,10 +1394,10 @@ impl Ui {
|
||||
if let Some((host, true, sent)) = self.coop_end {
|
||||
if !host || sent {
|
||||
let t = tr("net.coop_wait_host");
|
||||
gfx.text(&t, grid_center(sw, gfx.measure(&t, SMALL())), y + CH() * (5.3 + lines.len() as f32), SMALL(), GOLD);
|
||||
gfx.text(&t, grid_center(sw, gfx.measure(&t, SMALL())), y + CH() * (5.3 + lines.len() as f32 + chart_rows as f32), SMALL(), GOLD);
|
||||
}
|
||||
}
|
||||
let by = y + CH() * (6.0 + lines.len() as f32);
|
||||
let by = y + CH() * (6.0 + lines.len() as f32 + chart_rows as f32);
|
||||
let widths: Vec<f32> = labels.iter().map(|(l, _)| gfx.measure(l, S()) + CW() * 4.0).collect();
|
||||
let total: f32 = widths.iter().sum::<f32>() + CW() * 2.0 * (labels.len() as f32 - 1.0);
|
||||
let mut bx = grid_center(sw, total);
|
||||
|
||||
@@ -119,6 +119,7 @@ impl Game {
|
||||
tl.bed = 0;
|
||||
tl.food = 0.0;
|
||||
self.wall_plans.retain(|p| p.0 != w);
|
||||
self.count(crate::stats::WALLS, 1.0);
|
||||
self.fx(8, wx as f32 + 0.5, wy as f32 + 0.5, 0.0, 0.0, 0.5, 0xff4040e0);
|
||||
self.sfx(Sfx::Reinforce, wx as f32 + 0.5, wy as f32 + 0.5);
|
||||
false
|
||||
|
||||
@@ -79,6 +79,7 @@ impl Game {
|
||||
let kind = self.map.t[i].kind;
|
||||
let wood = if kind == Kind::Tree { WOOD_TREE } else { WOOD_MUSHROOM };
|
||||
self.wood += wood;
|
||||
self.count(crate::stats::WOOD, wood);
|
||||
if kind == Kind::Mushroom {
|
||||
self.woodwork.regrow.push((i, REGROW));
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user