use std::collections::HashMap; use ::rltk::{Point, RandomNumberGenerator}; use ::specs::prelude::*; use ::specs::saveload::{MarkedBuilder, SimpleMarker}; use crate::components::*; use crate::gamesystem::{mana_at_level, player_hp_at_level}; use crate::random_table::RandomTable; use crate::raws::{ get_spawn_table_for_depth, spawn_all_spells, spawn_named_entity, SpawnType, RAWS, }; use crate::{colors, Map, MasterDungeonMap, Rect, TileType}; /// Spawns the player and returns their entity object pub fn player(ecs: &mut World, player_x: i32, player_y: i32) -> Entity { spawn_all_spells(ecs); let player = ecs .create_entity() .with(Position { x: player_x, y: player_y, }) .with(Renderable { glyph: rltk::to_cp437('@'), fg: colors::YELLOW, bg: colors::BLACK, render_order: 0, }) .with(Player {}) .with(Viewshed::default()) .with(Name::from("Player")) .with(HungerClock { state: HungerState::WellFed, duration: 20, }) .with(Attributes { might: Attribute::new(11), fitness: Attribute::new(11), quickness: Attribute::new(11), intelligence: Attribute::new(11), }) .with(Skills::new(1)) .with(Pools { hit_points: Pool::new(player_hp_at_level(11, 1)), mana: Pool::new(mana_at_level(11, 1)), xp: 0, level: 1, total_weight: 0., total_initiative_penalty: 0., gold: 0., god_mode: false, }) .with(EquipmentChanged {}) .with(LightSource { color: colors::TORCH_LIGHT, range: 8, }) .with(Initiative { current: 0 }) .with(Faction::from("Player")) .with(KnownSpells { spells: vec![KnownSpell { display_name: "Zap".to_string(), mana_cost: 1, }], }) .marked::>() .build(); // Starting equipment spawn_named_entity( &RAWS.lock().unwrap(), ecs, "Rusty Longsword", SpawnType::Equipped { by: player }, ); spawn_named_entity( &RAWS.lock().unwrap(), ecs, "Dried Sausage", SpawnType::Carried { by: player }, ); spawn_named_entity( &RAWS.lock().unwrap(), ecs, "Beer", SpawnType::Carried { by: player }, ); spawn_named_entity( &RAWS.lock().unwrap(), ecs, "Stained Tunic", SpawnType::Equipped { by: player }, ); spawn_named_entity( &RAWS.lock().unwrap(), ecs, "Torn Trousers", SpawnType::Equipped { by: player }, ); spawn_named_entity( &RAWS.lock().unwrap(), ecs, "Old Boots", SpawnType::Equipped { by: player }, ); // Starting hangover ecs.create_entity() .with(StatusEffect { target: player }) .with(Duration { turns: 10 }) .with(Name::from("Hangover")) .with(AttributeBonus { might: Some(-1), fitness: None, quickness: Some(-1), intelligence: Some(-1), }) .marked::>() .build(); player } const MAX_MONSTERS: i32 = 4; fn room_table(map_depth: i32) -> RandomTable { get_spawn_table_for_depth(&RAWS.lock().unwrap(), map_depth) } /// fills a room with stuff! #[allow(clippy::map_entry)] pub fn spawn_room( map: &Map, rng: &mut RandomNumberGenerator, room: &Rect, map_depth: i32, spawn_list: &mut Vec<(usize, String)>, ) { let mut possible_targets: Vec = Vec::new(); // Borrow scope - to keep access to the map separated { 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(map, rng, &possible_targets, map_depth, spawn_list); } pub fn spawn_region( _map: &Map, rng: &mut RandomNumberGenerator, area: &[usize], map_depth: i32, spawn_list: &mut Vec<(usize, String)>, ) { let spawn_table = room_table(map_depth); let mut spawn_points: HashMap = HashMap::new(); let mut areas: Vec = Vec::from(area); // Scope to keep the borrow checker happy { let num_spawns = i32::min( areas.len() as i32, rng.roll_dice(1, MAX_MONSTERS + 3) + (map_depth - 1) - 3, ); if num_spawns == 0 { return; } for _i in 0..num_spawns { let array_index = if areas.len() == 1 { 0_usize } else { (rng.roll_dice(1, areas.len() as i32) - 1) as usize }; let map_idx = areas[array_index]; spawn_points.insert(map_idx, spawn_table.roll(rng)); areas.remove(array_index); } } // Actually spawn the monsters for spawn in spawn_points.iter() { spawn_list.push((*spawn.0, spawn.1.to_string())); } } /// 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 width: usize; { width = ecs.fetch::().width as usize; } let (idx, name) = *spawn; let x = (*idx % width) as i32; let y = (*idx / width) as i32; let item_result = spawn_named_entity( &RAWS.lock().unwrap(), ecs, name, SpawnType::AtPosition { x, y }, ); if item_result.is_some() { return; } if !name.is_empty() { ::rltk::console::log(format!("WARNING: We don't know how to spawn [{}]!", name)); } } pub fn spawn_town_portal(ecs: &mut World) { // Get current position & depth let map = ecs.fetch::(); let player_depth = map.depth; let player_pos = ecs.fetch::(); let player_x = player_pos.x; let player_y = player_pos.y; std::mem::drop(player_pos); std::mem::drop(map); // Find part of the town for the portal let dm = ecs.fetch::(); let town_map = dm.get_map(1).unwrap(); let mut stairs_idx = 0; for (idx, tt) in town_map.tiles.iter().enumerate() { if *tt == TileType::DownStairs { stairs_idx = idx; } } let portal_x = (stairs_idx as i32 % town_map.width) - 2; let portal_y = stairs_idx as i32 / town_map.width; std::mem::drop(dm); // Spawn the portal itself ecs.create_entity() .with(OtherLevelPosition { x: portal_x, y: portal_y, depth: 1, }) .with(Renderable { glyph: ::rltk::to_cp437('♥'), fg: colors::CYAN, bg: colors::BLACK, render_order: 0, }) .with(EntryTrigger {}) .with(TeleportTo { x: player_x, y: player_y, depth: player_depth, player_only: true, }) .with(Name::from("Town Portal")) .with(SingleActivation {}) .build(); }