// DungeonCraft – Dungeon-Aufbau & Verteidigung im Browser (Rust + macroquad, WASM). mod game; mod gfx; mod map; mod render; mod sound; mod ui; use game::Game; use gfx::Gfx; use macroquad::prelude::*; use map::{idx, inb, MH, MW}; use render::{draw_world, Overlay, View, ZOOMS}; use sound::SoundBank; use ui::{hover_info, Tool, Ui, UiAction, TOOLS}; fn conf() -> Conf { Conf { window_title: "DungeonCraft".into(), window_width: 1280, window_height: 800, high_dpi: false, ..Default::default() } } struct Input { ldown: Option<(i32, i32)>, // Start des Auswahlrechtecks rdown: Option<(f32, f32)>, // Startpunkt rechte Maustaste rlast: (f32, f32), rdrag: bool, minimap_drag: bool, } fn new_game(seed: u32) -> (Game, View) { let g = Game::new(seed); let (cx, cy) = g.map.core; let zoom = 32.0; 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, view) } fn clamp_cam(v: &mut View) { let vw = screen_width() / v.zoom; 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)] async fn main() { let gfx = Gfx::new(); let mut sounds = SoundBank::new(); sounds.generate_all().await; let mut seed = (miniquad::date::now() * 1000.0) as u32 | 1; let (mut game, mut view) = new_game(seed); let mut ui = Ui::new(); let mut inp = Input { ldown: None, rdown: None, rlast: (0.0, 0.0), rdrag: false, minimap_drag: false }; loop { let dt = get_frame_time(); let now = get_time(); let (mx, my) = mouse_position(); let over_ui = ui.over_ui(mx, my); let (wx, wy) = view.world(mx, my); let hover_tile = (wx.floor() as i32, wy.floor() as i32); // ---------------- Tastatur for (i, k) in [ KeyCode::Key1, KeyCode::Key2, KeyCode::Key3, KeyCode::Key4, KeyCode::Key5, KeyCode::Key6, KeyCode::Key7, KeyCode::Key8, KeyCode::Key9, KeyCode::Key0, KeyCode::Q, KeyCode::E, ] .iter() .enumerate() { if is_key_pressed(*k) { ui.tool = TOOLS[i]; inp.ldown = None; } } if is_key_pressed(KeyCode::H) || (is_key_pressed(KeyCode::Escape) && ui.show_help) { ui.show_help = !ui.show_help; } if is_key_pressed(KeyCode::Escape) { inp.ldown = None; } if (is_key_pressed(KeyCode::P) || is_key_pressed(KeyCode::Space)) && game.state == 0 { game.paused = !game.paused; } if is_key_pressed(KeyCode::M) { sounds.muted = !sounds.muted; } if is_key_pressed(KeyCode::Equal) || is_key_pressed(KeyCode::KpAdd) { sounds.change_volume(0.1); } if is_key_pressed(KeyCode::Minus) || is_key_pressed(KeyCode::KpSubtract) { sounds.change_volume(-0.1); } if is_key_pressed(KeyCode::N) && game.state != 0 { seed = seed.wrapping_mul(1103515245).wrapping_add(12345) | 1; (game, view) = new_game(seed); ui.show_help = false; } // ---------------- Kamera let pan = 900.0 * dt / view.zoom; if is_key_down(KeyCode::W) || is_key_down(KeyCode::Up) { view.cam_y -= pan; } if is_key_down(KeyCode::S) || is_key_down(KeyCode::Down) { view.cam_y += pan; } if is_key_down(KeyCode::A) || is_key_down(KeyCode::Left) { view.cam_x -= pan; } if is_key_down(KeyCode::D) || is_key_down(KeyCode::Right) { view.cam_x += pan; } // Bildschirmrand let edge = 6.0; if !ui.show_help && mx >= 0.0 && my >= 0.0 && mx < screen_width() && my < screen_height() { if mx < edge { view.cam_x -= pan; } else if mx > screen_width() - edge { view.cam_x += pan; } if my < edge { view.cam_y -= pan; } else if my > screen_height() - edge { view.cam_y += pan; } } // Zoom let (_, wheel) = mouse_wheel(); if wheel != 0.0 && !over_ui { let zi = ZOOMS.iter().position(|&z| z >= view.zoom).unwrap_or(2) as i32; let ni = (zi + if wheel > 0.0 { 1 } else { -1 }).clamp(0, ZOOMS.len() as i32 - 1) as usize; if ZOOMS[ni] != view.zoom { view.zoom = ZOOMS[ni]; view.cam_x = wx - mx / view.zoom; view.cam_y = wy - my / view.zoom; } } // Rechte Maustaste: ziehen = verschieben, klicken = absetzen if is_mouse_button_pressed(MouseButton::Right) { inp.rdown = Some((mx, my)); inp.rlast = (mx, my); inp.rdrag = false; } if let Some((sx, sy)) = inp.rdown { if is_mouse_button_down(MouseButton::Right) { if (mx - sx).abs() + (my - sy).abs() > 5.0 { inp.rdrag = true; } if inp.rdrag { view.cam_x -= (mx - inp.rlast.0) / view.zoom; view.cam_y -= (my - inp.rlast.1) / view.zoom; } inp.rlast = (mx, my); } } if is_mouse_button_released(MouseButton::Right) { if !inp.rdrag && !over_ui && game.state == 0 { if inp.ldown.is_some() { inp.ldown = None; } else { game.drop(wx, wy); } } inp.rdown = None; } // ---------------- Linke Maustaste if is_mouse_button_pressed(MouseButton::Left) { match ui.click(mx, my, &game) { UiAction::SelectTool(t) => { ui.tool = t; game.sfx_ui(game::Sfx::Click); } UiAction::MinimapJump(x, y) => { center_on(&mut view, x, y); inp.minimap_drag = true; } UiAction::CloseHelp => ui.show_help = false, UiAction::NewGame => { seed = seed.wrapping_mul(1103515245).wrapping_add(12345) | 1; (game, view) = new_game(seed); ui.show_help = false; } UiAction::Consumed => {} UiAction::None => { if game.state == 0 && inb(hover_tile.0, hover_tile.1) { match ui.tool { Tool::Dig | Tool::Room(_) | Tool::Sell => inp.ldown = Some(hover_tile), Tool::Hand => { game.pickup(wx, wy); } Tool::Flag => game.set_flag(hover_tile.0, hover_tile.1), Tool::Lightning => game.lightning(wx, wy), Tool::Heal => game.heal(wx, wy), Tool::Summon => game.summon(wx, wy), } } } } } if inp.minimap_drag { if is_mouse_button_down(MouseButton::Left) && ui.minimap_contains(mx, my) { let (x, y) = ui.minimap_world(mx, my); center_on(&mut view, x, y); } else if !is_mouse_button_down(MouseButton::Left) { inp.minimap_drag = false; } } if is_mouse_button_released(MouseButton::Left) { 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)); match ui.tool { Tool::Dig => { let on = !game.map.t[idx(sx, sy)].marked; 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), _ => {} } } } clamp_cam(&mut view); // ---------------- Update game.update(dt); for ev in game.events.drain(..) { sounds.play(ev, &view, now); } for m in game.msgs.drain(..) { ui.log.push((m, now)); } // ---------------- Zeichnen clear_background(Color::from_rgba(8, 6, 8, 255)); let (hover_color, radius) = match ui.tool { 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::Room(_) => (Color::new(0.4, 0.85, 1.0, 1.0), None), Tool::Sell => (Color::new(1.0, 0.5, 0.3, 1.0), None), Tool::Flag => (Color::new(1.0, 0.3, 0.3, 1.0), None), Tool::Lightning => (Color::new(1.0, 1.0, 0.6, 1.0), Some((wx, wy, 1.6))), Tool::Heal => (Color::new(0.4, 1.0, 0.5, 1.0), Some((wx, wy, 2.55))), Tool::Summon => (Color::new(0.8, 0.5, 1.0, 1.0), None), }; let ov = Overlay { hover: if over_ui || !inb(hover_tile.0, hover_tile.1) { None } else { Some(hover_tile) }, hover_color, 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))), rect_color: hover_color, radius: if over_ui { None } else { radius }, }; draw_world(&game, &gfx, &view, now, &ov); let info = if over_ui { Vec::new() } else { hover_info(&game, wx, wy) }; ui.draw(&game, &gfx, &view, &sounds, &info, now); next_frame().await } }