diff --git a/res/graphics/items/woodpick.png b/res/graphics/items/woodpick.png new file mode 100644 index 0000000..1cc34da Binary files /dev/null and b/res/graphics/items/woodpick.png differ diff --git a/res/graphics/ui/invframe.png b/res/graphics/ui/invframe.png new file mode 100644 index 0000000..a41e812 Binary files /dev/null and b/res/graphics/ui/invframe.png differ diff --git a/src/app.rs b/src/app.rs index 31e7b91..7b2a3dc 100644 --- a/src/app.rs +++ b/src/app.rs @@ -5,10 +5,10 @@ use gamedebug_core::{imm, imm_dbg}; use sfml::{ audio::SoundSource, graphics::{ - BlendMode, Color, Rect, RenderStates, RenderTarget, RenderTexture, RenderWindow, Sprite, - Transformable, View, + BlendMode, Color, Rect, RectangleShape, RenderStates, RenderTarget, RenderTexture, + RenderWindow, Shape, Sprite, Transformable, View, }, - system::Vector2u, + system::{Vector2, Vector2u}, window::{Event, Key}, }; @@ -17,6 +17,7 @@ use crate::{ game::{for_each_tile_on_screen, Biome, GameState}, graphics::{self, ScreenSc, ScreenVec}, input::Input, + inventory::TileLayer, math::{center_offset, TILE_SIZE}, res::Res, CliArgs, @@ -231,15 +232,22 @@ impl App { self.game.world.player.col_en.en.pos.y = wpos.y as i32; } if self.input.lmb_down { - let t = self.game.world.tile_at_mut(mouse_tpos, &self.game.worldgen); - t.mid = 0; - t.fg = 0; - } else if self.input.rmb_down { - let t = self.game.world.tile_at_mut(mouse_tpos, &self.game.worldgen); - if self.game.tile_to_place != 7 { - t.mid = self.game.tile_to_place; - } else { - t.bg = self.game.tile_to_place; + let active_slot = &self.game.inventory.slots[self.game.selected_inv_slot]; + if active_slot.qty != 0 { + let def = &self.game.itemdb.db[active_slot.id as usize]; + let t = self.game.world.tile_at_mut(mouse_tpos, &self.game.worldgen); + match &def.use_action { + crate::inventory::UseAction::PlaceTile { layer, id } => match layer { + TileLayer::Bg => t.bg = *id, + TileLayer::Mid => t.mid = *id, + TileLayer::Fg => t.fg = *id, + }, + crate::inventory::UseAction::RemoveTile { layer } => match layer { + TileLayer::Bg => t.bg = 0, + TileLayer::Mid => t.mid = 0, + TileLayer::Fg => t.fg = 0, + }, + } } } if self.game.camera_offset.y > 643_000 { @@ -262,7 +270,7 @@ impl App { } } } - self.game.update(); + self.game.update(&self.input); } fn do_freecam(&mut self) { @@ -305,6 +313,18 @@ impl App { self.light_map.display(); spr.set_texture(self.light_map.texture(), false); self.rw.draw_with_renderstates(&spr, &rst); + drop(spr); + // Draw ui on top of in-game scene + self.rt.clear(Color::TRANSPARENT); + let ui_dims = Vector2 { + x: (self.rw.size().x / self.scale as u32) as f32, + y: (self.rw.size().y / self.scale as u32) as f32, + }; + self.game.draw_ui(&mut self.rt, &self.res, ui_dims); + self.rt.display(); + let mut spr = Sprite::with_texture(self.rt.texture()); + spr.set_scale((self.scale as f32, self.scale as f32)); + self.rw.draw(&spr); self.sf_egui .do_frame(|ctx| { debug::do_debug_ui( @@ -317,6 +337,14 @@ impl App { }) .unwrap(); self.sf_egui.draw(&mut self.rw, None); + if self.debug.show_atlas { + let atlas = &self.res.atlas.tex; + let size = atlas.size(); + let mut rs = RectangleShape::from_rect(Rect::new(0., 0., size.x as f32, size.y as f32)); + rs.set_fill_color(Color::MAGENTA); + self.rw.draw(&rs); + self.rw.draw(&Sprite::with_texture(atlas)); + } self.rw.display(); } } diff --git a/src/debug.rs b/src/debug.rs index a6655f3..1ad62b1 100644 --- a/src/debug.rs +++ b/src/debug.rs @@ -1,4 +1,4 @@ -use egui_inspect::inspect; +use egui_inspect::{derive::Inspect, inspect}; use sfml::{audio::SoundSource, window::Key}; use crate::{ @@ -11,11 +11,12 @@ use crate::{ tiles::tiledb_edit_ui::tiledb_edit_ui, }; -#[derive(Default, Debug)] +#[derive(Default, Debug, Inspect)] pub struct DebugState { panel: bool, pub freecam: bool, tiledb_edit: bool, + pub show_atlas: bool, } impl DebugState { @@ -30,7 +31,7 @@ impl DebugState { } fn debug_panel_ui( - debug: &mut DebugState, + mut debug: &mut DebugState, mut game: &mut GameState, ctx: &egui::Context, res: &mut Res, @@ -85,7 +86,7 @@ fn debug_panel_ui( ui, scale, game, - debug.tiledb_edit + debug } }); if ui.button("Reload graphics").clicked() { diff --git a/src/game.rs b/src/game.rs index ab2c282..81789e0 100644 --- a/src/game.rs +++ b/src/game.rs @@ -3,16 +3,21 @@ use std::path::PathBuf; use derivative::Derivative; use egui_inspect::derive::Inspect; use sfml::{ - graphics::{Color, RectangleShape, RenderTarget, RenderTexture, Shape, Sprite, Transformable}, - system::Vector2u, + graphics::{ + Color, Rect, RectangleShape, RenderTarget, RenderTexture, Shape, Sprite, Transformable, + }, + system::{Vector2f, Vector2u}, + window::Key, }; use crate::{ graphics::{ScreenSc, ScreenVec}, + input::Input, + inventory::{Inventory, ItemDb}, math::{smoothwave, wp_to_tp, WorldPos}, res::Res, tiles::TileDb, - world::{Tile, TileId, TilePos, World}, + world::{Tile, TilePos, World}, worldgen::Worldgen, }; @@ -22,7 +27,6 @@ pub struct GameState { pub camera_offset: WorldPos, pub world: World, pub gravity: f32, - pub tile_to_place: TileId, pub current_biome: Biome, pub prev_biome: Biome, #[derivative(Debug = "ignore")] @@ -31,6 +35,9 @@ pub struct GameState { pub ambient_light: u8, pub light_sources: Vec, pub tile_db: TileDb, + pub inventory: Inventory, + pub itemdb: ItemDb, + pub selected_inv_slot: usize, } #[derive(Debug, Inspect)] @@ -45,7 +52,22 @@ pub enum Biome { } impl GameState { - pub fn update(&mut self) { + pub fn update(&mut self, input: &Input) { + if input.pressed(Key::Num1) { + self.selected_inv_slot = 0; + } + if input.pressed(Key::Num2) { + self.selected_inv_slot = 1; + } + if input.pressed(Key::Num3) { + self.selected_inv_slot = 2; + } + if input.pressed(Key::Num4) { + self.selected_inv_slot = 3; + } + if input.pressed(Key::Num5) { + self.selected_inv_slot = 4; + } self.world.ticks += 1; } pub(crate) fn draw_world(&mut self, rt: &mut RenderTexture, res: &mut Res) { @@ -75,7 +97,7 @@ impl GameState { } }); } - pub fn draw_entities(&mut self, rw: &mut RenderTexture) { + pub fn draw_entities(&mut self, rt: &mut RenderTexture) { let (x, y, w, h) = self.world.player.col_en.en.xywh(); let mut rect_sh = RectangleShape::new(); rect_sh.set_position(( @@ -83,14 +105,39 @@ impl GameState { (y - self.camera_offset.y as i32) as f32, )); rect_sh.set_size((w as f32, h as f32)); - rw.draw(&rect_sh); + rt.draw(&rect_sh); rect_sh.set_size((2., 2.)); rect_sh.set_fill_color(Color::RED); rect_sh.set_position(( (self.world.player.col_en.en.pos.x - self.camera_offset.x as i32) as f32, (self.world.player.col_en.en.pos.y - self.camera_offset.y as i32) as f32, )); - rw.draw(&rect_sh); + rt.draw(&rect_sh); + } + + pub fn draw_ui(&mut self, rt: &mut RenderTexture, res: &Res, ui_dims: Vector2f) { + let mut s = Sprite::with_texture(&res.atlas.tex); + let mut rs = RectangleShape::from_rect(Rect::new(0., 0., 32., 32.)); + rs.set_outline_color(Color::YELLOW); + rs.set_outline_thickness(1.0); + rs.set_fill_color(Color::TRANSPARENT); + for (i, slot) in self.inventory.slots.iter().enumerate() { + s.set_texture_rect(res.atlas.rects["ui/invframe"].to_sf()); + let pos = ((i * 38) as f32 + 8.0, (ui_dims.y - 38.)); + s.set_position(pos); + rt.draw(&s); + let item_def = &self.itemdb.db[slot.id as usize]; + if let Some(rect) = res.atlas.rects.get(&item_def.graphic_name) { + s.set_texture_rect(rect.to_sf()); + rt.draw(&s); + } else { + log::error!("Missing rect for item {}", item_def.name); + } + if i == self.selected_inv_slot { + rs.set_position(pos); + rt.draw(&rs); + } + } } pub(crate) fn light_pass(&mut self, lightmap: &mut RenderTexture, res: &Res) { @@ -126,13 +173,15 @@ impl GameState { camera_offset: spawn_point, world: World::new(spawn_point, &world_name, path), gravity: 0.55, - tile_to_place: 1, current_biome: Biome::Surface, prev_biome: Biome::Surface, worldgen: Worldgen::from_seed(0), ambient_light: 0, light_sources: Vec::new(), tile_db, + inventory: Inventory::new_debug(), + itemdb: ItemDb::default(), + selected_inv_slot: 0, } } } diff --git a/src/inventory.rs b/src/inventory.rs new file mode 100644 index 0000000..7922a1d --- /dev/null +++ b/src/inventory.rs @@ -0,0 +1,130 @@ +use egui_inspect::derive::Inspect; + +use crate::{math::IntRect, world::TileId}; + +/// We won't have more than 65535 different items +pub type ItemId = u16; +/// We won't have more than 65535 item quantity in a single slot +pub type ItemQty = u16; + +/// Inventory slot +#[derive(Debug, Inspect)] +pub struct Slot { + pub id: ItemId, + pub qty: ItemQty, +} + +#[derive(Debug, Inspect)] +pub struct Inventory { + pub slots: Vec, +} +impl Inventory { + /// A new inventory filled with some debug items + pub(crate) fn new_debug() -> Inventory { + Self { + slots: vec![ + Slot { + id: items::WOOD_PICK, + qty: 1, + }, + Slot { + id: items::DIRT_BLOCK, + qty: 100, + }, + Slot { + id: items::TORCH, + qty: 100, + }, + Slot { + id: items::PLATFORM, + qty: 100, + }, + ], + } + } +} + +#[derive(Debug, Inspect)] +pub struct ItemDef { + pub name: String, + pub graphic_name: String, + pub tex_rect: IntRect, + #[opaque] + pub use_action: UseAction, + pub consumable: bool, +} + +#[derive(Debug, Inspect, PartialEq)] +pub enum TileLayer { + Bg, + Mid, + Fg, +} + +#[derive(Debug)] +pub enum UseAction { + PlaceTile { layer: TileLayer, id: TileId }, + RemoveTile { layer: TileLayer }, +} + +#[derive(Debug, Inspect)] +pub struct ItemDb { + pub db: Vec, +} + +impl Default for ItemDb { + fn default() -> Self { + Self { + db: vec![ + ItemDef { + name: String::from("Dirt Block"), + graphic_name: String::from("tiles/dirt"), + tex_rect: IntRect::default(), + use_action: UseAction::PlaceTile { + layer: TileLayer::Mid, + id: 3, + }, + consumable: true, + }, + ItemDef { + name: String::from("Torch"), + graphic_name: String::from("tiles/torch"), + tex_rect: IntRect::default(), + use_action: UseAction::PlaceTile { + layer: TileLayer::Mid, + id: 4, + }, + consumable: true, + }, + ItemDef { + name: String::from("Platform"), + graphic_name: String::from("tiles/platform"), + tex_rect: IntRect::default(), + use_action: UseAction::PlaceTile { + layer: TileLayer::Mid, + id: 5, + }, + consumable: true, + }, + ItemDef { + name: String::from("Wood Pick"), + graphic_name: String::from("items/woodpick"), + tex_rect: IntRect::default(), + use_action: UseAction::RemoveTile { + layer: TileLayer::Mid, + }, + consumable: true, + }, + ], + } + } +} + +pub mod items { + use super::ItemId; + + pub const DIRT_BLOCK: ItemId = 0; + pub const TORCH: ItemId = 1; + pub const PLATFORM: ItemId = 2; + pub const WOOD_PICK: ItemId = 3; +} diff --git a/src/main.rs b/src/main.rs index c417982..b6fa0d1 100644 --- a/src/main.rs +++ b/src/main.rs @@ -4,6 +4,7 @@ mod debug; mod game; mod graphics; mod input; +mod inventory; mod math; mod player; mod res; diff --git a/src/texture_atlas.rs b/src/texture_atlas.rs index a4535c6..0c2551e 100644 --- a/src/texture_atlas.rs +++ b/src/texture_atlas.rs @@ -20,7 +20,7 @@ impl AtlasBundle { max_height: 4096, allow_rotation: false, border_padding: 0, - texture_padding: 1, + texture_padding: 0, texture_extrusion: 0, trim: true, texture_outlines: false, @@ -34,6 +34,11 @@ impl AtlasBundle { }); let mut rects = HashMap::new(); let mut tex = Texture::new().unwrap(); + log::info!( + "Texture atlas size is: {}x{}", + packer.width(), + packer.height() + ); if !tex.create(packer.width(), packer.height()) { panic!("Failed to create texture"); } diff --git a/src/tiles.rs b/src/tiles.rs index 53ca4b9..fbdbd26 100644 --- a/src/tiles.rs +++ b/src/tiles.rs @@ -19,7 +19,6 @@ pub struct TileDef { pub light: Option, /// Platform behavior: Horizontally passable, vertically passable upwards pub platform: bool, - #[serde(default)] pub graphic_name: String, pub tex_rect: IntRect, } diff --git a/tiles.dat b/tiles.dat index 9e30cc0..f42778f 100644 Binary files a/tiles.dat and b/tiles.dat differ