Switch repo to English, add i18n with EN/DE language files and first-start language selection
- in-game texts moved to lang/en.lang and lang/de.lang (key = text, placeholders) - game logic emits message keys + args, translated at display time - localStorage access via own miniquad JS plugin (www/dc_plugin.js) - code comments, DESIGN.md, CREDITS.md translated; README.md added Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
a101f6ddc4
commit
fbf6f223fc
18 files changed
+1072
-362
No files matched your search
@@ -1,15 +1,17 @@
|
||||
// Benutzeroberfläche im Canvas: CP437-Fenster mit DINOBYTE-Schrift.
|
||||
// In-canvas user interface: CP437 windows with the DINOBYTE bitmap font.
|
||||
// All texts come from the language files (see i18n.rs).
|
||||
|
||||
use crate::game::*;
|
||||
use crate::gfx::*;
|
||||
use crate::i18n::{languages, tr, trf};
|
||||
use crate::map::*;
|
||||
use crate::render3d::{minimap_color, Cam};
|
||||
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 S: f32 = 2.0; // UI font scale
|
||||
pub const CW: f32 = GW * S; // character width in px
|
||||
pub const CH: f32 = GH * S; // character height 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);
|
||||
@@ -52,37 +54,40 @@ pub const TOOLS: [Tool; 12] = [
|
||||
pub const TOOL_KEYS: [&str; 12] = ["1", "2", "3", "4", "5", "6", "7", "8", "9", "0", "X", "V"];
|
||||
|
||||
pub struct ToolInfo {
|
||||
pub label: &'static str,
|
||||
pub name: &'static str,
|
||||
pub desc: &'static str,
|
||||
pub label: String,
|
||||
pub name: String,
|
||||
pub desc: String,
|
||||
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),
|
||||
}
|
||||
let (id, icon, room) = match t {
|
||||
Tool::Dig => ("dig", I_PICK, None),
|
||||
Tool::Hand => ("hand", I_HAND, None),
|
||||
Tool::Room(0) => ("treasury", T_TREASURY, Some(0)),
|
||||
Tool::Room(1) => ("lair", T_LAIR, Some(1)),
|
||||
Tool::Room(2) => ("farm", T_FARM, Some(2)),
|
||||
Tool::Room(3) => ("training", T_TRAINING, Some(3)),
|
||||
Tool::Room(_) => ("library", T_LIBRARY, Some(4)),
|
||||
Tool::Sell => ("sell", I_SELL, None),
|
||||
Tool::Flag => ("flag", I_FLAG, None),
|
||||
Tool::Lightning => ("lightning", I_BOLT, None),
|
||||
Tool::Heal => ("heal", I_HEAL, None),
|
||||
Tool::Summon => ("summon", I_SUMMON, None),
|
||||
};
|
||||
let name = match room {
|
||||
Some(r) => tr(ROOM_NAMES[r]),
|
||||
None => tr(&format!("tool.{}.name", id)),
|
||||
};
|
||||
ToolInfo { label: tr(&format!("tool.{}.label", id)), name, desc: tr(&format!("tool.{}.desc", id)), icon }
|
||||
}
|
||||
|
||||
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),
|
||||
Tool::Room(r) => trf("cost.gold_tile", &[ROOM_COST[r].to_string()]),
|
||||
Tool::Lightning => trf("cost.mana", &["50".into()]),
|
||||
Tool::Heal => trf("cost.mana", &["60".into()]),
|
||||
Tool::Summon => trf("cost.mana", &[(g.summon_cost() as i32).to_string()]),
|
||||
_ => String::new(),
|
||||
}
|
||||
}
|
||||
@@ -93,13 +98,15 @@ pub enum UiAction {
|
||||
MinimapJump(f32, f32),
|
||||
CloseHelp,
|
||||
NewGame,
|
||||
SelectLanguage(String),
|
||||
Consumed,
|
||||
}
|
||||
|
||||
pub struct Ui {
|
||||
pub tool: Tool,
|
||||
pub show_help: bool,
|
||||
pub log: Vec<(String, f64)>,
|
||||
pub show_langsel: bool,
|
||||
pub log: Vec<(Msg, f64)>,
|
||||
pub rects: Vec<Rect>,
|
||||
minimap_img: Image,
|
||||
minimap_tex: Texture2D,
|
||||
@@ -107,6 +114,7 @@ pub struct Ui {
|
||||
minimap_rect: Rect,
|
||||
tool_rects: Vec<Rect>,
|
||||
end_button: Rect,
|
||||
lang_rects: Vec<(Rect, String)>,
|
||||
}
|
||||
|
||||
fn wrap(s: &str, cols: usize) -> Vec<String> {
|
||||
@@ -148,6 +156,7 @@ impl Ui {
|
||||
Ui {
|
||||
tool: Tool::Dig,
|
||||
show_help: true,
|
||||
show_langsel: false,
|
||||
log: Vec::new(),
|
||||
rects: Vec::new(),
|
||||
minimap_img: img,
|
||||
@@ -156,11 +165,12 @@ impl Ui {
|
||||
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),
|
||||
lang_rects: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn over_ui(&self, x: f32, y: f32) -> bool {
|
||||
self.show_help || self.rects.iter().any(|r| r.contains(vec2(x, y)))
|
||||
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 {
|
||||
@@ -174,6 +184,14 @@ impl Ui {
|
||||
|
||||
pub fn click(&mut self, x: f32, y: f32, g: &Game) -> UiAction {
|
||||
let p = vec2(x, y);
|
||||
if self.show_langsel {
|
||||
for (r, code) in &self.lang_rects {
|
||||
if r.contains(p) {
|
||||
return UiAction::SelectLanguage(code.clone());
|
||||
}
|
||||
}
|
||||
return UiAction::Consumed;
|
||||
}
|
||||
if g.state != 0 {
|
||||
if self.end_button.contains(p) {
|
||||
return UiAction::NewGame;
|
||||
@@ -204,7 +222,7 @@ impl Ui {
|
||||
let sh = screen_height();
|
||||
let (mx, my) = mouse_position();
|
||||
|
||||
// ---------------- obere Leiste
|
||||
// ---------------- top bar
|
||||
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));
|
||||
@@ -216,31 +234,34 @@ impl Ui {
|
||||
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;
|
||||
seg(&tr("hud.gold"), GOLD, &format!("{}/{}", g.gold as i32, g.gold_cap() as i32), TXT, &mut x);
|
||||
let mana = tr("hud.mana");
|
||||
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;
|
||||
let core = tr("hud.core");
|
||||
gfx.text(&core, x, y, S, RED);
|
||||
x += gfx.measure(&core, 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;
|
||||
let gate = tr("hud.gate");
|
||||
gfx.text(&gate, x, y, S, BLUE);
|
||||
x += gfx.measure(&gate, 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);
|
||||
seg(&tr("hud.creatures"), TXT, &format!("{}/{}", g.creature_count(), cap), TXT, &mut x);
|
||||
seg(&tr("hud.workers"), 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(&trf("hud.wave", &[(g.wave + 1).to_string()]), wave_col, &mmss(g.wave_timer), wave_col, &mut x);
|
||||
seg(&tr("hud.army"), TXT, &format!("{}", g.army_value() as i32), TXT, &mut x);
|
||||
let vol = if snd.muted { tr("hud.off") } else { format!("{}%", (snd.volume * 100.0).round() as i32) };
|
||||
seg("♪", DIM, &vol, DIM, &mut x);
|
||||
|
||||
// ---------------- Werkzeugleiste
|
||||
// ---------------- toolbar
|
||||
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);
|
||||
@@ -263,13 +284,13 @@ impl Ui {
|
||||
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 });
|
||||
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
|
||||
// hand contents
|
||||
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);
|
||||
gfx.text(&tr("hud.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;
|
||||
@@ -284,18 +305,18 @@ impl Ui {
|
||||
}
|
||||
}
|
||||
if g.hand.is_empty() {
|
||||
gfx.text("leer", hx, tb_y + CH * 2.5, 1.0, DIM);
|
||||
gfx.text(&tr("hud.empty"), hx, tb_y + CH * 2.5, 1.0, DIM);
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------- Minikarte
|
||||
// ---------------- minimap
|
||||
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);
|
||||
gfx.window_titled(mm_x, mm_y, mm_cols, mm_rows, S, false, FRAME, BG, &tr("ui.map"), 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;
|
||||
@@ -335,7 +356,7 @@ impl Ui {
|
||||
draw_line(pa.x, pa.y, pb.x, pb.y, 1.5, WHITE);
|
||||
}
|
||||
|
||||
// ---------------- Infofeld
|
||||
// ---------------- info panel
|
||||
if !info.is_empty() {
|
||||
let icols = 30;
|
||||
let mut lines: Vec<(String, Color)> = Vec::new();
|
||||
@@ -353,14 +374,14 @@ impl Ui {
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------- Meldungen
|
||||
let recent: Vec<&(String, f64)> = self.log.iter().filter(|(_, t)| now - t < 14.0).collect();
|
||||
// ---------------- 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();
|
||||
for (s, t) in recent.iter().rev().take(5).rev() {
|
||||
for (m, 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) {
|
||||
for l in wrap(&trf(m.key, &m.args), mcols as usize - 4) {
|
||||
lines.push((l, a));
|
||||
}
|
||||
}
|
||||
@@ -381,23 +402,25 @@ impl Ui {
|
||||
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 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((trf("ui.cost", &[cost]), GOLD));
|
||||
}
|
||||
lines.push((format!("Taste: {}", TOOL_KEYS[i]), DIM));
|
||||
lines.push((trf("ui.key", &[TOOL_KEYS[i].to_string()]), 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);
|
||||
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 {
|
||||
// ---------------- dialogs
|
||||
if self.show_langsel {
|
||||
self.draw_langsel(gfx);
|
||||
} else if g.state != 0 {
|
||||
self.draw_end(g, gfx);
|
||||
} else if self.show_help {
|
||||
self.draw_help(gfx);
|
||||
@@ -406,36 +429,37 @@ impl Ui {
|
||||
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);
|
||||
let t = tr("ui.pause");
|
||||
gfx.text(&t, (sw - gfx.measure(&t, 3.0)) * 0.5, y + CH, 3.0, FRAME_HI);
|
||||
let t2 = tr("ui.pause_hint");
|
||||
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),
|
||||
let keys: &[(&str, Color)] = &[
|
||||
("help.goal", 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),
|
||||
("Mitte ziehen Kamera drehen und neigen (auch Q/E, R/F)", TXT),
|
||||
("Mausrad Zoomen", TXT),
|
||||
("1-9, 0, X, V Werkzeug wählen", TXT),
|
||||
("P / Leertaste Pause M Ton an/aus +/- Lautstärke", TXT),
|
||||
("G Kachel-Umrisse an/aus H Diese Hilfe", TXT),
|
||||
("help.k_left", TXT),
|
||||
("help.k_right", TXT),
|
||||
("help.k_pan", TXT),
|
||||
("help.k_rot", TXT),
|
||||
("help.k_zoom", TXT),
|
||||
("help.k_tools", TXT),
|
||||
("help.k_misc", TXT),
|
||||
("help.k_misc2", 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),
|
||||
("help.s1", TXT),
|
||||
("help.s2", TXT),
|
||||
("help.s3", TXT),
|
||||
("help.s4", TXT),
|
||||
("help.s5", TXT),
|
||||
("", TXT),
|
||||
("Schrift: DINOBYTE von mby (CC BY 4.0)", DIM),
|
||||
("help.font", DIM),
|
||||
("", TXT),
|
||||
("Klick zum Starten", FRAME_HI),
|
||||
("help.start", FRAME_HI),
|
||||
];
|
||||
let lines: Vec<(String, Color)> = keys.iter().map(|(k, c)| (if k.is_empty() { String::new() } else { tr(k) }, *c)).collect();
|
||||
let cols = 74;
|
||||
let rows = lines.len() as i32 + 4;
|
||||
let sw = screen_width();
|
||||
@@ -449,6 +473,40 @@ impl Ui {
|
||||
self.rects.push(Rect::new(0.0, 0.0, sw, sh));
|
||||
}
|
||||
|
||||
// First start: choose the language (names shown in their own language)
|
||||
fn draw_langsel(&mut self, 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.6));
|
||||
let langs = languages();
|
||||
let cols = 50;
|
||||
let rows = 6 + langs.len() as i32 * 3;
|
||||
let x = ((sw - cols as f32 * CW) * 0.5).max(0.0);
|
||||
let y = ((sh - rows as f32 * CH) * 0.5).max(0.0);
|
||||
// the title shows all language names, since no language is chosen yet
|
||||
let title = langs.iter().map(|(c, _)| crate::i18n::tr_in(c, "langsel.title")).collect::<Vec<_>>().join(" / ");
|
||||
gfx.window_titled(x, y, cols, rows, S, true, FRAME_HI, Color::new(0.05, 0.03, 0.04, 0.97), &title, FRAME_HI);
|
||||
self.lang_rects.clear();
|
||||
let (mx, my) = mouse_position();
|
||||
for (k, (code, name)) in langs.iter().enumerate() {
|
||||
let bw = 24.0 * CW;
|
||||
let bx = (sw - bw) * 0.5;
|
||||
let by = y + CH * (2.0 + k as f32 * 3.0);
|
||||
let r = Rect::new(bx, by, bw, CH * 2.0);
|
||||
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, if hov { FRAME_HI } else { FRAME });
|
||||
gfx.text(name, bx + (bw - gfx.measure(name, S)) * 0.5, by + CH * 0.5, S, TXT);
|
||||
self.lang_rects.push((r, code.clone()));
|
||||
}
|
||||
let hint_y = y + CH * (2.0 + langs.len() as f32 * 3.0 + 0.5);
|
||||
for (k, (c, _)) in langs.iter().enumerate() {
|
||||
let h = crate::i18n::tr_in(c, "langsel.hint");
|
||||
gfx.text(&h, x + CW * 2.0, hint_y + CH * k as f32, 1.0, DIM);
|
||||
}
|
||||
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();
|
||||
@@ -460,13 +518,14 @@ impl Ui {
|
||||
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);
|
||||
let title = tr(if win { "end.victory" } else { "end.defeat" });
|
||||
gfx.text(&title, (sw - gfx.measure(&title, 4.0)) * 0.5, y + CH * 1.5, 4.0, col);
|
||||
let sub = tr(if win { "end.victory_sub" } else { "end.defeat_sub" });
|
||||
gfx.text(&sub, (sw - gfx.measure(&sub, S)) * 0.5, y + CH * 4.5, S, TXT);
|
||||
let st = trf("end.stats", &[mmss(g.time), g.wave.to_string(), (g.army_value() as i32).to_string()]);
|
||||
gfx.text(&st, (sw - gfx.measure(&st, S)) * 0.5, y + CH * 6.0, S, DIM);
|
||||
let b = " Neues Spiel (N) ";
|
||||
let b = format!(" {} ", tr("end.new"));
|
||||
let b = b.as_str();
|
||||
let bw = gfx.measure(b, S) + CW * 2.0;
|
||||
let bx = (sw - bw) * 0.5;
|
||||
let by = y + CH * 7.75;
|
||||
@@ -480,80 +539,81 @@ impl Ui {
|
||||
}
|
||||
}
|
||||
|
||||
// Infotext für Einheit oder Kachel unter dem Mauszeiger (erste Zeile = Titel)
|
||||
// Info text for the unit or tile under the mouse (first line = title)
|
||||
pub fn hover_info(g: &Game, ent: Option<usize>, tile: Option<(i32, i32)>) -> Vec<(String, Color)> {
|
||||
let mut out = Vec::new();
|
||||
if let Some(j) = ent {
|
||||
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));
|
||||
out.push((trf("info.level", &[tr(s.name), e.level.to_string()]), if e.hero { BLUE } else { FRAME_HI }));
|
||||
out.push((trf("info.hp", &[(e.hp.max(0.0) as i32).to_string(), (e.max_hp as i32).to_string()]), GREEN));
|
||||
out.push((tr(job_name(e)), TXT));
|
||||
if e.hero {
|
||||
out.push((if e.guard { "Torwächter".into() } else { "Eindringling".into() }, BLUE));
|
||||
out.push((tr(if e.guard { "info.guard" } else { "info.intruder" }), 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 pct = |v: f32| ((v.clamp(0.0, 1.0) * 100.0).round() as i32).to_string();
|
||||
out.push((trf("info.needs", &[pct(e.energy), pct(e.hunger / 2.0)]), TXT));
|
||||
let mood = if e.happy > 0.6 {
|
||||
("zufrieden", GREEN)
|
||||
("mood.happy", GREEN)
|
||||
} else if e.happy > 0.25 {
|
||||
("mürrisch", GOLD)
|
||||
("mood.grumpy", GOLD)
|
||||
} else {
|
||||
("wütend", RED)
|
||||
("mood.angry", 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));
|
||||
out.push((trf("info.mood", &[tr(mood.0)]), mood.1));
|
||||
let wage = (s.wage as f32 * (1.0 + 0.1 * (e.level - 1) as f32)) as i32;
|
||||
out.push((trf("info.wage", &[wage.to_string()]), GOLD));
|
||||
} else if e.carry > 0 {
|
||||
out.push((format!("Trägt {} Gold", e.carry), GOLD));
|
||||
out.push((trf("info.carries", &[e.carry.to_string()]), GOLD));
|
||||
}
|
||||
return out;
|
||||
}
|
||||
let Some((x, y)) = tile else { return out };
|
||||
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",
|
||||
Owner::Player => "owner.player",
|
||||
Owner::Hero => "owner.hero",
|
||||
Owner::None => "owner.none",
|
||||
};
|
||||
match t.kind {
|
||||
Kind::Rock => out.push(("Fels".into(), DIM)),
|
||||
Kind::Earth => out.push(("Erde".into(), FRAME_HI)),
|
||||
Kind::Rock => out.push((tr("tile.rock"), DIM)),
|
||||
Kind::Earth => out.push((tr("tile.earth"), FRAME_HI)),
|
||||
Kind::Gold => {
|
||||
out.push(("Goldader".into(), GOLD));
|
||||
out.push((format!("{} Gold", t.gold), GOLD));
|
||||
out.push((tr("tile.gold"), GOLD));
|
||||
out.push((trf("tile.gold_amount", &[t.gold.to_string()]), GOLD));
|
||||
}
|
||||
Kind::Floor => {
|
||||
out.push(("Boden".into(), TXT));
|
||||
out.push((owner.into(), DIM));
|
||||
out.push((tr("tile.floor"), TXT));
|
||||
out.push((tr(owner), DIM));
|
||||
}
|
||||
Kind::Core => {
|
||||
out.push(("Seelenkern".into(), RED));
|
||||
out.push((format!("HP {}/{}", g.core_hp as i32, CORE_HP as i32), RED));
|
||||
out.push((tr("tile.core"), RED));
|
||||
out.push((trf("info.hp", &[(g.core_hp as i32).to_string(), (CORE_HP as i32).to_string()]), 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));
|
||||
out.push((tr("tile.portal"), MANA));
|
||||
out.push((tr(if g.portal_owned() { "portal.claimed" } else { "portal.neutral" }), DIM));
|
||||
}
|
||||
Kind::Gate => {
|
||||
out.push(("Heldentor".into(), BLUE));
|
||||
out.push((format!("HP {}/{}", g.gate_hp as i32, GATE_HP as i32), BLUE));
|
||||
out.push((tr("tile.gate"), BLUE));
|
||||
out.push((trf("info.hp", &[(g.gate_hp as i32).to_string(), (GATE_HP as i32).to_string()]), BLUE));
|
||||
}
|
||||
k => {
|
||||
if let Some(r) = room_index(k) {
|
||||
out.push((ROOM_NAMES[r].into(), FRAME_HI));
|
||||
out.push((tr(ROOM_NAMES[r]), 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)),
|
||||
Kind::Farm => out.push((trf("farm.food", &[(t.food as i32).to_string()]), GREEN)),
|
||||
Kind::Lair => out.push((tr(if t.bed != 0 { "lair.taken" } else { "lair.free" }), DIM)),
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if t.kind == Kind::Rock {
|
||||
out.push(("unzerstörbar".into(), DIM));
|
||||
out.push((tr("tile.indestructible"), DIM));
|
||||
}
|
||||
if t.marked {
|
||||
out.push(("zum Graben markiert".into(), GOLD));
|
||||
out.push((tr("tile.marked"), GOLD));
|
||||
}
|
||||
out
|
||||
}
|
||||
Reference in new issue
Block a user