From f53b7673760714261f26a29dc117450fe3d1ced1 Mon Sep 17 00:00:00 2001 From: Llywelwyn Date: Tue, 25 Jul 2023 03:50:50 +0100 Subject: [PATCH] overhaul: data-driven architecture --- raws/entity_categories.json | 0 raws/items.json | 126 ++++++ raws/mobs.json | 26 ++ raws/props.json | 22 + raws/spawn_tables.json | 57 +++ raws/spawns.json | 180 -------- src/camera.rs | 2 +- src/gamelog/logstore.rs | 7 +- src/inventory_system.rs | 24 +- src/main.rs | 3 + src/map_builders/mod.rs | 2 +- src/map_builders/prefab_builder/mod.rs | 22 +- src/raws/item_structs.rs | 25 ++ src/raws/mob_structs.rs | 20 + src/raws/mod.rs | 62 +++ src/raws/prop_structs.rs | 12 + src/raws/rawmaster.rs | 248 +++++++++++ src/raws/spawn_table_structs.rs | 15 + src/spawner.rs | 554 ++----------------------- 19 files changed, 680 insertions(+), 727 deletions(-) create mode 100644 raws/entity_categories.json create mode 100644 raws/items.json create mode 100644 raws/mobs.json create mode 100644 raws/props.json create mode 100644 raws/spawn_tables.json delete mode 100644 raws/spawns.json create mode 100644 src/raws/item_structs.rs create mode 100644 src/raws/mob_structs.rs create mode 100644 src/raws/mod.rs create mode 100644 src/raws/prop_structs.rs create mode 100644 src/raws/rawmaster.rs create mode 100644 src/raws/spawn_table_structs.rs diff --git a/raws/entity_categories.json b/raws/entity_categories.json new file mode 100644 index 0000000..e69de29 diff --git a/raws/items.json b/raws/items.json new file mode 100644 index 0000000..5c14fff --- /dev/null +++ b/raws/items.json @@ -0,0 +1,126 @@ +[ + { + "id": "potion_health", + "name": { "name": "potion of health", "plural": "potions of health" }, + "renderable": { "glyph": "!", "fg": "#FF00FF", "bg": "#000000", "order": 2 }, + "flags": ["CONSUMABLE", "DESTRUCTIBLE"], + "effects": { "provides_healing": "12" } + }, + { + "id": "potion_health_weak", + "name": { "name": "potion of lesser health", "plural": "potions of lesser health" }, + "renderable": { "glyph": "!", "fg": "#FF00FF", "bg": "#000000", "order": 2 }, + "flags": ["CONSUMABLE", "DESTRUCTIBLE"], + "effects": { "provides_healing": "6" } + }, + { + "id": "scroll_magicmissile", + "name": { "name": "scroll of magic missile", "plural": "scrolls of magic missile" }, + "renderable": { "glyph": "?", "fg": "#00FFFF", "bg": "#000000", "order": 2 }, + "flags": ["CONSUMABLE", "DESTRUCTIBLE"], + "effects": { "ranged": "12", "damage": "10" } + }, + { + "id": "scroll_fireball", + "name": { "name": "scroll of fireball", "plural": "scrolls of fireball" }, + "renderable": { "glyph": "?", "fg": "#00FFFF", "bg": "#000000", "order": 2 }, + "flags": ["CONSUMABLE", "DESTRUCTIBLE"], + "effects": { "ranged": "10", "damage": "15", "aoe": "3" } + }, + { + "id": "scroll_fireball_cursed", + "name": { "name": "cursed scroll of fireball", "plural": "cursed scrolls of fireball" }, + "renderable": { "glyph": "?", "fg": "#00FFFF", "bg": "#000000", "order": 2 }, + "flags": ["CONSUMABLE", "DESTRUCTIBLE", "CURSED"], + "effects": { "ranged": "10", "damage": "15", "aoe": "3" } + }, + { + "id": "scroll_confusion", + "name": { "name": "scroll of confusion", "plural": "scrolls of confusion" }, + "renderable": { "glyph": "?", "fg": "#00FFFF", "bg": "#000000", "order": 2 }, + "flags": ["CONSUMABLE", "DESTRUCTIBLE"], + "effects": { "ranged": "10", "confusion": "4" } + }, + { + "id": "scroll_magicmap", + "name": { "name": "scroll of magic mapping", "plural": "scrolls of magic mapping" }, + "renderable": { "glyph": "?", "fg": "#00FFFF", "bg": "#000000", "order": 2 }, + "flags": ["CONSUMABLE", "DESTRUCTIBLE"], + "effects": { "magicmapper": "" } + }, + { + "id": "scroll_magicmap_cursed", + "name": { "name": "scroll of magic mapping", "plural": "scrolls of magic mapping" }, + "renderable": { "glyph": "?", "fg": "#00FFFF", "bg": "#000000", "order": 2 }, + "flags": ["CONSUMABLE", "DESTRUCTIBLE", "CURSED"], + "effects": { "magicmapper": "" } + }, + { + "id": "equip_dagger", + "name": { "name": "dagger", "plural": "daggers" }, + "renderable": { "glyph": ")", "fg": "#808080", "bg": "#000000", "order": 2 }, + "flags": ["EQUIP_MELEE"], + "effects": { "melee_power_bonus": "1" } + }, + { + "id": "equip_shortsword", + "name": { "name": "shortsword", "plural": "shortswords" }, + "renderable": { "glyph": ")", "fg": "#C0C0C0", "bg": "#000000", "order": 2 }, + "flags": ["EQUIP_MELEE"], + "effects": { "melee_power_bonus": "2" } + }, + { + "id": "equip_smallshield", + "name": { "name": "buckler", "plural": "bucklers" }, + "renderable": { "glyph": "[", "fg": "#808080", "bg": "#000000", "order": 2 }, + "flags": ["EQUIP_SHIELD"], + "effects": { "defence_bonus": "1" } + }, + { + "id": "equip_mediumshield", + "name": { "name": "medium shield", "plural": "medium shield" }, + "renderable": { "glyph": "[", "fg": "#C0C0C0", "bg": "#000000", "order": 2 }, + "flags": ["EQUIP_SHIELD"], + "effects": { "defence_bonus": "2", "melee_power_bonus": "-1" } + }, + { + "id": "wand_magicmissile", + "name": { "name": "wand of magic missile", "plural": "wands of magic missile" }, + "renderable": { "glyph": "/", "fg": "#00FFFF", "bg": "#000000", "order": 2 }, + "flags": ["WAND"], + "effects": { "ranged": "12", "damage": "10" } + }, + { + "id": "wand_fireball", + "name": { "name": "wand of fireball", "plural": "wands of fireball" }, + "renderable": { "glyph": "/", "fg": "#00FFFF", "bg": "#000000", "order": 2 }, + "flags": ["WAND"], + "effects": { "ranged": "10", "damage": "15", "aoe": "3" } + }, + { + "id": "wand_confusion", + "name": { "name": "wand of confusion", "plural": "wands of confusion" }, + "renderable": { "glyph": "/", "fg": "#00FFFF", "bg": "#000000", "order": 2 }, + "flags": ["WAND"], + "effects": { "ranged": "10", "confusion": "4" } + }, + { + "id": "wand_digging", + "name": { "name": "wand of digging", "plural": "wands of digging" }, + "renderable": { "glyph": "/", "fg": "#00FFFF", "bg": "#000000", "order": 2 }, + "flags": ["WAND"], + "effects": { "ranged": "10", "digger": "" } + }, + { + "id": "food_rations", + "name": { "name": "rations", "plural": "rations" }, + "renderable": { "glyph": "%", "fg": "#FFA07A", "bg": "#000000", "order": 2 }, + "flags": ["FOOD", "CONSUMABLE"] + }, + { + "id": "food_apple", + "name": { "name": "apple", "plural": "apples" }, + "renderable": { "glyph": "%", "fg": "#008000", "bg": "#000000", "order": 2 }, + "flags": ["FOOD", "CONSUMABLE"] + } +] diff --git a/raws/mobs.json b/raws/mobs.json new file mode 100644 index 0000000..3c722ae --- /dev/null +++ b/raws/mobs.json @@ -0,0 +1,26 @@ +[ + { + "id": "orc", + "name": "orc", + "renderable": { "glyph": "o", "fg": "#00FF00", "bg": "#000000", "order": 1 }, + "flags": ["BLOCKS_TILE"], + "stats": { "max_hp": 8, "hp": 8, "defence": 0, "power": 3 }, + "vision_range": 12 + }, + { + "id": "goblin", + "name": "goblin", + "renderable": { "glyph": "g", "fg": "#00FF00", "bg": "#000000", "order": 1 }, + "flags": ["BLOCKS_TILE"], + "stats": { "max_hp": 6, "hp": 6, "defence": 0, "power": 2 }, + "vision_range": 12 + }, + { + "id": "goblin_chieftain", + "name": "goblin chieftain", + "renderable": { "glyph": "G", "fg": "#00FF00", "bg": "#000000", "order": 1 }, + "flags": ["BLOCKS_TILE"], + "stats": { "max_hp": 8, "hp": 8, "defence": 0, "power": 3 }, + "vision_range": 12 + } +] diff --git a/raws/props.json b/raws/props.json new file mode 100644 index 0000000..670472f --- /dev/null +++ b/raws/props.json @@ -0,0 +1,22 @@ +[ + { + "id": "door", + "name": "door", + "renderable": { "glyph": "+", "fg": "#00FFFF", "bg": "#000000", "order": 2 }, + "flags": ["BLOCKS_TILE", "BLOCKS_VISIBILITY", "DOOR"] + }, + { + "id": "trap_bear", + "name": "bear trap", + "renderable": { "glyph": "^", "fg": "#C0C0C0", "bg": "#000000", "order": 2 }, + "flags": ["HIDDEN", "ENTRY_TRIGGER", "SINGLE_ACTIVATION"], + "effects": { "damage": "6" } + }, + { + "id": "trap_confusion", + "name": "magic trap", + "renderable": { "glyph": "^", "fg": "#800080", "bg": "#000000", "order": 2 }, + "flags": ["HIDDEN", "ENTRY_TRIGGER", "SINGLE_ACTIVATION"], + "effects": { "confusion": "3" } + } +] diff --git a/raws/spawn_tables.json b/raws/spawn_tables.json new file mode 100644 index 0000000..d15e90d --- /dev/null +++ b/raws/spawn_tables.json @@ -0,0 +1,57 @@ +[ + { + "id": "equipment", + "table": [ + { "id": "equip_dagger", "weight": 4, "min": 0, "max": 100 }, + { "id": "equip_shortsword", "weight": 2, "min": 0, "max": 100 }, + { "id": "equip_smallshield", "weight": 4, "min": 0, "max": 100 }, + { "id": "equip_mediumshield", "weight": 2, "min": 0, "max": 100 } + ] + }, + { + "id": "potions", + "table": [ + { "id": "potion_health_weak", "weight": 6, "min": 0, "max": 100 }, + { "id": "potion_health", "weight": 3, "min": 0, "max": 100 } + ] + }, + { + "id": "scrolls", + "table": [ + { "id": "scroll_fireball", "weight": 2, "min": 0, "max": 100 }, + { "id": "scroll_confusion", "weight": 2, "min": 0, "max": 100 }, + { "id": "scroll_magicmap", "weight": 2, "min": 0, "max": 100 } + ] + }, + { + "id": "wands", + "table": [ + { "id": "wand_magicmissile", "weight": 1, "min": 0, "max": 100 }, + { "id": "wand_fireball", "weight": 1, "min": 0, "max": 100 }, + { "id": "wand_confusion", "weight": 1, "min": 0, "max": 100 }, + { "id": "wand_digging", "weight": 1, "min": 0, "max": 100 } + ] + }, + { + "id": "food", + "table": [ + { "id": "food_rations", "weight": 1, "min": 0, "max": 100 }, + { "id": "food_apple", "weight": 1, "min": 0, "max": 100 } + ] + }, + { + "id": "mobs", + "table": [ + { "id": "goblin", "weight": 6, "min": 0, "max": 100 }, + { "id": "orc", "weight": 2, "min": 0, "max": 100 }, + { "id": "goblin_chieftain", "weight": 1, "min": 0, "max": 100 } + ] + }, + { + "id": "traps", + "table": [ + { "id": "trap_bear", "weight": 2, "min": 0, "max": 100 }, + { "id": "trap_confusion", "weight": 1, "min": 0, "max": 100 } + ] + } +] diff --git a/raws/spawns.json b/raws/spawns.json deleted file mode 100644 index aeff2df..0000000 --- a/raws/spawns.json +++ /dev/null @@ -1,180 +0,0 @@ -{ - "items": [ - { - "name": "potion of health", - "renderable": { - "glyph": "!", - "fg": "#FF00FF", - "bg": "#000000", - "order": 2 - }, - "consumable": { - "effects": { "provides_healing": "8" } - } - }, - { - "name": "potion of greater health", - "renderable": { - "glyph": "!", - "fg": "#FF00FF", - "bg": "#000000", - "order": 2 - }, - "consumable": { - "effects": { "provides_healing": "16" } - } - }, - { - "name": "potion of superior health", - "renderable": { - "glyph": "!", - "fg": "#FF00FF", - "bg": "#000000", - "order": 2 - }, - "consumable": { - "effects": { "provides_healing": "32" } - } - }, - { - "name": "scroll of magic missile", - "renderable": { - "glyph": "?", - "fg": "#00FFFF", - "bg": "#000000", - "order": 2 - }, - "consumable": { - "effects": { - "ranged": "12", - "damage": "10" - } - } - }, - { - "name": "scroll of fireball", - "renderable": { - "glyph": "?", - "fg": "#FFA500", - "bg": "#000000", - "order": 2 - }, - "consumable": { - "effects": { - "ranged": "10", - "damage": "20", - "aoe": "3" - } - } - }, - { - "name": "scroll of confusion", - "renderable": { - "glyph": "?", - "fg": "#A020F0", - "bg": "#000000", - "order": 2 - }, - "consumable": { - "effects": { - "ranged": "10", - "confusion": "4" - } - } - }, - { - "name": "scroll of magic mapping", - "renderable": { - "glyph": "?", - "fg": "#4169E1", - "bg": "#000000", - "order": 2 - }, - "consumable": { - "effects": { - "magic_mapping": "" - } - } - }, - { - "name": "rations", - "renderable": { - "glyph": "%", - "fg": "#FFA07A", - "bg": "#000000", - "order": 2 - }, - "consumable": { - "effects": { - "food": "" - } - } - }, - { - "name": "dagger", - "renderable": { - "glyph": ")", - "fg": "#BEBEBE", - "bg": "#000000", - "order": 2 - }, - "weapon": { - "range": "melee", - "power_bonus": 1 - } - }, - { - "name": "shortsword", - "renderable": { - "glyph": ")", - "fg": "#D3D3D3", - "bg": "#000000", - "order": 2 - }, - "weapon": { - "range": "melee", - "power_bonus": 2 - } - }, - { - "name": "buckler", - "renderable": { - "glyph": "[", - "fg": "#BEBEBE", - "bg": "#000000", - "order": 2 - }, - "shield": { - "defence_bonus": 1 - } - }, - { - "name": "medium shield", - "renderable": { - "glyph": "[", - "fg": "#BEBEBE", - "bg": "#000000", - "order": 2 - }, - "shield": { - "power_bonus": -1, - "defence_bonus": 1 - } - }, - { - "name": "large shield", - "renderable": { - "glyph": "[", - "fg": "#F5F5F5", - "bg": "#000000", - "order": 2 - }, - "shield": { - "power_bonus": -2, - "defence_bonus": 3 - } - } - ], - "mobs": [], - "props": [] -} diff --git a/src/camera.rs b/src/camera.rs index 81dcf8a..1b0ad1e 100644 --- a/src/camera.rs +++ b/src/camera.rs @@ -5,7 +5,7 @@ use std::ops::{Add, Mul}; const SHOW_BOUNDARIES: bool = false; -pub fn get_screen_bounds(ecs: &World, ctx: &mut Rltk) -> (i32, i32, i32, i32) { +pub fn get_screen_bounds(ecs: &World, _ctx: &mut Rltk) -> (i32, i32, i32, i32) { let player_pos = ecs.fetch::(); //let (x_chars, y_chars) = ctx.get_char_size(); let (x_chars, y_chars) = (80, 43); diff --git a/src/gamelog/logstore.rs b/src/gamelog/logstore.rs index 072e46b..3f3d1a6 100644 --- a/src/gamelog/logstore.rs +++ b/src/gamelog/logstore.rs @@ -43,7 +43,12 @@ pub fn setup_log() { for _ in 0..5 { Logger::new().log(); } - Logger::new().append("Welcome!").colour(rltk::CYAN).append("Press [?] at any time to view controls").period().log(); + Logger::new() + .append("Welcome!") + .colour(rltk::CYAN) + .append_n("Press [?] at any time to view controls") + .period() + .log(); } pub fn clone_log() -> Vec> { diff --git a/src/inventory_system.rs b/src/inventory_system.rs index 01672bd..5daf1df 100644 --- a/src/inventory_system.rs +++ b/src/inventory_system.rs @@ -238,12 +238,13 @@ impl<'a> System<'a> for ItemUseSystem { for (item_entity, already_equipped, _name) in (&entities, &equipped, &names).join() { if already_equipped.owner == target && already_equipped.slot == target_slot { to_unequip.push(item_entity); - if target == *player_entity { + /*if target == *player_entity { gamelog::Logger::new() .append("You unequip the") .item_name_n(&item_being_used.name) - .period(); - } + .period() + .log(); + }*/ } } for item in to_unequip.iter() { @@ -458,14 +459,25 @@ pub struct ItemRemoveSystem {} impl<'a> System<'a> for ItemRemoveSystem { #[allow(clippy::type_complexity)] - type SystemData = - (Entities<'a>, WriteStorage<'a, WantsToRemoveItem>, WriteStorage<'a, Equipped>, WriteStorage<'a, InBackpack>); + type SystemData = ( + Entities<'a>, + ReadExpect<'a, Entity>, + ReadStorage<'a, Name>, + WriteStorage<'a, WantsToRemoveItem>, + WriteStorage<'a, Equipped>, + WriteStorage<'a, InBackpack>, + ); fn run(&mut self, data: Self::SystemData) { - let (entities, mut wants_remove, mut equipped, mut backpack) = data; + let (entities, player_entity, names, mut wants_remove, mut equipped, mut backpack) = data; for (entity, to_remove) in (&entities, &wants_remove).join() { equipped.remove(to_remove.item); + if let Some(name) = names.get(to_remove.item) { + if entity == *player_entity { + gamelog::Logger::new().append("You unequip the").item_name_n(&name.name).period().log(); + } + } backpack.insert(to_remove.item, InBackpack { owner: entity }).expect("Unable to insert backpack"); } diff --git a/src/main.rs b/src/main.rs index b43e90d..ff1837c 100644 --- a/src/main.rs +++ b/src/main.rs @@ -5,6 +5,7 @@ extern crate serde; pub mod camera; mod components; +pub mod raws; pub use components::*; mod map; pub use map::*; @@ -543,6 +544,8 @@ fn main() -> rltk::BError { gs.ecs.register::(); gs.ecs.insert(SimpleMarkerAllocator::::new()); + raws::load_raws(); + let player_entity = spawner::player(&mut gs.ecs, 0, 0); gs.ecs.insert(Map::new(1, 64, 64)); gs.ecs.insert(Point::new(0, 0)); diff --git a/src/map_builders/mod.rs b/src/map_builders/mod.rs index 0e2b5d3..c547158 100644 --- a/src/map_builders/mod.rs +++ b/src/map_builders/mod.rs @@ -35,7 +35,7 @@ mod voronoi_spawning; use common::*; use specs::prelude::*; use voronoi_spawning::VoronoiSpawning; -use wfc::WaveFunctionCollapseBuilder; +//use wfc::WaveFunctionCollapseBuilder; mod room_exploder; use room_exploder::RoomExploder; mod room_corner_rounding; diff --git a/src/map_builders/prefab_builder/mod.rs b/src/map_builders/prefab_builder/mod.rs index c9a0fe5..ed96b64 100644 --- a/src/map_builders/prefab_builder/mod.rs +++ b/src/map_builders/prefab_builder/mod.rs @@ -1,7 +1,4 @@ -use super::{ - spawner::equipment_table, spawner::food_table, spawner::potion_table, spawner::scroll_table, spawner::wand_table, - BuilderMap, InitialMapBuilder, MetaMapBuilder, Position, TileType, -}; +use super::{spawner, BuilderMap, InitialMapBuilder, MetaMapBuilder, Position, TileType}; use rltk::RandomNumberGenerator; pub mod prefab_levels; pub mod prefab_sections; @@ -93,7 +90,7 @@ impl PrefabBuilder { } 'G' => { build_data.map.tiles[idx] = TileType::Floor; - build_data.spawn_list.push((idx, "goblin chieftain".to_string())); + build_data.spawn_list.push((idx, "goblin_chieftain".to_string())); } 'o' => { build_data.map.tiles[idx] = TileType::Floor; @@ -101,30 +98,27 @@ impl PrefabBuilder { } '^' => { build_data.map.tiles[idx] = TileType::Floor; - build_data.spawn_list.push((idx, "bear trap".to_string())); + build_data.spawn_list.push((idx, "trap_bear".to_string())); } '%' => { build_data.map.tiles[idx] = TileType::Floor; - build_data.spawn_list.push((idx, food_table(build_data.map.depth).roll(rng))); + build_data.spawn_list.push((idx, spawner::food_table(build_data.map.depth).roll(rng))); } '!' => { build_data.map.tiles[idx] = TileType::Floor; - build_data.spawn_list.push((idx, potion_table(build_data.map.depth).roll(rng))); + build_data.spawn_list.push((idx, spawner::potion_table(build_data.map.depth).roll(rng))); } '/' => { build_data.map.tiles[idx] = TileType::Floor; - build_data.spawn_list.push((idx, wand_table(build_data.map.depth).roll(rng))); - // Placeholder for wand spawn + build_data.spawn_list.push((idx, spawner::wand_table(build_data.map.depth).roll(rng))); } '?' => { build_data.map.tiles[idx] = TileType::Floor; - build_data.spawn_list.push((idx, scroll_table(build_data.map.depth).roll(rng))); - // Placeholder for scroll spawn + build_data.spawn_list.push((idx, spawner::scroll_table(build_data.map.depth).roll(rng))); } ')' => { build_data.map.tiles[idx] = TileType::Floor; - build_data.spawn_list.push((idx, equipment_table(build_data.map.depth).roll(rng))); - // Placeholder for scroll spawn + build_data.spawn_list.push((idx, spawner::equipment_table(build_data.map.depth).roll(rng))); } _ => { rltk::console::log(format!("Unknown glyph '{}' when loading prefab", (ch as u8) as char)); diff --git a/src/raws/item_structs.rs b/src/raws/item_structs.rs new file mode 100644 index 0000000..1dbd5da --- /dev/null +++ b/src/raws/item_structs.rs @@ -0,0 +1,25 @@ +use serde::Deserialize; +use std::collections::HashMap; + +#[derive(Deserialize, Debug)] +pub struct Item { + pub id: String, + pub name: Name, + pub renderable: Option, + pub flags: Option>, + pub effects: Option>, +} + +#[derive(Deserialize, Debug)] +pub struct Name { + pub name: String, + pub plural: String, +} + +#[derive(Deserialize, Debug)] +pub struct Renderable { + pub glyph: String, + pub fg: String, + pub bg: String, + pub order: i32, +} diff --git a/src/raws/mob_structs.rs b/src/raws/mob_structs.rs new file mode 100644 index 0000000..afdf46f --- /dev/null +++ b/src/raws/mob_structs.rs @@ -0,0 +1,20 @@ +use super::Renderable; +use serde::Deserialize; + +#[derive(Deserialize, Debug)] +pub struct Mob { + pub id: String, + pub name: String, + pub renderable: Option, + pub flags: Option>, + pub stats: MobStats, + pub vision_range: i32, +} + +#[derive(Deserialize, Debug)] +pub struct MobStats { + pub max_hp: i32, + pub hp: i32, + pub power: i32, + pub defence: i32, +} diff --git a/src/raws/mod.rs b/src/raws/mod.rs new file mode 100644 index 0000000..2b25521 --- /dev/null +++ b/src/raws/mod.rs @@ -0,0 +1,62 @@ +use serde::Deserialize; +mod rawmaster; +pub use rawmaster::*; +mod item_structs; +use item_structs::*; +mod mob_structs; +use mob_structs::*; +mod prop_structs; +use prop_structs::Prop; +mod spawn_table_structs; +use spawn_table_structs::*; +use std::sync::Mutex; + +lazy_static! { + pub static ref RAWS: Mutex = Mutex::new(RawMaster::empty()); +} + +#[derive(Deserialize, Debug)] +pub struct Raws { + pub items: Vec, + pub mobs: Vec, + pub props: Vec, + pub spawn_tables: Vec, +} + +rltk::embedded_resource!(RAW_ITEMS, "../../raws/items.json"); +rltk::embedded_resource!(RAW_MOBS, "../../raws/mobs.json"); +rltk::embedded_resource!(RAW_PROPS, "../../raws/props.json"); +rltk::embedded_resource!(RAW_SPAWN_TABLES, "../../raws/spawn_tables.json"); + +pub fn load_raws() { + rltk::link_resource!(RAW_ITEMS, "../../raws/items.json"); + rltk::link_resource!(RAW_MOBS, "../../raws/mobs.json"); + rltk::link_resource!(RAW_PROPS, "../../raws/props.json"); + rltk::link_resource!(RAW_SPAWN_TABLES, "../../raws/spawn_tables.json"); + + let decoded_raws = get_decoded_raws(); + RAWS.lock().unwrap().load(decoded_raws); +} + +pub fn get_decoded_raws() -> Raws { + // Get items from file + let mut raw_data = rltk::embedding::EMBED.lock().get_resource("../../raws/items.json".to_string()).unwrap(); + let mut raw_string = std::str::from_utf8(&raw_data).expect("Unable to convert to a valid UTF-8 string."); + let items: Vec = serde_json::from_str(&raw_string).expect("Unable to parse items.json"); + // Get mobs from file + raw_data = rltk::embedding::EMBED.lock().get_resource("../../raws/mobs.json".to_string()).unwrap(); + raw_string = std::str::from_utf8(&raw_data).expect("Unable to convert to a valid UTF-8 string."); + let mobs: Vec = serde_json::from_str(&raw_string).expect("Unable to parse mobs.json"); + // Get props from file + raw_data = rltk::embedding::EMBED.lock().get_resource("../../raws/props.json".to_string()).unwrap(); + raw_string = std::str::from_utf8(&raw_data).expect("Unable to convert to a valid UTF-8 string."); + let props: Vec = serde_json::from_str(&raw_string).expect("Unable to parse props.json"); + // Get spawntables from file + raw_data = rltk::embedding::EMBED.lock().get_resource("../../raws/spawn_tables.json".to_string()).unwrap(); + raw_string = std::str::from_utf8(&raw_data).expect("Unable to convert to a valid UTF-8 string."); + let spawn_tables: Vec = serde_json::from_str(&raw_string).expect("Unable to parse spawn_tables.json"); + + // Create combined raws + let raws = Raws { items, mobs, props, spawn_tables }; + return raws; +} diff --git a/src/raws/prop_structs.rs b/src/raws/prop_structs.rs new file mode 100644 index 0000000..845c67c --- /dev/null +++ b/src/raws/prop_structs.rs @@ -0,0 +1,12 @@ +use super::Renderable; +use serde::Deserialize; +use std::collections::HashMap; + +#[derive(Deserialize, Debug)] +pub struct Prop { + pub id: String, + pub name: String, + pub renderable: Option, + pub flags: Option>, + pub effects: Option>, +} diff --git a/src/raws/rawmaster.rs b/src/raws/rawmaster.rs new file mode 100644 index 0000000..f7c365f --- /dev/null +++ b/src/raws/rawmaster.rs @@ -0,0 +1,248 @@ +use super::Raws; +use crate::components::*; +use crate::random_table::RandomTable; +use specs::prelude::*; +use std::collections::HashMap; + +pub enum SpawnType { + AtPosition { x: i32, y: i32 }, +} + +pub struct RawMaster { + raws: Raws, + item_index: HashMap, + mob_index: HashMap, + prop_index: HashMap, + table_index: HashMap, +} + +impl RawMaster { + pub fn empty() -> RawMaster { + RawMaster { + raws: Raws { items: Vec::new(), mobs: Vec::new(), props: Vec::new(), spawn_tables: Vec::new() }, + item_index: HashMap::new(), + mob_index: HashMap::new(), + prop_index: HashMap::new(), + table_index: HashMap::new(), + } + } + + pub fn load(&mut self, raws: Raws) { + self.raws = raws; + self.item_index = HashMap::new(); + for (i, item) in self.raws.items.iter().enumerate() { + self.item_index.insert(item.id.clone(), i); + } + for (i, mob) in self.raws.mobs.iter().enumerate() { + self.mob_index.insert(mob.id.clone(), i); + } + for (i, prop) in self.raws.props.iter().enumerate() { + self.prop_index.insert(prop.id.clone(), i); + } + for (i, table) in self.raws.spawn_tables.iter().enumerate() { + self.table_index.insert(table.id.clone(), i); + } + } +} + +pub fn spawn_named_entity( + raws: &RawMaster, + new_entity: EntityBuilder, + key: &str, + pos: SpawnType, + rng: &mut rltk::RandomNumberGenerator, +) -> 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, rng); + } else if raws.prop_index.contains_key(key) { + return spawn_named_prop(raws, new_entity, key, pos); + } + None +} + +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; + + eb = eb.with(Name { name: item_template.name.name.clone(), plural: item_template.name.plural.clone() }); + eb = eb.with(Item {}); + eb = spawn_position(pos, eb); + + if let Some(renderable) = &item_template.renderable { + eb = eb.with(get_renderable_component(renderable)); + } + + 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" => eb = eb.with(Cursed {}), + "EQUIP_MELEE" => eb = eb.with(Equippable { slot: EquipmentSlot::Melee }), + "EQUIP_SHIELD" => eb = eb.with(Equippable { slot: EquipmentSlot::Shield }), + "WAND" => eb = eb.with(Wand { uses: 3, max_uses: 3 }), + "FOOD" => eb = eb.with(ProvidesNutrition {}), + _ => rltk::console::log(format!("Unrecognised flag: {}", flag.as_str())), + } + } + } + + 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" => eb = eb.with(ProvidesHealing { amount: effect.1.parse::().unwrap() }), + "ranged" => eb = eb.with(Ranged { range: effect.1.parse::().unwrap() }), + "damage" => eb = eb.with(InflictsDamage { amount: effect.1.parse::().unwrap() }), + "aoe" => eb = eb.with(AOE { radius: effect.1.parse::().unwrap() }), + "confusion" => eb = eb.with(Confusion { turns: effect.1.parse::().unwrap() }), + "melee_power_bonus" => eb = eb.with(MeleePowerBonus { amount: effect.1.parse::().unwrap() }), + "defence_bonus" => eb = eb.with(DefenceBonus { 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)), + } + } + } + + return Some(eb.build()); + } + None +} + +pub fn spawn_named_mob( + raws: &RawMaster, + new_entity: EntityBuilder, + key: &str, + pos: SpawnType, + rng: &mut rltk::RandomNumberGenerator, +) -> Option { + if raws.mob_index.contains_key(key) { + let mob_template = &raws.raws.mobs[raws.mob_index[key]]; + + // New entity with a position, name, combatstats, and viewshed + let mut eb = new_entity; + eb = spawn_position(pos, eb); + eb = eb.with(Name { name: mob_template.name.clone(), plural: mob_template.name.clone() }); + eb = eb.with(Monster {}); + let rolled_hp = roll_hit_dice(rng, 1, mob_template.stats.max_hp); + eb = eb.with(CombatStats { + max_hp: rolled_hp, + hp: rolled_hp, + power: mob_template.stats.power, + defence: mob_template.stats.defence, + }); + eb = eb.with(Viewshed { visible_tiles: Vec::new(), range: mob_template.vision_range, dirty: true }); + + if let Some(renderable) = &mob_template.renderable { + eb = eb.with(get_renderable_component(renderable)); + } + + if let Some(flags) = &mob_template.flags { + for flag in flags.iter() { + match flag.as_str() { + "BLOCKS_TILE" => eb = eb.with(BlocksTile {}), + _ => rltk::console::log(format!("Unrecognised flag: {}", flag.as_str())), + } + } + } + + return Some(eb.build()); + } + None +} + +pub fn roll_hit_dice(rng: &mut rltk::RandomNumberGenerator, n: i32, d: i32) -> i32 { + let mut rolled_hp: i32 = 0; + + for _i in 0..n { + rolled_hp += rng.roll_dice(1, d); + } + + return rolled_hp; +} + +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; + eb = spawn_position(pos, eb); + 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() }); + + 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" => eb = eb.with(Door { open: false }), + _ => rltk::console::log(format!("Unrecognised flag: {}", flag.as_str())), + } + } + } + + 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" => eb = eb.with(InflictsDamage { amount: effect.1.parse::().unwrap() }), + "confusion" => eb = eb.with(Confusion { turns: effect.1.parse::().unwrap() }), + _ => rltk::console::log(format!("Warning: effect {} not implemented.", effect_name)), + } + } + } + + return Some(eb.build()); + } + None +} + +fn spawn_position(pos: SpawnType, new_entity: EntityBuilder) -> EntityBuilder { + let mut eb = new_entity; + + 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 table_by_name(raws: &RawMaster, key: &str, depth: i32) -> RandomTable { + if raws.table_index.contains_key(key) { + let spawn_table = &raws.raws.spawn_tables[raws.table_index[key]]; + + use super::SpawnTableEntry; + + let available_options: Vec<&SpawnTableEntry> = + spawn_table.table.iter().filter(|a| depth >= a.min && depth <= a.max).collect(); + + let mut rt = RandomTable::new(); + for e in available_options.iter() { + rt = rt.add(e.id.clone(), e.weight); + } + + return rt; + } else { + return RandomTable::new().add("debug", 1); + } +} diff --git a/src/raws/spawn_table_structs.rs b/src/raws/spawn_table_structs.rs new file mode 100644 index 0000000..06734a6 --- /dev/null +++ b/src/raws/spawn_table_structs.rs @@ -0,0 +1,15 @@ +use serde::Deserialize; + +#[derive(Deserialize, Debug)] +pub struct SpawnTable { + pub id: String, + pub table: Vec, +} + +#[derive(Deserialize, Debug)] +pub struct SpawnTableEntry { + pub id: String, + pub weight: i32, + pub min: i32, + pub max: i32, +} diff --git a/src/spawner.rs b/src/spawner.rs index 6486dc0..e3dc5ea 100644 --- a/src/spawner.rs +++ b/src/spawner.rs @@ -1,11 +1,8 @@ use super::{ - random_table::RandomTable, Attribute, Attributes, BlocksTile, BlocksVisibility, CombatStats, Confusion, Consumable, - Cursed, DefenceBonus, Destructible, Digger, Door, EntryTrigger, EquipmentSlot, Equippable, Hidden, HungerClock, - HungerState, InflictsDamage, Item, MagicMapper, Map, MeleePowerBonus, Mind, Monster, Name, Player, Position, - ProvidesHealing, ProvidesNutrition, Ranged, Rect, Renderable, SerializeMe, SingleActivation, TileType, Viewshed, - Wand, AOE, + random_table::RandomTable, raws, Attribute, Attributes, CombatStats, HungerClock, HungerState, Map, Name, Player, + Position, Rect, Renderable, SerializeMe, TileType, Viewshed, }; -use rltk::{console, RandomNumberGenerator, RGB}; +use rltk::{RandomNumberGenerator, RGB}; use specs::prelude::*; use specs::saveload::{MarkedBuilder, SimpleMarker}; use std::collections::HashMap; @@ -38,45 +35,6 @@ pub fn player(ecs: &mut World, player_x: i32, player_y: i32) -> Entity { .build() } -fn monster(ecs: &mut World, x: i32, y: i32, glyph: rltk::FontCharType, name: S, hit_die: i32, power: i32) { - let rolled_hp = roll_hit_dice(ecs, 1, hit_die); - - ecs.create_entity() - .with(Position { x, y }) - .with(Renderable { glyph: glyph, fg: RGB::named(rltk::GREEN), bg: RGB::named(rltk::BLACK), render_order: 1 }) - .with(Viewshed { visible_tiles: Vec::new(), range: 12, dirty: true }) - .with(Monster {}) - .with(Mind {}) - .with(Name { name: name.to_string(), plural: format!("{}s", name.to_string()) }) - .with(BlocksTile {}) - .with(CombatStats { max_hp: rolled_hp, hp: rolled_hp, defence: 0, power: power }) - .marked::>() - .build(); -} - -fn orc(ecs: &mut World, x: i32, y: i32) { - monster(ecs, x, y, rltk::to_cp437('o'), "orc", 8, 3); -} - -fn goblin(ecs: &mut World, x: i32, y: i32) { - monster(ecs, x, y, rltk::to_cp437('g'), "goblin", 6, 2); -} - -fn goblin_chieftain(ecs: &mut World, x: i32, y: i32) { - monster(ecs, x, y, rltk::to_cp437('G'), "goblin chieftain", 8, 3); -} - -pub fn roll_hit_dice(ecs: &mut World, n: i32, d: i32) -> i32 { - let mut rng = ecs.write_resource::(); - let mut rolled_hp: i32 = 0; - - for _i in 0..n { - rolled_hp += rng.roll_dice(1, d); - } - - return rolled_hp; -} - // Consts const MAX_ENTITIES: i32 = 4; @@ -140,48 +98,26 @@ pub fn spawn_region( } } +/// Spawns a named entity (name in tuple.1) at the location in (tuple.0) pub fn spawn_entity(ecs: &mut World, spawn: &(&usize, &String)) { let map = ecs.fetch::(); let width = map.width as usize; - std::mem::drop(map); let x = (*spawn.0 % width) as i32; let y = (*spawn.0 / width) as i32; + std::mem::drop(map); - match spawn.1.as_ref() { - // Monsters - "goblin" => goblin(ecs, x, y), - "goblin chieftain" => goblin_chieftain(ecs, x, y), - "orc" => orc(ecs, x, y), - // Equipment - "dagger" => dagger(ecs, x, y), - "shortsword" => shortsword(ecs, x, y), - "buckler" => buckler(ecs, x, y), - "shield" => shield(ecs, x, y), - // Potions - "weak health potion" => weak_health_potion(ecs, x, y), - "health potion" => health_potion(ecs, x, y), - // Scrolls - "fireball scroll" => fireball_scroll(ecs, x, y), - "cursed fireball scroll" => cursed_fireball_scroll(ecs, x, y), - "confusion scroll" => confusion_scroll(ecs, x, y), - "magic missile scroll" => magic_missile_scroll(ecs, x, y), - "magic map scroll" => magic_map_scroll(ecs, x, y), - "cursed magic map scroll" => cursed_magic_map_scroll(ecs, x, y), - // Wands - "magic missile wand" => magic_missile_wand(ecs, x, y), - "fireball wand" => fireball_wand(ecs, x, y), - "confusion wand" => confusion_wand(ecs, x, y), - "digging wand" => digging_wand(ecs, x, y), - // Food - "rations" => rations(ecs, x, y), - "apple" => apple(ecs, x, y), - // Traps - "bear trap" => bear_trap(ecs, x, y), - "confusion trap" => confusion_trap(ecs, x, y), - // Other - "door" => door(ecs, x, y), - _ => console::log(format!("Tried to spawn nothing ({}). Bugfix needed!", spawn.1)), + let spawn_result = raws::spawn_named_entity( + &raws::RAWS.lock().unwrap(), + ecs.create_entity(), + &spawn.1, + raws::SpawnType::AtPosition { x, y }, + &mut rltk::RandomNumberGenerator::new(), + ); + if spawn_result.is_some() { + return; } + + rltk::console::log(format!("WARNING: We don't know how to spawn [{}]!", spawn.1)); } // 12 mobs : 6 items : 2 food : 1 trap @@ -193,15 +129,6 @@ fn debug_table() -> RandomTable { return RandomTable::new().add("debug", 1); } -// 6 goblins : 1 goblin chief : 2 orcs -fn mob_table(map_depth: i32) -> RandomTable { - return RandomTable::new() - // Monsters - .add("goblin", 6) - .add("goblin chieftain", 1) - .add("orc", 2 + map_depth); -} - // 6 equipment : 10 potions : 10 scrolls : 2 cursed scrolls fn item_table(map_depth: i32) -> RandomTable { return RandomTable::new() @@ -215,451 +142,30 @@ fn item_table(map_depth: i32) -> RandomTable { .add_table(wand_table(map_depth)); } -pub fn equipment_table(_map_depth: i32) -> RandomTable { - return RandomTable::new().add("dagger", 4).add("shortsword", 2).add("buckler", 4).add("shield", 2); +pub fn equipment_table(map_depth: i32) -> RandomTable { + raws::table_by_name(&raws::RAWS.lock().unwrap(), "equipment", map_depth) } -pub fn potion_table(_map_depth: i32) -> RandomTable { - return RandomTable::new().add("weak health potion", 14).add("health potion", 6); +pub fn potion_table(map_depth: i32) -> RandomTable { + raws::table_by_name(&raws::RAWS.lock().unwrap(), "potions", map_depth) } -pub fn scroll_table(_map_depth: i32) -> RandomTable { - return RandomTable::new() - .add("fireball scroll", 2) - .add("cursed fireball scroll", 2) - .add("confusion scroll", 4) - .add("magic missile scroll", 6) - .add("magic map scroll", 4) - .add("cursed magic map scroll", 2); +pub fn scroll_table(map_depth: i32) -> RandomTable { + raws::table_by_name(&raws::RAWS.lock().unwrap(), "scrolls", map_depth) } -pub fn wand_table(_map_depth: i32) -> RandomTable { - return RandomTable::new() - .add("magic missile wand", 1) - .add("fireball wand", 1) - .add("confusion wand", 1) - .add("digging wand", 1); +pub fn wand_table(map_depth: i32) -> RandomTable { + raws::table_by_name(&raws::RAWS.lock().unwrap(), "wands", map_depth) } -pub fn food_table(_map_depth: i32) -> RandomTable { - return RandomTable::new().add("rations", 1).add("apple", 1); +pub fn food_table(map_depth: i32) -> RandomTable { + raws::table_by_name(&raws::RAWS.lock().unwrap(), "food", map_depth) } -pub fn trap_table(_map_depth: i32) -> RandomTable { - return RandomTable::new().add("bear trap", 2).add("confusion trap", 1); +pub fn mob_table(map_depth: i32) -> RandomTable { + raws::table_by_name(&raws::RAWS.lock().unwrap(), "mobs", map_depth) } -fn door(ecs: &mut World, x: i32, y: i32) { - ecs.create_entity() - .with(Position { x, y }) - .with(Renderable { - glyph: rltk::to_cp437('+'), - fg: RGB::from_f32(0., 1., 1.), // Same colour as stairs, should probably define this somewhere. - bg: RGB::named(rltk::BLACK), - render_order: 2, - }) - .with(Name { name: "door".to_string(), plural: "doors".to_string() }) - .with(BlocksTile {}) - .with(BlocksVisibility {}) - .with(Door { open: false }) - .marked::>() - .build(); -} - -fn health_potion(ecs: &mut World, x: i32, y: i32) { - ecs.create_entity() - .with(Position { x, y }) - .with(Renderable { - glyph: rltk::to_cp437('!'), - fg: RGB::named(rltk::MAGENTA2), - bg: RGB::named(rltk::BLACK), - render_order: 2, - }) - .with(Name { name: "potion of health".to_string(), plural: "potions of health".to_string() }) - .with(Item {}) - .with(Consumable {}) - .with(Destructible {}) - .with(ProvidesHealing { amount: 12 }) - .marked::>() - .build(); -} - -fn weak_health_potion(ecs: &mut World, x: i32, y: i32) { - ecs.create_entity() - .with(Position { x, y }) - .with(Renderable { - glyph: rltk::to_cp437('!'), - fg: RGB::named(rltk::MAGENTA), - bg: RGB::named(rltk::BLACK), - render_order: 2, - }) - .with(Name { name: "potion of lesser health".to_string(), plural: "potions of lesser health".to_string() }) - .with(Item {}) - .with(Consumable {}) - .with(Destructible {}) - .with(ProvidesHealing { amount: 6 }) - .marked::>() - .build(); -} - -/* -fn poison_potion(ecs: &mut World, x: i32, y: i32) { - ecs.create_entity() - .with(Position { x, y }) - .with(Renderable { - glyph: rltk::to_cp437('i'), - fg: RGB::named(rltk::GREEN), - bg: RGB::named(rltk::BLACK), - render_order: 2, - }) - .with(Name { name: "potion of ... health?".to_string() }) - .with(Item {}) - .with(Consumable {}) - .with(Destructible {}) - .with(ProvidesHealing { amount: -12 }) - .marked::>() - .build(); -} -*/ - -// Scrolls -// ~10 range should be considered average here. -fn magic_missile_scroll(ecs: &mut World, x: i32, y: i32) { - ecs.create_entity() - .with(Position { x, y }) - .with(Renderable { - glyph: rltk::to_cp437('?'), - fg: RGB::named(rltk::BLUE), - bg: RGB::named(rltk::BLACK), - render_order: 2, - }) - .with(Name { name: "scroll of magic missile".to_string(), plural: "scrolls of magic missile".to_string() }) - .with(Item {}) - .with(Consumable {}) - .with(Destructible {}) - .with(Ranged { range: 12 }) // Long range - as far as default vision range - .with(InflictsDamage { amount: 10 }) // Low~ damage - .marked::>() - .build(); -} - -fn fireball_scroll(ecs: &mut World, x: i32, y: i32) { - ecs.create_entity() - .with(Position { x, y }) - .with(Renderable { - glyph: rltk::to_cp437('?'), - fg: RGB::named(rltk::ORANGE), - bg: RGB::named(rltk::BLACK), - render_order: 2, - }) - .with(Name { name: "scroll of fireball".to_string(), plural: "scrolls of fireball".to_string() }) - .with(Item {}) - .with(Consumable {}) - .with(Destructible {}) - .with(Ranged { range: 10 }) - .with(InflictsDamage { amount: 20 }) - .with(AOE { radius: 3 }) - .marked::>() - .build(); -} - -fn cursed_fireball_scroll(ecs: &mut World, x: i32, y: i32) { - ecs.create_entity() - .with(Position { x, y }) - .with(Renderable { - glyph: rltk::to_cp437('?'), - fg: RGB::named(rltk::ORANGE), - bg: RGB::named(rltk::BLACK), - render_order: 2, - }) - .with(Name { name: "cursed scroll of fireball".to_string(), plural: "cursed scrolls of fireball".to_string() }) - .with(Item {}) - .with(Cursed {}) - .with(Consumable {}) - .with(Destructible {}) - .with(Ranged { range: 10 }) - .with(InflictsDamage { amount: 20 }) - .with(AOE { radius: 3 }) - .marked::>() - .build(); -} - -fn confusion_scroll(ecs: &mut World, x: i32, y: i32) { - ecs.create_entity() - .with(Position { x, y }) - .with(Renderable { - glyph: rltk::to_cp437('?'), - fg: RGB::named(rltk::PURPLE), - bg: RGB::named(rltk::BLACK), - render_order: 2, - }) - .with(Name { name: "scroll of confusion".to_string(), plural: "scrolls of confusion".to_string() }) - .with(Item {}) - .with(Consumable {}) - .with(Destructible {}) - .with(Ranged { range: 10 }) - .with(Confusion { turns: 4 }) - .marked::>() - .build(); -} - -fn magic_map_scroll(ecs: &mut World, x: i32, y: i32) { - ecs.create_entity() - .with(Position { x, y }) - .with(Renderable { - glyph: rltk::to_cp437('?'), - fg: RGB::named(rltk::ROYALBLUE), - bg: RGB::named(rltk::BLACK), - render_order: 2, - }) - .with(Name { name: "scroll of magic mapping".to_string(), plural: "scrolls of magic mapping".to_string() }) - .with(Item {}) - .with(MagicMapper {}) - .with(Consumable {}) - .with(Destructible {}) - .marked::>() - .build(); -} - -fn cursed_magic_map_scroll(ecs: &mut World, x: i32, y: i32) { - ecs.create_entity() - .with(Position { x, y }) - .with(Renderable { - glyph: rltk::to_cp437('?'), - fg: RGB::named(rltk::ROYALBLUE), - bg: RGB::named(rltk::BLACK), - render_order: 2, - }) - .with(Name { - name: "cursed scroll of magic mapping".to_string(), - plural: "cursed scrolls of magic mapping".to_string(), - }) - .with(Item {}) - .with(Cursed {}) - .with(MagicMapper {}) - .with(Consumable {}) - .with(Destructible {}) - .marked::>() - .build(); -} - -// EQUIPMENT -fn dagger(ecs: &mut World, x: i32, y: i32) { - ecs.create_entity() - .with(Position { x, y }) - .with(Renderable { - glyph: rltk::to_cp437(')'), - fg: RGB::named(rltk::GREY), - bg: RGB::named(rltk::BLACK), - render_order: 2, - }) - .with(Name { name: "dagger".to_string(), plural: "daggers".to_string() }) - .with(Item {}) - .with(Equippable { slot: EquipmentSlot::Melee }) - .with(MeleePowerBonus { amount: 1 }) - .marked::>() - .build(); -} -fn shortsword(ecs: &mut World, x: i32, y: i32) { - ecs.create_entity() - .with(Position { x, y }) - .with(Renderable { - glyph: rltk::to_cp437(')'), - fg: RGB::named(rltk::LIGHTGREY), - bg: RGB::named(rltk::BLACK), - render_order: 2, - }) - .with(Name { name: "shortsword".to_string(), plural: "shortswords".to_string() }) - .with(Item {}) - .with(Equippable { slot: EquipmentSlot::Melee }) - .with(MeleePowerBonus { amount: 2 }) - .marked::>() - .build(); -} - -fn buckler(ecs: &mut World, x: i32, y: i32) { - ecs.create_entity() - .with(Position { x, y }) - .with(Renderable { - glyph: rltk::to_cp437('['), - fg: RGB::named(rltk::GREY), - bg: RGB::named(rltk::BLACK), - render_order: 2, - }) - .with(Name { name: "buckler".to_string(), plural: "bucklers".to_string() }) - .with(Item {}) - .with(DefenceBonus { amount: 1 }) - .with(Equippable { slot: EquipmentSlot::Shield }) - .marked::>() - .build(); -} - -fn shield(ecs: &mut World, x: i32, y: i32) { - ecs.create_entity() - .with(Position { x, y }) - .with(Renderable { - glyph: rltk::to_cp437('['), - fg: RGB::named(rltk::LIGHTGREY), - bg: RGB::named(rltk::BLACK), - render_order: 2, - }) - .with(Name { name: "shield".to_string(), plural: "shields".to_string() }) - .with(Item {}) - .with(DefenceBonus { amount: 2 }) - .with(MeleePowerBonus { amount: -1 }) - .with(Equippable { slot: EquipmentSlot::Shield }) - .marked::>() - .build(); -} - -// FOOD - -fn rations(ecs: &mut World, x: i32, y: i32) { - ecs.create_entity() - .with(Position { x, y }) - .with(Renderable { - glyph: rltk::to_cp437('%'), - fg: RGB::named(rltk::LIGHT_SALMON), - bg: RGB::named(rltk::BLACK), - render_order: 2, - }) - .with(Name { name: "rations".to_string(), plural: "rations".to_string() }) - .with(Item {}) - .with(ProvidesNutrition {}) - .with(Consumable {}) - .marked::>() - .build(); -} - -fn apple(ecs: &mut World, x: i32, y: i32) { - ecs.create_entity() - .with(Position { x, y }) - .with(Renderable { - glyph: rltk::to_cp437('%'), - fg: RGB::named(rltk::GREEN), - bg: RGB::named(rltk::BLACK), - render_order: 2, - }) - .with(Name { name: "apple".to_string(), plural: "apples".to_string() }) - .with(Item {}) - .with(ProvidesNutrition {}) - .with(Consumable {}) - .marked::>() - .build(); -} - -// WANDS - -fn fireball_wand(ecs: &mut World, x: i32, y: i32) { - ecs.create_entity() - .with(Position { x, y }) - .with(Renderable { - glyph: rltk::to_cp437('/'), - fg: RGB::named(rltk::ORANGE), - bg: RGB::named(rltk::BLACK), - render_order: 2, - }) - .with(Name { name: "wand of fireball".to_string(), plural: "wands of fireball".to_string() }) - .with(Item {}) - .with(Wand { uses: 3, max_uses: 3 }) - .with(Destructible {}) - .with(Ranged { range: 10 }) - .with(InflictsDamage { amount: 20 }) - .with(AOE { radius: 3 }) - .marked::>() - .build(); -} - -fn magic_missile_wand(ecs: &mut World, x: i32, y: i32) { - ecs.create_entity() - .with(Position { x, y }) - .with(Renderable { - glyph: rltk::to_cp437('/'), - fg: RGB::named(rltk::BLUE), - bg: RGB::named(rltk::BLACK), - render_order: 2, - }) - .with(Name { name: "wand of magic missile".to_string(), plural: "wands of magic missile".to_string() }) - .with(Item {}) - .with(Wand { uses: 3, max_uses: 3 }) - .with(Destructible {}) - .with(Ranged { range: 12 }) // Long range - as far as default vision range - .with(InflictsDamage { amount: 10 }) // Low~ damage - .marked::>() - .build(); -} - -fn confusion_wand(ecs: &mut World, x: i32, y: i32) { - ecs.create_entity() - .with(Position { x, y }) - .with(Renderable { - glyph: rltk::to_cp437('/'), - fg: RGB::named(rltk::PURPLE), - bg: RGB::named(rltk::BLACK), - render_order: 2, - }) - .with(Name { name: "wand of confusion".to_string(), plural: "wands of confusion".to_string() }) - .with(Item {}) - .with(Wand { uses: 3, max_uses: 3 }) - .with(Destructible {}) - .with(Ranged { range: 10 }) - .with(Confusion { turns: 4 }) - .marked::>() - .build(); -} - -fn digging_wand(ecs: &mut World, x: i32, y: i32) { - ecs.create_entity() - .with(Position { x, y }) - .with(Renderable { - glyph: rltk::to_cp437('/'), - fg: RGB::named(rltk::PURPLE), - bg: RGB::named(rltk::BLACK), - render_order: 2, - }) - .with(Name { name: "wand of digging".to_string(), plural: "wands of digging".to_string() }) - .with(Item {}) - .with(Wand { uses: 3, max_uses: 3 }) - .with(Destructible {}) - .with(Ranged { range: 10 }) - .with(Digger {}) - .marked::>() - .build(); -} - -// TRAPS -fn bear_trap(ecs: &mut World, x: i32, y: i32) { - ecs.create_entity() - .with(Position { x, y }) - .with(Renderable { - glyph: rltk::to_cp437('^'), - fg: RGB::named(rltk::GREY), - bg: RGB::named(rltk::BLACK), - render_order: 2, - }) - .with(Name { name: "bear trap".to_string(), plural: "bear traps".to_string() }) - .with(Hidden {}) - .with(EntryTrigger {}) - .with(SingleActivation {}) - .with(InflictsDamage { amount: 6 }) - .marked::>() - .build(); -} - -fn confusion_trap(ecs: &mut World, x: i32, y: i32) { - ecs.create_entity() - .with(Position { x, y }) - .with(Renderable { - glyph: rltk::to_cp437('^'), - fg: RGB::named(rltk::PURPLE), - bg: RGB::named(rltk::BLACK), - render_order: 2, - }) - .with(Name { name: "magic trap".to_string(), plural: "magic traps".to_string() }) - .with(Hidden {}) - .with(EntryTrigger {}) - .with(SingleActivation {}) - .with(Confusion { turns: 3 }) - .marked::>() - .build(); +pub fn trap_table(map_depth: i32) -> RandomTable { + raws::table_by_name(&raws::RAWS.lock().unwrap(), "traps", map_depth) }