Renderer, Oberfläche, Eingabe, Sound-Modul, Simulationstest und Docker-Setup (Port 18093)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
14e481cffb
commit
db417b3af8
13 files changed
+1930
-19
No files matched your search
+275
@@ -0,0 +1,275 @@
|
||||
// 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
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user