use std::collections::{HashMap, HashSet}; use ::regex::Regex; use ::specs::prelude::*; use crate::components::*; use crate::gamesystem::{mana_at_level, npc_hp}; use crate::random_table::RandomTable; use crate::raws::Raws; pub fn parse_dice_string(dice: &str) -> (i32, i32, i32) { lazy_static! { static ref DICE_RE: Regex = Regex::new(r"(\d+)d(\d+)([\+\-]\d+)?").unwrap(); } let mut n_dice = 1; let mut die_type = 4; let mut die_bonus = 0; for cap in DIC_RE.captures_iter(dice) { if let Some(group) = cap.get(1) { n_dice = group.as_str().parse::().expect("Not a digit"); } if let Some(group) = cap.get(2) { die_type = group.as_str().parse::().expect("Not a digit"); } if let Some(group) = cap.get(3) { die_bonus = group.as_str().parse::().expect("Not a digit"); } } (n_dice, die_type, die_bonus) } pub enum SpawnType { AtPosition { x: i32, y: i32 }, } pub struct RawMaster { raws: Raws, item_index: HashMap, mob_index: HashMap, prop_index: HashMap, } impl RawMaster { pub fn empty() -> RawMaster { RawMaster { raws: Raws { items: Vec::new(), mobs: Vec::new(), props: Vec::new(), spawn_table: Vec::new(), }, item_index: HashMap::new(), mob_index: HashMap::new(), prop_index: HashMap::new(), } } pub fn load(&mut self, raws: Raws) { self.raws = raws; self.item_index = HashMap::new(); let mut used_names: HashSet = HashSet::new(); for (i, item) in self.raws.items.iter().enumerate() { if used_names.contains(&item.name) { rltk::console::log(format!( "WARNING - duplicate item name in raws [{}]", item.name )); } self.item_index.insert(item.name.clone(), i); used_names.insert(item.name.clone()); } for (i, mob) in self.raws.mobs.iter().enumerate() { if used_names.contains(&mob.name) { rltk::console::log(format!( "WARNING - duplicate mob name in raws [{}]", mob.name )); } self.mob_index.insert(mob.name.clone(), i); used_names.insert(mob.name.clone()); } for (i, prop) in self.raws.props.iter().enumerate() { if used_names.contains(&prop.name) { rltk::console::log(format!( "WARNING - duplicate prop name in raws [{}]", prop.name )); } self.prop_index.insert(prop.name.clone(), i); used_names.insert(prop.name.clone()); } for spawn in self.raws.spawn_table.iter() { if !used_names.contains(&spawn.name) { rltk::console::log(format!( "WARNING - Spawn tables references unspecified entity {}", spawn.name )); } } } } fn spawn_position(pos: SpawnType, new_entity: EntityBuilder) -> EntityBuilder { let mut eb = new_entity; // Spawn in the specified location match pos { SpawnType::AtPosition { x, y } => { eb = eb.with(Position { x, y }); } } eb } fn get_renderable_component( renderable: &super::item_structs::Renderable, ) -> crate::components::Renderable { crate::components::Renderable { glyph: rltk::to_cp437(renderable.glyph.chars().next().unwrap()), fg: rltk::RGB::from_hex(&renderable.fg).expect("Invalid RGB"), bg: rltk::RGB::from_hex(&renderable.bg).expect("Invalid RGB"), render_order: renderable.order, } } pub fn spawn_named_item( raws: &RawMaster, new_entity: EntityBuilder, key: &str, pos: SpawnType, ) -> Option { if raws.item_index.contains_key(key) { let item_template = &raws.raws.items[raws.item_index[key]]; let mut eb = new_entity; // Spawn in the specified location eb = spawn_position(pos, eb); // Renderable if let Some(renderable) = &item_template.renderable { eb = eb.with(get_renderable_component(renderable)); } eb = eb.with(Name::from(&item_template.name)); eb = eb.with(Item {}); if let Some(consumable) = &item_template.consumable { eb = eb.with(Consumable {}); for effect in consumable.effects.iter() { let effect_name = effect.0.as_str(); match effect_name { "provides_healing" => { eb = eb.with(ProvidesHealing { heal_amount: effect.1.parse::().unwrap(), }) } "ranged" => { eb = eb.with(Ranged { range: effect.1.parse::().unwrap(), }) } "damage" => { eb = eb.with(InflictsDamage { damage: effect.1.parse::().unwrap(), }) } "area_of_effect" => { eb = eb.with(AreaOfEffect { radius: effect.1.parse::().unwrap(), }) } "confusion" => { eb = eb.with(Confusion { turns: effect.1.parse::().unwrap(), }) } "magic_mapping" => eb = eb.with(MagicMapper {}), "food" => eb = eb.with(ProvidesFood {}), _ => { rltk::console::log(format!( "Warning: consumable effect {} not implemented.", effect_name )); } } } } if let Some(weapon) = &item_template.weapon { eb = eb.with(Equippable { slot: EquipmentSlot::Melee, }); eb = eb.with(MeleePowerBonus { power: weapon.power_bonus, }); } if let Some(shield) = &item_template.shield { eb = eb.with(Equippable { slot: EquipmentSlot::Shield, }); eb = eb.with(DefenseBonus { defense: shield.defense_bonus, }); } return Some(eb.build()); } None } pub fn spawn_named_mob( raws: &RawMaster, new_entity: EntityBuilder, key: &str, pos: SpawnType, ) -> Option { if raws.mob_index.contains_key(key) { let mob_template = &raws.raws.mobs[raws.mob_index[key]]; let mut eb = new_entity; // Spawn in the specified location eb = spawn_position(pos, eb); // Renderable if let Some(renderable) = &mob_template.renderable { eb = eb.with(get_renderable_component(renderable)); } eb = eb.with(Name::from(&mob_template.name)); match mob_template.ai.as_ref() { "melee" => eb = eb.with(Monster {}), "bystander" => eb = eb.with(Bystander {}), "vendor" => eb = eb.with(Vendor {}), _ => {} }; // Quips if let Some(quips) = &mob_template.quips { eb = eb.with(Quips { available: quips.clone(), }); } let mut mob_fitness = 11; let mut mob_int = 11; let mut attr = Attributes { might: Attribute::new(11), fitness: Attribute::new(11), quickness: Attribute::new(11), intelligence: Attribute::new(11), }; if let Some(might) = mob_template.attributes.might { attr.might = Attribute::new(might); } if let Some(fitness) = mob_template.attributes.fitness { attr.fitness = Attribute::new(fitness); mob_fitness = fitness; } if let Some(quickness) = mob_template.attributes.quickness { attr.quickness = Attribute::new(quickness); } if let Some(intelligence) = mob_template.attributes.intelligence { attr.intelligence = Attribute::new(intelligence); mob_int = intelligence; } eb = eb.with(attr); let mob_level = if mob_template.level.is_some() { mob_template.level.unwrap() } else { 1 }; let mob_hp = npc_hp(mob_fitness, mob_level); let mob_mana = mana_at_level(mob_int, mob_level); let pools = Pools { level: mob_level, xp: 0, hit_points: Pool { current: mob_hp, max: mob_hp, }, mana: Pool { current: mob_mana, max: mob_mana, }, }; eb = eb.with(pools); let mut skills = Skills::new(1); if let Some(mobskills) = &mob_template.skills { for sk in mobskills.iter() { match sk.0.as_str() { "Melee" => { skills.skills.insert(Skill::Melee, *sk.1); } "Defense" => { skills.skills.insert(Skill::Defense, *sk.1); } "Magic" => { skills.skills.insert(Skill::Magic, *sk.1); } _ => { ::rltk::console::log(format!("Unknown skill referenced [{}]", sk.0)); } } } } eb = eb.with(skills); if mob_template.blocks_tile { eb = eb.with(BlocksTile {}); } eb = eb.with(Viewshed { visible_tiles: Vec::new(), range: mob_template.vision_range, dirty: true, }); return Some(eb.build()); } None } pub fn spawn_named_prop( raws: &RawMaster, new_entity: EntityBuilder, key: &str, pos: SpawnType, ) -> Option { if raws.prop_index.contains_key(key) { let prop_template = &raws.raws.props[raws.prop_index[key]]; let mut eb = new_entity; // Spawn in the specified location eb = spawn_position(pos, eb); // Renderable if let Some(renderable) = &prop_template.renderable { eb = eb.with(get_renderable_component(renderable)); } eb = eb.with(Name::from(&prop_template.name)); if let Some(hidden) = prop_template.hidden { if hidden { eb = eb.with(Hidden {}) } } if let Some(blocks_tile) = prop_template.blocks_tile { if blocks_tile { eb = eb.with(BlocksTile {}) } } if let Some(blocks_visibility) = prop_template.blocks_visibility { if blocks_visibility { eb = eb.with(BlocksVisibility {}) } } if let Some(door_open) = prop_template.door_open { eb = eb.with(Door { open: door_open }); } if let Some(entry_trigger) = &prop_template.entry_trigger { eb = eb.with(EntryTrigger {}); for effect in entry_trigger.effects.iter() { match effect.0.as_str() { "damage" => { eb = eb.with(InflictsDamage { damage: effect.1.parse::().unwrap(), }); } "single_activation" => eb = eb.with(SingleActivation {}), _ => {} } } } return Some(eb.build()); } None } pub fn spawn_named_entity( raws: &RawMaster, new_entity: EntityBuilder, key: &str, pos: SpawnType, ) -> Option { if raws.item_index.contains_key(key) { return spawn_named_item(raws, new_entity, key, pos); } else if raws.mob_index.contains_key(key) { return spawn_named_mob(raws, new_entity, key, pos); } else if raws.prop_index.contains_key(key) { return spawn_named_prop(raws, new_entity, key, pos); } None } pub fn get_spawn_table_for_depth(raws: &RawMaster, depth: i32) -> RandomTable { use super::SpawnTableEntry; let available_options: Vec<&SpawnTableEntry> = raws .raws .spawn_table .iter() .filter(|a| depth >= a.min_depth && depth <= a.max_depth) .collect(); let mut rt = RandomTable::new(); for e in available_options.iter() { let mut weight = e.weight; if e.add_map_depth_to_weight.is_some() { weight += depth; } rt = rt.add(e.name.clone(), weight); } rt }