mirror of
https://gitlab.com/veloren/veloren.git
synced 2024-08-30 18:12:32 +00:00
I don't know how much I put in this commit, but it's some stuff
This commit is contained in:
parent
353a0f67be
commit
290cb52d0d
@ -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
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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,
|
||||
|
@ -102,6 +102,7 @@ pub enum ServerEvent {
|
||||
stats: comp::Stats,
|
||||
body: comp::Body,
|
||||
agent: comp::Agent,
|
||||
alignment: comp::Alignment,
|
||||
scale: comp::Scale,
|
||||
},
|
||||
ClientDisconnect(EcsEntity),
|
||||
|
@ -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,
|
||||
};
|
||||
|
||||
|
@ -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>();
|
||||
|
@ -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());
|
||||
|
@ -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,
|
||||
}
|
||||
}
|
||||
|
@ -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())
|
||||
|
@ -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),
|
||||
})
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user