mirror of
https://gitlab.com/veloren/veloren.git
synced 2024-08-30 18:12:32 +00:00
Added rtsim entities moving when unloaded, better generation
This commit is contained in:
parent
427f431ac8
commit
22fb71905b
1
Cargo.lock
generated
1
Cargo.lock
generated
@ -5402,7 +5402,6 @@ dependencies = [
|
||||
"portpicker",
|
||||
"prometheus",
|
||||
"rand 0.7.3",
|
||||
"rand_chacha 0.2.2",
|
||||
"ron",
|
||||
"scan_fmt",
|
||||
"serde",
|
||||
|
@ -2,6 +2,7 @@ use crate::{
|
||||
comp::{humanoid, quadruped_low, quadruped_medium, quadruped_small, Body},
|
||||
path::Chaser,
|
||||
sync::Uid,
|
||||
rtsim::RtSimController,
|
||||
};
|
||||
use specs::{Component, Entity as EcsEntity};
|
||||
use specs_idvs::IdvStorage;
|
||||
@ -68,6 +69,7 @@ impl Component for Alignment {
|
||||
pub struct Psyche {
|
||||
pub aggro: f32, // 0.0 = always flees, 1.0 = always attacks, 0.5 = flee at 50% health
|
||||
}
|
||||
|
||||
impl<'a> From<&'a Body> for Psyche {
|
||||
fn from(body: &'a Body) -> Self {
|
||||
Self {
|
||||
@ -137,6 +139,7 @@ impl<'a> From<&'a Body> for Psyche {
|
||||
|
||||
#[derive(Clone, Debug, Default)]
|
||||
pub struct Agent {
|
||||
pub rtsim_controller: RtSimController,
|
||||
pub patrol_origin: Option<Vec3<f32>>,
|
||||
pub activity: Activity,
|
||||
/// Does the agent talk when e.g. hit by the player
|
||||
@ -151,8 +154,7 @@ impl Agent {
|
||||
self
|
||||
}
|
||||
|
||||
pub fn new(origin: Vec3<f32>, can_speak: bool, body: &Body) -> Self {
|
||||
let patrol_origin = Some(origin);
|
||||
pub fn new(patrol_origin: Option<Vec3<f32>>, can_speak: bool, body: &Body) -> Self {
|
||||
Agent {
|
||||
patrol_origin,
|
||||
can_speak,
|
||||
@ -168,7 +170,10 @@ impl Component for Agent {
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum Activity {
|
||||
Idle(Vec2<f32>),
|
||||
Idle {
|
||||
bearing: Vec2<f32>,
|
||||
chaser: Chaser,
|
||||
},
|
||||
Follow {
|
||||
target: EcsEntity,
|
||||
chaser: Chaser,
|
||||
@ -189,5 +194,10 @@ impl Activity {
|
||||
}
|
||||
|
||||
impl Default for Activity {
|
||||
fn default() -> Self { Activity::Idle(Vec2::zero()) }
|
||||
fn default() -> Self {
|
||||
Activity::Idle {
|
||||
bearing: Vec2::zero(),
|
||||
chaser: Chaser::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,9 +1,11 @@
|
||||
// We'd like to not have this file in `common`, but sadly there are
|
||||
// things in `common` that require it (currently, `ServerEvent`). When
|
||||
// possible, this should be moved to the `rtsim` module in `server`.
|
||||
// things in `common` that require it (currently, `ServerEvent` and
|
||||
// `Agent`). When possible, this should be moved to the `rtsim`
|
||||
// module in `server`.
|
||||
|
||||
use specs_idvs::IdvStorage;
|
||||
use specs::Component;
|
||||
use vek::*;
|
||||
|
||||
pub type RtSimId = usize;
|
||||
|
||||
@ -13,3 +15,37 @@ pub struct RtSimEntity(pub RtSimId);
|
||||
impl Component for RtSimEntity {
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
@ -5,7 +5,7 @@ use crate::{
|
||||
},
|
||||
event::LocalEvent,
|
||||
states::*,
|
||||
sys::{character_behavior::JoinData, phys::GRAVITY},
|
||||
sys::{character_behavior::JoinData, phys::{GRAVITY, FRIC_GROUND}},
|
||||
util::Dir,
|
||||
};
|
||||
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 {
|
||||
match self {
|
||||
Body::Humanoid(_) => 20.0,
|
||||
|
@ -212,40 +212,69 @@ impl<'a> System<'a> for Sys {
|
||||
|
||||
'activity: {
|
||||
match &mut agent.activity {
|
||||
Activity::Idle(bearing) => {
|
||||
*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
|
||||
});
|
||||
Activity::Idle { bearing, chaser } => {
|
||||
if let Some(travel_to) = agent.rtsim_controller.travel_to {
|
||||
if let Some((bearing, speed)) = chaser.chase(
|
||||
&*terrain,
|
||||
pos.0,
|
||||
vel.0,
|
||||
travel_to,
|
||||
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
|
||||
*bearing *= 0.1
|
||||
+ if terrain
|
||||
.ray(
|
||||
pos.0 + Vec3::unit_z(),
|
||||
pos.0
|
||||
+ Vec3::from(*bearing)
|
||||
.try_normalized()
|
||||
.unwrap_or(Vec3::unit_y())
|
||||
* 5.0
|
||||
+ Vec3::unit_z(),
|
||||
)
|
||||
.until(Block::is_solid)
|
||||
.cast()
|
||||
.1
|
||||
.map_or(true, |b| b.is_none())
|
||||
{
|
||||
0.9
|
||||
} else {
|
||||
0.0
|
||||
};
|
||||
// Stop if we're too close to a wall
|
||||
*bearing *= 0.1
|
||||
+ if terrain
|
||||
.ray(
|
||||
pos.0 + Vec3::unit_z(),
|
||||
pos.0
|
||||
+ Vec3::from(*bearing)
|
||||
.try_normalized()
|
||||
.unwrap_or(Vec3::unit_y())
|
||||
* 5.0
|
||||
+ Vec3::unit_z(),
|
||||
)
|
||||
.until(Block::is_solid)
|
||||
.cast()
|
||||
.1
|
||||
.map_or(true, |b| b.is_none())
|
||||
{
|
||||
0.9
|
||||
} else {
|
||||
0.0
|
||||
};
|
||||
|
||||
if bearing.magnitude_squared() > 0.5f32.powf(2.0) {
|
||||
inputs.move_dir = *bearing * 0.65;
|
||||
if bearing.magnitude_squared() > 0.5f32.powf(2.0) {
|
||||
inputs.move_dir = *bearing * 0.65;
|
||||
}
|
||||
|
||||
// Sit
|
||||
if thread_rng().gen::<f32>() < 0.0035 {
|
||||
controller.actions.push(ControlAction::Sit);
|
||||
}
|
||||
}
|
||||
|
||||
// Put away weapon
|
||||
@ -253,11 +282,6 @@ impl<'a> System<'a> for Sys {
|
||||
controller.actions.push(ControlAction::Unwield);
|
||||
}
|
||||
|
||||
// Sit
|
||||
if thread_rng().gen::<f32>() < 0.0035 {
|
||||
controller.actions.push(ControlAction::Sit);
|
||||
}
|
||||
|
||||
// Sometimes try searching for new targets
|
||||
if thread_rng().gen::<f32>() < 0.1 {
|
||||
choose_target = true;
|
||||
@ -621,7 +645,10 @@ impl<'a> System<'a> for Sys {
|
||||
}
|
||||
|
||||
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
|
||||
|
@ -19,16 +19,16 @@ use std::ops::Range;
|
||||
use vek::*;
|
||||
|
||||
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
|
||||
// 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
|
||||
// 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 ^
|
||||
// 60.
|
||||
const FRIC_GROUND: f32 = 0.15;
|
||||
const FRIC_AIR: f32 = 0.0125;
|
||||
const FRIC_FLUID: f32 = 0.2;
|
||||
pub const FRIC_GROUND: f32 = 0.15;
|
||||
pub const FRIC_AIR: f32 = 0.0125;
|
||||
pub const FRIC_FLUID: f32 = 0.2;
|
||||
|
||||
// Integrates forces, calculates the new velocity based off of the old velocity
|
||||
// dt = delta time
|
||||
|
@ -44,4 +44,3 @@ diesel = { version = "1.4.3", features = ["sqlite"] }
|
||||
diesel_migrations = "1.4.0"
|
||||
dotenv = "0.15.0"
|
||||
slab = "0.4"
|
||||
rand_chacha = "0.2"
|
||||
|
@ -326,6 +326,7 @@ impl Server {
|
||||
// Insert the world into the ECS (todo: Maybe not an Arc?)
|
||||
let world = Arc::new(world);
|
||||
state.ecs_mut().insert(world.clone());
|
||||
state.ecs_mut().insert(index.clone());
|
||||
|
||||
// Set starting time for the server.
|
||||
state.ecs_mut().write_resource::<TimeOfDay>().0 = settings.start_time;
|
||||
|
34
server/src/rtsim/chunks.rs
Normal file
34
server/src/rtsim/chunks.rs
Normal 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,
|
||||
}
|
93
server/src/rtsim/entity.rs
Normal file
93
server/src/rtsim/entity.rs
Normal 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)>,
|
||||
}
|
@ -10,7 +10,7 @@ impl<'a> System<'a> for Sys {
|
||||
);
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
|
@ -1,53 +1,55 @@
|
||||
mod load_chunks;
|
||||
mod unload_chunks;
|
||||
mod tick;
|
||||
mod entity;
|
||||
mod chunks;
|
||||
|
||||
use vek::*;
|
||||
use world::util::Grid;
|
||||
use common::{
|
||||
state::State,
|
||||
terrain::TerrainChunk,
|
||||
rtsim::{RtSimEntity, RtSimId},
|
||||
rtsim::{RtSimEntity, RtSimId, RtSimController},
|
||||
vol::RectRasterableVol,
|
||||
comp,
|
||||
};
|
||||
use specs::{DispatcherBuilder, WorldExt};
|
||||
use specs_idvs::IdvStorage;
|
||||
use slab::Slab;
|
||||
use rand::prelude::*;
|
||||
use self::{
|
||||
entity::Entity,
|
||||
chunks::Chunks,
|
||||
};
|
||||
|
||||
pub struct RtSim {
|
||||
world: RtWorld,
|
||||
tick: u64,
|
||||
chunks: Chunks,
|
||||
entities: Slab<Entity>,
|
||||
}
|
||||
|
||||
impl RtSim {
|
||||
pub fn new(world_chunk_size: Vec2<u32>) -> Self {
|
||||
Self {
|
||||
world: RtWorld {
|
||||
chunks: Grid::populate_from(world_chunk_size.map(|e| e as i32), |_| Chunk {
|
||||
is_loaded: false,
|
||||
}),
|
||||
chunks_to_load: Vec::new(),
|
||||
chunks_to_unload: Vec::new(),
|
||||
},
|
||||
tick: 0,
|
||||
chunks: Chunks::new(world_chunk_size),
|
||||
entities: Slab::new(),
|
||||
}
|
||||
}
|
||||
|
||||
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 {
|
||||
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>) {
|
||||
if let Some(chunk) = self.world.chunks.get_mut(key) {
|
||||
if let Some(chunk) = self.chunks.chunk_mut(key) {
|
||||
if chunk.is_loaded {
|
||||
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 UNLOAD_CHUNK_SYS: &str = "rtsim_unload_chunk_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) {
|
||||
let mut rtsim = RtSim::new(world.sim().get_size());
|
||||
|
||||
for _ in 0..10 {
|
||||
let pos = Vec2::new(
|
||||
thread_rng().gen_range(0, rtsim.world.chunks.size().x * TerrainChunk::RECT_SIZE.x as i32),
|
||||
thread_rng().gen_range(0, rtsim.world.chunks.size().y * TerrainChunk::RECT_SIZE.y as i32),
|
||||
for _ in 0..10000 {
|
||||
let pos = rtsim.chunks.size().map2(
|
||||
TerrainChunk::RECT_SIZE,
|
||||
|sz, chunk_sz| thread_rng().gen_range(0, sz * chunk_sz) as i32,
|
||||
);
|
||||
|
||||
let id = rtsim.entities.insert(Entity {
|
||||
is_loaded: false,
|
||||
pos: Vec3::from(pos.map(|e| e as f32)),
|
||||
seed: thread_rng().gen(),
|
||||
controller: RtSimController::default(),
|
||||
last_tick: 0,
|
||||
brain: Default::default(),
|
||||
});
|
||||
|
||||
tracing::info!("Spawned rtsim NPC {} at {:?}", id, pos);
|
||||
|
@ -2,49 +2,79 @@ use super::*;
|
||||
use common::{
|
||||
event::{EventBus, ServerEvent},
|
||||
terrain::TerrainGrid,
|
||||
rtsim::RtSimEntity,
|
||||
state::DeltaTime,
|
||||
comp,
|
||||
};
|
||||
use specs::{Join, Read, ReadStorage, System, Write, WriteExpect, ReadExpect};
|
||||
use rand_chacha::ChaChaRng;
|
||||
use specs::{Join, Read, ReadStorage, WriteStorage, System, WriteExpect, ReadExpect};
|
||||
use std::sync::Arc;
|
||||
|
||||
const ENTITY_TICK_PERIOD: u64 = 30;
|
||||
|
||||
pub struct Sys;
|
||||
impl<'a> System<'a> for Sys {
|
||||
type SystemData = (
|
||||
Read<'a, DeltaTime>,
|
||||
Read<'a, EventBus<ServerEvent>>,
|
||||
WriteExpect<'a, RtSim>,
|
||||
ReadExpect<'a, TerrainGrid>,
|
||||
ReadExpect<'a, Arc<world::World>>,
|
||||
ReadExpect<'a, world::IndexOwned>,
|
||||
ReadStorage<'a, comp::Pos>,
|
||||
ReadStorage<'a, RtSimEntity>,
|
||||
WriteStorage<'a, comp::Agent>,
|
||||
);
|
||||
|
||||
fn run(
|
||||
&mut self,
|
||||
(
|
||||
dt,
|
||||
server_event_bus,
|
||||
mut rtsim,
|
||||
terrain,
|
||||
world,
|
||||
index,
|
||||
positions,
|
||||
rtsim_entities,
|
||||
mut agents,
|
||||
): Self::SystemData,
|
||||
) {
|
||||
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
|
||||
// TODO: don't update all of them each tick
|
||||
let mut to_reify = Vec::new();
|
||||
for (id, entity) in rtsim.entities.iter_mut() {
|
||||
if entity.is_loaded {
|
||||
continue;
|
||||
} else if rtsim.world.chunk_at(entity.pos.xy()).map(|c| c.is_loaded).unwrap_or(false) {
|
||||
// No load-specific behaviour yet
|
||||
} else if rtsim.chunks.chunk_at(entity.pos.xy()).map(|c| c.is_loaded).unwrap_or(false) {
|
||||
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)) {
|
||||
entity.pos.z = chunk.alt;
|
||||
// Tick entity AI
|
||||
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 {
|
||||
rtsim.reify_entity(id);
|
||||
let entity = &rtsim.entities[id];
|
||||
let mut rng = ChaChaRng::from_seed([
|
||||
entity.seed.to_le_bytes()[0],
|
||||
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();
|
||||
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);
|
||||
let body = entity.get_body();
|
||||
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)),
|
||||
stats: comp::Stats::new("Rtsim Entity".to_string(), body),
|
||||
pos: comp::Pos(spawn_pos),
|
||||
stats: comp::Stats::new("Traveller [rt]".to_string(), body),
|
||||
health: comp::Health::new(body, 10),
|
||||
loadout: comp::Loadout::default(),
|
||||
loadout: entity.get_loadout(),
|
||||
body,
|
||||
agent: None,
|
||||
agent: Some(comp::Agent::new(None, true, &body)),
|
||||
alignment: comp::Alignment::Npc,
|
||||
scale: comp::Scale(1.0),
|
||||
drop_item: None,
|
||||
@ -80,8 +100,11 @@ impl<'a> System<'a> for Sys {
|
||||
}
|
||||
|
||||
// Update rtsim with real entity data
|
||||
for (pos, rtsim_entity) in (&positions, &rtsim_entities).join() {
|
||||
rtsim.entities.get_mut(rtsim_entity.0).map(|entity| entity.pos = pos.0);
|
||||
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;
|
||||
agent.rtsim_controller = entity.controller.clone();
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -28,7 +28,7 @@ impl<'a> System<'a> for Sys {
|
||||
positions,
|
||||
): 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 {
|
||||
// TODO
|
||||
|
@ -189,7 +189,7 @@ impl<'a> System<'a> for Sys {
|
||||
health,
|
||||
loadout,
|
||||
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 {
|
||||
None
|
||||
},
|
||||
|
@ -209,8 +209,8 @@ pub fn block_from_structure(
|
||||
) -> Option<Block> {
|
||||
let field = RandomField::new(structure_seed);
|
||||
|
||||
let lerp = ((field.get(Vec3::from(structure_pos)).rem_euclid(256)) as f32 / 255.0) * 0.85
|
||||
+ ((field.get(pos + std::i32::MAX / 2).rem_euclid(256)) as f32 / 255.0) * 0.15;
|
||||
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.2;
|
||||
|
||||
match sblock {
|
||||
StructureBlock::None => None,
|
||||
|
@ -41,21 +41,21 @@ const fn initial_civ_count(map_size_lg: MapSizeLg) -> u32 {
|
||||
#[allow(clippy::type_complexity)] // TODO: Pending review in #587
|
||||
#[derive(Default)]
|
||||
pub struct Civs {
|
||||
civs: Store<Civ>,
|
||||
places: Store<Place>,
|
||||
pub civs: Store<Civ>,
|
||||
pub places: Store<Place>,
|
||||
|
||||
tracks: Store<Track>,
|
||||
pub tracks: Store<Track>,
|
||||
/// We use this hasher (FxHasher64) because
|
||||
/// (1) we don't care about DDOS attacks (ruling out SipHash);
|
||||
/// (2) we care about determinism across computers (ruling out AAHash);
|
||||
/// (3) we have 8-byte keys (for which FxHash is fastest).
|
||||
track_map: HashMap<
|
||||
pub track_map: HashMap<
|
||||
Id<Site>,
|
||||
HashMap<Id<Site>, Id<Track>, BuildHasherDefault<FxHasher64>>,
|
||||
BuildHasherDefault<FxHasher64>,
|
||||
>,
|
||||
|
||||
sites: Store<Site>,
|
||||
pub sites: Store<Site>,
|
||||
}
|
||||
|
||||
// Change this to get rid of particularly horrid seeds
|
||||
|
@ -1,4 +1,5 @@
|
||||
use super::{seed_expan, Sampler};
|
||||
use rand::RngCore;
|
||||
use vek::*;
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
@ -59,3 +60,26 @@ impl Sampler<'static> for RandomPerm {
|
||||
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(())
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user