mirror of
https://gitlab.com/veloren/veloren.git
synced 2024-08-30 18:12:32 +00:00
Addressed review comments
This commit is contained in:
parent
a99313695c
commit
b50645c1ee
1
Cargo.lock
generated
1
Cargo.lock
generated
@ -6710,7 +6710,6 @@ dependencies = [
|
|||||||
"crossbeam-utils 0.8.11",
|
"crossbeam-utils 0.8.11",
|
||||||
"csv",
|
"csv",
|
||||||
"dot_vox",
|
"dot_vox",
|
||||||
"enum-iterator 1.1.3",
|
|
||||||
"enum-map",
|
"enum-map",
|
||||||
"fxhash",
|
"fxhash",
|
||||||
"hashbrown 0.12.3",
|
"hashbrown 0.12.3",
|
||||||
|
@ -1855,7 +1855,7 @@ impl Client {
|
|||||||
true,
|
true,
|
||||||
None,
|
None,
|
||||||
&self.connected_server_constants,
|
&self.connected_server_constants,
|
||||||
|_, _, _, _| {},
|
|_, _| {},
|
||||||
);
|
);
|
||||||
// TODO: avoid emitting these in the first place
|
// TODO: avoid emitting these in the first place
|
||||||
let _ = self
|
let _ = self
|
||||||
|
@ -26,7 +26,6 @@ common-base = { package = "veloren-common-base", path = "base" }
|
|||||||
serde = { version = "1.0.110", features = ["derive", "rc"] }
|
serde = { version = "1.0.110", features = ["derive", "rc"] }
|
||||||
|
|
||||||
# Util
|
# Util
|
||||||
enum-iterator = "1.1.3"
|
|
||||||
enum-map = "2.4"
|
enum-map = "2.4"
|
||||||
vek = { version = "0.15.8", features = ["serde"] }
|
vek = { version = "0.15.8", features = ["serde"] }
|
||||||
cfg-if = "1.0.0"
|
cfg-if = "1.0.0"
|
||||||
|
@ -5,7 +5,7 @@ use serde::{Deserialize, Serialize};
|
|||||||
|
|
||||||
/// The limit on how many characters that a player can have
|
/// The limit on how many characters that a player can have
|
||||||
pub const MAX_CHARACTERS_PER_PLAYER: usize = 8;
|
pub const MAX_CHARACTERS_PER_PLAYER: usize = 8;
|
||||||
#[derive(Copy, Clone, Debug, Serialize, Deserialize, PartialEq, Eq, Hash)]
|
#[derive(Copy, Clone, Debug, Serialize, Deserialize, PartialEq, Eq, Hash, PartialOrd, Ord)]
|
||||||
#[serde(transparent)]
|
#[serde(transparent)]
|
||||||
pub struct CharacterId(pub i64);
|
pub struct CharacterId(pub i64);
|
||||||
|
|
||||||
|
@ -659,7 +659,7 @@ impl ServerChatCommand {
|
|||||||
Enum("entity", ENTITIES.clone(), Required),
|
Enum("entity", ENTITIES.clone(), Required),
|
||||||
Integer("amount", 1, Optional),
|
Integer("amount", 1, Optional),
|
||||||
Boolean("ai", "true".to_string(), Optional),
|
Boolean("ai", "true".to_string(), Optional),
|
||||||
Float("ai", 1.0, Optional),
|
Float("scale", 1.0, Optional),
|
||||||
],
|
],
|
||||||
"Spawn a test entity",
|
"Spawn a test entity",
|
||||||
Some(Admin),
|
Some(Admin),
|
||||||
|
@ -6,4 +6,4 @@ mod build_areas;
|
|||||||
mod state;
|
mod state;
|
||||||
// TODO: breakup state module and remove glob
|
// TODO: breakup state module and remove glob
|
||||||
pub use build_areas::{BuildAreaError, BuildAreas};
|
pub use build_areas::{BuildAreaError, BuildAreas};
|
||||||
pub use state::{BlockChange, State, TerrainChanges};
|
pub use state::{BlockChange, BlockDiff, State, TerrainChanges};
|
||||||
|
@ -113,6 +113,13 @@ impl TerrainChanges {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct BlockDiff {
|
||||||
|
pub wpos: Vec3<i32>,
|
||||||
|
pub old: Block,
|
||||||
|
pub new: Block,
|
||||||
|
}
|
||||||
|
|
||||||
/// A type used to represent game state stored on both the client and the
|
/// A type used to represent game state stored on both the client and the
|
||||||
/// server. This includes things like entity components, terrain data, and
|
/// server. This includes things like entity components, terrain data, and
|
||||||
/// global states like weather, time of day, etc.
|
/// global states like weather, time of day, etc.
|
||||||
@ -525,10 +532,7 @@ impl State {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Apply terrain changes
|
// Apply terrain changes
|
||||||
pub fn apply_terrain_changes(
|
pub fn apply_terrain_changes(&self, block_update: impl FnMut(&specs::World, Vec<BlockDiff>)) {
|
||||||
&self,
|
|
||||||
block_update: impl FnMut(&specs::World, Vec3<i32>, Block, Block),
|
|
||||||
) {
|
|
||||||
self.apply_terrain_changes_internal(false, block_update);
|
self.apply_terrain_changes_internal(false, block_update);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -543,7 +547,7 @@ impl State {
|
|||||||
fn apply_terrain_changes_internal(
|
fn apply_terrain_changes_internal(
|
||||||
&self,
|
&self,
|
||||||
during_tick: bool,
|
during_tick: bool,
|
||||||
mut block_update: impl FnMut(&specs::World, Vec3<i32>, Block, Block),
|
mut block_update: impl FnMut(&specs::World, Vec<BlockDiff>),
|
||||||
) {
|
) {
|
||||||
span!(
|
span!(
|
||||||
_guard,
|
_guard,
|
||||||
@ -585,20 +589,30 @@ impl State {
|
|||||||
}
|
}
|
||||||
// Apply block modifications
|
// Apply block modifications
|
||||||
// Only include in `TerrainChanges` if successful
|
// Only include in `TerrainChanges` if successful
|
||||||
modified_blocks.retain(|pos, new_block| {
|
let mut updated_blocks = Vec::with_capacity(modified_blocks.len());
|
||||||
let res = terrain.map(*pos, |old_block| {
|
modified_blocks.retain(|wpos, new| {
|
||||||
block_update(&self.ecs, *pos, old_block, *new_block);
|
let res = terrain.map(*wpos, |old| {
|
||||||
*new_block
|
updated_blocks.push(BlockDiff {
|
||||||
|
wpos: *wpos,
|
||||||
|
old,
|
||||||
|
new: *new,
|
||||||
|
});
|
||||||
|
*new
|
||||||
});
|
});
|
||||||
if let (&Ok(old_block), true) = (&res, during_tick) {
|
if let (&Ok(old), true) = (&res, during_tick) {
|
||||||
// NOTE: If the changes are applied during the tick, we push the *old* value as
|
// NOTE: If the changes are applied during the tick, we push the *old* value as
|
||||||
// the modified block (since it otherwise can't be recovered after the tick).
|
// the modified block (since it otherwise can't be recovered after the tick).
|
||||||
// Otherwise, the changes will be applied after the tick, so we push the *new*
|
// Otherwise, the changes will be applied after the tick, so we push the *new*
|
||||||
// value.
|
// value.
|
||||||
*new_block = old_block;
|
*new = old;
|
||||||
}
|
}
|
||||||
res.is_ok()
|
res.is_ok()
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if !updated_blocks.is_empty() {
|
||||||
|
block_update(&self.ecs, updated_blocks);
|
||||||
|
}
|
||||||
|
|
||||||
self.ecs.write_resource::<TerrainChanges>().modified_blocks = modified_blocks;
|
self.ecs.write_resource::<TerrainChanges>().modified_blocks = modified_blocks;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -610,7 +624,7 @@ impl State {
|
|||||||
update_terrain_and_regions: bool,
|
update_terrain_and_regions: bool,
|
||||||
mut metrics: Option<&mut StateTickMetrics>,
|
mut metrics: Option<&mut StateTickMetrics>,
|
||||||
server_constants: &ServerConstants,
|
server_constants: &ServerConstants,
|
||||||
block_update: impl FnMut(&specs::World, Vec3<i32>, Block, Block),
|
block_update: impl FnMut(&specs::World, Vec<BlockDiff>),
|
||||||
) {
|
) {
|
||||||
span!(_guard, "tick", "State::tick");
|
span!(_guard, "tick", "State::tick");
|
||||||
|
|
||||||
|
@ -184,6 +184,9 @@ pub trait Action<R = ()>: Any + Send + Sync {
|
|||||||
/// want to return one of many actions (each with different types) from
|
/// want to return one of many actions (each with different types) from
|
||||||
/// the same function.
|
/// the same function.
|
||||||
///
|
///
|
||||||
|
/// Note that [`Either`] can often be used to unify mismatched types without
|
||||||
|
/// the need for boxing.
|
||||||
|
///
|
||||||
/// # Example
|
/// # Example
|
||||||
///
|
///
|
||||||
/// ```ignore
|
/// ```ignore
|
||||||
@ -569,7 +572,7 @@ where
|
|||||||
/// The inner function will be run every tick to decide on an action. When an
|
/// The inner function will be run every tick to decide on an action. When an
|
||||||
/// action is chosen, it will be performed until completed unless a different
|
/// action is chosen, it will be performed until completed unless a different
|
||||||
/// action of the same or higher priority is chosen in a subsequent tick.
|
/// action of the same or higher priority is chosen in a subsequent tick.
|
||||||
/// [`watch`] is very unfocussed and will happily switch between actions
|
/// [`watch`] is very unfocused and will happily switch between actions
|
||||||
/// rapidly between ticks if conditions change. If you want something that
|
/// rapidly between ticks if conditions change. If you want something that
|
||||||
/// tends to commit to actions until they are completed, see [`choose`].
|
/// tends to commit to actions until they are completed, see [`choose`].
|
||||||
///
|
///
|
||||||
|
@ -24,8 +24,19 @@ use std::{
|
|||||||
marker::PhantomData,
|
marker::PhantomData,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/// The current version of rtsim data.
|
||||||
|
///
|
||||||
|
/// Note that this number does *not* need incrementing on every change: most
|
||||||
|
/// field removals/additions are fine. This number should only be incremented
|
||||||
|
/// when we wish to perform a *hard purge* of rtsim data.
|
||||||
|
pub const CURRENT_VERSION: u32 = 0;
|
||||||
|
|
||||||
#[derive(Clone, Serialize, Deserialize)]
|
#[derive(Clone, Serialize, Deserialize)]
|
||||||
pub struct Data {
|
pub struct Data {
|
||||||
|
// Absence of field just implied version = 0
|
||||||
|
#[serde(default)]
|
||||||
|
pub version: u32,
|
||||||
|
|
||||||
pub nature: Nature,
|
pub nature: Nature,
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub npcs: Npcs,
|
pub npcs: Npcs,
|
||||||
@ -46,7 +57,21 @@ pub struct Data {
|
|||||||
pub should_purge: bool,
|
pub should_purge: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub type ReadError = rmp_serde::decode::Error;
|
pub enum ReadError {
|
||||||
|
Load(rmp_serde::decode::Error),
|
||||||
|
// Preserve old data
|
||||||
|
VersionMismatch(Box<Data>),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Debug for ReadError {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
|
match self {
|
||||||
|
Self::Load(err) => err.fmt(f),
|
||||||
|
Self::VersionMismatch(_) => write!(f, "VersionMismatch"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub type WriteError = rmp_serde::encode::Error;
|
pub type WriteError = rmp_serde::encode::Error;
|
||||||
|
|
||||||
impl Data {
|
impl Data {
|
||||||
@ -59,8 +84,16 @@ impl Data {
|
|||||||
id
|
id
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn from_reader<R: Read>(reader: R) -> Result<Self, ReadError> {
|
pub fn from_reader<R: Read>(reader: R) -> Result<Box<Self>, ReadError> {
|
||||||
rmp_serde::decode::from_read(reader)
|
rmp_serde::decode::from_read(reader)
|
||||||
|
.map_err(ReadError::Load)
|
||||||
|
.and_then(|data: Data| {
|
||||||
|
if data.version == CURRENT_VERSION {
|
||||||
|
Ok(Box::new(data))
|
||||||
|
} else {
|
||||||
|
Err(ReadError::VersionMismatch(Box::new(data)))
|
||||||
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn write_to<W: Write>(&self, mut writer: W) -> Result<(), WriteError> {
|
pub fn write_to<W: Write>(&self, mut writer: W) -> Result<(), WriteError> {
|
||||||
|
@ -55,6 +55,8 @@ pub struct Chunk {
|
|||||||
/// generation. This value represents only the variable 'depletion' factor
|
/// generation. This value represents only the variable 'depletion' factor
|
||||||
/// of that resource, which shall change over time as the world evolves
|
/// of that resource, which shall change over time as the world evolves
|
||||||
/// and players interact with it.
|
/// and players interact with it.
|
||||||
|
// TODO: Consider whether we can use `i16` or similar here instead: `f32` has more resolution
|
||||||
|
// than we might need.
|
||||||
#[serde(rename = "r")]
|
#[serde(rename = "r")]
|
||||||
#[serde(serialize_with = "crate::data::rugged_ser_enum_map::<_, _, _, 1>")]
|
#[serde(serialize_with = "crate::data::rugged_ser_enum_map::<_, _, _, 1>")]
|
||||||
#[serde(deserialize_with = "crate::data::rugged_de_enum_map::<_, _, _, 1>")]
|
#[serde(deserialize_with = "crate::data::rugged_de_enum_map::<_, _, _, 1>")]
|
||||||
|
@ -179,21 +179,25 @@ impl Npc {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: have a dedicated `NpcBuilder` type for this.
|
||||||
pub fn with_personality(mut self, personality: Personality) -> Self {
|
pub fn with_personality(mut self, personality: Personality) -> Self {
|
||||||
self.personality = personality;
|
self.personality = personality;
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: have a dedicated `NpcBuilder` type for this.
|
||||||
pub fn with_profession(mut self, profession: impl Into<Option<Profession>>) -> Self {
|
pub fn with_profession(mut self, profession: impl Into<Option<Profession>>) -> Self {
|
||||||
self.profession = profession.into();
|
self.profession = profession.into();
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: have a dedicated `NpcBuilder` type for this.
|
||||||
pub fn with_home(mut self, home: impl Into<Option<SiteId>>) -> Self {
|
pub fn with_home(mut self, home: impl Into<Option<SiteId>>) -> Self {
|
||||||
self.home = home.into();
|
self.home = home.into();
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: have a dedicated `NpcBuilder` type for this.
|
||||||
pub fn steering(mut self, vehicle: impl Into<Option<VehicleId>>) -> Self {
|
pub fn steering(mut self, vehicle: impl Into<Option<VehicleId>>) -> Self {
|
||||||
self.riding = vehicle.into().map(|vehicle| Riding {
|
self.riding = vehicle.into().map(|vehicle| Riding {
|
||||||
vehicle,
|
vehicle,
|
||||||
@ -202,6 +206,7 @@ impl Npc {
|
|||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: have a dedicated `NpcBuilder` type for this.
|
||||||
pub fn riding(mut self, vehicle: impl Into<Option<VehicleId>>) -> Self {
|
pub fn riding(mut self, vehicle: impl Into<Option<VehicleId>>) -> Self {
|
||||||
self.riding = vehicle.into().map(|vehicle| Riding {
|
self.riding = vehicle.into().map(|vehicle| Riding {
|
||||||
vehicle,
|
vehicle,
|
||||||
@ -210,6 +215,7 @@ impl Npc {
|
|||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: have a dedicated `NpcBuilder` type for this.
|
||||||
pub fn with_faction(mut self, faction: impl Into<Option<FactionId>>) -> Self {
|
pub fn with_faction(mut self, faction: impl Into<Option<FactionId>>) -> Self {
|
||||||
self.faction = faction.into();
|
self.faction = faction.into();
|
||||||
self
|
self
|
||||||
@ -217,10 +223,14 @@ impl Npc {
|
|||||||
|
|
||||||
pub fn rng(&self, perm: u32) -> impl Rng { RandomPerm::new(self.seed.wrapping_add(perm)) }
|
pub fn rng(&self, perm: u32) -> impl Rng { RandomPerm::new(self.seed.wrapping_add(perm)) }
|
||||||
|
|
||||||
|
// TODO: Don't make this depend on deterministic RNG, actually persist names
|
||||||
|
// once we've decided that we want to
|
||||||
pub fn get_name(&self) -> String { name::generate(&mut self.rng(Self::PERM_NAME)) }
|
pub fn get_name(&self) -> String { name::generate(&mut self.rng(Self::PERM_NAME)) }
|
||||||
|
|
||||||
pub fn cleanup(&mut self, reports: &Reports) {
|
pub fn cleanup(&mut self, reports: &Reports) {
|
||||||
// Clear old or superfluous sentiments
|
// Clear old or superfluous sentiments
|
||||||
|
// TODO: It might be worth giving more important NPCs a higher sentiment
|
||||||
|
// 'budget' than less important ones.
|
||||||
self.sentiments
|
self.sentiments
|
||||||
.cleanup(crate::data::sentiment::NPC_MAX_SENTIMENTS);
|
.cleanup(crate::data::sentiment::NPC_MAX_SENTIMENTS);
|
||||||
// Clear reports that have been forgotten
|
// Clear reports that have been forgotten
|
||||||
@ -305,6 +315,7 @@ pub struct Npcs {
|
|||||||
pub npcs: HopSlotMap<NpcId, Npc>,
|
pub npcs: HopSlotMap<NpcId, Npc>,
|
||||||
pub vehicles: HopSlotMap<VehicleId, Vehicle>,
|
pub vehicles: HopSlotMap<VehicleId, Vehicle>,
|
||||||
// TODO: This feels like it should be its own rtsim resource
|
// TODO: This feels like it should be its own rtsim resource
|
||||||
|
// TODO: Consider switching to `common::util::SpatialGrid` instead
|
||||||
#[serde(skip, default = "construct_npc_grid")]
|
#[serde(skip, default = "construct_npc_grid")]
|
||||||
pub npc_grid: Grid<GridCell>,
|
pub npc_grid: Grid<GridCell>,
|
||||||
#[serde(skip)]
|
#[serde(skip)]
|
||||||
@ -332,6 +343,8 @@ impl Npcs {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Queries nearby npcs, not garantueed to work if radius > 32.0
|
/// Queries nearby npcs, not garantueed to work if radius > 32.0
|
||||||
|
// TODO: Find a more efficient way to implement this, it's currently
|
||||||
|
// (theoretically) O(n^2).
|
||||||
pub fn nearby(
|
pub fn nearby(
|
||||||
&self,
|
&self,
|
||||||
this_npc: Option<NpcId>,
|
this_npc: Option<NpcId>,
|
||||||
|
@ -5,6 +5,7 @@ use common::{
|
|||||||
use hashbrown::HashMap;
|
use hashbrown::HashMap;
|
||||||
use rand::prelude::*;
|
use rand::prelude::*;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
use std::collections::BinaryHeap;
|
||||||
|
|
||||||
// Factions have a larger 'social memory' than individual NPCs and so we allow
|
// Factions have a larger 'social memory' than individual NPCs and so we allow
|
||||||
// them to have more sentiments
|
// them to have more sentiments
|
||||||
@ -22,7 +23,7 @@ const DECAY_TIME_FACTOR: f32 = 1.0; //6.0; TODO: Use this value when we're happy
|
|||||||
// - Occupations (hatred of hunters or chefs?)
|
// - Occupations (hatred of hunters or chefs?)
|
||||||
// - Ideologies (dislikes democracy, likes monarchy?)
|
// - Ideologies (dislikes democracy, likes monarchy?)
|
||||||
// - etc.
|
// - etc.
|
||||||
#[derive(Copy, Clone, Hash, PartialEq, Eq, Serialize, Deserialize)]
|
#[derive(Copy, Clone, Hash, PartialEq, Eq, Serialize, Deserialize, PartialOrd, Ord)]
|
||||||
pub enum Target {
|
pub enum Target {
|
||||||
Character(CharacterId),
|
Character(CharacterId),
|
||||||
Npc(NpcId),
|
Npc(NpcId),
|
||||||
@ -105,13 +106,12 @@ impl Sentiments {
|
|||||||
// For each sentiment, calculate how valuable it is for us to remember.
|
// For each sentiment, calculate how valuable it is for us to remember.
|
||||||
// For now, we just use the absolute value of the sentiment but later on we might want to favour
|
// For now, we just use the absolute value of the sentiment but later on we might want to favour
|
||||||
// sentiments toward factions and other 'larger' groups over, say, sentiments toward players/other NPCs
|
// sentiments toward factions and other 'larger' groups over, say, sentiments toward players/other NPCs
|
||||||
.map(|(tgt, sentiment)| (*tgt, sentiment.positivity.unsigned_abs()))
|
.map(|(tgt, sentiment)| (sentiment.positivity.unsigned_abs(), *tgt))
|
||||||
.collect::<Vec<_>>();
|
.collect::<BinaryHeap<_>>();
|
||||||
sentiments.sort_unstable_by_key(|(_, value)| *value);
|
|
||||||
|
|
||||||
// Remove the superfluous sentiments
|
// Remove the superfluous sentiments
|
||||||
for (tgt, _) in &sentiments[0..self.map.len() - max_sentiments] {
|
for (_, tgt) in sentiments.drain().take(self.map.len() - max_sentiments) {
|
||||||
self.map.remove(tgt);
|
self.map.remove(&tgt);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -156,7 +156,7 @@ impl Sentiment {
|
|||||||
/// generally try to harm the actor in any way they can.
|
/// generally try to harm the actor in any way they can.
|
||||||
pub const VILLAIN: f32 = -0.8;
|
pub const VILLAIN: f32 = -0.8;
|
||||||
|
|
||||||
fn value(&self) -> f32 { self.positivity as f32 / 126.0 }
|
fn value(&self) -> f32 { self.positivity as f32 * (1.0 / 126.0) }
|
||||||
|
|
||||||
fn change_by(&mut self, change: f32, cap: f32) {
|
fn change_by(&mut self, change: f32, cap: f32) {
|
||||||
// There's a bit of ceremony here for two reasons:
|
// There's a bit of ceremony here for two reasons:
|
||||||
@ -175,8 +175,8 @@ impl Sentiment {
|
|||||||
|
|
||||||
fn decay(&mut self, rng: &mut impl Rng, dt: f32) {
|
fn decay(&mut self, rng: &mut impl Rng, dt: f32) {
|
||||||
if self.positivity != 0 {
|
if self.positivity != 0 {
|
||||||
// TODO: Make dt-independent so we can slow tick rates
|
// TODO: Find a slightly nicer way to have sentiment decay, perhaps even by
|
||||||
// 36 = 6 * 6
|
// remembering the last interaction instead of constant updates.
|
||||||
if rng.gen_bool(
|
if rng.gen_bool(
|
||||||
(1.0 / (self.positivity.unsigned_abs() as f32 * DECAY_TIME_FACTOR.powi(2) * dt))
|
(1.0 / (self.positivity.unsigned_abs() as f32 * DECAY_TIME_FACTOR.powi(2) * dt))
|
||||||
as f64,
|
as f64,
|
||||||
|
@ -37,6 +37,7 @@ pub struct Site {
|
|||||||
#[serde(skip_serializing, skip_deserializing)]
|
#[serde(skip_serializing, skip_deserializing)]
|
||||||
pub world_site: Option<Id<WorldSite>>,
|
pub world_site: Option<Id<WorldSite>>,
|
||||||
|
|
||||||
|
// Note: there's currently no guarantee that site populations are non-intersecting
|
||||||
#[serde(skip_serializing, skip_deserializing)]
|
#[serde(skip_serializing, skip_deserializing)]
|
||||||
pub population: HashSet<NpcId>,
|
pub population: HashSet<NpcId>,
|
||||||
}
|
}
|
||||||
|
@ -6,7 +6,7 @@ use crate::data::{
|
|||||||
faction::Faction,
|
faction::Faction,
|
||||||
npc::{Npc, Npcs, Profession, Vehicle},
|
npc::{Npc, Npcs, Profession, Vehicle},
|
||||||
site::Site,
|
site::Site,
|
||||||
Data, Nature,
|
Data, Nature, CURRENT_VERSION,
|
||||||
};
|
};
|
||||||
use common::{
|
use common::{
|
||||||
comp::{self, Body},
|
comp::{self, Body},
|
||||||
@ -30,6 +30,7 @@ impl Data {
|
|||||||
let mut rng = SmallRng::from_seed(seed);
|
let mut rng = SmallRng::from_seed(seed);
|
||||||
|
|
||||||
let mut this = Self {
|
let mut this = Self {
|
||||||
|
version: CURRENT_VERSION,
|
||||||
nature: Nature::generate(world),
|
nature: Nature::generate(world),
|
||||||
npcs: Npcs {
|
npcs: Npcs {
|
||||||
npcs: Default::default(),
|
npcs: Default::default(),
|
||||||
|
@ -9,7 +9,7 @@ pub fn generate(rng: &mut impl Rng) -> String {
|
|||||||
|
|
||||||
name += starts.choose(rng).unwrap();
|
name += starts.choose(rng).unwrap();
|
||||||
|
|
||||||
for _ in 0..thread_rng().gen_range(1..=3) {
|
for _ in 0..rng.gen_range(1..=3) {
|
||||||
name += vowels.choose(rng).unwrap();
|
name += vowels.choose(rng).unwrap();
|
||||||
name += cons.choose(rng).unwrap();
|
name += cons.choose(rng).unwrap();
|
||||||
}
|
}
|
||||||
|
@ -91,6 +91,9 @@ impl RtState {
|
|||||||
.borrow_mut()
|
.borrow_mut()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: Consider whether it's worth explicitly calling rule event handlers
|
||||||
|
// instead of allowing them to bind event handlers. Less modular, but
|
||||||
|
// potentially easier to deal with data dependencies?
|
||||||
pub fn bind<R: Rule, E: Event>(
|
pub fn bind<R: Rule, E: Event>(
|
||||||
&mut self,
|
&mut self,
|
||||||
f: impl FnMut(EventCtx<R, E>) + Send + Sync + 'static,
|
f: impl FnMut(EventCtx<R, E>) + Send + Sync + 'static,
|
||||||
@ -114,6 +117,8 @@ impl RtState {
|
|||||||
|
|
||||||
pub fn data_mut(&self) -> impl DerefMut<Target = Data> + '_ { self.resource_mut() }
|
pub fn data_mut(&self) -> impl DerefMut<Target = Data> + '_ { self.resource_mut() }
|
||||||
|
|
||||||
|
pub fn get_data_mut(&mut self) -> &mut Data { self.get_resource_mut() }
|
||||||
|
|
||||||
pub fn resource<R: Send + Sync + 'static>(&self) -> impl Deref<Target = R> + '_ {
|
pub fn resource<R: Send + Sync + 'static>(&self) -> impl Deref<Target = R> + '_ {
|
||||||
self.resources
|
self.resources
|
||||||
.get::<AtomicRefCell<R>>()
|
.get::<AtomicRefCell<R>>()
|
||||||
@ -126,6 +131,18 @@ impl RtState {
|
|||||||
.borrow()
|
.borrow()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn get_resource_mut<R: Send + Sync + 'static>(&mut self) -> &mut R {
|
||||||
|
self.resources
|
||||||
|
.get_mut::<AtomicRefCell<R>>()
|
||||||
|
.unwrap_or_else(|| {
|
||||||
|
panic!(
|
||||||
|
"Tried to access resource '{}' but it does not exist",
|
||||||
|
type_name::<R>()
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.get_mut()
|
||||||
|
}
|
||||||
|
|
||||||
pub fn resource_mut<R: Send + Sync + 'static>(&self) -> impl DerefMut<Target = R> + '_ {
|
pub fn resource_mut<R: Send + Sync + 'static>(&self) -> impl DerefMut<Target = R> + '_ {
|
||||||
self.resources
|
self.resources
|
||||||
.get::<AtomicRefCell<R>>()
|
.get::<AtomicRefCell<R>>()
|
||||||
@ -139,6 +156,8 @@ impl RtState {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn emit<E: Event>(&mut self, e: E, world: &World, index: IndexRef) {
|
pub fn emit<E: Event>(&mut self, e: E, world: &World, index: IndexRef) {
|
||||||
|
// TODO: Queue these events up and handle them on a regular rtsim tick instead
|
||||||
|
// of executing their handlers immediately.
|
||||||
if let Some(handlers) = self.event_handlers.get::<EventHandlersOf<E>>() {
|
if let Some(handlers) = self.event_handlers.get::<EventHandlersOf<E>>() {
|
||||||
handlers.iter().for_each(|f| f(self, world, index, &e));
|
handlers.iter().for_each(|f| f(self, world, index, &e));
|
||||||
}
|
}
|
||||||
|
@ -20,13 +20,13 @@ impl Rule for CleanUp {
|
|||||||
let data = &mut *ctx.state.data_mut();
|
let data = &mut *ctx.state.data_mut();
|
||||||
let mut rng = ChaChaRng::from_seed(thread_rng().gen::<[u8; 32]>());
|
let mut rng = ChaChaRng::from_seed(thread_rng().gen::<[u8; 32]>());
|
||||||
|
|
||||||
for (_, npc) in data.npcs
|
// TODO: Use `.into_par_iter()` for these by implementing rayon traits in upstream slotmap.
|
||||||
|
|
||||||
|
data.npcs
|
||||||
.iter_mut()
|
.iter_mut()
|
||||||
// Only cleanup NPCs every few ticks
|
// Only cleanup NPCs every few ticks
|
||||||
.filter(|(_, npc)| (npc.seed as u64 + ctx.event.tick) % NPC_SENTIMENT_TICK_SKIP == 0)
|
.filter(|(_, npc)| (npc.seed as u64 + ctx.event.tick) % NPC_SENTIMENT_TICK_SKIP == 0)
|
||||||
{
|
.for_each(|(_, npc)| npc.sentiments.decay(&mut rng, ctx.event.dt * NPC_SENTIMENT_TICK_SKIP as f32));
|
||||||
npc.sentiments.decay(&mut rng, ctx.event.dt * NPC_SENTIMENT_TICK_SKIP as f32);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Clean up entities
|
// Clean up entities
|
||||||
data.npcs
|
data.npcs
|
||||||
|
@ -2,6 +2,7 @@ use crate::{data::Site, event::OnSetup, RtState, Rule, RuleError};
|
|||||||
use rand::prelude::*;
|
use rand::prelude::*;
|
||||||
use rand_chacha::ChaChaRng;
|
use rand_chacha::ChaChaRng;
|
||||||
use tracing::warn;
|
use tracing::warn;
|
||||||
|
use world::site::SiteKind;
|
||||||
|
|
||||||
/// This rule runs at rtsim startup and broadly acts to perform some primitive
|
/// This rule runs at rtsim startup and broadly acts to perform some primitive
|
||||||
/// migration/sanitisation in order to ensure that the state of rtsim is mostly
|
/// migration/sanitisation in order to ensure that the state of rtsim is mostly
|
||||||
@ -36,11 +37,6 @@ impl Rule for Migrate {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
for npc in data.npcs.values_mut() {
|
|
||||||
// TODO: Consider what to do with homeless npcs.
|
|
||||||
npc.home = npc.home.filter(|home| data.sites.contains_key(*home));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Generate rtsim sites for world sites that don't have a corresponding rtsim
|
// Generate rtsim sites for world sites that don't have a corresponding rtsim
|
||||||
// site yet
|
// site yet
|
||||||
for (world_site_id, _) in ctx.index.sites.iter() {
|
for (world_site_id, _) in ctx.index.sites.iter() {
|
||||||
@ -65,7 +61,25 @@ impl Rule for Migrate {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Reassign sites for NPCs if they don't have one
|
// Reassign NPCs to sites if their old one was deleted. If they were already homeless, no need to do anything.
|
||||||
|
for npc in data.npcs.values_mut() {
|
||||||
|
if let Some(home) = npc.home
|
||||||
|
&& !data.sites.contains_key(home)
|
||||||
|
{
|
||||||
|
// Choose the closest habitable site as the new home for the NPC
|
||||||
|
npc.home = data.sites.sites
|
||||||
|
.iter()
|
||||||
|
.filter(|(_, site)| {
|
||||||
|
// TODO: This is a bit silly, but needs to wait on the removal of site1
|
||||||
|
site.world_site.map_or(false, |ws| matches!(&ctx.index.sites.get(ws).kind, SiteKind::Refactor(_)
|
||||||
|
| SiteKind::CliffTown(_)
|
||||||
|
| SiteKind::SavannahPit(_)
|
||||||
|
| SiteKind::DesertCity(_)))
|
||||||
|
})
|
||||||
|
.min_by_key(|(_, site)| site.wpos.as_().distance_squared(npc.wpos.xy()) as i32)
|
||||||
|
.map(|(site_id, _)| site_id);
|
||||||
|
}
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
Ok(Self)
|
Ok(Self)
|
||||||
|
@ -469,24 +469,27 @@ fn timeout(time: f64) -> impl FnMut(&mut NpcCtx) -> bool + Clone + Send + Sync {
|
|||||||
fn socialize() -> impl Action {
|
fn socialize() -> impl Action {
|
||||||
now(|ctx| {
|
now(|ctx| {
|
||||||
// TODO: Bit odd, should wait for a while after greeting
|
// TODO: Bit odd, should wait for a while after greeting
|
||||||
if ctx.rng.gen_bool(0.002) && let Some(other) = ctx
|
if ctx.rng.gen_bool(0.002) {
|
||||||
.state
|
if ctx.rng.gen_bool(0.15) {
|
||||||
.data()
|
return just(|ctx| ctx.controller.do_dance())
|
||||||
.npcs
|
.repeat()
|
||||||
.nearby(Some(ctx.npc_id), ctx.npc.wpos, 8.0)
|
.stop_if(timeout(6.0))
|
||||||
.choose(&mut ctx.rng)
|
.debug(|| "dancing")
|
||||||
{
|
.map(|_| ())
|
||||||
just(move |ctx| ctx.controller.say(other, "npc-speech-villager_open")).boxed()
|
.boxed();
|
||||||
} else if ctx.rng.gen_bool(0.0003) {
|
} else if let Some(other) = ctx
|
||||||
just(|ctx| ctx.controller.do_dance())
|
.state
|
||||||
.repeat()
|
.data()
|
||||||
.stop_if(timeout(6.0))
|
.npcs
|
||||||
.debug(|| "dancing")
|
.nearby(Some(ctx.npc_id), ctx.npc.wpos, 8.0)
|
||||||
.map(|_| ())
|
.choose(&mut ctx.rng)
|
||||||
.boxed()
|
{
|
||||||
} else {
|
return just(move |ctx| ctx.controller.say(other, "npc-speech-villager_open"))
|
||||||
idle().boxed()
|
.boxed();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
idle().boxed()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -914,6 +917,12 @@ fn check_inbox(ctx: &mut NpcCtx) -> Option<impl Action> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn check_for_enemies(ctx: &mut NpcCtx) -> Option<impl Action> {
|
fn check_for_enemies(ctx: &mut NpcCtx) -> Option<impl Action> {
|
||||||
|
// TODO: Instead of checking all nearby actors every tick, it would be more
|
||||||
|
// effective to have the actor grid generate a per-tick diff so that we only
|
||||||
|
// need to check new actors in the local area. Be careful though:
|
||||||
|
// implementing this means accounting for changes in sentiment (that could
|
||||||
|
// suddenly make a nearby actor an enemy) as well as variable NPC tick
|
||||||
|
// rates!
|
||||||
ctx.state
|
ctx.state
|
||||||
.data()
|
.data()
|
||||||
.npcs
|
.npcs
|
||||||
|
@ -9,7 +9,11 @@ pub struct ReplenishResources;
|
|||||||
// TODO: Non-renewable resources?
|
// TODO: Non-renewable resources?
|
||||||
pub const REPLENISH_TIME: f32 = 60.0 * 60.0;
|
pub const REPLENISH_TIME: f32 = 60.0 * 60.0;
|
||||||
/// How many chunks should be replenished per tick?
|
/// How many chunks should be replenished per tick?
|
||||||
pub const REPLENISH_PER_TICK: usize = 100000;
|
// TODO: It should be possible to optimise this be remembering the last
|
||||||
|
// modification time for each chunk, then lazily projecting forward using a
|
||||||
|
// closed-form solution to the replenishment to calculate resources in a lazy
|
||||||
|
// manner.
|
||||||
|
pub const REPLENISH_PER_TICK: usize = 8192;
|
||||||
|
|
||||||
impl Rule for ReplenishResources {
|
impl Rule for ReplenishResources {
|
||||||
fn start(rtstate: &mut RtState) -> Result<Self, RuleError> {
|
fn start(rtstate: &mut RtState) -> Result<Self, RuleError> {
|
||||||
@ -19,9 +23,9 @@ impl Rule for ReplenishResources {
|
|||||||
|
|
||||||
// How much should be replenished for each chosen chunk to hit our target
|
// How much should be replenished for each chosen chunk to hit our target
|
||||||
// replenishment rate?
|
// replenishment rate?
|
||||||
let replenish_amount = world_size.product() as f32 * ctx.event.dt
|
let replenish_amount = world_size.product() as f32
|
||||||
/ REPLENISH_TIME
|
* ctx.event.dt
|
||||||
/ REPLENISH_PER_TICK as f32;
|
* (1.0 / REPLENISH_TIME / REPLENISH_PER_TICK as f32);
|
||||||
for _ in 0..REPLENISH_PER_TICK {
|
for _ in 0..REPLENISH_PER_TICK {
|
||||||
let key = world_size.map(|e| thread_rng().gen_range(0..e as i32));
|
let key = world_size.map(|e| thread_rng().gen_range(0..e as i32));
|
||||||
|
|
||||||
|
@ -33,6 +33,9 @@ fn on_death(ctx: EventCtx<ReportEvents, OnDeath>) {
|
|||||||
at: data.time_of_day,
|
at: data.time_of_day,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// TODO: Don't push report to NPC inboxes, have a dedicated data structure that
|
||||||
|
// tracks reports by chunks and then have NPCs decide to query this
|
||||||
|
// data structure in their own time.
|
||||||
for npc_id in nearby {
|
for npc_id in nearby {
|
||||||
if let Some(npc) = data.npcs.get_mut(npc_id) {
|
if let Some(npc) = data.npcs.get_mut(npc_id) {
|
||||||
npc.inbox.push_back(report);
|
npc.inbox.push_back(report);
|
||||||
|
@ -52,7 +52,7 @@ fn on_death(ctx: EventCtx<SimulateNpcs, OnDeath>) {
|
|||||||
let mut rng = ChaChaRng::from_seed(thread_rng().gen::<[u8; 32]>());
|
let mut rng = ChaChaRng::from_seed(thread_rng().gen::<[u8; 32]>());
|
||||||
|
|
||||||
// Respawn dead NPCs
|
// Respawn dead NPCs
|
||||||
match npc.body {
|
let details = match npc.body {
|
||||||
Body::Humanoid(_) => {
|
Body::Humanoid(_) => {
|
||||||
if let Some((site_id, site)) = data
|
if let Some((site_id, site)) = data
|
||||||
.sites
|
.sites
|
||||||
@ -76,15 +76,17 @@ fn on_death(ctx: EventCtx<SimulateNpcs, OnDeath>) {
|
|||||||
let species = comp::humanoid::ALL_SPECIES.choose(&mut *rng).unwrap();
|
let species = comp::humanoid::ALL_SPECIES.choose(&mut *rng).unwrap();
|
||||||
Body::Humanoid(comp::humanoid::Body::random_with(rng, species))
|
Body::Humanoid(comp::humanoid::Body::random_with(rng, species))
|
||||||
};
|
};
|
||||||
data.spawn_npc(
|
let npc_id = data.spawn_npc(
|
||||||
Npc::new(rng.gen(), rand_wpos(&mut rng), random_humanoid(&mut rng))
|
Npc::new(rng.gen(), rand_wpos(&mut rng), random_humanoid(&mut rng))
|
||||||
.with_personality(Personality::random(&mut rng))
|
.with_personality(Personality::random(&mut rng))
|
||||||
.with_home(site_id)
|
.with_home(site_id)
|
||||||
.with_faction(npc.faction)
|
.with_faction(npc.faction)
|
||||||
.with_profession(npc.profession.clone()),
|
.with_profession(npc.profession.clone()),
|
||||||
);
|
);
|
||||||
|
Some((npc_id, site_id))
|
||||||
} else {
|
} else {
|
||||||
warn!("No site found for respawning humaniod");
|
warn!("No site found for respawning humaniod");
|
||||||
|
None
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
Body::BirdLarge(_) => {
|
Body::BirdLarge(_) => {
|
||||||
@ -112,7 +114,7 @@ fn on_death(ctx: EventCtx<SimulateNpcs, OnDeath>) {
|
|||||||
]
|
]
|
||||||
.choose(&mut rng)
|
.choose(&mut rng)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
data.npcs.create_npc(
|
let npc_id = data.npcs.create_npc(
|
||||||
Npc::new(
|
Npc::new(
|
||||||
rng.gen(),
|
rng.gen(),
|
||||||
rand_wpos(&mut rng),
|
rand_wpos(&mut rng),
|
||||||
@ -122,11 +124,23 @@ fn on_death(ctx: EventCtx<SimulateNpcs, OnDeath>) {
|
|||||||
)
|
)
|
||||||
.with_home(site_id),
|
.with_home(site_id),
|
||||||
);
|
);
|
||||||
|
Some((npc_id, site_id))
|
||||||
} else {
|
} else {
|
||||||
warn!("No site found for respawning bird");
|
warn!("No site found for respawning bird");
|
||||||
|
None
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
_ => unimplemented!(),
|
body => {
|
||||||
|
error!("Tried to respawn rtsim NPC with invalid body: {:?}", body);
|
||||||
|
None
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
// Add the NPC to their home site
|
||||||
|
if let Some((npc_id, home_site)) = details {
|
||||||
|
if let Some(home) = data.sites.get_mut(home_site) {
|
||||||
|
home.population.insert(npc_id);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -22,7 +22,7 @@ fn on_setup(ctx: EventCtx<SyncNpcs, OnSetup>) {
|
|||||||
// Create NPC grid
|
// Create NPC grid
|
||||||
data.npcs.npc_grid = Grid::new(ctx.world.sim().get_size().as_(), Default::default());
|
data.npcs.npc_grid = Grid::new(ctx.world.sim().get_size().as_(), Default::default());
|
||||||
|
|
||||||
// Add NPCs to home population (TODO: Do this on entity creation?)
|
// Add NPCs to home population
|
||||||
for (npc_id, npc) in data.npcs.npcs.iter() {
|
for (npc_id, npc) in data.npcs.npcs.iter() {
|
||||||
if let Some(home) = npc.home.and_then(|home| data.sites.get_mut(home)) {
|
if let Some(home) = npc.home.and_then(|home| data.sites.get_mut(home)) {
|
||||||
home.population.insert(npc_id);
|
home.population.insert(npc_id);
|
||||||
|
@ -1329,6 +1329,8 @@ fn handle_rtsim_npc(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: Remove this command when rtsim becomes more mature and we're sure we
|
||||||
|
// don't need purges to fix broken state.
|
||||||
fn handle_rtsim_purge(
|
fn handle_rtsim_purge(
|
||||||
server: &mut Server,
|
server: &mut Server,
|
||||||
client: EcsEntity,
|
client: EcsEntity,
|
||||||
@ -1337,6 +1339,14 @@ fn handle_rtsim_purge(
|
|||||||
action: &ServerChatCommand,
|
action: &ServerChatCommand,
|
||||||
) -> CmdResult<()> {
|
) -> CmdResult<()> {
|
||||||
use crate::rtsim::RtSim;
|
use crate::rtsim::RtSim;
|
||||||
|
let client_uuid = uuid(server, client, "client")?;
|
||||||
|
if !matches!(real_role(server, client_uuid, "client")?, AdminRole::Admin) {
|
||||||
|
return Err(
|
||||||
|
"You must be a real admin (not just a temporary admin) to purge rtsim data."
|
||||||
|
.to_string(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
if let Some(should_purge) = parse_cmd_args!(args, bool) {
|
if let Some(should_purge) = parse_cmd_args!(args, bool) {
|
||||||
server
|
server
|
||||||
.state
|
.state
|
||||||
@ -2082,6 +2092,7 @@ fn handle_kill_npcs(
|
|||||||
let healths = ecs.write_storage::<comp::Health>();
|
let healths = ecs.write_storage::<comp::Health>();
|
||||||
let players = ecs.read_storage::<comp::Player>();
|
let players = ecs.read_storage::<comp::Player>();
|
||||||
let alignments = ecs.read_storage::<Alignment>();
|
let alignments = ecs.read_storage::<Alignment>();
|
||||||
|
let rtsim_entities = ecs.read_storage::<common::rtsim::RtSimEntity>();
|
||||||
|
|
||||||
(
|
(
|
||||||
&entities,
|
&entities,
|
||||||
@ -2101,11 +2112,7 @@ fn handle_kill_npcs(
|
|||||||
};
|
};
|
||||||
|
|
||||||
if should_kill {
|
if should_kill {
|
||||||
if let Some(rtsim_entity) = ecs
|
if let Some(rtsim_entity) = rtsim_entities.get(entity).copied() {
|
||||||
.read_storage::<common::rtsim::RtSimEntity>()
|
|
||||||
.get(entity)
|
|
||||||
.copied()
|
|
||||||
{
|
|
||||||
ecs.write_resource::<crate::rtsim::RtSim>()
|
ecs.write_resource::<crate::rtsim::RtSim>()
|
||||||
.hook_rtsim_actor_death(
|
.hook_rtsim_actor_death(
|
||||||
&ecs.read_resource::<Arc<world::World>>(),
|
&ecs.read_resource::<Arc<world::World>>(),
|
||||||
|
@ -195,7 +195,7 @@ pub fn handle_create_ship(
|
|||||||
ship: comp::ship::Body,
|
ship: comp::ship::Body,
|
||||||
rtsim_vehicle: Option<RtSimVehicle>,
|
rtsim_vehicle: Option<RtSimVehicle>,
|
||||||
driver: Option<NpcBuilder>,
|
driver: Option<NpcBuilder>,
|
||||||
passangers: Vec<NpcBuilder>,
|
passengers: Vec<NpcBuilder>,
|
||||||
) {
|
) {
|
||||||
let mut entity = server
|
let mut entity = server
|
||||||
.state
|
.state
|
||||||
@ -234,8 +234,8 @@ pub fn handle_create_ship(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for passanger in passangers {
|
for passenger in passengers {
|
||||||
handle_create_npc(server, Pos(pos.0 + Vec3::unit_z() * 5.0), passanger);
|
handle_create_npc(server, Pos(pos.0 + Vec3::unit_z() * 5.0), passenger);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -80,7 +80,7 @@ use common::{
|
|||||||
rtsim::{RtSimEntity, RtSimVehicle},
|
rtsim::{RtSimEntity, RtSimVehicle},
|
||||||
shared_server_config::ServerConstants,
|
shared_server_config::ServerConstants,
|
||||||
slowjob::SlowJobPool,
|
slowjob::SlowJobPool,
|
||||||
terrain::{Block, TerrainChunk, TerrainChunkSize},
|
terrain::{TerrainChunk, TerrainChunkSize},
|
||||||
vol::RectRasterableVol,
|
vol::RectRasterableVol,
|
||||||
};
|
};
|
||||||
use common_ecs::run_now;
|
use common_ecs::run_now;
|
||||||
@ -88,7 +88,7 @@ use common_net::{
|
|||||||
msg::{ClientType, DisconnectReason, ServerGeneral, ServerInfo, ServerMsg},
|
msg::{ClientType, DisconnectReason, ServerGeneral, ServerInfo, ServerMsg},
|
||||||
sync::WorldSyncExt,
|
sync::WorldSyncExt,
|
||||||
};
|
};
|
||||||
use common_state::{BuildAreas, State};
|
use common_state::{BlockDiff, BuildAreas, State};
|
||||||
use common_systems::add_local_systems;
|
use common_systems::add_local_systems;
|
||||||
use metrics::{EcsSystemMetrics, PhysicsMetrics, TickMetrics};
|
use metrics::{EcsSystemMetrics, PhysicsMetrics, TickMetrics};
|
||||||
use network::{ListenAddr, Network, Pid};
|
use network::{ListenAddr, Network, Pid};
|
||||||
@ -698,21 +698,15 @@ impl Server {
|
|||||||
|
|
||||||
let before_state_tick = Instant::now();
|
let before_state_tick = Instant::now();
|
||||||
|
|
||||||
fn on_block_update(
|
fn on_block_update(ecs: &specs::World, changes: Vec<BlockDiff>) {
|
||||||
ecs: &specs::World,
|
|
||||||
wpos: Vec3<i32>,
|
|
||||||
old_block: Block,
|
|
||||||
new_block: Block,
|
|
||||||
) {
|
|
||||||
// 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 changes.iter().any(|c| {
|
||||||
{
|
c.old.get_rtsim_resource().is_some() || c.new.get_rtsim_resource().is_some()
|
||||||
|
}) {
|
||||||
ecs.write_resource::<rtsim::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,
|
changes,
|
||||||
old_block,
|
|
||||||
new_block,
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -838,35 +832,26 @@ impl Server {
|
|||||||
.collect::<Vec<_>>()
|
.collect::<Vec<_>>()
|
||||||
};
|
};
|
||||||
|
|
||||||
for entity in to_delete {
|
{
|
||||||
// Assimilate entities that are part of the real-time world simulation
|
let mut rtsim = self.state.ecs().write_resource::<rtsim::RtSim>();
|
||||||
#[cfg(feature = "worldgen")]
|
let rtsim_entities = self.state.ecs().read_storage::<RtSimEntity>();
|
||||||
if let Some(rtsim_entity) = self
|
let rtsim_vehicles = self.state.ecs().read_storage::<RtSimVehicle>();
|
||||||
.state
|
|
||||||
.ecs()
|
|
||||||
.read_storage::<RtSimEntity>()
|
|
||||||
.get(entity)
|
|
||||||
.copied()
|
|
||||||
{
|
|
||||||
self.state
|
|
||||||
.ecs()
|
|
||||||
.write_resource::<rtsim::RtSim>()
|
|
||||||
.hook_rtsim_entity_unload(rtsim_entity);
|
|
||||||
}
|
|
||||||
#[cfg(feature = "worldgen")]
|
|
||||||
if let Some(rtsim_vehicle) = self
|
|
||||||
.state
|
|
||||||
.ecs()
|
|
||||||
.read_storage::<RtSimVehicle>()
|
|
||||||
.get(entity)
|
|
||||||
.copied()
|
|
||||||
{
|
|
||||||
self.state
|
|
||||||
.ecs()
|
|
||||||
.write_resource::<rtsim::RtSim>()
|
|
||||||
.hook_rtsim_vehicle_unload(rtsim_vehicle);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
// Assimilate entities that are part of the real-time world simulation
|
||||||
|
for entity in &to_delete {
|
||||||
|
#[cfg(feature = "worldgen")]
|
||||||
|
if let Some(rtsim_entity) = rtsim_entities.get(*entity) {
|
||||||
|
rtsim.hook_rtsim_entity_unload(*rtsim_entity);
|
||||||
|
}
|
||||||
|
#[cfg(feature = "worldgen")]
|
||||||
|
if let Some(rtsim_vehicle) = rtsim_vehicles.get(*entity) {
|
||||||
|
rtsim.hook_rtsim_vehicle_unload(*rtsim_vehicle);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Actually perform entity deletion
|
||||||
|
for entity in to_delete {
|
||||||
if let Err(e) = self.state.delete_entity_recorded(entity) {
|
if let Err(e) = self.state.delete_entity_recorded(entity) {
|
||||||
error!(?e, "Failed to delete agent outside the terrain");
|
error!(?e, "Failed to delete agent outside the terrain");
|
||||||
}
|
}
|
||||||
|
@ -1,12 +1,9 @@
|
|||||||
use common::terrain::Block;
|
use common_state::BlockDiff;
|
||||||
use rtsim::Event;
|
use rtsim::Event;
|
||||||
use vek::*;
|
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct OnBlockChange {
|
pub struct OnBlockChange {
|
||||||
pub wpos: Vec3<i32>,
|
pub changes: Vec<BlockDiff>,
|
||||||
pub old: Block,
|
|
||||||
pub new: Block,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Event for OnBlockChange {}
|
impl Event for OnBlockChange {}
|
||||||
|
@ -6,13 +6,13 @@ use atomicwrites::{AtomicFile, OverwriteBehavior};
|
|||||||
use common::{
|
use common::{
|
||||||
grid::Grid,
|
grid::Grid,
|
||||||
rtsim::{Actor, ChunkResource, RtSimEntity, RtSimVehicle, WorldSettings},
|
rtsim::{Actor, ChunkResource, RtSimEntity, RtSimVehicle, WorldSettings},
|
||||||
terrain::Block,
|
|
||||||
};
|
};
|
||||||
use common_ecs::dispatch;
|
use common_ecs::dispatch;
|
||||||
|
use common_state::BlockDiff;
|
||||||
use crossbeam_channel::{unbounded, Receiver, Sender};
|
use crossbeam_channel::{unbounded, Receiver, Sender};
|
||||||
use enum_map::EnumMap;
|
use enum_map::EnumMap;
|
||||||
use rtsim::{
|
use rtsim::{
|
||||||
data::{npc::SimulationMode, Data},
|
data::{npc::SimulationMode, Data, ReadError},
|
||||||
event::{OnDeath, OnSetup},
|
event::{OnDeath, OnSetup},
|
||||||
RtState,
|
RtState,
|
||||||
};
|
};
|
||||||
@ -51,8 +51,17 @@ impl RtSim {
|
|||||||
match File::open(&file_path) {
|
match File::open(&file_path) {
|
||||||
Ok(file) => {
|
Ok(file) => {
|
||||||
info!("Rtsim data found. Attempting to load...");
|
info!("Rtsim data found. Attempting to load...");
|
||||||
|
|
||||||
|
let ignore_version = std::env::var("RTSIM_IGNORE_VERSION").is_ok();
|
||||||
|
|
||||||
match Data::from_reader(io::BufReader::new(file)) {
|
match Data::from_reader(io::BufReader::new(file)) {
|
||||||
Ok(data) => {
|
Err(ReadError::VersionMismatch(_)) if !ignore_version => {
|
||||||
|
warn!(
|
||||||
|
"Rtsim data version mismatch (implying a breaking change), \
|
||||||
|
rtsim data will be purged"
|
||||||
|
);
|
||||||
|
},
|
||||||
|
Ok(data) | Err(ReadError::VersionMismatch(data)) => {
|
||||||
info!("Rtsim data loaded.");
|
info!("Rtsim data loaded.");
|
||||||
if data.should_purge {
|
if data.should_purge {
|
||||||
warn!(
|
warn!(
|
||||||
@ -60,11 +69,11 @@ impl RtSim {
|
|||||||
generating afresh"
|
generating afresh"
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
break 'load data;
|
break 'load *data;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
Err(e) => {
|
Err(ReadError::Load(err)) => {
|
||||||
error!("Rtsim data failed to load: {}", e);
|
error!("Rtsim data failed to load: {}", err);
|
||||||
info!("Old rtsim data will now be moved to a backup file");
|
info!("Old rtsim data will now be moved to a backup file");
|
||||||
let mut i = 0;
|
let mut i = 0;
|
||||||
loop {
|
loop {
|
||||||
@ -139,37 +148,30 @@ impl RtSim {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn hook_load_chunk(&mut self, key: Vec2<i32>, max_res: EnumMap<ChunkResource, usize>) {
|
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) {
|
if let Some(chunk_state) = self.state.get_resource_mut::<ChunkStates>().0.get_mut(key) {
|
||||||
*chunk_state = Some(LoadedChunkState { max_res });
|
*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_state) = self.state.resource_mut::<ChunkStates>().0.get_mut(key) {
|
if let Some(chunk_state) = self.state.get_resource_mut::<ChunkStates>().0.get_mut(key) {
|
||||||
*chunk_state = None;
|
*chunk_state = None;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn hook_block_update(
|
pub fn hook_block_update(&mut self, world: &World, index: IndexRef, changes: Vec<BlockDiff>) {
|
||||||
&mut self,
|
|
||||||
world: &World,
|
|
||||||
index: IndexRef,
|
|
||||||
wpos: Vec3<i32>,
|
|
||||||
old: Block,
|
|
||||||
new: Block,
|
|
||||||
) {
|
|
||||||
self.state
|
self.state
|
||||||
.emit(event::OnBlockChange { wpos, old, new }, world, index);
|
.emit(event::OnBlockChange { changes }, world, index);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn hook_rtsim_entity_unload(&mut self, entity: RtSimEntity) {
|
pub fn hook_rtsim_entity_unload(&mut self, entity: RtSimEntity) {
|
||||||
if let Some(npc) = self.state.data_mut().npcs.get_mut(entity.0) {
|
if let Some(npc) = self.state.get_data_mut().npcs.get_mut(entity.0) {
|
||||||
npc.mode = SimulationMode::Simulated;
|
npc.mode = SimulationMode::Simulated;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn hook_rtsim_vehicle_unload(&mut self, entity: RtSimVehicle) {
|
pub fn hook_rtsim_vehicle_unload(&mut self, entity: RtSimVehicle) {
|
||||||
if let Some(vehicle) = self.state.data_mut().npcs.vehicles.get_mut(entity.0) {
|
if let Some(vehicle) = self.state.get_data_mut().npcs.vehicles.get_mut(entity.0) {
|
||||||
vehicle.mode = SimulationMode::Simulated;
|
vehicle.mode = SimulationMode::Simulated;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -7,32 +7,35 @@ pub struct DepleteResources;
|
|||||||
impl Rule for DepleteResources {
|
impl Rule for DepleteResources {
|
||||||
fn start(rtstate: &mut RtState) -> Result<Self, RuleError> {
|
fn start(rtstate: &mut RtState) -> Result<Self, RuleError> {
|
||||||
rtstate.bind::<Self, OnBlockChange>(|ctx| {
|
rtstate.bind::<Self, OnBlockChange>(|ctx| {
|
||||||
let key = ctx.event.wpos.xy().wpos_to_cpos();
|
let chunk_states = ctx.state.resource::<ChunkStates>();
|
||||||
if let Some(Some(chunk_state)) = ctx.state.resource_mut::<ChunkStates>().0.get(key) {
|
let mut data = ctx.state.data_mut();
|
||||||
let mut chunk_res = ctx.state.data().nature.get_chunk_resources(key);
|
for change in &ctx.event.changes {
|
||||||
// Remove resources
|
let key = change.wpos.xy().wpos_to_cpos();
|
||||||
if let Some(res) = ctx.event.old.get_rtsim_resource() {
|
if let Some(Some(chunk_state)) = chunk_states.0.get(key) {
|
||||||
if chunk_state.max_res[res] > 0 {
|
let mut chunk_res = data.nature.get_chunk_resources(key);
|
||||||
chunk_res[res] = (chunk_res[res] * chunk_state.max_res[res] as f32 - 1.0)
|
// Remove resources
|
||||||
.round()
|
if let Some(res) = change.old.get_rtsim_resource() {
|
||||||
.max(0.0)
|
if chunk_state.max_res[res] > 0 {
|
||||||
/ chunk_state.max_res[res] as f32;
|
chunk_res[res] = (chunk_res[res] * chunk_state.max_res[res] as f32
|
||||||
|
- 1.0)
|
||||||
|
.round()
|
||||||
|
.max(0.0)
|
||||||
|
/ chunk_state.max_res[res] as f32;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
// Replenish resources
|
||||||
// Replenish resources
|
if let Some(res) = change.new.get_rtsim_resource() {
|
||||||
if let Some(res) = ctx.event.new.get_rtsim_resource() {
|
if chunk_state.max_res[res] > 0 {
|
||||||
if chunk_state.max_res[res] > 0 {
|
chunk_res[res] = (chunk_res[res] * chunk_state.max_res[res] as f32
|
||||||
chunk_res[res] = (chunk_res[res] * chunk_state.max_res[res] as f32 + 1.0)
|
+ 1.0)
|
||||||
.round()
|
.round()
|
||||||
.max(0.0)
|
.max(0.0)
|
||||||
/ chunk_state.max_res[res] as f32;
|
/ chunk_state.max_res[res] as f32;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
ctx.state
|
data.nature.set_chunk_resources(key, chunk_res);
|
||||||
.data_mut()
|
}
|
||||||
.nature
|
|
||||||
.set_chunk_resources(key, chunk_res);
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user