Multiplayer continues after a player leaves: AI side takeover, background match while menus are open, match chat, five-tile fog start

This commit is contained in:
Richard Zink committed 2026-11-04 18:07:02 +01:00
1 parent 2721638910
commit dc22e27184
11 files changed
+436 -70

No files matched your search

+93 -14
View File
@@ -138,6 +138,22 @@ pub const FRAME_HI: Color = Color::new(0.95, 0.8, 0.4, 1.0);
pub const BG: Color = Color::new(0.06, 0.04, 0.05, 0.92);
pub const TXT: Color = Color::new(0.9, 0.86, 0.78, 1.0);
pub const DIM: Color = Color::new(0.6, 0.56, 0.5, 1.0);
pub fn side_color(side: Option<u8>) -> Color {
match side {
Some(0) => Color::new(1.0, 0.85, 0.45, 1.0),
Some(1) => Color::new(0.85, 0.55, 0.25, 1.0),
Some(2) => Color::new(0.35, 0.95, 0.40, 1.0),
Some(3) => Color::new(0.40, 0.60, 1.00, 1.0),
Some(4) => Color::new(0.85, 0.75, 0.30, 1.0),
_ => TXT,
}
}
pub fn chat_color(side: Option<u8>, alpha: f32) -> Color {
let c = side_color(side);
Color::new(c.r, c.g, c.b, alpha)
}
pub const GOLD: Color = Color::new(1.0, 0.84, 0.25, 1.0);
pub const MANA: Color = Color::new(0.7, 0.45, 1.0, 1.0);
pub const RED: Color = Color::new(0.95, 0.3, 0.3, 1.0);
@@ -343,6 +359,8 @@ pub enum UiAction {
CloseHelp,
NewGame,
SelectLanguage(String),
ChatSetTeam(bool),
ChatSend,
Consumed,
}
@@ -353,6 +371,12 @@ pub struct Ui {
ov_scroll: usize,
pub show_langsel: bool,
pub log: Vec<(Msg, f64)>,
pub chat_log: Vec<(Option<u8>, String, String, f64)>,
pub chat_open: bool,
pub chat_text: String,
pub chat_team: bool,
chat_rects: [(Rect, Option<bool>); 2],
chat_send_rect: Rect,
pub rects: Vec<Rect>,
minimap_img: Image,
minimap_tex: Texture2D,
@@ -407,6 +431,12 @@ impl Ui {
ov_scroll: 0,
show_langsel: false,
log: Vec::new(),
chat_log: Vec::new(),
chat_open: false,
chat_text: String::new(),
chat_team: false,
chat_rects: [(Rect::new(0.0, 0.0, 0.0, 0.0), Some(false)); 2],
chat_send_rect: Rect::new(0.0, 0.0, 0.0, 0.0),
rects: Vec::new(),
minimap_img: img,
minimap_tex: tex,
@@ -425,7 +455,7 @@ impl Ui {
}
pub fn over_ui(&self, x: f32, y: f32) -> bool {
self.show_help || self.show_langsel || self.rects.iter().any(|r| r.contains(vec2(x, y)))
self.chat_open || self.show_help || self.show_langsel || self.rects.iter().any(|r| r.contains(vec2(x, y)))
}
pub fn minimap_contains(&self, x: f32, y: f32) -> bool {
@@ -439,6 +469,20 @@ impl Ui {
pub fn click(&mut self, x: f32, y: f32, g: &Game) -> UiAction {
let p = vec2(x, y);
if self.chat_open {
if self.chat_rects[0].0.contains(p) {
self.chat_team = false;
return UiAction::ChatSetTeam(false);
}
if self.chat_rects[1].0.contains(p) {
self.chat_team = true;
return UiAction::ChatSetTeam(true);
}
if self.chat_send_rect.contains(p) {
return UiAction::ChatSend;
}
return UiAction::Consumed;
}
if self.show_langsel {
for (r, code) in &self.lang_rects {
if r.contains(p) {
@@ -853,29 +897,64 @@ impl Ui {
}
}
// ---------------- messages (translated at display time, so a language switch applies at once)
let recent: Vec<&(Msg, f64)> = self.log.iter().filter(|(_, t)| now - t < 14.0).collect();
if !recent.is_empty() {
let mcols = (((sw - mm_w) / CW()) as i32 - 2).clamp(20, 70);
let mut lines: Vec<(String, f32)> = Vec::new();
// ---------------- messages + chat (translated at display time, so a language switch applies at once)
let mut recent: Vec<(f64, String, Color)> = Vec::new();
for (m, t) in self.log.iter().filter(|(_, t)| now - t < 14.0) {
let a = (1.0 - ((now - t) as f32 - 11.0) / 3.0).clamp(0.2, 1.0);
recent.push((*t, trf(m.key, &m.args), Color::new(TXT.r, TXT.g, TXT.b, a)));
}
for (side, name, text, t) in self.chat_log.iter().filter(|(_, _, _, t)| now - t < 14.0) {
let a = (1.0 - ((now - t) as f32 - 11.0) / 3.0).clamp(0.2, 1.0);
recent.push((*t, format!("{name}: {text}"), chat_color(*side, a)));
}
recent.sort_by(|a, b| a.0.partial_cmp(&b.0).unwrap_or(std::cmp::Ordering::Equal));
if !recent.is_empty() || self.chat_open {
let mcols = (((sw - mm_w) / CW()) as i32 - 2).clamp(34, 70);
let mut lines: Vec<(String, Color)> = Vec::new();
let keep = if touch_ui() { 3 } else { 5 };
for (m, t) in recent.iter().rev().take(keep).rev() {
let a = (1.0 - ((now - t) as f32 - 11.0) / 3.0).clamp(0.2, 1.0);
for l in wrap(&trf(m.key, &m.args), mcols as usize - 4) {
lines.push((l, a));
for (_, line, col) in recent.iter().rev().take(keep).rev() {
for l in wrap(line, mcols as usize - 4) {
lines.push((l.clone(), *col));
}
}
let mrows = lines.len() as i32 + 2;
let chat_rows = if self.chat_open { 2 } else { 0 };
let mrows = lines.len() as i32 + chat_rows + 2;
let my0 = tb_y - mrows as f32 * CH();
gfx.window(0.0, my0, mcols, mrows, S(), false, FRAME, Color::new(0.06, 0.04, 0.05, 0.75));
gfx.window(0.0, my0, mcols, mrows, S(), false, FRAME, Color::new(0.06, 0.04, 0.05, 0.82));
self.rects.push(Rect::new(0.0, my0, mcols as f32 * CW(), mrows as f32 * CH()));
for (k, (l, a)) in lines.iter().enumerate() {
gfx.text(l, CW() * 2.0, my0 + CH() * (k as f32 + 1.0), S(), Color::new(TXT.r, TXT.g, TXT.b, *a));
for (k, (l, col)) in lines.iter().enumerate() {
gfx.text(l, CW() * 2.0, my0 + CH() * (k as f32 + 1.0), S(), *col);
}
if self.chat_open {
let cy = my0 + CH() * (lines.len() as f32 + 1.0);
let all = tr("chat.all");
let team = tr("chat.team");
let send = tr("chat.send");
let r0 = Rect::new(CW() * 2.0, cy, 10.0 * CW(), CH());
let r1 = Rect::new(CW() * 14.0, cy, 10.0 * CW(), CH());
let r2 = Rect::new(mcols as f32 * CW() - 8.0 * CW(), cy + CH(), 6.0 * CW(), CH());
self.chat_rects = [(r0, Some(false)), (r1, Some(true))];
self.chat_send_rect = r2;
self.rects.push(r0);
self.rects.push(r1);
self.rects.push(r2);
let all_line = format!("{} {all}", if self.chat_team { " " } else { "o" });
let team_line = format!("{} {team}", if self.chat_team { "o" } else { " " });
gfx.text(&all_line, r0.x + CW(), cy + 4.0 * px, SMALL(), if !self.chat_team { FRAME_HI } else { DIM });
gfx.text(&team_line, r1.x + CW(), cy + 4.0 * px, SMALL(), if self.chat_team { FRAME_HI } else { DIM });
let shown = format!("> {}_", self.chat_text);
let maxc = (((mcols as f32 * CW() - 4.0 * CW() - 8.0 * CW()) / (GW * SMALL())) as usize).max(8);
let clipped: String = shown.chars().take(maxc).collect();
gfx.text(&clipped, CW() * 2.0, cy + CH() + 4.0 * px, SMALL(), TXT);
gfx.text(&send, r2.x + CW() * 1.5, cy + CH() + 4.0 * px, SMALL(), TXT);
}
}
if self.log.len() > 100 {
self.log.drain(0..50);
}
if self.chat_log.len() > 100 {
self.chat_log.drain(0..50);
}
// the open row of tools (wraps into several rows if needed)
if let Some((c, x0, bw, bcols)) = popup {