From 21f9bcb8e2a9e3544647f9d6d52a6a23a598efa4 Mon Sep 17 00:00:00 2001 From: IsseW Date: Sat, 13 Aug 2022 18:58:10 +0200 Subject: [PATCH] added professions, and loadouts --- rtsim/src/data/npc.rs | 26 ++++++++++++- rtsim/src/gen/mod.rs | 18 ++++++--- rtsim/src/rule/setup.rs | 13 +++++-- server/src/rtsim2/tick.rs | 79 ++++++++++++++++++++++++++++++--------- 4 files changed, 109 insertions(+), 27 deletions(-) diff --git a/rtsim/src/data/npc.rs b/rtsim/src/data/npc.rs index 386a0eb1ff..bc0594d608 100644 --- a/rtsim/src/data/npc.rs +++ b/rtsim/src/data/npc.rs @@ -13,6 +13,15 @@ use common::{ use world::util::RandomPerm; pub use common::rtsim::NpcId; +#[derive(Clone, Serialize, Deserialize)] +pub enum Profession { + Farmer, + Hunter, + Merchant, + Guard, + Adventurer(u32), +} + #[derive(Copy, Clone, Default)] pub enum NpcMode { /// The NPC is unloaded and is being simulated via rtsim. @@ -30,6 +39,9 @@ pub struct Npc { pub seed: u32, pub wpos: Vec3, + pub profession: Option, + pub home: Option, + // Unpersisted state /// (wpos, speed_factor) @@ -50,12 +62,24 @@ impl Npc { Self { seed, wpos, + profession: None, + home: None, target: None, mode: NpcMode::Simulated, } } - pub fn rng(&self, perm: u32) -> impl Rng { RandomPerm::new(self.seed + perm) } + pub fn with_profession(mut self, profession: Profession) -> Self { + self.profession = Some(profession); + self + } + + pub fn with_home(mut self, home: SiteId) -> Self { + self.home = Some(home); + self + } + + pub fn rng(&self, perm: u32) -> impl Rng { RandomPerm::new(self.seed.wrapping_add(perm)) } pub fn get_body(&self) -> comp::Body { let species = *(&comp::humanoid::ALL_SPECIES) diff --git a/rtsim/src/gen/mod.rs b/rtsim/src/gen/mod.rs index c0a6ba4f94..4ecc42ee14 100644 --- a/rtsim/src/gen/mod.rs +++ b/rtsim/src/gen/mod.rs @@ -1,7 +1,7 @@ pub mod site; use crate::data::{ - npc::{Npcs, Npc}, + npc::{Npcs, Npc, Profession}, site::{Sites, Site}, Data, Nature, @@ -41,12 +41,20 @@ impl Data { // Spawn some test entities at the sites for (site_id, site) in this.sites.iter() { - for _ in 0..10 { + let rand_wpos = |rng: &mut SmallRng| { let wpos2d = site.wpos.map(|e| e + rng.gen_range(-10..10)); - let wpos = wpos2d.map(|e| e as f32 + 0.5) - .with_z(world.sim().get_alt_approx(wpos2d).unwrap_or(0.0)); - this.npcs.create(Npc::new(rng.gen(), wpos)); + wpos2d.map(|e| e as f32 + 0.5) + .with_z(world.sim().get_alt_approx(wpos2d).unwrap_or(0.0)) + }; + for _ in 0..10 { + + this.npcs.create(Npc::new(rng.gen(), rand_wpos(&mut rng)).with_home(site_id).with_profession(match rng.gen_range(0..10) { + 0 => Profession::Hunter, + 1..=4 => Profession::Farmer, + _ => Profession::Guard, + })); } + this.npcs.create(Npc::new(rng.gen(), rand_wpos(&mut rng)).with_home(site_id).with_profession(Profession::Merchant)); } this diff --git a/rtsim/src/rule/setup.rs b/rtsim/src/rule/setup.rs index 5bdb00a373..ae35d1f27c 100644 --- a/rtsim/src/rule/setup.rs +++ b/rtsim/src/rule/setup.rs @@ -12,8 +12,9 @@ pub struct Setup; impl Rule for Setup { fn start(rtstate: &mut RtState) -> Result { rtstate.bind::(|ctx| { + let data = &mut *ctx.state.data_mut(); // Delete rtsim sites that don't correspond to a world site - ctx.state.data_mut().sites.retain(|site_id, site| { + data.sites.retain(|site_id, site| { if let Some((world_site_id, _)) = ctx.index.sites .iter() .find(|(_, world_site)| world_site.get_origin() == site.wpos) @@ -26,15 +27,19 @@ impl Rule for Setup { } }); + for npc in data.npcs.values_mut() { + // TODO: Consider what to do with homeless npcs. + npc.home = npc.home.filter(|home| data.sites.contains_key(*home)); + } + // Generate rtsim sites for world sites that don't have a corresponding rtsim site yet for (world_site_id, _) in ctx.index.sites.iter() { - if !ctx.state.data().sites + if !data.sites .values() .any(|site| site.world_site.expect("Rtsim site not assigned to world site") == world_site_id) { warn!("{:?} is new and does not have a corresponding rtsim site. One will now be generated afresh.", world_site_id); - ctx.state - .data_mut() + data .sites .create(Site::generate(world_site_id, ctx.world, ctx.index)); } diff --git a/server/src/rtsim2/tick.rs b/server/src/rtsim2/tick.rs index 55eab5f289..34be4709d8 100644 --- a/server/src/rtsim2/tick.rs +++ b/server/src/rtsim2/tick.rs @@ -3,17 +3,19 @@ use super::*; use crate::sys::terrain::NpcData; use common::{ - comp, + comp::{self, inventory::loadout::Loadout}, event::{EventBus, ServerEvent}, generation::{BodyBuilder, EntityConfig, EntityInfo}, resources::{DeltaTime, Time}, + rtsim::{RtSimController, RtSimEntity}, slowjob::SlowJobPool, - rtsim::{RtSimEntity, RtSimController}, + LoadoutBuilder, }; use common_ecs::{Job, Origin, Phase, System}; -use rtsim2::data::npc::NpcMode; +use rtsim2::data::npc::{NpcMode, Profession}; use specs::{Join, Read, ReadExpect, ReadStorage, WriteExpect, WriteStorage}; use std::{sync::Arc, time::Duration}; +use world::site::settlement::merchant_loadout; #[derive(Default)] pub struct Sys; @@ -37,35 +39,78 @@ impl<'a> System<'a> for Sys { fn run( _job: &mut Job, - (dt, time, mut server_event_bus, mut rtsim, world, index, slow_jobs, positions, rtsim_entities, mut agents): Self::SystemData, + ( + dt, + time, + mut server_event_bus, + mut rtsim, + world, + index, + slow_jobs, + positions, + rtsim_entities, + mut agents, + ): Self::SystemData, ) { let mut emitter = server_event_bus.emitter(); let rtsim = &mut *rtsim; rtsim.state.tick(&world, index.as_index_ref(), dt.0); - if rtsim.last_saved.map_or(true, |ls| ls.elapsed() > Duration::from_secs(60)) { + if rtsim + .last_saved + .map_or(true, |ls| ls.elapsed() > Duration::from_secs(60)) + { rtsim.save(&slow_jobs); } let chunk_states = rtsim.state.resource::(); - for (npc_id, npc) in rtsim.state.data_mut().npcs.iter_mut() { - let chunk = npc.wpos - .xy() - .map2(TerrainChunk::RECT_SIZE, |e, sz| (e as i32).div_euclid(sz as i32)); + let data = &mut *rtsim.state.data_mut(); + for (npc_id, npc) in data.npcs.iter_mut() { + let chunk = npc.wpos.xy().map2(TerrainChunk::RECT_SIZE, |e, sz| { + (e as i32).div_euclid(sz as i32) + }); - // Load the NPC into the world if it's in a loaded chunk and is not already loaded - if matches!(npc.mode, NpcMode::Simulated) && chunk_states.0.get(chunk).map_or(false, |c| c.is_some()) { + // Load the NPC into the world if it's in a loaded chunk and is not already + // loaded + if matches!(npc.mode, NpcMode::Simulated) + && chunk_states.0.get(chunk).map_or(false, |c| c.is_some()) + { npc.mode = NpcMode::Loaded; - let body = npc.get_body(); + let mut loadout_builder = LoadoutBuilder::from_default(&body); + let mut rng = npc.rng(3); + + if let Some(ref profession) = npc.profession { + loadout_builder = match profession { + Profession::Guard => loadout_builder + .with_asset_expect("common.loadout.village.guard", &mut rng), + + Profession::Merchant => { + merchant_loadout( + loadout_builder, + npc.home + .and_then(|home| { + let site = data.sites.get(home)?.world_site?; + index.sites.get(site).trade_information(site.id()) + }).as_ref(), + ) + } + + Profession::Farmer | Profession::Hunter => loadout_builder + .with_asset_expect("common.loadout.village.villager", &mut rng), + + Profession::Adventurer(level) => todo!(), + }; + } + emitter.emit(ServerEvent::CreateNpc { pos: comp::Pos(npc.wpos), stats: comp::Stats::new("Rtsim NPC".to_string()), skill_set: comp::SkillSet::default(), health: None, poise: comp::Poise::new(body), - inventory: comp::Inventory::with_empty(), + inventory: comp::Inventory::with_loadout(loadout_builder.build(), body), body, agent: Some(comp::Agent::from_body(&body)), alignment: comp::Alignment::Wild, @@ -79,10 +124,10 @@ impl<'a> System<'a> for Sys { } // Synchronise rtsim NPC with entity data - for (pos, rtsim_entity, agent) in (&positions, &rtsim_entities, (&mut agents).maybe()).join() { - rtsim - .state - .data_mut() + for (pos, rtsim_entity, agent) in + (&positions, &rtsim_entities, (&mut agents).maybe()).join() + { + data .npcs .get_mut(rtsim_entity.0) .filter(|npc| matches!(npc.mode, NpcMode::Loaded))