Resume interrupted editor co-op runs

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Richard ZinkandClaude Opus 5.5 committed 2026-11-05 21:08:33 +01:00
1 parent f87b59390e
commit c7c3e979d6
24 files changed
+145 -6

No files matched your search

+35 -3
View File
@@ -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
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()
}
// 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) {
let mut all = coop_progress_all();
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));
p.run_seed = self.run_seed;
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);
}
} 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"));
}
}
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::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),
@@ -703,11 +731,14 @@ impl App {
self.coop_editor = true;
self.recorded = false;
self.pending_briefing = briefing_of(doc, mission);
// the host keeps the progress of the run (a new run starts with the room's seed)
if !info.next && info.local_side == Some(0) {
// the host keeps the progress of the run (a new run starts with the room's seed) and
// 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));
p.ensure(doc.missions.len());
p.run_seed = info.seed;
p.next = Some(mission);
p.carry = info.carry.clone();
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 });
@@ -1506,6 +1537,7 @@ app.pending_briefing = briefing_of(&menu.campaign_doc, i);
| MenuAction::MatchLeave
| MenuAction::MatchCoopEditorCreate(..)
| MenuAction::MatchCoopSolo(..)
| MenuAction::MatchCoopResume(..)
| MenuAction::MatchChat(..)
| MenuAction::MatchEditField(..)) => account_action(a, &mut cloud, &mut menu, &mut app, now),
MenuAction::EditName => {