From cbca2a66b64a3027b619e0f0ae8032d0fd052e94 Mon Sep 17 00:00:00 2001 From: Sam Date: Sun, 28 Mar 2021 19:17:23 -0400 Subject: [PATCH] Support for creature specific materails and materials having a quantity. --- common/src/comp/body.rs | 7 + common/src/comp/inventory/item/mod.rs | 8 +- common/src/lottery.rs | 22 +- server/src/events/entity_manipulation.rs | 2 +- world/src/site/dungeon/mod.rs | 595 ++++++++++++----------- 5 files changed, 350 insertions(+), 284 deletions(-) diff --git a/common/src/comp/body.rs b/common/src/comp/body.rs index d1f124f184..f0ac684bef 100644 --- a/common/src/comp/body.rs +++ b/common/src/comp/body.rs @@ -16,6 +16,7 @@ pub mod theropod; use crate::{ assets::{self, Asset}, + comp::Item, make_case_elim, npc::NpcKind, }; @@ -582,6 +583,12 @@ impl Body { _ => Vec3::unit_z(), } } + + pub fn get_loot(&self) -> Item { + Item::new_from_asset_expect(match self { + _ => "common.items.food.cheese" + }) + } } impl Component for Body { diff --git a/common/src/comp/inventory/item/mod.rs b/common/src/comp/inventory/item/mod.rs index ec49927469..6dacdcd593 100644 --- a/common/src/comp/inventory/item/mod.rs +++ b/common/src/comp/inventory/item/mod.rs @@ -610,7 +610,7 @@ impl Item { _ => "common.loot_tables.armor_misc", }) .read(); - return Some(chosen.choose().to_item()) + return Some(chosen.choose().to_item(None)) }, SpriteKind::ChestBurried => { chosen = Lottery::::load_expect(match rng.gen_range(0..7) { @@ -620,7 +620,7 @@ impl Item { _ => "common.loot_tables.armor_misc", }) .read(); - return Some(chosen.choose().to_item()) + return Some(chosen.choose().to_item(None)) }, SpriteKind::Mud => { chosen = Lottery::::load_expect(match rng.gen_range(0..5) { @@ -630,7 +630,7 @@ impl Item { _ => "common.loot_tables.rocks", }) .read(); - return Some(chosen.choose().to_item()) + return Some(chosen.choose().to_item(None)) }, SpriteKind::Crate => { chosen = Lottery::::load_expect(match rng.gen_range(0..4) { @@ -638,7 +638,7 @@ impl Item { _ => "common.loot_tables.food", }) .read(); - return Some(chosen.choose().to_item()) + return Some(chosen.choose().to_item(None)) }, SpriteKind::Beehive => "common.items.crafting_ing.honey", diff --git a/common/src/lottery.rs b/common/src/lottery.rs index 3976d7a986..1e4851ae8e 100644 --- a/common/src/lottery.rs +++ b/common/src/lottery.rs @@ -26,7 +26,7 @@ // Cheese drop rate = 3/X = 29.6% // Coconut drop rate = 1/X = 9.85% -use crate::{assets::{self, AssetExt}, comp::Item}; +use crate::{assets::{self, AssetExt}, comp::{Body, Item}}; use rand::prelude::*; use serde::{de::DeserializeOwned, Deserialize, Serialize}; @@ -74,17 +74,31 @@ impl Lottery { #[derive(Clone, Debug, PartialEq, Deserialize, Serialize)] pub enum LootSpec { + /// Asset specifier Item(String), + /// Asset specifier, lower range, upper range + ItemQuantity(String, u32, u32), + /// Loot table LootTable(String), + /// Matches on species to provide a crafting material + CreatureMaterial, } impl LootSpec { - pub fn to_item(&self) -> Item { + pub fn to_item(&self, body: Option) -> Item { match self { Self::Item(item) => Item::new_from_asset_expect(&item), - Self::LootTable(table) => { - Lottery::::load_expect(&table).read().choose().to_item() + Self::ItemQuantity(item, lower, upper) => { + let range = *lower..=*upper; + let quantity = thread_rng().gen_range(range); + let mut item = Item::new_from_asset_expect(&item); + item.set_amount(quantity); + item }, + Self::LootTable(table) => { + Lottery::::load_expect(&table).read().choose().to_item(body) + }, + Self::CreatureMaterial => body.map_or(Item::new_from_asset_expect("common.items.food.cheese"), |b| b.get_loot()), } } } diff --git a/server/src/events/entity_manipulation.rs b/server/src/events/entity_manipulation.rs index 350bd3bdaa..7b2fcf8bf5 100644 --- a/server/src/events/entity_manipulation.rs +++ b/server/src/events/entity_manipulation.rs @@ -444,7 +444,7 @@ pub fn handle_destroy(server: &mut Server, entity: EcsEntity, cause: HealthSourc let item = { let mut item_drops = state.ecs().write_storage::(); item_drops.remove(entity).map_or_else( - || lottery().read().choose().to_item(), + || lottery().read().choose().to_item(old_body), |item_drop| item_drop.0, ) }; diff --git a/world/src/site/dungeon/mod.rs b/world/src/site/dungeon/mod.rs index 3c5c96ad91..9e7e85539c 100644 --- a/world/src/site/dungeon/mod.rs +++ b/world/src/site/dungeon/mod.rs @@ -617,160 +617,178 @@ impl Floor { .with_alignment(comp::Alignment::Enemy) .with_loadout_config(loadout_builder::LoadoutConfig::CultistAcolyte) .with_skillset_config(common::skillset_builder::SkillSetConfig::CultistAcolyte) - .with_loot_drop(chosen.to_item()) + .with_loot_drop(chosen.to_item(None)) .with_level(dynamic_rng.gen_range((room.difficulty as f32).powf(1.25) + 3.0..(room.difficulty as f32).powf(1.5) + 4.0).round() as u16); let entity = match room.difficulty { - 0 => entity - .with_body(comp::Body::BipedSmall( + 0 => { + let body = comp::Body::BipedSmall( comp::biped_small::Body::random_with( dynamic_rng, &comp::biped_small::Species::Gnarling, ), - )) - .with_name("Gnarling") - .with_loadout_config(loadout_builder::LoadoutConfig::Gnarling) - .with_skillset_config( - common::skillset_builder::SkillSetConfig::Gnarling, - ) - .with_loot_drop(chosen.to_item()) - .with_main_tool(comp::Item::new_from_asset_expect( - match dynamic_rng.gen_range(0..5) { - 0 => { - "common.items.npc_weapons.biped_small.gnarling.\ - adlet_bow" + ); + entity + .with_body(body) + .with_name("Gnarling") + .with_loadout_config(loadout_builder::LoadoutConfig::Gnarling) + .with_skillset_config( + common::skillset_builder::SkillSetConfig::Gnarling, + ) + .with_loot_drop(chosen.to_item(Some(body))) + .with_main_tool(comp::Item::new_from_asset_expect( + match dynamic_rng.gen_range(0..5) { + 0 => { + "common.items.npc_weapons.biped_small.gnarling.\ + adlet_bow" + }, + 1 => { + "common.items.npc_weapons.biped_small.gnarling.\ + gnoll_staff" + }, + _ => { + "common.items.npc_weapons.biped_small.gnarling.\ + wooden_spear" + }, }, - 1 => { - "common.items.npc_weapons.biped_small.gnarling.\ - gnoll_staff" - }, - _ => { - "common.items.npc_weapons.biped_small.gnarling.\ - wooden_spear" - }, - }, - )), - 1 => entity - .with_body(comp::Body::BipedSmall( + )) + }, + 1 => { + let body = comp::Body::BipedSmall( comp::biped_small::Body::random_with( dynamic_rng, &comp::biped_small::Species::Adlet, ), - )) - .with_name("Adlet") - .with_loadout_config(loadout_builder::LoadoutConfig::Adlet) - .with_skillset_config( - common::skillset_builder::SkillSetConfig::Adlet, - ) - .with_loot_drop(chosen.to_item()) - .with_main_tool(comp::Item::new_from_asset_expect( - match dynamic_rng.gen_range(0..5) { - 0 => "common.items.npc_weapons.biped_small.adlet.adlet_bow", - 1 => { - "common.items.npc_weapons.biped_small.adlet.gnoll_staff" + ); + entity + .with_body(body) + .with_name("Adlet") + .with_loadout_config(loadout_builder::LoadoutConfig::Adlet) + .with_skillset_config( + common::skillset_builder::SkillSetConfig::Adlet, + ) + .with_loot_drop(chosen.to_item(Some(body))) + .with_main_tool(comp::Item::new_from_asset_expect( + match dynamic_rng.gen_range(0..5) { + 0 => "common.items.npc_weapons.biped_small.adlet.adlet_bow", + 1 => { + "common.items.npc_weapons.biped_small.adlet.gnoll_staff" + }, + _ => { + "common.items.npc_weapons.biped_small.adlet.\ + wooden_spear" + }, }, - _ => { - "common.items.npc_weapons.biped_small.adlet.\ - wooden_spear" - }, - }, - )), - 2 => entity - .with_body(comp::Body::BipedSmall( + )) + }, + 2 => { + let body = comp::Body::BipedSmall( comp::biped_small::Body::random_with( dynamic_rng, &comp::biped_small::Species::Sahagin, ), - )) - .with_name("Sahagin") - .with_loadout_config(loadout_builder::LoadoutConfig::Sahagin) - .with_skillset_config( - common::skillset_builder::SkillSetConfig::Sahagin, - ) - .with_loot_drop(chosen.to_item()) - .with_main_tool(comp::Item::new_from_asset_expect( - match dynamic_rng.gen_range(0..5) { - 0 => { - "common.items.npc_weapons.biped_small.sahagin.adlet_bow" + ); + entity + .with_body(body) + .with_name("Sahagin") + .with_loadout_config(loadout_builder::LoadoutConfig::Sahagin) + .with_skillset_config( + common::skillset_builder::SkillSetConfig::Sahagin, + ) + .with_loot_drop(chosen.to_item(Some(body))) + .with_main_tool(comp::Item::new_from_asset_expect( + match dynamic_rng.gen_range(0..5) { + 0 => { + "common.items.npc_weapons.biped_small.sahagin.adlet_bow" + }, + 1 => { + "common.items.npc_weapons.biped_small.sahagin.\ + gnoll_staff" + }, + _ => { + "common.items.npc_weapons.biped_small.sahagin.\ + wooden_spear" + }, }, - 1 => { - "common.items.npc_weapons.biped_small.sahagin.\ - gnoll_staff" - }, - _ => { - "common.items.npc_weapons.biped_small.sahagin.\ - wooden_spear" - }, - }, - )), - 3 => entity - .with_body(comp::Body::BipedSmall( + )) + }, + 3 => { + let body = comp::Body::BipedSmall( comp::biped_small::Body::random_with( dynamic_rng, &comp::biped_small::Species::Haniwa, ), - )) - .with_name("Haniwa") - .with_loadout_config(loadout_builder::LoadoutConfig::Haniwa) - .with_skillset_config( - common::skillset_builder::SkillSetConfig::Haniwa, - ) - .with_loot_drop(chosen.to_item()) - .with_main_tool(comp::Item::new_from_asset_expect( - match dynamic_rng.gen_range(0..5) { - 0 => { - "common.items.npc_weapons.biped_small.haniwa.adlet_bow" + ); + entity + .with_body(body) + .with_name("Haniwa") + .with_loadout_config(loadout_builder::LoadoutConfig::Haniwa) + .with_skillset_config( + common::skillset_builder::SkillSetConfig::Haniwa, + ) + .with_loot_drop(chosen.to_item(Some(body))) + .with_main_tool(comp::Item::new_from_asset_expect( + match dynamic_rng.gen_range(0..5) { + 0 => { + "common.items.npc_weapons.biped_small.haniwa.adlet_bow" + }, + 1 => { + "common.items.npc_weapons.biped_small.haniwa.\ + gnoll_staff" + }, + _ => { + "common.items.npc_weapons.biped_small.haniwa.\ + wooden_spear" + }, }, - 1 => { - "common.items.npc_weapons.biped_small.haniwa.\ - gnoll_staff" - }, - _ => { - "common.items.npc_weapons.biped_small.haniwa.\ - wooden_spear" - }, - }, - )), - 4 => entity - .with_body(comp::Body::BipedSmall( + )) + }, + 4 => { + let body = comp::Body::BipedSmall( comp::biped_small::Body::random_with( dynamic_rng, &comp::biped_small::Species::Myrmidon, ), - )) - .with_name("Myrmidon") - .with_loadout_config(loadout_builder::LoadoutConfig::Myrmidon) - .with_skillset_config( - common::skillset_builder::SkillSetConfig::Myrmidon, - ) - .with_loot_drop(chosen.to_item()) - .with_main_tool(comp::Item::new_from_asset_expect( - match dynamic_rng.gen_range(0..5) { - 0 => { - "common.items.npc_weapons.biped_small.myrmidon.\ - adlet_bow" - }, - 1 => { - "common.items.npc_weapons.biped_small.myrmidon.\ - gnoll_staff" - }, - _ => { - "common.items.npc_weapons.biped_small.myrmidon.\ - wooden_spear" - }, - }, - )), - 5 => match dynamic_rng.gen_range(0..6) { - 0 => entity - .with_body(comp::Body::Humanoid(comp::humanoid::Body::random())) - .with_name("Cultist Warlock") - .with_loadout_config(loadout_builder::LoadoutConfig::Warlock) + ); + entity + .with_body(body) + .with_name("Myrmidon") + .with_loadout_config(loadout_builder::LoadoutConfig::Myrmidon) .with_skillset_config( - common::skillset_builder::SkillSetConfig::Warlock, + common::skillset_builder::SkillSetConfig::Myrmidon, ) - .with_loot_drop(chosen.to_item()) + .with_loot_drop(chosen.to_item(Some(body))) .with_main_tool(comp::Item::new_from_asset_expect( - "common.items.weapons.staff.cultist_staff", - )), + match dynamic_rng.gen_range(0..5) { + 0 => { + "common.items.npc_weapons.biped_small.myrmidon.\ + adlet_bow" + }, + 1 => { + "common.items.npc_weapons.biped_small.myrmidon.\ + gnoll_staff" + }, + _ => { + "common.items.npc_weapons.biped_small.myrmidon.\ + wooden_spear" + }, + }, + )) + }, + 5 => match dynamic_rng.gen_range(0..6) { + 0 => { + let body = comp::Body::Humanoid(comp::humanoid::Body::random()); + entity + .with_body(body) + .with_name("Cultist Warlock") + .with_loadout_config(loadout_builder::LoadoutConfig::Warlock) + .with_skillset_config( + common::skillset_builder::SkillSetConfig::Warlock, + ) + .with_loot_drop(chosen.to_item(Some(body))) + .with_main_tool(comp::Item::new_from_asset_expect( + "common.items.weapons.staff.cultist_staff", + )) + }, 1 => entity .with_body(comp::Body::Object(comp::object::Body::Crossbow)) .with_name("Possessed Turret".to_string()) @@ -783,7 +801,7 @@ impl Floor { .with_skillset_config( common::skillset_builder::SkillSetConfig::Warlord, ) - .with_loot_drop(chosen.to_item()) + .with_loot_drop(chosen.to_item(None)) .with_main_tool(comp::Item::new_from_asset_expect( match dynamic_rng.gen_range(0..5) { 0 => "common.items.weapons.axe.malachite_axe-0", @@ -846,75 +864,93 @@ impl Floor { let chosen = chosen.read(); let chosen = chosen.choose(); let entity = match room.difficulty { - 0 => vec![ - EntityInfo::at(tile_wcenter.map(|e| e as f32)) - .with_body(comp::Body::BipedLarge( - comp::biped_large::Body::random_with( - dynamic_rng, - &comp::biped_large::Species::Harvester, - ), - )) - .with_name("Harvester".to_string()) - .with_loot_drop(chosen.to_item()), - ], - 1 => vec![ - EntityInfo::at(tile_wcenter.map(|e| e as f32)) - .with_body(comp::Body::BipedLarge( - comp::biped_large::Body::random_with( - dynamic_rng, - &comp::biped_large::Species::Yeti, - ), - )) - .with_name("Yeti".to_string()) - .with_loot_drop(chosen.to_item()), - ], - 2 => vec![ - EntityInfo::at(tile_wcenter.map(|e| e as f32)) - .with_body(comp::Body::BipedLarge( - comp::biped_large::Body::random_with( - dynamic_rng, - &comp::biped_large::Species::Tidalwarrior, - ), - )) - .with_name("Tidal Warrior".to_string()) - .with_loot_drop(chosen.to_item()), - ], - 3 => vec![ - EntityInfo::at(tile_wcenter.map(|e| e as f32)) - .with_body(comp::Body::Golem( - comp::golem::Body::random_with( - dynamic_rng, - &comp::golem::Species::ClayGolem, - ), - )) - .with_name("Clay Golem".to_string()) - .with_loot_drop(chosen.to_item()), - ], - 4 => vec![ - EntityInfo::at(tile_wcenter.map(|e| e as f32)) - .with_body(comp::Body::BipedLarge( - comp::biped_large::Body::random_with( - dynamic_rng, - &comp::biped_large::Species::Minotaur, - ), - )) - .with_name("Minotaur".to_string()) - .with_loot_drop(chosen.to_item()), - ], - 5 => vec![ - EntityInfo::at(tile_wcenter.map(|e| e as f32)) - .with_body(comp::Body::BipedLarge( - comp::biped_large::Body::random_with( - dynamic_rng, - &comp::biped_large::Species::Mindflayer, - ), - )) - .with_name("Mindflayer".to_string()) - .with_loot_drop(chosen.to_item()) - .with_skillset_config( - common::skillset_builder::SkillSetConfig::Mindflayer, + 0 => { + let body = comp::Body::BipedLarge( + comp::biped_large::Body::random_with( + dynamic_rng, + &comp::biped_large::Species::Harvester, ), - ], + ); + vec![ + EntityInfo::at(tile_wcenter.map(|e| e as f32)) + .with_body(body) + .with_name("Harvester".to_string()) + .with_loot_drop(chosen.to_item(Some(body))), + ] + }, + 1 => { + let body = comp::Body::BipedLarge( + comp::biped_large::Body::random_with( + dynamic_rng, + &comp::biped_large::Species::Yeti, + ), + ); + vec![ + EntityInfo::at(tile_wcenter.map(|e| e as f32)) + .with_body(body) + .with_name("Yeti".to_string()) + .with_loot_drop(chosen.to_item(Some(body))), + ] + }, + 2 => { + let body = comp::Body::BipedLarge( + comp::biped_large::Body::random_with( + dynamic_rng, + &comp::biped_large::Species::Tidalwarrior, + ), + ); + vec![ + EntityInfo::at(tile_wcenter.map(|e| e as f32)) + .with_body(body) + .with_name("Tidal Warrior".to_string()) + .with_loot_drop(chosen.to_item(Some(body))), + ] + }, + 3 => { + let body = comp::Body::Golem( + comp::golem::Body::random_with( + dynamic_rng, + &comp::golem::Species::ClayGolem, + ), + ); + vec![ + EntityInfo::at(tile_wcenter.map(|e| e as f32)) + .with_body(body) + .with_name("Clay Golem".to_string()) + .with_loot_drop(chosen.to_item(Some(body))), + ] + }, + 4 => { + let body = comp::Body::BipedLarge( + comp::biped_large::Body::random_with( + dynamic_rng, + &comp::biped_large::Species::Minotaur, + ), + ); + vec![ + EntityInfo::at(tile_wcenter.map(|e| e as f32)) + .with_body(body) + .with_name("Minotaur".to_string()) + .with_loot_drop(chosen.to_item(Some(body))), + ] + }, + 5 => { + let body = comp::Body::BipedLarge( + comp::biped_large::Body::random_with( + dynamic_rng, + &comp::biped_large::Species::Mindflayer, + ), + ); + vec![ + EntityInfo::at(tile_wcenter.map(|e| e as f32)) + .with_body(body) + .with_name("Mindflayer".to_string()) + .with_loot_drop(chosen.to_item(Some(body))) + .with_skillset_config( + common::skillset_builder::SkillSetConfig::Mindflayer, + ), + ] + }, _ => { vec![EntityInfo::at(tile_wcenter.map(|e| e as f32)).with_body( comp::Body::QuadrupedSmall( @@ -986,96 +1022,105 @@ impl Floor { let chosen = chosen.read(); let chosen = chosen.choose(); let entity = match room.difficulty { - 0 => vec![ - EntityInfo::at(tile_wcenter.map(|e| e as f32)) - .with_body(comp::Body::QuadrupedMedium( - comp::quadruped_medium::Body::random_with( - dynamic_rng, - &comp::quadruped_medium::Species::Bonerattler, - ), - )) - .with_name("Bonerattler".to_string()) - .with_loot_drop(chosen.to_item()), - ], - 1 => vec![ - EntityInfo::at(tile_wcenter.map(|e| e as f32)) - .with_body(comp::Body::QuadrupedMedium( - comp::quadruped_medium::Body::random_with( - dynamic_rng, - &comp::quadruped_medium::Species::Bonerattler, - ) - )) - .with_name("Bonerattler".to_string()) - .with_loot_drop(chosen.to_item()); - 3 - ], + 0 => { + let body = comp::Body::QuadrupedMedium( + comp::quadruped_medium::Body::random_with( + dynamic_rng, + &comp::quadruped_medium::Species::Bonerattler, + ), + ); + vec![ + EntityInfo::at(tile_wcenter.map(|e| e as f32)) + .with_body(body) + .with_name("Bonerattler".to_string()) + .with_loot_drop(chosen.to_item(Some(body))), + ] + }, + 1 => { + let body = comp::Body::QuadrupedMedium( + comp::quadruped_medium::Body::random_with( + dynamic_rng, + &comp::quadruped_medium::Species::Bonerattler, + ) + ); + vec![ + EntityInfo::at(tile_wcenter.map(|e| e as f32)) + .with_body(body) + .with_name("Bonerattler".to_string()) + .with_loot_drop(chosen.to_item(Some(body))); + 3 + ] + }, 2 => { let mut entities = Vec::new(); + let body = comp::Body::QuadrupedLow( + comp::quadruped_low::Body::random_with( + dynamic_rng, + &comp::quadruped_low::Species::Hakulaq, + ), + ); entities.resize_with(6, || { EntityInfo::at(tile_wcenter.map(|e| e as f32)) - .with_body(comp::Body::QuadrupedLow( - comp::quadruped_low::Body::random_with( - dynamic_rng, - &comp::quadruped_low::Species::Hakulaq, - ), - )) + .with_body(body) .with_name("Hakulaq".to_string()) - .with_loot_drop(chosen.to_item()) + .with_loot_drop(chosen.to_item(Some(body))) }); entities }, - 3 => vec![ - EntityInfo::at(tile_wcenter.map(|e| e as f32)) - .with_body(comp::Body::Humanoid( - comp::humanoid::Body::random(), - )) - .with_name("Animal Trainer".to_string()) - .with_loot_drop(chosen.to_item()) - .with_loadout_config(loadout_builder::LoadoutConfig::CultistAcolyte) - .with_skillset_config( - common::skillset_builder::SkillSetConfig::CultistAcolyte - ) - .with_main_tool(comp::Item::new_from_asset_expect( - match dynamic_rng.gen_range(0..6) { - 0 => "common.items.weapons.axe.malachite_axe-0", - 1..=2 => "common.items.weapons.sword.cultist", - 3 => { - "common.items.weapons.hammer.cultist_purp_2h-0" + 3 => { + let mut entities = Vec::new(); + let body = comp::Body::Humanoid( + comp::humanoid::Body::random(), + ); + entities.push( + EntityInfo::at(tile_wcenter.map(|e| e as f32)) + .with_body(body) + .with_name("Animal Trainer".to_string()) + .with_loot_drop(chosen.to_item(Some(body))) + .with_loadout_config(loadout_builder::LoadoutConfig::CultistAcolyte) + .with_skillset_config( + common::skillset_builder::SkillSetConfig::CultistAcolyte + ) + .with_main_tool(comp::Item::new_from_asset_expect( + match dynamic_rng.gen_range(0..6) { + 0 => "common.items.weapons.axe.malachite_axe-0", + 1..=2 => "common.items.weapons.sword.cultist", + 3 => { + "common.items.weapons.hammer.cultist_purp_2h-0" + }, + 4 => "common.items.weapons.staff.cultist_staff", + _ => "common.items.weapons.bow.bone-1", }, - 4 => "common.items.weapons.staff.cultist_staff", - _ => "common.items.weapons.bow.bone-1", - }, - )), - EntityInfo::at(tile_wcenter.map(|e| e as f32)) - .with_body(comp::Body::QuadrupedMedium( - comp::quadruped_medium::Body::random_with( - dynamic_rng, - &comp::quadruped_medium::Species::Darkhound, - ), - )) - .with_name("Tamed Darkhound".to_string()) - .with_loot_drop(chosen.to_item()), - EntityInfo::at(tile_wcenter.map(|e| e as f32)) - .with_body(comp::Body::QuadrupedMedium( - comp::quadruped_medium::Body::random_with( - dynamic_rng, - &comp::quadruped_medium::Species::Darkhound, - ), - )) - .with_name("Tamed Darkhound".to_string()) - .with_loot_drop(chosen.to_item()), - ], - 4 => vec![ - EntityInfo::at(tile_wcenter.map(|e| e as f32)) - .with_body(comp::Body::BipedLarge( - comp::biped_large::Body::random_with( - dynamic_rng, - &comp::biped_large::Species::Dullahan, - ), - )) - .with_name("Dullahan Guard".to_string()) - .with_loot_drop(chosen.to_item()), - ], + )), + ); + let body = comp::Body::QuadrupedMedium( + comp::quadruped_medium::Body::random_with( + dynamic_rng, + &comp::quadruped_medium::Species::Darkhound, + ), + ); + entities.resize_with(entities.len() + 2, || { + EntityInfo::at(tile_wcenter.map(|e| e as f32)) + .with_body(body) + .with_name("Tamed Darkhound".to_string()) + .with_loot_drop(chosen.to_item(Some(body))) + }); + entities + }, + 4 => { + let body = comp::Body::BipedLarge( + comp::biped_large::Body::random_with( + dynamic_rng, + &comp::biped_large::Species::Dullahan, + ), + ); + vec![ + EntityInfo::at(tile_wcenter.map(|e| e as f32)) + .with_body(body) + .with_name("Dullahan Guard".to_string()) + .with_loot_drop(chosen.to_item(Some(body))), + ] + }, 5 => { let mut entities = Vec::new(); entities.resize_with(10, || {