I don't know how much I put in this commit, but it's some stuff

This commit is contained in:
Joshua Barretto 2020-01-24 21:24:57 +00:00
parent 02ead906a3
commit ca65700aea
9 changed files with 175 additions and 46 deletions

View File

@ -3,32 +3,38 @@ use specs::{Component, Entity as EcsEntity};
use specs_idvs::IDVStorage; use specs_idvs::IDVStorage;
use vek::*; use vek::*;
#[derive(Clone, Debug)] #[derive(Copy, Clone, Debug, PartialEq)]
pub enum Agent { pub enum Alignment {
Wanderer(Vec2<f32>), Wild,
Pet { Enemy,
target: EcsEntity, Npc,
chaser: Chaser, }
},
Enemy { impl Alignment {
bearing: Vec2<f32>, pub fn hostile_towards(self, other: Alignment) -> bool {
target: Option<EcsEntity>, match (self, other) {
}, (Alignment::Wild, Alignment::Npc) => true,
_ => self != other,
}
}
}
impl Component for Alignment {
type Storage = IDVStorage<Self>;
}
#[derive(Clone, Debug, Default)]
pub struct Agent {
pub chaser: Chaser,
pub target: Option<EcsEntity>,
pub owner: Option<EcsEntity>,
pub patrol_origin: Option<Vec3<f32>>,
} }
impl Agent { impl Agent {
pub fn enemy() -> Self { pub fn with_pet(mut self, owner: EcsEntity) -> Self {
Agent::Enemy { self.owner = Some(owner);
bearing: Vec2::zero(), self
target: None,
}
}
pub fn pet(target: EcsEntity) -> Self {
Agent::Pet {
target,
chaser: Chaser::default(),
}
} }
} }

View File

