Made rtsim aware of character locations

This commit is contained in:
Joshua Barretto 2023-04-03 15:16:17 +01:00
parent 7175f7f02f
commit 2d7d172f49
5 changed files with 89 additions and 35 deletions

View File

@ -7,8 +7,10 @@ use common::{
Actor, ChunkResource, FactionId, NpcAction, NpcActivity, Personality, SiteId, VehicleId, Actor, ChunkResource, FactionId, NpcAction, NpcActivity, Personality, SiteId, VehicleId,
}, },
store::Id, store::Id,
terrain::TerrainChunkSize,
vol::RectVolSize, vol::RectVolSize,
}; };
use hashbrown::HashMap;
use rand::prelude::*; use rand::prelude::*;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use slotmap::HopSlotMap; use slotmap::HopSlotMap;
@ -87,22 +89,22 @@ pub struct Npc {
pub personality: Personality, pub personality: Personality,
// Unpersisted state // Unpersisted state
#[serde(skip_serializing, skip_deserializing)] #[serde(skip)]
pub chunk_pos: Option<Vec2<i32>>, pub chunk_pos: Option<Vec2<i32>>,
#[serde(skip_serializing, skip_deserializing)] #[serde(skip)]
pub current_site: Option<SiteId>, pub current_site: Option<SiteId>,
#[serde(skip_serializing, skip_deserializing)] #[serde(skip)]
pub controller: Controller, pub controller: Controller,
/// Whether the NPC is in simulated or loaded mode (when rtsim is run on the /// Whether the NPC is in simulated or loaded mode (when rtsim is run on the
/// server, loaded corresponds to being within a loaded chunk). When in /// server, loaded corresponds to being within a loaded chunk). When in
/// loaded mode, the interactions of the NPC should not be simulated but /// loaded mode, the interactions of the NPC should not be simulated but
/// should instead be derived from the game. /// should instead be derived from the game.
#[serde(skip_serializing, skip_deserializing)] #[serde(skip)]
pub mode: SimulationMode, pub mode: SimulationMode,
#[serde(skip_serializing, skip_deserializing)] #[serde(skip)]
pub brain: Option<Brain>, pub brain: Option<Brain>,
} }
@ -203,13 +205,13 @@ pub struct Vehicle {
pub body: comp::ship::Body, pub body: comp::ship::Body,
#[serde(skip_serializing, skip_deserializing)] #[serde(skip)]
pub chunk_pos: Option<Vec2<i32>>, pub chunk_pos: Option<Vec2<i32>>,
#[serde(skip_serializing, skip_deserializing)] #[serde(skip)]
pub driver: Option<Actor>, pub driver: Option<Actor>,
#[serde(skip_serializing, skip_deserializing)] #[serde(skip)]
// TODO: Find a way to detect riders when the vehicle is loaded // TODO: Find a way to detect riders when the vehicle is loaded
pub riders: Vec<Actor>, pub riders: Vec<Actor>,
@ -217,7 +219,7 @@ pub struct Vehicle {
/// the server, loaded corresponds to being within a loaded chunk). When /// the server, loaded corresponds to being within a loaded chunk). When
/// in loaded mode, the interactions of the Vehicle should not be /// in loaded mode, the interactions of the Vehicle should not be
/// simulated but should instead be derived from the game. /// simulated but should instead be derived from the game.
#[serde(skip_serializing, skip_deserializing)] #[serde(skip)]
pub mode: SimulationMode, pub mode: SimulationMode,
} }
@ -250,7 +252,6 @@ impl Vehicle {
#[derive(Default, Clone, Serialize, Deserialize)] #[derive(Default, Clone, Serialize, Deserialize)]
pub struct GridCell { pub struct GridCell {
pub npcs: Vec<NpcId>, pub npcs: Vec<NpcId>,
pub characters: Vec<common::character::CharacterId>,
pub vehicles: Vec<VehicleId>, pub vehicles: Vec<VehicleId>,
} }
@ -258,8 +259,11 @@ pub struct GridCell {
pub struct Npcs { pub struct Npcs {
pub npcs: HopSlotMap<NpcId, Npc>, pub npcs: HopSlotMap<NpcId, Npc>,
pub vehicles: HopSlotMap<VehicleId, Vehicle>, pub vehicles: HopSlotMap<VehicleId, Vehicle>,
// TODO: This feels like it should be its own rtsim resource
#[serde(skip, default = "construct_npc_grid")] #[serde(skip, default = "construct_npc_grid")]
pub npc_grid: Grid<GridCell>, pub npc_grid: Grid<GridCell>,
#[serde(skip)]
pub character_map: HashMap<Vec2<i32>, Vec<(common::character::CharacterId, Vec3<f32>)>>,
} }
fn construct_npc_grid() -> Grid<GridCell> { Grid::new(Vec2::zero(), Default::default()) } fn construct_npc_grid() -> Grid<GridCell> { Grid::new(Vec2::zero(), Default::default()) }
@ -273,8 +277,11 @@ impl Npcs {
/// Queries nearby npcs, not garantueed to work if radius > 32.0 /// Queries nearby npcs, not garantueed to work if radius > 32.0
pub fn nearby(&self, wpos: Vec2<f32>, radius: f32) -> impl Iterator<Item = Actor> + '_ { pub fn nearby(&self, wpos: Vec2<f32>, radius: f32) -> impl Iterator<Item = Actor> + '_ {
let chunk_pos = let chunk_pos = wpos
wpos.as_::<i32>() / common::terrain::TerrainChunkSize::RECT_SIZE.as_::<i32>(); .as_::<i32>()
.map2(TerrainChunkSize::RECT_SIZE.as_::<i32>(), |e, sz| {
e.div_euclid(sz)
});
let r_sqr = radius * radius; let r_sqr = radius * radius;
LOCALITY LOCALITY
.into_iter() .into_iter()
@ -289,19 +296,24 @@ impl Npcs {
.map_or(false, |npc| npc.wpos.xy().distance_squared(wpos) < r_sqr) .map_or(false, |npc| npc.wpos.xy().distance_squared(wpos) < r_sqr)
}) })
.map(Actor::Npc) .map(Actor::Npc)
.chain(cell.characters
.iter()
.copied()
// TODO: Filter characters by distance too
// .filter(move |npc| {
// self.npcs
// .get(*npc)
// .map_or(false, |npc| npc.wpos.xy().distance_squared(wpos) < r_sqr)
// })
.map(Actor::Character))
}) })
}) })
.flatten() .flatten()
.chain(
self.character_map
.get(&chunk_pos)
.map(|characters| {
characters.iter().filter_map(move |(character, c_wpos)| {
if c_wpos.xy().distance_squared(wpos) < r_sqr {
Some(Actor::Character(*character))
} else {
None
}
})
})
.into_iter()
.flatten(),
)
} }
} }

