use std::collections::HashMap; use rltk::{FontCharType, RandomNumberGenerator, RGB}; use specs::prelude::*; use specs::saveload::{MarkedBuilder, SimpleMarker}; use crate::random_table::RandomTable; use crate::rect::Rect; use crate::{ AreaOfEffect, BlocksTile, CombatStats, Confusion, Consumable, DefenseBonus, EntryTrigger, EquipmentSlot, Equippable, Hidden, HungerClock, HungerState, InflictsDamage, Item, MagicMapper, Map, MeleePowerBonus, Monster, Name, Player, Position, ProvidesFood, ProvidesHealing, Ranged, Renderable, SerializeMe, SingleActivation, TileType, Viewshed, MAP_WIDTH, MAX_MONSTER, }; pub fn player(ecs: &mut World, player_x: i32, player_y: i32) -> Entity { ecs.create_entity() .with(Position { x: player_x, y: player_y, }) .with(Renderable { glyph: rltk::to_cp437('@'), fg: RGB::named(rltk::YELLOW), bg: RGB::named(rltk::BLACK), render_order: 0, }) .with(Player {}) .with(Viewshed { visible_tiles: Vec::new(), range: 8, dirty: true, }) .with(Name { name: "Player".to_string(), }) .with(CombatStats { max_hp: 30, hp: 30, defense: 2, power: 5, }) .with(HungerClock { duration: 20, state: HungerState::WellFed, }) .marked::>() .build() } fn orc(ecs: &mut World, x: i32, y: i32) { monster(ecs, x, y, rltk::to_cp437('o'), "Orc") } fn goblin(ecs: &mut World, x: i32, y: i32) { monster(ecs, x, y, rltk::to_cp437('g'), "Goblin") } fn monster(ecs: &mut World, x: i32, y: i32, glyph: FontCharType, name: S) { ecs.create_entity() .with(Position { x, y }) .with(Renderable { glyph, fg: RGB::named(rltk::RED), bg: RGB::named(rltk::BLACK), render_order: 1, }) .with(Viewshed { visible_tiles: Vec::new(), range: 8, dirty: true, }) .with(Monster {}) .with(Name { name: name.to_string(), }) .with(BlocksTile {}) .with(CombatStats { max_hp: 16, hp: 16, defense: 1, power: 4, }) .marked::>() .build(); } #[allow(clippy::map_entry)] pub fn spawn_room(ecs: &mut World, room: &Rect, map_depth: i32) { let mut possible_targets: Vec = Vec::new(); { let map = ecs.fetch::(); for y in room.y1 + 1..room.y2 { for x in room.x1 + 1..room.x2 { let idx = map.xy_idx(x, y); if map.tiles[idx] == TileType::Floor { possible_targets.push(idx) } } } } spawn_region(ecs, &possible_targets, map_depth); } pub(crate) fn spawn_region(ecs: &mut World, area: &[usize], map_depth: i32) { if let Some(spawn_points) = calculate_spawn_points(ecs, area, map_depth) { spawn_entities(ecs, spawn_points) } } fn spawn_entities(ecs: &mut World, spawn_points: HashMap) { for spawn in spawn_points.iter() { spawn_entity(ecs, &spawn); } } fn calculate_spawn_points(ecs: &mut World, area: &[usize], map_depth: i32) -> Option> { let mut spawn_points: HashMap = HashMap::new(); let spawn_table = room_table(map_depth); let mut areas: Vec = Vec::from(area); let mut rng = ecs.write_resource::(); let num_spawns = i32::min(areas.len() as i32, rng.roll_dice(1, MAX_MONSTER + 3)); if num_spawns == 0 { return None; } for _i in 0..num_spawns { let array_idx = if areas.len() == 1 { 0usize } else { (rng.roll_dice(1, areas.len() as i32 - 1) as usize) }; let map_idx = areas[array_idx]; spawn_points.insert(map_idx, spawn_table.roll(&mut rng)); areas.remove(array_idx); } Some(spawn_points) } fn spawn_entity(ecs: &mut World, spawn: &(&usize, &String)) { let x = (*spawn.0 % MAP_WIDTH) as i32; let y = (*spawn.0 / MAP_WIDTH) as i32; match spawn.1.as_ref() { "Goblin" => goblin(ecs, x, y), "Orc" => orc(ecs, x, y), "Health Potion" => health_potion(ecs, x, y), "Fireball Scroll" => fireball_scroll(ecs, x, y), "Confusion Scroll" => confusion_scroll(ecs, x, y), "Magic Missile Scroll" => magic_missile_scroll(ecs, x, y), "Dagger" => dagger(ecs, x, y), "Longsword" => longsword(ecs, x, y), "Shield" => shield(ecs, x, y), "Tower Shield" => tower_shield(ecs, x, y), "Helmet" => helmet(ecs, x, y), "Breastplate" => breastplate(ecs, x, y), "Leggings" => leggings(ecs, x, y), "Sabatons" => sabatons(ecs, x, y), "Rations" => rations(ecs, x, y), "Magic Mapping Scroll" => magic_mapper_scroll(ecs, x, y), "Bear Trap" => bear_trap(ecs, x, y), _ => {} } } pub fn health_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::MAGENTA), bg: RGB::named(rltk::BLACK), render_order: 2, }) .with(Name { name: "Health Potion".to_string(), }) .with(Item {}) .with(Consumable {}) .with(ProvidesHealing { heal_amount: 8 }) .marked::>() .build(); } pub 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::CYAN), bg: RGB::named(rltk::BLACK), render_order: 2, }) .with(Name { name: "Magic Missile Scroll".to_string(), }) .with(Item {}) .with(Consumable {}) .with(Ranged { range: 6 }) .with(InflictsDamage { damage: 20 }) .marked::>() .build(); } pub 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: "Fireball Scroll".to_string(), }) .with(Item {}) .with(Consumable {}) .with(Ranged { range: 6 }) .with(InflictsDamage { damage: 20 }) .with(AreaOfEffect { radius: 3 }) .marked::>() .build(); } pub 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::PINK), bg: RGB::named(rltk::BLACK), render_order: 2, }) .with(Name { name: "Confusion Scroll".to_string(), }) .with(Item {}) .with(Consumable {}) .with(Ranged { range: 6 }) .with(Confusion { turns: 4 }) .marked::>() .build(); } pub fn room_table(map_depth: i32) -> RandomTable { RandomTable::new() .add("Goblin", 10) .add("Orc", 1 + map_depth) .add("Health Potion", 7) .add("Fireball Scroll", 2 + map_depth) .add("Confusion Scroll", 2 + map_depth) .add("Magic Missile Scroll", 4) .add("Dagger", 3) .add("Longsword", map_depth - 1) .add("Shield", 3) .add("Tower Shield", map_depth - 1) .add("Helmet", map_depth - 2) .add("Breastplate", map_depth - 3) .add("Leggings", map_depth - 4) .add("Sabatons", map_depth - 4) .add("Rations", 10) .add("Magic Mapping Scroll", 2) .add("Bear Trap", 2) } fn dagger(ecs: &mut World, x: i32, y: i32) { ecs.create_entity() .with(Position { x, y }) .with(Renderable { glyph: rltk::to_cp437('/'), render_order: 2, fg: RGB::named(rltk::CYAN), bg: RGB::named(rltk::BLACK), }) .with(Item {}) .with(Name { name: "Dagger".to_string(), }) .with(Equippable { slot: EquipmentSlot::Melee, }) .with(MeleePowerBonus { power: 2 }) .marked::>() .build(); } fn longsword(ecs: &mut World, x: i32, y: i32) { ecs.create_entity() .with(Position { x, y }) .with(Renderable { glyph: rltk::to_cp437('/'), render_order: 2, fg: RGB::named(rltk::CYAN), bg: RGB::named(rltk::BLACK), }) .with(Item {}) .with(Name { name: "Longsword".to_string(), }) .with(Equippable { slot: EquipmentSlot::Melee, }) .with(MeleePowerBonus { power: 4 }) .marked::>() .build(); } fn shield(ecs: &mut World, x: i32, y: i32) { ecs.create_entity() .with(Position { x, y }) .with(Renderable { glyph: rltk::to_cp437('('), render_order: 2, fg: RGB::named(rltk::CYAN), bg: RGB::named(rltk::BLACK), }) .with(Item {}) .with(Name { name: "Shield".to_string(), }) .with(Equippable { slot: EquipmentSlot::Shield, }) .with(DefenseBonus { defense: 1 }) .marked::>() .build(); } fn tower_shield(ecs: &mut World, x: i32, y: i32) { ecs.create_entity() .with(Position { x, y }) .with(Renderable { glyph: rltk::to_cp437('('), render_order: 2, fg: RGB::named(rltk::CYAN), bg: RGB::named(rltk::BLACK), }) .with(Item {}) .with(Name { name: "Tower Shield".to_string(), }) .with(Equippable { slot: EquipmentSlot::Shield, }) .with(DefenseBonus { defense: 3 }) .marked::>() .build(); } fn helmet(ecs: &mut World, x: i32, y: i32) { ecs.create_entity() .with(Position { x, y }) .with(Renderable { glyph: rltk::to_cp437('^'), render_order: 2, fg: RGB::named(rltk::CYAN), bg: RGB::named(rltk::BLACK), }) .with(Item {}) .with(Name { name: "Helmet".to_string(), }) .with(Equippable { slot: EquipmentSlot::Head, }) .with(DefenseBonus { defense: 1 }) .with(MeleePowerBonus { power: 1 }) .marked::>() .build(); } fn breastplate(ecs: &mut World, x: i32, y: i32) { ecs.create_entity() .with(Position { x, y }) .with(Renderable { glyph: rltk::to_cp437('x'), render_order: 2, fg: RGB::named(rltk::CYAN), bg: RGB::named(rltk::BLACK), }) .with(Item {}) .with(Name { name: "Breastplate".to_string(), }) .with(Equippable { slot: EquipmentSlot::Chest, }) .with(DefenseBonus { defense: 2 }) .marked::>() .build(); } fn leggings(ecs: &mut World, x: i32, y: i32) { ecs.create_entity() .with(Position { x, y }) .with(Renderable { glyph: rltk::to_cp437('"'), render_order: 2, fg: RGB::named(rltk::CYAN), bg: RGB::named(rltk::BLACK), }) .with(Item {}) .with(Name { name: "Leggings".to_string(), }) .with(Equippable { slot: EquipmentSlot::Legs, }) .with(DefenseBonus { defense: 1 }) .marked::>() .build(); } fn sabatons(ecs: &mut World, x: i32, y: i32) { ecs.create_entity() .with(Position { x, y }) .with(Renderable { glyph: rltk::to_cp437(','), render_order: 2, fg: RGB::named(rltk::CYAN), bg: RGB::named(rltk::BLACK), }) .with(Item {}) .with(Name { name: "Sabatons".to_string(), }) .with(Equippable { slot: EquipmentSlot::Feet, }) .with(DefenseBonus { defense: 1 }) .with(MeleePowerBonus { power: 1 }) .marked::>() .build(); } fn rations(ecs: &mut World, x: i32, y: i32) { ecs.create_entity() .with(Position { x, y }) .with(Renderable { glyph: rltk::to_cp437('%'), render_order: 2, fg: RGB::named(rltk::GREEN), bg: RGB::named(rltk::BLACK), }) .with(Item {}) .with(Name { name: "Rations".to_string(), }) .with(Consumable {}) .with(ProvidesFood {}) .marked::>() .build(); } fn magic_mapper_scroll(ecs: &mut World, x: i32, y: i32) { ecs.create_entity() .with(Position { x, y }) .with(Renderable { glyph: rltk::to_cp437(')'), render_order: 2, fg: RGB::named(rltk::CYAN3), bg: RGB::named(rltk::BLACK), }) .with(Item {}) .with(Name { name: "Scroll to Magic Mapping".to_string(), }) .with(Consumable {}) .with(MagicMapper {}) .marked::>() .build(); } fn bear_trap(ecs: &mut World, x: i32, y: i32) { ecs.create_entity() .with(Position { x, y }) .with(Renderable { glyph: rltk::to_cp437('^'), render_order: 2, fg: RGB::named(rltk::RED), bg: RGB::named(rltk::BLACK), }) .with(Name { name: "Bear Trap".to_string(), }) .with(Hidden {}) .with(EntryTrigger {}) .with(InflictsDamage { damage: 6 }) .with(SingleActivation {}) .marked::>() .build(); }