diff --git a/raws/items.json b/raws/items.json index 41137ae..4c95525 100644 --- a/raws/items.json +++ b/raws/items.json @@ -3,6 +3,7 @@ "id": "potion_health", "name": { "name": "potion of health", "plural": "potions of health" }, "renderable": { "glyph": "!", "fg": "#FF00FF", "bg": "#000000", "order": 4 }, + "class": "potion", "weight": 1, "value": 50, "flags": ["CONSUMABLE", "DESTRUCTIBLE", "STACKABLE"], @@ -13,6 +14,7 @@ "id": "potion_health_weak", "name": { "name": "potion of lesser health", "plural": "potions of lesser health" }, "renderable": { "glyph": "!", "fg": "#FF00FF", "bg": "#000000", "order": 4 }, + "class": "potion", "weight": 1, "value": 25, "flags": ["CONSUMABLE", "DESTRUCTIBLE", "STACKABLE"], @@ -23,6 +25,7 @@ "id": "scroll_identify", "name": { "name": "scroll of identify", "plural": "scrolls of identify" }, "renderable": { "glyph": "?", "sprite": { "id": "scroll", "colour": false }, "fg": "#0FFFFF", "bg": "#000000", "order": 4 }, + "class": "scroll", "weight": 0.5, "value": 100, "flags": ["CONSUMABLE", "DESTRUCTIBLE", "IDENTIFY"], @@ -32,6 +35,7 @@ "id": "scroll_removecurse", "name": { "name": "scroll of remove curse", "plural": "scrolls of remove curse" }, "renderable": { "glyph": "?", "sprite": { "id": "scroll", "colour": false }, "fg": "#0FFFFF", "bg": "#000000", "order": 4 }, + "class": "scroll", "weight": 0.5, "value": 200, "flags": ["CONSUMABLE", "DESTRUCTIBLE", "REMOVE_CURSE"], @@ -41,6 +45,7 @@ "id": "scroll_health", "name": { "name": "scroll of healing word", "plural": "scrolls of healing word" }, "renderable": { "glyph": "?", "sprite": { "id": "scroll", "colour": false }, "fg": "#00FFFF", "bg": "#000000", "order": 4 }, + "class": "scroll", "weight": 0.5, "value": 50, "flags": ["CONSUMABLE", "DESTRUCTIBLE"], @@ -51,6 +56,7 @@ "id": "scroll_mass_health", "name": { "name": "scroll of mass healing word", "plural": "scrolls of mass healing word" }, "renderable": { "glyph": "?", "sprite": { "id": "scroll", "colour": false }, "fg": "#00FFFF", "bg": "#000000", "order": 4 }, + "class": "scroll", "weight": 0.5, "value": 200, "flags": ["CONSUMABLE", "DESTRUCTIBLE"], @@ -61,6 +67,7 @@ "id": "scroll_magicmissile", "name": { "name": "scroll of magic missile", "plural": "scrolls of magic missile" }, "renderable": { "glyph": "?", "sprite": { "id": "scroll", "colour": false }, "fg": "#00FFFF", "bg": "#000000", "order": 4 }, + "class": "scroll", "weight": 0.5, "value": 50, "flags": ["CONSUMABLE", "DESTRUCTIBLE"], @@ -71,6 +78,7 @@ "id": "scroll_embers", "name": { "name": "scroll of embers", "plural": "scrolls of embers" }, "renderable": { "glyph": "?", "sprite": { "id": "scroll", "colour": false }, "fg": "#00FFFF", "bg": "#000000", "order": 4 }, + "class": "scroll", "weight": 0.5, "value": 100, "flags": ["CONSUMABLE", "DESTRUCTIBLE"], @@ -81,6 +89,7 @@ "id": "scroll_fireball", "name": { "name": "scroll of fireball", "plural": "scrolls of fireball" }, "renderable": { "glyph": "?", "sprite": { "id": "scroll", "colour": false }, "fg": "#00FFFF", "bg": "#000000", "order": 4 }, + "class": "scroll", "weight": 0.5, "value": 200, "flags": ["CONSUMABLE", "DESTRUCTIBLE"], @@ -96,6 +105,7 @@ "id": "scroll_confusion", "name": { "name": "scroll of confusion", "plural": "scrolls of confusion" }, "renderable": { "glyph": "?", "sprite": { "id": "scroll", "colour": false }, "fg": "#00FFFF", "bg": "#000000", "order": 4 }, + "class": "scroll", "weight": 0.5, "value": 100, "flags": ["CONSUMABLE", "DESTRUCTIBLE"], @@ -106,6 +116,7 @@ "id": "scroll_mass_confusion", "name": { "name": "scroll of mass confusion", "plural": "scrolls of mass confusion" }, "renderable": { "glyph": "?", "sprite": { "id": "scroll", "colour": false }, "fg": "#00FFFF", "bg": "#000000", "order": 4 }, + "class": "scroll", "weight": 0.5, "value": 200, "flags": ["CONSUMABLE", "DESTRUCTIBLE"], @@ -116,6 +127,7 @@ "id": "scroll_magicmap", "name": { "name": "scroll of magic mapping", "plural": "scrolls of magic mapping" }, "renderable": { "glyph": "?", "sprite": { "id": "scroll", "colour": false }, "fg": "#00FFFF", "bg": "#000000", "order": 4 }, + "class": "scroll", "weight": 0.5, "value": 50, "flags": ["CONSUMABLE", "DESTRUCTIBLE", "MAGICMAP"], @@ -126,6 +138,7 @@ "id": "equip_dagger", "name": { "name": "dagger", "plural": "daggers" }, "renderable": { "glyph": ")", "fg": "#808080", "bg": "#000000", "order": 4 }, + "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": 4 }, + "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": 4 }, + "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": 4 }, + "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": 4 }, + "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": 4 }, + "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": 4 }, + "class": "weapon", "weight": 3, "value": 15, "flags": ["EQUIP_MELEE"], @@ -189,6 +208,7 @@ "id": "artifact_icingdeath", "name": { "name": "Icingdeath", "plural": "Icingdeath" }, "renderable": { "glyph": ")", "fg": "#37aecc", "bg": "#000000", "order": 4 }, + "class": "weapon", "weight": 3, "value": 300, "flags": ["EQUIP_MELEE"], @@ -198,6 +218,7 @@ "id": "equip_smallshield", "name": { "name": "buckler", "plural": "bucklers" }, "renderable": { "glyph": "[", "fg": "#808080", "bg": "#000000", "order": 4 }, + "class": "armour", "weight": 2, "value": 5, "flags": ["EQUIP_SHIELD"], @@ -207,6 +228,7 @@ "id": "equip_mediumshield", "name": { "name": "medium shield", "plural": "medium shields" }, "renderable": { "glyph": "[", "fg": "#C0C0C0", "bg": "#000000", "order": 4 }, + "class": "armour", "weight": 6, "value": 10, "flags": ["EQUIP_SHIELD"], @@ -216,6 +238,7 @@ "id": "equip_largeshield", "name": { "name": "large shield", "plural": "large shields" }, "renderable": { "glyph": "[", "fg": "#FFF8DC", "bg": "#000000", "order": 4 }, + "class": "armour", "weight": 12, "value": 35, "flags": ["EQUIP_SHIELD"], @@ -225,6 +248,7 @@ "id": "equip_body_weakleather", "name": { "name": "leather jacket", "plural": "leather jackets" }, "renderable": { "glyph": "[", "fg": "#aa6000", "bg": "#000000", "order": 4 }, + "class": "armour", "weight": 8, "value": 5, "flags": ["EQUIP_BODY"], @@ -234,6 +258,7 @@ "id": "equip_body_leather", "name": { "name": "leather chestpiece", "plural": "leather chestpiece" }, "renderable": { "glyph": "[", "fg": "#aa6000", "bg": "#000000", "order": 4 }, + "class": "armour", "weight": 10, "value": 10, "flags": ["EQUIP_BODY"], @@ -243,6 +268,7 @@ "id": "equip_body_studdedleather", "name": { "name": "studded leather chestpiece", "plural": "studded leather chestpieces" }, "renderable": { "glyph": "[", "fg": "#aa6000", "bg": "#000000", "order": 4 }, + "class": "armour", "weight": 13, "value": 45, "flags": ["EQUIP_BODY"], @@ -252,6 +278,7 @@ "id": "equip_body_ringmail_o", "name": { "name": "orcish ring mail", "plural": "orcish ring mail" }, "renderable": { "glyph": "[", "fg": "#aa6000", "bg": "#000000", "order": 4 }, + "class": "armour", "weight": 45, "value": 50, "flags": ["EQUIP_BODY"], @@ -261,6 +288,7 @@ "id": "equip_body_ringmail", "name": { "name": "ring mail", "plural": "ring mail" }, "renderable": { "glyph": "[", "fg": "#aa6000", "bg": "#000000", "order": 4 }, + "class": "armour", "weight": 45, "value": 70, "flags": ["EQUIP_BODY"], @@ -270,6 +298,7 @@ "id": "equip_head_leather", "name": { "name": "leather cap", "plural": "leather caps" }, "renderable": { "glyph": "[", "fg": "#aa6000", "bg": "#000000", "order": 4 }, + "class": "armour", "weight": 2, "value": 10, "flags": ["EQUIP_HEAD"], @@ -279,6 +308,7 @@ "id": "equip_head_elvish", "name": { "name": "elvish leather helm", "plural": "elvish leather helms" }, "renderable": { "glyph": "[", "fg": "#aa6000", "bg": "#000000", "order": 4 }, + "class": "armour", "weight": 2, "value": 25, "flags": ["EQUIP_HEAD"], @@ -288,6 +318,7 @@ "id": "equip_head_o", "name": { "name": "orcish helm", "plural": "orcish helm" }, "renderable": { "glyph": "[", "fg": "#aa6000", "bg": "#000000", "order": 4 }, + "class": "armour", "weight": 6, "value": 25, "flags": ["EQUIP_HEAD"], @@ -297,6 +328,7 @@ "id": "equip_head_iron", "name": { "name": "iron helm", "plural": "iron helm" }, "renderable": { "glyph": "[", "fg": "#aa6000", "bg": "#000000", "order": 4 }, + "class": "armour", "weight": 10, "value": 45, "flags": ["EQUIP_HEAD"], @@ -306,6 +338,7 @@ "id": "equip_feet_leather", "name": { "name": "leather shoes", "plural": "leather shoes" }, "renderable": { "glyph": "[", "fg": "#aa6000", "bg": "#000000", "order": 4 }, + "class": "armour", "weight": 2, "value": 10, "flags": ["EQUIP_FEET"] @@ -314,6 +347,7 @@ "id": "equip_feet_elvish", "name": { "name": "elvish leather shoes", "plural": "elvish leather shoes" }, "renderable": { "glyph": "[", "fg": "#aa6000", "bg": "#000000", "order": 4 }, + "class": "armour", "weight": 2, "value": 25, "flags": ["EQUIP_FEET"], @@ -323,6 +357,7 @@ "id": "equip_feet_o", "name": { "name": "orcish boots", "plural": "orcish boots" }, "renderable": { "glyph": "[", "fg": "#aa6000", "bg": "#000000", "order": 4 }, + "class": "armour", "weight": 6, "value": 25, "flags": ["EQUIP_FEET"], @@ -332,6 +367,7 @@ "id": "equip_feet_iron", "name": { "name": "iron boots", "plural": "iron boots" }, "renderable": { "glyph": "[", "fg": "#aa6000", "bg": "#000000", "order": 4 }, + "class": "armour", "weight": 10, "value": 45, "flags": ["EQUIP_FEET"], @@ -341,6 +377,7 @@ "id": "equip_neck_protection", "name": { "name": "amulet of protection", "plural": "amulets of protection" }, "renderable": { "glyph": "\"", "fg": "#aa6000", "bg": "#000000", "order": 4 }, + "class": "amulet", "weight": 1, "value": 200, "flags": ["EQUIP_NECK"], @@ -350,6 +387,7 @@ "id": "equip_back_protection", "name": { "name": "cloak of protection", "plural": "cloaks of protection" }, "renderable": { "glyph": "[", "fg": "#aa6000", "bg": "#000000", "order": 4 }, + "class": "armour", "weight": 1, "value": 200, "flags": ["EQUIP_BACK"], @@ -359,6 +397,7 @@ "id": "wand_magicmissile", "name": { "name": "wand of magic missile", "plural": "wands of magic missile" }, "renderable": { "glyph": "/", "fg": "#00FFFF", "bg": "#000000", "order": 4 }, + "class": "wand", "weight": 2, "value": 100, "flags": ["CHARGES"], @@ -369,6 +408,7 @@ "id": "wand_fireball", "name": { "name": "wand of fireball", "plural": "wands of fireball" }, "renderable": { "glyph": "/", "fg": "#00FFFF", "bg": "#000000", "order": 4 }, + "class": "wand", "weight": 2, "value": 300, "flags": ["CHARGES"], @@ -379,6 +419,7 @@ "id": "wand_confusion", "name": { "name": "wand of confusion", "plural": "wands of confusion" }, "renderable": { "glyph": "/", "fg": "#00FFFF", "bg": "#000000", "order": 4 }, + "class": "wand", "weight": 2, "value": 200, "flags": ["CHARGES"], @@ -389,6 +430,7 @@ "id": "wand_digging", "name": { "name": "wand of digging", "plural": "wands of digging" }, "renderable": { "glyph": "/", "fg": "#00FFFF", "bg": "#000000", "order": 4 }, + "class": "wand", "weight": 2, "value": 300, "flags": ["CHARGES", "DIGGER"], @@ -399,6 +441,7 @@ "id": "food_rations", "name": { "name": "rations", "plural": "rations" }, "renderable": { "glyph": "%", "fg": "#FFA07A", "bg": "#000000", "order": 4 }, + "class": "comestible", "weight": 1, "value": 1, "flags": ["FOOD", "CONSUMABLE", "STACKABLE"] @@ -407,6 +450,7 @@ "id": "food_apple", "name": { "name": "apple", "plural": "apples" }, "renderable": { "glyph": "%", "fg": "#00FF00", "bg": "#000000", "order": 4 }, + "class": "comestible", "weight": 0.5, "value": 1, "flags": ["FOOD", "CONSUMABLE", "STACKABLE"] diff --git a/src/components.rs b/src/components.rs index 0e41714..61bdb83 100644 --- a/src/components.rs +++ b/src/components.rs @@ -437,10 +437,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)] diff --git a/src/gui/identify_menu.rs b/src/gui/identify_menu.rs index e5e8834..4362a11 100644 --- a/src/gui/identify_menu.rs +++ b/src/gui/identify_menu.rs @@ -3,10 +3,12 @@ use super::{ item_colour_ecs, obfuscate_name_ecs, print_options, + unique_ecs, renderable_colour, ItemMenuResult, UniqueInventoryItem, BUC, + Key, }; use crate::{ gamelog, @@ -23,7 +25,7 @@ use crate::{ }; 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 +39,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 +96,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); @@ -135,7 +121,7 @@ pub fn identify(gs: &mut State, ctx: &mut BTerm) -> (ItemMenuResult, Option (ItemMenuResult::NoResponse, None), Some(key) => match key { @@ -161,4 +147,6 @@ pub fn identify(gs: &mut State, ctx: &mut BTerm) -> (ItemMenuResult, Option i32 { +pub fn letter_to_option(key: KeyCode, shift: bool) -> Option { if shift { match key { - KeyCode::A => 26, - KeyCode::B => 27, - KeyCode::C => 28, - KeyCode::D => 29, - KeyCode::E => 30, - KeyCode::F => 31, - KeyCode::G => 32, - KeyCode::H => 33, - KeyCode::I => 34, - KeyCode::J => 35, - KeyCode::K => 36, - KeyCode::L => 37, - KeyCode::M => 38, - KeyCode::N => 39, - KeyCode::O => 40, - KeyCode::P => 41, - KeyCode::Q => 42, - KeyCode::R => 43, - KeyCode::S => 44, - KeyCode::T => 45, - KeyCode::U => 46, - KeyCode::V => 47, - KeyCode::W => 48, - KeyCode::X => 49, - KeyCode::Y => 50, - KeyCode::Z => 51, - _ => -1, + KeyCode::A => Some(26), + KeyCode::B => Some(27), + KeyCode::C => Some(28), + KeyCode::D => Some(29), + KeyCode::E => Some(30), + KeyCode::F => Some(31), + KeyCode::G => Some(32), + KeyCode::H => Some(33), + KeyCode::I => Some(34), + KeyCode::J => Some(35), + KeyCode::K => Some(36), + KeyCode::L => Some(37), + KeyCode::M => Some(38), + KeyCode::N => Some(39), + KeyCode::O => Some(40), + KeyCode::P => Some(41), + KeyCode::Q => Some(42), + KeyCode::R => Some(43), + KeyCode::S => Some(44), + KeyCode::T => Some(45), + KeyCode::U => Some(46), + KeyCode::V => Some(47), + KeyCode::W => Some(48), + KeyCode::X => Some(49), + KeyCode::Y => Some(50), + KeyCode::Z => Some(51), + _ => None, } } else { match key { - KeyCode::A => 0, - KeyCode::B => 1, - KeyCode::C => 2, - KeyCode::D => 3, - KeyCode::E => 4, - KeyCode::F => 5, - KeyCode::G => 6, - KeyCode::H => 7, - KeyCode::I => 8, - KeyCode::J => 9, - KeyCode::K => 10, - KeyCode::L => 11, - KeyCode::M => 12, - KeyCode::N => 13, - KeyCode::O => 14, - KeyCode::P => 15, - KeyCode::Q => 16, - KeyCode::R => 17, - KeyCode::S => 18, - KeyCode::T => 19, - KeyCode::U => 20, - KeyCode::V => 21, - KeyCode::W => 22, - KeyCode::X => 23, - KeyCode::Y => 24, - KeyCode::Z => 25, - _ => -1, + KeyCode::A => Some(0), + KeyCode::B => Some(1), + KeyCode::C => Some(2), + KeyCode::D => Some(3), + KeyCode::E => Some(4), + KeyCode::F => Some(5), + KeyCode::G => Some(6), + KeyCode::H => Some(7), + KeyCode::I => Some(8), + KeyCode::J => Some(9), + KeyCode::K => Some(10), + KeyCode::L => Some(11), + KeyCode::M => Some(12), + KeyCode::N => Some(13), + KeyCode::O => Some(14), + KeyCode::P => Some(15), + KeyCode::Q => Some(16), + KeyCode::R => Some(17), + KeyCode::S => Some(18), + KeyCode::T => Some(19), + KeyCode::U => Some(20), + KeyCode::V => Some(21), + KeyCode::W => Some(22), + KeyCode::X => Some(23), + KeyCode::Y => Some(24), + KeyCode::Z => Some(25), + _ => None, } } } diff --git a/src/gui/mod.rs b/src/gui/mod.rs index 7673861..efe9674 100644 --- a/src/gui/mod.rs +++ b/src/gui/mod.rs @@ -32,6 +32,9 @@ use super::{ Skills, Viewshed, BUC, + Key, + Item, + ItemType, consts::ids::get_local_col, }; use crate::consts::prelude::*; @@ -51,7 +54,8 @@ use notan::draw::{ Draw, DrawTextSection, DrawImages, DrawShapes }; use std::collections::HashMap; use bracket_lib::prelude::*; use specs::prelude::*; -use std::collections::BTreeMap; +use std::collections::{ BTreeMap, HashSet }; +use crate::invkeys::check_key; mod character_creation; mod cheat_menu; @@ -361,7 +365,8 @@ pub fn draw_ui2(ecs: &World, draw: &mut Draw, atlas: &HashMap, .size(FONTSIZE); } // Equipment - let renderables = ecs.read_storage::(); + draw_all(ecs, draw, font, ((VIEWPORT_W + 3) as f32) * TILESIZE, TILESIZE); + /*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) @@ -424,8 +429,8 @@ pub fn draw_ui2(ecs: &World, draw: &mut Draw, atlas: &HashMap, ) .position(((DISPLAYWIDTH - 1) as f32) * TILESIZE, (y as f32) * TILESIZE) .size(FONTSIZE) - .h_align_right(); - let player_inventory = get_player_inventory(&ecs); + .h_align_right();*/ + //let player_inventory = get_player_inventory(&ecs); // TODO: print_options() } } @@ -690,7 +695,7 @@ pub fn draw_ui(ecs: &World, ctx: &mut BTerm) { ) ); y += 1; - let player_inventory = get_player_inventory(&ecs); + //let player_inventory = get_player_inventory(&ecs); // Draw spells - if we have any -- NYI! if let Some(known_spells) = ecs.read_storage::().get(*player_entity) { y += 1; @@ -914,72 +919,84 @@ pub enum ItemMenuResult { } pub fn print_options( + ecs: &World, draw: &mut Draw, font: &Fonts, inventory: &PlayerInventory, mut x: f32, mut y: f32 -) -> (f32, i32) { - let mut j = 0; +) -> f32 { let initial_x: f32 = 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 { - draw.text(&font.b(), &format!("{} ", (97 + j) as u8 as char)) + if slot.idx < 26 { + draw.text(&font.b(), &format!("{} ", (97 + slot.idx) as u8 as char)) .position(x, y) .color(Color::YELLOW) .size(FONTSIZE); } else { // If we somehow have more than 26, start using capitals - draw.text(&font.b(), &format!("{} ", (65 - 26 + j) as u8 as char)) + draw.text(&font.b(), &format!("{} ", (65 - 26 + slot.idx) as u8 as char)) .position(x, y) .color(Color::YELLOW) .size(FONTSIZE); } x = draw.last_text_bounds().max_x(); - let fg = RGB::from_u8(item.renderables.0, item.renderables.1, item.renderables.2); - draw.text(&font.n(), &format!("{} ", item.glyph as u8 as char)) + let fg = RGB::from_u8(info.renderables.0, info.renderables.1, info.renderables.2); + draw.text(&font.n(), &format!("{} ", info.glyph as u8 as char)) .position(x, y) .size(FONTSIZE) .color(Color::from_rgb(fg.r, fg.g, fg.b)); x = draw.last_text_bounds().max_x(); - let fg = RGB::from_u8(item.rgb.0, item.rgb.1, item.rgb.2); - if item_count > &1 { - draw.text(&font.n(), &format!("{} {}", item_count, item.display_name.plural)) + let fg = RGB::from_u8(info.rgb.0, info.rgb.1, info.rgb.2); + if slot.count > 1 { + draw.text(&font.n(), &format!("{} {}", slot.count, info.display_name.plural)) .position(x, y) .color(Color::from_rgb(fg.r, fg.g, fg.b)) .size(FONTSIZE); } else { - let prefix = if item.display_name.singular.to_lowercase().ends_with("s") { + let prefix = if info.display_name.singular.to_lowercase().ends_with("s") { "some" } 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)) { "an" } else { "a" }; - draw.text(&font.n(), &format!("{} {}", prefix, item.display_name.singular)) + draw.text(&font.n(), &format!("{} {}", prefix, info.display_name.singular)) .position(x, y) .color(Color::from_rgb(fg.r, fg.g, fg.b)) .size(FONTSIZE); + if let Some(worn) = ecs.read_storage::().get(slot.item) { + x = draw.last_text_bounds().max_x(); + use crate::EquipmentSlot; + let text = match worn.slot { + EquipmentSlot::Melee | EquipmentSlot::Shield => "being held", + _ => "being worn", + }; + draw.text(&font.ib(), &format!(" ({})", text)) + .position(x, y) + .color(Color::WHITE) + .size(FONTSIZE); + }; } y += TILESIZE; - j += 1; } - return (y, width); + return y; } 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 = 4; // The spaces before and after the character to select this item, etc. - if count <= &1 { + if slot.count <= 1 { this_width += item.display_name.singular.len() as i32; if item.display_name.singular == item.display_name.plural { this_width += 4; // "some".len @@ -992,7 +1009,7 @@ pub fn get_max_inventory_width(inventory: &PlayerInventory) -> i32 { } } else { this_width += item.display_name.plural.len() as i32; - this_width += count.to_string().len() as i32; // i.e. "12".len + this_width += slot.count.to_string().len() as i32; // i.e. "12".len } width = if width > this_width { width } else { this_width }; } @@ -1043,7 +1060,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() { @@ -1098,6 +1115,12 @@ pub fn obfuscate_name_ecs(ecs: &World, item: Entity) -> (String, String) { } } } + if let Some(worn) = ecs.read_storage::().get(item) { + if worn.owner == *ecs.fetch::() { + singular.insert_str(singular.len(), " (worn)"); + plural.insert_str(plural.len(), " (worn)"); + } + } return (singular, plural); } @@ -1254,8 +1277,6 @@ pub struct UniqueInventoryItem { name: String, } -pub type PlayerInventory = BTreeMap; - pub fn unique( entity: Entity, names: &ReadStorage, @@ -1325,57 +1346,74 @@ pub fn unique_ecs(ecs: &World, entity: Entity) -> UniqueInventoryItem { ); } -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 struct InventorySlot { + pub item: Entity, + pub count: i32, + pub idx: usize, +} - 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 type PlayerInventory = HashMap; + +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, + }); + }; +} + +pub fn items(ecs: &World, filter: Filter) -> HashMap { + let entities = ecs.entities(); + let keys = ecs.read_storage::(); + + let mut inv: HashMap = 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); + } + } } - return player_inventory; + inv } pub fn show_inventory(gs: &mut State, ctx: &mut App) -> (ItemMenuResult, Option) { - let player_inventory = get_player_inventory(&gs.ecs); let on_overmap = gs.ecs.fetch::().overmap; - let count = player_inventory.len(); let key = &ctx.keyboard; for keycode in key.pressed.iter() { @@ -1385,20 +1423,27 @@ pub fn show_inventory(gs: &mut State, ctx: &mut App) -> (ItemMenuResult, Option< } _ => { let shift = key.shift(); - let selection = letter_to_option::letter_to_option(*keycode, shift); - if selection > -1 && selection < (count as i32) { + let selection = if + let Some(key) = letter_to_option::letter_to_option(*keycode, shift) + { + key + } else { + continue; + }; + if check_key(selection) { 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 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 { + return (ItemMenuResult::Selected, Some(e)); + } + } + // TODO: Probably some gamelog about not having the selected item? } } } @@ -1408,8 +1453,6 @@ pub fn show_inventory(gs: &mut State, ctx: &mut App) -> (ItemMenuResult, Option< } pub fn drop_item_menu(gs: &mut State, ctx: &mut App) -> (ItemMenuResult, Option) { - let player_inventory = get_player_inventory(&gs.ecs); - let count = player_inventory.len(); let on_overmap = gs.ecs.fetch::().overmap; let key = &ctx.keyboard; @@ -1420,20 +1463,26 @@ pub fn drop_item_menu(gs: &mut State, ctx: &mut App) -> (ItemMenuResult, Option< } _ => { let shift = key.shift(); - let selection = letter_to_option::letter_to_option(*keycode, shift); - if selection > -1 && selection < (count as i32) { + let selection = if + let Some(key) = letter_to_option::letter_to_option(*keycode, shift) + { + key + } else { + continue; + }; + if check_key(selection) { 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 { + return (ItemMenuResult::Selected, Some(e)); + } + } } } } diff --git a/src/gui/remove_curse_menu.rs b/src/gui/remove_curse_menu.rs index 1db7a58..18861bd 100644 --- a/src/gui/remove_curse_menu.rs +++ b/src/gui/remove_curse_menu.rs @@ -3,9 +3,11 @@ use super::{ item_colour_ecs, obfuscate_name_ecs, print_options, + unique_ecs, renderable_colour, ItemMenuResult, UniqueInventoryItem, + InventorySlot, }; use crate::{ gamelog, @@ -18,10 +20,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 +36,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 +90,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 +104,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); @@ -130,7 +131,7 @@ pub fn remove_curse(gs: &mut State, ctx: &mut BTerm) -> (ItemMenuResult, Option< ctx.draw_box(x, y, width + 2, count + 1, RGB::named(WHITE), RGB::named(BLACK)); // Input - match ctx.key { + /*match ctx.key { None => (ItemMenuResult::NoResponse, None), Some(key) => match key { @@ -155,5 +156,6 @@ pub fn remove_curse(gs: &mut State, ctx: &mut BTerm) -> (ItemMenuResult, Option< (ItemMenuResult::NoResponse, None) } } - } + }*/ + (ItemMenuResult::NoResponse, None) } diff --git a/src/invkeys.rs b/src/invkeys.rs index f7e5d3a..e52824b 100644 --- a/src/invkeys.rs +++ b/src/invkeys.rs @@ -18,6 +18,11 @@ pub fn restore_invkeys(invkeys: HashMap) { 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::*; diff --git a/src/raws/item_structs.rs b/src/raws/item_structs.rs index 2015bd7..2bc1d5c 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 3c29d0b..0cd73b6 100644 --- a/src/raws/rawmaster.rs +++ b/src/raws/rawmaster.rs @@ -290,6 +290,18 @@ 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 {