2020-12-01 00:28:00 +00:00
|
|
|
use common::{
|
2020-04-18 18:28:19 +00:00
|
|
|
comp::{
|
|
|
|
self,
|
2021-02-07 07:22:06 +00:00
|
|
|
agent::{AgentEvent, Tactic, Target, DEFAULT_INTERACTION_TIME},
|
2020-04-26 17:03:19 +00:00
|
|
|
group,
|
2021-01-08 19:12:09 +00:00
|
|
|
inventory::slot::EquipSlot,
|
2021-02-07 07:22:06 +00:00
|
|
|
invite::InviteResponse,
|
2020-11-06 17:39:49 +00:00
|
|
|
item::{
|
|
|
|
tool::{ToolKind, UniqueKind},
|
|
|
|
ItemKind,
|
|
|
|
},
|
2021-01-08 20:53:52 +00:00
|
|
|
skills::{AxeSkill, BowSkill, HammerSkill, Skill, StaffSkill, SwordSkill},
|
2020-11-19 08:04:36 +00:00
|
|
|
Agent, Alignment, Body, CharacterState, ControlAction, ControlEvent, Controller, Energy,
|
2021-02-13 23:32:55 +00:00
|
|
|
Health, Inventory, LightEmitter, MountState, Ori, PhysicsState, Pos, Scale, Stats,
|
|
|
|
UnresolvedChatMsg, Vel,
|
2020-04-18 18:28:19 +00:00
|
|
|
},
|
2021-02-07 07:22:06 +00:00
|
|
|
event::{Emitter, EventBus, ServerEvent},
|
|
|
|
path::TraversalConfig,
|
|
|
|
resources::{DeltaTime, TimeOfDay},
|
2020-09-26 13:55:01 +00:00
|
|
|
terrain::{Block, TerrainGrid},
|
2020-10-07 02:23:20 +00:00
|
|
|
time::DayPeriod,
|
2020-12-13 17:40:15 +00:00
|
|
|
uid::{Uid, UidAllocator},
|
2020-03-28 01:31:22 +00:00
|
|
|
util::Dir,
|
2020-01-27 10:02:36 +00:00
|
|
|
vol::ReadVol,
|
2020-01-24 21:24:57 +00:00
|
|
|
};
|
2021-03-09 10:52:57 +00:00
|
|
|
use common_ecs::{Job, Origin, ParMode, Phase, System};
|
2020-01-27 15:51:07 +00:00
|
|
|
use rand::{thread_rng, Rng};
|
2020-11-25 22:47:16 +00:00
|
|
|
use rayon::iter::ParallelIterator;
|
2020-01-24 21:24:57 +00:00
|
|
|
use specs::{
|
|
|
|
saveload::{Marker, MarkerAllocator},
|
2021-02-07 07:22:06 +00:00
|
|
|
shred::ResourceId,
|
2021-03-04 14:00:16 +00:00
|
|
|
Entities, Entity as EcsEntity, Join, ParJoin, Read, ReadExpect, ReadStorage, SystemData, World,
|
|
|
|
Write, WriteStorage,
|
2020-01-24 21:24:57 +00:00
|
|
|
};
|
2020-11-11 03:18:45 +00:00
|
|
|
use std::f32::consts::PI;
|
2019-04-16 21:06:33 +00:00
|
|
|
use vek::*;
|
|
|
|
|
2021-02-07 07:22:06 +00:00
|
|
|
struct AgentData<'a> {
|
|
|
|
entity: &'a EcsEntity,
|
|
|
|
uid: &'a Uid,
|
|
|
|
pos: &'a Pos,
|
|
|
|
vel: &'a Vel,
|
|
|
|
ori: &'a Ori,
|
|
|
|
energy: &'a Energy,
|
|
|
|
body: Option<&'a Body>,
|
|
|
|
inventory: &'a Inventory,
|
|
|
|
stats: &'a Stats,
|
|
|
|
physics_state: &'a PhysicsState,
|
|
|
|
alignment: Option<&'a Alignment>,
|
|
|
|
traversal_config: TraversalConfig,
|
|
|
|
scale: f32,
|
|
|
|
flees: bool,
|
|
|
|
damage: f32,
|
|
|
|
light_emitter: Option<&'a LightEmitter>,
|
|
|
|
glider_equipped: bool,
|
|
|
|
is_gliding: bool,
|
|
|
|
}
|
|
|
|
|
|
|
|
#[derive(SystemData)]
|
|
|
|
pub struct ReadData<'a> {
|
|
|
|
entities: Entities<'a>,
|
|
|
|
uid_allocator: Read<'a, UidAllocator>,
|
|
|
|
dt: Read<'a, DeltaTime>,
|
|
|
|
group_manager: Read<'a, group::GroupManager>,
|
|
|
|
energies: ReadStorage<'a, Energy>,
|
|
|
|
positions: ReadStorage<'a, Pos>,
|
|
|
|
velocities: ReadStorage<'a, Vel>,
|
|
|
|
orientations: ReadStorage<'a, Ori>,
|
|
|
|
scales: ReadStorage<'a, Scale>,
|
|
|
|
healths: ReadStorage<'a, Health>,
|
|
|
|
inventories: ReadStorage<'a, Inventory>,
|
|
|
|
stats: ReadStorage<'a, Stats>,
|
|
|
|
physics_states: ReadStorage<'a, PhysicsState>,
|
|
|
|
char_states: ReadStorage<'a, CharacterState>,
|
|
|
|
uids: ReadStorage<'a, Uid>,
|
|
|
|
groups: ReadStorage<'a, group::Group>,
|
|
|
|
terrain: ReadExpect<'a, TerrainGrid>,
|
|
|
|
alignments: ReadStorage<'a, Alignment>,
|
|
|
|
bodies: ReadStorage<'a, Body>,
|
|
|
|
mount_states: ReadStorage<'a, MountState>,
|
|
|
|
//ReadStorage<'a, Invite>,
|
|
|
|
time_of_day: Read<'a, TimeOfDay>,
|
|
|
|
light_emitter: ReadStorage<'a, LightEmitter>,
|
|
|
|
}
|
|
|
|
|
2021-01-31 20:29:50 +00:00
|
|
|
// This is 3.1 to last longer than the last damage timer (3.0 seconds)
|
2021-03-03 03:55:28 +00:00
|
|
|
const DAMAGE_MEMORY_DURATION: f64 = 0.1;
|
|
|
|
const FLEE_DURATION: f32 = 3.0;
|
2021-02-07 07:22:06 +00:00
|
|
|
const MAX_FOLLOW_DIST: f32 = 12.0;
|
2021-03-06 15:50:04 +00:00
|
|
|
const MAX_CHASE_DIST: f32 = 250.0;
|
2021-02-07 07:22:06 +00:00
|
|
|
const MAX_FLEE_DIST: f32 = 20.0;
|
|
|
|
const LISTEN_DIST: f32 = 16.0;
|
|
|
|
const SEARCH_DIST: f32 = 48.0;
|
|
|
|
const SIGHT_DIST: f32 = 80.0;
|
|
|
|
const SNEAK_COEFFICIENT: f32 = 0.25;
|
|
|
|
const AVG_FOLLOW_DIST: f32 = 6.0;
|
2021-01-31 20:29:50 +00:00
|
|
|
|
2019-06-09 19:33:20 +00:00
|
|
|
/// This system will allow NPCs to modify their controller
|
2021-03-04 14:00:16 +00:00
|
|
|
#[derive(Default)]
|
2019-04-16 21:06:33 +00:00
|
|
|
pub struct Sys;
|
2021-03-08 11:13:59 +00:00
|
|
|
impl<'a> System<'a> for Sys {
|
2020-06-10 19:47:36 +00:00
|
|
|
#[allow(clippy::type_complexity)]
|
2019-04-16 21:06:33 +00:00
|
|
|
type SystemData = (
|
2021-02-07 07:22:06 +00:00
|
|
|
ReadData<'a>,
|
2020-06-04 07:11:35 +00:00
|
|
|
Write<'a, EventBus<ServerEvent>>,
|
2019-08-02 20:25:33 +00:00
|
|
|
WriteStorage<'a, Agent>,
|
2019-06-09 14:20:20 +00:00
|
|
|
WriteStorage<'a, Controller>,
|
2019-04-16 21:06:33 +00:00
|
|
|
);
|
|
|
|
|
2021-03-04 14:00:16 +00:00
|
|
|
const NAME: &'static str = "agent";
|
|
|
|
const ORIGIN: Origin = Origin::Server;
|
|
|
|
const PHASE: Phase = Phase::Create;
|
|
|
|
|
2020-06-10 19:47:36 +00:00
|
|
|
#[allow(clippy::or_fun_call)] // TODO: Pending review in #587
|
2019-08-04 08:21:29 +00:00
|
|
|
fn run(
|
2021-03-09 10:52:57 +00:00
|
|
|
job: &mut Job<Self>,
|
2021-03-08 08:36:53 +00:00
|
|
|
(read_data, event_bus, mut agents, mut controllers): Self::SystemData,
|
2019-08-04 08:21:29 +00:00
|
|
|
) {
|
2021-03-09 10:52:57 +00:00
|
|
|
job.cpu_stats.measure(ParMode::Rayon);
|
2020-11-25 22:47:16 +00:00
|
|
|
(
|
2021-02-07 07:22:06 +00:00
|
|
|
&read_data.entities,
|
|
|
|
(&read_data.energies, &read_data.healths),
|
|
|
|
&read_data.positions,
|
|
|
|
&read_data.velocities,
|
|
|
|
&read_data.orientations,
|
|
|
|
read_data.bodies.maybe(),
|
|
|
|
&read_data.inventories,
|
|
|
|
&read_data.stats,
|
|
|
|
&read_data.physics_states,
|
|
|
|
&read_data.uids,
|
2019-09-09 19:11:40 +00:00
|
|
|
&mut agents,
|
|
|
|
&mut controllers,
|
2021-02-07 07:22:06 +00:00
|
|
|
read_data.light_emitter.maybe(),
|
|
|
|
read_data.groups.maybe(),
|
|
|
|
read_data.mount_states.maybe(),
|
2019-09-09 19:11:40 +00:00
|
|
|
)
|
2020-11-25 22:47:16 +00:00
|
|
|
.par_join()
|
2021-02-07 07:22:06 +00:00
|
|
|
.filter(|(_, _, _, _, _, _, _, _, _, _, _, _, _, _, mount_state)| {
|
2020-11-25 22:47:16 +00:00
|
|
|
// Skip mounted entities
|
2021-02-07 07:22:06 +00:00
|
|
|
mount_state
|
|
|
|
.map(|ms| *ms == MountState::Unmounted)
|
|
|
|
.unwrap_or(true)
|
2020-11-25 22:47:16 +00:00
|
|
|
})
|
2021-02-07 07:22:06 +00:00
|
|
|
.for_each(
|
|
|
|
|(
|
|
|
|
entity,
|
|
|
|
(energy, health),
|
|
|
|
pos,
|
|
|
|
vel,
|
|
|
|
ori,
|
|
|
|
body,
|
|
|
|
inventory,
|
|
|
|
stats,
|
|
|
|
physics_state,
|
|
|
|
uid,
|
|
|
|
agent,
|
|
|
|
controller,
|
|
|
|
light_emitter,
|
|
|
|
groups,
|
|
|
|
_,
|
|
|
|
)| {
|
|
|
|
//// Hack, replace with better system when groups are more sophisticated
|
|
|
|
//// Override alignment if in a group unless entity is owned already
|
|
|
|
let alignment = if !matches!(
|
|
|
|
&read_data.alignments.get(entity),
|
|
|
|
&Some(Alignment::Owned(_))
|
|
|
|
) {
|
|
|
|
groups
|
|
|
|
.and_then(|g| read_data.group_manager.group_info(*g))
|
|
|
|
.and_then(|info| read_data.uids.get(info.leader))
|
|
|
|
.copied()
|
|
|
|
.map(Alignment::Owned)
|
|
|
|
.or(read_data.alignments.get(entity).copied())
|
|
|
|
} else {
|
|
|
|
read_data.alignments.get(entity).copied()
|
|
|
|
};
|
2019-09-09 19:11:40 +00:00
|
|
|
|
2021-02-07 07:22:06 +00:00
|
|
|
controller.reset();
|
|
|
|
let mut event_emitter = event_bus.emitter();
|
2021-02-22 00:57:25 +00:00
|
|
|
|
2021-02-07 07:22:06 +00:00
|
|
|
// Default to looking in orientation direction (can be overridden below)
|
|
|
|
controller.inputs.look_dir = ori.look_dir();
|
2021-02-22 00:57:25 +00:00
|
|
|
|
2021-02-07 07:22:06 +00:00
|
|
|
let scale = read_data.scales.get(entity).map(|s| s.0).unwrap_or(1.0);
|
2020-01-25 18:49:47 +00:00
|
|
|
|
2021-02-07 07:22:06 +00:00
|
|
|
let glider_equipped = inventory
|
|
|
|
.equipped(EquipSlot::Glider)
|
|
|
|
.as_ref()
|
|
|
|
.map_or(false, |item| {
|
|
|
|
matches!(item.kind(), comp::item::ItemKind::Glider(_))
|
|
|
|
});
|
|
|
|
let is_gliding = matches!(
|
|
|
|
read_data.char_states.get(entity),
|
|
|
|
Some(CharacterState::GlideWield) | Some(CharacterState::Glide)
|
|
|
|
) && !physics_state.on_ground;
|
2020-11-12 21:31:28 +00:00
|
|
|
|
2021-02-07 07:22:06 +00:00
|
|
|
// This controls how picky NPCs are about their pathfinding. Giants are larger
|
|
|
|
// and so can afford to be less precise when trying to move around
|
|
|
|
// the world (especially since they would otherwise get stuck on
|
|
|
|
// obstacles that smaller entities would not).
|
|
|
|
let node_tolerance = scale * 1.5;
|
|
|
|
let slow_factor = body.map(|b| b.base_accel() / 250.0).unwrap_or(0.0).min(1.0);
|
|
|
|
let traversal_config = TraversalConfig {
|
|
|
|
node_tolerance,
|
|
|
|
slow_factor,
|
|
|
|
on_ground: physics_state.on_ground,
|
|
|
|
in_liquid: physics_state.in_liquid.is_some(),
|
|
|
|
min_tgt_dist: 1.0,
|
|
|
|
can_climb: body.map(|b| b.can_climb()).unwrap_or(false),
|
|
|
|
can_fly: body.map(|b| b.can_fly()).unwrap_or(false),
|
|
|
|
};
|
2020-01-26 12:47:41 +00:00
|
|
|
|
2021-02-07 07:22:06 +00:00
|
|
|
let flees = alignment
|
|
|
|
.map(|a| !matches!(a, Alignment::Enemy | Alignment::Owned(_)))
|
|
|
|
.unwrap_or(true);
|
2020-11-12 21:31:28 +00:00
|
|
|
|
2021-02-07 07:22:06 +00:00
|
|
|
let damage = health.current() as f32 / health.maximum() as f32;
|
2020-11-23 19:27:18 +00:00
|
|
|
|
2021-02-07 07:22:06 +00:00
|
|
|
// Package all this agent's data into a convenient struct
|
|
|
|
let data = AgentData {
|
|
|
|
entity: &entity,
|
|
|
|
uid,
|
|
|
|
pos,
|
|
|
|
vel,
|
|
|
|
ori,
|
|
|
|
energy,
|
|
|
|
body,
|
|
|
|
inventory,
|
|
|
|
stats,
|
|
|
|
physics_state,
|
|
|
|
alignment: alignment.as_ref(),
|
|
|
|
traversal_config,
|
|
|
|
scale,
|
|
|
|
flees,
|
|
|
|
damage,
|
|
|
|
light_emitter,
|
|
|
|
glider_equipped,
|
|
|
|
is_gliding,
|
|
|
|
};
|
2021-01-24 04:00:57 +00:00
|
|
|
|
2021-02-07 07:22:06 +00:00
|
|
|
///////////////////////////////////////////////////////////
|
|
|
|
// Behavior tree
|
|
|
|
///////////////////////////////////////////////////////////
|
2020-01-25 23:39:38 +00:00
|
|
|
|
2021-02-07 07:22:06 +00:00
|
|
|
// If falling fast and can glide, save yourself!
|
|
|
|
if data.fall_glide() {
|
|
|
|
//toggle glider when vertical velocity is above some threshold (here ~
|
|
|
|
// glider fall vertical speed)
|
|
|
|
if vel.0.z < -26.0 {
|
|
|
|
controller.actions.push(ControlAction::GlideWield);
|
2021-03-03 03:55:28 +00:00
|
|
|
if let Some(Target { target, hostile: _ }) = agent.target {
|
|
|
|
if let Some(tgt_pos) = read_data.positions.get(target) {
|
|
|
|
controller.inputs.move_dir = (pos.0 - tgt_pos.0)
|
|
|
|
.xy()
|
|
|
|
.try_normalized()
|
|
|
|
.unwrap_or_else(Vec2::zero);
|
|
|
|
}
|
|
|
|
}
|
2021-02-07 07:22:06 +00:00
|
|
|
}
|
2021-03-03 03:55:28 +00:00
|
|
|
} else if let Some(Target { target, hostile }) = agent.target {
|
|
|
|
if let Some(tgt_health) = read_data.healths.get(target) {
|
|
|
|
// If the target is hostile (either based on alignment or if
|
|
|
|
// the target just attacked
|
|
|
|
if !tgt_health.is_dead {
|
|
|
|
if hostile {
|
|
|
|
data.hostile_tree(
|
|
|
|
agent,
|
|
|
|
controller,
|
|
|
|
&read_data,
|
|
|
|
&mut event_emitter,
|
|
|
|
);
|
|
|
|
// Target is something worth following methinks
|
|
|
|
} else if let Some(Alignment::Owned(_)) = data.alignment {
|
|
|
|
if let Some(tgt_pos) = read_data.positions.get(target) {
|
|
|
|
let dist_sqrd = pos.0.distance_squared(tgt_pos.0);
|
|
|
|
// If really far away drop everything and follow
|
|
|
|
if dist_sqrd > (2.0 * MAX_FOLLOW_DIST).powi(2) {
|
|
|
|
agent.bearing = Vec2::zero();
|
|
|
|
data.follow(
|
|
|
|
agent,
|
|
|
|
controller,
|
|
|
|
&read_data.terrain,
|
|
|
|
tgt_pos,
|
|
|
|
);
|
|
|
|
// Attack target's attacker
|
|
|
|
} else if tgt_health.last_change.0 < 5.0
|
|
|
|
&& tgt_health.last_change.1.amount < 0
|
2021-02-07 07:22:06 +00:00
|
|
|
{
|
2021-03-03 03:55:28 +00:00
|
|
|
if let comp::HealthSource::Damage {
|
|
|
|
by: Some(by), ..
|
|
|
|
} = tgt_health.last_change.1.cause
|
|
|
|
{
|
|
|
|
if let Some(attacker) = read_data
|
|
|
|
.uid_allocator
|
|
|
|
.retrieve_entity_internal(by.id())
|
2021-02-07 07:22:06 +00:00
|
|
|
{
|
|
|
|
agent.target = Some(Target {
|
2021-03-03 03:55:28 +00:00
|
|
|
target: attacker,
|
|
|
|
hostile: true,
|
2021-02-07 07:22:06 +00:00
|
|
|
});
|
2021-03-03 03:55:28 +00:00
|
|
|
if let (Some(tgt_pos), Some(tgt_health)) = (
|
|
|
|
read_data.positions.get(attacker),
|
|
|
|
read_data.healths.get(attacker),
|
|
|
|
) {
|
|
|
|
if tgt_health.is_dead
|
|
|
|
|| dist_sqrd > MAX_CHASE_DIST.powi(2)
|
|
|
|
{
|
|
|
|
agent.target = Some(Target {
|
|
|
|
target,
|
|
|
|
hostile: false,
|
|
|
|
});
|
|
|
|
data.idle(
|
|
|
|
agent, controller, &read_data,
|
|
|
|
);
|
|
|
|
} else {
|
|
|
|
data.attack(
|
|
|
|
agent,
|
|
|
|
controller,
|
|
|
|
&read_data.terrain,
|
|
|
|
tgt_pos,
|
|
|
|
read_data.bodies.get(attacker),
|
|
|
|
&read_data.dt,
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
2021-01-31 20:29:50 +00:00
|
|
|
}
|
|
|
|
}
|
2021-03-03 03:55:28 +00:00
|
|
|
// Follow owner if too far away and not
|
|
|
|
// fighting
|
|
|
|
} else if dist_sqrd > MAX_FOLLOW_DIST.powi(2) {
|
|
|
|
data.follow(
|
|
|
|
agent,
|
|
|
|
controller,
|
|
|
|
&read_data.terrain,
|
|
|
|
tgt_pos,
|
|
|
|
);
|
|
|
|
|
|
|
|
// Otherwise just idle
|
|
|
|
} else {
|
|
|
|
data.idle_tree(
|
|
|
|
agent,
|
|
|
|
controller,
|
|
|
|
&read_data,
|
|
|
|
&mut event_emitter,
|
|
|
|
);
|
2021-01-31 20:29:50 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
} else {
|
2021-02-07 07:22:06 +00:00
|
|
|
data.idle_tree(
|
|
|
|
agent,
|
|
|
|
controller,
|
|
|
|
&read_data,
|
|
|
|
&mut event_emitter,
|
|
|
|
);
|
2021-01-31 20:29:50 +00:00
|
|
|
}
|
2021-02-07 07:22:06 +00:00
|
|
|
} else {
|
2021-03-03 03:55:28 +00:00
|
|
|
agent.target = None;
|
2021-02-07 07:22:06 +00:00
|
|
|
data.idle_tree(agent, controller, &read_data, &mut event_emitter);
|
|
|
|
}
|
|
|
|
} else {
|
2021-03-03 03:55:28 +00:00
|
|
|
agent.target = None;
|
2021-02-07 07:22:06 +00:00
|
|
|
data.idle_tree(agent, controller, &read_data, &mut event_emitter);
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
// Target an entity that's attacking us if the attack was recent
|
|
|
|
if health.last_change.0 < DAMAGE_MEMORY_DURATION {
|
|
|
|
if let comp::HealthSource::Damage { by: Some(by), .. } =
|
|
|
|
health.last_change.1.cause
|
|
|
|
{
|
|
|
|
if let Some(attacker) =
|
|
|
|
read_data.uid_allocator.retrieve_entity_internal(by.id())
|
|
|
|
{
|
|
|
|
if let Some(tgt_pos) = read_data.positions.get(attacker) {
|
2021-03-03 03:55:28 +00:00
|
|
|
// If the target is dead, remove the target and idle.
|
2021-02-07 07:22:06 +00:00
|
|
|
if read_data
|
|
|
|
.healths
|
|
|
|
.get(attacker)
|
2021-03-03 03:55:28 +00:00
|
|
|
.map_or(true, |a| a.is_dead)
|
2021-01-05 01:04:01 +00:00
|
|
|
{
|
2021-03-03 03:55:28 +00:00
|
|
|
agent.target = None;
|
|
|
|
data.idle_tree(
|
|
|
|
agent,
|
|
|
|
controller,
|
|
|
|
&read_data,
|
|
|
|
&mut event_emitter,
|
|
|
|
);
|
|
|
|
} else {
|
2021-02-07 07:22:06 +00:00
|
|
|
agent.target = Some(Target {
|
|
|
|
target: attacker,
|
|
|
|
hostile: true,
|
|
|
|
});
|
|
|
|
data.attack(
|
|
|
|
agent,
|
|
|
|
controller,
|
|
|
|
&read_data.terrain,
|
|
|
|
tgt_pos,
|
|
|
|
read_data.bodies.get(attacker),
|
|
|
|
&read_data.dt,
|
|
|
|
);
|
|
|
|
}
|
2021-01-05 01:04:01 +00:00
|
|
|
} else {
|
2021-02-07 07:22:06 +00:00
|
|
|
agent.target = None;
|
|
|
|
data.idle_tree(
|
|
|
|
agent,
|
|
|
|
controller,
|
|
|
|
&read_data,
|
|
|
|
&mut event_emitter,
|
|
|
|
);
|
|
|
|
}
|
2020-01-26 12:47:41 +00:00
|
|
|
}
|
|
|
|
} else {
|
2021-02-07 07:22:06 +00:00
|
|
|
agent.target = None;
|
|
|
|
data.idle_tree(agent, controller, &read_data, &mut event_emitter);
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
data.idle_tree(agent, controller, &read_data, &mut event_emitter);
|
|
|
|
}
|
|
|
|
}
|
2020-11-11 04:36:40 +00:00
|
|
|
|
2021-02-07 07:22:06 +00:00
|
|
|
debug_assert!(controller.inputs.move_dir.map(|e| !e.is_nan()).reduce_and());
|
|
|
|
debug_assert!(controller.inputs.look_dir.map(|e| !e.is_nan()).reduce_and());
|
|
|
|
},
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
2020-03-17 17:27:52 +00:00
|
|
|
|
2021-02-07 07:22:06 +00:00
|
|
|
impl<'a> AgentData<'a> {
|
|
|
|
fn fall_glide(&self) -> bool { self.glider_equipped && !self.physics_state.on_ground }
|
2020-01-26 00:06:03 +00:00
|
|
|
|
2021-02-07 07:22:06 +00:00
|
|
|
fn idle_tree(
|
|
|
|
&self,
|
|
|
|
agent: &mut Agent,
|
|
|
|
controller: &mut Controller,
|
|
|
|
read_data: &ReadData,
|
|
|
|
event_emitter: &mut Emitter<'_, ServerEvent>,
|
|
|
|
) {
|
2021-03-03 03:55:28 +00:00
|
|
|
// Set owner if no target
|
|
|
|
if agent.target.is_none() && thread_rng().gen_bool(0.1) {
|
|
|
|
if let Some(Alignment::Owned(owner)) = self.alignment {
|
|
|
|
if let Some(owner) = read_data.uid_allocator.retrieve_entity_internal(owner.id()) {
|
|
|
|
agent.target = Some(Target {
|
|
|
|
target: owner,
|
|
|
|
hostile: false,
|
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2021-02-07 07:22:06 +00:00
|
|
|
// Interact if incoming messages
|
|
|
|
if !agent.inbox.is_empty() {
|
2021-03-03 03:55:28 +00:00
|
|
|
agent.action_timer = 0.1;
|
2021-02-07 07:22:06 +00:00
|
|
|
}
|
|
|
|
if agent.action_timer > 0.0 {
|
2021-03-03 03:55:28 +00:00
|
|
|
if agent.action_timer < DEFAULT_INTERACTION_TIME {
|
|
|
|
self.interact(agent, controller, &read_data, event_emitter);
|
2021-02-07 07:22:06 +00:00
|
|
|
} else {
|
2021-03-03 03:55:28 +00:00
|
|
|
agent.action_timer = 0.0;
|
2021-02-07 07:22:06 +00:00
|
|
|
agent.target = None;
|
2021-03-03 03:55:28 +00:00
|
|
|
controller.actions.push(ControlAction::Stand);
|
2021-02-07 07:22:06 +00:00
|
|
|
self.idle(agent, controller, &read_data);
|
|
|
|
}
|
|
|
|
} else if thread_rng().gen::<f32>() < 0.1 {
|
|
|
|
self.choose_target(agent, controller, &read_data);
|
|
|
|
} else {
|
|
|
|
self.idle(agent, controller, &read_data);
|
|
|
|
}
|
|
|
|
}
|
2020-07-29 19:58:14 +00:00
|
|
|
|
2021-02-07 07:22:06 +00:00
|
|
|
fn hostile_tree(
|
|
|
|
&self,
|
|
|
|
agent: &mut Agent,
|
|
|
|
controller: &mut Controller,
|
|
|
|
read_data: &ReadData,
|
|
|
|
event_emitter: &mut Emitter<'_, ServerEvent>,
|
|
|
|
) {
|
|
|
|
if let Some(Target { target, .. }) = agent.target {
|
|
|
|
if let (Some(tgt_pos), Some(tgt_health)) = (
|
|
|
|
read_data.positions.get(target),
|
|
|
|
read_data.healths.get(target),
|
|
|
|
) {
|
|
|
|
let dist_sqrd = self.pos.0.distance_squared(tgt_pos.0);
|
|
|
|
// Should the agent flee?
|
|
|
|
if 1.0 - agent.psyche.aggro > self.damage && self.flees {
|
|
|
|
if agent.action_timer == 0.0 && agent.can_speak {
|
|
|
|
let msg = "npc.speech.villager_under_attack".to_string();
|
|
|
|
event_emitter
|
|
|
|
.emit(ServerEvent::Chat(UnresolvedChatMsg::npc(*self.uid, msg)));
|
|
|
|
agent.action_timer = 0.01;
|
|
|
|
} else if agent.action_timer < FLEE_DURATION || dist_sqrd < MAX_FLEE_DIST {
|
|
|
|
self.flee(
|
|
|
|
agent,
|
|
|
|
controller,
|
|
|
|
&read_data.terrain,
|
|
|
|
tgt_pos,
|
|
|
|
&read_data.dt,
|
|
|
|
);
|
|
|
|
} else {
|
|
|
|
agent.action_timer = 0.0;
|
|
|
|
agent.target = None;
|
|
|
|
self.idle(agent, controller, &read_data);
|
|
|
|
}
|
2021-01-31 20:29:50 +00:00
|
|
|
|
2021-02-07 07:22:06 +00:00
|
|
|
// If not fleeing, attack the hostile
|
|
|
|
// entity!
|
|
|
|
} else {
|
|
|
|
// If the hostile entity is dead, return to idle
|
|
|
|
if tgt_health.is_dead {
|
|
|
|
agent.target = None;
|
|
|
|
if agent.can_speak {
|
|
|
|
let msg = "I have destroyed my enemy!".to_string();
|
|
|
|
event_emitter
|
|
|
|
.emit(ServerEvent::Chat(UnresolvedChatMsg::npc(*self.uid, msg)));
|
|
|
|
}
|
|
|
|
} else if dist_sqrd < SIGHT_DIST.powi(2) {
|
|
|
|
self.attack(
|
|
|
|
agent,
|
|
|
|
controller,
|
|
|
|
&read_data.terrain,
|
|
|
|
tgt_pos,
|
|
|
|
read_data.bodies.get(target),
|
|
|
|
&read_data.dt,
|
|
|
|
);
|
|
|
|
} else {
|
|
|
|
agent.target = None;
|
|
|
|
self.idle(agent, controller, &read_data);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2020-11-25 22:47:16 +00:00
|
|
|
|
2021-02-07 07:22:06 +00:00
|
|
|
fn idle(&self, agent: &mut Agent, controller: &mut Controller, read_data: &ReadData) {
|
|
|
|
// Light lanterns at night
|
|
|
|
// TODO Add a method to turn on NPC lanterns underground
|
|
|
|
let lantern_equipped = self
|
|
|
|
.inventory
|
|
|
|
.equipped(EquipSlot::Lantern)
|
|
|
|
.as_ref()
|
|
|
|
.map_or(false, |item| {
|
|
|
|
matches!(item.kind(), comp::item::ItemKind::Lantern(_))
|
|
|
|
});
|
|
|
|
let lantern_turned_on = self.light_emitter.is_some();
|
|
|
|
let day_period = DayPeriod::from(read_data.time_of_day.0);
|
|
|
|
// Only emit event for agents that have a lantern equipped
|
2021-03-03 03:55:28 +00:00
|
|
|
if lantern_equipped && thread_rng().gen_bool(0.001) {
|
2021-02-07 07:22:06 +00:00
|
|
|
if day_period.is_dark() && !lantern_turned_on {
|
|
|
|
// Agents with turned off lanterns turn them on randomly once it's
|
|
|
|
// nighttime and keep them on
|
|
|
|
// Only emit event for agents that sill need to
|
|
|
|
// turn on their lantern
|
2021-03-03 03:55:28 +00:00
|
|
|
controller.events.push(ControlEvent::EnableLantern)
|
2021-02-07 07:22:06 +00:00
|
|
|
} else if lantern_turned_on && day_period.is_light() {
|
|
|
|
// agents with turned on lanterns turn them off randomly once it's
|
|
|
|
// daytime and keep them off
|
2021-03-03 03:55:28 +00:00
|
|
|
controller.events.push(ControlEvent::DisableLantern)
|
2021-02-07 07:22:06 +00:00
|
|
|
}
|
|
|
|
};
|
2020-11-25 22:47:16 +00:00
|
|
|
|
2021-02-07 07:22:06 +00:00
|
|
|
agent.action_timer = 0.0;
|
|
|
|
if let Some((travel_to, _destination)) = &agent.rtsim_controller.travel_to {
|
|
|
|
// if it has an rtsim destination and can fly then it should
|
|
|
|
// if it is flying and bumps something above it then it should move down
|
|
|
|
controller.inputs.fly.set_state(
|
|
|
|
self.traversal_config.can_fly
|
|
|
|
&& !read_data
|
|
|
|
.terrain
|
|
|
|
.ray(self.pos.0, self.pos.0 + (Vec3::unit_z() * 3.0))
|
|
|
|
.until(Block::is_solid)
|
|
|
|
.cast()
|
|
|
|
.1
|
|
|
|
.map_or(true, |b| b.is_some()),
|
|
|
|
);
|
|
|
|
if let Some((bearing, speed)) = agent.chaser.chase(
|
|
|
|
&*read_data.terrain,
|
|
|
|
self.pos.0,
|
|
|
|
self.vel.0,
|
|
|
|
*travel_to,
|
|
|
|
TraversalConfig {
|
|
|
|
min_tgt_dist: 1.25,
|
|
|
|
..self.traversal_config
|
|
|
|
},
|
|
|
|
) {
|
|
|
|
controller.inputs.move_dir =
|
|
|
|
bearing.xy().try_normalized().unwrap_or_else(Vec2::zero)
|
|
|
|
* speed.min(agent.rtsim_controller.speed_factor);
|
|
|
|
controller.inputs.jump.set_state(
|
|
|
|
bearing.z > 1.5
|
|
|
|
|| self.traversal_config.can_fly && self.traversal_config.on_ground,
|
|
|
|
);
|
|
|
|
controller.inputs.climb = Some(comp::Climb::Up);
|
|
|
|
//.filter(|_| bearing.z > 0.1 || self.physics_state.in_liquid.is_some());
|
2020-11-25 22:47:16 +00:00
|
|
|
|
2021-02-07 07:22:06 +00:00
|
|
|
controller.inputs.move_z = bearing.z
|
|
|
|
+ if self.traversal_config.can_fly {
|
|
|
|
if read_data
|
|
|
|
.terrain
|
|
|
|
.ray(
|
|
|
|
self.pos.0 + Vec3::unit_z(),
|
|
|
|
self.pos.0
|
|
|
|
+ bearing.try_normalized().unwrap_or_else(Vec3::unit_y) * 60.0
|
|
|
|
+ Vec3::unit_z(),
|
|
|
|
)
|
|
|
|
.until(Block::is_solid)
|
|
|
|
.cast()
|
|
|
|
.1
|
|
|
|
.map_or(true, |b| b.is_some())
|
|
|
|
{
|
|
|
|
1.0 //fly up when approaching obstacles
|
|
|
|
} else {
|
|
|
|
-0.1
|
|
|
|
} //flying things should slowly come down from the stratosphere
|
|
|
|
} else {
|
|
|
|
0.05 //normal land traveller offset
|
|
|
|
};
|
2020-11-25 22:47:16 +00:00
|
|
|
|
2021-02-07 07:22:06 +00:00
|
|
|
// Put away weapon
|
2021-03-03 03:55:28 +00:00
|
|
|
if thread_rng().gen_bool(0.1)
|
2021-02-07 07:22:06 +00:00
|
|
|
&& matches!(
|
|
|
|
read_data.char_states.get(*self.entity),
|
|
|
|
Some(CharacterState::Wielding)
|
|
|
|
)
|
|
|
|
{
|
|
|
|
controller.actions.push(ControlAction::Unwield);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
agent.bearing += Vec2::new(
|
|
|
|
thread_rng().gen::<f32>() - 0.5,
|
|
|
|
thread_rng().gen::<f32>() - 0.5,
|
|
|
|
) * 0.1
|
|
|
|
- agent.bearing * 0.003
|
|
|
|
- agent.patrol_origin.map_or(Vec2::zero(), |patrol_origin| {
|
|
|
|
(self.pos.0 - patrol_origin).xy() * 0.0002
|
|
|
|
});
|
2020-11-25 22:47:16 +00:00
|
|
|
|
2021-02-07 07:22:06 +00:00
|
|
|
// Stop if we're too close to a wall
|
|
|
|
agent.bearing *= 0.1
|
|
|
|
+ if read_data
|
|
|
|
.terrain
|
|
|
|
.ray(
|
|
|
|
self.pos.0 + Vec3::unit_z(),
|
|
|
|
self.pos.0
|
|
|
|
+ Vec3::from(agent.bearing)
|
|
|
|
.try_normalized()
|
|
|
|
.unwrap_or_else(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
|
|
|
|
};
|
2020-11-25 22:47:16 +00:00
|
|
|
|
2021-02-07 07:22:06 +00:00
|
|
|
if agent.bearing.magnitude_squared() > 0.5f32.powi(2) {
|
|
|
|
controller.inputs.move_dir = agent.bearing * 0.65;
|
|
|
|
}
|
2020-11-25 22:47:16 +00:00
|
|
|
|
2021-02-07 07:22:06 +00:00
|
|
|
// Put away weapon
|
2021-03-03 03:55:28 +00:00
|
|
|
if thread_rng().gen_bool(0.1)
|
2021-02-07 07:22:06 +00:00
|
|
|
&& matches!(
|
|
|
|
read_data.char_states.get(*self.entity),
|
|
|
|
Some(CharacterState::Wielding)
|
|
|
|
)
|
|
|
|
{
|
|
|
|
controller.actions.push(ControlAction::Unwield);
|
|
|
|
}
|
2020-11-25 22:47:16 +00:00
|
|
|
|
2021-02-07 07:22:06 +00:00
|
|
|
// Sit
|
|
|
|
if thread_rng().gen::<f32>() < 0.0035 {
|
|
|
|
controller.actions.push(ControlAction::Sit);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2020-11-25 22:47:16 +00:00
|
|
|
|
2021-02-07 07:22:06 +00:00
|
|
|
fn interact(
|
|
|
|
&self,
|
|
|
|
agent: &mut Agent,
|
|
|
|
controller: &mut Controller,
|
|
|
|
read_data: &ReadData,
|
|
|
|
event_emitter: &mut Emitter<'_, ServerEvent>,
|
|
|
|
) {
|
|
|
|
// TODO: Process group invites
|
|
|
|
// TODO: Add Group AgentEvent
|
|
|
|
let accept = false; // set back to "matches!(alignment, Alignment::Npc)" when we got better NPC recruitment mechanics
|
|
|
|
if accept {
|
|
|
|
// Clear agent comp
|
|
|
|
*agent = Agent::default();
|
|
|
|
controller
|
|
|
|
.events
|
|
|
|
.push(ControlEvent::InviteResponse(InviteResponse::Accept));
|
|
|
|
} else {
|
|
|
|
controller
|
|
|
|
.events
|
|
|
|
.push(ControlEvent::InviteResponse(InviteResponse::Decline));
|
|
|
|
}
|
|
|
|
agent.action_timer += read_data.dt.0;
|
2021-03-03 03:55:28 +00:00
|
|
|
if agent.can_speak {
|
|
|
|
if let Some(AgentEvent::Talk(by)) = agent.inbox.pop_back() {
|
|
|
|
if let Some(target) = read_data.uid_allocator.retrieve_entity_internal(by.id()) {
|
|
|
|
agent.target = Some(Target {
|
|
|
|
target,
|
|
|
|
hostile: false,
|
|
|
|
});
|
|
|
|
if let Some(tgt_pos) = read_data.positions.get(target) {
|
|
|
|
let eye_offset = self.body.map_or(0.0, |b| b.eye_height());
|
|
|
|
let tgt_eye_offset =
|
|
|
|
read_data.bodies.get(target).map_or(0.0, |b| b.eye_height());
|
|
|
|
if let Some(dir) = Dir::from_unnormalized(
|
|
|
|
Vec3::new(tgt_pos.0.x, tgt_pos.0.y, tgt_pos.0.z + tgt_eye_offset)
|
|
|
|
- Vec3::new(self.pos.0.x, self.pos.0.y, self.pos.0.z + eye_offset),
|
|
|
|
) {
|
|
|
|
controller.inputs.look_dir = dir;
|
|
|
|
}
|
|
|
|
controller.actions.push(ControlAction::Stand);
|
|
|
|
controller.actions.push(ControlAction::Talk);
|
|
|
|
if let Some((_travel_to, destination_name)) =
|
|
|
|
&agent.rtsim_controller.travel_to
|
|
|
|
{
|
|
|
|
let msg =
|
|
|
|
format!("I'm heading to {}! Want to come along?", destination_name);
|
|
|
|
event_emitter
|
|
|
|
.emit(ServerEvent::Chat(UnresolvedChatMsg::npc(*self.uid, msg)));
|
|
|
|
} else {
|
|
|
|
let msg = "npc.speech.villager".to_string();
|
|
|
|
event_emitter
|
|
|
|
.emit(ServerEvent::Chat(UnresolvedChatMsg::npc(*self.uid, msg)));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} else if let Some(Target { target, .. }) = &agent.target {
|
|
|
|
if let Some(tgt_pos) = read_data.positions.get(*target) {
|
2021-02-07 07:22:06 +00:00
|
|
|
let eye_offset = self.body.map_or(0.0, |b| b.eye_height());
|
2021-03-03 03:55:28 +00:00
|
|
|
let tgt_eye_offset = read_data
|
|
|
|
.bodies
|
|
|
|
.get(*target)
|
|
|
|
.map_or(0.0, |b| b.eye_height());
|
2021-02-07 07:22:06 +00:00
|
|
|
if let Some(dir) = Dir::from_unnormalized(
|
|
|
|
Vec3::new(tgt_pos.0.x, tgt_pos.0.y, tgt_pos.0.z + tgt_eye_offset)
|
|
|
|
- Vec3::new(self.pos.0.x, self.pos.0.y, self.pos.0.z + eye_offset),
|
|
|
|
) {
|
|
|
|
controller.inputs.look_dir = dir;
|
|
|
|
}
|
|
|
|
}
|
2021-03-03 03:55:28 +00:00
|
|
|
} else {
|
|
|
|
agent.action_timer = 0.0;
|
2021-02-07 07:22:06 +00:00
|
|
|
}
|
|
|
|
} else {
|
2021-03-03 03:55:28 +00:00
|
|
|
agent.inbox.clear();
|
2021-02-07 07:22:06 +00:00
|
|
|
}
|
|
|
|
}
|
2020-01-26 00:06:03 +00:00
|
|
|
|
2021-02-07 07:22:06 +00:00
|
|
|
fn flee(
|
|
|
|
&self,
|
|
|
|
agent: &mut Agent,
|
|
|
|
controller: &mut Controller,
|
|
|
|
terrain: &TerrainGrid,
|
|
|
|
tgt_pos: &Pos,
|
|
|
|
dt: &DeltaTime,
|
|
|
|
) {
|
|
|
|
if let Some(body) = self.body {
|
|
|
|
if body.can_strafe() && !self.is_gliding {
|
|
|
|
controller.actions.push(ControlAction::Unwield);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if let Some((bearing, speed)) = agent.chaser.chase(
|
|
|
|
&*terrain,
|
|
|
|
self.pos.0,
|
|
|
|
self.vel.0,
|
|
|
|
// Away from the target (ironically)
|
|
|
|
self.pos.0
|
|
|
|
+ (self.pos.0 - tgt_pos.0)
|
|
|
|
.try_normalized()
|
|
|
|
.unwrap_or_else(Vec3::unit_y)
|
|
|
|
* 50.0,
|
|
|
|
TraversalConfig {
|
|
|
|
min_tgt_dist: 1.25,
|
|
|
|
..self.traversal_config
|
|
|
|
},
|
|
|
|
) {
|
|
|
|
controller.inputs.move_dir =
|
|
|
|
bearing.xy().try_normalized().unwrap_or_else(Vec2::zero) * speed;
|
|
|
|
controller.inputs.jump.set_state(bearing.z > 1.5);
|
|
|
|
controller.inputs.move_z = bearing.z;
|
|
|
|
}
|
|
|
|
agent.action_timer += dt.0;
|
|
|
|
}
|
|
|
|
|
|
|
|
fn choose_target(&self, agent: &mut Agent, controller: &mut Controller, read_data: &ReadData) {
|
|
|
|
agent.action_timer = 0.0;
|
|
|
|
|
|
|
|
// 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 target = (&read_data.entities, &read_data.positions, &read_data.healths, read_data.alignments.maybe(), read_data.char_states.maybe())
|
|
|
|
.join()
|
|
|
|
.filter(|(e, e_pos, e_health, e_alignment, char_state)| {
|
|
|
|
let mut search_dist = SEARCH_DIST;
|
|
|
|
let mut listen_dist = LISTEN_DIST;
|
|
|
|
if char_state.map_or(false, |c_s| c_s.is_stealthy()) {
|
|
|
|
// TODO: make sneak more effective based on a stat like e_stats.fitness
|
|
|
|
search_dist *= SNEAK_COEFFICIENT;
|
|
|
|
listen_dist *= SNEAK_COEFFICIENT;
|
2021-02-22 00:57:25 +00:00
|
|
|
}
|
2021-02-07 07:22:06 +00:00
|
|
|
((e_pos.0.distance_squared(self.pos.0) < search_dist.powi(2) &&
|
|
|
|
// Within our view
|
|
|
|
(e_pos.0 - self.pos.0).try_normalized().map(|v| v.dot(*controller.inputs.look_dir) > 0.15).unwrap_or(true))
|
|
|
|
// Within listen distance
|
|
|
|
|| e_pos.0.distance_squared(self.pos.0) < listen_dist.powi(2)) // TODO implement proper sound system for agents
|
|
|
|
&& e != self.entity
|
|
|
|
&& !e_health.is_dead
|
|
|
|
&& self.alignment.and_then(|a| e_alignment.map(|b| a.hostile_towards(*b))).unwrap_or(false)
|
|
|
|
})
|
|
|
|
// Can we even see them?
|
|
|
|
.filter(|(_, e_pos, _, _, _)| read_data.terrain
|
|
|
|
.ray(self.pos.0 + Vec3::unit_z(), e_pos.0 + Vec3::unit_z())
|
|
|
|
.until(Block::is_opaque)
|
|
|
|
.cast()
|
|
|
|
.0 >= e_pos.0.distance(self.pos.0))
|
|
|
|
.min_by_key(|(_, e_pos, _, _, _)| (e_pos.0.distance_squared(self.pos.0) * 100.0) as i32) // TODO choose target by more than just distance
|
|
|
|
.map(|(e, _, _, _, _)| e);
|
|
|
|
if let Some(target) = target {
|
|
|
|
agent.target = Some(Target {
|
|
|
|
target,
|
|
|
|
hostile: true,
|
|
|
|
})
|
|
|
|
} else {
|
|
|
|
agent.target = None;
|
|
|
|
}
|
|
|
|
}
|
2021-02-22 00:57:25 +00:00
|
|
|
|
2021-02-07 07:22:06 +00:00
|
|
|
fn attack(
|
|
|
|
&self,
|
|
|
|
agent: &mut Agent,
|
|
|
|
controller: &mut Controller,
|
|
|
|
terrain: &TerrainGrid,
|
|
|
|
tgt_pos: &Pos,
|
|
|
|
tgt_body: Option<&Body>,
|
|
|
|
dt: &DeltaTime,
|
|
|
|
) {
|
|
|
|
let min_attack_dist = self.body.map_or(3.0, |b| b.radius() * self.scale + 2.0);
|
|
|
|
let tactic = match self
|
|
|
|
.inventory
|
|
|
|
.equipped(EquipSlot::Mainhand)
|
|
|
|
.as_ref()
|
|
|
|
.and_then(|item| {
|
|
|
|
if let ItemKind::Tool(tool) = &item.kind() {
|
|
|
|
Some(&tool.kind)
|
|
|
|
} else {
|
|
|
|
None
|
2020-01-26 00:06:03 +00:00
|
|
|
}
|
2021-02-07 07:22:06 +00:00
|
|
|
}) {
|
2021-03-03 03:55:28 +00:00
|
|
|
Some(ToolKind::Bow) | Some(ToolKind::BowSimple) => Tactic::Bow,
|
|
|
|
Some(ToolKind::Staff) | Some(ToolKind::StaffSimple) => Tactic::Staff,
|
2021-02-07 07:22:06 +00:00
|
|
|
Some(ToolKind::Hammer) => Tactic::Hammer,
|
2021-03-03 03:55:28 +00:00
|
|
|
Some(ToolKind::Sword)
|
|
|
|
| Some(ToolKind::Spear)
|
|
|
|
| Some(ToolKind::SwordSimple)
|
|
|
|
| Some(ToolKind::AxeSimple) => Tactic::Sword,
|
2021-02-07 07:22:06 +00:00
|
|
|
Some(ToolKind::Axe) => Tactic::Axe,
|
|
|
|
Some(ToolKind::Unique(UniqueKind::StoneGolemFist)) => Tactic::StoneGolemBoss,
|
|
|
|
Some(ToolKind::Unique(UniqueKind::QuadMedQuick)) => Tactic::CircleCharge {
|
|
|
|
radius: 3,
|
|
|
|
circle_time: 2,
|
|
|
|
},
|
|
|
|
Some(ToolKind::Unique(UniqueKind::QuadMedCharge)) => Tactic::CircleCharge {
|
|
|
|
radius: 15,
|
|
|
|
circle_time: 1,
|
|
|
|
},
|
2020-01-26 00:06:03 +00:00
|
|
|
|
2021-02-07 07:22:06 +00:00
|
|
|
Some(ToolKind::Unique(UniqueKind::QuadMedJump)) => Tactic::QuadMedJump,
|
|
|
|
Some(ToolKind::Unique(UniqueKind::QuadMedBasic)) => Tactic::QuadMedBasic,
|
|
|
|
Some(ToolKind::Unique(UniqueKind::QuadLowRanged)) => Tactic::QuadLowRanged,
|
|
|
|
Some(ToolKind::Unique(UniqueKind::QuadLowTail)) => Tactic::TailSlap,
|
|
|
|
Some(ToolKind::Unique(UniqueKind::QuadLowQuick)) => Tactic::QuadLowQuick,
|
|
|
|
Some(ToolKind::Unique(UniqueKind::QuadLowBasic)) => Tactic::QuadLowBasic,
|
2021-03-03 03:55:28 +00:00
|
|
|
Some(ToolKind::Unique(UniqueKind::QuadLowBreathe))
|
|
|
|
| Some(ToolKind::Unique(UniqueKind::QuadLowBeam)) => Tactic::Lavadrake,
|
2021-02-07 07:22:06 +00:00
|
|
|
Some(ToolKind::Unique(UniqueKind::TheropodBasic)) => Tactic::Theropod,
|
|
|
|
Some(ToolKind::Unique(UniqueKind::TheropodBird)) => Tactic::Theropod,
|
|
|
|
Some(ToolKind::Unique(UniqueKind::ObjectTurret)) => Tactic::Turret,
|
|
|
|
_ => Tactic::Melee,
|
|
|
|
};
|
2020-01-25 18:49:47 +00:00
|
|
|
|
2021-02-07 07:22:06 +00:00
|
|
|
// Wield the weapon as running towards the target
|
|
|
|
controller.actions.push(ControlAction::Wield);
|
2020-07-30 19:26:49 +00:00
|
|
|
|
2021-02-07 07:22:06 +00:00
|
|
|
let eye_offset = self.body.map_or(0.0, |b| b.eye_height());
|
2020-11-25 22:47:16 +00:00
|
|
|
|
2021-02-07 07:22:06 +00:00
|
|
|
let tgt_eye_offset = tgt_body.map_or(0.0, |b| b.eye_height()) +
|
|
|
|
// Special case for jumping attacks to jump at the body
|
|
|
|
// of the target and not the ground around the target
|
|
|
|
// For the ranged it is to shoot at the feet and not
|
|
|
|
// the head to get splash damage
|
|
|
|
if tactic == Tactic::QuadMedJump {
|
|
|
|
1.0
|
|
|
|
} else if matches!(tactic, Tactic::QuadLowRanged) {
|
|
|
|
-1.0
|
|
|
|
} else {
|
|
|
|
0.0
|
|
|
|
};
|
2021-01-31 20:29:50 +00:00
|
|
|
|
2021-02-07 07:22:06 +00:00
|
|
|
// Hacky distance offset for ranged weapons. This is
|
|
|
|
// intentionally hacky for now before we make ranged
|
|
|
|
// NPCs lead targets and implement varying aiming
|
|
|
|
// skill
|
|
|
|
let distance_offset = match tactic {
|
|
|
|
Tactic::Bow => {
|
|
|
|
0.0004 /* Yay magic numbers */ * self.pos.0.distance_squared(tgt_pos.0)
|
|
|
|
},
|
|
|
|
Tactic::Staff => {
|
|
|
|
0.0015 /* Yay magic numbers */ * self.pos.0.distance_squared(tgt_pos.0)
|
|
|
|
},
|
|
|
|
Tactic::QuadLowRanged => {
|
|
|
|
0.03 /* Yay magic numbers */ * self.pos.0.distance_squared(tgt_pos.0)
|
|
|
|
},
|
|
|
|
_ => 0.0,
|
|
|
|
};
|
|
|
|
|
|
|
|
// Apply the distance and eye offsets to make the
|
|
|
|
// look_dir the vector from projectile launch to
|
|
|
|
// target point
|
|
|
|
if let Some(dir) = Dir::from_unnormalized(
|
|
|
|
Vec3::new(
|
|
|
|
tgt_pos.0.x,
|
|
|
|
tgt_pos.0.y,
|
|
|
|
tgt_pos.0.z + tgt_eye_offset + distance_offset,
|
|
|
|
) - Vec3::new(self.pos.0.x, self.pos.0.y, self.pos.0.z + eye_offset),
|
|
|
|
) {
|
|
|
|
controller.inputs.look_dir = dir;
|
|
|
|
}
|
|
|
|
|
|
|
|
let dist_sqrd = self.pos.0.distance_squared(tgt_pos.0);
|
|
|
|
|
|
|
|
// Match on tactic. Each tactic has different controls
|
|
|
|
// depending on the distance from the agent to the target
|
|
|
|
match tactic {
|
|
|
|
Tactic::Melee => {
|
|
|
|
if dist_sqrd < (min_attack_dist * self.scale).powi(2) {
|
|
|
|
controller.inputs.primary.set_state(true);
|
|
|
|
controller.inputs.move_dir = Vec2::zero();
|
|
|
|
} else if dist_sqrd < MAX_CHASE_DIST.powi(2) {
|
|
|
|
if let Some((bearing, speed)) = agent.chaser.chase(
|
|
|
|
&*terrain,
|
|
|
|
self.pos.0,
|
|
|
|
self.vel.0,
|
|
|
|
tgt_pos.0,
|
|
|
|
TraversalConfig {
|
|
|
|
min_tgt_dist: 1.25,
|
|
|
|
..self.traversal_config
|
|
|
|
},
|
|
|
|
) {
|
|
|
|
controller.inputs.move_dir =
|
|
|
|
bearing.xy().try_normalized().unwrap_or_else(Vec2::zero) * speed;
|
|
|
|
controller.inputs.jump.set_state(bearing.z > 1.5);
|
|
|
|
controller.inputs.move_z = bearing.z;
|
|
|
|
}
|
|
|
|
|
|
|
|
if self.body.map(|b| b.is_humanoid()).unwrap_or(false)
|
|
|
|
&& dist_sqrd < 16.0f32.powi(2)
|
|
|
|
&& thread_rng().gen::<f32>() < 0.02
|
|
|
|
{
|
|
|
|
controller.inputs.roll.set_state(true);
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
agent.target = None;
|
|
|
|
}
|
|
|
|
},
|
|
|
|
Tactic::Axe => {
|
|
|
|
if dist_sqrd < (min_attack_dist * self.scale).powi(2) {
|
|
|
|
controller.inputs.move_dir = Vec2::zero();
|
|
|
|
if agent.action_timer > 6.0 {
|
|
|
|
controller.inputs.secondary.set_state(false);
|
|
|
|
agent.action_timer = 0.0;
|
|
|
|
} else if agent.action_timer > 4.0 && self.energy.current() > 10 {
|
|
|
|
controller.inputs.secondary.set_state(true);
|
|
|
|
agent.action_timer += dt.0;
|
|
|
|
} else if self
|
|
|
|
.stats
|
|
|
|
.skill_set
|
|
|
|
.has_skill(Skill::Axe(AxeSkill::UnlockLeap))
|
|
|
|
&& self.energy.current() > 800
|
|
|
|
&& thread_rng().gen_bool(0.5)
|
|
|
|
{
|
|
|
|
controller.inputs.ability3.set_state(true);
|
|
|
|
agent.action_timer += dt.0;
|
|
|
|
} else {
|
|
|
|
controller.inputs.primary.set_state(true);
|
|
|
|
agent.action_timer += dt.0;
|
|
|
|
}
|
|
|
|
} else if dist_sqrd < MAX_CHASE_DIST.powi(2) {
|
|
|
|
if let Some((bearing, speed)) = agent.chaser.chase(
|
|
|
|
&*terrain,
|
|
|
|
self.pos.0,
|
|
|
|
self.vel.0,
|
|
|
|
tgt_pos.0,
|
|
|
|
TraversalConfig {
|
|
|
|
min_tgt_dist: 1.25,
|
|
|
|
..self.traversal_config
|
|
|
|
},
|
|
|
|
) {
|
|
|
|
controller.inputs.move_dir =
|
|
|
|
bearing.xy().try_normalized().unwrap_or_else(Vec2::zero) * speed;
|
|
|
|
controller.inputs.jump.set_state(bearing.z > 1.5);
|
|
|
|
controller.inputs.move_z = bearing.z;
|
|
|
|
}
|
|
|
|
if self.body.map(|b| b.is_humanoid()).unwrap_or(false)
|
|
|
|
&& dist_sqrd < 16.0f32.powi(2)
|
|
|
|
&& thread_rng().gen::<f32>() < 0.02
|
|
|
|
{
|
|
|
|
controller.inputs.roll.set_state(true);
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
agent.target = None;
|
|
|
|
}
|
|
|
|
},
|
|
|
|
Tactic::Hammer => {
|
|
|
|
if dist_sqrd < (min_attack_dist * self.scale).powi(2) {
|
|
|
|
controller.inputs.move_dir = Vec2::zero();
|
|
|
|
if agent.action_timer > 4.0 {
|
|
|
|
controller.inputs.secondary.set_state(false);
|
|
|
|
agent.action_timer = 0.0;
|
|
|
|
} else if agent.action_timer > 2.0 {
|
|
|
|
controller.inputs.secondary.set_state(true);
|
|
|
|
agent.action_timer += dt.0;
|
|
|
|
} else if self
|
|
|
|
.stats
|
|
|
|
.skill_set
|
|
|
|
.has_skill(Skill::Hammer(HammerSkill::UnlockLeap))
|
|
|
|
&& self.energy.current() > 700
|
|
|
|
&& thread_rng().gen_bool(0.9)
|
|
|
|
{
|
|
|
|
controller.inputs.ability3.set_state(true);
|
|
|
|
agent.action_timer += dt.0;
|
|
|
|
} else {
|
|
|
|
controller.inputs.primary.set_state(true);
|
|
|
|
agent.action_timer += dt.0;
|
|
|
|
}
|
|
|
|
} else if dist_sqrd < MAX_CHASE_DIST.powi(2) {
|
|
|
|
if let Some((bearing, speed)) = agent.chaser.chase(
|
|
|
|
&*terrain,
|
|
|
|
self.pos.0,
|
|
|
|
self.vel.0,
|
|
|
|
tgt_pos.0,
|
|
|
|
TraversalConfig {
|
|
|
|
min_tgt_dist: 1.25,
|
|
|
|
..self.traversal_config
|
|
|
|
},
|
|
|
|
) {
|
|
|
|
if can_see_tgt(&*terrain, self.pos, tgt_pos, dist_sqrd) {
|
|
|
|
controller.inputs.move_dir =
|
|
|
|
bearing.xy().try_normalized().unwrap_or_else(Vec2::zero) * speed;
|
|
|
|
if self
|
|
|
|
.stats
|
|
|
|
.skill_set
|
|
|
|
.has_skill(Skill::Hammer(HammerSkill::UnlockLeap))
|
|
|
|
&& agent.action_timer > 5.0
|
|
|
|
{
|
|
|
|
controller.inputs.ability3.set_state(true);
|
|
|
|
agent.action_timer = 0.0;
|
|
|
|
} else {
|
|
|
|
agent.action_timer += dt.0;
|
2020-01-25 18:49:47 +00:00
|
|
|
}
|
2021-02-07 07:22:06 +00:00
|
|
|
} else {
|
|
|
|
controller.inputs.move_dir =
|
|
|
|
bearing.xy().try_normalized().unwrap_or_else(Vec2::zero) * speed;
|
|
|
|
controller.inputs.jump.set_state(bearing.z > 1.5);
|
|
|
|
controller.inputs.move_z = bearing.z;
|
2020-01-25 18:49:47 +00:00
|
|
|
}
|
|
|
|
}
|
2021-02-07 07:22:06 +00:00
|
|
|
if self.body.map(|b| b.is_humanoid()).unwrap_or(false)
|
|
|
|
&& dist_sqrd < 16.0f32.powi(2)
|
|
|
|
&& thread_rng().gen::<f32>() < 0.02
|
|
|
|
{
|
|
|
|
controller.inputs.roll.set_state(true);
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
agent.target = None;
|
2020-01-25 18:49:47 +00:00
|
|
|
}
|
2021-02-07 07:22:06 +00:00
|
|
|
},
|
|
|
|
Tactic::Sword => {
|
|
|
|
if dist_sqrd < (min_attack_dist * self.scale).powi(2) {
|
|
|
|
controller.inputs.move_dir = Vec2::zero();
|
|
|
|
if self
|
|
|
|
.stats
|
|
|
|
.skill_set
|
|
|
|
.has_skill(Skill::Sword(SwordSkill::UnlockSpin))
|
|
|
|
&& agent.action_timer < 2.0
|
|
|
|
&& self.energy.current() > 600
|
|
|
|
{
|
|
|
|
controller.inputs.ability3.set_state(true);
|
|
|
|
agent.action_timer += dt.0;
|
|
|
|
} else if agent.action_timer > 2.0 {
|
|
|
|
agent.action_timer = 0.0;
|
|
|
|
} else {
|
|
|
|
controller.inputs.primary.set_state(true);
|
|
|
|
agent.action_timer += dt.0;
|
|
|
|
}
|
|
|
|
} else if dist_sqrd < MAX_CHASE_DIST.powi(2) {
|
|
|
|
if let Some((bearing, speed)) = agent.chaser.chase(
|
|
|
|
&*terrain,
|
|
|
|
self.pos.0,
|
|
|
|
self.vel.0,
|
|
|
|
tgt_pos.0,
|
|
|
|
TraversalConfig {
|
|
|
|
min_tgt_dist: 1.25,
|
|
|
|
..self.traversal_config
|
|
|
|
},
|
|
|
|
) {
|
|
|
|
if can_see_tgt(&*terrain, self.pos, tgt_pos, dist_sqrd) {
|
|
|
|
controller.inputs.move_dir =
|
|
|
|
bearing.xy().try_normalized().unwrap_or_else(Vec2::zero) * speed;
|
|
|
|
if agent.action_timer > 4.0 {
|
|
|
|
controller.inputs.secondary.set_state(true);
|
|
|
|
agent.action_timer = 0.0;
|
|
|
|
} else {
|
|
|
|
agent.action_timer += dt.0;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
controller.inputs.move_dir =
|
|
|
|
bearing.xy().try_normalized().unwrap_or_else(Vec2::zero) * speed;
|
|
|
|
controller.inputs.jump.set_state(bearing.z > 1.5);
|
|
|
|
controller.inputs.move_z = bearing.z;
|
2020-11-25 22:47:16 +00:00
|
|
|
}
|
2021-02-07 07:22:06 +00:00
|
|
|
}
|
|
|
|
if self.body.map(|b| b.is_humanoid()).unwrap_or(false)
|
|
|
|
&& dist_sqrd < 16.0f32.powi(2)
|
|
|
|
&& thread_rng().gen::<f32>() < 0.02
|
|
|
|
{
|
|
|
|
controller.inputs.roll.set_state(true);
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
agent.target = None;
|
|
|
|
}
|
|
|
|
},
|
|
|
|
Tactic::Bow => {
|
|
|
|
if self.body.map(|b| b.is_humanoid()).unwrap_or(false)
|
|
|
|
&& dist_sqrd < (2.0 * min_attack_dist * self.scale).powi(2)
|
|
|
|
{
|
|
|
|
controller.inputs.roll.set_state(true);
|
|
|
|
} else if dist_sqrd < MAX_CHASE_DIST.powi(2) {
|
|
|
|
if let Some((bearing, speed)) = agent.chaser.chase(
|
|
|
|
&*terrain,
|
|
|
|
self.pos.0,
|
|
|
|
self.vel.0,
|
|
|
|
tgt_pos.0,
|
|
|
|
TraversalConfig {
|
|
|
|
min_tgt_dist: 1.25,
|
|
|
|
..self.traversal_config
|
|
|
|
},
|
|
|
|
) {
|
|
|
|
if can_see_tgt(&*terrain, self.pos, tgt_pos, dist_sqrd) {
|
|
|
|
controller.inputs.move_dir = bearing
|
|
|
|
.xy()
|
|
|
|
.rotated_z(thread_rng().gen_range(0.5..1.57))
|
|
|
|
.try_normalized()
|
|
|
|
.unwrap_or_else(Vec2::zero)
|
|
|
|
* speed;
|
|
|
|
if agent.action_timer > 4.0 {
|
|
|
|
controller.inputs.secondary.set_state(false);
|
|
|
|
agent.action_timer = 0.0;
|
|
|
|
} else if agent.action_timer > 2.0 && self.energy.current() > 300 {
|
|
|
|
controller.inputs.secondary.set_state(true);
|
|
|
|
agent.action_timer += dt.0;
|
|
|
|
} else if self
|
|
|
|
.stats
|
|
|
|
.skill_set
|
|
|
|
.has_skill(Skill::Bow(BowSkill::UnlockRepeater))
|
|
|
|
&& self.energy.current() > 400
|
|
|
|
&& thread_rng().gen_bool(0.8)
|
2020-11-25 22:47:16 +00:00
|
|
|
{
|
2021-02-07 07:22:06 +00:00
|
|
|
controller.inputs.secondary.set_state(false);
|
|
|
|
controller.inputs.ability3.set_state(true);
|
|
|
|
agent.action_timer += dt.0;
|
|
|
|
} else {
|
|
|
|
controller.inputs.secondary.set_state(false);
|
|
|
|
controller.inputs.primary.set_state(true);
|
|
|
|
agent.action_timer += dt.0;
|
2020-01-25 18:49:47 +00:00
|
|
|
}
|
2021-02-07 07:22:06 +00:00
|
|
|
} else {
|
|
|
|
controller.inputs.move_dir =
|
|
|
|
bearing.xy().try_normalized().unwrap_or_else(Vec2::zero) * speed;
|
|
|
|
controller.inputs.jump.set_state(bearing.z > 1.5);
|
|
|
|
controller.inputs.move_z = bearing.z;
|
2020-01-25 18:49:47 +00:00
|
|
|
}
|
2021-02-07 07:22:06 +00:00
|
|
|
}
|
|
|
|
if self.body.map(|b| b.is_humanoid()).unwrap_or(false)
|
|
|
|
&& dist_sqrd < 16.0f32.powi(2)
|
|
|
|
&& thread_rng().gen::<f32>() < 0.02
|
|
|
|
{
|
|
|
|
controller.inputs.roll.set_state(true);
|
|
|
|
}
|
|
|
|
} else if can_see_tgt(&*terrain, self.pos, tgt_pos, dist_sqrd) {
|
|
|
|
if let Some((bearing, speed)) = agent.chaser.chase(
|
|
|
|
&*terrain,
|
|
|
|
self.pos.0,
|
|
|
|
self.vel.0,
|
|
|
|
tgt_pos.0,
|
|
|
|
TraversalConfig {
|
|
|
|
min_tgt_dist: 1.25,
|
|
|
|
..self.traversal_config
|
|
|
|
},
|
|
|
|
) {
|
|
|
|
controller.inputs.move_dir =
|
|
|
|
bearing.xy().try_normalized().unwrap_or_else(Vec2::zero) * speed;
|
|
|
|
controller.inputs.jump.set_state(bearing.z > 1.5);
|
|
|
|
controller.inputs.move_z = bearing.z;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
agent.target = None;
|
2020-11-25 22:47:16 +00:00
|
|
|
}
|
2021-02-07 07:22:06 +00:00
|
|
|
},
|
|
|
|
Tactic::Staff => {
|
|
|
|
if self.body.map(|b| b.is_humanoid()).unwrap_or(false)
|
|
|
|
&& dist_sqrd < (min_attack_dist * self.scale).powi(2)
|
|
|
|
{
|
|
|
|
controller.inputs.roll.set_state(true);
|
|
|
|
} else if dist_sqrd < (5.0 * min_attack_dist * self.scale).powi(2) {
|
|
|
|
if agent.action_timer < 1.5 {
|
|
|
|
controller.inputs.move_dir = (tgt_pos.0 - self.pos.0)
|
|
|
|
.xy()
|
|
|
|
.rotated_z(0.47 * PI)
|
|
|
|
.try_normalized()
|
|
|
|
.unwrap_or_else(Vec2::unit_y);
|
|
|
|
agent.action_timer += dt.0;
|
|
|
|
} else if agent.action_timer < 3.0 {
|
|
|
|
controller.inputs.move_dir = (tgt_pos.0 - self.pos.0)
|
|
|
|
.xy()
|
|
|
|
.rotated_z(-0.47 * PI)
|
|
|
|
.try_normalized()
|
|
|
|
.unwrap_or_else(Vec2::unit_y);
|
|
|
|
agent.action_timer += dt.0;
|
|
|
|
} else {
|
|
|
|
agent.action_timer = 0.0;
|
|
|
|
}
|
|
|
|
if self
|
|
|
|
.stats
|
|
|
|
.skill_set
|
|
|
|
.has_skill(Skill::Staff(StaffSkill::UnlockShockwave))
|
|
|
|
&& self.energy.current() > 800
|
|
|
|
&& thread_rng().gen::<f32>() > 0.8
|
|
|
|
{
|
|
|
|
controller.inputs.ability3.set_state(true);
|
|
|
|
} else if self.energy.current() > 10 {
|
|
|
|
controller.inputs.secondary.set_state(true);
|
|
|
|
} else {
|
|
|
|
controller.inputs.primary.set_state(true);
|
|
|
|
}
|
|
|
|
} else if dist_sqrd < MAX_CHASE_DIST.powi(2) {
|
|
|
|
if let Some((bearing, speed)) = agent.chaser.chase(
|
|
|
|
&*terrain,
|
|
|
|
self.pos.0,
|
|
|
|
self.vel.0,
|
|
|
|
tgt_pos.0,
|
|
|
|
TraversalConfig {
|
|
|
|
min_tgt_dist: 1.25,
|
|
|
|
..self.traversal_config
|
|
|
|
},
|
|
|
|
) {
|
|
|
|
if can_see_tgt(&*terrain, self.pos, tgt_pos, dist_sqrd) {
|
|
|
|
controller.inputs.move_dir = bearing
|
|
|
|
.xy()
|
|
|
|
.rotated_z(thread_rng().gen_range(-1.57..-0.5))
|
|
|
|
.try_normalized()
|
|
|
|
.unwrap_or_else(Vec2::zero)
|
|
|
|
* speed;
|
|
|
|
controller.inputs.primary.set_state(true);
|
|
|
|
} else {
|
|
|
|
controller.inputs.move_dir =
|
|
|
|
bearing.xy().try_normalized().unwrap_or_else(Vec2::zero) * speed;
|
|
|
|
controller.inputs.jump.set_state(bearing.z > 1.5);
|
|
|
|
controller.inputs.move_z = bearing.z;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if self.body.map(|b| b.is_humanoid()).unwrap_or(false)
|
|
|
|
&& dist_sqrd < 16.0f32.powi(2)
|
|
|
|
&& thread_rng().gen::<f32>() < 0.02
|
|
|
|
{
|
|
|
|
controller.inputs.roll.set_state(true);
|
|
|
|
}
|
|
|
|
} else if can_see_tgt(&*terrain, self.pos, tgt_pos, dist_sqrd) {
|
|
|
|
if let Some((bearing, speed)) = agent.chaser.chase(
|
|
|
|
&*terrain,
|
|
|
|
self.pos.0,
|
|
|
|
self.vel.0,
|
|
|
|
tgt_pos.0,
|
|
|
|
TraversalConfig {
|
|
|
|
min_tgt_dist: 1.25,
|
|
|
|
..self.traversal_config
|
|
|
|
},
|
|
|
|
) {
|
|
|
|
controller.inputs.move_dir =
|
|
|
|
bearing.xy().try_normalized().unwrap_or_else(Vec2::zero) * speed;
|
|
|
|
controller.inputs.jump.set_state(bearing.z > 1.5);
|
|
|
|
controller.inputs.move_z = bearing.z;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
agent.target = None;
|
|
|
|
}
|
|
|
|
},
|
|
|
|
Tactic::StoneGolemBoss => {
|
|
|
|
if dist_sqrd < (min_attack_dist * self.scale).powi(2) {
|
|
|
|
// 2.0 is temporary correction factor to allow them to melee with their
|
|
|
|
// large hitbox
|
|
|
|
controller.inputs.move_dir = Vec2::zero();
|
|
|
|
controller.inputs.primary.set_state(true);
|
|
|
|
} else if dist_sqrd < MAX_CHASE_DIST.powi(2) {
|
|
|
|
if self.vel.0.is_approx_zero() {
|
|
|
|
controller.inputs.ability3.set_state(true);
|
|
|
|
}
|
|
|
|
if let Some((bearing, speed)) = agent.chaser.chase(
|
|
|
|
&*terrain,
|
|
|
|
self.pos.0,
|
|
|
|
self.vel.0,
|
|
|
|
tgt_pos.0,
|
|
|
|
TraversalConfig {
|
|
|
|
min_tgt_dist: 1.25,
|
|
|
|
..self.traversal_config
|
|
|
|
},
|
|
|
|
) {
|
|
|
|
if can_see_tgt(&*terrain, self.pos, tgt_pos, dist_sqrd) {
|
|
|
|
controller.inputs.move_dir =
|
|
|
|
bearing.xy().try_normalized().unwrap_or_else(Vec2::zero) * speed;
|
|
|
|
if agent.action_timer > 5.0 {
|
|
|
|
controller.inputs.secondary.set_state(true);
|
|
|
|
agent.action_timer = 0.0;
|
|
|
|
} else {
|
|
|
|
agent.action_timer += dt.0;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
controller.inputs.move_dir =
|
|
|
|
bearing.xy().try_normalized().unwrap_or_else(Vec2::zero) * speed;
|
|
|
|
controller.inputs.jump.set_state(bearing.z > 1.5);
|
|
|
|
controller.inputs.move_z = bearing.z;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
agent.target = None;
|
|
|
|
}
|
|
|
|
},
|
|
|
|
Tactic::CircleCharge {
|
|
|
|
radius,
|
|
|
|
circle_time,
|
|
|
|
} => {
|
|
|
|
if dist_sqrd < (min_attack_dist * self.scale).powi(2) && thread_rng().gen_bool(0.5)
|
|
|
|
{
|
|
|
|
controller.inputs.move_dir = Vec2::zero();
|
|
|
|
controller.inputs.primary.set_state(true);
|
|
|
|
} else if dist_sqrd < (radius as f32 * min_attack_dist * self.scale).powi(2) {
|
|
|
|
controller.inputs.move_dir = (self.pos.0 - tgt_pos.0)
|
|
|
|
.xy()
|
|
|
|
.try_normalized()
|
|
|
|
.unwrap_or_else(Vec2::unit_y);
|
|
|
|
} else if dist_sqrd < ((radius as f32 + 1.0) * min_attack_dist * self.scale).powi(2)
|
|
|
|
&& dist_sqrd > (radius as f32 * min_attack_dist * self.scale).powi(2)
|
|
|
|
{
|
|
|
|
if agent.action_timer < circle_time as f32 {
|
|
|
|
controller.inputs.move_dir = (tgt_pos.0 - self.pos.0)
|
|
|
|
.xy()
|
|
|
|
.rotated_z(0.47 * PI)
|
|
|
|
.try_normalized()
|
|
|
|
.unwrap_or_else(Vec2::unit_y);
|
|
|
|
agent.action_timer += dt.0;
|
|
|
|
} else if agent.action_timer < circle_time as f32 + 0.5 {
|
|
|
|
controller.inputs.secondary.set_state(true);
|
|
|
|
agent.action_timer += dt.0;
|
|
|
|
} else if agent.action_timer < 2.0 * circle_time as f32 + 0.5 {
|
|
|
|
controller.inputs.move_dir = (tgt_pos.0 - self.pos.0)
|
|
|
|
.xy()
|
|
|
|
.rotated_z(-0.47 * PI)
|
|
|
|
.try_normalized()
|
|
|
|
.unwrap_or_else(Vec2::unit_y);
|
|
|
|
agent.action_timer += dt.0;
|
|
|
|
} else if agent.action_timer < 2.0 * circle_time as f32 + 1.0 {
|
|
|
|
controller.inputs.secondary.set_state(true);
|
|
|
|
agent.action_timer += dt.0;
|
|
|
|
} else {
|
|
|
|
agent.action_timer = 0.0;
|
|
|
|
}
|
|
|
|
} else if dist_sqrd < MAX_CHASE_DIST.powi(2) {
|
|
|
|
if let Some((bearing, speed)) = agent.chaser.chase(
|
|
|
|
&*terrain,
|
|
|
|
self.pos.0,
|
|
|
|
self.vel.0,
|
|
|
|
tgt_pos.0,
|
|
|
|
TraversalConfig {
|
|
|
|
min_tgt_dist: 1.25,
|
|
|
|
..self.traversal_config
|
|
|
|
},
|
|
|
|
) {
|
|
|
|
controller.inputs.move_dir =
|
|
|
|
bearing.xy().try_normalized().unwrap_or_else(Vec2::zero) * speed;
|
|
|
|
controller.inputs.jump.set_state(bearing.z > 1.5);
|
|
|
|
controller.inputs.move_z = bearing.z;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
agent.target = None;
|
|
|
|
}
|
|
|
|
},
|
|
|
|
Tactic::QuadLowRanged => {
|
|
|
|
if dist_sqrd < (3.0 * min_attack_dist * self.scale).powi(2) {
|
|
|
|
controller.inputs.move_dir = (tgt_pos.0 - self.pos.0)
|
|
|
|
.xy()
|
|
|
|
.try_normalized()
|
|
|
|
.unwrap_or_else(Vec2::unit_y);
|
|
|
|
controller.inputs.primary.set_state(true);
|
|
|
|
} else if dist_sqrd < MAX_CHASE_DIST.powi(2) {
|
|
|
|
if let Some((bearing, speed)) = agent.chaser.chase(
|
|
|
|
&*terrain,
|
|
|
|
self.pos.0,
|
|
|
|
self.vel.0,
|
|
|
|
tgt_pos.0,
|
|
|
|
TraversalConfig {
|
|
|
|
min_tgt_dist: 1.25,
|
|
|
|
..self.traversal_config
|
|
|
|
},
|
|
|
|
) {
|
|
|
|
if can_see_tgt(&*terrain, self.pos, tgt_pos, dist_sqrd) {
|
|
|
|
if agent.action_timer > 5.0 {
|
|
|
|
agent.action_timer = 0.0;
|
|
|
|
} else if agent.action_timer > 2.5 {
|
|
|
|
controller.inputs.move_dir = (tgt_pos.0 - self.pos.0)
|
|
|
|
.xy()
|
|
|
|
.rotated_z(1.75 * PI)
|
|
|
|
.try_normalized()
|
|
|
|
.unwrap_or_else(Vec2::zero)
|
|
|
|
* speed;
|
|
|
|
agent.action_timer += dt.0;
|
|
|
|
} else {
|
|
|
|
controller.inputs.move_dir = (tgt_pos.0 - self.pos.0)
|
|
|
|
.xy()
|
|
|
|
.rotated_z(0.25 * PI)
|
|
|
|
.try_normalized()
|
|
|
|
.unwrap_or_else(Vec2::zero)
|
|
|
|
* speed;
|
|
|
|
agent.action_timer += dt.0;
|
|
|
|
}
|
|
|
|
controller.inputs.secondary.set_state(true);
|
|
|
|
controller.inputs.jump.set_state(bearing.z > 1.5);
|
|
|
|
controller.inputs.move_z = bearing.z;
|
|
|
|
} else {
|
|
|
|
controller.inputs.move_dir =
|
|
|
|
bearing.xy().try_normalized().unwrap_or_else(Vec2::zero) * speed;
|
|
|
|
controller.inputs.jump.set_state(bearing.z > 1.5);
|
|
|
|
controller.inputs.move_z = bearing.z;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
agent.target = None;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
agent.target = None;
|
|
|
|
}
|
|
|
|
},
|
|
|
|
Tactic::TailSlap => {
|
|
|
|
if dist_sqrd < (1.5 * min_attack_dist * self.scale).powi(2) {
|
|
|
|
if agent.action_timer > 4.0 {
|
|
|
|
controller.inputs.primary.set_state(false);
|
|
|
|
agent.action_timer = 0.0;
|
|
|
|
} else if agent.action_timer > 1.0 {
|
|
|
|
controller.inputs.primary.set_state(true);
|
|
|
|
agent.action_timer += dt.0;
|
|
|
|
} else {
|
|
|
|
controller.inputs.secondary.set_state(true);
|
|
|
|
agent.action_timer += dt.0;
|
|
|
|
}
|
|
|
|
controller.inputs.move_dir = (tgt_pos.0 - self.pos.0)
|
|
|
|
.xy()
|
|
|
|
.try_normalized()
|
|
|
|
.unwrap_or_else(Vec2::unit_y)
|
|
|
|
* 0.1;
|
|
|
|
} else if dist_sqrd < MAX_CHASE_DIST.powi(2) {
|
|
|
|
if let Some((bearing, speed)) = agent.chaser.chase(
|
|
|
|
&*terrain,
|
|
|
|
self.pos.0,
|
|
|
|
self.vel.0,
|
|
|
|
tgt_pos.0,
|
|
|
|
TraversalConfig {
|
|
|
|
min_tgt_dist: 1.25,
|
|
|
|
..self.traversal_config
|
|
|
|
},
|
|
|
|
) {
|
|
|
|
controller.inputs.move_dir =
|
|
|
|
bearing.xy().try_normalized().unwrap_or_else(Vec2::zero) * speed;
|
|
|
|
controller.inputs.jump.set_state(bearing.z > 1.5);
|
|
|
|
controller.inputs.move_z = bearing.z;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
agent.target = None;
|
|
|
|
}
|
|
|
|
},
|
|
|
|
Tactic::QuadLowQuick => {
|
|
|
|
if dist_sqrd < (1.5 * min_attack_dist * self.scale).powi(2) {
|
|
|
|
controller.inputs.move_dir = Vec2::zero();
|
|
|
|
controller.inputs.secondary.set_state(true);
|
|
|
|
} else if dist_sqrd < (3.0 * min_attack_dist * self.scale).powi(2)
|
|
|
|
&& dist_sqrd > (2.0 * min_attack_dist * self.scale).powi(2)
|
|
|
|
{
|
|
|
|
controller.inputs.primary.set_state(true);
|
|
|
|
controller.inputs.move_dir = (tgt_pos.0 - self.pos.0)
|
|
|
|
.xy()
|
|
|
|
.rotated_z(-0.47 * PI)
|
|
|
|
.try_normalized()
|
|
|
|
.unwrap_or_else(Vec2::unit_y);
|
|
|
|
} else if dist_sqrd < MAX_CHASE_DIST.powi(2) {
|
|
|
|
if let Some((bearing, speed)) = agent.chaser.chase(
|
|
|
|
&*terrain,
|
|
|
|
self.pos.0,
|
|
|
|
self.vel.0,
|
|
|
|
tgt_pos.0,
|
|
|
|
TraversalConfig {
|
|
|
|
min_tgt_dist: 1.25,
|
|
|
|
..self.traversal_config
|
|
|
|
},
|
|
|
|
) {
|
|
|
|
controller.inputs.move_dir =
|
|
|
|
bearing.xy().try_normalized().unwrap_or_else(Vec2::zero) * speed;
|
|
|
|
controller.inputs.jump.set_state(bearing.z > 1.5);
|
|
|
|
controller.inputs.move_z = bearing.z;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
agent.target = None;
|
|
|
|
}
|
|
|
|
},
|
|
|
|
Tactic::QuadLowBasic => {
|
|
|
|
if dist_sqrd < (1.5 * min_attack_dist * self.scale).powi(2) {
|
|
|
|
controller.inputs.move_dir = Vec2::zero();
|
|
|
|
if agent.action_timer > 5.0 {
|
|
|
|
agent.action_timer = 0.0;
|
|
|
|
} else if agent.action_timer > 2.0 {
|
|
|
|
controller.inputs.secondary.set_state(true);
|
|
|
|
agent.action_timer += dt.0;
|
|
|
|
} else {
|
|
|
|
controller.inputs.primary.set_state(true);
|
|
|
|
agent.action_timer += dt.0;
|
|
|
|
}
|
|
|
|
} else if dist_sqrd < MAX_CHASE_DIST.powi(2) {
|
|
|
|
if let Some((bearing, speed)) = agent.chaser.chase(
|
|
|
|
&*terrain,
|
|
|
|
self.pos.0,
|
|
|
|
self.vel.0,
|
|
|
|
tgt_pos.0,
|
|
|
|
TraversalConfig {
|
|
|
|
min_tgt_dist: 1.25,
|
|
|
|
..self.traversal_config
|
|
|
|
},
|
|
|
|
) {
|
|
|
|
controller.inputs.move_dir =
|
|
|
|
bearing.xy().try_normalized().unwrap_or_else(Vec2::zero) * speed;
|
|
|
|
controller.inputs.jump.set_state(bearing.z > 1.5);
|
|
|
|
controller.inputs.move_z = bearing.z;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
agent.target = None;
|
|
|
|
}
|
|
|
|
},
|
|
|
|
Tactic::QuadMedJump => {
|
|
|
|
if dist_sqrd < (1.5 * min_attack_dist * self.scale).powi(2) {
|
|
|
|
controller.inputs.move_dir = Vec2::zero();
|
|
|
|
controller.inputs.secondary.set_state(true);
|
|
|
|
} else if dist_sqrd < (5.0 * min_attack_dist * self.scale).powi(2) {
|
|
|
|
controller.inputs.ability3.set_state(true);
|
|
|
|
} else if dist_sqrd < MAX_CHASE_DIST.powi(2) {
|
|
|
|
if let Some((bearing, speed)) = agent.chaser.chase(
|
|
|
|
&*terrain,
|
|
|
|
self.pos.0,
|
|
|
|
self.vel.0,
|
|
|
|
tgt_pos.0,
|
|
|
|
TraversalConfig {
|
|
|
|
min_tgt_dist: 1.25,
|
|
|
|
..self.traversal_config
|
|
|
|
},
|
|
|
|
) {
|
|
|
|
if can_see_tgt(&*terrain, self.pos, tgt_pos, dist_sqrd) {
|
|
|
|
controller.inputs.primary.set_state(true);
|
|
|
|
controller.inputs.move_dir =
|
|
|
|
bearing.xy().try_normalized().unwrap_or_else(Vec2::zero) * speed;
|
|
|
|
} else {
|
|
|
|
controller.inputs.move_dir =
|
|
|
|
bearing.xy().try_normalized().unwrap_or_else(Vec2::zero) * speed;
|
|
|
|
controller.inputs.jump.set_state(bearing.z > 1.5);
|
|
|
|
controller.inputs.move_z = bearing.z;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
agent.target = None;
|
|
|
|
}
|
|
|
|
},
|
|
|
|
Tactic::QuadMedBasic => {
|
|
|
|
if dist_sqrd < (min_attack_dist * self.scale).powi(2) {
|
|
|
|
controller.inputs.move_dir = Vec2::zero();
|
|
|
|
if agent.action_timer < 2.0 {
|
|
|
|
controller.inputs.secondary.set_state(true);
|
|
|
|
agent.action_timer += dt.0;
|
|
|
|
} else if agent.action_timer < 3.0 {
|
|
|
|
controller.inputs.primary.set_state(true);
|
|
|
|
agent.action_timer += dt.0;
|
|
|
|
} else {
|
|
|
|
agent.action_timer = 0.0;
|
|
|
|
}
|
|
|
|
} else if dist_sqrd < MAX_CHASE_DIST.powi(2) {
|
|
|
|
if let Some((bearing, speed)) = agent.chaser.chase(
|
|
|
|
&*terrain,
|
|
|
|
self.pos.0,
|
|
|
|
self.vel.0,
|
|
|
|
tgt_pos.0,
|
|
|
|
TraversalConfig {
|
|
|
|
min_tgt_dist: 1.25,
|
|
|
|
..self.traversal_config
|
|
|
|
},
|
|
|
|
) {
|
|
|
|
controller.inputs.move_dir =
|
|
|
|
bearing.xy().try_normalized().unwrap_or_else(Vec2::zero) * speed;
|
|
|
|
controller.inputs.jump.set_state(bearing.z > 1.5);
|
|
|
|
controller.inputs.move_z = bearing.z;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
agent.target = None;
|
|
|
|
}
|
|
|
|
},
|
2021-03-03 03:55:28 +00:00
|
|
|
Tactic::Lavadrake | Tactic::QuadLowBeam => {
|
2021-02-07 07:22:06 +00:00
|
|
|
if dist_sqrd < (2.5 * min_attack_dist * self.scale).powi(2) {
|
|
|
|
controller.inputs.move_dir = Vec2::zero();
|
|
|
|
controller.inputs.secondary.set_state(true);
|
|
|
|
} else if dist_sqrd < (7.0 * min_attack_dist * self.scale).powi(2) {
|
|
|
|
if agent.action_timer < 2.0 {
|
|
|
|
controller.inputs.move_dir = (tgt_pos.0 - self.pos.0)
|
|
|
|
.xy()
|
|
|
|
.rotated_z(0.47 * PI)
|
|
|
|
.try_normalized()
|
|
|
|
.unwrap_or_else(Vec2::unit_y);
|
|
|
|
controller.inputs.primary.set_state(true);
|
|
|
|
agent.action_timer += dt.0;
|
|
|
|
} else if agent.action_timer < 4.0 {
|
|
|
|
controller.inputs.move_dir = (tgt_pos.0 - self.pos.0)
|
|
|
|
.xy()
|
|
|
|
.rotated_z(-0.47 * PI)
|
|
|
|
.try_normalized()
|
|
|
|
.unwrap_or_else(Vec2::unit_y);
|
|
|
|
controller.inputs.primary.set_state(true);
|
|
|
|
agent.action_timer += dt.0;
|
|
|
|
} else if agent.action_timer < 6.0 {
|
|
|
|
controller.inputs.ability3.set_state(true);
|
|
|
|
agent.action_timer += dt.0;
|
|
|
|
} else {
|
|
|
|
agent.action_timer = 0.0;
|
|
|
|
}
|
|
|
|
} else if dist_sqrd < MAX_CHASE_DIST.powi(2) {
|
|
|
|
if let Some((bearing, speed)) = agent.chaser.chase(
|
|
|
|
&*terrain,
|
|
|
|
self.pos.0,
|
|
|
|
self.vel.0,
|
|
|
|
tgt_pos.0,
|
|
|
|
TraversalConfig {
|
|
|
|
min_tgt_dist: 1.25,
|
|
|
|
..self.traversal_config
|
|
|
|
},
|
|
|
|
) {
|
|
|
|
controller.inputs.move_dir =
|
|
|
|
bearing.xy().try_normalized().unwrap_or_else(Vec2::zero) * speed;
|
|
|
|
controller.inputs.jump.set_state(bearing.z > 1.5);
|
|
|
|
controller.inputs.move_z = bearing.z;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
agent.target = None;
|
|
|
|
}
|
|
|
|
},
|
|
|
|
Tactic::Theropod => {
|
|
|
|
if dist_sqrd < (2.0 * min_attack_dist * self.scale).powi(2) {
|
|
|
|
controller.inputs.move_dir = Vec2::zero();
|
|
|
|
controller.inputs.primary.set_state(true);
|
|
|
|
} else if dist_sqrd < MAX_CHASE_DIST.powi(2) {
|
|
|
|
if let Some((bearing, speed)) = agent.chaser.chase(
|
|
|
|
&*terrain,
|
|
|
|
self.pos.0,
|
|
|
|
self.vel.0,
|
|
|
|
tgt_pos.0,
|
|
|
|
TraversalConfig {
|
|
|
|
min_tgt_dist: 1.25,
|
|
|
|
..self.traversal_config
|
|
|
|
},
|
|
|
|
) {
|
|
|
|
controller.inputs.move_dir =
|
|
|
|
bearing.xy().try_normalized().unwrap_or_else(Vec2::zero) * speed;
|
|
|
|
controller.inputs.jump.set_state(bearing.z > 1.5);
|
|
|
|
controller.inputs.move_z = bearing.z;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
agent.target = None;
|
|
|
|
}
|
|
|
|
},
|
|
|
|
Tactic::Turret => {
|
2021-03-03 03:55:28 +00:00
|
|
|
if can_see_tgt(&*terrain, self.pos, tgt_pos, dist_sqrd) {
|
2021-02-07 07:22:06 +00:00
|
|
|
controller.inputs.primary.set_state(true);
|
|
|
|
} else {
|
|
|
|
agent.target = None;
|
|
|
|
}
|
|
|
|
},
|
|
|
|
Tactic::FixedTurret => {
|
|
|
|
controller.inputs.look_dir = self.ori.look_dir();
|
2021-03-03 03:55:28 +00:00
|
|
|
if can_see_tgt(&*terrain, self.pos, tgt_pos, dist_sqrd) {
|
2021-02-07 07:22:06 +00:00
|
|
|
controller.inputs.primary.set_state(true);
|
|
|
|
} else {
|
|
|
|
agent.target = None;
|
|
|
|
}
|
|
|
|
},
|
|
|
|
Tactic::RotatingTurret => {
|
|
|
|
controller.inputs.look_dir = Dir::new(
|
|
|
|
Quaternion::from_xyzw(self.ori.look_dir().x, self.ori.look_dir().y, 0.0, 0.0)
|
2021-03-03 03:55:28 +00:00
|
|
|
.rotated_z(6.0 * dt.0 as f32)
|
|
|
|
.into_vec3()
|
|
|
|
.try_normalized()
|
|
|
|
.unwrap_or_default(),
|
2021-02-07 07:22:06 +00:00
|
|
|
);
|
2021-03-03 03:55:28 +00:00
|
|
|
if can_see_tgt(&*terrain, self.pos, tgt_pos, dist_sqrd) {
|
2021-02-07 07:22:06 +00:00
|
|
|
controller.inputs.primary.set_state(true);
|
|
|
|
} else {
|
|
|
|
agent.target = None;
|
|
|
|
}
|
|
|
|
},
|
2020-08-07 01:59:28 +00:00
|
|
|
}
|
2021-02-07 07:22:06 +00:00
|
|
|
}
|
2021-02-06 06:15:25 +00:00
|
|
|
|
2021-02-07 07:22:06 +00:00
|
|
|
fn follow(
|
|
|
|
&self,
|
|
|
|
agent: &mut Agent,
|
|
|
|
controller: &mut Controller,
|
|
|
|
terrain: &TerrainGrid,
|
|
|
|
tgt_pos: &Pos,
|
|
|
|
) {
|
|
|
|
if let Some((bearing, speed)) = agent.chaser.chase(
|
|
|
|
&*terrain,
|
|
|
|
self.pos.0,
|
|
|
|
self.vel.0,
|
|
|
|
tgt_pos.0,
|
|
|
|
TraversalConfig {
|
|
|
|
min_tgt_dist: AVG_FOLLOW_DIST,
|
|
|
|
..self.traversal_config
|
|
|
|
},
|
|
|
|
) {
|
|
|
|
let dist_sqrd = self.pos.0.distance_squared(tgt_pos.0);
|
|
|
|
controller.inputs.move_dir = bearing.xy().try_normalized().unwrap_or_else(Vec2::zero)
|
|
|
|
* speed.min(0.2 + (dist_sqrd - AVG_FOLLOW_DIST.powi(2)) / 8.0);
|
|
|
|
controller.inputs.jump.set_state(bearing.z > 1.5);
|
|
|
|
controller.inputs.move_z = bearing.z;
|
|
|
|
}
|
2019-04-16 21:06:33 +00:00
|
|
|
}
|
|
|
|
}
|
2020-11-23 19:27:18 +00:00
|
|
|
|
|
|
|
fn can_see_tgt(terrain: &TerrainGrid, pos: &Pos, tgt_pos: &Pos, dist_sqrd: f32) -> bool {
|
|
|
|
terrain
|
|
|
|
.ray(pos.0 + Vec3::unit_z(), tgt_pos.0 + Vec3::unit_z())
|
|
|
|
.until(Block::is_opaque)
|
|
|
|
.cast()
|
|
|
|
.0
|
2020-11-25 00:28:24 +00:00
|
|
|
.powi(2)
|
2020-11-23 19:27:18 +00:00
|
|
|
>= dist_sqrd
|
|
|
|
}
|