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 353a0f67be
commit 290cb52d0d
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 vek::*;
#[derive(Clone, Debug)]
pub enum Agent {
Wanderer(Vec2<f32>),
Pet {
target: EcsEntity,
chaser: Chaser,
},
Enemy {
bearing: Vec2<f32>,
target: Option<EcsEntity>,
},
#[derive(Copy, Clone, Debug, PartialEq)]
pub enum Alignment {
Wild,
Enemy,
Npc,
}
impl Alignment {
pub fn hostile_towards(self, other: Alignment) -> bool {
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 {
pub fn enemy() -> Self {
Agent::Enemy {
bearing: Vec2::zero(),
target: None,
}
}
pub fn pet(target: EcsEntity) -> Self {
Agent::Pet {
target,
chaser: Chaser::default(),
}
pub fn with_pet(mut self, owner: EcsEntity) -> Self {
self.owner = Some(owner);
self
}
}

View File

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

View File

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

View File

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

View File

@ -141,6 +141,7 @@ impl State {
ecs.register::<comp::Last<comp::Ori>>();
ecs.register::<comp::Last<comp::CharacterState>>();
ecs.register::<comp::Agent>();
ecs.register::<comp::Alignment>();
ecs.register::<comp::ForceUpdate>();
ecs.register::<comp::InventoryUpdate>();
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 rand::{seq::SliceRandom, thread_rng};
use specs::{Entities, Join, ReadExpect, ReadStorage, System, WriteStorage};
use crate::{
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::*;
/// This system will allow NPCs to modify their controller
pub struct Sys;
impl<'a> System<'a> for Sys {
type SystemData = (
Read<'a, UidAllocator>,
Entities<'a>,
ReadStorage<'a, Pos>,
ReadStorage<'a, Stats>,
ReadStorage<'a, CharacterState>,
ReadExpect<'a, TerrainGrid>,
ReadStorage<'a, Alignment>,
WriteStorage<'a, Agent>,
WriteStorage<'a, Controller>,
ReadStorage<'a, MountState>,
@ -23,19 +32,22 @@ impl<'a> System<'a> for Sys {
fn run(
&mut self,
(
uid_allocator,
entities,
positions,
stats,
character_states,
terrain,
alignments,
mut agents,
mut controllers,
mount_states,
): Self::SystemData,
) {
for (entity, pos, agent, controller, mount_state) in (
for (entity, pos, alignment, agent, controller, mount_state) in (
&entities,
&positions,
alignments.maybe(),
&mut agents,
&mut controllers,
mount_states.maybe(),
@ -60,6 +72,99 @@ impl<'a> System<'a> for Sys {
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 {
Agent::Wanderer(bearing) => {
*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 } => {
// Run towards 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 =
Vec2::from(bearing).try_normalized().unwrap_or(Vec2::zero());
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.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) {
match scan_fmt_some!(&args, action.arg_fmt, String, NpcKind, String) {
(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
.and_then(|a| a.parse().ok())
.filter(|x| *x > 0)
.unwrap_or(1)
.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) {
Some(pos) => {
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::MountState::Unmounted)
.with(agent.clone())
.with(alignment)
.build();
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 {
"hostile" => Some(comp::Agent::enemy()),
"friendly" => Some(comp::Agent::pet(target)),
// passive?
"wild" => Some(comp::Alignment::Wild),
"enemy" => Some(comp::Alignment::Enemy),
"npc" => Some(comp::Alignment::Npc),
_ => None,
}
}

View File

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

View File

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