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",
"prometheus",
"rand 0.7.3",
"rand_chacha 0.2.2",
"ron",
"scan_fmt",
"serde",

View File

@ -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(),
}
}
}

View File

@ -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();
}
}

View File

@ -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,

View File

@ -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

View File

@ -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

View File

@ -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"

View File

@ -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;

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) {
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
}
}

View File

@ -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);

View File

@ -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();
});
}
}
}

View File

@ -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

View File

@ -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
},

View File

@ -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,

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
#[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

View File

@ -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(())
}
}