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-net = { package = "veloren-common-net", path = "../common/net" }
|
||||
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 }
|
||||
|
||||
server-agent = {package = "veloren-server-agent", path = "agent"}
|
||||
|
@ -1,6 +1,6 @@
|
||||
#[cfg(not(feature = "worldgen"))]
|
||||
use crate::test_world::{IndexOwned, World};
|
||||
use crate::{metrics::ChunkGenMetrics, rtsim2::RtSim};
|
||||
use crate::{metrics::ChunkGenMetrics, rtsim::RtSim};
|
||||
use common::{
|
||||
calendar::Calendar, generation::ChunkSupplement, resources::TimeOfDay, slowjob::SlowJobPool,
|
||||
terrain::TerrainChunk,
|
||||
|
@ -1193,7 +1193,7 @@ fn handle_rtsim_tp(
|
||||
args: Vec<String>,
|
||||
action: &ServerChatCommand,
|
||||
) -> CmdResult<()> {
|
||||
use crate::rtsim2::RtSim;
|
||||
use crate::rtsim::RtSim;
|
||||
let pos = if let Some(id) = parse_cmd_args!(args, u32) {
|
||||
// TODO: Take some other identifier than an integer to this command.
|
||||
server
|
||||
@ -1222,7 +1222,7 @@ fn handle_rtsim_info(
|
||||
args: Vec<String>,
|
||||
action: &ServerChatCommand,
|
||||
) -> CmdResult<()> {
|
||||
use crate::rtsim2::RtSim;
|
||||
use crate::rtsim::RtSim;
|
||||
if let Some(id) = parse_cmd_args!(args, u32) {
|
||||
// TODO: Take some other identifier than an integer to this command.
|
||||
let rtsim = server.state.ecs().read_resource::<RtSim>();
|
||||
@ -1271,7 +1271,7 @@ fn handle_rtsim_purge(
|
||||
args: Vec<String>,
|
||||
action: &ServerChatCommand,
|
||||
) -> CmdResult<()> {
|
||||
use crate::rtsim2::RtSim;
|
||||
use crate::rtsim::RtSim;
|
||||
if let Some(should_purge) = parse_cmd_args!(args, bool) {
|
||||
server
|
||||
.state
|
||||
@ -1301,7 +1301,7 @@ fn handle_rtsim_chunk(
|
||||
args: Vec<String>,
|
||||
action: &ServerChatCommand,
|
||||
) -> CmdResult<()> {
|
||||
use crate::rtsim2::{ChunkStates, RtSim};
|
||||
use crate::rtsim::{ChunkStates, RtSim};
|
||||
let pos = position(server, target, "target")?;
|
||||
|
||||
let chunk_key = pos.0.xy().as_::<i32>().wpos_to_cpos();
|
||||
@ -2034,7 +2034,7 @@ fn handle_kill_npcs(
|
||||
.get(entity)
|
||||
.copied()
|
||||
{
|
||||
ecs.write_resource::<crate::rtsim2::RtSim>()
|
||||
ecs.write_resource::<crate::rtsim::RtSim>()
|
||||
.hook_rtsim_entity_delete(
|
||||
&ecs.read_resource::<Arc<world::World>>(),
|
||||
ecs.read_resource::<world::IndexOwned>().as_index_ref(),
|
||||
|
@ -7,7 +7,7 @@ use crate::{
|
||||
skillset::SkillGroupKind,
|
||||
BuffKind, BuffSource, PhysicsState,
|
||||
},
|
||||
rtsim2,
|
||||
rtsim,
|
||||
sys::terrain::SAFE_ZONE_RADIUS,
|
||||
Server, SpawnPoint, StateExt,
|
||||
};
|
||||
@ -527,7 +527,7 @@ pub fn handle_destroy(server: &mut Server, entity: EcsEntity, last_change: Healt
|
||||
{
|
||||
state
|
||||
.ecs()
|
||||
.write_resource::<rtsim2::RtSim>()
|
||||
.write_resource::<rtsim::RtSim>()
|
||||
.hook_rtsim_entity_delete(
|
||||
&state.ecs().read_resource::<Arc<world::World>>(),
|
||||
state
|
||||
|
@ -30,9 +30,7 @@ pub mod metrics;
|
||||
pub mod persistence;
|
||||
mod pet;
|
||||
pub mod presence;
|
||||
// TODO: Remove
|
||||
//pub mod rtsim;
|
||||
pub mod rtsim2;
|
||||
pub mod rtsim;
|
||||
pub mod settings;
|
||||
pub mod state_ext;
|
||||
pub mod sys;
|
||||
@ -566,7 +564,7 @@ impl Server {
|
||||
// Init rtsim, loading it from disk if possible
|
||||
#[cfg(feature = "worldgen")]
|
||||
{
|
||||
match rtsim2::RtSim::new(
|
||||
match rtsim::RtSim::new(
|
||||
&settings.world,
|
||||
index.as_index_ref(),
|
||||
&world,
|
||||
@ -721,7 +719,7 @@ impl Server {
|
||||
// When a resource block updates, inform rtsim
|
||||
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::<world::IndexOwned>().as_index_ref(),
|
||||
wpos,
|
||||
@ -750,7 +748,7 @@ impl Server {
|
||||
*/
|
||||
#[cfg(feature = "worldgen")]
|
||||
{
|
||||
rtsim2::add_server_systems(dispatcher_builder);
|
||||
rtsim::add_server_systems(dispatcher_builder);
|
||||
weather::add_server_systems(dispatcher_builder);
|
||||
}
|
||||
},
|
||||
@ -871,7 +869,7 @@ impl Server {
|
||||
{
|
||||
self.state
|
||||
.ecs()
|
||||
.write_resource::<rtsim2::RtSim>()
|
||||
.write_resource::<rtsim::RtSim>()
|
||||
.hook_rtsim_entity_unload(rtsim_entity);
|
||||
}
|
||||
#[cfg(feature = "worldgen")]
|
||||
@ -884,7 +882,7 @@ impl Server {
|
||||
{
|
||||
self.state
|
||||
.ecs()
|
||||
.write_resource::<rtsim2::RtSim>()
|
||||
.write_resource::<rtsim::RtSim>()
|
||||
.hook_rtsim_vehicle_unload(rtsim_vehicle);
|
||||
}
|
||||
|
||||
@ -1039,7 +1037,7 @@ impl Server {
|
||||
let client = ecs.read_storage::<Client>();
|
||||
let mut terrain = ecs.write_resource::<common::terrain::TerrainGrid>();
|
||||
#[cfg(feature = "worldgen")]
|
||||
let rtsim = ecs.read_resource::<rtsim2::RtSim>();
|
||||
let rtsim = ecs.read_resource::<rtsim::RtSim>();
|
||||
#[cfg(not(feature = "worldgen"))]
|
||||
let rtsim = ();
|
||||
|
||||
@ -1222,7 +1220,7 @@ impl Server {
|
||||
let ecs = self.state.ecs();
|
||||
let slow_jobs = ecs.read_resource::<SlowJobPool>();
|
||||
#[cfg(feature = "worldgen")]
|
||||
let rtsim = ecs.read_resource::<rtsim2::RtSim>();
|
||||
let rtsim = ecs.read_resource::<rtsim::RtSim>();
|
||||
#[cfg(not(feature = "worldgen"))]
|
||||
let rtsim = ();
|
||||
ecs.write_resource::<ChunkGenerator>().generate_chunk(
|
||||
@ -1467,10 +1465,7 @@ impl Drop for Server {
|
||||
#[cfg(feature = "worldgen")]
|
||||
{
|
||||
info!("Saving rtsim state...");
|
||||
self.state
|
||||
.ecs()
|
||||
.write_resource::<rtsim2::RtSim>()
|
||||
.save(true);
|
||||
self.state.ecs().write_resource::<rtsim::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 rtsim2::Event;
|
||||
use rtsim::Event;
|
||||
use vek::*;
|
||||
|
||||
#[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::{
|
||||
comp,
|
||||
rtsim::{Memory, RtSimController, RtSimEntity, RtSimId},
|
||||
terrain::TerrainChunk,
|
||||
grid::Grid,
|
||||
rtsim::{ChunkResource, RtSimEntity, RtSimVehicle, WorldSettings},
|
||||
slowjob::SlowJobPool,
|
||||
terrain::{Block, TerrainChunk},
|
||||
vol::RectRasterableVol,
|
||||
};
|
||||
use common_ecs::{dispatch, System};
|
||||
use common_state::State;
|
||||
use rand::prelude::*;
|
||||
use slab::Slab;
|
||||
use enum_map::EnumMap;
|
||||
use rtsim::{
|
||||
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::*;
|
||||
|
||||
pub use self::entity::{Brain, Entity, RtSimEntityKind};
|
||||
use world::{IndexRef, World};
|
||||
|
||||
pub struct RtSim {
|
||||
tick: u64,
|
||||
chunks: Chunks,
|
||||
entities: Slab<Entity>,
|
||||
file_path: PathBuf,
|
||||
last_saved: Option<Instant>,
|
||||
state: RtState,
|
||||
}
|
||||
|
||||
impl RtSim {
|
||||
pub fn new(world_chunk_size: Vec2<u32>) -> Self {
|
||||
Self {
|
||||
tick: 0,
|
||||
chunks: Chunks::new(world_chunk_size),
|
||||
entities: Slab::new(),
|
||||
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)."
|
||||
);
|
||||
}
|
||||
|
||||
pub fn hook_load_chunk(&mut self, key: Vec2<i32>) {
|
||||
if let Some(chunk) = self.chunks.chunk_mut(key) {
|
||||
if !chunk.is_loaded {
|
||||
chunk.is_loaded = true;
|
||||
self.chunks.chunks_to_load.push(key);
|
||||
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) = self.chunks.chunk_mut(key) {
|
||||
if chunk.is_loaded {
|
||||
chunk.is_loaded = false;
|
||||
self.chunks.chunks_to_unload.push(key);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn assimilate_entity(&mut self, entity: RtSimId) {
|
||||
// tracing::info!("Assimilated rtsim entity {}", entity);
|
||||
self.entities.get_mut(entity).map(|e| e.is_loaded = false);
|
||||
}
|
||||
|
||||
pub fn reify_entity(&mut self, entity: RtSimId) {
|
||||
// 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>) {
|
||||
self.entities.get_mut(entity).map(|e| e.pos = pos);
|
||||
}
|
||||
|
||||
pub fn destroy_entity(&mut self, entity: RtSimId) {
|
||||
// tracing::info!("Destroyed rtsim entity {}", entity);
|
||||
self.entities.remove(entity);
|
||||
}
|
||||
|
||||
pub fn get_entity(&self, entity: RtSimId) -> Option<&Entity> { self.entities.get(entity) }
|
||||
|
||||
pub fn insert_entity_memory(&mut self, entity: RtSimId, memory: Memory) {
|
||||
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);
|
||||
if let Some(chunk_state) = self.state.resource_mut::<ChunkStates>().0.get_mut(key) {
|
||||
*chunk_state = None;
|
||||
}
|
||||
}
|
||||
|
||||
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 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::<unload_chunks::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");
|
||||
dispatch::<tick::Sys>(dispatch_builder, &[]);
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
pub mod deplete_resources;
|
||||
|
||||
use rtsim2::RtState;
|
||||
use rtsim::RtState;
|
||||
use tracing::info;
|
||||
|
||||
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::{
|
||||
terrain::{CoordinateConversions, TerrainChunk},
|
||||
vol::RectRasterableVol,
|
||||
};
|
||||
use rtsim2::{RtState, Rule, RuleError};
|
||||
use rtsim::{RtState, Rule, RuleError};
|
||||
|
||||
pub struct DepleteResources;
|
||||
|
@ -3,29 +3,190 @@
|
||||
use super::*;
|
||||
use crate::sys::terrain::NpcData;
|
||||
use common::{
|
||||
comp,
|
||||
event::{EventBus, ServerEvent},
|
||||
comp::{self, inventory::loadout::Loadout, skillset::skills, Agent, Body},
|
||||
event::{EventBus, NpcBuilder, ServerEvent},
|
||||
generation::{BodyBuilder, EntityConfig, EntityInfo},
|
||||
resources::{DeltaTime, Time},
|
||||
terrain::TerrainGrid,
|
||||
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 rtsim::data::{
|
||||
npc::{Profession, SimulationMode},
|
||||
Actor, Npc, Sites,
|
||||
};
|
||||
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)]
|
||||
pub struct Sys;
|
||||
impl<'a> System<'a> for Sys {
|
||||
type SystemData = (
|
||||
Read<'a, Time>,
|
||||
Read<'a, DeltaTime>,
|
||||
Read<'a, Time>,
|
||||
Read<'a, TimeOfDay>,
|
||||
Read<'a, EventBus<ServerEvent>>,
|
||||
WriteExpect<'a, RtSim>,
|
||||
ReadExpect<'a, TerrainGrid>,
|
||||
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>,
|
||||
);
|
||||
|
||||
@ -36,114 +197,118 @@ impl<'a> System<'a> for Sys {
|
||||
fn run(
|
||||
_job: &mut Job<Self>,
|
||||
(
|
||||
dt,
|
||||
time,
|
||||
_dt,
|
||||
time_of_day,
|
||||
server_event_bus,
|
||||
mut rtsim,
|
||||
terrain,
|
||||
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.tick += 1;
|
||||
|
||||
// Update unloaded rtsim entities, in groups at a time
|
||||
const TICK_STAGGER: usize = 30;
|
||||
let entities_per_iteration = rtsim.entities.len() / TICK_STAGGER;
|
||||
let mut to_reify = Vec::new();
|
||||
for (id, entity) in rtsim
|
||||
.entities
|
||||
.iter_mut()
|
||||
.skip((rtsim.tick as usize % TICK_STAGGER) * entities_per_iteration)
|
||||
.take(entities_per_iteration)
|
||||
.filter(|(_, e)| !e.is_loaded)
|
||||
{
|
||||
// Calculating dt ourselves because the dt provided to this fn was since the
|
||||
// last frame, not since the last iteration that these entities acted
|
||||
let dt = (time.0 - entity.last_time_ticked) as f32;
|
||||
entity.last_time_ticked = time.0;
|
||||
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
|
||||
.chunks
|
||||
.chunk_at(entity.pos.xy())
|
||||
.map(|c| c.is_loaded)
|
||||
.unwrap_or(false)
|
||||
.last_saved
|
||||
.map_or(true, |ls| ls.elapsed() > Duration::from_secs(60))
|
||||
{
|
||||
to_reify.push(id);
|
||||
} 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;
|
||||
// TODO: Use slow jobs
|
||||
let _ = slow_jobs;
|
||||
rtsim.save(/* &slow_jobs, */ false);
|
||||
}
|
||||
|
||||
if let Some(alt) = world
|
||||
.sim()
|
||||
.get_alt_approx(entity.pos.xy().map(|e| e.floor() as i32))
|
||||
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())
|
||||
{
|
||||
entity.pos.z = alt;
|
||||
}
|
||||
}
|
||||
entity.tick(&time, &terrain, &world, &index.as_index_ref());
|
||||
}
|
||||
vehicle.mode = SimulationMode::Loaded;
|
||||
|
||||
// Tick entity AI each time if it's loaded
|
||||
for (_, entity) in rtsim.entities.iter_mut().filter(|(_, e)| e.is_loaded) {
|
||||
entity.last_time_ticked = time.0;
|
||||
entity.tick(&time, &terrain, &world, &index.as_index_ref());
|
||||
}
|
||||
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());
|
||||
|
||||
let mut server_emitter = server_event_bus.emitter();
|
||||
for id in to_reify {
|
||||
rtsim.reify_entity(id);
|
||||
let entity = &rtsim.entities[id];
|
||||
let rtsim_entity = Some(RtSimEntity(id));
|
||||
|
||||
let body = entity.get_body();
|
||||
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,
|
||||
}
|
||||
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 {
|
||||
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);
|
||||
error!("Npc is loaded but vehicle is unloaded");
|
||||
None
|
||||
}
|
||||
match NpcData::from_entity_info(entity_info) {
|
||||
};
|
||||
|
||||
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,
|
||||
@ -158,38 +323,62 @@ impl<'a> System<'a> for Sys {
|
||||
loot,
|
||||
} => ServerEvent::CreateNpc {
|
||||
pos,
|
||||
stats,
|
||||
skill_set,
|
||||
health,
|
||||
poise,
|
||||
inventory,
|
||||
agent,
|
||||
body,
|
||||
alignment,
|
||||
scale,
|
||||
anchor: None,
|
||||
loot,
|
||||
rtsim_entity,
|
||||
projectile: None,
|
||||
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!(),
|
||||
});
|
||||
}
|
||||
};
|
||||
server_emitter.emit(event);
|
||||
}
|
||||
|
||||
// Update rtsim with real entity data
|
||||
for (pos, rtsim_entity, agent) in (&positions, &rtsim_entities, &mut agents).join() {
|
||||
rtsim
|
||||
.entities
|
||||
// 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(|e| e.is_loaded)
|
||||
.map(|entity| {
|
||||
entity.pos = pos.0;
|
||||
agent.rtsim_controller = entity.controller.clone();
|
||||
.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 {
|
||||
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,
|
||||
pet::restore_pet,
|
||||
presence::{Presence, RepositionOnChunkLoad},
|
||||
rtsim2::RtSim,
|
||||
rtsim::RtSim,
|
||||
settings::Settings,
|
||||
sys::sentinel::DeletedEntities,
|
||||
wiring, BattleModeBuffer, SpawnPoint,
|
||||
|
@ -10,7 +10,7 @@ use crate::{
|
||||
chunk_serialize::ChunkSendEntry,
|
||||
client::Client,
|
||||
presence::{Presence, RepositionOnChunkLoad},
|
||||
rtsim2,
|
||||
rtsim,
|
||||
settings::Settings,
|
||||
ChunkRequest, Tick,
|
||||
};
|
||||
@ -50,7 +50,7 @@ pub type TerrainPersistenceData<'a> = ();
|
||||
pub const SAFE_ZONE_RADIUS: f32 = 200.0;
|
||||
|
||||
#[cfg(feature = "worldgen")]
|
||||
type RtSimData<'a> = WriteExpect<'a, rtsim2::RtSim>;
|
||||
type RtSimData<'a> = WriteExpect<'a, rtsim::RtSim>;
|
||||
#[cfg(not(feature = "worldgen"))]
|
||||
type RtSimData<'a> = ();
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user