mirror of
https://gitlab.com/veloren/veloren.git
synced 2024-08-30 18:12:32 +00:00
Removed old rtsim
This commit is contained in:
parent
5062920b5c
commit
6035234c6e
@ -23,7 +23,7 @@ common-state = { package = "veloren-common-state", path = "../common/state" }
|
|||||||
common-systems = { package = "veloren-common-systems", path = "../common/systems" }
|
common-systems = { package = "veloren-common-systems", path = "../common/systems" }
|
||||||
common-net = { package = "veloren-common-net", path = "../common/net" }
|
common-net = { package = "veloren-common-net", path = "../common/net" }
|
||||||
world = { package = "veloren-world", path = "../world" }
|
world = { package = "veloren-world", path = "../world" }
|
||||||
rtsim2 = { package = "veloren-rtsim", path = "../rtsim" }
|
rtsim = { package = "veloren-rtsim", path = "../rtsim" }
|
||||||
network = { package = "veloren-network", path = "../network", features = ["metrics", "compression", "quic"], default-features = false }
|
network = { package = "veloren-network", path = "../network", features = ["metrics", "compression", "quic"], default-features = false }
|
||||||
|
|
||||||
server-agent = {package = "veloren-server-agent", path = "agent"}
|
server-agent = {package = "veloren-server-agent", path = "agent"}
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
#[cfg(not(feature = "worldgen"))]
|
#[cfg(not(feature = "worldgen"))]
|
||||||
use crate::test_world::{IndexOwned, World};
|
use crate::test_world::{IndexOwned, World};
|
||||||
use crate::{metrics::ChunkGenMetrics, rtsim2::RtSim};
|
use crate::{metrics::ChunkGenMetrics, rtsim::RtSim};
|
||||||
use common::{
|
use common::{
|
||||||
calendar::Calendar, generation::ChunkSupplement, resources::TimeOfDay, slowjob::SlowJobPool,
|
calendar::Calendar, generation::ChunkSupplement, resources::TimeOfDay, slowjob::SlowJobPool,
|
||||||
terrain::TerrainChunk,
|
terrain::TerrainChunk,
|
||||||
|
@ -1193,7 +1193,7 @@ fn handle_rtsim_tp(
|
|||||||
args: Vec<String>,
|
args: Vec<String>,
|
||||||
action: &ServerChatCommand,
|
action: &ServerChatCommand,
|
||||||
) -> CmdResult<()> {
|
) -> CmdResult<()> {
|
||||||
use crate::rtsim2::RtSim;
|
use crate::rtsim::RtSim;
|
||||||
let pos = if let Some(id) = parse_cmd_args!(args, u32) {
|
let pos = if let Some(id) = parse_cmd_args!(args, u32) {
|
||||||
// TODO: Take some other identifier than an integer to this command.
|
// TODO: Take some other identifier than an integer to this command.
|
||||||
server
|
server
|
||||||
@ -1222,7 +1222,7 @@ fn handle_rtsim_info(
|
|||||||
args: Vec<String>,
|
args: Vec<String>,
|
||||||
action: &ServerChatCommand,
|
action: &ServerChatCommand,
|
||||||
) -> CmdResult<()> {
|
) -> CmdResult<()> {
|
||||||
use crate::rtsim2::RtSim;
|
use crate::rtsim::RtSim;
|
||||||
if let Some(id) = parse_cmd_args!(args, u32) {
|
if let Some(id) = parse_cmd_args!(args, u32) {
|
||||||
// TODO: Take some other identifier than an integer to this command.
|
// TODO: Take some other identifier than an integer to this command.
|
||||||
let rtsim = server.state.ecs().read_resource::<RtSim>();
|
let rtsim = server.state.ecs().read_resource::<RtSim>();
|
||||||
@ -1271,7 +1271,7 @@ fn handle_rtsim_purge(
|
|||||||
args: Vec<String>,
|
args: Vec<String>,
|
||||||
action: &ServerChatCommand,
|
action: &ServerChatCommand,
|
||||||
) -> CmdResult<()> {
|
) -> CmdResult<()> {
|
||||||
use crate::rtsim2::RtSim;
|
use crate::rtsim::RtSim;
|
||||||
if let Some(should_purge) = parse_cmd_args!(args, bool) {
|
if let Some(should_purge) = parse_cmd_args!(args, bool) {
|
||||||
server
|
server
|
||||||
.state
|
.state
|
||||||
@ -1301,7 +1301,7 @@ fn handle_rtsim_chunk(
|
|||||||
args: Vec<String>,
|
args: Vec<String>,
|
||||||
action: &ServerChatCommand,
|
action: &ServerChatCommand,
|
||||||
) -> CmdResult<()> {
|
) -> CmdResult<()> {
|
||||||
use crate::rtsim2::{ChunkStates, RtSim};
|
use crate::rtsim::{ChunkStates, RtSim};
|
||||||
let pos = position(server, target, "target")?;
|
let pos = position(server, target, "target")?;
|
||||||
|
|
||||||
let chunk_key = pos.0.xy().as_::<i32>().wpos_to_cpos();
|
let chunk_key = pos.0.xy().as_::<i32>().wpos_to_cpos();
|
||||||
@ -2034,7 +2034,7 @@ fn handle_kill_npcs(
|
|||||||
.get(entity)
|
.get(entity)
|
||||||
.copied()
|
.copied()
|
||||||
{
|
{
|
||||||
ecs.write_resource::<crate::rtsim2::RtSim>()
|
ecs.write_resource::<crate::rtsim::RtSim>()
|
||||||
.hook_rtsim_entity_delete(
|
.hook_rtsim_entity_delete(
|
||||||
&ecs.read_resource::<Arc<world::World>>(),
|
&ecs.read_resource::<Arc<world::World>>(),
|
||||||
ecs.read_resource::<world::IndexOwned>().as_index_ref(),
|
ecs.read_resource::<world::IndexOwned>().as_index_ref(),
|
||||||
|
@ -7,7 +7,7 @@ use crate::{
|
|||||||
skillset::SkillGroupKind,
|
skillset::SkillGroupKind,
|
||||||
BuffKind, BuffSource, PhysicsState,
|
BuffKind, BuffSource, PhysicsState,
|
||||||
},
|
},
|
||||||
rtsim2,
|
rtsim,
|
||||||
sys::terrain::SAFE_ZONE_RADIUS,
|
sys::terrain::SAFE_ZONE_RADIUS,
|
||||||
Server, SpawnPoint, StateExt,
|
Server, SpawnPoint, StateExt,
|
||||||
};
|
};
|
||||||
@ -527,7 +527,7 @@ pub fn handle_destroy(server: &mut Server, entity: EcsEntity, last_change: Healt
|
|||||||
{
|
{
|
||||||
state
|
state
|
||||||
.ecs()
|
.ecs()
|
||||||
.write_resource::<rtsim2::RtSim>()
|
.write_resource::<rtsim::RtSim>()
|
||||||
.hook_rtsim_entity_delete(
|
.hook_rtsim_entity_delete(
|
||||||
&state.ecs().read_resource::<Arc<world::World>>(),
|
&state.ecs().read_resource::<Arc<world::World>>(),
|
||||||
state
|
state
|
||||||
|
@ -30,9 +30,7 @@ pub mod metrics;
|
|||||||
pub mod persistence;
|
pub mod persistence;
|
||||||
mod pet;
|
mod pet;
|
||||||
pub mod presence;
|
pub mod presence;
|
||||||
// TODO: Remove
|
pub mod rtsim;
|
||||||
//pub mod rtsim;
|
|
||||||
pub mod rtsim2;
|
|
||||||
pub mod settings;
|
pub mod settings;
|
||||||
pub mod state_ext;
|
pub mod state_ext;
|
||||||
pub mod sys;
|
pub mod sys;
|
||||||
@ -566,7 +564,7 @@ impl Server {
|
|||||||
// Init rtsim, loading it from disk if possible
|
// Init rtsim, loading it from disk if possible
|
||||||
#[cfg(feature = "worldgen")]
|
#[cfg(feature = "worldgen")]
|
||||||
{
|
{
|
||||||
match rtsim2::RtSim::new(
|
match rtsim::RtSim::new(
|
||||||
&settings.world,
|
&settings.world,
|
||||||
index.as_index_ref(),
|
index.as_index_ref(),
|
||||||
&world,
|
&world,
|
||||||
@ -721,7 +719,7 @@ impl Server {
|
|||||||
// When a resource block updates, inform rtsim
|
// When a resource block updates, inform rtsim
|
||||||
if old_block.get_rtsim_resource().is_some() || new_block.get_rtsim_resource().is_some()
|
if old_block.get_rtsim_resource().is_some() || new_block.get_rtsim_resource().is_some()
|
||||||
{
|
{
|
||||||
ecs.write_resource::<rtsim2::RtSim>().hook_block_update(
|
ecs.write_resource::<rtsim::RtSim>().hook_block_update(
|
||||||
&ecs.read_resource::<Arc<world::World>>(),
|
&ecs.read_resource::<Arc<world::World>>(),
|
||||||
ecs.read_resource::<world::IndexOwned>().as_index_ref(),
|
ecs.read_resource::<world::IndexOwned>().as_index_ref(),
|
||||||
wpos,
|
wpos,
|
||||||
@ -750,7 +748,7 @@ impl Server {
|
|||||||
*/
|
*/
|
||||||
#[cfg(feature = "worldgen")]
|
#[cfg(feature = "worldgen")]
|
||||||
{
|
{
|
||||||
rtsim2::add_server_systems(dispatcher_builder);
|
rtsim::add_server_systems(dispatcher_builder);
|
||||||
weather::add_server_systems(dispatcher_builder);
|
weather::add_server_systems(dispatcher_builder);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -871,7 +869,7 @@ impl Server {
|
|||||||
{
|
{
|
||||||
self.state
|
self.state
|
||||||
.ecs()
|
.ecs()
|
||||||
.write_resource::<rtsim2::RtSim>()
|
.write_resource::<rtsim::RtSim>()
|
||||||
.hook_rtsim_entity_unload(rtsim_entity);
|
.hook_rtsim_entity_unload(rtsim_entity);
|
||||||
}
|
}
|
||||||
#[cfg(feature = "worldgen")]
|
#[cfg(feature = "worldgen")]
|
||||||
@ -884,7 +882,7 @@ impl Server {
|
|||||||
{
|
{
|
||||||
self.state
|
self.state
|
||||||
.ecs()
|
.ecs()
|
||||||
.write_resource::<rtsim2::RtSim>()
|
.write_resource::<rtsim::RtSim>()
|
||||||
.hook_rtsim_vehicle_unload(rtsim_vehicle);
|
.hook_rtsim_vehicle_unload(rtsim_vehicle);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1039,7 +1037,7 @@ impl Server {
|
|||||||
let client = ecs.read_storage::<Client>();
|
let client = ecs.read_storage::<Client>();
|
||||||
let mut terrain = ecs.write_resource::<common::terrain::TerrainGrid>();
|
let mut terrain = ecs.write_resource::<common::terrain::TerrainGrid>();
|
||||||
#[cfg(feature = "worldgen")]
|
#[cfg(feature = "worldgen")]
|
||||||
let rtsim = ecs.read_resource::<rtsim2::RtSim>();
|
let rtsim = ecs.read_resource::<rtsim::RtSim>();
|
||||||
#[cfg(not(feature = "worldgen"))]
|
#[cfg(not(feature = "worldgen"))]
|
||||||
let rtsim = ();
|
let rtsim = ();
|
||||||
|
|
||||||
@ -1222,7 +1220,7 @@ impl Server {
|
|||||||
let ecs = self.state.ecs();
|
let ecs = self.state.ecs();
|
||||||
let slow_jobs = ecs.read_resource::<SlowJobPool>();
|
let slow_jobs = ecs.read_resource::<SlowJobPool>();
|
||||||
#[cfg(feature = "worldgen")]
|
#[cfg(feature = "worldgen")]
|
||||||
let rtsim = ecs.read_resource::<rtsim2::RtSim>();
|
let rtsim = ecs.read_resource::<rtsim::RtSim>();
|
||||||
#[cfg(not(feature = "worldgen"))]
|
#[cfg(not(feature = "worldgen"))]
|
||||||
let rtsim = ();
|
let rtsim = ();
|
||||||
ecs.write_resource::<ChunkGenerator>().generate_chunk(
|
ecs.write_resource::<ChunkGenerator>().generate_chunk(
|
||||||
@ -1467,10 +1465,7 @@ impl Drop for Server {
|
|||||||
#[cfg(feature = "worldgen")]
|
#[cfg(feature = "worldgen")]
|
||||||
{
|
{
|
||||||
info!("Saving rtsim state...");
|
info!("Saving rtsim state...");
|
||||||
self.state
|
self.state.ecs().write_resource::<rtsim::RtSim>().save(true);
|
||||||
.ecs()
|
|
||||||
.write_resource::<rtsim2::RtSim>()
|
|
||||||
.save(true);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,34 +0,0 @@
|
|||||||
use super::*;
|
|
||||||
use ::world::util::Grid;
|
|
||||||
|
|
||||||
pub struct Chunks {
|
|
||||||
chunks: Grid<Chunk>,
|
|
||||||
pub chunks_to_load: Vec<Vec2<i32>>,
|
|
||||||
pub chunks_to_unload: Vec<Vec2<i32>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Chunks {
|
|
||||||
pub fn new(size: Vec2<u32>) -> Self {
|
|
||||||
Chunks {
|
|
||||||
chunks: Grid::populate_from(size.map(|e| e as i32), |_| Chunk { is_loaded: false }),
|
|
||||||
chunks_to_load: Vec::new(),
|
|
||||||
chunks_to_unload: Vec::new(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn chunk(&self, key: Vec2<i32>) -> Option<&Chunk> { self.chunks.get(key) }
|
|
||||||
|
|
||||||
pub fn size(&self) -> Vec2<u32> { self.chunks.size().map(|e| e as u32) }
|
|
||||||
|
|
||||||
pub fn chunk_mut(&mut self, key: Vec2<i32>) -> Option<&mut Chunk> { self.chunks.get_mut(key) }
|
|
||||||
|
|
||||||
pub fn chunk_at(&self, pos: Vec2<f32>) -> Option<&Chunk> {
|
|
||||||
self.chunks.get(pos.map2(TerrainChunk::RECT_SIZE, |e, sz| {
|
|
||||||
(e.floor() as i32).div_euclid(sz as i32)
|
|
||||||
}))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct Chunk {
|
|
||||||
pub is_loaded: bool,
|
|
||||||
}
|
|
File diff suppressed because it is too large
Load Diff
@ -1,5 +1,5 @@
|
|||||||
use common::terrain::Block;
|
use common::terrain::Block;
|
||||||
use rtsim2::Event;
|
use rtsim::Event;
|
||||||
use vek::*;
|
use vek::*;
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
@ -1,20 +0,0 @@
|
|||||||
use super::*;
|
|
||||||
use common::event::{EventBus, ServerEvent};
|
|
||||||
use common_ecs::{Job, Origin, Phase, System};
|
|
||||||
use specs::{Read, WriteExpect};
|
|
||||||
|
|
||||||
#[derive(Default)]
|
|
||||||
pub struct Sys;
|
|
||||||
impl<'a> System<'a> for Sys {
|
|
||||||
type SystemData = (Read<'a, EventBus<ServerEvent>>, WriteExpect<'a, RtSim>);
|
|
||||||
|
|
||||||
const NAME: &'static str = "rtsim::load_chunks";
|
|
||||||
const ORIGIN: Origin = Origin::Server;
|
|
||||||
const PHASE: Phase = Phase::Create;
|
|
||||||
|
|
||||||
fn run(_job: &mut Job<Self>, (_server_event_bus, mut rtsim): Self::SystemData) {
|
|
||||||
for _chunk in std::mem::take(&mut rtsim.chunks.chunks_to_load) {
|
|
||||||
// TODO
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,414 +1,242 @@
|
|||||||
#![allow(dead_code)] // TODO: Remove this when rtsim is fleshed out
|
pub mod event;
|
||||||
|
pub mod rule;
|
||||||
|
pub mod tick;
|
||||||
|
|
||||||
mod chunks;
|
|
||||||
pub(crate) mod entity;
|
|
||||||
mod load_chunks;
|
|
||||||
mod tick;
|
|
||||||
mod unload_chunks;
|
|
||||||
|
|
||||||
use crate::rtsim::entity::{Personality, Travel};
|
|
||||||
|
|
||||||
use self::chunks::Chunks;
|
|
||||||
use common::{
|
use common::{
|
||||||
comp,
|
grid::Grid,
|
||||||
rtsim::{Memory, RtSimController, RtSimEntity, RtSimId},
|
rtsim::{ChunkResource, RtSimEntity, RtSimVehicle, WorldSettings},
|
||||||
terrain::TerrainChunk,
|
slowjob::SlowJobPool,
|
||||||
|
terrain::{Block, TerrainChunk},
|
||||||
vol::RectRasterableVol,
|
vol::RectRasterableVol,
|
||||||
};
|
};
|
||||||
use common_ecs::{dispatch, System};
|
use common_ecs::{dispatch, System};
|
||||||
use common_state::State;
|
use enum_map::EnumMap;
|
||||||
use rand::prelude::*;
|
use rtsim::{
|
||||||
use slab::Slab;
|
data::{npc::SimulationMode, Data, ReadError},
|
||||||
|
event::{OnDeath, OnSetup},
|
||||||
|
rule::Rule,
|
||||||
|
RtState,
|
||||||
|
};
|
||||||
use specs::{DispatcherBuilder, WorldExt};
|
use specs::{DispatcherBuilder, WorldExt};
|
||||||
|
use std::{
|
||||||
|
error::Error,
|
||||||
|
fs::{self, File},
|
||||||
|
io::{self, Write},
|
||||||
|
path::PathBuf,
|
||||||
|
sync::Arc,
|
||||||
|
time::Instant,
|
||||||
|
};
|
||||||
|
use tracing::{debug, error, info, warn};
|
||||||
use vek::*;
|
use vek::*;
|
||||||
|
use world::{IndexRef, World};
|
||||||
pub use self::entity::{Brain, Entity, RtSimEntityKind};
|
|
||||||
|
|
||||||
pub struct RtSim {
|
pub struct RtSim {
|
||||||
tick: u64,
|
file_path: PathBuf,
|
||||||
chunks: Chunks,
|
last_saved: Option<Instant>,
|
||||||
entities: Slab<Entity>,
|
state: RtState,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl RtSim {
|
impl RtSim {
|
||||||
pub fn new(world_chunk_size: Vec2<u32>) -> Self {
|
pub fn new(
|
||||||
Self {
|
settings: &WorldSettings,
|
||||||
tick: 0,
|
index: IndexRef,
|
||||||
chunks: Chunks::new(world_chunk_size),
|
world: &World,
|
||||||
entities: Slab::new(),
|
data_dir: PathBuf,
|
||||||
}
|
) -> Result<Self, ron::Error> {
|
||||||
|
let file_path = Self::get_file_path(data_dir);
|
||||||
|
|
||||||
|
info!("Looking for rtsim data at {}...", file_path.display());
|
||||||
|
let data = 'load: {
|
||||||
|
if std::env::var("RTSIM_NOLOAD").map_or(true, |v| v != "1") {
|
||||||
|
match File::open(&file_path) {
|
||||||
|
Ok(file) => {
|
||||||
|
info!("Rtsim data found. Attempting to load...");
|
||||||
|
match Data::from_reader(io::BufReader::new(file)) {
|
||||||
|
Ok(data) => {
|
||||||
|
info!("Rtsim data loaded.");
|
||||||
|
if data.should_purge {
|
||||||
|
warn!(
|
||||||
|
"The should_purge flag was set on the rtsim data, \
|
||||||
|
generating afresh"
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
break 'load data;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Err(e) => {
|
||||||
|
error!("Rtsim data failed to load: {}", e);
|
||||||
|
let mut i = 0;
|
||||||
|
loop {
|
||||||
|
let mut backup_path = file_path.clone();
|
||||||
|
backup_path.set_extension(if i == 0 {
|
||||||
|
format!("backup_{}", i)
|
||||||
|
} else {
|
||||||
|
"ron_backup".to_string()
|
||||||
|
});
|
||||||
|
if !backup_path.exists() {
|
||||||
|
fs::rename(&file_path, &backup_path)?;
|
||||||
|
warn!(
|
||||||
|
"Failed rtsim data was moved to {}",
|
||||||
|
backup_path.display()
|
||||||
|
);
|
||||||
|
info!("A fresh rtsim data will now be generated.");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
i += 1;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Err(e) if e.kind() == io::ErrorKind::NotFound => {
|
||||||
|
info!("No rtsim data found. Generating from world...")
|
||||||
|
},
|
||||||
|
Err(e) => return Err(e.into()),
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
warn!(
|
||||||
|
"'RTSIM_NOLOAD' is set, skipping loading of rtsim state (old state will be \
|
||||||
|
overwritten)."
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
let data = Data::generate(settings, &world, index);
|
||||||
|
info!("Rtsim data generated.");
|
||||||
|
data
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut this = Self {
|
||||||
|
last_saved: None,
|
||||||
|
state: RtState::new(data).with_resource(ChunkStates(Grid::populate_from(
|
||||||
|
world.sim().get_size().as_(),
|
||||||
|
|_| None,
|
||||||
|
))),
|
||||||
|
file_path,
|
||||||
|
};
|
||||||
|
|
||||||
|
rule::start_rules(&mut this.state);
|
||||||
|
|
||||||
|
this.state.emit(OnSetup, world, index);
|
||||||
|
|
||||||
|
Ok(this)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn hook_load_chunk(&mut self, key: Vec2<i32>) {
|
fn get_file_path(mut data_dir: PathBuf) -> PathBuf {
|
||||||
if let Some(chunk) = self.chunks.chunk_mut(key) {
|
let mut path = std::env::var("VELOREN_RTSIM")
|
||||||
if !chunk.is_loaded {
|
.map(PathBuf::from)
|
||||||
chunk.is_loaded = true;
|
.unwrap_or_else(|_| {
|
||||||
self.chunks.chunks_to_load.push(key);
|
data_dir.push("rtsim");
|
||||||
}
|
data_dir
|
||||||
|
});
|
||||||
|
path.push("data.dat");
|
||||||
|
path
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn hook_load_chunk(&mut self, key: Vec2<i32>, max_res: EnumMap<ChunkResource, usize>) {
|
||||||
|
if let Some(chunk_state) = self.state.resource_mut::<ChunkStates>().0.get_mut(key) {
|
||||||
|
*chunk_state = Some(LoadedChunkState { max_res });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn hook_unload_chunk(&mut self, key: Vec2<i32>) {
|
pub fn hook_unload_chunk(&mut self, key: Vec2<i32>) {
|
||||||
if let Some(chunk) = self.chunks.chunk_mut(key) {
|
if let Some(chunk_state) = self.state.resource_mut::<ChunkStates>().0.get_mut(key) {
|
||||||
if chunk.is_loaded {
|
*chunk_state = None;
|
||||||
chunk.is_loaded = false;
|
}
|
||||||
self.chunks.chunks_to_unload.push(key);
|
}
|
||||||
|
|
||||||
|
pub fn hook_block_update(
|
||||||
|
&mut self,
|
||||||
|
world: &World,
|
||||||
|
index: IndexRef,
|
||||||
|
wpos: Vec3<i32>,
|
||||||
|
old: Block,
|
||||||
|
new: Block,
|
||||||
|
) {
|
||||||
|
self.state
|
||||||
|
.emit(event::OnBlockChange { wpos, old, new }, world, index);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn hook_rtsim_entity_unload(&mut self, entity: RtSimEntity) {
|
||||||
|
if let Some(npc) = self.state.data_mut().npcs.get_mut(entity.0) {
|
||||||
|
npc.mode = SimulationMode::Simulated;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn hook_rtsim_vehicle_unload(&mut self, entity: RtSimVehicle) {
|
||||||
|
if let Some(vehicle) = self.state.data_mut().npcs.vehicles.get_mut(entity.0) {
|
||||||
|
vehicle.mode = SimulationMode::Simulated;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn hook_rtsim_entity_delete(
|
||||||
|
&mut self,
|
||||||
|
world: &World,
|
||||||
|
index: IndexRef,
|
||||||
|
entity: RtSimEntity,
|
||||||
|
) {
|
||||||
|
// Should entity deletion be death? They're not exactly the same thing...
|
||||||
|
self.state.emit(OnDeath { npc_id: entity.0 }, world, index);
|
||||||
|
self.state.data_mut().npcs.remove(entity.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn save(&mut self, /* slowjob_pool: &SlowJobPool, */ wait_until_finished: bool) {
|
||||||
|
info!("Saving rtsim data...");
|
||||||
|
let file_path = self.file_path.clone();
|
||||||
|
let data = self.state.data().clone();
|
||||||
|
debug!("Starting rtsim data save job...");
|
||||||
|
// TODO: Use slow job
|
||||||
|
// slowjob_pool.spawn("RTSIM_SAVE", move || {
|
||||||
|
let handle = std::thread::spawn(move || {
|
||||||
|
let tmp_file_name = "data_tmp.dat";
|
||||||
|
if let Err(e) = file_path
|
||||||
|
.parent()
|
||||||
|
.map(|dir| {
|
||||||
|
fs::create_dir_all(dir)?;
|
||||||
|
// We write to a temporary file and then rename to avoid corruption.
|
||||||
|
Ok(dir.join(tmp_file_name))
|
||||||
|
})
|
||||||
|
.unwrap_or_else(|| Ok(tmp_file_name.into()))
|
||||||
|
.and_then(|tmp_file_path| Ok((File::create(&tmp_file_path)?, tmp_file_path)))
|
||||||
|
.map_err(|e: io::Error| Box::new(e) as Box<dyn Error>)
|
||||||
|
.and_then(|(mut file, tmp_file_path)| {
|
||||||
|
debug!("Writing rtsim data to file...");
|
||||||
|
data.write_to(io::BufWriter::new(&mut file))?;
|
||||||
|
file.flush()?;
|
||||||
|
drop(file);
|
||||||
|
fs::rename(tmp_file_path, file_path)?;
|
||||||
|
debug!("Rtsim data saved.");
|
||||||
|
Ok(())
|
||||||
|
})
|
||||||
|
{
|
||||||
|
error!("Saving rtsim data failed: {}", e);
|
||||||
}
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if wait_until_finished {
|
||||||
|
handle.join().expect("Save thread failed to join");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
self.last_saved = Some(Instant::now());
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn assimilate_entity(&mut self, entity: RtSimId) {
|
// TODO: Clean up this API a bit
|
||||||
// tracing::info!("Assimilated rtsim entity {}", entity);
|
pub fn get_chunk_resources(&self, key: Vec2<i32>) -> EnumMap<ChunkResource, f32> {
|
||||||
self.entities.get_mut(entity).map(|e| e.is_loaded = false);
|
self.state.data().nature.get_chunk_resources(key)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn reify_entity(&mut self, entity: RtSimId) {
|
pub fn state(&self) -> &RtState { &self.state }
|
||||||
// tracing::info!("Reified rtsim entity {}", entity);
|
|
||||||
self.entities.get_mut(entity).map(|e| e.is_loaded = true);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn update_entity(&mut self, entity: RtSimId, pos: Vec3<f32>) {
|
pub fn set_should_purge(&mut self, should_purge: bool) {
|
||||||
self.entities.get_mut(entity).map(|e| e.pos = pos);
|
self.state.data_mut().should_purge = should_purge;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn destroy_entity(&mut self, entity: RtSimId) {
|
pub struct ChunkStates(pub Grid<Option<LoadedChunkState>>);
|
||||||
// tracing::info!("Destroyed rtsim entity {}", entity);
|
|
||||||
self.entities.remove(entity);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get_entity(&self, entity: RtSimId) -> Option<&Entity> { self.entities.get(entity) }
|
pub struct LoadedChunkState {
|
||||||
|
// The maximum possible number of each resource in this chunk
|
||||||
pub fn insert_entity_memory(&mut self, entity: RtSimId, memory: Memory) {
|
pub max_res: EnumMap<ChunkResource, usize>,
|
||||||
self.entities
|
|
||||||
.get_mut(entity)
|
|
||||||
.map(|entity| entity.brain.add_memory(memory));
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn forget_entity_enemy(&mut self, entity: RtSimId, name: &str) {
|
|
||||||
if let Some(entity) = self.entities.get_mut(entity) {
|
|
||||||
entity.brain.forget_enemy(name);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn set_entity_mood(&mut self, entity: RtSimId, memory: Memory) {
|
|
||||||
self.entities
|
|
||||||
.get_mut(entity)
|
|
||||||
.map(|entity| entity.brain.set_mood(memory));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn add_server_systems(dispatch_builder: &mut DispatcherBuilder) {
|
pub fn add_server_systems(dispatch_builder: &mut DispatcherBuilder) {
|
||||||
dispatch::<unload_chunks::Sys>(dispatch_builder, &[]);
|
dispatch::<tick::Sys>(dispatch_builder, &[]);
|
||||||
dispatch::<load_chunks::Sys>(dispatch_builder, &[&unload_chunks::Sys::sys_name()]);
|
|
||||||
dispatch::<tick::Sys>(dispatch_builder, &[
|
|
||||||
&load_chunks::Sys::sys_name(),
|
|
||||||
&unload_chunks::Sys::sys_name(),
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn init(
|
|
||||||
state: &mut State,
|
|
||||||
#[cfg(feature = "worldgen")] world: &world::World,
|
|
||||||
#[cfg(feature = "worldgen")] index: world::IndexRef,
|
|
||||||
) {
|
|
||||||
#[cfg(feature = "worldgen")]
|
|
||||||
let mut rtsim = RtSim::new(world.sim().get_size());
|
|
||||||
#[cfg(not(feature = "worldgen"))]
|
|
||||||
let mut rtsim = RtSim::new(Vec2::new(40, 40));
|
|
||||||
|
|
||||||
// TODO: Determine number of rtsim entities based on things like initial site
|
|
||||||
// populations rather than world size
|
|
||||||
#[cfg(feature = "worldgen")]
|
|
||||||
{
|
|
||||||
for _ in 0..world.sim().get_size().product() / 400 {
|
|
||||||
let pos = rtsim
|
|
||||||
.chunks
|
|
||||||
.size()
|
|
||||||
.map2(TerrainChunk::RECT_SIZE, |sz, chunk_sz| {
|
|
||||||
thread_rng().gen_range(0..sz * chunk_sz) as i32
|
|
||||||
});
|
|
||||||
|
|
||||||
rtsim.entities.insert(Entity {
|
|
||||||
is_loaded: false,
|
|
||||||
pos: Vec3::from(pos.map(|e| e as f32)),
|
|
||||||
seed: thread_rng().gen(),
|
|
||||||
controller: RtSimController::default(),
|
|
||||||
last_time_ticked: 0.0,
|
|
||||||
kind: RtSimEntityKind::Wanderer,
|
|
||||||
brain: Brain {
|
|
||||||
begin: None,
|
|
||||||
tgt: None,
|
|
||||||
route: Travel::Lost,
|
|
||||||
last_visited: None,
|
|
||||||
memories: Vec::new(),
|
|
||||||
personality: Personality::random(&mut thread_rng()),
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
for (site_id, site) in world
|
|
||||||
.civs()
|
|
||||||
.sites
|
|
||||||
.iter()
|
|
||||||
.filter_map(|(site_id, site)| site.site_tmp.map(|id| (site_id, &index.sites[id])))
|
|
||||||
{
|
|
||||||
use world::site::SiteKind;
|
|
||||||
match &site.kind {
|
|
||||||
#[allow(clippy::single_match)]
|
|
||||||
SiteKind::Dungeon(dungeon) => match dungeon.dungeon_difficulty() {
|
|
||||||
Some(5) => {
|
|
||||||
let pos = site.get_origin();
|
|
||||||
if let Some(nearest_village) = world
|
|
||||||
.civs()
|
|
||||||
.sites
|
|
||||||
.iter()
|
|
||||||
.filter(|(_, site)| site.is_settlement())
|
|
||||||
.min_by_key(|(_, site)| {
|
|
||||||
let wpos = site.center * TerrainChunk::RECT_SIZE.map(|e| e as i32);
|
|
||||||
wpos.map(|e| e as f32)
|
|
||||||
.distance_squared(pos.map(|x| x as f32))
|
|
||||||
as u32
|
|
||||||
})
|
|
||||||
.map(|(id, _)| id)
|
|
||||||
{
|
|
||||||
for _ in 0..25 {
|
|
||||||
rtsim.entities.insert(Entity {
|
|
||||||
is_loaded: false,
|
|
||||||
pos: Vec3::from(pos.map(|e| e as f32)),
|
|
||||||
seed: thread_rng().gen(),
|
|
||||||
controller: RtSimController::default(),
|
|
||||||
last_time_ticked: 0.0,
|
|
||||||
kind: RtSimEntityKind::Cultist,
|
|
||||||
brain: Brain::raid(site_id, nearest_village, &mut thread_rng()),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
_ => {},
|
|
||||||
},
|
|
||||||
SiteKind::Refactor(site2) => {
|
|
||||||
// villagers
|
|
||||||
for _ in 0..site.economy.population().min(site2.plots().len() as f32) as usize {
|
|
||||||
rtsim.entities.insert(Entity {
|
|
||||||
is_loaded: false,
|
|
||||||
pos: site2
|
|
||||||
.plots()
|
|
||||||
.choose(&mut thread_rng())
|
|
||||||
.map_or(site.get_origin(), |plot| {
|
|
||||||
site2.tile_center_wpos(plot.root_tile())
|
|
||||||
})
|
|
||||||
.with_z(0)
|
|
||||||
.map(|e| e as f32),
|
|
||||||
seed: thread_rng().gen(),
|
|
||||||
controller: RtSimController::default(),
|
|
||||||
last_time_ticked: 0.0,
|
|
||||||
kind: RtSimEntityKind::Villager,
|
|
||||||
brain: Brain::villager(site_id, &mut thread_rng()),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// guards
|
|
||||||
for _ in 0..site2.plazas().len() {
|
|
||||||
rtsim.entities.insert(Entity {
|
|
||||||
is_loaded: false,
|
|
||||||
pos: site2
|
|
||||||
.plazas()
|
|
||||||
.choose(&mut thread_rng())
|
|
||||||
.map_or(site.get_origin(), |p| {
|
|
||||||
site2.tile_center_wpos(site2.plot(p).root_tile())
|
|
||||||
+ Vec2::new(
|
|
||||||
thread_rng().gen_range(-8..9),
|
|
||||||
thread_rng().gen_range(-8..9),
|
|
||||||
)
|
|
||||||
})
|
|
||||||
.with_z(0)
|
|
||||||
.map(|e| e as f32),
|
|
||||||
seed: thread_rng().gen(),
|
|
||||||
controller: RtSimController::default(),
|
|
||||||
last_time_ticked: 0.0,
|
|
||||||
kind: RtSimEntityKind::TownGuard,
|
|
||||||
brain: Brain::town_guard(site_id, &mut thread_rng()),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// merchants
|
|
||||||
for _ in 0..site2.plazas().len() {
|
|
||||||
rtsim.entities.insert(Entity {
|
|
||||||
is_loaded: false,
|
|
||||||
pos: site2
|
|
||||||
.plazas()
|
|
||||||
.choose(&mut thread_rng())
|
|
||||||
.map_or(site.get_origin(), |p| {
|
|
||||||
site2.tile_center_wpos(site2.plot(p).root_tile())
|
|
||||||
+ Vec2::new(
|
|
||||||
thread_rng().gen_range(-8..9),
|
|
||||||
thread_rng().gen_range(-8..9),
|
|
||||||
)
|
|
||||||
})
|
|
||||||
.with_z(0)
|
|
||||||
.map(|e| e as f32),
|
|
||||||
seed: thread_rng().gen(),
|
|
||||||
controller: RtSimController::default(),
|
|
||||||
last_time_ticked: 0.0,
|
|
||||||
kind: RtSimEntityKind::Merchant,
|
|
||||||
brain: Brain::merchant(site_id, &mut thread_rng()),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
},
|
|
||||||
SiteKind::CliffTown(site2) => {
|
|
||||||
for _ in 0..(site2.plazas().len() as f32 * 1.5) as usize {
|
|
||||||
rtsim.entities.insert(Entity {
|
|
||||||
is_loaded: false,
|
|
||||||
pos: site2
|
|
||||||
.plazas()
|
|
||||||
.choose(&mut thread_rng())
|
|
||||||
.map_or(site.get_origin(), |p| {
|
|
||||||
site2.tile_center_wpos(site2.plot(p).root_tile())
|
|
||||||
+ Vec2::new(
|
|
||||||
thread_rng().gen_range(-8..9),
|
|
||||||
thread_rng().gen_range(-8..9),
|
|
||||||
)
|
|
||||||
})
|
|
||||||
.with_z(0)
|
|
||||||
.map(|e| e as f32),
|
|
||||||
seed: thread_rng().gen(),
|
|
||||||
controller: RtSimController::default(),
|
|
||||||
last_time_ticked: 0.0,
|
|
||||||
kind: RtSimEntityKind::Merchant,
|
|
||||||
brain: Brain::merchant(site_id, &mut thread_rng()),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
},
|
|
||||||
SiteKind::SavannahPit(site2) => {
|
|
||||||
for _ in 0..4 {
|
|
||||||
rtsim.entities.insert(Entity {
|
|
||||||
is_loaded: false,
|
|
||||||
pos: site2
|
|
||||||
.plots()
|
|
||||||
.filter(|plot| {
|
|
||||||
matches!(plot.kind(), world::site2::PlotKind::SavannahPit(_))
|
|
||||||
})
|
|
||||||
.choose(&mut thread_rng())
|
|
||||||
.map_or(site.get_origin(), |plot| {
|
|
||||||
site2.tile_center_wpos(
|
|
||||||
plot.root_tile()
|
|
||||||
+ Vec2::new(
|
|
||||||
thread_rng().gen_range(-5..5),
|
|
||||||
thread_rng().gen_range(-5..5),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
})
|
|
||||||
.with_z(0)
|
|
||||||
.map(|e| e as f32),
|
|
||||||
seed: thread_rng().gen(),
|
|
||||||
controller: RtSimController::default(),
|
|
||||||
last_time_ticked: 0.0,
|
|
||||||
kind: RtSimEntityKind::Merchant,
|
|
||||||
brain: Brain::merchant(site_id, &mut thread_rng()),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
},
|
|
||||||
SiteKind::DesertCity(site2) => {
|
|
||||||
// villagers
|
|
||||||
for _ in 0..(site2.plazas().len() as f32 * 1.5) as usize {
|
|
||||||
rtsim.entities.insert(Entity {
|
|
||||||
is_loaded: false,
|
|
||||||
pos: site2
|
|
||||||
.plots()
|
|
||||||
.choose(&mut thread_rng())
|
|
||||||
.map_or(site.get_origin(), |plot| {
|
|
||||||
site2.tile_center_wpos(plot.root_tile())
|
|
||||||
})
|
|
||||||
.with_z(0)
|
|
||||||
.map(|e| e as f32),
|
|
||||||
seed: thread_rng().gen(),
|
|
||||||
controller: RtSimController::default(),
|
|
||||||
last_time_ticked: 0.0,
|
|
||||||
kind: RtSimEntityKind::Villager,
|
|
||||||
brain: Brain::villager(site_id, &mut thread_rng()),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// guards
|
|
||||||
for _ in 0..site2.plazas().len() {
|
|
||||||
rtsim.entities.insert(Entity {
|
|
||||||
is_loaded: false,
|
|
||||||
pos: site2
|
|
||||||
.plazas()
|
|
||||||
.choose(&mut thread_rng())
|
|
||||||
.map_or(site.get_origin(), |p| {
|
|
||||||
site2.tile_center_wpos(site2.plot(p).root_tile())
|
|
||||||
+ Vec2::new(
|
|
||||||
thread_rng().gen_range(-8..9),
|
|
||||||
thread_rng().gen_range(-8..9),
|
|
||||||
)
|
|
||||||
})
|
|
||||||
.with_z(0)
|
|
||||||
.map(|e| e as f32),
|
|
||||||
seed: thread_rng().gen(),
|
|
||||||
controller: RtSimController::default(),
|
|
||||||
last_time_ticked: 0.0,
|
|
||||||
kind: RtSimEntityKind::TownGuard,
|
|
||||||
brain: Brain::town_guard(site_id, &mut thread_rng()),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// merchants
|
|
||||||
for _ in 0..site2.plazas().len() {
|
|
||||||
rtsim.entities.insert(Entity {
|
|
||||||
is_loaded: false,
|
|
||||||
pos: site2
|
|
||||||
.plazas()
|
|
||||||
.choose(&mut thread_rng())
|
|
||||||
.map_or(site.get_origin(), |p| {
|
|
||||||
site2.tile_center_wpos(site2.plot(p).root_tile())
|
|
||||||
+ Vec2::new(
|
|
||||||
thread_rng().gen_range(-8..9),
|
|
||||||
thread_rng().gen_range(-8..9),
|
|
||||||
)
|
|
||||||
})
|
|
||||||
.with_z(0)
|
|
||||||
.map(|e| e as f32),
|
|
||||||
seed: thread_rng().gen(),
|
|
||||||
controller: RtSimController::default(),
|
|
||||||
last_time_ticked: 0.0,
|
|
||||||
kind: RtSimEntityKind::Merchant,
|
|
||||||
brain: Brain::merchant(site_id, &mut thread_rng()),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
},
|
|
||||||
SiteKind::ChapelSite(site2) => {
|
|
||||||
// prisoners
|
|
||||||
for _ in 0..10 {
|
|
||||||
rtsim.entities.insert(Entity {
|
|
||||||
is_loaded: false,
|
|
||||||
pos: site2
|
|
||||||
.plots()
|
|
||||||
.filter(|plot| {
|
|
||||||
matches!(plot.kind(), world::site2::PlotKind::SeaChapel(_))
|
|
||||||
})
|
|
||||||
.choose(&mut thread_rng())
|
|
||||||
.map_or(site.get_origin(), |plot| {
|
|
||||||
site2.tile_center_wpos(Vec2::new(
|
|
||||||
plot.root_tile().x,
|
|
||||||
plot.root_tile().y + 4,
|
|
||||||
))
|
|
||||||
})
|
|
||||||
.with_z(0)
|
|
||||||
.map(|e| e as f32),
|
|
||||||
seed: thread_rng().gen(),
|
|
||||||
controller: RtSimController::default(),
|
|
||||||
last_time_ticked: 0.0,
|
|
||||||
kind: RtSimEntityKind::Prisoner,
|
|
||||||
brain: Brain::villager(site_id, &mut thread_rng()),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
},
|
|
||||||
_ => {},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
state.ecs_mut().insert(rtsim);
|
|
||||||
state.ecs_mut().register::<RtSimEntity>();
|
|
||||||
tracing::info!("Initiated real-time world simulation");
|
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
pub mod deplete_resources;
|
pub mod deplete_resources;
|
||||||
|
|
||||||
use rtsim2::RtState;
|
use rtsim::RtState;
|
||||||
use tracing::info;
|
use tracing::info;
|
||||||
|
|
||||||
pub fn start_rules(rtstate: &mut RtState) {
|
pub fn start_rules(rtstate: &mut RtState) {
|
@ -1,9 +1,9 @@
|
|||||||
use crate::rtsim2::{event::OnBlockChange, ChunkStates};
|
use crate::rtsim::{event::OnBlockChange, ChunkStates};
|
||||||
use common::{
|
use common::{
|
||||||
terrain::{CoordinateConversions, TerrainChunk},
|
terrain::{CoordinateConversions, TerrainChunk},
|
||||||
vol::RectRasterableVol,
|
vol::RectRasterableVol,
|
||||||
};
|
};
|
||||||
use rtsim2::{RtState, Rule, RuleError};
|
use rtsim::{RtState, Rule, RuleError};
|
||||||
|
|
||||||
pub struct DepleteResources;
|
pub struct DepleteResources;
|
||||||
|
|
@ -3,29 +3,190 @@
|
|||||||
use super::*;
|
use super::*;
|
||||||
use crate::sys::terrain::NpcData;
|
use crate::sys::terrain::NpcData;
|
||||||
use common::{
|
use common::{
|
||||||
comp,
|
comp::{self, inventory::loadout::Loadout, skillset::skills, Agent, Body},
|
||||||
event::{EventBus, ServerEvent},
|
event::{EventBus, NpcBuilder, ServerEvent},
|
||||||
generation::{BodyBuilder, EntityConfig, EntityInfo},
|
generation::{BodyBuilder, EntityConfig, EntityInfo},
|
||||||
resources::{DeltaTime, Time},
|
lottery::LootSpec,
|
||||||
terrain::TerrainGrid,
|
resources::{DeltaTime, Time, TimeOfDay},
|
||||||
|
rtsim::{RtSimController, RtSimEntity, RtSimVehicle},
|
||||||
|
slowjob::SlowJobPool,
|
||||||
|
terrain::CoordinateConversions,
|
||||||
|
trade::{Good, SiteInformation},
|
||||||
|
LoadoutBuilder, SkillSetBuilder,
|
||||||
};
|
};
|
||||||
use common_ecs::{Job, Origin, Phase, System};
|
use common_ecs::{Job, Origin, Phase, System};
|
||||||
|
use rtsim::data::{
|
||||||
|
npc::{Profession, SimulationMode},
|
||||||
|
Actor, Npc, Sites,
|
||||||
|
};
|
||||||
use specs::{Join, Read, ReadExpect, ReadStorage, WriteExpect, WriteStorage};
|
use specs::{Join, Read, ReadExpect, ReadStorage, WriteExpect, WriteStorage};
|
||||||
use std::sync::Arc;
|
use std::{sync::Arc, time::Duration};
|
||||||
|
use world::site::settlement::trader_loadout;
|
||||||
|
|
||||||
|
fn humanoid_config(profession: &Profession) -> &'static str {
|
||||||
|
match profession {
|
||||||
|
Profession::Farmer => "common.entity.village.farmer",
|
||||||
|
Profession::Hunter => "common.entity.village.hunter",
|
||||||
|
Profession::Herbalist => "common.entity.village.herbalist",
|
||||||
|
Profession::Captain => "common.entity.village.captain",
|
||||||
|
Profession::Merchant => "common.entity.village.merchant",
|
||||||
|
Profession::Guard => "common.entity.village.guard",
|
||||||
|
Profession::Adventurer(rank) => match rank {
|
||||||
|
0 => "common.entity.world.traveler0",
|
||||||
|
1 => "common.entity.world.traveler1",
|
||||||
|
2 => "common.entity.world.traveler2",
|
||||||
|
3 => "common.entity.world.traveler3",
|
||||||
|
_ => panic!("Not a valid adventurer rank"),
|
||||||
|
},
|
||||||
|
Profession::Blacksmith => "common.entity.village.blacksmith",
|
||||||
|
Profession::Chef => "common.entity.village.chef",
|
||||||
|
Profession::Alchemist => "common.entity.village.alchemist",
|
||||||
|
Profession::Pirate => "common.entity.spot.pirate",
|
||||||
|
Profession::Cultist => "common.entity.dungeon.tier-5.cultist",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn loadout_default(loadout: LoadoutBuilder, _economy: Option<&SiteInformation>) -> LoadoutBuilder {
|
||||||
|
loadout
|
||||||
|
}
|
||||||
|
|
||||||
|
fn merchant_loadout(
|
||||||
|
loadout_builder: LoadoutBuilder,
|
||||||
|
economy: Option<&SiteInformation>,
|
||||||
|
) -> LoadoutBuilder {
|
||||||
|
trader_loadout(loadout_builder, economy, |_| true)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn farmer_loadout(
|
||||||
|
loadout_builder: LoadoutBuilder,
|
||||||
|
economy: Option<&SiteInformation>,
|
||||||
|
) -> LoadoutBuilder {
|
||||||
|
trader_loadout(loadout_builder, economy, |good| matches!(good, Good::Food))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn herbalist_loadout(
|
||||||
|
loadout_builder: LoadoutBuilder,
|
||||||
|
economy: Option<&SiteInformation>,
|
||||||
|
) -> LoadoutBuilder {
|
||||||
|
trader_loadout(loadout_builder, economy, |good| {
|
||||||
|
matches!(good, Good::Ingredients)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn chef_loadout(
|
||||||
|
loadout_builder: LoadoutBuilder,
|
||||||
|
economy: Option<&SiteInformation>,
|
||||||
|
) -> LoadoutBuilder {
|
||||||
|
trader_loadout(loadout_builder, economy, |good| matches!(good, Good::Food))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn blacksmith_loadout(
|
||||||
|
loadout_builder: LoadoutBuilder,
|
||||||
|
economy: Option<&SiteInformation>,
|
||||||
|
) -> LoadoutBuilder {
|
||||||
|
trader_loadout(loadout_builder, economy, |good| {
|
||||||
|
matches!(good, Good::Tools | Good::Armor)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn alchemist_loadout(
|
||||||
|
loadout_builder: LoadoutBuilder,
|
||||||
|
economy: Option<&SiteInformation>,
|
||||||
|
) -> LoadoutBuilder {
|
||||||
|
trader_loadout(loadout_builder, economy, |good| {
|
||||||
|
matches!(good, Good::Potions)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn profession_extra_loadout(
|
||||||
|
profession: Option<&Profession>,
|
||||||
|
) -> fn(LoadoutBuilder, Option<&SiteInformation>) -> LoadoutBuilder {
|
||||||
|
match profession {
|
||||||
|
Some(Profession::Merchant) => merchant_loadout,
|
||||||
|
Some(Profession::Farmer) => farmer_loadout,
|
||||||
|
Some(Profession::Herbalist) => herbalist_loadout,
|
||||||
|
Some(Profession::Chef) => chef_loadout,
|
||||||
|
Some(Profession::Blacksmith) => blacksmith_loadout,
|
||||||
|
Some(Profession::Alchemist) => alchemist_loadout,
|
||||||
|
_ => loadout_default,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn profession_agent_mark(profession: Option<&Profession>) -> Option<comp::agent::Mark> {
|
||||||
|
match profession {
|
||||||
|
Some(
|
||||||
|
Profession::Merchant
|
||||||
|
| Profession::Farmer
|
||||||
|
| Profession::Herbalist
|
||||||
|
| Profession::Chef
|
||||||
|
| Profession::Blacksmith
|
||||||
|
| Profession::Alchemist,
|
||||||
|
) => Some(comp::agent::Mark::Merchant),
|
||||||
|
Some(Profession::Guard) => Some(comp::agent::Mark::Guard),
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_npc_entity_info(npc: &Npc, sites: &Sites, index: IndexRef) -> EntityInfo {
|
||||||
|
let pos = comp::Pos(npc.wpos);
|
||||||
|
|
||||||
|
let mut rng = npc.rng(3);
|
||||||
|
if let Some(ref profession) = npc.profession {
|
||||||
|
let economy = npc.home.and_then(|home| {
|
||||||
|
let site = sites.get(home)?.world_site?;
|
||||||
|
index.sites.get(site).trade_information(site.id())
|
||||||
|
});
|
||||||
|
|
||||||
|
let config_asset = humanoid_config(profession);
|
||||||
|
|
||||||
|
let entity_config = EntityConfig::from_asset_expect_owned(config_asset)
|
||||||
|
.with_body(BodyBuilder::Exact(npc.body));
|
||||||
|
EntityInfo::at(pos.0)
|
||||||
|
.with_entity_config(entity_config, Some(config_asset), &mut rng)
|
||||||
|
.with_alignment(if matches!(profession, Profession::Cultist) {
|
||||||
|
comp::Alignment::Enemy
|
||||||
|
} else {
|
||||||
|
comp::Alignment::Npc
|
||||||
|
})
|
||||||
|
.with_economy(economy.as_ref())
|
||||||
|
.with_lazy_loadout(profession_extra_loadout(npc.profession.as_ref()))
|
||||||
|
.with_agent_mark(profession_agent_mark(npc.profession.as_ref()))
|
||||||
|
} else {
|
||||||
|
let config_asset = match npc.body {
|
||||||
|
Body::BirdLarge(body) => match body.species {
|
||||||
|
comp::bird_large::Species::Phoenix => "common.entity.wild.peaceful.phoenix",
|
||||||
|
comp::bird_large::Species::Cockatrice => "common.entity.wild.aggressive.cockatrice",
|
||||||
|
comp::bird_large::Species::Roc => "common.entity.wild.aggressive.roc",
|
||||||
|
// Wildcard match used here as there is an array above
|
||||||
|
// which limits what species are used
|
||||||
|
_ => unimplemented!(),
|
||||||
|
},
|
||||||
|
_ => unimplemented!(),
|
||||||
|
};
|
||||||
|
let entity_config = EntityConfig::from_asset_expect_owned(config_asset)
|
||||||
|
.with_body(BodyBuilder::Exact(npc.body));
|
||||||
|
|
||||||
|
EntityInfo::at(pos.0)
|
||||||
|
.with_entity_config(entity_config, Some(config_asset), &mut rng)
|
||||||
|
.with_alignment(comp::Alignment::Wild)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
pub struct Sys;
|
pub struct Sys;
|
||||||
impl<'a> System<'a> for Sys {
|
impl<'a> System<'a> for Sys {
|
||||||
type SystemData = (
|
type SystemData = (
|
||||||
Read<'a, Time>,
|
|
||||||
Read<'a, DeltaTime>,
|
Read<'a, DeltaTime>,
|
||||||
|
Read<'a, Time>,
|
||||||
|
Read<'a, TimeOfDay>,
|
||||||
Read<'a, EventBus<ServerEvent>>,
|
Read<'a, EventBus<ServerEvent>>,
|
||||||
WriteExpect<'a, RtSim>,
|
WriteExpect<'a, RtSim>,
|
||||||
ReadExpect<'a, TerrainGrid>,
|
|
||||||
ReadExpect<'a, Arc<world::World>>,
|
ReadExpect<'a, Arc<world::World>>,
|
||||||
ReadExpect<'a, world::IndexOwned>,
|
ReadExpect<'a, world::IndexOwned>,
|
||||||
|
ReadExpect<'a, SlowJobPool>,
|
||||||
ReadStorage<'a, comp::Pos>,
|
ReadStorage<'a, comp::Pos>,
|
||||||
ReadStorage<'a, RtSimEntity>,
|
ReadStorage<'a, RtSimEntity>,
|
||||||
|
ReadStorage<'a, RtSimVehicle>,
|
||||||
WriteStorage<'a, comp::Agent>,
|
WriteStorage<'a, comp::Agent>,
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -36,114 +197,118 @@ impl<'a> System<'a> for Sys {
|
|||||||
fn run(
|
fn run(
|
||||||
_job: &mut Job<Self>,
|
_job: &mut Job<Self>,
|
||||||
(
|
(
|
||||||
|
dt,
|
||||||
time,
|
time,
|
||||||
_dt,
|
time_of_day,
|
||||||
server_event_bus,
|
server_event_bus,
|
||||||
mut rtsim,
|
mut rtsim,
|
||||||
terrain,
|
|
||||||
world,
|
world,
|
||||||
index,
|
index,
|
||||||
|
slow_jobs,
|
||||||
positions,
|
positions,
|
||||||
rtsim_entities,
|
rtsim_entities,
|
||||||
|
rtsim_vehicles,
|
||||||
mut agents,
|
mut agents,
|
||||||
): Self::SystemData,
|
): Self::SystemData,
|
||||||
) {
|
) {
|
||||||
|
let mut emitter = server_event_bus.emitter();
|
||||||
let rtsim = &mut *rtsim;
|
let rtsim = &mut *rtsim;
|
||||||
rtsim.tick += 1;
|
|
||||||
|
|
||||||
// Update unloaded rtsim entities, in groups at a time
|
rtsim.state.data_mut().time_of_day = *time_of_day;
|
||||||
const TICK_STAGGER: usize = 30;
|
rtsim
|
||||||
let entities_per_iteration = rtsim.entities.len() / TICK_STAGGER;
|
.state
|
||||||
let mut to_reify = Vec::new();
|
.tick(&world, index.as_index_ref(), *time_of_day, *time, dt.0);
|
||||||
for (id, entity) in rtsim
|
|
||||||
.entities
|
if rtsim
|
||||||
.iter_mut()
|
.last_saved
|
||||||
.skip((rtsim.tick as usize % TICK_STAGGER) * entities_per_iteration)
|
.map_or(true, |ls| ls.elapsed() > Duration::from_secs(60))
|
||||||
.take(entities_per_iteration)
|
|
||||||
.filter(|(_, e)| !e.is_loaded)
|
|
||||||
{
|
{
|
||||||
// Calculating dt ourselves because the dt provided to this fn was since the
|
// TODO: Use slow jobs
|
||||||
// last frame, not since the last iteration that these entities acted
|
let _ = slow_jobs;
|
||||||
let dt = (time.0 - entity.last_time_ticked) as f32;
|
rtsim.save(/* &slow_jobs, */ false);
|
||||||
entity.last_time_ticked = time.0;
|
}
|
||||||
|
|
||||||
if rtsim
|
let chunk_states = rtsim.state.resource::<ChunkStates>();
|
||||||
.chunks
|
let data = &mut *rtsim.state.data_mut();
|
||||||
.chunk_at(entity.pos.xy())
|
|
||||||
.map(|c| c.is_loaded)
|
for (vehicle_id, vehicle) in data.npcs.vehicles.iter_mut() {
|
||||||
.unwrap_or(false)
|
let chunk = vehicle.wpos.xy().as_::<i32>().wpos_to_cpos();
|
||||||
|
|
||||||
|
if matches!(vehicle.mode, SimulationMode::Simulated)
|
||||||
|
&& chunk_states.0.get(chunk).map_or(false, |c| c.is_some())
|
||||||
{
|
{
|
||||||
to_reify.push(id);
|
vehicle.mode = SimulationMode::Loaded;
|
||||||
} else {
|
|
||||||
// Simulate behaviour
|
|
||||||
if let Some(travel_to) = &entity.controller.travel_to {
|
|
||||||
// Move towards target at approximate character speed
|
|
||||||
entity.pos += Vec3::from(
|
|
||||||
(travel_to.0.xy() - entity.pos.xy())
|
|
||||||
.try_normalized()
|
|
||||||
.unwrap_or_else(Vec2::zero)
|
|
||||||
* entity.get_body().max_speed_approx()
|
|
||||||
* entity.controller.speed_factor,
|
|
||||||
) * dt;
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(alt) = world
|
let mut actor_info = |actor: Actor| {
|
||||||
.sim()
|
let npc_id = actor.npc()?;
|
||||||
.get_alt_approx(entity.pos.xy().map(|e| e.floor() as i32))
|
let npc = data.npcs.npcs.get_mut(npc_id)?;
|
||||||
{
|
if matches!(npc.mode, SimulationMode::Simulated) {
|
||||||
entity.pos.z = alt;
|
npc.mode = SimulationMode::Loaded;
|
||||||
}
|
let entity_info =
|
||||||
|
get_npc_entity_info(npc, &data.sites, index.as_index_ref());
|
||||||
|
|
||||||
|
Some(match NpcData::from_entity_info(entity_info) {
|
||||||
|
NpcData::Data {
|
||||||
|
pos: _,
|
||||||
|
stats,
|
||||||
|
skill_set,
|
||||||
|
health,
|
||||||
|
poise,
|
||||||
|
inventory,
|
||||||
|
agent,
|
||||||
|
body,
|
||||||
|
alignment,
|
||||||
|
scale,
|
||||||
|
loot,
|
||||||
|
} => NpcBuilder::new(stats, body, alignment)
|
||||||
|
.with_skill_set(skill_set)
|
||||||
|
.with_health(health)
|
||||||
|
.with_poise(poise)
|
||||||
|
.with_inventory(inventory)
|
||||||
|
.with_agent(agent)
|
||||||
|
.with_scale(scale)
|
||||||
|
.with_loot(loot)
|
||||||
|
.with_rtsim(RtSimEntity(npc_id)),
|
||||||
|
// EntityConfig can't represent Waypoints at all
|
||||||
|
// as of now, and if someone will try to spawn
|
||||||
|
// rtsim waypoint it is definitely error.
|
||||||
|
NpcData::Waypoint(_) => unimplemented!(),
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
error!("Npc is loaded but vehicle is unloaded");
|
||||||
|
None
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
emitter.emit(ServerEvent::CreateShip {
|
||||||
|
pos: comp::Pos(vehicle.wpos),
|
||||||
|
ship: vehicle.body,
|
||||||
|
// agent: None,//Some(Agent::from_body(&Body::Ship(ship))),
|
||||||
|
rtsim_entity: Some(RtSimVehicle(vehicle_id)),
|
||||||
|
driver: vehicle.driver.and_then(&mut actor_info),
|
||||||
|
passangers: vehicle
|
||||||
|
.riders
|
||||||
|
.iter()
|
||||||
|
.copied()
|
||||||
|
.filter(|actor| vehicle.driver != Some(*actor))
|
||||||
|
.filter_map(actor_info)
|
||||||
|
.collect(),
|
||||||
|
});
|
||||||
}
|
}
|
||||||
entity.tick(&time, &terrain, &world, &index.as_index_ref());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Tick entity AI each time if it's loaded
|
for (npc_id, npc) in data.npcs.npcs.iter_mut() {
|
||||||
for (_, entity) in rtsim.entities.iter_mut().filter(|(_, e)| e.is_loaded) {
|
let chunk = npc.wpos.xy().as_::<i32>().wpos_to_cpos();
|
||||||
entity.last_time_ticked = time.0;
|
|
||||||
entity.tick(&time, &terrain, &world, &index.as_index_ref());
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut server_emitter = server_event_bus.emitter();
|
// Load the NPC into the world if it's in a loaded chunk and is not already
|
||||||
for id in to_reify {
|
// loaded
|
||||||
rtsim.reify_entity(id);
|
if matches!(npc.mode, SimulationMode::Simulated)
|
||||||
let entity = &rtsim.entities[id];
|
&& chunk_states.0.get(chunk).map_or(false, |c| c.is_some())
|
||||||
let rtsim_entity = Some(RtSimEntity(id));
|
{
|
||||||
|
npc.mode = SimulationMode::Loaded;
|
||||||
|
let entity_info = get_npc_entity_info(npc, &data.sites, index.as_index_ref());
|
||||||
|
|
||||||
let body = entity.get_body();
|
emitter.emit(match NpcData::from_entity_info(entity_info) {
|
||||||
let spawn_pos = terrain
|
|
||||||
.find_space(entity.pos.map(|e| e.floor() as i32))
|
|
||||||
.map(|e| e as f32)
|
|
||||||
+ Vec3::new(0.5, 0.5, body.flying_height());
|
|
||||||
|
|
||||||
let pos = comp::Pos(spawn_pos);
|
|
||||||
|
|
||||||
let event = if let comp::Body::Ship(ship) = body {
|
|
||||||
ServerEvent::CreateShip {
|
|
||||||
pos,
|
|
||||||
ship,
|
|
||||||
mountable: false,
|
|
||||||
agent: Some(comp::Agent::from_body(&body)),
|
|
||||||
rtsim_entity,
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
let entity_config_path = entity.get_entity_config();
|
|
||||||
let mut loadout_rng = entity.loadout_rng();
|
|
||||||
let ad_hoc_loadout = entity.get_adhoc_loadout();
|
|
||||||
// Body is rewritten so that body parameters
|
|
||||||
// are consistent between reifications
|
|
||||||
let entity_config = EntityConfig::from_asset_expect_owned(entity_config_path)
|
|
||||||
.with_body(BodyBuilder::Exact(body));
|
|
||||||
|
|
||||||
let mut entity_info = EntityInfo::at(pos.0)
|
|
||||||
.with_entity_config(entity_config, Some(entity_config_path), &mut loadout_rng)
|
|
||||||
.with_lazy_loadout(ad_hoc_loadout);
|
|
||||||
// Merchants can be traded with
|
|
||||||
if let Some(economy) = entity.get_trade_info(&world, &index) {
|
|
||||||
entity_info = entity_info
|
|
||||||
.with_agent_mark(comp::agent::Mark::Merchant)
|
|
||||||
.with_economy(&economy);
|
|
||||||
}
|
|
||||||
match NpcData::from_entity_info(entity_info) {
|
|
||||||
NpcData::Data {
|
NpcData::Data {
|
||||||
pos,
|
pos,
|
||||||
stats,
|
stats,
|
||||||
@ -158,38 +323,62 @@ impl<'a> System<'a> for Sys {
|
|||||||
loot,
|
loot,
|
||||||
} => ServerEvent::CreateNpc {
|
} => ServerEvent::CreateNpc {
|
||||||
pos,
|
pos,
|
||||||
stats,
|
npc: NpcBuilder::new(stats, body, alignment)
|
||||||
skill_set,
|
.with_skill_set(skill_set)
|
||||||
health,
|
.with_health(health)
|
||||||
poise,
|
.with_poise(poise)
|
||||||
inventory,
|
.with_inventory(inventory)
|
||||||
agent,
|
.with_agent(agent)
|
||||||
body,
|
.with_scale(scale)
|
||||||
alignment,
|
.with_loot(loot)
|
||||||
scale,
|
.with_rtsim(RtSimEntity(npc_id)),
|
||||||
anchor: None,
|
|
||||||
loot,
|
|
||||||
rtsim_entity,
|
|
||||||
projectile: None,
|
|
||||||
},
|
},
|
||||||
// EntityConfig can't represent Waypoints at all
|
// EntityConfig can't represent Waypoints at all
|
||||||
// as of now, and if someone will try to spawn
|
// as of now, and if someone will try to spawn
|
||||||
// rtsim waypoint it is definitely error.
|
// rtsim waypoint it is definitely error.
|
||||||
NpcData::Waypoint(_) => unimplemented!(),
|
NpcData::Waypoint(_) => unimplemented!(),
|
||||||
}
|
});
|
||||||
};
|
}
|
||||||
server_emitter.emit(event);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update rtsim with real entity data
|
// Synchronise rtsim NPC with entity data
|
||||||
for (pos, rtsim_entity, agent) in (&positions, &rtsim_entities, &mut agents).join() {
|
for (pos, rtsim_vehicle) in (&positions, &rtsim_vehicles).join() {
|
||||||
rtsim
|
data.npcs
|
||||||
.entities
|
.vehicles
|
||||||
|
.get_mut(rtsim_vehicle.0)
|
||||||
|
.filter(|npc| matches!(npc.mode, SimulationMode::Loaded))
|
||||||
|
.map(|vehicle| {
|
||||||
|
// Update rtsim NPC state
|
||||||
|
vehicle.wpos = pos.0;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Synchronise rtsim NPC with entity data
|
||||||
|
for (pos, rtsim_entity, agent) in
|
||||||
|
(&positions, &rtsim_entities, (&mut agents).maybe()).join()
|
||||||
|
{
|
||||||
|
data.npcs
|
||||||
.get_mut(rtsim_entity.0)
|
.get_mut(rtsim_entity.0)
|
||||||
.filter(|e| e.is_loaded)
|
.filter(|npc| matches!(npc.mode, SimulationMode::Loaded))
|
||||||
.map(|entity| {
|
.map(|npc| {
|
||||||
entity.pos = pos.0;
|
// Update rtsim NPC state
|
||||||
agent.rtsim_controller = entity.controller.clone();
|
npc.wpos = pos.0;
|
||||||
|
|
||||||
|
// Update entity state
|
||||||
|
if let Some(agent) = agent {
|
||||||
|
agent.rtsim_controller.personality = npc.personality;
|
||||||
|
if let Some(action) = npc.action {
|
||||||
|
match action {
|
||||||
|
rtsim::data::npc::NpcAction::Goto(wpos, sf) => {
|
||||||
|
agent.rtsim_controller.travel_to = Some(wpos);
|
||||||
|
agent.rtsim_controller.speed_factor = sf;
|
||||||
|
},
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
agent.rtsim_controller.travel_to = None;
|
||||||
|
agent.rtsim_controller.speed_factor = 1.0;
|
||||||
|
}
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,43 +0,0 @@
|
|||||||
use super::*;
|
|
||||||
use common::{
|
|
||||||
comp::Pos,
|
|
||||||
event::{EventBus, ServerEvent},
|
|
||||||
terrain::TerrainGrid,
|
|
||||||
};
|
|
||||||
use common_ecs::{Job, Origin, Phase, System};
|
|
||||||
use specs::{Entities, Read, ReadExpect, ReadStorage, WriteExpect};
|
|
||||||
|
|
||||||
#[derive(Default)]
|
|
||||||
pub struct Sys;
|
|
||||||
impl<'a> System<'a> for Sys {
|
|
||||||
type SystemData = (
|
|
||||||
Read<'a, EventBus<ServerEvent>>,
|
|
||||||
WriteExpect<'a, RtSim>,
|
|
||||||
ReadExpect<'a, TerrainGrid>,
|
|
||||||
Entities<'a>,
|
|
||||||
ReadStorage<'a, RtSimEntity>,
|
|
||||||
ReadStorage<'a, Pos>,
|
|
||||||
);
|
|
||||||
|
|
||||||
const NAME: &'static str = "rtsim::unload_chunks";
|
|
||||||
const ORIGIN: Origin = Origin::Server;
|
|
||||||
const PHASE: Phase = Phase::Create;
|
|
||||||
|
|
||||||
fn run(
|
|
||||||
_job: &mut Job<Self>,
|
|
||||||
(
|
|
||||||
_server_event_bus,
|
|
||||||
mut rtsim,
|
|
||||||
_terrain_grid,
|
|
||||||
_entities,
|
|
||||||
_rtsim_entities,
|
|
||||||
_positions,
|
|
||||||
): Self::SystemData,
|
|
||||||
) {
|
|
||||||
let chunks = std::mem::take(&mut rtsim.chunks.chunks_to_unload);
|
|
||||||
|
|
||||||
for _chunk in chunks {
|
|
||||||
// TODO
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,242 +0,0 @@
|
|||||||
pub mod event;
|
|
||||||
pub mod rule;
|
|
||||||
pub mod tick;
|
|
||||||
|
|
||||||
use common::{
|
|
||||||
grid::Grid,
|
|
||||||
rtsim::{ChunkResource, RtSimEntity, RtSimVehicle, WorldSettings},
|
|
||||||
slowjob::SlowJobPool,
|
|
||||||
terrain::{Block, TerrainChunk},
|
|
||||||
vol::RectRasterableVol,
|
|
||||||
};
|
|
||||||
use common_ecs::{dispatch, System};
|
|
||||||
use enum_map::EnumMap;
|
|
||||||
use rtsim2::{
|
|
||||||
data::{npc::SimulationMode, Data, ReadError},
|
|
||||||
event::{OnDeath, OnSetup},
|
|
||||||
rule::Rule,
|
|
||||||
RtState,
|
|
||||||
};
|
|
||||||
use specs::{DispatcherBuilder, WorldExt};
|
|
||||||
use std::{
|
|
||||||
error::Error,
|
|
||||||
fs::{self, File},
|
|
||||||
io::{self, Write},
|
|
||||||
path::PathBuf,
|
|
||||||
sync::Arc,
|
|
||||||
time::Instant,
|
|
||||||
};
|
|
||||||
use tracing::{debug, error, info, warn};
|
|
||||||
use vek::*;
|
|
||||||
use world::{IndexRef, World};
|
|
||||||
|
|
||||||
pub struct RtSim {
|
|
||||||
file_path: PathBuf,
|
|
||||||
last_saved: Option<Instant>,
|
|
||||||
state: RtState,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl RtSim {
|
|
||||||
pub fn new(
|
|
||||||
settings: &WorldSettings,
|
|
||||||
index: IndexRef,
|
|
||||||
world: &World,
|
|
||||||
data_dir: PathBuf,
|
|
||||||
) -> Result<Self, ron::Error> {
|
|
||||||
let file_path = Self::get_file_path(data_dir);
|
|
||||||
|
|
||||||
info!("Looking for rtsim data at {}...", file_path.display());
|
|
||||||
let data = 'load: {
|
|
||||||
if std::env::var("RTSIM_NOLOAD").map_or(true, |v| v != "1") {
|
|
||||||
match File::open(&file_path) {
|
|
||||||
Ok(file) => {
|
|
||||||
info!("Rtsim data found. Attempting to load...");
|
|
||||||
match Data::from_reader(io::BufReader::new(file)) {
|
|
||||||
Ok(data) => {
|
|
||||||
info!("Rtsim data loaded.");
|
|
||||||
if data.should_purge {
|
|
||||||
warn!(
|
|
||||||
"The should_purge flag was set on the rtsim data, \
|
|
||||||
generating afresh"
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
break 'load data;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
Err(e) => {
|
|
||||||
error!("Rtsim data failed to load: {}", e);
|
|
||||||
let mut i = 0;
|
|
||||||
loop {
|
|
||||||
let mut backup_path = file_path.clone();
|
|
||||||
backup_path.set_extension(if i == 0 {
|
|
||||||
format!("backup_{}", i)
|
|
||||||
} else {
|
|
||||||
"ron_backup".to_string()
|
|
||||||
});
|
|
||||||
if !backup_path.exists() {
|
|
||||||
fs::rename(&file_path, &backup_path)?;
|
|
||||||
warn!(
|
|
||||||
"Failed rtsim data was moved to {}",
|
|
||||||
backup_path.display()
|
|
||||||
);
|
|
||||||
info!("A fresh rtsim data will now be generated.");
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
i += 1;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
}
|
|
||||||
},
|
|
||||||
Err(e) if e.kind() == io::ErrorKind::NotFound => {
|
|
||||||
info!("No rtsim data found. Generating from world...")
|
|
||||||
},
|
|
||||||
Err(e) => return Err(e.into()),
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
warn!(
|
|
||||||
"'RTSIM_NOLOAD' is set, skipping loading of rtsim state (old state will be \
|
|
||||||
overwritten)."
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
let data = Data::generate(settings, &world, index);
|
|
||||||
info!("Rtsim data generated.");
|
|
||||||
data
|
|
||||||
};
|
|
||||||
|
|
||||||
let mut this = Self {
|
|
||||||
last_saved: None,
|
|
||||||
state: RtState::new(data).with_resource(ChunkStates(Grid::populate_from(
|
|
||||||
world.sim().get_size().as_(),
|
|
||||||
|_| None,
|
|
||||||
))),
|
|
||||||
file_path,
|
|
||||||
};
|
|
||||||
|
|
||||||
rule::start_rules(&mut this.state);
|
|
||||||
|
|
||||||
this.state.emit(OnSetup, world, index);
|
|
||||||
|
|
||||||
Ok(this)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_file_path(mut data_dir: PathBuf) -> PathBuf {
|
|
||||||
let mut path = std::env::var("VELOREN_RTSIM")
|
|
||||||
.map(PathBuf::from)
|
|
||||||
.unwrap_or_else(|_| {
|
|
||||||
data_dir.push("rtsim");
|
|
||||||
data_dir
|
|
||||||
});
|
|
||||||
path.push("data.dat");
|
|
||||||
path
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn hook_load_chunk(&mut self, key: Vec2<i32>, max_res: EnumMap<ChunkResource, usize>) {
|
|
||||||
if let Some(chunk_state) = self.state.resource_mut::<ChunkStates>().0.get_mut(key) {
|
|
||||||
*chunk_state = Some(LoadedChunkState { max_res });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn hook_unload_chunk(&mut self, key: Vec2<i32>) {
|
|
||||||
if let Some(chunk_state) = self.state.resource_mut::<ChunkStates>().0.get_mut(key) {
|
|
||||||
*chunk_state = None;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn hook_block_update(
|
|
||||||
&mut self,
|
|
||||||
world: &World,
|
|
||||||
index: IndexRef,
|
|
||||||
wpos: Vec3<i32>,
|
|
||||||
old: Block,
|
|
||||||
new: Block,
|
|
||||||
) {
|
|
||||||
self.state
|
|
||||||
.emit(event::OnBlockChange { wpos, old, new }, world, index);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn hook_rtsim_entity_unload(&mut self, entity: RtSimEntity) {
|
|
||||||
if let Some(npc) = self.state.data_mut().npcs.get_mut(entity.0) {
|
|
||||||
npc.mode = SimulationMode::Simulated;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn hook_rtsim_vehicle_unload(&mut self, entity: RtSimVehicle) {
|
|
||||||
if let Some(vehicle) = self.state.data_mut().npcs.vehicles.get_mut(entity.0) {
|
|
||||||
vehicle.mode = SimulationMode::Simulated;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn hook_rtsim_entity_delete(
|
|
||||||
&mut self,
|
|
||||||
world: &World,
|
|
||||||
index: IndexRef,
|
|
||||||
entity: RtSimEntity,
|
|
||||||
) {
|
|
||||||
// Should entity deletion be death? They're not exactly the same thing...
|
|
||||||
self.state.emit(OnDeath { npc_id: entity.0 }, world, index);
|
|
||||||
self.state.data_mut().npcs.remove(entity.0);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn save(&mut self, /* slowjob_pool: &SlowJobPool, */ wait_until_finished: bool) {
|
|
||||||
info!("Saving rtsim data...");
|
|
||||||
let file_path = self.file_path.clone();
|
|
||||||
let data = self.state.data().clone();
|
|
||||||
debug!("Starting rtsim data save job...");
|
|
||||||
// TODO: Use slow job
|
|
||||||
// slowjob_pool.spawn("RTSIM_SAVE", move || {
|
|
||||||
let handle = std::thread::spawn(move || {
|
|
||||||
let tmp_file_name = "data_tmp.dat";
|
|
||||||
if let Err(e) = file_path
|
|
||||||
.parent()
|
|
||||||
.map(|dir| {
|
|
||||||
fs::create_dir_all(dir)?;
|
|
||||||
// We write to a temporary file and then rename to avoid corruption.
|
|
||||||
Ok(dir.join(tmp_file_name))
|
|
||||||
})
|
|
||||||
.unwrap_or_else(|| Ok(tmp_file_name.into()))
|
|
||||||
.and_then(|tmp_file_path| Ok((File::create(&tmp_file_path)?, tmp_file_path)))
|
|
||||||
.map_err(|e: io::Error| Box::new(e) as Box<dyn Error>)
|
|
||||||
.and_then(|(mut file, tmp_file_path)| {
|
|
||||||
debug!("Writing rtsim data to file...");
|
|
||||||
data.write_to(io::BufWriter::new(&mut file))?;
|
|
||||||
file.flush()?;
|
|
||||||
drop(file);
|
|
||||||
fs::rename(tmp_file_path, file_path)?;
|
|
||||||
debug!("Rtsim data saved.");
|
|
||||||
Ok(())
|
|
||||||
})
|
|
||||||
{
|
|
||||||
error!("Saving rtsim data failed: {}", e);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
if wait_until_finished {
|
|
||||||
handle.join().expect("Save thread failed to join");
|
|
||||||
}
|
|
||||||
|
|
||||||
self.last_saved = Some(Instant::now());
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: Clean up this API a bit
|
|
||||||
pub fn get_chunk_resources(&self, key: Vec2<i32>) -> EnumMap<ChunkResource, f32> {
|
|
||||||
self.state.data().nature.get_chunk_resources(key)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn state(&self) -> &RtState { &self.state }
|
|
||||||
|
|
||||||
pub fn set_should_purge(&mut self, should_purge: bool) {
|
|
||||||
self.state.data_mut().should_purge = should_purge;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct ChunkStates(pub Grid<Option<LoadedChunkState>>);
|
|
||||||
|
|
||||||
pub struct LoadedChunkState {
|
|
||||||
// The maximum possible number of each resource in this chunk
|
|
||||||
pub max_res: EnumMap<ChunkResource, usize>,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn add_server_systems(dispatch_builder: &mut DispatcherBuilder) {
|
|
||||||
dispatch::<tick::Sys>(dispatch_builder, &[]);
|
|
||||||
}
|
|
@ -1,385 +0,0 @@
|
|||||||
#![allow(dead_code)] // TODO: Remove this when rtsim is fleshed out
|
|
||||||
|
|
||||||
use super::*;
|
|
||||||
use crate::sys::terrain::NpcData;
|
|
||||||
use common::{
|
|
||||||
comp::{self, inventory::loadout::Loadout, skillset::skills, Agent, Body},
|
|
||||||
event::{EventBus, NpcBuilder, ServerEvent},
|
|
||||||
generation::{BodyBuilder, EntityConfig, EntityInfo},
|
|
||||||
lottery::LootSpec,
|
|
||||||
resources::{DeltaTime, Time, TimeOfDay},
|
|
||||||
rtsim::{RtSimController, RtSimEntity, RtSimVehicle},
|
|
||||||
slowjob::SlowJobPool,
|
|
||||||
terrain::CoordinateConversions,
|
|
||||||
trade::{Good, SiteInformation},
|
|
||||||
LoadoutBuilder, SkillSetBuilder,
|
|
||||||
};
|
|
||||||
use common_ecs::{Job, Origin, Phase, System};
|
|
||||||
use rtsim2::data::{
|
|
||||||
npc::{Profession, SimulationMode},
|
|
||||||
Actor, Npc, Sites,
|
|
||||||
};
|
|
||||||
use specs::{Join, Read, ReadExpect, ReadStorage, WriteExpect, WriteStorage};
|
|
||||||
use std::{sync::Arc, time::Duration};
|
|
||||||
use world::site::settlement::trader_loadout;
|
|
||||||
|
|
||||||
fn humanoid_config(profession: &Profession) -> &'static str {
|
|
||||||
match profession {
|
|
||||||
Profession::Farmer => "common.entity.village.farmer",
|
|
||||||
Profession::Hunter => "common.entity.village.hunter",
|
|
||||||
Profession::Herbalist => "common.entity.village.herbalist",
|
|
||||||
Profession::Captain => "common.entity.village.captain",
|
|
||||||
Profession::Merchant => "common.entity.village.merchant",
|
|
||||||
Profession::Guard => "common.entity.village.guard",
|
|
||||||
Profession::Adventurer(rank) => match rank {
|
|
||||||
0 => "common.entity.world.traveler0",
|
|
||||||
1 => "common.entity.world.traveler1",
|
|
||||||
2 => "common.entity.world.traveler2",
|
|
||||||
3 => "common.entity.world.traveler3",
|
|
||||||
_ => panic!("Not a valid adventurer rank"),
|
|
||||||
},
|
|
||||||
Profession::Blacksmith => "common.entity.village.blacksmith",
|
|
||||||
Profession::Chef => "common.entity.village.chef",
|
|
||||||
Profession::Alchemist => "common.entity.village.alchemist",
|
|
||||||
Profession::Pirate => "common.entity.spot.pirate",
|
|
||||||
Profession::Cultist => "common.entity.dungeon.tier-5.cultist",
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn loadout_default(loadout: LoadoutBuilder, _economy: Option<&SiteInformation>) -> LoadoutBuilder {
|
|
||||||
loadout
|
|
||||||
}
|
|
||||||
|
|
||||||
fn merchant_loadout(
|
|
||||||
loadout_builder: LoadoutBuilder,
|
|
||||||
economy: Option<&SiteInformation>,
|
|
||||||
) -> LoadoutBuilder {
|
|
||||||
trader_loadout(loadout_builder, economy, |_| true)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn farmer_loadout(
|
|
||||||
loadout_builder: LoadoutBuilder,
|
|
||||||
economy: Option<&SiteInformation>,
|
|
||||||
) -> LoadoutBuilder {
|
|
||||||
trader_loadout(loadout_builder, economy, |good| matches!(good, Good::Food))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn herbalist_loadout(
|
|
||||||
loadout_builder: LoadoutBuilder,
|
|
||||||
economy: Option<&SiteInformation>,
|
|
||||||
) -> LoadoutBuilder {
|
|
||||||
trader_loadout(loadout_builder, economy, |good| {
|
|
||||||
matches!(good, Good::Ingredients)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
fn chef_loadout(
|
|
||||||
loadout_builder: LoadoutBuilder,
|
|
||||||
economy: Option<&SiteInformation>,
|
|
||||||
) -> LoadoutBuilder {
|
|
||||||
trader_loadout(loadout_builder, economy, |good| matches!(good, Good::Food))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn blacksmith_loadout(
|
|
||||||
loadout_builder: LoadoutBuilder,
|
|
||||||
economy: Option<&SiteInformation>,
|
|
||||||
) -> LoadoutBuilder {
|
|
||||||
trader_loadout(loadout_builder, economy, |good| {
|
|
||||||
matches!(good, Good::Tools | Good::Armor)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
fn alchemist_loadout(
|
|
||||||
loadout_builder: LoadoutBuilder,
|
|
||||||
economy: Option<&SiteInformation>,
|
|
||||||
) -> LoadoutBuilder {
|
|
||||||
trader_loadout(loadout_builder, economy, |good| {
|
|
||||||
matches!(good, Good::Potions)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
fn profession_extra_loadout(
|
|
||||||
profession: Option<&Profession>,
|
|
||||||
) -> fn(LoadoutBuilder, Option<&SiteInformation>) -> LoadoutBuilder {
|
|
||||||
match profession {
|
|
||||||
Some(Profession::Merchant) => merchant_loadout,
|
|
||||||
Some(Profession::Farmer) => farmer_loadout,
|
|
||||||
Some(Profession::Herbalist) => herbalist_loadout,
|
|
||||||
Some(Profession::Chef) => chef_loadout,
|
|
||||||
Some(Profession::Blacksmith) => blacksmith_loadout,
|
|
||||||
Some(Profession::Alchemist) => alchemist_loadout,
|
|
||||||
_ => loadout_default,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn profession_agent_mark(profession: Option<&Profession>) -> Option<comp::agent::Mark> {
|
|
||||||
match profession {
|
|
||||||
Some(
|
|
||||||
Profession::Merchant
|
|
||||||
| Profession::Farmer
|
|
||||||
| Profession::Herbalist
|
|
||||||
| Profession::Chef
|
|
||||||
| Profession::Blacksmith
|
|
||||||
| Profession::Alchemist,
|
|
||||||
) => Some(comp::agent::Mark::Merchant),
|
|
||||||
Some(Profession::Guard) => Some(comp::agent::Mark::Guard),
|
|
||||||
_ => None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_npc_entity_info(npc: &Npc, sites: &Sites, index: IndexRef) -> EntityInfo {
|
|
||||||
let pos = comp::Pos(npc.wpos);
|
|
||||||
|
|
||||||
let mut rng = npc.rng(3);
|
|
||||||
if let Some(ref profession) = npc.profession {
|
|
||||||
let economy = npc.home.and_then(|home| {
|
|
||||||
let site = sites.get(home)?.world_site?;
|
|
||||||
index.sites.get(site).trade_information(site.id())
|
|
||||||
});
|
|
||||||
|
|
||||||
let config_asset = humanoid_config(profession);
|
|
||||||
|
|
||||||
let entity_config = EntityConfig::from_asset_expect_owned(config_asset)
|
|
||||||
.with_body(BodyBuilder::Exact(npc.body));
|
|
||||||
EntityInfo::at(pos.0)
|
|
||||||
.with_entity_config(entity_config, Some(config_asset), &mut rng)
|
|
||||||
.with_alignment(if matches!(profession, Profession::Cultist) {
|
|
||||||
comp::Alignment::Enemy
|
|
||||||
} else {
|
|
||||||
comp::Alignment::Npc
|
|
||||||
})
|
|
||||||
.with_economy(economy.as_ref())
|
|
||||||
.with_lazy_loadout(profession_extra_loadout(npc.profession.as_ref()))
|
|
||||||
.with_agent_mark(profession_agent_mark(npc.profession.as_ref()))
|
|
||||||
} else {
|
|
||||||
let config_asset = match npc.body {
|
|
||||||
Body::BirdLarge(body) => match body.species {
|
|
||||||
comp::bird_large::Species::Phoenix => "common.entity.wild.peaceful.phoenix",
|
|
||||||
comp::bird_large::Species::Cockatrice => "common.entity.wild.aggressive.cockatrice",
|
|
||||||
comp::bird_large::Species::Roc => "common.entity.wild.aggressive.roc",
|
|
||||||
// Wildcard match used here as there is an array above
|
|
||||||
// which limits what species are used
|
|
||||||
_ => unimplemented!(),
|
|
||||||
},
|
|
||||||
_ => unimplemented!(),
|
|
||||||
};
|
|
||||||
let entity_config = EntityConfig::from_asset_expect_owned(config_asset)
|
|
||||||
.with_body(BodyBuilder::Exact(npc.body));
|
|
||||||
|
|
||||||
EntityInfo::at(pos.0)
|
|
||||||
.with_entity_config(entity_config, Some(config_asset), &mut rng)
|
|
||||||
.with_alignment(comp::Alignment::Wild)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Default)]
|
|
||||||
pub struct Sys;
|
|
||||||
impl<'a> System<'a> for Sys {
|
|
||||||
type SystemData = (
|
|
||||||
Read<'a, DeltaTime>,
|
|
||||||
Read<'a, Time>,
|
|
||||||
Read<'a, TimeOfDay>,
|
|
||||||
Read<'a, EventBus<ServerEvent>>,
|
|
||||||
WriteExpect<'a, RtSim>,
|
|
||||||
ReadExpect<'a, Arc<world::World>>,
|
|
||||||
ReadExpect<'a, world::IndexOwned>,
|
|
||||||
ReadExpect<'a, SlowJobPool>,
|
|
||||||
ReadStorage<'a, comp::Pos>,
|
|
||||||
ReadStorage<'a, RtSimEntity>,
|
|
||||||
ReadStorage<'a, RtSimVehicle>,
|
|
||||||
WriteStorage<'a, comp::Agent>,
|
|
||||||
);
|
|
||||||
|
|
||||||
const NAME: &'static str = "rtsim::tick";
|
|
||||||
const ORIGIN: Origin = Origin::Server;
|
|
||||||
const PHASE: Phase = Phase::Create;
|
|
||||||
|
|
||||||
fn run(
|
|
||||||
_job: &mut Job<Self>,
|
|
||||||
(
|
|
||||||
dt,
|
|
||||||
time,
|
|
||||||
time_of_day,
|
|
||||||
server_event_bus,
|
|
||||||
mut rtsim,
|
|
||||||
world,
|
|
||||||
index,
|
|
||||||
slow_jobs,
|
|
||||||
positions,
|
|
||||||
rtsim_entities,
|
|
||||||
rtsim_vehicles,
|
|
||||||
mut agents,
|
|
||||||
): Self::SystemData,
|
|
||||||
) {
|
|
||||||
let mut emitter = server_event_bus.emitter();
|
|
||||||
let rtsim = &mut *rtsim;
|
|
||||||
|
|
||||||
rtsim.state.data_mut().time_of_day = *time_of_day;
|
|
||||||
rtsim
|
|
||||||
.state
|
|
||||||
.tick(&world, index.as_index_ref(), *time_of_day, *time, dt.0);
|
|
||||||
|
|
||||||
if rtsim
|
|
||||||
.last_saved
|
|
||||||
.map_or(true, |ls| ls.elapsed() > Duration::from_secs(60))
|
|
||||||
{
|
|
||||||
// TODO: Use slow jobs
|
|
||||||
let _ = slow_jobs;
|
|
||||||
rtsim.save(/* &slow_jobs, */ false);
|
|
||||||
}
|
|
||||||
|
|
||||||
let chunk_states = rtsim.state.resource::<ChunkStates>();
|
|
||||||
let data = &mut *rtsim.state.data_mut();
|
|
||||||
|
|
||||||
for (vehicle_id, vehicle) in data.npcs.vehicles.iter_mut() {
|
|
||||||
let chunk = vehicle.wpos.xy().as_::<i32>().wpos_to_cpos();
|
|
||||||
|
|
||||||
if matches!(vehicle.mode, SimulationMode::Simulated)
|
|
||||||
&& chunk_states.0.get(chunk).map_or(false, |c| c.is_some())
|
|
||||||
{
|
|
||||||
vehicle.mode = SimulationMode::Loaded;
|
|
||||||
|
|
||||||
let mut actor_info = |actor: Actor| {
|
|
||||||
let npc_id = actor.npc()?;
|
|
||||||
let npc = data.npcs.npcs.get_mut(npc_id)?;
|
|
||||||
if matches!(npc.mode, SimulationMode::Simulated) {
|
|
||||||
npc.mode = SimulationMode::Loaded;
|
|
||||||
let entity_info =
|
|
||||||
get_npc_entity_info(npc, &data.sites, index.as_index_ref());
|
|
||||||
|
|
||||||
Some(match NpcData::from_entity_info(entity_info) {
|
|
||||||
NpcData::Data {
|
|
||||||
pos: _,
|
|
||||||
stats,
|
|
||||||
skill_set,
|
|
||||||
health,
|
|
||||||
poise,
|
|
||||||
inventory,
|
|
||||||
agent,
|
|
||||||
body,
|
|
||||||
alignment,
|
|
||||||
scale,
|
|
||||||
loot,
|
|
||||||
} => NpcBuilder::new(stats, body, alignment)
|
|
||||||
.with_skill_set(skill_set)
|
|
||||||
.with_health(health)
|
|
||||||
.with_poise(poise)
|
|
||||||
.with_inventory(inventory)
|
|
||||||
.with_agent(agent)
|
|
||||||
.with_scale(scale)
|
|
||||||
.with_loot(loot)
|
|
||||||
.with_rtsim(RtSimEntity(npc_id)),
|
|
||||||
// EntityConfig can't represent Waypoints at all
|
|
||||||
// as of now, and if someone will try to spawn
|
|
||||||
// rtsim waypoint it is definitely error.
|
|
||||||
NpcData::Waypoint(_) => unimplemented!(),
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
error!("Npc is loaded but vehicle is unloaded");
|
|
||||||
None
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
emitter.emit(ServerEvent::CreateShip {
|
|
||||||
pos: comp::Pos(vehicle.wpos),
|
|
||||||
ship: vehicle.body,
|
|
||||||
// agent: None,//Some(Agent::from_body(&Body::Ship(ship))),
|
|
||||||
rtsim_entity: Some(RtSimVehicle(vehicle_id)),
|
|
||||||
driver: vehicle.driver.and_then(&mut actor_info),
|
|
||||||
passangers: vehicle
|
|
||||||
.riders
|
|
||||||
.iter()
|
|
||||||
.copied()
|
|
||||||
.filter(|actor| vehicle.driver != Some(*actor))
|
|
||||||
.filter_map(actor_info)
|
|
||||||
.collect(),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for (npc_id, npc) in data.npcs.npcs.iter_mut() {
|
|
||||||
let chunk = npc.wpos.xy().as_::<i32>().wpos_to_cpos();
|
|
||||||
|
|
||||||
// Load the NPC into the world if it's in a loaded chunk and is not already
|
|
||||||
// loaded
|
|
||||||
if matches!(npc.mode, SimulationMode::Simulated)
|
|
||||||
&& chunk_states.0.get(chunk).map_or(false, |c| c.is_some())
|
|
||||||
{
|
|
||||||
npc.mode = SimulationMode::Loaded;
|
|
||||||
let entity_info = get_npc_entity_info(npc, &data.sites, index.as_index_ref());
|
|
||||||
|
|
||||||
emitter.emit(match NpcData::from_entity_info(entity_info) {
|
|
||||||
NpcData::Data {
|
|
||||||
pos,
|
|
||||||
stats,
|
|
||||||
skill_set,
|
|
||||||
health,
|
|
||||||
poise,
|
|
||||||
inventory,
|
|
||||||
agent,
|
|
||||||
body,
|
|
||||||
alignment,
|
|
||||||
scale,
|
|
||||||
loot,
|
|
||||||
} => ServerEvent::CreateNpc {
|
|
||||||
pos,
|
|
||||||
npc: NpcBuilder::new(stats, body, alignment)
|
|
||||||
.with_skill_set(skill_set)
|
|
||||||
.with_health(health)
|
|
||||||
.with_poise(poise)
|
|
||||||
.with_inventory(inventory)
|
|
||||||
.with_agent(agent)
|
|
||||||
.with_scale(scale)
|
|
||||||
.with_loot(loot)
|
|
||||||
.with_rtsim(RtSimEntity(npc_id)),
|
|
||||||
},
|
|
||||||
// EntityConfig can't represent Waypoints at all
|
|
||||||
// as of now, and if someone will try to spawn
|
|
||||||
// rtsim waypoint it is definitely error.
|
|
||||||
NpcData::Waypoint(_) => unimplemented!(),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Synchronise rtsim NPC with entity data
|
|
||||||
for (pos, rtsim_vehicle) in (&positions, &rtsim_vehicles).join() {
|
|
||||||
data.npcs
|
|
||||||
.vehicles
|
|
||||||
.get_mut(rtsim_vehicle.0)
|
|
||||||
.filter(|npc| matches!(npc.mode, SimulationMode::Loaded))
|
|
||||||
.map(|vehicle| {
|
|
||||||
// Update rtsim NPC state
|
|
||||||
vehicle.wpos = pos.0;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Synchronise rtsim NPC with entity data
|
|
||||||
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, SimulationMode::Loaded))
|
|
||||||
.map(|npc| {
|
|
||||||
// Update rtsim NPC state
|
|
||||||
npc.wpos = pos.0;
|
|
||||||
|
|
||||||
// Update entity state
|
|
||||||
if let Some(agent) = agent {
|
|
||||||
agent.rtsim_controller.personality = npc.personality;
|
|
||||||
if let Some(action) = npc.action {
|
|
||||||
match action {
|
|
||||||
rtsim2::data::npc::NpcAction::Goto(wpos, sf) => {
|
|
||||||
agent.rtsim_controller.travel_to = Some(wpos);
|
|
||||||
agent.rtsim_controller.speed_factor = sf;
|
|
||||||
},
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
agent.rtsim_controller.travel_to = None;
|
|
||||||
agent.rtsim_controller.speed_factor = 1.0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -5,7 +5,7 @@ use crate::{
|
|||||||
persistence::PersistedComponents,
|
persistence::PersistedComponents,
|
||||||
pet::restore_pet,
|
pet::restore_pet,
|
||||||
presence::{Presence, RepositionOnChunkLoad},
|
presence::{Presence, RepositionOnChunkLoad},
|
||||||
rtsim2::RtSim,
|
rtsim::RtSim,
|
||||||
settings::Settings,
|
settings::Settings,
|
||||||
sys::sentinel::DeletedEntities,
|
sys::sentinel::DeletedEntities,
|
||||||
wiring, BattleModeBuffer, SpawnPoint,
|
wiring, BattleModeBuffer, SpawnPoint,
|
||||||
|
@ -10,7 +10,7 @@ use crate::{
|
|||||||
chunk_serialize::ChunkSendEntry,
|
chunk_serialize::ChunkSendEntry,
|
||||||
client::Client,
|
client::Client,
|
||||||
presence::{Presence, RepositionOnChunkLoad},
|
presence::{Presence, RepositionOnChunkLoad},
|
||||||
rtsim2,
|
rtsim,
|
||||||
settings::Settings,
|
settings::Settings,
|
||||||
ChunkRequest, Tick,
|
ChunkRequest, Tick,
|
||||||
};
|
};
|
||||||
@ -50,7 +50,7 @@ pub type TerrainPersistenceData<'a> = ();
|
|||||||
pub const SAFE_ZONE_RADIUS: f32 = 200.0;
|
pub const SAFE_ZONE_RADIUS: f32 = 200.0;
|
||||||
|
|
||||||
#[cfg(feature = "worldgen")]
|
#[cfg(feature = "worldgen")]
|
||||||
type RtSimData<'a> = WriteExpect<'a, rtsim2::RtSim>;
|
type RtSimData<'a> = WriteExpect<'a, rtsim::RtSim>;
|
||||||
#[cfg(not(feature = "worldgen"))]
|
#[cfg(not(feature = "worldgen"))]
|
||||||
type RtSimData<'a> = ();
|
type RtSimData<'a> = ();
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user