This commit is contained in:
Joshua Barretto 2022-09-03 10:47:18 +01:00
parent 3a52cc1fa3
commit e8b489a71a
37 changed files with 435 additions and 315 deletions

View File

@ -9,8 +9,8 @@ use common::{
character_state::OutputEvents,
inventory::item::{tool::AbilityMap, MaterialStatManifest},
ActiveAbilities, Beam, Body, CharacterState, Combo, Controller, Density, Energy, Health,
Inventory, InventoryManip, Mass, Melee, Ori, PhysicsState, Poise, Pos, SkillSet, Stance,
StateUpdate, Stats, Vel, Scale,
Inventory, InventoryManip, Mass, Melee, Ori, PhysicsState, Poise, Pos, Scale, SkillSet, Stance,
StateUpdate, Stats, Vel,
},
event::{EventBus, LocalEvent, ServerEvent},
link::Is,

View File

@ -1,5 +1,5 @@
use common::{
comp::{Body, Controller, InputKind, Ori, Pos, Vel, Scale},
comp::{Body, Controller, InputKind, Ori, Pos, Scale, Vel},
link::Is,
mounting::Mount,
uid::UidAllocator,
@ -69,8 +69,10 @@ impl<'a> System<'a> for Sys {
let vel = velocities.get(entity).copied();
if let (Some(pos), Some(ori), Some(vel)) = (pos, ori, vel) {
let mounter_body = bodies.get(rider);
let mounting_offset = body.map_or(Vec3::unit_z(), Body::mount_offset) * scales.get(entity).map_or(1.0, |s| s.0)
+ mounter_body.map_or(Vec3::zero(), Body::rider_offset) * scales.get(rider).map_or(1.0, |s| s.0);
let mounting_offset = body.map_or(Vec3::unit_z(), Body::mount_offset)
* scales.get(entity).map_or(1.0, |s| s.0)
+ mounter_body.map_or(Vec3::zero(), Body::rider_offset)
* scales.get(rider).map_or(1.0, |s| s.0);
let _ = positions.insert(rider, Pos(pos.0 + ori.to_quat() * mounting_offset));
let _ = orientations.insert(rider, ori);
let _ = velocities.insert(rider, vel);

View File

@ -62,7 +62,13 @@ fn integrate_forces(
// Aerodynamic/hydrodynamic forces
if !rel_flow.0.is_approx_zero() {
debug_assert!(!rel_flow.0.map(|a| a.is_nan()).reduce_or());
let impulse = dt.0 * body.aerodynamic_forces(&rel_flow, fluid_density.0, wings, scale.map_or(1.0, |s| s.0));
let impulse = dt.0
* body.aerodynamic_forces(
&rel_flow,
fluid_density.0,
wings,
scale.map_or(1.0, |s| s.0),
);
debug_assert!(!impulse.map(|a| a.is_nan()).reduce_or());
if !impulse.is_approx_zero() {
let new_v = vel.0 + impulse / mass.0;

View File

@ -1,14 +1,11 @@
use hashbrown::HashMap;
use serde::{Serialize, Deserialize};
use slotmap::HopSlotMap;
use vek::*;
use std::ops::{Deref, DerefMut};
use common::{
uid::Uid,
store::Id,
};
use super::Actor;
pub use common::rtsim::FactionId;
use common::{store::Id, uid::Uid};
use hashbrown::HashMap;
use serde::{Deserialize, Serialize};
use slotmap::HopSlotMap;
use std::ops::{Deref, DerefMut};
use vek::*;
#[derive(Clone, Serialize, Deserialize)]
pub struct Faction {
@ -21,13 +18,12 @@ pub struct Factions {
}
impl Factions {
pub fn create(&mut self, faction: Faction) -> FactionId {
self.factions.insert(faction)
}
pub fn create(&mut self, faction: Faction) -> FactionId { self.factions.insert(faction) }
}
impl Deref for Factions {
type Target = HopSlotMap<FactionId, Faction>;
fn deref(&self) -> &Self::Target { &self.factions }
}

View File

@ -1,23 +1,26 @@
pub mod faction;
pub mod nature;
pub mod npc;
pub mod site;
pub mod nature;
pub use self::{
npc::{Npc, NpcId, Npcs},
site::{Site, SiteId, Sites},
faction::{Faction, FactionId, Factions},
nature::Nature,
npc::{Npc, NpcId, Npcs},
site::{Site, SiteId, Sites},
};
use common::resources::TimeOfDay;
use enum_map::{EnumMap, EnumArray, enum_map};
use serde::{Serialize, Deserialize, ser, de};
use enum_map::{enum_map, EnumArray, EnumMap};
use serde::{
de::{self, Error as _},
ser, Deserialize, Serialize,
};
use std::{
io::{Read, Write},
marker::PhantomData,
cmp::PartialEq,
fmt,
io::{Read, Write},
marker::PhantomData,
};
#[derive(Copy, Clone, Serialize, Deserialize)]
@ -54,11 +57,15 @@ fn rugged_ser_enum_map<
V: From<i16> + PartialEq + Serialize,
S: ser::Serializer,
const DEFAULT: i16,
>(map: &EnumMap<K, V>, ser: S) -> Result<S::Ok, S::Error> {
ser.collect_map(map
.iter()
.filter(|(k, v)| v != &&V::from(DEFAULT))
.map(|(k, v)| (k, v)))
>(
map: &EnumMap<K, V>,
ser: S,
) -> Result<S::Ok, S::Error> {
ser.collect_map(
map.iter()
.filter(|(k, v)| v != &&V::from(DEFAULT))
.map(|(k, v)| (k, v)),
)
}
fn rugged_de_enum_map<
@ -67,7 +74,9 @@ fn rugged_de_enum_map<
V: From<i16> + Deserialize<'a>,
D: de::Deserializer<'a>,
const DEFAULT: i16,
>(mut de: D) -> Result<EnumMap<K, V>, D::Error> {
>(
mut de: D,
) -> Result<EnumMap<K, V>, D::Error> {
struct Visitor<K, V, const DEFAULT: i16>(PhantomData<(K, V)>);
impl<'de, K, V, const DEFAULT: i16> de::Visitor<'de> for Visitor<K, V, DEFAULT>

View File

@ -1,17 +1,15 @@
use serde::{Serialize, Deserialize};
use common::{grid::Grid, rtsim::ChunkResource};
use enum_map::EnumMap;
use common::{
grid::Grid,
rtsim::ChunkResource,
};
use world::World;
use serde::{Deserialize, Serialize};
use vek::*;
use world::World;
/// Represents the state of 'natural' elements of the world such as plant/animal/resource populations, weather systems,
/// etc.
/// Represents the state of 'natural' elements of the world such as
/// plant/animal/resource populations, weather systems, etc.
///
/// Where possible, this data does not define the state of natural aspects of the world, but instead defines
/// 'modifications' that sit on top of the world data generated by initial generation.
/// Where possible, this data does not define the state of natural aspects of
/// the world, but instead defines 'modifications' that sit on top of the world
/// data generated by initial generation.
#[derive(Clone, Serialize, Deserialize)]
pub struct Nature {
chunks: Grid<Chunk>,
@ -20,22 +18,17 @@ pub struct Nature {
impl Nature {
pub fn generate(world: &World) -> Self {
Self {
chunks: Grid::populate_from(
world.sim().get_size().map(|e| e as i32),
|pos| Chunk {
res: EnumMap::<_, f32>::default().map(|_, _| 1.0),
},
),
chunks: Grid::populate_from(world.sim().get_size().map(|e| e as i32), |pos| Chunk {
res: EnumMap::<_, f32>::default().map(|_, _| 1.0),
}),
}
}
// TODO: Clean up this API a bit
pub fn get_chunk_resources(&self, key: Vec2<i32>) -> EnumMap<ChunkResource, f32> {
self.chunks
.get(key)
.map(|c| c.res)
.unwrap_or_default()
self.chunks.get(key).map(|c| c.res).unwrap_or_default()
}
pub fn set_chunk_resources(&mut self, key: Vec2<i32>, res: EnumMap<ChunkResource, f32>) {
if let Some(chunk) = self.chunks.get_mut(key) {
chunk.res = res;
@ -45,16 +38,21 @@ impl Nature {
#[derive(Clone, Serialize, Deserialize)]
pub struct Chunk {
/// Represent the 'naturally occurring' resource proportion that exists in this chunk.
/// Represent the 'naturally occurring' resource proportion that exists in
/// this chunk.
///
/// 0.0 => None of the resources generated by terrain generation should be present
/// 1.0 => All of the resources generated by terrain generation should be present
/// 0.0 => None of the resources generated by terrain generation should be
/// present 1.0 => All of the resources generated by terrain generation
/// should be present
///
/// It's important to understand this this number does not represent the total amount of a resource present in a
/// chunk, nor is it even proportional to the amount of the resource present. To get the total amount of the
/// resource in a chunk, one must first multiply this factor by the amount of 'natural' resources given by terrain
/// generation. This value represents only the variable 'depletion' factor of that resource, which shall change
/// over time as the world evolves and players interact with it.
/// It's important to understand this this number does not represent the
/// total amount of a resource present in a chunk, nor is it even
/// proportional to the amount of the resource present. To get the total
/// amount of the resource in a chunk, one must first multiply this
/// factor by the amount of 'natural' resources given by terrain
/// generation. This value represents only the variable 'depletion' factor
/// of that resource, which shall change over time as the world evolves
/// and players interact with it.
#[serde(rename = "r")]
#[serde(serialize_with = "crate::data::rugged_ser_enum_map::<_, _, _, 1>")]
#[serde(deserialize_with = "crate::data::rugged_de_enum_map::<_, _, _, 1>")]

View File

@ -1,18 +1,20 @@
use hashbrown::HashMap;
use serde::{Serialize, Deserialize};
use slotmap::HopSlotMap;
use vek::*;
use rand::prelude::*;
use std::{ops::{Deref, DerefMut}, collections::VecDeque};
use common::{
uid::Uid,
store::Id,
rtsim::{SiteId, FactionId, RtSimController},
comp,
};
use world::{util::RandomPerm, civ::Track};
use world::site::Site as WorldSite;
pub use common::rtsim::{NpcId, Profession};
use common::{
comp,
rtsim::{FactionId, RtSimController, SiteId},
store::Id,
uid::Uid,
};
use hashbrown::HashMap;
use rand::prelude::*;
use serde::{Deserialize, Serialize};
use slotmap::HopSlotMap;
use std::{
collections::VecDeque,
ops::{Deref, DerefMut},
};
use vek::*;
use world::{civ::Track, site::Site as WorldSite, util::RandomPerm};
#[derive(Copy, Clone, Default)]
pub enum NpcMode {
@ -39,7 +41,6 @@ pub struct PathingMemory {
#[derive(Clone, Serialize, Deserialize)]
pub struct Npc {
// Persisted state
/// Represents the location of the NPC.
pub seed: u32,
pub wpos: Vec3<f32>,
@ -58,17 +59,18 @@ pub struct Npc {
/// (wpos, speed_factor)
#[serde(skip_serializing, skip_deserializing)]
pub goto: Option<(Vec3<f32>, f32)>,
/// Whether the NPC is in simulated or loaded mode (when rtsim is run on the server, loaded corresponds to being
/// within a loaded chunk). When in loaded mode, the interactions of the NPC should not be simulated but should
/// instead be derived from the game.
/// Whether the NPC is in simulated or loaded mode (when rtsim is run on the
/// server, loaded corresponds to being within a loaded chunk). When in
/// loaded mode, the interactions of the NPC should not be simulated but
/// should instead be derived from the game.
#[serde(skip_serializing, skip_deserializing)]
pub mode: NpcMode,
}
impl Npc {
const PERM_SPECIES: u32 = 0;
const PERM_BODY: u32 = 1;
const PERM_SPECIES: u32 = 0;
pub fn new(seed: u32, wpos: Vec3<f32>) -> Self {
Self {
@ -115,13 +117,12 @@ pub struct Npcs {
}
impl Npcs {
pub fn create(&mut self, npc: Npc) -> NpcId {
self.npcs.insert(npc)
}
pub fn create(&mut self, npc: Npc) -> NpcId { self.npcs.insert(npc) }
}
impl Deref for Npcs {
type Target = HopSlotMap<NpcId, Npc>;
fn deref(&self) -> &Self::Target { &self.npcs }
}

View File

@ -1,14 +1,10 @@
use hashbrown::HashMap;
use serde::{Serialize, Deserialize};
use slotmap::HopSlotMap;
use vek::*;
use std::ops::{Deref, DerefMut};
use common::{
uid::Uid,
store::Id,
rtsim::FactionId,
};
pub use common::rtsim::SiteId;
use common::{rtsim::FactionId, store::Id, uid::Uid};
use hashbrown::HashMap;
use serde::{Deserialize, Serialize};
use slotmap::HopSlotMap;
use std::ops::{Deref, DerefMut};
use vek::*;
use world::site::Site as WorldSite;
#[derive(Clone, Serialize, Deserialize)]
@ -16,14 +12,19 @@ pub struct Site {
pub wpos: Vec2<i32>,
pub faction: Option<FactionId>,
/// The site generated during initial worldgen that this site corresponds to.
/// The site generated during initial worldgen that this site corresponds
/// to.
///
/// Eventually, rtsim should replace initial worldgen's site system and this will not be necessary.
/// Eventually, rtsim should replace initial worldgen's site system and this
/// will not be necessary.
///
/// When setting up rtsim state, we try to 'link' these two definitions of a site: but if initial worldgen has
/// changed, this might not be possible. We try to delete sites that no longer exist during setup, but this is an
/// inherent fallible process. If linking fails, we try to delete the site in rtsim2 in order to avoid an
/// 'orphaned' site. (TODO: create new sites for new initial worldgen sites that come into being too).
/// When setting up rtsim state, we try to 'link' these two definitions of a
/// site: but if initial worldgen has changed, this might not be
/// possible. We try to delete sites that no longer exist during setup, but
/// this is an inherent fallible process. If linking fails, we try to
/// delete the site in rtsim2 in order to avoid an 'orphaned' site.
/// (TODO: create new sites for new initial worldgen sites that come into
/// being too).
#[serde(skip_serializing, skip_deserializing)]
pub world_site: Option<Id<WorldSite>>,
}
@ -38,7 +39,7 @@ impl Site {
#[derive(Clone, Serialize, Deserialize)]
pub struct Sites {
pub sites: HopSlotMap<SiteId, Site>,
#[serde(skip_serializing, skip_deserializing)]
pub world_site_map: HashMap<Id<WorldSite>, SiteId>,
}
@ -56,6 +57,7 @@ impl Sites {
impl Deref for Sites {
type Target = HopSlotMap<SiteId, Site>;
fn deref(&self) -> &Self::Target { &self.sites }
}

View File

@ -1,6 +1,6 @@
use super::{RtState, Rule};
use common::resources::{Time, TimeOfDay};
use world::{World, IndexRef};
use super::{Rule, RtState};
use world::{IndexRef, World};
pub trait Event: Clone + 'static {}

View File

@ -1,10 +1,7 @@
use crate::data::Faction;
use vek::*;
use rand::prelude::*;
use world::{
World,
IndexRef,
};
use vek::*;
use world::{IndexRef, World};
impl Faction {
pub fn generate(world: &World, index: IndexRef, rng: &mut impl Rng) -> Self {

View File

@ -1,40 +1,41 @@
pub mod site;
pub mod faction;
pub mod site;
use crate::data::{
npc::{Npcs, Npc, Profession},
site::{Sites, Site},
faction::{Factions, Faction},
Data,
Nature,
faction::{Faction, Factions},
npc::{Npc, Npcs, Profession},
site::{Site, Sites},
Data, Nature,
};
use common::{
resources::TimeOfDay, rtsim::WorldSettings, terrain::TerrainChunkSize, vol::RectVolSize,
};
use hashbrown::HashMap;
use rand::prelude::*;
use tracing::info;
use vek::*;
use common::{
rtsim::WorldSettings,
resources::TimeOfDay,
terrain::TerrainChunkSize,
vol::RectVolSize,
};
use world::{
site::SiteKind,
IndexRef,
World,
};
use world::{site::SiteKind, IndexRef, World};
impl Data {
pub fn generate(settings: &WorldSettings, world: &World, index: IndexRef) -> Self {
let mut seed = [0; 32];
seed.iter_mut().zip(&mut index.seed.to_le_bytes()).for_each(|(dst, src)| *dst = *src);
seed.iter_mut()
.zip(&mut index.seed.to_le_bytes())
.for_each(|(dst, src)| *dst = *src);
let mut rng = SmallRng::from_seed(seed);
let mut this = Self {
nature: Nature::generate(world),
npcs: Npcs { npcs: Default::default() },
sites: Sites { sites: Default::default(), world_site_map: Default::default() },
factions: Factions { factions: Default::default() },
npcs: Npcs {
npcs: Default::default(),
},
sites: Sites {
sites: Default::default(),
world_site_map: Default::default(),
},
factions: Factions {
factions: Default::default(),
},
time_of_day: TimeOfDay(settings.start_time),
};
@ -45,44 +46,53 @@ impl Data {
let wpos = world
.sim()
.get_size()
.map2(TerrainChunkSize::RECT_SIZE, |e, sz| rng.gen_range(0..(e * sz) as i32));
.map2(TerrainChunkSize::RECT_SIZE, |e, sz| {
rng.gen_range(0..(e * sz) as i32)
});
(wpos, this.factions.create(faction))
})
.collect::<Vec<_>>();
info!("Generated {} rtsim factions.", this.factions.len());
// Register sites with rtsim
for (world_site_id, _) in index
.sites
.iter()
{
for (world_site_id, _) in index.sites.iter() {
let site = Site::generate(world_site_id, world, index, &initial_factions);
this.sites.create(site);
}
info!("Registering {} rtsim sites from world sites.", this.sites.len());
info!(
"Registering {} rtsim sites from world sites.",
this.sites.len()
);
// Spawn some test entities at the sites
for (site_id, site) in this.sites.iter() {
let rand_wpos = |rng: &mut SmallRng| {
let wpos2d = site.wpos.map(|e| e + rng.gen_range(-10..10));
wpos2d.map(|e| e as f32 + 0.5)
wpos2d
.map(|e| e as f32 + 0.5)
.with_z(world.sim().get_alt_approx(wpos2d).unwrap_or(0.0))
};
for _ in 0..20 {
this.npcs.create(Npc::new(rng.gen(), rand_wpos(&mut rng))
.with_faction(site.faction)
.with_home(site_id).with_profession(match rng.gen_range(0..20) {
0 => Profession::Hunter,
1 => Profession::Blacksmith,
2 => Profession::Chef,
3 => Profession::Alchemist,
5..=10 => Profession::Farmer,
11..=15 => Profession::Guard,
_ => Profession::Adventurer(rng.gen_range(0..=3)),
}));
this.npcs.create(
Npc::new(rng.gen(), rand_wpos(&mut rng))
.with_faction(site.faction)
.with_home(site_id)
.with_profession(match rng.gen_range(0..20) {
0 => Profession::Hunter,
1 => Profession::Blacksmith,
2 => Profession::Chef,
3 => Profession::Alchemist,
5..=10 => Profession::Farmer,
11..=15 => Profession::Guard,
_ => Profession::Adventurer(rng.gen_range(0..=3)),
}),
);
}
this.npcs.create(Npc::new(rng.gen(), rand_wpos(&mut rng)).with_home(site_id).with_profession(Profession::Merchant));
this.npcs.create(
Npc::new(rng.gen(), rand_wpos(&mut rng))
.with_home(site_id)
.with_profession(Profession::Merchant),
);
}
info!("Generated {} rtsim NPCs.", this.npcs.len());

View File

@ -1,14 +1,15 @@
use crate::data::{Site, FactionId};
use crate::data::{FactionId, Site};
use common::store::Id;
use vek::*;
use world::{
site::Site as WorldSite,
World,
IndexRef,
};
use world::{site::Site as WorldSite, IndexRef, World};
impl Site {
pub fn generate(world_site: Id<WorldSite>, world: &World, index: IndexRef, nearby_factions: &[(Vec2<i32>, FactionId)]) -> Self {
pub fn generate(
world_site: Id<WorldSite>,
world: &World,
index: IndexRef,
nearby_factions: &[(Vec2<i32>, FactionId)],
) -> Self {
let wpos = index.sites.get(world_site).get_origin();
Self {

View File

@ -10,15 +10,15 @@ pub use self::{
event::{Event, EventCtx, OnTick},
rule::{Rule, RuleError},
};
use common::resources::{Time, TimeOfDay};
use world::{World, IndexRef};
use anymap2::SendSyncAnyMap;
use tracing::{info, error};
use atomic_refcell::AtomicRefCell;
use common::resources::{Time, TimeOfDay};
use std::{
any::type_name,
ops::{Deref, DerefMut},
};
use tracing::{error, info};
use world::{IndexRef, World};
pub struct RtState {
resources: SendSyncAnyMap,
@ -36,7 +36,7 @@ impl RtState {
rules: SendSyncAnyMap::new(),
event_handlers: SendSyncAnyMap::new(),
}
.with_resource(data);
.with_resource(data);
this.start_default_rules();
@ -58,7 +58,9 @@ impl RtState {
pub fn start_rule<R: Rule>(&mut self) {
info!("Initiating '{}' rule...", type_name::<R>());
match R::start(self) {
Ok(rule) => { self.rules.insert::<RuleState<R>>(AtomicRefCell::new(rule)); },
Ok(rule) => {
self.rules.insert::<RuleState<R>>(AtomicRefCell::new(rule));
},
Err(e) => error!("Error when initiating '{}' rule: {}", type_name::<R>(), e),
}
}
@ -66,11 +68,19 @@ impl RtState {
fn rule_mut<R: Rule>(&self) -> impl DerefMut<Target = R> + '_ {
self.rules
.get::<RuleState<R>>()
.unwrap_or_else(|| panic!("Tried to access rule '{}' but it does not exist", type_name::<R>()))
.unwrap_or_else(|| {
panic!(
"Tried to access rule '{}' but it does not exist",
type_name::<R>()
)
})
.borrow_mut()
}
pub fn bind<R: Rule, E: Event>(&mut self, mut f: impl FnMut(EventCtx<R, E>) + Send + Sync + 'static) {
pub fn bind<R: Rule, E: Event>(
&mut self,
mut f: impl FnMut(EventCtx<R, E>) + Send + Sync + 'static,
) {
let f = AtomicRefCell::new(f);
self.event_handlers
.entry::<EventHandlersOf<E>>()
@ -87,33 +97,53 @@ impl RtState {
}
pub fn data(&self) -> impl Deref<Target = Data> + '_ { self.resource() }
pub fn data_mut(&self) -> impl DerefMut<Target = Data> + '_ { self.resource_mut() }
pub fn resource<R: Send + Sync + 'static>(&self) -> impl Deref<Target = R> + '_ {
self.resources
.get::<AtomicRefCell<R>>()
.unwrap_or_else(|| panic!("Tried to access resource '{}' but it does not exist", type_name::<R>()))
.unwrap_or_else(|| {
panic!(
"Tried to access resource '{}' but it does not exist",
type_name::<R>()
)
})
.borrow()
}
pub fn resource_mut<R: Send + Sync + 'static>(&self) -> impl DerefMut<Target = R> + '_ {
self.resources
.get::<AtomicRefCell<R>>()
.unwrap_or_else(|| panic!("Tried to access resource '{}' but it does not exist", type_name::<R>()))
.unwrap_or_else(|| {
panic!(
"Tried to access resource '{}' but it does not exist",
type_name::<R>()
)
})
.borrow_mut()
}
pub fn emit<E: Event>(&mut self, e: E, world: &World, index: IndexRef) {
self.event_handlers
.get::<EventHandlersOf<E>>()
.map(|handlers| handlers
.iter()
.for_each(|f| f(self, world, index, &e)));
.map(|handlers| handlers.iter().for_each(|f| f(self, world, index, &e)));
}
pub fn tick(&mut self, world: &World, index: IndexRef, time_of_day: TimeOfDay, time: Time, dt: f32) {
pub fn tick(
&mut self,
world: &World,
index: IndexRef,
time_of_day: TimeOfDay,
time: Time,
dt: f32,
) {
self.data_mut().time_of_day = time_of_day;
let event = OnTick { time_of_day, time, dt };
let event = OnTick {
time_of_day,
time,
dt,
};
self.emit(event, world, index);
}
}

View File

@ -1,9 +1,9 @@
pub mod npc_ai;
pub mod setup;
pub mod simulate_npcs;
pub mod npc_ai;
use std::fmt;
use super::RtState;
use std::fmt;
#[derive(Debug)]
pub enum RuleError {
@ -13,7 +13,9 @@ pub enum RuleError {
impl fmt::Display for RuleError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
Self::NoSuchRule(r) => write!(f, "tried to fetch rule state '{}' but it does not exist", r),
Self::NoSuchRule(r) => {
write!(f, "tried to fetch rule state '{}' but it does not exist", r)
},
}
}
}

View File

@ -10,7 +10,8 @@ use common::{
path::Path,
rtsim::{Profession, SiteId},
store::Id,
terrain::TerrainChunkSize, vol::RectVolSize,
terrain::TerrainChunkSize,
vol::RectVolSize,
};
use fxhash::FxHasher64;
use itertools::Itertools;
@ -236,10 +237,12 @@ impl Rule for NpcAi {
let data = &mut *ctx.state.data_mut();
let mut dynamic_rng = rand::thread_rng();
for npc in data.npcs.values_mut() {
npc.current_site = ctx.world.sim().get(npc.wpos.xy().as_::<i32>() / TerrainChunkSize::RECT_SIZE.as_()).and_then(|chunk| {
data.sites.world_site_map.get(chunk.sites.first()?).copied()
});
npc.current_site = ctx
.world
.sim()
.get(npc.wpos.xy().as_::<i32>() / TerrainChunkSize::RECT_SIZE.as_())
.and_then(|chunk| data.sites.world_site_map.get(chunk.sites.first()?).copied());
if let Some(home_id) = npc.home {
if let Some((target, _)) = npc.goto {
// Walk to the current target

View File

@ -1,12 +1,9 @@
use crate::{data::Site, event::OnSetup, RtState, Rule, RuleError};
use tracing::warn;
use crate::{
data::Site,
event::OnSetup,
RtState, Rule, RuleError,
};
/// 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 sensible.
/// 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
/// sensible.
pub struct Setup;
impl Rule for Setup {
@ -15,14 +12,20 @@ impl Rule for Setup {
let data = &mut *ctx.state.data_mut();
// Delete rtsim sites that don't correspond to a world site
data.sites.retain(|site_id, site| {
if let Some((world_site_id, _)) = ctx.index.sites
if let Some((world_site_id, _)) = ctx
.index
.sites
.iter()
.find(|(_, world_site)| world_site.get_origin() == site.wpos)
{
site.world_site = Some(world_site_id);
true
} else {
warn!("{:?} is no longer valid because the site it was derived from no longer exists. It will now be deleted.", site_id);
warn!(
"{:?} is no longer valid because the site it was derived from no longer \
exists. It will now be deleted.",
site_id
);
false
}
});
@ -32,15 +35,20 @@ impl Rule for Setup {
npc.home = npc.home.filter(|home| data.sites.contains_key(*home));
}
// Generate rtsim sites for world sites that don't have a corresponding rtsim site yet
// Generate rtsim sites for world sites that don't have a corresponding rtsim
// site yet
for (world_site_id, _) in ctx.index.sites.iter() {
if !data.sites
.values()
.any(|site| site.world_site.expect("Rtsim site not assigned to world site") == world_site_id)
{
warn!("{:?} is new and does not have a corresponding rtsim site. One will now be generated afresh.", world_site_id);
data
.sites
if !data.sites.values().any(|site| {
site.world_site
.expect("Rtsim site not assigned to world site")
== world_site_id
}) {
warn!(
"{:?} is new and does not have a corresponding rtsim site. One will now \
be generated afresh.",
world_site_id
);
data.sites
.create(Site::generate(world_site_id, ctx.world, ctx.index, &[]));
}
}

View File

@ -1,17 +1,12 @@
use crate::{data::npc::NpcMode, event::OnTick, RtState, Rule, RuleError};
use common::{terrain::TerrainChunkSize, vol::RectVolSize};
use tracing::info;
use vek::*;
use crate::{
data::npc::NpcMode,
event::OnTick,
RtState, Rule, RuleError,
};
pub struct SimulateNpcs;
impl Rule for SimulateNpcs {
fn start(rtstate: &mut RtState) -> Result<Self, RuleError> {
rtstate.bind::<Self, OnTick>(|ctx| {
let data = &mut *ctx.state.data_mut();
for npc in data
@ -28,14 +23,17 @@ impl Rule for SimulateNpcs {
if dist2 > 0.5f32.powi(2) {
npc.wpos += (diff
* (body.max_speed_approx() * speed_factor * ctx.event.dt / dist2.sqrt())
.min(1.0))
* (body.max_speed_approx() * speed_factor * ctx.event.dt
/ dist2.sqrt())
.min(1.0))
.with_z(0.0);
}
}
// Make sure NPCs remain on the surface
npc.wpos.z = ctx.world.sim()
npc.wpos.z = ctx
.world
.sim()
.get_alt_approx(npc.wpos.xy().map(|e| e as i32))
.unwrap_or(0.0);
}

View File

@ -1,9 +1,6 @@
use crate::{
metrics::ChunkGenMetrics,
rtsim2::RtSim,
};
#[cfg(not(feature = "worldgen"))]
use crate::test_world::{IndexOwned, World};
use crate::{metrics::ChunkGenMetrics, rtsim2::RtSim};
use common::{
calendar::Calendar, generation::ChunkSupplement, resources::TimeOfDay, slowjob::SlowJobPool,
terrain::TerrainChunk,
@ -47,10 +44,8 @@ impl ChunkGenerator {
key: Vec2<i32>,
slowjob_pool: &SlowJobPool,
world: Arc<World>,
#[cfg(feature = "worldgen")]
rtsim: &RtSim,
#[cfg(not(feature = "worldgen"))]
rtsim: &(),
#[cfg(feature = "worldgen")] rtsim: &RtSim,
#[cfg(not(feature = "worldgen"))] rtsim: &(),
index: IndexOwned,
time: (TimeOfDay, Calendar),
) {

View File

@ -1192,9 +1192,19 @@ fn handle_tp_npc(
) -> CmdResult<()> {
use crate::rtsim2::RtSim;
let pos = if let Some(id) = parse_cmd_args!(args, u32) {
// TODO: Take some other identifier than an integer to this command.
server.state.ecs().read_resource::<RtSim>().state().data().npcs.values().nth(id as usize).ok_or(action.help_string())?.wpos
} else {
// TODO: Take some other identifier than an integer to this command.
server
.state
.ecs()
.read_resource::<RtSim>()
.state()
.data()
.npcs
.values()
.nth(id as usize)
.ok_or(action.help_string())?
.wpos
} else {
return Err(action.help_string());
};
position_mut(server, target, "target", |target_pos| {
@ -3873,12 +3883,7 @@ fn handle_scale(
.write_storage::<comp::Scale>()
.insert(target, comp::Scale(scale));
if reset_mass.unwrap_or(true) {
if let Some(body) = server
.state
.ecs()
.read_storage::<comp::Body>()
.get(target)
{
if let Some(body) = server.state.ecs().read_storage::<comp::Body>().get(target) {
let _ = server
.state
.ecs()

View File

@ -7,9 +7,8 @@ use crate::{
skillset::SkillGroupKind,
BuffKind, BuffSource, PhysicsState,
},
// rtsim::RtSim,
sys::terrain::SAFE_ZONE_RADIUS,
rtsim2,
sys::terrain::SAFE_ZONE_RADIUS,
Server, SpawnPoint, StateExt,
};
use authc::Uuid;

View File

@ -83,7 +83,7 @@ use common::{
rtsim::RtSimEntity,
shared_server_config::ServerConstants,
slowjob::SlowJobPool,
terrain::{TerrainChunk, TerrainChunkSize, Block},
terrain::{Block, TerrainChunk, TerrainChunkSize},
vol::RectRasterableVol,
};
use common_ecs::run_now;
@ -565,7 +565,12 @@ impl Server {
// Init rtsim, loading it from disk if possible
#[cfg(feature = "worldgen")]
{
match rtsim2::RtSim::new(&settings.world, index.as_index_ref(), &world, data_dir.to_owned()) {
match rtsim2::RtSim::new(
&settings.world,
index.as_index_ref(),
&world,
data_dir.to_owned(),
) {
Ok(rtsim) => {
state.ecs_mut().insert(rtsim.state().data().time_of_day);
state.ecs_mut().insert(rtsim);
@ -706,9 +711,15 @@ impl Server {
let before_state_tick = Instant::now();
fn on_block_update(ecs: &specs::World, wpos: Vec3<i32>, old_block: Block, new_block: Block) {
fn on_block_update(
ecs: &specs::World,
wpos: Vec3<i32>,
old_block: Block,
new_block: Block,
) {
// When a resource block updates, inform rtsim
if old_block.get_rtsim_resource().is_some() || new_block.get_rtsim_resource().is_some() {
if old_block.get_rtsim_resource().is_some() || new_block.get_rtsim_resource().is_some()
{
ecs.write_resource::<rtsim2::RtSim>().hook_block_update(
&ecs.read_resource::<Arc<world::World>>(),
ecs.read_resource::<world::IndexOwned>().as_index_ref(),

View File

@ -1,5 +1,5 @@
use rtsim2::Event;
use common::terrain::Block;
use rtsim2::Event;
use vek::*;
#[derive(Clone)]

View File

@ -4,33 +4,29 @@ pub mod tick;
use common::{
grid::Grid,
slowjob::SlowJobPool,
rtsim::{ChunkResource, RtSimEntity, WorldSettings},
terrain::{TerrainChunk, Block},
slowjob::SlowJobPool,
terrain::{Block, TerrainChunk},
vol::RectRasterableVol,
};
use common_ecs::{dispatch, System};
use enum_map::EnumMap;
use rtsim2::{
data::{
npc::NpcMode,
Data,
ReadError,
},
rule::Rule,
data::{npc::NpcMode, Data, ReadError},
event::OnSetup,
rule::Rule,
RtState,
};
use specs::{DispatcherBuilder, WorldExt};
use std::{
error::Error,
fs::{self, File},
io::{self, Write},
path::PathBuf,
sync::Arc,
time::Instant,
io::{self, Write},
error::Error,
};
use enum_map::EnumMap;
use tracing::{error, warn, info, debug};
use tracing::{debug, error, info, warn};
use vek::*;
use world::{IndexRef, World};
@ -41,7 +37,12 @@ pub struct RtSim {
}
impl RtSim {
pub fn new(settings: &WorldSettings, index: IndexRef, world: &World, data_dir: PathBuf) -> Result<Self, ron::Error> {
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());
@ -51,7 +52,10 @@ impl RtSim {
Ok(file) => {
info!("Rtsim data found. Attempting to load...");
match Data::from_reader(io::BufReader::new(file)) {
Ok(data) => { info!("Rtsim data loaded."); break 'load data },
Ok(data) => {
info!("Rtsim data loaded.");
break 'load data;
},
Err(e) => {
error!("Rtsim data failed to load: {}", e);
let mut i = 0;
@ -64,7 +68,10 @@ impl RtSim {
});
if !backup_path.exists() {
fs::rename(&file_path, &backup_path)?;
warn!("Failed rtsim data was moved to {}", backup_path.display());
warn!(
"Failed rtsim data was moved to {}",
backup_path.display()
);
info!("A fresh rtsim data will now be generated.");
break;
}
@ -73,12 +80,16 @@ impl RtSim {
},
}
},
Err(e) if e.kind() == io::ErrorKind::NotFound =>
info!("No rtsim data found. Generating from world..."),
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).");
warn!(
"'RTSIM_NOLOAD' is set, skipping loading of rtsim state (old state will be \
overwritten)."
);
}
let data = Data::generate(settings, &world, index);
@ -88,8 +99,10 @@ impl RtSim {
let mut this = Self {
last_saved: None,
state: RtState::new(data)
.with_resource(ChunkStates(Grid::populate_from(world.sim().get_size().as_(), |_| None))),
state: RtState::new(data).with_resource(ChunkStates(Grid::populate_from(
world.sim().get_size().as_(),
|_| None,
))),
file_path,
};
@ -123,8 +136,16 @@ impl RtSim {
}
}
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_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) {
@ -155,10 +176,8 @@ impl RtSim {
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(|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))?;
@ -180,9 +199,7 @@ impl RtSim {
self.state.data().nature.get_chunk_resources(key)
}
pub fn state(&self) -> &RtState {
&self.state
}
pub fn state(&self) -> &RtState { &self.state }
}
struct ChunkStates(pub Grid<Option<LoadedChunkState>>);

View File

@ -1,7 +1,7 @@
pub mod deplete_resources;
use tracing::info;
use rtsim2::RtState;
use tracing::info;
pub fn start_rules(rtstate: &mut RtState) {
info!("Starting server rtsim rules...");

View File

@ -1,13 +1,7 @@
use tracing::info;
use crate::rtsim2::{event::OnBlockChange, ChunkStates};
use common::{terrain::TerrainChunk, vol::RectRasterableVol};
use rtsim2::{RtState, Rule, RuleError};
use crate::rtsim2::{
event::OnBlockChange,
ChunkStates,
};
use common::{
terrain::TerrainChunk,
vol::RectRasterableVol,
};
use tracing::info;
pub struct DepleteResources;
@ -16,7 +10,9 @@ impl Rule for DepleteResources {
info!("Hello from the resource depletion rule!");
rtstate.bind::<Self, OnBlockChange>(|ctx| {
let key = ctx.event.wpos
let key = ctx
.event
.wpos
.xy()
.map2(TerrainChunk::RECT_SIZE, |e, sz| e.div_euclid(sz as i32));
if let Some(Some(chunk_state)) = ctx.state.resource_mut::<ChunkStates>().0.get(key) {
@ -26,7 +22,8 @@ impl Rule for DepleteResources {
if chunk_state.max_res[res] > 0 {
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;
.max(0.0)
/ chunk_state.max_res[res] as f32;
}
}
// Add resources
@ -34,11 +31,15 @@ impl Rule for DepleteResources {
if chunk_state.max_res[res] > 0 {
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;
.max(0.0)
/ chunk_state.max_res[res] as f32;
}
}
println!("Chunk resources = {:?}", chunk_res);
ctx.state.data_mut().nature.set_chunk_resources(key, chunk_res);
ctx.state
.data_mut()
.nature
.set_chunk_resources(key, chunk_res);
}
});

View File

@ -100,7 +100,11 @@ fn profession_extra_loadout(
fn profession_agent_mark(profession: Option<&Profession>) -> Option<comp::agent::Mark> {
match profession {
Some(
Profession::Merchant | Profession::Farmer | Profession::Chef | Profession::Blacksmith | Profession::Alchemist,
Profession::Merchant
| Profession::Farmer
| Profession::Chef
| Profession::Blacksmith
| Profession::Alchemist,
) => Some(comp::agent::Mark::Merchant),
Some(Profession::Guard) => Some(comp::agent::Mark::Guard),
_ => None,

View File

@ -5,9 +5,9 @@ use crate::{
persistence::PersistedComponents,
pet::restore_pet,
presence::{Presence, RepositionOnChunkLoad},
rtsim2::RtSim,
settings::Settings,
sys::sentinel::DeletedEntities,
rtsim2::RtSim,
wiring, BattleModeBuffer, SpawnPoint,
};
use common::{

View File

@ -1,12 +1,9 @@
pub mod behavior_tree;
pub use server_agent::{action_nodes, attack, consts, data, util};
use crate::{
// rtsim::{entity::PersonalityTrait, RtSim},
sys::agent::{
use crate::sys::agent::{
behavior_tree::{BehaviorData, BehaviorTree},
data::{AgentData, ReadData},
},
};
use common::{
comp::{

View File

@ -9,8 +9,9 @@ use common::{
mounting::Mount,
path::TraversalConfig,
resources::{DeltaTime, Time, TimeOfDay},
rtsim::RtSimEntity,
terrain::TerrainGrid,
uid::{Uid, UidAllocator}, rtsim::RtSimEntity,
uid::{Uid, UidAllocator},
};
use specs::{
shred::ResourceId, Entities, Entity as EcsEntity, Read, ReadExpect, ReadStorage, SystemData,
@ -154,7 +155,6 @@ pub struct ReadData<'a> {
#[cfg(feature = "worldgen")]
pub world: ReadExpect<'a, Arc<world::World>>,
pub rtsim_entity: ReadStorage<'a, RtSimEntity>,
//pub rtsim_entities: ReadStorage<'a, RtSimEntity>,
pub buffs: ReadStorage<'a, Buffs>,
pub combos: ReadStorage<'a, Combo>,
pub active_abilities: ReadStorage<'a, ActiveAbilities>,

View File

@ -149,9 +149,10 @@ impl PlayState for MainMenuState {
)
.into_owned(),
server::Error::RtsimError(e) => localized_strings
.get("main.servers.rtsim_error")
.to_owned()
.replace("{raw_error}", e.to_string().as_str()),
.get_msg_ctx("main-servers-rtsim_error", &i18n::fluent_args! {
"raw_error" => e.to_string(),
})
.into_owned(),
server::Error::Other(e) => localized_strings
.get_msg_ctx("main-servers-other_error", &i18n::fluent_args! {
"raw_error" => e,

View File

@ -293,9 +293,7 @@ impl Debug {
id
}
pub fn get_shape(&self, id: DebugShapeId) -> Option<&DebugShape> {
self.shapes.get(&id)
}
pub fn get_shape(&self, id: DebugShapeId) -> Option<&DebugShape> { self.shapes.get(&id) }
pub fn set_context(&mut self, id: DebugShapeId, pos: [f32; 4], color: [f32; 4], ori: [f32; 4]) {
self.pending_locals.insert(id, (pos, color, ori));

View File

@ -776,7 +776,8 @@ impl FigureMgr {
.enumerate()
{
// Velocity relative to the current ground
let rel_vel = anim::vek::Vec3::<f32>::from(vel.0 - physics.ground_vel) / scale.map_or(1.0, |s| s.0);
let rel_vel = anim::vek::Vec3::<f32>::from(vel.0 - physics.ground_vel)
/ scale.map_or(1.0, |s| s.0);
let look_dir = controller.map(|c| c.inputs.look_dir).unwrap_or_default();
let is_viewpoint = scene_data.viewpoint_entity == entity;
@ -809,8 +810,9 @@ impl FigureMgr {
const MIN_PERFECT_RATE_DIST: f32 = 100.0;
if (i as u64 + tick)
% ((((pos.0.distance_squared(focus_pos) / scale.map_or(1.0, |s| s.0)).powf(0.25) - MIN_PERFECT_RATE_DIST.sqrt())
.max(0.0)
% ((((pos.0.distance_squared(focus_pos) / scale.map_or(1.0, |s| s.0)).powf(0.25)
- MIN_PERFECT_RATE_DIST.sqrt())
.max(0.0)
/ 3.0) as u64)
.saturating_add(1)
!= 0
@ -6707,8 +6709,10 @@ impl FigureMgr {
} {
let model_entry = model_entry?;
let figure_low_detail_distance = figure_lod_render_distance * scale.map_or(1.0, |s| s.0) * 0.75;
let figure_mid_detail_distance = figure_lod_render_distance * scale.map_or(1.0, |s| s.0) * 0.5;
let figure_low_detail_distance =
figure_lod_render_distance * scale.map_or(1.0, |s| s.0) * 0.75;
let figure_mid_detail_distance =
figure_lod_render_distance * scale.map_or(1.0, |s| s.0) * 0.5;
let model = if pos.distance_squared(cam_pos) > figure_low_detail_distance.powi(2) {
model_entry.lod_model(2)

View File

@ -409,7 +409,10 @@ impl Scene {
// when zooming in the distance the camera travelles should be based on the
// final distance. This is to make sure the camera travelles the
// same distance when zooming in and out
let player_scale = client.state().read_component_copied::<comp::Scale>(client.entity()).map_or(1.0, |s| s.0);
let player_scale = client
.state()
.read_component_copied::<comp::Scale>(client.entity())
.map_or(1.0, |s| s.0);
if delta < 0.0 {
self.camera.zoom_switch(
// Thank you Imbris for doing the math
@ -418,7 +421,11 @@ impl Scene {
player_scale,
);
} else {
self.camera.zoom_switch(delta * (0.05 + self.camera.get_distance() * 0.01), cap, player_scale);
self.camera.zoom_switch(
delta * (0.05 + self.camera.get_distance() * 0.01),
cap,
player_scale,
);
}
true
},
@ -1477,15 +1484,19 @@ impl Scene {
// If this shape no longer matches, remove the old one
if let Some(shape_id) = hitboxes.get(&entity) {
if self.debug.get_shape(*shape_id).map_or(false, |s| s != &shape) {
if self
.debug
.get_shape(*shape_id)
.map_or(false, |s| s != &shape)
{
self.debug.remove_shape(*shape_id);
hitboxes.remove(&entity);
}
}
let shape_id = hitboxes.entry(entity).or_insert_with(|| {
self.debug.add_shape(shape)
});
let shape_id = hitboxes
.entry(entity)
.or_insert_with(|| self.debug.add_shape(shape));
let hb_pos = [pos.0.x, pos.0.y, pos.0.z + *z_min * scale, 0.0];
let color = if group == Some(&comp::group::ENEMY) {
[1.0, 0.0, 0.0, 0.5]

View File

@ -628,12 +628,17 @@ impl Civs {
}
}
/// Return the direct track between two places, bool if the track should be reversed or not
/// Return the direct track between two places, bool if the track should be
/// reversed or not
pub fn track_between(&self, a: Id<Site>, b: Id<Site>) -> Option<(Id<Track>, bool)> {
self.track_map
.get(&a)
.and_then(|dests| Some((*dests.get(&b)?, false)))
.or_else(|| self.track_map.get(&b).and_then(|dests| Some((*dests.get(&a)?, true))))
.or_else(|| {
self.track_map
.get(&b)
.and_then(|dests| Some((*dests.get(&a)?, true)))
})
}
/// Return an iterator over a site's neighbors
@ -663,8 +668,9 @@ impl Civs {
.sqrt()
};
let neighbors = |p: &Id<Site>| self.neighbors(*p);
let transition =
|a: &Id<Site>, b: &Id<Site>| self.tracks.get(self.track_between(*a, *b).unwrap().0).cost;
let transition = |a: &Id<Site>, b: &Id<Site>| {
self.tracks.get(self.track_between(*a, *b).unwrap().0).cost
};
let satisfied = |p: &Id<Site>| *p == b;
// We use this hasher (FxHasher64) because
// (1) we don't care about DDOS attacks (ruling out SipHash);

View File

@ -49,11 +49,11 @@ use common::{
generation::{ChunkSupplement, EntityInfo},
lod,
resources::TimeOfDay,
rtsim::ChunkResource,
terrain::{
Block, BlockKind, SpriteKind, TerrainChunk, TerrainChunkMeta, TerrainChunkSize, TerrainGrid,
},
vol::{ReadVol, RectVolSize, WriteVol},
rtsim::ChunkResource,
};
use common_net::msg::{world_msg, WorldMapMsg};
use enum_map::EnumMap;
@ -492,17 +492,19 @@ impl World {
// Finally, defragment to minimize space consumption.
chunk.defragment();
// Before we finish, we check candidate rtsim resource blocks, deduplicating positions and only keeping those
// that actually do have resources. Although this looks potentially very expensive, only blocks that are rtsim
// Before we finish, we check candidate rtsim resource blocks, deduplicating
// positions and only keeping those that actually do have resources.
// Although this looks potentially very expensive, only blocks that are rtsim
// resources (i.e: a relatively small number of sprites) are processed here.
if let Some(rtsim_resources) = rtsim_resources {
rtsim_resource_blocks.sort_unstable_by_key(|pos| pos.into_array());
rtsim_resource_blocks.dedup();
for wpos in rtsim_resource_blocks {
chunk.map(
wpos - chunk_wpos2d.with_z(0),
|block| if let Some(res) = block.get_rtsim_resource() {
// Note: this represents the upper limit, not the actual number spanwed, so we increment this before deciding whether we're going to spawn the resource.
chunk.map(wpos - chunk_wpos2d.with_z(0), |block| {
if let Some(res) = block.get_rtsim_resource() {
// Note: this represents the upper limit, not the actual number spanwed, so
// we increment this before deciding whether we're going to spawn the
// resource.
supplement.rtsim_max_resources[res] += 1;
// Throw a dice to determine whether this resource should actually spawn
// TODO: Don't throw a dice, try to generate the *exact* correct number
@ -513,12 +515,11 @@ impl World {
}
} else {
block
},
);
}
});
}
}
Ok((chunk, supplement))
}

View File

@ -1012,8 +1012,11 @@ pub fn merchant_loadout(
loadout_builder: LoadoutBuilder,
economy: Option<&SiteInformation>,
) -> LoadoutBuilder {
trader_loadout(loadout_builder
.with_asset_expect("common.loadout.village.merchant", &mut thread_rng()), economy, |_| true)
trader_loadout(
loadout_builder.with_asset_expect("common.loadout.village.merchant", &mut thread_rng()),
economy,
|_| true,
)
}
pub fn trader_loadout(
@ -1030,10 +1033,13 @@ pub fn trader_loadout(
let mut bag4 = Item::new_from_asset_expect("common.items.armor.misc.bag.sturdy_red_backpack");
let slots = backpack.slots().len() + 4 * bag1.slots().len();
let mut stockmap: HashMap<Good, f32> = economy
.map(|e| e.unconsumed_stock.clone()
.into_iter()
.filter(|(good, _)| permitted(*good))
.collect())
.map(|e| {
e.unconsumed_stock
.clone()
.into_iter()
.filter(|(good, _)| permitted(*good))
.collect()
})
.unwrap_or_default();
// modify stock for better gameplay

View File

@ -7,7 +7,8 @@ use self::tile::{HazardKind, KeepKind, RoofKind, Tile, TileGrid, TILE_SIZE};
pub use self::{
gen::{aabr_with_z, Fill, Painter, Primitive, PrimitiveRef, Structure},
plot::{Plot, PlotKind},
util::Dir, tile::TileKind,
tile::TileKind,
util::Dir,
};
use crate::{
sim::Path,