mirror of
https://gitlab.com/veloren/veloren.git
synced 2024-08-30 18:12:32 +00:00
added professions, and loadouts
This commit is contained in:
parent
ac0e62df8e
commit
21f9bcb8e2
@ -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)
|
||||||
|
@ -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
|
||||||
|
@ -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));
|
||||||
}
|
}
|
||||||
|
@ -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))
|
||||||
|
Loading…
Reference in New Issue
Block a user