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
+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