Calendar based gear for villagers

This commit is contained in:
Christof Petig 2023-12-14 16:31:48 +01:00
parent ba02d083da
commit 9b485456eb
34 changed files with 637 additions and 265 deletions

View File

@ -1,8 +1,12 @@
// Christmas event
//(1.0, Some(Item("common.items.calendar.christmas.armor.misc.head.woolly_wintercap"))),
#![enable(implicit_some)]
(
head: Item("common.items.armor.pirate.hat"),
head: Seasonal([
(Some(Christmas), Choice([
(1, Some(Item("common.items.calendar.christmas.armor.misc.head.woolly_wintercap"))),
(1, Some(Item("common.items.armor.pirate.hat")))
])),
(None, Item("common.items.armor.pirate.hat")),
]),
shoulders: Item("common.items.armor.mail.orichalcum.shoulder"),
chest: Item("common.items.armor.mail.orichalcum.chest"),
gloves: Item("common.items.armor.mail.orichalcum.hand"),

View File

@ -1,11 +1,17 @@
// Christmas event
//(1.0, Some(Item("common.items.calendar.christmas.armor.misc.head.woolly_wintercap"))),
#![enable(implicit_some)]
(
head: Choice([
(3, Item("common.items.armor.misc.head.straw")),
(3, Item("common.items.armor.misc.head.bamboo_twig")),
(2, None),
head: Seasonal([
(Some(Christmas), Choice([
(9, Some(Item("common.items.calendar.christmas.armor.misc.head.woolly_wintercap"))),
(3, Item("common.items.armor.misc.head.straw")),
(3, Item("common.items.armor.misc.head.bamboo_twig")),
(2, None),
])),
(None, Choice([
(3, Item("common.items.armor.misc.head.straw")),
(3, Item("common.items.armor.misc.head.bamboo_twig")),
(2, None),
])),
]),
chest: Choice([
(1, Item("common.items.armor.misc.chest.worker_green_0")),

View File

@ -1,14 +1,11 @@
// Christmas event
//(1.0, Some(Item("common.items.calendar.christmas.armor.misc.head.woolly_wintercap"))),
// Christmas event
//(1.0, Some(Item("common.items.lantern.blue_0"))),
//(1.0, Some(Item("common.items.lantern.polaris"))),
//(2.0, Some(Item("common.items.lantern.red_0"))),
//(2.0, Some(Item("common.items.lantern.green_0"))),
#![enable(implicit_some)]
(
head: Choice([
(1, Item("common.items.armor.misc.head.helmet")),
head: Seasonal([
(Some(Christmas), Choice([
(1, Some(Item("common.items.armor.misc.head.helmet"))),
(1, Some(Item("common.items.calendar.christmas.armor.misc.head.woolly_wintercap"))),
])),
(None, Item("common.items.armor.misc.head.helmet")),
]),
shoulders: Item("common.items.armor.leather_plate.shoulder"),
chest: Item("common.items.armor.leather_plate.chest"),
@ -16,8 +13,18 @@
belt: Item("common.items.armor.leather_plate.belt"),
legs: Item("common.items.armor.leather_plate.pants"),
feet: Item("common.items.armor.leather_plate.foot"),
lantern: Choice([
(1, Item("common.items.lantern.black_0")),
(2, None),
]),
lantern: Seasonal([
(Some(Christmas), Choice([
(1, Some(Item("common.items.lantern.blue_0"))),
(1, Some(Item("common.items.lantern.polaris"))),
(2, Some(Item("common.items.lantern.red_0"))),
(2, Some(Item("common.items.lantern.green_0"))),
(1, Item("common.items.lantern.black_0")),
(2, None),
])),
(None, Choice([
(1, Item("common.items.lantern.black_0")),
(2, None),
])),
])
)

View File

@ -1,11 +1,17 @@
// Christmas event
//(1.0, Some(Item("common.items.calendar.christmas.armor.misc.head.woolly_wintercap"))),
#![enable(implicit_some)]
(
head: Choice([
(3, Item("common.items.armor.misc.head.straw")),
(3, Item("common.items.armor.misc.head.hood")),
(2, None),
head: Seasonal([
(Some(Christmas), Choice([
(9, Some(Item("common.items.calendar.christmas.armor.misc.head.woolly_wintercap"))),
(3, Item("common.items.armor.misc.head.straw")),
(3, Item("common.items.armor.misc.head.hood")),
(2, None),
])),
(None, Choice([
(3, Item("common.items.armor.misc.head.straw")),
(3, Item("common.items.armor.misc.head.hood")),
(2, None),
])),
]),
chest: Choice([
(1, Item("common.items.armor.twigs.chest")),

View File

@ -1,12 +1,19 @@
// Christmas event
//(1.0, Some(Item("common.items.calendar.christmas.armor.misc.head.woolly_wintercap"))),
#![enable(implicit_some)]
(
head: Choice([
(6, None),
(2, Item("common.items.armor.misc.head.straw")),
(3, Item("common.items.armor.misc.head.hood")),
(3, Item("common.items.armor.misc.head.hood_dark")),
head: Seasonal([
(Some(Christmas), Choice([
(15, Some(Item("common.items.calendar.christmas.armor.misc.head.woolly_wintercap"))),
(6, None),
(2, Item("common.items.armor.misc.head.straw")),
(3, Item("common.items.armor.misc.head.hood")),
(3, Item("common.items.armor.misc.head.hood_dark")),
])),
(None, Choice([
(6, None),
(2, Item("common.items.armor.misc.head.straw")),
(3, Item("common.items.armor.misc.head.hood")),
(3, Item("common.items.armor.misc.head.hood_dark")),
])),
]),
chest: Choice([
(1, Item("common.items.armor.hide.leather.chest")),

View File

@ -1,10 +1,15 @@
// Christmas event
//(1.0, Some(Item("common.items.calendar.christmas.armor.misc.head.woolly_wintercap"))),
#![enable(implicit_some)]
(
head: Choice([
(1, Item("common.items.armor.misc.head.headband")),
(2, None),
head: Seasonal([
(Some(Christmas), Choice([
(3, Some(Item("common.items.calendar.christmas.armor.misc.head.woolly_wintercap"))),
(2, None),
(1, Item("common.items.armor.misc.head.headband")),
])),
(None, Choice([
(2, None),
(1, Item("common.items.armor.misc.head.headband")),
])),
]),
shoulders: Choice([
(1, Item("common.items.armor.cloth_blue.shoulder_0")),

View File

@ -1,10 +1,15 @@
// Christmas event
//(1.0, Some(Item("common.items.calendar.christmas.armor.misc.head.woolly_wintercap"))),
#![enable(implicit_some)]
(
head: Choice([
(1, Item("common.items.armor.misc.head.straw")),
(2, None),
head: Seasonal([
(Some(Christmas), Choice([
(3, Some(Item("common.items.calendar.christmas.armor.misc.head.woolly_wintercap"))),
(1, Item("common.items.armor.misc.head.straw")),
(2, None),
])),
(None, Choice([
(1, Item("common.items.armor.misc.head.straw")),
(2, None),
])),
]),
chest: Choice([
(1, Item("common.items.armor.misc.chest.worker_green_0")),

View File

@ -1,5 +1,6 @@
use crate::{
assets::{self, AssetExt},
calendar::{Calendar, CalendarEvent},
comp::{
arthropod, biped_large, biped_small, bird_large, bird_medium, golem,
inventory::{
@ -9,7 +10,7 @@ use crate::{
item::{self, Item},
object, quadruped_low, quadruped_medium, quadruped_small, theropod, Body,
},
resources::Time,
resources::{Time, TimeOfDay},
trade::SiteInformation,
};
use rand::{self, distributions::WeightedError, seq::SliceRandom, Rng};
@ -48,10 +49,15 @@ enum ItemSpec {
hands: Option<item::tool::Hands>,
},
Choice(Vec<(Weight, Option<ItemSpec>)>),
Seasonal(Vec<(Option<CalendarEvent>, ItemSpec)>),
}
impl ItemSpec {
fn try_to_item(&self, rng: &mut impl Rng) -> Result<Option<Item>, SpecError> {
fn try_to_item(
&self,
rng: &mut impl Rng,
time: Option<&(TimeOfDay, Calendar)>,
) -> Result<Option<Item>, SpecError> {
match self {
ItemSpec::Item(item_asset) => {
let item = Item::new_from_asset(item_asset).map_err(SpecError::ItemAssetError)?;
@ -63,7 +69,7 @@ impl ItemSpec {
.map_err(SpecError::ItemChoiceError)?;
let item = if let Some(item_spec) = item_spec {
item_spec.try_to_item(rng)?
item_spec.try_to_item(rng, time)?
} else {
None
};
@ -76,12 +82,28 @@ impl ItemSpec {
} => item::modular::random_weapon(*tool, *material, *hands, rng)
.map(Some)
.map_err(SpecError::ModularWeaponCreationError),
ItemSpec::Seasonal(specs) => specs
.iter()
.find_map(|(season, spec)| match (season, time) {
(Some(season), Some((_time, calendar))) => {
if calendar.is_event(*season) {
Some(spec.try_to_item(rng, time))
} else {
None
}
},
(Some(_season), None) => None,
(None, _) => Some(spec.try_to_item(rng, time)),
})
.unwrap_or(Ok(None)),
}
}
// Check if ItemSpec is valid and can be turned into Item
#[cfg(test)]
fn validate(&self) -> Result<(), ValidationError> {
use itertools::Itertools;
let mut rng = rand::thread_rng();
match self {
ItemSpec::Item(item_asset) => Item::new_from_asset(item_asset)
@ -103,6 +125,9 @@ impl ItemSpec {
} => item::modular::random_weapon(*tool, *material, *hands, &mut rng)
.map(drop)
.map_err(ValidationError::ModularWeaponCreationError),
ItemSpec::Seasonal(specs) => {
specs.iter().try_for_each(|(_season, spec)| spec.validate())
},
}
}
}
@ -116,10 +141,14 @@ enum Hands {
}
impl Hands {
fn try_to_pair(&self, rng: &mut impl Rng) -> Result<(Option<Item>, Option<Item>), SpecError> {
fn try_to_pair(
&self,
rng: &mut impl Rng,
time: Option<&(TimeOfDay, Calendar)>,
) -> Result<(Option<Item>, Option<Item>), SpecError> {
match self {
Hands::InHands((mainhand, offhand)) => {
let mut from_spec = |i: &ItemSpec| i.try_to_item(rng);
let mut from_spec = |i: &ItemSpec| i.try_to_item(rng, time);
let mainhand = mainhand.as_ref().map(&mut from_spec).transpose()?.flatten();
let offhand = offhand.as_ref().map(&mut from_spec).transpose()?.flatten();
@ -130,7 +159,7 @@ impl Hands {
.choose_weighted(rng, |(weight, _)| *weight)
.map_err(SpecError::ItemChoiceError)?;
pair_spec.try_to_pair(rng)
pair_spec.try_to_pair(rng, time)
},
}
}
@ -893,14 +922,22 @@ impl LoadoutBuilder {
#[must_use]
/// Construct new `LoadoutBuilder` from `asset_specifier`
/// Will panic if asset is broken
pub fn from_asset_expect(asset_specifier: &str, rng: &mut impl Rng) -> Self {
Self::from_asset(asset_specifier, rng).expect("failed to load loadut config")
pub fn from_asset_expect(
asset_specifier: &str,
rng: &mut impl Rng,
time: Option<&(TimeOfDay, Calendar)>,
) -> Self {
Self::from_asset(asset_specifier, rng, time).expect("failed to load loadut config")
}
/// Construct new `LoadoutBuilder` from `asset_specifier`
pub fn from_asset(asset_specifier: &str, rng: &mut impl Rng) -> Result<Self, SpecError> {
pub fn from_asset(
asset_specifier: &str,
rng: &mut impl Rng,
time: Option<&(TimeOfDay, Calendar)>,
) -> Result<Self, SpecError> {
let loadout = Self::empty();
loadout.with_asset(asset_specifier, rng)
loadout.with_asset(asset_specifier, rng, time)
}
#[must_use]
@ -919,17 +956,22 @@ impl LoadoutBuilder {
pub fn from_loadout_spec(
loadout_spec: LoadoutSpec,
rng: &mut impl Rng,
time: Option<&(TimeOfDay, Calendar)>,
) -> Result<Self, SpecError> {
let loadout = Self::empty();
loadout.with_loadout_spec(loadout_spec, rng)
loadout.with_loadout_spec(loadout_spec, rng, time)
}
#[must_use]
/// Construct new `LoadoutBuilder` from `asset_specifier`
///
/// Will panic if asset is broken
pub fn from_loadout_spec_expect(loadout_spec: LoadoutSpec, rng: &mut impl Rng) -> Self {
Self::from_loadout_spec(loadout_spec, rng).expect("failed to load loadout spec")
pub fn from_loadout_spec_expect(
loadout_spec: LoadoutSpec,
rng: &mut impl Rng,
time: Option<&(TimeOfDay, Calendar)>,
) -> Self {
Self::from_loadout_spec(loadout_spec, rng, time).expect("failed to load loadout spec")
}
#[must_use = "Method consumes builder and returns updated builder."]
@ -1045,14 +1087,18 @@ impl LoadoutBuilder {
let rng = &mut rand::thread_rng();
match preset {
Preset::HuskSummon => {
self = self.with_asset_expect("common.loadout.dungeon.cultist.husk", rng);
self = self.with_asset_expect("common.loadout.dungeon.cultist.husk", rng, None);
},
Preset::BorealSummon => {
self = self.with_asset_expect("common.loadout.world.boreal.boreal_warrior", rng);
self =
self.with_asset_expect("common.loadout.world.boreal.boreal_warrior", rng, None);
},
Preset::ClockworkSummon => {
self =
self.with_asset_expect("common.loadout.dungeon.dwarven_quarry.clockwork", rng);
self = self.with_asset_expect(
"common.loadout.dungeon.dwarven_quarry.clockwork",
rng,
None,
);
},
}
@ -1062,10 +1108,15 @@ impl LoadoutBuilder {
#[must_use = "Method consumes builder and returns updated builder."]
pub fn with_creator(
mut self,
creator: fn(LoadoutBuilder, Option<&SiteInformation>) -> LoadoutBuilder,
creator: fn(
LoadoutBuilder,
Option<&SiteInformation>,
time: Option<&(TimeOfDay, Calendar)>,
) -> LoadoutBuilder,
economy: Option<&SiteInformation>,
time: Option<&(TimeOfDay, Calendar)>,
) -> LoadoutBuilder {
self = creator(self, economy);
self = creator(self, economy, time);
self
}
@ -1075,6 +1126,7 @@ impl LoadoutBuilder {
mut self,
spec: LoadoutSpec,
rng: &mut R,
time: Option<&(TimeOfDay, Calendar)>,
) -> Result<Self, SpecError> {
// Include any inheritance
let spec = spec.eval(rng)?;
@ -1082,7 +1134,7 @@ impl LoadoutBuilder {
// Utility function to unwrap our itemspec
let mut to_item = |maybe_item: Option<ItemSpec>| {
if let Some(item) = maybe_item {
item.try_to_item(rng)
item.try_to_item(rng, time)
} else {
Ok(None)
}
@ -1090,7 +1142,7 @@ impl LoadoutBuilder {
let to_pair = |maybe_hands: Option<Hands>, rng: &mut R| {
if let Some(hands) = maybe_hands {
hands.try_to_pair(rng)
hands.try_to_pair(rng, time)
} else {
Ok((None, None))
}
@ -1170,10 +1222,15 @@ impl LoadoutBuilder {
}
#[must_use = "Method consumes builder and returns updated builder."]
pub fn with_asset(self, asset_specifier: &str, rng: &mut impl Rng) -> Result<Self, SpecError> {
pub fn with_asset(
self,
asset_specifier: &str,
rng: &mut impl Rng,
time: Option<&(TimeOfDay, Calendar)>,
) -> Result<Self, SpecError> {
let spec =
LoadoutSpec::load_cloned(asset_specifier).map_err(SpecError::LoadoutAssetError)?;
self.with_loadout_spec(spec, rng)
self.with_loadout_spec(spec, rng, time)
}
/// # Usage
@ -1184,8 +1241,13 @@ impl LoadoutBuilder {
/// 1) Will panic if there is no asset with such `asset_specifier`
/// 2) Will panic if path to item specified in loadout file doesn't exist
#[must_use = "Method consumes builder and returns updated builder."]
pub fn with_asset_expect(self, asset_specifier: &str, rng: &mut impl Rng) -> Self {
self.with_asset(asset_specifier, rng)
pub fn with_asset_expect(
self,
asset_specifier: &str,
rng: &mut impl Rng,
time: Option<&(TimeOfDay, Calendar)>,
) -> Self {
self.with_asset(asset_specifier, rng, time)
.expect("failed loading loadout config")
}
@ -1194,7 +1256,7 @@ impl LoadoutBuilder {
#[must_use = "Method consumes builder and returns updated builder."]
pub fn defaults(self) -> Self {
let rng = &mut rand::thread_rng();
self.with_asset_expect("common.loadout.default", rng)
self.with_asset_expect("common.loadout.default", rng, None)
}
#[must_use = "Method consumes builder and returns updated builder."]

View File

@ -1,5 +1,6 @@
use crate::{
assets::{self, AssetExt, Error},
calendar::Calendar,
comp::{
self, agent, humanoid,
inventory::loadout_builder::{LoadoutBuilder, LoadoutSpec},
@ -8,6 +9,7 @@ use crate::{
},
lottery::LootSpec,
npc::{self, NPC_NAMES},
resources::TimeOfDay,
rtsim,
trade::SiteInformation,
};
@ -189,7 +191,13 @@ pub struct EntityInfo {
// Loadout
pub inventory: Vec<(u32, Item)>,
pub loadout: LoadoutBuilder,
pub make_loadout: Option<fn(LoadoutBuilder, Option<&SiteInformation>) -> LoadoutBuilder>,
pub make_loadout: Option<
fn(
LoadoutBuilder,
Option<&SiteInformation>,
time: Option<&(TimeOfDay, Calendar)>,
) -> LoadoutBuilder,
>,
// Skills
pub skillset_asset: Option<String>,
@ -234,13 +242,18 @@ impl EntityInfo {
/// Helper function for applying config from asset
/// with specified Rng for managing loadout.
#[must_use]
pub fn with_asset_expect<R>(self, asset_specifier: &str, loadout_rng: &mut R) -> Self
pub fn with_asset_expect<R>(
self,
asset_specifier: &str,
loadout_rng: &mut R,
time: Option<&(TimeOfDay, Calendar)>,
) -> Self
where
R: rand::Rng,
{
let config = EntityConfig::load_expect_cloned(asset_specifier);
self.with_entity_config(config, Some(asset_specifier), loadout_rng)
self.with_entity_config(config, Some(asset_specifier), loadout_rng, time)
}
/// Evaluate and apply EntityConfig
@ -250,6 +263,7 @@ impl EntityInfo {
config: EntityConfig,
config_asset: Option<&str>,
loadout_rng: &mut R,
time: Option<&(TimeOfDay, Calendar)>,
) -> Self
where
R: rand::Rng,
@ -297,7 +311,7 @@ impl EntityInfo {
self = self.with_loot_drop(loot);
// NOTE: set loadout after body, as it's used with default equipement
self = self.with_inventory(inventory, config_asset, loadout_rng);
self = self.with_inventory(inventory, config_asset, loadout_rng, time);
// Prefer the new configuration, if possible
let AgentConfig {
@ -330,6 +344,7 @@ impl EntityInfo {
inventory: InventorySpec,
config_asset: Option<&str>,
rng: &mut R,
time: Option<&(TimeOfDay, Calendar)>,
) -> Self
where
R: rand::Rng,
@ -350,14 +365,14 @@ impl EntityInfo {
self = self.with_default_equip();
},
LoadoutKind::Asset(loadout) => {
let loadout = LoadoutBuilder::from_asset(&loadout, rng).unwrap_or_else(|e| {
let loadout = LoadoutBuilder::from_asset(&loadout, rng, time).unwrap_or_else(|e| {
panic!("failed to load loadout for {config_asset}: {e:?}");
});
self.loadout = loadout;
},
LoadoutKind::Inline(loadout_spec) => {
let loadout =
LoadoutBuilder::from_loadout_spec(*loadout_spec, rng).unwrap_or_else(|e| {
let loadout = LoadoutBuilder::from_loadout_spec(*loadout_spec, rng, time)
.unwrap_or_else(|e| {
panic!("failed to load loadout for {config_asset}: {e:?}");
});
self.loadout = loadout;
@ -436,7 +451,11 @@ impl EntityInfo {
#[must_use]
pub fn with_lazy_loadout(
mut self,
creator: fn(LoadoutBuilder, Option<&SiteInformation>) -> LoadoutBuilder,
creator: fn(
LoadoutBuilder,
Option<&SiteInformation>,
time: Option<&(TimeOfDay, Calendar)>,
) -> LoadoutBuilder,
) -> Self {
self.make_loadout = Some(creator);
self

View File

@ -654,6 +654,7 @@ fn handle_make_npc(
config.clone(),
Some(&entity_config),
&mut loadout_rng,
None,
);
match NpcData::from_entity_info(entity_info) {

View File

@ -3,6 +3,7 @@
use super::*;
use crate::sys::terrain::NpcData;
use common::{
calendar::Calendar,
comp::{self, Agent, Body, Presence, PresenceKind},
event::{EventBus, NpcBuilder, ServerEvent},
generation::{BodyBuilder, EntityConfig, EntityInfo},
@ -53,13 +54,18 @@ fn humanoid_config(profession: &Profession) -> &'static str {
}
}
fn loadout_default(loadout: LoadoutBuilder, _economy: Option<&SiteInformation>) -> LoadoutBuilder {
fn loadout_default(
loadout: LoadoutBuilder,
_economy: Option<&SiteInformation>,
_time: Option<&(TimeOfDay, Calendar)>,
) -> LoadoutBuilder {
loadout
}
fn merchant_loadout(
loadout_builder: LoadoutBuilder,
economy: Option<&SiteInformation>,
_time: Option<&(TimeOfDay, Calendar)>,
) -> LoadoutBuilder {
trader_loadout(loadout_builder, economy, |_| true)
}
@ -67,6 +73,7 @@ fn merchant_loadout(
fn farmer_loadout(
loadout_builder: LoadoutBuilder,
economy: Option<&SiteInformation>,
_time: Option<&(TimeOfDay, Calendar)>,
) -> LoadoutBuilder {
trader_loadout(loadout_builder, economy, |good| matches!(good, Good::Food))
}
@ -74,6 +81,7 @@ fn farmer_loadout(
fn herbalist_loadout(
loadout_builder: LoadoutBuilder,
economy: Option<&SiteInformation>,
_time: Option<&(TimeOfDay, Calendar)>,
) -> LoadoutBuilder {
trader_loadout(loadout_builder, economy, |good| {
matches!(good, Good::Ingredients)
@ -83,6 +91,7 @@ fn herbalist_loadout(
fn chef_loadout(
loadout_builder: LoadoutBuilder,
economy: Option<&SiteInformation>,
_time: Option<&(TimeOfDay, Calendar)>,
) -> LoadoutBuilder {
trader_loadout(loadout_builder, economy, |good| matches!(good, Good::Food))
}
@ -90,6 +99,7 @@ fn chef_loadout(
fn blacksmith_loadout(
loadout_builder: LoadoutBuilder,
economy: Option<&SiteInformation>,
_time: Option<&(TimeOfDay, Calendar)>,
) -> LoadoutBuilder {
trader_loadout(loadout_builder, economy, |good| {
matches!(good, Good::Tools | Good::Armor)
@ -99,6 +109,7 @@ fn blacksmith_loadout(
fn alchemist_loadout(
loadout_builder: LoadoutBuilder,
economy: Option<&SiteInformation>,
_time: Option<&(TimeOfDay, Calendar)>,
) -> LoadoutBuilder {
trader_loadout(loadout_builder, economy, |good| {
matches!(good, Good::Potions)
@ -107,7 +118,11 @@ fn alchemist_loadout(
fn profession_extra_loadout(
profession: Option<&Profession>,
) -> fn(LoadoutBuilder, Option<&SiteInformation>) -> LoadoutBuilder {
) -> fn(
LoadoutBuilder,
Option<&SiteInformation>,
time: Option<&(TimeOfDay, Calendar)>,
) -> LoadoutBuilder {
match profession {
Some(Profession::Merchant) => merchant_loadout,
Some(Profession::Farmer) => farmer_loadout,
@ -134,7 +149,7 @@ fn profession_agent_mark(profession: Option<&Profession>) -> Option<comp::agent:
}
}
fn get_npc_entity_info(npc: &Npc, sites: &Sites, index: IndexRef) -> EntityInfo {
fn get_npc_entity_info(npc: &Npc, sites: &Sites, index: IndexRef, time: Option<&(TimeOfDay, Calendar)>) -> EntityInfo {
let pos = comp::Pos(npc.wpos);
let mut rng = npc.rng(Npc::PERM_ENTITY_CONFIG);
@ -149,7 +164,7 @@ fn get_npc_entity_info(npc: &Npc, sites: &Sites, index: IndexRef) -> EntityInfo
let entity_config = EntityConfig::from_asset_expect_owned(config_asset)
.with_body(BodyBuilder::Exact(npc.body));
EntityInfo::at(pos.0)
.with_entity_config(entity_config, Some(config_asset), &mut rng)
.with_entity_config(entity_config, Some(config_asset), &mut rng, time)
.with_alignment(if matches!(profession, Profession::Cultist) {
comp::Alignment::Enemy
} else {
@ -204,7 +219,7 @@ fn get_npc_entity_info(npc: &Npc, sites: &Sites, index: IndexRef) -> EntityInfo
let entity_config = EntityConfig::from_asset_expect_owned(config_asset)
.with_body(BodyBuilder::Exact(npc.body));
EntityInfo::at(pos.0).with_entity_config(entity_config, Some(config_asset), &mut rng)
EntityInfo::at(pos.0).with_entity_config(entity_config, Some(config_asset), &mut rng, time)
}
}
@ -225,6 +240,7 @@ impl<'a> System<'a> for Sys {
ReadStorage<'a, RtSimEntity>,
WriteStorage<'a, comp::Agent>,
ReadStorage<'a, Presence>,
ReadExpect<'a, Calendar>,
);
const NAME: &'static str = "rtsim::tick";
@ -247,10 +263,12 @@ impl<'a> System<'a> for Sys {
rtsim_entities,
mut agents,
presences,
calendar,
): Self::SystemData,
) {
let mut emitter = server_event_bus.emitter();
let rtsim = &mut *rtsim;
let calendar_data = (*time_of_day, (*calendar).clone());
// Set up rtsim inputs
{
@ -303,7 +321,7 @@ impl<'a> System<'a> for Sys {
});
},
_ => {
let entity_info = get_npc_entity_info(npc, &data.sites, index.as_index_ref());
let entity_info = get_npc_entity_info(npc, &data.sites, index.as_index_ref(), Some(&calendar_data));
emitter.emit(match NpcData::from_entity_info(entity_info) {
NpcData::Data {
@ -360,7 +378,7 @@ impl<'a> System<'a> for Sys {
if matches!(npc.mode, SimulationMode::Simulated) {
npc.mode = SimulationMode::Loaded;
let entity_info =
get_npc_entity_info(npc, &data.sites, index.as_index_ref());
get_npc_entity_info(npc, &data.sites, index.as_index_ref(), Some(&calendar_data));
Some(match NpcData::from_entity_info(entity_info) {
NpcData::Data {

View File

@ -476,7 +476,8 @@ impl NpcData {
let inventory = {
// Evaluate lazy function for loadout creation
if let Some(make_loadout) = make_loadout {
loadout_builder = loadout_builder.with_creator(make_loadout, economy.as_ref());
loadout_builder =
loadout_builder.with_creator(make_loadout, economy.as_ref(), None);
}
let loadout = loadout_builder.build();
let mut inventory = comp::inventory::Inventory::with_loadout(loadout, body);

View File

@ -305,7 +305,7 @@ impl<'a> Canvas<'a> {
}
});
for (pos, spec) in entities.drain(..) {
self.spawn(EntityInfo::at(pos).with_asset_expect(&spec, &mut rng));
self.spawn(EntityInfo::at(pos).with_asset_expect(&spec, &mut rng, None));
}
}

View File

@ -942,8 +942,11 @@ fn apply_entity_spawns<R: Rng>(canvas: &mut Canvas, wpos: Vec3<i32>, biome: &Bio
.ok()
.and_then(|s| s.0)
{
canvas
.spawn(EntityInfo::at(wpos.map(|e| e as f32)).with_asset_expect(entity_asset, rng));
canvas.spawn(EntityInfo::at(wpos.map(|e| e as f32)).with_asset_expect(
entity_asset,
rng,
None,
));
}
}

View File

@ -668,7 +668,7 @@ pub fn apply_caves_supplement<'a>(
_ => "common.entity.wild.aggressive.cave_troll",
}
};
entity.with_asset_expect(asset, dynamic_rng)
entity.with_asset_expect(asset, dynamic_rng, None)
};
supplement.add_entity(entity);

View File

@ -618,7 +618,7 @@ pub fn apply_spots_to(canvas: &mut Canvas, _dynamic_rng: &mut impl Rng) {
{
canvas.spawn(
EntityInfo::at(wpos.map(|e| e as f32) + Vec3::new(0.5, 0.5, 0.0))
.with_asset_expect(spec, &mut rng),
.with_asset_expect(spec, &mut rng, None),
);
}
}

View File

@ -138,7 +138,7 @@ impl Pack {
.groups
.choose_weighted(dynamic_rng, |(p, _group)| *p)
.expect("Failed to choose group");
let entity = EntityInfo::at(pos).with_asset_expect(entity_asset, dynamic_rng);
let entity = EntityInfo::at(pos).with_asset_expect(entity_asset, dynamic_rng, None);
let group_size = dynamic_rng.gen_range(*from..=*to);
(entity, group_size)
@ -665,7 +665,8 @@ mod tests {
let (_, (_, _, asset)) = group;
let dummy_pos = Vec3::new(0.0, 0.0, 0.0);
let mut dummy_rng = thread_rng();
let entity = EntityInfo::at(dummy_pos).with_asset_expect(asset, &mut dummy_rng);
let entity =
EntityInfo::at(dummy_pos).with_asset_expect(asset, &mut dummy_rng, None);
drop(entity);
}
}

View File

@ -542,6 +542,7 @@ impl World {
sample_get,
&mut supplement,
site.id(),
time.as_ref(),
)
});

View File

@ -13,7 +13,7 @@ pub use self::{
pub use common::terrain::site::{DungeonKindMeta, SettlementKindMeta, SiteKindMeta};
use crate::{column::ColumnSample, site2, Canvas};
use common::generation::ChunkSupplement;
use common::{calendar::Calendar, generation::ChunkSupplement, resources::TimeOfDay};
use rand::Rng;
use serde::Deserialize;
use vek::*;
@ -378,13 +378,14 @@ impl Site {
get_column: impl FnMut(Vec2<i32>) -> Option<&'a ColumnSample<'a>>,
supplement: &mut ChunkSupplement,
site_id: common::trade::SiteId,
time: Option<&(TimeOfDay, Calendar)>,
) {
match &self.kind {
SiteKind::Settlement(s) => {
let economy = self
.trade_information(site_id)
.expect("Settlement has no economy");
s.apply_supplement(dynamic_rng, wpos2d, get_column, supplement, economy)
s.apply_supplement(dynamic_rng, wpos2d, get_column, supplement, economy, time)
},
SiteKind::Dungeon(d) => d.apply_supplement(dynamic_rng, wpos2d, supplement),
SiteKind::Castle(c) => c.apply_supplement(dynamic_rng, wpos2d, get_column, supplement),

View File

@ -15,6 +15,7 @@ use crate::{
};
use common::{
astar::Astar,
calendar::Calendar,
comp::{
self, agent, bird_medium,
inventory::{
@ -24,6 +25,7 @@ use common::{
},
generation::{ChunkSupplement, EntityInfo},
path::Path,
resources::TimeOfDay,
spiral::Spiral2d,
store::{Id, Store},
terrain::{Block, BlockKind, SpriteKind, TerrainChunkSize},
@ -853,6 +855,7 @@ impl Settlement {
mut get_column: impl FnMut(Vec2<i32>) -> Option<&'a ColumnSample<'a>>,
supplement: &mut ChunkSupplement,
economy: SiteInformation,
time: Option<&(TimeOfDay, Calendar)>,
) {
// let economy: HashMap<Good, (f32, f32)> = SiteInformation::economy
// .values
@ -893,12 +896,12 @@ impl Settlement {
let entity = if is_dummy {
EntityInfo::at(entity_wpos)
.with_agency(false)
.with_asset_expect("common.entity.village.dummy", dynamic_rng)
.with_asset_expect("common.entity.village.dummy", dynamic_rng, time)
} else {
match dynamic_rng.gen_range(0..=4) {
0 => barnyard(entity_wpos, dynamic_rng),
1 => bird(entity_wpos, dynamic_rng),
_ => humanoid(entity_wpos, &economy, dynamic_rng),
_ => humanoid(entity_wpos, &economy, dynamic_rng, time),
}
};
@ -993,27 +996,37 @@ fn bird(pos: Vec3<f32>, dynamic_rng: &mut impl Rng) -> EntityInfo {
.with_automatic_name(None)
}
fn humanoid(pos: Vec3<f32>, economy: &SiteInformation, dynamic_rng: &mut impl Rng) -> EntityInfo {
fn humanoid(
pos: Vec3<f32>,
economy: &SiteInformation,
dynamic_rng: &mut impl Rng,
time: Option<&(TimeOfDay, Calendar)>,
) -> EntityInfo {
let entity = EntityInfo::at(pos);
match dynamic_rng.gen_range(0..8) {
0 | 1 => entity
.with_agent_mark(agent::Mark::Guard)
.with_asset_expect("common.entity.village.guard", dynamic_rng),
.with_asset_expect("common.entity.village.guard", dynamic_rng, time),
2 => entity
.with_agent_mark(agent::Mark::Merchant)
.with_economy(economy)
.with_lazy_loadout(merchant_loadout)
.with_asset_expect("common.entity.village.merchant", dynamic_rng),
_ => entity.with_asset_expect("common.entity.village.villager", dynamic_rng),
.with_asset_expect("common.entity.village.merchant", dynamic_rng, time),
_ => entity.with_asset_expect("common.entity.village.villager", dynamic_rng, time),
}
}
pub fn merchant_loadout(
loadout_builder: LoadoutBuilder,
economy: Option<&SiteInformation>,
time: Option<&(TimeOfDay, Calendar)>,
) -> LoadoutBuilder {
trader_loadout(
loadout_builder.with_asset_expect("common.loadout.village.merchant", &mut thread_rng()),
loadout_builder.with_asset_expect(
"common.loadout.village.merchant",
&mut thread_rng(),
time,
),
economy,
|_| true,
)

View File

@ -44,6 +44,7 @@ impl Tree {
pub fn render(&self, canvas: &mut Canvas, dynamic_rng: &mut impl Rng) {
let nz = FastNoise::new(self.seed);
let calendar = None;
canvas.foreach_col(|canvas, wpos2d, col| {
let rpos2d = wpos2d - self.origin;
@ -81,6 +82,7 @@ impl Tree {
_ => "common.entity.wild.aggressive.maneater",
},
dynamic_rng,
calendar,
),
);
} else if above && dynamic_rng.gen_bool(0.0001) {
@ -89,6 +91,7 @@ impl Tree {
.with_asset_expect(
"common.entity.wild.aggressive.swamp_troll",
dynamic_rng,
calendar,
),
);
}

View File

@ -1639,6 +1639,7 @@ impl Site {
(-border..TILE_SIZE as i32 + border)
.map(move |x| (twpos + Vec2::new(x, y), Vec2::new(x, y)))
});
let calendar = None;
#[allow(clippy::single_match)]
match &tile.kind {
@ -1693,7 +1694,7 @@ impl Site {
.unwrap();
canvas.spawn(
EntityInfo::at(Vec3::new(wpos2d.x, wpos2d.y, alt).as_())
.with_asset_expect(spec, dynamic_rng)
.with_asset_expect(spec, dynamic_rng, calendar)
.with_alignment(Alignment::Tame),
);
}

View File

@ -2290,18 +2290,27 @@ impl RibCageGenerator {
}
fn adlet_hunter<R: Rng>(pos: Vec3<i32>, rng: &mut R) -> EntityInfo {
EntityInfo::at(pos.map(|x| x as f32))
.with_asset_expect("common.entity.dungeon.adlet.hunter", rng)
EntityInfo::at(pos.map(|x| x as f32)).with_asset_expect(
"common.entity.dungeon.adlet.hunter",
rng,
None,
)
}
fn adlet_icepicker<R: Rng>(pos: Vec3<i32>, rng: &mut R) -> EntityInfo {
EntityInfo::at(pos.map(|x| x as f32))
.with_asset_expect("common.entity.dungeon.adlet.icepicker", rng)
EntityInfo::at(pos.map(|x| x as f32)).with_asset_expect(
"common.entity.dungeon.adlet.icepicker",
rng,
None,
)
}
fn adlet_tracker<R: Rng>(pos: Vec3<i32>, rng: &mut R) -> EntityInfo {
EntityInfo::at(pos.map(|x| x as f32))
.with_asset_expect("common.entity.dungeon.adlet.tracker", rng)
EntityInfo::at(pos.map(|x| x as f32)).with_asset_expect(
"common.entity.dungeon.adlet.tracker",
rng,
None,
)
}
fn random_adlet<R: Rng>(pos: Vec3<i32>, rng: &mut R) -> EntityInfo {
@ -2313,42 +2322,67 @@ fn random_adlet<R: Rng>(pos: Vec3<i32>, rng: &mut R) -> EntityInfo {
}
fn adlet_elder<R: Rng>(pos: Vec3<i32>, rng: &mut R) -> EntityInfo {
EntityInfo::at(pos.map(|x| x as f32))
.with_asset_expect("common.entity.dungeon.adlet.elder", rng)
EntityInfo::at(pos.map(|x| x as f32)).with_asset_expect(
"common.entity.dungeon.adlet.elder",
rng,
None,
)
}
fn rat<R: Rng>(pos: Vec3<i32>, rng: &mut R) -> EntityInfo {
EntityInfo::at(pos.map(|x| x as f32)).with_asset_expect("common.entity.wild.peaceful.rat", rng)
EntityInfo::at(pos.map(|x| x as f32)).with_asset_expect(
"common.entity.wild.peaceful.rat",
rng,
None,
)
}
fn wolf<R: Rng>(pos: Vec3<i32>, rng: &mut R) -> EntityInfo {
EntityInfo::at(pos.map(|x| x as f32))
.with_asset_expect("common.entity.wild.aggressive.wolf", rng)
EntityInfo::at(pos.map(|x| x as f32)).with_asset_expect(
"common.entity.wild.aggressive.wolf",
rng,
None,
)
}
fn bear<R: Rng>(pos: Vec3<i32>, rng: &mut R) -> EntityInfo {
EntityInfo::at(pos.map(|x| x as f32))
.with_asset_expect("common.entity.wild.aggressive.bear", rng)
EntityInfo::at(pos.map(|x| x as f32)).with_asset_expect(
"common.entity.wild.aggressive.bear",
rng,
None,
)
}
fn frostfang<R: Rng>(pos: Vec3<i32>, rng: &mut R) -> EntityInfo {
EntityInfo::at(pos.map(|x| x as f32))
.with_asset_expect("common.entity.wild.aggressive.frostfang", rng)
EntityInfo::at(pos.map(|x| x as f32)).with_asset_expect(
"common.entity.wild.aggressive.frostfang",
rng,
None,
)
}
fn roshwalr<R: Rng>(pos: Vec3<i32>, rng: &mut R) -> EntityInfo {
EntityInfo::at(pos.map(|x| x as f32))
.with_asset_expect("common.entity.wild.aggressive.roshwalr", rng)
EntityInfo::at(pos.map(|x| x as f32)).with_asset_expect(
"common.entity.wild.aggressive.roshwalr",
rng,
None,
)
}
fn icedrake<R: Rng>(pos: Vec3<i32>, rng: &mut R) -> EntityInfo {
EntityInfo::at(pos.map(|x| x as f32))
.with_asset_expect("common.entity.wild.aggressive.icedrake", rng)
EntityInfo::at(pos.map(|x| x as f32)).with_asset_expect(
"common.entity.wild.aggressive.icedrake",
rng,
None,
)
}
fn tursus<R: Rng>(pos: Vec3<i32>, rng: &mut R) -> EntityInfo {
EntityInfo::at(pos.map(|x| x as f32))
.with_asset_expect("common.entity.wild.aggressive.tursus", rng)
EntityInfo::at(pos.map(|x| x as f32)).with_asset_expect(
"common.entity.wild.aggressive.tursus",
rng,
None,
)
}
fn random_yetipit_mob<R: Rng>(pos: Vec3<i32>, rng: &mut R) -> EntityInfo {
@ -2361,7 +2395,11 @@ fn random_yetipit_mob<R: Rng>(pos: Vec3<i32>, rng: &mut R) -> EntityInfo {
}
fn yeti<R: Rng>(pos: Vec3<i32>, rng: &mut R) -> EntityInfo {
EntityInfo::at(pos.map(|x| x as f32)).with_asset_expect("common.entity.dungeon.adlet.yeti", rng)
EntityInfo::at(pos.map(|x| x as f32)).with_asset_expect(
"common.entity.dungeon.adlet.yeti",
rng,
None,
)
}
#[cfg(test)]

View File

@ -511,8 +511,11 @@ fn render_heightened_viaduct(bridge: &Bridge, painter: &Painter, data: &Heighten
let mut rng = thread_rng();
if rng.gen_bool(0.1) {
painter.spawn(
EntityInfo::at(c.with_z(bridge.center.z).as_())
.with_asset_expect("common.entity.wild.aggressive.swamp_troll", &mut rng),
EntityInfo::at(c.with_z(bridge.center.z).as_()).with_asset_expect(
"common.entity.wild.aggressive.swamp_troll",
&mut rng,
None,
),
);
}
}

View File

@ -88,8 +88,11 @@ impl Structure for Camp {
CampType::Pirate => {
for p in 0..npc_rng {
painter.spawn(
EntityInfo::at((center + p).with_z(base + 2).as_())
.with_asset_expect("common.entity.spot.pirate", &mut thread_rng),
EntityInfo::at((center + p).with_z(base + 2).as_()).with_asset_expect(
"common.entity.spot.pirate",
&mut thread_rng,
None,
),
)
}
let pet = if npc_rng < 3 {
@ -98,21 +101,30 @@ impl Structure for Camp {
"common.entity.wild.peaceful.rat"
};
painter.spawn(
EntityInfo::at(center.with_z(base + 2).as_())
.with_asset_expect(pet, &mut thread_rng),
EntityInfo::at(center.with_z(base + 2).as_()).with_asset_expect(
pet,
&mut thread_rng,
None,
),
)
},
_ => {
if npc_rng > 2 {
painter.spawn(
EntityInfo::at((center - 1).with_z(base + 2).as_())
.with_asset_expect("common.entity.village.bowman", &mut thread_rng),
EntityInfo::at((center - 1).with_z(base + 2).as_()).with_asset_expect(
"common.entity.village.bowman",
&mut thread_rng,
None,
),
);
}
if npc_rng < 4 {
painter.spawn(
EntityInfo::at((center + 1).with_z(base + 2).as_())
.with_asset_expect("common.entity.village.skinner", &mut thread_rng),
EntityInfo::at((center + 1).with_z(base + 2).as_()).with_asset_expect(
"common.entity.village.skinner",
&mut thread_rng,
None,
),
)
}
},

View File

@ -704,9 +704,19 @@ fn enemy_2(dynamic_rng: &mut impl Rng, tile_wcenter: Vec3<i32>) -> Vec<EntityInf
// TODO: give enemies health skills?
let entity = EntityInfo::at(tile_wcenter.map(|e| e as f32));
match dynamic_rng.gen_range(0..=4) {
0 => entity.with_asset_expect("common.entity.dungeon.sahagin.sniper", dynamic_rng),
1 => entity.with_asset_expect("common.entity.dungeon.sahagin.sorcerer", dynamic_rng),
_ => entity.with_asset_expect("common.entity.dungeon.sahagin.spearman", dynamic_rng),
0 => {
entity.with_asset_expect("common.entity.dungeon.sahagin.sniper", dynamic_rng, None)
},
1 => entity.with_asset_expect(
"common.entity.dungeon.sahagin.sorcerer",
dynamic_rng,
None,
),
_ => entity.with_asset_expect(
"common.entity.dungeon.sahagin.spearman",
dynamic_rng,
None,
),
}
});
@ -720,9 +730,11 @@ fn enemy_3(dynamic_rng: &mut impl Rng, tile_wcenter: Vec3<i32>) -> Vec<EntityInf
// TODO: give enemies health skills?
let entity = EntityInfo::at(tile_wcenter.map(|e| e as f32));
match dynamic_rng.gen_range(0..=4) {
0 => entity.with_asset_expect("common.entity.dungeon.haniwa.archer", dynamic_rng),
1 => entity.with_asset_expect("common.entity.dungeon.haniwa.soldier", dynamic_rng),
_ => entity.with_asset_expect("common.entity.dungeon.haniwa.guard", dynamic_rng),
0 => entity.with_asset_expect("common.entity.dungeon.haniwa.archer", dynamic_rng, None),
1 => {
entity.with_asset_expect("common.entity.dungeon.haniwa.soldier", dynamic_rng, None)
},
_ => entity.with_asset_expect("common.entity.dungeon.haniwa.guard", dynamic_rng, None),
}
});
@ -736,9 +748,21 @@ fn enemy_4(dynamic_rng: &mut impl Rng, tile_wcenter: Vec3<i32>) -> Vec<EntityInf
// TODO: give enemies health skills?
let entity = EntityInfo::at(tile_wcenter.map(|e| e as f32));
match dynamic_rng.gen_range(0..=4) {
0 => entity.with_asset_expect("common.entity.dungeon.myrmidon.marksman", dynamic_rng),
1 => entity.with_asset_expect("common.entity.dungeon.myrmidon.strategian", dynamic_rng),
_ => entity.with_asset_expect("common.entity.dungeon.myrmidon.hoplite", dynamic_rng),
0 => entity.with_asset_expect(
"common.entity.dungeon.myrmidon.marksman",
dynamic_rng,
None,
),
1 => entity.with_asset_expect(
"common.entity.dungeon.myrmidon.strategian",
dynamic_rng,
None,
),
_ => entity.with_asset_expect(
"common.entity.dungeon.myrmidon.hoplite",
dynamic_rng,
None,
),
}
});
@ -752,9 +776,15 @@ fn enemy_5(dynamic_rng: &mut impl Rng, tile_wcenter: Vec3<i32>) -> Vec<EntityInf
// TODO: give enemies health skills?
let entity = EntityInfo::at(tile_wcenter.map(|e| e as f32));
match dynamic_rng.gen_range(0..=4) {
0 => entity.with_asset_expect("common.entity.dungeon.cultist.warlock", dynamic_rng),
1 => entity.with_asset_expect("common.entity.dungeon.cultist.warlord", dynamic_rng),
_ => entity.with_asset_expect("common.entity.dungeon.cultist.cultist", dynamic_rng),
0 => {
entity.with_asset_expect("common.entity.dungeon.cultist.warlock", dynamic_rng, None)
},
1 => {
entity.with_asset_expect("common.entity.dungeon.cultist.warlord", dynamic_rng, None)
},
_ => {
entity.with_asset_expect("common.entity.dungeon.cultist.cultist", dynamic_rng, None)
},
}
});
@ -766,31 +796,37 @@ fn enemy_fallback(dynamic_rng: &mut impl Rng, tile_wcenter: Vec3<i32>) -> Vec<En
let mut entities = Vec::new();
entities.resize_with(number, || {
let entity = EntityInfo::at(tile_wcenter.map(|e| e as f32));
entity.with_asset_expect("common.entity.dungeon.fallback.enemy", dynamic_rng)
entity.with_asset_expect("common.entity.dungeon.fallback.enemy", dynamic_rng, None)
});
entities
}
fn turret_3(dynamic_rng: &mut impl Rng, pos: Vec3<f32>) -> EntityInfo {
EntityInfo::at(pos).with_asset_expect("common.entity.dungeon.haniwa.sentry", dynamic_rng)
EntityInfo::at(pos).with_asset_expect("common.entity.dungeon.haniwa.sentry", dynamic_rng, None)
}
fn turret_5(dynamic_rng: &mut impl Rng, pos: Vec3<f32>) -> EntityInfo {
EntityInfo::at(pos).with_asset_expect("common.entity.dungeon.cultist.turret", dynamic_rng)
EntityInfo::at(pos).with_asset_expect("common.entity.dungeon.cultist.turret", dynamic_rng, None)
}
fn boss_2(dynamic_rng: &mut impl Rng, tile_wcenter: Vec3<i32>) -> Vec<EntityInfo> {
vec![
EntityInfo::at(tile_wcenter.map(|e| e as f32))
.with_asset_expect("common.entity.dungeon.sahagin.tidalwarrior", dynamic_rng),
EntityInfo::at(tile_wcenter.map(|e| e as f32)).with_asset_expect(
"common.entity.dungeon.sahagin.tidalwarrior",
dynamic_rng,
None,
),
]
}
fn boss_3(dynamic_rng: &mut impl Rng, tile_wcenter: Vec3<i32>) -> Vec<EntityInfo> {
let mut entities = Vec::new();
entities.resize_with(2, || {
EntityInfo::at(tile_wcenter.map(|e| e as f32))
.with_asset_expect("common.entity.dungeon.haniwa.claygolem", dynamic_rng)
EntityInfo::at(tile_wcenter.map(|e| e as f32)).with_asset_expect(
"common.entity.dungeon.haniwa.claygolem",
dynamic_rng,
None,
)
});
entities
@ -798,30 +834,42 @@ fn boss_3(dynamic_rng: &mut impl Rng, tile_wcenter: Vec3<i32>) -> Vec<EntityInfo
fn boss_4(dynamic_rng: &mut impl Rng, tile_wcenter: Vec3<i32>) -> Vec<EntityInfo> {
vec![
EntityInfo::at(tile_wcenter.map(|e| e as f32))
.with_asset_expect("common.entity.dungeon.myrmidon.minotaur", dynamic_rng),
EntityInfo::at(tile_wcenter.map(|e| e as f32)).with_asset_expect(
"common.entity.dungeon.myrmidon.minotaur",
dynamic_rng,
None,
),
]
}
fn boss_5(dynamic_rng: &mut impl Rng, tile_wcenter: Vec3<i32>) -> Vec<EntityInfo> {
vec![
EntityInfo::at(tile_wcenter.map(|e| e as f32))
.with_asset_expect("common.entity.dungeon.cultist.mindflayer", dynamic_rng),
EntityInfo::at(tile_wcenter.map(|e| e as f32)).with_asset_expect(
"common.entity.dungeon.cultist.mindflayer",
dynamic_rng,
None,
),
]
}
fn boss_fallback(dynamic_rng: &mut impl Rng, tile_wcenter: Vec3<i32>) -> Vec<EntityInfo> {
vec![
EntityInfo::at(tile_wcenter.map(|e| e as f32))
.with_asset_expect("common.entity.dungeon.fallback.boss", dynamic_rng),
EntityInfo::at(tile_wcenter.map(|e| e as f32)).with_asset_expect(
"common.entity.dungeon.fallback.boss",
dynamic_rng,
None,
),
]
}
fn mini_boss_2(dynamic_rng: &mut impl Rng, tile_wcenter: Vec3<i32>) -> Vec<EntityInfo> {
let mut entities = Vec::new();
entities.resize_with(6, || {
EntityInfo::at(tile_wcenter.map(|e| e as f32))
.with_asset_expect("common.entity.dungeon.sahagin.hakulaq", dynamic_rng)
EntityInfo::at(tile_wcenter.map(|e| e as f32)).with_asset_expect(
"common.entity.dungeon.sahagin.hakulaq",
dynamic_rng,
None,
)
});
entities
}
@ -829,16 +877,22 @@ fn mini_boss_2(dynamic_rng: &mut impl Rng, tile_wcenter: Vec3<i32>) -> Vec<Entit
fn mini_boss_3(dynamic_rng: &mut impl Rng, tile_wcenter: Vec3<i32>) -> Vec<EntityInfo> {
let mut entities = Vec::new();
entities.resize_with(3, || {
EntityInfo::at(tile_wcenter.map(|e| e as f32))
.with_asset_expect("common.entity.dungeon.haniwa.bonerattler", dynamic_rng)
EntityInfo::at(tile_wcenter.map(|e| e as f32)).with_asset_expect(
"common.entity.dungeon.haniwa.bonerattler",
dynamic_rng,
None,
)
});
entities
}
fn mini_boss_4(dynamic_rng: &mut impl Rng, tile_wcenter: Vec3<i32>) -> Vec<EntityInfo> {
vec![
EntityInfo::at(tile_wcenter.map(|e| e as f32))
.with_asset_expect("common.entity.dungeon.myrmidon.cyclops", dynamic_rng),
EntityInfo::at(tile_wcenter.map(|e| e as f32)).with_asset_expect(
"common.entity.dungeon.myrmidon.cyclops",
dynamic_rng,
None,
),
]
}
@ -847,24 +901,36 @@ fn mini_boss_5(dynamic_rng: &mut impl Rng, tile_wcenter: Vec3<i32>) -> Vec<Entit
match dynamic_rng.gen_range(0..=2) {
0 => {
entities.push(
EntityInfo::at(tile_wcenter.map(|e| e as f32))
.with_asset_expect("common.entity.dungeon.cultist.beastmaster", dynamic_rng),
EntityInfo::at(tile_wcenter.map(|e| e as f32)).with_asset_expect(
"common.entity.dungeon.cultist.beastmaster",
dynamic_rng,
None,
),
);
entities.resize_with(entities.len() + 4, || {
EntityInfo::at(tile_wcenter.map(|e| e as f32))
.with_asset_expect("common.entity.dungeon.cultist.hound", dynamic_rng)
EntityInfo::at(tile_wcenter.map(|e| e as f32)).with_asset_expect(
"common.entity.dungeon.cultist.hound",
dynamic_rng,
None,
)
});
},
1 => {
entities.resize_with(2, || {
EntityInfo::at(tile_wcenter.map(|e| e as f32))
.with_asset_expect("common.entity.dungeon.cultist.husk_brute", dynamic_rng)
EntityInfo::at(tile_wcenter.map(|e| e as f32)).with_asset_expect(
"common.entity.dungeon.cultist.husk_brute",
dynamic_rng,
None,
)
});
},
_ => {
entities.resize_with(10, || {
EntityInfo::at(tile_wcenter.map(|e| e as f32))
.with_asset_expect("common.entity.dungeon.cultist.husk", dynamic_rng)
EntityInfo::at(tile_wcenter.map(|e| e as f32)).with_asset_expect(
"common.entity.dungeon.cultist.husk",
dynamic_rng,
None,
)
});
},
}
@ -873,8 +939,11 @@ fn mini_boss_5(dynamic_rng: &mut impl Rng, tile_wcenter: Vec3<i32>) -> Vec<Entit
fn mini_boss_fallback(dynamic_rng: &mut impl Rng, tile_wcenter: Vec3<i32>) -> Vec<EntityInfo> {
vec![
EntityInfo::at(tile_wcenter.map(|e| e as f32))
.with_asset_expect("common.entity.dungeon.fallback.miniboss", dynamic_rng),
EntityInfo::at(tile_wcenter.map(|e| e as f32)).with_asset_expect(
"common.entity.dungeon.fallback.miniboss",
dynamic_rng,
None,
),
]
}

View File

@ -637,7 +637,7 @@ fn spawn_entity(pos: Vec3<f32>, painter: &Painter, entity_path: &str) {
let mut rng = thread_rng();
painter.spawn(
EntityInfo::at(pos)
.with_asset_expect(entity_path, &mut rng)
.with_asset_expect(entity_path, &mut rng, None)
.with_no_flee(),
);
}
@ -668,7 +668,7 @@ fn spawn_entities(
let spawn_pos = pos + Vec3::new(x_offset, y_offset, 0.0);
painter.spawn(EntityInfo::at(spawn_pos).with_asset_expect(entity_path, &mut rng));
painter.spawn(EntityInfo::at(spawn_pos).with_asset_expect(entity_path, &mut rng, None));
}
}

View File

@ -49,27 +49,31 @@ impl GiantTree {
if above_block.kind() == BlockKind::Leaves && dynamic_rng.gen_bool(0.001) {
let entity = EntityInfo::at(pos.as_());
match dynamic_rng.gen_range(0..=4) {
0 => {
Some(entity.with_asset_expect(
"common.entity.wild.aggressive.horn_beetle",
dynamic_rng,
))
},
1 => {
Some(entity.with_asset_expect(
"common.entity.wild.aggressive.stag_beetle",
dynamic_rng,
))
},
2 => Some(
entity.with_asset_expect("common.entity.wild.aggressive.deadwood", dynamic_rng),
),
3 => Some(
entity.with_asset_expect("common.entity.wild.aggressive.maneater", dynamic_rng),
),
4 => Some(
entity.with_asset_expect("common.entity.wild.peaceful.parrot", dynamic_rng),
),
0 => Some(entity.with_asset_expect(
"common.entity.wild.aggressive.horn_beetle",
dynamic_rng,
None,
)),
1 => Some(entity.with_asset_expect(
"common.entity.wild.aggressive.stag_beetle",
dynamic_rng,
None,
)),
2 => Some(entity.with_asset_expect(
"common.entity.wild.aggressive.deadwood",
dynamic_rng,
None,
)),
3 => Some(entity.with_asset_expect(
"common.entity.wild.aggressive.maneater",
dynamic_rng,
None,
)),
4 => Some(entity.with_asset_expect(
"common.entity.wild.peaceful.parrot",
dynamic_rng,
None,
)),
_ => None,
}
} else {

View File

@ -1874,18 +1874,27 @@ impl Structure for GnarlingFortification {
}
fn gnarling_mugger<R: Rng>(pos: Vec3<i32>, rng: &mut R) -> EntityInfo {
EntityInfo::at(pos.map(|x| x as f32))
.with_asset_expect("common.entity.dungeon.gnarling.mugger", rng)
EntityInfo::at(pos.map(|x| x as f32)).with_asset_expect(
"common.entity.dungeon.gnarling.mugger",
rng,
None,
)
}
fn gnarling_stalker<R: Rng>(pos: Vec3<i32>, rng: &mut R) -> EntityInfo {
EntityInfo::at(pos.map(|x| x as f32))
.with_asset_expect("common.entity.dungeon.gnarling.stalker", rng)
EntityInfo::at(pos.map(|x| x as f32)).with_asset_expect(
"common.entity.dungeon.gnarling.stalker",
rng,
None,
)
}
fn gnarling_logger<R: Rng>(pos: Vec3<i32>, rng: &mut R) -> EntityInfo {
EntityInfo::at(pos.map(|x| x as f32))
.with_asset_expect("common.entity.dungeon.gnarling.logger", rng)
EntityInfo::at(pos.map(|x| x as f32)).with_asset_expect(
"common.entity.dungeon.gnarling.logger",
rng,
None,
)
}
fn random_gnarling<R: Rng>(pos: Vec3<i32>, rng: &mut R) -> EntityInfo {
@ -1898,28 +1907,40 @@ fn random_gnarling<R: Rng>(pos: Vec3<i32>, rng: &mut R) -> EntityInfo {
fn gnarling_chieftain<R: Rng>(pos: Vec3<i32>, rng: &mut R) -> EntityInfo {
EntityInfo::at(pos.map(|x| x as f32))
.with_asset_expect("common.entity.dungeon.gnarling.chieftain", rng)
.with_asset_expect("common.entity.dungeon.gnarling.chieftain", rng, None)
.with_no_flee()
}
fn deadwood<R: Rng>(pos: Vec3<i32>, rng: &mut R) -> EntityInfo {
EntityInfo::at(pos.map(|x| x as f32))
.with_asset_expect("common.entity.wild.aggressive.deadwood", rng)
EntityInfo::at(pos.map(|x| x as f32)).with_asset_expect(
"common.entity.wild.aggressive.deadwood",
rng,
None,
)
}
fn mandragora<R: Rng>(pos: Vec3<i32>, rng: &mut R) -> EntityInfo {
EntityInfo::at(pos.map(|x| x as f32))
.with_asset_expect("common.entity.dungeon.gnarling.mandragora", rng)
EntityInfo::at(pos.map(|x| x as f32)).with_asset_expect(
"common.entity.dungeon.gnarling.mandragora",
rng,
None,
)
}
fn wood_golem<R: Rng>(pos: Vec3<i32>, rng: &mut R) -> EntityInfo {
EntityInfo::at(pos.map(|x| x as f32))
.with_asset_expect("common.entity.dungeon.gnarling.woodgolem", rng)
EntityInfo::at(pos.map(|x| x as f32)).with_asset_expect(
"common.entity.dungeon.gnarling.woodgolem",
rng,
None,
)
}
fn harvester_boss<R: Rng>(pos: Vec3<i32>, rng: &mut R) -> EntityInfo {
EntityInfo::at(pos.map(|x| x as f32))
.with_asset_expect("common.entity.dungeon.gnarling.harvester", rng)
EntityInfo::at(pos.map(|x| x as f32)).with_asset_expect(
"common.entity.dungeon.gnarling.harvester",
rng,
None,
)
}
#[derive(Default)]

View File

@ -240,17 +240,24 @@ impl Structure for JungleRuin {
EntityInfo::at(npc_pos.with_z(plot_base + 5).as_()).with_asset_expect(
"common.entity.spot.dwarf_grave_robber",
&mut thread_rng,
None,
),
),
// sauroks
1 => painter.spawn(
EntityInfo::at(npc_pos.with_z(plot_base + 5).as_())
.with_asset_expect("common.entity.spot.saurok", &mut thread_rng),
EntityInfo::at(npc_pos.with_z(plot_base + 5).as_()).with_asset_expect(
"common.entity.spot.saurok",
&mut thread_rng,
None,
),
),
// grim salvager
2 => painter.spawn(
EntityInfo::at(npc_pos.with_z(plot_base + 5).as_())
.with_asset_expect("common.entity.spot.grim_salvager", &mut thread_rng),
EntityInfo::at(npc_pos.with_z(plot_base + 5).as_()).with_asset_expect(
"common.entity.spot.grim_salvager",
&mut thread_rng,
None,
),
),
_ => {},
}

View File

@ -68,18 +68,27 @@ impl Structure for PirateHideout {
match RandomField::new(0).get(npc_pos.with_z(base)) % 10 {
// rat
0 => painter.spawn(
EntityInfo::at(npc_pos.with_z(base).as_())
.with_asset_expect("common.entity.wild.peaceful.rat", &mut thread_rng),
EntityInfo::at(npc_pos.with_z(base).as_()).with_asset_expect(
"common.entity.wild.peaceful.rat",
&mut thread_rng,
None,
),
),
// parrot
1 => painter.spawn(
EntityInfo::at(npc_pos.with_z(base).as_())
.with_asset_expect("common.entity.wild.peaceful.parrot", &mut thread_rng),
EntityInfo::at(npc_pos.with_z(base).as_()).with_asset_expect(
"common.entity.wild.peaceful.parrot",
&mut thread_rng,
None,
),
),
// pirates
_ => painter.spawn(
EntityInfo::at(npc_pos.with_z(base).as_())
.with_asset_expect("common.entity.spot.pirate", &mut thread_rng),
EntityInfo::at(npc_pos.with_z(base).as_()).with_asset_expect(
"common.entity.spot.pirate",
&mut thread_rng,
None,
),
),
}
}

View File

@ -57,8 +57,11 @@ impl Structure for RockCircle {
if thread_rng.gen_range(0..=8) < 1 {
// dullahan
painter.spawn(
EntityInfo::at(center.with_z(base + 2).as_())
.with_asset_expect("common.entity.wild.aggressive.dullahan", &mut thread_rng),
EntityInfo::at(center.with_z(base + 2).as_()).with_asset_expect(
"common.entity.wild.aggressive.dullahan",
&mut thread_rng,
None,
),
)
}
}

View File

@ -611,10 +611,11 @@ impl Structure for SeaChapel {
// cellar sea crocodiles
let cellar_sea_croc_pos = (center - (diameter / 4)).with_z(base - (diameter / 2));
for _ in 0..(3 + ((RandomField::new(0).get((cellar_sea_croc_pos).with_z(base))) % 5)) {
painter.spawn(
EntityInfo::at(cellar_sea_croc_pos.as_())
.with_asset_expect("common.entity.wild.aggressive.sea_crocodile", &mut rng),
)
painter.spawn(EntityInfo::at(cellar_sea_croc_pos.as_()).with_asset_expect(
"common.entity.wild.aggressive.sea_crocodile",
&mut rng,
None,
))
}
// clear chapel main room
painter
@ -681,20 +682,29 @@ impl Structure for SeaChapel {
// organ on chapel top floor organ podium
let first_floor_organ_pos = center_o2.with_z(base - (diameter / 8) + diameter - 4);
painter.spawn(
EntityInfo::at(first_floor_organ_pos.as_())
.with_asset_expect("common.entity.dungeon.sea_chapel.organ", &mut rng),
EntityInfo::at(first_floor_organ_pos.as_()).with_asset_expect(
"common.entity.dungeon.sea_chapel.organ",
&mut rng,
None,
),
);
// sea clerics, bishop on top floor
let first_floor_spawn_pos = (center_o2 - 2).with_z(base - (diameter / 8) + diameter - 4);
for _ in 0..(2 + ((RandomField::new(0).get((first_floor_spawn_pos).with_z(base))) % 2)) {
painter.spawn(
EntityInfo::at(first_floor_spawn_pos.as_())
.with_asset_expect("common.entity.dungeon.sea_chapel.sea_cleric", &mut rng),
EntityInfo::at(first_floor_spawn_pos.as_()).with_asset_expect(
"common.entity.dungeon.sea_chapel.sea_cleric",
&mut rng,
None,
),
)
}
painter.spawn(
EntityInfo::at(first_floor_spawn_pos.as_())
.with_asset_expect("common.entity.dungeon.sea_chapel.sea_bishop", &mut rng),
EntityInfo::at(first_floor_spawn_pos.as_()).with_asset_expect(
"common.entity.dungeon.sea_chapel.sea_bishop",
&mut rng,
None,
),
);
// chapel main room gold decor ring and floor
painter
@ -739,26 +749,38 @@ impl Structure for SeaChapel {
// organ on chapel main room organ podium
let first_floor_organ_pos = center_o1.with_z(base + 2);
painter.spawn(
EntityInfo::at(first_floor_organ_pos.as_())
.with_asset_expect("common.entity.dungeon.sea_chapel.organ", &mut rng),
EntityInfo::at(first_floor_organ_pos.as_()).with_asset_expect(
"common.entity.dungeon.sea_chapel.organ",
&mut rng,
None,
),
);
// sea clerics on main floor
let main_room_sea_clerics_pos = (center_o1 - 2).with_z(base + 2);
for _ in 0..(3 + ((RandomField::new(0).get((main_room_sea_clerics_pos).with_z(base))) % 3))
{
painter.spawn(
EntityInfo::at(main_room_sea_clerics_pos.as_())
.with_asset_expect("common.entity.dungeon.sea_chapel.sea_cleric", &mut rng),
EntityInfo::at(main_room_sea_clerics_pos.as_()).with_asset_expect(
"common.entity.dungeon.sea_chapel.sea_cleric",
&mut rng,
None,
),
)
}
// coral golem on main floor
painter.spawn(
EntityInfo::at((first_floor_organ_pos + 2).as_())
.with_asset_expect("common.entity.dungeon.sea_chapel.coralgolem", &mut rng),
EntityInfo::at((first_floor_organ_pos + 2).as_()).with_asset_expect(
"common.entity.dungeon.sea_chapel.coralgolem",
&mut rng,
None,
),
);
painter.spawn(
EntityInfo::at((first_floor_organ_pos + 4).as_())
.with_asset_expect("common.entity.dungeon.sea_chapel.sea_bishop", &mut rng),
EntityInfo::at((first_floor_organ_pos + 4).as_()).with_asset_expect(
"common.entity.dungeon.sea_chapel.sea_bishop",
&mut rng,
None,
),
);
// chapel main room glassbarrier to cellar
let center_g = center - diameter / 7;
@ -833,17 +855,19 @@ impl Structure for SeaChapel {
// cardinals room sea clerics
let cr_sea_clerics_pos = (center - (diameter / 5)).with_z(base - (diameter / 4) - 3);
for _ in 0..(2 + ((RandomField::new(0).get((cr_sea_clerics_pos).with_z(base))) % 3)) {
painter.spawn(
EntityInfo::at(cr_sea_clerics_pos.as_())
.with_asset_expect("common.entity.dungeon.sea_chapel.sea_cleric", &mut rng),
)
painter.spawn(EntityInfo::at(cr_sea_clerics_pos.as_()).with_asset_expect(
"common.entity.dungeon.sea_chapel.sea_cleric",
&mut rng,
None,
))
}
// Cardinal
let cr_cardinal_pos = (center - (diameter / 6)).with_z(base - (diameter / 4) - 3);
painter.spawn(
EntityInfo::at(cr_cardinal_pos.as_())
.with_asset_expect("common.entity.dungeon.sea_chapel.cardinal", &mut rng),
);
painter.spawn(EntityInfo::at(cr_cardinal_pos.as_()).with_asset_expect(
"common.entity.dungeon.sea_chapel.cardinal",
&mut rng,
None,
));
// glassbarrier to water basin
painter
.cylinder(Aabb {
@ -1394,6 +1418,7 @@ impl Structure for SeaChapel {
painter.spawn(EntityInfo::at(room_clerics_pos.as_()).with_asset_expect(
"common.entity.dungeon.sea_chapel.sea_cleric",
&mut rng,
None,
));
};
// decor for top rooms
@ -1668,8 +1693,11 @@ impl Structure for SeaChapel {
let underwater_organ_pos =
(center - (diameter / 4)).with_z(base - (3 * diameter) + (diameter / 2) + 1);
painter.spawn(
EntityInfo::at(underwater_organ_pos.as_())
.with_asset_expect("common.entity.dungeon.sea_chapel.organ", &mut rng),
EntityInfo::at(underwater_organ_pos.as_()).with_asset_expect(
"common.entity.dungeon.sea_chapel.organ",
&mut rng,
None,
),
);
// underwater chamber decor ring
painter
@ -1724,10 +1752,11 @@ impl Structure for SeaChapel {
.fill(gold_chain);
// underwater chamber dagon
let cellar_miniboss_pos = (center + 6).with_z(base - (3 * diameter) + (diameter / 2) + 1);
painter.spawn(
EntityInfo::at(cellar_miniboss_pos.as_())
.with_asset_expect("common.entity.dungeon.sea_chapel.dagon", &mut rng),
);
painter.spawn(EntityInfo::at(cellar_miniboss_pos.as_()).with_asset_expect(
"common.entity.dungeon.sea_chapel.dagon",
&mut rng,
None,
));
// underwater chamber floor entry
painter
.cylinder_with_radius(
@ -2764,8 +2793,11 @@ impl Structure for SeaChapel {
0..(2 + ((RandomField::new(0).get((bldg_cellar_sea_croc_pos).with_z(base))) % 2))
{
painter.spawn(
EntityInfo::at(bldg_cellar_sea_croc_pos.as_())
.with_asset_expect("common.entity.wild.aggressive.sea_crocodile", &mut rng),
EntityInfo::at(bldg_cellar_sea_croc_pos.as_()).with_asset_expect(
"common.entity.wild.aggressive.sea_crocodile",
&mut rng,
None,
),
)
}
match bldg_variant {
@ -2821,6 +2853,7 @@ impl Structure for SeaChapel {
EntityInfo::at(bldg_floor_sea_cleric_pos.as_()).with_asset_expect(
"common.entity.dungeon.sea_chapel.sea_cleric",
&mut rng,
None,
),
)
}
@ -2962,6 +2995,7 @@ impl Structure for SeaChapel {
EntityInfo::at(bldg_floor_sea_cleric_pos.as_()).with_asset_expect(
"common.entity.dungeon.sea_chapel.sea_cleric",
&mut rng,
None,
),
)
}
@ -2974,6 +3008,7 @@ impl Structure for SeaChapel {
EntityInfo::at(bldg_floor3_sea_cleric_pos.as_()).with_asset_expect(
"common.entity.dungeon.sea_chapel.sea_cleric",
&mut rng,
None,
),
)
}
@ -3319,6 +3354,7 @@ impl Structure for SeaChapel {
EntityInfo::at(prisoner_pos.as_()).with_asset_expect(
"common.entity.dungeon.sea_chapel.prisoner",
&mut rng,
None,
),
)
}

View File

@ -73,13 +73,19 @@ impl Structure for TrollCave {
// troll
painter.spawn(
EntityInfo::at(center.with_z(base - 15).as_())
.with_asset_expect(troll, &mut thread_rng),
EntityInfo::at(center.with_z(base - 15).as_()).with_asset_expect(
troll,
&mut thread_rng,
None,
),
);
// bat
painter.spawn(
EntityInfo::at((center - 2).with_z(base + 5).as_())
.with_asset_expect("common.entity.wild.aggressive.bat", &mut thread_rng),
EntityInfo::at((center - 2).with_z(base + 5).as_()).with_asset_expect(
"common.entity.wild.aggressive.bat",
&mut thread_rng,
None,
),
)
}
}