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:
Richard ZinkandClaude Opus 5.5 committed 2026-11-02 22:09:38 +01:00
1 parent ec9cab9453
commit 14e481cffb
7 files changed
+1182 -45

No files matched your search

+748
View File
@@ -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);
}
}
}