diff --git a/res/graphics/tiles/panzerium.png b/res/graphics/tiles/panzerium.png new file mode 100644 index 0000000..5d83001 Binary files /dev/null and b/res/graphics/tiles/panzerium.png differ diff --git a/res/graphics/tiles/platform.png b/res/graphics/tiles/platform.png index ab06dbc..c9733ee 100644 Binary files a/res/graphics/tiles/platform.png and b/res/graphics/tiles/platform.png differ diff --git a/src/app.rs b/src/app.rs index 41c7dce..f8be5bc 100644 --- a/src/app.rs +++ b/src/app.rs @@ -13,6 +13,7 @@ use sfml::{ }; use crate::{ + command::{Cmd, CmdVec}, debug::{self, DebugState}, game::{for_each_tile_on_screen, Biome, GameState}, graphics::{self, ScreenSc, ScreenVec}, @@ -39,6 +40,7 @@ pub struct App { /// Light map overlay, blended together with the non-lighted version of the scene pub light_map: RenderTexture, pub project_dirs: ProjectDirs, + pub cmdvec: CmdVec, } impl App { @@ -69,6 +71,7 @@ impl App { rt, light_map, project_dirs, + cmdvec: CmdVec::default(), }) } @@ -87,7 +90,14 @@ impl App { fn do_event_handling(&mut self) { while let Some(ev) = self.rw.poll_event() { self.sf_egui.add_event(&ev); - self.input.update_from_event(&ev); + { + let ctx = self.sf_egui.context(); + self.input.update_from_event( + &ev, + ctx.wants_keyboard_input(), + ctx.wants_pointer_input(), + ); + } match ev { Event::Closed => self.should_quit = true, Event::Resized { width, height } => { @@ -100,13 +110,20 @@ impl App { let view = View::from_rect(Rect::new(0., 0., width as f32, height as f32)); self.rw.set_view(&view); } + Event::KeyPressed { code, .. } => match code { + Key::F11 => { + self.debug.console.show ^= true; + self.debug.console.just_opened = true; + } + Key::F12 => self.debug.panel ^= true, + _ => {} + }, _ => {} } } } fn do_update(&mut self) { - self.debug.update(&self.input); let rt_size = self.rt.size(); if self.debug.freecam { self.do_freecam(); @@ -246,9 +263,21 @@ impl App { let t = self.game.world.tile_at_mut(mouse_tpos, &self.game.worldgen); match &def.use_action { UseAction::PlaceTile { layer, id } => match layer { - TileLayer::Bg => t.bg = *id, - TileLayer::Mid => t.mid = *id, - TileLayer::Fg => t.fg = *id, + TileLayer::Bg => { + if t.bg == 0 { + t.bg = *id + } + } + TileLayer::Mid => { + if t.mid == 0 { + t.mid = *id + } + } + TileLayer::Fg => { + if t.fg == 0 { + t.fg = *id + } + } }, UseAction::RemoveTile { layer } => match layer { TileLayer::Bg => t.bg = 0, @@ -340,10 +369,10 @@ impl App { &mut self.game, &mut self.res, &mut self.scale, + &mut self.cmdvec, ); }) .unwrap(); - self.sf_egui.draw(&mut self.rw, None); if self.debug.show_atlas { let atlas = &self.res.atlas.tex; let size = atlas.size(); @@ -352,7 +381,31 @@ impl App { self.rw.draw(&rs); self.rw.draw(&Sprite::with_texture(atlas)); } + self.sf_egui.draw(&mut self.rw, None); self.rw.display(); + drop(spr); + self.execute_commands(); + } + + fn execute_commands(&mut self) { + for cmd in self.cmdvec.drain(..) { + match cmd { + Cmd::QuitApp => self.should_quit = true, + Cmd::ToggleFreecam => self.debug.freecam ^= true, + Cmd::TeleportPlayer { pos, relative } => { + if relative { + let s2dc = pos.to_s2dc(); + self.game.world.player.col_en.en.pos.x += s2dc.x; + self.game.world.player.col_en.en.pos.y += s2dc.y; + } else { + self.game.world.player.col_en.en.pos = pos.to_s2dc() + } + } + Cmd::TeleportPlayerSpawn => { + self.game.world.player.col_en.en.pos = self.game.spawn_point.to_s2dc() + } + } + } } } diff --git a/src/cmdline.rs b/src/cmdline.rs new file mode 100644 index 0000000..dcaae1e --- /dev/null +++ b/src/cmdline.rs @@ -0,0 +1,54 @@ +use clap::Parser; + +use crate::{command::Cmd, math::WorldPos}; + +#[derive(Parser)] +pub enum CmdLine { + Quit, + Freecam, + Clear, + Tp(Tp), + Spawn, +} + +#[derive(Parser)] +pub struct Tp { + x: u32, + y: u32, + /// Relative to current position + #[arg(short, long)] + rel: bool, +} +impl Tp { + fn to_world_pos(&self) -> WorldPos { + WorldPos { + x: self.x, + y: self.y, + } + } +} + +pub enum Dispatch { + Cmd(Cmd), + ClearConsole, +} + +impl CmdLine { + pub fn parse_cmdline(cmdline: &str) -> anyhow::Result { + let words = std::iter::once(" ").chain(cmdline.split_whitespace()); + Ok(Self::try_parse_from(words)?) + } + + pub(crate) fn dispatch(&self) -> Dispatch { + match self { + CmdLine::Quit => Dispatch::Cmd(Cmd::QuitApp), + CmdLine::Freecam => Dispatch::Cmd(Cmd::ToggleFreecam), + CmdLine::Clear => Dispatch::ClearConsole, + CmdLine::Tp(tp) => Dispatch::Cmd(Cmd::TeleportPlayer { + pos: tp.to_world_pos(), + relative: tp.rel, + }), + CmdLine::Spawn => Dispatch::Cmd(Cmd::TeleportPlayerSpawn), + } + } +} diff --git a/src/command.rs b/src/command.rs new file mode 100644 index 0000000..5972422 --- /dev/null +++ b/src/command.rs @@ -0,0 +1,15 @@ +use crate::math::WorldPos; + +/// A command that can change application or game state +pub enum Cmd { + /// Quit the application + QuitApp, + ToggleFreecam, + TeleportPlayer { + pos: WorldPos, + relative: bool, + }, + TeleportPlayerSpawn, +} + +pub type CmdVec = Vec; diff --git a/src/debug.rs b/src/debug.rs index 1ad62b1..c9e32a2 100644 --- a/src/debug.rs +++ b/src/debug.rs @@ -1,9 +1,12 @@ +use std::fmt::Write; + use egui_inspect::{derive::Inspect, inspect}; -use sfml::{audio::SoundSource, window::Key}; +use sfml::audio::SoundSource; use crate::{ + cmdline::CmdLine, + command::CmdVec, game::GameState, - input::Input, math::{px_per_frame_to_km_h, WorldPos}, res::Res, stringfmt::LengthDisp, @@ -13,21 +16,19 @@ use crate::{ #[derive(Default, Debug, Inspect)] pub struct DebugState { - panel: bool, + pub panel: bool, pub freecam: bool, - tiledb_edit: bool, + pub tiledb_edit: bool, pub show_atlas: bool, + pub console: Console, } -impl DebugState { - pub fn update(&mut self, input: &Input) { - if input.pressed(Key::F12) { - self.panel ^= true; - } - if input.pressed(Key::F10) { - self.freecam ^= true; - } - } +#[derive(Default, Debug, Inspect)] +pub struct Console { + pub show: bool, + pub cmdline: String, + pub log: String, + pub just_opened: bool, } fn debug_panel_ui( @@ -112,6 +113,7 @@ pub(crate) fn do_debug_ui( game: &mut GameState, res: &mut Res, scale: &mut u8, + cmd: &mut CmdVec, ) { if debug.panel { debug_panel_ui(debug, game, ctx, res, scale); @@ -119,4 +121,39 @@ pub(crate) fn do_debug_ui( if debug.tiledb_edit { tiledb_edit_ui(ctx, &mut game.tile_db); } + if debug.console.show { + console_ui(ctx, debug, cmd); + } +} + +fn console_ui(ctx: &egui::Context, debug: &mut DebugState, cmd: &mut CmdVec) { + egui::Window::new("Console (F11)").show(ctx, |ui| { + let re = + ui.add(egui::TextEdit::singleline(&mut debug.console.cmdline).hint_text("Command")); + if debug.console.just_opened { + re.request_focus(); + } + if re.lost_focus() && ui.input(|inp| inp.key_pressed(egui::Key::Enter)) { + re.request_focus(); + let cmdline = match CmdLine::parse_cmdline(&debug.console.cmdline) { + Ok(cmd) => cmd, + Err(e) => { + writeln!(&mut debug.console.log, "{e}").unwrap(); + debug.console.cmdline.clear(); + return; + } + }; + debug.console.cmdline.clear(); + match cmdline.dispatch() { + crate::cmdline::Dispatch::Cmd(command) => cmd.push(command), + crate::cmdline::Dispatch::ClearConsole => debug.console.log.clear(), + } + } + egui::ScrollArea::vertical() + .stick_to_bottom(true) + .show(ui, |ui| { + ui.add(egui::TextEdit::multiline(&mut &debug.console.log[..])); + }); + }); + debug.console.just_opened = false; } diff --git a/src/game.rs b/src/game.rs index 81789e0..5d39a50 100644 --- a/src/game.rs +++ b/src/game.rs @@ -38,6 +38,7 @@ pub struct GameState { pub inventory: Inventory, pub itemdb: ItemDb, pub selected_inv_slot: usize, + pub spawn_point: WorldPos, } #[derive(Debug, Inspect)] @@ -182,6 +183,7 @@ impl GameState { inventory: Inventory::new_debug(), itemdb: ItemDb::default(), selected_inv_slot: 0, + spawn_point, } } } diff --git a/src/input.rs b/src/input.rs index 4f65191..c2bf7c1 100644 --- a/src/input.rs +++ b/src/input.rs @@ -14,7 +14,7 @@ pub struct Input { } impl Input { - pub fn update_from_event(&mut self, ev: &Event) { + pub fn update_from_event(&mut self, ev: &Event, egui_kbd: bool, egui_ptr: bool) { match ev { &Event::KeyPressed { code, .. } => { self.pressed.insert(code); @@ -52,6 +52,15 @@ impl Input { } _ => {} } + if egui_kbd { + self.pressed.clear(); + self.down.clear(); + } + if egui_ptr { + self.lmb_down = false; + self.rmb_down = false; + self.mid_pressed = false; + } } /// Pressed event should be cleared every frame pub fn clear_pressed(&mut self) { diff --git a/src/main.rs b/src/main.rs index b6fa0d1..c9f45fa 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,5 +1,7 @@ mod app; mod bitmanip; +mod cmdline; +mod command; mod debug; mod game; mod graphics; diff --git a/src/math.rs b/src/math.rs index c91dd79..2f9edd4 100644 --- a/src/math.rs +++ b/src/math.rs @@ -50,6 +50,13 @@ impl WorldPos { x: Self::CENTER, y: Self::SURFACE, }; + + pub(crate) fn to_s2dc(self) -> s2dc::Vec2 { + s2dc::Vec2 { + x: self.x as i32, + y: self.y as i32, + } + } } pub fn wp_to_tp(wp: WPosSc) -> TPosSc { diff --git a/src/texture_atlas.rs b/src/texture_atlas.rs index 0c2551e..514c94b 100644 --- a/src/texture_atlas.rs +++ b/src/texture_atlas.rs @@ -16,8 +16,8 @@ pub struct AtlasBundle { impl AtlasBundle { pub fn new() -> anyhow::Result { let cfg = TexturePackerConfig { - max_width: 4096, - max_height: 4096, + max_width: 512, + max_height: 512, allow_rotation: false, border_padding: 0, texture_padding: 0, @@ -65,7 +65,6 @@ fn make_pix_buf(packer: &TexturePacker) -> Vec let (w, h) = (packer.width(), packer.height()); let px_size = 4; let mut vec = vec![0; w as usize * h as usize * px_size as usize]; - dbg!(w, h); for y in 0..h { for x in 0..w { let idx = ((y * w + x) * px_size) as usize; diff --git a/tiles.dat b/tiles.dat index f42778f..d3897fa 100644 Binary files a/tiles.dat and b/tiles.dat differ