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:
Richard ZinkandClaude Opus 5.5 committed 2026-11-05 19:59:43 +01:00
1 parent 811c955bad
commit fc9c49d4ca
16 files changed
+1896 -49

No files matched your search

+68 -5
View File
@@ -346,6 +346,8 @@ enum TBtn {
Overview,
OvRow(u32),
OvScroll(i32),
GoldTo, // the next ally to receive gold
Gold(u32), // send that much gold to him
}
pub enum UiAction {
@@ -362,6 +364,7 @@ pub enum UiAction {
SelectLanguage(String),
ChatSetTeam(bool),
ChatSend,
SendGold(u8, u32), // gold for an ally of a match (side, amount)
Consumed,
}
@@ -369,6 +372,8 @@ pub struct Ui {
pub tool: Tool,
pub show_help: bool,
pub show_overview: bool, // list of all creatures
pub coop_end: Option<(bool, bool, bool)>, // end of an editor co-op mission: (host, a next mission, asked for it)
gold_to: usize, // the ally chosen in the creature overview
ov_scroll: usize,
ov_rect: Option<Rect>, // where the creature overview is (mouse wheel, swipe)
pub show_langsel: bool,
@@ -431,6 +436,8 @@ impl Ui {
tool: Tool::Dig,
show_help: true,
show_overview: false,
coop_end: None,
gold_to: 0,
ov_scroll: 0,
ov_rect: None,
show_langsel: false,
@@ -527,6 +534,17 @@ impl Ui {
self.ov_scroll = (self.ov_scroll as i32 + d).max(0) as usize;
UiAction::Consumed
}
TBtn::GoldTo => {
self.gold_to += 1;
UiAction::Consumed
}
TBtn::Gold(amount) => {
let allies = g.gold_allies();
match allies.get(self.gold_to % allies.len().max(1)) {
Some(&to) => UiAction::SendGold(to, amount),
None => UiAction::Consumed,
}
}
TBtn::OvRow(id) => match g.ents.iter().find(|e| e.id == id) {
Some(e) => UiAction::MinimapJump(e.disp_x, e.disp_y),
None => UiAction::Consumed,
@@ -1036,6 +1054,32 @@ impl Ui {
let mood = if mine.is_empty() { 0.0 } else { mine.iter().map(|e| e.happy.clamp(0.0, 1.0)).sum::<f32>() / mine.len() as f32 };
let head = trf("ov.summary", &[mine.len().to_string(), (wage as i32).to_string(), ((mood * 100.0) as i32).to_string()]);
gfx.text(&head, x + CW() * 2.0, y + CH(), S(), GOLD);
// a match with allies: a row to send them gold (the name switches between the allies)
let allies = g.gold_allies();
let gold_row = !allies.is_empty();
if gold_row {
let to = allies[self.gold_to % allies.len()];
let gy = y + CH() * 2.2;
let name = trf("gold.send_to", &[tr(&format!("net.side{to}"))]);
let mut bx = x + CW() * 2.0;
let r = Rect::new(bx, gy, gfx.measure(&name, SMALL()) + CW() * 2.0, CH() * 1.6);
let hov = r.contains(vec2(mx, my));
draw_rectangle(r.x, r.y, r.w, r.h, if hov { Color::new(0.35, 0.22, 0.1, 1.0) } else { Color::new(0.15, 0.1, 0.08, 1.0) });
draw_rectangle_lines(r.x, r.y, r.w, r.h, 2.0, side_color(Some(to)));
gfx.text(&name, r.x + CW(), r.y + (r.h - GH * SMALL()) * 0.5, SMALL(), side_color(Some(to)));
self.touch_btns.push((r, TBtn::GoldTo));
bx += r.w + CW();
for amount in crate::netplay::GOLD_AMOUNTS {
let label = if amount == crate::netplay::GOLD_ALL { tr("gold.all") } else { amount.to_string() };
let r = Rect::new(bx, gy, gfx.measure(&label, SMALL()) + CW() * 2.0, CH() * 1.6);
let hov = r.contains(vec2(mx, my));
draw_rectangle(r.x, r.y, r.w, r.h, if hov { Color::new(0.35, 0.22, 0.1, 1.0) } else { Color::new(0.15, 0.1, 0.08, 1.0) });
draw_rectangle_lines(r.x, r.y, r.w, r.h, 2.0, GOLD);
gfx.text(&label, r.x + CW(), r.y + (r.h - GH * SMALL()) * 0.5, SMALL(), GOLD);
self.touch_btns.push((r, TBtn::Gold(amount)));
bx += r.w + CW() * 0.5;
}
}
// lines: kind headers and creatures
let mut kinds: Vec<EK> = Vec::new();
for e in &mine {
@@ -1059,8 +1103,8 @@ impl Ui {
lines.push((line, col, Some(e.id), e.hp / e.max_hp));
}
}
let first = y + CH() * 3.0;
let visible = (rows - 5).max(1) as usize;
let first = y + CH() * if gold_row { 4.2 } else { 3.0 };
let visible = (rows - if gold_row { 7 } else { 5 }).max(1) as usize;
self.ov_scroll = self.ov_scroll.min(lines.len().saturating_sub(visible));
let lw = (cols - 4) as f32 * CW();
for (n, (text, col, id, hp)) in lines.iter().enumerate().skip(self.ov_scroll).take(visible) {
@@ -1094,6 +1138,15 @@ impl Ui {
pub fn draw_dialogs(&mut self, g: &Game, gfx: &Gfx) {
let sw = sw();
let sh = sh();
if g.spectating() && g.state == 0 && !self.show_langsel {
// the heart fell: the player watches the rest of the match
let t = tr("net.spectating");
let w = ((gfx.measure(&t, S()) / CW()).ceil() as i32 + 4).min((sw / CW()) as i32);
let x = grid_center(sw, w as f32 * CW());
let y = self.hud_h + CH() * 0.5;
gfx.window(x, y, w, 3, S(), false, RED, BG);
gfx.text(&t, grid_center(sw, gfx.measure(&t, S())), y + CH(), S(), RED);
}
if self.show_langsel {
self.draw_langsel(gfx);
} else if g.state != 0 {
@@ -1258,10 +1311,20 @@ impl Ui {
let st = trf("end.stats", &[mmss(g.time), g.wave.to_string(), (g.army_value() as i32).to_string()]);
gfx.text(&st, grid_center(sw, gfx.measure(&st, S())), y + CH() * (4.5 + lines.len() as f32), S(), DIM);
// buttons: free game: new game; mission: next / again and back to the campaign
let labels: Vec<(String, u8)> = match mission {
None => vec![(tr("end.new"), 0)],
Some(_) => vec![(tr(if !win { "end.again" } else if last { "end.to_campaign" } else { "end.next" }), 1), (tr("end.campaign"), 2)],
let labels: Vec<(String, u8)> = match (mission, self.coop_end) {
// an editor campaign in co-op: the host goes on for everybody, the others wait or leave
(_, Some((true, true, false))) => vec![(tr(if win { "end.next" } else { "end.again" }), 1), (tr("net.leave"), 2)],
(_, Some((true, true, true))) | (_, Some((false, true, _))) => vec![(tr("net.leave"), 2)],
(_, Some((_, false, _))) => vec![(tr("net.coop_run_over"), 2)],
(None, None) => vec![(tr("end.new"), 0)],
(Some(_), None) => vec![(tr(if !win { "end.again" } else if last { "end.to_campaign" } else { "end.next" }), 1), (tr("end.campaign"), 2)],
};
if let Some((host, true, sent)) = self.coop_end {
if !host || sent {
let t = tr("net.coop_wait_host");
gfx.text(&t, grid_center(sw, gfx.measure(&t, SMALL())), y + CH() * (5.3 + lines.len() as f32), SMALL(), GOLD);
}
}
let by = y + CH() * (6.0 + lines.len() as f32);
let widths: Vec<f32> = labels.iter().map(|(l, _)| gfx.measure(l, S()) + CW() * 4.0).collect();
let total: f32 = widths.iter().sum::<f32>() + CW() * 2.0 * (labels.len() as f32 - 1.0);