View File

@ -34,6 +34,7 @@ impl Data {
npcs: Default::default(), npcs: Default::default(),
vehicles: Default::default(), vehicles: Default::default(),
npc_grid: Grid::new(Vec2::zero(), Default::default()), npc_grid: Grid::new(Vec2::zero(), Default::default()),
character_map: Default::default(),
}, },
sites: Sites { sites: Sites {
sites: Default::default(), sites: Default::default(),

View File

@ -457,7 +457,7 @@ fn socialize() -> impl Action {
just(|ctx| { just(|ctx| {
let mut rng = thread_rng(); let mut rng = thread_rng();
// TODO: Bit odd, should wait for a while after greeting // TODO: Bit odd, should wait for a while after greeting
if thread_rng().gen_bool(0.0002) { if thread_rng().gen_bool(0.0003) {
if let Some(other) = ctx if let Some(other) = ctx
.state .state
.data() .data()

View File

@ -3,14 +3,15 @@
use super::*; use super::*;
use crate::sys::terrain::NpcData; use crate::sys::terrain::NpcData;
use common::{ use common::{
comp::{self, Body}, comp::{self, Body, Presence, PresenceKind},
event::{EventBus, NpcBuilder, ServerEvent}, event::{EventBus, NpcBuilder, ServerEvent},
generation::{BodyBuilder, EntityConfig, EntityInfo}, generation::{BodyBuilder, EntityConfig, EntityInfo},
resources::{DeltaTime, Time, TimeOfDay}, resources::{DeltaTime, Time, TimeOfDay},
rtsim::{Actor, RtSimEntity, RtSimVehicle}, rtsim::{Actor, RtSimEntity, RtSimVehicle},
slowjob::SlowJobPool, slowjob::SlowJobPool,
terrain::CoordinateConversions, terrain::{CoordinateConversions, TerrainChunkSize},
trade::{Good, SiteInformation}, trade::{Good, SiteInformation},
vol::RectVolSize,
LoadoutBuilder, LoadoutBuilder,
}; };
use common_ecs::{Job, Origin, Phase, System}; use common_ecs::{Job, Origin, Phase, System};
@ -187,6 +188,7 @@ impl<'a> System<'a> for Sys {
ReadStorage<'a, RtSimEntity>, ReadStorage<'a, RtSimEntity>,
ReadStorage<'a, RtSimVehicle>, ReadStorage<'a, RtSimVehicle>,
WriteStorage<'a, comp::Agent>, WriteStorage<'a, comp::Agent>,
ReadStorage<'a, Presence>,
); );
const NAME: &'static str = "rtsim::tick"; const NAME: &'static str = "rtsim::tick";
@ -208,16 +210,53 @@ impl<'a> System<'a> for Sys {
rtsim_entities, rtsim_entities,
rtsim_vehicles, rtsim_vehicles,
mut agents, mut agents,
presences,
): Self::SystemData, ): 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.data_mut().time_of_day = *time_of_day; // Set up rtsim inputs
{
let mut data = rtsim.state.data_mut();
// Update time of day
data.time_of_day = *time_of_day;
// Update character map (i.e: so that rtsim knows where players are)
// TODO: Other entities too? Or do we now care about that?
data.npcs.character_map.clear();
for (character, wpos) in
(&presences, &positions)
.join()
.filter_map(|(presence, pos)| {
if let PresenceKind::Character(character) = &presence.kind {
Some((character, pos.0))
} else {
None
}
})
{
let chunk_pos = wpos
.xy()
.as_::<i32>()
.map2(TerrainChunkSize::RECT_SIZE.as_::<i32>(), |e, sz| {
e.div_euclid(sz)
});
data.npcs
.character_map
.entry(chunk_pos)
.or_default()
.push((*character, wpos));
}
}
// Tick rtsim
rtsim rtsim
.state .state
.tick(&world, index.as_index_ref(), *time_of_day, *time, dt.0); .tick(&world, index.as_index_ref(), *time_of_day, *time, dt.0);
// Perform a save if required
if rtsim if rtsim
.last_saved .last_saved
.map_or(true, |ls| ls.elapsed() > Duration::from_secs(60)) .map_or(true, |ls| ls.elapsed() > Duration::from_secs(60))
@ -230,6 +269,7 @@ impl<'a> System<'a> for Sys {
let chunk_states = rtsim.state.resource::<ChunkStates>(); let chunk_states = rtsim.state.resource::<ChunkStates>();
let data = &mut *rtsim.state.data_mut(); let data = &mut *rtsim.state.data_mut();
// Load in vehicles
for (vehicle_id, vehicle) in data.npcs.vehicles.iter_mut() { for (vehicle_id, vehicle) in data.npcs.vehicles.iter_mut() {
let chunk = vehicle.wpos.xy().as_::<i32>().wpos_to_cpos(); let chunk = vehicle.wpos.xy().as_::<i32>().wpos_to_cpos();
@ -296,6 +336,7 @@ impl<'a> System<'a> for Sys {
} }
} }
// Load in NPCs
for (npc_id, npc) in data.npcs.npcs.iter_mut() { for (npc_id, npc) in data.npcs.npcs.iter_mut() {
let chunk = npc.wpos.xy().as_::<i32>().wpos_to_cpos(); let chunk = npc.wpos.xy().as_::<i32>().wpos_to_cpos();

View File

@ -478,24 +478,24 @@ fn handle_rtsim_actions(bdata: &mut BehaviorData) -> bool {
if let Some(target) = bdata.read_data.lookup_actor(actor) { if let Some(target) = bdata.read_data.lookup_actor(actor) {
let target_pos = bdata.read_data.positions.get(target).map(|pos| pos.0); let target_pos = bdata.read_data.positions.get(target).map(|pos| pos.0);
bdata.agent.target = Some(Target::new(
target,
false,
bdata.read_data.time.0,
false,
target_pos,
));
if bdata.agent_data.look_toward( if bdata.agent_data.look_toward(
&mut bdata.controller, &mut bdata.controller,
&bdata.read_data, &bdata.read_data,
target, target,
) { ) {
bdata.agent.target = Some(Target::new(
target,
false,
bdata.read_data.time.0,
false,
target_pos,
));
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
.agent_data .agent_data
.chat_npc("npc-speech-villager", &mut bdata.event_emitter); .chat_npc("npc-speech-villager_open", &mut bdata.event_emitter);
// Start a timer so that they eventually stop interacting // Start a timer so that they eventually stop interacting
bdata bdata
.agent .agent