Switched to _squared(), added comments, parallelised waypoint gen

This commit is contained in:
Joshua Barretto 2020-01-26 12:47:41 +00:00
parent 9ee72eb810
commit 631124f3fc
6 changed files with 122 additions and 111 deletions

View File

@ -1,6 +1,6 @@
#![deny(unsafe_code)]
#![type_length_limit = "1664759"]
#![feature(trait_alias, arbitrary_enum_discriminant)]
#![feature(trait_alias, arbitrary_enum_discriminant, label_break_value)]
#[macro_use]
extern crate serde_derive;

View File

@ -89,8 +89,7 @@ impl Route {
}
}
// Chaser: A self-contained system that attempts to chase a moving target
/// A self-contained system that attempts to chase a moving target, only performing pathfinding if necessary
#[derive(Default, Clone, Debug)]
pub struct Chaser {
last_search_tgt: Option<Vec3<f32>>,

View File

@ -80,83 +80,93 @@ impl<'a> System<'a> for Sys {
let mut do_idle = false;
let mut choose_target = false;
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.01
- if let Some(patrol_origin) = agent.patrol_origin {
Vec2::<f32>::from(pos.0 - patrol_origin) * 0.0002
} else {
Vec2::zero()
};
'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.01
- if let Some(patrol_origin) = agent.patrol_origin {
Vec2::<f32>::from(pos.0 - patrol_origin) * 0.0002
} else {
Vec2::zero()
};
if bearing.magnitude_squared() > 0.25f32.powf(2.0) {
inputs.move_dir = bearing.normalized() * 0.65;
}
// Sometimes try searching for new targets
if thread_rng().gen::<f32>() < 0.1 {
choose_target = true;
}
}
Activity::Follow(target, chaser) => {
if let (Some(tgt_pos), _tgt_stats) =
(positions.get(*target), stats.get(*target))
{
let dist = pos.0.distance(tgt_pos.0);
// Follow, or return to idle
if dist > AVG_FOLLOW_DIST {
if let Some(bearing) =
chaser.chase(&*terrain, pos.0, tgt_pos.0, AVG_FOLLOW_DIST)
{
inputs.move_dir =
Vec2::from(bearing).try_normalized().unwrap_or(Vec2::zero());
inputs.jump.set_state(bearing.z > 1.0);
}
} else {
do_idle = true;
if bearing.magnitude_squared() > 0.25f32.powf(2.0) {
inputs.move_dir = bearing.normalized() * 0.65;
}
// Sometimes try searching for new targets
if thread_rng().gen::<f32>() < 0.1 {
choose_target = true;
}
} else {
do_idle = true;
}
}
Activity::Attack(target, chaser, _) => {
if let (Some(tgt_pos), _tgt_stats, tgt_alignment) = (
positions.get(*target),
stats.get(*target),
alignments.get(*target),
) {
// Don't attack aligned entities
if let (Some(alignment), Some(tgt_alignment)) = (alignment, tgt_alignment) {
if !tgt_alignment.hostile_towards(*alignment) {
Activity::Follow(target, chaser) => {
if let (Some(tgt_pos), _tgt_stats) =
(positions.get(*target), stats.get(*target))
{
let dist_sqrd = pos.0.distance_squared(tgt_pos.0);
// Follow, or return to idle
if dist_sqrd > AVG_FOLLOW_DIST.powf(2.0) {
if let Some(bearing) =
chaser.chase(&*terrain, pos.0, tgt_pos.0, AVG_FOLLOW_DIST)
{
inputs.move_dir = Vec2::from(bearing)
.try_normalized()
.unwrap_or(Vec2::zero());
inputs.jump.set_state(bearing.z > 1.0);
}
} else {
do_idle = true;
}
} else {
do_idle = true;
}
}
Activity::Attack(target, chaser, _) => {
if let (Some(tgt_pos), _tgt_stats, tgt_alignment) = (
positions.get(*target),
stats.get(*target),
alignments.get(*target),
) {
// Don't attack aligned entities
// TODO: This is a bit of a hack, find a better way to do this
if let (Some(alignment), Some(tgt_alignment)) =
(alignment, tgt_alignment)
{
if !tgt_alignment.hostile_towards(*alignment) {
do_idle = true;
break 'activity;
}
}
let dist = pos.0.distance(tgt_pos.0);
if dist < MIN_ATTACK_DIST {
// Close-range attack
inputs.look_dir = tgt_pos.0 - pos.0;
inputs.move_dir = Vec2::from(tgt_pos.0 - pos.0)
.try_normalized()
.unwrap_or(Vec2::unit_y())
* 0.01;
inputs.primary.set_state(true);
} else if dist < MAX_CHASE_DIST {
// Long-range chase
if let Some(bearing) = chaser.chase(&*terrain, pos.0, tgt_pos.0, 1.25) {
inputs.move_dir =
Vec2::from(bearing).try_normalized().unwrap_or(Vec2::zero());
inputs.jump.set_state(bearing.z > 1.0);
let dist_sqrd = pos.0.distance_squared(tgt_pos.0);
if dist_sqrd < MIN_ATTACK_DIST.powf(2.0) {
// Close-range attack
inputs.look_dir = tgt_pos.0 - pos.0;
inputs.move_dir = Vec2::from(tgt_pos.0 - pos.0)
.try_normalized()
.unwrap_or(Vec2::unit_y())
* 0.01;
inputs.primary.set_state(true);
} else if dist_sqrd < MAX_CHASE_DIST.powf(2.0) {
// Long-range chase
if let Some(bearing) =
chaser.chase(&*terrain, pos.0, tgt_pos.0, 1.25)
{
inputs.move_dir = Vec2::from(bearing)
.try_normalized()
.unwrap_or(Vec2::zero());
inputs.jump.set_state(bearing.z > 1.0);
}
} else {
do_idle = true;
}
} else {
do_idle = true;
}
} else {
do_idle = true;
}
}
}
@ -165,12 +175,15 @@ impl<'a> System<'a> for Sys {
agent.activity = Activity::Idle(Vec2::zero());
}
// Choose a new target to attack: only go out of our way to attack targets we are
// hostile toward!
if choose_target {
// Search for new targets
// Search for new targets (this looks expensive, but it's only run occasionally)
// TODO: Replace this with a better system that doesn't consider *all* entities
let entities = (&entities, &positions, &stats, alignments.maybe())
.join()
.filter(|(e, e_pos, e_stats, e_alignment)| {
(e_pos.0 - pos.0).magnitude() < SIGHT_DIST
(e_pos.0 - pos.0).magnitude_squared() < SIGHT_DIST.powf(2.0)
&& *e != entity
&& !e_stats.is_dead
&& alignment
@ -206,8 +219,8 @@ impl<'a> System<'a> for Sys {
// Follow owner if we're too far, or if they're under attack
if let Some(owner) = agent.owner {
if let Some(owner_pos) = positions.get(owner) {
let dist = pos.0.distance(owner_pos.0);
if dist > MAX_FOLLOW_DIST && !agent.activity.is_follow() {
let dist_sqrd = pos.0.distance_squared(owner_pos.0);
if dist_sqrd > MAX_FOLLOW_DIST.powf(2.0) && !agent.activity.is_follow() {
agent.activity = Activity::Follow(owner, Chaser::default());
}

View File

@ -1088,10 +1088,11 @@ fn handle_remove_lights(
match opt_player_pos {
Some(player_pos) => {
let ecs = server.state.ecs();
for (entity, pos, _, _) in (
for (entity, pos, _, _, _) in (
&ecs.entities(),
&ecs.read_storage::<comp::Pos>(),
&ecs.read_storage::<comp::LightEmitter>(),
!&ecs.read_storage::<comp::WaypointArea>(),
!&ecs.read_storage::<comp::Player>(),
)
.join()

View File

@ -1,11 +1,8 @@
use common::comp::{Player, Pos, Waypoint, WaypointArea};
use specs::{Entities, Join, ReadStorage, System, WriteStorage};
/// This system will handle loading generated chunks and unloading uneeded chunks.
/// 1. Inserts newly generated chunks into the TerrainGrid
/// 2. Sends new chunks to neaby clients
/// 3. Handles the chunk's supplement (e.g. npcs)
/// 4. Removes chunks outside the range of players
/// This system updates player waypoints
/// TODO: Make this faster by only considering local waypoints
pub struct Sys;
impl<'a> System<'a> for Sys {
type SystemData = (

View File

@ -1454,40 +1454,41 @@ impl WorldSim {
.map(|i| {
(0..WORLD_SIZE.y)
.step_by(WAYPOINT_EVERY)
.filter_map(move |j| {
let mut pos = Vec2::new(i as i32, j as i32);
// Slide the waypoints down hills
for _ in 0..32 {
let last_pos = pos;
let chunk = this.get(pos)?;
for dir in [
Vec2::new(1, 0),
Vec2::new(-1, 0),
Vec2::new(0, 1),
Vec2::new(0, -1),
]
.iter()
{
const MAX_HEIGHT_DIFF: f32 = 8.0;
let tgt_chunk = this.get(pos + *dir)?;
if tgt_chunk.alt + MAX_HEIGHT_DIFF < chunk.alt
&& !tgt_chunk.is_underwater
{
pos += *dir;
}
}
if last_pos == pos {
break;
}
}
Some(pos)
})
.map(move |j| (i, j))
})
.flatten()
.collect::<Vec<_>>()
.into_par_iter()
.filter_map(|(i, j)| {
let mut pos = Vec2::new(i as i32, j as i32);
// Slide the waypoints down hills
for _ in 0..32 {
let last_pos = pos;
let chunk = this.get(pos)?;
for dir in [
Vec2::new(1, 0),
Vec2::new(-1, 0),
Vec2::new(0, 1),
Vec2::new(0, -1),
]
.iter()
{
const MAX_HEIGHT_DIFF: f32 = 8.0;
let tgt_chunk = this.get(pos + *dir)?;
if tgt_chunk.alt + MAX_HEIGHT_DIFF < chunk.alt && !tgt_chunk.is_underwater {
pos += *dir;
}
}
if last_pos == pos {
break;
}
}
Some(pos)
})
.collect::<Vec<_>>();
for waypoint in waypoints {