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:
1 parent
14e481cffb
commit
db417b3af8
13 files changed
+1930
-19
No files matched your search
@@ -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
|
||||
}
|
||||
Reference in new issue
Block a user