Versus teams, gold for allies, fallen hearts as spectators, multi-mission editor co-op
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
811c955bad
commit
fc9c49d4ca
16 files changed
+1896
-49
No files matched your search
+69
-2
@@ -281,6 +281,9 @@ enum Btn {
|
||||
MatchCoopSource, // switch between the built-in campaign and the editor campaign
|
||||
MatchTake(usize),
|
||||
MatchAi(usize),
|
||||
MatchTeam(usize), // next team for that seat
|
||||
GoldTo, // pause menu: the next ally to receive gold
|
||||
Gold(u32), // pause menu: send that much gold (GOLD_ALL: everything)
|
||||
MatchReady,
|
||||
MatchStart,
|
||||
MatchResume,
|
||||
@@ -396,6 +399,8 @@ pub enum MenuAction {
|
||||
MatchCoopEditorCreate(usize), // co-op match of a mission of `campaign_doc`
|
||||
MatchTakeSide(u8),
|
||||
MatchSetAi(u8, bool),
|
||||
MatchSetTeam(u8, u8, bool), // side, team, the own seat
|
||||
SendGold(u8, u32), // ally side, amount
|
||||
MatchReady(bool),
|
||||
MatchStart,
|
||||
MatchResume,
|
||||
@@ -419,6 +424,8 @@ pub struct Menu {
|
||||
pub coop_editor: bool, // the Match page offers the editor campaign instead of the built-in one
|
||||
pub coop_editor_mission: usize, // mission of `campaign_doc` selected for co-op
|
||||
pub in_match: bool, // a match is being played; the pause menu changes its shape
|
||||
pub gold_allies: Vec<u8>, // allies of the local master that can get gold (pause menu)
|
||||
pub gold_to: usize, // the one chosen among them
|
||||
pub editor_test: bool, // a mission of the editor is being tested: back to the editor
|
||||
pub custom_briefing: (String, String), // title and text of an editor mission's briefing
|
||||
pub campaign_doc: CampaignDoc,
|
||||
@@ -458,6 +465,8 @@ impl Menu {
|
||||
coop_editor: false,
|
||||
coop_editor_mission: 0,
|
||||
in_match: false,
|
||||
gold_allies: Vec::new(),
|
||||
gold_to: 0,
|
||||
editor_test: false,
|
||||
custom_briefing: (String::new(), String::new()),
|
||||
campaign_doc: CampaignDoc::default(),
|
||||
@@ -791,6 +800,18 @@ impl Menu {
|
||||
Some(s) => MenuAction::MatchTakeSide(s.side),
|
||||
None => MenuAction::Consumed,
|
||||
},
|
||||
Some(Btn::MatchTeam(i)) => match self.match_ui.seats.get(i) {
|
||||
Some(s) => MenuAction::MatchSetTeam(s.side, (s.team + 1) % 4, Some(s.side) == self.match_ui.local_side),
|
||||
None => MenuAction::Consumed,
|
||||
},
|
||||
Some(Btn::GoldTo) => {
|
||||
self.gold_to = (self.gold_to + 1) % self.gold_allies.len().max(1);
|
||||
MenuAction::Consumed
|
||||
}
|
||||
Some(Btn::Gold(amount)) => match self.gold_allies.get(self.gold_to.min(self.gold_allies.len().saturating_sub(1))) {
|
||||
Some(&to) => MenuAction::SendGold(to, amount),
|
||||
None => MenuAction::Consumed,
|
||||
},
|
||||
Some(Btn::MatchAi(i)) => match self.match_ui.seats.get(i) {
|
||||
Some(s) if s.side != 0 => MenuAction::MatchSetAi(s.side, !s.ai),
|
||||
_ => MenuAction::Consumed,
|
||||
@@ -1156,13 +1177,32 @@ impl Menu {
|
||||
(Btn::MainMenu, tr("menu.main")),
|
||||
]);
|
||||
}
|
||||
let cols = 32;
|
||||
let rows = items.len() as i32 * 3 + 4;
|
||||
// a match with allies: gold for one of them (the ally button switches between them)
|
||||
let gold = self.in_match && !self.gold_allies.is_empty();
|
||||
let cols = if gold { 40 } else { 32 };
|
||||
let rows = items.len() as i32 * 3 + 4 + if gold { 6 } else { 0 };
|
||||
let x = grid_center(sw, cols as f32 * CW());
|
||||
let y = (grid_center_y(sh, rows as f32 * CH())).max(0.0);
|
||||
gfx.window_titled(x, y, cols, rows, S(), true, FRAME_HI, Color::new(0.05, 0.03, 0.04, 0.97), &tr("menu.paused"), FRAME_HI);
|
||||
let w = (cols - 6) as f32 * CW();
|
||||
self.column(gfx, x + CW() * 3.0, y + CH() * 2.0, w, &items);
|
||||
if gold {
|
||||
let (mx, my) = mouse();
|
||||
self.gold_to = self.gold_to.min(self.gold_allies.len() - 1);
|
||||
let to = self.gold_allies[self.gold_to];
|
||||
let gy = y + CH() * (2.0 + items.len() as f32 * 3.0);
|
||||
let label = trf("gold.send_to", &[tr(&format!("net.side{to}"))]);
|
||||
let r = Rect::new(x + CW() * 3.0, gy, w, CH() * 2.0);
|
||||
button(gfx, r, &label, r.contains(vec2(mx, my)), side_color(Some(to)));
|
||||
self.buttons.push((r, Btn::GoldTo));
|
||||
let bw = (w - CW() * 3.0) / 4.0;
|
||||
for (k, amount) in crate::netplay::GOLD_AMOUNTS.iter().enumerate() {
|
||||
let label = if *amount == crate::netplay::GOLD_ALL { tr("gold.all") } else { amount.to_string() };
|
||||
let r = Rect::new(x + CW() * 3.0 + k as f32 * (bw + CW()), gy + CH() * 2.6, bw, CH() * 2.0);
|
||||
button(gfx, r, &label, r.contains(vec2(mx, my)), GOLD);
|
||||
self.buttons.push((r, Btn::Gold(*amount)));
|
||||
}
|
||||
}
|
||||
let ver = tr("menu.version");
|
||||
let line = format!("{} {}", ver, version());
|
||||
gfx.text(&line, x + (cols as f32 * CW() - gfx.measure(&line, SMALL())) * 0.5, y + (rows as f32 - 1.7) * CH(), SMALL(), DIM);
|
||||
@@ -1970,6 +2010,21 @@ impl Menu {
|
||||
let r = Rect::new(ix, yy, 24.0 * CW(), CH() * 2.0);
|
||||
button(gfx, r, &label, r.contains(vec2(mx, my)), FRAME);
|
||||
self.buttons.push((r, Btn::MatchJoinRoom(k)));
|
||||
// what the room plays: an editor campaign (name and mission), a co-op mission or versus
|
||||
let what = match (&room.campaign, room.mission) {
|
||||
(Some(name), _) => {
|
||||
let name = if name.trim().is_empty() { "?".to_string() } else { name.clone() };
|
||||
match &room.title {
|
||||
Some(t) if !t.trim().is_empty() => format!("{name}: {t}"),
|
||||
_ => name,
|
||||
}
|
||||
}
|
||||
(None, Some(m)) => missions().get(m).map_or_else(|| tr("net.coop_create"), |d| tr(&format!("camp.{}.title", d.key))),
|
||||
(None, None) => tr("net.versus"),
|
||||
};
|
||||
let maxc = ((w - 25.0 * CW()) / (GW * SMALL())).max(4.0) as usize;
|
||||
let what: String = what.chars().take(maxc).collect();
|
||||
gfx.text(&what, ix + 25.0 * CW(), yy + (CH() * 2.0 - GH * SMALL()) * 0.5, SMALL(), if room.campaign.is_some() { GOLD } else { DIM });
|
||||
yy += CH() * 2.2;
|
||||
}
|
||||
} else if ui.rooms.is_empty() && !ui.started {
|
||||
@@ -1999,6 +2054,18 @@ impl Menu {
|
||||
let ready = if s.ready || s.ai { "✓" } else { "—" };
|
||||
let line = format!("{} {} {} {} {}", side_label, name, status, ready, if s.side == 0 { tr("net.host") } else { String::new() });
|
||||
gfx.text(&line, ix, yy, SMALL(), if s.connected || s.ai { TXT } else { DIM });
|
||||
// versus: every seat chooses its team (the host also for free and AI seats)
|
||||
if !ui.coop {
|
||||
let team = trf("net.team", &[((b'A' + s.team.min(3)) as char).to_string()]);
|
||||
let r = Rect::new(ix + 49.0 * CW(), yy, 10.0 * CW(), CH() * 2.0);
|
||||
let may = !ui.started && (is_local || (ui.is_host && !s.connected));
|
||||
if may {
|
||||
button(gfx, r, &team, r.contains(vec2(mx, my)), FRAME);
|
||||
self.buttons.push((r, Btn::MatchTeam(k)));
|
||||
} else {
|
||||
gfx.text(&team, r.x + CW(), yy + CH() * 0.5, SMALL(), DIM);
|
||||
}
|
||||
}
|
||||
if !ui.started && ui.local_side.is_some() && !s.connected {
|
||||
if ui.is_host && s.side != 0 {
|
||||
if s.ai {
|
||||
|
||||
Reference in new issue
Block a user