Bonus round

This commit is contained in:
Michael Smith 2021-11-20 17:43:01 +01:00
parent 30d409149b
commit e38e119562

View File

@ -101,6 +101,7 @@ struct Object {
item: Option<Item>, item: Option<Item>,
always_visible: bool, always_visible: bool,
level: i32, level: i32,
equipment: Option<Equipment>,
} }
impl Object { impl Object {
@ -118,6 +119,7 @@ impl Object {
item: None, item: None,
always_visible: false, always_visible: false,
level: 1, level: 1,
equipment: None,
} }
} }
@ -202,6 +204,56 @@ impl Object {
pub fn distance(&self, x: i32, y: i32) -> f32 { pub fn distance(&self, x: i32, y: i32) -> f32 {
(((x - self.x).pow(2) + (y - self.y).pow(2)) as f32).sqrt() (((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)] #[derive(Clone, Copy, Debug, Serialize, Deserialize)]
@ -304,6 +356,30 @@ struct Transition {
value: u32, 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)] #[derive(Clone, Copy, Debug, PartialEq)]
enum PlayerAction { enum PlayerAction {
TookTurn, TookTurn,
@ -343,11 +419,13 @@ enum Item {
Lightning, Lightning,
Confuse, Confuse,
Fireball, Fireball,
Equipment,
} }
enum UseResult { enum UseResult {
UsedUp, UsedUp,
Cancelled, Cancelled,
UsedAndKept,
} }
fn handle_keys(tcod: &mut Tcod, game: &mut Game, objects: &mut Vec<Object>) -> PlayerAction { fn handle_keys(tcod: &mut Tcod, game: &mut Game, objects: &mut Vec<Object>) -> PlayerAction {
@ -638,6 +716,10 @@ fn place_objects(room: Rect, map: &Map, objects: &mut Vec<Object>, level: u32) {
), ),
item: Item::Confuse, item: Item::Confuse,
}, },
Weighted {
weight: 1000,
item: Item::Equipment,
},
]; ];
let item_choice = WeightedChoice::new(item_chances); let item_choice = WeightedChoice::new(item_chances);
@ -721,6 +803,16 @@ fn place_objects(room: Rect, map: &Map, objects: &mut Vec<Object>, level: u32) {
object.item = Some(Item::Confuse); object.item = Some(Item::Confuse);
object 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; 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 { let options = if inventory.len() == 0 {
vec!["Inventory is empty.".into()] vec!["Inventory is empty.".into()]
} else { } 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); 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<Object>) {
game game
.messages .messages
.add(format!("You picked up a {}!", item.name), GREEN); .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); 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, Lightning => cast_lightning,
Confuse => cast_confuse, Confuse => cast_confuse,
Fireball => cast_fireball, Fireball => cast_fireball,
Equipment => toggle_equipment,
}; };
match on_use(inventory_id, tcod, game, objects) { match on_use(inventory_id, tcod, game, objects) {
UseResult::UsedUp => { UseResult::UsedUp => {
@ -1053,6 +1166,7 @@ fn use_item(inventory_id: usize, tcod: &mut Tcod, game: &mut Game, objects: &mut
UseResult::Cancelled => { UseResult::Cancelled => {
game.messages.add("Cancelled", WHITE); game.messages.add("Cancelled", WHITE);
} }
UseResult::UsedAndKept => {}
} }
} else { } else {
game.messages.add( game.messages.add(
@ -1197,6 +1311,28 @@ fn cast_fireball(
UseResult::UsedUp 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 // 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<usize> { fn closest_monster(tcod: &Tcod, objects: &[Object], max_range: i32) -> Option<usize> {
let mut closest_enemy = None; let mut closest_enemy = None;
@ -1283,6 +1419,9 @@ fn target_monster(
fn drop_item(inventory_id: usize, game: &mut Game, objects: &mut Vec<Object>) { fn drop_item(inventory_id: usize, game: &mut Game, objects: &mut Vec<Object>) {
let mut item = game.inventory.remove(inventory_id); 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); item.set_pos(objects[PLAYER].x, objects[PLAYER].y);
game game
.messages .messages
@ -1663,6 +1802,19 @@ fn from_dungeon_level(table: &[Transition], level: u32) -> u32 {
.map_or(0, |transition| transition.value) .map_or(0, |transition| transition.value)
} }
fn get_equipped_in_slot(slot: Slot, inventory: &[Object]) -> Option<usize> {
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() { fn main() {
tcod::system::set_fps(FPS); tcod::system::set_fps(FPS);