3D isometric renderer: chunked meshes with noise-displaced walls, lit shader with flickering torches, billboard sprites, rotatable/tiltable ortho camera, ray picking
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
91bb91b6ee
commit
220d9543cc
6 files changed
+180
-589
No files matched your search
-16
@@ -1683,22 +1683,6 @@ impl Game {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn ent_at(&self, wx: f32, wy: f32, player_only: bool) -> Option<usize> {
|
|
||||||
let mut best = None;
|
|
||||||
let mut bd = 0.45f32;
|
|
||||||
for (j, e) in self.ents.iter().enumerate() {
|
|
||||||
if !e.alive || e.held || (player_only && e.hero) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
let d = (e.x - wx).powi(2) + (e.y - 0.25 - wy).powi(2);
|
|
||||||
if d < bd {
|
|
||||||
bd = d;
|
|
||||||
best = Some(j);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
best
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn pickup(&mut self, target: Option<usize>) -> bool {
|
pub fn pickup(&mut self, target: Option<usize>) -> bool {
|
||||||
if self.hand.len() >= 8 {
|
if self.hand.len() >= 8 {
|
||||||
self.hint(8, "Deine Hand ist voll (max. 8). Rechtsklick lässt Einheiten fallen.");
|
self.hint(8, "Deine Hand ist voll (max. 8). Rechtsklick lässt Einheiten fallen.");
|
||||||
|
|||||||
+138
-93
@@ -1,9 +1,9 @@
|
|||||||
// DungeonCraft – Dungeon-Aufbau & Verteidigung im Browser (Rust + macroquad, WASM).
|
// DungeonCraft – dungeon building & defence in the browser (Rust + macroquad, WASM).
|
||||||
|
|
||||||
mod game;
|
mod game;
|
||||||
mod gfx;
|
mod gfx;
|
||||||
mod map;
|
mod map;
|
||||||
mod render;
|
mod render3d;
|
||||||
mod sound;
|
mod sound;
|
||||||
mod ui;
|
mod ui;
|
||||||
|
|
||||||
@@ -11,62 +11,68 @@ use game::Game;
|
|||||||
use gfx::Gfx;
|
use gfx::Gfx;
|
||||||
use macroquad::prelude::*;
|
use macroquad::prelude::*;
|
||||||
use map::{idx, inb, MH, MW};
|
use map::{idx, inb, MH, MW};
|
||||||
use render::{draw_world, Overlay, View, ZOOMS};
|
use render3d::{Cam, Overlay, R3d};
|
||||||
use sound::SoundBank;
|
use sound::SoundBank;
|
||||||
use ui::{hover_info, Tool, Ui, UiAction, TOOLS};
|
use ui::{hover_info, Tool, Ui, UiAction, TOOLS};
|
||||||
|
|
||||||
fn conf() -> Conf {
|
fn conf() -> macroquad::conf::Conf {
|
||||||
Conf { window_title: "DungeonCraft".into(), window_width: 1280, window_height: 800, high_dpi: false, ..Default::default() }
|
macroquad::conf::Conf {
|
||||||
|
miniquad_conf: miniquad::conf::Conf {
|
||||||
|
window_title: "DungeonCraft".into(),
|
||||||
|
window_width: 1280,
|
||||||
|
window_height: 800,
|
||||||
|
high_dpi: false,
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
draw_call_vertex_capacity: 40000,
|
||||||
|
draw_call_index_capacity: 60000,
|
||||||
|
..Default::default()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
struct Input {
|
struct Input {
|
||||||
ldown: Option<(i32, i32)>, // Start des Auswahlrechtecks
|
ldown: Option<(i32, i32)>, // start of the selection rectangle
|
||||||
rdown: Option<(f32, f32)>, // Startpunkt rechte Maustaste
|
rdown: Option<(f32, f32)>, // right button press position
|
||||||
rlast: (f32, f32),
|
rlast: (f32, f32),
|
||||||
rdrag: bool,
|
rdrag: bool,
|
||||||
|
mlast: Option<(f32, f32)>, // middle button drag
|
||||||
minimap_drag: bool,
|
minimap_drag: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
fn new_game(seed: u32) -> (Game, View) {
|
fn new_game(seed: u32) -> (Game, Cam) {
|
||||||
let g = Game::new(seed);
|
let g = Game::new(seed);
|
||||||
let (cx, cy) = g.map.core;
|
let (cx, cy) = g.map.core;
|
||||||
let zoom = 32.0;
|
let cam = Cam::new(cx as f32 + 1.5, cy as f32 + 1.5);
|
||||||
let view = View { cam_x: cx as f32 + 1.5 - screen_width() / zoom * 0.5, cam_y: cy as f32 + 1.5 - screen_height() / zoom * 0.5, zoom };
|
(g, cam)
|
||||||
(g, view)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn clamp_cam(v: &mut View) {
|
fn next_seed(seed: u32) -> u32 {
|
||||||
let vw = screen_width() / v.zoom;
|
seed.wrapping_mul(1103515245).wrapping_add(12345) | 1
|
||||||
let vh = screen_height() / v.zoom;
|
|
||||||
v.cam_x = v.cam_x.clamp(-vw * 0.5, MW as f32 - vw * 0.5);
|
|
||||||
v.cam_y = v.cam_y.clamp(-vh * 0.5, MH as f32 - vh * 0.5);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn center_on(v: &mut View, wx: f32, wy: f32) {
|
|
||||||
v.cam_x = wx - screen_width() / v.zoom * 0.5;
|
|
||||||
v.cam_y = wy - screen_height() / v.zoom * 0.5;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[macroquad::main(conf)]
|
#[macroquad::main(conf)]
|
||||||
async fn main() {
|
async fn main() {
|
||||||
let gfx = Gfx::new();
|
let gfx = Gfx::new();
|
||||||
|
let mut r3d = R3d::new();
|
||||||
let mut sounds = SoundBank::new();
|
let mut sounds = SoundBank::new();
|
||||||
sounds.generate_all().await;
|
sounds.generate_all().await;
|
||||||
let mut seed = (miniquad::date::now() * 1000.0) as u32 | 1;
|
let mut seed = (miniquad::date::now() * 1000.0) as u32 | 1;
|
||||||
let (mut game, mut view) = new_game(seed);
|
let (mut game, mut cam) = new_game(seed);
|
||||||
let mut ui = Ui::new();
|
let mut ui = Ui::new();
|
||||||
let mut inp = Input { ldown: None, rdown: None, rlast: (0.0, 0.0), rdrag: false, minimap_drag: false };
|
let mut inp = Input { ldown: None, rdown: None, rlast: (0.0, 0.0), rdrag: false, mlast: None, minimap_drag: false };
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
let dt = get_frame_time();
|
let dt = get_frame_time().min(0.1);
|
||||||
let now = get_time();
|
let now = get_time();
|
||||||
let (mx, my) = mouse_position();
|
let (mx, my) = mouse_position();
|
||||||
let over_ui = ui.over_ui(mx, my);
|
let over_ui = ui.over_ui(mx, my);
|
||||||
let (wx, wy) = view.world(mx, my);
|
let picked = cam.pick_tile(&game.map, mx, my);
|
||||||
let hover_tile = (wx.floor() as i32, wy.floor() as i32);
|
let hover_tile = picked.map(|(x, z, _)| (x, z));
|
||||||
|
let ground = cam.ground_point(mx, my);
|
||||||
|
let (wx, wy) = (ground.x, ground.y);
|
||||||
|
|
||||||
// ---------------- Tastatur
|
// ---------------- keyboard
|
||||||
for (i, k) in [
|
let tool_keys = [
|
||||||
KeyCode::Key1,
|
KeyCode::Key1,
|
||||||
KeyCode::Key2,
|
KeyCode::Key2,
|
||||||
KeyCode::Key3,
|
KeyCode::Key3,
|
||||||
@@ -77,12 +83,10 @@ async fn main() {
|
|||||||
KeyCode::Key8,
|
KeyCode::Key8,
|
||||||
KeyCode::Key9,
|
KeyCode::Key9,
|
||||||
KeyCode::Key0,
|
KeyCode::Key0,
|
||||||
KeyCode::Q,
|
KeyCode::X,
|
||||||
KeyCode::E,
|
KeyCode::V,
|
||||||
]
|
];
|
||||||
.iter()
|
for (i, k) in tool_keys.iter().enumerate() {
|
||||||
.enumerate()
|
|
||||||
{
|
|
||||||
if is_key_pressed(*k) {
|
if is_key_pressed(*k) {
|
||||||
ui.tool = TOOLS[i];
|
ui.tool = TOOLS[i];
|
||||||
inp.ldown = None;
|
inp.ldown = None;
|
||||||
@@ -107,51 +111,78 @@ async fn main() {
|
|||||||
sounds.change_volume(-0.1);
|
sounds.change_volume(-0.1);
|
||||||
}
|
}
|
||||||
if is_key_pressed(KeyCode::N) && game.state != 0 {
|
if is_key_pressed(KeyCode::N) && game.state != 0 {
|
||||||
seed = seed.wrapping_mul(1103515245).wrapping_add(12345) | 1;
|
seed = next_seed(seed);
|
||||||
(game, view) = new_game(seed);
|
(game, cam) = new_game(seed);
|
||||||
ui.show_help = false;
|
ui.show_help = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---------------- Kamera
|
// ---------------- camera: pan
|
||||||
let pan = 900.0 * dt / view.zoom;
|
let (right, fwd) = cam.ground_axes();
|
||||||
|
let pan = cam.zoom * 0.9 * dt;
|
||||||
|
let move_cam = |dx: f32, dz: f32, cam: &mut Cam| {
|
||||||
|
let d = right * dx + fwd * dz;
|
||||||
|
cam.tx += d.x;
|
||||||
|
cam.tz += d.y;
|
||||||
|
};
|
||||||
if is_key_down(KeyCode::W) || is_key_down(KeyCode::Up) {
|
if is_key_down(KeyCode::W) || is_key_down(KeyCode::Up) {
|
||||||
view.cam_y -= pan;
|
move_cam(0.0, pan, &mut cam);
|
||||||
}
|
}
|
||||||
if is_key_down(KeyCode::S) || is_key_down(KeyCode::Down) {
|
if is_key_down(KeyCode::S) || is_key_down(KeyCode::Down) {
|
||||||
view.cam_y += pan;
|
move_cam(0.0, -pan, &mut cam);
|
||||||
}
|
}
|
||||||
if is_key_down(KeyCode::A) || is_key_down(KeyCode::Left) {
|
if is_key_down(KeyCode::A) || is_key_down(KeyCode::Left) {
|
||||||
view.cam_x -= pan;
|
move_cam(-pan, 0.0, &mut cam);
|
||||||
}
|
}
|
||||||
if is_key_down(KeyCode::D) || is_key_down(KeyCode::Right) {
|
if is_key_down(KeyCode::D) || is_key_down(KeyCode::Right) {
|
||||||
view.cam_x += pan;
|
move_cam(pan, 0.0, &mut cam);
|
||||||
}
|
}
|
||||||
// Bildschirmrand
|
|
||||||
let edge = 6.0;
|
let edge = 6.0;
|
||||||
if !ui.show_help && mx >= 0.0 && my >= 0.0 && mx < screen_width() && my < screen_height() {
|
if !ui.show_help && mx >= 0.0 && my >= 0.0 && mx < screen_width() && my < screen_height() {
|
||||||
if mx < edge {
|
if mx < edge {
|
||||||
view.cam_x -= pan;
|
move_cam(-pan, 0.0, &mut cam);
|
||||||
} else if mx > screen_width() - edge {
|
} else if mx > screen_width() - edge {
|
||||||
view.cam_x += pan;
|
move_cam(pan, 0.0, &mut cam);
|
||||||
}
|
}
|
||||||
if my < edge {
|
if my < edge {
|
||||||
view.cam_y -= pan;
|
move_cam(0.0, pan, &mut cam);
|
||||||
} else if my > screen_height() - edge {
|
} else if my > screen_height() - edge {
|
||||||
view.cam_y += pan;
|
move_cam(0.0, -pan, &mut cam);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Zoom
|
// ---------------- camera: rotate / tilt
|
||||||
|
let rot_speed = 1.8 * dt;
|
||||||
|
if is_key_down(KeyCode::Q) {
|
||||||
|
cam.yaw += rot_speed;
|
||||||
|
}
|
||||||
|
if is_key_down(KeyCode::E) {
|
||||||
|
cam.yaw -= rot_speed;
|
||||||
|
}
|
||||||
|
if is_key_down(KeyCode::R) {
|
||||||
|
cam.pitch += rot_speed * 0.6;
|
||||||
|
}
|
||||||
|
if is_key_down(KeyCode::F) {
|
||||||
|
cam.pitch -= rot_speed * 0.6;
|
||||||
|
}
|
||||||
|
if is_mouse_button_down(MouseButton::Middle) {
|
||||||
|
if let Some((lx, ly)) = inp.mlast {
|
||||||
|
cam.yaw -= (mx - lx) * 0.008;
|
||||||
|
cam.pitch += (my - ly) * 0.005;
|
||||||
|
}
|
||||||
|
inp.mlast = Some((mx, my));
|
||||||
|
} else {
|
||||||
|
inp.mlast = None;
|
||||||
|
}
|
||||||
|
cam.pitch = cam.pitch.clamp(0.35, 1.45);
|
||||||
|
// ---------------- camera: zoom around the mouse
|
||||||
let (_, wheel) = mouse_wheel();
|
let (_, wheel) = mouse_wheel();
|
||||||
if wheel != 0.0 && !over_ui {
|
if wheel != 0.0 && !over_ui {
|
||||||
let zi = ZOOMS.iter().position(|&z| z >= view.zoom).unwrap_or(2) as i32;
|
let before = cam.ground_point(mx, my);
|
||||||
let ni = (zi + if wheel > 0.0 { 1 } else { -1 }).clamp(0, ZOOMS.len() as i32 - 1) as usize;
|
cam.zoom = (cam.zoom * if wheel > 0.0 { 0.88 } else { 1.0 / 0.88 }).clamp(6.0, 45.0);
|
||||||
if ZOOMS[ni] != view.zoom {
|
let after = cam.ground_point(mx, my);
|
||||||
view.zoom = ZOOMS[ni];
|
cam.tx += before.x - after.x;
|
||||||
view.cam_x = wx - mx / view.zoom;
|
cam.tz += before.y - after.y;
|
||||||
view.cam_y = wy - my / view.zoom;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
// Rechte Maustaste: ziehen = verschieben, klicken = absetzen
|
// ---------------- right button: drag = pan, click = drop
|
||||||
if is_mouse_button_pressed(MouseButton::Right) {
|
if is_mouse_button_pressed(MouseButton::Right) {
|
||||||
inp.rdown = Some((mx, my));
|
inp.rdown = Some((mx, my));
|
||||||
inp.rlast = (mx, my);
|
inp.rlast = (mx, my);
|
||||||
@@ -163,8 +194,10 @@ async fn main() {
|
|||||||
inp.rdrag = true;
|
inp.rdrag = true;
|
||||||
}
|
}
|
||||||
if inp.rdrag {
|
if inp.rdrag {
|
||||||
view.cam_x -= (mx - inp.rlast.0) / view.zoom;
|
let a = cam.ground_point(inp.rlast.0, inp.rlast.1);
|
||||||
view.cam_y -= (my - inp.rlast.1) / view.zoom;
|
let b = cam.ground_point(mx, my);
|
||||||
|
cam.tx -= b.x - a.x;
|
||||||
|
cam.tz -= b.y - a.y;
|
||||||
}
|
}
|
||||||
inp.rlast = (mx, my);
|
inp.rlast = (mx, my);
|
||||||
}
|
}
|
||||||
@@ -180,7 +213,7 @@ async fn main() {
|
|||||||
inp.rdown = None;
|
inp.rdown = None;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---------------- Linke Maustaste
|
// ---------------- left button
|
||||||
if is_mouse_button_pressed(MouseButton::Left) {
|
if is_mouse_button_pressed(MouseButton::Left) {
|
||||||
match ui.click(mx, my, &game) {
|
match ui.click(mx, my, &game) {
|
||||||
UiAction::SelectTool(t) => {
|
UiAction::SelectTool(t) => {
|
||||||
@@ -188,27 +221,30 @@ async fn main() {
|
|||||||
game.sfx_ui(game::Sfx::Click);
|
game.sfx_ui(game::Sfx::Click);
|
||||||
}
|
}
|
||||||
UiAction::MinimapJump(x, y) => {
|
UiAction::MinimapJump(x, y) => {
|
||||||
center_on(&mut view, x, y);
|
cam.tx = x;
|
||||||
|
cam.tz = y;
|
||||||
inp.minimap_drag = true;
|
inp.minimap_drag = true;
|
||||||
}
|
}
|
||||||
UiAction::CloseHelp => ui.show_help = false,
|
UiAction::CloseHelp => ui.show_help = false,
|
||||||
UiAction::NewGame => {
|
UiAction::NewGame => {
|
||||||
seed = seed.wrapping_mul(1103515245).wrapping_add(12345) | 1;
|
seed = next_seed(seed);
|
||||||
(game, view) = new_game(seed);
|
(game, cam) = new_game(seed);
|
||||||
ui.show_help = false;
|
ui.show_help = false;
|
||||||
}
|
}
|
||||||
UiAction::Consumed => {}
|
UiAction::Consumed => {}
|
||||||
UiAction::None => {
|
UiAction::None => {
|
||||||
if game.state == 0 && inb(hover_tile.0, hover_tile.1) {
|
if game.state == 0 {
|
||||||
match ui.tool {
|
match (ui.tool, hover_tile) {
|
||||||
Tool::Dig | Tool::Room(_) | Tool::Sell => inp.ldown = Some(hover_tile),
|
(Tool::Dig | Tool::Room(_) | Tool::Sell, Some(t)) => inp.ldown = Some(t),
|
||||||
Tool::Hand => {
|
(Tool::Hand, _) => {
|
||||||
game.pickup(wx, wy);
|
let j = r3d.pick_ent(&game, &cam, mx, my, true);
|
||||||
|
game.pickup(j);
|
||||||
}
|
}
|
||||||
Tool::Flag => game.set_flag(hover_tile.0, hover_tile.1),
|
(Tool::Flag, Some((x, z))) => game.set_flag(x, z),
|
||||||
Tool::Lightning => game.lightning(wx, wy),
|
(Tool::Lightning, _) => game.lightning(wx, wy),
|
||||||
Tool::Heal => game.heal(wx, wy),
|
(Tool::Heal, _) => game.heal(wx, wy),
|
||||||
Tool::Summon => game.summon(wx, wy),
|
(Tool::Summon, _) => game.summon(wx, wy),
|
||||||
|
_ => {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -217,39 +253,43 @@ async fn main() {
|
|||||||
if inp.minimap_drag {
|
if inp.minimap_drag {
|
||||||
if is_mouse_button_down(MouseButton::Left) && ui.minimap_contains(mx, my) {
|
if is_mouse_button_down(MouseButton::Left) && ui.minimap_contains(mx, my) {
|
||||||
let (x, y) = ui.minimap_world(mx, my);
|
let (x, y) = ui.minimap_world(mx, my);
|
||||||
center_on(&mut view, x, y);
|
cam.tx = x;
|
||||||
|
cam.tz = y;
|
||||||
} else if !is_mouse_button_down(MouseButton::Left) {
|
} else if !is_mouse_button_down(MouseButton::Left) {
|
||||||
inp.minimap_drag = false;
|
inp.minimap_drag = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if is_mouse_button_released(MouseButton::Left) {
|
if is_mouse_button_released(MouseButton::Left) {
|
||||||
if let Some((sx, sy)) = inp.ldown.take() {
|
if let Some((sx, sy)) = inp.ldown.take() {
|
||||||
let (ex, ey) = (hover_tile.0.clamp(0, MW as i32 - 1), hover_tile.1.clamp(0, MH as i32 - 1));
|
if let Some((ex, ey)) = hover_tile {
|
||||||
match ui.tool {
|
match ui.tool {
|
||||||
Tool::Dig => {
|
Tool::Dig => {
|
||||||
let on = !game.map.t[idx(sx, sy)].marked;
|
let on = !game.map.t[idx(sx, sy)].marked;
|
||||||
game.mark_rect(sx, sy, ex, ey, on);
|
game.mark_rect(sx, sy, ex, ey, on);
|
||||||
|
}
|
||||||
|
Tool::Room(r) => game.build_rect(r, sx, sy, ex, ey),
|
||||||
|
Tool::Sell => game.sell_rect(sx, sy, ex, ey),
|
||||||
|
_ => {}
|
||||||
}
|
}
|
||||||
Tool::Room(r) => game.build_rect(r, sx, sy, ex, ey),
|
|
||||||
Tool::Sell => game.sell_rect(sx, sy, ex, ey),
|
|
||||||
_ => {}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
clamp_cam(&mut view);
|
cam.tx = cam.tx.clamp(0.0, MW as f32);
|
||||||
|
cam.tz = cam.tz.clamp(0.0, MH as f32);
|
||||||
|
|
||||||
// ---------------- Update
|
// ---------------- update
|
||||||
game.update(dt);
|
game.update(dt);
|
||||||
|
let hear = cam.zoom * 1.2;
|
||||||
for ev in game.events.drain(..) {
|
for ev in game.events.drain(..) {
|
||||||
sounds.play(ev, &view, now);
|
sounds.play(ev, vec2(cam.tx, cam.tz), hear, now);
|
||||||
}
|
}
|
||||||
for m in game.msgs.drain(..) {
|
for m in game.msgs.drain(..) {
|
||||||
ui.log.push((m, now));
|
ui.log.push((m, now));
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---------------- Zeichnen
|
// ---------------- draw
|
||||||
clear_background(Color::from_rgba(8, 6, 8, 255));
|
clear_background(Color::from_rgba(4, 3, 5, 255));
|
||||||
let (hover_color, radius) = match ui.tool {
|
let (color, radius) = match ui.tool {
|
||||||
Tool::Dig => (Color::new(1.0, 0.85, 0.3, 1.0), None),
|
Tool::Dig => (Color::new(1.0, 0.85, 0.3, 1.0), None),
|
||||||
Tool::Hand => (Color::new(0.9, 0.75, 0.6, 1.0), None),
|
Tool::Hand => (Color::new(0.9, 0.75, 0.6, 1.0), None),
|
||||||
Tool::Room(_) => (Color::new(0.4, 0.85, 1.0, 1.0), None),
|
Tool::Room(_) => (Color::new(0.4, 0.85, 1.0, 1.0), None),
|
||||||
@@ -260,15 +300,20 @@ async fn main() {
|
|||||||
Tool::Summon => (Color::new(0.8, 0.5, 1.0, 1.0), None),
|
Tool::Summon => (Color::new(0.8, 0.5, 1.0, 1.0), None),
|
||||||
};
|
};
|
||||||
let ov = Overlay {
|
let ov = Overlay {
|
||||||
hover: if over_ui || !inb(hover_tile.0, hover_tile.1) { None } else { Some(hover_tile) },
|
hover: if over_ui { None } else { hover_tile.filter(|&(x, z)| inb(x, z)) },
|
||||||
hover_color,
|
rect: inp.ldown.and_then(|(sx, sy)| hover_tile.map(|(ex, ey)| (sx, sy, ex.clamp(0, MW as i32 - 1), ey.clamp(0, MH as i32 - 1)))),
|
||||||
rect: inp.ldown.map(|(sx, sy)| (sx, sy, hover_tile.0.clamp(0, MW as i32 - 1), hover_tile.1.clamp(0, MH as i32 - 1))),
|
color,
|
||||||
rect_color: hover_color,
|
|
||||||
radius: if over_ui { None } else { radius },
|
radius: if over_ui { None } else { radius },
|
||||||
|
high: ui.tool == Tool::Dig,
|
||||||
};
|
};
|
||||||
draw_world(&game, &gfx, &view, now, &ov);
|
r3d.draw(&game, &gfx, &cam, now, &ov);
|
||||||
let info = if over_ui { Vec::new() } else { hover_info(&game, wx, wy) };
|
let info = if over_ui {
|
||||||
ui.draw(&game, &gfx, &view, &sounds, &info, now);
|
Vec::new()
|
||||||
|
} else {
|
||||||
|
let ent = r3d.pick_ent(&game, &cam, mx, my, false);
|
||||||
|
hover_info(&game, ent, hover_tile)
|
||||||
|
};
|
||||||
|
ui.draw(&game, &gfx, &cam, &sounds, &info, now);
|
||||||
|
|
||||||
next_frame().await
|
next_frame().await
|
||||||
}
|
}
|
||||||
|
|||||||
-455
@@ -1,455 +0,0 @@
|
|||||||
// Zeichnet die Spielwelt: Kacheln, Wände, Räume, Kern/Portal/Tor, Einheiten, Effekte.
|
|
||||||
|
|
||||||
use crate::game::*;
|
|
||||||
use crate::gfx::*;
|
|
||||||
use crate::map::*;
|
|
||||||
use macroquad::prelude::*;
|
|
||||||
|
|
||||||
pub const ZOOMS: [f32; 6] = [16.0, 24.0, 32.0, 40.0, 48.0, 64.0];
|
|
||||||
|
|
||||||
pub struct View {
|
|
||||||
pub cam_x: f32, // Weltkoordinate (Kacheln) am linken Bildschirmrand
|
|
||||||
pub cam_y: f32,
|
|
||||||
pub zoom: f32, // Pixel pro Kachel
|
|
||||||
}
|
|
||||||
|
|
||||||
impl View {
|
|
||||||
pub fn sx(&self, wx: f32) -> f32 {
|
|
||||||
((wx - self.cam_x) * self.zoom).floor()
|
|
||||||
}
|
|
||||||
pub fn sy(&self, wy: f32) -> f32 {
|
|
||||||
((wy - self.cam_y) * self.zoom).floor()
|
|
||||||
}
|
|
||||||
pub fn world(&self, sx: f32, sy: f32) -> (f32, f32) {
|
|
||||||
(sx / self.zoom + self.cam_x, sy / self.zoom + self.cam_y)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Overlay-Infos aus der Eingabe (Hover, Auswahlrechteck, Zauberradius)
|
|
||||||
pub struct Overlay {
|
|
||||||
pub hover: Option<(i32, i32)>,
|
|
||||||
pub hover_color: Color,
|
|
||||||
pub rect: Option<(i32, i32, i32, i32)>,
|
|
||||||
pub rect_color: Color,
|
|
||||||
pub radius: Option<(f32, f32, f32)>, // Weltpos + Radius
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn rgba(u: u32) -> Color {
|
|
||||||
Color::from_rgba(u as u8, (u >> 8) as u8, (u >> 16) as u8, (u >> 24) as u8)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn with_a(c: Color, a: f32) -> Color {
|
|
||||||
Color::new(c.r, c.g, c.b, a)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn open(t: Option<&Tile>) -> bool {
|
|
||||||
match t {
|
|
||||||
Some(t) => !t.solid() && t.kind != Kind::Core,
|
|
||||||
None => false,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn solid(t: Option<&Tile>) -> bool {
|
|
||||||
t.map_or(true, |t| t.solid())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn draw_world(g: &Game, gfx: &Gfx, v: &View, time: f64, ov: &Overlay) {
|
|
||||||
let z = v.zoom;
|
|
||||||
let t = time as f32;
|
|
||||||
let x0 = (v.cam_x.floor() as i32 - 1).max(0);
|
|
||||||
let y0 = (v.cam_y.floor() as i32 - 1).max(0);
|
|
||||||
let x1 = ((v.cam_x + screen_width() / z).ceil() as i32 + 1).min(MW as i32 - 1);
|
|
||||||
let y1 = ((v.cam_y + screen_height() / z).ceil() as i32 + 1).min(MH as i32 - 1);
|
|
||||||
let m = &g.map;
|
|
||||||
let gold_fill = (g.gold / g.gold_cap()).clamp(0.0, 1.0);
|
|
||||||
|
|
||||||
// --- Kacheln
|
|
||||||
for y in y0..=y1 {
|
|
||||||
for x in x0..=x1 {
|
|
||||||
let tile = &m.t[idx(x, y)];
|
|
||||||
let px = v.sx(x as f32);
|
|
||||||
let py = v.sy(y as f32);
|
|
||||||
let tex = Gfx::tile_tex(tile.kind, tile.owner == Owner::Player, tile.owner == Owner::Hero, tile.var);
|
|
||||||
gfx.draw_cell(tex, px, py, z, z, WHITE);
|
|
||||||
|
|
||||||
if tile.solid() {
|
|
||||||
// Wandfront, wenn unten offen
|
|
||||||
if open(m.get(x, y + 1)) {
|
|
||||||
let face = match tile.kind {
|
|
||||||
Kind::Rock => T_FACE_ROCK,
|
|
||||||
Kind::Gold => T_FACE_GOLD,
|
|
||||||
_ => T_FACE_EARTH,
|
|
||||||
};
|
|
||||||
let fh = (z * 0.3).floor();
|
|
||||||
gfx.draw_cell(face, px, py + z - fh, z, fh, WHITE);
|
|
||||||
draw_rectangle(px, py + z - fh, z, (z / 16.0).max(1.0), Color::new(0.0, 0.0, 0.0, 0.35));
|
|
||||||
}
|
|
||||||
if open(m.get(x, y - 1)) {
|
|
||||||
draw_rectangle(px, py, z, (z / 16.0).max(1.0), Color::new(1.0, 1.0, 1.0, 0.14));
|
|
||||||
}
|
|
||||||
if open(m.get(x - 1, y)) {
|
|
||||||
draw_rectangle(px, py, (z / 16.0).max(1.0), z, Color::new(0.0, 0.0, 0.0, 0.3));
|
|
||||||
}
|
|
||||||
if open(m.get(x + 1, y)) {
|
|
||||||
draw_rectangle(px + z - (z / 16.0).max(1.0), py, (z / 16.0).max(1.0), z, Color::new(0.0, 0.0, 0.0, 0.3));
|
|
||||||
}
|
|
||||||
if tile.marked {
|
|
||||||
let a = 0.75 + 0.25 * (t * 4.0).sin();
|
|
||||||
gfx.draw_cell(T_MARK, px, py, z, z, with_a(WHITE, a));
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// Schatten von angrenzenden Wänden
|
|
||||||
if solid(m.get(x, y - 1)) {
|
|
||||||
draw_rectangle(px, py, z, z * 0.28, Color::new(0.0, 0.0, 0.0, 0.22));
|
|
||||||
draw_rectangle(px, py, z, z * 0.12, Color::new(0.0, 0.0, 0.0, 0.25));
|
|
||||||
}
|
|
||||||
if solid(m.get(x - 1, y)) {
|
|
||||||
draw_rectangle(px, py, z * 0.14, z, Color::new(0.0, 0.0, 0.0, 0.22));
|
|
||||||
}
|
|
||||||
if solid(m.get(x + 1, y)) {
|
|
||||||
draw_rectangle(px + z * 0.9, py, z * 0.1, z, Color::new(0.0, 0.0, 0.0, 0.18));
|
|
||||||
}
|
|
||||||
// Raumdeko
|
|
||||||
match tile.kind {
|
|
||||||
Kind::Treasury if gold_fill > 0.02 => {
|
|
||||||
let s = z * (0.35 + 0.6 * gold_fill);
|
|
||||||
gfx.draw_cell(D_COINS, px + (z - s) * 0.5, py + (z - s) * 0.7, s, s, WHITE);
|
|
||||||
}
|
|
||||||
Kind::Lair => {
|
|
||||||
gfx.draw_cell(D_BED, px + z * 0.05, py + z * 0.05, z * 0.9, z * 0.9, WHITE);
|
|
||||||
}
|
|
||||||
Kind::Farm => {
|
|
||||||
let pos = [(0.05, 0.1), (0.45, 0.05), (0.25, 0.45)];
|
|
||||||
for (k, &(ox, oy)) in pos.iter().enumerate() {
|
|
||||||
if tile.food >= k as f32 + 1.0 {
|
|
||||||
gfx.draw_cell(D_MUSHROOM, px + z * ox, py + z * oy, z * 0.55, z * 0.55, WHITE);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Kind::Training if (x + y) % 2 == 0 => {
|
|
||||||
gfx.draw_cell(D_DUMMY, px + z * 0.1, py, z * 0.8, z * 0.8, WHITE);
|
|
||||||
}
|
|
||||||
Kind::Library if y % 2 == 0 => {
|
|
||||||
gfx.draw_cell(D_SHELF, px, py, z, z, WHITE);
|
|
||||||
}
|
|
||||||
_ => {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
draw_core(g, v, t);
|
|
||||||
draw_portal(g, v, t);
|
|
||||||
draw_gate(g, gfx, v, t);
|
|
||||||
|
|
||||||
// --- Angriffsflagge
|
|
||||||
if let Some(f) = g.flag {
|
|
||||||
let (fx, fy) = xy(f);
|
|
||||||
let bx = v.sx(fx as f32 + 0.35);
|
|
||||||
let by = v.sy(fy as f32 + 0.9);
|
|
||||||
let top = v.sy(fy as f32 + 0.05);
|
|
||||||
draw_line(bx, by, bx, top, (z / 12.0).max(2.0), Color::from_rgba(200, 200, 200, 255));
|
|
||||||
let wave = (t * 6.0).sin() * z * 0.06;
|
|
||||||
let p1 = vec2(bx, top);
|
|
||||||
let p2 = vec2(bx + z * 0.55, top + z * 0.15 + wave);
|
|
||||||
let p3 = vec2(bx, top + z * 0.35);
|
|
||||||
draw_triangle(p1, p2, p3, Color::from_rgba(210, 40, 40, 255));
|
|
||||||
draw_triangle_lines(p1, p2, p3, 1.0, Color::from_rgba(90, 10, 10, 255));
|
|
||||||
}
|
|
||||||
|
|
||||||
// --- Einheiten (nach y sortiert)
|
|
||||||
let mut order: Vec<usize> = (0..g.ents.len())
|
|
||||||
.filter(|&i| {
|
|
||||||
let e = &g.ents[i];
|
|
||||||
e.alive && !e.held && e.x >= x0 as f32 - 1.0 && e.x <= x1 as f32 + 2.0 && e.y >= y0 as f32 - 1.0 && e.y <= y1 as f32 + 2.0
|
|
||||||
})
|
|
||||||
.collect();
|
|
||||||
order.sort_by(|&a, &b| g.ents[a].y.partial_cmp(&g.ents[b].y).unwrap_or(std::cmp::Ordering::Equal));
|
|
||||||
for i in order {
|
|
||||||
draw_ent(&g.ents[i], gfx, v, t);
|
|
||||||
}
|
|
||||||
|
|
||||||
// --- Effekte
|
|
||||||
for f in &g.fx {
|
|
||||||
draw_fx(f, v, t);
|
|
||||||
}
|
|
||||||
|
|
||||||
// --- Overlay
|
|
||||||
if let Some((x0, y0, x1, y1)) = ov.rect {
|
|
||||||
let (ax, bx) = (x0.min(x1), x0.max(x1));
|
|
||||||
let (ay, by) = (y0.min(y1), y0.max(y1));
|
|
||||||
let px = v.sx(ax as f32);
|
|
||||||
let py = v.sy(ay as f32);
|
|
||||||
let w = (bx - ax + 1) as f32 * z;
|
|
||||||
let h = (by - ay + 1) as f32 * z;
|
|
||||||
draw_rectangle(px, py, w, h, with_a(ov.rect_color, 0.18));
|
|
||||||
draw_rectangle_lines(px, py, w, h, 2.0, ov.rect_color);
|
|
||||||
} else if let Some((hx, hy)) = ov.hover {
|
|
||||||
draw_rectangle_lines(v.sx(hx as f32), v.sy(hy as f32), z, z, 2.0, ov.hover_color);
|
|
||||||
}
|
|
||||||
if let Some((wx, wy, r)) = ov.radius {
|
|
||||||
draw_circle_lines(v.sx(wx), v.sy(wy), r * z, 2.0, with_a(ov.hover_color, 0.8));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn draw_core(g: &Game, v: &View, t: f32) {
|
|
||||||
let (cx, cy) = g.map.core;
|
|
||||||
let x = v.sx(cx as f32 + 1.5);
|
|
||||||
let y = v.sy(cy as f32 + 1.5);
|
|
||||||
let z = v.zoom;
|
|
||||||
let hp = (g.core_hp / CORE_HP).clamp(0.0, 1.0);
|
|
||||||
let pulse = 1.0 + (t * (2.0 + (1.0 - hp) * 6.0)).sin() * 0.05;
|
|
||||||
let r = z * 1.15 * pulse;
|
|
||||||
// Sockel
|
|
||||||
draw_circle(x, y + z * 0.15, z * 1.35, Color::from_rgba(30, 10, 14, 255));
|
|
||||||
draw_circle_lines(x, y + z * 0.15, z * 1.35, (z / 10.0).max(2.0), Color::from_rgba(90, 40, 40, 255));
|
|
||||||
for k in 0..6 {
|
|
||||||
let f = k as f32 / 6.0;
|
|
||||||
let rr = r * (1.0 - f * 0.8);
|
|
||||||
let col = Color::new(0.35 + 0.65 * f * hp + 0.2, 0.05 + 0.5 * f * f * hp, 0.08 + 0.1 * f, 0.9);
|
|
||||||
draw_circle(x, y - f * z * 0.15, rr, col);
|
|
||||||
}
|
|
||||||
// Glanzpunkt
|
|
||||||
draw_circle(x - r * 0.3, y - r * 0.4, r * 0.15, Color::new(1.0, 0.9, 0.8, 0.6));
|
|
||||||
// Risse bei Schaden
|
|
||||||
if hp < 0.6 {
|
|
||||||
let n = ((1.0 - hp) * 8.0) as i32;
|
|
||||||
for k in 0..n {
|
|
||||||
let a = k as f32 * 2.39;
|
|
||||||
let l = r * (0.5 + 0.4 * ((k * 7 % 5) as f32 / 5.0));
|
|
||||||
draw_line(x, y, x + a.cos() * l, y + a.sin() * l, 2.0, Color::new(0.1, 0.0, 0.0, 0.8));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn draw_portal(g: &Game, v: &View, t: f32) {
|
|
||||||
let (px, py) = g.map.portal;
|
|
||||||
let x = v.sx(px as f32 + 1.5);
|
|
||||||
let y = v.sy(py as f32 + 1.5);
|
|
||||||
let z = v.zoom;
|
|
||||||
let owned = g.portal_owned();
|
|
||||||
let (base, arm) = if owned {
|
|
||||||
(Color::from_rgba(40, 10, 60, 255), Color::from_rgba(200, 90, 255, 255))
|
|
||||||
} else {
|
|
||||||
(Color::from_rgba(30, 28, 36, 255), Color::from_rgba(110, 100, 120, 255))
|
|
||||||
};
|
|
||||||
draw_circle(x, y, z * 1.35, base);
|
|
||||||
let speed = if owned { 1.6 } else { 0.3 };
|
|
||||||
for a in 0..3 {
|
|
||||||
for k in 0..28 {
|
|
||||||
let f = k as f32 / 28.0;
|
|
||||||
let ang = t * speed + a as f32 * 2.094 + f * 4.0;
|
|
||||||
let r = f * z * 1.3;
|
|
||||||
draw_circle(x + ang.cos() * r, y + ang.sin() * r, z * (0.04 + 0.07 * f), with_a(arm, 0.3 + 0.7 * (1.0 - f)));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
draw_circle_lines(x, y, z * 1.35, (z / 10.0).max(2.0), with_a(arm, 0.7));
|
|
||||||
}
|
|
||||||
|
|
||||||
fn draw_gate(g: &Game, gfx: &Gfx, v: &View, t: f32) {
|
|
||||||
let (gx, gy) = g.gate_center();
|
|
||||||
let x = v.sx(gx);
|
|
||||||
let y = v.sy(gy);
|
|
||||||
let z = v.zoom;
|
|
||||||
let hp = (g.gate_hp / GATE_HP).clamp(0.0, 1.0);
|
|
||||||
if hp <= 0.0 {
|
|
||||||
for k in 0..12 {
|
|
||||||
let a = k as f32 * 1.7;
|
|
||||||
draw_circle(x + a.cos() * z * 0.9, y + a.sin() * z * 0.7, z * 0.2, Color::from_rgba(120, 120, 130, 255));
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
draw_circle(x, y, z * 1.4, Color::from_rgba(150, 160, 190, 255));
|
|
||||||
draw_circle(x, y, z * 1.15, Color::from_rgba(30, 40, 70, 255));
|
|
||||||
// Lichtstrahlen
|
|
||||||
for k in 0..10 {
|
|
||||||
let a = t * 0.5 + k as f32 * 0.628;
|
|
||||||
let r0 = z * 0.25;
|
|
||||||
let r1 = z * (0.8 + 0.25 * ((t * 2.0 + k as f32).sin()));
|
|
||||||
draw_line(x + a.cos() * r0, y + a.sin() * r0, x + a.cos() * r1, y + a.sin() * r1, (z / 12.0).max(2.0), Color::new(0.8, 0.9, 1.0, 0.5 * hp + 0.2));
|
|
||||||
}
|
|
||||||
draw_circle(x, y, z * (0.35 + 0.05 * (t * 3.0).sin()), Color::new(0.95, 0.97, 1.0, 0.9));
|
|
||||||
draw_circle_lines(x, y, z * 1.4, (z / 10.0).max(2.0), Color::from_rgba(230, 210, 120, 255));
|
|
||||||
// HP-Balken
|
|
||||||
if hp < 1.0 {
|
|
||||||
let w = z * 3.0;
|
|
||||||
let bx = x - w * 0.5;
|
|
||||||
let by = y - z * 1.9;
|
|
||||||
draw_rectangle(bx - 1.0, by - 1.0, w + 2.0, 8.0, BLACK);
|
|
||||||
draw_rectangle(bx, by, w * hp, 6.0, Color::from_rgba(120, 180, 255, 255));
|
|
||||||
gfx.text_sh("Heldentor", bx, by - 10.0, 1.0, WHITE);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn draw_ent(e: &Ent, gfx: &Gfx, v: &View, t: f32) {
|
|
||||||
let z = v.zoom;
|
|
||||||
let big = matches!(e.kind, EK::Ogre | EK::Paladin);
|
|
||||||
let sc = z / 16.0 * if big { 1.25 } else { 1.0 };
|
|
||||||
let w = 10.0 * sc;
|
|
||||||
let h = 10.0 * sc;
|
|
||||||
let cx = v.sx(e.x);
|
|
||||||
let foot = v.sy(e.y + 0.32);
|
|
||||||
let sleeping = matches!(e.job, Job::Sleep(_)) && !e.moving;
|
|
||||||
let bob = if e.moving { -((e.anim * 12.0).sin().abs()) * sc * 1.2 } else { 0.0 };
|
|
||||||
let x = cx - w * 0.5;
|
|
||||||
let y = foot - h + bob;
|
|
||||||
|
|
||||||
// Schatten + Teamring
|
|
||||||
draw_ellipse(cx, foot, w * 0.42, w * 0.16, 0.0, Color::new(0.0, 0.0, 0.0, 0.35));
|
|
||||||
let ring = if e.guard {
|
|
||||||
Color::new(1.0, 0.85, 0.3, 0.8)
|
|
||||||
} else if e.hero {
|
|
||||||
Color::new(0.5, 0.8, 1.0, 0.7)
|
|
||||||
} else {
|
|
||||||
Color::new(0.9, 0.25, 0.3, 0.6)
|
|
||||||
};
|
|
||||||
draw_ellipse_lines(cx, foot, w * 0.45, w * 0.18, 0.0, 1.5, ring);
|
|
||||||
|
|
||||||
let rot = if e.working && !sleeping { (t * 14.0 + e.anim).sin() * 0.15 } else { 0.0 };
|
|
||||||
let tint = if sleeping { Color::new(0.55, 0.55, 0.7, 1.0) } else { WHITE };
|
|
||||||
let params = |white: bool| DrawTextureParams {
|
|
||||||
dest_size: Some(vec2(w, h)),
|
|
||||||
source: Some(Gfx::sprite_rect(e.kind, white)),
|
|
||||||
flip_x: e.face < 0.0,
|
|
||||||
rotation: rot,
|
|
||||||
..Default::default()
|
|
||||||
};
|
|
||||||
draw_texture_ex(&gfx.sprites, x, y, tint, params(false));
|
|
||||||
if e.flash > 0.0 {
|
|
||||||
draw_texture_ex(&gfx.sprites, x, y, Color::new(1.0, 1.0, 1.0, (e.flash / 0.15).min(1.0)), params(true));
|
|
||||||
}
|
|
||||||
if sleeping {
|
|
||||||
let k = (t * 1.5 + e.anim) % 1.0;
|
|
||||||
gfx.glyph(b'z', cx + w * 0.3, y - k * z * 0.4, (z / 32.0).max(1.0).round(), Color::new(0.8, 0.85, 1.0, 1.0 - k));
|
|
||||||
}
|
|
||||||
if e.carry > 0 {
|
|
||||||
let r = (sc * 1.5).max(2.0);
|
|
||||||
draw_circle(cx + w * 0.45, foot - h * 0.35, r, Color::from_rgba(250, 210, 60, 255));
|
|
||||||
draw_circle_lines(cx + w * 0.45, foot - h * 0.35, r, 1.0, Color::from_rgba(120, 80, 10, 255));
|
|
||||||
}
|
|
||||||
|
|
||||||
// HP-Balken
|
|
||||||
if e.hp < e.max_hp || e.hero {
|
|
||||||
let bw = w.max(12.0);
|
|
||||||
let bx = cx - bw * 0.5;
|
|
||||||
let by = y - 5.0;
|
|
||||||
let f = (e.hp / e.max_hp).clamp(0.0, 1.0);
|
|
||||||
let col = if e.hero {
|
|
||||||
Color::from_rgba(120, 180, 255, 255)
|
|
||||||
} else if f > 0.5 {
|
|
||||||
Color::from_rgba(90, 220, 90, 255)
|
|
||||||
} else if f > 0.25 {
|
|
||||||
Color::from_rgba(240, 200, 60, 255)
|
|
||||||
} else {
|
|
||||||
Color::from_rgba(230, 60, 50, 255)
|
|
||||||
};
|
|
||||||
draw_rectangle(bx - 1.0, by - 1.0, bw + 2.0, 4.0, Color::new(0.0, 0.0, 0.0, 0.8));
|
|
||||||
draw_rectangle(bx, by, bw * f, 2.0, col);
|
|
||||||
}
|
|
||||||
if e.level > 1 && z >= 24.0 {
|
|
||||||
gfx.text_sh(&e.level.to_string(), cx - w * 0.5 - 7.0, y - 7.0, 1.0, Color::from_rgba(255, 220, 90, 255));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn draw_fx(f: &Fx, v: &View, t: f32) {
|
|
||||||
let z = v.zoom;
|
|
||||||
let p = (f.t / f.max).clamp(0.0, 1.0);
|
|
||||||
let col = rgba(f.col);
|
|
||||||
let x = v.sx(f.x);
|
|
||||||
let y = v.sy(f.y);
|
|
||||||
match f.kind {
|
|
||||||
0 => {
|
|
||||||
let x2 = v.sx(f.x2);
|
|
||||||
let y2 = v.sy(f.y2);
|
|
||||||
let hx = x + (x2 - x) * p;
|
|
||||||
let hy = y + (y2 - y) * p;
|
|
||||||
let q = (p - 0.35).max(0.0);
|
|
||||||
let tx = x + (x2 - x) * q;
|
|
||||||
let ty = y + (y2 - y) * q;
|
|
||||||
draw_line(tx, ty, hx, hy, (z / 12.0).max(2.0), with_a(col, 0.6));
|
|
||||||
draw_circle(hx, hy, (z * 0.1).max(2.0), col);
|
|
||||||
}
|
|
||||||
1 => {
|
|
||||||
let a = 1.0 - p;
|
|
||||||
let mut px = x;
|
|
||||||
let mut py = y - z * 7.0;
|
|
||||||
let steps = 9;
|
|
||||||
for k in 1..=steps {
|
|
||||||
let ny = y - z * 7.0 + z * 7.0 * k as f32 / steps as f32;
|
|
||||||
let jitter = if k == steps { 0.0 } else { ((t * 50.0 + k as f32 * 13.7).sin()) * z * 0.6 };
|
|
||||||
let nx = x + jitter;
|
|
||||||
draw_line(px, py, nx, ny, (z / 8.0).max(2.0), Color::new(1.0, 1.0, 0.85, a));
|
|
||||||
px = nx;
|
|
||||||
py = ny;
|
|
||||||
}
|
|
||||||
draw_circle(x, y, z * 1.3 * (0.5 + p), Color::new(1.0, 1.0, 0.7, 0.35 * a));
|
|
||||||
}
|
|
||||||
2 => {
|
|
||||||
let r = z * (0.1 + 0.3 * p);
|
|
||||||
for k in 0..4 {
|
|
||||||
let a = k as f32 * 1.571 + 0.785;
|
|
||||||
draw_line(x + a.cos() * r * 0.4, y + a.sin() * r * 0.4, x + a.cos() * r, y + a.sin() * r, 2.0, with_a(col, 1.0 - p));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
3 => {
|
|
||||||
draw_circle(x, y, z * (0.2 + 0.6 * p), with_a(col, 0.45 * (1.0 - p)));
|
|
||||||
}
|
|
||||||
4 => {
|
|
||||||
for k in 0..3 {
|
|
||||||
let ox = (k as f32 - 1.0) * z * 0.25;
|
|
||||||
let oy = -p * z * 0.8 - k as f32 * z * 0.1;
|
|
||||||
let s = (z * 0.08).max(1.5);
|
|
||||||
let c = with_a(col, 1.0 - p);
|
|
||||||
draw_rectangle(x + ox - s * 1.5, y + oy - s * 0.5, s * 3.0, s, c);
|
|
||||||
draw_rectangle(x + ox - s * 0.5, y + oy - s * 1.5, s, s * 3.0, c);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
5 => {
|
|
||||||
for k in 0..4 {
|
|
||||||
let a = k as f32 * 1.9 + f.x * 3.0;
|
|
||||||
let ox = a.cos() * z * 0.3 * p;
|
|
||||||
let oy = -p * z * 0.7 + a.sin() * z * 0.1;
|
|
||||||
let s = (z * 0.09).max(2.0);
|
|
||||||
draw_rectangle(x + ox, y + oy, s, s, with_a(col, 1.0 - p));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
6 => {
|
|
||||||
for k in 0..4 {
|
|
||||||
let a = k as f32 * 1.7 + f.y * 5.0;
|
|
||||||
let ox = a.cos() * z * 0.35 * p;
|
|
||||||
let oy = a.sin().abs() * z * 0.2 * p + p * z * 0.2;
|
|
||||||
let s = (z * 0.08).max(2.0);
|
|
||||||
draw_rectangle(x + ox, y + oy, s, s, with_a(col, 1.0 - p));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
7 => {
|
|
||||||
draw_circle_lines(x, y, z * (0.3 + 1.2 * p), 2.0, with_a(col, 1.0 - p));
|
|
||||||
}
|
|
||||||
8 => {
|
|
||||||
draw_rectangle(x - z * 0.5, y - z * 0.5, z, z, with_a(col, 0.45 * (1.0 - p)));
|
|
||||||
}
|
|
||||||
_ => {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Minikarten-Farbe einer Kachel
|
|
||||||
pub fn minimap_color(tile: &Tile) -> [u8; 4] {
|
|
||||||
match tile.kind {
|
|
||||||
Kind::Rock => [40, 40, 48, 255],
|
|
||||||
Kind::Earth => [86, 60, 40, 255],
|
|
||||||
Kind::Gold => [220, 180, 50, 255],
|
|
||||||
Kind::Floor => match tile.owner {
|
|
||||||
Owner::Player => [140, 60, 80, 255],
|
|
||||||
Owner::Hero => [170, 175, 190, 255],
|
|
||||||
Owner::None => [70, 64, 60, 255],
|
|
||||||
},
|
|
||||||
Kind::Treasury => [200, 160, 60, 255],
|
|
||||||
Kind::Lair => [150, 110, 60, 255],
|
|
||||||
Kind::Farm => [70, 140, 60, 255],
|
|
||||||
Kind::Training => [160, 100, 60, 255],
|
|
||||||
Kind::Library => [150, 40, 60, 255],
|
|
||||||
Kind::Core => [255, 60, 60, 255],
|
|
||||||
Kind::Portal => [190, 90, 240, 255],
|
|
||||||
Kind::Gate => [230, 230, 255, 255],
|
|
||||||
}
|
|
||||||
}
|
|
||||||
+22
-9
@@ -446,7 +446,7 @@ fn build_chunk(map: &Map, amb: &[f32], cx: i32, cz: i32, gold_fill: f32, atlas:
|
|||||||
[Vec3::Y; 4],
|
[Vec3::Y; 4],
|
||||||
);
|
);
|
||||||
let n = WALL_SUB;
|
let n = WALL_SUB;
|
||||||
let top_amb = amb_at(amb, xf + 0.5, zf + 0.5) * 0.85 + 0.05;
|
let top_amb = amb_at(amb, xf + 0.5, zf + 0.5) * 0.95 + 0.1;
|
||||||
build_grid(
|
build_grid(
|
||||||
&mut mb,
|
&mut mb,
|
||||||
|u, v| vec3(xf + u, WALL_H, zf + v),
|
|u, v| vec3(xf + u, WALL_H, zf + v),
|
||||||
@@ -690,6 +690,7 @@ impl R3d {
|
|||||||
let t = time as f32;
|
let t = time as f32;
|
||||||
self.update_chunks(g, gfx);
|
self.update_chunks(g, gfx);
|
||||||
let m = cam.matrix();
|
let m = cam.matrix();
|
||||||
|
EYE.with(|e| e.set(cam.eye_dir()));
|
||||||
set_camera(&cam.camera());
|
set_camera(&cam.camera());
|
||||||
|
|
||||||
// ---- lit geometry
|
// ---- lit geometry
|
||||||
@@ -855,6 +856,20 @@ impl R3d {
|
|||||||
|
|
||||||
// ------------------------------------------------------------------ landmarks, fx, overlay
|
// ------------------------------------------------------------------ landmarks, fx, overlay
|
||||||
|
|
||||||
|
// Camera direction for camera-facing line quads (set once per frame)
|
||||||
|
thread_local! {
|
||||||
|
static EYE: std::cell::Cell<Vec3> = std::cell::Cell::new(Vec3::Y);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Thick 3D line as a camera-facing quad (GL lines don't work with custom materials)
|
||||||
|
fn line3(a: Vec3, b: Vec3, w: f32, color: Color) {
|
||||||
|
let eye = EYE.with(|e| e.get());
|
||||||
|
let side = (b - a).cross(eye).normalize_or_zero() * (w * 0.5);
|
||||||
|
let c = [(color.r * 255.0) as u8, (color.g * 255.0) as u8, (color.b * 255.0) as u8, (color.a * 255.0) as u8];
|
||||||
|
let v = |p: Vec3| Vertex { position: p, uv: Vec2::ZERO, color: c, normal: Vec4::Y };
|
||||||
|
draw_mesh(&Mesh { vertices: vec![v(a - side), v(a + side), v(b + side), v(b - side)], indices: vec![0, 1, 2, 0, 2, 3], texture: None });
|
||||||
|
}
|
||||||
|
|
||||||
fn disc(c: Vec3, r: f32, color: Color, seg: usize) {
|
fn disc(c: Vec3, r: f32, color: Color, seg: usize) {
|
||||||
let mut v = Vec::with_capacity(seg + 1);
|
let mut v = Vec::with_capacity(seg + 1);
|
||||||
let cc = [(color.r * 255.0) as u8, (color.g * 255.0) as u8, (color.b * 255.0) as u8, (color.a * 255.0) as u8];
|
let cc = [(color.r * 255.0) as u8, (color.g * 255.0) as u8, (color.b * 255.0) as u8, (color.a * 255.0) as u8];
|
||||||
@@ -875,7 +890,7 @@ fn ring(c: Vec3, r: f32, color: Color) {
|
|||||||
for k in 0..seg {
|
for k in 0..seg {
|
||||||
let a0 = k as f32 / seg as f32 * std::f32::consts::TAU;
|
let a0 = k as f32 / seg as f32 * std::f32::consts::TAU;
|
||||||
let a1 = (k + 1) as f32 / seg as f32 * std::f32::consts::TAU;
|
let a1 = (k + 1) as f32 / seg as f32 * std::f32::consts::TAU;
|
||||||
draw_line_3d(c + vec3(a0.cos() * r, 0.0, a0.sin() * r), c + vec3(a1.cos() * r, 0.0, a1.sin() * r), color);
|
line3(c + vec3(a0.cos() * r, 0.0, a0.sin() * r), c + vec3(a1.cos() * r, 0.0, a1.sin() * r), 0.035, color);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -935,7 +950,7 @@ fn draw_landmarks_glow(g: &Game, t: f32) {
|
|||||||
draw_sphere(gc, 0.8, None, Color::new(0.7, 0.85, 1.0, 0.2));
|
draw_sphere(gc, 0.8, None, Color::new(0.7, 0.85, 1.0, 0.2));
|
||||||
for k in 0..8 {
|
for k in 0..8 {
|
||||||
let a = t * 0.6 + k as f32 * 0.785;
|
let a = t * 0.6 + k as f32 * 0.785;
|
||||||
draw_line_3d(gc, gc + vec3(a.cos() * 1.0, a.sin() * 0.9, 0.0), Color::new(0.85, 0.92, 1.0, 0.6));
|
line3(gc, gc + vec3(a.cos() * 1.0, a.sin() * 0.9, 0.0), 0.05, Color::new(0.85, 0.92, 1.0, 0.6));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -986,7 +1001,7 @@ fn draw_fx(f: &Fx, t: f32) {
|
|||||||
let to = vec3(f.x2, 0.45, f.y2);
|
let to = vec3(f.x2, 0.45, f.y2);
|
||||||
let head = from.lerp(to, p);
|
let head = from.lerp(to, p);
|
||||||
let tail = from.lerp(to, (p - 0.35).max(0.0));
|
let tail = from.lerp(to, (p - 0.35).max(0.0));
|
||||||
draw_line_3d(tail, head, a(0.8));
|
line3(tail, head, 0.06, a(0.8));
|
||||||
draw_sphere(head, 0.08, None, c);
|
draw_sphere(head, 0.08, None, c);
|
||||||
}
|
}
|
||||||
1 => {
|
1 => {
|
||||||
@@ -995,7 +1010,7 @@ fn draw_fx(f: &Fx, t: f32) {
|
|||||||
let y = 7.0 - 7.0 * k as f32 / 9.0;
|
let y = 7.0 - 7.0 * k as f32 / 9.0;
|
||||||
let j = if k == 9 { 0.0 } else { (t * 50.0 + k as f32 * 13.7).sin() * 0.5 };
|
let j = if k == 9 { 0.0 } else { (t * 50.0 + k as f32 * 13.7).sin() * 0.5 };
|
||||||
let next = vec3(f.x + j, y, f.y + j * 0.5);
|
let next = vec3(f.x + j, y, f.y + j * 0.5);
|
||||||
draw_line_3d(prev, next, Color::new(1.0, 1.0, 0.85, 1.0 - p));
|
line3(prev, next, 0.12, Color::new(1.0, 1.0, 0.85, 1.0 - p));
|
||||||
prev = next;
|
prev = next;
|
||||||
}
|
}
|
||||||
disc(vec3(f.x, 0.05, f.y), 1.6 * (0.5 + p), Color::new(1.0, 1.0, 0.7, 0.35 * (1.0 - p)), 24);
|
disc(vec3(f.x, 0.05, f.y), 1.6 * (0.5 + p), Color::new(1.0, 1.0, 0.7, 0.35 * (1.0 - p)), 24);
|
||||||
@@ -1004,7 +1019,7 @@ fn draw_fx(f: &Fx, t: f32) {
|
|||||||
let r = 0.1 + 0.3 * p;
|
let r = 0.1 + 0.3 * p;
|
||||||
for k in 0..6 {
|
for k in 0..6 {
|
||||||
let ang = k as f32 * 1.047;
|
let ang = k as f32 * 1.047;
|
||||||
draw_line_3d(at, at + vec3(ang.cos() * r, (k as f32 - 2.5) * 0.06, ang.sin() * r), a(1.0 - p));
|
line3(at, at + vec3(ang.cos() * r, (k as f32 - 2.5) * 0.06, ang.sin() * r), 0.04, a(1.0 - p));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
3 => draw_sphere(vec3(f.x, 0.3, f.y), 0.2 + 0.5 * p, None, a(0.4 * (1.0 - p))),
|
3 => draw_sphere(vec3(f.x, 0.3, f.y), 0.2 + 0.5 * p, None, a(0.4 * (1.0 - p))),
|
||||||
@@ -1038,9 +1053,7 @@ fn draw_fx(f: &Fx, t: f32) {
|
|||||||
fn outline(x0: f32, z0: f32, x1: f32, z1: f32, y: f32, c: Color) {
|
fn outline(x0: f32, z0: f32, x1: f32, z1: f32, y: f32, c: Color) {
|
||||||
let p = [vec3(x0, y, z0), vec3(x1, y, z0), vec3(x1, y, z1), vec3(x0, y, z1)];
|
let p = [vec3(x0, y, z0), vec3(x1, y, z0), vec3(x1, y, z1), vec3(x0, y, z1)];
|
||||||
for k in 0..4 {
|
for k in 0..4 {
|
||||||
draw_line_3d(p[k], p[(k + 1) % 4], c);
|
line3(p[k], p[(k + 1) % 4], 0.06, c);
|
||||||
// double line for visibility
|
|
||||||
draw_line_3d(p[k] + vec3(0.0, 0.015, 0.0), p[(k + 1) % 4] + vec3(0.0, 0.015, 0.0), c);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+3
-5
@@ -3,7 +3,6 @@
|
|||||||
// Ohne registrierten Klang bleibt ein Ereignis stumm.
|
// Ohne registrierten Klang bleibt ein Ereignis stumm.
|
||||||
|
|
||||||
use crate::game::{Sfx, SfxEvent, SFX_ALL};
|
use crate::game::{Sfx, SfxEvent, SFX_ALL};
|
||||||
use crate::render::View;
|
|
||||||
use macroquad::audio::{load_sound_from_bytes, play_sound, PlaySoundParams, Sound};
|
use macroquad::audio::{load_sound_from_bytes, play_sound, PlaySoundParams, Sound};
|
||||||
use macroquad::prelude::*;
|
use macroquad::prelude::*;
|
||||||
|
|
||||||
@@ -47,7 +46,8 @@ impl SoundBank {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn play(&mut self, ev: SfxEvent, view: &View, now: f64) {
|
// listener: camera target on the ground; hear: audible radius in tiles
|
||||||
|
pub fn play(&mut self, ev: SfxEvent, listener: Vec2, hear: f32, now: f64) {
|
||||||
if self.muted || self.volume <= 0.0 {
|
if self.muted || self.volume <= 0.0 {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -59,9 +59,7 @@ impl SoundBank {
|
|||||||
// Lautstärke nach Entfernung zur Bildschirmmitte
|
// Lautstärke nach Entfernung zur Bildschirmmitte
|
||||||
let mut vol = self.volume;
|
let mut vol = self.volume;
|
||||||
if let Some((wx, wy)) = ev.pos {
|
if let Some((wx, wy)) = ev.pos {
|
||||||
let (cx, cy) = view.world(screen_width() * 0.5, screen_height() * 0.5);
|
let d = ((wx - listener.x).powi(2) + (wy - listener.y).powi(2)).sqrt();
|
||||||
let d = ((wx - cx).powi(2) + (wy - cy).powi(2)).sqrt();
|
|
||||||
let hear = screen_width().max(screen_height()) / view.zoom * 0.6;
|
|
||||||
let f = 1.0 - ((d - hear * 0.5) / hear).clamp(0.0, 1.0);
|
let f = 1.0 - ((d - hear * 0.5) / hear).clamp(0.0, 1.0);
|
||||||
if f <= 0.05 {
|
if f <= 0.05 {
|
||||||
return;
|
return;
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
use crate::game::*;
|
use crate::game::*;
|
||||||
use crate::gfx::*;
|
use crate::gfx::*;
|
||||||
use crate::map::*;
|
use crate::map::*;
|
||||||
use crate::render::{minimap_color, View};
|
use crate::render3d::{minimap_color, Cam};
|
||||||
use crate::sound::SoundBank;
|
use crate::sound::SoundBank;
|
||||||
use macroquad::prelude::*;
|
use macroquad::prelude::*;
|
||||||
|
|
||||||
@@ -49,7 +49,7 @@ pub const TOOLS: [Tool; 12] = [
|
|||||||
Tool::Summon,
|
Tool::Summon,
|
||||||
];
|
];
|
||||||
|
|
||||||
pub const TOOL_KEYS: [&str; 12] = ["1", "2", "3", "4", "5", "6", "7", "8", "9", "0", "Q", "E"];
|
pub const TOOL_KEYS: [&str; 12] = ["1", "2", "3", "4", "5", "6", "7", "8", "9", "0", "X", "V"];
|
||||||
|
|
||||||
pub struct ToolInfo {
|
pub struct ToolInfo {
|
||||||
pub label: &'static str,
|
pub label: &'static str,
|
||||||
@@ -198,7 +198,7 @@ impl Ui {
|
|||||||
UiAction::None
|
UiAction::None
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn draw(&mut self, g: &Game, gfx: &Gfx, view: &View, snd: &SoundBank, info: &[(String, Color)], now: f64) {
|
pub fn draw(&mut self, g: &Game, gfx: &Gfx, cam: &Cam, snd: &SoundBank, info: &[(String, Color)], now: f64) {
|
||||||
self.rects.clear();
|
self.rects.clear();
|
||||||
let sw = screen_width();
|
let sw = screen_width();
|
||||||
let sh = screen_height();
|
let sh = screen_height();
|
||||||
@@ -325,10 +325,15 @@ impl Ui {
|
|||||||
let (fx, fy) = xy(f);
|
let (fx, fy) = xy(f);
|
||||||
draw_rectangle(mrx + fx as f32 * ms - 2.0, mry + fy as f32 * ms - 2.0, 5.0, 5.0, RED);
|
draw_rectangle(mrx + fx as f32 * ms - 2.0, mry + fy as f32 * ms - 2.0, 5.0, 5.0, RED);
|
||||||
}
|
}
|
||||||
// Kamerarahmen
|
// camera footprint on the ground
|
||||||
let vw = sw / view.zoom * ms;
|
let corners = [cam.ground_point(0.0, 0.0), cam.ground_point(sw, 0.0), cam.ground_point(sw, sh), cam.ground_point(0.0, sh)];
|
||||||
let vh = sh / view.zoom * ms;
|
for k in 0..4 {
|
||||||
draw_rectangle_lines(mrx + view.cam_x * ms, mry + view.cam_y * ms, vw, vh, 1.5, WHITE);
|
let a = corners[k];
|
||||||
|
let b = corners[(k + 1) % 4];
|
||||||
|
let clampp = |p: Vec2| vec2(mrx + p.x.clamp(0.0, MW as f32) * ms, mry + p.y.clamp(0.0, MH as f32) * ms);
|
||||||
|
let (pa, pb) = (clampp(a), clampp(b));
|
||||||
|
draw_line(pa.x, pa.y, pb.x, pb.y, 1.5, WHITE);
|
||||||
|
}
|
||||||
|
|
||||||
// ---------------- Infofeld
|
// ---------------- Infofeld
|
||||||
if !info.is_empty() {
|
if !info.is_empty() {
|
||||||
@@ -415,8 +420,9 @@ impl Ui {
|
|||||||
("Linksklick Werkzeug benutzen (Graben/Räume: Rechteck ziehen)", TXT),
|
("Linksklick Werkzeug benutzen (Graben/Räume: Rechteck ziehen)", TXT),
|
||||||
("Rechtsklick Einheit aus der Hand absetzen", TXT),
|
("Rechtsklick Einheit aus der Hand absetzen", TXT),
|
||||||
("Rechts ziehen Karte verschieben (auch WASD/Pfeile, Bildrand)", TXT),
|
("Rechts ziehen Karte verschieben (auch WASD/Pfeile, Bildrand)", TXT),
|
||||||
|
("Mitte ziehen Kamera drehen und neigen (auch Q/E, R/F)", TXT),
|
||||||
("Mausrad Zoomen", TXT),
|
("Mausrad Zoomen", TXT),
|
||||||
("1-9, 0, Q, E Werkzeug wählen", TXT),
|
("1-9, 0, X, V Werkzeug wählen", TXT),
|
||||||
("P / Leertaste Pause M Ton an/aus +/- Lautstärke", TXT),
|
("P / Leertaste Pause M Ton an/aus +/- Lautstärke", TXT),
|
||||||
("H Diese Hilfe", TXT),
|
("H Diese Hilfe", TXT),
|
||||||
("", TXT),
|
("", TXT),
|
||||||
@@ -475,9 +481,9 @@ impl Ui {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Infotext für Einheit oder Kachel unter dem Mauszeiger (erste Zeile = Titel)
|
// Infotext für Einheit oder Kachel unter dem Mauszeiger (erste Zeile = Titel)
|
||||||
pub fn hover_info(g: &Game, wx: f32, wy: f32) -> Vec<(String, Color)> {
|
pub fn hover_info(g: &Game, ent: Option<usize>, tile: Option<(i32, i32)>) -> Vec<(String, Color)> {
|
||||||
let mut out = Vec::new();
|
let mut out = Vec::new();
|
||||||
if let Some(j) = g.ent_at(wx, wy, false) {
|
if let Some(j) = ent {
|
||||||
let e = &g.ents[j];
|
let e = &g.ents[j];
|
||||||
let s = st(e.kind);
|
let s = st(e.kind);
|
||||||
out.push((format!("{} (Stufe {})", s.name, e.level), if e.hero { BLUE } else { FRAME_HI }));
|
out.push((format!("{} (Stufe {})", s.name, e.level), if e.hero { BLUE } else { FRAME_HI }));
|
||||||
@@ -502,7 +508,7 @@ pub fn hover_info(g: &Game, wx: f32, wy: f32) -> Vec<(String, Color)> {
|
|||||||
}
|
}
|
||||||
return out;
|
return out;
|
||||||
}
|
}
|
||||||
let (x, y) = (wx.floor() as i32, wy.floor() as i32);
|
let Some((x, y)) = tile else { return out };
|
||||||
let Some(t) = g.map.get(x, y) else { return out };
|
let Some(t) = g.map.get(x, y) else { return out };
|
||||||
let owner = match t.owner {
|
let owner = match t.owner {
|
||||||
Owner::Player => "erobert",
|
Owner::Player => "erobert",
|
||||||
|
|||||||
Reference in new issue
Block a user