Support for creature specific materails and materials having a quantity.

This commit is contained in:
Sam 2021-03-28 19:17:23 -04:00
parent 247004d180
commit cbca2a66b6
5 changed files with 350 additions and 284 deletions

View File

@ -16,6 +16,7 @@ pub mod theropod;
use crate::{ use crate::{
assets::{self, Asset}, assets::{self, Asset},
comp::Item,
make_case_elim, make_case_elim,
npc::NpcKind, npc::NpcKind,
}; };
@ -582,6 +583,12 @@ impl Body {
_ => Vec3::unit_z(), _ => Vec3::unit_z(),
} }
} }
pub fn get_loot(&self) -> Item {
Item::new_from_asset_expect(match self {
_ => "common.items.food.cheese"
})
}
} }
impl Component for Body { impl Component for Body {

View File

@ -610,7 +610,7 @@ impl Item {
_ => "common.loot_tables.armor_misc", _ => "common.loot_tables.armor_misc",
}) })
.read(); .read();
return Some(chosen.choose().to_item()) return Some(chosen.choose().to_item(None))
}, },
SpriteKind::ChestBurried => { SpriteKind::ChestBurried => {
chosen = Lottery::<LootSpec>::load_expect(match rng.gen_range(0..7) { chosen = Lottery::<LootSpec>::load_expect(match rng.gen_range(0..7) {
@ -620,7 +620,7 @@ impl Item {
_ => "common.loot_tables.armor_misc", _ => "common.loot_tables.armor_misc",
}) })
.read(); .read();
return Some(chosen.choose().to_item()) return Some(chosen.choose().to_item(None))
}, },
SpriteKind::Mud => { SpriteKind::Mud => {
chosen = Lottery::<LootSpec>::load_expect(match rng.gen_range(0..5) { chosen = Lottery::<LootSpec>::load_expect(match rng.gen_range(0..5) {
@ -630,7 +630,7 @@ impl Item {
_ => "common.loot_tables.rocks", _ => "common.loot_tables.rocks",
}) })
.read(); .read();
return Some(chosen.choose().to_item()) return Some(chosen.choose().to_item(None))
}, },
SpriteKind::Crate => { SpriteKind::Crate => {
chosen = Lottery::<LootSpec>::load_expect(match rng.gen_range(0..4) { chosen = Lottery::<LootSpec>::load_expect(match rng.gen_range(0..4) {
@ -638,7 +638,7 @@ impl Item {
_ => "common.loot_tables.food", _ => "common.loot_tables.food",
}) })
.read(); .read();
return Some(chosen.choose().to_item()) return Some(chosen.choose().to_item(None))
}, },
SpriteKind::Beehive => "common.items.crafting_ing.honey", SpriteKind::Beehive => "common.items.crafting_ing.honey",

View File

@ -26,7 +26,7 @@
// Cheese drop rate = 3/X = 29.6% // Cheese drop rate = 3/X = 29.6%
// Coconut drop rate = 1/X = 9.85% // 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 rand::prelude::*;
use serde::{de::DeserializeOwned, Deserialize, Serialize}; use serde::{de::DeserializeOwned, Deserialize, Serialize};
@ -74,17 +74,31 @@ impl<T> Lottery<T> {
#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)] #[derive(Clone, Debug, PartialEq, Deserialize, Serialize)]
pub enum LootSpec { pub enum LootSpec {
/// Asset specifier
Item(String), Item(String),
/// Asset specifier, lower range, upper range
ItemQuantity(String, u32, u32),
/// Loot table
LootTable(String), LootTable(String),
/// Matches on species to provide a crafting material
CreatureMaterial,
} }
impl LootSpec { impl LootSpec {
pub fn to_item(&self) -> Item { pub fn to_item(&self, body: Option<Body>) -> Item {
match self { match self {
Self::Item(item) => Item::new_from_asset_expect(&item), Self::Item(item) => Item::new_from_asset_expect(&item),
Self::LootTable(table) => { Self::ItemQuantity(item, lower, upper) => {
Lottery::<LootSpec>::load_expect(&table).read().choose().to_item() 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::<LootSpec>::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()),
} }
} }
} }

View File

@ -444,7 +444,7 @@ pub fn handle_destroy(server: &mut Server, entity: EcsEntity, cause: HealthSourc
let item = { let item = {
let mut item_drops = state.ecs().write_storage::<comp::ItemDrop>(); let mut item_drops = state.ecs().write_storage::<comp::ItemDrop>();
item_drops.remove(entity).map_or_else( item_drops.remove(entity).map_or_else(
|| lottery().read().choose().to_item(), || lottery().read().choose().to_item(old_body),
|item_drop| item_drop.0, |item_drop| item_drop.0,
) )
}; };

View File

@ -617,22 +617,24 @@ impl Floor {
.with_alignment(comp::Alignment::Enemy) .with_alignment(comp::Alignment::Enemy)
.with_loadout_config(loadout_builder::LoadoutConfig::CultistAcolyte) .with_loadout_config(loadout_builder::LoadoutConfig::CultistAcolyte)
.with_skillset_config(common::skillset_builder::SkillSetConfig::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); .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 { let entity = match room.difficulty {
0 => entity 0 => {
.with_body(comp::Body::BipedSmall( let body = comp::Body::BipedSmall(
comp::biped_small::Body::random_with( comp::biped_small::Body::random_with(
dynamic_rng, dynamic_rng,
&comp::biped_small::Species::Gnarling, &comp::biped_small::Species::Gnarling,
), ),
)) );
entity
.with_body(body)
.with_name("Gnarling") .with_name("Gnarling")
.with_loadout_config(loadout_builder::LoadoutConfig::Gnarling) .with_loadout_config(loadout_builder::LoadoutConfig::Gnarling)
.with_skillset_config( .with_skillset_config(
common::skillset_builder::SkillSetConfig::Gnarling, common::skillset_builder::SkillSetConfig::Gnarling,
) )
.with_loot_drop(chosen.to_item()) .with_loot_drop(chosen.to_item(Some(body)))
.with_main_tool(comp::Item::new_from_asset_expect( .with_main_tool(comp::Item::new_from_asset_expect(
match dynamic_rng.gen_range(0..5) { match dynamic_rng.gen_range(0..5) {
0 => { 0 => {
@ -648,20 +650,23 @@ impl Floor {
wooden_spear" wooden_spear"
}, },
}, },
)), ))
1 => entity },
.with_body(comp::Body::BipedSmall( 1 => {
let body = comp::Body::BipedSmall(
comp::biped_small::Body::random_with( comp::biped_small::Body::random_with(
dynamic_rng, dynamic_rng,
&comp::biped_small::Species::Adlet, &comp::biped_small::Species::Adlet,
), ),
)) );
entity
.with_body(body)
.with_name("Adlet") .with_name("Adlet")
.with_loadout_config(loadout_builder::LoadoutConfig::Adlet) .with_loadout_config(loadout_builder::LoadoutConfig::Adlet)
.with_skillset_config( .with_skillset_config(
common::skillset_builder::SkillSetConfig::Adlet, common::skillset_builder::SkillSetConfig::Adlet,
) )
.with_loot_drop(chosen.to_item()) .with_loot_drop(chosen.to_item(Some(body)))
.with_main_tool(comp::Item::new_from_asset_expect( .with_main_tool(comp::Item::new_from_asset_expect(
match dynamic_rng.gen_range(0..5) { match dynamic_rng.gen_range(0..5) {
0 => "common.items.npc_weapons.biped_small.adlet.adlet_bow", 0 => "common.items.npc_weapons.biped_small.adlet.adlet_bow",
@ -673,20 +678,23 @@ impl Floor {
wooden_spear" wooden_spear"
}, },
}, },
)), ))
2 => entity },
.with_body(comp::Body::BipedSmall( 2 => {
let body = comp::Body::BipedSmall(
comp::biped_small::Body::random_with( comp::biped_small::Body::random_with(
dynamic_rng, dynamic_rng,
&comp::biped_small::Species::Sahagin, &comp::biped_small::Species::Sahagin,
), ),
)) );
entity
.with_body(body)
.with_name("Sahagin") .with_name("Sahagin")
.with_loadout_config(loadout_builder::LoadoutConfig::Sahagin) .with_loadout_config(loadout_builder::LoadoutConfig::Sahagin)
.with_skillset_config( .with_skillset_config(
common::skillset_builder::SkillSetConfig::Sahagin, common::skillset_builder::SkillSetConfig::Sahagin,
) )
.with_loot_drop(chosen.to_item()) .with_loot_drop(chosen.to_item(Some(body)))
.with_main_tool(comp::Item::new_from_asset_expect( .with_main_tool(comp::Item::new_from_asset_expect(
match dynamic_rng.gen_range(0..5) { match dynamic_rng.gen_range(0..5) {
0 => { 0 => {
@ -701,20 +709,23 @@ impl Floor {
wooden_spear" wooden_spear"
}, },
}, },
)), ))
3 => entity },
.with_body(comp::Body::BipedSmall( 3 => {
let body = comp::Body::BipedSmall(
comp::biped_small::Body::random_with( comp::biped_small::Body::random_with(
dynamic_rng, dynamic_rng,
&comp::biped_small::Species::Haniwa, &comp::biped_small::Species::Haniwa,
), ),
)) );
entity
.with_body(body)
.with_name("Haniwa") .with_name("Haniwa")
.with_loadout_config(loadout_builder::LoadoutConfig::Haniwa) .with_loadout_config(loadout_builder::LoadoutConfig::Haniwa)
.with_skillset_config( .with_skillset_config(
common::skillset_builder::SkillSetConfig::Haniwa, common::skillset_builder::SkillSetConfig::Haniwa,
) )
.with_loot_drop(chosen.to_item()) .with_loot_drop(chosen.to_item(Some(body)))
.with_main_tool(comp::Item::new_from_asset_expect( .with_main_tool(comp::Item::new_from_asset_expect(
match dynamic_rng.gen_range(0..5) { match dynamic_rng.gen_range(0..5) {
0 => { 0 => {
@ -729,20 +740,23 @@ impl Floor {
wooden_spear" wooden_spear"
}, },
}, },
)), ))
4 => entity },
.with_body(comp::Body::BipedSmall( 4 => {
let body = comp::Body::BipedSmall(
comp::biped_small::Body::random_with( comp::biped_small::Body::random_with(
dynamic_rng, dynamic_rng,
&comp::biped_small::Species::Myrmidon, &comp::biped_small::Species::Myrmidon,
), ),
)) );
entity
.with_body(body)
.with_name("Myrmidon") .with_name("Myrmidon")
.with_loadout_config(loadout_builder::LoadoutConfig::Myrmidon) .with_loadout_config(loadout_builder::LoadoutConfig::Myrmidon)
.with_skillset_config( .with_skillset_config(
common::skillset_builder::SkillSetConfig::Myrmidon, 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( .with_main_tool(comp::Item::new_from_asset_expect(
match dynamic_rng.gen_range(0..5) { match dynamic_rng.gen_range(0..5) {
0 => { 0 => {
@ -758,19 +772,23 @@ impl Floor {
wooden_spear" wooden_spear"
}, },
}, },
)), ))
},
5 => match dynamic_rng.gen_range(0..6) { 5 => match dynamic_rng.gen_range(0..6) {
0 => entity 0 => {
.with_body(comp::Body::Humanoid(comp::humanoid::Body::random())) let body = comp::Body::Humanoid(comp::humanoid::Body::random());
entity
.with_body(body)
.with_name("Cultist Warlock") .with_name("Cultist Warlock")
.with_loadout_config(loadout_builder::LoadoutConfig::Warlock) .with_loadout_config(loadout_builder::LoadoutConfig::Warlock)
.with_skillset_config( .with_skillset_config(
common::skillset_builder::SkillSetConfig::Warlock, common::skillset_builder::SkillSetConfig::Warlock,
) )
.with_loot_drop(chosen.to_item()) .with_loot_drop(chosen.to_item(Some(body)))
.with_main_tool(comp::Item::new_from_asset_expect( .with_main_tool(comp::Item::new_from_asset_expect(
"common.items.weapons.staff.cultist_staff", "common.items.weapons.staff.cultist_staff",
)), ))
},
1 => entity 1 => entity
.with_body(comp::Body::Object(comp::object::Body::Crossbow)) .with_body(comp::Body::Object(comp::object::Body::Crossbow))
.with_name("Possessed Turret".to_string()) .with_name("Possessed Turret".to_string())
@ -783,7 +801,7 @@ impl Floor {
.with_skillset_config( .with_skillset_config(
common::skillset_builder::SkillSetConfig::Warlord, 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( .with_main_tool(comp::Item::new_from_asset_expect(
match dynamic_rng.gen_range(0..5) { match dynamic_rng.gen_range(0..5) {
0 => "common.items.weapons.axe.malachite_axe-0", 0 => "common.items.weapons.axe.malachite_axe-0",
@ -846,75 +864,93 @@ impl Floor {
let chosen = chosen.read(); let chosen = chosen.read();
let chosen = chosen.choose(); let chosen = chosen.choose();
let entity = match room.difficulty { let entity = match room.difficulty {
0 => vec![ 0 => {
EntityInfo::at(tile_wcenter.map(|e| e as f32)) let body = comp::Body::BipedLarge(
.with_body(comp::Body::BipedLarge(
comp::biped_large::Body::random_with( comp::biped_large::Body::random_with(
dynamic_rng, dynamic_rng,
&comp::biped_large::Species::Harvester, &comp::biped_large::Species::Harvester,
), ),
)) );
.with_name("Harvester".to_string()) vec![
.with_loot_drop(chosen.to_item()),
],
1 => vec![
EntityInfo::at(tile_wcenter.map(|e| e as f32)) EntityInfo::at(tile_wcenter.map(|e| e as f32))
.with_body(comp::Body::BipedLarge( .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( comp::biped_large::Body::random_with(
dynamic_rng, dynamic_rng,
&comp::biped_large::Species::Yeti, &comp::biped_large::Species::Yeti,
), ),
)) );
.with_name("Yeti".to_string()) vec![
.with_loot_drop(chosen.to_item()),
],
2 => vec![
EntityInfo::at(tile_wcenter.map(|e| e as f32)) EntityInfo::at(tile_wcenter.map(|e| e as f32))
.with_body(comp::Body::BipedLarge( .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( comp::biped_large::Body::random_with(
dynamic_rng, dynamic_rng,
&comp::biped_large::Species::Tidalwarrior, &comp::biped_large::Species::Tidalwarrior,
), ),
)) );
.with_name("Tidal Warrior".to_string()) vec![
.with_loot_drop(chosen.to_item()),
],
3 => vec![
EntityInfo::at(tile_wcenter.map(|e| e as f32)) EntityInfo::at(tile_wcenter.map(|e| e as f32))
.with_body(comp::Body::Golem( .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( comp::golem::Body::random_with(
dynamic_rng, dynamic_rng,
&comp::golem::Species::ClayGolem, &comp::golem::Species::ClayGolem,
), ),
)) );
.with_name("Clay Golem".to_string()) vec![
.with_loot_drop(chosen.to_item()),
],
4 => vec![
EntityInfo::at(tile_wcenter.map(|e| e as f32)) EntityInfo::at(tile_wcenter.map(|e| e as f32))
.with_body(comp::Body::BipedLarge( .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( comp::biped_large::Body::random_with(
dynamic_rng, dynamic_rng,
&comp::biped_large::Species::Minotaur, &comp::biped_large::Species::Minotaur,
), ),
)) );
.with_name("Minotaur".to_string()) vec![
.with_loot_drop(chosen.to_item()),
],
5 => vec![
EntityInfo::at(tile_wcenter.map(|e| e as f32)) EntityInfo::at(tile_wcenter.map(|e| e as f32))
.with_body(comp::Body::BipedLarge( .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( comp::biped_large::Body::random_with(
dynamic_rng, dynamic_rng,
&comp::biped_large::Species::Mindflayer, &comp::biped_large::Species::Mindflayer,
), ),
)) );
vec![
EntityInfo::at(tile_wcenter.map(|e| e as f32))
.with_body(body)
.with_name("Mindflayer".to_string()) .with_name("Mindflayer".to_string())
.with_loot_drop(chosen.to_item()) .with_loot_drop(chosen.to_item(Some(body)))
.with_skillset_config( .with_skillset_config(
common::skillset_builder::SkillSetConfig::Mindflayer, common::skillset_builder::SkillSetConfig::Mindflayer,
), ),
], ]
},
_ => { _ => {
vec![EntityInfo::at(tile_wcenter.map(|e| e as f32)).with_body( vec![EntityInfo::at(tile_wcenter.map(|e| e as f32)).with_body(
comp::Body::QuadrupedSmall( comp::Body::QuadrupedSmall(
@ -986,51 +1022,61 @@ impl Floor {
let chosen = chosen.read(); let chosen = chosen.read();
let chosen = chosen.choose(); let chosen = chosen.choose();
let entity = match room.difficulty { let entity = match room.difficulty {
0 => vec![ 0 => {
EntityInfo::at(tile_wcenter.map(|e| e as f32)) let body = comp::Body::QuadrupedMedium(
.with_body(comp::Body::QuadrupedMedium(
comp::quadruped_medium::Body::random_with( comp::quadruped_medium::Body::random_with(
dynamic_rng, dynamic_rng,
&comp::quadruped_medium::Species::Bonerattler, &comp::quadruped_medium::Species::Bonerattler,
), ),
)) );
.with_name("Bonerattler".to_string()) vec![
.with_loot_drop(chosen.to_item()),
],
1 => vec![
EntityInfo::at(tile_wcenter.map(|e| e as f32)) EntityInfo::at(tile_wcenter.map(|e| e as f32))
.with_body(comp::Body::QuadrupedMedium( .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( comp::quadruped_medium::Body::random_with(
dynamic_rng, dynamic_rng,
&comp::quadruped_medium::Species::Bonerattler, &comp::quadruped_medium::Species::Bonerattler,
) )
)) );
vec![
EntityInfo::at(tile_wcenter.map(|e| e as f32))
.with_body(body)
.with_name("Bonerattler".to_string()) .with_name("Bonerattler".to_string())
.with_loot_drop(chosen.to_item()); .with_loot_drop(chosen.to_item(Some(body)));
3 3
], ]
},
2 => { 2 => {
let mut entities = Vec::new(); let mut entities = Vec::new();
entities.resize_with(6, || { let body = comp::Body::QuadrupedLow(
EntityInfo::at(tile_wcenter.map(|e| e as f32))
.with_body(comp::Body::QuadrupedLow(
comp::quadruped_low::Body::random_with( comp::quadruped_low::Body::random_with(
dynamic_rng, dynamic_rng,
&comp::quadruped_low::Species::Hakulaq, &comp::quadruped_low::Species::Hakulaq,
), ),
)) );
entities.resize_with(6, || {
EntityInfo::at(tile_wcenter.map(|e| e as f32))
.with_body(body)
.with_name("Hakulaq".to_string()) .with_name("Hakulaq".to_string())
.with_loot_drop(chosen.to_item()) .with_loot_drop(chosen.to_item(Some(body)))
}); });
entities entities
}, },
3 => vec![ 3 => {
EntityInfo::at(tile_wcenter.map(|e| e as f32)) let mut entities = Vec::new();
.with_body(comp::Body::Humanoid( let body = comp::Body::Humanoid(
comp::humanoid::Body::random(), 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_name("Animal Trainer".to_string())
.with_loot_drop(chosen.to_item()) .with_loot_drop(chosen.to_item(Some(body)))
.with_loadout_config(loadout_builder::LoadoutConfig::CultistAcolyte) .with_loadout_config(loadout_builder::LoadoutConfig::CultistAcolyte)
.with_skillset_config( .with_skillset_config(
common::skillset_builder::SkillSetConfig::CultistAcolyte common::skillset_builder::SkillSetConfig::CultistAcolyte
@ -1046,36 +1092,35 @@ impl Floor {
_ => "common.items.weapons.bow.bone-1", _ => "common.items.weapons.bow.bone-1",
}, },
)), )),
EntityInfo::at(tile_wcenter.map(|e| e as f32)) );
.with_body(comp::Body::QuadrupedMedium( let body = comp::Body::QuadrupedMedium(
comp::quadruped_medium::Body::random_with( comp::quadruped_medium::Body::random_with(
dynamic_rng, dynamic_rng,
&comp::quadruped_medium::Species::Darkhound, &comp::quadruped_medium::Species::Darkhound,
), ),
)) );
.with_name("Tamed Darkhound".to_string()) entities.resize_with(entities.len() + 2, || {
.with_loot_drop(chosen.to_item()),
EntityInfo::at(tile_wcenter.map(|e| e as f32)) EntityInfo::at(tile_wcenter.map(|e| e as f32))
.with_body(comp::Body::QuadrupedMedium( .with_body(body)
comp::quadruped_medium::Body::random_with(
dynamic_rng,
&comp::quadruped_medium::Species::Darkhound,
),
))
.with_name("Tamed Darkhound".to_string()) .with_name("Tamed Darkhound".to_string())
.with_loot_drop(chosen.to_item()), .with_loot_drop(chosen.to_item(Some(body)))
], });
4 => vec![ entities
EntityInfo::at(tile_wcenter.map(|e| e as f32)) },
.with_body(comp::Body::BipedLarge( 4 => {
let body = comp::Body::BipedLarge(
comp::biped_large::Body::random_with( comp::biped_large::Body::random_with(
dynamic_rng, dynamic_rng,
&comp::biped_large::Species::Dullahan, &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_name("Dullahan Guard".to_string())
.with_loot_drop(chosen.to_item()), .with_loot_drop(chosen.to_item(Some(body))),
], ]
},
5 => { 5 => {
let mut entities = Vec::new(); let mut entities = Vec::new();
entities.resize_with(10, || { entities.resize_with(10, || {