2021-12-23 13:07:50 -05:00
|
|
|
use std::collections::{HashMap, HashSet};
|
2021-12-23 11:38:37 -05:00
|
|
|
|
2022-01-04 10:08:06 -05:00
|
|
|
use ::regex::Regex;
|
2022-01-21 11:57:36 -05:00
|
|
|
use ::rltk::{console, RandomNumberGenerator, RGB};
|
2021-12-24 10:38:44 -05:00
|
|
|
use ::specs::prelude::*;
|
2022-01-04 11:11:38 -05:00
|
|
|
use ::specs::saveload::{MarkedBuilder, SimpleMarker};
|
2021-12-23 11:38:37 -05:00
|
|
|
|
|
|
|
use crate::components::*;
|
2022-01-03 16:30:14 -05:00
|
|
|
use crate::gamesystem::{mana_at_level, npc_hp};
|
2022-01-19 10:15:51 -05:00
|
|
|
use crate::map::MasterDungeonMap;
|
2021-12-23 13:07:50 -05:00
|
|
|
use crate::random_table::RandomTable;
|
2022-01-19 10:15:51 -05:00
|
|
|
use crate::raws::{Raws, Reaction, RAWS};
|
2021-12-23 11:38:37 -05:00
|
|
|
|
2022-01-04 10:08:06 -05:00
|
|
|
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;
|
|
|
|
|
2022-01-04 11:11:38 -05:00
|
|
|
for cap in DICE_RE.captures_iter(dice) {
|
2022-01-04 10:08:06 -05:00
|
|
|
if let Some(group) = cap.get(1) {
|
|
|
|
n_dice = group.as_str().parse::<i32>().expect("Not a digit");
|
|
|
|
}
|
|
|
|
if let Some(group) = cap.get(2) {
|
|
|
|
die_type = group.as_str().parse::<i32>().expect("Not a digit");
|
|
|
|
}
|
|
|
|
if let Some(group) = cap.get(3) {
|
|
|
|
die_bonus = group.as_str().parse::<i32>().expect("Not a digit");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
(n_dice, die_type, die_bonus)
|
|
|
|
}
|
|
|
|
|
2021-12-23 11:38:37 -05:00
|
|
|
pub enum SpawnType {
|
|
|
|
AtPosition { x: i32, y: i32 },
|
2022-01-04 11:11:38 -05:00
|
|
|
Equipped { by: Entity },
|
|
|
|
Carried { by: Entity },
|
2021-12-23 11:38:37 -05:00
|
|
|
}
|
|
|
|
|
2022-01-25 09:58:30 -05:00
|
|
|
#[derive(Debug, Default)]
|
2021-12-23 11:38:37 -05:00
|
|
|
pub struct RawMaster {
|
|
|
|
raws: Raws,
|
|
|
|
item_index: HashMap<String, usize>,
|
2021-12-23 12:31:03 -05:00
|
|
|
mob_index: HashMap<String, usize>,
|
2021-12-23 12:48:09 -05:00
|
|
|
prop_index: HashMap<String, usize>,
|
2022-01-05 09:42:36 -05:00
|
|
|
loot_index: HashMap<String, usize>,
|
2022-01-11 14:16:23 -05:00
|
|
|
faction_index: HashMap<String, HashMap<String, Reaction>>,
|
2022-01-25 09:58:30 -05:00
|
|
|
spell_index: HashMap<String, usize>,
|
2021-12-23 11:38:37 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
impl RawMaster {
|
|
|
|
pub fn empty() -> RawMaster {
|
2022-01-25 09:58:30 -05:00
|
|
|
RawMaster::default()
|
2021-12-23 11:38:37 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
pub fn load(&mut self, raws: Raws) {
|
|
|
|
self.raws = raws;
|
|
|
|
self.item_index = HashMap::new();
|
2021-12-23 13:07:50 -05:00
|
|
|
let mut used_names: HashSet<String> = HashSet::new();
|
2021-12-23 11:38:37 -05:00
|
|
|
for (i, item) in self.raws.items.iter().enumerate() {
|
2021-12-23 13:07:50 -05:00
|
|
|
if used_names.contains(&item.name) {
|
2022-01-21 11:57:36 -05:00
|
|
|
console::log(format!(
|
2021-12-23 13:07:50 -05:00
|
|
|
"WARNING - duplicate item name in raws [{}]",
|
|
|
|
item.name
|
|
|
|
));
|
|
|
|
}
|
2021-12-23 11:38:37 -05:00
|
|
|
self.item_index.insert(item.name.clone(), i);
|
2021-12-23 13:07:50 -05:00
|
|
|
used_names.insert(item.name.clone());
|
2021-12-23 11:38:37 -05:00
|
|
|
}
|
2021-12-23 12:31:03 -05:00
|
|
|
for (i, mob) in self.raws.mobs.iter().enumerate() {
|
2021-12-23 13:07:50 -05:00
|
|
|
if used_names.contains(&mob.name) {
|
2022-01-21 11:57:36 -05:00
|
|
|
console::log(format!(
|
2021-12-23 13:07:50 -05:00
|
|
|
"WARNING - duplicate mob name in raws [{}]",
|
|
|
|
mob.name
|
|
|
|
));
|
|
|
|
}
|
2021-12-23 12:31:03 -05:00
|
|
|
self.mob_index.insert(mob.name.clone(), i);
|
2021-12-23 13:07:50 -05:00
|
|
|
used_names.insert(mob.name.clone());
|
2021-12-23 12:31:03 -05:00
|
|
|
}
|
2021-12-23 12:48:09 -05:00
|
|
|
for (i, prop) in self.raws.props.iter().enumerate() {
|
2021-12-23 13:07:50 -05:00
|
|
|
if used_names.contains(&prop.name) {
|
2022-01-21 11:57:36 -05:00
|
|
|
console::log(format!(
|
2021-12-23 13:07:50 -05:00
|
|
|
"WARNING - duplicate prop name in raws [{}]",
|
|
|
|
prop.name
|
|
|
|
));
|
|
|
|
}
|
2021-12-23 12:48:09 -05:00
|
|
|
self.prop_index.insert(prop.name.clone(), i);
|
2021-12-23 13:07:50 -05:00
|
|
|
used_names.insert(prop.name.clone());
|
|
|
|
}
|
|
|
|
|
|
|
|
for spawn in self.raws.spawn_table.iter() {
|
|
|
|
if !used_names.contains(&spawn.name) {
|
2022-01-21 11:57:36 -05:00
|
|
|
console::log(format!(
|
2021-12-23 13:07:50 -05:00
|
|
|
"WARNING - Spawn tables references unspecified entity {}",
|
|
|
|
spawn.name
|
|
|
|
));
|
|
|
|
}
|
2021-12-23 12:48:09 -05:00
|
|
|
}
|
2022-01-05 09:42:36 -05:00
|
|
|
|
|
|
|
for (i, loot) in self.raws.loot_tables.iter().enumerate() {
|
|
|
|
self.loot_index.insert(loot.name.clone(), i);
|
|
|
|
}
|
2022-01-11 14:16:23 -05:00
|
|
|
|
|
|
|
for faction in self.raws.faction_table.iter() {
|
|
|
|
let mut reactions: HashMap<String, Reaction> = HashMap::new();
|
|
|
|
for other in faction.responses.iter() {
|
|
|
|
reactions.insert(
|
|
|
|
other.0.clone(),
|
|
|
|
match other.1.as_str() {
|
|
|
|
"ignore" => Reaction::Ignore,
|
|
|
|
"flee" => Reaction::Flee,
|
|
|
|
_ => Reaction::Attack,
|
|
|
|
},
|
|
|
|
);
|
|
|
|
}
|
|
|
|
self.faction_index.insert(faction.name.clone(), reactions);
|
|
|
|
}
|
2022-01-25 09:58:30 -05:00
|
|
|
|
|
|
|
for (i, spell) in self.raws.spells.iter().enumerate() {
|
|
|
|
self.spell_index.insert(spell.name.clone(), i);
|
|
|
|
}
|
2021-12-23 12:31:03 -05:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-01-11 14:16:23 -05:00
|
|
|
#[inline(always)]
|
|
|
|
pub fn faction_reaction(my_faction: &str, their_faction: &str, raws: &RawMaster) -> Reaction {
|
|
|
|
if raws.faction_index.contains_key(my_faction) {
|
|
|
|
let mf = &raws.faction_index[my_faction];
|
|
|
|
|
|
|
|
return if mf.contains_key(their_faction) {
|
|
|
|
mf[their_faction]
|
|
|
|
} else if mf.contains_key("Default") {
|
|
|
|
mf["Default"]
|
|
|
|
} else {
|
|
|
|
Reaction::Ignore
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
Reaction::Ignore
|
|
|
|
}
|
|
|
|
|
2022-01-04 11:11:38 -05:00
|
|
|
fn find_slot_for_equippable_item(tag: &str, raws: &RawMaster) -> EquipmentSlot {
|
|
|
|
if !raws.item_index.contains_key(tag) {
|
|
|
|
panic!("Trying to equip an unknown item: {}", tag);
|
|
|
|
}
|
|
|
|
|
|
|
|
let item_index = raws.item_index[tag];
|
|
|
|
let item = &raws.raws.items[item_index];
|
|
|
|
|
|
|
|
if item.weapon.is_some() {
|
|
|
|
return EquipmentSlot::Melee;
|
|
|
|
} else if let Some(wearable) = &item.wearable {
|
|
|
|
return string_to_slot(&wearable.slot);
|
|
|
|
}
|
|
|
|
|
|
|
|
panic!("Trying to equip {}, but it has not slot tag", tag);
|
|
|
|
}
|
|
|
|
|
2022-01-13 11:29:20 -05:00
|
|
|
pub fn get_vendor_items(categories: &[String], raws: &RawMaster) -> Vec<(String, f32)> {
|
|
|
|
let mut result = Vec::new();
|
|
|
|
|
|
|
|
for item in raws.raws.items.iter() {
|
|
|
|
if let Some(cat) = &item.vendor_category {
|
|
|
|
if categories.contains(cat) && item.base_value.is_some() {
|
|
|
|
result.push((item.name.clone(), item.base_value.unwrap()))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
result
|
|
|
|
}
|
|
|
|
|
2022-01-19 10:15:51 -05:00
|
|
|
pub fn get_scroll_tags() -> Vec<String> {
|
|
|
|
let raws = &RAWS.lock().unwrap();
|
|
|
|
let mut result = Vec::new();
|
|
|
|
|
|
|
|
for item in raws.raws.items.iter() {
|
|
|
|
if let Some(magic) = &item.magic {
|
|
|
|
if &magic.naming == "scroll" {
|
|
|
|
result.push(item.name.clone());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
result
|
|
|
|
}
|
|
|
|
|
2022-01-19 14:35:13 -05:00
|
|
|
pub fn get_potion_tags() -> Vec<String> {
|
|
|
|
let raws = &RAWS.lock().unwrap();
|
|
|
|
let mut result = Vec::new();
|
|
|
|
|
|
|
|
for item in raws.raws.items.iter() {
|
|
|
|
if let Some(magic) = &item.magic {
|
|
|
|
if &magic.naming == "potion" {
|
|
|
|
result.push(item.name.clone());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
result
|
|
|
|
}
|
|
|
|
|
2022-01-19 11:04:10 -05:00
|
|
|
pub fn is_tag_magic(tag: &str) -> bool {
|
|
|
|
let raws = &RAWS.lock().unwrap();
|
|
|
|
if raws.item_index.contains_key(tag) {
|
|
|
|
let item_template = &raws.raws.items[raws.item_index[tag]];
|
|
|
|
item_template.magic.is_some()
|
|
|
|
} else {
|
|
|
|
false
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-01-04 11:11:38 -05:00
|
|
|
fn spawn_position<'a>(
|
|
|
|
pos: SpawnType,
|
|
|
|
new_entity: EntityBuilder<'a>,
|
|
|
|
tag: &str,
|
|
|
|
raws: &RawMaster,
|
|
|
|
) -> EntityBuilder<'a> {
|
|
|
|
let eb = new_entity;
|
2021-12-23 12:31:03 -05:00
|
|
|
|
|
|
|
// Spawn in the specified location
|
|
|
|
match pos {
|
2022-01-04 11:11:38 -05:00
|
|
|
SpawnType::AtPosition { x, y } => eb.with(Position { x, y }),
|
|
|
|
SpawnType::Carried { by } => eb.with(InBackpack { owner: by }),
|
|
|
|
SpawnType::Equipped { by } => {
|
|
|
|
let slot = find_slot_for_equippable_item(tag, raws);
|
|
|
|
eb.with(Equipped { owner: by, slot })
|
2021-12-23 12:31:03 -05:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fn get_renderable_component(
|
|
|
|
renderable: &super::item_structs::Renderable,
|
|
|
|
) -> crate::components::Renderable {
|
|
|
|
crate::components::Renderable {
|
|
|
|
glyph: rltk::to_cp437(renderable.glyph.chars().next().unwrap()),
|
2022-01-14 12:19:46 -05:00
|
|
|
fg: RGB::from_hex(&renderable.fg).expect("Invalid color"),
|
|
|
|
bg: RGB::from_hex(&renderable.bg).expect("Invalid color"),
|
2021-12-23 12:31:03 -05:00
|
|
|
render_order: renderable.order,
|
2021-12-23 11:38:37 -05:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-01-04 11:11:38 -05:00
|
|
|
pub fn string_to_slot(slot: &str) -> EquipmentSlot {
|
|
|
|
match slot {
|
|
|
|
"Shield" => EquipmentSlot::Shield,
|
|
|
|
"Head" => EquipmentSlot::Head,
|
|
|
|
"Torso" => EquipmentSlot::Torso,
|
|
|
|
"Legs" => EquipmentSlot::Legs,
|
|
|
|
"Feet" => EquipmentSlot::Feet,
|
|
|
|
"Hands" => EquipmentSlot::Hands,
|
|
|
|
"Melee" => EquipmentSlot::Melee,
|
|
|
|
_ => {
|
2022-01-21 11:57:36 -05:00
|
|
|
console::log(format!("Warning: unknown equipment slot type [{}]", slot));
|
2022-01-04 11:11:38 -05:00
|
|
|
|
|
|
|
EquipmentSlot::Melee
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-01-20 19:41:16 -05:00
|
|
|
fn parse_particle_line(n: &str) -> SpawnParticleLine {
|
|
|
|
let tokens: Vec<_> = n.split(';').collect();
|
|
|
|
SpawnParticleLine {
|
|
|
|
glyph: rltk::to_cp437(tokens[0].chars().next().unwrap()),
|
|
|
|
color: RGB::from_hex(tokens[1]).expect("Invalid hex rgb color"),
|
|
|
|
lifetime_ms: tokens[2].parse::<f32>().unwrap(),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fn parse_particle(n: &str) -> SpawnParticleBurst {
|
|
|
|
let tokens: Vec<_> = n.split(';').collect();
|
|
|
|
SpawnParticleBurst {
|
|
|
|
glyph: rltk::to_cp437(tokens[0].chars().next().unwrap()),
|
|
|
|
color: RGB::from_hex(tokens[1]).expect("Invalid hex rgb color"),
|
|
|
|
lifetime_ms: tokens[2].parse::<f32>().unwrap(),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-01-20 16:24:12 -05:00
|
|
|
macro_rules! apply_effects {
|
|
|
|
($effects:expr, $eb:expr) => {
|
|
|
|
for effect in $effects.iter() {
|
|
|
|
let effect_name = effect.0.as_str();
|
|
|
|
match effect_name {
|
|
|
|
"provides_healing" => {
|
|
|
|
$eb = $eb.with(ProvidesHealing {
|
|
|
|
heal_amount: effect.1.parse::<i32>().unwrap(),
|
2022-01-24 10:58:37 -05:00
|
|
|
});
|
2022-01-20 16:24:12 -05:00
|
|
|
}
|
2022-01-25 11:42:02 -05:00
|
|
|
"provides_mana" => {
|
|
|
|
$eb = $eb.with(ProvidesMana {
|
|
|
|
mana_amount: effect.1.parse::<i32>().unwrap(),
|
|
|
|
})
|
|
|
|
}
|
2022-01-25 13:45:44 -05:00
|
|
|
"teach_spell" => {
|
|
|
|
$eb = $eb.with(TeachesSpell {
|
|
|
|
spell: effect.1.to_string(),
|
|
|
|
})
|
|
|
|
}
|
2022-01-20 16:24:12 -05:00
|
|
|
"ranged" => {
|
|
|
|
$eb = $eb.with(Ranged {
|
|
|
|
range: effect.1.parse::<i32>().unwrap(),
|
2022-01-24 10:58:37 -05:00
|
|
|
});
|
2022-01-20 16:24:12 -05:00
|
|
|
}
|
|
|
|
"damage" => {
|
|
|
|
$eb = $eb.with(InflictsDamage {
|
|
|
|
damage: effect.1.parse::<i32>().unwrap(),
|
2022-01-24 10:58:37 -05:00
|
|
|
});
|
2022-01-20 16:24:12 -05:00
|
|
|
}
|
|
|
|
"area_of_effect" => {
|
|
|
|
$eb = $eb.with(AreaOfEffect {
|
|
|
|
radius: effect.1.parse::<i32>().unwrap(),
|
2022-01-24 10:58:37 -05:00
|
|
|
});
|
2022-01-20 16:24:12 -05:00
|
|
|
}
|
|
|
|
"confusion" => {
|
2022-01-24 10:58:37 -05:00
|
|
|
$eb = $eb.with(Confusion {});
|
|
|
|
$eb = $eb.with(Duration {
|
2022-01-20 16:24:12 -05:00
|
|
|
turns: effect.1.parse::<i32>().unwrap(),
|
2022-01-24 10:58:37 -05:00
|
|
|
});
|
2022-01-20 16:24:12 -05:00
|
|
|
}
|
|
|
|
"magic_mapping" => $eb = $eb.with(MagicMapper {}),
|
|
|
|
"town_portal" => $eb = $eb.with(TownPortal {}),
|
|
|
|
"food" => $eb = $eb.with(ProvidesFood {}),
|
|
|
|
"single_activation" => $eb = $eb.with(SingleActivation {}),
|
2022-01-20 19:41:16 -05:00
|
|
|
"particle_line" => $eb = $eb.with(parse_particle_line(&effect.1)),
|
|
|
|
"particle" => $eb = $eb.with(parse_particle(&effect.1)),
|
2022-01-21 11:57:36 -05:00
|
|
|
"remove_curse" => $eb = $eb.with(ProvidesRemoveCurse {}),
|
2022-01-25 11:42:02 -05:00
|
|
|
"identify" => $eb = $eb.with(ProvidesIdentification {}),
|
2022-01-25 13:45:44 -05:00
|
|
|
"slow" => {
|
|
|
|
$eb = $eb.with(Slow {
|
|
|
|
initiative_penalty: effect.1.parse::<f32>().unwrap(),
|
|
|
|
})
|
|
|
|
}
|
|
|
|
"damage_over_time" => {
|
|
|
|
$eb = $eb.with(DamageOverTime {
|
|
|
|
damage: effect.1.parse::<i32>().unwrap(),
|
2022-01-25 13:39:43 -05:00
|
|
|
})
|
|
|
|
}
|
2022-01-20 16:24:12 -05:00
|
|
|
_ => {
|
2022-01-21 11:57:36 -05:00
|
|
|
console::log(format!(
|
2022-01-25 11:42:02 -05:00
|
|
|
"WARNING: consumable effect '{}' not implemented.",
|
2022-01-20 16:24:12 -05:00
|
|
|
effect_name
|
|
|
|
));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
2021-12-23 11:38:37 -05:00
|
|
|
pub fn spawn_named_item(
|
|
|
|
raws: &RawMaster,
|
2022-01-04 11:11:38 -05:00
|
|
|
ecs: &mut World,
|
2021-12-23 11:38:37 -05:00
|
|
|
key: &str,
|
|
|
|
pos: SpawnType,
|
|
|
|
) -> Option<Entity> {
|
|
|
|
if raws.item_index.contains_key(key) {
|
|
|
|
let item_template = &raws.raws.items[raws.item_index[key]];
|
2022-01-19 11:04:10 -05:00
|
|
|
|
|
|
|
let dm = ecs.fetch::<MasterDungeonMap>();
|
|
|
|
let scroll_names = dm.scroll_mappings.clone();
|
2022-01-19 14:35:13 -05:00
|
|
|
let potion_names = dm.potion_mappings.clone();
|
2022-01-19 11:04:10 -05:00
|
|
|
let identified = dm.identified_items.clone();
|
|
|
|
std::mem::drop(dm);
|
|
|
|
|
2022-01-04 11:11:38 -05:00
|
|
|
let mut eb = ecs.create_entity().marked::<SimpleMarker<SerializeMe>>();
|
2021-12-23 11:38:37 -05:00
|
|
|
|
|
|
|
// Spawn in the specified location
|
2022-01-04 11:11:38 -05:00
|
|
|
eb = spawn_position(pos, eb, key, raws);
|
2021-12-23 11:38:37 -05:00
|
|
|
|
|
|
|
// Renderable
|
|
|
|
if let Some(renderable) = &item_template.renderable {
|
2021-12-23 12:31:03 -05:00
|
|
|
eb = eb.with(get_renderable_component(renderable));
|
2021-12-23 11:38:37 -05:00
|
|
|
}
|
|
|
|
|
2021-12-23 12:05:56 -05:00
|
|
|
eb = eb.with(Name::from(&item_template.name));
|
|
|
|
|
2022-01-13 10:14:13 -05:00
|
|
|
eb = eb.with(Item {
|
|
|
|
initiative_penalty: item_template.initiative_penalty.unwrap_or(0.),
|
|
|
|
weight_lbs: item_template.weight_lbs.unwrap_or(0.),
|
|
|
|
base_value: item_template.base_value.unwrap_or(0.),
|
|
|
|
});
|
2021-12-23 11:38:37 -05:00
|
|
|
|
|
|
|
if let Some(consumable) = &item_template.consumable {
|
2022-01-24 10:58:37 -05:00
|
|
|
let max_charges = consumable.charges.unwrap_or(1);
|
|
|
|
eb = eb.with(Consumable {
|
|
|
|
max_charges,
|
|
|
|
charges: max_charges,
|
|
|
|
});
|
2022-01-20 16:24:12 -05:00
|
|
|
apply_effects!(consumable.effects, eb);
|
2021-12-23 11:38:37 -05:00
|
|
|
}
|
|
|
|
|
2021-12-23 12:05:56 -05:00
|
|
|
if let Some(weapon) = &item_template.weapon {
|
|
|
|
eb = eb.with(Equippable {
|
|
|
|
slot: EquipmentSlot::Melee,
|
|
|
|
});
|
2022-01-04 11:11:38 -05:00
|
|
|
let (n_dice, die_type, bonus) = parse_dice_string(&weapon.base_damage);
|
|
|
|
let mut wpn = MeleeWeapon {
|
|
|
|
attribute: WeaponAttribute::Might,
|
|
|
|
damage_n_dice: n_dice,
|
|
|
|
damage_die_type: die_type,
|
|
|
|
damage_bonus: bonus,
|
|
|
|
hit_bonus: weapon.hit_bonus,
|
|
|
|
};
|
|
|
|
|
|
|
|
wpn.attribute = match weapon.attribute.as_str() {
|
|
|
|
"Quickness" => WeaponAttribute::Quickness,
|
|
|
|
_ => WeaponAttribute::Might,
|
|
|
|
};
|
|
|
|
|
|
|
|
eb = eb.with(wpn);
|
2021-12-23 12:05:56 -05:00
|
|
|
}
|
|
|
|
|
2022-01-04 11:11:38 -05:00
|
|
|
if let Some(wearable) = &item_template.wearable {
|
|
|
|
let slot = string_to_slot(&wearable.slot);
|
|
|
|
eb = eb.with(Equippable { slot });
|
|
|
|
eb = eb.with(Wearable {
|
|
|
|
slot,
|
|
|
|
armor_class: wearable.armor_class,
|
2021-12-23 12:05:56 -05:00
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2022-01-19 09:40:21 -05:00
|
|
|
if let Some(magic) = &item_template.magic {
|
|
|
|
let class = match magic.class.as_str() {
|
|
|
|
"rare" => MagicItemClass::Rare,
|
|
|
|
"legendary" => MagicItemClass::Legendary,
|
|
|
|
_ => MagicItemClass::Common,
|
|
|
|
};
|
2022-01-19 10:15:51 -05:00
|
|
|
eb = eb.with(MagicItem { class });
|
|
|
|
|
2022-01-19 11:04:10 -05:00
|
|
|
if !identified.contains(&item_template.name) {
|
|
|
|
#[allow(clippy::single_match)]
|
|
|
|
match magic.naming.as_str() {
|
|
|
|
"scroll" => {
|
|
|
|
eb = eb.with(ObfuscatedName {
|
|
|
|
name: scroll_names[&item_template.name].clone(),
|
|
|
|
})
|
|
|
|
}
|
2022-01-19 14:35:13 -05:00
|
|
|
"potion" => {
|
|
|
|
eb = eb.with(ObfuscatedName {
|
|
|
|
name: potion_names[&item_template.name].clone(),
|
|
|
|
})
|
|
|
|
}
|
|
|
|
_ => {
|
|
|
|
eb = eb.with(ObfuscatedName {
|
|
|
|
name: magic.naming.clone(),
|
|
|
|
})
|
|
|
|
}
|
2022-01-19 10:15:51 -05:00
|
|
|
}
|
|
|
|
}
|
2022-01-21 11:18:53 -05:00
|
|
|
|
|
|
|
if let Some(cursed) = magic.cursed {
|
|
|
|
if cursed {
|
|
|
|
eb = eb.with(CursedItem {});
|
|
|
|
}
|
|
|
|
}
|
2022-01-19 09:40:21 -05:00
|
|
|
}
|
|
|
|
|
2022-01-24 09:56:42 -05:00
|
|
|
if let Some(ab) = &item_template.attributes {
|
|
|
|
eb = eb.with(AttributeBonus {
|
|
|
|
might: ab.might,
|
|
|
|
fitness: ab.fitness,
|
|
|
|
quickness: ab.quickness,
|
|
|
|
intelligence: ab.intelligence,
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2021-12-23 11:38:37 -05:00
|
|
|
return Some(eb.build());
|
|
|
|
}
|
|
|
|
|
|
|
|
None
|
|
|
|
}
|
2021-12-23 12:31:03 -05:00
|
|
|
|
|
|
|
pub fn spawn_named_mob(
|
|
|
|
raws: &RawMaster,
|
2022-01-04 11:11:38 -05:00
|
|
|
ecs: &mut World,
|
2021-12-23 12:31:03 -05:00
|
|
|
key: &str,
|
|
|
|
pos: SpawnType,
|
|
|
|
) -> Option<Entity> {
|
|
|
|
if raws.mob_index.contains_key(key) {
|
|
|
|
let mob_template = &raws.raws.mobs[raws.mob_index[key]];
|
|
|
|
|
2022-01-04 11:11:38 -05:00
|
|
|
let mut eb = ecs.create_entity().marked::<SimpleMarker<SerializeMe>>();
|
2021-12-23 12:31:03 -05:00
|
|
|
|
|
|
|
// Spawn in the specified location
|
2022-01-04 11:11:38 -05:00
|
|
|
eb = spawn_position(pos, eb, key, raws);
|
2021-12-23 12:31:03 -05:00
|
|
|
|
2022-01-11 09:28:45 -05:00
|
|
|
// Initiative of 2
|
|
|
|
eb = eb.with(Initiative { current: 2 });
|
|
|
|
|
2021-12-23 12:31:03 -05:00
|
|
|
// Renderable
|
|
|
|
if let Some(renderable) = &mob_template.renderable {
|
|
|
|
eb = eb.with(get_renderable_component(renderable));
|
|
|
|
}
|
|
|
|
|
|
|
|
eb = eb.with(Name::from(&mob_template.name));
|
|
|
|
|
2022-01-11 14:16:23 -05:00
|
|
|
eb = match mob_template.movement.as_ref() {
|
|
|
|
"random" => eb.with(MoveMode {
|
|
|
|
mode: Movement::Random,
|
|
|
|
}),
|
2022-01-11 15:35:59 -05:00
|
|
|
"random_waypoint" => eb.with(MoveMode {
|
|
|
|
mode: Movement::RandomWaypoint { path: None },
|
|
|
|
}),
|
2022-01-11 14:16:23 -05:00
|
|
|
_ => eb.with(MoveMode {
|
|
|
|
mode: Movement::Static,
|
|
|
|
}),
|
2021-12-24 13:23:56 -05:00
|
|
|
};
|
|
|
|
|
2022-01-03 10:49:12 -05:00
|
|
|
// Quips
|
|
|
|
if let Some(quips) = &mob_template.quips {
|
|
|
|
eb = eb.with(Quips {
|
|
|
|
available: quips.clone(),
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2022-01-04 11:11:38 -05:00
|
|
|
if mob_template.blocks_tile {
|
|
|
|
eb = eb.with(BlocksTile {});
|
|
|
|
}
|
|
|
|
|
2022-01-03 16:30:14 -05:00
|
|
|
let mut mob_fitness = 11;
|
|
|
|
let mut mob_int = 11;
|
2022-01-03 15:21:12 -05:00
|
|
|
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);
|
2022-01-03 16:30:14 -05:00
|
|
|
mob_fitness = fitness;
|
2022-01-03 15:21:12 -05:00
|
|
|
}
|
|
|
|
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);
|
2022-01-03 16:30:14 -05:00
|
|
|
mob_int = intelligence;
|
2022-01-03 15:21:12 -05:00
|
|
|
}
|
|
|
|
eb = eb.with(attr);
|
|
|
|
|
2022-01-03 16:30:14 -05:00
|
|
|
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,
|
2022-01-13 10:14:13 -05:00
|
|
|
hit_points: Pool::new(mob_hp),
|
|
|
|
mana: Pool::new(mob_mana),
|
|
|
|
total_weight: 0.,
|
|
|
|
total_initiative_penalty: 0.,
|
2022-01-13 10:42:02 -05:00
|
|
|
gold: if let Some(gold) = &mob_template.gold {
|
|
|
|
let mut rng = RandomNumberGenerator::new();
|
2022-01-13 11:29:20 -05:00
|
|
|
let (n, d, b) = parse_dice_string(gold);
|
2022-01-13 10:42:02 -05:00
|
|
|
(rng.roll_dice(n, d) + b) as f32
|
|
|
|
} else {
|
|
|
|
0.0
|
|
|
|
},
|
2022-01-14 09:44:01 -05:00
|
|
|
god_mode: false,
|
2022-01-03 16:30:14 -05:00
|
|
|
};
|
|
|
|
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);
|
|
|
|
}
|
|
|
|
_ => {
|
2022-01-21 11:57:36 -05:00
|
|
|
console::log(format!("Unknown skill referenced [{}]", sk.0));
|
2022-01-03 16:30:14 -05:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
eb = eb.with(skills);
|
|
|
|
|
2021-12-23 12:31:03 -05:00
|
|
|
eb = eb.with(Viewshed {
|
|
|
|
visible_tiles: Vec::new(),
|
|
|
|
range: mob_template.vision_range,
|
|
|
|
dirty: true,
|
|
|
|
});
|
|
|
|
|
2022-01-04 11:29:23 -05:00
|
|
|
if let Some(na) = &mob_template.natural {
|
|
|
|
let mut nature = NaturalAttackDefense {
|
|
|
|
armor_class: na.armor_class,
|
|
|
|
attacks: Vec::new(),
|
|
|
|
};
|
|
|
|
if let Some(attacks) = &na.attacks {
|
|
|
|
for nattack in attacks.iter() {
|
|
|
|
let (n, d, b) = parse_dice_string(&nattack.damage);
|
|
|
|
let attack = NaturalAttack {
|
|
|
|
name: nattack.name.clone(),
|
|
|
|
hit_bonus: nattack.hit_bonus,
|
|
|
|
damage_n_dice: n,
|
|
|
|
damage_die_type: d,
|
|
|
|
damage_bonus: b,
|
|
|
|
};
|
|
|
|
nature.attacks.push(attack);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
eb = eb.with(nature);
|
|
|
|
}
|
|
|
|
|
2022-01-05 09:42:36 -05:00
|
|
|
if let Some(loot) = &mob_template.loot_table {
|
|
|
|
eb = eb.with(LootTable {
|
|
|
|
table: loot.clone(),
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2022-01-10 10:28:04 -05:00
|
|
|
if let Some(light) = &mob_template.light {
|
|
|
|
eb = eb.with(LightSource {
|
|
|
|
range: light.range,
|
2022-01-14 12:19:46 -05:00
|
|
|
color: RGB::from_hex(&light.color).expect("Invalid color"),
|
2022-01-10 10:28:04 -05:00
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2022-01-11 14:16:23 -05:00
|
|
|
if let Some(faction) = &mob_template.faction {
|
|
|
|
eb = eb.with(Faction::from(faction));
|
|
|
|
} else {
|
|
|
|
eb = eb.with(Faction::from("Mindless"));
|
|
|
|
}
|
|
|
|
|
2022-01-13 11:29:20 -05:00
|
|
|
if let Some(vendor) = &mob_template.vendor {
|
|
|
|
eb = eb.with(Vendor {
|
|
|
|
categories: vendor.clone(),
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2022-01-04 11:11:38 -05:00
|
|
|
let new_mob = eb.build();
|
|
|
|
|
2022-01-13 11:29:20 -05:00
|
|
|
// Are they wielding anything?
|
2022-01-04 11:11:38 -05:00
|
|
|
if let Some(wielding) = &mob_template.equipped {
|
|
|
|
for tag in wielding.iter() {
|
|
|
|
spawn_named_entity(raws, ecs, tag, SpawnType::Equipped { by: new_mob });
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return Some(new_mob);
|
2021-12-23 12:31:03 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
None
|
|
|
|
}
|
|
|
|
|
2021-12-23 12:48:09 -05:00
|
|
|
pub fn spawn_named_prop(
|
|
|
|
raws: &RawMaster,
|
2022-01-04 11:11:38 -05:00
|
|
|
ecs: &mut World,
|
2021-12-23 12:48:09 -05:00
|
|
|
key: &str,
|
|
|
|
pos: SpawnType,
|
|
|
|
) -> Option<Entity> {
|
|
|
|
if raws.prop_index.contains_key(key) {
|
|
|
|
let prop_template = &raws.raws.props[raws.prop_index[key]];
|
|
|
|
|
2022-01-04 11:11:38 -05:00
|
|
|
let mut eb = ecs.create_entity().marked::<SimpleMarker<SerializeMe>>();
|
2021-12-23 12:48:09 -05:00
|
|
|
|
|
|
|
// Spawn in the specified location
|
2022-01-04 11:11:38 -05:00
|
|
|
eb = spawn_position(pos, eb, key, raws);
|
2021-12-23 12:48:09 -05:00
|
|
|
|
|
|
|
// 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 {});
|
2022-01-20 16:24:12 -05:00
|
|
|
apply_effects!(entry_trigger.effects, eb);
|
2021-12-23 12:48:09 -05:00
|
|
|
}
|
2022-01-14 10:47:43 -05:00
|
|
|
if let Some(light) = &prop_template.light {
|
|
|
|
eb = eb
|
|
|
|
.with(LightSource {
|
|
|
|
range: light.range,
|
|
|
|
color: RGB::from_hex(&light.color).expect("Invalid color"),
|
|
|
|
})
|
|
|
|
.with(Viewshed {
|
|
|
|
range: light.range,
|
|
|
|
dirty: true,
|
|
|
|
visible_tiles: Vec::new(),
|
|
|
|
});
|
|
|
|
}
|
2021-12-23 12:48:09 -05:00
|
|
|
|
|
|
|
return Some(eb.build());
|
|
|
|
}
|
|
|
|
|
|
|
|
None
|
|
|
|
}
|
|
|
|
|
2021-12-23 12:31:03 -05:00
|
|
|
pub fn spawn_named_entity(
|
|
|
|
raws: &RawMaster,
|
2022-01-04 11:11:38 -05:00
|
|
|
ecs: &mut World,
|
2021-12-23 12:31:03 -05:00
|
|
|
key: &str,
|
|
|
|
pos: SpawnType,
|
|
|
|
) -> Option<Entity> {
|
|
|
|
if raws.item_index.contains_key(key) {
|
2022-01-04 11:11:38 -05:00
|
|
|
return spawn_named_item(raws, ecs, key, pos);
|
2021-12-23 12:31:03 -05:00
|
|
|
} else if raws.mob_index.contains_key(key) {
|
2022-01-04 11:11:38 -05:00
|
|
|
return spawn_named_mob(raws, ecs, key, pos);
|
2021-12-23 12:48:09 -05:00
|
|
|
} else if raws.prop_index.contains_key(key) {
|
2022-01-04 11:11:38 -05:00
|
|
|
return spawn_named_prop(raws, ecs, key, pos);
|
2021-12-23 12:31:03 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
None
|
|
|
|
}
|
2021-12-23 13:07:50 -05:00
|
|
|
|
2022-01-25 09:58:30 -05:00
|
|
|
pub fn spawn_named_spell(raws: &RawMaster, ecs: &mut World, key: &str) -> Option<Entity> {
|
|
|
|
if raws.spell_index.contains_key(key) {
|
|
|
|
let spell_template = &raws.raws.spells[raws.spell_index[key]];
|
|
|
|
|
|
|
|
let mut eb = ecs
|
|
|
|
.create_entity()
|
|
|
|
.marked::<SimpleMarker<SerializeMe>>()
|
|
|
|
.with(SpellTemplate {
|
|
|
|
mana_cost: spell_template.mana_cost,
|
|
|
|
})
|
2022-01-25 11:15:32 -05:00
|
|
|
.with(Name::from(spell_template.name.clone()));
|
2022-01-25 09:58:30 -05:00
|
|
|
|
|
|
|
apply_effects!(spell_template.effects, eb);
|
|
|
|
|
|
|
|
return Some(eb.build());
|
|
|
|
}
|
|
|
|
|
|
|
|
None
|
|
|
|
}
|
|
|
|
|
2021-12-23 13:07:50 -05:00
|
|
|
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
|
|
|
|
}
|
2022-01-05 10:01:05 -05:00
|
|
|
|
|
|
|
pub fn get_item_drop(
|
|
|
|
raws: &RawMaster,
|
|
|
|
rng: &mut RandomNumberGenerator,
|
|
|
|
table: &str,
|
|
|
|
) -> Option<String> {
|
|
|
|
if raws.loot_index.contains_key(table) {
|
|
|
|
let mut rt = RandomTable::new();
|
|
|
|
let available_options = &raws.raws.loot_tables[raws.loot_index[table]];
|
|
|
|
for item in available_options.drops.iter() {
|
|
|
|
rt = rt.add(item.name.clone(), item.weight);
|
|
|
|
}
|
|
|
|
|
|
|
|
return Some(rt.roll(rng));
|
|
|
|
}
|
|
|
|
|
|
|
|
None
|
|
|
|
}
|
2022-01-25 09:58:30 -05:00
|
|
|
|
|
|
|
pub fn spawn_all_spells(ecs: &mut World) {
|
|
|
|
let raws = &RAWS.lock().unwrap();
|
|
|
|
for spell in raws.raws.spells.iter() {
|
|
|
|
spawn_named_spell(raws, ecs, &spell.name);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn find_spell_entity(ecs: &World, name: &str) -> Option<Entity> {
|
|
|
|
let names = ecs.read_storage::<Name>();
|
|
|
|
let spell_templates = ecs.read_storage::<SpellTemplate>();
|
|
|
|
let entities = ecs.entities();
|
|
|
|
|
|
|
|
for (entity, sname, _template) in (&entities, &names, &spell_templates).join() {
|
|
|
|
if name == sname.name {
|
|
|
|
return Some(entity);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
None
|
|
|
|
}
|