Co-op offline with an AI partner
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
e3a9d32d61
commit
f87b59390e
22 files changed
+272
-5
No files matched your search
@@ -90,6 +90,13 @@ per turn. Fog of war, particles and the camera are local and not part of the sha
|
||||
five strongest creatures and 20% of his gold, like the single-player `Carry`. The host stores the progress
|
||||
(missions done, chosen branches, run seed) in local storage under `dc.coop_editor_progress` (campaign name ->
|
||||
`CampaignProgress`). A run ends when no next mission fits co-op.
|
||||
- **Co-op offline with an AI partner** (*AI partner* on the Match page, works without the server): the selected
|
||||
co-op mission (built-in, the league or the editor campaign) starts like a match with the player on side 0 and
|
||||
an AI seat on side 2 (`App::start_offline_coop`: `start_match` + `sync_ai_sides`), then `end_match` drops the
|
||||
turn clock and the world runs on this computer like a single-player game. The partner (`Game::is_partner`) is
|
||||
an allied rival-style master with his own dungeon: two of three healthy creatures follow the player's attack
|
||||
flag (and attack the gate next to it), and every minute he sends 400 gold to an ally below 300. An editor
|
||||
campaign run goes on locally: the next mission (or the same after a defeat) with the run seed and the carry.
|
||||
- **Teams in versus**: on the Match page every seat has a team A-D (default: a team of its own), set with
|
||||
`{"t":"set_team","team":n}` for the own seat; the host may add `"side"` for a free or AI seat. Only before the
|
||||
start. Room updates carry `team` per seat, the room list `teams`, and `start` a `teams` list in the order of
|
||||
|
||||
@@ -662,6 +662,8 @@ net.coop_create = Kooperace
|
||||
net.coop_editor = Kampaň z editoru
|
||||
net.coop_builtin = Vestavěná kampaň
|
||||
net.coop_league = Liga proti Světlé stráži
|
||||
net.coop_solo = AI partner
|
||||
net.coop_solo_started = Kooperace bez serveru: tvůj AI partner si staví vlastní kobku, následuje tvou útočnou vlajku a pomáhá zlatem.
|
||||
net.coop_editor_mission = Kampaň z editoru {0}: mise {1} ({2})
|
||||
net.coop_editor_none = Žádná mise kampaně z editoru se nehodí pro kooperaci (mapa aspoň 72x72, jiný cíl než soupeřící mistři)
|
||||
net.join = Připojit
|
||||
@@ -709,6 +711,7 @@ msg.wave_on_rival1 = Vlna {0}: hrdinové ({1}) táhnou na zeleného mistra.
|
||||
msg.wave_on_rival2 = Vlna {0}: hrdinové ({1}) táhnou na modrou mistryni.
|
||||
msg.wave_warn_on_master = Zvědové hlásí: vlna hrdinů {0} dorazí za 30 sekund a míří na pána {1}.
|
||||
msg.wave_on_master = Vlna {0}: {1} hrdinů táhne na pána {2}.
|
||||
msg.partner_out = Srdce tvého AI partnera padlo – bojuj dál sám!
|
||||
hud.rival1 = Zelený
|
||||
hud.rival2 = Modrá
|
||||
msg.creature_freed = Tvor přetrhl řetězy a přidal se k tobě: {0}!
|
||||
|
||||
@@ -661,6 +661,8 @@ net.coop_create = Co-op-kampagne
|
||||
net.coop_editor = Editor-kampagne
|
||||
net.coop_builtin = Indbygget kampagne
|
||||
net.coop_league = Forbundet mod Lysvagten
|
||||
net.coop_solo = AI-partner
|
||||
net.coop_solo_started = Co-op uden server: din AI-partner bygger sit eget fangehul, følger dit angrebsflag og hjælper med guld.
|
||||
net.coop_editor_mission = Editor-kampagne {0}: mission {1} ({2})
|
||||
net.coop_editor_none = Ingen mission i editor-kampagnen passer til co-op (kort mindst 72x72, et mål ud over rivaliserende mestre)
|
||||
net.join = Deltag
|
||||
@@ -708,6 +710,7 @@ msg.wave_on_rival1 = Bølge {0}: {1} helte marcherer mod den grønne mester.
|
||||
msg.wave_on_rival2 = Bølge {0}: {1} helte marcherer mod den blå mester.
|
||||
msg.wave_warn_on_master = Spejdere melder: heltebølge {0} kommer om 30 sekunder og går mod mester {1}.
|
||||
msg.wave_on_master = Bølge {0}: {1} helte marcherer mod mester {2}.
|
||||
msg.partner_out = Din AI-partners hjerte er faldet – kæmp videre alene!
|
||||
hud.rival1 = Grøn
|
||||
hud.rival2 = Blå
|
||||
msg.creature_freed = En {0} sprænger sine lænker og slutter sig til dig!
|
||||
|
||||
@@ -661,6 +661,8 @@ net.coop_create = Koop-Kampagne
|
||||
net.coop_editor = Editor-Kampagne
|
||||
net.coop_builtin = Eingebaute Kampagne
|
||||
net.coop_league = Bund gegen die Lichtwacht
|
||||
net.coop_solo = KI-Partner
|
||||
net.coop_solo_started = Koop ohne Server: Dein KI-Partner baut einen eigenen Kerker, folgt deiner Angriffsflagge und hilft mit Gold aus.
|
||||
net.coop_editor_mission = Editor-Kampagne {0}: Mission {1} ({2})
|
||||
net.coop_editor_none = Keine Mission der Editor-Kampagne passt für Koop (Karte mindestens 72x72, ein Ziel außer Rivalen-Meistern)
|
||||
net.join = Beitreten
|
||||
@@ -708,6 +710,7 @@ msg.wave_on_rival1 = Welle {0}: {1} Helden ziehen gegen den grünen Meister.
|
||||
msg.wave_on_rival2 = Welle {0}: {1} Helden ziehen gegen den blauen Meister.
|
||||
msg.wave_warn_on_master = Späher melden: Heldenwelle {0} kommt in 30 Sekunden – sie zieht gegen Meister {1}.
|
||||
msg.wave_on_master = Welle {0}: {1} Helden ziehen gegen Meister {2}.
|
||||
msg.partner_out = Das Herz deines KI-Partners ist gefallen – kämpfe allein weiter!
|
||||
hud.rival1 = Grün
|
||||
hud.rival2 = Blau
|
||||
msg.creature_freed = Ein {0} sprengt seine Ketten und schließt sich dir an!
|
||||
|
||||
@@ -662,6 +662,8 @@ net.coop_create = Συνεργατική εκστρ.
|
||||
net.coop_editor = Εκστρατεία επεξεργαστή
|
||||
net.coop_builtin = Ενσωματωμένη εκστρατεία
|
||||
net.coop_league = Συμμαχία κατά της Φωτοφρουράς
|
||||
net.coop_solo = Σύμμαχος ΤΝ
|
||||
net.coop_solo_started = Συνεργασία χωρίς διακομιστή: ο σύμμαχός σου ΤΝ χτίζει το δικό του μπουντρούμι, ακολουθεί τη σημαία επίθεσής σου και βοηθά με χρυσό.
|
||||
net.coop_editor_mission = Εκστρατεία επεξεργαστή {0}: αποστολή {1} ({2})
|
||||
net.coop_editor_none = Καμία αποστολή της εκστρατείας επεξεργαστή δεν ταιριάζει για συνεργασία (χάρτης τουλάχιστον 72x72, ένας στόχος πέρα από τους αντίπαλους άρχοντες)
|
||||
net.join = Συμμετοχή
|
||||
@@ -709,6 +711,7 @@ msg.wave_on_rival1 = Κύμα {0}: {1} ήρωες βαδίζουν κατά το
|
||||
msg.wave_on_rival2 = Κύμα {0}: {1} ήρωες βαδίζουν κατά της γαλάζιας άρχοντος.
|
||||
msg.wave_warn_on_master = Οι ανιχνευτές αναφέρουν: το κύμα ηρώων {0} έρχεται σε 30 δευτερόλεπτα και κατευθύνεται στον άρχοντα {1}.
|
||||
msg.wave_on_master = Κύμα {0}: {1} ήρωες βαδίζουν κατά του άρχοντα {2}.
|
||||
msg.partner_out = Η καρδιά του συμμάχου σου ΤΝ έπεσε – πολέμησε μόνος!
|
||||
hud.rival1 = Πράσινος
|
||||
hud.rival2 = Γαλάζια
|
||||
msg.creature_freed = Σπάει τις αλυσίδες του και έρχεται μαζί σου: {0}!
|
||||
|
||||
@@ -662,6 +662,8 @@ net.coop_create = Co-op campaign
|
||||
net.coop_editor = Editor campaign
|
||||
net.coop_builtin = Built-in campaign
|
||||
net.coop_league = League against the Lightwatch
|
||||
net.coop_solo = AI partner
|
||||
net.coop_solo_started = Co-op without the server: your AI partner builds its own dungeon, follows your attack flag and helps out with gold.
|
||||
net.coop_editor_mission = Editor campaign {0}: mission {1} ({2})
|
||||
net.coop_editor_none = No mission of the editor campaign fits co-op (map at least 72x72, a goal besides rival masters)
|
||||
net.join = Join
|
||||
@@ -709,6 +711,7 @@ msg.wave_on_rival1 = Wave {0}: {1} heroes march on the green master.
|
||||
msg.wave_on_rival2 = Wave {0}: {1} heroes march on the blue master.
|
||||
msg.wave_warn_on_master = Scouts report: hero wave {0} comes in 30 seconds – it heads for master {1}.
|
||||
msg.wave_on_master = Wave {0}: {1} heroes march on master {2}.
|
||||
msg.partner_out = Your AI partner's heart has fallen – fight on alone!
|
||||
hud.rival1 = Green
|
||||
hud.rival2 = Blue
|
||||
msg.creature_freed = A {0} breaks its chains and joins you!
|
||||
|
||||
@@ -704,6 +704,7 @@ msg.wave_on_rival1 = Oleada {0}: {1} héroes marchan contra el señor verde.
|
||||
msg.wave_on_rival2 = Oleada {0}: {1} héroes marchan contra la señora azul.
|
||||
msg.wave_warn_on_master = Los exploradores informan: la oleada {0} llega en 30 segundos y va a por el maestro {1}.
|
||||
msg.wave_on_master = Oleada {0}: {1} héroes marchan contra el maestro {2}.
|
||||
msg.partner_out = ¡El corazón de tu socio IA ha caído: sigue luchando solo!
|
||||
hud.rival1 = Verde
|
||||
hud.rival2 = Azul
|
||||
msg.creature_freed = ¡Un {0} rompe sus cadenas y se une a ti!
|
||||
@@ -1333,6 +1334,8 @@ demo.m2.wave = Han pasado dos minutos: llegan los héroes.
|
||||
net.coop_editor = Campaña del editor
|
||||
net.coop_builtin = Campaña integrada
|
||||
net.coop_league = Liga contra la Guardia Luminosa
|
||||
net.coop_solo = Socio IA
|
||||
net.coop_solo_started = Cooperativo sin servidor: tu socio IA construye su propia mazmorra, sigue tu bandera de ataque y te ayuda con oro.
|
||||
net.coop_editor_mission = Campaña del editor {0}: misión {1} ({2})
|
||||
net.coop_editor_none = Ninguna misión de la campaña del editor sirve para cooperativo (mapa de al menos 72x72, un objetivo aparte de los maestros rivales)
|
||||
blk.until = Repetir hasta
|
||||
|
||||
@@ -662,6 +662,8 @@ net.coop_create = Yhteiskampanja
|
||||
net.coop_editor = Editorikampanja
|
||||
net.coop_builtin = Sisäinen kampanja
|
||||
net.coop_league = Liitto Valonvartiota vastaan
|
||||
net.coop_solo = Kumppani
|
||||
net.coop_solo_started = Yhteistyö ilman palvelinta: tekoälykumppanisi rakentaa oman luolastonsa, seuraa hyökkäyslippuasi ja auttaa kullalla.
|
||||
net.coop_editor_mission = Editorikampanja {0}: tehtävä {1} ({2})
|
||||
net.coop_editor_none = Mikään editorikampanjan tehtävä ei sovi yhteispeliin (kartta vähintään 72x72, muukin tavoite kuin kilpailevat mestarit)
|
||||
net.join = Liity
|
||||
@@ -709,6 +711,7 @@ msg.wave_on_rival1 = Aalto {0}: {1} sankaria marssii vihreää mestaria vastaan.
|
||||
msg.wave_on_rival2 = Aalto {0}: {1} sankaria marssii sinistä mestaria vastaan.
|
||||
msg.wave_warn_on_master = Tiedustelijat ilmoittavat: sankariaalto {0} saapuu 30 sekunnissa ja hyökkää mestari {1} vastaan.
|
||||
msg.wave_on_master = Aalto {0}: {1} sankaria marssii mestari {2} vastaan.
|
||||
msg.partner_out = Tekoälykumppanisi sydän on murtunut – taistele yksin!
|
||||
hud.rival1 = Vihreä
|
||||
hud.rival2 = Sininen
|
||||
msg.creature_freed = Kahleet katkeavat ja uusi olento liittyy joukkoosi: {0}!
|
||||
|
||||
@@ -662,6 +662,8 @@ net.coop_create = Co-op kampanja
|
||||
net.coop_editor = Kampanja uređivača
|
||||
net.coop_builtin = Ugrađena kampanja
|
||||
net.coop_league = Savez protiv Svjetlostraže
|
||||
net.coop_solo = AI partner
|
||||
net.coop_solo_started = Suradnja bez poslužitelja: tvoj AI partner gradi vlastitu tamnicu, slijedi tvoju zastavu napada i pomaže zlatom.
|
||||
net.coop_editor_mission = Kampanja uređivača {0}: misija {1} ({2})
|
||||
net.coop_editor_none = Nijedna misija kampanje uređivača ne odgovara co-op igri (karta barem 72x72, cilj osim suparničkih gospodara)
|
||||
net.join = Pridruži
|
||||
@@ -709,6 +711,7 @@ msg.wave_on_rival1 = Val {0}: junaci ({1}) marširaju na zelenog gospodara.
|
||||
msg.wave_on_rival2 = Val {0}: junaci ({1}) marširaju na plavu gospodaricu.
|
||||
msg.wave_warn_on_master = Izvidnici javljaju: val junaka {0} stiže za 30 sekundi i kreće na gospodara {1}.
|
||||
msg.wave_on_master = Val {0}: {1} junaka maršira na gospodara {2}.
|
||||
msg.partner_out = Srce tvog AI partnera je palo – bori se dalje sam!
|
||||
hud.rival1 = Zeleni
|
||||
hud.rival2 = Plava
|
||||
msg.creature_freed = Raskida okove i pridružuje ti se: {0}!
|
||||
|
||||
@@ -662,6 +662,8 @@ net.coop_create = Koop hadjárat
|
||||
net.coop_editor = Saját hadjárat
|
||||
net.coop_builtin = Beépített hadjárat
|
||||
net.coop_league = Szövetség a Fényőrség ellen
|
||||
net.coop_solo = MI-társ
|
||||
net.coop_solo_started = Kooperatív szerver nélkül: MI-társad saját kazamatát épít, követi a támadási zászlódat és arannyal segít.
|
||||
net.coop_editor_mission = Saját hadjárat ({0}): {1}. küldetés ({2})
|
||||
net.coop_editor_none = A saját hadjárat egyik küldetése sem alkalmas kooperatív játékra (térkép legalább 72x72, a rivális mestereken kívül még egy cél)
|
||||
net.join = Belép
|
||||
@@ -709,6 +711,7 @@ msg.wave_on_rival1 = {0}. hullám: {1} hős vonul a zöld mester ellen.
|
||||
msg.wave_on_rival2 = {0}. hullám: {1} hős vonul a kék mester ellen.
|
||||
msg.wave_warn_on_master = A felderítők jelentik: a(z) {0}. hősroham 30 másodperc múlva érkezik, és {1} mester ellen vonul.
|
||||
msg.wave_on_master = {0}. roham: {1} hős vonul {2} mester ellen.
|
||||
msg.partner_out = MI-társad szíve elesett – harcolj tovább egyedül!
|
||||
hud.rival1 = Zöld
|
||||
hud.rival2 = Kék
|
||||
msg.creature_freed = Széttépi a láncait, és hozzád csatlakozik: {0}!
|
||||
|
||||
@@ -704,6 +704,7 @@ msg.wave_on_rival1 = Ondata {0}: {1} eroi marciano sul maestro verde.
|
||||
msg.wave_on_rival2 = Ondata {0}: {1} eroi marciano sulla maestra blu.
|
||||
msg.wave_warn_on_master = Gli esploratori riferiscono: l'ondata {0} arriva tra 30 secondi e punta sul maestro {1}.
|
||||
msg.wave_on_master = Ondata {0}: {1} eroi marciano contro il maestro {2}.
|
||||
msg.partner_out = Il cuore del tuo alleato IA è caduto: combatti da solo!
|
||||
hud.rival1 = Verde
|
||||
hud.rival2 = Blu
|
||||
msg.creature_freed = Spezza le catene e si unisce a te: {0}!
|
||||
@@ -1333,6 +1334,8 @@ demo.m2.wave = Sono passati due minuti: arrivano gli eroi.
|
||||
net.coop_editor = Campagna dell'editor
|
||||
net.coop_builtin = Campagna integrata
|
||||
net.coop_league = Lega contro la Guardia della Luce
|
||||
net.coop_solo = Alleato IA
|
||||
net.coop_solo_started = Cooperativa senza server: il tuo alleato IA costruisce il suo dungeon, segue la tua bandiera d'attacco e ti aiuta con l'oro.
|
||||
net.coop_editor_mission = Campagna dell'editor {0}: missione {1} ({2})
|
||||
net.coop_editor_none = Nessuna missione della campagna dell'editor è adatta alla cooperativa (mappa almeno 72x72, un obiettivo oltre ai maestri rivali)
|
||||
blk.until = Ripeti finché
|
||||
|
||||
@@ -662,6 +662,8 @@ net.coop_create = Co-op-veldtocht
|
||||
net.coop_editor = Editorveldtocht
|
||||
net.coop_builtin = Vaste veldtocht
|
||||
net.coop_league = Liga tegen de Lichtwacht
|
||||
net.coop_solo = AI-partner
|
||||
net.coop_solo_started = Co-op zonder server: je AI-partner bouwt een eigen kerker, volgt je aanvalsvlag en helpt met goud.
|
||||
net.coop_editor_mission = Editorveldtocht {0}: missie {1} ({2})
|
||||
net.coop_editor_none = Geen missie van de editorveldtocht is geschikt voor co-op (kaart minstens 72x72, een doel naast rivaliserende meesters)
|
||||
net.join = Meedoen
|
||||
@@ -709,6 +711,7 @@ msg.wave_on_rival1 = Golf {0}: {1} helden trekken op tegen de groene meester.
|
||||
msg.wave_on_rival2 = Golf {0}: {1} helden trekken op tegen de blauwe meester.
|
||||
msg.wave_warn_on_master = Verkenners melden: heldengolf {0} komt over 30 seconden en trekt naar meester {1}.
|
||||
msg.wave_on_master = Golf {0}: {1} helden trekken op tegen meester {2}.
|
||||
msg.partner_out = Het hart van je AI-partner is gevallen – vecht alleen verder!
|
||||
hud.rival1 = Groen
|
||||
hud.rival2 = Blauw
|
||||
msg.creature_freed = Een {0} breekt zijn ketens en sluit zich bij je aan!
|
||||
|
||||
@@ -661,6 +661,8 @@ net.coop_create = Co-op-kampanje
|
||||
net.coop_editor = Editorkampanje
|
||||
net.coop_builtin = Innebygd kampanje
|
||||
net.coop_league = Forbundet mot Lysvakten
|
||||
net.coop_solo = KI-partner
|
||||
net.coop_solo_started = Samarbeid uten server: KI-partneren din bygger sitt eget fangehull, følger angrepsflagget ditt og hjelper med gull.
|
||||
net.coop_editor_mission = Editorkampanje {0}: oppdrag {1} ({2})
|
||||
net.coop_editor_none = Ingen oppdrag i editorkampanjen passer for co-op (kart minst 72x72, et mål i tillegg til rivaliserende mestere)
|
||||
net.join = Bli med
|
||||
@@ -708,6 +710,7 @@ msg.wave_on_rival1 = Bølge {0}: {1} helter marsjerer mot den grønne mesteren.
|
||||
msg.wave_on_rival2 = Bølge {0}: {1} helter marsjerer mot den blå mesteren.
|
||||
msg.wave_warn_on_master = Speidere melder: heltebølge {0} kommer om 30 sekunder og går mot mester {1}.
|
||||
msg.wave_on_master = Bølge {0}: {1} helter marsjerer mot mester {2}.
|
||||
msg.partner_out = Hjertet til KI-partneren din har falt – kjemp videre alene!
|
||||
hud.rival1 = Grønn
|
||||
hud.rival2 = Blå
|
||||
msg.creature_freed = En {0} sprenger lenkene sine og slutter seg til deg!
|
||||
|
||||
@@ -662,6 +662,8 @@ net.coop_create = Campanha coop.
|
||||
net.coop_editor = Campanha do editor
|
||||
net.coop_builtin = Campanha incluída
|
||||
net.coop_league = Liga contra a Guarda da Luz
|
||||
net.coop_solo = Parceiro IA
|
||||
net.coop_solo_started = Cooperativo sem servidor: o teu parceiro IA constrói a sua própria masmorra, segue a tua bandeira de ataque e ajuda com ouro.
|
||||
net.coop_editor_mission = Campanha do editor {0}: missão {1} ({2})
|
||||
net.coop_editor_none = Nenhuma missão da campanha do editor serve para cooperativo (mapa de pelo menos 72x72, um objetivo além dos mestres rivais)
|
||||
net.join = Entrar
|
||||
@@ -709,6 +711,7 @@ msg.wave_on_rival1 = Vaga {0}: {1} heróis marcham contra o mestre verde.
|
||||
msg.wave_on_rival2 = Vaga {0}: {1} heróis marcham contra a mestra azul.
|
||||
msg.wave_warn_on_master = Os batedores informam: a onda {0} chega em 30 segundos e vai contra o mestre {1}.
|
||||
msg.wave_on_master = Onda {0}: {1} heróis marcham contra o mestre {2}.
|
||||
msg.partner_out = O coração do teu parceiro IA caiu – continua a lutar sozinho!
|
||||
hud.rival1 = Verde
|
||||
hud.rival2 = Azul
|
||||
msg.creature_freed = Um {0} parte as correntes e junta-se a ti!
|
||||
|
||||
@@ -662,6 +662,8 @@ net.coop_create = Campanie co-op
|
||||
net.coop_editor = Campanie din editor
|
||||
net.coop_builtin = Campanie inclusă
|
||||
net.coop_league = Liga împotriva Străjii Luminii
|
||||
net.coop_solo = Partener IA
|
||||
net.coop_solo_started = Cooperativ fără server: partenerul tău IA își construiește propria temniță, îți urmează steagul de atac și te ajută cu aur.
|
||||
net.coop_editor_mission = Campania din editor {0}: misiunea {1} ({2})
|
||||
net.coop_editor_none = Nicio misiune din campania editorului nu se potrivește pentru co-op (hartă de cel puțin 72x72, un obiectiv în afară de stăpânii rivali)
|
||||
net.join = Intră
|
||||
@@ -709,6 +711,7 @@ msg.wave_on_rival1 = Valul {0}: {1} eroi mărșăluiesc asupra stăpânului verd
|
||||
msg.wave_on_rival2 = Valul {0}: {1} eroi mărșăluiesc asupra stăpânei albastre.
|
||||
msg.wave_warn_on_master = Cercetașii raportează: valul de eroi {0} sosește în 30 de secunde și merge spre stăpânul {1}.
|
||||
msg.wave_on_master = Valul {0}: {1} eroi mărșăluiesc spre stăpânul {2}.
|
||||
msg.partner_out = Inima partenerului tău IA a căzut – luptă mai departe singur!
|
||||
hud.rival1 = Verde
|
||||
hud.rival2 = Albastru
|
||||
msg.creature_freed = Un {0} își rupe lanțurile și ți se alătură!
|
||||
|
||||
@@ -704,6 +704,7 @@ msg.wave_on_rival1 = Волна {0}: {1} героев идут на зелёно
|
||||
msg.wave_on_rival2 = Волна {0}: {1} героев идут на синюю хозяйку.
|
||||
msg.wave_warn_on_master = Разведчики докладывают: волна {0} будет через 30 секунд и идёт на мастера {1}.
|
||||
msg.wave_on_master = Волна {0}: {1} героев идут на мастера {2}.
|
||||
msg.partner_out = Сердце вашего ИИ-союзника пало – сражайтесь в одиночку!
|
||||
hud.rival1 = Зелёный
|
||||
hud.rival2 = Синяя
|
||||
msg.creature_freed = Существо рвёт цепи и присоединяется к тебе: {0}!
|
||||
@@ -1333,6 +1334,8 @@ demo.m2.wave = Две минуты прошли: идут герои.
|
||||
net.coop_editor = Кампания из редактора
|
||||
net.coop_builtin = Встроенная кампания
|
||||
net.coop_league = Союз против Светлого Дозора
|
||||
net.coop_solo = ИИ-союзник
|
||||
net.coop_solo_started = Кооператив без сервера: ИИ-союзник строит своё подземелье, идёт к вашему флагу атаки и помогает золотом.
|
||||
net.coop_editor_mission = Кампания из редактора {0}: миссия {1} ({2})
|
||||
net.coop_editor_none = Ни одна миссия кампании из редактора не подходит для кооператива (карта не меньше 72x72, цель помимо мастеров-соперников)
|
||||
blk.until = Повторять, пока не
|
||||
|
||||
@@ -662,6 +662,8 @@ net.coop_create = Kooperácia
|
||||
net.coop_editor = Kampaň z editora
|
||||
net.coop_builtin = Vstavaná kampaň
|
||||
net.coop_league = Liga proti Svetlej stráži
|
||||
net.coop_solo = AI partner
|
||||
net.coop_solo_started = Kooperácia bez servera: tvoj AI partner si stavia vlastnú kobku, nasleduje tvoju útočnú vlajku a pomáha zlatom.
|
||||
net.coop_editor_mission = Kampaň z editora {0}: misia {1} ({2})
|
||||
net.coop_editor_none = Žiadna misia kampane z editora sa nehodí na kooperáciu (mapa aspoň 72x72, iný cieľ než súperiaci majstri)
|
||||
net.join = Pripojiť
|
||||
@@ -709,6 +711,7 @@ msg.wave_on_rival1 = Vlna {0}: hrdinovia ({1}) tiahnu na zeleného majstra.
|
||||
msg.wave_on_rival2 = Vlna {0}: hrdinovia ({1}) tiahnu na modrú majsterku.
|
||||
msg.wave_warn_on_master = Prieskumníci hlásia: vlna hrdinov {0} príde o 30 sekúnd a mieri na pána {1}.
|
||||
msg.wave_on_master = Vlna {0}: {1} hrdinov pochoduje na pána {2}.
|
||||
msg.partner_out = Srdce tvojho AI partnera padlo – bojuj ďalej sám!
|
||||
hud.rival1 = Zelený
|
||||
hud.rival2 = Modrá
|
||||
msg.creature_freed = Tvor roztrhol reťaze a pridal sa k tebe: {0}!
|
||||
|
||||
@@ -662,6 +662,8 @@ net.coop_create = Ко-оп кампања
|
||||
net.coop_editor = Кампања уређивача
|
||||
net.coop_builtin = Уграђена кампања
|
||||
net.coop_league = Савез против Светлостраже
|
||||
net.coop_solo = АИ партнер
|
||||
net.coop_solo_started = Сарадња без сервера: твој АИ партнер гради сопствену тамницу, прати твоју заставу напада и помаже златом.
|
||||
net.coop_editor_mission = Кампања уређивача {0}: мисија {1} ({2})
|
||||
net.coop_editor_none = Ниједна мисија кампање уређивача не одговара ко-оп игри (мапа најмање 72x72, циљ осим супарничких господара)
|
||||
net.join = Придружи
|
||||
@@ -709,6 +711,7 @@ msg.wave_on_rival1 = Талас {0}: хероји ({1}) марширају на
|
||||
msg.wave_on_rival2 = Талас {0}: хероји ({1}) марширају на плаву господарицу.
|
||||
msg.wave_warn_on_master = Извиђачи јављају: талас јунака {0} стиже за 30 секунди и креће на господара {1}.
|
||||
msg.wave_on_master = Талас {0}: {1} јунака маршира на господара {2}.
|
||||
msg.partner_out = Срце твог АИ партнера је пало – бори се даље сам!
|
||||
hud.rival1 = Зелени
|
||||
hud.rival2 = Плава
|
||||
msg.creature_freed = Раскида окове и придружује ти се: {0}!
|
||||
|
||||
@@ -662,6 +662,8 @@ net.coop_create = Co-op-kampanj
|
||||
net.coop_editor = Egen kampanj
|
||||
net.coop_builtin = Inbyggd kampanj
|
||||
net.coop_league = Förbundet mot Ljusvakten
|
||||
net.coop_solo = AI-partner
|
||||
net.coop_solo_started = Samarbete utan server: din AI-partner bygger en egen fängelsehåla, följer din attackflagga och hjälper till med guld.
|
||||
net.coop_editor_mission = Egen kampanj {0}: uppdrag {1} ({2})
|
||||
net.coop_editor_none = Inget uppdrag i redigerarens kampanj passar för co-op (karta minst 72x72, ett mål utöver rivaliserande mästare)
|
||||
net.join = Gå med
|
||||
@@ -709,6 +711,7 @@ msg.wave_on_rival1 = Våg {0}: {1} hjältar marscherar mot den gröna mästaren.
|
||||
msg.wave_on_rival2 = Våg {0}: {1} hjältar marscherar mot den blå mästaren.
|
||||
msg.wave_warn_on_master = Spejare rapporterar: hjältevåg {0} kommer om 30 sekunder och går mot mästare {1}.
|
||||
msg.wave_on_master = Våg {0}: {1} hjältar marscherar mot mästare {2}.
|
||||
msg.partner_out = Din AI-partners hjärta har fallit – kämpa vidare ensam!
|
||||
hud.rival1 = Grön
|
||||
hud.rival2 = Blå
|
||||
msg.creature_freed = En {0} bryter sina kedjor och ansluter sig till dig!
|
||||
|
||||
+67
-2
@@ -343,6 +343,16 @@ fn account_action(a: MenuAction, cloud: &mut Cloud, menu: &mut Menu, app: &mut A
|
||||
menu.error = Some(i18n::tr("net.coop_editor_none"));
|
||||
}
|
||||
}
|
||||
MenuAction::MatchCoopSolo(doc, mission) => {
|
||||
// the open lobby connection is not needed: the game runs on this computer alone
|
||||
if app.start_offline_coop(doc, mission, None, Vec::new()) {
|
||||
menu.page = None;
|
||||
menu.error = None;
|
||||
app.set_status(i18n::tr("net.coop_solo_started"), now);
|
||||
} else {
|
||||
menu.error = Some(i18n::tr("net.coop_editor_none"));
|
||||
}
|
||||
}
|
||||
MenuAction::MatchTakeSide(side) => app.client.take_side(side),
|
||||
MenuAction::MatchSetAi(side, ai) => app.client.set_ai(side, ai),
|
||||
MenuAction::MatchSetTeam(side, team, own) => app.client.set_team(if own { None } else { Some(side) }, team),
|
||||
@@ -500,6 +510,7 @@ struct App {
|
||||
coop: bool,
|
||||
coop_editor: bool, // the co-op match plays a mission of an editor campaign
|
||||
coop_run: Option<CoopRun>, // ... and this is where the run through that campaign stands
|
||||
offline: bool, // a co-op game played without the server: the other seat is an AI partner
|
||||
fps: f32,
|
||||
renderer: String,
|
||||
frame_ms: f32,
|
||||
@@ -624,9 +635,32 @@ impl App {
|
||||
self.match_mode = false;
|
||||
self.coop = false;
|
||||
self.coop_editor = false;
|
||||
self.offline = false;
|
||||
self.client.on_disconnect();
|
||||
}
|
||||
|
||||
// A co-op mission without the server: built like a match start with the player on side 0 and an
|
||||
// AI partner on side 2, then the world runs on this computer alone (no turns, no relay).
|
||||
// `doc` is an editor campaign (None: a built-in co-op mission); `run_seed`/`carry` continue a run.
|
||||
fn start_offline_coop(&mut self, doc: Option<campaign_doc::CampaignDoc>, mission: usize, run_seed: Option<u32>, carry: Vec<(u8, campaign::Carry)>) -> bool {
|
||||
let size = match &doc {
|
||||
Some(d) => d.missions.get(mission).map(|m| m.size),
|
||||
None => campaign::missions().get(mission).map(|m| m.size),
|
||||
};
|
||||
let Some((map_w, map_h)) = size else { return false };
|
||||
let next = run_seed.is_some();
|
||||
let seed = run_seed.unwrap_or_else(|| next_seed(self.seed));
|
||||
let editor = doc.is_some();
|
||||
let info = StartInfo { seed, map_w, map_h, difficulty: self.settings.difficulty, sides: vec![0, 2], local_side: Some(0), mission: Some(mission), ai: vec![2], doc, editor, teams: Vec::new(), next, carry };
|
||||
if !self.start_match_game(info) {
|
||||
return false;
|
||||
}
|
||||
self.game.end_match();
|
||||
self.offline = true;
|
||||
self.recorded = false;
|
||||
true
|
||||
}
|
||||
|
||||
// the shared part of a co-op start (built-in or editor mission): the masters, AI seats, settings
|
||||
fn start_coop_game(&mut self, mut game: Game, info: &StartInfo) -> bool {
|
||||
let local = info.local_side.unwrap_or(0);
|
||||
@@ -758,7 +792,7 @@ async fn main() {
|
||||
let seed = (miniquad::date::now() * 1000.0) as u32 | 1;
|
||||
// behind the title menu: the endless bridge battle
|
||||
let (game, cam) = Game::new_demo(seed);
|
||||
let mut app = App { game, cam, settings, seed, status: None, recorded: false, client: MatchClient::new(), ws_id: 0, match_mode: false, coop: false, coop_editor: false, coop_run: None, fps: 0.0, renderer: platform::renderer(), frame_ms: 0.0, frame_hist: [0.0; FRAME_HIST], frame_hist_pos: 0, custom_campaign: None, custom_progress: None, custom_mission_index: None, custom_next_index: None, editor: None, editor_testing: false, editor_test_only: false, editor_public_pending: false, editor_loading: None, pending_briefing: None };
|
||||
let mut app = App { game, cam, settings, seed, status: None, recorded: false, client: MatchClient::new(), ws_id: 0, match_mode: false, coop: false, coop_editor: false, coop_run: None, offline: false, fps: 0.0, renderer: platform::renderer(), frame_ms: 0.0, frame_hist: [0.0; FRAME_HIST], frame_hist_pos: 0, custom_campaign: None, custom_progress: None, custom_mission_index: None, custom_next_index: None, editor: None, editor_testing: false, editor_test_only: false, editor_public_pending: false, editor_loading: None, pending_briefing: None };
|
||||
menu.progress = platform::storage_get(PROGRESS_KEY).and_then(|s| serde_json::from_str(&s).ok()).unwrap_or_default();
|
||||
apply_game_settings(&mut app.game, &app.settings);
|
||||
let mut inp = Input { ldown: None, rdown: None, rlast: (0.0, 0.0), rdrag: false, mlast: None, minimap_drag: false, tlast: None, touch_world: false, inspect: None };
|
||||
@@ -1471,6 +1505,7 @@ app.pending_briefing = briefing_of(&menu.campaign_doc, i);
|
||||
| MenuAction::MatchAbort
|
||||
| MenuAction::MatchLeave
|
||||
| MenuAction::MatchCoopEditorCreate(..)
|
||||
| MenuAction::MatchCoopSolo(..)
|
||||
| MenuAction::MatchChat(..)
|
||||
| MenuAction::MatchEditField(..)) => account_action(a, &mut cloud, &mut menu, &mut app, now),
|
||||
MenuAction::EditName => {
|
||||
@@ -1517,13 +1552,41 @@ app.pending_briefing = briefing_of(&menu.campaign_doc, i);
|
||||
// a co-op mission of an editor campaign is over: the host starts the next one (or the
|
||||
// same one again) for everybody; the others wait for it
|
||||
UiAction::EndNext if app.coop_editor && app.game.local_side == 0 && app.coop_run.as_ref().map_or(false, |r| r.next.is_some() && !r.sent) => {
|
||||
if let Some(run) = app.coop_run.as_mut() {
|
||||
if app.offline {
|
||||
// no relay: the next mission of the run starts here at once
|
||||
let run = app.coop_run.take().expect("a co-op run");
|
||||
let next = run.next.unwrap_or(run.mission);
|
||||
if app.start_offline_coop(Some(run.doc), next, Some(run.run_seed), run.carry_out) {
|
||||
ui.log.clear();
|
||||
autosave_t = 0.0;
|
||||
}
|
||||
} else if let Some(run) = app.coop_run.as_mut() {
|
||||
if let Some(next) = run.next {
|
||||
app.client.next_mission(next, run.run_seed, &run.carry_out);
|
||||
run.sent = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
// a built-in co-op mission without the server: the next one after a victory, else again
|
||||
UiAction::EndNext if app.offline && !app.coop_editor => {
|
||||
let i = app.game.mission.as_ref().map_or(0, |m| m.index);
|
||||
let next = if app.game.state == 1 { campaign::next_coop_mission(i, 1) } else { i };
|
||||
if next <= i && app.game.state == 1 {
|
||||
app.leave_match();
|
||||
menu.page = Some(Page::Title);
|
||||
app.seed = next_seed(app.seed);
|
||||
(app.game, app.cam) = Game::new_demo(app.seed);
|
||||
} else if app.start_offline_coop(None, next, None, Vec::new()) {
|
||||
ui.log.clear();
|
||||
autosave_t = 0.0;
|
||||
}
|
||||
}
|
||||
UiAction::EndCampaign if app.offline && !app.coop_editor => {
|
||||
app.leave_match();
|
||||
menu.page = Some(Page::Title);
|
||||
app.seed = next_seed(app.seed);
|
||||
(app.game, app.cam) = Game::new_demo(app.seed);
|
||||
}
|
||||
UiAction::EndNext if app.coop_editor && app.coop_run.as_ref().map_or(false, |r| r.next.is_some()) => {}
|
||||
// ... or the run is over (or the player leaves): the match ends, back to the lobby
|
||||
UiAction::EndNext | UiAction::EndCampaign if app.coop_editor => {
|
||||
@@ -1755,6 +1818,8 @@ app.pending_briefing = briefing_of(&campaign, i);
|
||||
run.finish(&app.game);
|
||||
}
|
||||
}
|
||||
} else if app.coop {
|
||||
// a built-in co-op mission is no step of the single-player campaign: no progress
|
||||
} else if app.custom_campaign.is_none() {
|
||||
if let (Some(i), false) = (app.game.mission.as_ref().map(|m| m.index), app.recorded) {
|
||||
if app.game.state != 0 {
|
||||
|
||||
@@ -279,6 +279,7 @@ enum Btn {
|
||||
MatchCoopNext,
|
||||
MatchCoopCreate,
|
||||
MatchCoopSource, // switch between the built-in campaign and the editor campaign
|
||||
MatchCoopSolo, // the selected co-op mission without the server, with an AI partner
|
||||
MatchTake(usize),
|
||||
MatchAi(usize),
|
||||
MatchTeam(usize), // next team for that seat
|
||||
@@ -397,6 +398,7 @@ pub enum MenuAction {
|
||||
MatchCreate,
|
||||
MatchCoopCreate(usize),
|
||||
MatchCoopEditorCreate(usize), // co-op match of a mission of `campaign_doc`
|
||||
MatchCoopSolo(Option<crate::campaign_doc::CampaignDoc>, usize), // offline with an AI partner: editor campaign or built-in, mission
|
||||
MatchTakeSide(u8),
|
||||
MatchSetAi(u8, bool),
|
||||
MatchSetTeam(u8, u8, bool), // side, team, the own seat
|
||||
@@ -798,6 +800,8 @@ impl Menu {
|
||||
self.confirm = None;
|
||||
MenuAction::Consumed
|
||||
}
|
||||
Some(Btn::MatchCoopSolo) if self.coop_editor => MenuAction::MatchCoopSolo(Some(self.coop_doc().clone()), self.coop_editor_mission),
|
||||
Some(Btn::MatchCoopSolo) => MenuAction::MatchCoopSolo(None, self.coop_mission),
|
||||
Some(Btn::MatchCoopCreate) if self.coop_editor => MenuAction::MatchCoopEditorCreate(self.coop_editor_mission),
|
||||
Some(Btn::MatchCoopCreate) => MenuAction::MatchCoopCreate(self.coop_mission),
|
||||
Some(Btn::MatchReady) => MenuAction::MatchReady(!self.match_ui.ready),
|
||||
@@ -1992,6 +1996,7 @@ impl Menu {
|
||||
items.push((Btn::MatchCoopPrev, "<".to_string()));
|
||||
items.push((Btn::MatchCoopCreate, tr("net.coop_create")));
|
||||
items.push((Btn::MatchCoopNext, ">".to_string()));
|
||||
items.push((Btn::MatchCoopSolo, tr("net.coop_solo")));
|
||||
}
|
||||
// the button names what comes next
|
||||
let next = match (editor_coop, self.coop_league) {
|
||||
|
||||
+139
-3
@@ -7,6 +7,10 @@ use crate::map::*;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
pub const RIVAL_CORE_HP: f32 = 2000.0;
|
||||
// a co-op AI partner helps masters short of gold: every minute it may send some
|
||||
pub const PARTNER_GIFT_EVERY: f32 = 60.0;
|
||||
pub const PARTNER_GIFT_BELOW: f32 = 300.0;
|
||||
pub const PARTNER_GIFT: f32 = 400.0;
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
pub struct Rival {
|
||||
@@ -20,6 +24,8 @@ pub struct Rival {
|
||||
pub attack_t: f32,
|
||||
pub difficulty: u8, // 0 easy, 1 medium, 2 hard
|
||||
pub power: f32, // grows with the time: level of the new creatures
|
||||
#[serde(default)]
|
||||
pub gift_t: f32, // co-op AI partner: seconds until it looks whether its allies need gold
|
||||
}
|
||||
|
||||
struct Pace {
|
||||
@@ -135,7 +141,7 @@ impl Game {
|
||||
t.gold = 300;
|
||||
t.res = 0;
|
||||
}
|
||||
self.rivals.push(Rival { side, core, hp: RIVAL_CORE_HP, alive: true, radius: r, grow_t: 20.0, spawn_t: 5.0, attack_t: pace(difficulty).attack, difficulty, power: 0.0 });
|
||||
self.rivals.push(Rival { side, core, hp: RIVAL_CORE_HP, alive: true, radius: r, grow_t: 20.0, spawn_t: 5.0, attack_t: pace(difficulty).attack, difficulty, power: 0.0, gift_t: PARTNER_GIFT_EVERY });
|
||||
for k in 0..3 {
|
||||
self.spawn_rival_unit(side, EK::Kobold, 1, k as f32);
|
||||
}
|
||||
@@ -167,6 +173,14 @@ impl Game {
|
||||
r.spawn_t -= dt;
|
||||
r.attack_t -= dt;
|
||||
}
|
||||
// a co-op partner sends gold to allied masters who run short
|
||||
if self.is_partner(side) {
|
||||
self.rivals[k].gift_t -= dt;
|
||||
if self.rivals[k].gift_t <= 0.0 {
|
||||
self.rivals[k].gift_t = PARTNER_GIFT_EVERY;
|
||||
self.partner_gift(side);
|
||||
}
|
||||
}
|
||||
// growth: one more ring of ground (no imps needed)
|
||||
if self.rivals[k].grow_t <= 0.0 {
|
||||
self.rivals[k].grow_t = p.grow * (0.8 + self.rng.f() * 0.4);
|
||||
@@ -294,7 +308,14 @@ impl Game {
|
||||
let (x, y) = self.rivals[k].centre();
|
||||
self.fx(1, x, y, 0.0, 0.0, 1.0, 0xffffffff);
|
||||
self.sfx(Sfx::Victory, x, y);
|
||||
self.msg(if side == 2 { "msg.rival1_out" } else { "msg.rival2_out" }, vec![]);
|
||||
let key = if self.is_partner(side) {
|
||||
"msg.partner_out"
|
||||
} else if side == 2 {
|
||||
"msg.rival1_out"
|
||||
} else {
|
||||
"msg.rival2_out"
|
||||
};
|
||||
self.msg(key, vec![]);
|
||||
// its creatures scatter and vanish, its ground is free
|
||||
for e in self.ents.iter_mut() {
|
||||
if e.alive && !e.hero && e.side == side {
|
||||
@@ -385,6 +406,41 @@ impl Game {
|
||||
}
|
||||
|
||||
// units of a rival master: fight enemies nearby, march when sent, otherwise guard home
|
||||
// an AI seat of a co-op game: an allied master who builds his own dungeon
|
||||
pub fn is_partner(&self, side: u8) -> bool {
|
||||
self.coop && self.ai_sides.contains(&side)
|
||||
}
|
||||
|
||||
// the partner gives gold to every allied master below the limit
|
||||
fn partner_gift(&mut self, side: u8) {
|
||||
let me = self.me_side;
|
||||
for s in self.master_sides() {
|
||||
if self.is_out(s) || self.gold_of(s) >= PARTNER_GIFT_BELOW {
|
||||
continue;
|
||||
}
|
||||
self.switch_to(s);
|
||||
let got = self.add_gold(PARTNER_GIFT).floor();
|
||||
if got >= 1.0 {
|
||||
self.msg("msg.gold_received", vec![(got as i32).to_string(), format!("@net.side{side}")]);
|
||||
self.sfx_ui(Sfx::Payday);
|
||||
}
|
||||
}
|
||||
self.switch_to(me);
|
||||
}
|
||||
|
||||
// a co-op partner's creatures follow the attack flag of the first master still standing
|
||||
fn partner_flag(&self, side: u8) -> Option<usize> {
|
||||
if !self.is_partner(side) {
|
||||
return None;
|
||||
}
|
||||
let lead = self.master_sides().into_iter().find(|s| !self.is_out(*s))?;
|
||||
if lead == self.me_side {
|
||||
self.flag
|
||||
} else {
|
||||
self.masters.iter().find(|m| m.side == lead)?.flag
|
||||
}
|
||||
}
|
||||
|
||||
pub fn rival_ai(&mut self, e: &mut Ent, dt: f32) {
|
||||
if e.think <= 0.0 {
|
||||
e.think = 0.5;
|
||||
@@ -397,18 +453,41 @@ impl Game {
|
||||
}
|
||||
}
|
||||
}
|
||||
// two of three healthy creatures of a co-op partner join the attack at the allies' flag
|
||||
let assist = if e.id % 3 != 0 && e.hp > e.max_hp * 0.4 { self.partner_flag(e.side) } else { None };
|
||||
if let Job::Attack(id) = e.job {
|
||||
let (hx, hy) = e.home.unwrap_or((e.x, e.y));
|
||||
let far = e.target.is_none() && (e.x - hx).powi(2) + (e.y - hy).powi(2) > 15.0 * 15.0;
|
||||
let far = e.target.is_none() && assist.is_none() && (e.x - hx).powi(2) + (e.y - hy).powi(2) > 15.0 * 15.0;
|
||||
if !far && self.attack_job(e, id, dt) {
|
||||
return;
|
||||
}
|
||||
e.job = Job::None;
|
||||
e.path.clear();
|
||||
}
|
||||
if let Some(f) = assist {
|
||||
// the flag at the humans' gate: attack the gate
|
||||
let (fx, fy) = xy(f);
|
||||
let (fx, fy) = (fx as f32 + 0.5, fy as f32 + 0.5);
|
||||
let (gx, gy) = self.gate_center();
|
||||
if e.target.is_none() && self.has_gate() && self.gate_hp > 0.0 && (fx - gx).powi(2) + (fy - gy).powi(2) <= 8.0 * 8.0 {
|
||||
e.target = Some(1);
|
||||
}
|
||||
}
|
||||
if self.march_on(e, dt) {
|
||||
return;
|
||||
}
|
||||
if let Some(f) = assist {
|
||||
let (fx, fy) = xy(f);
|
||||
let d2 = (e.x - fx as f32 - 0.5).powi(2) + (e.y - fy as f32 - 0.5).powi(2);
|
||||
if d2 > 2.5 * 2.5 && e.path.is_empty() && e.repath <= 0.0 {
|
||||
e.repath = 1.5;
|
||||
if let Some(p) = self.map.creature_path(flies(e.kind), e.tile(), f) {
|
||||
e.path = p;
|
||||
}
|
||||
}
|
||||
self.step_path(e, dt);
|
||||
return;
|
||||
}
|
||||
// guard the own dungeon
|
||||
let (hx, hy) = e.home.unwrap_or((e.x, e.y));
|
||||
let d2 = (e.x - hx).powi(2) + (e.y - hy).powi(2);
|
||||
@@ -538,4 +617,61 @@ mod tests {
|
||||
g.spawn_wave();
|
||||
assert!(g.ents[before..].iter().filter(|e| e.hero).all(|e| e.target.is_none()), "the wave comes for the player");
|
||||
}
|
||||
|
||||
// the first mission of the co-op league, set up like an offline co-op game: player on side 0,
|
||||
// an AI partner on side 2, no server
|
||||
fn offline_league_game() -> Game {
|
||||
let doc = crate::editor::coop_campaign::coop_campaign();
|
||||
let mut g = crate::campaign::start_editor_coop_mission(&doc.missions[0], 0, 77);
|
||||
g.coop = true;
|
||||
assert!(g.start_match(&[0, 2], 0));
|
||||
g.sync_ai_sides(&[2], 1);
|
||||
g.end_match();
|
||||
g
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn an_offline_coop_partner_is_an_allied_ai_master() {
|
||||
let mut g = offline_league_game();
|
||||
assert!(g.netplay.is_none() && g.masters.is_empty());
|
||||
assert!(g.rival_alive(2) && g.is_partner(2));
|
||||
assert!(g.allied(0, 2) && g.allied(2, 0));
|
||||
// short of gold: the partner helps out within a minute
|
||||
g.gold = 0.0;
|
||||
for _ in 0..(65 * 20) {
|
||||
g.update(0.05);
|
||||
}
|
||||
assert!(g.msgs.iter().any(|m| m.key == "msg.gold_received"), "the partner sent gold");
|
||||
assert_eq!(g.state, 0);
|
||||
// the partner's creatures never fight the player's
|
||||
assert!(!g.ents.iter().any(|e| e.alive && !e.hero && e.side == 0 && e.hp < e.max_hp && matches!(e.job, Job::Attack(_))));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn the_partner_follows_the_attack_flag() {
|
||||
let mut g = offline_league_game();
|
||||
for _ in 0..(90 * 20) {
|
||||
g.update(0.05);
|
||||
}
|
||||
let (rx, ry) = g.rivals.iter().find(|r| r.side == 2).unwrap().centre();
|
||||
// a flag on the partner's own ground a little away from its core
|
||||
let f = (0..g.map.t.len())
|
||||
.find(|&i| {
|
||||
let (x, y) = xy(i);
|
||||
let d2 = (x as f32 - rx).powi(2) + (y as f32 - ry).powi(2);
|
||||
g.map.t[i].owner == Owner::of_side(2) && g.map.walk(x, y) && d2 > 16.0 && d2 < 36.0
|
||||
})
|
||||
.expect("partner ground");
|
||||
g.flag = Some(f);
|
||||
let (fx, fy) = xy(f);
|
||||
let mut near = false;
|
||||
for _ in 0..(30 * 20) {
|
||||
g.update(0.05);
|
||||
near |= g.ents.iter().any(|e| e.alive && e.side == 2 && !e.hero && e.id % 3 != 0 && (e.x - fx as f32 - 0.5).powi(2) + (e.y - fy as f32 - 0.5).powi(2) < 3.0 * 3.0);
|
||||
if near {
|
||||
break;
|
||||
}
|
||||
}
|
||||
assert!(near, "partner creatures gather at the flag");
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user