From 180532ee3e3bb53565ba8801515e5af8beb9994a Mon Sep 17 00:00:00 2001 From: Llywelwyn Date: Tue, 24 Oct 2023 11:13:43 +0100 Subject: [PATCH 01/18] cherry pick -> serde_json saves to bincode --- Cargo.toml | 1 + src/saveload_system.rs | 20 ++++++++++---------- 2 files changed, 11 insertions(+), 10 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 24b6918..e764a60 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,6 +14,7 @@ serde = { version = "1.0.93", features = ["derive"]} serde_json = "1.0.39" toml = "0.5" lazy_static = "1.4.0" +bincode = "1.3.3" [dev-dependencies] criterion = { version = "^0.5" } diff --git a/src/saveload_system.rs b/src/saveload_system.rs index 362b35f..a589413 100644 --- a/src/saveload_system.rs +++ b/src/saveload_system.rs @@ -1,6 +1,5 @@ use super::components::*; use bracket_lib::prelude::*; -use specs::error::NoError; use specs::prelude::*; use specs::saveload::{ DeserializeComponents, @@ -12,11 +11,12 @@ use specs::saveload::{ use std::fs; use std::fs::File; use std::path::Path; +use std::convert::Infallible; macro_rules! serialize_individually { ($ecs:expr, $ser:expr, $data:expr, $($type:ty),*) => { $( - SerializeComponents::>::serialize( + SerializeComponents::>::serialize( &( $ecs.read_storage::<$type>(), ), &$data.0, &$data.1, @@ -55,8 +55,8 @@ pub fn save_game(ecs: &mut World) { { let data = (ecs.entities(), ecs.read_storage::>()); - let writer = File::create("./savegame.json").unwrap(); - let mut serializer = serde_json::Serializer::new(writer); + let writer = File::create("./savegame.bin").unwrap(); + let mut serializer = bincode::Serializer::new(writer, bincode::options()); serialize_individually!( ecs, serializer, @@ -150,13 +150,13 @@ pub fn save_game(ecs: &mut World) { } pub fn does_save_exist() -> bool { - Path::new("./savegame.json").exists() + Path::new("./savegame.bin").exists() } macro_rules! deserialize_individually { ($ecs:expr, $de:expr, $data:expr, $($type:ty),*) => { $( - DeserializeComponents::::deserialize( + DeserializeComponents::::deserialize( &mut ( &mut $ecs.write_storage::<$type>(), ), &$data.0, // entities &mut $data.1, // marker @@ -180,8 +180,8 @@ pub fn load_game(ecs: &mut World) { } } - let data = fs::read_to_string("./savegame.json").unwrap(); - let mut de = serde_json::Deserializer::from_str(&data); + let data = fs::read("./savegame.bin").unwrap(); + let mut de = bincode::Deserializer::with_reader(&*data, bincode::options()); { let mut d = ( @@ -311,7 +311,7 @@ pub fn load_game(ecs: &mut World) { } pub fn delete_save() { - if Path::new("./savegame.json").exists() { - std::fs::remove_file("./savegame.json").expect("Unable to delete file"); + if Path::new("./savegame.bin").exists() { + std::fs::remove_file("./savegame.bin").expect("Unable to delete file"); } } From 9c8f3014911730cd402ae2842fbbb4b7a32cb724 Mon Sep 17 00:00:00 2001 From: Llywelwyn Date: Tue, 24 Oct 2023 23:32:42 +0100 Subject: [PATCH 02/18] Infallible -> NoError --- src/saveload_system.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/saveload_system.rs b/src/saveload_system.rs index a589413..5260b82 100644 --- a/src/saveload_system.rs +++ b/src/saveload_system.rs @@ -8,15 +8,15 @@ use specs::saveload::{ SimpleMarker, SimpleMarkerAllocator, }; +use specs::error::NoError; use std::fs; use std::fs::File; use std::path::Path; -use std::convert::Infallible; macro_rules! serialize_individually { ($ecs:expr, $ser:expr, $data:expr, $($type:ty),*) => { $( - SerializeComponents::>::serialize( + SerializeComponents::>::serialize( &( $ecs.read_storage::<$type>(), ), &$data.0, &$data.1, @@ -156,7 +156,7 @@ pub fn does_save_exist() -> bool { macro_rules! deserialize_individually { ($ecs:expr, $de:expr, $data:expr, $($type:ty),*) => { $( - DeserializeComponents::::deserialize( + DeserializeComponents::::deserialize( &mut ( &mut $ecs.write_storage::<$type>(), ), &$data.0, // entities &mut $data.1, // marker From c73f9a545863de84297faf4fe932314724125a2b Mon Sep 17 00:00:00 2001 From: Llywelwyn Date: Sat, 15 Jun 2024 16:42:59 +0100 Subject: [PATCH 03/18] Revert "cherry pick -> serde_json saves to bincode" This reverts commit 180532ee3e3bb53565ba8801515e5af8beb9994a. --- Cargo.toml | 1 - src/saveload_system.rs | 15 ++++++++------- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index e764a60..24b6918 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,7 +14,6 @@ serde = { version = "1.0.93", features = ["derive"]} serde_json = "1.0.39" toml = "0.5" lazy_static = "1.4.0" -bincode = "1.3.3" [dev-dependencies] criterion = { version = "^0.5" } diff --git a/src/saveload_system.rs b/src/saveload_system.rs index 5260b82..ccdc1ee 100644 --- a/src/saveload_system.rs +++ b/src/saveload_system.rs @@ -1,5 +1,6 @@ use super::components::*; use bracket_lib::prelude::*; +use specs::error::NoError; use specs::prelude::*; use specs::saveload::{ DeserializeComponents, @@ -55,8 +56,8 @@ pub fn save_game(ecs: &mut World) { { let data = (ecs.entities(), ecs.read_storage::>()); - let writer = File::create("./savegame.bin").unwrap(); - let mut serializer = bincode::Serializer::new(writer, bincode::options()); + let writer = File::create("./savegame.json").unwrap(); + let mut serializer = serde_json::Serializer::new(writer); serialize_individually!( ecs, serializer, @@ -150,7 +151,7 @@ pub fn save_game(ecs: &mut World) { } pub fn does_save_exist() -> bool { - Path::new("./savegame.bin").exists() + Path::new("./savegame.json").exists() } macro_rules! deserialize_individually { @@ -180,8 +181,8 @@ pub fn load_game(ecs: &mut World) { } } - let data = fs::read("./savegame.bin").unwrap(); - let mut de = bincode::Deserializer::with_reader(&*data, bincode::options()); + let data = fs::read_to_string("./savegame.json").unwrap(); + let mut de = serde_json::Deserializer::from_str(&data); { let mut d = ( @@ -311,7 +312,7 @@ pub fn load_game(ecs: &mut World) { } pub fn delete_save() { - if Path::new("./savegame.bin").exists() { - std::fs::remove_file("./savegame.bin").expect("Unable to delete file"); + if Path::new("./savegame.json").exists() { + std::fs::remove_file("./savegame.json").expect("Unable to delete file"); } } From 30697a98bb4b4bbe3188f42ea75206929dbf22ba Mon Sep 17 00:00:00 2001 From: Llywelwyn Date: Sat, 15 Jun 2024 16:43:17 +0100 Subject: [PATCH 04/18] rm room_accretion for now --- src/map_builders/mod.rs | 11 +- src/map_builders/room_accretion/consts.rs | 127 ------- src/map_builders/room_accretion/mod.rs | 397 ---------------------- 3 files changed, 1 insertion(+), 534 deletions(-) delete mode 100644 src/map_builders/room_accretion/consts.rs delete mode 100644 src/map_builders/room_accretion/mod.rs diff --git a/src/map_builders/mod.rs b/src/map_builders/mod.rs index b9dcbfe..7b38efe 100644 --- a/src/map_builders/mod.rs +++ b/src/map_builders/mod.rs @@ -1,8 +1,6 @@ use super::{ spawner, Map, Position, Rect, TileType }; use bracket_lib::prelude::*; -mod room_accretion; -use room_accretion::RoomAccretionBuilder; mod bsp_dungeon; use bsp_dungeon::BspDungeonBuilder; mod bsp_interior; @@ -447,7 +445,7 @@ pub fn level_builder( initial_player_level: i32 ) -> BuilderChain { match id { - ID_OVERMAP => room_accretion(), + ID_OVERMAP => overmap_builder(), ID_TOWN => town_builder(id, rng, width, height, 0, initial_player_level), ID_TOWN2 => forest_builder(id, rng, width, height, 1, initial_player_level), ID_TOWN3 => @@ -492,10 +490,3 @@ pub fn level_builder( fn diff(branch_id: i32, lvl_id: i32) -> i32 { return lvl_id - branch_id; } - -fn room_accretion() -> BuilderChain { - let mut builder = BuilderChain::new(false, 110, 64, 64, 0, "room_accretion", "accretion", 0, 1); - builder.start_with(RoomAccretionBuilder::new()); - builder.with(AreaStartingPosition::new(XStart::CENTRE, YStart::CENTRE)); - builder -} diff --git a/src/map_builders/room_accretion/consts.rs b/src/map_builders/room_accretion/consts.rs deleted file mode 100644 index 8c10d16..0000000 --- a/src/map_builders/room_accretion/consts.rs +++ /dev/null @@ -1,127 +0,0 @@ -use lazy_static::lazy_static; -use bracket_lib::prelude::*; - -pub const HEIGHT: usize = 64; -pub const WIDTH: usize = 64; -pub const HALLWAY_CHANCE: f32 = 0.5; -pub const VERTICAL_CORRIDOR_MIN_LENGTH: i32 = 2; -pub const VERTICAL_CORRIDOR_MAX_LENGTH: i32 = 9; -pub const HORIZONTAL_CORRIDOR_MIN_LENGTH: i32 = 5; -pub const HORIZONTAL_CORRIDOR_MAX_LENGTH: i32 = 15; - -pub enum Operator { - LessThan, - GreaterThan, - LessThanEqualTo, - GreaterThanEqualTo, - EqualTo, -} - -impl Operator { - pub fn eval(&self, a: i32, b: i32) -> bool { - match self { - Operator::LessThan => a < b, - Operator::GreaterThan => a > b, - Operator::LessThanEqualTo => a <= b, - Operator::GreaterThanEqualTo => a >= b, - Operator::EqualTo => a == b, - } - } - pub fn string(&self) -> &str { - match self { - Operator::LessThan => "<", - Operator::GreaterThan => ">", - Operator::LessThanEqualTo => "<=", - Operator::GreaterThanEqualTo => ">=", - Operator::EqualTo => "==", - } - } -} - -pub struct CellRules { - pub adjacent_type: i32, - pub into: i32, - pub operator: Operator, - pub n: i32, -} - -impl CellRules { - const fn new(adjacent_type: i32, into: i32, operator: Operator, n: i32) -> CellRules { - CellRules { - adjacent_type, - into, - operator, - n, - } - } -} - -lazy_static! { - pub static ref CA: Vec> = vec![ - vec![CellRules::new(1, 1, Operator::GreaterThanEqualTo, 4)], - vec![ - CellRules::new(0, 0, Operator::GreaterThanEqualTo, 5), - CellRules::new(1, 0, Operator::LessThan, 2) - ] - ]; -} - -#[derive(Debug, Copy, Clone, PartialEq)] -pub enum Direction { - NoDir = -1, - North = 0, - East = 1, - South = 2, - West = 3, -} - -impl Direction { - pub fn transform(&self) -> Point { - match self { - Direction::NoDir => unreachable!("Direction::NoDir should never be transformed"), - Direction::North => Point::new(0, -1), - Direction::East => Point::new(1, 0), - Direction::South => Point::new(0, 1), - Direction::West => Point::new(-1, 0), - } - } - pub fn opposite_dir(&self) -> Direction { - match self { - Direction::NoDir => unreachable!("Direction::NoDir has no opposite."), - Direction::North => Direction::South, - Direction::East => Direction::West, - Direction::South => Direction::North, - Direction::West => Direction::East, - } - } -} - -pub struct DirectionIterator { - current: Direction, -} - -impl DirectionIterator { - pub fn new() -> DirectionIterator { - DirectionIterator { - current: Direction::North, - } - } -} - -impl Iterator for DirectionIterator { - type Item = Direction; - fn next(&mut self) -> Option { - use Direction::*; - let next_direction = match self.current { - North => East, - East => South, - South => West, - West => { - return None; - } - NoDir => unreachable!("Direction::NoDir should never be iterated over."), - }; - self.current = next_direction; - Some(next_direction) - } -} diff --git a/src/map_builders/room_accretion/mod.rs b/src/map_builders/room_accretion/mod.rs deleted file mode 100644 index 13f729b..0000000 --- a/src/map_builders/room_accretion/mod.rs +++ /dev/null @@ -1,397 +0,0 @@ -use super::{ BuilderMap, Map, InitialMapBuilder, TileType, Point }; -use bracket_lib::prelude::*; - -mod consts; -use consts::*; - -/// Room Accretion map builder. -pub struct RoomAccretionBuilder {} - -impl InitialMapBuilder for RoomAccretionBuilder { - #[allow(dead_code)] - fn build_map(&mut self, rng: &mut RandomNumberGenerator, build_data: &mut BuilderMap) { - self.build(rng, build_data); - } -} - -impl RoomAccretionBuilder { - /// Constructor for Room Accretion. - pub fn new() -> Box { - Box::new(RoomAccretionBuilder {}) - } - - fn build(&mut self, rng: &mut RandomNumberGenerator, build_data: &mut BuilderMap) { - accrete_rooms(rng, build_data); - } -} - -fn grid_with_dimensions(h: usize, w: usize, value: i32) -> Vec> { - vec![vec![value; w]; h] -} - -fn in_bounds(row: i32, col: i32, build_data: &BuilderMap) -> bool { - row > 0 && row < build_data.height && col > 0 && col < build_data.width -} - -fn draw_continuous_shape_on_grid( - room: &Vec>, - top_offset: usize, - left_offset: usize, - grid: &mut Vec> -) { - for row in 0..room.len() { - for col in 0..room[0].len() { - if room[row][col] != 0 { - let target_row = row + top_offset; - let target_col = col + left_offset; - if target_row < grid.len() && target_col < grid[0].len() { - grid[target_row][target_col] = room[row][col]; - } - } - } - } -} - -struct Coordinate { - pub location: Point, - pub value: i32, -} - -fn draw_individual_coordinates_on_grid(coordinates: &Vec, grid: &mut Vec>) { - for c in coordinates { - let x = c.location.x as usize; - let y = c.location.y as usize; - if y < grid.len() && x < grid[0].len() { - grid[y][x] = c.value; - } - } -} - -fn get_cell_neighbours( - cells: &Vec>, - row: usize, - col: usize, - h: usize, - w: usize -) -> Vec { - let mut neighbours = Vec::new(); - for x in row.saturating_sub(1)..=std::cmp::min(row + 1, h - 1) { - for y in col.saturating_sub(1)..=std::cmp::min(col + 1, w - 1) { - if x != row || y != col { - neighbours.push(cells[x][y]); - } - } - } - console::log(&format!("neighbours: {:?}", neighbours)); - neighbours -} - -fn make_ca_room(rng: &mut RandomNumberGenerator, build_data: &mut BuilderMap) -> Vec> { - let width = rng.range(5, 12); - let height = rng.range(5, 12); - let mut cells = grid_with_dimensions(height, width, 0); - cells = cells - .into_iter() - .map(|row| { - row.into_iter() - .map(|_| if rng.roll_dice(1, 2) == 1 { 1 } else { 0 }) - .collect() - }) - .collect(); - - let transform_cell = |state: i32, neighbours: &Vec| -> i32 { - let rules: &[CellRules] = &CA[state as usize]; - let mut new_state = state; - for rule in rules { - let n_neighbours = neighbours - .iter() - .filter(|&&neighbour| neighbour == rule.adjacent_type) - .count(); - if rule.operator.eval(n_neighbours as i32, rule.n) { - new_state = rule.into; - } - } - new_state - }; - - for _ in 0..5 { - let mut new_cells = vec![vec![0; width]; height]; - for row in 0..height { - for col in 0..width { - let neighbours = get_cell_neighbours(&cells, row, col, height, width); - let new_state = transform_cell(cells[row][col], &neighbours); - new_cells[row][col] = new_state; - } - } - cells = new_cells; - } - // TODO: Floodfill to keep largest contiguous blob - cells -} - -fn room_fits_at( - hyperspace: Vec>, - top_offset: usize, - left_offset: usize, - build_data: &BuilderMap -) -> bool { - let mut x_dungeon: usize; - let mut y_dungeon: usize; - for y in 0..HEIGHT { - for x in 0..WIDTH { - if hyperspace[y][x] != 2 { - y_dungeon = y + top_offset; - x_dungeon = x + left_offset; - for i in y_dungeon.saturating_sub(1)..=std::cmp::min(y_dungeon + 1, WIDTH - 1) { - for j in x_dungeon.saturating_sub(1)..=std::cmp::min( - x_dungeon + 1, - HEIGHT - 1 - ) { - let pt = build_data.map.xy_idx(i as i32, j as i32); - if - !in_bounds(i as i32, j as i32, &build_data) || - !(build_data.map.tiles[pt] == TileType::Wall) || - build_data.spawn_list.contains(&(pt, "door".to_string())) - { - return false; - } - } - } - } - } - } - return true; -} - -fn direction_of_door( - grid: &Vec>, - row: usize, - col: usize, - build_data: &BuilderMap -) -> Direction { - if grid[row][col] != 0 { - return Direction::NoDir; - } - let mut solution = Direction::NoDir; - let mut dir_iter = DirectionIterator::new(); - for dir in &mut dir_iter { - let new_col = (col as i32) + dir.transform().x; - let new_row = (row as i32) + dir.transform().y; - let opp_col = (col as i32) - dir.transform().x; - let opp_row = (row as i32) - dir.transform().y; - if - in_bounds(new_row, new_col, &build_data) && - in_bounds(opp_row, opp_col, &build_data) && - grid[opp_row as usize][opp_col as usize] != 0 - { - solution = dir; - } - } - return solution; -} - -#[derive(Debug, Copy, Clone, PartialEq)] -pub struct DoorSite { - pub x: i32, - pub y: i32, - pub dir: Direction, -} - -fn choose_random_door_site( - room: Vec>, - rng: &mut RandomNumberGenerator, - build_data: &BuilderMap -) -> Vec> { - let mut grid = grid_with_dimensions(HEIGHT, WIDTH, 0); - let mut door_sites: Vec = Vec::new(); - const LEFT_OFFSET: usize = ((WIDTH as f32) / 2.0) as usize; - const TOP_OFFSET: usize = ((HEIGHT as f32) / 2.0) as usize; - draw_continuous_shape_on_grid(&room, TOP_OFFSET, LEFT_OFFSET, &mut grid); - for row in 0..HEIGHT { - for col in 0..WIDTH { - if grid[row][col] == 0 { - let door_dir = direction_of_door(&grid, row, col, &build_data); - if door_dir == Direction::NoDir { - continue; - } - let mut door_failed = false; - let (mut trace_row, mut trace_col) = ( - (row as i32) + door_dir.transform().y, - (col as i32) + door_dir.transform().x, - ); - let mut i = 0; - while i < 10 && in_bounds(trace_row, trace_col, &build_data) && !door_failed { - if grid[trace_row as usize][trace_col as usize] != 0 { - door_failed = true; - } - trace_col += door_dir.transform().x; - trace_row += door_dir.transform().y; - i += 1; - } - if !door_failed { - // May need more information here. - door_sites.push(DoorSite { - x: col as i32, - y: row as i32, - dir: door_dir, - }); - } - } - } - } - let mut chosen_doors: Vec> = vec![None; 4]; - let mut dir_iter = DirectionIterator::new(); - for dir in &mut dir_iter { - let doors_facing_this_dir: Vec<&DoorSite> = door_sites - .iter() - .filter(|&door| door.dir == dir) - .collect(); - if !doors_facing_this_dir.is_empty() { - let index = rng.range(0, doors_facing_this_dir.len()); - chosen_doors[dir as usize] = Some(*doors_facing_this_dir[index]); - } - } - chosen_doors -} - -fn shuffle(list: &mut Vec, rng: &mut RandomNumberGenerator) { - let len = list.len(); - for i in (1..len).rev() { - let j = rng.range(0, i + 1); - list.swap(i, j); - } -} - -fn clamp(x: T, min: T, max: T) -> T { - if x < min { min } else if x > max { max } else { x } -} - -fn attach_hallway_to( - door_sites: &mut Vec>, - hyperspace: &mut Vec>, - rng: &mut RandomNumberGenerator, - build_data: &BuilderMap -) { - let mut directions = vec![Direction::North, Direction::East, Direction::South, Direction::West]; - shuffle(&mut directions, rng); - let mut hallway_dir: Direction = Direction::NoDir; - for i in 0..4 { - hallway_dir = directions[i]; - if - door_sites[hallway_dir as usize].is_some() && - in_bounds( - door_sites[hallway_dir as usize].unwrap().y + - hallway_dir.transform().y * VERTICAL_CORRIDOR_MAX_LENGTH, - door_sites[hallway_dir as usize].unwrap().x + - hallway_dir.transform().x * HORIZONTAL_CORRIDOR_MAX_LENGTH, - &build_data - ) - { - break; - } - } - let transform = hallway_dir.transform(); - let hallway_len: i32 = match hallway_dir { - Direction::NoDir => { - return; - } - Direction::North | Direction::South => - rng.range(VERTICAL_CORRIDOR_MIN_LENGTH, VERTICAL_CORRIDOR_MAX_LENGTH + 1), - Direction::East | Direction::West => - rng.range(HORIZONTAL_CORRIDOR_MIN_LENGTH, HORIZONTAL_CORRIDOR_MAX_LENGTH + 1), - }; - let mut x = door_sites[hallway_dir as usize].unwrap().x; - let mut y = door_sites[hallway_dir as usize].unwrap().y; - for _i in 0..hallway_len { - if in_bounds(y, x, &build_data) { - hyperspace[y as usize][x as usize] = 1; // Dig out corridor. - } - x += transform.x; - y += transform.y; - } - - y = clamp(y - transform.y, 0, (HEIGHT as i32) - 1); - x = clamp(x - transform.x, 0, (WIDTH as i32) - 1); - - let mut dir_iter = DirectionIterator::new(); - for dir in &mut dir_iter { - if dir != hallway_dir.opposite_dir() { - let door_y = y + dir.transform().y; - let door_x = x + dir.transform().x; - door_sites[dir as usize] = Some(DoorSite { - x: door_x, - y: door_y, - dir, - }); - } else { - door_sites[dir as usize] = None; - } - } - console::log(&format!("door_sites: {:?}", door_sites)); -} - -fn design_room_in_hyperspace( - rng: &mut RandomNumberGenerator, - build_data: &mut BuilderMap -) -> Vec> { - // Project onto hyperspace - let mut hyperspace = grid_with_dimensions(HEIGHT, WIDTH, 0); - let room_type = rng.range(0, 1); - let room = match room_type { - 0 => make_ca_room(rng, build_data), - _ => unreachable!("Invalid room type."), - }; - draw_continuous_shape_on_grid(&room, HEIGHT / 2, WIDTH / 2, &mut hyperspace); - let mut door_sites = choose_random_door_site(room, rng, &build_data); - let roll: f32 = rng.rand(); - if roll < HALLWAY_CHANCE { - attach_hallway_to(&mut door_sites, &mut hyperspace, rng, &build_data); - } - let coords: Vec = door_sites - .iter() - .filter(|&door| door.is_some()) - .map(|&door| Coordinate { - location: Point::new(door.unwrap().x, door.unwrap().y), - value: 2, - }) - .collect(); - draw_individual_coordinates_on_grid(&coords, &mut hyperspace); - hyperspace -} - -fn map_i32_to_tiletype(val: i32, build_data: &mut BuilderMap) -> TileType { - match val { - 0 => TileType::Wall, - 1 => TileType::Floor, - 2 => TileType::Floor, // With door. - _ => unreachable!("Unknown TileType"), - } -} - -fn flatten_hyperspace_into_dungeon( - hyperspace: Vec>, - build_data: &mut BuilderMap -) -> Vec { - let flattened_hyperspace: Vec = hyperspace.into_iter().flatten().collect(); - flattened_hyperspace - .into_iter() - .enumerate() - .map(|(idx, cell)| { - if cell != 0 { - match cell { - 2 => build_data.spawn_list.push((idx, "door".to_string())), - _ => {} - } - map_i32_to_tiletype(cell, build_data) - } else { - build_data.map.tiles[idx % (build_data.map.width as usize)] - } - }) - .collect() -} - -fn accrete_rooms(rng: &mut RandomNumberGenerator, build_data: &mut BuilderMap) { - let hyperspace = design_room_in_hyperspace(rng, build_data); - build_data.map.tiles = flatten_hyperspace_into_dungeon(hyperspace, build_data); - build_data.take_snapshot(); -} From 678636c57dc56960ea3e9fd95cd1020e7d134157 Mon Sep 17 00:00:00 2001 From: Llywelwyn Date: Sat, 15 Jun 2024 16:44:13 +0100 Subject: [PATCH 05/18] Revert "Infallible -> NoError" This reverts commit 9c8f3014911730cd402ae2842fbbb4b7a32cb724. --- src/saveload_system.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/saveload_system.rs b/src/saveload_system.rs index ccdc1ee..3e3debe 100644 --- a/src/saveload_system.rs +++ b/src/saveload_system.rs @@ -9,15 +9,15 @@ use specs::saveload::{ SimpleMarker, SimpleMarkerAllocator, }; -use specs::error::NoError; use std::fs; use std::fs::File; use std::path::Path; +use std::convert::Infallible; macro_rules! serialize_individually { ($ecs:expr, $ser:expr, $data:expr, $($type:ty),*) => { $( - SerializeComponents::>::serialize( + SerializeComponents::>::serialize( &( $ecs.read_storage::<$type>(), ), &$data.0, &$data.1, @@ -157,7 +157,7 @@ pub fn does_save_exist() -> bool { macro_rules! deserialize_individually { ($ecs:expr, $de:expr, $data:expr, $($type:ty),*) => { $( - DeserializeComponents::::deserialize( + DeserializeComponents::::deserialize( &mut ( &mut $ecs.write_storage::<$type>(), ), &$data.0, // entities &mut $data.1, // marker From a7c5d2167cdeda0c852028916da79be3b7412fd1 Mon Sep 17 00:00:00 2001 From: Llywelwyn Date: Sat, 15 Jun 2024 16:46:15 +0100 Subject: [PATCH 06/18] back to serde_json --- src/saveload_system.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/saveload_system.rs b/src/saveload_system.rs index 3e3debe..3a3f733 100644 --- a/src/saveload_system.rs +++ b/src/saveload_system.rs @@ -9,15 +9,15 @@ use specs::saveload::{ SimpleMarker, SimpleMarkerAllocator, }; + use std::fs; use std::fs::File; use std::path::Path; -use std::convert::Infallible; macro_rules! serialize_individually { ($ecs:expr, $ser:expr, $data:expr, $($type:ty),*) => { $( - SerializeComponents::>::serialize( + SerializeComponents::>::serialize( &( $ecs.read_storage::<$type>(), ), &$data.0, &$data.1, @@ -157,7 +157,7 @@ pub fn does_save_exist() -> bool { macro_rules! deserialize_individually { ($ecs:expr, $de:expr, $data:expr, $($type:ty),*) => { $( - DeserializeComponents::::deserialize( + DeserializeComponents::::deserialize( &mut ( &mut $ecs.write_storage::<$type>(), ), &$data.0, // entities &mut $data.1, // marker From 2eaf431942695554a03a086179cf5e7b462c63f7 Mon Sep 17 00:00:00 2001 From: Llywelwyn Date: Sat, 15 Jun 2024 17:24:37 +0100 Subject: [PATCH 07/18] disable show_mapgen for default config --- src/config/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/config/mod.rs b/src/config/mod.rs index 5309151..f86c5df 100644 --- a/src/config/mod.rs +++ b/src/config/mod.rs @@ -34,7 +34,7 @@ impl Default for Config { fn default() -> Self { Config { logging: LogConfig { - show_mapgen: true, + show_mapgen: false, log_combat: false, log_spawning: false, log_ticks: false, From 9719ebbe889aecf2dec102d4acc11657eb769f41 Mon Sep 17 00:00:00 2001 From: Llywelwyn Date: Sat, 15 Jun 2024 17:35:40 +0100 Subject: [PATCH 08/18] docs tldr --- docs/combat_system.txt | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/combat_system.txt b/docs/combat_system.txt index 13a780b..44e2f65 100644 --- a/docs/combat_system.txt +++ b/docs/combat_system.txt @@ -49,3 +49,7 @@ Complex example, with negative AC: bloodstains: if starts on bloodied tile, remove blood + heal, gain xp, grow (little dog -> dog), etc. - You have negative AC, so you roll 1d14 for damage reduction, and get an 8. - The total damage is 6 - 8 = -2, but damage can't be negative, so you take 1 point of damage. + +tl;dr +1. Lower AC is better +2. Aim for 0 AC - it's an important breakpoint. Every point of AC before 0 counts for a lot. From c5106a63b59fcc16bc6de146a5845cdcd3cf80fd Mon Sep 17 00:00:00 2001 From: Llywelwyn Date: Sat, 15 Jun 2024 20:14:38 +0100 Subject: [PATCH 09/18] static inventory keys - items remember their slots this is the biggest refactor of my entire life --- raws/items.json | 71 ++++- src/components.rs | 47 ++++ src/damage_system.rs | 15 +- src/data/messages.rs | 1 + src/effects/triggers.rs | 7 +- src/gui/identify_menu.rs | 124 ++++----- src/gui/mod.rs | 404 ++++++++++++++++------------- src/gui/remove_curse_menu.rs | 63 +++-- src/inventory/collection_system.rs | 35 ++- src/inventory/drop_system.rs | 6 + src/inventory/keyhandling.rs | 153 +++++++++++ src/inventory/mod.rs | 2 + src/invkeys.rs | 60 +++++ src/lib.rs | 1 + src/main.rs | 5 + src/player.rs | 3 + src/raws/item_structs.rs | 1 + src/raws/rawmaster.rs | 49 +++- src/saveload_system.rs | 10 + src/states/state.rs | 8 +- 20 files changed, 758 insertions(+), 307 deletions(-) create mode 100644 src/inventory/keyhandling.rs create mode 100644 src/invkeys.rs diff --git a/raws/items.json b/raws/items.json index 2c0c678..7599afe 100644 --- a/raws/items.json +++ b/raws/items.json @@ -3,9 +3,10 @@ "id": "potion_health", "name": { "name": "potion of health", "plural": "potions of health" }, "renderable": { "glyph": "!", "fg": "#FF00FF", "bg": "#000000", "order": 2 }, + "class": "potion", "weight": 1, "value": 50, - "flags": ["CONSUMABLE", "DESTRUCTIBLE"], + "flags": ["CONSUMABLE", "DESTRUCTIBLE", "STACKABLE"], "effects": { "heal": "4d4+2" }, "magic": { "class": "uncommon", "naming": "potion" } }, @@ -13,9 +14,10 @@ "id": "potion_health_weak", "name": { "name": "potion of lesser health", "plural": "potions of lesser health" }, "renderable": { "glyph": "!", "fg": "#FF00FF", "bg": "#000000", "order": 2 }, + "class": "potion", "weight": 1, "value": 25, - "flags": ["CONSUMABLE", "DESTRUCTIBLE"], + "flags": ["CONSUMABLE", "DESTRUCTIBLE", "STACKABLE"], "effects": { "heal": "2d4+2" }, "magic": { "class": "uncommon", "naming": "potion" } }, @@ -23,27 +25,30 @@ "id": "scroll_identify", "name": { "name": "scroll of identify", "plural": "scrolls of identify" }, "renderable": { "glyph": "?", "fg": "#0FFFFF", "bg": "#000000", "order": 2 }, + "class": "scroll", "weight": 0.5, "value": 100, - "flags": ["CONSUMABLE", "DESTRUCTIBLE", "IDENTIFY"], + "flags": ["CONSUMABLE", "DESTRUCTIBLE", "STACKABLE", "IDENTIFY"], "magic": { "class": "uncommon", "naming": "scroll" } }, { "id": "scroll_removecurse", "name": { "name": "scroll of remove curse", "plural": "scrolls of remove curse" }, "renderable": { "glyph": "?", "fg": "#0FFFFF", "bg": "#000000", "order": 2 }, + "class": "scroll", "weight": 0.5, "value": 200, - "flags": ["CONSUMABLE", "DESTRUCTIBLE", "REMOVE_CURSE"], + "flags": ["CONSUMABLE", "DESTRUCTIBLE", "STACKABLE", "REMOVE_CURSE"], "magic": { "class": "rare", "naming": "scroll" } }, { "id": "scroll_health", "name": { "name": "scroll of healing word", "plural": "scrolls of healing word" }, "renderable": { "glyph": "?", "fg": "#00FFFF", "bg": "#000000", "order": 2 }, + "class": "scroll", "weight": 0.5, "value": 50, - "flags": ["CONSUMABLE", "DESTRUCTIBLE"], + "flags": ["CONSUMABLE", "DESTRUCTIBLE", "STACKABLE"], "effects": { "particle_line": "*;-;#53f06d;75.0;#f9ff9f;100.0", "ranged": "12", "heal": "1d4+2" }, "magic": { "class": "uncommon", "naming": "scroll" } }, @@ -51,9 +56,10 @@ "id": "scroll_mass_health", "name": { "name": "scroll of mass healing word", "plural": "scrolls of mass healing word" }, "renderable": { "glyph": "?", "fg": "#00FFFF", "bg": "#000000", "order": 2 }, + "class": "scroll", "weight": 0.5, "value": 200, - "flags": ["CONSUMABLE", "DESTRUCTIBLE"], + "flags": ["CONSUMABLE", "DESTRUCTIBLE", "STACKABLE"], "effects": { "particle": "*;#53f06d;200.0", "ranged": "12", "aoe": "3", "heal": "1d4+2" }, "magic": { "class": "rare", "naming": "scroll" } }, @@ -61,9 +67,10 @@ "id": "scroll_magicmissile", "name": { "name": "scroll of magic missile", "plural": "scrolls of magic missile" }, "renderable": { "glyph": "?", "fg": "#00FFFF", "bg": "#000000", "order": 2 }, + "class": "scroll", "weight": 0.5, "value": 50, - "flags": ["CONSUMABLE", "DESTRUCTIBLE"], + "flags": ["CONSUMABLE", "DESTRUCTIBLE", "STACKABLE"], "effects": { "particle_line": "*;-;#00b7ff;75.0;#f4fc83;100.0", "ranged": "12", "damage": "3d4+3;magic" }, "magic": { "class": "uncommon", "naming": "scroll" } }, @@ -71,9 +78,10 @@ "id": "scroll_embers", "name": { "name": "scroll of embers", "plural": "scrolls of embers" }, "renderable": { "glyph": "?", "fg": "#00FFFF", "bg": "#000000", "order": 2 }, + "class": "scroll", "weight": 0.5, "value": 100, - "flags": ["CONSUMABLE", "DESTRUCTIBLE"], + "flags": ["CONSUMABLE", "DESTRUCTIBLE", "STACKABLE"], "effects": { "particle": "*;#FFA500;200.0", "ranged": "10", "damage": "4d6;fire", "aoe": "2" }, "magic": { "class": "uncommon", "naming": "scroll" } }, @@ -81,9 +89,10 @@ "id": "scroll_fireball", "name": { "name": "scroll of fireball", "plural": "scrolls of fireball" }, "renderable": { "glyph": "?", "fg": "#00FFFF", "bg": "#000000", "order": 2 }, + "class": "scroll", "weight": 0.5, "value": 200, - "flags": ["CONSUMABLE", "DESTRUCTIBLE"], + "flags": ["CONSUMABLE", "DESTRUCTIBLE", "STACKABLE"], "effects": { "particle_burst": "▓;*;~;#FFA500;#000000;500.0;#ffd381;60.0", "ranged": "10", @@ -96,9 +105,10 @@ "id": "scroll_confusion", "name": { "name": "scroll of confusion", "plural": "scrolls of confusion" }, "renderable": { "glyph": "?", "fg": "#00FFFF", "bg": "#000000", "order": 2 }, + "class": "scroll", "weight": 0.5, "value": 100, - "flags": ["CONSUMABLE", "DESTRUCTIBLE"], + "flags": ["CONSUMABLE", "DESTRUCTIBLE", "STACKABLE"], "effects": { "particle_line": "*;-;#ad56a6;75.0;#cacaca;100.0", "ranged": "10", "confusion": "4" }, "magic": { "class": "uncommon", "naming": "scroll" } }, @@ -106,9 +116,10 @@ "id": "scroll_mass_confusion", "name": { "name": "scroll of mass confusion", "plural": "scrolls of mass confusion" }, "renderable": { "glyph": "?", "fg": "#00FFFF", "bg": "#000000", "order": 2 }, + "class": "scroll", "weight": 0.5, "value": 200, - "flags": ["CONSUMABLE", "DESTRUCTIBLE"], + "flags": ["CONSUMABLE", "DESTRUCTIBLE", "STACKABLE"], "effects": { "particle": "*;#ad56a6;200.0", "ranged": "10", "aoe": "3", "confusion": "3" }, "magic": { "class": "veryrare", "naming": "scroll" } }, @@ -116,9 +127,10 @@ "id": "scroll_magicmap", "name": { "name": "scroll of magic mapping", "plural": "scrolls of magic mapping" }, "renderable": { "glyph": "?", "fg": "#00FFFF", "bg": "#000000", "order": 2 }, + "class": "scroll", "weight": 0.5, "value": 50, - "flags": ["CONSUMABLE", "DESTRUCTIBLE", "MAGICMAP"], + "flags": ["CONSUMABLE", "DESTRUCTIBLE", "STACKABLE", "MAGICMAP"], "effects": {}, "magic": { "class": "common", "naming": "scroll" } }, @@ -126,6 +138,7 @@ "id": "equip_dagger", "name": { "name": "dagger", "plural": "daggers" }, "renderable": { "glyph": ")", "fg": "#808080", "bg": "#000000", "order": 2 }, + "class": "weapon", "weight": 1, "value": 2, "flags": ["EQUIP_MELEE"], @@ -135,6 +148,7 @@ "id": "equip_shortsword", "name": { "name": "shortsword", "plural": "shortswords" }, "renderable": { "glyph": ")", "fg": "#C0C0C0", "bg": "#000000", "order": 2 }, + "class": "weapon", "weight": 2, "value": 10, "flags": ["EQUIP_MELEE"], @@ -144,6 +158,7 @@ "id": "equip_rapier", "name": { "name": "rapier", "plural": "rapiers" }, "renderable": { "glyph": ")", "fg": "#C0C0C0", "bg": "#000000", "order": 2 }, + "class": "weapon", "weight": 2, "value": 10, "flags": ["EQUIP_MELEE"], @@ -153,6 +168,7 @@ "id": "equip_pitchfork", "name": { "name": "pitchfork", "plural": "pitchforks" }, "renderable": { "glyph": ")", "fg": "#C0C0C0", "bg": "#000000", "order": 2 }, + "class": "weapon", "weight": 2, "value": 5, "flags": ["EQUIP_MELEE"], @@ -162,6 +178,7 @@ "id": "equip_sickle", "name": { "name": "sickle", "plural": "sickles" }, "renderable": { "glyph": ")", "fg": "#C0C0C0", "bg": "#000000", "order": 2 }, + "class": "weapon", "weight": 2, "value": 5, "flags": ["EQUIP_MELEE"], @@ -171,6 +188,7 @@ "id": "equip_handaxe", "name": { "name": "handaxe", "plural": "handaxes" }, "renderable": { "glyph": ")", "fg": "#C0C0C0", "bg": "#000000", "order": 2 }, + "class": "weapon", "weight": 2, "value": 5, "flags": ["EQUIP_MELEE"], @@ -180,6 +198,7 @@ "id": "equip_longsword", "name": { "name": "longsword", "plural": "longswords" }, "renderable": { "glyph": ")", "fg": "#FFF8DC", "bg": "#000000", "order": 2 }, + "class": "weapon", "weight": 3, "value": 15, "flags": ["EQUIP_MELEE"], @@ -189,6 +208,7 @@ "id": "equip_smallshield", "name": { "name": "buckler", "plural": "bucklers" }, "renderable": { "glyph": "[", "fg": "#808080", "bg": "#000000", "order": 2 }, + "class": "armour", "weight": 2, "value": 5, "flags": ["EQUIP_SHIELD"], @@ -198,6 +218,7 @@ "id": "equip_mediumshield", "name": { "name": "medium shield", "plural": "medium shields" }, "renderable": { "glyph": "[", "fg": "#C0C0C0", "bg": "#000000", "order": 2 }, + "class": "armour", "weight": 6, "value": 10, "flags": ["EQUIP_SHIELD"], @@ -207,6 +228,7 @@ "id": "equip_largeshield", "name": { "name": "large shield", "plural": "large shields" }, "renderable": { "glyph": "[", "fg": "#FFF8DC", "bg": "#000000", "order": 2 }, + "class": "armour", "weight": 12, "value": 35, "flags": ["EQUIP_SHIELD"], @@ -216,6 +238,7 @@ "id": "equip_body_weakleather", "name": { "name": "leather jacket", "plural": "leather jackets" }, "renderable": { "glyph": "[", "fg": "#aa6000", "bg": "#000000", "order": 2 }, + "class": "armour", "weight": 8, "value": 5, "flags": ["EQUIP_BODY"], @@ -225,6 +248,7 @@ "id": "equip_body_leather", "name": { "name": "leather chestpiece", "plural": "leather chestpiece" }, "renderable": { "glyph": "[", "fg": "#aa6000", "bg": "#000000", "order": 2 }, + "class": "armour", "weight": 10, "value": 10, "flags": ["EQUIP_BODY"], @@ -234,6 +258,7 @@ "id": "equip_body_studdedleather", "name": { "name": "studded leather chestpiece", "plural": "studded leather chestpieces" }, "renderable": { "glyph": "[", "fg": "#aa6000", "bg": "#000000", "order": 2 }, + "class": "armour", "weight": 13, "value": 45, "flags": ["EQUIP_BODY"], @@ -243,6 +268,7 @@ "id": "equip_body_ringmail_o", "name": { "name": "orcish ring mail", "plural": "orcish ring mail" }, "renderable": { "glyph": "[", "fg": "#aa6000", "bg": "#000000", "order": 2 }, + "class": "armour", "weight": 45, "value": 50, "flags": ["EQUIP_BODY"], @@ -252,6 +278,7 @@ "id": "equip_body_ringmail", "name": { "name": "ring mail", "plural": "ring mail" }, "renderable": { "glyph": "[", "fg": "#aa6000", "bg": "#000000", "order": 2 }, + "class": "armour", "weight": 45, "value": 70, "flags": ["EQUIP_BODY"], @@ -261,6 +288,7 @@ "id": "equip_head_leather", "name": { "name": "leather cap", "plural": "leather caps" }, "renderable": { "glyph": "[", "fg": "#aa6000", "bg": "#000000", "order": 2 }, + "class": "armour", "weight": 2, "value": 10, "flags": ["EQUIP_HEAD"], @@ -270,6 +298,7 @@ "id": "equip_head_elvish", "name": { "name": "elvish leather helm", "plural": "elvish leather helms" }, "renderable": { "glyph": "[", "fg": "#aa6000", "bg": "#000000", "order": 2 }, + "class": "armour", "weight": 2, "value": 25, "flags": ["EQUIP_HEAD"], @@ -279,6 +308,7 @@ "id": "equip_head_o", "name": { "name": "orcish helm", "plural": "orcish helm" }, "renderable": { "glyph": "[", "fg": "#aa6000", "bg": "#000000", "order": 2 }, + "class": "armour", "weight": 6, "value": 25, "flags": ["EQUIP_HEAD"], @@ -288,6 +318,7 @@ "id": "equip_head_iron", "name": { "name": "iron helm", "plural": "iron helm" }, "renderable": { "glyph": "[", "fg": "#aa6000", "bg": "#000000", "order": 2 }, + "class": "armour", "weight": 10, "value": 45, "flags": ["EQUIP_HEAD"], @@ -297,6 +328,7 @@ "id": "equip_feet_leather", "name": { "name": "leather shoes", "plural": "leather shoes" }, "renderable": { "glyph": "[", "fg": "#aa6000", "bg": "#000000", "order": 2 }, + "class": "armour", "weight": 2, "value": 10, "flags": ["EQUIP_FEET"] @@ -305,6 +337,7 @@ "id": "equip_feet_elvish", "name": { "name": "elvish leather shoes", "plural": "elvish leather shoes" }, "renderable": { "glyph": "[", "fg": "#aa6000", "bg": "#000000", "order": 2 }, + "class": "armour", "weight": 2, "value": 25, "flags": ["EQUIP_FEET"], @@ -314,6 +347,7 @@ "id": "equip_feet_o", "name": { "name": "orcish boots", "plural": "orcish boots" }, "renderable": { "glyph": "[", "fg": "#aa6000", "bg": "#000000", "order": 2 }, + "class": "armour", "weight": 6, "value": 25, "flags": ["EQUIP_FEET"], @@ -323,6 +357,7 @@ "id": "equip_feet_iron", "name": { "name": "iron boots", "plural": "iron boots" }, "renderable": { "glyph": "[", "fg": "#aa6000", "bg": "#000000", "order": 2 }, + "class": "armour", "weight": 10, "value": 45, "flags": ["EQUIP_FEET"], @@ -332,6 +367,7 @@ "id": "equip_neck_protection", "name": { "name": "amulet of protection", "plural": "amulets of protection" }, "renderable": { "glyph": "\"", "fg": "#aa6000", "bg": "#000000", "order": 2 }, + "class": "amulet", "weight": 1, "value": 200, "flags": ["EQUIP_NECK"], @@ -341,6 +377,7 @@ "id": "equip_back_protection", "name": { "name": "cloak of protection", "plural": "cloaks of protection" }, "renderable": { "glyph": "[", "fg": "#aa6000", "bg": "#000000", "order": 2 }, + "class": "armour", "weight": 1, "value": 200, "flags": ["EQUIP_BACK"], @@ -350,6 +387,7 @@ "id": "wand_magicmissile", "name": { "name": "wand of magic missile", "plural": "wands of magic missile" }, "renderable": { "glyph": "/", "fg": "#00FFFF", "bg": "#000000", "order": 2 }, + "class": "wand", "weight": 2, "value": 100, "flags": ["CHARGES"], @@ -360,6 +398,7 @@ "id": "wand_fireball", "name": { "name": "wand of fireball", "plural": "wands of fireball" }, "renderable": { "glyph": "/", "fg": "#00FFFF", "bg": "#000000", "order": 2 }, + "class": "wand", "weight": 2, "value": 300, "flags": ["CHARGES"], @@ -370,6 +409,7 @@ "id": "wand_confusion", "name": { "name": "wand of confusion", "plural": "wands of confusion" }, "renderable": { "glyph": "/", "fg": "#00FFFF", "bg": "#000000", "order": 2 }, + "class": "wand", "weight": 2, "value": 200, "flags": ["CHARGES"], @@ -380,6 +420,7 @@ "id": "wand_digging", "name": { "name": "wand of digging", "plural": "wands of digging" }, "renderable": { "glyph": "/", "fg": "#00FFFF", "bg": "#000000", "order": 2 }, + "class": "wand", "weight": 2, "value": 300, "flags": ["CHARGES", "DIGGER"], @@ -390,16 +431,18 @@ "id": "food_rations", "name": { "name": "rations", "plural": "rations" }, "renderable": { "glyph": "%", "fg": "#FFA07A", "bg": "#000000", "order": 2 }, + "class": "comestible", "weight": 1, "value": 1, - "flags": ["FOOD", "CONSUMABLE"] + "flags": ["FOOD", "CONSUMABLE", "STACKABLE"] }, { "id": "food_apple", "name": { "name": "apple", "plural": "apples" }, "renderable": { "glyph": "%", "fg": "#00FF00", "bg": "#000000", "order": 2 }, + "class": "comestible", "weight": 0.5, "value": 1, - "flags": ["FOOD", "CONSUMABLE"] + "flags": ["FOOD", "CONSUMABLE", "STACKABLE"] } ] diff --git a/src/components.rs b/src/components.rs index 696eab4..45ca1cf 100644 --- a/src/components.rs +++ b/src/components.rs @@ -258,10 +258,40 @@ pub struct Beatitude { pub known: bool, } +#[derive(Debug, Serialize, Deserialize, Copy, Clone, PartialEq, Eq)] +pub enum ItemType { + Amulet, + Weapon, + Armour, + Comestible, + Scroll, + Spellbook, + Potion, + Ring, + Wand, +} + +impl ItemType { + pub fn string(&self) -> &str { + match self { + ItemType::Amulet => "Amulets", + ItemType::Weapon => "Weapons", + ItemType::Armour => "Armour", + ItemType::Comestible => "Comestibles", + ItemType::Scroll => "Scrolls", + ItemType::Spellbook => "Spellbooks", + ItemType::Potion => "Potions", + ItemType::Ring => "Rings", + ItemType::Wand => "Wands", + } + } +} + #[derive(Component, Debug, Serialize, Deserialize, Clone)] pub struct Item { pub weight: f32, // in lbs pub value: f32, // base + pub category: ItemType, } #[derive(Debug, Serialize, Deserialize, Clone, Eq, PartialEq, Hash)] @@ -618,3 +648,20 @@ pub struct EntityMoved {} #[derive(Component, Debug, Serialize, Deserialize, Clone)] pub struct MultiAttack {} + +#[derive(Component, Debug, Serialize, Deserialize, Clone)] +pub struct Stackable {} + +#[derive(Component, Debug, Serialize, Deserialize, Clone)] +pub struct WantsToRemoveKey {} + +#[derive(Component, Debug, Serialize, Deserialize, Clone)] +pub struct WantsToDelete {} + +#[derive(Component, Debug, Serialize, Deserialize, Clone)] +pub struct Key { + pub idx: usize, +} + +#[derive(Component, Debug, Serialize, Deserialize, Clone)] +pub struct WantsToAssignKey {} diff --git a/src/damage_system.rs b/src/damage_system.rs index a4224a7..b0cc566 100644 --- a/src/damage_system.rs +++ b/src/damage_system.rs @@ -11,6 +11,8 @@ use super::{ Position, Renderable, RunState, + WantsToRemoveKey, + WantsToDelete, }; use bracket_lib::prelude::*; use specs::prelude::*; @@ -65,7 +67,17 @@ pub fn delete_the_dead(ecs: &mut World) { } } } - let (items_to_delete, loot_to_spawn) = handle_dead_entity_items(ecs, &dead); + let (mut items_to_delete, loot_to_spawn) = handle_dead_entity_items(ecs, &dead); + { + let entities = ecs.entities(); + let removekeys = ecs.read_storage::(); + let delete = ecs.read_storage::(); + // Add items marked for deletion to the list, but only if they've already had their + // key assignments handled, to ensurew we don't leave any dangling references behind. + for (e, _d, _r) in (&entities, &delete, !&removekeys).join() { + items_to_delete.push(e); + } + } for loot in loot_to_spawn { crate::raws::spawn_named_entity( &crate::raws::RAWS.lock().unwrap(), @@ -82,6 +94,7 @@ pub fn delete_the_dead(ecs: &mut World) { // For everything that died, increment the event log, and delete. for victim in dead { gamelog::record_event(events::EVENT::Turn(1)); + // TODO: Delete stuff from inventory? This should be handled elsewhere. ecs.delete_entity(victim).expect("Unable to delete."); } } diff --git a/src/data/messages.rs b/src/data/messages.rs index 89e39c8..7175b2a 100644 --- a/src/data/messages.rs +++ b/src/data/messages.rs @@ -25,6 +25,7 @@ pub const NUTRITION_BLESSED: &str = "Delicious"; pub const LEVELUP_PLAYER: &str = "Welcome to experience level"; pub const YOU_PICKUP_ITEM: &str = "You pick up the"; +pub const NO_MORE_KEYS: &str = "Your backpack cannot accomodate any more items"; pub const YOU_DROP_ITEM: &str = "You drop the"; pub const YOU_EQUIP_ITEM: &str = "You equip the"; pub const YOU_REMOVE_ITEM: &str = "You unequip your"; diff --git a/src/effects/triggers.rs b/src/effects/triggers.rs index a82496c..fb54eed 100644 --- a/src/effects/triggers.rs +++ b/src/effects/triggers.rs @@ -33,6 +33,8 @@ use crate::{ KnownSpells, Position, Viewshed, + WantsToRemoveKey, + WantsToDelete, }; use crate::data::messages::*; use bracket_lib::prelude::*; @@ -57,7 +59,10 @@ pub fn item_trigger(source: Option, item: Entity, target: &Targets, ecs: let did_something = event_trigger(source, item, target, ecs); // If it's a consumable, delete it if did_something && ecs.read_storage::().get(item).is_some() { - ecs.entities().delete(item).expect("Failed to delete item"); + let mut removekey = ecs.write_storage::(); + removekey.insert(item, WantsToRemoveKey {}).expect("Unable to insert WantsToRemoveKey"); + let mut delete = ecs.write_storage::(); + delete.insert(item, WantsToDelete {}).expect("Unable to insert WantsToDelete"); } } diff --git a/src/gui/identify_menu.rs b/src/gui/identify_menu.rs index 14e0686..422b2a5 100644 --- a/src/gui/identify_menu.rs +++ b/src/gui/identify_menu.rs @@ -3,7 +3,10 @@ use super::{ item_colour_ecs, obfuscate_name_ecs, print_options, + unique_ecs, + check_key, renderable_colour, + letter_to_option, ItemMenuResult, UniqueInventoryItem, BUC, @@ -19,11 +22,12 @@ use crate::{ Name, ObfuscatedName, Renderable, + Key, states::state::*, }; use bracket_lib::prelude::*; use specs::prelude::*; -use std::collections::BTreeMap; +use std::collections::HashMap; /// Handles the Identify menu. pub fn identify(gs: &mut State, ctx: &mut BTerm) -> (ItemMenuResult, Option) { @@ -37,38 +41,41 @@ pub fn identify(gs: &mut State, ctx: &mut BTerm) -> (ItemMenuResult, Option(); let renderables = gs.ecs.read_storage::(); let beatitudes = gs.ecs.read_storage::(); + let keys = gs.ecs.read_storage::(); let build_identify_iterator = || { - (&entities, &items, &renderables, &names).join().filter(|(item_entity, _i, _r, n)| { - // If not owned by the player, return false. - let mut keep = false; - if let Some(bp) = backpack.get(*item_entity) { - if bp.owner == *player_entity { - keep = true; + (&entities, &items, &renderables, &names, &keys) + .join() + .filter(|(item_entity, _i, _r, n, _k)| { + // If not owned by the player, return false. + let mut keep = false; + if let Some(bp) = backpack.get(*item_entity) { + if bp.owner == *player_entity { + keep = true; + } } - } - // If not equipped by the player, return false. - if let Some(equip) = equipped.get(*item_entity) { - if equip.owner == *player_entity { - keep = true; + // If not equipped by the player, return false. + if let Some(equip) = equipped.get(*item_entity) { + if equip.owner == *player_entity { + keep = true; + } } - } - if !keep { - return false; - } - // If not obfuscated, or already identified, return false. - if - (!obfuscated.get(*item_entity).is_some() || - dm.identified_items.contains(&n.name)) && - beatitudes - .get(*item_entity) - .map(|beatitude| beatitude.known) - .unwrap_or(true) - { - return false; - } - return true; - }) + if !keep { + return false; + } + // If not obfuscated, or already identified, return false. + if + (!obfuscated.get(*item_entity).is_some() || + dm.identified_items.contains(&n.name)) && + beatitudes + .get(*item_entity) + .map(|beatitude| beatitude.known) + .unwrap_or(true) + { + return false; + } + return true; + }) }; // Build list of items to display @@ -91,34 +98,15 @@ pub fn identify(gs: &mut State, ctx: &mut BTerm) -> (ItemMenuResult, Option().get(entity) - { - match beatitude.buc { - BUC::Blessed => 1, - BUC::Uncursed => 2, - BUC::Cursed => 3, - } - } else { - 0 - }; - let unique_item = UniqueInventoryItem { - display_name: super::DisplayName { singular: singular.clone(), plural: plural.clone() }, - rgb: item_colour_ecs(&gs.ecs, entity), - renderables: renderable_colour(&renderables, entity), - glyph: renderable.glyph, - beatitude_status: beatitude_status, - name: name.name.clone(), - }; + let mut player_inventory: super::PlayerInventory = HashMap::new(); + for (entity, _i, renderable, name, key) in build_identify_iterator() { + let unique_item = unique_ecs(&gs.ecs, entity); player_inventory .entry(unique_item) - .and_modify(|(_e, count)| { - *count += 1; + .and_modify(|slot| { + slot.count += 1; }) - .or_insert((entity, 1)); + .or_insert(super::InventorySlot { item: entity, count: 1, idx: key.idx }); } // Get display args let width = get_max_inventory_width(&player_inventory); @@ -133,7 +121,7 @@ pub fn identify(gs: &mut State, ctx: &mut BTerm) -> (ItemMenuResult, Option (ItemMenuResult::NoResponse, None), @@ -141,21 +129,17 @@ pub fn identify(gs: &mut State, ctx: &mut BTerm) -> (ItemMenuResult, Option (ItemMenuResult::Cancel, None), _ => { - let selection = letter_to_option(key); - if selection > -1 && selection < (count as i32) { - let item = player_inventory - .iter() - .nth(selection as usize) - .unwrap().1.0; - gamelog::Logger - ::new() - .append("You identify the") - .colour(item_colour_ecs(&gs.ecs, item)) - .append_n(obfuscate_name_ecs(&gs.ecs, item).0) - .colour(WHITE) - .append("!") - .log(); - return (ItemMenuResult::Selected, Some(item)); + let selection = letter_to_option::letter_to_option(key, ctx.shift); + if selection != -1 && check_key(selection as usize) { + // Get the first entity with a Key {} component that has an idx matching "selection". + let entities = gs.ecs.entities(); + let keyed_items = gs.ecs.read_storage::(); + let backpack = gs.ecs.read_storage::(); + for (e, key, _b) in (&entities, &keyed_items, &backpack).join() { + if key.idx == (selection as usize) { + return (ItemMenuResult::Selected, Some(e)); + } + } } (ItemMenuResult::NoResponse, None) } diff --git a/src/gui/mod.rs b/src/gui/mod.rs index 13e32d0..73c70f5 100644 --- a/src/gui/mod.rs +++ b/src/gui/mod.rs @@ -32,6 +32,9 @@ use super::{ Skills, Viewshed, BUC, + Key, + Item, + ItemType, data::ids::get_local_col, }; use crate::data::entity::CARRY_CAPACITY_PER_STRENGTH; @@ -43,7 +46,9 @@ use crate::data::visuals::{ }; use bracket_lib::prelude::*; use specs::prelude::*; -use std::collections::BTreeMap; +use std::collections::{ HashMap, HashSet }; +use crate::invkeys::check_key; + mod character_creation; mod cheat_menu; mod letter_to_option; @@ -271,41 +276,16 @@ pub fn draw_ui(ecs: &World, ctx: &mut BTerm) { ctx.print_color(20, 20, RGB::named(YELLOW), RGB::named(BLACK), "--- GODMODE: ON ---"); } // Draw equipment - let renderables = ecs.read_storage::(); - let mut equipment: Vec<(String, RGB, RGB, FontCharType)> = Vec::new(); - let entities = ecs.entities(); - for (entity, _equipped, renderable) in (&entities, &equipped, &renderables) - .join() - .filter(|item| item.1.owner == *player_entity) { - equipment.push(( - obfuscate_name_ecs(ecs, entity).0, - RGB::named(item_colour_ecs(ecs, entity)), - renderable.fg, - renderable.glyph, - )); - } let mut y = 1; + let equipment = items(&ecs, Filter::Equipped); if !equipment.is_empty() { ctx.print_color(72, y, RGB::named(BLACK), RGB::named(WHITE), "Equipment"); - let mut j = 0; - for item in equipment { - y += 1; - ctx.set(72, y, RGB::named(YELLOW), RGB::named(BLACK), 97 + (j as FontCharType)); - j += 1; - ctx.set(74, y, item.2, RGB::named(BLACK), item.3); - ctx.print_color(76, y, item.1, RGB::named(BLACK), &item.0); - ctx.print_color( - 76 + &item.0.len() + 1, - y, - RGB::named(WHITE), - RGB::named(BLACK), - "(worn)" - ); - } - y += 2; + y += 1; + y = print_options(&ecs, &equipment, 72, y, ctx); + y += 1; } - // Draw consumables + // Draw backpack ctx.print_color(72, y, RGB::named(BLACK), RGB::named(WHITE), "Backpack"); ctx.print_color( 81, @@ -320,8 +300,8 @@ pub fn draw_ui(ecs: &World, ctx: &mut BTerm) { ) ); y += 1; - let player_inventory = get_player_inventory(&ecs); - y = print_options(&player_inventory, 72, y, ctx).0; + let backpack = items(&ecs, Filter::Backpack); + y = print_options(&ecs, &backpack, 72, y, ctx); // Draw spells - if we have any -- NYI! if let Some(known_spells) = ecs.read_storage::().get(*player_entity) { @@ -505,46 +485,46 @@ pub enum ItemMenuResult { } pub fn print_options( + ecs: &World, inventory: &PlayerInventory, mut x: i32, mut y: i32, ctx: &mut BTerm -) -> (i32, i32) { - let mut j = 0; +) -> i32 { let initial_x: i32 = x; - let mut width: i32 = -1; - for (item, (_e, item_count)) in inventory { + let mut sorted: Vec<_> = inventory.iter().collect(); + sorted.sort_by(|a, b| a.1.idx.cmp(&b.1.idx)); + + for (info, slot) in sorted { x = initial_x; // Print the character required to access this item. i.e. (a) - if j < 26 { - ctx.set(x, y, RGB::named(YELLOW), RGB::named(BLACK), 97 + (j as FontCharType)); + if slot.idx < 26 { + ctx.set(x, y, RGB::named(YELLOW), RGB::named(BLACK), 97 + slot.idx); } else { // If we somehow have more than 26, start using capitals - ctx.set(x, y, RGB::named(YELLOW), RGB::named(BLACK), 65 - 26 + (j as FontCharType)); + ctx.set(x, y, RGB::named(YELLOW), RGB::named(BLACK), 65 - 26 + slot.idx); } x += 2; - let fg = RGB::from_u8(item.renderables.0, item.renderables.1, item.renderables.2); - ctx.set(x, y, fg, RGB::named(BLACK), item.glyph); + let fg = RGB::from_u8(info.renderables.0, info.renderables.1, info.renderables.2); + ctx.set(x, y, fg, RGB::named(BLACK), info.glyph); x += 2; - let fg = RGB::from_u8(item.rgb.0, item.rgb.1, item.rgb.2); - if item_count > &1 { + let fg = RGB::from_u8(info.rgb.0, info.rgb.1, info.rgb.2); + if slot.count > 1 { // If more than one, print the number and pluralise // i.e. (a) 3 daggers - ctx.print_color(x, y, fg, RGB::named(BLACK), item_count); + ctx.print_color(x, y, fg, RGB::named(BLACK), slot.count); x += 2; - ctx.print_color(x, y, fg, RGB::named(BLACK), item.display_name.plural.to_string()); - let this_width = x - initial_x + (item.display_name.plural.len() as i32); - width = if width > this_width { width } else { this_width }; + ctx.print_color(x, y, fg, RGB::named(BLACK), info.display_name.plural.to_string()); } else { - if item.display_name.singular.to_lowercase().ends_with("s") { + if info.display_name.singular.to_lowercase().ends_with("s") { ctx.print_color(x, y, fg, RGB::named(BLACK), "some"); x += 5; } else if ['a', 'e', 'i', 'o', 'u'] .iter() - .any(|&v| item.display_name.singular.to_lowercase().starts_with(v)) + .any(|&v| info.display_name.singular.to_lowercase().starts_with(v)) { // If one and starts with a vowel, print 'an' // i.e. (a) an apple @@ -556,40 +536,54 @@ pub fn print_options( ctx.print_color(x, y, fg, RGB::named(BLACK), "a"); x += 2; } - ctx.print_color(x, y, fg, RGB::named(BLACK), item.display_name.singular.to_string()); - let this_width = x - initial_x + (item.display_name.singular.len() as i32); - width = if width > this_width { width } else { this_width }; + /* + let text = if let Some(worn) = ecs.read_storage::().get(slot.item) { + use crate::EquipmentSlot; + let text = match worn.slot { + EquipmentSlot::Melee | EquipmentSlot::Shield => "being held", + _ => "being worn", + }; + format!("{} ({})", info.display_name.singular.to_string(), text) + } else { + info.display_name.singular.to_string() + }; + */ + let text = info.display_name.singular.to_string(); + ctx.print_color(x, y, fg, RGB::named(BLACK), text); } y += 1; - j += 1; } - return (y, width); + return y; } +const PADDING: i32 = 4; +const SOME: i32 = 4; +const AN: i32 = 2; +const A: i32 = 1; + pub fn get_max_inventory_width(inventory: &PlayerInventory) -> i32 { let mut width: i32 = 0; - for (item, (_e, count)) in inventory { + for (item, slot) in inventory { let mut this_width = item.display_name.singular.len() as i32; - // Clean this up. It should use consts. - this_width += 4; // The spaces before and after the character to select this item, etc. - if count <= &1 { + if slot.count <= 1 { if item.display_name.singular == item.display_name.plural { - this_width += 4; // "some".len + this_width += SOME; } else if ['a', 'e', 'i', 'o', 'u'].iter().any(|&v| item.display_name.singular.starts_with(v)) { - this_width += 2; // "an".len + this_width += AN; } else { - this_width += 1; // "a".len + this_width += A; } } else { - this_width += count.to_string().len() as i32; // i.e. "12".len + this_width = + (item.display_name.plural.len() as i32) + (slot.count.to_string().len() as i32); // i.e. "12".len } width = if width > this_width { width } else { this_width }; } - return width; + return width + PADDING; } // Inside the ECS @@ -636,7 +630,7 @@ pub fn obfuscate_name( if has_beatitude.known { let prefix = match has_beatitude.buc { BUC::Cursed => Some("cursed "), - BUC::Uncursed => None, + BUC::Uncursed => Some("uncursed "), BUC::Blessed => Some("blessed "), }; if prefix.is_some() { @@ -831,13 +825,13 @@ pub fn show_help(ctx: &mut BTerm) -> YesNoResult { } } -#[derive(PartialEq, Eq, PartialOrd, Ord)] +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] struct DisplayName { singular: String, plural: String, } -#[derive(PartialEq, Eq, PartialOrd, Ord)] +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] pub struct UniqueInventoryItem { display_name: DisplayName, rgb: (u8, u8, u8), @@ -847,57 +841,71 @@ pub struct UniqueInventoryItem { name: String, } -pub type PlayerInventory = BTreeMap; +pub struct InventorySlot { + pub item: Entity, + pub count: i32, + pub idx: usize, +} -pub fn get_player_inventory(ecs: &World) -> PlayerInventory { - let player_entity = ecs.fetch::(); - let names = ecs.read_storage::(); - let backpack = ecs.read_storage::(); - let entities = ecs.entities(); - let renderables = ecs.read_storage::(); +pub type PlayerInventory = HashMap; - let mut player_inventory: BTreeMap = BTreeMap::new(); - for (entity, _pack, name, renderable) in (&entities, &backpack, &names, &renderables) - .join() - .filter(|item| item.1.owner == *player_entity) { - // RGB can't be used as a key. This is converting the RGB (tuple of f32) into a tuple of u8s. - let item_colour = item_colour_ecs(ecs, entity); - let renderables = ( - (renderable.fg.r * 255.0) as u8, - (renderable.fg.g * 255.0) as u8, - (renderable.fg.b * 255.0) as u8, - ); - let (singular, plural) = obfuscate_name_ecs(ecs, entity); - let beatitude_status = if let Some(beatitude) = ecs.read_storage::().get(entity) { - match beatitude.buc { - BUC::Blessed => 1, - BUC::Uncursed => 2, - BUC::Cursed => 3, - } - } else { - 0 - }; - let unique_item = UniqueInventoryItem { - display_name: DisplayName { singular: singular.clone(), plural: plural }, - rgb: item_colour, - renderables: renderables, - glyph: renderable.glyph, - beatitude_status: beatitude_status, - name: name.name.clone(), - }; - player_inventory - .entry(unique_item) - .and_modify(|(_e, count)| { - *count += 1; +pub enum Filter { + All, + Backpack, + Equipped, + Category(ItemType), +} + +macro_rules! includeitem { + ($inv:expr, $ecs:expr, $e:expr, $k:expr) => { + $inv.entry(unique_ecs($ecs, $e)) + .and_modify(|slot| { + slot.count += 1; }) - .or_insert((entity, 1)); - } + .or_insert(InventorySlot { + item: $e, + count: 1, + idx: $k.idx, + }); + }; +} - return player_inventory; +pub fn items(ecs: &World, filter: Filter) -> PlayerInventory { + let entities = ecs.entities(); + let keys = ecs.read_storage::(); + let mut inv: PlayerInventory = HashMap::new(); + match filter { + Filter::All => { + for (e, k) in (&entities, &keys).join() { + includeitem!(inv, ecs, e, k); + } + } + Filter::Backpack => { + let backpack = ecs.read_storage::(); + for (e, k, _b) in (&entities, &keys, &backpack).join() { + includeitem!(inv, ecs, e, k); + } + } + Filter::Equipped => { + let equipped = ecs.read_storage::(); + for (e, k, _e) in (&entities, &keys, &equipped).join() { + includeitem!(inv, ecs, e, k); + } + } + Filter::Category(itemtype) => { + let items = ecs.read_storage::(); + for (e, k, _i) in (&entities, &keys, &items) + .join() + .filter(|e| e.2.category == itemtype) { + includeitem!(inv, ecs, e, k); + } + } + } + inv } pub fn show_inventory(gs: &mut State, ctx: &mut BTerm) -> (ItemMenuResult, Option) { - let player_inventory = get_player_inventory(&gs.ecs); + let player_inventory = items(&gs.ecs, Filter::Backpack); let count = player_inventory.len(); let (x_offset, y_offset) = (1, 10); @@ -915,7 +923,7 @@ pub fn show_inventory(gs: &mut State, ctx: &mut BTerm) -> (ItemMenuResult, Optio let y = 3 + y_offset; let width = get_max_inventory_width(&player_inventory); ctx.draw_box(x, y, width + 2, (count + 1) as i32, RGB::named(WHITE), RGB::named(BLACK)); - print_options(&player_inventory, x + 1, y + 1, ctx); + print_options(&gs.ecs, &player_inventory, x + 1, y + 1, ctx); match ctx.key { None => (ItemMenuResult::NoResponse, None), @@ -924,22 +932,23 @@ pub fn show_inventory(gs: &mut State, ctx: &mut BTerm) -> (ItemMenuResult, Optio VirtualKeyCode::Escape => (ItemMenuResult::Cancel, None), _ => { let selection = letter_to_option::letter_to_option(key, ctx.shift); - if selection > -1 && selection < (count as i32) { + if selection != -1 && check_key(selection as usize) { if on_overmap { gamelog::Logger ::new() .append("You can't use items on the overmap.") .log(); } else { - return ( - ItemMenuResult::Selected, - Some( - player_inventory - .iter() - .nth(selection as usize) - .unwrap().1.0 - ), - ); + // Get the first entity with a Key {} component that has idx matching selection + let entities = gs.ecs.entities(); + let keyed_items = gs.ecs.read_storage::(); + let backpack = gs.ecs.read_storage::(); + for (e, key, _b) in (&entities, &keyed_items, &backpack).join() { + if key.idx == (selection as usize) { + return (ItemMenuResult::Selected, Some(e)); + } + } + // TODO: Gamelog about not having selected item? } } (ItemMenuResult::NoResponse, None) @@ -949,7 +958,7 @@ pub fn show_inventory(gs: &mut State, ctx: &mut BTerm) -> (ItemMenuResult, Optio } pub fn drop_item_menu(gs: &mut State, ctx: &mut BTerm) -> (ItemMenuResult, Option) { - let player_inventory = get_player_inventory(&gs.ecs); + let player_inventory = items(&gs.ecs, Filter::Backpack); let count = player_inventory.len(); let (x_offset, y_offset) = (1, 10); @@ -967,7 +976,7 @@ pub fn drop_item_menu(gs: &mut State, ctx: &mut BTerm) -> (ItemMenuResult, Optio let y = 3 + y_offset; let width = get_max_inventory_width(&player_inventory); ctx.draw_box(x, y, width + 2, (count + 1) as i32, RGB::named(WHITE), RGB::named(BLACK)); - print_options(&player_inventory, x + 1, y + 1, ctx); + print_options(&gs.ecs, &player_inventory, x + 1, y + 1, ctx); match ctx.key { None => (ItemMenuResult::NoResponse, None), @@ -975,23 +984,23 @@ pub fn drop_item_menu(gs: &mut State, ctx: &mut BTerm) -> (ItemMenuResult, Optio match key { VirtualKeyCode::Escape => (ItemMenuResult::Cancel, None), _ => { - let selection = letter_to_option(key); - if selection > -1 && selection < (count as i32) { + let selection = letter_to_option::letter_to_option(key, ctx.shift); + if selection != -1 && check_key(selection as usize) { if on_overmap { gamelog::Logger ::new() .append("You can't drop items on the overmap.") .log(); } else { - return ( - ItemMenuResult::Selected, - Some( - player_inventory - .iter() - .nth(selection as usize) - .unwrap().1.0 - ), - ); + // Get the first entity with a Key {} component that has an idx matching "selection". + let entities = gs.ecs.entities(); + let keyed_items = gs.ecs.read_storage::(); + let backpack = gs.ecs.read_storage::(); + for (e, key, _b) in (&entities, &keyed_items, &backpack).join() { + if key.idx == (selection as usize) { + return (ItemMenuResult::Selected, Some(e)); + } + } } } (ItemMenuResult::NoResponse, None) @@ -1001,11 +1010,8 @@ pub fn drop_item_menu(gs: &mut State, ctx: &mut BTerm) -> (ItemMenuResult, Optio } pub fn remove_item_menu(gs: &mut State, ctx: &mut BTerm) -> (ItemMenuResult, Option) { - let player_entity = gs.ecs.fetch::(); - let backpack = gs.ecs.read_storage::(); - let entities = gs.ecs.entities(); - let inventory = (&backpack).join().filter(|item| item.owner == *player_entity); - let count = inventory.count(); + let player_inventory = items(&gs.ecs, Filter::Equipped); + let count = player_inventory.len(); let (x_offset, y_offset) = (1, 10); @@ -1017,38 +1023,11 @@ pub fn remove_item_menu(gs: &mut State, ctx: &mut BTerm) -> (ItemMenuResult, Opt "Unequip what? [aA-zZ][Esc.]" ); - let mut equippable: Vec<(Entity, String)> = Vec::new(); - let mut width = 2; - for (entity, _pack) in (&entities, &backpack) - .join() - .filter(|item| item.1.owner == *player_entity) { - let this_name = &obfuscate_name_ecs(&gs.ecs, entity).0; - let this_width = 5 + this_name.len(); - width = if width > this_width { width } else { this_width }; - equippable.push((entity, this_name.to_string())); - } - let x = 1 + x_offset; - let mut y = 3 + y_offset; - - ctx.draw_box(x, y, width, (count + 1) as i32, RGB::named(WHITE), RGB::named(BLACK)); - y += 1; - - let mut j = 0; - let renderables = gs.ecs.read_storage::(); - for (e, name) in &equippable { - let (mut fg, glyph) = if let Some(renderable) = renderables.get(*e) { - (renderable.fg, renderable.glyph) - } else { - (RGB::named(WHITE), to_cp437('-')) - }; - ctx.set(x + 1, y, RGB::named(YELLOW), RGB::named(BLACK), 97 + (j as FontCharType)); - ctx.set(x + 3, y, fg, RGB::named(BLACK), glyph); - fg = RGB::named(item_colour_ecs(&gs.ecs, *e)); - ctx.print_color(x + 5, y, fg, RGB::named(BLACK), name); - y += 1; - j += 1; - } + let y = 3 + y_offset; + let width = get_max_inventory_width(&player_inventory); + ctx.draw_box(x, y, width + 2, (count + 1) as i32, RGB::named(WHITE), RGB::named(BLACK)); + print_options(&gs.ecs, &player_inventory, x + 1, y + 1, ctx); match ctx.key { None => (ItemMenuResult::NoResponse, None), @@ -1056,9 +1035,17 @@ pub fn remove_item_menu(gs: &mut State, ctx: &mut BTerm) -> (ItemMenuResult, Opt match key { VirtualKeyCode::Escape => (ItemMenuResult::Cancel, None), _ => { - let selection = letter_to_option(key); - if selection > -1 && selection < (count as i32) { - return (ItemMenuResult::Selected, Some(equippable[selection as usize].0)); + let selection = letter_to_option::letter_to_option(key, ctx.shift); + if selection != -1 && check_key(selection as usize) { + // Get the first entity with a Key {} component that has an idx matching "selection". + let entities = gs.ecs.entities(); + let keyed_items = gs.ecs.read_storage::(); + let equipped = gs.ecs.read_storage::(); + for (e, key, _e) in (&entities, &keyed_items, &equipped).join() { + if key.idx == (selection as usize) { + return (ItemMenuResult::Selected, Some(e)); + } + } } (ItemMenuResult::NoResponse, None) } @@ -1458,3 +1445,72 @@ pub fn with_article(name: String) -> String { } format!("a {}", name) } + +pub fn unique( + entity: Entity, + names: &ReadStorage, + obfuscated_names: &ReadStorage, + renderables: &ReadStorage, + beatitudes: &ReadStorage, + magic_items: &ReadStorage, + charges: Option<&ReadStorage>, + dm: &MasterDungeonMap +) -> UniqueInventoryItem { + let item_colour = item_colour(entity, beatitudes); + let (singular, plural) = obfuscate_name( + entity, + names, + magic_items, + obfuscated_names, + beatitudes, + dm, + charges + ); + let (renderables, glyph) = if let Some(renderable) = renderables.get(entity) { + ( + ( + (renderable.fg.r * 255.0) as u8, + (renderable.fg.g * 255.0) as u8, + (renderable.fg.b * 255.0) as u8, + ), + renderable.glyph, + ) + } else { + unreachable!("Item has no renderable component.") + }; + let name = if let Some(name) = names.get(entity) { + name + } else { + unreachable!("Item has no name component.") + }; + let beatitude_status = if let Some(beatitude) = beatitudes.get(entity) { + match beatitude.buc { + BUC::Blessed => 1, + BUC::Uncursed => 2, + BUC::Cursed => 3, + } + } else { + 0 + }; + UniqueInventoryItem { + display_name: DisplayName { singular: singular.clone(), plural }, + rgb: item_colour, + renderables, + glyph, + beatitude_status, + name: name.name.clone(), + } +} + +pub fn unique_ecs(ecs: &World, entity: Entity) -> UniqueInventoryItem { + return unique( + entity, + &ecs.read_storage::(), + &ecs.read_storage::(), + &ecs.read_storage::(), + &ecs.read_storage::(), + &ecs.read_storage::(), + Some(&ecs.read_storage::()), + &ecs.fetch::() + ); +} diff --git a/src/gui/remove_curse_menu.rs b/src/gui/remove_curse_menu.rs index f8d1f14..247fd41 100644 --- a/src/gui/remove_curse_menu.rs +++ b/src/gui/remove_curse_menu.rs @@ -3,9 +3,13 @@ use super::{ item_colour_ecs, obfuscate_name_ecs, print_options, + unique_ecs, renderable_colour, + check_key, + letter_to_option, ItemMenuResult, UniqueInventoryItem, + InventorySlot, }; use crate::{ gamelog, @@ -18,10 +22,11 @@ use crate::{ Renderable, states::state::*, BUC, + Key, }; use bracket_lib::prelude::*; use specs::prelude::*; -use std::collections::BTreeMap; +use std::collections::HashMap; /// Handles the Remove Curse menu. pub fn remove_curse(gs: &mut State, ctx: &mut BTerm) -> (ItemMenuResult, Option) { @@ -33,11 +38,12 @@ pub fn remove_curse(gs: &mut State, ctx: &mut BTerm) -> (ItemMenuResult, Option< let beatitudes = gs.ecs.read_storage::(); let names = gs.ecs.read_storage::(); let renderables = gs.ecs.read_storage::(); + let keys = gs.ecs.read_storage::(); let build_cursed_iterator = || { - (&entities, &items, &beatitudes, &renderables, &names) + (&entities, &items, &beatitudes, &renderables, &names, &keys) .join() - .filter(|(item_entity, _i, b, _r, _n)| { + .filter(|(item_entity, _i, b, _r, _n, _k)| { // Set all items to FALSE initially. let mut keep = false; // If found in the player's backpack, set to TRUE @@ -86,8 +92,8 @@ pub fn remove_curse(gs: &mut State, ctx: &mut BTerm) -> (ItemMenuResult, Option< .log(); return (ItemMenuResult::Selected, Some(item)); } - let mut player_inventory: super::PlayerInventory = BTreeMap::new(); - for (entity, _i, _b, renderable, name) in build_cursed_iterator() { + let mut player_inventory: super::PlayerInventory = HashMap::new(); + for (entity, _i, _b, renderable, name, key) in build_cursed_iterator() { let (singular, plural) = obfuscate_name_ecs(&gs.ecs, entity); let beatitude_status = if let Some(beatitude) = gs.ecs.read_storage::().get(entity) @@ -100,20 +106,17 @@ pub fn remove_curse(gs: &mut State, ctx: &mut BTerm) -> (ItemMenuResult, Option< } else { 0 }; - let unique_item = UniqueInventoryItem { - display_name: super::DisplayName { singular: singular.clone(), plural: plural.clone() }, - rgb: item_colour_ecs(&gs.ecs, entity), - renderables: renderable_colour(&renderables, entity), - glyph: renderable.glyph, - beatitude_status: beatitude_status, - name: name.name.clone(), - }; + let unique_item = unique_ecs(&gs.ecs, entity); player_inventory .entry(unique_item) - .and_modify(|(_e, count)| { - *count += 1; + .and_modify(|slot| { + slot.count += 1; }) - .or_insert((entity, 1)); + .or_insert(InventorySlot { + item: entity, + count: 1, + idx: key.idx, + }); } // Get display args let width = get_max_inventory_width(&player_inventory); @@ -128,7 +131,7 @@ pub fn remove_curse(gs: &mut State, ctx: &mut BTerm) -> (ItemMenuResult, Option< "Decurse which item? [aA-zZ][Esc.]" ); ctx.draw_box(x, y, width + 2, count + 1, RGB::named(WHITE), RGB::named(BLACK)); - print_options(&player_inventory, x + 1, y + 1, ctx); + print_options(&gs.ecs, &player_inventory, x + 1, y + 1, ctx); // Input match ctx.key { None => (ItemMenuResult::NoResponse, None), @@ -136,21 +139,17 @@ pub fn remove_curse(gs: &mut State, ctx: &mut BTerm) -> (ItemMenuResult, Option< match key { VirtualKeyCode::Escape => (ItemMenuResult::Cancel, None), _ => { - let selection = letter_to_option(key); - if selection > -1 && selection < (count as i32) { - let item = player_inventory - .iter() - .nth(selection as usize) - .unwrap().1.0; - gamelog::Logger - ::new() - .append("You decurse the") - .colour(item_colour_ecs(&gs.ecs, item)) - .append_n(obfuscate_name_ecs(&gs.ecs, item).0) - .colour(WHITE) - .append("!") - .log(); - return (ItemMenuResult::Selected, Some(item)); + let selection = letter_to_option::letter_to_option(key, ctx.shift); + if selection != -1 && check_key(selection as usize) { + // Get the first entity with a Key {} component that has an idx matching "selection". + let entities = gs.ecs.entities(); + let keyed_items = gs.ecs.read_storage::(); + let backpack = gs.ecs.read_storage::(); + for (e, key, _b) in (&entities, &keyed_items, &backpack).join() { + if key.idx == (selection as usize) { + return (ItemMenuResult::Selected, Some(e)); + } + } } (ItemMenuResult::NoResponse, None) } diff --git a/src/inventory/collection_system.rs b/src/inventory/collection_system.rs index 2fb7276..5ef9619 100644 --- a/src/inventory/collection_system.rs +++ b/src/inventory/collection_system.rs @@ -12,6 +12,9 @@ use crate::{ ObfuscatedName, Position, WantsToPickupItem, + WantsToAssignKey, + Renderable, + Stackable, }; use specs::prelude::*; use crate::data::messages; @@ -30,9 +33,12 @@ impl<'a> System<'a> for ItemCollectionSystem { WriteStorage<'a, EquipmentChanged>, ReadStorage<'a, MagicItem>, ReadStorage<'a, ObfuscatedName>, + ReadStorage<'a, Renderable>, ReadStorage<'a, Beatitude>, ReadExpect<'a, MasterDungeonMap>, ReadStorage<'a, Charges>, + ReadStorage<'a, WantsToAssignKey>, + ReadStorage<'a, Stackable>, ); fn run(&mut self, data: Self::SystemData) { @@ -45,20 +51,16 @@ impl<'a> System<'a> for ItemCollectionSystem { mut equipment_changed, magic_items, obfuscated_names, + renderables, beatitudes, dm, wands, + wants_key, + stackable, ) = data; - - for pickup in wants_pickup.join() { - positions.remove(pickup.item); - backpack - .insert(pickup.item, InBackpack { owner: pickup.collected_by }) - .expect("Unable to pickup item."); - equipment_changed - .insert(pickup.collected_by, EquipmentChanged {}) - .expect("Unable to insert EquipmentChanged."); - + let mut to_remove: Vec = Vec::new(); + // For every item that wants to be picked up that *isn't* waiting on a key assignment. + for (pickup, _key) in (&wants_pickup, !&wants_key).join() { if pickup.collected_by == *player_entity { gamelog::Logger ::new() @@ -82,8 +84,17 @@ impl<'a> System<'a> for ItemCollectionSystem { .period() .log(); } + positions.remove(pickup.item); + backpack + .insert(pickup.item, InBackpack { owner: pickup.collected_by }) + .expect("Unable to pickup item"); + equipment_changed + .insert(pickup.collected_by, EquipmentChanged {}) + .expect("Unable to insert EquipmentChanged"); + to_remove.push(pickup.collected_by); + } + for item in to_remove.iter() { + wants_pickup.remove(*item); } - - wants_pickup.clear(); } } diff --git a/src/inventory/drop_system.rs b/src/inventory/drop_system.rs index 34084b4..af2d8a2 100644 --- a/src/inventory/drop_system.rs +++ b/src/inventory/drop_system.rs @@ -12,6 +12,7 @@ use crate::{ ObfuscatedName, Position, WantsToDropItem, + WantsToRemoveKey, }; use specs::prelude::*; use crate::data::messages; @@ -34,6 +35,7 @@ impl<'a> System<'a> for ItemDropSystem { ReadStorage<'a, ObfuscatedName>, ReadExpect<'a, MasterDungeonMap>, ReadStorage<'a, Charges>, + WriteStorage<'a, WantsToRemoveKey>, ); fn run(&mut self, data: Self::SystemData) { @@ -50,6 +52,7 @@ impl<'a> System<'a> for ItemDropSystem { obfuscated_names, dm, wands, + mut keys, ) = data; for (entity, to_drop) in (&entities, &wants_drop).join() { @@ -68,6 +71,9 @@ impl<'a> System<'a> for ItemDropSystem { backpack.remove(to_drop.item); if entity == *player_entity { + keys.insert(to_drop.item, WantsToRemoveKey {}).expect( + "Unable to insert WantsToRemoveKey" + ); gamelog::Logger ::new() .append(messages::YOU_DROP_ITEM) diff --git a/src/inventory/keyhandling.rs b/src/inventory/keyhandling.rs new file mode 100644 index 0000000..7194a18 --- /dev/null +++ b/src/inventory/keyhandling.rs @@ -0,0 +1,153 @@ +use crate::{ + gamelog, + gui::unique, + Beatitude, + Charges, + MagicItem, + MasterDungeonMap, + Name, + ObfuscatedName, + Stackable, + Renderable, + WantsToAssignKey, + WantsToRemoveKey, + Key, +}; +use specs::prelude::*; +use crate::data::messages; +use bracket_lib::prelude::*; +use crate::invkeys::*; + +pub struct KeyHandling {} + +const DEBUG_KEYHANDLING: bool = true; + +impl<'a> System<'a> for KeyHandling { + #[allow(clippy::type_complexity)] + type SystemData = ( + Entities<'a>, + WriteStorage<'a, WantsToAssignKey>, + WriteStorage<'a, WantsToRemoveKey>, + WriteStorage<'a, Key>, + ReadStorage<'a, Stackable>, + ReadStorage<'a, Name>, + ReadStorage<'a, ObfuscatedName>, + ReadStorage<'a, Renderable>, + ReadStorage<'a, Beatitude>, + ReadStorage<'a, MagicItem>, + ReadStorage<'a, Charges>, + ReadExpect<'a, MasterDungeonMap>, + ); + + fn run(&mut self, data: Self::SystemData) { + let ( + entities, + mut wants_keys, + mut wants_removekey, + mut keys, + stackable, + names, + obfuscated_names, + renderables, + beatitudes, + magic_items, + wands, + dm, + ) = data; + + // For every entity that wants to be picked up, that still needs a key assigned. + for (e, _wants_key) in (&entities, &wants_keys).join() { + if DEBUG_KEYHANDLING { + console::log(&format!("KEYHANDLING: Assigning key to {:?}", e)); + } + let (stacks, mut handled, unique) = ( + if let Some(_) = stackable.get(e) { true } else { false }, + false, + unique( + e, + &names, + &obfuscated_names, + &renderables, + &beatitudes, + &magic_items, + Some(&wands), + &dm + ), + ); + if stacks { + console::log(&format!("KEYHANDLING: Item is stackable.")); + let maybe_key = item_exists(&unique); + if maybe_key.is_some() { + console::log(&format!("KEYHANDLING: Existing stack found for this item.")); + let key = maybe_key.unwrap(); + keys.insert(e, Key { idx: key }).expect("Unable to insert Key."); + console::log(&format!("KEYHANDLING: Assigned key idx {} to item.", key)); + handled = true; + } + } + if !handled { + console::log( + &format!("KEYHANDLING: Item is not stackable, or no existing stack found.") + ); + if let Some(idx) = assign_next_available() { + console::log( + &format!("KEYHANDLING: Assigned next available index {} to item.", idx) + ); + keys.insert(e, Key { idx }).expect("Unable to insert Key."); + register_stackable(stacks, unique, idx); + } else { + console::log(&format!("KEYHANDLING: No more keys available.")); + gamelog::Logger + ::new() + .append(messages::NO_MORE_KEYS) + .colour(WHITE) + .period() + .log(); + } + } + } + for (e, _wants_key) in (&entities, &wants_removekey).join() { + let idx = keys.get(e).unwrap().idx; + if DEBUG_KEYHANDLING { + console::log(&format!("KEYHANDLING: Removing key from {:?}", e)); + } + // If the item is *not* stackable, then we can just remove the key and clear the index. + if let None = stackable.get(e) { + console::log( + &format!("KEYHANDLING: Item is not stackable, clearing index {}.", idx) + ); + clear_idx(idx); + keys.remove(e); + continue; + } + // If the item *is* stackable, then we need to check if there are any other items that + // share this key assignment, before clearing the index. + console::log( + &format!( + "KEYHANDLING: Item is stackable, checking if any other items share this key." + ) + ); + let mut sole_item_with_key = true; + for (entity, key) in (&entities, &keys).join() { + if entity != e && key.idx == idx { + console::log(&format!("KEYHANDLING: Another item shares index {}", idx)); + sole_item_with_key = false; + break; + } + } + // If no other items shared this key, free up the index. + if sole_item_with_key { + console::log( + &format!("KEYHANDLING: No other items found, clearing index {}.", idx) + ); + clear_idx(idx); + } + // Either way, remove the key component from this item, because we're dropping it. + console::log(&format!("KEYHANDLING: Removing key component from item.")); + keys.remove(e); + } + + wants_removekey.clear(); + wants_keys.clear(); + } +} diff --git a/src/inventory/mod.rs b/src/inventory/mod.rs index eceaccb..76748e0 100644 --- a/src/inventory/mod.rs +++ b/src/inventory/mod.rs @@ -4,6 +4,7 @@ mod equip_system; mod identification_system; mod remove_system; mod use_system; +mod keyhandling; pub use self::{ collection_system::ItemCollectionSystem, @@ -12,4 +13,5 @@ pub use self::{ identification_system::ItemIdentificationSystem, remove_system::ItemRemoveSystem, use_system::ItemUseSystem, + keyhandling::KeyHandling, }; diff --git a/src/invkeys.rs b/src/invkeys.rs new file mode 100644 index 0000000..e52824b --- /dev/null +++ b/src/invkeys.rs @@ -0,0 +1,60 @@ +use std::sync::Mutex; +use std::collections::{ HashMap }; +use specs::prelude::*; +use crate::gui::UniqueInventoryItem; + +lazy_static! { + pub static ref INVKEYS: Mutex> = Mutex::new(HashMap::new()); + pub static ref ASSIGNEDKEYS: Mutex> = Mutex::new(vec![false; 52]); +} + +/// For (de)serialization. +pub fn clone_invkeys() -> HashMap { + let invkeys = INVKEYS.lock().unwrap(); + invkeys.clone() +} +pub fn restore_invkeys(invkeys: HashMap) { + INVKEYS.lock().unwrap().clear(); + INVKEYS.lock().unwrap().extend(invkeys); +} + +pub fn check_key(idx: usize) -> bool { + let lock = ASSIGNEDKEYS.lock().unwrap(); + lock[idx] +} + +pub fn item_exists(item: &UniqueInventoryItem) -> Option { + let invkeys = INVKEYS.lock().unwrap(); + use bracket_lib::prelude::*; + console::log(&format!("{:?}", item)); + if invkeys.contains_key(item) { + Some(*invkeys.get(item).unwrap()) + } else { + None + } +} + +pub fn assign_next_available() -> Option { + let mut lock = ASSIGNEDKEYS.lock().unwrap(); + for (i, key) in lock.iter_mut().enumerate() { + if !*key { + *key = true; + return Some(i); + } + } + None +} + +pub fn register_stackable(stacks: bool, item: UniqueInventoryItem, idx: usize) { + if stacks { + let mut invkeys = INVKEYS.lock().unwrap(); + invkeys.insert(item, idx); + } +} + +pub fn clear_idx(idx: usize) { + let mut lock = ASSIGNEDKEYS.lock().unwrap(); + lock[idx] = false; + let mut invkeys = INVKEYS.lock().unwrap(); + invkeys.retain(|_k, v| *v != idx); +} diff --git a/src/lib.rs b/src/lib.rs index 6bbdd04..e184a58 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -38,6 +38,7 @@ pub mod rex_assets; pub mod spatial; pub mod morgue; pub mod states; +pub mod invkeys; pub use components::*; use particle_system::ParticleBuilder; diff --git a/src/main.rs b/src/main.rs index f430376..fc2c72b 100644 --- a/src/main.rs +++ b/src/main.rs @@ -112,6 +112,11 @@ fn main() -> BError { gs.ecs.register::(); gs.ecs.register::(); gs.ecs.register::(); + gs.ecs.register::(); + gs.ecs.register::(); + gs.ecs.register::(); + gs.ecs.register::(); + gs.ecs.register::(); gs.ecs.register::>(); gs.ecs.register::(); gs.ecs.register::(); diff --git a/src/player.rs b/src/player.rs index dd8ecf3..36af74b 100644 --- a/src/player.rs +++ b/src/player.rs @@ -30,6 +30,7 @@ use super::{ Viewshed, WantsToMelee, WantsToPickupItem, + WantsToAssignKey, get_dest, Destination, DamageType, @@ -633,7 +634,9 @@ fn get_item(ecs: &mut World) -> RunState { return RunState::AwaitingInput; } Some(item) => { + let mut assignkey = ecs.write_storage::(); let mut pickup = ecs.write_storage::(); + assignkey.insert(item, WantsToAssignKey {}).expect("Unable to insert WantsToAssignKey"); pickup .insert(*player_entity, WantsToPickupItem { collected_by: *player_entity, item }) .expect("Unable to insert want to pickup item."); diff --git a/src/raws/item_structs.rs b/src/raws/item_structs.rs index 897d48d..c670e54 100644 --- a/src/raws/item_structs.rs +++ b/src/raws/item_structs.rs @@ -6,6 +6,7 @@ pub struct Item { pub id: String, pub name: Name, pub renderable: Option, + pub class: String, pub weight: Option, pub value: Option, pub equip: Option, diff --git a/src/raws/rawmaster.rs b/src/raws/rawmaster.rs index 813dbe2..0e2b227 100644 --- a/src/raws/rawmaster.rs +++ b/src/raws/rawmaster.rs @@ -66,6 +66,7 @@ macro_rules! apply_flags { "IDENTIFY" => $eb = $eb.with(ProvidesIdentify {}), "DIGGER" => $eb = $eb.with(Digger {}), "MAGICMAP" => $eb = $eb.with(MagicMapper {}), + "STACKABLE" => $eb = $eb.with(Stackable {}), // CAN BE DESTROYED BY DAMAGE "DESTRUCTIBLE" => $eb = $eb.with(Destructible {}), // --- EQUIP SLOTS --- @@ -281,6 +282,7 @@ pub fn spawn_named_item( if known_beatitude && !identified_items.contains(&item_template.name.name) { dm.identified_items.insert(item_template.name.name.clone()); } + let needs_key = is_player_owned(&player_entity, &pos); std::mem::drop(player_entity); std::mem::drop(dm); // -- DROP EVERYTHING THAT INVOLVES THE ECS BEFORE THIS POINT --- @@ -293,9 +295,23 @@ pub fn spawn_named_item( eb = eb.with(Item { weight: item_template.weight.unwrap_or(0.0), value: item_template.value.unwrap_or(0.0), + category: match item_template.class.as_str() { + "amulet" => ItemType::Amulet, + "weapon" => ItemType::Weapon, + "armour" => ItemType::Armour, + "comestible" => ItemType::Comestible, + "scroll" => ItemType::Scroll, + "spellbook" => ItemType::Spellbook, + "potion" => ItemType::Potion, + "ring" => ItemType::Ring, + "wand" => ItemType::Wand, + _ => unreachable!("Unknown item type."), + }, }); eb = spawn_position(pos, eb, key, raws); - + if needs_key { + eb = eb.with(WantsToAssignKey {}); + } if let Some(renderable) = &item_template.renderable { eb = eb.with(get_renderable_component(renderable)); } @@ -392,6 +408,7 @@ pub fn spawn_named_mob( if raws.mob_index.contains_key(key) { let mob_template = &raws.raws.mobs[raws.mob_index[key]]; let mut player_level = 1; + let needs_key; { let pools = ecs.read_storage::(); let player_entity = ecs.fetch::(); @@ -399,12 +416,15 @@ pub fn spawn_named_mob( if let Some(pool) = player_pool { player_level = pool.level; } + needs_key = is_player_owned(&player_entity, &pos); } - let mut eb; // New entity with a position, name, combatstats, and viewshed eb = ecs.create_entity().marked::>(); eb = spawn_position(pos, eb, key, raws); + if needs_key { + eb = eb.with(WantsToAssignKey {}); + } eb = eb.with(Name { name: mob_template.name.clone(), plural: mob_template.name.clone() }); eb = eb.with(Viewshed { visible_tiles: Vec::new(), @@ -632,10 +652,18 @@ pub fn spawn_named_prop( pos: SpawnType ) -> Option { if raws.prop_index.contains_key(key) { + let needs_key; + { + let player_entity = ecs.fetch::(); + needs_key = is_player_owned(&player_entity, &pos); + } // ENTITY BUILDER PREP let prop_template = &raws.raws.props[raws.prop_index[key]]; let mut eb = ecs.create_entity().marked::>(); eb = spawn_position(pos, eb, key, raws); + if needs_key { + eb = eb.with(WantsToAssignKey {}); + } // APPLY MANDATORY COMPONENTS FOR A PROP: // - Name // - Prop {} @@ -686,6 +714,23 @@ fn spawn_position<'a>( eb } +fn is_player_owned(player: &Entity, pos: &SpawnType) -> bool { + match pos { + SpawnType::Carried { by } => { + if by == player { + return true; + } + } + SpawnType::Equipped { by } => { + if by == player { + return true; + } + } + _ => {} + } + false +} + fn get_renderable_component( renderable: &super::item_structs::Renderable ) -> crate::components::Renderable { diff --git a/src/saveload_system.rs b/src/saveload_system.rs index 3a3f733..f3b284d 100644 --- a/src/saveload_system.rs +++ b/src/saveload_system.rs @@ -99,6 +99,7 @@ pub fn save_game(ecs: &mut World) { IntrinsicChanged, Intrinsics, Item, + Key, KnownSpells, LootTable, MagicItem, @@ -128,17 +129,21 @@ pub fn save_game(ecs: &mut World) { SpawnParticleBurst, SpawnParticleLine, SpawnParticleSimple, + Stackable, TakingTurn, Telepath, ToHitBonus, Viewshed, Charges, WantsToApproach, + WantsToAssignKey, + WantsToDelete, WantsToDropItem, WantsToFlee, WantsToMelee, WantsToPickupItem, WantsToRemoveItem, + WantsToRemoveKey, WantsToUseItem, SerializationHelper, DMSerializationHelper @@ -232,6 +237,7 @@ pub fn load_game(ecs: &mut World) { IntrinsicChanged, Intrinsics, Item, + Key, KnownSpells, LootTable, MagicItem, @@ -261,17 +267,21 @@ pub fn load_game(ecs: &mut World) { SpawnParticleBurst, SpawnParticleLine, SpawnParticleSimple, + Stackable, TakingTurn, Telepath, ToHitBonus, Viewshed, Charges, WantsToApproach, + WantsToAssignKey, + WantsToDelete, WantsToDropItem, WantsToFlee, WantsToMelee, WantsToPickupItem, WantsToRemoveItem, + WantsToRemoveKey, WantsToUseItem, SerializationHelper, DMSerializationHelper diff --git a/src/states/state.rs b/src/states/state.rs index 0217cca..3dde32b 100644 --- a/src/states/state.rs +++ b/src/states/state.rs @@ -64,12 +64,13 @@ impl State { fn resolve_entity_decisions(&mut self) { let mut trigger_system = trigger_system::TriggerSystem {}; - let mut inventory_system = inventory::ItemCollectionSystem {}; let mut item_equip_system = inventory::ItemEquipSystem {}; let mut item_use_system = inventory::ItemUseSystem {}; let mut item_drop_system = inventory::ItemDropSystem {}; let mut item_remove_system = inventory::ItemRemoveSystem {}; + let mut inventory_system = inventory::ItemCollectionSystem {}; let mut item_id_system = inventory::ItemIdentificationSystem {}; + let mut key_system = inventory::KeyHandling {}; let mut melee_system = MeleeCombatSystem {}; trigger_system.run_now(&self.ecs); inventory_system.run_now(&self.ecs); @@ -78,6 +79,7 @@ impl State { item_drop_system.run_now(&self.ecs); item_remove_system.run_now(&self.ecs); item_id_system.run_now(&self.ecs); + key_system.run_now(&self.ecs); melee_system.run_now(&self.ecs); effects::run_effects_queue(&mut self.ecs); @@ -342,7 +344,11 @@ impl GameState for State { gui::ItemMenuResult::NoResponse => {} gui::ItemMenuResult::Selected => { let item_entity = result.1.unwrap(); + let mut removekey = self.ecs.write_storage::(); let mut intent = self.ecs.write_storage::(); + removekey + .insert(item_entity, WantsToRemoveKey {}) + .expect("Unable to insert WantsToRemoveKey"); intent .insert(*self.ecs.fetch::(), WantsToDropItem { item: item_entity, From ba5d120fef177083b7a8bbd56b5b8d62b38882a3 Mon Sep 17 00:00:00 2001 From: Llywelwyn Date: Sat, 15 Jun 2024 20:40:51 +0100 Subject: [PATCH 10/18] fix warns, bump ver --- Cargo.toml | 2 +- src/gui/identify_menu.rs | 5 +---- src/gui/mod.rs | 4 ++-- src/gui/remove_curse_menu.rs | 16 +--------------- src/inventory/collection_system.rs | 6 ------ src/invkeys.rs | 3 +-- 6 files changed, 6 insertions(+), 30 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 24b6918..4ef01ec 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "rust-rl" -version = "0.1.1" +version = "0.1.2" edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html diff --git a/src/gui/identify_menu.rs b/src/gui/identify_menu.rs index 422b2a5..31ce8d7 100644 --- a/src/gui/identify_menu.rs +++ b/src/gui/identify_menu.rs @@ -5,11 +5,8 @@ use super::{ print_options, unique_ecs, check_key, - renderable_colour, letter_to_option, ItemMenuResult, - UniqueInventoryItem, - BUC, }; use crate::{ gamelog, @@ -99,7 +96,7 @@ pub fn identify(gs: &mut State, ctx: &mut BTerm) -> (ItemMenuResult, Option (ItemMenuResult, Option< return (ItemMenuResult::Selected, Some(item)); } let mut player_inventory: super::PlayerInventory = HashMap::new(); - for (entity, _i, _b, renderable, name, key) in build_cursed_iterator() { - let (singular, plural) = obfuscate_name_ecs(&gs.ecs, entity); - let beatitude_status = if - let Some(beatitude) = gs.ecs.read_storage::().get(entity) - { - match beatitude.buc { - BUC::Blessed => 1, - BUC::Uncursed => 2, - BUC::Cursed => 3, - } - } else { - 0 - }; + for (entity, _i, _b, _r, _n, key) in build_cursed_iterator() { let unique_item = unique_ecs(&gs.ecs, entity); player_inventory .entry(unique_item) diff --git a/src/inventory/collection_system.rs b/src/inventory/collection_system.rs index 5ef9619..70fb25c 100644 --- a/src/inventory/collection_system.rs +++ b/src/inventory/collection_system.rs @@ -13,8 +13,6 @@ use crate::{ Position, WantsToPickupItem, WantsToAssignKey, - Renderable, - Stackable, }; use specs::prelude::*; use crate::data::messages; @@ -33,12 +31,10 @@ impl<'a> System<'a> for ItemCollectionSystem { WriteStorage<'a, EquipmentChanged>, ReadStorage<'a, MagicItem>, ReadStorage<'a, ObfuscatedName>, - ReadStorage<'a, Renderable>, ReadStorage<'a, Beatitude>, ReadExpect<'a, MasterDungeonMap>, ReadStorage<'a, Charges>, ReadStorage<'a, WantsToAssignKey>, - ReadStorage<'a, Stackable>, ); fn run(&mut self, data: Self::SystemData) { @@ -51,12 +47,10 @@ impl<'a> System<'a> for ItemCollectionSystem { mut equipment_changed, magic_items, obfuscated_names, - renderables, beatitudes, dm, wands, wants_key, - stackable, ) = data; let mut to_remove: Vec = Vec::new(); // For every item that wants to be picked up that *isn't* waiting on a key assignment. diff --git a/src/invkeys.rs b/src/invkeys.rs index e52824b..2cee2f4 100644 --- a/src/invkeys.rs +++ b/src/invkeys.rs @@ -1,6 +1,5 @@ use std::sync::Mutex; -use std::collections::{ HashMap }; -use specs::prelude::*; +use std::collections::HashMap; use crate::gui::UniqueInventoryItem; lazy_static! { From f494efbf3f82525f592737e70af5f09e7d6711b9 Mon Sep 17 00:00:00 2001 From: Llywelwyn Date: Sat, 15 Jun 2024 20:41:21 +0100 Subject: [PATCH 11/18] bump ver --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 4ef01ec..55d7645 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "rust-rl" -version = "0.1.2" +version = "0.1.4" edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html From d465592c0fc52dcb96505c2f20d12d10f607b4fd Mon Sep 17 00:00:00 2001 From: Llywelwyn Date: Sun, 16 Jun 2024 10:31:06 +0100 Subject: [PATCH 12/18] abstracts ui drawing --- src/gui/mod.rs | 103 +++++++++++++++++++++++-------------------------- 1 file changed, 48 insertions(+), 55 deletions(-) diff --git a/src/gui/mod.rs b/src/gui/mod.rs index 5a50698..c28ff0b 100644 --- a/src/gui/mod.rs +++ b/src/gui/mod.rs @@ -106,6 +106,50 @@ pub fn draw_lerping_bar( ctx.print(sx + width, sy, "]"); } +fn draw_xp(ctx: &mut BTerm, pt: Point, pool: &Pools) { + ctx.print_color( + pt.x, + pt.y, + RGB::named(WHITE), + RGB::named(BLACK), + format!("XP{}/{}", pool.level, pool.xp) + ); +} + +fn calc_ac(ecs: &World, skills: &Skills, stats: &Pools, attr: &Attributes) -> i32 { + let skill_ac_bonus = gamesystem::skill_bonus(Skill::Defence, skills); + let mut armour_ac_bonus = 0; + let equipped = ecs.read_storage::(); + let ac = ecs.read_storage::(); + let player_entity = ecs.fetch::(); + for (wielded, ac) in (&equipped, &ac).join() { + if wielded.owner == *player_entity { + armour_ac_bonus += ac.amount; + } + } + stats.bac - attr.dexterity.bonus / 2 - skill_ac_bonus - armour_ac_bonus +} + +fn draw_ac(ctx: &mut BTerm, pt: Point, ac: i32) { + ctx.print_color(pt.x, pt.y, RGB::named(PINK), RGB::named(BLACK), "AC"); + ctx.print_color(pt.x + 2, pt.y, RGB::named(WHITE), RGB::named(BLACK), ac); +} + +fn draw_attributes(ctx: &mut BTerm, pt: Point, a: &Attributes) { + ctx.print_color(pt.x, pt.y, RGB::named(RED), RGB::named(BLACK), "STR"); + ctx.print_color(pt.x + 3, pt.y, RGB::named(WHITE), RGB::named(BLACK), a.strength.base); + ctx.print_color(pt.x + 7, pt.y, RGB::named(GREEN), RGB::named(BLACK), "DEX"); + ctx.print_color(pt.x + 10, pt.y, RGB::named(WHITE), RGB::named(BLACK), a.dexterity.base); + ctx.print_color(pt.x + 14, pt.y, RGB::named(ORANGE), RGB::named(BLACK), "CON"); + ctx.print_color(pt.x + 17, pt.y, RGB::named(WHITE), RGB::named(BLACK), a.constitution.base); + ctx.print_color(pt.x, 54, RGB::named(CYAN), RGB::named(BLACK), "INT"); + ctx.print_color(pt.x + 3, pt.y + 1, RGB::named(WHITE), RGB::named(BLACK), a.intelligence.base); + ctx.print_color(pt.x + 7, pt.y + 1, RGB::named(YELLOW), RGB::named(BLACK), "WIS"); + ctx.print_color(pt.x + 10, pt.y + 1, RGB::named(WHITE), RGB::named(BLACK), a.wisdom.base); + ctx.print_color(pt.x + 14, pt.y + 1, RGB::named(PURPLE), RGB::named(BLACK), "CHA"); + ctx.print_color(pt.x + 17, pt.y + 1, RGB::named(WHITE), RGB::named(BLACK), a.charisma.base); +} + pub fn draw_ui(ecs: &World, ctx: &mut BTerm) { // Render stats let pools = ecs.read_storage::(); @@ -142,61 +186,9 @@ pub fn draw_ui(ecs: &World, ctx: &mut BTerm) { RGB::named(BLUE), RGB::named(BLACK) ); - // Draw AC - let skill_ac_bonus = gamesystem::skill_bonus(Skill::Defence, &*skills); - let mut armour_ac_bonus = 0; - let equipped = ecs.read_storage::(); - let ac = ecs.read_storage::(); - let player_entity = ecs.fetch::(); - for (wielded, ac) in (&equipped, &ac).join() { - if wielded.owner == *player_entity { - armour_ac_bonus += ac.amount; - } - } - let armour_class = - stats.bac - attributes.dexterity.bonus / 2 - skill_ac_bonus - armour_ac_bonus; - ctx.print_color(26, 53, RGB::named(PINK), RGB::named(BLACK), "AC"); - ctx.print_color(28, 53, RGB::named(WHITE), RGB::named(BLACK), armour_class); - // Draw level - ctx.print_color( - 26, - 54, - RGB::named(WHITE), - RGB::named(BLACK), - format!("XP{}/{}", stats.level, stats.xp) - ); - // Draw attributes - let x = 38; - ctx.print_color(x, 53, RGB::named(RED), RGB::named(BLACK), "STR"); - ctx.print_color(x + 3, 53, RGB::named(WHITE), RGB::named(BLACK), attributes.strength.base); - ctx.print_color(x + 7, 53, RGB::named(GREEN), RGB::named(BLACK), "DEX"); - ctx.print_color( - x + 10, - 53, - RGB::named(WHITE), - RGB::named(BLACK), - attributes.dexterity.base - ); - ctx.print_color(x + 14, 53, RGB::named(ORANGE), RGB::named(BLACK), "CON"); - ctx.print_color( - x + 17, - 53, - RGB::named(WHITE), - RGB::named(BLACK), - attributes.constitution.base - ); - ctx.print_color(x, 54, RGB::named(CYAN), RGB::named(BLACK), "INT"); - ctx.print_color( - x + 3, - 54, - RGB::named(WHITE), - RGB::named(BLACK), - attributes.intelligence.base - ); - ctx.print_color(x + 7, 54, RGB::named(YELLOW), RGB::named(BLACK), "WIS"); - ctx.print_color(x + 10, 54, RGB::named(WHITE), RGB::named(BLACK), attributes.wisdom.base); - ctx.print_color(x + 14, 54, RGB::named(PURPLE), RGB::named(BLACK), "CHA"); - ctx.print_color(x + 17, 54, RGB::named(WHITE), RGB::named(BLACK), attributes.charisma.base); + draw_ac(ctx, Point::new(26, 53), calc_ac(ecs, skills, stats, attributes)); + draw_xp(ctx, Point::new(26, 54), stats); + draw_attributes(ctx, Point::new(38, 53), attributes); // Draw hunger match hunger.state { HungerState::Satiated => { @@ -247,6 +239,7 @@ pub fn draw_ui(ecs: &World, ctx: &mut BTerm) { } } // Burden + let player_entity = ecs.fetch::(); if let Some(burden) = burden.get(*player_entity) { match burden.level { crate::BurdenLevel::Burdened => { From 6324449c1601e85121494bab73cdb88c3368407a Mon Sep 17 00:00:00 2001 From: Llywelwyn Date: Mon, 17 Jun 2024 23:19:20 +0100 Subject: [PATCH 13/18] draw_hunger() --- src/gui/mod.rs | 101 +++++++++++++++++++++++++------------------------ 1 file changed, 52 insertions(+), 49 deletions(-) diff --git a/src/gui/mod.rs b/src/gui/mod.rs index c28ff0b..bde2cf0 100644 --- a/src/gui/mod.rs +++ b/src/gui/mod.rs @@ -150,6 +150,57 @@ fn draw_attributes(ctx: &mut BTerm, pt: Point, a: &Attributes) { ctx.print_color(pt.x + 17, pt.y + 1, RGB::named(WHITE), RGB::named(BLACK), a.charisma.base); } +fn draw_hunger(ctx: &mut BTerm, pt: Point, hunger: &HungerClock) { + match hunger.state { + HungerState::Satiated => { + ctx.print_color_right( + 70, + 53, + get_hunger_colour(hunger.state), + RGB::named(BLACK), + "Satiated" + ); + } + HungerState::Normal => {} + HungerState::Hungry => { + ctx.print_color_right( + 70, + 53, + get_hunger_colour(hunger.state), + RGB::named(BLACK), + "Hungry" + ); + } + HungerState::Weak => { + ctx.print_color_right( + 70, + 53, + get_hunger_colour(hunger.state), + RGB::named(BLACK), + "Weak" + ); + } + HungerState::Fainting => { + ctx.print_color_right( + 70, + 53, + get_hunger_colour(hunger.state), + RGB::named(BLACK), + "Fainting" + ); + } + HungerState::Starving => { + ctx.print_color_right( + 70, + 53, + get_hunger_colour(hunger.state), + RGB::named(BLACK), + "Starving" + ); + } + } +} + pub fn draw_ui(ecs: &World, ctx: &mut BTerm) { // Render stats let pools = ecs.read_storage::(); @@ -189,55 +240,7 @@ pub fn draw_ui(ecs: &World, ctx: &mut BTerm) { draw_ac(ctx, Point::new(26, 53), calc_ac(ecs, skills, stats, attributes)); draw_xp(ctx, Point::new(26, 54), stats); draw_attributes(ctx, Point::new(38, 53), attributes); - // Draw hunger - match hunger.state { - HungerState::Satiated => { - ctx.print_color_right( - 70, - 53, - get_hunger_colour(hunger.state), - RGB::named(BLACK), - "Satiated" - ); - } - HungerState::Normal => {} - HungerState::Hungry => { - ctx.print_color_right( - 70, - 53, - get_hunger_colour(hunger.state), - RGB::named(BLACK), - "Hungry" - ); - } - HungerState::Weak => { - ctx.print_color_right( - 70, - 53, - get_hunger_colour(hunger.state), - RGB::named(BLACK), - "Weak" - ); - } - HungerState::Fainting => { - ctx.print_color_right( - 70, - 53, - get_hunger_colour(hunger.state), - RGB::named(BLACK), - "Fainting" - ); - } - HungerState::Starving => { - ctx.print_color_right( - 70, - 53, - get_hunger_colour(hunger.state), - RGB::named(BLACK), - "Starving" - ); - } - } + draw_hunger(ctx, Point::new(70, 53), hunger); // Burden let player_entity = ecs.fetch::(); if let Some(burden) = burden.get(*player_entity) { From 99c17f85213c3f5fbaa8d369bdc6bed75bfab696 Mon Sep 17 00:00:00 2001 From: Llywelwyn Date: Mon, 17 Jun 2024 23:22:30 +0100 Subject: [PATCH 14/18] draw hunger uses Point --- src/gui/mod.rs | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/src/gui/mod.rs b/src/gui/mod.rs index bde2cf0..7604527 100644 --- a/src/gui/mod.rs +++ b/src/gui/mod.rs @@ -154,8 +154,8 @@ fn draw_hunger(ctx: &mut BTerm, pt: Point, hunger: &HungerClock) { match hunger.state { HungerState::Satiated => { ctx.print_color_right( - 70, - 53, + pt.x, + pt.y, get_hunger_colour(hunger.state), RGB::named(BLACK), "Satiated" @@ -164,8 +164,8 @@ fn draw_hunger(ctx: &mut BTerm, pt: Point, hunger: &HungerClock) { HungerState::Normal => {} HungerState::Hungry => { ctx.print_color_right( - 70, - 53, + pt.x, + pt.y, get_hunger_colour(hunger.state), RGB::named(BLACK), "Hungry" @@ -173,8 +173,8 @@ fn draw_hunger(ctx: &mut BTerm, pt: Point, hunger: &HungerClock) { } HungerState::Weak => { ctx.print_color_right( - 70, - 53, + pt.x, + pt.y, get_hunger_colour(hunger.state), RGB::named(BLACK), "Weak" @@ -182,8 +182,8 @@ fn draw_hunger(ctx: &mut BTerm, pt: Point, hunger: &HungerClock) { } HungerState::Fainting => { ctx.print_color_right( - 70, - 53, + pt.x, + pt.y, get_hunger_colour(hunger.state), RGB::named(BLACK), "Fainting" @@ -191,8 +191,8 @@ fn draw_hunger(ctx: &mut BTerm, pt: Point, hunger: &HungerClock) { } HungerState::Starving => { ctx.print_color_right( - 70, - 53, + pt.x, + pt.y, get_hunger_colour(hunger.state), RGB::named(BLACK), "Starving" From bdcd55c8a583422819908a841ef46fe4f978e4bb Mon Sep 17 00:00:00 2001 From: Lewis Wynne Date: Sun, 9 Mar 2025 10:50:35 +0000 Subject: [PATCH 15/18] Fixes miscoloured logs (fixes #26) --- src/ai/turn_status_system.rs | 4 ---- src/effects/triggers.rs | 4 ---- 2 files changed, 8 deletions(-) diff --git a/src/ai/turn_status_system.rs b/src/ai/turn_status_system.rs index db3acaa..e072e45 100644 --- a/src/ai/turn_status_system.rs +++ b/src/ai/turn_status_system.rs @@ -65,9 +65,7 @@ impl<'a> System<'a> for TurnStatusSystem { not_confused.push(entity); if entity == *player_entity { logger = logger - .colour(renderable_colour(&renderables, entity)) .append("You") - .colour(WHITE) .append("snap out of it."); log = true; } else { @@ -94,9 +92,7 @@ impl<'a> System<'a> for TurnStatusSystem { not_my_turn.push(entity); if entity == *player_entity { logger = logger - .colour(renderable_colour(&renderables, entity)) .append("You") - .colour(WHITE) .append("are confused!"); log = true; gamelog::record_event(EVENT::PlayerConfused(1)); diff --git a/src/effects/triggers.rs b/src/effects/triggers.rs index fb54eed..cb4e5d3 100644 --- a/src/effects/triggers.rs +++ b/src/effects/triggers.rs @@ -223,9 +223,7 @@ fn handle_healing( let renderables = ecs.read_storage::(); if ecs.read_storage::().get(target).is_some() { logger = logger - .colour(renderable_colour(&renderables, target)) .append("You") - .colour(WHITE) .append(HEAL_PLAYER_HIT) .buc(event.buc.clone(), None, Some(HEAL_PLAYER_HIT_BLESSED)); } else { @@ -267,9 +265,7 @@ fn handle_damage( let player_viewshed = viewsheds.get(*ecs.fetch::()).unwrap(); if ecs.read_storage::().get(target).is_some() { logger = logger - .colour(renderable_colour(&renderables, target)) .append("You") - .colour(WHITE) .append(DAMAGE_PLAYER_HIT); event.log = true; } else if From 45b9b330396c31cbf1d7fcab8466ba8cb6d005f2 Mon Sep 17 00:00:00 2001 From: Llywelwyn <82828093+Llywelwyn@users.noreply.github.com> Date: Sun, 9 Mar 2025 11:02:16 +0000 Subject: [PATCH 16/18] Update cargo-build-test.yml --- .github/workflows/cargo-build-test.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/cargo-build-test.yml b/.github/workflows/cargo-build-test.yml index d54fa2f..ae0ca42 100644 --- a/.github/workflows/cargo-build-test.yml +++ b/.github/workflows/cargo-build-test.yml @@ -16,6 +16,8 @@ jobs: steps: - uses: actions/checkout@v3 + - name: Deps + run: sudo dnf install fontconfig-devel - name: Build run: cargo build --verbose - name: Run tests From 0584d07a1f9bf45d9f591f399a1b22e720a5072f Mon Sep 17 00:00:00 2001 From: Llywelwyn <82828093+Llywelwyn@users.noreply.github.com> Date: Sun, 9 Mar 2025 11:04:32 +0000 Subject: [PATCH 17/18] Update cargo-build-test.yml to use ubuntu-22.04 --- .github/workflows/cargo-build-test.yml | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/.github/workflows/cargo-build-test.yml b/.github/workflows/cargo-build-test.yml index ae0ca42..cafaa58 100644 --- a/.github/workflows/cargo-build-test.yml +++ b/.github/workflows/cargo-build-test.yml @@ -12,12 +12,10 @@ env: jobs: build: - runs-on: ubuntu-latest + runs-on: ubuntu-22.04 steps: - uses: actions/checkout@v3 - - name: Deps - run: sudo dnf install fontconfig-devel - name: Build run: cargo build --verbose - name: Run tests From c29b93337c5e3a29ebbf286cbd0bb86369faeccd Mon Sep 17 00:00:00 2001 From: Llywelwyn <82828093+Llywelwyn@users.noreply.github.com> Date: Sun, 9 Mar 2025 11:12:11 +0000 Subject: [PATCH 18/18] Update README.md --- README.md | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 9127091..c1809cf 100644 --- a/README.md +++ b/README.md @@ -2,18 +2,18 @@ #### using _rltk/bracket-lib_, and _specs_ -check out the page in the header for the wasm version, pick [a release of your choice](https://github.com/Llywelwyn/rust-rl/releases), or build manually with: +[![Rust](https://github.com/Llywelwyn/rust-rl/actions/workflows/cargo-build-test.yml/badge.svg)](https://github.com/Llywelwyn/rust-rl/actions/workflows/cargo-build-test.yml) + +check out the page in the header for the wasm version, pick [a release](https://github.com/Llywelwyn/rust-rl/releases), or build manually with: `git clone https://github.com/Llywelwyn/rust-rl/ && cd rust-rl && cargo build --release`, ![image](https://github.com/Llywelwyn/rust-rl/assets/82828093/b05e4f0b-2062-4abe-9fee-c679f9ef420d) -this year for roguelikedev does the complete tutorial, i followed along with thebracket's [_roguelike tutorial - in rust_](https://bfnightly.bracketproductions.com). the notes i made during the sprint are being kept below for posterity - further changes since then are noted in [changelog.txt](https://github.com/Llywelwyn/rust-rl/blob/9150ed39e45bee536060cdc769d274e639021012/changelog.txt), and in the release notes. - -i'm also working on translating over my progress into blog entries on my site @ [llyw.co.uk](https://llyw.co.uk/), with a larger focus on some of the more interesting implementation details. - --- +
+ boring details about the sprint where this project started
week 1 @@ -157,3 +157,4 @@ i'm also working on translating over my progress into blog entries on my site @ ![squares](https://github.com/Llywelwyn/rust-rl/assets/82828093/b752e1cb-340d-475d-84ae-68fdb4977a80)
+