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

View File

@ -457,7 +457,7 @@ fn socialize() -> impl Action {
just(|ctx| {
let mut rng = thread_rng();
// 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
.state
.data()

View File

@ -3,14 +3,15 @@
use super::*;
use crate::sys::terrain::NpcData;
use common::{
comp::{self, Body},
comp::{self, Body, Presence, PresenceKind},
event::{EventBus, NpcBuilder, ServerEvent},
generation::{BodyBuilder, EntityConfig, EntityInfo},
resources::{DeltaTime, Time, TimeOfDay},
rtsim::{Actor, RtSimEntity, RtSimVehicle},
slowjob::SlowJobPool,
terrain::CoordinateConversions,
terrain::{CoordinateConversions, TerrainChunkSize},
trade::{Good, SiteInformation},
vol::RectVolSize,
LoadoutBuilder,
};
use common_ecs::{Job, Origin, Phase, System};
@ -187,6 +188,7 @@ impl<'a> System<'a> for Sys {
ReadStorage<'a, RtSimEntity>,
ReadStorage<'a, RtSimVehicle>,
WriteStorage<'a, comp::Agent>,
ReadStorage<'a, Presence>,
);
const NAME: &'static str = "rtsim::tick";
@ -208,16 +210,53 @@ impl<'a> System<'a> for Sys {
rtsim_entities,
rtsim_vehicles,
mut agents,
presences,
): Self::SystemData,
) {
let mut emitter = server_event_bus.emitter();
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
.state
.tick(&world, index.as_index_ref(), *time_of_day, *time, dt.0);
// Perform a save if required
if rtsim
.last_saved
.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 data = &mut *rtsim.state.data_mut();
// Load in vehicles
for (vehicle_id, vehicle) in data.npcs.vehicles.iter_mut() {
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() {
let chunk = npc.wpos.xy().as_::<i32>().wpos_to_cpos();

View File

@ -478,6 +478,11 @@ fn handle_rtsim_actions(bdata: &mut BehaviorData) -> bool {
if let Some(target) = bdata.read_data.lookup_actor(actor) {
let target_pos = bdata.read_data.positions.get(target).map(|pos| pos.0);
if bdata.agent_data.look_toward(
&mut bdata.controller,
&bdata.read_data,
target,
) {
bdata.agent.target = Some(Target::new(
target,
false,
@ -486,16 +491,11 @@ fn handle_rtsim_actions(bdata: &mut BehaviorData) -> bool {
target_pos,
));
if bdata.agent_data.look_toward(
&mut bdata.controller,
&bdata.read_data,
target,
) {
bdata.controller.push_action(ControlAction::Talk);
bdata.controller.push_utterance(UtteranceKind::Greeting);
bdata
.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
bdata
.agent