@ -16,7 +16,7 @@ mod visual;
// Reexports // Reexports
pub use admin::Admin; pub use admin::Admin;
pub use agent::Agent; pub use agent::{Agent, Alignment};
pub use body::{ pub use body::{
biped_large, bird_medium, bird_small, critter, dragon, fish_medium, fish_small, humanoid, biped_large, bird_medium, bird_small, critter, dragon, fish_medium, fish_small, humanoid,
object, quadruped_medium, quadruped_small, Body, object, quadruped_medium, quadruped_small, Body,

View File

@ -102,6 +102,7 @@ pub enum ServerEvent {
stats: comp::Stats, stats: comp::Stats,
body: comp::Body, body: comp::Body,
agent: comp::Agent, agent: comp::Agent,
alignment: comp::Alignment,
scale: comp::Scale, scale: comp::Scale,
}, },
ClientDisconnect(EcsEntity), ClientDisconnect(EcsEntity),

View File

@ -3,6 +3,7 @@ use crate::{
terrain::Block, terrain::Block,
vol::{BaseVol, ReadVol}, vol::{BaseVol, ReadVol},
}; };
use rand::{thread_rng, Rng};
use std::iter::FromIterator; use std::iter::FromIterator;
use vek::*; use vek::*;
@ -89,13 +90,19 @@ pub struct Chaser {
} }
impl Chaser { impl Chaser {
pub fn chase<V>(&mut self, vol: &V, pos: Vec3<f32>, tgt: Vec3<f32>) -> Option<Vec3<f32>> pub fn chase<V>(
&mut self,
vol: &V,
pos: Vec3<f32>,
tgt: Vec3<f32>,
min_dist: f32,
) -> Option<Vec3<f32>>
where where
V: BaseVol<Vox = Block> + ReadVol, V: BaseVol<Vox = Block> + ReadVol,
{ {
let pos_to_tgt = pos.distance(tgt); let pos_to_tgt = pos.distance(tgt);
if pos_to_tgt < 4.0 { if pos_to_tgt < min_dist {
return None; return None;
} }
@ -104,7 +111,7 @@ impl Chaser {
if end_to_tgt > pos_to_tgt * 0.3 + 5.0 { if end_to_tgt > pos_to_tgt * 0.3 + 5.0 {
None None
} else { } else {
if rand::random::<f32>() < 0.005 { if thread_rng().gen::<f32>() < 0.005 {
// TODO: Only re-calculate route when we're stuck // TODO: Only re-calculate route when we're stuck
self.route = Route::default(); self.route = Route::default();
} }
@ -205,12 +212,7 @@ where
let satisfied = |pos: &Vec3<i32>| pos == &end; let satisfied = |pos: &Vec3<i32>| pos == &end;
let mut new_astar = match astar.take() { let mut new_astar = match astar.take() {
None => { None => Astar::new(20_000, start, heuristic.clone()),
let max_iters = ((Vec2::<f32>::from(startf).distance(Vec2::from(endf)) * 2.0 + 25.0)
.powf(2.0) as usize)
.min(25_000);
Astar::new(max_iters, start, heuristic.clone())
}
Some(astar) => astar, Some(astar) => astar,
}; };

View File

@ -141,6 +141,7 @@ impl State {
ecs.register::<comp::Last<comp::Ori>>(); ecs.register::<comp::Last<comp::Ori>>();
ecs.register::<comp::Last<comp::CharacterState>>(); ecs.register::<comp::Last<comp::CharacterState>>();
ecs.register::<comp::Agent>(); ecs.register::<comp::Agent>();
ecs.register::<comp::Alignment>();
ecs.register::<comp::ForceUpdate>(); ecs.register::<comp::ForceUpdate>();
ecs.register::<comp::InventoryUpdate>(); ecs.register::<comp::InventoryUpdate>();
ecs.register::<comp::Admin>(); ecs.register::<comp::Admin>();

View File

@ -1,20 +1,29 @@
use crate::comp::{
Agent, CharacterState, Controller, MountState, MovementState::Glide, Pos, Stats,
};
use crate::terrain::TerrainGrid; use crate::terrain::TerrainGrid;
use rand::{seq::SliceRandom, thread_rng}; use crate::{
use specs::{Entities, Join, ReadExpect, ReadStorage, System, WriteStorage}; comp::{
self, Agent, Alignment, CharacterState, Controller, MountState, MovementState::Glide, Pos,
Stats,
},
sync::{Uid, UidAllocator},
};
use rand::{seq::SliceRandom, thread_rng, Rng};
use specs::{
saveload::{Marker, MarkerAllocator},
Entities, Join, Read, ReadExpect, ReadStorage, System, WriteStorage,
};
use vek::*; use vek::*;
/// This system will allow NPCs to modify their controller /// This system will allow NPCs to modify their controller
pub struct Sys; pub struct Sys;
impl<'a> System<'a> for Sys { impl<'a> System<'a> for Sys {
type SystemData = ( type SystemData = (
Read<'a, UidAllocator>,
Entities<'a>, Entities<'a>,
ReadStorage<'a, Pos>, ReadStorage<'a, Pos>,
ReadStorage<'a, Stats>, ReadStorage<'a, Stats>,
ReadStorage<'a, CharacterState>, ReadStorage<'a, CharacterState>,
ReadExpect<'a, TerrainGrid>, ReadExpect<'a, TerrainGrid>,
ReadStorage<'a, Alignment>,
WriteStorage<'a, Agent>, WriteStorage<'a, Agent>,
WriteStorage<'a, Controller>, WriteStorage<'a, Controller>,
ReadStorage<'a, MountState>, ReadStorage<'a, MountState>,
@ -23,19 +32,22 @@ impl<'a> System<'a> for Sys {
fn run( fn run(
&mut self, &mut self,
( (
uid_allocator,
entities, entities,
positions, positions,
stats, stats,
character_states, character_states,
terrain, terrain,
alignments,
mut agents, mut agents,
mut controllers, mut controllers,
mount_states, mount_states,
): Self::SystemData, ): Self::SystemData,
) { ) {
for (entity, pos, agent, controller, mount_state) in ( for (entity, pos, alignment, agent, controller, mount_state) in (
&entities, &entities,
&positions, &positions,
alignments.maybe(),
&mut agents, &mut agents,
&mut controllers, &mut controllers,
mount_states.maybe(), mount_states.maybe(),
@ -60,6 +72,99 @@ impl<'a> System<'a> for Sys {
let mut inputs = &mut controller.inputs; let mut inputs = &mut controller.inputs;
const PET_DIST: f32 = 12.0;
const PATROL_DIST: f32 = 48.0;
const SIGHT_DIST: f32 = 18.0;
const MIN_ATTACK_DIST: f32 = 3.25;
let mut chase_tgt = None;
let mut choose_target = false;
if let Some(target) = agent.target {
// Chase / attack target
if let (Some(tgt_pos), stats) = (positions.get(target), stats.get(target)) {
if stats.map(|s| s.is_dead).unwrap_or(false) {
// Don't target dead entities
choose_target = true;
} else if pos.0.distance(tgt_pos.0) < SIGHT_DIST {
chase_tgt = Some((tgt_pos.0, 1.0, true))
} else {
// Lose sight of enemies
choose_target = true;
}
} else {
choose_target = true;
}
} else if let Some(owner) = agent.owner {
if let Some(tgt_pos) = positions.get(owner) {
if pos.0.distance(tgt_pos.0) > PET_DIST || agent.target.is_none() {
// Follow owner
chase_tgt = Some((tgt_pos.0, 6.0, false));
} else {
choose_target = thread_rng().gen::<f32>() < 0.02;
}
} else {
agent.owner = None;
}
} else if let Some(patrol_origin) = agent.patrol_origin {
if pos.0.distance(patrol_origin) < PATROL_DIST {
// Return to patrol origin
chase_tgt = Some((patrol_origin, 64.0, false));
}
} else {
choose_target = thread_rng().gen::<f32>() < 0.05;
}
// Attack a target that's attacking us
if let Some(stats) = stats.get(entity) {
match stats.health.last_change.1.cause {
comp::HealthSource::Attack { by } => {
if agent.target.is_none() {
agent.target = uid_allocator.retrieve_entity_internal(by.id());
} else if thread_rng().gen::<f32>() < 0.005 {
agent.target = uid_allocator.retrieve_entity_internal(by.id());
}
}
_ => {}
}
}
if choose_target {
// Search for new targets
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 != entity
&& !e_stats.is_dead
&& alignment
.and_then(|a| e_alignment.map(|b| a.hostile_towards(*b)))
.unwrap_or(false)
})
.map(|(e, _, _, _)| e)
.collect::<Vec<_>>();
agent.target = (&entities).choose(&mut thread_rng()).cloned();
}
// Chase target
if let Some((tgt_pos, min_dist, aggressive)) = chase_tgt {
if let Some(bearing) = agent.chaser.chase(&*terrain, pos.0, tgt_pos, min_dist) {
inputs.move_dir = Vec2::from(bearing).try_normalized().unwrap_or(Vec2::zero());
inputs.jump.set_state(bearing.z > 1.0);
}
if pos.0.distance(tgt_pos) < MIN_ATTACK_DIST && aggressive {
inputs.look_dir = tgt_pos - pos.0;
inputs.move_dir = Vec2::from(tgt_pos - pos.0)
.try_normalized()
.unwrap_or(Vec2::zero())
* 0.01;
inputs.primary.set_state(true);
}
}
/*
match agent { match agent {
Agent::Wanderer(bearing) => { Agent::Wanderer(bearing) => {
*bearing += Vec2::new(rand::random::<f32>() - 0.5, rand::random::<f32>() - 0.5) *bearing += Vec2::new(rand::random::<f32>() - 0.5, rand::random::<f32>() - 0.5)
@ -74,7 +179,7 @@ impl<'a> System<'a> for Sys {
Agent::Pet { target, chaser } => { Agent::Pet { target, chaser } => {
// Run towards target. // Run towards target.
if let Some(tgt_pos) = positions.get(*target) { if let Some(tgt_pos) = positions.get(*target) {
if let Some(bearing) = chaser.chase(&*terrain, pos.0, tgt_pos.0) { if let Some(bearing) = chaser.chase(&*terrain, pos.0, tgt_pos.0, 5.0) {
inputs.move_dir = inputs.move_dir =
Vec2::from(bearing).try_normalized().unwrap_or(Vec2::zero()); Vec2::from(bearing).try_normalized().unwrap_or(Vec2::zero());
inputs.jump.set_state(bearing.z > 1.0); inputs.jump.set_state(bearing.z > 1.0);
@ -160,6 +265,7 @@ impl<'a> System<'a> for Sys {
} }
} }
} }
*/
debug_assert!(inputs.move_dir.map(|e| !e.is_nan()).reduce_and()); debug_assert!(inputs.move_dir.map(|e| !e.is_nan()).reduce_and());
debug_assert!(inputs.look_dir.map(|e| !e.is_nan()).reduce_and()); debug_assert!(inputs.look_dir.map(|e| !e.is_nan()).reduce_and());

View File

@ -458,13 +458,19 @@ fn handle_tp(server: &mut Server, entity: EcsEntity, args: String, action: &Chat
fn handle_spawn(server: &mut Server, entity: EcsEntity, args: String, action: &ChatCommand) { fn handle_spawn(server: &mut Server, entity: EcsEntity, args: String, action: &ChatCommand) {
match scan_fmt_some!(&args, action.arg_fmt, String, NpcKind, String) { match scan_fmt_some!(&args, action.arg_fmt, String, NpcKind, String) {
(Some(opt_align), Some(id), opt_amount) => { (Some(opt_align), Some(id), opt_amount) => {
if let Some(agent) = alignment_to_agent(&opt_align, entity) { if let Some(alignment) = parse_alignment(&opt_align) {
let amount = opt_amount let amount = opt_amount
.and_then(|a| a.parse().ok()) .and_then(|a| a.parse().ok())
.filter(|x| *x > 0) .filter(|x| *x > 0)
.unwrap_or(1) .unwrap_or(1)
.min(10); .min(10);
let agent = if let comp::Alignment::Npc = alignment {
comp::Agent::default().with_pet(entity)
} else {
comp::Agent::default()
};
match server.state.read_component_cloned::<comp::Pos>(entity) { match server.state.read_component_cloned::<comp::Pos>(entity) {
Some(pos) => { Some(pos) => {
for _ in 0..amount { for _ in 0..amount {
@ -486,6 +492,7 @@ fn handle_spawn(server: &mut Server, entity: EcsEntity, args: String, action: &C
.with(comp::Vel(vel)) .with(comp::Vel(vel))
.with(comp::MountState::Unmounted) .with(comp::MountState::Unmounted)
.with(agent.clone()) .with(agent.clone())
.with(alignment)
.build(); .build();
if let Some(uid) = server.state.ecs().uid_from_entity(new_entity) { if let Some(uid) = server.state.ecs().uid_from_entity(new_entity) {
@ -576,11 +583,11 @@ fn handle_help(server: &mut Server, entity: EcsEntity, _args: String, _action: &
} }
} }
fn alignment_to_agent(alignment: &str, target: EcsEntity) -> Option<comp::Agent> { fn parse_alignment(alignment: &str) -> Option<comp::Alignment> {
match alignment { match alignment {
"hostile" => Some(comp::Agent::enemy()), "wild" => Some(comp::Alignment::Wild),
"friendly" => Some(comp::Agent::pet(target)), "enemy" => Some(comp::Alignment::Enemy),
// passive? "npc" => Some(comp::Alignment::Npc),
_ => None, _ => None,
} }
} }

View File

@ -133,6 +133,7 @@ impl Server {
#[cfg(not(feature = "worldgen"))] #[cfg(not(feature = "worldgen"))]
let world = World::generate(settings.world_seed); let world = World::generate(settings.world_seed);
#[cfg(not(feature = "worldgen"))]
let map = vec![0]; let map = vec![0];
#[cfg(feature = "worldgen")] #[cfg(feature = "worldgen")]
@ -299,6 +300,7 @@ impl Server {
state.write_component(entity, comp::Ori(Vec3::unit_y())); state.write_component(entity, comp::Ori(Vec3::unit_y()));
state.write_component(entity, comp::Gravity(1.0)); state.write_component(entity, comp::Gravity(1.0));
state.write_component(entity, comp::CharacterState::default()); state.write_component(entity, comp::CharacterState::default());
state.write_component(entity, comp::Alignment::Npc);
state.write_component(entity, comp::Inventory::default()); state.write_component(entity, comp::Inventory::default());
state.write_component(entity, comp::InventoryUpdate); state.write_component(entity, comp::InventoryUpdate);
// Make sure physics are accepted. // Make sure physics are accepted.
@ -834,12 +836,14 @@ impl Server {
stats, stats,
body, body,
agent, agent,
alignment,
scale, scale,
} => { } => {
state state
.create_npc(pos, stats, body) .create_npc(pos, stats, body)
.with(agent) .with(agent)
.with(scale) .with(scale)
.with(alignment)
.build(); .build();
} }
@ -1228,6 +1232,7 @@ impl StateExt for State {
.with(comp::Controller::default()) .with(comp::Controller::default())
.with(body) .with(body)
.with(stats) .with(stats)
.with(comp::Alignment::Npc)
.with(comp::Energy::new(500)) .with(comp::Energy::new(500))
.with(comp::Gravity(1.0)) .with(comp::Gravity(1.0))
.with(comp::CharacterState::default()) .with(comp::CharacterState::default())

View File

@ -173,7 +173,8 @@ impl<'a> System<'a> for Sys {
pos: Pos(npc.pos), pos: Pos(npc.pos),
stats, stats,
body, body,
agent: comp::Agent::enemy(), alignment: comp::Alignment::Enemy,
agent: comp::Agent::default(),
scale: comp::Scale(scale), scale: comp::Scale(scale),
}) })
} }