Added rtsim entities moving when unloaded, better generation

This commit is contained in:
Joshua Barretto 2020-11-12 21:31:28 +00:00
parent 427f431ac8
commit 22fb71905b
18 changed files with 374 additions and 126 deletions

1
Cargo.lock generated
View File

@ -5402,7 +5402,6 @@ dependencies = [
"portpicker", "portpicker",
"prometheus", "prometheus",
"rand 0.7.3", "rand 0.7.3",
"rand_chacha 0.2.2",
"ron", "ron",
"scan_fmt", "scan_fmt",
"serde", "serde",

View File

@ -2,6 +2,7 @@ use crate::{
comp::{humanoid, quadruped_low, quadruped_medium, quadruped_small, Body}, comp::{humanoid, quadruped_low, quadruped_medium, quadruped_small, Body},
path::Chaser, path::Chaser,
sync::Uid, sync::Uid,
rtsim::RtSimController,
}; };
use specs::{Component, Entity as EcsEntity}; use specs::{Component, Entity as EcsEntity};
use specs_idvs::IdvStorage; use specs_idvs::IdvStorage;
@ -68,6 +69,7 @@ impl Component for Alignment {
pub struct Psyche { pub struct Psyche {
pub aggro: f32, // 0.0 = always flees, 1.0 = always attacks, 0.5 = flee at 50% health pub aggro: f32, // 0.0 = always flees, 1.0 = always attacks, 0.5 = flee at 50% health
} }
impl<'a> From<&'a Body> for Psyche { impl<'a> From<&'a Body> for Psyche {
fn from(body: &'a Body) -> Self { fn from(body: &'a Body) -> Self {
Self { Self {
@ -137,6 +139,7 @@ impl<'a> From<&'a Body> for Psyche {
#[derive(Clone, Debug, Default)] #[derive(Clone, Debug, Default)]
pub struct Agent { pub struct Agent {
pub rtsim_controller: RtSimController,
pub patrol_origin: Option<Vec3<f32>>, pub patrol_origin: Option<Vec3<f32>>,
pub activity: Activity, pub activity: Activity,
/// Does the agent talk when e.g. hit by the player /// Does the agent talk when e.g. hit by the player
@ -151,8 +154,7 @@ impl Agent {
self self
} }
pub fn new(origin: Vec3<f32>, can_speak: bool, body: &Body) -> Self { pub fn new(patrol_origin: Option<Vec3<f32>>, can_speak: bool, body: &Body) -> Self {
let patrol_origin = Some(origin);
Agent { Agent {
patrol_origin, patrol_origin,
can_speak, can_speak,
@ -168,7 +170,10 @@ impl Component for Agent {
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub enum Activity { pub enum Activity {
Idle(Vec2<f32>), Idle {
bearing: Vec2<f32>,
chaser: Chaser,
},
Follow { Follow {
target: EcsEntity, target: EcsEntity,
chaser: Chaser, chaser: Chaser,
@ -189,5 +194,10 @@ impl Activity {
} }
impl Default for Activity { impl Default for Activity {
fn default() -> Self { Activity::Idle(Vec2::zero()) } fn default() -> Self {
Activity::Idle {
bearing: Vec2::zero(),
chaser: Chaser::default(),
}
}
} }

View File

@ -1,9 +1,11 @@
// We'd like to not have this file in `common`, but sadly there are // We'd like to not have this file in `common`, but sadly there are
// things in `common` that require it (currently, `ServerEvent`). When // things in `common` that require it (currently, `ServerEvent` and
// possible, this should be moved to the `rtsim` module in `server`. // `Agent`). When possible, this should be moved to the `rtsim`
// module in `server`.
use specs_idvs::IdvStorage; use specs_idvs::IdvStorage;
use specs::Component; use specs::Component;
use vek::*;
pub type RtSimId = usize; pub type RtSimId = usize;
@ -13,3 +15,37 @@ pub struct RtSimEntity(pub RtSimId);
impl Component for RtSimEntity { impl Component for RtSimEntity {
type Storage = IdvStorage<Self>; type Storage = IdvStorage<Self>;
} }
/// This type is the map route through which the rtsim (real-time simulation) aspect
/// of the game communicates with the rest of the game. It is analagous to
/// `comp::Controller` in that it provides a consistent interface for simulation NPCs
/// to control their actions. Unlike `comp::Controller`, it is very abstract and is
/// intended for consumption by both the agent code and the internal rtsim simulation
/// code (depending on whether the entity is loaded into the game as a physical entity
/// or not). Agent code should attempt to act upon its instructions where reasonable
/// although deviations for various reasons (obstacle avoidance, counter-attacking,
/// etc.) are expected.
#[derive(Clone, Debug)]
pub struct RtSimController {
/// When this field is `Some(..)`, the agent should attempt to make progress
/// toward the given location, accounting for obstacles and other high-priority
/// situations like being attacked.
pub travel_to: Option<Vec3<f32>>,
/// Proportion of full speed to move
pub speed_factor: f32,
}
impl Default for RtSimController {
fn default() -> Self {
Self {
travel_to: None,
speed_factor: 1.0,
}
}
}
impl RtSimController {
pub fn reset(&mut self) {
*self = Self::default();
}
}

View File

@ -5,7 +5,7 @@ use crate::{
}, },
event::LocalEvent, event::LocalEvent,
states::*, states::*,
sys::{character_behavior::JoinData, phys::GRAVITY}, sys::{character_behavior::JoinData, phys::{GRAVITY, FRIC_GROUND}},
util::Dir, util::Dir,
}; };
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
@ -49,6 +49,25 @@ impl Body {
} }
} }
/// Attempt to determine the maximum speed of the character
/// when moving on the ground
pub fn max_speed_approx(&self) -> f32 {
// Inverse kinematics: at what velocity will acceleration
// be cancelled out by friction drag?
// Note: we assume no air (this is fine, current physics
// uses max(air_drag, ground_drag)).
// Derived via...
// v = (v + dv / 30) * (1 - drag).powf(2) (accel cancels drag)
// => 1 = (1 + (dv / 30) / v) * (1 - drag).powf(2)
// => 1 / (1 - drag).powf(2) = 1 + (dv / 30) / v
// => 1 / (1 - drag).powf(2) - 1 = (dv / 30) / v
// => 1 / (1 / (1 - drag).powf(2) - 1) = v / (dv / 30)
// => (dv / 30) / (1 / (1 - drag).powf(2) - 1) = v
let v = (-self.base_accel() / 30.0) / ((1.0 - FRIC_GROUND).powf(2.0) - 1.0);
debug_assert!(v >= 0.0, "Speed must be positive!");
v
}
pub fn base_ori_rate(&self) -> f32 { pub fn base_ori_rate(&self) -> f32 {
match self { match self {
Body::Humanoid(_) => 20.0, Body::Humanoid(_) => 20.0,

View File

@ -212,40 +212,69 @@ impl<'a> System<'a> for Sys {
'activity: { 'activity: {
match &mut agent.activity { match &mut agent.activity {
Activity::Idle(bearing) => { Activity::Idle { bearing, chaser } => {
*bearing += Vec2::new( if let Some(travel_to) = agent.rtsim_controller.travel_to {
thread_rng().gen::<f32>() - 0.5, if let Some((bearing, speed)) = chaser.chase(
thread_rng().gen::<f32>() - 0.5, &*terrain,
) * 0.1 pos.0,
- *bearing * 0.003 vel.0,
- agent.patrol_origin.map_or(Vec2::zero(), |patrol_origin| { travel_to,
(pos.0 - patrol_origin).xy() * 0.0002 TraversalConfig {
}); node_tolerance,
slow_factor,
on_ground: physics_state.on_ground,
min_tgt_dist: 1.25,
},
) {
inputs.move_dir = bearing
.xy()
.try_normalized()
.unwrap_or(Vec2::zero())
* speed.min(agent.rtsim_controller.speed_factor);
inputs.jump.set_state(bearing.z > 1.5);
inputs.climb = Some(comp::Climb::Up);
inputs.move_z = bearing.z;
}
} else {
*bearing += Vec2::new(
thread_rng().gen::<f32>() - 0.5,
thread_rng().gen::<f32>() - 0.5,
) * 0.1
- *bearing * 0.003
- agent.patrol_origin.map_or(Vec2::zero(), |patrol_origin| {
(pos.0 - patrol_origin).xy() * 0.0002
});
// Stop if we're too close to a wall // Stop if we're too close to a wall
*bearing *= 0.1 *bearing *= 0.1
+ if terrain + if terrain
.ray( .ray(
pos.0 + Vec3::unit_z(), pos.0 + Vec3::unit_z(),
pos.0 pos.0
+ Vec3::from(*bearing) + Vec3::from(*bearing)
.try_normalized() .try_normalized()
.unwrap_or(Vec3::unit_y()) .unwrap_or(Vec3::unit_y())
* 5.0 * 5.0
+ Vec3::unit_z(), + Vec3::unit_z(),
) )
.until(Block::is_solid) .until(Block::is_solid)
.cast() .cast()
.1 .1
.map_or(true, |b| b.is_none()) .map_or(true, |b| b.is_none())
{ {
0.9 0.9
} else { } else {
0.0 0.0
}; };
if bearing.magnitude_squared() > 0.5f32.powf(2.0) { if bearing.magnitude_squared() > 0.5f32.powf(2.0) {
inputs.move_dir = *bearing * 0.65; inputs.move_dir = *bearing * 0.65;
}
// Sit
if thread_rng().gen::<f32>() < 0.0035 {
controller.actions.push(ControlAction::Sit);
}
} }
// Put away weapon // Put away weapon
@ -253,11 +282,6 @@ impl<'a> System<'a> for Sys {
controller.actions.push(ControlAction::Unwield); controller.actions.push(ControlAction::Unwield);
} }
// Sit
if thread_rng().gen::<f32>() < 0.0035 {
controller.actions.push(ControlAction::Sit);
}
// Sometimes try searching for new targets // Sometimes try searching for new targets
if thread_rng().gen::<f32>() < 0.1 { if thread_rng().gen::<f32>() < 0.1 {
choose_target = true; choose_target = true;
@ -621,7 +645,10 @@ impl<'a> System<'a> for Sys {
} }
if do_idle { if do_idle {
agent.activity = Activity::Idle(Vec2::zero()); agent.activity = Activity::Idle {
bearing: Vec2::zero(),
chaser: Chaser::default(),
};
} }
// Choose a new target to attack: only go out of our way to attack targets we // Choose a new target to attack: only go out of our way to attack targets we

View File

@ -19,16 +19,16 @@ use std::ops::Range;
use vek::*; use vek::*;
pub const GRAVITY: f32 = 9.81 * 5.0; pub const GRAVITY: f32 = 9.81 * 5.0;
const BOUYANCY: f32 = 1.0; pub const BOUYANCY: f32 = 1.0;
// Friction values used for linear damping. They are unitless quantities. The // Friction values used for linear damping. They are unitless quantities. The
// value of these quantities must be between zero and one. They represent the // value of these quantities must be between zero and one. They represent the
// amount an object will slow down within 1/60th of a second. Eg. if the // amount an object will slow down within 1/60th of a second. Eg. if the
// friction is 0.01, and the speed is 1.0, then after 1/60th of a second the // friction is 0.01, and the speed is 1.0, then after 1/60th of a second the
// speed will be 0.99. after 1 second the speed will be 0.54, which is 0.99 ^ // speed will be 0.99. after 1 second the speed will be 0.54, which is 0.99 ^
// 60. // 60.
const FRIC_GROUND: f32 = 0.15; pub const FRIC_GROUND: f32 = 0.15;
const FRIC_AIR: f32 = 0.0125; pub const FRIC_AIR: f32 = 0.0125;
const FRIC_FLUID: f32 = 0.2; pub const FRIC_FLUID: f32 = 0.2;
// Integrates forces, calculates the new velocity based off of the old velocity // Integrates forces, calculates the new velocity based off of the old velocity
// dt = delta time // dt = delta time

View File

@ -44,4 +44,3 @@ diesel = { version = "1.4.3", features = ["sqlite"] }
diesel_migrations = "1.4.0" diesel_migrations = "1.4.0"
dotenv = "0.15.0" dotenv = "0.15.0"
slab = "0.4" slab = "0.4"
rand_chacha = "0.2"

View File

@ -326,6 +326,7 @@ impl Server {
// Insert the world into the ECS (todo: Maybe not an Arc?) // Insert the world into the ECS (todo: Maybe not an Arc?)
let world = Arc::new(world); let world = Arc::new(world);
state.ecs_mut().insert(world.clone()); state.ecs_mut().insert(world.clone());
state.ecs_mut().insert(index.clone());
// Set starting time for the server. // Set starting time for the server.
state.ecs_mut().write_resource::<TimeOfDay>().0 = settings.start_time; state.ecs_mut().write_resource::<TimeOfDay>().0 = settings.start_time;

View File

@ -0,0 +1,34 @@
use super::*;
use ::world::util::Grid;
pub struct Chunks {
chunks: Grid<Chunk>,
pub chunks_to_load: Vec<Vec2<i32>>,
pub chunks_to_unload: Vec<Vec2<i32>>,
}
impl Chunks {
pub fn new(size: Vec2<u32>) -> Self {
Chunks {
chunks: Grid::populate_from(size.map(|e| e as i32), |_| Chunk {
is_loaded: false,
}),
chunks_to_load: Vec::new(),
chunks_to_unload: Vec::new(),
}
}
pub fn chunk(&self, key: Vec2<i32>) -> Option<&Chunk> { self.chunks.get(key) }
pub fn size(&self) -> Vec2<u32> { self.chunks.size().map(|e| e as u32) }
pub fn chunk_mut(&mut self, key: Vec2<i32>) -> Option<&mut Chunk> { self.chunks.get_mut(key) }
pub fn chunk_at(&self, pos: Vec2<f32>) -> Option<&Chunk> {
self.chunks.get(pos.map2(TerrainChunk::RECT_SIZE, |e, sz| (e.floor() as i32).div_euclid(sz as i32)))
}
}
pub struct Chunk {
pub is_loaded: bool,
}

View File

@ -0,0 +1,93 @@
use super::*;
use world::{
util::RandomPerm,
World,
civ::{Site, Track},
};
use common::{
terrain::TerrainGrid,
store::Id,
LoadoutBuilder,
};
pub struct Entity {
pub is_loaded: bool,
pub pos: Vec3<f32>,
pub seed: u32,
pub last_tick: u64,
pub controller: RtSimController,
pub brain: Brain,
}
const PERM_SPECIES: u32 = 0;
const PERM_BODY: u32 = 1;
const PERM_LOADOUT: u32 = 2;
impl Entity {
pub fn rng(&self, perm: u32) -> impl Rng {
RandomPerm::new(self.seed + perm)
}
pub fn get_body(&self) -> comp::Body {
let species = *(&comp::humanoid::ALL_SPECIES).choose(&mut self.rng(PERM_SPECIES)).unwrap();
comp::humanoid::Body::random_with(&mut self.rng(PERM_BODY), &species).into()
}
pub fn get_loadout(&self) -> comp::Loadout {
let main_tool = comp::Item::new_from_asset_expect((&[
"common.items.weapons.sword.wood_sword",
"common.items.weapons.sword.starter_sword",
"common.items.weapons.sword.short_sword_0",
"common.items.weapons.bow.starter_bow",
"common.items.weapons.bow.leafy_longbow-0",
]).choose(&mut self.rng(PERM_LOADOUT)).unwrap());
LoadoutBuilder::build_loadout(self.get_body(), comp::Alignment::Npc, Some(main_tool), false)
.back(Some(comp::Item::new_from_asset_expect("common.items.armor.back.leather_adventurer")))
.build()
}
pub fn tick(&mut self, terrain: &TerrainGrid, world: &World) {
let tgt_site = self.brain.tgt.or_else(|| {
world.civs().sites
.iter()
.filter(|_| thread_rng().gen_range(0i32, 4) == 0)
.min_by_key(|(_, site)| {
let wpos = site.center * TerrainChunk::RECT_SIZE.map(|e| e as i32);
let dist = wpos.map(|e| e as f32).distance(self.pos.xy()) as u32;
dist + if dist < 96 {
100_000
} else {
0
}
})
.map(|(id, _)| id)
});
self.brain.tgt = tgt_site;
tgt_site.map(|tgt_site| {
let site = &world.civs().sites[tgt_site];
let wpos = site.center * TerrainChunk::RECT_SIZE.map(|e| e as i32);
let dist = wpos.map(|e| e as f32).distance(self.pos.xy()) as u32;
if dist < 64 {
self.brain.tgt = None;
}
let travel_to = self.pos.xy() + Vec3::from((wpos.map(|e| e as f32 + 0.5) - self.pos.xy())
.try_normalized()
.unwrap_or_else(Vec2::zero)) * 32.0;
let travel_to_alt = world.sim().get_alt_approx(travel_to.map(|e| e as i32)).unwrap_or(0.0) as i32;
let travel_to = terrain.find_space(Vec3::new(travel_to.x as i32, travel_to.y as i32, travel_to_alt)).map(|e| e as f32) + Vec3::new(0.5, 0.5, 0.0);
self.controller.travel_to = Some(travel_to);
self.controller.speed_factor = 0.75;
});
}
}
#[derive(Default)]
pub struct Brain {
tgt: Option<Id<Site>>,
track: Option<(Track, usize)>,
}

View File

@ -10,7 +10,7 @@ impl<'a> System<'a> for Sys {
); );
fn run(&mut self, (server_event_bus, mut rtsim): Self::SystemData) { fn run(&mut self, (server_event_bus, mut rtsim): Self::SystemData) {
for chunk in std::mem::take(&mut rtsim.world.chunks_to_load) { for chunk in std::mem::take(&mut rtsim.chunks.chunks_to_load) {
// TODO // TODO
} }
} }

View File

@ -1,53 +1,55 @@
mod load_chunks; mod load_chunks;
mod unload_chunks; mod unload_chunks;
mod tick; mod tick;
mod entity;
mod chunks;
use vek::*; use vek::*;
use world::util::Grid;
use common::{ use common::{
state::State, state::State,
terrain::TerrainChunk, terrain::TerrainChunk,
rtsim::{RtSimEntity, RtSimId}, rtsim::{RtSimEntity, RtSimId, RtSimController},
vol::RectRasterableVol, vol::RectRasterableVol,
comp,
}; };
use specs::{DispatcherBuilder, WorldExt}; use specs::{DispatcherBuilder, WorldExt};
use specs_idvs::IdvStorage; use specs_idvs::IdvStorage;
use slab::Slab; use slab::Slab;
use rand::prelude::*; use rand::prelude::*;
use self::{
entity::Entity,
chunks::Chunks,
};
pub struct RtSim { pub struct RtSim {
world: RtWorld, tick: u64,
chunks: Chunks,
entities: Slab<Entity>, entities: Slab<Entity>,
} }
impl RtSim { impl RtSim {
pub fn new(world_chunk_size: Vec2<u32>) -> Self { pub fn new(world_chunk_size: Vec2<u32>) -> Self {
Self { Self {
world: RtWorld { tick: 0,
chunks: Grid::populate_from(world_chunk_size.map(|e| e as i32), |_| Chunk { chunks: Chunks::new(world_chunk_size),
is_loaded: false,
}),
chunks_to_load: Vec::new(),
chunks_to_unload: Vec::new(),
},
entities: Slab::new(), entities: Slab::new(),
} }
} }
pub fn hook_load_chunk(&mut self, key: Vec2<i32>) { pub fn hook_load_chunk(&mut self, key: Vec2<i32>) {
if let Some(chunk) = self.world.chunks.get_mut(key) { if let Some(chunk) = self.chunks.chunk_mut(key) {
if !chunk.is_loaded { if !chunk.is_loaded {
chunk.is_loaded = true; chunk.is_loaded = true;
self.world.chunks_to_load.push(key); self.chunks.chunks_to_load.push(key);
} }
} }
} }
pub fn hook_unload_chunk(&mut self, key: Vec2<i32>) { pub fn hook_unload_chunk(&mut self, key: Vec2<i32>) {
if let Some(chunk) = self.world.chunks.get_mut(key) { if let Some(chunk) = self.chunks.chunk_mut(key) {
if chunk.is_loaded { if chunk.is_loaded {
chunk.is_loaded = false; chunk.is_loaded = false;
self.world.chunks_to_unload.push(key); self.chunks.chunks_to_unload.push(key);
} }
} }
} }
@ -72,28 +74,6 @@ impl RtSim {
} }
} }
pub struct RtWorld {
chunks: Grid<Chunk>,
chunks_to_load: Vec<Vec2<i32>>,
chunks_to_unload: Vec<Vec2<i32>>,
}
impl RtWorld {
pub fn chunk_at(&self, pos: Vec2<f32>) -> Option<&Chunk> {
self.chunks.get(pos.map2(TerrainChunk::RECT_SIZE, |e, sz| (e.floor() as i32).div_euclid(sz as i32)))
}
}
pub struct Chunk {
is_loaded: bool,
}
pub struct Entity {
is_loaded: bool,
pos: Vec3<f32>,
seed: u32,
}
const LOAD_CHUNK_SYS: &str = "rtsim_load_chunk_sys"; const LOAD_CHUNK_SYS: &str = "rtsim_load_chunk_sys";
const UNLOAD_CHUNK_SYS: &str = "rtsim_unload_chunk_sys"; const UNLOAD_CHUNK_SYS: &str = "rtsim_unload_chunk_sys";
const TICK_SYS: &str = "rtsim_tick_sys"; const TICK_SYS: &str = "rtsim_tick_sys";
@ -107,16 +87,19 @@ pub fn add_server_systems(dispatch_builder: &mut DispatcherBuilder) {
pub fn init(state: &mut State, world: &world::World) { pub fn init(state: &mut State, world: &world::World) {
let mut rtsim = RtSim::new(world.sim().get_size()); let mut rtsim = RtSim::new(world.sim().get_size());
for _ in 0..10 { for _ in 0..10000 {
let pos = Vec2::new( let pos = rtsim.chunks.size().map2(
thread_rng().gen_range(0, rtsim.world.chunks.size().x * TerrainChunk::RECT_SIZE.x as i32), TerrainChunk::RECT_SIZE,
thread_rng().gen_range(0, rtsim.world.chunks.size().y * TerrainChunk::RECT_SIZE.y as i32), |sz, chunk_sz| thread_rng().gen_range(0, sz * chunk_sz) as i32,
); );
let id = rtsim.entities.insert(Entity { let id = rtsim.entities.insert(Entity {
is_loaded: false, is_loaded: false,
pos: Vec3::from(pos.map(|e| e as f32)), pos: Vec3::from(pos.map(|e| e as f32)),
seed: thread_rng().gen(), seed: thread_rng().gen(),
controller: RtSimController::default(),
last_tick: 0,
brain: Default::default(),
}); });
tracing::info!("Spawned rtsim NPC {} at {:?}", id, pos); tracing::info!("Spawned rtsim NPC {} at {:?}", id, pos);

View File

@ -2,49 +2,79 @@ use super::*;
use common::{ use common::{
event::{EventBus, ServerEvent}, event::{EventBus, ServerEvent},
terrain::TerrainGrid, terrain::TerrainGrid,
rtsim::RtSimEntity, state::DeltaTime,
comp, comp,
}; };
use specs::{Join, Read, ReadStorage, System, Write, WriteExpect, ReadExpect}; use specs::{Join, Read, ReadStorage, WriteStorage, System, WriteExpect, ReadExpect};
use rand_chacha::ChaChaRng;
use std::sync::Arc; use std::sync::Arc;
const ENTITY_TICK_PERIOD: u64 = 30;
pub struct Sys; pub struct Sys;
impl<'a> System<'a> for Sys { impl<'a> System<'a> for Sys {
type SystemData = ( type SystemData = (
Read<'a, DeltaTime>,
Read<'a, EventBus<ServerEvent>>, Read<'a, EventBus<ServerEvent>>,
WriteExpect<'a, RtSim>, WriteExpect<'a, RtSim>,
ReadExpect<'a, TerrainGrid>, ReadExpect<'a, TerrainGrid>,
ReadExpect<'a, Arc<world::World>>, ReadExpect<'a, Arc<world::World>>,
ReadExpect<'a, world::IndexOwned>,
ReadStorage<'a, comp::Pos>, ReadStorage<'a, comp::Pos>,
ReadStorage<'a, RtSimEntity>, ReadStorage<'a, RtSimEntity>,
WriteStorage<'a, comp::Agent>,
); );
fn run( fn run(
&mut self, &mut self,
( (
dt,
server_event_bus, server_event_bus,
mut rtsim, mut rtsim,
terrain, terrain,
world, world,
index,
positions, positions,
rtsim_entities, rtsim_entities,
mut agents,
): Self::SystemData, ): Self::SystemData,
) { ) {
let rtsim = &mut *rtsim; let rtsim = &mut *rtsim;
rtsim.tick += 1;
if rtsim.tick % 300 == 0 {
if let Some((id, entity)) = rtsim.entities.iter().next() {
tracing::info!("Entity {} is at {:?}", id, entity.pos);
}
}
// Update rtsim entities // Update rtsim entities
// TODO: don't update all of them each tick // TODO: don't update all of them each tick
let mut to_reify = Vec::new(); let mut to_reify = Vec::new();
for (id, entity) in rtsim.entities.iter_mut() { for (id, entity) in rtsim.entities.iter_mut() {
if entity.is_loaded { if entity.is_loaded {
continue; // No load-specific behaviour yet
} else if rtsim.world.chunk_at(entity.pos.xy()).map(|c| c.is_loaded).unwrap_or(false) { } else if rtsim.chunks.chunk_at(entity.pos.xy()).map(|c| c.is_loaded).unwrap_or(false) {
to_reify.push(id); to_reify.push(id);
} else {
// Simulate behaviour
if let Some(travel_to) = entity.controller.travel_to {
// Move towards target at approximate character speed
entity.pos += Vec3::from((travel_to.xy() - entity.pos.xy())
.try_normalized()
.unwrap_or_else(Vec2::zero)
* entity.get_body().max_speed_approx()
* entity.controller.speed_factor)
* dt.0;
}
if let Some(alt) = world.sim().get_alt_approx(entity.pos.xy().map(|e| e.floor() as i32)) {
entity.pos.z = alt;
}
} }
if let Some(chunk) = world.sim().get_wpos(entity.pos.xy().map(|e| e.floor() as i32)) { // Tick entity AI
entity.pos.z = chunk.alt; if entity.last_tick + ENTITY_TICK_PERIOD <= rtsim.tick {
entity.tick(&terrain, &world);
entity.last_tick = rtsim.tick;
} }
} }
@ -52,25 +82,15 @@ impl<'a> System<'a> for Sys {
for id in to_reify { for id in to_reify {
rtsim.reify_entity(id); rtsim.reify_entity(id);
let entity = &rtsim.entities[id]; let entity = &rtsim.entities[id];
let mut rng = ChaChaRng::from_seed([ let spawn_pos = terrain.find_space(entity.pos.map(|e| e.floor() as i32)).map(|e| e as f32) + Vec3::new(0.5, 0.5, 0.0);
entity.seed.to_le_bytes()[0], let body = entity.get_body();
entity.seed.to_le_bytes()[1],
entity.seed.to_le_bytes()[2],
entity.seed.to_le_bytes()[3],
0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
]);
let species = *(&comp::humanoid::ALL_SPECIES).choose(&mut rng).unwrap();
let body = comp::humanoid::Body::random_with(&mut rng, &species).into();
server_emitter.emit(ServerEvent::CreateNpc { server_emitter.emit(ServerEvent::CreateNpc {
pos: comp::Pos(terrain.find_space(entity.pos.map(|e| e.floor() as i32)).map(|e| e as f32) + Vec3::new(0.5, 0.5, 0.0)), pos: comp::Pos(spawn_pos),
stats: comp::Stats::new("Rtsim Entity".to_string(), body), stats: comp::Stats::new("Traveller [rt]".to_string(), body),
health: comp::Health::new(body, 10), health: comp::Health::new(body, 10),
loadout: comp::Loadout::default(), loadout: entity.get_loadout(),
body, body,
agent: None, agent: Some(comp::Agent::new(None, true, &body)),
alignment: comp::Alignment::Npc, alignment: comp::Alignment::Npc,
scale: comp::Scale(1.0), scale: comp::Scale(1.0),
drop_item: None, drop_item: None,
@ -80,8 +100,11 @@ impl<'a> System<'a> for Sys {
} }
// Update rtsim with real entity data // Update rtsim with real entity data
for (pos, rtsim_entity) in (&positions, &rtsim_entities).join() { for (pos, rtsim_entity, agent) in (&positions, &rtsim_entities, &mut agents).join() {
rtsim.entities.get_mut(rtsim_entity.0).map(|entity| entity.pos = pos.0); rtsim.entities.get_mut(rtsim_entity.0).map(|entity| {
entity.pos = pos.0;
agent.rtsim_controller = entity.controller.clone();
});
} }
} }
} }

View File

@ -28,7 +28,7 @@ impl<'a> System<'a> for Sys {
positions, positions,
): Self::SystemData, ): Self::SystemData,
) { ) {
let chunks = std::mem::take(&mut rtsim.world.chunks_to_unload); let chunks = std::mem::take(&mut rtsim.chunks.chunks_to_unload);
for chunk in chunks { for chunk in chunks {
// TODO // TODO

View File

@ -189,7 +189,7 @@ impl<'a> System<'a> for Sys {
health, health,
loadout, loadout,
agent: if entity.has_agency { agent: if entity.has_agency {
Some(comp::Agent::new(entity.pos, can_speak, &body)) Some(comp::Agent::new(Some(entity.pos), can_speak, &body))
} else { } else {
None None
}, },

View File

@ -209,8 +209,8 @@ pub fn block_from_structure(
) -> Option<Block> { ) -> Option<Block> {
let field = RandomField::new(structure_seed); let field = RandomField::new(structure_seed);
let lerp = ((field.get(Vec3::from(structure_pos)).rem_euclid(256)) as f32 / 255.0) * 0.85 let lerp = ((field.get(Vec3::from(structure_pos)).rem_euclid(256)) as f32 / 255.0) * 0.8
+ ((field.get(pos + std::i32::MAX / 2).rem_euclid(256)) as f32 / 255.0) * 0.15; + ((field.get(pos + std::i32::MAX / 2).rem_euclid(256)) as f32 / 255.0) * 0.2;
match sblock { match sblock {
StructureBlock::None => None, StructureBlock::None => None,

View File

@ -41,21 +41,21 @@ const fn initial_civ_count(map_size_lg: MapSizeLg) -> u32 {
#[allow(clippy::type_complexity)] // TODO: Pending review in #587 #[allow(clippy::type_complexity)] // TODO: Pending review in #587
#[derive(Default)] #[derive(Default)]
pub struct Civs { pub struct Civs {
civs: Store<Civ>, pub civs: Store<Civ>,
places: Store<Place>, pub places: Store<Place>,
tracks: Store<Track>, pub tracks: Store<Track>,
/// We use this hasher (FxHasher64) because /// We use this hasher (FxHasher64) because
/// (1) we don't care about DDOS attacks (ruling out SipHash); /// (1) we don't care about DDOS attacks (ruling out SipHash);
/// (2) we care about determinism across computers (ruling out AAHash); /// (2) we care about determinism across computers (ruling out AAHash);
/// (3) we have 8-byte keys (for which FxHash is fastest). /// (3) we have 8-byte keys (for which FxHash is fastest).
track_map: HashMap< pub track_map: HashMap<
Id<Site>, Id<Site>,
HashMap<Id<Site>, Id<Track>, BuildHasherDefault<FxHasher64>>, HashMap<Id<Site>, Id<Track>, BuildHasherDefault<FxHasher64>>,
BuildHasherDefault<FxHasher64>, BuildHasherDefault<FxHasher64>,
>, >,
sites: Store<Site>, pub sites: Store<Site>,
} }
// Change this to get rid of particularly horrid seeds // Change this to get rid of particularly horrid seeds

View File

@ -1,4 +1,5 @@
use super::{seed_expan, Sampler}; use super::{seed_expan, Sampler};
use rand::RngCore;
use vek::*; use vek::*;
#[derive(Clone, Copy)] #[derive(Clone, Copy)]
@ -59,3 +60,26 @@ impl Sampler<'static> for RandomPerm {
seed_expan::diffuse_mult(&[self.seed, perm]) seed_expan::diffuse_mult(&[self.seed, perm])
} }
} }
// `RandomPerm` is not high-quality but it is at least fast and deterministic.
impl RngCore for RandomPerm {
fn next_u32(&mut self) -> u32 {
self.seed = self.get(self.seed);
self.seed
}
fn next_u64(&mut self) -> u64 {
let a = self.next_u32();
let b = self.next_u32();
(a as u64) << 32 | b as u64
}
fn fill_bytes(&mut self, dest: &mut [u8]) {
dest.iter_mut().for_each(|b| *b = (self.next_u32() & 0xFF) as u8);
}
fn try_fill_bytes(&mut self, dest: &mut [u8]) -> Result<(), rand::Error> {
self.fill_bytes(dest);
Ok(())
}
}