From fe19a449e3edfab7596ac526ceb46025b4bf73d8 Mon Sep 17 00:00:00 2001 From: Llywelwyn Date: Mon, 21 Aug 2023 18:05:08 +0100 Subject: [PATCH] rawmaster macros --- raws/items.json | 33 ++--- src/raws/item_structs.rs | 9 ++ src/raws/rawmaster.rs | 295 +++++++++++++++++---------------------- 3 files changed, 147 insertions(+), 190 deletions(-) diff --git a/raws/items.json b/raws/items.json index 8bf9ffc..6807ad1 100644 --- a/raws/items.json +++ b/raws/items.json @@ -6,7 +6,7 @@ "weight": 1, "value": 50, "flags": ["CONSUMABLE", "DESTRUCTIBLE"], - "effects": { "provides_healing": "4d4+2" }, + "effects": { "healing": "4d4+2" }, "magic": { "class": "uncommon", "naming": "potion" } }, { @@ -16,7 +16,7 @@ "weight": 1, "value": 25, "flags": ["CONSUMABLE", "DESTRUCTIBLE"], - "effects": { "provides_healing": "2d4+2" }, + "effects": { "healing": "2d4+2" }, "magic": { "class": "uncommon", "naming": "potion" } }, { @@ -26,7 +26,7 @@ "weight": 0.5, "value": 50, "flags": ["CONSUMABLE", "DESTRUCTIBLE"], - "effects": { "ranged": "12", "provides_healing": "1d4+2" }, + "effects": { "ranged": "12", "healing": "1d4+2" }, "magic": { "class": "uncommon", "naming": "scroll" } }, { @@ -36,7 +36,7 @@ "weight": 0.5, "value": 200, "flags": ["CONSUMABLE", "DESTRUCTIBLE"], - "effects": { "ranged": "12", "aoe": "3", "provides_healing": "1d4+2" }, + "effects": { "ranged": "12", "aoe": "3", "healing": "1d4+2" }, "magic": { "class": "rare", "naming": "scroll" } }, { @@ -125,8 +125,7 @@ "renderable": { "glyph": ")", "fg": "#808080", "bg": "#000000", "order": 2 }, "weight": 1, "value": 2, - "flags": ["EQUIP_MELEE", "FINESSE"], - "effects": { "base_damage": "1d4" } + "equip": { "slot": "EQUIP_MELEE", "flag": "FINESSE", "damage": "1d4", "to_hit": 0 } }, { "id": "equip_shortsword", @@ -134,8 +133,7 @@ "renderable": { "glyph": ")", "fg": "#C0C0C0", "bg": "#000000", "order": 2 }, "weight": 2, "value": 10, - "flags": ["EQUIP_MELEE", "STRENGTH"], - "effects": { "base_damage": "1d6" } + "equip": { "slot": "EQUIP_MELEE", "flag": "STRENGTH", "damage": "1d6", "to_hit": 0 } }, { "id": "equip_rapier", @@ -143,8 +141,7 @@ "renderable": { "glyph": ")", "fg": "#C0C0C0", "bg": "#000000", "order": 2 }, "weight": 2, "value": 10, - "flags": ["EQUIP_MELEE", "FINESSE"], - "effects": { "base_damage": "1d8" } + "equip": { "slot": "EQUIP_MELEE", "flag": "FINESSE", "damage": "1d8", "to_hit": 0 } }, { "id": "equip_pitchfork", @@ -152,8 +149,7 @@ "renderable": { "glyph": ")", "fg": "#C0C0C0", "bg": "#000000", "order": 2 }, "weight": 2, "value": 5, - "flags": ["EQUIP_MELEE", "FINESSE"], - "effects": { "base_damage": "1d6" } + "equip": { "slot": "EQUIP_MELEE", "flag": "FINESSE", "damage": "1d6", "to_hit": 0 } }, { "id": "equip_sickle", @@ -161,8 +157,7 @@ "renderable": { "glyph": ")", "fg": "#C0C0C0", "bg": "#000000", "order": 2 }, "weight": 2, "value": 5, - "flags": ["EQUIP_MELEE", "FINESSE"], - "effects": { "base_damage": "1d6" } + "equip": { "slot": "EQUIP_MELEE", "flag": "FINESSE", "damage": "1d6", "to_hit": 0 } }, { "id": "equip_handaxe", @@ -170,8 +165,7 @@ "renderable": { "glyph": ")", "fg": "#C0C0C0", "bg": "#000000", "order": 2 }, "weight": 2, "value": 5, - "flags": ["EQUIP_MELEE", "FINESSE"], - "effects": { "base_damage": "1d6" } + "equip": { "slot": "EQUIP_MELEE", "flag": "STRENGTH", "damage": "1d6", "to_hit": 0 } }, { "id": "equip_longsword", @@ -179,8 +173,7 @@ "renderable": { "glyph": ")", "fg": "#FFF8DC", "bg": "#000000", "order": 2 }, "weight": 3, "value": 15, - "flags": ["EQUIP_MELEE", "STRENGTH"], - "effects": { "base_damage": "1d8" } + "equip": { "slot": "EQUIP_MELEE", "flag": "STRENGTH", "damage": "1d8", "to_hit": 0 } }, { "id": "equip_smallshield", @@ -198,7 +191,7 @@ "weight": 6, "value": 10, "flags": ["EQUIP_SHIELD"], - "effects": { "ac": "2", "melee_power_bonus": "-1" } + "effects": { "ac": "2" } }, { "id": "equip_largeshield", @@ -207,7 +200,7 @@ "weight": 12, "value": 35, "flags": ["EQUIP_SHIELD"], - "effects": { "ac": "4", "melee_power_bonus": "-2" } + "effects": { "ac": "3" } }, { "id": "equip_body_weakleather", diff --git a/src/raws/item_structs.rs b/src/raws/item_structs.rs index f93fd89..598745d 100644 --- a/src/raws/item_structs.rs +++ b/src/raws/item_structs.rs @@ -8,6 +8,7 @@ pub struct Item { pub renderable: Option, pub weight: Option, pub value: Option, + pub equip: Option, pub flags: Option>, pub effects: Option>, pub magic: Option, @@ -19,6 +20,14 @@ pub struct Name { pub plural: String, } +#[derive(Deserialize, Debug)] +pub struct Equippable { + pub slot: String, + pub flag: String, + pub damage: String, + pub to_hit: Option, +} + #[derive(Deserialize, Debug)] pub struct Renderable { pub glyph: String, diff --git a/src/raws/rawmaster.rs b/src/raws/rawmaster.rs index dfd0ea5..f14171c 100644 --- a/src/raws/rawmaster.rs +++ b/src/raws/rawmaster.rs @@ -10,6 +10,85 @@ use specs::prelude::*; use specs::saveload::{MarkedBuilder, SimpleMarker}; use std::collections::{HashMap, HashSet}; +macro_rules! apply_effects { + ($effects:expr, $eb:expr) => { + for effect in $effects.iter() { + let effect_name = effect.0.as_str(); + match effect_name { + "healing" => { + let (n_dice, sides, modifier) = parse_dice_string(effect.1.as_str()); + $eb = $eb.with(ProvidesHealing { n_dice, sides, modifier }) + } + "ranged" => $eb = $eb.with(Ranged { range: effect.1.parse::().unwrap() }), + "damage" => { + let (n_dice, sides, modifier) = parse_dice_string(effect.1.as_str()); + $eb = $eb.with(InflictsDamage { n_dice, sides, modifier }) + } + "aoe" => $eb = $eb.with(AOE { radius: effect.1.parse::().unwrap() }), + "confusion" => $eb = $eb.with(Confusion { turns: effect.1.parse::().unwrap() }), + "ac" => $eb = $eb.with(ArmourClassBonus { amount: effect.1.parse::().unwrap() }), + "magicmapper" => $eb = $eb.with(MagicMapper {}), + "digger" => $eb = $eb.with(Digger {}), + _ => console::log(format!("Warning: effect {} not implemented.", effect_name)), + } + } + }; +} + +macro_rules! apply_flags { + ($flags:expr, $eb:expr) => { + for flag in $flags.iter() { + match flag.as_str() { + // --- PROP/ITEM FLAGS BEGIN HERE --- + "HIDDEN" => $eb = $eb.with(Hidden {}), + "BLOCKS_TILE" => $eb = $eb.with(BlocksTile {}), + "BLOCKS_VISIBILITY" => $eb = $eb.with(BlocksVisibility {}), + "ENTRY_TRIGGER" => $eb = $eb.with(EntryTrigger {}), + "SINGLE_ACTIVATION" => $eb = $eb.with(SingleActivation {}), + "DOOR" => $eb = $eb.with(Door { open: false }), + // RESTORES NUTRITION + "FOOD" => $eb = $eb.with(ProvidesNutrition {}), + // IS DELETED ON USE + "CONSUMABLE" => $eb = $eb.with(Consumable {}), + // HAS CHARGES + "CHARGES" => $eb = $eb.with(Charges { uses: 3, max_uses: 3 }), + // CAN BE DESTROYED BY DAMAGE + "DESTRUCTIBLE" => $eb = $eb.with(Destructible {}), + // EQUIP SLOTS + "EQUIP_MELEE" => $eb = $eb.with(Equippable { slot: EquipmentSlot::Melee }), + "EQUIP_SHIELD" => $eb = $eb.with(Equippable { slot: EquipmentSlot::Shield }), + "EQUIP_HEAD" => $eb = $eb.with(Equippable { slot: EquipmentSlot::Head }), + "EQUIP_BODY" => $eb = $eb.with(Equippable { slot: EquipmentSlot::Body }), + "EQUIP_FEET" => $eb = $eb.with(Equippable { slot: EquipmentSlot::Feet }), + "EQUIP_HANDS" => $eb = $eb.with(Equippable { slot: EquipmentSlot::Hands }), + "EQUIP_NECK" => $eb = $eb.with(Equippable { slot: EquipmentSlot::Neck }), + "EQUIP_BACK" => $eb = $eb.with(Equippable { slot: EquipmentSlot::Back }), + // --- MOB FLAGS BEGIN HERE --- + // RACES + "IS_PLAYER" => $eb = $eb.with(Player {}), + "IS_HUMAN" => $eb = $eb.with(HasAncestry { name: Ancestry::Human }), + "IS_DWARF" => $eb = $eb.with(HasAncestry { name: Ancestry::Dwarf }), + "IS_ELF" => $eb = $eb.with(HasAncestry { name: Ancestry::Elf }), + "IS_CATFOLK" => $eb = $eb.with(HasAncestry { name: Ancestry::Catfolk }), + "IS_GNOME" => $eb = $eb.with(HasAncestry { name: Ancestry::Gnome }), + // FACTIONS + "MINDLESS" => $eb = $eb.with(Faction { name: "mindless".to_string() }), + "NEUTRAL" => $eb = $eb.with(Faction { name: "neutral".to_string() }), + "HERBIVORE" => $eb = $eb.with(Faction { name: "herbivore".to_string() }), + "CARNIVORE" => $eb = $eb.with(Faction { name: "carnivore".to_string() }), + // MOVEMENT FLAGS (DEFAULTS TO WANDERING) + "STATIC" => $eb = $eb.with(MoveMode { mode: Movement::Static }), + "RANDOM_PATH" => $eb = $eb.with(MoveMode { mode: Movement::RandomWaypoint { path: None } }), + // ATTRIBUTES/MISC + "SMALL_GROUP" => {} // These flags are for region spawning, + "LARGE_GROUP" => {} // and don't need to apply a component. + "MULTIATTACK" => $eb = $eb.with(MultiAttack {}), + _ => rltk::console::log(format!("Unrecognised flag: {}", flag.as_str())), + } + } + }; +} + pub enum SpawnType { AtPosition { x: i32, y: i32 }, Equipped { by: Entity }, @@ -159,7 +238,7 @@ pub fn spawn_named_item( eb = eb.with(get_renderable_component(renderable)); } // BEATITUDE - let mut buc = if let Some(buc_status) = buc { + let buc = if let Some(buc_status) = buc { buc_status } else { match roll { @@ -168,60 +247,15 @@ pub fn spawn_named_item( _ => BUC::Uncursed, } }; - let mut weapon_type = -1; - if let Some(flags) = &item_template.flags { - for flag in flags.iter() { - match flag.as_str() { - "CONSUMABLE" => eb = eb.with(Consumable {}), - "DESTRUCTIBLE" => eb = eb.with(Destructible {}), - "CURSED" => buc = BUC::Cursed, - "BLESSED" => buc = BUC::Blessed, - "EQUIP_MELEE" => eb = eb.with(Equippable { slot: EquipmentSlot::Melee }), - "EQUIP_SHIELD" => eb = eb.with(Equippable { slot: EquipmentSlot::Shield }), - "EQUIP_HEAD" => eb = eb.with(Equippable { slot: EquipmentSlot::Head }), - "EQUIP_BODY" => eb = eb.with(Equippable { slot: EquipmentSlot::Body }), - "EQUIP_FEET" => eb = eb.with(Equippable { slot: EquipmentSlot::Feet }), - "EQUIP_HANDS" => eb = eb.with(Equippable { slot: EquipmentSlot::Hands }), - "EQUIP_NECK" => eb = eb.with(Equippable { slot: EquipmentSlot::Neck }), - "EQUIP_BACK" => eb = eb.with(Equippable { slot: EquipmentSlot::Back }), - "CHARGES" => eb = eb.with(Charges { uses: 3, max_uses: 3 }), - "FOOD" => eb = eb.with(ProvidesNutrition {}), - "STRENGTH" => weapon_type = 0, - "DEXTERITY" => weapon_type = 2, - "FINESSE" => weapon_type = 3, - _ => rltk::console::log(format!("Unrecognised flag: {}", flag.as_str())), - } - } - } eb = eb.with(Beatitude { buc, known: true }); - let mut base_damage = "1d4"; - let mut hit_bonus = 0; - - if let Some(effects_list) = &item_template.effects { - for effect in effects_list.iter() { - let effect_name = effect.0.as_str(); - match effect_name { - "provides_healing" => { - let (n_dice, sides, modifier) = parse_dice_string(effect.1.as_str()); - eb = eb.with(ProvidesHealing { n_dice, sides, modifier }) - } - "ranged" => eb = eb.with(Ranged { range: effect.1.parse::().unwrap() }), - "damage" => { - let (n_dice, sides, modifier) = parse_dice_string(effect.1.as_str()); - eb = eb.with(InflictsDamage { n_dice, sides, modifier }) - } - "aoe" => eb = eb.with(AOE { radius: effect.1.parse::().unwrap() }), - "confusion" => eb = eb.with(Confusion { turns: effect.1.parse::().unwrap() }), - "base_damage" => base_damage = effect.1, - "hit_bonus" => hit_bonus = effect.1.parse::().unwrap(), - "ac" => eb = eb.with(ArmourClassBonus { amount: effect.1.parse::().unwrap() }), - "magicmapper" => eb = eb.with(MagicMapper {}), - "digger" => eb = eb.with(Digger {}), - _ => rltk::console::log(format!("Warning: effect {} not implemented.", effect_name)), - } - } + if let Some(flags) = &item_template.flags { + apply_flags!(flags, eb); } + if let Some(effects_list) = &item_template.effects { + apply_effects!(effects_list, eb); + } + if let Some(magic_item) = &item_template.magic { let item_class = match magic_item.class.as_str() { "common" => MagicItemClass::Common, @@ -231,7 +265,6 @@ pub fn spawn_named_item( _ => MagicItemClass::Legendary, }; eb = eb.with(MagicItem { class: item_class }); - if !identified_items.contains(&item_template.name.name) { #[allow(clippy::single_match)] match magic_item.naming.as_str() { @@ -263,20 +296,20 @@ pub fn spawn_named_item( } } - if weapon_type != -1 { - let (n_dice, die_type, bonus) = parse_dice_string(base_damage); - let mut wpn = MeleeWeapon { - attribute: WeaponAttribute::Strength, + if let Some(weapon) = &item_template.equip { + let (n_dice, die_type, bonus) = parse_dice_string(weapon.damage.as_str()); + let weapon_attribute = match weapon.flag.as_str() { + "DEXTERITY" => WeaponAttribute::Dexterity, + "FINESSE" => WeaponAttribute::Finesse, + _ => WeaponAttribute::Strength, + }; + let wpn = MeleeWeapon { + attribute: weapon_attribute, damage_n_dice: n_dice, damage_die_type: die_type, damage_bonus: bonus, - hit_bonus: hit_bonus, + hit_bonus: weapon.to_hit.unwrap_or(0), }; - match weapon_type { - 0 => wpn.attribute = WeaponAttribute::Strength, - 1 => wpn.attribute = WeaponAttribute::Dexterity, - _ => wpn.attribute = WeaponAttribute::Finesse, - } eb = eb.with(wpn); } @@ -306,7 +339,6 @@ pub fn spawn_named_mob( } let mut eb; - let mut xp_value = 1; // New entity with a position, name, combatstats, and viewshed eb = ecs.create_entity().marked::>(); eb = spawn_position(pos, eb, key, raws); @@ -315,79 +347,23 @@ pub fn spawn_named_mob( if let Some(renderable) = &mob_template.renderable { eb = eb.with(get_renderable_component(renderable)); } + eb = eb.with(BlocksTile {}); + eb = eb.with(Faction { name: "hostile".to_string() }); + eb = eb.with(MoveMode { mode: Movement::Random }); + let mut xp_value = 1; let mut has_mind = true; - let mut has_faction = false; - let mut blocks_tile = true; - let mut has_move_mode = false; if let Some(flags) = &mob_template.flags { - for flag in flags.iter() { - match flag.as_str() { - "PASSABLE" => blocks_tile = false, - "STATIC" => { - eb = eb.with(MoveMode { mode: Movement::Static }); - has_move_mode = true; - } - "RANDOM_PATH" => { - eb = eb.with(MoveMode { mode: Movement::RandomWaypoint { path: None } }); - has_move_mode = true; - } - "MINDLESS" => { - eb = eb.with(Faction { name: "mindless".to_string() }); - has_faction = true; - has_mind = false; - } - "NEUTRAL" => { - eb = eb.with(Faction { name: "neutral".to_string() }); - has_faction = true; - } - "HERBIVORE" => { - eb = eb.with(Faction { name: "herbivore".to_string() }); - has_faction = true; - } - "CARNIVORE" => { - eb = eb.with(Faction { name: "carnivore".to_string() }); - has_faction = true; - } - "IS_GNOME" => { - eb = eb.with(HasAncestry { name: Ancestry::Gnome }); - } - "IS_DWARF" => { - eb = eb.with(HasAncestry { name: Ancestry::Dwarf }); - } - "IS_HUMAN" => { - eb = eb.with(HasAncestry { name: Ancestry::Human }); - } - "IS_CATFOLK" => { - eb = eb.with(HasAncestry { name: Ancestry::Catfolk }); - } - "IS_Elf" => { - eb = eb.with(HasAncestry { name: Ancestry::Elf }); - } - "SMALL_GROUP" => {} // These flags are for region spawning, - "LARGE_GROUP" => {} // and don't matter here (yet)? - "MULTIATTACK" => { - eb = eb.with(MultiAttack {}); - xp_value += 3; - } - _ => rltk::console::log(format!("Unrecognised flag: {}", flag.as_str())), - } + apply_flags!(flags, eb); + if flags.contains(&"MULTIATTACK".to_string()) { + xp_value += 3; + } + if flags.contains(&"MINDLESS".to_string()) { + has_mind = false; } } - if blocks_tile { - eb = eb.with(BlocksTile {}); - } - // If we didn't already add one, just move randomly. - if !has_move_mode { - eb = eb.with(MoveMode { mode: Movement::Random }); - } - // If we're anything other than MINDLESS, add a mind. if has_mind { eb = eb.with(Mind {}); } - // If we didn't add a faction flag, default to hostile (attacks everything except other hostiles). - if !has_faction { - eb = eb.with(Faction { name: "hostile".to_string() }); - } // Add quips, if we have some listed. if let Some(quips) = &mob_template.quips { eb = eb.with(Quips { available: quips.clone() }); @@ -554,57 +530,31 @@ pub fn spawn_named_mob( pub fn spawn_named_prop(raws: &RawMaster, ecs: &mut World, key: &str, pos: SpawnType) -> Option { if raws.prop_index.contains_key(key) { + // ENTITY BUILDER PREP let prop_template = &raws.raws.props[raws.prop_index[key]]; - let mut eb = ecs.create_entity().marked::>(); eb = spawn_position(pos, eb, key, raws); + // APPLY MANDATORY COMPONENTS FOR A PROP: + // - Name + // - Prop {} + eb = eb.with(Name { name: prop_template.name.clone(), plural: prop_template.name.clone() }); + eb = eb.with(Prop {}); + // APPLY OPTIONAL COMPONENTS FOR A PROP: + // - Renderable + // - Flags + // - Effects if let Some(renderable) = &prop_template.renderable { eb = eb.with(get_renderable_component(renderable)); } - eb = eb.with(Name { name: prop_template.name.clone(), plural: prop_template.name.clone() }); - eb = eb.with(Prop {}); if let Some(flags) = &prop_template.flags { - for flag in flags.iter() { - match flag.as_str() { - "HIDDEN" => eb = eb.with(Hidden {}), - "BLOCKS_TILE" => eb = eb.with(BlocksTile {}), - "BLOCKS_VISIBILITY" => eb = eb.with(BlocksVisibility {}), - "ENTRY_TRIGGER" => eb = eb.with(EntryTrigger {}), - "SINGLE_ACTIVATION" => eb = eb.with(SingleActivation {}), - "DOOR" => { - // TODO: Spawn some doors open? - let open = false; - eb = eb.with(Door { open: open }); - if !open { - eb = eb.with(BlocksTile {}); - eb = eb.with(BlocksVisibility {}); - } - } - _ => rltk::console::log(format!("Unrecognised flag: {}", flag.as_str())), - } - } + apply_flags!(flags, eb); } if let Some(effects_list) = &prop_template.effects { - for effect in effects_list.iter() { - let effect_name = effect.0.as_str(); - match effect_name { - "damage" => { - let (n_dice, sides, modifier) = parse_dice_string(effect.1.as_str()); - eb = eb.with(InflictsDamage { n_dice, sides, modifier }) - } - "aoe" => eb = eb.with(AOE { radius: effect.1.parse::().unwrap() }), - "healing" => { - let (n_dice, sides, modifier) = parse_dice_string(effect.1.as_str()); - eb = eb.with(ProvidesHealing { n_dice, sides, modifier }) - } - "confusion" => eb = eb.with(Confusion { turns: effect.1.parse::().unwrap() }), - _ => rltk::console::log(format!("Warning: effect {} not implemented.", effect_name)), - } - } + apply_effects!(effects_list, eb); } - + // BUILD THE ENTITY return Some(eb.build()); } None @@ -702,7 +652,6 @@ fn find_slot_for_equippable_item(tag: &str, raws: &RawMaster) -> EquipmentSlot { if let Some(flags) = &item.flags { for flag in flags { match flag.as_str() { - "EQUIP_MELEE" => return EquipmentSlot::Melee, "EQUIP_SHIELD" => return EquipmentSlot::Shield, "EQUIP_BODY" => return EquipmentSlot::Body, "EQUIP_HEAD" => return EquipmentSlot::Head, @@ -714,6 +663,12 @@ fn find_slot_for_equippable_item(tag: &str, raws: &RawMaster) -> EquipmentSlot { } } } + if let Some(equip) = &item.equip { + match equip.slot.as_str() { + "EQUIP_MELEE" => return EquipmentSlot::Melee, + _ => {} + } + } panic!("Trying to equip {}, but it has no slot tag.", tag); }