Creature overview: a toolbar button opens the list of all creatures grouped by kind (level, mood, wage, job, health) with wages and mood in total; a click moves the camera there

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Richard ZinkandClaude Opus 5.5 committed 2026-11-04 08:02:29 +01:00
1 parent bbab2b5a58
commit 51814b8ac8
3 files changed
+124 -2

No files matched your search

+7
View File
@@ -916,3 +916,10 @@ hint.order_nobody = Keine Kreatur ist für diesen Befehl frei.
msg.order_attack = {0} Kreaturen greifen an!
msg.order_guard = {0} Kreaturen beziehen Stellung.
msg.orders_cancelled = Wachbefehle aufgehoben.
# creature overview
cat.overview = Kreaturen
ov.title = Deine Kreaturen
ov.summary = {0} Kreaturen - Lohn {1} Gold pro Zahltag - Laune {2}%
ov.none = Noch keine Kreaturen. Baue ein Quartier und einen Pilzgarten und nimm ein Portal ein.
ov.hint = St., Laune, Lohn, Tätigkeit, Leben - Klick: hingehen
+7
View File
@@ -917,3 +917,10 @@ hint.order_nobody = No creature is free for that order.
msg.order_attack = {0} creatures attack!
msg.order_guard = {0} creatures take their positions.
msg.orders_cancelled = Guard orders cancelled.
# creature overview
cat.overview = Creatures
ov.title = Your creatures
ov.summary = {0} creatures - wages {1} gold per payday - mood {2}%
ov.none = No creatures yet. Build a lair and a mushroom garden and claim a portal.
ov.hint = Lv, mood, wage, job, health - click: go there
+110 -2
View File
@@ -326,6 +326,9 @@ enum TBtn {
Cat(usize),
Tool(usize),
Menu,
Overview,
OvRow(u32),
OvScroll(i32),
}
pub enum UiAction {
@@ -346,6 +349,8 @@ pub enum UiAction {
pub struct Ui {
pub tool: Tool,
pub show_help: bool,
pub show_overview: bool, // list of all creatures
ov_scroll: usize,
pub show_langsel: bool,
pub log: Vec<(Msg, f64)>,
pub rects: Vec<Rect>,
@@ -398,6 +403,8 @@ impl Ui {
Ui {
tool: Tool::Dig,
show_help: true,
show_overview: false,
ov_scroll: 0,
show_langsel: false,
log: Vec::new(),
rects: Vec::new(),
@@ -461,6 +468,20 @@ impl Ui {
self.open_cat = None;
UiAction::OpenMenu
}
TBtn::Overview => {
self.open_cat = None;
self.show_overview = !self.show_overview;
self.ov_scroll = 0;
UiAction::Consumed
}
TBtn::OvScroll(d) => {
self.ov_scroll = (self.ov_scroll as i32 + d).max(0) as usize;
UiAction::Consumed
}
TBtn::OvRow(id) => match g.ents.iter().find(|e| e.id == id) {
Some(e) => UiAction::MinimapJump(e.x, e.y),
None => UiAction::Consumed,
},
TBtn::Tool(i) if !g.tool_allowed(i) => {
self.last_locked = Some(i);
UiAction::Locked
@@ -643,7 +664,7 @@ impl Ui {
let mut popup_top = tb_y;
// categories + menu button; a category with several tools opens a row above
// (the same layout for mouse and touch; on the desktop the keys still select tools directly)
let n = CATS.len() + 1;
let n = CATS.len() + 2;
let bcols = (((cols - 2) as usize / n) as f32).clamp(4.0, 8.0);
let bw = bcols * CW();
let x0 = grid_center(sw, bw * n as f32);
@@ -672,10 +693,22 @@ impl Ui {
}
self.touch_btns.push((r, TBtn::Cat(c)));
}
// menu button
// creature overview button
let bx = x0 + CATS.len() as f32 * bw;
let r = Rect::new(bx + px, by, bw - 2.0 * px, 3.0 * CH());
let hovered = !touch_ui() && r.contains(vec2(mx, my));
draw_rectangle(r.x, r.y, r.w, r.h, if hovered || self.show_overview { Color::new(0.18, 0.13, 0.1, 1.0) } else { Color::new(0.1, 0.07, 0.07, 1.0) });
draw_rectangle_lines(r.x, r.y, r.w, r.h, 2.0 * px, if self.show_overview { FRAME_HI } else { Color::new(0.35, 0.28, 0.2, 1.0) });
gfx.glyph(0x02, bx + (bw - GW * TITLE()) * 0.5, by + 2.0 * px, TITLE(), FRAME_HI);
let ol = tr("cat.overview");
let maxc = ((bw - 2.0 * px) / (GW * SMALL())).floor() as usize;
let ol: String = ol.chars().take(maxc).collect();
gfx.text(&ol, bx + (bw - gfx.measure(&ol, SMALL())) * 0.5, by + 2.0 * CH() + 4.0 * px, SMALL(), TXT);
self.touch_btns.push((r, TBtn::Overview));
// menu button
let bx = x0 + (CATS.len() + 1) as f32 * bw;
let r = Rect::new(bx + px, by, bw - 2.0 * px, 3.0 * CH());
let hovered = !touch_ui() && r.contains(vec2(mx, my));
draw_rectangle(r.x, r.y, r.w, r.h, if hovered { Color::new(0.18, 0.13, 0.1, 1.0) } else { Color::new(0.1, 0.07, 0.07, 1.0) });
draw_rectangle_lines(r.x, r.y, r.w, r.h, 2.0 * px, Color::new(0.35, 0.28, 0.2, 1.0));
gfx.glyph(0xF0, bx + (bw - GW * TITLE()) * 0.5, by + 2.0 * px, TITLE(), FRAME_HI);
@@ -872,6 +905,11 @@ impl Ui {
popup_top = py0;
}
// ---------------- creature overview
if self.show_overview {
self.draw_overview(g, gfx, tb_y);
}
// ---------------- Tooltip
if let Some(i) = hover_tool {
let t = TOOLS[i];
@@ -897,6 +935,76 @@ impl Ui {
}
// all creatures, grouped by kind: level, mood, wage, job, health; a click jumps there
fn draw_overview(&mut self, g: &Game, gfx: &Gfx, bottom: f32) {
let (sw, _) = (sw(), sh());
let (mx, my) = mouse();
let cols = ((sw / CW()) as i32 - 2).min(56);
let top = CH() * 4.0;
let rows = (((bottom - top) / CH()) as i32 - 1).clamp(8, 40);
let x = (sw - cols as f32 * CW() - CW()).max(0.0);
let y = (bottom - rows as f32 * CH()).max(top);
gfx.window_titled(x, y, cols, rows, S(), false, FRAME_HI, BG, &tr("ov.title"), FRAME_HI);
self.rects.push(Rect::new(x, y, cols as f32 * CW(), rows as f32 * CH()));
let mine: Vec<&Ent> = g.ents.iter().filter(|e| e.alive && e.creature()).collect();
let wage: f32 = mine.iter().map(|e| st(e.kind).wage as f32 * (1.0 + 0.1 * (e.level - 1) as f32)).sum();
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);
// lines: kind headers and creatures
let mut kinds: Vec<EK> = Vec::new();
for e in &mine {
if !kinds.contains(&e.kind) {
kinds.push(e.kind);
}
}
kinds.sort_by_key(|k| std::cmp::Reverse(mine.iter().filter(|e| e.kind == *k).count()));
let mut lines: Vec<(String, Color, Option<u32>, f32)> = Vec::new();
for k in kinds {
let group: Vec<&&Ent> = mine.iter().filter(|e| e.kind == k).collect();
lines.push((format!("{} x{}", tr(st(k).name), group.len()), FRAME_HI, None, -1.0));
let mut group = group;
group.sort_by_key(|e| std::cmp::Reverse(e.level));
for e in group {
let w = (st(e.kind).wage as f32 * (1.0 + 0.1 * (e.level - 1) as f32)) as i32;
let happy = (e.happy.clamp(0.0, 1.0) * 100.0) as i32;
let job: String = tr(job_name(e)).chars().take(16).collect();
let line = format!(" {:>2} {:>3}% {:>4}g {:<16}", e.level, happy, w, job);
let col = if e.happy > 0.6 { TXT } else if e.happy > 0.25 { GOLD } else { RED };
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;
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) {
let ly = first + (n - self.ov_scroll) as f32 * CH();
let r = Rect::new(x + CW() * 2.0, ly, lw, CH());
if id.is_some() && r.contains(vec2(mx, my)) {
draw_rectangle(r.x, r.y, r.w, r.h, Color::new(0.25, 0.16, 0.08, 1.0));
}
gfx.text(text, r.x, ly, S(), *col);
if let Some(id) = id {
ascii_bar(gfx, r.x + r.w - CW() * 4.0, ly + (CH() - GH * SMALL()) * 0.5, 6, *hp, if *hp > 0.5 { GREEN } else { RED }, SMALL());
self.touch_btns.push((r, TBtn::OvRow(*id)));
}
}
if lines.len() > visible {
let up = Rect::new(x + (cols - 4) as f32 * CW(), y + CH(), CW() * 2.0, CH() * 1.5);
let down = Rect::new(x + (cols - 4) as f32 * CW(), y + (rows - 2) as f32 * CH(), CW() * 2.0, CH() * 1.5);
gfx.text("\u{25b2}", up.x + CW() * 0.5, up.y, S(), FRAME_HI);
gfx.text("\u{25bc}", down.x + CW() * 0.5, down.y, S(), FRAME_HI);
self.touch_btns.push((up, TBtn::OvScroll(-(visible as i32) / 2)));
self.touch_btns.push((down, TBtn::OvScroll(visible as i32 / 2)));
}
if mine.is_empty() {
gfx.text(&tr("ov.none"), x + CW() * 2.0, first, S(), DIM);
}
let hint = tr("ov.hint");
gfx.text(&hint, x + CW() * 2.0, y + (rows - 2) as f32 * CH(), SMALL(), DIM);
}
// Dialogs on top of everything (help, language selection, end screen, pause)
pub fn draw_dialogs(&mut self, g: &Game, gfx: &Gfx) {
let sw = sw();