Resume interrupted editor co-op runs
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
f87b59390e
commit
c7c3e979d6
24 files changed
+145
-6
No files matched your search
@@ -90,6 +90,11 @@ 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
|
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 ->
|
(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.
|
`CampaignProgress`). A run ends when no next mission fits co-op.
|
||||||
|
- **Resuming a run**: the host's progress also keeps where the run stands (`CampaignProgress::next`, set when a
|
||||||
|
mission starts and after a victory) and the carry into that mission. While a run is open the Match page shows
|
||||||
|
*Continue Mn*: with the relay it creates a room whose `create` carries `"seed"` (the run seed) and `"carry"`;
|
||||||
|
the relay's `start` then has that seed, the carry and `"resume":true`, and the clients treat it like `next`
|
||||||
|
(the progress is not reset). Without the relay the run goes on offline with the AI partner.
|
||||||
- **Co-op offline with an AI partner** (*AI partner* on the Match page, works without the server): the selected
|
- **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
|
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
|
an AI seat on side 2 (`App::start_offline_coop`: `start_match` + `sync_ai_sides`), then `end_match` drops the
|
||||||
|
|||||||
@@ -664,6 +664,7 @@ net.coop_builtin = Vestavěná kampaň
|
|||||||
net.coop_league = Liga proti Světlé stráži
|
net.coop_league = Liga proti Světlé stráži
|
||||||
net.coop_solo = AI partner
|
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_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_resume = Dál: M{0}
|
||||||
net.coop_editor_mission = Kampaň z editoru {0}: mise {1} ({2})
|
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.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
|
net.join = Připojit
|
||||||
|
|||||||
@@ -663,6 +663,7 @@ net.coop_builtin = Indbygget kampagne
|
|||||||
net.coop_league = Forbundet mod Lysvagten
|
net.coop_league = Forbundet mod Lysvagten
|
||||||
net.coop_solo = AI-partner
|
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_solo_started = Co-op uden server: din AI-partner bygger sit eget fangehul, følger dit angrebsflag og hjælper med guld.
|
||||||
|
net.coop_resume = Fortsæt M{0}
|
||||||
net.coop_editor_mission = Editor-kampagne {0}: mission {1} ({2})
|
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.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
|
net.join = Deltag
|
||||||
|
|||||||
@@ -663,6 +663,7 @@ net.coop_builtin = Eingebaute Kampagne
|
|||||||
net.coop_league = Bund gegen die Lichtwacht
|
net.coop_league = Bund gegen die Lichtwacht
|
||||||
net.coop_solo = KI-Partner
|
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_solo_started = Koop ohne Server: Dein KI-Partner baut einen eigenen Kerker, folgt deiner Angriffsflagge und hilft mit Gold aus.
|
||||||
|
net.coop_resume = Weiter: M{0}
|
||||||
net.coop_editor_mission = Editor-Kampagne {0}: Mission {1} ({2})
|
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.coop_editor_none = Keine Mission der Editor-Kampagne passt für Koop (Karte mindestens 72x72, ein Ziel außer Rivalen-Meistern)
|
||||||
net.join = Beitreten
|
net.join = Beitreten
|
||||||
|
|||||||
@@ -664,6 +664,7 @@ net.coop_builtin = Ενσωματωμένη εκστρατεία
|
|||||||
net.coop_league = Συμμαχία κατά της Φωτοφρουράς
|
net.coop_league = Συμμαχία κατά της Φωτοφρουράς
|
||||||
net.coop_solo = Σύμμαχος ΤΝ
|
net.coop_solo = Σύμμαχος ΤΝ
|
||||||
net.coop_solo_started = Συνεργασία χωρίς διακομιστή: ο σύμμαχός σου ΤΝ χτίζει το δικό του μπουντρούμι, ακολουθεί τη σημαία επίθεσής σου και βοηθά με χρυσό.
|
net.coop_solo_started = Συνεργασία χωρίς διακομιστή: ο σύμμαχός σου ΤΝ χτίζει το δικό του μπουντρούμι, ακολουθεί τη σημαία επίθεσής σου και βοηθά με χρυσό.
|
||||||
|
net.coop_resume = Συνέχεια M{0}
|
||||||
net.coop_editor_mission = Εκστρατεία επεξεργαστή {0}: αποστολή {1} ({2})
|
net.coop_editor_mission = Εκστρατεία επεξεργαστή {0}: αποστολή {1} ({2})
|
||||||
net.coop_editor_none = Καμία αποστολή της εκστρατείας επεξεργαστή δεν ταιριάζει για συνεργασία (χάρτης τουλάχιστον 72x72, ένας στόχος πέρα από τους αντίπαλους άρχοντες)
|
net.coop_editor_none = Καμία αποστολή της εκστρατείας επεξεργαστή δεν ταιριάζει για συνεργασία (χάρτης τουλάχιστον 72x72, ένας στόχος πέρα από τους αντίπαλους άρχοντες)
|
||||||
net.join = Συμμετοχή
|
net.join = Συμμετοχή
|
||||||
|
|||||||
@@ -664,6 +664,7 @@ net.coop_builtin = Built-in campaign
|
|||||||
net.coop_league = League against the Lightwatch
|
net.coop_league = League against the Lightwatch
|
||||||
net.coop_solo = AI partner
|
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_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_resume = Continue M{0}
|
||||||
net.coop_editor_mission = Editor campaign {0}: mission {1} ({2})
|
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.coop_editor_none = No mission of the editor campaign fits co-op (map at least 72x72, a goal besides rival masters)
|
||||||
net.join = Join
|
net.join = Join
|
||||||
|
|||||||
@@ -1336,6 +1336,7 @@ net.coop_builtin = Campaña integrada
|
|||||||
net.coop_league = Liga contra la Guardia Luminosa
|
net.coop_league = Liga contra la Guardia Luminosa
|
||||||
net.coop_solo = Socio IA
|
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_solo_started = Cooperativo sin servidor: tu socio IA construye su propia mazmorra, sigue tu bandera de ataque y te ayuda con oro.
|
||||||
|
net.coop_resume = Seguir M{0}
|
||||||
net.coop_editor_mission = Campaña del editor {0}: misión {1} ({2})
|
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)
|
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
|
blk.until = Repetir hasta
|
||||||
|
|||||||
@@ -664,6 +664,7 @@ net.coop_builtin = Sisäinen kampanja
|
|||||||
net.coop_league = Liitto Valonvartiota vastaan
|
net.coop_league = Liitto Valonvartiota vastaan
|
||||||
net.coop_solo = Kumppani
|
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_solo_started = Yhteistyö ilman palvelinta: tekoälykumppanisi rakentaa oman luolastonsa, seuraa hyökkäyslippuasi ja auttaa kullalla.
|
||||||
|
net.coop_resume = Jatka: M{0}
|
||||||
net.coop_editor_mission = Editorikampanja {0}: tehtävä {1} ({2})
|
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.coop_editor_none = Mikään editorikampanjan tehtävä ei sovi yhteispeliin (kartta vähintään 72x72, muukin tavoite kuin kilpailevat mestarit)
|
||||||
net.join = Liity
|
net.join = Liity
|
||||||
|
|||||||
@@ -664,6 +664,7 @@ net.coop_builtin = Ugrađena kampanja
|
|||||||
net.coop_league = Savez protiv Svjetlostraže
|
net.coop_league = Savez protiv Svjetlostraže
|
||||||
net.coop_solo = AI partner
|
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_solo_started = Suradnja bez poslužitelja: tvoj AI partner gradi vlastitu tamnicu, slijedi tvoju zastavu napada i pomaže zlatom.
|
||||||
|
net.coop_resume = Nastavi M{0}
|
||||||
net.coop_editor_mission = Kampanja uređivača {0}: misija {1} ({2})
|
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.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
|
net.join = Pridruži
|
||||||
|
|||||||
@@ -664,6 +664,7 @@ net.coop_builtin = Beépített hadjárat
|
|||||||
net.coop_league = Szövetség a Fényőrség ellen
|
net.coop_league = Szövetség a Fényőrség ellen
|
||||||
net.coop_solo = MI-társ
|
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_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_resume = Tovább: M{0}
|
||||||
net.coop_editor_mission = Saját hadjárat ({0}): {1}. küldetés ({2})
|
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.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
|
net.join = Belép
|
||||||
|
|||||||
@@ -1336,6 +1336,7 @@ net.coop_builtin = Campagna integrata
|
|||||||
net.coop_league = Lega contro la Guardia della Luce
|
net.coop_league = Lega contro la Guardia della Luce
|
||||||
net.coop_solo = Alleato IA
|
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_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_resume = Continua M{0}
|
||||||
net.coop_editor_mission = Campagna dell'editor {0}: missione {1} ({2})
|
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)
|
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é
|
blk.until = Ripeti finché
|
||||||
|
|||||||
@@ -664,6 +664,7 @@ net.coop_builtin = Vaste veldtocht
|
|||||||
net.coop_league = Liga tegen de Lichtwacht
|
net.coop_league = Liga tegen de Lichtwacht
|
||||||
net.coop_solo = AI-partner
|
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_solo_started = Co-op zonder server: je AI-partner bouwt een eigen kerker, volgt je aanvalsvlag en helpt met goud.
|
||||||
|
net.coop_resume = Verder: M{0}
|
||||||
net.coop_editor_mission = Editorveldtocht {0}: missie {1} ({2})
|
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.coop_editor_none = Geen missie van de editorveldtocht is geschikt voor co-op (kaart minstens 72x72, een doel naast rivaliserende meesters)
|
||||||
net.join = Meedoen
|
net.join = Meedoen
|
||||||
|
|||||||
@@ -663,6 +663,7 @@ net.coop_builtin = Innebygd kampanje
|
|||||||
net.coop_league = Forbundet mot Lysvakten
|
net.coop_league = Forbundet mot Lysvakten
|
||||||
net.coop_solo = KI-partner
|
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_solo_started = Samarbeid uten server: KI-partneren din bygger sitt eget fangehull, følger angrepsflagget ditt og hjelper med gull.
|
||||||
|
net.coop_resume = Fortsett M{0}
|
||||||
net.coop_editor_mission = Editorkampanje {0}: oppdrag {1} ({2})
|
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.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
|
net.join = Bli med
|
||||||
|
|||||||
@@ -664,6 +664,7 @@ net.coop_builtin = Campanha incluída
|
|||||||
net.coop_league = Liga contra a Guarda da Luz
|
net.coop_league = Liga contra a Guarda da Luz
|
||||||
net.coop_solo = Parceiro IA
|
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_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_resume = Continuar M{0}
|
||||||
net.coop_editor_mission = Campanha do editor {0}: missão {1} ({2})
|
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.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
|
net.join = Entrar
|
||||||
|
|||||||
@@ -664,6 +664,7 @@ net.coop_builtin = Campanie inclusă
|
|||||||
net.coop_league = Liga împotriva Străjii Luminii
|
net.coop_league = Liga împotriva Străjii Luminii
|
||||||
net.coop_solo = Partener IA
|
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_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_resume = Continuă M{0}
|
||||||
net.coop_editor_mission = Campania din editor {0}: misiunea {1} ({2})
|
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.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ă
|
net.join = Intră
|
||||||
|
|||||||
@@ -1336,6 +1336,7 @@ net.coop_builtin = Встроенная кампания
|
|||||||
net.coop_league = Союз против Светлого Дозора
|
net.coop_league = Союз против Светлого Дозора
|
||||||
net.coop_solo = ИИ-союзник
|
net.coop_solo = ИИ-союзник
|
||||||
net.coop_solo_started = Кооператив без сервера: ИИ-союзник строит своё подземелье, идёт к вашему флагу атаки и помогает золотом.
|
net.coop_solo_started = Кооператив без сервера: ИИ-союзник строит своё подземелье, идёт к вашему флагу атаки и помогает золотом.
|
||||||
|
net.coop_resume = Дальше: M{0}
|
||||||
net.coop_editor_mission = Кампания из редактора {0}: миссия {1} ({2})
|
net.coop_editor_mission = Кампания из редактора {0}: миссия {1} ({2})
|
||||||
net.coop_editor_none = Ни одна миссия кампании из редактора не подходит для кооператива (карта не меньше 72x72, цель помимо мастеров-соперников)
|
net.coop_editor_none = Ни одна миссия кампании из редактора не подходит для кооператива (карта не меньше 72x72, цель помимо мастеров-соперников)
|
||||||
blk.until = Повторять, пока не
|
blk.until = Повторять, пока не
|
||||||
|
|||||||
@@ -664,6 +664,7 @@ net.coop_builtin = Vstavaná kampaň
|
|||||||
net.coop_league = Liga proti Svetlej stráži
|
net.coop_league = Liga proti Svetlej stráži
|
||||||
net.coop_solo = AI partner
|
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_solo_started = Kooperácia bez servera: tvoj AI partner si stavia vlastnú kobku, nasleduje tvoju útočnú vlajku a pomáha zlatom.
|
||||||
|
net.coop_resume = Ďalej: M{0}
|
||||||
net.coop_editor_mission = Kampaň z editora {0}: misia {1} ({2})
|
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.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ť
|
net.join = Pripojiť
|
||||||
|
|||||||
@@ -664,6 +664,7 @@ net.coop_builtin = Уграђена кампања
|
|||||||
net.coop_league = Савез против Светлостраже
|
net.coop_league = Савез против Светлостраже
|
||||||
net.coop_solo = АИ партнер
|
net.coop_solo = АИ партнер
|
||||||
net.coop_solo_started = Сарадња без сервера: твој АИ партнер гради сопствену тамницу, прати твоју заставу напада и помаже златом.
|
net.coop_solo_started = Сарадња без сервера: твој АИ партнер гради сопствену тамницу, прати твоју заставу напада и помаже златом.
|
||||||
|
net.coop_resume = Настави M{0}
|
||||||
net.coop_editor_mission = Кампања уређивача {0}: мисија {1} ({2})
|
net.coop_editor_mission = Кампања уређивача {0}: мисија {1} ({2})
|
||||||
net.coop_editor_none = Ниједна мисија кампање уређивача не одговара ко-оп игри (мапа најмање 72x72, циљ осим супарничких господара)
|
net.coop_editor_none = Ниједна мисија кампање уређивача не одговара ко-оп игри (мапа најмање 72x72, циљ осим супарничких господара)
|
||||||
net.join = Придружи
|
net.join = Придружи
|
||||||
|
|||||||
@@ -664,6 +664,7 @@ net.coop_builtin = Inbyggd kampanj
|
|||||||
net.coop_league = Förbundet mot Ljusvakten
|
net.coop_league = Förbundet mot Ljusvakten
|
||||||
net.coop_solo = AI-partner
|
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_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_resume = Fortsätt M{0}
|
||||||
net.coop_editor_mission = Egen kampanj {0}: uppdrag {1} ({2})
|
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.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
|
net.join = Gå med
|
||||||
|
|||||||
+29
-2
@@ -9,6 +9,8 @@
|
|||||||
// {"t":"create","sides":[0,2,3,4]?,"map_w":96?,"map_h":128?,"difficulty":1?,"mission":12?,"doc":{..}?}
|
// {"t":"create","sides":[0,2,3,4]?,"map_w":96?,"map_h":128?,"difficulty":1?,"mission":12?,"doc":{..}?}
|
||||||
// -> "doc": an editor campaign (CampaignDoc JSON) for a co-op
|
// -> "doc": an editor campaign (CampaignDoc JSON) for a co-op
|
||||||
// match of its mission "mission"; it goes along with "start"
|
// match of its mission "mission"; it goes along with "start"
|
||||||
|
// "seed":77?,"carry":[..]? with a doc: an interrupted run goes
|
||||||
|
// on - "start" then has this seed, the carry and "resume":true
|
||||||
// {"t":"join","code":"ABC123"}
|
// {"t":"join","code":"ABC123"}
|
||||||
// {"t":"take_side","side":2}
|
// {"t":"take_side","side":2}
|
||||||
// {"t":"ready","ready":true}
|
// {"t":"ready","ready":true}
|
||||||
@@ -85,6 +87,7 @@ struct Room {
|
|||||||
difficulty: u8,
|
difficulty: u8,
|
||||||
mission: Option<usize>,
|
mission: Option<usize>,
|
||||||
doc: Option<Value>, // editor campaign of a co-op match (sent to every client at the start)
|
doc: Option<Value>, // editor campaign of a co-op match (sent to every client at the start)
|
||||||
|
resume: Option<(u32, Value)>, // a resumed editor campaign run: its seed and what the masters carry
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
@@ -358,6 +361,10 @@ impl Netplay {
|
|||||||
let difficulty = msg.get("difficulty").and_then(|v| v.as_u64()).unwrap_or(1).clamp(0, 2) as u8;
|
let difficulty = msg.get("difficulty").and_then(|v| v.as_u64()).unwrap_or(1).clamp(0, 2) as u8;
|
||||||
let mission = mission_of(msg.get("mission"));
|
let mission = mission_of(msg.get("mission"));
|
||||||
let doc = msg.get("doc").filter(|d| !d.is_null()).cloned();
|
let doc = msg.get("doc").filter(|d| !d.is_null()).cloned();
|
||||||
|
let resume = match (&doc, msg.get("seed").and_then(|v| v.as_u64())) {
|
||||||
|
(Some(_), Some(seed)) => Some((seed as u32, msg.get("carry").filter(|c| c.is_array()).cloned().unwrap_or_else(|| json!([])))),
|
||||||
|
_ => None,
|
||||||
|
};
|
||||||
match &doc {
|
match &doc {
|
||||||
Some(d) => {
|
Some(d) => {
|
||||||
let count = d.get("missions").and_then(|m| m.as_array()).map_or(0, |m| m.len());
|
let count = d.get("missions").and_then(|m| m.as_array()).map_or(0, |m| m.len());
|
||||||
@@ -375,7 +382,7 @@ impl Netplay {
|
|||||||
}
|
}
|
||||||
let name = self.clients.get(&conn).map(|c| c.name.clone()).unwrap_or_else(|| "Guest".into());
|
let name = self.clients.get(&conn).map(|c| c.name.clone()).unwrap_or_else(|| "Guest".into());
|
||||||
let seats = sides.iter().map(|s| (*s, new_seat(*s, false))).collect::<HashMap<_, _>>();
|
let seats = sides.iter().map(|s| (*s, new_seat(*s, false))).collect::<HashMap<_, _>>();
|
||||||
let room = Room { code: code.clone(), sides, seats, pending: HashMap::new(), turn: 0, started: false, paused: false, missing: false, seed: None, map_w, map_h, difficulty, mission, doc };
|
let room = Room { code: code.clone(), sides, seats, pending: HashMap::new(), turn: 0, started: false, paused: false, missing: false, seed: None, map_w, map_h, difficulty, mission, doc, resume };
|
||||||
self.rooms.insert(code.clone(), room);
|
self.rooms.insert(code.clone(), room);
|
||||||
if let Some(c) = self.clients.get_mut(&conn) {
|
if let Some(c) = self.clients.get_mut(&conn) {
|
||||||
c.room = Some(code.clone());
|
c.room = Some(code.clone());
|
||||||
@@ -630,7 +637,7 @@ impl Netplay {
|
|||||||
r.started = true;
|
r.started = true;
|
||||||
r.paused = false;
|
r.paused = false;
|
||||||
r.missing = false;
|
r.missing = false;
|
||||||
r.seed = Some(random_seed());
|
r.seed = Some(r.resume.as_ref().map_or_else(random_seed, |x| x.0));
|
||||||
r.turn = 0;
|
r.turn = 0;
|
||||||
r.sides = active;
|
r.sides = active;
|
||||||
}
|
}
|
||||||
@@ -640,6 +647,10 @@ impl Netplay {
|
|||||||
if let Some(doc) = &r.doc {
|
if let Some(doc) = &r.doc {
|
||||||
m["doc"] = doc.clone();
|
m["doc"] = doc.clone();
|
||||||
}
|
}
|
||||||
|
if let Some((_, carry)) = &r.resume {
|
||||||
|
m["carry"] = carry.clone();
|
||||||
|
m["resume"] = json!(true);
|
||||||
|
}
|
||||||
m
|
m
|
||||||
}
|
}
|
||||||
None => return,
|
None => return,
|
||||||
@@ -971,6 +982,22 @@ mod tests {
|
|||||||
assert_eq!(start_red["mission"].as_u64(), Some(2));
|
assert_eq!(start_red["mission"].as_u64(), Some(2));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn a_resumed_run_starts_with_its_seed_and_carry() {
|
||||||
|
let mut net = Netplay::new();
|
||||||
|
let (tx, mut rx) = mpsc::unbounded_channel();
|
||||||
|
let red = net.connect(tx, Some("red".into()), None);
|
||||||
|
let doc = json!({"format":"dungeoncraft-campaign","name":"Mine","missions":[{"key":"a","title":"First"},{"key":"b","title":"Second","size":[96,96]}]});
|
||||||
|
let carry = json!([[0, {"gold": 100.0, "creatures": []}]]);
|
||||||
|
net.handle(red, json!({"t":"create","sides":[0,2],"mission":1,"doc":doc,"seed":4242,"carry":carry}));
|
||||||
|
net.handle(red, json!({"t":"ready"}));
|
||||||
|
net.handle(red, json!({"t":"start"}));
|
||||||
|
let start = until(&mut rx, "start");
|
||||||
|
assert_eq!(start["seed"].as_u64(), Some(4242));
|
||||||
|
assert_eq!(start["carry"], carry);
|
||||||
|
assert_eq!(start["resume"].as_bool(), Some(true));
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn an_editor_campaign_goes_from_the_create_to_every_start() {
|
fn an_editor_campaign_goes_from_the_create_to_every_start() {
|
||||||
let mut net = Netplay::new();
|
let mut net = Netplay::new();
|
||||||
|
|||||||
@@ -18,6 +18,12 @@ pub struct CampaignProgress {
|
|||||||
// random maps are rolled once per run of the campaign: this seed is fixed for the run
|
// random maps are rolled once per run of the campaign: this seed is fixed for the run
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub run_seed: u32,
|
pub run_seed: u32,
|
||||||
|
// co-op runs (kept by the host): the mission the run goes on with and what every master carries
|
||||||
|
// into it; None once the run is over
|
||||||
|
#[serde(default)]
|
||||||
|
pub next: Option<usize>,
|
||||||
|
#[serde(default)]
|
||||||
|
pub carry: Vec<(u8, crate::campaign::Carry)>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl CampaignProgress {
|
impl CampaignProgress {
|
||||||
@@ -27,6 +33,8 @@ impl CampaignProgress {
|
|||||||
done: vec![false; n],
|
done: vec![false; n],
|
||||||
completed_choice: vec![None; n],
|
completed_choice: vec![None; n],
|
||||||
run_seed: (macroquad::miniquad::date::now() * 1000.0) as u64 as u32 | 1,
|
run_seed: (macroquad::miniquad::date::now() * 1000.0) as u64 as u32 | 1,
|
||||||
|
next: None,
|
||||||
|
carry: Vec::new(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+41
-1
@@ -230,10 +230,23 @@ impl MatchClient {
|
|||||||
// a co-op match of an editor campaign: the whole campaign file goes to the relay, which hands
|
// a co-op match of an editor campaign: the whole campaign file goes to the relay, which hands
|
||||||
// it to every client with the start message
|
// it to every client with the start message
|
||||||
pub fn create_coop_doc(&mut self, doc: &crate::campaign_doc::CampaignDoc, mission: usize, sides: &[u8], difficulty: u8) {
|
pub fn create_coop_doc(&mut self, doc: &crate::campaign_doc::CampaignDoc, mission: usize, sides: &[u8], difficulty: u8) {
|
||||||
|
self.create_coop_doc_resumed(doc, mission, sides, difficulty, None);
|
||||||
|
}
|
||||||
|
|
||||||
|
// the same, going on with an interrupted run: its seed and what every master carries along
|
||||||
|
pub fn create_coop_doc_resumed(&mut self, doc: &crate::campaign_doc::CampaignDoc, mission: usize, sides: &[u8], difficulty: u8, resume: Option<(u32, &[(u8, crate::campaign::Carry)])>) {
|
||||||
let Some(m) = doc.missions.get(mission) else { return };
|
let Some(m) = doc.missions.get(mission) else { return };
|
||||||
let (map_w, map_h) = m.size;
|
let (map_w, map_h) = m.size;
|
||||||
let doc = serde_json::to_value(doc).unwrap_or(Value::Null);
|
let doc = serde_json::to_value(doc).unwrap_or(Value::Null);
|
||||||
|
let n = self.outbox.len();
|
||||||
self.send_create_with(sides, map_w, map_h, difficulty, Some(mission), Some(doc));
|
self.send_create_with(sides, map_w, map_h, difficulty, Some(mission), Some(doc));
|
||||||
|
if let (Some((seed, carry)), Some(text)) = (resume, self.outbox.get_mut(n)) {
|
||||||
|
if let Ok(mut msg) = serde_json::from_str::<Value>(text) {
|
||||||
|
msg["seed"] = json!(seed);
|
||||||
|
msg["carry"] = serde_json::to_value(carry).unwrap_or(Value::Array(Vec::new()));
|
||||||
|
*text = msg.to_string();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn send_create(&mut self, sides: &[u8], map_w: usize, map_h: usize, difficulty: u8, mission: Option<usize>) {
|
fn send_create(&mut self, sides: &[u8], map_w: usize, map_h: usize, difficulty: u8, mission: Option<usize>) {
|
||||||
@@ -534,7 +547,10 @@ impl MatchClient {
|
|||||||
}
|
}
|
||||||
r.ai = ai.clone();
|
r.ai = ai.clone();
|
||||||
}
|
}
|
||||||
self.start = Some(StartInfo { seed, map_w, map_h, difficulty, sides, local_side, mission, ai, doc, editor, teams, next: false, carry: Vec::new() });
|
// a resumed editor campaign run goes on like the next mission of a running one
|
||||||
|
let resume = editor && v.get("resume").and_then(|x| x.as_bool()).unwrap_or(false);
|
||||||
|
let carry: Vec<(u8, crate::campaign::Carry)> = if resume { v.get("carry").cloned().and_then(|c| serde_json::from_value(c).ok()).unwrap_or_default() } else { Vec::new() };
|
||||||
|
self.start = Some(StartInfo { seed, map_w, map_h, difficulty, sides, local_side, mission, ai, doc, editor, teams, next: resume, carry });
|
||||||
self.error = None;
|
self.error = None;
|
||||||
self.status = None;
|
self.status = None;
|
||||||
}
|
}
|
||||||
@@ -898,6 +914,30 @@ mod tests {
|
|||||||
assert_eq!(start.teams, vec![(0, 1), (2, 1)]);
|
assert_eq!(start.teams, vec![(0, 1), (2, 1)]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn an_interrupted_coop_run_goes_on_with_its_seed_and_carry() {
|
||||||
|
let doc = coop_doc();
|
||||||
|
let mut host = MatchClient::new();
|
||||||
|
host.on_connect("red");
|
||||||
|
pop(&mut host); // rooms
|
||||||
|
let carry = vec![(0u8, crate::campaign::Carry { gold: 250.0, ..Default::default() })];
|
||||||
|
host.create_coop_doc_resumed(&doc, 1, &[0, 2, 3, 4], 1, Some((4242, &carry)));
|
||||||
|
let create = pop(&mut host);
|
||||||
|
assert_eq!(create["seed"].as_u64(), Some(4242));
|
||||||
|
assert_eq!(create["carry"][0][0].as_u64(), Some(0));
|
||||||
|
assert_eq!(create["mission"].as_u64(), Some(1));
|
||||||
|
let start = json!({"t":"start","seed":4242,"map_w":96,"map_h":96,"difficulty":1,"sides":[0,2],"mission":1,"ai":[],"doc":serde_json::to_value(&doc).unwrap(),"carry":create["carry"].clone(),"resume":true}).to_string();
|
||||||
|
host.feed(&start);
|
||||||
|
let info = host.take_start().unwrap();
|
||||||
|
assert!(info.next, "a resumed run keeps its progress");
|
||||||
|
assert_eq!(info.seed, 4242);
|
||||||
|
assert_eq!(info.carry.len(), 1);
|
||||||
|
assert_eq!(info.carry[0].1.gold, 250.0);
|
||||||
|
// old stored progress without the run position still reads
|
||||||
|
let p: crate::campaign_doc::CampaignProgress = serde_json::from_str(r#"{"done":[true],"completed_choice":[null],"run_seed":5}"#).unwrap();
|
||||||
|
assert_eq!((p.next, p.carry.len()), (None, 0));
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn two_clients_play_the_same_editor_campaign_mission_in_coop() {
|
fn two_clients_play_the_same_editor_campaign_mission_in_coop() {
|
||||||
let doc = coop_doc();
|
let doc = coop_doc();
|
||||||
|
|||||||
+35
-3
@@ -225,10 +225,17 @@ fn store_progress(p: &campaign::Progress) {
|
|||||||
// progress of editor campaigns played in co-op, kept by the host: campaign name -> progress
|
// progress of editor campaigns played in co-op, kept by the host: campaign name -> progress
|
||||||
const COOP_PROGRESS_KEY: &str = "dc.coop_editor_progress";
|
const COOP_PROGRESS_KEY: &str = "dc.coop_editor_progress";
|
||||||
|
|
||||||
fn coop_progress_all() -> std::collections::BTreeMap<String, campaign_doc::CampaignProgress> {
|
pub(crate) fn coop_progress_all() -> std::collections::BTreeMap<String, campaign_doc::CampaignProgress> {
|
||||||
platform::storage_get(COOP_PROGRESS_KEY).and_then(|s| serde_json::from_str(&s).ok()).unwrap_or_default()
|
platform::storage_get(COOP_PROGRESS_KEY).and_then(|s| serde_json::from_str(&s).ok()).unwrap_or_default()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// where an interrupted co-op run of this campaign goes on: mission, run seed, carry
|
||||||
|
pub(crate) fn coop_resume_of(doc: &campaign_doc::CampaignDoc) -> Option<(usize, u32, Vec<(u8, campaign::Carry)>)> {
|
||||||
|
let p = coop_progress_all().remove(&doc.name)?;
|
||||||
|
let next = p.next.filter(|n| doc.missions.get(*n).map_or(false, campaign::is_editor_coop_mission))?;
|
||||||
|
Some((next, p.run_seed, p.carry))
|
||||||
|
}
|
||||||
|
|
||||||
fn store_coop_progress(name: &str, p: &campaign_doc::CampaignProgress) {
|
fn store_coop_progress(name: &str, p: &campaign_doc::CampaignProgress) {
|
||||||
let mut all = coop_progress_all();
|
let mut all = coop_progress_all();
|
||||||
all.insert(name.to_string(), p.clone());
|
all.insert(name.to_string(), p.clone());
|
||||||
@@ -260,6 +267,9 @@ impl CoopRun {
|
|||||||
let mut p = coop_progress_all().remove(&self.doc.name).unwrap_or_else(|| campaign_doc::CampaignProgress::for_doc(&self.doc));
|
let mut p = coop_progress_all().remove(&self.doc.name).unwrap_or_else(|| campaign_doc::CampaignProgress::for_doc(&self.doc));
|
||||||
p.run_seed = self.run_seed;
|
p.run_seed = self.run_seed;
|
||||||
p.mark_done(self.mission, self.doc.missions.len(), choice);
|
p.mark_done(self.mission, self.doc.missions.len(), choice);
|
||||||
|
// the run goes on with the next mission (or is over)
|
||||||
|
p.next = self.next;
|
||||||
|
p.carry = self.carry_out.clone();
|
||||||
store_coop_progress(&self.doc.name, &p);
|
store_coop_progress(&self.doc.name, &p);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@@ -353,6 +363,24 @@ fn account_action(a: MenuAction, cloud: &mut Cloud, menu: &mut Menu, app: &mut A
|
|||||||
menu.error = Some(i18n::tr("net.coop_editor_none"));
|
menu.error = Some(i18n::tr("net.coop_editor_none"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
MenuAction::MatchCoopResume(doc) => {
|
||||||
|
// with the relay: a room that goes on with the run; without it: offline with an AI partner
|
||||||
|
match coop_resume_of(&doc) {
|
||||||
|
Some((mission, seed, carry)) if app.client.connected => {
|
||||||
|
app.client.create_coop_doc_resumed(&doc, mission, &[0, 2, 3, 4], app.settings.difficulty, Some((seed, &carry)));
|
||||||
|
}
|
||||||
|
Some((mission, seed, carry)) => {
|
||||||
|
if app.start_offline_coop(Some(doc), mission, Some(seed), carry) {
|
||||||
|
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"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None => menu.error = Some(i18n::tr("net.coop_editor_none")),
|
||||||
|
}
|
||||||
|
}
|
||||||
MenuAction::MatchTakeSide(side) => app.client.take_side(side),
|
MenuAction::MatchTakeSide(side) => app.client.take_side(side),
|
||||||
MenuAction::MatchSetAi(side, ai) => app.client.set_ai(side, ai),
|
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),
|
MenuAction::MatchSetTeam(side, team, own) => app.client.set_team(if own { None } else { Some(side) }, team),
|
||||||
@@ -703,11 +731,14 @@ impl App {
|
|||||||
self.coop_editor = true;
|
self.coop_editor = true;
|
||||||
self.recorded = false;
|
self.recorded = false;
|
||||||
self.pending_briefing = briefing_of(doc, mission);
|
self.pending_briefing = briefing_of(doc, mission);
|
||||||
// the host keeps the progress of the run (a new run starts with the room's seed)
|
// the host keeps the progress of the run (a new run starts with the room's seed) and
|
||||||
if !info.next && info.local_side == Some(0) {
|
// where it stands, so an interrupted run can go on later
|
||||||
|
if info.local_side == Some(0) {
|
||||||
let mut p = coop_progress_all().remove(&doc.name).unwrap_or_else(|| campaign_doc::CampaignProgress::for_doc(doc));
|
let mut p = coop_progress_all().remove(&doc.name).unwrap_or_else(|| campaign_doc::CampaignProgress::for_doc(doc));
|
||||||
p.ensure(doc.missions.len());
|
p.ensure(doc.missions.len());
|
||||||
p.run_seed = info.seed;
|
p.run_seed = info.seed;
|
||||||
|
p.next = Some(mission);
|
||||||
|
p.carry = info.carry.clone();
|
||||||
store_coop_progress(&doc.name, &p);
|
store_coop_progress(&doc.name, &p);
|
||||||
}
|
}
|
||||||
self.coop_run = Some(CoopRun { doc: doc.clone(), mission, run_seed: info.seed, carry_in: info.carry.clone(), next: None, carry_out: Vec::new(), sent: false });
|
self.coop_run = Some(CoopRun { doc: doc.clone(), mission, run_seed: info.seed, carry_in: info.carry.clone(), next: None, carry_out: Vec::new(), sent: false });
|
||||||
@@ -1506,6 +1537,7 @@ app.pending_briefing = briefing_of(&menu.campaign_doc, i);
|
|||||||
| MenuAction::MatchLeave
|
| MenuAction::MatchLeave
|
||||||
| MenuAction::MatchCoopEditorCreate(..)
|
| MenuAction::MatchCoopEditorCreate(..)
|
||||||
| MenuAction::MatchCoopSolo(..)
|
| MenuAction::MatchCoopSolo(..)
|
||||||
|
| MenuAction::MatchCoopResume(..)
|
||||||
| MenuAction::MatchChat(..)
|
| MenuAction::MatchChat(..)
|
||||||
| MenuAction::MatchEditField(..)) => account_action(a, &mut cloud, &mut menu, &mut app, now),
|
| MenuAction::MatchEditField(..)) => account_action(a, &mut cloud, &mut menu, &mut app, now),
|
||||||
MenuAction::EditName => {
|
MenuAction::EditName => {
|
||||||
|
|||||||
@@ -280,6 +280,7 @@ enum Btn {
|
|||||||
MatchCoopCreate,
|
MatchCoopCreate,
|
||||||
MatchCoopSource, // switch between the built-in campaign and the editor campaign
|
MatchCoopSource, // switch between the built-in campaign and the editor campaign
|
||||||
MatchCoopSolo, // the selected co-op mission without the server, with an AI partner
|
MatchCoopSolo, // the selected co-op mission without the server, with an AI partner
|
||||||
|
MatchCoopResume, // go on with the interrupted co-op run of the selected campaign
|
||||||
MatchTake(usize),
|
MatchTake(usize),
|
||||||
MatchAi(usize),
|
MatchAi(usize),
|
||||||
MatchTeam(usize), // next team for that seat
|
MatchTeam(usize), // next team for that seat
|
||||||
@@ -399,6 +400,7 @@ pub enum MenuAction {
|
|||||||
MatchCoopCreate(usize),
|
MatchCoopCreate(usize),
|
||||||
MatchCoopEditorCreate(usize), // co-op match of a mission of `campaign_doc`
|
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
|
MatchCoopSolo(Option<crate::campaign_doc::CampaignDoc>, usize), // offline with an AI partner: editor campaign or built-in, mission
|
||||||
|
MatchCoopResume(crate::campaign_doc::CampaignDoc), // an interrupted run of this campaign goes on
|
||||||
MatchTakeSide(u8),
|
MatchTakeSide(u8),
|
||||||
MatchSetAi(u8, bool),
|
MatchSetAi(u8, bool),
|
||||||
MatchSetTeam(u8, u8, bool), // side, team, the own seat
|
MatchSetTeam(u8, u8, bool), // side, team, the own seat
|
||||||
@@ -800,6 +802,7 @@ impl Menu {
|
|||||||
self.confirm = None;
|
self.confirm = None;
|
||||||
MenuAction::Consumed
|
MenuAction::Consumed
|
||||||
}
|
}
|
||||||
|
Some(Btn::MatchCoopResume) => MenuAction::MatchCoopResume(self.coop_doc().clone()),
|
||||||
Some(Btn::MatchCoopSolo) if self.coop_editor => MenuAction::MatchCoopSolo(Some(self.coop_doc().clone()), self.coop_editor_mission),
|
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::MatchCoopSolo) => MenuAction::MatchCoopSolo(None, self.coop_mission),
|
||||||
Some(Btn::MatchCoopCreate) if self.coop_editor => MenuAction::MatchCoopEditorCreate(self.coop_editor_mission),
|
Some(Btn::MatchCoopCreate) if self.coop_editor => MenuAction::MatchCoopEditorCreate(self.coop_editor_mission),
|
||||||
@@ -1998,6 +2001,12 @@ impl Menu {
|
|||||||
items.push((Btn::MatchCoopNext, ">".to_string()));
|
items.push((Btn::MatchCoopNext, ">".to_string()));
|
||||||
items.push((Btn::MatchCoopSolo, tr("net.coop_solo")));
|
items.push((Btn::MatchCoopSolo, tr("net.coop_solo")));
|
||||||
}
|
}
|
||||||
|
// an interrupted run of the shown campaign can go on (online, or offline with the AI)
|
||||||
|
if editor_coop {
|
||||||
|
if let Some((m, _, _)) = crate::coop_resume_of(&doc) {
|
||||||
|
items.push((Btn::MatchCoopResume, trf("net.coop_resume", &[(m + 1).to_string()])));
|
||||||
|
}
|
||||||
|
}
|
||||||
// the button names what comes next
|
// the button names what comes next
|
||||||
let next = match (editor_coop, self.coop_league) {
|
let next = match (editor_coop, self.coop_league) {
|
||||||
(false, _) => "net.coop_league",
|
(false, _) => "net.coop_league",
|
||||||
|
|||||||
Reference in new issue
Block a user