Renderer, Oberfläche, Eingabe, Sound-Modul, Simulationstest und Docker-Setup (Port 18093)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Richard ZinkandClaude Opus 5.5 committed 2026-11-02 22:16:41 +01:00
1 parent 14e481cffb
commit db417b3af8
13 files changed
+1930 -19

No files matched your search

+553
View File
@@ -0,0 +1,553 @@
// Benutzeroberfläche im Canvas: CP437-Fenster mit DINOBYTE-Schrift.
use crate::game::*;
use crate::gfx::*;
use crate::map::*;
use crate::render::{minimap_color, View};
use crate::sound::SoundBank;
use macroquad::prelude::*;
pub const S: f32 = 2.0; // UI-Skalierung der Schrift
pub const CW: f32 = GW * S; // Zeichenbreite in px
pub const CH: f32 = GH * S; // Zeichenhöhe in px
pub const FRAME: Color = Color::new(0.62, 0.52, 0.36, 1.0);
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 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);
pub const GREEN: Color = Color::new(0.45, 0.9, 0.45, 1.0);
pub const BLUE: Color = Color::new(0.5, 0.75, 1.0, 1.0);
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub enum Tool {
Dig,
Hand,
Room(usize),
Sell,
Flag,
Lightning,
Heal,
Summon,
}
pub const TOOLS: [Tool; 12] = [
Tool::Dig,
Tool::Hand,
Tool::Room(0),
Tool::Room(1),
Tool::Room(2),
Tool::Room(3),
Tool::Room(4),
Tool::Sell,
Tool::Flag,
Tool::Lightning,
Tool::Heal,
Tool::Summon,
];
pub const TOOL_KEYS: [&str; 12] = ["1", "2", "3", "4", "5", "6", "7", "8", "9", "0", "Q", "E"];
pub struct ToolInfo {
pub label: &'static str,
pub name: &'static str,
pub desc: &'static str,
pub icon: u32,
}
pub fn tool_info(t: Tool) -> ToolInfo {
let i = |label, name, desc, icon| ToolInfo { label, name, desc, icon };
match t {
Tool::Dig => i("Graben", "Graben", "Erde und Gold markieren. Ziehen für Rechteck; auf markiertem Feld beginnen hebt Markierung auf.", I_PICK),
Tool::Hand => i("Hand", "Hand", "Eigene Einheit aufnehmen (max. 8). Rechtsklick setzt sie auf eigenem Gebiet ab.", I_HAND),
Tool::Room(0) => i("Schatz", "Schatzkammer", "Lagert Gold: +500 Kapazität pro Feld.", T_TREASURY),
Tool::Room(1) => i("Quartier", "Quartier", "Ein Schlafplatz pro Feld. Bestimmt, wie viele Kreaturen kommen.", T_LAIR),
Tool::Room(2) => i("Pilze", "Pilzgarten", "Nahrung für Kreaturen. Ab 6 Feldern kommen Höhlenspinnen.", T_FARM),
Tool::Room(3) => i("Training", "Trainingshalle", "Kreaturen steigen Stufen auf (kostet Gold). Ab 9 Feldern kommen Steinoger.", T_TRAINING),
Tool::Room(4) => i("Bücher", "Bibliothek", "Kreaturen erzeugen Mana. Ab 6 Feldern kommen Schattenmagier.", T_LIBRARY),
Tool::Sell => i("Verkauf", "Verkaufen", "Räume zurück in Boden verwandeln (50 % Erstattung).", I_SELL),
Tool::Flag => i("Flagge", "Angriffsflagge", "Kampfbereite Kreaturen sammeln sich hier. Nahe am Heldentor greifen sie es an. Erneut klicken entfernt sie.", I_FLAG),
Tool::Lightning => i("Blitz", "Blitz", "55 Schaden an Helden im Umkreis.", I_BOLT),
Tool::Heal => i("Heilung", "Heilung", "Heilt eigene Einheiten im Umkreis um 50 %.", I_HEAL),
Tool::Summon => i("Wicht", "Grubenwicht beschwören", "Erschafft einen neuen Grubenwicht auf eigenem Gebiet.", I_SUMMON),
Tool::Room(_) => i("?", "?", "", 0),
}
}
pub fn tool_cost(t: Tool, g: &Game) -> String {
match t {
Tool::Room(r) => format!("{} Gold/Feld", ROOM_COST[r]),
Tool::Lightning => "50 Mana".into(),
Tool::Heal => "60 Mana".into(),
Tool::Summon => format!("{} Mana", g.summon_cost() as i32),
_ => String::new(),
}
}
pub enum UiAction {
None,
SelectTool(Tool),
MinimapJump(f32, f32),
CloseHelp,
NewGame,
Consumed,
}
pub struct Ui {
pub tool: Tool,
pub show_help: bool,
pub log: Vec<(String, f64)>,
pub rects: Vec<Rect>,
minimap_img: Image,
minimap_tex: Texture2D,
minimap_t: f64,
minimap_rect: Rect,
tool_rects: Vec<Rect>,
end_button: Rect,
}
fn wrap(s: &str, cols: usize) -> Vec<String> {
let mut out = Vec::new();
let mut line = String::new();
for word in s.split(' ') {
let len = line.chars().count();
if len > 0 && len + 1 + word.chars().count() > cols {
out.push(std::mem::take(&mut line));
}
if !line.is_empty() {
line.push(' ');
}
line.push_str(word);
}
if !line.is_empty() {
out.push(line);
}
out
}
fn bar(x: f32, y: f32, w: f32, h: f32, frac: f32, col: Color) {
draw_rectangle(x - 1.0, y - 1.0, w + 2.0, h + 2.0, Color::new(0.0, 0.0, 0.0, 1.0));
draw_rectangle(x, y, w, h, Color::new(col.r * 0.25, col.g * 0.25, col.b * 0.25, 1.0));
draw_rectangle(x, y, w * frac.clamp(0.0, 1.0), h, col);
draw_rectangle(x, y, w * frac.clamp(0.0, 1.0), (h * 0.3).max(1.0), Color::new(1.0, 1.0, 1.0, 0.25));
}
fn mmss(t: f32) -> String {
let t = t.max(0.0) as i32;
format!("{}:{:02}", t / 60, t % 60)
}
impl Ui {
pub fn new() -> Ui {
let img = Image::gen_image_color(MW as u16, MH as u16, BLACK);
let tex = Texture2D::from_image(&img);
tex.set_filter(FilterMode::Nearest);
Ui {
tool: Tool::Dig,
show_help: true,
log: Vec::new(),
rects: Vec::new(),
minimap_img: img,
minimap_tex: tex,
minimap_t: -1.0,
minimap_rect: Rect::new(0.0, 0.0, 0.0, 0.0),
tool_rects: Vec::new(),
end_button: Rect::new(0.0, 0.0, 0.0, 0.0),
}
}
pub fn over_ui(&self, x: f32, y: f32) -> bool {
self.show_help || self.rects.iter().any(|r| r.contains(vec2(x, y)))
}
pub fn minimap_contains(&self, x: f32, y: f32) -> bool {
self.minimap_rect.contains(vec2(x, y))
}
pub fn minimap_world(&self, x: f32, y: f32) -> (f32, f32) {
let r = self.minimap_rect;
((x - r.x) / r.w * MW as f32, (y - r.y) / r.h * MH as f32)
}
pub fn click(&mut self, x: f32, y: f32, g: &Game) -> UiAction {
let p = vec2(x, y);
if g.state != 0 {
if self.end_button.contains(p) {
return UiAction::NewGame;
}
return UiAction::Consumed;
}
if self.show_help {
return UiAction::CloseHelp;
}
for (i, r) in self.tool_rects.iter().enumerate() {
if r.contains(p) {
return UiAction::SelectTool(TOOLS[i]);
}
}
if self.minimap_rect.contains(p) {
let (wx, wy) = self.minimap_world(x, y);
return UiAction::MinimapJump(wx, wy);
}
if self.over_ui(x, y) {
return UiAction::Consumed;
}
UiAction::None
}
pub fn draw(&mut self, g: &Game, gfx: &Gfx, view: &View, snd: &SoundBank, info: &[(String, Color)], now: f64) {
self.rects.clear();
let sw = screen_width();
let sh = screen_height();
let (mx, my) = mouse_position();
// ---------------- obere Leiste
let cols = (sw / CW).ceil() as i32;
gfx.window(0.0, 0.0, cols, 3, S, false, FRAME, BG);
self.rects.push(Rect::new(0.0, 0.0, sw, 3.0 * CH));
let y = CH;
let mut x = CW * 2.0;
let seg = |label: &str, lc: Color, value: &str, vc: Color, x: &mut f32| {
gfx.text(label, *x, y, S, lc);
*x += gfx.measure(label, S) + CW;
gfx.text(value, *x, y, S, vc);
*x += gfx.measure(value, S) + CW * 2.0;
};
seg("Gold", GOLD, &format!("{}/{}", g.gold as i32, g.gold_cap() as i32), TXT, &mut x);
gfx.text("Mana", x, y, S, MANA);
x += gfx.measure("Mana", S) + CW * 0.5;
bar(x, y + 3.0, 70.0, 10.0, g.mana / MANA_MAX, MANA);
x += 70.0 + CW * 0.5;
gfx.text(&format!("{}", g.mana as i32), x, y, S, TXT);
x += CW * 5.0;
gfx.text("Kern", x, y, S, RED);
x += gfx.measure("Kern", S) + CW * 0.5;
bar(x, y + 3.0, 70.0, 10.0, g.core_hp / CORE_HP, RED);
x += 70.0 + CW * 1.5;
gfx.text("Tor", x, y, S, BLUE);
x += gfx.measure("Tor", S) + CW * 0.5;
bar(x, y + 3.0, 70.0, 10.0, g.gate_hp / GATE_HP, BLUE);
x += 70.0 + CW * 1.5;
let cap = g.counts[1].min(30);
seg("Kreaturen", TXT, &format!("{}/{}", g.creature_count(), cap), TXT, &mut x);
seg("Wichte", TXT, &format!("{}", g.worker_count()), TXT, &mut x);
let wave_col = if g.wave_timer < 30.0 { RED } else { TXT };
seg(&format!("Welle {}", g.wave + 1), wave_col, &mmss(g.wave_timer), wave_col, &mut x);
seg("Armee", TXT, &format!("{}", g.army_value() as i32), TXT, &mut x);
let vol = if snd.muted { "aus".to_string() } else { format!("{}%", (snd.volume * 100.0).round() as i32) };
seg("♪", DIM, &vol, DIM, &mut x);
// ---------------- Werkzeugleiste
let tb_rows = 5;
let tb_y = sh - tb_rows as f32 * CH;
gfx.window(0.0, tb_y, cols, tb_rows, S, false, FRAME, BG);
self.rects.push(Rect::new(0.0, tb_y, sw, tb_rows as f32 * CH));
self.tool_rects.clear();
let bw = 80.0;
let mut hover_tool = None;
for (i, &t) in TOOLS.iter().enumerate() {
let bx = CW + i as f32 * (bw + 4.0);
let by = tb_y + CH * 0.75;
let r = Rect::new(bx, by, bw, CH * 3.5);
self.tool_rects.push(r);
let hovered = r.contains(vec2(mx, my));
if hovered {
hover_tool = Some(i);
}
let sel = self.tool == t;
draw_rectangle(r.x, r.y, r.w, r.h, if sel { Color::new(0.35, 0.22, 0.1, 1.0) } else 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, if sel { FRAME_HI } else { Color::new(0.35, 0.28, 0.2, 1.0) });
let ti = tool_info(t);
gfx.draw_cell(ti.icon, bx + (bw - 32.0) * 0.5, by + 4.0, 32.0, 32.0, WHITE);
gfx.text(TOOL_KEYS[i], bx + 3.0, by + 3.0, 1.0, if sel { FRAME_HI } else { DIM });
let lw = gfx.measure(ti.label, 1.0);
gfx.text(ti.label, bx + (bw - lw) * 0.5, by + 42.0, 1.0, if sel { FRAME_HI } else { TXT });
}
// Hand
let hx = CW + TOOLS.len() as f32 * (bw + 4.0) + CW;
if hx + 120.0 < sw - 200.0 {
gfx.text("Hand", hx, tb_y + CH, S, TXT);
for (k, id) in g.hand.iter().enumerate() {
if let Some(e) = g.ents.iter().find(|e| e.id == *id) {
let sx = hx + (k % 4) as f32 * 26.0;
let sy = tb_y + CH * 2.2 + (k / 4) as f32 * 24.0;
draw_texture_ex(
&gfx.sprites,
sx,
sy,
WHITE,
DrawTextureParams { dest_size: Some(vec2(20.0, 20.0)), source: Some(Gfx::sprite_rect(e.kind, false)), ..Default::default() },
);
}
}
if g.hand.is_empty() {
gfx.text("leer", hx, tb_y + CH * 2.5, 1.0, DIM);
}
}
// ---------------- Minikarte
let mm_cols = 16;
let mm_rows = 12;
let mm_w = mm_cols as f32 * CW;
let mm_h = mm_rows as f32 * CH;
let mm_x = sw - mm_w;
let mm_y = tb_y - mm_h;
gfx.window_titled(mm_x, mm_y, mm_cols, mm_rows, S, false, FRAME, BG, "Karte", FRAME_HI);
self.rects.push(Rect::new(mm_x, mm_y, mm_w, mm_h));
if now - self.minimap_t > 0.25 {
self.minimap_t = now;
for (i, t) in g.map.t.iter().enumerate() {
let (x, y) = xy(i);
let mut c = minimap_color(t);
if t.marked {
c = [255, 230, 90, 255];
}
self.minimap_img.set_pixel(x as u32, y as u32, Color::from_rgba(c[0], c[1], c[2], c[3]));
}
self.minimap_tex.update(&self.minimap_img);
}
let map_px = (mm_w - CW * 2.0).min(mm_h - CH * 2.0);
let mrx = mm_x + (mm_w - map_px) * 0.5;
let mry = mm_y + CH + (mm_h - CH * 2.0 - map_px) * 0.5;
self.minimap_rect = Rect::new(mrx, mry, map_px, map_px);
draw_texture_ex(&self.minimap_tex, mrx, mry, WHITE, DrawTextureParams { dest_size: Some(vec2(map_px, map_px)), ..Default::default() });
let ms = map_px / MW as f32;
for e in &g.ents {
if e.alive && !e.held {
let c = if e.hero { Color::new(0.6, 0.85, 1.0, 1.0) } else if e.kind == EK::Worker { Color::new(1.0, 0.6, 0.3, 1.0) } else { Color::new(1.0, 0.25, 0.25, 1.0) };
draw_rectangle(mrx + e.x * ms - 1.0, mry + e.y * ms - 1.0, 3.0, 3.0, c);
}
}
if let Some(f) = g.flag {
let (fx, fy) = xy(f);
draw_rectangle(mrx + fx as f32 * ms - 2.0, mry + fy as f32 * ms - 2.0, 5.0, 5.0, RED);
}
// Kamerarahmen
let vw = sw / view.zoom * ms;
let vh = sh / view.zoom * ms;
draw_rectangle_lines(mrx + view.cam_x * ms, mry + view.cam_y * ms, vw, vh, 1.5, WHITE);
// ---------------- Infofeld
if !info.is_empty() {
let icols = 30;
let mut lines: Vec<(String, Color)> = Vec::new();
for (s, c) in info.iter().skip(1) {
for l in wrap(s, icols as usize - 4) {
lines.push((l, *c));
}
}
let irows = lines.len() as i32 + 2;
let ix = sw - icols as f32 * CW;
let iy = 3.0 * CH;
gfx.window_titled(ix, iy, icols, irows, S, false, FRAME, BG, &info[0].0, info[0].1);
for (k, (l, c)) in lines.iter().enumerate() {
gfx.text(l, ix + CW * 2.0, iy + CH * (k as f32 + 1.0), S, *c);
}
}
// ---------------- Meldungen
let recent: Vec<&(String, 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();
for (s, t) in recent.iter().rev().take(5).rev() {
let a = (1.0 - ((now - t) as f32 - 11.0) / 3.0).clamp(0.2, 1.0);
for l in wrap(s, mcols as usize - 4) {
lines.push((l, a));
}
}
let mrows = lines.len() as i32 + 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));
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));
}
}
if self.log.len() > 100 {
self.log.drain(0..50);
}
// ---------------- Tooltip
if let Some(i) = hover_tool {
let t = TOOLS[i];
let ti = tool_info(t);
let tcols = 34;
let mut lines: Vec<(String, Color)> = wrap(ti.desc, tcols - 4).into_iter().map(|l| (l, TXT)).collect();
let cost = tool_cost(t, g);
if !cost.is_empty() {
lines.push((format!("Kosten: {}", cost), GOLD));
}
lines.push((format!("Taste: {}", TOOL_KEYS[i]), DIM));
let rows = lines.len() as i32 + 2;
let tx = (mx - tcols as f32 * CW * 0.5).clamp(0.0, sw - tcols as f32 * CW);
let ty = tb_y - rows as f32 * CH - 4.0;
gfx.window_titled(tx, ty, tcols as i32, rows, S, false, FRAME_HI, BG, ti.name, FRAME_HI);
for (k, (l, c)) in lines.iter().enumerate() {
gfx.text(l, tx + CW * 2.0, ty + CH * (k as f32 + 1.0), S, *c);
}
}
// ---------------- Dialoge
if g.state != 0 {
self.draw_end(g, gfx);
} else if self.show_help {
self.draw_help(gfx);
} else if g.paused {
let w = 24;
let x = (sw - w as f32 * CW) * 0.5;
let y = sh * 0.35;
gfx.window(x, y, w, 5, S, true, FRAME_HI, BG);
let t = "PAUSE";
gfx.text(t, (sw - gfx.measure(t, 3.0)) * 0.5, y + CH, 3.0, FRAME_HI);
let t2 = "P: weiter";
gfx.text(t2, (sw - gfx.measure(t2, S)) * 0.5, y + CH * 3.0, S, DIM);
}
}
fn draw_help(&mut self, gfx: &Gfx) {
let lines: &[(&str, Color)] = &[
("Ziel: Zerstöre das Heldentor und schütze deinen Seelenkern.", GOLD),
("", TXT),
("Linksklick Werkzeug benutzen (Graben/Räume: Rechteck ziehen)", TXT),
("Rechtsklick Einheit aus der Hand absetzen", TXT),
("Rechts ziehen Karte verschieben (auch WASD/Pfeile, Bildrand)", TXT),
("Mausrad Zoomen", TXT),
("1-9, 0, Q, E Werkzeug wählen", TXT),
("P / Leertaste Pause M Ton an/aus +/- Lautstärke", TXT),
("H Diese Hilfe", TXT),
("", TXT),
("1. Grabe zum Portal und lass es von Grubenwichten erobern.", TXT),
("2. Baue Quartier, Pilzgarten und Schatzkammer.", TXT),
("3. Trainiere deine Kreaturen, zahle ihren Lohn pünktlich.", TXT),
("4. Wehre die Heldenwellen ab. Sie passen sich deiner Armee an.", TXT),
("5. Setze die Angriffsflagge nahe ans Tor und greife an!", TXT),
("", TXT),
("Schrift: DINOBYTE von mby (CC BY 4.0)", DIM),
("", TXT),
("Klick zum Starten", FRAME_HI),
];
let cols = 74;
let rows = lines.len() as i32 + 4;
let sw = screen_width();
let sh = screen_height();
let x = ((sw - cols as f32 * CW) * 0.5).max(0.0);
let y = ((sh - rows as f32 * CH) * 0.5).max(0.0);
gfx.window_titled(x, y, cols, rows, S, true, FRAME_HI, Color::new(0.05, 0.03, 0.04, 0.97), "DUNGEONCRAFT", FRAME_HI);
for (k, (l, c)) in lines.iter().enumerate() {
gfx.text(l, x + CW * 3.0, y + CH * (k as f32 + 2.0), S, *c);
}
self.rects.push(Rect::new(0.0, 0.0, sw, sh));
}
fn draw_end(&mut self, g: &Game, gfx: &Gfx) {
let sw = screen_width();
let sh = screen_height();
draw_rectangle(0.0, 0.0, sw, sh, Color::new(0.0, 0.0, 0.0, 0.5));
let win = g.state == 1;
let cols = 44;
let rows = 11;
let x = (sw - cols as f32 * CW) * 0.5;
let y = (sh - rows as f32 * CH) * 0.5;
let col = if win { GOLD } else { RED };
gfx.window(x, y, cols, rows, S, true, col, Color::new(0.05, 0.03, 0.04, 0.97));
let title = if win { "SIEG!" } else { "NIEDERLAGE" };
gfx.text(title, (sw - gfx.measure(title, 4.0)) * 0.5, y + CH * 1.5, 4.0, col);
let sub = if win { "Das Heldentor ist gefallen." } else { "Dein Seelenkern ist zerbrochen." };
gfx.text(sub, (sw - gfx.measure(sub, S)) * 0.5, y + CH * 4.5, S, TXT);
let st = format!("Spielzeit {} Wellen {} Armee {}", mmss(g.time), g.wave, g.army_value() as i32);
gfx.text(&st, (sw - gfx.measure(&st, S)) * 0.5, y + CH * 6.0, S, DIM);
let b = " Neues Spiel (N) ";
let bw = gfx.measure(b, S) + CW * 2.0;
let bx = (sw - bw) * 0.5;
let by = y + CH * 7.75;
self.end_button = Rect::new(bx, by, bw, CH * 2.0);
let (mx, my) = mouse_position();
let hov = self.end_button.contains(vec2(mx, my));
draw_rectangle(bx, by, bw, CH * 2.0, 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(bx, by, bw, CH * 2.0, 2.0, col);
gfx.text(b, bx + CW, by + CH * 0.5, S, TXT);
self.rects.push(Rect::new(0.0, 0.0, sw, sh));
}
}
// Infotext für Einheit oder Kachel unter dem Mauszeiger (erste Zeile = Titel)
pub fn hover_info(g: &Game, wx: f32, wy: f32) -> Vec<(String, Color)> {
let mut out = Vec::new();
if let Some(j) = g.ent_at(wx, wy, false) {
let e = &g.ents[j];
let s = st(e.kind);
out.push((format!("{} (Stufe {})", s.name, e.level), if e.hero { BLUE } else { FRAME_HI }));
out.push((format!("HP {}/{}", e.hp.max(0.0) as i32, e.max_hp as i32), GREEN));
out.push((job_name(e).to_string(), TXT));
if e.hero {
out.push((if e.guard { "Torwächter".into() } else { "Eindringling".into() }, BLUE));
} else if e.creature() {
let pct = |v: f32| (v.clamp(0.0, 1.0) * 100.0).round() as i32;
out.push((format!("Energie {}% Hunger {}%", pct(e.energy), pct(e.hunger / 2.0)), TXT));
let mood = if e.happy > 0.6 {
("zufrieden", GREEN)
} else if e.happy > 0.25 {
("mürrisch", GOLD)
} else {
("wütend", RED)
};
out.push((format!("Laune: {}", mood.0), mood.1));
out.push((format!("Lohn: {} Gold", (s.wage as f32 * (1.0 + 0.1 * (e.level - 1) as f32)) as i32), GOLD));
} else if e.carry > 0 {
out.push((format!("Trägt {} Gold", e.carry), GOLD));
}
return out;
}
let (x, y) = (wx.floor() as i32, wy.floor() as i32);
let Some(t) = g.map.get(x, y) else { return out };
let owner = match t.owner {
Owner::Player => "erobert",
Owner::Hero => "Heldengebiet",
Owner::None => "neutral",
};
match t.kind {
Kind::Rock => out.push(("Fels".into(), DIM)),
Kind::Earth => out.push(("Erde".into(), FRAME_HI)),
Kind::Gold => {
out.push(("Goldader".into(), GOLD));
out.push((format!("{} Gold", t.gold), GOLD));
}
Kind::Floor => {
out.push(("Boden".into(), TXT));
out.push((owner.into(), DIM));
}
Kind::Core => {
out.push(("Seelenkern".into(), RED));
out.push((format!("HP {}/{}", g.core_hp as i32, CORE_HP as i32), RED));
}
Kind::Portal => {
out.push(("Portal".into(), MANA));
out.push((if g.portal_owned() { "erobert".into() } else { "neutral: Wichte können es erobern".into() }, DIM));
}
Kind::Gate => {
out.push(("Heldentor".into(), BLUE));
out.push((format!("HP {}/{}", g.gate_hp as i32, GATE_HP as i32), BLUE));
}
k => {
if let Some(r) = room_index(k) {
out.push((ROOM_NAMES[r].into(), FRAME_HI));
match k {
Kind::Farm => out.push((format!("{} Pilze", t.food as i32), GREEN)),
Kind::Lair => out.push((if t.bed != 0 { "Schlafplatz belegt".into() } else { "Schlafplatz frei".into() }, DIM)),
_ => {}
}
}
}
}
if t.kind == Kind::Rock {
out.push(("unzerstörbar".into(), DIM));
}
if t.marked {
out.push(("zum Graben markiert".into(), GOLD));
}
out
}