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;
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<f32>,
pub profession: Option<Profession>,
pub home: Option<SiteId>,
// 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)

View File

@ -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

View File

@ -12,8 +12,9 @@ pub struct Setup;
impl Rule for Setup {
fn start(rtstate: &mut RtState) -> Result<Self, RuleError> {
rtstate.bind::<Self, OnSetup>(|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));
}

View File

@ -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<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 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::<ChunkStates>();
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))