diff --git a/src/components.rs b/src/components.rs index b56ebdb..f0f6e30 100644 --- a/src/components.rs +++ b/src/components.rs @@ -135,3 +135,41 @@ pub struct SerializeMe; pub struct SerializationHelper { pub map: super::map::Map, } + +#[derive(PartialEq, Copy, Clone, Serialize, Deserialize)] +pub enum EquipmentSlot { + Melee, + Shield, + Head, + Shoulder, + Chest, + Legs, + Hands, + Feet, +} + +#[derive(Component, Copy, Clone, Serialize, Deserialize)] +pub struct Equippable { + pub slot: EquipmentSlot, +} + +#[derive(Component, ConvertSaveload, Clone)] +pub struct Equipped { + pub owner: Entity, + pub slot: EquipmentSlot, +} + +#[derive(Component, ConvertSaveload, Clone)] +pub struct MeleePowerBonus { + pub power: i32, +} + +#[derive(Component, ConvertSaveload, Clone)] +pub struct DefenseBonus { + pub defense: i32, +} + +#[derive(Component, ConvertSaveload, Clone)] +pub struct WantsToRemoveItem { + pub item: Entity, +} diff --git a/src/damage_system.rs b/src/damage_system.rs index 6e7e948..6b30ce8 100644 --- a/src/damage_system.rs +++ b/src/damage_system.rs @@ -1,9 +1,9 @@ -use crate::gamelog::GameLog; -use crate::Name; use rltk::console; use specs::prelude::*; +use crate::{Name, RunState}; use crate::components::{CombatStats, Player, SufferDamage}; +use crate::gamelog::GameLog; pub struct DamageSystem {} @@ -44,7 +44,10 @@ pub fn delete_the_dead(ecs: &mut World) { dead.push(entity) } - Some(_) => console::log("You are dead"), + Some(_) => { + let mut runstate = ecs.write_resource::(); + *runstate = RunState::GameOver; + } } } } diff --git a/src/gui.rs b/src/gui.rs index 47da6aa..0ff358d 100644 --- a/src/gui.rs +++ b/src/gui.rs @@ -1,14 +1,14 @@ -use rltk::Rltk; -use rltk::RGB; +use rltk::{Rltk}; use rltk::{Point, VirtualKeyCode}; +use rltk::RGB; use specs::prelude::*; +use crate::{CombatStats, InBackpack, RunState, State, Viewshed}; +use crate::{Equipped, Map}; use crate::gamelog::GameLog; -use crate::Map; use crate::Name; use crate::Player; use crate::Position; -use crate::{CombatStats, InBackpack, RunState, State, Viewshed}; pub fn draw_ui(ecs: &World, ctx: &mut Rltk) { ctx.draw_box( @@ -514,3 +514,106 @@ pub fn main_menu(gs: &mut State, ctx: &mut Rltk) -> MainMenuResult { selected: MainMenuSelection::NewGame, } } + +pub fn remove_item_menu(gs: &mut State, ctx: &mut Rltk) -> (ItemMenuResult, Option) { + let player_entity = gs.ecs.fetch::(); + let names = gs.ecs.read_storage::(); + let backpack = gs.ecs.read_storage::(); + let entities = gs.ecs.entities(); + + let inventory = (&backpack, &names) + .join() + .filter(|item| item.0.owner == *player_entity); + let count = inventory.count(); + + let mut y = (25 - (count / 2)) as i32; + ctx.draw_box( + 15, + y - 2, + 31, + (count + 3) as i32, + RGB::named(rltk::WHITE), + RGB::named(rltk::BLACK), + ); + ctx.print_color( + 18, + y - 2, + RGB::named(rltk::YELLOW), + RGB::named(rltk::BLACK), + "Remove Which Item?", + ); + ctx.print_color( + 18, + y + count as i32 + 1, + RGB::named(rltk::YELLOW), + RGB::named(rltk::BLACK), + "ESCAPE to cancel", + ); + + let mut equippable: Vec = Vec::new(); + let mut j = 0; + for (entity, _pack, name) in (&entities, &backpack, &names) + .join() + .filter(|item| item.1.owner == *player_entity) + { + ctx.set( + 17, + y, + RGB::named(rltk::WHITE), + RGB::named(rltk::BLACK), + rltk::to_cp437('('), + ); + ctx.set( + 18, + y, + RGB::named(rltk::YELLOW), + RGB::named(rltk::BLACK), + 97 + j as rltk::FontCharType, + ); + ctx.set( + 19, + y, + RGB::named(rltk::WHITE), + RGB::named(rltk::BLACK), + rltk::to_cp437(')'), + ); + + ctx.print(21, y, &name.name.to_string()); + equippable.push(entity); + y += 1; + j += 1; + } + + match ctx.key { + None => (ItemMenuResult::NoResponse, None), + Some(key) => match key { + VirtualKeyCode::Escape => (ItemMenuResult::Cancel, None), + _ => { + let selection = rltk::letter_to_option(key); + if selection > -1 && selection < count as i32 { + return ( + ItemMenuResult::Selected, + Some(equippable[selection as usize]), + ); + } + (ItemMenuResult::NoResponse, None) + } + }, + } +} + +#[derive(PartialEq, Copy, Clone)] +pub enum GameOverResult { NoSelection, QuitToMenu } + +pub fn game_over(ctx: &mut Rltk) -> GameOverResult { + ctx.print_color_centered(15, RGB::named(rltk::YELLOW), RGB::named(rltk::BLACK), "Your journey has ended!"); + ctx.print_color_centered(17, RGB::named(rltk::WHITE), RGB::named(rltk::BLACK), "One day, we'll tell you all about how you did."); + ctx.print_color_centered(18, RGB::named(rltk::WHITE), RGB::named(rltk::BLACK), "That day, sadly, is not in this chapter.."); + + ctx.print_color_centered(20, RGB::named(rltk::MAGENTA), RGB::named(rltk::BLACK), "Press any key to return to the menu."); + + match ctx.key { + None => GameOverResult::NoSelection, + Some(_) => GameOverResult::QuitToMenu + } +} \ No newline at end of file diff --git a/src/inventory_system.rs b/src/inventory_system.rs index bca9812..4ba8d74 100644 --- a/src/inventory_system.rs +++ b/src/inventory_system.rs @@ -2,14 +2,15 @@ use specs::prelude::*; use crate::gamelog::GameLog; use crate::{ - AreaOfEffect, CombatStats, Confusion, Consumable, InBackpack, InflictsDamage, Map, Name, - Position, ProvidesHealing, Ranged, SufferDamage, WantsToDropItem, WantsToPickupItem, - WantsToUseItem, + AreaOfEffect, CombatStats, Confusion, Consumable, Equippable, Equipped, InBackpack, + InflictsDamage, Map, Name, Position, ProvidesHealing, Ranged, SufferDamage, WantsToDropItem, + WantsToPickupItem, WantsToRemoveItem, WantsToUseItem, }; pub struct ItemCollectionSystem {} impl<'a> System<'a> for ItemCollectionSystem { + #[allow(clippy::complexity)] type SystemData = ( ReadExpect<'a, Entity>, WriteExpect<'a, GameLog>, @@ -49,6 +50,7 @@ impl<'a> System<'a> for ItemCollectionSystem { pub struct ItemUseSystem {} impl<'a> System<'a> for ItemUseSystem { + #[allow(clippy::complexity)] type SystemData = ( ReadExpect<'a, Entity>, WriteExpect<'a, GameLog>, @@ -64,6 +66,9 @@ impl<'a> System<'a> for ItemUseSystem { WriteStorage<'a, SufferDamage>, ReadStorage<'a, AreaOfEffect>, WriteStorage<'a, Confusion>, + ReadStorage<'a, Equippable>, + WriteStorage<'a, Equipped>, + WriteStorage<'a, InBackpack>, ); fn run(&mut self, data: Self::SystemData) { @@ -82,6 +87,9 @@ impl<'a> System<'a> for ItemUseSystem { mut suffer_damage, aoe, mut confused, + equippable, + mut equipped, + mut backpack, ) = data; for (entity, use_item) in (&entities, &wants_use).join() { @@ -116,6 +124,43 @@ impl<'a> System<'a> for ItemUseSystem { } } + if let Some(item_equippable) = equippable.get(use_item.item) { + let target_slot = item_equippable.slot; + let target = targets[0]; + let mut to_unequip: Vec = Vec::new(); + for (item_entity, already_equipped, name) in (&entities, &equipped, &names).join() { + if already_equipped.owner == target && already_equipped.slot == target_slot { + to_unequip.push(item_entity); + if target == *player_entity { + game_log.entries.push(format!("You unequip {}.", name.name)); + } + } + } + for item in to_unequip.iter() { + equipped.remove(*item); + backpack + .insert(*item, InBackpack { owner: target }) + .expect("Unable to insert item into backpack"); + } + + equipped + .insert( + use_item.item, + Equipped { + owner: target, + slot: target_slot, + }, + ) + .expect("Unable to equip item"); + backpack.remove(use_item.item); + if target == *player_entity { + game_log.entries.push(format!( + "You equip item {}.", + names.get(use_item.item).unwrap().name + )) + } + } + if let Some(item_damages) = inflicts_damage.get(use_item.item) { used_item = false; for mob in targets.iter() { @@ -242,3 +287,26 @@ impl<'a> System<'a> for ItemDropSystem { wants_drop.clear(); } } + +pub struct ItemRemoveSystem {} + +impl<'a> System<'a> for ItemRemoveSystem { + type SystemData = ( + Entities<'a>, + WriteStorage<'a, WantsToRemoveItem>, + WriteStorage<'a, Equipped>, + WriteStorage<'a, InBackpack>, + ); + + fn run(&mut self, data: Self::SystemData) { + let (entities, mut wants_to_remove_item, mut equipped, mut backpack) = data; + for (entity, to_remove) in (&entities, &wants_to_remove_item).join() { + equipped.remove(to_remove.item); + backpack + .insert(to_remove.item, InBackpack { owner: entity }) + .expect("Unable to insert item into backpack"); + } + + wants_to_remove_item.clear(); + } +} diff --git a/src/main.rs b/src/main.rs index 7886cc1..eb88e40 100644 --- a/src/main.rs +++ b/src/main.rs @@ -14,7 +14,7 @@ use player::*; use visibility_system::*; use crate::gamelog::GameLog; -use crate::inventory_system::{ItemCollectionSystem, ItemDropSystem, ItemUseSystem}; +use crate::inventory_system::{ItemCollectionSystem, ItemDropSystem, ItemRemoveSystem, ItemUseSystem}; mod components; mod damage_system; @@ -49,25 +49,75 @@ pub enum RunState { }, SaveGame, NextLevel, + ShowRemoveItem, + GameOver, } pub struct State { pub ecs: World, } +impl State { + pub fn game_over_cleanup(&mut self) { + // Delete everything + let mut to_delete = Vec::new(); + for e in self.ecs.entities().join() { + to_delete.push(e); + } + for del in to_delete.iter() { + self.ecs.delete_entity(*del).expect("Deletion failed"); + } + + // Build a new map and place the player + let worldmap; + { + let mut worldmap_resource = self.ecs.write_resource::(); + *worldmap_resource = Map::new_map_rooms_and_corridors(1); + worldmap = worldmap_resource.clone(); + } + + // Spawn bad guys + for room in worldmap.rooms.iter().skip(1) { + spawner::spawn_room(&mut self.ecs, room, 1); + } + + // Place the player and update resources + let (player_x, player_y) = worldmap.rooms[0].center(); + let player_entity = spawner::player(&mut self.ecs, player_x, player_y); + let mut player_position = self.ecs.write_resource::(); + *player_position = Point::new(player_x, player_y); + let mut position_components = self.ecs.write_storage::(); + let mut player_entity_writer = self.ecs.write_resource::(); + *player_entity_writer = player_entity; + let player_pos_comp = position_components.get_mut(player_entity); + if let Some(player_pos_comp) = player_pos_comp { + player_pos_comp.x = player_x; + player_pos_comp.y = player_y; + } + + // Mark the player's visibility as dirty + let mut viewshed_components = self.ecs.write_storage::(); + let vs = viewshed_components.get_mut(player_entity); + if let Some(vs) = vs { + vs.dirty = true; + } + } +} + impl State { fn entities_to_remove_on_level_change(&mut self) -> Vec { let entities = self.ecs.entities(); let player = self.ecs.read_storage::(); let backpack = self.ecs.read_storage::(); let player_entity = self.ecs.fetch::(); + let equipped = self.ecs.read_storage::(); let mut to_delete: Vec = Vec::new(); for entity in entities.join() { let mut should_delete = true; let p = player.get(entity); - if let Some(p) = p { + if let Some(_p) = p { should_delete = false; } @@ -78,6 +128,12 @@ impl State { } } + if let Some(eq) = equipped.get(entity) { + if eq.owner == *player_entity { + should_delete = false; + } + } + if should_delete { to_delete.push(entity); } @@ -162,6 +218,9 @@ impl State { let mut drop_items = ItemDropSystem {}; drop_items.run_now(&self.ecs); + let mut remove_items = ItemRemoveSystem {}; + remove_items.run_now(&self.ecs); + self.ecs.maintain(); } } @@ -309,6 +368,34 @@ impl GameState for State { self.goto_next_level(); new_run_state = RunState::PreRun; } + RunState::ShowRemoveItem => { + let result = gui::remove_item_menu(self, ctx); + match result.0 { + gui::ItemMenuResult::Cancel => new_run_state = RunState::AwaitingInput, + gui::ItemMenuResult::NoResponse => {} + gui::ItemMenuResult::Selected => { + let item_entity = result.1.unwrap(); + let mut intent = self.ecs.write_storage::(); + intent + .insert( + *self.ecs.fetch::(), + WantsToRemoveItem { item: item_entity }, + ) + .expect("Unable to insert intent"); + new_run_state = RunState::PlayerTurn; + } + } + } + RunState::GameOver => { + let result = gui::game_over(ctx); + match result { + gui::GameOverResult::NoSelection => {} + gui::GameOverResult::QuitToMenu => { + self.game_over_cleanup(); + new_run_state = RunState::MainMenu { menu_selection: gui::MainMenuSelection::NewGame }; + } + } + } } { @@ -354,6 +441,11 @@ fn main() -> rltk::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.insert(SimpleMarkerAllocator::::new()); diff --git a/src/melee_combat_system.rs b/src/melee_combat_system.rs index 283bb4a..88e1565 100644 --- a/src/melee_combat_system.rs +++ b/src/melee_combat_system.rs @@ -2,6 +2,7 @@ use specs::prelude::*; use crate::components::{CombatStats, Name, SufferDamage, WantsToMelee}; use crate::gamelog::GameLog; +use crate::{DefenseBonus, Equipped, MeleePowerBonus}; pub struct MeleeCombatSystem {} @@ -13,20 +14,54 @@ impl<'a> System<'a> for MeleeCombatSystem { ReadStorage<'a, Name>, ReadStorage<'a, CombatStats>, WriteStorage<'a, SufferDamage>, + ReadStorage<'a, MeleePowerBonus>, + ReadStorage<'a, DefenseBonus>, + ReadStorage<'a, Equipped>, ); fn run(&mut self, data: Self::SystemData) { - let (entities, mut log, mut wants_melee, names, combat_stats, mut inflict_damage) = data; + let ( + entities, + mut log, + mut wants_melee, + names, + combat_stats, + mut inflict_damage, + melee_bonus, + defense_bonus, + equipped, + ) = data; - for (_entity, wants_melee, name, stats) in + for (entity, wants_melee, name, stats) in (&entities, &wants_melee, &names, &combat_stats).join() { if stats.hp > 0 { + let mut offensive_bonus = 0; + for (_item_entity, power_bonus, equipped_by) in + (&entities, &melee_bonus, &equipped).join() + { + if equipped_by.owner == entity { + offensive_bonus += power_bonus.power; + } + } + let target_stats = combat_stats.get(wants_melee.target).unwrap(); if target_stats.hp > 0 { let target_name = names.get(wants_melee.target).unwrap(); - let damage = i32::max(0, stats.power - target_stats.defense); + let mut defensive_bonus = 0; + for (_item_entity, defense_bonus, equipped_by) in + (&entities, &defense_bonus, &equipped).join() + { + if equipped_by.owner == wants_melee.target { + defensive_bonus += defense_bonus.defense; + } + } + + let damage = i32::max( + 0, + (stats.power + offensive_bonus) - (target_stats.defense + defensive_bonus), + ); if damage == 0 { log.entries.push(format!( diff --git a/src/player.rs b/src/player.rs index 3f0a3de..5178a0c 100644 --- a/src/player.rs +++ b/src/player.rs @@ -109,6 +109,7 @@ pub fn player_input(gs: &mut State, ctx: &mut Rltk) -> RunState { VirtualKeyCode::I => return RunState::ShowInventory, VirtualKeyCode::D => return RunState::ShowDropItem, VirtualKeyCode::Escape => return RunState::SaveGame, + VirtualKeyCode::R => return RunState::ShowRemoveItem, VirtualKeyCode::Period => { if try_next_level(&mut gs.ecs) { return RunState::NextLevel; @@ -132,7 +133,7 @@ fn skip_turn(ecs: &mut World) -> RunState { for tile in viewshed.visible_tiles.iter() { let idx = worldmap_resource.xy_idx(tile.x, tile.y); for entity_id in worldmap_resource.tile_content[idx].iter() { - if let Some(mob) = monsters.get(*entity_id) { + if let Some(_mob) = monsters.get(*entity_id) { can_heal = true; } } diff --git a/src/random_table.rs b/src/random_table.rs index 2c8254b..52fe645 100644 --- a/src/random_table.rs +++ b/src/random_table.rs @@ -29,8 +29,10 @@ impl RandomTable { } pub fn add(mut self, name: S, weight: i32) -> RandomTable { - self.total_weight += weight; - self.entries.push(RandomEntry::new(name, weight)); + if weight > 0 { + self.total_weight += weight; + self.entries.push(RandomEntry::new(name, weight)); + } self } diff --git a/src/save_load_system.rs b/src/save_load_system.rs index 8354610..cd91996 100644 --- a/src/save_load_system.rs +++ b/src/save_load_system.rs @@ -71,7 +71,12 @@ pub fn save_game(ecs: &mut World) { WantsToPickupItem, WantsToUseItem, WantsToDropItem, - SerializationHelper + SerializationHelper, + Equippable, + Equipped, + MeleePowerBonus, + DefenseBonus, + WantsToRemoveItem ); } @@ -145,7 +150,12 @@ pub fn load_game(ecs: &mut World) { WantsToPickupItem, WantsToUseItem, WantsToDropItem, - SerializationHelper + SerializationHelper, + Equippable, + Equipped, + MeleePowerBonus, + DefenseBonus, + WantsToRemoveItem ); } diff --git a/src/spawner.rs b/src/spawner.rs index a0c343d..9ec3587 100644 --- a/src/spawner.rs +++ b/src/spawner.rs @@ -7,9 +7,9 @@ use specs::saveload::{MarkedBuilder, SimpleMarker}; use crate::random_table::RandomTable; use crate::rect::Rect; use crate::{ - AreaOfEffect, BlocksTile, CombatStats, Confusion, Consumable, InflictsDamage, Item, Monster, - Name, Player, Position, ProvidesHealing, Ranged, Renderable, SerializeMe, Viewshed, MAP_WIDTH, - MAX_ITEMS, MAX_MONSTER, + AreaOfEffect, BlocksTile, CombatStats, Confusion, Consumable, DefenseBonus, EquipmentSlot, + Equippable, InflictsDamage, Item, MeleePowerBonus, Monster, Name, Player, Position, + ProvidesHealing, Ranged, Renderable, SerializeMe, Viewshed, MAP_WIDTH, MAX_ITEMS, MAX_MONSTER, }; pub fn player(ecs: &mut World, player_x: i32, player_y: i32) -> Entity { @@ -117,6 +117,14 @@ pub fn spawn_room(ecs: &mut World, room: &Rect, map_depth: i32) { "Fireball Scroll" => fireball_scroll(ecs, x, y), "Confusion Scroll" => confusion_scroll(ecs, x, y), "Magic Missile Scroll" => magic_missile_scroll(ecs, x, y), + "Dagger" => dagger(ecs, x, y), + "Longsword" => longsword(ecs, x, y), + "Shield" => shield(ecs, x, y), + "Tower Shield" => tower_shield(ecs, x, y), + "Helmet" => helmet(ecs, x, y), + "Breastplate" => breastplate(ecs, x, y), + "Leggings" => leggings(ecs, x, y), + "Sabatons" => sabatons(ecs, x, y), _ => {} } } @@ -210,4 +218,184 @@ pub fn room_table(map_depth: i32) -> RandomTable { .add("Fireball Scroll", 2 + map_depth) .add("Confusion Scroll", 2 + map_depth) .add("Magic Missile Scroll", 4) + .add("Dagger", 3) + .add("Longsword", map_depth - 1) + .add("Shield", 3) + .add("Tower Shield", map_depth - 1) + .add("Helmet", map_depth - 2) + .add("Breastplate", map_depth - 3) + .add("Leggings", map_depth - 4) + .add("Sabatons", map_depth - 4) +} + +fn dagger(ecs: &mut World, x: i32, y: i32) { + ecs.create_entity() + .with(Position { x, y }) + .with(Renderable { + glyph: rltk::to_cp437('/'), + render_order: 2, + fg: RGB::named(rltk::CYAN), + bg: RGB::named(rltk::BLACK), + }) + .with(Item {}) + .with(Name { + name: "Dagger".to_string(), + }) + .with(Equippable { + slot: EquipmentSlot::Melee, + }) + .with(MeleePowerBonus { power: 2 }) + .marked::>() + .build(); +} + + +fn longsword(ecs: &mut World, x: i32, y: i32) { + ecs.create_entity() + .with(Position { x, y }) + .with(Renderable { + glyph: rltk::to_cp437('/'), + render_order: 2, + fg: RGB::named(rltk::CYAN), + bg: RGB::named(rltk::BLACK), + }) + .with(Item {}) + .with(Name { + name: "Longsword".to_string(), + }) + .with(Equippable { + slot: EquipmentSlot::Melee, + }) + .with(MeleePowerBonus { power: 4 }) + .marked::>() + .build(); +} + +fn shield(ecs: &mut World, x: i32, y: i32) { + ecs.create_entity() + .with(Position { x, y }) + .with(Renderable { + glyph: rltk::to_cp437('('), + render_order: 2, + fg: RGB::named(rltk::CYAN), + bg: RGB::named(rltk::BLACK), + }) + .with(Item {}) + .with(Name { + name: "Shield".to_string(), + }) + .with(Equippable { + slot: EquipmentSlot::Shield, + }) + .with(DefenseBonus { defense: 1 }) + .marked::>() + .build(); +} + + +fn tower_shield(ecs: &mut World, x: i32, y: i32) { + ecs.create_entity() + .with(Position { x, y }) + .with(Renderable { + glyph: rltk::to_cp437('('), + render_order: 2, + fg: RGB::named(rltk::CYAN), + bg: RGB::named(rltk::BLACK), + }) + .with(Item {}) + .with(Name { + name: "Tower Shield".to_string(), + }) + .with(Equippable { + slot: EquipmentSlot::Shield, + }) + .with(DefenseBonus { defense: 3 }) + .marked::>() + .build(); +} + +fn helmet(ecs: &mut World, x: i32, y: i32) { + ecs.create_entity() + .with(Position { x, y }) + .with(Renderable { + glyph: rltk::to_cp437('^'), + render_order: 2, + fg: RGB::named(rltk::CYAN), + bg: RGB::named(rltk::BLACK), + }) + .with(Item {}) + .with(Name { + name: "Helmet".to_string(), + }) + .with(Equippable { + slot: EquipmentSlot::Head, + }) + .with(DefenseBonus { defense: 1 }) + .with(MeleePowerBonus { power: 1 }) + .marked::>() + .build(); +} + +fn breastplate(ecs: &mut World, x: i32, y: i32) { + ecs.create_entity() + .with(Position { x, y }) + .with(Renderable { + glyph: rltk::to_cp437('x'), + render_order: 2, + fg: RGB::named(rltk::CYAN), + bg: RGB::named(rltk::BLACK), + }) + .with(Item {}) + .with(Name { + name: "Breastplate".to_string(), + }) + .with(Equippable { + slot: EquipmentSlot::Chest, + }) + .with(DefenseBonus { defense: 2 }) + .marked::>() + .build(); +} + +fn leggings(ecs: &mut World, x: i32, y: i32) { + ecs.create_entity() + .with(Position { x, y }) + .with(Renderable { + glyph: rltk::to_cp437('"'), + render_order: 2, + fg: RGB::named(rltk::CYAN), + bg: RGB::named(rltk::BLACK), + }) + .with(Item {}) + .with(Name { + name: "Leggings".to_string(), + }) + .with(Equippable { + slot: EquipmentSlot::Legs, + }) + .with(DefenseBonus { defense: 1 }) + .marked::>() + .build(); +} + +fn sabatons(ecs: &mut World, x: i32, y: i32) { + ecs.create_entity() + .with(Position { x, y }) + .with(Renderable { + glyph: rltk::to_cp437(','), + render_order: 2, + fg: RGB::named(rltk::CYAN), + bg: RGB::named(rltk::BLACK), + }) + .with(Item {}) + .with(Name { + name: "Sabatons".to_string(), + }) + .with(Equippable { + slot: EquipmentSlot::Feet, + }) + .with(DefenseBonus { defense: 1 }) + .with(MeleePowerBonus { power: 1 }) + .marked::>() + .build(); }