added professions, and loadouts

This commit is contained in:
IsseW 2022-08-13 18:58:10 +02:00 committed by Joshua Barretto
parent ac0e62df8e
commit 21f9bcb8e2
4 changed files with 109 additions and 27 deletions

View File

@ -13,6 +13,15 @@ use common::{
use world::util::RandomPerm; use world::util::RandomPerm;
pub use common::rtsim::NpcId; pub use common::rtsim::NpcId;
#[derive(Clone, Serialize, Deserialize)]
pub enum Profession {
Farmer,
Hunter,
Merchant,
Guard,
Adventurer(u32),
}
#[derive(Copy, Clone, Default)] #[derive(Copy, Clone, Default)]
pub enum NpcMode { pub enum NpcMode {
/// The NPC is unloaded and is being simulated via rtsim. /// The NPC is unloaded and is being simulated via rtsim.
@ -30,6 +39,9 @@ pub struct Npc {
pub seed: u32, pub seed: u32,
pub wpos: Vec3<f32>, pub wpos: Vec3<f32>,
pub profession: Option<Profession>,
pub home: Option<SiteId>,
// Unpersisted state // Unpersisted state
/// (wpos, speed_factor) /// (wpos, speed_factor)
@ -50,12 +62,24 @@ impl Npc {
Self { Self {
seed, seed,
wpos, wpos,
profession: None,
home: None,
target: None, target: None,
mode: NpcMode::Simulated, 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 { pub fn get_body(&self) -> comp::Body {
let species = *(&comp::humanoid::ALL_SPECIES) let species = *(&comp::humanoid::ALL_SPECIES)

View File

@ -1,7 +1,7 @@
pub mod site; pub mod site;
use crate::data::{ use crate::data::{
npc::{Npcs, Npc}, npc::{Npcs, Npc, Profession},
site::{Sites, Site}, site::{Sites, Site},
Data, Data,
Nature, Nature,
@ -41,12 +41,20 @@ impl Data {
// Spawn some test entities at the sites // Spawn some test entities at the sites
for (site_id, site) in this.sites.iter() { 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 wpos2d = site.wpos.map(|e| e + rng.gen_range(-10..10));
let wpos = wpos2d.map(|e| e as f32 + 0.5) wpos2d.map(|e| e as f32 + 0.5)
.with_z(world.sim().get_alt_approx(wpos2d).unwrap_or(0.0)); .with_z(world.sim().get_alt_approx(wpos2d).unwrap_or(0.0))
this.npcs.create(Npc::new(rng.gen(), wpos)); };
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 this

View File

@ -12,8 +12,9 @@ pub struct Setup;
impl Rule for Setup { impl Rule for Setup {
fn start(rtstate: &mut RtState) -> Result<Self, RuleError> { fn start(rtstate: &mut RtState) -> Result<Self, RuleError> {
rtstate.bind::<Self, OnSetup>(|ctx| { rtstate.bind::<Self, OnSetup>(|ctx| {
let data = &mut *ctx.state.data_mut();
// Delete rtsim sites that don't correspond to a world site // 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 if let Some((world_site_id, _)) = ctx.index.sites
.iter() .iter()
.find(|(_, world_site)| world_site.get_origin() == site.wpos) .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 // Generate rtsim sites for world sites that don't have a corresponding rtsim site yet
for (world_site_id, _) in ctx.index.sites.iter() { for (world_site_id, _) in ctx.index.sites.iter() {
if !ctx.state.data().sites if !data.sites
.values() .values()
.any(|site| site.world_site.expect("Rtsim site not assigned to world site") == world_site_id) .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); warn!("{:?} is new and does not have a corresponding rtsim site. One will now be generated afresh.", world_site_id);
ctx.state data
.data_mut()
.sites .sites
.create(Site::generate(world_site_id, ctx.world, ctx.index)); .create(Site::generate(world_site_id, ctx.world, ctx.index));
} }

View File

@ -3,17 +3,19 @@
use super::*; use super::*;
use crate::sys::terrain::NpcData; use crate::sys::terrain::NpcData;
use common::{ use common::{
comp, comp::{self, inventory::loadout::Loadout},
event::{EventBus, ServerEvent}, event::{EventBus, ServerEvent},
generation::{BodyBuilder, EntityConfig, EntityInfo}, generation::{BodyBuilder, EntityConfig, EntityInfo},
resources::{DeltaTime, Time}, resources::{DeltaTime, Time},
rtsim::{RtSimController, RtSimEntity},
slowjob::SlowJobPool, slowjob::SlowJobPool,
rtsim::{RtSimEntity, RtSimController}, LoadoutBuilder,
}; };
use common_ecs::{Job, Origin, Phase, System}; 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 specs::{Join, Read, ReadExpect, ReadStorage, WriteExpect, WriteStorage};
use std::{sync::Arc, time::Duration}; use std::{sync::Arc, time::Duration};
use world::site::settlement::merchant_loadout;
#[derive(Default)] #[derive(Default)]
pub struct Sys; pub struct Sys;
@ -37,35 +39,78 @@ impl<'a> System<'a> for Sys {
fn run( fn run(
_job: &mut Job<Self>, _job: &mut Job<Self>,
(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 mut emitter = server_event_bus.emitter();
let rtsim = &mut *rtsim; let rtsim = &mut *rtsim;
rtsim.state.tick(&world, index.as_index_ref(), dt.0); 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); rtsim.save(&slow_jobs);
} }
let chunk_states = rtsim.state.resource::<ChunkStates>(); let chunk_states = rtsim.state.resource::<ChunkStates>();
for (npc_id, npc) in rtsim.state.data_mut().npcs.iter_mut() { let data = &mut *rtsim.state.data_mut();
let chunk = npc.wpos for (npc_id, npc) in data.npcs.iter_mut() {
.xy() let chunk = npc.wpos.xy().map2(TerrainChunk::RECT_SIZE, |e, sz| {
.map2(TerrainChunk::RECT_SIZE, |e, sz| (e as i32).div_euclid(sz as i32)); (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 // Load the NPC into the world if it's in a loaded chunk and is not already
if matches!(npc.mode, NpcMode::Simulated) && chunk_states.0.get(chunk).map_or(false, |c| c.is_some()) { // loaded
if matches!(npc.mode, NpcMode::Simulated)
&& chunk_states.0.get(chunk).map_or(false, |c| c.is_some())
{
npc.mode = NpcMode::Loaded; npc.mode = NpcMode::Loaded;
let body = npc.get_body(); 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 { emitter.emit(ServerEvent::CreateNpc {
pos: comp::Pos(npc.wpos), pos: comp::Pos(npc.wpos),
stats: comp::Stats::new("Rtsim NPC".to_string()), stats: comp::Stats::new("Rtsim NPC".to_string()),
skill_set: comp::SkillSet::default(), skill_set: comp::SkillSet::default(),
health: None, health: None,
poise: comp::Poise::new(body), poise: comp::Poise::new(body),
inventory: comp::Inventory::with_empty(), inventory: comp::Inventory::with_loadout(loadout_builder.build(), body),
body, body,
agent: Some(comp::Agent::from_body(&body)), agent: Some(comp::Agent::from_body(&body)),
alignment: comp::Alignment::Wild, alignment: comp::Alignment::Wild,
@ -79,10 +124,10 @@ impl<'a> System<'a> for Sys {
} }
// Synchronise rtsim NPC with entity data // Synchronise rtsim NPC with entity data
for (pos, rtsim_entity, agent) in (&positions, &rtsim_entities, (&mut agents).maybe()).join() { for (pos, rtsim_entity, agent) in
rtsim (&positions, &rtsim_entities, (&mut agents).maybe()).join()
.state {
.data_mut() data
.npcs .npcs
.get_mut(rtsim_entity.0) .get_mut(rtsim_entity.0)
.filter(|npc| matches!(npc.mode, NpcMode::Loaded)) .filter(|npc| matches!(npc.mode, NpcMode::Loaded))