Umstellung auf macroquad: endlose Wellen, Heldentor mit Verteidigern, Angriffsflagge, Sound-Events, Grafikmodul mit DINOBYTE-Bitmap-Font (CC BY 4.0) und CP437-Rahmen
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
ec9cab9453
commit
14e481cffb
7 files changed
+1182
-45
No files matched your search
+407
-39
@@ -2,11 +2,65 @@
|
||||
|
||||
use crate::map::*;
|
||||
|
||||
pub const WAVES: u32 = 6;
|
||||
pub const CORE_HP: f32 = 2000.0;
|
||||
pub const GATE_HP: f32 = 3000.0;
|
||||
pub const PAYDAY: f32 = 150.0;
|
||||
pub const FIRST_WAVE: f32 = 300.0;
|
||||
pub const WAVE_GAP: f32 = 210.0;
|
||||
pub const WAVE_GAP: f32 = 180.0;
|
||||
pub const MAX_SQUAD: usize = 18;
|
||||
|
||||
// Sound-Ereignisse. Die Spiellogik meldet sie, das Sound-Modul spielt sie ab.
|
||||
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
|
||||
pub enum Sfx {
|
||||
Dig,
|
||||
GoldDig,
|
||||
DigDone,
|
||||
Claim,
|
||||
Deposit,
|
||||
Build,
|
||||
Sell,
|
||||
MeleeHit,
|
||||
Shoot,
|
||||
Death,
|
||||
HeroDeath,
|
||||
Lightning,
|
||||
Heal,
|
||||
Summon,
|
||||
PortalArrive,
|
||||
WaveAlarm,
|
||||
WaveStart,
|
||||
CoreHit,
|
||||
GateHit,
|
||||
GateDefenders,
|
||||
LevelUp,
|
||||
Pickup,
|
||||
Drop,
|
||||
Payday,
|
||||
FlagSet,
|
||||
Victory,
|
||||
Defeat,
|
||||
Click,
|
||||
Error,
|
||||
}
|
||||
|
||||
pub const SFX_ALL: [Sfx; 29] = [
|
||||
Sfx::Dig, Sfx::GoldDig, Sfx::DigDone, Sfx::Claim, Sfx::Deposit, Sfx::Build, Sfx::Sell, Sfx::MeleeHit,
|
||||
Sfx::Shoot, Sfx::Death, Sfx::HeroDeath, Sfx::Lightning, Sfx::Heal, Sfx::Summon, Sfx::PortalArrive,
|
||||
Sfx::WaveAlarm, Sfx::WaveStart, Sfx::CoreHit, Sfx::GateHit, Sfx::GateDefenders, Sfx::LevelUp,
|
||||
Sfx::Pickup, Sfx::Drop, Sfx::Payday, Sfx::FlagSet, Sfx::Victory, Sfx::Defeat, Sfx::Click, Sfx::Error,
|
||||
];
|
||||
|
||||
impl Sfx {
|
||||
pub fn index(self) -> usize {
|
||||
self as usize
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
pub struct SfxEvent {
|
||||
pub sfx: Sfx,
|
||||
pub pos: Option<(f32, f32)>, // Weltposition (für Lautstärke nach Entfernung), None = UI
|
||||
}
|
||||
pub const MANA_MAX: f32 = 400.0;
|
||||
pub const CARRY_MAX: i32 = 300;
|
||||
|
||||
@@ -65,6 +119,7 @@ pub enum Job {
|
||||
Attack(u32),
|
||||
AttackCore,
|
||||
Leave,
|
||||
Rally,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
@@ -96,6 +151,7 @@ pub struct Ent {
|
||||
pub face: f32,
|
||||
pub moving: bool,
|
||||
pub working: bool,
|
||||
pub guard: bool, // Torverteidiger
|
||||
}
|
||||
|
||||
impl Ent {
|
||||
@@ -128,6 +184,7 @@ impl Ent {
|
||||
face: 1.0,
|
||||
moving: false,
|
||||
working: false,
|
||||
guard: false,
|
||||
}
|
||||
}
|
||||
pub fn tile(&self) -> usize {
|
||||
@@ -173,6 +230,12 @@ pub struct Game {
|
||||
pub paused: bool,
|
||||
pub hand: Vec<u32>,
|
||||
pub counts: [usize; 5], // Schatzkammer, Quartier, Pilzgarten, Trainingshalle, Bibliothek
|
||||
pub gate_hp: f32,
|
||||
pub gate_hit_t: f32,
|
||||
pub gate_stage: u32, // ausgelöste Verteidiger-Schwellen (0..3)
|
||||
pub flag: Option<usize>,
|
||||
pub events: Vec<SfxEvent>,
|
||||
pub last_wave_value: f32,
|
||||
hint_t: [f32; 16],
|
||||
}
|
||||
|
||||
@@ -221,6 +284,12 @@ impl Game {
|
||||
paused: false,
|
||||
hand: Vec::new(),
|
||||
counts: [0; 5],
|
||||
gate_hp: GATE_HP,
|
||||
gate_hit_t: -100.0,
|
||||
gate_stage: 0,
|
||||
flag: None,
|
||||
events: Vec::new(),
|
||||
last_wave_value: 0.0,
|
||||
hint_t: [-1000.0; 16],
|
||||
};
|
||||
let (cx, cy) = g.map.core;
|
||||
@@ -228,9 +297,10 @@ impl Game {
|
||||
g.spawn(EK::Worker, false, cx as f32 - 0.5, cy as f32 + k as f32 * 0.7 + 0.3, 1);
|
||||
}
|
||||
g.recount();
|
||||
g.msg("Willkommen, Herrscher des Unterreichs! Beschütze deinen Seelenkern.".into());
|
||||
g.msg("Willkommen, Herrscher des Unterreichs! Beschütze deinen Seelenkern und zerstöre das Heldentor.".into());
|
||||
g.msg("Markiere Erde und Goldadern mit dem Werkzeug »Graben« (Taste 1). Deine Grubenwichte erledigen den Rest.".into());
|
||||
g.msg("Grabe dich zum Portal durch und erobere es. Kreaturen brauchen ein Quartier und einen Pilzgarten.".into());
|
||||
g.msg("Mit der Angriffsflagge (Taste 9) schickst du deine Kreaturen in den Kampf, auch gegen das Heldentor.".into());
|
||||
g
|
||||
}
|
||||
|
||||
@@ -241,6 +311,34 @@ impl Game {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn sfx(&mut self, sfx: Sfx, x: f32, y: f32) {
|
||||
if self.events.len() < 64 {
|
||||
self.events.push(SfxEvent { sfx, pos: Some((x, y)) });
|
||||
}
|
||||
}
|
||||
|
||||
pub fn sfx_ui(&mut self, sfx: Sfx) {
|
||||
if self.events.len() < 64 {
|
||||
self.events.push(SfxEvent { sfx, pos: None });
|
||||
}
|
||||
}
|
||||
|
||||
pub fn gate_center(&self) -> (f32, f32) {
|
||||
(self.map.gate.0 as f32 + 1.5, self.map.gate.1 as f32 + 1.5)
|
||||
}
|
||||
|
||||
// Armeewert: sqrt(Summe HP * Summe Schaden/s) aller eigenen Kreaturen.
|
||||
pub fn army_value(&self) -> f32 {
|
||||
let (mut h, mut d) = (0.0, 0.0);
|
||||
for e in &self.ents {
|
||||
if e.alive && e.creature() {
|
||||
h += e.max_hp;
|
||||
d += e.atk() / st(e.kind).interval;
|
||||
}
|
||||
}
|
||||
(h * d).sqrt()
|
||||
}
|
||||
|
||||
fn hint(&mut self, key: usize, s: &str) {
|
||||
if self.time - self.hint_t[key] > 30.0 {
|
||||
self.hint_t[key] = self.time;
|
||||
@@ -359,18 +457,20 @@ impl Game {
|
||||
self.do_payday();
|
||||
}
|
||||
|
||||
if self.wave < WAVES {
|
||||
self.wave_timer -= dt;
|
||||
if !self.warned && self.wave_timer < 30.0 {
|
||||
self.warned = true;
|
||||
self.msg(format!("Späher melden: Welle {} der Helden erreicht in 30 Sekunden das Tor!", self.wave + 1));
|
||||
}
|
||||
if self.wave_timer <= 0.0 {
|
||||
self.spawn_wave();
|
||||
self.wave += 1;
|
||||
self.wave_timer = WAVE_GAP;
|
||||
self.warned = false;
|
||||
}
|
||||
self.wave_timer -= dt;
|
||||
if !self.warned && self.wave_timer < 30.0 {
|
||||
self.warned = true;
|
||||
self.msg(format!("Späher melden: Welle {} der Helden erreicht in 30 Sekunden das Tor!", self.wave + 1));
|
||||
self.sfx_ui(Sfx::WaveAlarm);
|
||||
}
|
||||
if self.wave_timer <= 0.0 {
|
||||
self.spawn_wave();
|
||||
self.wave += 1;
|
||||
self.wave_timer = WAVE_GAP + self.rng.f() * 40.0;
|
||||
self.warned = false;
|
||||
}
|
||||
if self.time - self.gate_hit_t > 10.0 {
|
||||
self.gate_hp = (self.gate_hp + 8.0 * dt).min(GATE_HP);
|
||||
}
|
||||
|
||||
for i in 0..self.ents.len() {
|
||||
@@ -387,9 +487,12 @@ impl Game {
|
||||
self.core_hp = 0.0;
|
||||
self.state = 2;
|
||||
self.msg("Dein Seelenkern ist zerbrochen. Das Unterreich ist verloren.".into());
|
||||
} else if self.wave >= WAVES && self.hero_count() == 0 {
|
||||
self.sfx_ui(Sfx::Defeat);
|
||||
} else if self.gate_hp <= 0.0 {
|
||||
self.gate_hp = 0.0;
|
||||
self.state = 1;
|
||||
self.msg("Alle Heldenwellen sind vernichtet. Das Unterreich gehört dir!".into());
|
||||
self.msg("Das Heldentor ist zerstört. Das Unterreich gehört dir!".into());
|
||||
self.sfx_ui(Sfx::Victory);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -433,6 +536,7 @@ impl Game {
|
||||
let (px, py) = self.map.portal;
|
||||
self.spawn(kind, false, px as f32 + 1.5, py as f32 + 1.5, 1);
|
||||
self.fx(3, px as f32 + 1.5, py as f32 + 1.5, 0.0, 0.0, 0.8, 0xffcc44ff);
|
||||
self.sfx(Sfx::PortalArrive, px as f32 + 1.5, py as f32 + 1.5);
|
||||
self.msg(format!("Ein {} ist durch das Portal gekommen.", st(kind).name));
|
||||
}
|
||||
|
||||
@@ -458,6 +562,7 @@ impl Game {
|
||||
}
|
||||
}
|
||||
if paid + unpaid > 0 {
|
||||
self.sfx_ui(Sfx::Payday);
|
||||
if unpaid > 0 {
|
||||
self.msg(format!("Zahltag! {} Kreaturen bezahlt ({} Gold), {} gingen leer aus und sind wütend!", paid, total as i32, unpaid));
|
||||
} else {
|
||||
@@ -466,32 +571,129 @@ impl Game {
|
||||
}
|
||||
}
|
||||
|
||||
// Stärke eines Trupps (gleiche Formel wie der Armeewert)
|
||||
fn squad_value(list: &[(EK, u32)]) -> f32 {
|
||||
let (mut h, mut d) = (0.0, 0.0);
|
||||
for &(k, l) in list {
|
||||
let s = st(k);
|
||||
h += s.hp * (1.0 + 0.15 * (l - 1) as f32);
|
||||
d += s.atk * (1.0 + 0.12 * (l - 1) as f32) / s.interval;
|
||||
}
|
||||
(h * d).sqrt()
|
||||
}
|
||||
|
||||
// Stellt einen Heldentrupp mit mindestens der Zielstärke zusammen.
|
||||
fn build_squad(&mut self, target: f32, base_level: u32, pool: &[(EK, u32)], boss: Option<EK>, min: usize) -> Vec<(EK, u32)> {
|
||||
let total: u32 = pool.iter().map(|p| p.1).sum();
|
||||
let mut list = Vec::new();
|
||||
for level in base_level.min(10)..=10 {
|
||||
list.clear();
|
||||
if let Some(b) = boss {
|
||||
list.push((b, level));
|
||||
}
|
||||
while list.len() < MAX_SQUAD && (list.len() < min || Self::squad_value(&list) < target) {
|
||||
let mut r = self.rng.next() % total;
|
||||
for &(k, w) in pool {
|
||||
if r < w {
|
||||
list.push((k, level));
|
||||
break;
|
||||
}
|
||||
r -= w;
|
||||
}
|
||||
}
|
||||
if Self::squad_value(&list) >= target {
|
||||
break;
|
||||
}
|
||||
}
|
||||
list
|
||||
}
|
||||
|
||||
fn spawn_squad(&mut self, list: &[(EK, u32)], guard: bool) {
|
||||
let (gx, gy) = self.map.gate;
|
||||
for &(k, l) in list {
|
||||
let x = gx as f32 + 0.5 + self.rng.f() * 2.0;
|
||||
let y = gy as f32 + 0.5 + self.rng.f() * 2.0;
|
||||
let id = self.spawn(k, true, x, y, l);
|
||||
if let Some(e) = self.ents.iter_mut().find(|e| e.id == id) {
|
||||
e.guard = guard;
|
||||
}
|
||||
}
|
||||
self.fx(3, gx as f32 + 1.5, gy as f32 + 1.5, 0.0, 0.0, 1.0, 0xffffeecc);
|
||||
}
|
||||
|
||||
// Endlose Wellen: Stärke 60 % -> 100 % des Armeewerts, mit etwas Zufall, mindestens 2 Ritter.
|
||||
fn spawn_wave(&mut self) {
|
||||
use EK::*;
|
||||
let w = self.wave + 1;
|
||||
let list: Vec<EK> = match w {
|
||||
1 => vec![Knight, Knight, Archer],
|
||||
2 => vec![Knight, Knight, Archer, Archer, Dwarf],
|
||||
3 => vec![Knight, Knight, Knight, Archer, Archer, Priest, Dwarf],
|
||||
4 => vec![Knight, Knight, Knight, Knight, Archer, Archer, Archer, Priest, Dwarf, Dwarf],
|
||||
5 => vec![Knight, Knight, Knight, Knight, Knight, Archer, Archer, Archer, Archer, Priest, Priest, Dwarf, Dwarf],
|
||||
_ => vec![Paladin, Knight, Knight, Knight, Knight, Archer, Archer, Archer, Archer, Priest, Priest, Dwarf],
|
||||
};
|
||||
let (gx, gy) = self.map.gate;
|
||||
let n = list.len();
|
||||
for k in list {
|
||||
let x = gx as f32 + 0.5 + self.rng.f() * 2.0;
|
||||
let y = gy as f32 + 0.5 + self.rng.f() * 2.0;
|
||||
self.spawn(k, true, x, y, w);
|
||||
let ratio = (0.6 + 0.05 * (w - 1) as f32).min(1.0) * (0.88 + self.rng.f() * 0.24);
|
||||
let min_value = Self::squad_value(&[(Knight, 1), (Knight, 1)]);
|
||||
let target = (self.army_value() * ratio).max(min_value);
|
||||
let mut pool = vec![(Knight, 4), (Archer, 3)];
|
||||
if w >= 2 {
|
||||
pool.push((Dwarf, 2));
|
||||
}
|
||||
self.fx(3, gx as f32 + 1.5, gy as f32 + 1.5, 0.0, 0.0, 1.0, 0xffffeecc);
|
||||
if w == WAVES {
|
||||
self.msg(format!("Letzte Welle: Der Lichtpaladin führt {} Helden in dein Reich!", n));
|
||||
if w >= 3 {
|
||||
pool.push((Priest, 2));
|
||||
}
|
||||
let boss = if w % 5 == 0 { Some(Paladin) } else { None };
|
||||
let base_level = 1 + (w - 1) / 3;
|
||||
let list = self.build_squad(target, base_level, &pool, boss, 2);
|
||||
self.last_wave_value = Self::squad_value(&list);
|
||||
self.spawn_squad(&list, false);
|
||||
self.sfx_ui(Sfx::WaveStart);
|
||||
if boss.is_some() {
|
||||
self.msg(format!("Welle {}: Ein Lichtpaladin führt {} Helden in dein Reich!", w, list.len()));
|
||||
} else {
|
||||
self.msg(format!("Welle {}: {} Helden dringen durch das Tor!", w, n));
|
||||
self.msg(format!("Welle {}: {} Helden dringen durch das Tor!", w, list.len()));
|
||||
}
|
||||
}
|
||||
|
||||
// Verteidiger bei 75 / 50 / 25 % Tor-HP
|
||||
fn check_gate_stage(&mut self) {
|
||||
use EK::*;
|
||||
let frac = self.gate_hp / GATE_HP;
|
||||
let want = if frac <= 0.25 { 3 } else if frac <= 0.5 { 2 } else if frac <= 0.75 { 1 } else { 0 };
|
||||
while self.gate_stage < want {
|
||||
self.gate_stage += 1;
|
||||
let st_i = self.gate_stage;
|
||||
let ratio = [0.5, 0.65, 0.85][(st_i - 1) as usize] * (0.9 + self.rng.f() * 0.2);
|
||||
let min_value = Self::squad_value(&[(Knight, 2), (Knight, 2), (Archer, 2)]);
|
||||
let target = (self.army_value() * ratio).max(min_value);
|
||||
let pool = [(Knight, 4), (Archer, 3), (Priest, 2), (Dwarf, 1)];
|
||||
let boss = if st_i == 3 { Some(Paladin) } else { None };
|
||||
let base_level = 2 + self.wave / 3 + st_i;
|
||||
let list = self.build_squad(target, base_level, &pool, boss, 3);
|
||||
self.spawn_squad(&list, true);
|
||||
self.sfx_ui(Sfx::GateDefenders);
|
||||
if st_i == 3 {
|
||||
self.msg(format!("Das Tor wankt! Der Torwächter erscheint mit {} Verteidigern!", list.len() - 1));
|
||||
} else {
|
||||
self.msg(format!("Das Tor ruft {} Verteidiger herbei!", list.len()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn hit_gate(&mut self, e: &mut Ent) {
|
||||
let s = st(e.kind);
|
||||
e.cd = s.interval;
|
||||
let dmg = e.atk() * (0.85 + self.rng.f() * 0.3);
|
||||
self.gate_hp -= dmg;
|
||||
self.gate_hit_t = self.time;
|
||||
e.xp += dmg * 0.3;
|
||||
let (gx, gy) = self.gate_center();
|
||||
if s.range > 1.5 {
|
||||
let col = if e.kind == EK::Mage { 0xffff44cc } else { 0xff70b0d0 };
|
||||
self.fx(0, e.x, e.y - 0.3, gx, gy, 0.25, col);
|
||||
} else {
|
||||
let fx = (e.x + gx) * 0.5;
|
||||
let fy = (e.y + gy) * 0.5;
|
||||
self.fx(2, fx, fy, 0.0, 0.0, 0.25, 0xffffffff);
|
||||
}
|
||||
self.sfx(Sfx::GateHit, gx, gy);
|
||||
self.hint(10, "Deine Kreaturen greifen das Heldentor an!");
|
||||
self.check_gate_stage();
|
||||
}
|
||||
|
||||
fn update_ent(&mut self, e: &mut Ent, dt: f32) {
|
||||
e.anim += dt;
|
||||
e.flash = (e.flash - dt).max(0.0);
|
||||
@@ -508,6 +710,9 @@ impl Game {
|
||||
e.max_hp = s.hp * (1.0 + 0.15 * (e.level - 1) as f32);
|
||||
e.hp += e.max_hp - old;
|
||||
self.fx(7, e.x, e.y, 0.0, 0.0, 0.8, 0xff44ddff);
|
||||
if !e.hero {
|
||||
self.sfx(Sfx::LevelUp, e.x, e.y);
|
||||
}
|
||||
if !e.hero && e.level >= 3 {
|
||||
self.msg(format!("{} erreicht Stufe {}.", s.name, e.level));
|
||||
}
|
||||
@@ -543,6 +748,7 @@ impl Game {
|
||||
_ => 1.0,
|
||||
};
|
||||
self.fx(6, tx as f32 + 0.5, ty as f32 + 0.5, 0.0, 0.0, 0.4, 0xff3a5a7a);
|
||||
self.sfx(Sfx::Dig, tx as f32 + 0.5, ty as f32 + 0.5);
|
||||
let t = &mut self.map.t[next];
|
||||
t.hp -= pow;
|
||||
if t.hp <= 0.0 {
|
||||
@@ -588,6 +794,7 @@ impl Game {
|
||||
t.gold = 0;
|
||||
let (x, y) = xy(i);
|
||||
self.fx(3, x as f32 + 0.5, y as f32 + 0.5, 0.0, 0.0, 0.6, 0xff304a66);
|
||||
self.sfx(Sfx::DigDone, x as f32 + 0.5, y as f32 + 0.5);
|
||||
}
|
||||
|
||||
fn unreserve(&mut self, me: u32) {
|
||||
@@ -694,8 +901,10 @@ impl Game {
|
||||
_ => 0xff70b0d0,
|
||||
};
|
||||
self.fx(0, e.x, e.y - 0.3, tx, ty - 0.3, 0.25, col);
|
||||
self.sfx(Sfx::Shoot, e.x, e.y);
|
||||
} else {
|
||||
self.fx(2, tx, ty - 0.2, 0.0, 0.0, 0.25, 0xffffffff);
|
||||
self.sfx(Sfx::MeleeHit, tx, ty);
|
||||
}
|
||||
e.xp += dmg * 0.6;
|
||||
let my_id = e.id;
|
||||
@@ -722,8 +931,10 @@ impl Game {
|
||||
let b = s.bounty as f32;
|
||||
let got = self.add_gold(b);
|
||||
self.fx(5, x, y, 0.0, 0.0, 1.0, 0xff30d0ff);
|
||||
self.sfx(Sfx::HeroDeath, x, y);
|
||||
self.msg(format!("{} besiegt! Beute: {} Gold.", s.name, got as i32));
|
||||
} else {
|
||||
self.sfx(Sfx::Death, x, y);
|
||||
self.msg(format!("Dein {} ist gefallen.", s.name));
|
||||
}
|
||||
}
|
||||
@@ -823,10 +1034,12 @@ impl Game {
|
||||
e.carry += take;
|
||||
done = tl.gold <= 0;
|
||||
self.fx(5, fxx, fxy, 0.0, 0.0, 0.5, 0xff30d0ff);
|
||||
self.sfx(Sfx::GoldDig, fxx, fxy);
|
||||
} else {
|
||||
tl.hp -= 1.0;
|
||||
done = tl.hp <= 0.0;
|
||||
self.fx(6, fxx, fxy, 0.0, 0.0, 0.4, 0xff3a5a7a);
|
||||
self.sfx(Sfx::Dig, fxx, fxy);
|
||||
}
|
||||
if done {
|
||||
self.dig_out(t);
|
||||
@@ -864,6 +1077,7 @@ impl Game {
|
||||
}
|
||||
let (x, y) = xy(t);
|
||||
self.fx(8, x as f32 + 0.5, y as f32 + 0.5, 0.0, 0.0, 0.5, 0xff4040e0);
|
||||
self.sfx(Sfx::Claim, x as f32 + 0.5, y as f32 + 0.5);
|
||||
}
|
||||
}
|
||||
Job::Deliver => {
|
||||
@@ -875,6 +1089,7 @@ impl Game {
|
||||
}
|
||||
if got > 0.0 {
|
||||
self.fx(5, e.x, e.y, 0.0, 0.0, 0.8, 0xff30d0ff);
|
||||
self.sfx(Sfx::Deposit, e.x, e.y);
|
||||
}
|
||||
e.carry = 0;
|
||||
e.job = Job::None;
|
||||
@@ -950,7 +1165,8 @@ impl Game {
|
||||
if e.think <= 0.0 {
|
||||
e.think = 0.4 + self.rng.f() * 0.3;
|
||||
if !matches!(e.job, Job::Attack(_) | Job::Leave) {
|
||||
if let Some(id) = self.nearest_enemy(e, 7.0) {
|
||||
let radius = if e.job == Job::Rally { 8.5 } else { 7.0 };
|
||||
if let Some(id) = self.nearest_enemy(e, radius) {
|
||||
self.unreserve(me);
|
||||
e.job = Job::Attack(id);
|
||||
e.path.clear();
|
||||
@@ -965,6 +1181,10 @@ impl Game {
|
||||
}
|
||||
return;
|
||||
}
|
||||
if e.job == Job::Rally {
|
||||
self.rally(e, dt);
|
||||
return;
|
||||
}
|
||||
if self.step_path(e, dt) {
|
||||
return;
|
||||
}
|
||||
@@ -1060,6 +1280,104 @@ impl Game {
|
||||
}
|
||||
}
|
||||
|
||||
fn wants_rally(&self, e: &Ent) -> bool {
|
||||
self.flag.is_some() && e.hp > e.max_hp * 0.5 && e.energy > 0.15 && e.hunger < 1.8 && e.happy > 0.0
|
||||
}
|
||||
|
||||
// Angriffsflagge: zur Flagge marschieren; liegt das Tor in der Nähe, wird es angegriffen.
|
||||
fn rally(&mut self, e: &mut Ent, dt: f32) {
|
||||
let Some(f) = self.flag else {
|
||||
e.job = Job::None;
|
||||
e.path.clear();
|
||||
return;
|
||||
};
|
||||
if e.hp < e.max_hp * 0.3 || e.energy < 0.1 || e.hunger > 2.2 {
|
||||
// erschöpft oder schwer verletzt: zurückziehen
|
||||
e.job = Job::None;
|
||||
e.path.clear();
|
||||
return;
|
||||
}
|
||||
let (fx, fy) = xy(f);
|
||||
let (fx, fy) = (fx as f32 + 0.5, fy as f32 + 0.5);
|
||||
let (gx, gy) = self.gate_center();
|
||||
let gate_near = self.gate_hp > 0.0 && (fx - gx).powi(2) + (fy - gy).powi(2) <= 8.0 * 8.0;
|
||||
let s = st(e.kind);
|
||||
if gate_near {
|
||||
let dg = ((e.x - gx).powi(2) + (e.y - gy).powi(2)).sqrt();
|
||||
if dg <= s.range + 1.6 && (s.range < 1.5 || self.los(e.x, e.y, gx, gy)) {
|
||||
e.path.clear();
|
||||
e.face = sgn(gx - e.x);
|
||||
e.working = true;
|
||||
if e.cd <= 0.0 {
|
||||
self.hit_gate(e);
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
let (target, tx, ty, near) = if gate_near {
|
||||
(idx(gx as i32, gy as i32), gx, gy, s.range + 1.2)
|
||||
} else {
|
||||
(f, fx, fy, 1.5)
|
||||
};
|
||||
let d = ((e.x - tx).powi(2) + (e.y - ty).powi(2)).sqrt();
|
||||
if d <= near && e.path.is_empty() {
|
||||
return; // wartet an der Flagge
|
||||
}
|
||||
if e.path.is_empty() || e.repath <= 0.0 {
|
||||
e.repath = 1.5;
|
||||
match self.map.path_to(e.tile(), target) {
|
||||
Some(p) => e.path = p,
|
||||
None => {
|
||||
self.hint(11, "Die Angriffsflagge ist für deine Kreaturen nicht erreichbar.");
|
||||
e.job = Job::Wander;
|
||||
e.timer = 3.0;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
self.step_path(e, dt);
|
||||
}
|
||||
|
||||
pub fn set_flag(&mut self, x: i32, y: i32) {
|
||||
if !inb(x, y) {
|
||||
return;
|
||||
}
|
||||
let i = idx(x, y);
|
||||
if self.flag == Some(i) {
|
||||
self.flag = None;
|
||||
self.msg("Angriffsflagge entfernt. Deine Kreaturen kehren zurück.".into());
|
||||
self.sfx_ui(Sfx::Click);
|
||||
for e in self.ents.iter_mut() {
|
||||
if e.alive && e.creature() && e.job == Job::Rally {
|
||||
e.job = Job::None;
|
||||
e.path.clear();
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
if !self.map.t[i].walkable() {
|
||||
self.hint(12, "Die Flagge muss auf begehbarem Boden stehen.");
|
||||
self.sfx_ui(Sfx::Error);
|
||||
return;
|
||||
}
|
||||
self.flag = Some(i);
|
||||
self.sfx(Sfx::FlagSet, x as f32 + 0.5, y as f32 + 0.5);
|
||||
self.msg("Angriffsflagge gesetzt! Kampfbereite Kreaturen sammeln sich dort.".into());
|
||||
let mut ids = Vec::new();
|
||||
for e in self.ents.iter() {
|
||||
if e.alive && !e.held && e.creature() && matches!(e.job, Job::None | Job::Wander | Job::Train(_) | Job::Study(_) | Job::Rally) {
|
||||
ids.push(e.id);
|
||||
}
|
||||
}
|
||||
for id in ids {
|
||||
self.unreserve(id + 1);
|
||||
if let Some(e) = self.ents.iter_mut().find(|e| e.id == id) {
|
||||
e.job = Job::None;
|
||||
e.path.clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn creature_choose(&mut self, e: &mut Ent) {
|
||||
let me = e.id + 1;
|
||||
let cur = e.tile();
|
||||
@@ -1104,7 +1422,7 @@ impl Game {
|
||||
e.job = Job::Sleep(cur);
|
||||
return;
|
||||
}
|
||||
if e.hunger > 1.0 {
|
||||
if e.hunger > 1.0 && !(self.flag.is_some() && e.hunger < 1.5) {
|
||||
if let Some(p) = self.find_room_tile(cur, Kind::Farm, me, true) {
|
||||
let t = p.last().copied().unwrap_or(cur);
|
||||
self.map.t[t].reserved = me;
|
||||
@@ -1117,6 +1435,18 @@ impl Game {
|
||||
self.hint(5, "Deine Kreaturen hungern! Vergrößere den Pilzgarten.");
|
||||
}
|
||||
}
|
||||
if self.wants_rally(e) {
|
||||
e.job = Job::Rally;
|
||||
e.path.clear();
|
||||
e.repath = 0.0;
|
||||
return;
|
||||
}
|
||||
if e.hp < e.max_hp * 0.5 && e.energy >= 0.25 && self.counts[1] > 0 {
|
||||
// verletzt: im Quartier ausruhen
|
||||
e.energy = e.energy.min(0.2);
|
||||
self.creature_choose(e);
|
||||
return;
|
||||
}
|
||||
let r = self.rng.f();
|
||||
if (e.kind == EK::Mage && r < 0.8) || r < 0.2 {
|
||||
if let Some(p) = self.find_room_tile(cur, Kind::Library, me, false) {
|
||||
@@ -1172,7 +1502,8 @@ impl Game {
|
||||
}
|
||||
}
|
||||
if !matches!(e.job, Job::Attack(_)) {
|
||||
if let Some(id) = self.nearest_enemy(e, 6.5) {
|
||||
let radius = if e.guard { 8.0 } else { 6.5 };
|
||||
if let Some(id) = self.nearest_enemy(e, radius) {
|
||||
e.job = Job::Attack(id);
|
||||
e.path.clear();
|
||||
e.repath = 0.0;
|
||||
@@ -1180,13 +1511,29 @@ impl Game {
|
||||
}
|
||||
}
|
||||
if let Job::Attack(id) = e.job {
|
||||
if self.attack_job(e, id, dt) {
|
||||
let (gx, gy) = self.gate_center();
|
||||
let far = e.guard && (e.x - gx).powi(2) + (e.y - gy).powi(2) > 14.0 * 14.0;
|
||||
if !far && self.attack_job(e, id, dt) {
|
||||
return;
|
||||
}
|
||||
e.job = Job::AttackCore;
|
||||
e.path.clear();
|
||||
e.think = 1.5;
|
||||
}
|
||||
if e.guard {
|
||||
// Torwächter bleiben am Tor
|
||||
let (gx, gy) = self.gate_center();
|
||||
let d2 = (e.x - gx).powi(2) + (e.y - gy).powi(2);
|
||||
if d2 > 2.5 * 2.5 && e.path.is_empty() && e.repath <= 0.0 {
|
||||
e.repath = 1.0;
|
||||
let target = idx(gx as i32, gy as i32);
|
||||
if let Some(p) = self.map.path_to(e.tile(), target) {
|
||||
e.path = p;
|
||||
}
|
||||
}
|
||||
self.step_path(e, dt);
|
||||
return;
|
||||
}
|
||||
let cur = e.tile();
|
||||
if e.path.is_empty() && self.map.adj_core(cur) {
|
||||
e.working = true;
|
||||
@@ -1198,6 +1545,7 @@ impl Game {
|
||||
let fx = (e.x + cx as f32 + 1.5) * 0.5;
|
||||
let fy = (e.y + cy as f32 + 1.5) * 0.5;
|
||||
self.fx(2, fx, fy, 0.0, 0.0, 0.3, 0xff4040ff);
|
||||
self.sfx(Sfx::CoreHit, fx, fy);
|
||||
self.hint(6, "Dein Seelenkern wird angegriffen!");
|
||||
}
|
||||
return;
|
||||
@@ -1250,11 +1598,15 @@ impl Game {
|
||||
t.bed = 0;
|
||||
built += 1;
|
||||
self.fx(8, x as f32 + 0.5, y as f32 + 0.5, 0.0, 0.0, 0.5, 0xff40d0ff);
|
||||
if built == 1 {
|
||||
self.sfx(Sfx::Build, x as f32 + 0.5, y as f32 + 0.5);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if broke {
|
||||
self.msg("Nicht genug Gold!".into());
|
||||
self.sfx_ui(Sfx::Error);
|
||||
}
|
||||
if built == 0 && !broke {
|
||||
self.hint(7, "Räume können nur auf eroberten, leeren Bodenfeldern gebaut werden.");
|
||||
@@ -1283,6 +1635,7 @@ impl Game {
|
||||
self.recount();
|
||||
if refund > 0.0 {
|
||||
let got = self.add_gold(refund);
|
||||
self.sfx_ui(Sfx::Sell);
|
||||
self.msg(format!("Räume verkauft: +{} Gold.", got as i32));
|
||||
}
|
||||
}
|
||||
@@ -1317,6 +1670,7 @@ impl Game {
|
||||
e.job = Job::None;
|
||||
e.path.clear();
|
||||
self.hand.push(id);
|
||||
self.sfx_ui(Sfx::Pickup);
|
||||
return true;
|
||||
}
|
||||
false
|
||||
@@ -1343,6 +1697,7 @@ impl Game {
|
||||
e.path.clear();
|
||||
}
|
||||
self.fx(3, wx, wy, 0.0, 0.0, 0.4, 0xff707090);
|
||||
self.sfx(Sfx::Drop, wx, wy);
|
||||
true
|
||||
}
|
||||
|
||||
@@ -1350,9 +1705,11 @@ impl Game {
|
||||
const COST: f32 = 50.0;
|
||||
if self.mana < COST {
|
||||
self.msg("Nicht genug Mana für den Blitz.".into());
|
||||
self.sfx_ui(Sfx::Error);
|
||||
return;
|
||||
}
|
||||
self.mana -= COST;
|
||||
self.sfx(Sfx::Lightning, wx, wy);
|
||||
self.fx(1, wx, wy, 0.0, 0.0, 0.5, 0xffffeeaa);
|
||||
let mut kills = Vec::new();
|
||||
for (j, e) in self.ents.iter_mut().enumerate() {
|
||||
@@ -1373,9 +1730,11 @@ impl Game {
|
||||
const COST: f32 = 60.0;
|
||||
if self.mana < COST {
|
||||
self.msg("Nicht genug Mana für die Heilung.".into());
|
||||
self.sfx_ui(Sfx::Error);
|
||||
return;
|
||||
}
|
||||
self.mana -= COST;
|
||||
self.sfx(Sfx::Heal, wx, wy);
|
||||
let mut fxs = Vec::new();
|
||||
for e in self.ents.iter_mut() {
|
||||
if e.alive && !e.held && !e.hero && (e.x - wx).powi(2) + (e.y - wy).powi(2) < 6.5 {
|
||||
@@ -1398,9 +1757,11 @@ impl Game {
|
||||
}
|
||||
if self.mana < cost {
|
||||
self.msg(format!("Nicht genug Mana ({} benötigt).", cost as i32));
|
||||
self.sfx_ui(Sfx::Error);
|
||||
return;
|
||||
}
|
||||
self.mana -= cost;
|
||||
self.sfx(Sfx::Summon, wx, wy);
|
||||
self.spawn(EK::Worker, false, wx, wy, 1);
|
||||
self.fx(7, wx, wy, 0.0, 0.0, 0.6, 0xffff66cc);
|
||||
}
|
||||
@@ -1432,5 +1793,12 @@ pub fn job_name(e: &Ent) -> &'static str {
|
||||
}
|
||||
}
|
||||
Job::Leave => "Verlässt das Reich",
|
||||
Job::Rally => {
|
||||
if e.working {
|
||||
"Greift das Heldentor an"
|
||||
} else {
|
||||
"Folgt der Angriffsflagge"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+748
@@ -0,0 +1,748 @@
|
||||
// Prozedural erzeugte Pixelart: Kachel-Atlas und Figuren-Sprites.
|
||||
|
||||
use crate::game::EK;
|
||||
use crate::map::Kind;
|
||||
use macroquad::prelude::*;
|
||||
|
||||
pub const CELL: f32 = 16.0;
|
||||
const ATLAS_COLS: u32 = 16;
|
||||
const ATLAS_ROWS: u32 = 5;
|
||||
|
||||
// Atlas-Indizes (jeweils 4 Varianten)
|
||||
pub const T_ROCK: u32 = 0;
|
||||
pub const T_EARTH: u32 = 4;
|
||||
pub const T_GOLD: u32 = 8;
|
||||
pub const T_FLOOR: u32 = 12;
|
||||
pub const T_CLAIM: u32 = 16;
|
||||
pub const T_HEROFLOOR: u32 = 20;
|
||||
pub const T_TREASURY: u32 = 24;
|
||||
pub const T_LAIR: u32 = 28;
|
||||
pub const T_FARM: u32 = 32;
|
||||
pub const T_TRAINING: u32 = 36;
|
||||
pub const T_LIBRARY: u32 = 40;
|
||||
pub const T_COREFLOOR: u32 = 44;
|
||||
pub const T_PORTALFLOOR: u32 = 48;
|
||||
pub const T_GATEFLOOR: u32 = 52;
|
||||
// Einzelzellen
|
||||
pub const T_FACE_EARTH: u32 = 56;
|
||||
pub const T_FACE_ROCK: u32 = 57;
|
||||
pub const T_FACE_GOLD: u32 = 58;
|
||||
pub const T_MARK: u32 = 59;
|
||||
pub const D_BED: u32 = 60;
|
||||
pub const D_MUSHROOM: u32 = 61;
|
||||
pub const D_DUMMY: u32 = 62;
|
||||
pub const D_COINS: u32 = 63;
|
||||
pub const D_SHELF: u32 = 64;
|
||||
pub const I_PICK: u32 = 65;
|
||||
pub const I_HAND: u32 = 66;
|
||||
pub const I_SELL: u32 = 67;
|
||||
pub const I_FLAG: u32 = 68;
|
||||
pub const I_BOLT: u32 = 69;
|
||||
pub const I_HEAL: u32 = 70;
|
||||
pub const I_SUMMON: u32 = 71;
|
||||
|
||||
pub struct Gfx {
|
||||
pub atlas: Texture2D,
|
||||
pub sprites: Texture2D,
|
||||
pub font: Texture2D, // DINOBYTE, CP437, 16x16 Zeichen à 6x8 px
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------ Bitmap-Schrift (CP437)
|
||||
|
||||
pub const GW: f32 = 6.0; // Glyphbreite
|
||||
pub const GH: f32 = 8.0; // Glyphhöhe
|
||||
|
||||
const CP437_LOW: &str = " ☺☻♥♦♣♠•◘○◙♂♀♪♫☼►◄↕‼¶§▬↨↑↓→←∟↔▲▼";
|
||||
const CP437_HIGH: &str = "ÇüéâäàåçêëèïîìÄÅÉæÆôöòûùÿÖÜ¢£¥₧ƒáíóúñѪº¿⌐¬½¼¡«»░▒▓│┤╡╢╖╕╣║╗╝╜╛┐└┴┬├─┼╞╟╚╔╩╦╠═╬╧╨╤╥╙╘╒╓╫╪┘┌█▄▌▐▀αßΓπΣσµτΦΘΩδ∞φε∩≡±≥≤⌠⌡÷≈°∙·√ⁿ²■\u{a0}";
|
||||
|
||||
pub fn cp437(ch: char) -> u8 {
|
||||
let u = ch as u32;
|
||||
if (32..127).contains(&u) {
|
||||
return u as u8;
|
||||
}
|
||||
if ch == '⌂' {
|
||||
return 127;
|
||||
}
|
||||
if let Some(i) = CP437_LOW.chars().position(|c| c == ch) {
|
||||
if i > 0 {
|
||||
return i as u8;
|
||||
}
|
||||
}
|
||||
if let Some(i) = CP437_HIGH.chars().position(|c| c == ch) {
|
||||
return 128 + i as u8;
|
||||
}
|
||||
match ch {
|
||||
'ẞ' => 0xE1,
|
||||
'–' | '—' => b'-',
|
||||
'„' | '“' | '”' => b'"',
|
||||
'‚' | '‘' | '’' => b'\'',
|
||||
'…' => b'.',
|
||||
'×' => b'x',
|
||||
_ => b'?',
|
||||
}
|
||||
}
|
||||
|
||||
// Rahmenzeichen: [oben-links, oben-rechts, unten-links, unten-rechts, waagrecht, senkrecht]
|
||||
pub const BOX_SINGLE: [u8; 6] = [0xDA, 0xBF, 0xC0, 0xD9, 0xC4, 0xB3];
|
||||
pub const BOX_DOUBLE: [u8; 6] = [0xC9, 0xBB, 0xC8, 0xBC, 0xCD, 0xBA];
|
||||
|
||||
fn hash(x: i32, y: i32, s: u32) -> u32 {
|
||||
let mut h = (x as u32).wrapping_mul(374761393) ^ (y as u32).wrapping_mul(668265263) ^ s.wrapping_mul(2246822519);
|
||||
h = (h ^ (h >> 13)).wrapping_mul(1274126177);
|
||||
h ^ (h >> 16)
|
||||
}
|
||||
fn rnd(x: i32, y: i32, s: u32) -> f32 {
|
||||
(hash(x, y, s) & 0xffff) as f32 / 65535.0
|
||||
}
|
||||
|
||||
fn c(r: i32, g: i32, b: i32) -> Color {
|
||||
Color::from_rgba(r.clamp(0, 255) as u8, g.clamp(0, 255) as u8, b.clamp(0, 255) as u8, 255)
|
||||
}
|
||||
fn shade(base: (i32, i32, i32), d: i32) -> Color {
|
||||
c(base.0 + d, base.1 + d, base.2 + d)
|
||||
}
|
||||
|
||||
struct Canvas {
|
||||
img: Image,
|
||||
}
|
||||
|
||||
impl Canvas {
|
||||
fn cell(&mut self, id: u32, f: impl Fn(i32, i32) -> Option<Color>) {
|
||||
let ox = (id % ATLAS_COLS) * 16;
|
||||
let oy = (id / ATLAS_COLS) * 16;
|
||||
for y in 0..16 {
|
||||
for x in 0..16 {
|
||||
if let Some(col) = f(x, y) {
|
||||
self.img.set_pixel(ox + x as u32, oy + y as u32, col);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Pflasterstein-Muster mit Fugen
|
||||
fn stones(x: i32, y: i32, v: u32, base: (i32, i32, i32), grout: (i32, i32, i32), var: i32) -> Color {
|
||||
let row = y / 8;
|
||||
let off = if row % 2 == 1 { 4 } else { 0 };
|
||||
let sx = (x + off) % 16;
|
||||
if y % 8 == 0 || sx % 8 == 0 {
|
||||
return shade(grout, (rnd(x, y, v + 7) * 6.0) as i32);
|
||||
}
|
||||
let stone = hash((x + off) / 8, row, v + 3) % 5;
|
||||
let d = (rnd(x, y, v) * var as f32) as i32 - var / 2 + stone as i32 * 3 - 6;
|
||||
// Lichtkante oben/links, Schatten unten/rechts
|
||||
let edge = if y % 8 == 1 || sx % 8 == 1 {
|
||||
10
|
||||
} else if y % 8 == 7 || sx % 8 == 7 {
|
||||
-8
|
||||
} else {
|
||||
0
|
||||
};
|
||||
shade(base, d + edge)
|
||||
}
|
||||
|
||||
fn earth_px(x: i32, y: i32, v: u32) -> Color {
|
||||
let base = (98, 68, 44);
|
||||
let big = rnd(x / 3, y / 3, v + 11) * 16.0 - 8.0;
|
||||
let fine = rnd(x, y, v) * 14.0 - 7.0;
|
||||
if hash(x, y, v + 99) % 37 == 0 {
|
||||
return c(150, 120, 90);
|
||||
}
|
||||
if hash(x, y, v + 98) % 29 == 0 {
|
||||
return c(62, 42, 28);
|
||||
}
|
||||
shade(base, (big + fine) as i32)
|
||||
}
|
||||
|
||||
fn gen_atlas() -> Image {
|
||||
let mut cv = Canvas { img: Image::gen_image_color((ATLAS_COLS * 16) as u16, (ATLAS_ROWS * 16) as u16, Color::new(0.0, 0.0, 0.0, 0.0)) };
|
||||
for v in 0..4u32 {
|
||||
// Fels
|
||||
cv.cell(T_ROCK + v, |x, y| {
|
||||
let blk = rnd(x / 4 + (y / 4) % 2, y / 4, v + 21) * 20.0 - 10.0;
|
||||
let fine = rnd(x, y, v + 5) * 10.0 - 5.0;
|
||||
let crack = (x + y * 3 + v as i32 * 5) % 23 == 0 || (x * 2 - y + v as i32) % 31 == 0;
|
||||
if crack {
|
||||
Some(c(40, 40, 48))
|
||||
} else {
|
||||
Some(shade((80, 80, 92), (blk + fine) as i32))
|
||||
}
|
||||
});
|
||||
// Erde
|
||||
cv.cell(T_EARTH + v, |x, y| Some(earth_px(x, y, v)));
|
||||
// Gold
|
||||
cv.cell(T_GOLD + v, |x, y| {
|
||||
let mut best = 99.0f32;
|
||||
for k in 0..6 {
|
||||
let nx = 2 + (hash(k, 1, v + 40) % 12) as i32;
|
||||
let ny = 2 + (hash(k, 2, v + 40) % 12) as i32;
|
||||
let d = (((x - nx) * (x - nx) + (y - ny) * (y - ny)) as f32).sqrt();
|
||||
best = best.min(d - if k % 2 == 0 { 0.5 } else { 0.0 });
|
||||
}
|
||||
if best < 0.6 {
|
||||
Some(c(255, 240, 150))
|
||||
} else if best < 1.4 {
|
||||
Some(c(240, 196, 52))
|
||||
} else if best < 2.0 {
|
||||
Some(c(168, 118, 30))
|
||||
} else {
|
||||
Some(earth_px(x, y, v + 60))
|
||||
}
|
||||
});
|
||||
cv.cell(T_FLOOR + v, |x, y| Some(stones(x, y, v, (64, 56, 50), (34, 30, 28), 10)));
|
||||
cv.cell(T_CLAIM + v, |x, y| {
|
||||
let col = stones(x, y, v + 10, (78, 44, 58), (36, 20, 28), 10);
|
||||
// kleines Hoheitszeichen in der Mitte jedes zweiten Steins
|
||||
if (x % 8 == 4 && y % 8 == 4) && hash(x / 8, y / 8, v) % 2 == 0 {
|
||||
Some(c(150, 60, 80))
|
||||
} else {
|
||||
Some(col)
|
||||
}
|
||||
});
|
||||
cv.cell(T_HEROFLOOR + v, |x, y| Some(stones(x, y, v + 20, (132, 136, 150), (80, 84, 96), 12)));
|
||||
cv.cell(T_TREASURY + v, |x, y| {
|
||||
let chk = ((x / 4) + (y / 4)) % 2 == 0;
|
||||
let base = if chk { (120, 92, 36) } else { (100, 74, 30) };
|
||||
let d = (rnd(x, y, v + 30) * 10.0) as i32 - 5;
|
||||
if x % 4 == 0 || y % 4 == 0 {
|
||||
Some(shade((70, 52, 22), d))
|
||||
} else {
|
||||
Some(shade(base, d))
|
||||
}
|
||||
});
|
||||
cv.cell(T_LAIR + v, |x, y| {
|
||||
let streak = ((x * 2 + y + (hash(y, 0, v) % 3) as i32) % 5) as i32;
|
||||
let d = (rnd(x, y, v + 31) * 18.0) as i32 - 9 + streak * 4;
|
||||
Some(shade((112, 86, 46), d))
|
||||
});
|
||||
cv.cell(T_FARM + v, |x, y| {
|
||||
let moss = rnd(x / 2, y / 2, v + 32);
|
||||
let d = (rnd(x, y, v + 33) * 14.0) as i32 - 7;
|
||||
if moss > 0.55 {
|
||||
Some(shade((58, 98, 40), d))
|
||||
} else {
|
||||
Some(shade((62, 46, 32), d))
|
||||
}
|
||||
});
|
||||
cv.cell(T_TRAINING + v, |x, y| {
|
||||
let plank = y / 4;
|
||||
let off = (hash(plank, 0, v + 34) % 16) as i32;
|
||||
if y % 4 == 0 || (x + off) % 16 == 0 {
|
||||
Some(c(58, 38, 24))
|
||||
} else {
|
||||
let grain = ((x + plank * 5) % 7 == 0) as i32 * -10;
|
||||
Some(shade((116, 80, 48), (rnd(x, y, v + 35) * 10.0) as i32 + grain))
|
||||
}
|
||||
});
|
||||
cv.cell(T_LIBRARY + v, |x, y| {
|
||||
if x >= 3 && x <= 12 && y >= 3 && y <= 12 {
|
||||
let border = x == 3 || x == 12 || y == 3 || y == 12;
|
||||
if border {
|
||||
Some(c(170, 130, 50))
|
||||
} else {
|
||||
Some(shade((116, 30, 42), (rnd(x, y, v + 36) * 10.0) as i32))
|
||||
}
|
||||
} else {
|
||||
let plank = x / 4;
|
||||
if x % 4 == 0 {
|
||||
Some(c(40, 24, 16))
|
||||
} else {
|
||||
Some(shade((74, 46, 32), (rnd(x, y, v + 37) * 10.0) as i32 + (plank % 2) * 4))
|
||||
}
|
||||
}
|
||||
});
|
||||
cv.cell(T_COREFLOOR + v, |x, y| Some(stones(x, y, v + 40, (86, 32, 34), (40, 14, 16), 10)));
|
||||
cv.cell(T_PORTALFLOOR + v, |x, y| Some(stones(x, y, v + 50, (66, 44, 92), (32, 20, 46), 10)));
|
||||
cv.cell(T_GATEFLOOR + v, |x, y| {
|
||||
let vein = ((x as f32 * 0.7 + (y as f32 * 0.5).sin() * 3.0 + v as f32 * 4.0) as i32) % 9 == 0;
|
||||
if vein {
|
||||
Some(c(150, 150, 165))
|
||||
} else {
|
||||
Some(shade((196, 196, 206), (rnd(x, y, v + 60) * 12.0) as i32 - 6))
|
||||
}
|
||||
});
|
||||
}
|
||||
// Wandfronten (Pseudo-3D)
|
||||
cv.cell(T_FACE_EARTH, |x, y| {
|
||||
let streak = (hash(x, 0, 70) % 3) as i32 * 5;
|
||||
Some(shade((62, 42, 28), (rnd(x, y, 71) * 10.0) as i32 - streak - y / 2))
|
||||
});
|
||||
cv.cell(T_FACE_ROCK, |x, y| {
|
||||
let streak = (hash(x, 0, 72) % 3) as i32 * 5;
|
||||
Some(shade((54, 54, 64), (rnd(x, y, 73) * 10.0) as i32 - streak - y / 2))
|
||||
});
|
||||
cv.cell(T_FACE_GOLD, |x, y| {
|
||||
if hash(x, y, 74) % 9 == 0 {
|
||||
Some(c(200, 160, 40))
|
||||
} else {
|
||||
Some(shade((62, 42, 28), (rnd(x, y, 75) * 10.0) as i32 - y / 2))
|
||||
}
|
||||
});
|
||||
// Grab-Markierung: diagonale Streifen
|
||||
cv.cell(T_MARK, |x, y| {
|
||||
if (x + y) % 6 < 2 {
|
||||
Some(Color::from_rgba(255, 220, 60, 150))
|
||||
} else if x == 0 || y == 0 || x == 15 || y == 15 {
|
||||
Some(Color::from_rgba(255, 220, 60, 200))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
});
|
||||
// Bett
|
||||
cv.cell(D_BED, |x, y| {
|
||||
let dx = (x as f32 - 7.5) / 6.5;
|
||||
let dy = (y as f32 - 8.0) / 5.5;
|
||||
let d = dx * dx + dy * dy;
|
||||
if d < 0.55 {
|
||||
Some(if x < 5 { c(200, 190, 160) } else { c(110, 50, 60) })
|
||||
} else if d < 1.0 {
|
||||
Some(c(150, 118, 64))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
});
|
||||
// Pilze
|
||||
cv.cell(D_MUSHROOM, |x, y| {
|
||||
let caps = [(4, 6, 3), (11, 5, 2), (8, 11, 3)];
|
||||
for &(cx, cy, r) in &caps {
|
||||
let dx = x - cx;
|
||||
let dy = y - cy;
|
||||
if dy <= 0 && dx * dx + dy * dy * 2 <= r * r + 1 {
|
||||
return Some(if (x + y) % 3 == 0 && dy < 0 { c(250, 230, 220) } else { c(200, 40, 50) });
|
||||
}
|
||||
if dx.abs() <= 0 && dy > 0 && dy <= r {
|
||||
return Some(c(230, 220, 200));
|
||||
}
|
||||
}
|
||||
None
|
||||
});
|
||||
// Trainingspuppe
|
||||
cv.cell(D_DUMMY, |x, y| {
|
||||
if (x == 7 || x == 8) && y >= 4 {
|
||||
Some(c(90, 60, 36))
|
||||
} else if y == 7 && (3..=12).contains(&x) {
|
||||
Some(c(90, 60, 36))
|
||||
} else if (x - 7).pow(2) + (y - 3).pow(2) <= 5 {
|
||||
Some(c(200, 170, 110))
|
||||
} else if y >= 14 && (4..=11).contains(&x) {
|
||||
Some(c(60, 40, 24))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
});
|
||||
// Goldhaufen
|
||||
cv.cell(D_COINS, |x, y| {
|
||||
let dx = (x as f32 - 7.5) / 7.0;
|
||||
let dy = (y as f32 - 11.0) / 5.0;
|
||||
let d = dx * dx + dy * dy;
|
||||
if d < 1.0 && y <= 14 {
|
||||
let hl = hash(x, y, 80) % 5 == 0;
|
||||
Some(if hl { c(255, 245, 170) } else if d > 0.6 { c(190, 140, 30) } else { c(242, 200, 60) })
|
||||
} else {
|
||||
None
|
||||
}
|
||||
});
|
||||
// Bücherregal
|
||||
cv.cell(D_SHELF, |x, y| {
|
||||
if y > 9 || x == 0 || x == 15 {
|
||||
return if y <= 10 { Some(c(60, 36, 22)) } else { None };
|
||||
}
|
||||
if y == 0 || y == 5 {
|
||||
return Some(c(60, 36, 22));
|
||||
}
|
||||
let book = hash(x, y / 5, 81) % 5;
|
||||
Some(match book {
|
||||
0 => c(150, 40, 40),
|
||||
1 => c(40, 80, 140),
|
||||
2 => c(60, 120, 60),
|
||||
3 => c(160, 130, 50),
|
||||
_ => c(100, 60, 120),
|
||||
})
|
||||
});
|
||||
// Werkzeug-Icons
|
||||
cv.cell(I_PICK, |x, y| {
|
||||
let handle = (x + y == 16 || x + y == 17) && x >= 3;
|
||||
let head = (y == 3 || y == 4) && (2..=13).contains(&x) && (x - 8).abs() + (y - 2) * 2 < 9;
|
||||
if head {
|
||||
Some(c(200, 205, 215))
|
||||
} else if handle {
|
||||
Some(c(140, 90, 50))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
});
|
||||
cv.cell(I_HAND, |x, y| {
|
||||
let palm = (3..=12).contains(&x) && (7..=14).contains(&y);
|
||||
let fingers = y >= 2 && y < 8 && (x == 4 || x == 6 || x == 8 || x == 10) && !(x == 10 && y < 4);
|
||||
let thumb = (x == 13 || x == 14) && (6..=10).contains(&y);
|
||||
if palm || fingers || thumb {
|
||||
Some(c(220, 180, 150))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
});
|
||||
cv.cell(I_SELL, |x, y| {
|
||||
let d = (x - 8) * (x - 8) + (y - 8) * (y - 8);
|
||||
if d <= 36 && d >= 20 {
|
||||
Some(c(240, 200, 60))
|
||||
} else if (x == 8 && (4..=12).contains(&y)) || (y == 8 && (5..=11).contains(&x)) {
|
||||
Some(c(240, 200, 60))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
});
|
||||
cv.cell(I_FLAG, |x, y| {
|
||||
if x == 3 && y >= 1 {
|
||||
Some(c(180, 180, 180))
|
||||
} else if x > 3 && x < 14 && y >= 2 && y < 9 - ((x - 4) / 3) % 2 {
|
||||
Some(c(210, 40, 40))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
});
|
||||
cv.cell(I_BOLT, |x, y| {
|
||||
let line1 = y < 8 && (x - (10 - y / 2)).abs() <= 1;
|
||||
let line2 = y >= 7 && y <= 9 && (5..=11).contains(&x);
|
||||
let line3 = y > 8 && (x - (8 - (y - 9) / 2)).abs() <= 1;
|
||||
if line1 || line2 || line3 {
|
||||
Some(c(255, 240, 120))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
});
|
||||
cv.cell(I_HEAL, |x, y| {
|
||||
if ((6..=9).contains(&x) && (2..=13).contains(&y)) || ((2..=13).contains(&x) && (6..=9).contains(&y)) {
|
||||
Some(c(90, 230, 110))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
});
|
||||
cv.cell(I_SUMMON, |x, y| {
|
||||
let d = (((x - 8) * (x - 8) + (y - 8) * (y - 8)) as f32).sqrt();
|
||||
let a = ((y - 8) as f32).atan2((x - 8) as f32);
|
||||
if d < 7.0 && ((a * 3.0 + d * 0.9).sin() > 0.3) {
|
||||
Some(c(200, 90, 230))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
});
|
||||
cv.img
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------ Sprites (10x10)
|
||||
|
||||
const SPR: usize = 10;
|
||||
const SPR_CELL: u32 = 12;
|
||||
|
||||
fn sprite_rows(k: EK) -> [&'static str; 10] {
|
||||
match k {
|
||||
EK::Worker => [
|
||||
"..........",
|
||||
"k..kkkk..k",
|
||||
"okkooookko",
|
||||
".koeooeok.",
|
||||
"..kooook..",
|
||||
"...kRRk..S",
|
||||
"..kRRRRkSS",
|
||||
"..kRRRRkb.",
|
||||
"...kRRkb..",
|
||||
"...k..k...",
|
||||
],
|
||||
EK::Kobold => [
|
||||
"........s.",
|
||||
"..kkkk..s.",
|
||||
".kggggk.b.",
|
||||
".kgegegkb.",
|
||||
".kggggkkb.",
|
||||
"..kGGkggb.",
|
||||
".kgGGGgkb.",
|
||||
".kgGGGk.b.",
|
||||
"..kGkGk...",
|
||||
"..kk.kk...",
|
||||
],
|
||||
EK::Spider => [
|
||||
"..........",
|
||||
"...kkkk...",
|
||||
"k.kPPPPk.k",
|
||||
".kPPPPPPk.",
|
||||
"kkPePPePkk",
|
||||
".kPPPPPPk.",
|
||||
"k.kPPPPk.k",
|
||||
".k.kkkk.k.",
|
||||
"k........k",
|
||||
"..........",
|
||||
],
|
||||
EK::Mage => [
|
||||
"....kk....",
|
||||
"...kPPk...",
|
||||
"..kPPPPk..",
|
||||
".kkkkkkkk.",
|
||||
"..kfefek.y",
|
||||
"..kPPPPk.b",
|
||||
".kPpPPpPkb",
|
||||
".kPPpPPPkb",
|
||||
".kPPPPPPk.",
|
||||
"..kkkkkk..",
|
||||
],
|
||||
EK::Ogre => [
|
||||
"..kkkkkk..",
|
||||
".kcccccck.",
|
||||
".kcecceck.",
|
||||
".kcCccCck.",
|
||||
"kcccccccck",
|
||||
"kcCcccccCk",
|
||||
"kccCccCcck",
|
||||
".kcccccck.",
|
||||
".kcCk.kCk.",
|
||||
".kkk...kk.",
|
||||
],
|
||||
EK::Knight => [
|
||||
"...kkkk...",
|
||||
"..kssssk..",
|
||||
"..kSkkSk.s",
|
||||
"..kssssk.s",
|
||||
"..kuuuuk.s",
|
||||
".ksuuuusks",
|
||||
".kSuyyuSk.",
|
||||
"..kuuuuk..",
|
||||
"..kSkkSk..",
|
||||
"..kk..kk..",
|
||||
],
|
||||
EK::Archer => [
|
||||
"...kkkk..b",
|
||||
"..kGGGGkbk",
|
||||
"..kGfkfkb.",
|
||||
"..kGfffkb.",
|
||||
"..kgggggb.",
|
||||
".kfgggggb.",
|
||||
"..kggggkbk",
|
||||
"..kBBBBk.b",
|
||||
"..kBkkBk..",
|
||||
"..kk..kk..",
|
||||
],
|
||||
EK::Priest => [
|
||||
"....yy....",
|
||||
"...kkkk...",
|
||||
"..kffffk..",
|
||||
"..kfkfkk..",
|
||||
"..kwwwwk..",
|
||||
".kwwyywwk.",
|
||||
".kwwyywwk.",
|
||||
".kwwwwwwk.",
|
||||
".kwwwwwwk.",
|
||||
"..kkkkkk..",
|
||||
],
|
||||
EK::Dwarf => [
|
||||
"..........",
|
||||
"...kkkk...",
|
||||
"..kSSSSk..",
|
||||
"..kSSSSkS.",
|
||||
"..kfkfkkSS",
|
||||
"..kooook.b",
|
||||
".kboooobkb",
|
||||
".kbBBBBbk.",
|
||||
"..kBkkBk..",
|
||||
"..kk..kk..",
|
||||
],
|
||||
EK::Paladin => [
|
||||
"...kkkk..w",
|
||||
"..kyyyyk.w",
|
||||
"..kYkkYk.w",
|
||||
"..kyyyykkw",
|
||||
".kwwwwwwkk",
|
||||
"kywwyywwyk",
|
||||
".kwyyyywk.",
|
||||
"..kwwwwk..",
|
||||
"..kYkkYk..",
|
||||
"..kk..kk..",
|
||||
],
|
||||
}
|
||||
}
|
||||
|
||||
fn pal(ch: char) -> Option<Color> {
|
||||
Some(match ch {
|
||||
'k' => c(22, 16, 22),
|
||||
'w' => c(240, 240, 236),
|
||||
'y' => c(250, 210, 60),
|
||||
'Y' => c(190, 140, 30),
|
||||
'r' => c(200, 40, 40),
|
||||
'R' => c(130, 34, 30),
|
||||
'g' => c(96, 176, 64),
|
||||
'G' => c(44, 104, 44),
|
||||
'b' => c(140, 92, 52),
|
||||
'B' => c(92, 58, 32),
|
||||
's' => c(206, 210, 220),
|
||||
'S' => c(124, 128, 144),
|
||||
'p' => c(176, 96, 220),
|
||||
'P' => c(92, 40, 124),
|
||||
'u' => c(64, 104, 204),
|
||||
'U' => c(32, 52, 124),
|
||||
'f' => c(232, 184, 144),
|
||||
'e' => c(255, 240, 80),
|
||||
'c' => c(150, 152, 140),
|
||||
'C' => c(96, 98, 92),
|
||||
'o' => c(226, 124, 48),
|
||||
'O' => c(160, 72, 24),
|
||||
_ => return None,
|
||||
})
|
||||
}
|
||||
|
||||
pub const SPRITE_KINDS: [EK; 10] = [EK::Worker, EK::Kobold, EK::Spider, EK::Mage, EK::Ogre, EK::Knight, EK::Archer, EK::Priest, EK::Dwarf, EK::Paladin];
|
||||
|
||||
pub fn sprite_index(k: EK) -> u32 {
|
||||
SPRITE_KINDS.iter().position(|&s| s == k).unwrap_or(0) as u32
|
||||
}
|
||||
|
||||
// Zeile 0: normale Sprites, Zeile 1: weiße Silhouetten (Treffer-Blitz)
|
||||
fn gen_sprites() -> Image {
|
||||
let n = SPRITE_KINDS.len() as u32;
|
||||
let mut img = Image::gen_image_color((n * SPR_CELL) as u16, (2 * SPR_CELL) as u16, Color::new(0.0, 0.0, 0.0, 0.0));
|
||||
for (i, &k) in SPRITE_KINDS.iter().enumerate() {
|
||||
let rows = sprite_rows(k);
|
||||
for (y, row) in rows.iter().enumerate() {
|
||||
for (x, ch) in row.chars().enumerate().take(SPR) {
|
||||
if let Some(col) = pal(ch) {
|
||||
let px = i as u32 * SPR_CELL + 1 + x as u32;
|
||||
let py = 1 + y as u32;
|
||||
img.set_pixel(px, py, col);
|
||||
img.set_pixel(px, py + SPR_CELL, WHITE);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
img
|
||||
}
|
||||
|
||||
impl Gfx {
|
||||
pub fn new() -> Gfx {
|
||||
let atlas = Texture2D::from_image(&gen_atlas());
|
||||
atlas.set_filter(FilterMode::Nearest);
|
||||
let sprites = Texture2D::from_image(&gen_sprites());
|
||||
sprites.set_filter(FilterMode::Nearest);
|
||||
// Schrift laden; Hintergrund transparent machen, Glyphen weiß (zum Einfärben)
|
||||
let mut img = Image::from_file_with_format(include_bytes!("../assets/dbyte_1x.png"), Some(ImageFormat::Png)).expect("Schrift");
|
||||
for px in img.get_image_data_mut().iter_mut() {
|
||||
let on = px[3] > 128 && px[0] > 128;
|
||||
*px = if on { [255, 255, 255, 255] } else { [0, 0, 0, 0] };
|
||||
}
|
||||
let font = Texture2D::from_image(&img);
|
||||
font.set_filter(FilterMode::Nearest);
|
||||
Gfx { atlas, sprites, font }
|
||||
}
|
||||
|
||||
pub fn cell(id: u32) -> Rect {
|
||||
Rect::new((id % ATLAS_COLS) as f32 * CELL, (id / ATLAS_COLS) as f32 * CELL, CELL, CELL)
|
||||
}
|
||||
|
||||
pub fn sprite_rect(k: EK, white: bool) -> Rect {
|
||||
let i = sprite_index(k);
|
||||
Rect::new((i * SPR_CELL + 1) as f32, (1 + if white { SPR_CELL } else { 0 }) as f32, SPR as f32, SPR as f32)
|
||||
}
|
||||
|
||||
pub fn tile_tex(kind: Kind, claimed: bool, hero: bool, var: u8) -> u32 {
|
||||
let v = var as u32 & 3;
|
||||
match kind {
|
||||
Kind::Rock => T_ROCK + v,
|
||||
Kind::Earth => T_EARTH + v,
|
||||
Kind::Gold => T_GOLD + v,
|
||||
Kind::Floor => {
|
||||
if claimed {
|
||||
T_CLAIM + v
|
||||
} else if hero {
|
||||
T_HEROFLOOR + v
|
||||
} else {
|
||||
T_FLOOR + v
|
||||
}
|
||||
}
|
||||
Kind::Treasury => T_TREASURY + v,
|
||||
Kind::Lair => T_LAIR + v,
|
||||
Kind::Farm => T_FARM + v,
|
||||
Kind::Training => T_TRAINING + v,
|
||||
Kind::Library => T_LIBRARY + v,
|
||||
Kind::Core => T_COREFLOOR + v,
|
||||
Kind::Portal => T_PORTALFLOOR + v,
|
||||
Kind::Gate => T_GATEFLOOR + v,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn draw_cell(&self, id: u32, x: f32, y: f32, w: f32, h: f32, color: Color) {
|
||||
draw_texture_ex(
|
||||
&self.atlas,
|
||||
x,
|
||||
y,
|
||||
color,
|
||||
DrawTextureParams { dest_size: Some(vec2(w, h)), source: Some(Self::cell(id)), ..Default::default() },
|
||||
);
|
||||
}
|
||||
|
||||
// Einzelnes CP437-Zeichen zeichnen (x, y = linke obere Ecke)
|
||||
pub fn glyph(&self, code: u8, x: f32, y: f32, scale: f32, color: Color) {
|
||||
let src = Rect::new((code % 16) as f32 * GW, (code / 16) as f32 * GH, GW, GH);
|
||||
draw_texture_ex(
|
||||
&self.font,
|
||||
x.floor(),
|
||||
y.floor(),
|
||||
color,
|
||||
DrawTextureParams { dest_size: Some(vec2(GW * scale, GH * scale)), source: Some(src), ..Default::default() },
|
||||
);
|
||||
}
|
||||
|
||||
// Text zeichnen (x, y = linke obere Ecke); scale sollte ganzzahlig sein
|
||||
pub fn text(&self, s: &str, x: f32, y: f32, scale: f32, color: Color) {
|
||||
let mut cx = x;
|
||||
for ch in s.chars() {
|
||||
if ch != ' ' {
|
||||
self.glyph(cp437(ch), cx, y, scale, color);
|
||||
}
|
||||
cx += GW * scale;
|
||||
}
|
||||
}
|
||||
|
||||
pub fn measure(&self, s: &str, scale: f32) -> f32 {
|
||||
s.chars().count() as f32 * GW * scale
|
||||
}
|
||||
|
||||
// Text mit dunklem Schatten
|
||||
pub fn text_sh(&self, s: &str, x: f32, y: f32, scale: f32, color: Color) {
|
||||
self.text(s, x + scale, y + scale, scale, Color::new(0.0, 0.0, 0.0, color.a * 0.85));
|
||||
self.text(s, x, y, scale, color);
|
||||
}
|
||||
|
||||
// Fenster mit CP437-Rahmen; Größe in Zeichen (cols x rows inkl. Rahmen)
|
||||
pub fn window(&self, x: f32, y: f32, cols: i32, rows: i32, scale: f32, double: bool, frame: Color, bg: Color) {
|
||||
let cw = GW * scale;
|
||||
let ch = GH * scale;
|
||||
draw_rectangle(x, y, cols as f32 * cw, rows as f32 * ch, bg);
|
||||
let b = if double { BOX_DOUBLE } else { BOX_SINGLE };
|
||||
for cx in 0..cols {
|
||||
for cy in 0..rows {
|
||||
let top = cy == 0;
|
||||
let bottom = cy == rows - 1;
|
||||
let left = cx == 0;
|
||||
let right = cx == cols - 1;
|
||||
let code = match (top, bottom, left, right) {
|
||||
(true, _, true, _) => b[0],
|
||||
(true, _, _, true) => b[1],
|
||||
(_, true, true, _) => b[2],
|
||||
(_, true, _, true) => b[3],
|
||||
(true, _, _, _) | (_, true, _, _) => b[4],
|
||||
(_, _, true, _) | (_, _, _, true) => b[5],
|
||||
_ => continue,
|
||||
};
|
||||
self.glyph(code, x + cx as f32 * cw, y + cy as f32 * ch, scale, frame);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Fenster mit Titel in der oberen Rahmenlinie
|
||||
pub fn window_titled(&self, x: f32, y: f32, cols: i32, rows: i32, scale: f32, double: bool, frame: Color, bg: Color, title: &str, tcol: Color) {
|
||||
self.window(x, y, cols, rows, scale, double, frame, bg);
|
||||
if !title.is_empty() {
|
||||
let cw = GW * scale;
|
||||
let t = format!(" {} ", title);
|
||||
let tx = x + 2.0 * cw;
|
||||
draw_rectangle(tx, y, self.measure(&t, scale), GH * scale, bg);
|
||||
self.text(&t, tx, y, scale, tcol);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user