From 7079a7eedd0734de4740ef794295b2a1a6dd74f8 Mon Sep 17 00:00:00 2001 From: Timothy Warren Date: Fri, 12 Nov 2021 14:06:55 -0500 Subject: [PATCH] Weight spawn odds, and simplify item spawning --- src/main.rs | 1 + src/random_table.rs | 57 +++++++++++++++++++ src/spawner.rs | 136 +++++++++++++++++--------------------------- 3 files changed, 111 insertions(+), 83 deletions(-) create mode 100644 src/random_table.rs diff --git a/src/main.rs b/src/main.rs index 9241cf7..9eb07b2 100644 --- a/src/main.rs +++ b/src/main.rs @@ -13,6 +13,7 @@ mod map_indexing_system; mod melee_combat_system; mod monster_ai_system; mod player; +pub mod random_table; mod rect; pub mod saveload_system; mod spawner; diff --git a/src/random_table.rs b/src/random_table.rs new file mode 100644 index 0000000..c6344e4 --- /dev/null +++ b/src/random_table.rs @@ -0,0 +1,57 @@ +use rltk::RandomNumberGenerator; + +pub struct RandomEntry { + name: String, + weight: i32, +} + +impl RandomEntry { + pub fn new(name: S, weight: i32) -> RandomEntry { + RandomEntry { + name: name.to_string(), + weight, + } + } +} + +#[derive(Default)] +pub struct RandomTable { + entries: Vec, + total_weight: i32, +} + +impl RandomTable { + pub fn new() -> RandomTable { + RandomTable { + entries: Vec::new(), + total_weight: 0, + } + } + + pub fn add(mut self, name: S, weight: i32) -> Self { + self.total_weight += weight; + self.entries.push(RandomEntry::new(name, weight)); + + self + } + + pub fn roll(&self, rng: &mut RandomNumberGenerator) -> String { + if self.total_weight == 0 { + return "None".to_string(); + } + + let mut roll = rng.roll_dice(1, self.total_weight) - 1; + let mut index: usize = 0; + + while roll > 0 { + if roll < self.entries[index].weight { + return self.entries[index].name.clone(); + } + + roll -= self.entries[index].weight; + index += 1; + } + + "None".to_string() + } +} diff --git a/src/spawner.rs b/src/spawner.rs index ddf83a7..90c6e99 100644 --- a/src/spawner.rs +++ b/src/spawner.rs @@ -2,13 +2,11 @@ use crate::components::{ AreaOfEffect, BlocksTile, CombatStats, Confusion, Consumable, InflictsDamage, Item, Monster, Name, Player, Position, ProvidesHealing, Ranged, Renderable, SerializeMe, Viewshed, }; -use crate::{Rect, MAP_WIDTH}; +use crate::{random_table::RandomTable, Rect, MAP_WIDTH}; use rltk::{RandomNumberGenerator, RGB}; use specs::prelude::*; use specs::saveload::{MarkedBuilder, SimpleMarker}; - -const MAX_MONSTERS: i32 = 4; -const MAX_ITEMS: i32 = 2; +use std::collections::HashMap; /// Spawns the player and returns their entity object pub fn player(ecs: &mut World, player_x: i32, player_y: i32) -> Entity { @@ -36,18 +34,62 @@ pub fn player(ecs: &mut World, player_x: i32, player_y: i32) -> Entity { .build() } -/// Spawns a random monster at a given location -pub fn random_monster(ecs: &mut World, x: i32, y: i32) { - let roll: i32; +const MAX_MONSTERS: i32 = 4; +fn room_table() -> RandomTable { + RandomTable::new() + .add("Goblin", 10) + .add("Orc", 1) + .add("Health Potion", 7) + .add("Fireball Scroll", 2) + .add("Confusion Scroll", 2) + .add("Magic Missile Scroll", 4) +} + +/// fills a room with stuff! +#[allow(clippy::map_entry)] +pub fn spawn_room(ecs: &mut World, room: &Rect) { + let spawn_table = room_table(); + let mut spawn_points: HashMap = HashMap::new(); + + // Scope to keep the borrow checker happy { let mut rng = ecs.write_resource::(); - roll = rng.roll_dice(1, 2); + let num_spawns = rng.roll_dice(1, MAX_MONSTERS + 3) - 3; + + for _i in 0..num_spawns { + let mut added = false; + let mut tries = 0; + + while !added && tries < 20 { + let x = (room.x1 + rng.roll_dice(1, i32::abs(room.x2 - room.x1))) as usize; + let y = (room.y1 + rng.roll_dice(1, i32::abs(room.y2 - room.y1))) as usize; + let idx = (y * MAP_WIDTH) + x; + + if !spawn_points.contains_key(&idx) { + spawn_points.insert(idx, spawn_table.roll(&mut rng)); + added = true; + } else { + tries += 1; + } + } + } } - match roll { - 1 => orc(ecs, x, y), - _ => goblin(ecs, x, y), + // Actually spawn stuff + for spawn in spawn_points.iter() { + 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), + _ => {} + } } } @@ -82,63 +124,6 @@ fn monster(ecs: &mut World, x: i32, y: i32, glyph: rltk::FontCharTy .build(); } -/// fills a room with stuff! -pub fn spawn_room(ecs: &mut World, room: &Rect) { - let mut monster_spawn_points: Vec = Vec::new(); - let mut item_spawn_points: Vec = Vec::new(); - - // Scope to keep the borrow checker happy - { - let mut rng = ecs.write_resource::(); - let num_monsters = rng.roll_dice(1, MAX_MONSTERS + 2) - 3; - let num_items = rng.roll_dice(1, MAX_ITEMS + 2) - 3; - - for _i in 0..num_monsters { - let mut added = false; - while !added { - let x = (room.x1 + rng.roll_dice(1, i32::abs(room.x2 - room.x1))) as usize; - let y = (room.y1 + rng.roll_dice(1, i32::abs(room.y2 - room.y1))) as usize; - let idx = (y * MAP_WIDTH) + x; - - if !monster_spawn_points.contains(&idx) { - monster_spawn_points.push(idx); - added = true; - } - } - } - - for _i in 0..num_items { - let mut added = false; - while !added { - let x = (room.x1 + rng.roll_dice(1, i32::abs(room.x2 - room.x1))) as usize; - let y = (room.y1 + rng.roll_dice(1, i32::abs(room.y2 - room.y1))) as usize; - let idx = (y * MAP_WIDTH) + x; - - if !item_spawn_points.contains(&idx) { - item_spawn_points.push(idx); - added = true; - } - } - } - } - - // Actually spawn the monsters - for idx in monster_spawn_points.iter() { - let x = *idx % MAP_WIDTH; - let y = *idx / MAP_WIDTH; - - random_monster(ecs, x as i32, y as i32); - } - - // Actually spawn the items - for idx in item_spawn_points.iter() { - let x = *idx % MAP_WIDTH; - let y = *idx / MAP_WIDTH; - - random_item(ecs, x as i32, y as i32); - } -} - fn health_potion(ecs: &mut World, x: i32, y: i32) { ecs.create_entity() .with(Position { x, y }) @@ -173,21 +158,6 @@ fn magic_missile_scroll(ecs: &mut World, x: i32, y: i32) { .build(); } -fn random_item(ecs: &mut World, x: i32, y: i32) { - let roll: i32; - { - let mut rng = ecs.write_resource::(); - roll = rng.roll_dice(1, 4); - } - - match roll { - 1 => health_potion(ecs, x, y), - 2 => fireball_scroll(ecs, x, y), - 3 => confusion_scroll(ecs, x, y), - _ => magic_missile_scroll(ecs, x, y), - } -} - fn fireball_scroll(ecs: &mut World, x: i32, y: i32) { ecs.create_entity() .with(Position { x, y })