mirror of
https://gitlab.com/veloren/veloren.git
synced 2024-08-30 18:12:32 +00:00
Cleaned up rtsim rules
This commit is contained in:
parent
3e0f5295c0
commit
5614eaa7a5
@ -296,6 +296,11 @@ pub enum ServerChatCommand {
|
|||||||
Respawn,
|
Respawn,
|
||||||
RevokeBuild,
|
RevokeBuild,
|
||||||
RevokeBuildAll,
|
RevokeBuildAll,
|
||||||
|
RtsimChunk,
|
||||||
|
RtsimInfo,
|
||||||
|
RtsimNpc,
|
||||||
|
RtsimPurge,
|
||||||
|
RtsimTp,
|
||||||
Safezone,
|
Safezone,
|
||||||
Say,
|
Say,
|
||||||
Scale,
|
Scale,
|
||||||
@ -310,11 +315,6 @@ pub enum ServerChatCommand {
|
|||||||
Tell,
|
Tell,
|
||||||
Time,
|
Time,
|
||||||
Tp,
|
Tp,
|
||||||
RtsimChunk,
|
|
||||||
RtsimInfo,
|
|
||||||
RtsimNpc,
|
|
||||||
RtsimPurge,
|
|
||||||
RtsimTp,
|
|
||||||
Unban,
|
Unban,
|
||||||
Version,
|
Version,
|
||||||
Waypoint,
|
Waypoint,
|
||||||
|
@ -60,8 +60,9 @@ impl RtState {
|
|||||||
|
|
||||||
fn start_default_rules(&mut self) {
|
fn start_default_rules(&mut self) {
|
||||||
info!("Starting default rtsim rules...");
|
info!("Starting default rtsim rules...");
|
||||||
self.start_rule::<rule::setup::Setup>();
|
self.start_rule::<rule::migrate::Migrate>();
|
||||||
self.start_rule::<rule::replenish_resources::ReplenishResources>();
|
self.start_rule::<rule::replenish_resources::ReplenishResources>();
|
||||||
|
self.start_rule::<rule::sync_npcs::SyncNpcs>();
|
||||||
self.start_rule::<rule::simulate_npcs::SimulateNpcs>();
|
self.start_rule::<rule::simulate_npcs::SimulateNpcs>();
|
||||||
self.start_rule::<rule::npc_ai::NpcAi>();
|
self.start_rule::<rule::npc_ai::NpcAi>();
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,8 @@
|
|||||||
|
pub mod migrate;
|
||||||
pub mod npc_ai;
|
pub mod npc_ai;
|
||||||
pub mod replenish_resources;
|
pub mod replenish_resources;
|
||||||
pub mod setup;
|
|
||||||
pub mod simulate_npcs;
|
pub mod simulate_npcs;
|
||||||
|
pub mod sync_npcs;
|
||||||
|
|
||||||
use super::RtState;
|
use super::RtState;
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
|
@ -4,9 +4,9 @@ use tracing::warn;
|
|||||||
/// This rule runs at rtsim startup and broadly acts to perform some primitive
|
/// This rule runs at rtsim startup and broadly acts to perform some primitive
|
||||||
/// migration/sanitisation in order to ensure that the state of rtsim is mostly
|
/// migration/sanitisation in order to ensure that the state of rtsim is mostly
|
||||||
/// sensible.
|
/// sensible.
|
||||||
pub struct Setup;
|
pub struct Migrate;
|
||||||
|
|
||||||
impl Rule for Setup {
|
impl Rule for Migrate {
|
||||||
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();
|
let data = &mut *ctx.state.data_mut();
|
@ -20,7 +20,7 @@ use common::{
|
|||||||
vol::RectVolSize,
|
vol::RectVolSize,
|
||||||
};
|
};
|
||||||
use fxhash::FxHasher64;
|
use fxhash::FxHasher64;
|
||||||
use itertools::Itertools;
|
use itertools::{Either, Itertools};
|
||||||
use rand::prelude::*;
|
use rand::prelude::*;
|
||||||
use rand_chacha::ChaChaRng;
|
use rand_chacha::ChaChaRng;
|
||||||
use rayon::iter::{IntoParallelRefMutIterator, ParallelIterator};
|
use rayon::iter::{IntoParallelRefMutIterator, ParallelIterator};
|
||||||
@ -325,7 +325,7 @@ where
|
|||||||
}
|
}
|
||||||
|
|
||||||
if let Some(path) = path_site(wpos, site_exit, site, ctx.index) {
|
if let Some(path) = path_site(wpos, site_exit, site, ctx.index) {
|
||||||
Some(itertools::Either::Left(
|
Some(Either::Left(
|
||||||
seq(path.into_iter().map(|wpos| goto_2d(wpos, 1.0, 8.0))).then(goto_2d(
|
seq(path.into_iter().map(|wpos| goto_2d(wpos, 1.0, 8.0))).then(goto_2d(
|
||||||
site_exit,
|
site_exit,
|
||||||
speed_factor,
|
speed_factor,
|
||||||
@ -333,14 +333,10 @@ where
|
|||||||
)),
|
)),
|
||||||
))
|
))
|
||||||
} else {
|
} else {
|
||||||
Some(itertools::Either::Right(goto_2d(
|
Some(Either::Right(goto_2d(site_exit, speed_factor, 8.0)))
|
||||||
site_exit,
|
|
||||||
speed_factor,
|
|
||||||
8.0,
|
|
||||||
)))
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
Some(itertools::Either::Right(goto_2d(wpos, speed_factor, 8.0)))
|
Some(Either::Right(goto_2d(wpos, speed_factor, 8.0)))
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@ -410,9 +406,9 @@ fn travel_to_site(tgt_site: SiteId, speed_factor: f32) -> impl Action {
|
|||||||
// let track_len = ctx.world.civs().tracks.get(track_id).path().len();
|
// let track_len = ctx.world.civs().tracks.get(track_id).path().len();
|
||||||
// // Tracks can be traversed backward (i.e: from end to beginning). Account for this.
|
// // Tracks can be traversed backward (i.e: from end to beginning). Account for this.
|
||||||
// seq(if reversed {
|
// seq(if reversed {
|
||||||
// itertools::Either::Left((0..track_len).rev())
|
// Either::Left((0..track_len).rev())
|
||||||
// } else {
|
// } else {
|
||||||
// itertools::Either::Right(0..track_len)
|
// Either::Right(0..track_len)
|
||||||
// }
|
// }
|
||||||
// .enumerate()
|
// .enumerate()
|
||||||
// .map(move |(i, node_idx)| now(move |ctx| {
|
// .map(move |(i, node_idx)| now(move |ctx| {
|
||||||
@ -456,7 +452,7 @@ fn timeout(time: f64) -> impl FnMut(&mut NpcCtx) -> bool + Clone + Send + Sync {
|
|||||||
fn socialize() -> impl Action {
|
fn socialize() -> impl Action {
|
||||||
now(|ctx| {
|
now(|ctx| {
|
||||||
// TODO: Bit odd, should wait for a while after greeting
|
// TODO: Bit odd, should wait for a while after greeting
|
||||||
if ctx.rng.gen_bool(0.0003) && let Some(other) = ctx
|
if ctx.rng.gen_bool(0.004) && let Some(other) = ctx
|
||||||
.state
|
.state
|
||||||
.data()
|
.data()
|
||||||
.npcs
|
.npcs
|
||||||
@ -680,20 +676,18 @@ fn villager(visiting_site: SiteId) -> impl Action {
|
|||||||
})
|
})
|
||||||
{
|
{
|
||||||
// Walk to the plaza...
|
// Walk to the plaza...
|
||||||
travel_to_point(plaza_wpos, 0.5)
|
Either::Left(travel_to_point(plaza_wpos, 0.5)
|
||||||
.debug(|| "walk to plaza")
|
.debug(|| "walk to plaza"))
|
||||||
// ...then wait for some time before moving on
|
|
||||||
.then({
|
|
||||||
let wait_time = ctx.rng.gen_range(30.0..90.0);
|
|
||||||
socialize().repeat().stop_if(timeout(wait_time))
|
|
||||||
.debug(|| "wait at plaza")
|
|
||||||
})
|
|
||||||
.map(|_| ())
|
|
||||||
.boxed()
|
|
||||||
} else {
|
} else {
|
||||||
// No plazas? :(
|
// No plazas? :(
|
||||||
finish().boxed()
|
Either::Right(finish())
|
||||||
}
|
}
|
||||||
|
// ...then socialize for some time before moving on
|
||||||
|
.then(socialize()
|
||||||
|
.repeat()
|
||||||
|
.stop_if(timeout(ctx.rng.gen_range(30.0..90.0)))
|
||||||
|
.debug(|| "wait at plaza"))
|
||||||
|
.map(|_| ())
|
||||||
}))
|
}))
|
||||||
})
|
})
|
||||||
.debug(move || format!("villager at site {:?}", visiting_site))
|
.debug(move || format!("villager at site {:?}", visiting_site))
|
||||||
|
@ -1,16 +1,16 @@
|
|||||||
use crate::{
|
use crate::{
|
||||||
data::{npc::SimulationMode, Npc},
|
data::{npc::SimulationMode, Npc},
|
||||||
event::{OnDeath, OnSetup, OnTick},
|
event::{EventCtx, OnDeath, OnSetup, OnTick},
|
||||||
RtState, Rule, RuleError,
|
RtState, Rule, RuleError,
|
||||||
};
|
};
|
||||||
use common::{
|
use common::{
|
||||||
comp::{self, Body},
|
comp::{self, Body},
|
||||||
grid::Grid,
|
|
||||||
rtsim::{Actor, NpcAction, NpcActivity, Personality},
|
rtsim::{Actor, NpcAction, NpcActivity, Personality},
|
||||||
terrain::TerrainChunkSize,
|
terrain::TerrainChunkSize,
|
||||||
vol::RectVolSize,
|
vol::RectVolSize,
|
||||||
};
|
};
|
||||||
use rand::{rngs::ThreadRng, seq::SliceRandom, Rng};
|
use rand::prelude::*;
|
||||||
|
use rand_chacha::ChaChaRng;
|
||||||
use tracing::warn;
|
use tracing::warn;
|
||||||
use world::site::SiteKind;
|
use world::site::SiteKind;
|
||||||
|
|
||||||
@ -18,10 +18,18 @@ pub struct SimulateNpcs;
|
|||||||
|
|
||||||
impl Rule for SimulateNpcs {
|
impl Rule for SimulateNpcs {
|
||||||
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>(on_setup);
|
||||||
let data = &mut *ctx.state.data_mut();
|
rtstate.bind::<Self, OnDeath>(on_death);
|
||||||
data.npcs.npc_grid = Grid::new(ctx.world.sim().get_size().as_(), Default::default());
|
rtstate.bind::<Self, OnTick>(on_tick);
|
||||||
|
|
||||||
|
Ok(Self)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn on_setup(ctx: EventCtx<SimulateNpcs, OnSetup>) {
|
||||||
|
let data = &mut *ctx.state.data_mut();
|
||||||
|
|
||||||
|
// Add riders to vehicles
|
||||||
for (npc_id, npc) in data.npcs.npcs.iter() {
|
for (npc_id, npc) in data.npcs.npcs.iter() {
|
||||||
if let Some(ride) = &npc.riding {
|
if let Some(ride) = &npc.riding {
|
||||||
if let Some(vehicle) = data.npcs.vehicles.get_mut(ride.vehicle) {
|
if let Some(vehicle) = data.npcs.vehicles.get_mut(ride.vehicle) {
|
||||||
@ -32,23 +40,18 @@ impl Rule for SimulateNpcs {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(home) = npc.home.and_then(|home| data.sites.get_mut(home)) {
|
|
||||||
home.population.insert(npc_id);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
|
||||||
|
|
||||||
rtstate.bind::<Self, OnDeath>(|ctx| {
|
fn on_death(ctx: EventCtx<SimulateNpcs, OnDeath>) {
|
||||||
let data = &mut *ctx.state.data_mut();
|
let data = &mut *ctx.state.data_mut();
|
||||||
let npc_id = ctx.event.npc_id;
|
let npc_id = ctx.event.npc_id;
|
||||||
let Some(npc) = data.npcs.get(npc_id) else {
|
let Some(npc) = data.npcs.get(npc_id) else {
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
if let Some(home) = npc.home.and_then(|home| data.sites.get_mut(home)) {
|
let mut rng = ChaChaRng::from_seed(thread_rng().gen::<[u8; 32]>());
|
||||||
home.population.remove(&npc_id);
|
|
||||||
}
|
// Respawn dead NPCs
|
||||||
let mut rng = rand::thread_rng();
|
|
||||||
match npc.body {
|
match npc.body {
|
||||||
Body::Humanoid(_) => {
|
Body::Humanoid(_) => {
|
||||||
if let Some((site_id, site)) = data
|
if let Some((site_id, site)) = data
|
||||||
@ -63,13 +66,13 @@ impl Rule for SimulateNpcs {
|
|||||||
})
|
})
|
||||||
.min_by_key(|(_, site)| site.population.len())
|
.min_by_key(|(_, site)| site.population.len())
|
||||||
{
|
{
|
||||||
let rand_wpos = |rng: &mut ThreadRng| {
|
let rand_wpos = |rng: &mut ChaChaRng| {
|
||||||
let wpos2d = site.wpos.map(|e| e + rng.gen_range(-10..10));
|
let wpos2d = site.wpos.map(|e| e + rng.gen_range(-10..10));
|
||||||
wpos2d
|
wpos2d
|
||||||
.map(|e| e as f32 + 0.5)
|
.map(|e| e as f32 + 0.5)
|
||||||
.with_z(ctx.world.sim().get_alt_approx(wpos2d).unwrap_or(0.0))
|
.with_z(ctx.world.sim().get_alt_approx(wpos2d).unwrap_or(0.0))
|
||||||
};
|
};
|
||||||
let random_humanoid = |rng: &mut ThreadRng| {
|
let random_humanoid = |rng: &mut ChaChaRng| {
|
||||||
let species = comp::humanoid::ALL_SPECIES.choose(&mut *rng).unwrap();
|
let species = comp::humanoid::ALL_SPECIES.choose(&mut *rng).unwrap();
|
||||||
Body::Humanoid(comp::humanoid::Body::random_with(rng, species))
|
Body::Humanoid(comp::humanoid::Body::random_with(rng, species))
|
||||||
};
|
};
|
||||||
@ -96,7 +99,7 @@ impl Rule for SimulateNpcs {
|
|||||||
})
|
})
|
||||||
.min_by_key(|(_, site)| site.population.len())
|
.min_by_key(|(_, site)| site.population.len())
|
||||||
{
|
{
|
||||||
let rand_wpos = |rng: &mut ThreadRng| {
|
let rand_wpos = |rng: &mut ChaChaRng| {
|
||||||
let wpos2d = site.wpos.map(|e| e + rng.gen_range(-10..10));
|
let wpos2d = site.wpos.map(|e| e + rng.gen_range(-10..10));
|
||||||
wpos2d
|
wpos2d
|
||||||
.map(|e| e as f32 + 0.5)
|
.map(|e| e as f32 + 0.5)
|
||||||
@ -125,77 +128,29 @@ impl Rule for SimulateNpcs {
|
|||||||
},
|
},
|
||||||
_ => unimplemented!(),
|
_ => unimplemented!(),
|
||||||
}
|
}
|
||||||
});
|
}
|
||||||
|
|
||||||
rtstate.bind::<Self, OnTick>(|ctx| {
|
fn on_tick(ctx: EventCtx<SimulateNpcs, OnTick>) {
|
||||||
let data = &mut *ctx.state.data_mut();
|
let data = &mut *ctx.state.data_mut();
|
||||||
for (vehicle_id, vehicle) in data.npcs.vehicles.iter_mut() {
|
for npc in data
|
||||||
let chunk_pos =
|
.npcs
|
||||||
vehicle.wpos.xy().as_::<i32>() / TerrainChunkSize::RECT_SIZE.as_::<i32>();
|
.npcs
|
||||||
if vehicle.chunk_pos != Some(chunk_pos) {
|
.values_mut()
|
||||||
if let Some(cell) = vehicle
|
.filter(|npc| matches!(npc.mode, SimulationMode::Simulated))
|
||||||
.chunk_pos
|
|
||||||
.and_then(|chunk_pos| data.npcs.npc_grid.get_mut(chunk_pos))
|
|
||||||
{
|
{
|
||||||
if let Some(index) = cell.vehicles.iter().position(|id| *id == vehicle_id) {
|
|
||||||
cell.vehicles.swap_remove(index);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
vehicle.chunk_pos = Some(chunk_pos);
|
|
||||||
if let Some(cell) = data.npcs.npc_grid.get_mut(chunk_pos) {
|
|
||||||
cell.vehicles.push(vehicle_id);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for (npc_id, npc) in data.npcs.npcs.iter_mut() {
|
|
||||||
// Update the NPC's current site, if any
|
|
||||||
npc.current_site = ctx
|
|
||||||
.world
|
|
||||||
.sim()
|
|
||||||
.get(npc.wpos.xy().as_::<i32>() / TerrainChunkSize::RECT_SIZE.as_())
|
|
||||||
.and_then(|chunk| {
|
|
||||||
chunk
|
|
||||||
.sites
|
|
||||||
.iter()
|
|
||||||
.find_map(|site| data.sites.world_site_map.get(site).copied())
|
|
||||||
});
|
|
||||||
|
|
||||||
let chunk_pos =
|
|
||||||
npc.wpos.xy().as_::<i32>() / TerrainChunkSize::RECT_SIZE.as_::<i32>();
|
|
||||||
if npc.chunk_pos != Some(chunk_pos) {
|
|
||||||
if let Some(cell) = npc
|
|
||||||
.chunk_pos
|
|
||||||
.and_then(|chunk_pos| data.npcs.npc_grid.get_mut(chunk_pos))
|
|
||||||
{
|
|
||||||
if let Some(index) = cell.npcs.iter().position(|id| *id == npc_id) {
|
|
||||||
cell.npcs.swap_remove(index);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
npc.chunk_pos = Some(chunk_pos);
|
|
||||||
if let Some(cell) = data.npcs.npc_grid.get_mut(chunk_pos) {
|
|
||||||
cell.npcs.push(npc_id);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Simulate the NPC's movement and interactions
|
|
||||||
if matches!(npc.mode, SimulationMode::Simulated) {
|
|
||||||
// Simulate NPC movement when riding
|
// Simulate NPC movement when riding
|
||||||
if let Some(riding) = &npc.riding {
|
if let Some(riding) = &npc.riding {
|
||||||
if let Some(vehicle) = data.npcs.vehicles.get_mut(riding.vehicle) {
|
if let Some(vehicle) = data.npcs.vehicles.get_mut(riding.vehicle) {
|
||||||
match npc.controller.activity {
|
match npc.controller.activity {
|
||||||
// If steering, the NPC controls the vehicle's motion
|
// If steering, the NPC controls the vehicle's motion
|
||||||
Some(NpcActivity::Goto(target, speed_factor))
|
Some(NpcActivity::Goto(target, speed_factor)) if riding.steering => {
|
||||||
if riding.steering =>
|
|
||||||
{
|
|
||||||
let diff = target.xy() - vehicle.wpos.xy();
|
let diff = target.xy() - vehicle.wpos.xy();
|
||||||
let dist2 = diff.magnitude_squared();
|
let dist2 = diff.magnitude_squared();
|
||||||
|
|
||||||
if dist2 > 0.5f32.powi(2) {
|
if dist2 > 0.5f32.powi(2) {
|
||||||
let mut wpos = vehicle.wpos
|
let mut wpos = vehicle.wpos
|
||||||
+ (diff
|
+ (diff
|
||||||
* (vehicle.get_speed()
|
* (vehicle.get_speed() * speed_factor * ctx.event.dt
|
||||||
* speed_factor
|
|
||||||
* ctx.event.dt
|
|
||||||
/ dist2.sqrt())
|
/ dist2.sqrt())
|
||||||
.min(1.0))
|
.min(1.0))
|
||||||
.with_z(0.0);
|
.with_z(0.0);
|
||||||
@ -205,8 +160,11 @@ impl Rule for SimulateNpcs {
|
|||||||
| common::comp::ship::Body::AirBalloon => true,
|
| common::comp::ship::Body::AirBalloon => true,
|
||||||
common::comp::ship::Body::SailBoat
|
common::comp::ship::Body::SailBoat
|
||||||
| common::comp::ship::Body::Galleon => {
|
| common::comp::ship::Body::Galleon => {
|
||||||
let chunk_pos = wpos.xy().as_::<i32>()
|
let chunk_pos =
|
||||||
/ TerrainChunkSize::RECT_SIZE.as_::<i32>();
|
wpos.xy().as_::<i32>().map2(
|
||||||
|
TerrainChunkSize::RECT_SIZE.as_::<i32>(),
|
||||||
|
|e, sz| e.div_euclid(sz),
|
||||||
|
);
|
||||||
ctx.world
|
ctx.world
|
||||||
.sim()
|
.sim()
|
||||||
.get(chunk_pos)
|
.get(chunk_pos)
|
||||||
@ -269,19 +227,13 @@ impl Rule for SimulateNpcs {
|
|||||||
|
|
||||||
if dist2 > 0.5f32.powi(2) {
|
if dist2 > 0.5f32.powi(2) {
|
||||||
npc.wpos += (diff
|
npc.wpos += (diff
|
||||||
* (npc.body.max_speed_approx()
|
* (npc.body.max_speed_approx() * speed_factor * ctx.event.dt
|
||||||
* speed_factor
|
|
||||||
* ctx.event.dt
|
|
||||||
/ dist2.sqrt())
|
/ dist2.sqrt())
|
||||||
.min(1.0))
|
.min(1.0))
|
||||||
.with_z(0.0);
|
.with_z(0.0);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
Some(
|
Some(NpcActivity::Gather(_) | NpcActivity::HuntAnimals | NpcActivity::Dance) => {
|
||||||
NpcActivity::Gather(_)
|
|
||||||
| NpcActivity::HuntAnimals
|
|
||||||
| NpcActivity::Dance,
|
|
||||||
) => {
|
|
||||||
// TODO: Maybe they should walk around randomly
|
// TODO: Maybe they should walk around randomly
|
||||||
// when gathering resources?
|
// when gathering resources?
|
||||||
},
|
},
|
||||||
@ -292,7 +244,8 @@ impl Rule for SimulateNpcs {
|
|||||||
// Consume NPC actions
|
// Consume NPC actions
|
||||||
for action in std::mem::take(&mut npc.controller.actions) {
|
for action in std::mem::take(&mut npc.controller.actions) {
|
||||||
match action {
|
match action {
|
||||||
NpcAction::Greet(_) | NpcAction::Say(_) => {}, // Currently, just swallow interactions
|
NpcAction::Greet(_) | NpcAction::Say(_) => {}, /* Currently, just swallow
|
||||||
|
* interactions */
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -305,8 +258,3 @@ impl Rule for SimulateNpcs {
|
|||||||
+ npc.body.flying_height();
|
+ npc.body.flying_height();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
|
||||||
|
|
||||||
Ok(Self)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
112
rtsim/src/rule/sync_npcs.rs
Normal file
112
rtsim/src/rule/sync_npcs.rs
Normal file
@ -0,0 +1,112 @@
|
|||||||
|
use crate::{
|
||||||
|
event::{EventCtx, OnDeath, OnSetup, OnTick},
|
||||||
|
RtState, Rule, RuleError,
|
||||||
|
};
|
||||||
|
use common::{grid::Grid, terrain::TerrainChunkSize, vol::RectVolSize};
|
||||||
|
|
||||||
|
pub struct SyncNpcs;
|
||||||
|
|
||||||
|
impl Rule for SyncNpcs {
|
||||||
|
fn start(rtstate: &mut RtState) -> Result<Self, RuleError> {
|
||||||
|
rtstate.bind::<Self, OnSetup>(on_setup);
|
||||||
|
rtstate.bind::<Self, OnDeath>(on_death);
|
||||||
|
rtstate.bind::<Self, OnTick>(on_tick);
|
||||||
|
|
||||||
|
Ok(Self)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn on_setup(ctx: EventCtx<SyncNpcs, OnSetup>) {
|
||||||
|
let data = &mut *ctx.state.data_mut();
|
||||||
|
|
||||||
|
// Create NPC grid
|
||||||
|
data.npcs.npc_grid = Grid::new(ctx.world.sim().get_size().as_(), Default::default());
|
||||||
|
|
||||||
|
// Add NPCs to home population (TODO: Do this on entity creation?)
|
||||||
|
for (npc_id, npc) in data.npcs.npcs.iter() {
|
||||||
|
if let Some(home) = npc.home.and_then(|home| data.sites.get_mut(home)) {
|
||||||
|
home.population.insert(npc_id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn on_death(ctx: EventCtx<SyncNpcs, OnDeath>) {
|
||||||
|
let data = &mut *ctx.state.data_mut();
|
||||||
|
|
||||||
|
println!("NPC DIED!");
|
||||||
|
|
||||||
|
// Remove NPC from home population
|
||||||
|
if let Some(home) = data
|
||||||
|
.npcs
|
||||||
|
.get(ctx.event.npc_id)
|
||||||
|
.and_then(|npc| npc.home)
|
||||||
|
.and_then(|home| data.sites.get_mut(home))
|
||||||
|
{
|
||||||
|
home.population.remove(&ctx.event.npc_id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn on_tick(ctx: EventCtx<SyncNpcs, OnTick>) {
|
||||||
|
let data = &mut *ctx.state.data_mut();
|
||||||
|
// Update vehicle grid cells
|
||||||
|
for (vehicle_id, vehicle) in data.npcs.vehicles.iter_mut() {
|
||||||
|
let chunk_pos = vehicle.wpos.xy().as_::<i32>() / TerrainChunkSize::RECT_SIZE.as_::<i32>();
|
||||||
|
if vehicle.chunk_pos != Some(chunk_pos) {
|
||||||
|
if let Some(cell) = vehicle
|
||||||
|
.chunk_pos
|
||||||
|
.and_then(|chunk_pos| data.npcs.npc_grid.get_mut(chunk_pos))
|
||||||
|
{
|
||||||
|
if let Some(index) = cell.vehicles.iter().position(|id| *id == vehicle_id) {
|
||||||
|
cell.vehicles.swap_remove(index);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
vehicle.chunk_pos = Some(chunk_pos);
|
||||||
|
if let Some(cell) = data.npcs.npc_grid.get_mut(chunk_pos) {
|
||||||
|
cell.vehicles.push(vehicle_id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (npc_id, npc) in data.npcs.npcs.iter_mut() {
|
||||||
|
// Update the NPC's current site, if any
|
||||||
|
npc.current_site = ctx
|
||||||
|
.world
|
||||||
|
.sim()
|
||||||
|
.get(
|
||||||
|
npc.wpos
|
||||||
|
.xy()
|
||||||
|
.as_::<i32>()
|
||||||
|
.map2(TerrainChunkSize::RECT_SIZE.as_::<i32>(), |e, sz| {
|
||||||
|
e.div_euclid(sz)
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
.and_then(|chunk| {
|
||||||
|
chunk
|
||||||
|
.sites
|
||||||
|
.iter()
|
||||||
|
.find_map(|site| data.sites.world_site_map.get(site).copied())
|
||||||
|
});
|
||||||
|
|
||||||
|
// Update the NPC's grid cell
|
||||||
|
let chunk_pos = npc
|
||||||
|
.wpos
|
||||||
|
.xy()
|
||||||
|
.as_::<i32>()
|
||||||
|
.map2(TerrainChunkSize::RECT_SIZE.as_::<i32>(), |e, sz| {
|
||||||
|
e.div_euclid(sz)
|
||||||
|
});
|
||||||
|
if npc.chunk_pos != Some(chunk_pos) {
|
||||||
|
if let Some(cell) = npc
|
||||||
|
.chunk_pos
|
||||||
|
.and_then(|chunk_pos| data.npcs.npc_grid.get_mut(chunk_pos))
|
||||||
|
{
|
||||||
|
if let Some(index) = cell.npcs.iter().position(|id| *id == npc_id) {
|
||||||
|
cell.npcs.swap_remove(index);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
npc.chunk_pos = Some(chunk_pos);
|
||||||
|
if let Some(cell) = data.npcs.npc_grid.get_mut(chunk_pos) {
|
||||||
|
cell.npcs.push(npc_id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -224,20 +224,12 @@ impl<'a> System<'a> for Sys {
|
|||||||
data.time_of_day = *time_of_day;
|
data.time_of_day = *time_of_day;
|
||||||
|
|
||||||
// Update character map (i.e: so that rtsim knows where players are)
|
// Update character map (i.e: so that rtsim knows where players are)
|
||||||
// TODO: Other entities too? Or do we now care about that?
|
// TODO: Other entities too like animals? Or do we now care about that?
|
||||||
data.npcs.character_map.clear();
|
data.npcs.character_map.clear();
|
||||||
for (character, wpos) in
|
for (presence, wpos) in (&presences, &positions).join() {
|
||||||
(&presences, &positions)
|
|
||||||
.join()
|
|
||||||
.filter_map(|(presence, pos)| {
|
|
||||||
if let PresenceKind::Character(character) = &presence.kind {
|
if let PresenceKind::Character(character) = &presence.kind {
|
||||||
Some((character, pos.0))
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
})
|
|
||||||
{
|
|
||||||
let chunk_pos = wpos
|
let chunk_pos = wpos
|
||||||
|
.0
|
||||||
.xy()
|
.xy()
|
||||||
.as_::<i32>()
|
.as_::<i32>()
|
||||||
.map2(TerrainChunkSize::RECT_SIZE.as_::<i32>(), |e, sz| {
|
.map2(TerrainChunkSize::RECT_SIZE.as_::<i32>(), |e, sz| {
|
||||||
@ -247,7 +239,8 @@ impl<'a> System<'a> for Sys {
|
|||||||
.character_map
|
.character_map
|
||||||
.entry(chunk_pos)
|
.entry(chunk_pos)
|
||||||
.or_default()
|
.or_default()
|
||||||
.push((*character, wpos));
|
.push((*character, wpos.0));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -477,18 +477,18 @@ fn handle_rtsim_actions(bdata: &mut BehaviorData) -> bool {
|
|||||||
NpcAction::Greet(actor) => {
|
NpcAction::Greet(actor) => {
|
||||||
if bdata.agent.allowed_to_speak()
|
if bdata.agent.allowed_to_speak()
|
||||||
&& let Some(target) = bdata.read_data.lookup_actor(actor)
|
&& let Some(target) = bdata.read_data.lookup_actor(actor)
|
||||||
&& let Some(target_pos) = bdata.read_data.positions.get(target)
|
|
||||||
{
|
{
|
||||||
bdata.agent.target = Some(Target::new(
|
bdata.agent.target = Some(Target::new(
|
||||||
target,
|
target,
|
||||||
false,
|
false,
|
||||||
bdata.read_data.time.0,
|
bdata.read_data.time.0,
|
||||||
false,
|
false,
|
||||||
Some(target_pos.0),
|
bdata.read_data.positions.get(target).map(|p| p.0),
|
||||||
));
|
));
|
||||||
// We're always aware of someone we're talking to
|
// We're always aware of someone we're talking to
|
||||||
bdata.agent.awareness.set_maximally_aware();
|
bdata.agent.awareness.set_maximally_aware();
|
||||||
|
|
||||||
|
bdata.controller.push_action(ControlAction::Stand);
|
||||||
bdata.controller.push_action(ControlAction::Talk);
|
bdata.controller.push_action(ControlAction::Talk);
|
||||||
bdata.controller.push_utterance(UtteranceKind::Greeting);
|
bdata.controller.push_utterance(UtteranceKind::Greeting);
|
||||||
bdata
|
bdata
|
||||||
@ -499,20 +499,15 @@ fn handle_rtsim_actions(bdata: &mut BehaviorData) -> bool {
|
|||||||
.agent
|
.agent
|
||||||
.timer
|
.timer
|
||||||
.start(bdata.read_data.time.0, TimerAction::Interact);
|
.start(bdata.read_data.time.0, TimerAction::Interact);
|
||||||
true
|
|
||||||
} else {
|
|
||||||
false
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
NpcAction::Say(msg) => {
|
NpcAction::Say(msg) => {
|
||||||
bdata.controller.push_utterance(UtteranceKind::Greeting);
|
bdata.controller.push_utterance(UtteranceKind::Greeting);
|
||||||
bdata.agent_data.chat_npc(msg, bdata.event_emitter);
|
bdata.agent_data.chat_npc(msg, bdata.event_emitter);
|
||||||
false
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
false
|
|
||||||
}
|
}
|
||||||
|
false
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Handle timed events, like looking at the player we are talking to
|
/// Handle timed events, like looking at the player we are talking to
|
||||||
|
Loading…
Reference in New Issue
Block a user