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",
|
"portpicker",
|
||||||
"prometheus",
|
"prometheus",
|
||||||
"rand 0.7.3",
|
"rand 0.7.3",
|
||||||
"rand_chacha 0.2.2",
|
|
||||||
"ron",
|
"ron",
|
||||||
"scan_fmt",
|
"scan_fmt",
|
||||||
"serde",
|
"serde",
|
||||||
|
@ -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(),
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -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,
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
@ -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"
|
|
||||||
|
@ -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;
|
||||||
|
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) {
|
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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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);
|
||||||
|
@ -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();
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
},
|
},
|
||||||
|
@ -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,
|
||||||
|
@ -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
|
||||||
|
@ -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(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user