diff --git a/lang/cs.lang b/lang/cs.lang index 84c795e..4a3a53a 100644 --- a/lang/cs.lang +++ b/lang/cs.lang @@ -53,6 +53,12 @@ msg.creature_arrived = Portálem přišel nový tvor: {0}. msg.payday_partial = Výplata! {0} tvorů dostalo zaplaceno ({1} zlata), {2} nedostalo nic a zuří! msg.payday = Výplata! {0} tvorů dostalo {1} zlata. msg.wave_boss = Vlna {0}: paladin světla vede do tvé říše hrdiny ({1})! +boss.aldric = Sir Aldric Neoblomný +boss.seraphine = Dáma Seraphine z Úsvitu +boss.halvard = Bratr Halvard Zbožný +boss.rothgar = Kapitán Rothgar Železný Bok +msg.boss_comes = Vlnu vede šampion řádu: {0}! +msg.boss_slain = {0} padl! Kořist: {1} zlata a plány věže runové kovárny. msg.wave = Vlna {0}: bránou se valí hrdinové ({1})! msg.gate_boss = Brána se hroutí! Objevuje se strážce brány s obránci ({0})! msg.gate_defenders = Brána povolává obránce ({0})! diff --git a/lang/da.lang b/lang/da.lang index a036f96..6d44b1c 100644 --- a/lang/da.lang +++ b/lang/da.lang @@ -53,6 +53,12 @@ msg.creature_arrived = En {0} kom gennem portalen. msg.payday_partial = Lønningsdag! {0} væsner fik løn ({1} guld), {2} fik intet og er vrede! msg.payday = Lønningsdag! {0} væsner fik {1} guld. msg.wave_boss = Bølge {0}: en lysets paladin fører {1} helte ind i dit rige! +boss.aldric = Sir Aldric den Ubøjelige +boss.seraphine = Dame Seraphine af Daggry +boss.halvard = Broder Halvard den Fromme +boss.rothgar = Kaptajn Rothgar Jernflanke +msg.boss_comes = En af ordenens kæmper fører bølgen: {0}! +msg.boss_slain = {0} er faldet! Bytte: {1} guld og tegningerne til et runesmedjetårn. msg.wave = Bølge {0}: {1} helte bryder gennem porten! msg.gate_boss = Porten smuldrer! Portvogteren dukker op med {0} forsvarere! msg.gate_defenders = Porten tilkalder {0} forsvarere! diff --git a/lang/de.lang b/lang/de.lang index 222af33..8aac4b9 100644 --- a/lang/de.lang +++ b/lang/de.lang @@ -52,6 +52,12 @@ msg.creature_arrived = Ein {0} ist durch das Portal gekommen. msg.payday_partial = Zahltag! {0} Kreaturen bezahlt ({1} Gold), {2} gingen leer aus und sind wütend! msg.payday = Zahltag! {0} Kreaturen erhielten {1} Gold. msg.wave_boss = Welle {0}: Ein Lichtpaladin führt {1} Helden in dein Reich! +boss.aldric = Sir Aldric der Unbeugsame +boss.seraphine = Dame Seraphine von der Morgenröte +boss.halvard = Bruder Halvard der Fromme +boss.rothgar = Hauptmann Rothgar Eisenflanke +msg.boss_comes = Ein Champion des Ordens führt die Welle an: {0}! +msg.boss_slain = {0} ist gefallen! Beute: {1} Gold und die Pläne eines Runenschmiede-Turms. msg.wave = Welle {0}: {1} Helden dringen durch das Tor! msg.gate_boss = Das Tor wankt! Der Torwächter erscheint mit {0} Verteidigern! msg.gate_defenders = Das Tor ruft {0} Verteidiger herbei! diff --git a/lang/el.lang b/lang/el.lang index 8d9df25..c31960e 100644 --- a/lang/el.lang +++ b/lang/el.lang @@ -53,6 +53,12 @@ msg.creature_arrived = Ήρθε από την πύλη: {0}. msg.payday_partial = Μέρα πληρωμής! Πληρώθηκαν {0} πλάσματα ({1} χρυσός), {2} δεν πήραν τίποτα και είναι έξαλλα! msg.payday = Μέρα πληρωμής! {0} πλάσματα πήραν {1} χρυσό. msg.wave_boss = Κύμα {0}: ένας Παλαδίνος του Φωτός οδηγεί {1} ήρωες στο βασίλειό σου! +boss.aldric = Σερ Άλντρικ ο Αλύγιστος +boss.seraphine = Κυρία Σεραφίνα της Αυγής +boss.halvard = Αδελφός Χάλβαρντ ο Ευσεβής +boss.rothgar = Λοχαγός Ρόθγκαρ Σιδερόπλευρος +msg.boss_comes = Ένας πρωταθλητής του Τάγματος οδηγεί το κύμα: {0}! +msg.boss_slain = {0} νικήθηκε! Λάφυρα: {1} χρυσός και τα σχέδια ενός πύργου ρουνικού σιδηρουργείου. msg.wave = Κύμα {0}: {1} ήρωες διασχίζουν την πύλη! msg.gate_boss = Η πύλη καταρρέει! Ο Φύλακας της Πύλης εμφανίζεται με {0} υπερασπιστές! msg.gate_defenders = Η πύλη καλεί {0} υπερασπιστές! diff --git a/lang/en.lang b/lang/en.lang index 9f99b9a..39417ce 100644 --- a/lang/en.lang +++ b/lang/en.lang @@ -53,6 +53,12 @@ msg.creature_arrived = A {0} came through the portal. msg.payday_partial = Payday! {0} creatures paid ({1} gold), {2} got nothing and are angry! msg.payday = Payday! {0} creatures received {1} gold. msg.wave_boss = Wave {0}: a Paladin of Light leads {1} heroes into your realm! +boss.aldric = Sir Aldric the Unbending +boss.seraphine = Dame Seraphine of the Dawn +boss.halvard = Brother Halvard the Pious +boss.rothgar = Captain Rothgar Ironside +msg.boss_comes = A champion of the Order leads the wave: {0}! +msg.boss_slain = {0} has fallen! Loot: {1} gold and the plans of a rune forge tower. msg.wave = Wave {0}: {1} heroes break through the gate! msg.gate_boss = The gate is crumbling! The Gate Warden appears with {0} defenders! msg.gate_defenders = The gate summons {0} defenders! diff --git a/lang/es.lang b/lang/es.lang index 50166cd..2009ce4 100644 --- a/lang/es.lang +++ b/lang/es.lang @@ -53,6 +53,12 @@ msg.creature_arrived = Un {0} ha cruzado el portal. msg.payday_partial = ¡Día de paga! {0} criaturas cobran ({1} de oro), ¡{2} no reciben nada y están furiosas! msg.payday = ¡Día de paga! {0} criaturas reciben {1} de oro. msg.wave_boss = Oleada {0}: ¡un Paladín de la Luz guía a {1} héroes hacia tu reino! +boss.aldric = Sir Aldric el Inflexible +boss.seraphine = Dama Seraphine del Alba +boss.halvard = Hermano Halvard el Piadoso +boss.rothgar = Capitán Rothgar Flanco de Hierro +msg.boss_comes = Un campeón de la Orden encabeza la oleada: ¡{0}! +msg.boss_slain = ¡{0} ha caído! Botín: {1} de oro y los planos de una torre de forja rúnica. msg.wave = Oleada {0}: ¡{1} héroes atraviesan la puerta! msg.gate_boss = ¡La puerta se desmorona! ¡El Guardián de la Puerta aparece con {0} defensores! msg.gate_defenders = ¡La puerta invoca a {0} defensores! diff --git a/lang/fi.lang b/lang/fi.lang index 9f602f6..8013cb8 100644 --- a/lang/fi.lang +++ b/lang/fi.lang @@ -53,6 +53,12 @@ msg.creature_arrived = Portaalista tuli olento: {0}. msg.payday_partial = Palkkapäivä! {0} olentoa sai palkan ({1} kultaa), {2} jäi ilman ja on vihainen! msg.payday = Palkkapäivä! {0} olentoa sai yhteensä {1} kultaa. msg.wave_boss = Aalto {0}: Valon paladiini johtaa {1} sankaria valtakuntaasi! +boss.aldric = Ser Aldric Taipumaton +boss.seraphine = Rouva Seraphine Aamunkoitosta +boss.halvard = Veli Halvard Hurskas +boss.rothgar = Kapteeni Rothgar Rautakylki +msg.boss_comes = Ritarikunnan mestari johtaa aaltoa: {0}! +msg.boss_slain = {0} on kaatunut! Saalis: {1} kultaa ja riimupajatornin piirustukset. msg.wave = Aalto {0}: {1} sankaria murtautuu portista! msg.gate_boss = Portti horjuu! Portinvartija ilmestyy, mukanaan {0} puolustajaa! msg.gate_defenders = Portti kutsuu {0} puolustajaa! diff --git a/lang/hr.lang b/lang/hr.lang index c007fc6..0ab130b 100644 --- a/lang/hr.lang +++ b/lang/hr.lang @@ -53,6 +53,12 @@ msg.creature_arrived = Kroz portal je stiglo stvorenje: {0}. msg.payday_partial = Dan isplate! Plaćeno stvorenja: {0} ({1} zlata), {2} nisu dobila ništa i bijesna su! msg.payday = Dan isplate! Stvorenja ({0}) primila su {1} zlata. msg.wave_boss = Val {0}: Paladin svjetla vodi junake ({1}) u tvoje carstvo! +boss.aldric = Sir Aldric Nepokolebljivi +boss.seraphine = Dama Seraphine od Zore +boss.halvard = Brat Halvard Pobožni +boss.rothgar = Kapetan Rothgar Željezni Bok +msg.boss_comes = Prvak reda predvodi val: {0}! +msg.boss_slain = {0} je pao! Plijen: {1} zlata i nacrti tornja runske kovačnice. msg.wave = Val {0}: junaci ({1}) probijaju vrata! msg.gate_boss = Vrata se urušavaju! Pojavljuje se Čuvar vrata s braniteljima ({0})! msg.gate_defenders = Vrata dozivaju branitelje: {0}! diff --git a/lang/hu.lang b/lang/hu.lang index 5bd64dd..872e984 100644 --- a/lang/hu.lang +++ b/lang/hu.lang @@ -53,6 +53,12 @@ msg.creature_arrived = Új lény érkezett a portálon át: {0}. msg.payday_partial = Fizetésnap! {0} lény kapott bért ({1} arany), {2} semmit sem kapott, és dühös! msg.payday = Fizetésnap! {0} lény összesen {1} aranyat kapott. msg.wave_boss = {0}. hullám: a Fény egyik paladinja {1} hőst vezet a birodalmadba! +boss.aldric = Aldric lovag, a Hajthatatlan +boss.seraphine = Seraphine úrnő, a Hajnal +boss.halvard = Halvard testvér, a Jámbor +boss.rothgar = Rothgar kapitány, a Vasoldalú +msg.boss_comes = A rend bajnoka vezeti a rohamot: {0}! +msg.boss_slain = {0} elesett! Zsákmány: {1} arany és egy rúnakovács-torony tervrajza. msg.wave = {0}. hullám: {1} hős tör át a kapun! msg.gate_boss = A kapu omladozik! Megjelent a Kapuőr {0} védelmezővel! msg.gate_defenders = A kapu {0} védelmezőt idéz! diff --git a/lang/it.lang b/lang/it.lang index f1fc935..1851df8 100644 --- a/lang/it.lang +++ b/lang/it.lang @@ -53,6 +53,12 @@ msg.creature_arrived = Nuovo arrivo dal portale: {0}. msg.payday_partial = Giorno di paga! {0} creature pagate ({1} oro), {2} a mani vuote e furiose! msg.payday = Giorno di paga! {0} creature hanno ricevuto {1} oro. msg.wave_boss = Ondata {0}: un Paladino della Luce guida {1} eroi nel tuo regno! +boss.aldric = Sir Aldric l'Inflessibile +boss.seraphine = Dama Seraphine dell'Aurora +boss.halvard = Fratello Halvard il Pio +boss.rothgar = Capitano Rothgar Fianco di Ferro +msg.boss_comes = Un campione dell'Ordine guida l'ondata: {0}! +msg.boss_slain = {0} è caduto! Bottino: {1} oro e i piani di una torre della forgia runica. msg.wave = Ondata {0}: {1} eroi sfondano la porta! msg.gate_boss = La porta si sgretola! Appare il Guardiano della Porta con {0} difensori! msg.gate_defenders = La porta evoca {0} difensori! diff --git a/lang/nl.lang b/lang/nl.lang index 78a70be..e1631ef 100644 --- a/lang/nl.lang +++ b/lang/nl.lang @@ -53,6 +53,12 @@ msg.creature_arrived = Er kwam een {0} door het portaal. msg.payday_partial = Betaaldag! {0} wezens betaald ({1} goud), {2} kregen niets en zijn woedend! msg.payday = Betaaldag! {0} wezens kregen {1} goud. msg.wave_boss = Golf {0}: een Lichtpaladijn leidt {1} helden je rijk binnen! +boss.aldric = Sir Aldric de Onbuigzame +boss.seraphine = Dame Seraphine van de Dageraad +boss.halvard = Broeder Halvard de Vrome +boss.rothgar = Kapitein Rothgar IJzerflank +msg.boss_comes = Een kampioen van de Orde leidt de golf: {0}! +msg.boss_slain = {0} is gevallen! Buit: {1} goud en de plannen van een runensmidstoren. msg.wave = Golf {0}: {1} helden breken door de poort! msg.gate_boss = De poort brokkelt af! De Poortwachter verschijnt met {0} verdedigers! msg.gate_defenders = De poort roept {0} verdedigers op! diff --git a/lang/no.lang b/lang/no.lang index c32c69d..820f07c 100644 --- a/lang/no.lang +++ b/lang/no.lang @@ -53,6 +53,12 @@ msg.creature_arrived = En {0} kom gjennom portalen. msg.payday_partial = Lønningsdag! {0} skapninger fikk lønn ({1} gull), {2} fikk ingenting og er sinte! msg.payday = Lønningsdag! {0} skapninger fikk {1} gull. msg.wave_boss = Bølge {0}: en lysets paladin leder {1} helter inn i riket ditt! +boss.aldric = Sir Aldric den Ubøyelige +boss.seraphine = Dame Seraphine av Daggry +boss.halvard = Bror Halvard den Fromme +boss.rothgar = Kaptein Rothgar Jernflanke +msg.boss_comes = En av ordenens forkjempere leder bølgen: {0}! +msg.boss_slain = {0} har falt! Bytte: {1} gull og tegningene til et runesmietårn. msg.wave = Bølge {0}: {1} helter bryter gjennom porten! msg.gate_boss = Porten smuldrer! Portvokteren dukker opp med {0} forsvarere! msg.gate_defenders = Porten tilkaller {0} forsvarere! diff --git a/lang/pt.lang b/lang/pt.lang index 2762ad4..554bf0c 100644 --- a/lang/pt.lang +++ b/lang/pt.lang @@ -53,6 +53,12 @@ msg.creature_arrived = Um {0} atravessou o portal. msg.payday_partial = Dia de pagamento! {0} criaturas pagas ({1} de ouro), {2} ficaram sem nada e estão furiosas! msg.payday = Dia de pagamento! {0} criaturas receberam {1} de ouro. msg.wave_boss = Vaga {0}: um Paladino da Luz conduz {1} heróis ao teu reino! +boss.aldric = Sir Aldric, o Inflexível +boss.seraphine = Dama Seraphine da Aurora +boss.halvard = Irmão Halvard, o Pio +boss.rothgar = Capitão Rothgar Flanco-de-Ferro +msg.boss_comes = Um campeão da Ordem lidera a onda: {0}! +msg.boss_slain = {0} caiu! Saque: {1} de ouro e os planos de uma torre da forja rúnica. msg.wave = Vaga {0}: {1} heróis irrompem pelo portão! msg.gate_boss = O portão está a ruir! Surge o Guardião do Portão com {0} defensores! msg.gate_defenders = O portão invoca {0} defensores! diff --git a/lang/ro.lang b/lang/ro.lang index b8e3846..3a3a695 100644 --- a/lang/ro.lang +++ b/lang/ro.lang @@ -53,6 +53,12 @@ msg.creature_arrived = Un {0} a venit prin portal. msg.payday_partial = Ziua de plată! {0} creaturi plătite ({1} aur), {2} n-au primit nimic și sunt furioase! msg.payday = Ziua de plată! {0} creaturi au primit {1} aur. msg.wave_boss = Valul {0}: un Paladin al Luminii conduce {1} eroi în tărâmul tău! +boss.aldric = Sir Aldric cel Neînduplecat +boss.seraphine = Dama Seraphine a Zorilor +boss.halvard = Fratele Halvard cel Evlavios +boss.rothgar = Căpitanul Rothgar Flanc-de-Fier +msg.boss_comes = Un campion al Ordinului conduce valul: {0}! +msg.boss_slain = {0} a căzut! Pradă: {1} aur și planurile unui turn al forjei runice. msg.wave = Valul {0}: {1} eroi sparg poarta! msg.gate_boss = Poarta se năruie! Paznicul Porții apare cu {0} apărători! msg.gate_defenders = Poarta cheamă {0} apărători! diff --git a/lang/ru.lang b/lang/ru.lang index ccd01fd..ca16e3b 100644 --- a/lang/ru.lang +++ b/lang/ru.lang @@ -53,6 +53,12 @@ msg.creature_arrived = Через портал пришёл новичок: {0}. msg.payday_partial = День выплат! {0} существ получили жалованье ({1} золота), {2} остались ни с чем и злятся! msg.payday = День выплат! {0} существ получили {1} золота. msg.wave_boss = Волна {0}: Паладин Света ведёт в твои владения {1} героев! +boss.aldric = Сэр Алдрик Непреклонный +boss.seraphine = Дама Серафина Рассветная +boss.halvard = Брат Халвард Благочестивый +boss.rothgar = Капитан Ротгар Железный Бок +msg.boss_comes = Волну ведёт чемпион ордена: {0}! +msg.boss_slain = {0} пал! Добыча: {1} золота и чертежи башни рунной кузницы. msg.wave = Волна {0}: {1} героев прорываются через врата! msg.gate_boss = Врата рушатся! Появляется Страж врат и {0} защитников! msg.gate_defenders = Врата призывают {0} защитников! diff --git a/lang/sk.lang b/lang/sk.lang index be58b26..f51ce7d 100644 --- a/lang/sk.lang +++ b/lang/sk.lang @@ -53,6 +53,12 @@ msg.creature_arrived = Portálom prišiel nový tvor: {0}. msg.payday_partial = Výplata! {0} tvorov dostalo zaplatené ({1} zlata), {2} nedostalo nič a zúri! msg.payday = Výplata! {0} tvorov dostalo {1} zlata. msg.wave_boss = Vlna {0}: paladin svetla vedie do tvojej ríše hrdinov ({1})! +boss.aldric = Sir Aldric Neoblomný +boss.seraphine = Dáma Seraphine z Úsvitu +boss.halvard = Brat Halvard Zbožný +boss.rothgar = Kapitán Rothgar Železný Bok +msg.boss_comes = Vlnu vedie šampión rádu: {0}! +msg.boss_slain = {0} padol! Korisť: {1} zlata a plány veže runovej vyhne. msg.wave = Vlna {0}: bránou sa valia hrdinovia ({1})! msg.gate_boss = Brána sa rúca! Objavuje sa strážca brány s obrancami ({0})! msg.gate_defenders = Brána povoláva obrancov ({0})! diff --git a/lang/sr.lang b/lang/sr.lang index 0a3f876..488e6e6 100644 --- a/lang/sr.lang +++ b/lang/sr.lang @@ -53,6 +53,12 @@ msg.creature_arrived = Кроз портал је стигло створење: msg.payday_partial = Дан исплате! Плаћено створења: {0} ({1} злата), {2} нису добила ништа и бесна су! msg.payday = Дан исплате! Створења ({0}) примила су {1} злата. msg.wave_boss = Талас {0}: Паладин светлости води хероје ({1}) у твоје царство! +boss.aldric = Сер Алдрик Непоколебљиви +boss.seraphine = Дама Серафина од Зоре +boss.halvard = Брат Халвард Побожни +boss.rothgar = Капетан Ротгар Гвоздени Бок +msg.boss_comes = Првак реда предводи талас: {0}! +msg.boss_slain = {0} је пао! Плен: {1} злата и нацрти куле рунске ковачнице. msg.wave = Талас {0}: хероји ({1}) пробијају капију! msg.gate_boss = Капија се руши! Појављује се Чувар капије са браниоцима ({0})! msg.gate_defenders = Капија призива браниоце: {0}! diff --git a/lang/sv.lang b/lang/sv.lang index 214f546..99940c5 100644 --- a/lang/sv.lang +++ b/lang/sv.lang @@ -53,6 +53,12 @@ msg.creature_arrived = En {0} kom genom portalen. msg.payday_partial = Lönedag! {0} varelser fick betalt ({1} guld), {2} fick ingenting och är arga! msg.payday = Lönedag! {0} varelser fick {1} guld. msg.wave_boss = Våg {0}: en Ljusets paladin leder {1} hjältar in i ditt rike! +boss.aldric = Sir Aldric den Obeveklige +boss.seraphine = Dam Seraphine av Gryningen +boss.halvard = Broder Halvard den Fromme +boss.rothgar = Kapten Rothgar Järnflank +msg.boss_comes = En av ordens kämpar leder vågen: {0}! +msg.boss_slain = {0} har fallit! Byte: {1} guld och ritningarna till ett runsmedjetorn. msg.wave = Våg {0}: {1} hjältar bryter igenom porten! msg.gate_boss = Porten vacklar! Portväktaren dyker upp med {0} försvarare! msg.gate_defenders = Porten kallar på {0} försvarare! diff --git a/src/bosses.rs b/src/bosses.rs new file mode 100644 index 0000000..1b6fd63 --- /dev/null +++ b/src/bosses.rs @@ -0,0 +1,168 @@ +// Bosses: every fifth wave is led by one of the Order's champions - a paladin with a name, more +// than twice the health, an ability of his own and loot for whoever brings him down (gold and a +// free rune forge tower). His health bar stands at the top of the screen while he lives. + +use crate::game::*; +use crate::particles::*; + +// the champions (Ent::boss = index + 1): ability cooldowns in seconds +pub const BOSSES: [(&str, f32); 4] = [("aldric", 12.0), ("seraphine", 10.0), ("halvard", 18.0), ("rothgar", 9.0)]; +pub const BOSS_HP: f32 = 2.5; // health factor +pub const BOSS_GOLD: f32 = 600.0; + +pub fn boss_name(b: u8) -> String { + format!("@boss.{}", BOSSES[(b.max(1) as usize - 1) % BOSSES.len()].0) +} + +impl Game { + // the unit at index j leads the wave as champion number `b` (1..=4) + pub(crate) fn make_boss(&mut self, j: usize, b: u8) { + let e = &mut self.ents[j]; + e.boss = b; + e.max_hp *= BOSS_HP; + e.hp = e.max_hp; + e.ab_cd = 4.0; + let name = boss_name(b); + self.msg("msg.boss_comes", vec![name]); + } + + // the champion's ability, used by itself whenever it is ready + pub(crate) fn boss_ability(&mut self, e: &mut Ent, dt: f32) { + e.ab_cd -= dt; + if e.ab_cd > 0.0 || e.captive != 0 { + return; + } + let (bx, by) = (e.x, e.y); + let idx_b = (e.boss.max(1) as usize - 1) % BOSSES.len(); + let foe = |o: &Ent| o.alive && !o.held && !o.hero && o.captive == 0 && !o.chained; + let near = |o: &Ent, r: f32| (o.x - bx).powi(2) + (o.y - by).powi(2) < r * r; + let used = match idx_b { + // Sir Aldric: shield wall - heroes around are healed and shielded + 0 => { + for o in self.ents.iter_mut().filter(|o| o.alive && o.hero && (o.x - bx).powi(2) + (o.y - by).powi(2) < 16.0) { + o.hp = (o.hp + 60.0).min(o.max_hp); + o.shield = o.shield.max(6.0); + } + self.part(RING, (bx, by, 0.1), (0.0, 0.0, 0.0), 0.7, abgr(255, 230, 140), 4.0); + true + } + // Dame Seraphine: radiant burst - the dungeon's units around are burnt by light + 1 => { + let hit: Vec = (0..self.ents.len()).filter(|&k| foe(&self.ents[k]) && near(&self.ents[k], 3.0)).collect(); + if hit.is_empty() { + false + } else { + for k in hit { + self.damage_unit(k, 45.0); + } + self.part(FLASH, (bx, by, 0.5), (0.0, 0.0, 0.0), 0.5, abgr(255, 250, 200), 3.0); + true + } + } + // Brother Halvard: call to arms - two knights join him + 2 => { + let level = e.level; + for k in 0..2 { + let a = k as f32 * 3.1; + let id = self.spawn(EK::Knight, true, bx + a.cos(), by + a.sin(), level.max(2) - 1); + if let Some(o) = self.ents.iter_mut().find(|o| o.id == id) { + o.target = e.target; + } + } + self.fx(4, bx, by, 0.0, 0.0, 0.8, 0xffffe0a0); + true + } + // Captain Rothgar: charge - he rushes at an enemy, hits hard and stuns + _ => { + let target = (0..self.ents.len()).filter(|&k| foe(&self.ents[k]) && near(&self.ents[k], 6.0)).min_by(|&a, &b| { + let da = (self.ents[a].x - bx).powi(2) + (self.ents[a].y - by).powi(2); + let db = (self.ents[b].x - bx).powi(2) + (self.ents[b].y - by).powi(2); + da.partial_cmp(&db).unwrap_or(std::cmp::Ordering::Equal) + }); + match target { + Some(k) => { + let (tx, ty) = (self.ents[k].x, self.ents[k].y); + let d = ((tx - bx).powi(2) + (ty - by).powi(2)).sqrt().max(0.01); + if self.map.walk((tx - (tx - bx) / d * 0.8) as i32, (ty - (ty - by) / d * 0.8) as i32) { + e.x = tx - (tx - bx) / d * 0.8; + e.y = ty - (ty - by) / d * 0.8; + e.path.clear(); + } + self.ents[k].stun = self.ents[k].stun.max(1.5); + self.damage_unit(k, 50.0); + for _ in 0..10 { + let v = (self.vrng.f() * 2.0 - 1.0, self.vrng.f() * 2.0 - 1.0, 0.5); + self.parts.push(Part { kind: DEBRIS, x: bx, y: by, h: 0.1, vx: v.0, vy: v.1, vh: v.2, t: 0.0, max: 0.8, col: abgr(150, 130, 110), size: 0.05 }); + } + true + } + None => false, + } + } + }; + e.ab_cd = if used { BOSSES[idx_b].1 } else { 1.0 }; + } + + // a champion fell (or was captured): his loot + pub(crate) fn boss_loot(&mut self, j: usize) { + let b = self.ents[j].boss; + if b == 0 { + return; + } + self.ents[j].boss = 0; + let got = self.add_gold(BOSS_GOLD); + self.rune_free += 1; + let (x, y) = (self.ents[j].x, self.ents[j].y); + self.fx(7, x, y, 0.0, 0.0, 1.2, 0xff40e0ff); + self.sfx_ui(Sfx::Victory); + self.msg("msg.boss_slain", vec![boss_name(b), (got as i32).to_string()]); + } + + // the living champion with the most health (for the bar at the top) + pub fn boss_alive(&self) -> Option<&Ent> { + self.ents.iter().filter(|e| e.alive && e.boss > 0 && e.captive == 0).max_by(|a, b| a.hp.partial_cmp(&b.hp).unwrap_or(std::cmp::Ordering::Equal)) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn every_fifth_wave_brings_a_champion_with_loot() { + let mut g = Game::new(8, 80, 64); + g.wave = 4; // the next wave is the fifth + let before = g.ents.len(); + g.spawn_wave(); + let boss = g.ents[before..].iter().position(|e| e.boss > 0).map(|k| k + before).expect("a champion"); + assert!(g.ents[boss].max_hp > st(g.ents[boss].kind).hp * 2.0); + assert!(g.boss_alive().is_some()); + let (gold, runes) = (g.gold, g.rune_free); + g.boss_loot(boss); + assert!(g.gold > gold && g.rune_free == runes + 1); + assert!(g.boss_alive().is_none()); + } + + #[test] + fn champions_use_their_abilities() { + for b in 1..=4u8 { + let mut g = Game::new(8, 80, 64); + let (cx, cy) = g.map.core; + let (x, y) = (cx as f32 + 1.5, cy as f32 + 4.5); + let id = g.spawn(EK::Paladin, true, x, y, 5); + let j = g.ents.iter().position(|e| e.id == id).unwrap(); + g.make_boss(j, b); + // an ogre of the dungeon close by + g.spawn(EK::Ogre, false, x + 1.5, y, 3); + let n0 = g.ents.len(); + let mut e = g.ents[j].clone(); + e.ab_cd = 0.0; + e.hp = e.max_hp * 0.5; + g.boss_ability(&mut e, 0.1); + assert!(e.ab_cd > 1.5, "champion {} used his ability", b); + if b == 3 { + assert_eq!(g.ents.len(), n0 + 2, "two knights joined"); + } + } + } +} diff --git a/src/game.rs b/src/game.rs index ca44e1d..8f0ed04 100644 --- a/src/game.rs +++ b/src/game.rs @@ -296,6 +296,8 @@ pub struct Ent { pub target: Option, // attackers: side whose core (or gate) they march on #[serde(default)] pub chained: bool, // a lone creature chained in a sealed cave: joins when the cave is claimed + #[serde(default)] + pub boss: u8, // a champion of the Order leading a wave (see bosses.rs), 0: none #[serde(skip, default = "zero_f32")] pub disp_x: f32, // smoothed screen position (not part of the saved or synced world state) #[serde(skip, default = "zero_f32")] @@ -366,6 +368,7 @@ impl Ent { side: 0, target: None, chained: false, + boss: 0, disp_x: 0.0, disp_y: 0.0, } @@ -2152,6 +2155,12 @@ impl Game { self.last_wave_value = Self::squad_value(&list); let before = self.ents.len(); self.spawn_squad(&list, false); + // the paladin of every fifth wave is one of the Order's champions + if boss.is_some() { + if let Some(j) = (before..self.ents.len()).find(|&j| self.ents[j].kind == Paladin) { + self.make_boss(j, 1 + ((w / 5 - 1) % 4) as u8); + } + } if goal != self.me_side { for e in self.ents[before..].iter_mut() { e.target = Some(goal); @@ -2731,6 +2740,7 @@ impl Game { } pub(crate) fn kill(&mut self, j: usize) { + self.boss_loot(j); let e = &self.ents[j]; if e.hero && e.captive == 0 && self.prison_space() > 0 { // knocked out instead of killed: imps carry the hero to the torture chamber @@ -4450,6 +4460,9 @@ impl Game { // ---------------------------------------------------------------- heroes fn hero_ai(&mut self, e: &mut Ent, dt: f32) { + if e.boss > 0 { + self.boss_ability(e, dt); + } if e.kind == EK::Fairy { // fairy dust: the heroes around are healed a little all the time for o in self.ents.iter_mut() { diff --git a/src/main.rs b/src/main.rs index 56bd306..4a3ad4c 100644 --- a/src/main.rs +++ b/src/main.rs @@ -30,6 +30,7 @@ mod save; #[cfg(test)] mod scenes; mod blocks; +mod bosses; mod bridges; mod mscript; mod settings; diff --git a/src/traps.rs b/src/traps.rs index 6c6fa0c..05bd951 100644 --- a/src/traps.rs +++ b/src/traps.rs @@ -377,7 +377,7 @@ impl Game { true } - fn damage_unit(&mut self, j: usize, dmg: f32) { + pub(crate) fn damage_unit(&mut self, j: usize, dmg: f32) { let o = &mut self.ents[j]; o.hp -= dmg; o.flash = 0.15; diff --git a/src/ui.rs b/src/ui.rs index 85f5ef5..0b57f81 100644 --- a/src/ui.rs +++ b/src/ui.rs @@ -719,6 +719,18 @@ impl Ui { } } + // ---------------- a champion of the Order on the field: his name and health under the top bar + if let Some(b) = g.boss_alive() { + let name = crate::i18n::tr(crate::bosses::boss_name(b.boss).trim_start_matches('@')); + let bw = (name.chars().count() as i32 + 26).min(cols - 4); + let bx = ((cols - bw) / 2) as f32 * CW(); + let by = self.hud_h; + gfx.window(bx, by, bw, 3, S(), false, RED, BG); + gfx.text(&name, bx + CW() * 2.0, by + CH(), S(), GOLD); + let bar_cols = (bw - name.chars().count() as i32 - 6).max(6); + ascii_bar(gfx, bx + CW() * (name.chars().count() as f32 + 4.0), by + CH(), bar_cols as usize, b.hp / b.max_hp, RED, S()); + } + // ---------------- toolbar: buttons of 6 x 3 characters, icons and labels on the grid let tb_rows = 5; let tb_y = sh - tb_rows as f32 * CH();