diff --git a/src/main.rs b/src/main.rs index e534e0e..12482f0 100644 --- a/src/main.rs +++ b/src/main.rs @@ -101,6 +101,7 @@ struct Object { item: Option, always_visible: bool, level: i32, + equipment: Option, } impl Object { @@ -118,6 +119,7 @@ impl Object { item: None, always_visible: false, level: 1, + equipment: None, } } @@ -202,6 +204,56 @@ impl Object { pub fn distance(&self, x: i32, y: i32) -> f32 { (((x - self.x).pow(2) + (y - self.y).pow(2)) as f32).sqrt() } + + // Equip object and show a message about it + pub fn equip(&mut self, messages: &mut Messages) { + if self.item.is_none() { + messages.add( + format!("Can't equip {:?} because it's not an Item.", self), + RED, + ); + return; + }; + if let Some(ref mut equipment) = self.equipment { + if !equipment.equipped { + equipment.equipped = true; + messages.add( + format!("Equipped {} on {}.", self.name, equipment.slot), + LIGHT_GREEN, + ); + } else { + messages.add( + format!("Can't equip {:?} because it's not an Equipment.", self), + RED, + ); + } + } + } + + // Dequip object and show a message about it + pub fn dequip(&mut self, messages: &mut Messages) { + if self.item.is_none() { + messages.add( + format!("Can't dequip {:?} because it's not an Item.", self), + RED, + ); + return; + }; + if let Some(ref mut equipment) = self.equipment { + if equipment.equipped { + equipment.equipped = false; + messages.add( + format!("Dequipped {} from {}.", self.name, equipment.slot), + LIGHT_YELLOW, + ); + } + } else { + messages.add( + format!("Can't dequip {:?} because it's not an Equipment.", self), + RED, + ); + } + } } #[derive(Clone, Copy, Debug, Serialize, Deserialize)] @@ -304,6 +356,30 @@ struct Transition { value: u32, } +// An object that can be equipped, yielding bonuses +#[derive(Clone, Copy, Debug, PartialEq, Serialize, Deserialize)] +struct Equipment { + slot: Slot, + equipped: bool, +} + +#[derive(Clone, Copy, Debug, PartialEq, Serialize, Deserialize)] +enum Slot { + LeftHand, + RightHand, + Head, +} + +impl std::fmt::Display for Slot { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + match *self { + Slot::LeftHand => write!(f, "left hand"), + Slot::RightHand => write!(f, "right hand"), + Slot::Head => write!(f, "head"), + } + } +} + #[derive(Clone, Copy, Debug, PartialEq)] enum PlayerAction { TookTurn, @@ -343,11 +419,13 @@ enum Item { Lightning, Confuse, Fireball, + Equipment, } enum UseResult { UsedUp, Cancelled, + UsedAndKept, } fn handle_keys(tcod: &mut Tcod, game: &mut Game, objects: &mut Vec) -> PlayerAction { @@ -638,6 +716,10 @@ fn place_objects(room: Rect, map: &Map, objects: &mut Vec, level: u32) { ), item: Item::Confuse, }, + Weighted { + weight: 1000, + item: Item::Equipment, + }, ]; let item_choice = WeightedChoice::new(item_chances); @@ -721,6 +803,16 @@ fn place_objects(room: Rect, map: &Map, objects: &mut Vec, level: u32) { object.item = Some(Item::Confuse); object } + Item::Equipment => { + // Create a sword + let mut object = Object::new(x, y, '/', "sword", SKY, false); + object.item = Some(Item::Equipment); + object.equipment = Some(Equipment { + equipped: false, + slot: Slot::RightHand, + }); + object + } }; item.always_visible = true; @@ -956,7 +1048,18 @@ fn inventory_menu(inventory: &[Object], header: &str, root: &mut Root) -> Option let options = if inventory.len() == 0 { vec!["Inventory is empty.".into()] } else { - inventory.iter().map(|item| item.name.clone()).collect() + inventory + .iter() + .map(|item| { + // Show additional information, in case it's equipped + match item.equipment { + Some(equipment) if equipment.equipped => { + format!("{} (on {})", item.name, equipment.slot) + } + _ => item.name.clone(), + } + }) + .collect() }; let inventory_index = menu(header, &options, INVENTORY_WIDTH, root); @@ -1031,7 +1134,16 @@ fn pick_item_up(object_id: usize, game: &mut Game, objects: &mut Vec) { game .messages .add(format!("You picked up a {}!", item.name), GREEN); + let index = game.inventory.len(); + let slot = item.equipment.map(|e| e.slot); game.inventory.push(item); + + // Automatically equip, if the corresponding equipment slot is unused + if let Some(slot) = slot { + if get_equipped_in_slot(slot, &game.inventory).is_none() { + game.inventory[index].equip(&mut game.messages); + } + } } } @@ -1044,6 +1156,7 @@ fn use_item(inventory_id: usize, tcod: &mut Tcod, game: &mut Game, objects: &mut Lightning => cast_lightning, Confuse => cast_confuse, Fireball => cast_fireball, + Equipment => toggle_equipment, }; match on_use(inventory_id, tcod, game, objects) { UseResult::UsedUp => { @@ -1053,6 +1166,7 @@ fn use_item(inventory_id: usize, tcod: &mut Tcod, game: &mut Game, objects: &mut UseResult::Cancelled => { game.messages.add("Cancelled", WHITE); } + UseResult::UsedAndKept => {} } } else { game.messages.add( @@ -1197,6 +1311,28 @@ fn cast_fireball( UseResult::UsedUp } +fn toggle_equipment( + inventory_id: usize, + _tcod: &mut Tcod, + game: &mut Game, + _objects: &mut [Object], +) -> UseResult { + let equipment = match game.inventory[inventory_id].equipment { + Some(equipment) => equipment, + None => return UseResult::Cancelled, + }; + if equipment.equipped { + game.inventory[inventory_id].dequip(&mut game.messages); + } else { + // If the slot is already being used, dequip whatever is there first + if let Some(current) = get_equipped_in_slot(equipment.slot, &game.inventory) { + game.inventory[current].dequip(&mut game.messages); + } + game.inventory[inventory_id].equip(&mut game.messages); + } + UseResult::UsedAndKept +} + // Find closest enemy, up to a maximum range, and in the player's FOV fn closest_monster(tcod: &Tcod, objects: &[Object], max_range: i32) -> Option { let mut closest_enemy = None; @@ -1283,6 +1419,9 @@ fn target_monster( fn drop_item(inventory_id: usize, game: &mut Game, objects: &mut Vec) { let mut item = game.inventory.remove(inventory_id); + if item.equipment.is_some() { + item.dequip(&mut game.messages); + } item.set_pos(objects[PLAYER].x, objects[PLAYER].y); game .messages @@ -1663,6 +1802,19 @@ fn from_dungeon_level(table: &[Transition], level: u32) -> u32 { .map_or(0, |transition| transition.value) } +fn get_equipped_in_slot(slot: Slot, inventory: &[Object]) -> Option { + for (inventory_id, item) in inventory.iter().enumerate() { + if item + .equipment + .as_ref() + .map_or(false, |e| e.equipped && e.slot == slot) + { + return Some(inventory_id); + } + } + None +} + fn main() { tcod::system::set_fps(FPS);