2020-12-01 00:28:00 +00:00
|
|
|
use common::{
|
2020-04-18 18:28:19 +00:00
|
|
|
comp::{
|
|
|
|
self,
|
|
|
|
agent::Activity,
|
2020-04-26 17:03:19 +00:00
|
|
|
group,
|
2020-08-07 01:59:28 +00:00
|
|
|
group::Invite,
|
2021-01-08 19:12:09 +00:00
|
|
|
inventory::slot::EquipSlot,
|
2020-11-06 17:39:49 +00:00
|
|
|
item::{
|
|
|
|
tool::{ToolKind, UniqueKind},
|
|
|
|
ItemKind,
|
|
|
|
},
|
2020-11-19 08:04:36 +00:00
|
|
|
Agent, Alignment, Body, CharacterState, ControlAction, ControlEvent, Controller, Energy,
|
2021-01-08 19:12:09 +00:00
|
|
|
GroupManip, Health, Inventory, LightEmitter, MountState, Ori, PhysicsState, Pos, Scale,
|
2020-11-19 08:04:36 +00:00
|
|
|
UnresolvedChatMsg, Vel,
|
2020-04-18 18:28:19 +00:00
|
|
|
},
|
2020-06-04 07:11:35 +00:00
|
|
|
event::{EventBus, ServerEvent},
|
2020-09-14 12:56:05 +00:00
|
|
|
metrics::SysMetrics,
|
2020-07-13 22:23:44 +00:00
|
|
|
path::{Chaser, TraversalConfig},
|
2020-12-01 00:28:00 +00:00
|
|
|
resources::{DeltaTime, Time, TimeOfDay},
|
2020-08-26 08:11:31 +00:00
|
|
|
span,
|
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
|
|
|
};
|
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},
|
2020-11-25 22:47:16 +00:00
|
|
|
Entities, Join, ParJoin, Read, ReadExpect, ReadStorage, System, 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::*;
|
|
|
|
|
2019-06-09 19:33:20 +00:00
|
|
|
/// This system will allow NPCs to modify their controller
|
2019-04-16 21:06:33 +00:00
|
|
|
pub struct Sys;
|
|
|
|
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 = (
|
2020-10-08 22:55:30 +00:00
|
|
|
(
|
|
|
|
Read<'a, UidAllocator>,
|
|
|
|
Read<'a, Time>,
|
|
|
|
Read<'a, DeltaTime>,
|
|
|
|
Read<'a, group::GroupManager>,
|
|
|
|
),
|
2020-09-14 12:56:05 +00:00
|
|
|
ReadExpect<'a, SysMetrics>,
|
2020-06-04 07:11:35 +00:00
|
|
|
Write<'a, EventBus<ServerEvent>>,
|
2019-05-25 21:13:38 +00:00
|
|
|
Entities<'a>,
|
2020-10-07 20:52:29 +00:00
|
|
|
ReadStorage<'a, Energy>,
|
2019-04-16 21:06:33 +00:00
|
|
|
ReadStorage<'a, Pos>,
|
2020-07-03 19:30:21 +00:00
|
|
|
ReadStorage<'a, Vel>,
|
2020-03-28 01:31:22 +00:00
|
|
|
ReadStorage<'a, Ori>,
|
2020-04-16 23:07:29 +00:00
|
|
|
ReadStorage<'a, Scale>,
|
2020-10-31 22:34:08 +00:00
|
|
|
ReadStorage<'a, Health>,
|
2021-01-08 19:12:09 +00:00
|
|
|
ReadStorage<'a, Inventory>,
|
2020-07-09 23:43:11 +00:00
|
|
|
ReadStorage<'a, PhysicsState>,
|
2020-06-04 07:11:35 +00:00
|
|
|
ReadStorage<'a, Uid>,
|
2020-04-26 17:03:19 +00:00
|
|
|
ReadStorage<'a, group::Group>,
|
2019-12-11 05:28:45 +00:00
|
|
|
ReadExpect<'a, TerrainGrid>,
|
2020-01-24 21:24:57 +00:00
|
|
|
ReadStorage<'a, Alignment>,
|
2020-07-10 14:00:20 +00:00
|
|
|
ReadStorage<'a, Body>,
|
2019-08-02 20:25:33 +00:00
|
|
|
WriteStorage<'a, Agent>,
|
2019-06-09 14:20:20 +00:00
|
|
|
WriteStorage<'a, Controller>,
|
2019-09-09 19:11:40 +00:00
|
|
|
ReadStorage<'a, MountState>,
|
2020-08-07 01:59:28 +00:00
|
|
|
ReadStorage<'a, Invite>,
|
2020-10-07 02:23:20 +00:00
|
|
|
Read<'a, TimeOfDay>,
|
|
|
|
ReadStorage<'a, LightEmitter>,
|
2020-11-03 04:09:38 +00:00
|
|
|
ReadStorage<'a, CharacterState>,
|
2019-04-16 21:06:33 +00:00
|
|
|
);
|
|
|
|
|
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(
|
|
|
|
&mut self,
|
2019-12-11 05:28:45 +00:00
|
|
|
(
|
2020-10-08 22:55:30 +00:00
|
|
|
(uid_allocator, time, dt, group_manager),
|
2020-09-14 12:56:05 +00:00
|
|
|
sys_metrics,
|
2020-06-04 07:11:35 +00:00
|
|
|
event_bus,
|
2019-12-11 05:28:45 +00:00
|
|
|
entities,
|
2020-10-07 20:52:29 +00:00
|
|
|
energies,
|
2019-12-11 05:28:45 +00:00
|
|
|
positions,
|
2020-07-03 19:30:21 +00:00
|
|
|
velocities,
|
2020-03-28 01:31:22 +00:00
|
|
|
orientations,
|
2020-04-16 23:07:29 +00:00
|
|
|
scales,
|
2020-10-31 22:34:08 +00:00
|
|
|
healths,
|
2021-01-08 19:12:09 +00:00
|
|
|
inventories,
|
2020-07-09 23:43:11 +00:00
|
|
|
physics_states,
|
2020-06-04 07:11:35 +00:00
|
|
|
uids,
|
2020-04-26 17:03:19 +00:00
|
|
|
groups,
|
2019-12-11 05:28:45 +00:00
|
|
|
terrain,
|
2020-01-24 21:24:57 +00:00
|
|
|
alignments,
|
2020-07-10 14:00:20 +00:00
|
|
|
bodies,
|
2019-12-11 05:28:45 +00:00
|
|
|
mut agents,
|
|
|
|
mut controllers,
|
|
|
|
mount_states,
|
2020-08-07 01:59:28 +00:00
|
|
|
invites,
|
2020-10-07 02:23:20 +00:00
|
|
|
time_of_day,
|
|
|
|
light_emitter,
|
2020-11-03 04:09:38 +00:00
|
|
|
char_states,
|
2019-12-11 05:28:45 +00:00
|
|
|
): Self::SystemData,
|
2019-08-04 08:21:29 +00:00
|
|
|
) {
|
2020-09-14 12:56:05 +00:00
|
|
|
let start_time = std::time::Instant::now();
|
2020-09-07 04:59:16 +00:00
|
|
|
span!(_guard, "run", "agent::Sys::run");
|
2020-11-25 22:47:16 +00:00
|
|
|
|
|
|
|
(
|
2019-09-09 19:11:40 +00:00
|
|
|
&entities,
|
2020-10-07 20:52:29 +00:00
|
|
|
&energies,
|
2019-09-09 19:11:40 +00:00
|
|
|
&positions,
|
2020-07-03 19:30:21 +00:00
|
|
|
&velocities,
|
2020-03-28 01:31:22 +00:00
|
|
|
&orientations,
|
2020-01-24 21:24:57 +00:00
|
|
|
alignments.maybe(),
|
2021-01-08 19:12:09 +00:00
|
|
|
&inventories,
|
2020-07-09 23:43:11 +00:00
|
|
|
&physics_states,
|
2020-07-10 14:00:20 +00:00
|
|
|
bodies.maybe(),
|
2020-06-04 07:11:35 +00:00
|
|
|
&uids,
|
2019-09-09 19:11:40 +00:00
|
|
|
&mut agents,
|
|
|
|
&mut controllers,
|
|
|
|
mount_states.maybe(),
|
2020-04-26 17:03:19 +00:00
|
|
|
groups.maybe(),
|
2020-10-07 02:23:20 +00:00
|
|
|
light_emitter.maybe(),
|
2019-09-09 19:11:40 +00:00
|
|
|
)
|
2020-11-25 22:47:16 +00:00
|
|
|
.par_join()
|
|
|
|
.filter(|(_, _, _, _, _, _, _, _, _, _, _, _, mount_state, _, _)| {
|
|
|
|
// Skip mounted entities
|
|
|
|
mount_state.map(|ms| *ms == MountState::Unmounted).unwrap_or(true)
|
|
|
|
})
|
|
|
|
.for_each(|(
|
|
|
|
entity,
|
|
|
|
energy,
|
|
|
|
pos,
|
|
|
|
vel,
|
|
|
|
ori,
|
|
|
|
alignment,
|
2021-01-08 19:12:09 +00:00
|
|
|
inventory,
|
2020-11-25 22:47:16 +00:00
|
|
|
physics_state,
|
|
|
|
body,
|
|
|
|
uid,
|
|
|
|
agent,
|
|
|
|
controller,
|
|
|
|
_,
|
|
|
|
group,
|
|
|
|
light_emitter,
|
|
|
|
)| {
|
|
|
|
// 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!(alignment, Some(Alignment::Owned(_))) {
|
|
|
|
group
|
|
|
|
.and_then(|g| group_manager.group_info(*g))
|
|
|
|
.and_then(|info| uids.get(info.leader))
|
|
|
|
.copied()
|
|
|
|
.map(Alignment::Owned)
|
|
|
|
.or(alignment.copied())
|
|
|
|
} else {
|
|
|
|
alignment.copied()
|
|
|
|
};
|
2019-09-09 19:11:40 +00:00
|
|
|
|
2020-11-25 22:47:16 +00:00
|
|
|
controller.reset();
|
|
|
|
let mut event_emitter = event_bus.emitter();
|
|
|
|
// Light lanterns at night
|
|
|
|
// TODO Add a method to turn on NPC lanterns underground
|
2021-01-08 19:12:09 +00:00
|
|
|
let lantern_equipped = inventory.equipped(EquipSlot::Lantern).as_ref().map_or(false, |item| {
|
2020-11-25 22:47:16 +00:00
|
|
|
matches!(item.kind(), comp::item::ItemKind::Lantern(_))
|
|
|
|
});
|
|
|
|
let lantern_turned_on = light_emitter.is_some();
|
|
|
|
let day_period = DayPeriod::from(time_of_day.0);
|
|
|
|
// Only emit event for agents that have a lantern equipped
|
|
|
|
if lantern_equipped {
|
|
|
|
let mut rng = thread_rng();
|
|
|
|
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
|
|
|
|
if let 0 = rng.gen_range(0, 1000) {
|
|
|
|
controller.events.push(ControlEvent::EnableLantern)
|
|
|
|
}
|
|
|
|
} 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
|
|
|
|
if let 0 = rng.gen_range(0, 2000) {
|
|
|
|
controller.events.push(ControlEvent::DisableLantern)
|
|
|
|
}
|
2020-10-07 02:23:20 +00:00
|
|
|
}
|
2020-11-25 22:47:16 +00:00
|
|
|
};
|
2020-01-25 18:49:47 +00:00
|
|
|
|
2020-11-25 22:47:16 +00:00
|
|
|
let mut inputs = &mut controller.inputs;
|
2020-11-12 21:31:28 +00:00
|
|
|
|
2020-11-25 22:47:16 +00:00
|
|
|
// Default to looking in orientation direction (can be overridden below)
|
|
|
|
inputs.look_dir = ori.0;
|
2020-01-26 12:47:41 +00:00
|
|
|
|
2020-11-25 22:47:16 +00:00
|
|
|
const AVG_FOLLOW_DIST: f32 = 6.0;
|
|
|
|
const MAX_FOLLOW_DIST: f32 = 12.0;
|
|
|
|
const MAX_CHASE_DIST: f32 = 18.0;
|
|
|
|
const LISTEN_DIST: f32 = 16.0;
|
|
|
|
const SEARCH_DIST: f32 = 48.0;
|
|
|
|
const SIGHT_DIST: f32 = 80.0;
|
|
|
|
const MIN_ATTACK_DIST: f32 = 2.0;
|
|
|
|
const MAX_FLEE_DIST: f32 = 20.0;
|
|
|
|
const SNEAK_COEFFICIENT: f32 = 0.25;
|
2020-11-12 21:31:28 +00:00
|
|
|
|
2020-11-25 22:47:16 +00:00
|
|
|
let scale = scales.get(entity).map(|s| s.0).unwrap_or(1.0);
|
2020-11-23 19:27:18 +00:00
|
|
|
|
2020-11-25 22:47:16 +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),
|
2021-01-05 01:04:01 +00:00
|
|
|
can_fly: body.map(|b| b.can_fly()).unwrap_or(false),
|
2020-11-25 22:47:16 +00:00
|
|
|
};
|
2020-01-25 23:39:38 +00:00
|
|
|
|
2020-11-25 22:47:16 +00:00
|
|
|
let mut do_idle = false;
|
|
|
|
let mut choose_target = false;
|
2020-04-19 11:50:25 +00:00
|
|
|
|
2020-11-25 22:47:16 +00:00
|
|
|
'activity: {
|
|
|
|
match &mut agent.activity {
|
|
|
|
Activity::Idle { bearing, chaser } => {
|
|
|
|
if let Some(travel_to) = agent.rtsim_controller.travel_to {
|
2021-01-15 00:42:57 +00:00
|
|
|
// 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
|
|
|
|
inputs.fly.set_state(traversal_config.can_fly && !terrain
|
|
|
|
.ray(
|
|
|
|
pos.0,
|
|
|
|
pos.0 + (Vec3::unit_z() * 3.0))
|
|
|
|
.until(Block::is_solid)
|
|
|
|
.cast()
|
|
|
|
.1
|
|
|
|
.map_or(true, |b| b.is_some()));
|
2020-11-25 22:47:16 +00:00
|
|
|
if let Some((bearing, speed)) =
|
|
|
|
chaser.chase(&*terrain, pos.0, vel.0, travel_to, TraversalConfig {
|
|
|
|
min_tgt_dist: 1.25,
|
2020-11-14 17:00:32 +00:00
|
|
|
..traversal_config
|
2020-11-25 22:47:16 +00:00
|
|
|
})
|
|
|
|
{
|
2020-07-04 19:22:55 +00:00
|
|
|
inputs.move_dir =
|
|
|
|
bearing.xy().try_normalized().unwrap_or(Vec2::zero())
|
2020-11-25 22:47:16 +00:00
|
|
|
* speed.min(agent.rtsim_controller.speed_factor);
|
2021-01-05 01:04:01 +00:00
|
|
|
inputs.jump.set_state(bearing.z > 1.5 || traversal_config.can_fly && traversal_config.on_ground);
|
2020-11-25 22:47:16 +00:00
|
|
|
inputs.climb = Some(comp::Climb::Up);
|
|
|
|
//.filter(|_| bearing.z > 0.1 || physics_state.in_liquid.is_some());
|
2021-01-05 01:04:01 +00:00
|
|
|
|
|
|
|
inputs.move_z = bearing.z + if traversal_config.can_fly {
|
|
|
|
if terrain
|
|
|
|
.ray(
|
|
|
|
pos.0 + Vec3::unit_z(),
|
|
|
|
pos.0
|
|
|
|
+ bearing
|
|
|
|
.try_normalized()
|
|
|
|
.unwrap_or(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-01-26 12:47:41 +00:00
|
|
|
}
|
|
|
|
} else {
|
2020-11-25 22:47:16 +00:00
|
|
|
*bearing += Vec2::new(
|
|
|
|
thread_rng().gen::<f32>() - 0.5,
|
|
|
|
thread_rng().gen::<f32>() - 0.5,
|
|
|
|
) * 0.1
|
|
|
|
- *bearing * 0.003
|
|
|
|
- agent.patrol_origin.map_or(Vec2::zero(), |patrol_origin| {
|
|
|
|
(pos.0 - patrol_origin).xy() * 0.0002
|
|
|
|
});
|
2020-11-11 04:36:40 +00:00
|
|
|
|
2020-11-25 22:47:16 +00:00
|
|
|
// Stop if we're too close to a wall
|
|
|
|
*bearing *= 0.1
|
|
|
|
+ if terrain
|
|
|
|
.ray(
|
|
|
|
pos.0 + Vec3::unit_z(),
|
|
|
|
pos.0
|
|
|
|
+ Vec3::from(*bearing)
|
|
|
|
.try_normalized()
|
|
|
|
.unwrap_or(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-20 01:42:22 +00:00
|
|
|
|
2020-11-25 22:47:16 +00:00
|
|
|
if bearing.magnitude_squared() > 0.5f32.powi(2) {
|
|
|
|
inputs.move_dir = *bearing * 0.65;
|
|
|
|
}
|
2020-11-11 04:36:40 +00:00
|
|
|
|
2020-11-25 22:47:16 +00:00
|
|
|
// Put away weapon
|
|
|
|
if thread_rng().gen::<f32>() < 0.005 {
|
|
|
|
controller.actions.push(ControlAction::Unwield);
|
|
|
|
}
|
2020-03-17 17:27:52 +00:00
|
|
|
|
2020-11-25 22:47:16 +00:00
|
|
|
// Sit
|
|
|
|
if thread_rng().gen::<f32>() < 0.0035 {
|
|
|
|
controller.actions.push(ControlAction::Sit);
|
2020-01-26 12:47:41 +00:00
|
|
|
}
|
2020-01-26 00:06:03 +00:00
|
|
|
}
|
|
|
|
|
2020-11-25 22:47:16 +00:00
|
|
|
controller.actions.push(ControlAction::Unwield);
|
2020-07-29 19:58:14 +00:00
|
|
|
|
2020-11-25 22:47:16 +00:00
|
|
|
// Sometimes try searching for new targets
|
|
|
|
if thread_rng().gen::<f32>() < 0.1 {
|
|
|
|
choose_target = true;
|
|
|
|
}
|
|
|
|
},
|
|
|
|
Activity::Follow { target, chaser } => {
|
|
|
|
if let (Some(tgt_pos), _tgt_health) =
|
|
|
|
(positions.get(*target), healths.get(*target))
|
|
|
|
{
|
|
|
|
let dist = pos.0.distance(tgt_pos.0);
|
|
|
|
// Follow, or return to idle
|
|
|
|
if dist > AVG_FOLLOW_DIST {
|
2020-07-29 23:14:06 +00:00
|
|
|
if let Some((bearing, speed)) = chaser.chase(
|
|
|
|
&*terrain,
|
|
|
|
pos.0,
|
|
|
|
vel.0,
|
2020-11-25 22:47:16 +00:00
|
|
|
tgt_pos.0,
|
2020-07-29 23:14:06 +00:00
|
|
|
TraversalConfig {
|
2020-11-25 22:47:16 +00:00
|
|
|
min_tgt_dist: AVG_FOLLOW_DIST,
|
2020-11-14 17:00:32 +00:00
|
|
|
..traversal_config
|
2020-07-29 23:14:06 +00:00
|
|
|
},
|
|
|
|
) {
|
2020-10-17 01:27:57 +00:00
|
|
|
inputs.move_dir =
|
|
|
|
bearing.xy().try_normalized().unwrap_or(Vec2::zero())
|
2020-11-25 22:47:16 +00:00
|
|
|
* speed.min(0.2 + (dist - AVG_FOLLOW_DIST) / 8.0);
|
2020-07-29 23:14:06 +00:00
|
|
|
inputs.jump.set_state(bearing.z > 1.5);
|
2020-11-03 22:46:07 +00:00
|
|
|
inputs.move_z = bearing.z;
|
2020-07-29 23:14:06 +00:00
|
|
|
}
|
|
|
|
} else {
|
|
|
|
do_idle = true;
|
2020-07-29 19:58:14 +00:00
|
|
|
}
|
2020-11-23 19:27:18 +00:00
|
|
|
} else {
|
2020-11-25 22:47:16 +00:00
|
|
|
do_idle = true;
|
|
|
|
}
|
|
|
|
},
|
|
|
|
Activity::Attack {
|
|
|
|
target,
|
|
|
|
chaser,
|
|
|
|
been_close,
|
|
|
|
powerup,
|
|
|
|
..
|
|
|
|
} => {
|
|
|
|
#[derive(Eq, PartialEq)]
|
|
|
|
enum Tactic {
|
|
|
|
Melee,
|
|
|
|
Axe,
|
|
|
|
Hammer,
|
|
|
|
Sword,
|
|
|
|
Bow,
|
|
|
|
Staff,
|
|
|
|
StoneGolemBoss,
|
|
|
|
CircleCharge { radius: u32, circle_time: u32 },
|
|
|
|
QuadLowRanged,
|
|
|
|
TailSlap,
|
|
|
|
QuadLowQuick,
|
|
|
|
QuadLowBasic,
|
|
|
|
QuadMedJump,
|
|
|
|
QuadMedBasic,
|
|
|
|
Lavadrake,
|
|
|
|
Theropod,
|
|
|
|
}
|
2020-11-11 03:18:45 +00:00
|
|
|
|
2021-01-08 19:12:09 +00:00
|
|
|
let tactic = match inventory.equipped(EquipSlot::Mainhand).as_ref().and_then(|item| {
|
|
|
|
if let ItemKind::Tool(tool) = &item.kind() {
|
2020-11-25 22:47:16 +00:00
|
|
|
Some(&tool.kind)
|
|
|
|
} else {
|
|
|
|
None
|
|
|
|
}
|
|
|
|
}) {
|
|
|
|
Some(ToolKind::Bow) => Tactic::Bow,
|
|
|
|
Some(ToolKind::Staff) => Tactic::Staff,
|
|
|
|
Some(ToolKind::Hammer) => Tactic::Hammer,
|
|
|
|
Some(ToolKind::Sword) => Tactic::Sword,
|
|
|
|
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,
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
|
|
|
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
|
|
|
|
},
|
|
|
|
Some(ToolKind::Unique(UniqueKind::QuadLowBreathe)) => Tactic::Lavadrake,
|
|
|
|
Some(ToolKind::Unique(UniqueKind::TheropodBasic)) => Tactic::Theropod,
|
|
|
|
Some(ToolKind::Unique(UniqueKind::TheropodBird)) => Tactic::Theropod,
|
|
|
|
_ => Tactic::Melee,
|
|
|
|
};
|
|
|
|
|
|
|
|
if let (Some(tgt_pos), Some(tgt_health), tgt_alignment) = (
|
|
|
|
positions.get(*target),
|
|
|
|
healths.get(*target),
|
|
|
|
alignments.get(*target).copied().unwrap_or(
|
|
|
|
uids.get(*target)
|
|
|
|
.copied()
|
|
|
|
.map(Alignment::Owned)
|
|
|
|
.unwrap_or(Alignment::Wild),
|
|
|
|
),
|
|
|
|
) {
|
|
|
|
// Wield the weapon as running towards the target
|
|
|
|
controller.actions.push(ControlAction::Wield);
|
|
|
|
|
|
|
|
let eye_offset = body.map_or(0.0, |b| b.eye_height());
|
|
|
|
|
|
|
|
let tgt_eye_offset = bodies.get(*target).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
|
|
|
|
};
|
|
|
|
|
|
|
|
// Hacky distance offset for ranged weapons
|
|
|
|
let distance_offset = match tactic {
|
|
|
|
Tactic::Bow => 0.0004 * pos.0.distance_squared(tgt_pos.0),
|
|
|
|
Tactic::Staff => 0.0015 * pos.0.distance_squared(tgt_pos.0),
|
|
|
|
Tactic::QuadLowRanged => 0.03 * 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(pos.0.x, pos.0.y, pos.0.z + eye_offset),
|
|
|
|
) {
|
|
|
|
inputs.look_dir = dir;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Don't attack entities we are passive towards
|
|
|
|
// TODO: This is here, it's a bit of a hack
|
|
|
|
if let Some(alignment) = alignment {
|
|
|
|
if alignment.passive_towards(tgt_alignment) || tgt_health.is_dead {
|
|
|
|
do_idle = true;
|
|
|
|
break 'activity;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
let dist_sqrd = pos.0.distance_squared(tgt_pos.0);
|
|
|
|
|
|
|
|
let damage = healths
|
|
|
|
.get(entity)
|
|
|
|
.map(|h| h.current() as f32 / h.maximum() as f32)
|
|
|
|
.unwrap_or(0.5);
|
|
|
|
|
|
|
|
// Flee
|
|
|
|
let flees = alignment
|
|
|
|
.map(|a| !matches!(a, Alignment::Enemy | Alignment::Owned(_)))
|
|
|
|
.unwrap_or(true);
|
|
|
|
if 1.0 - agent.psyche.aggro > damage && flees {
|
|
|
|
if let Some(body) = body {
|
|
|
|
if body.can_strafe() {
|
|
|
|
controller.actions.push(ControlAction::Unwield);
|
2020-11-13 06:13:37 +00:00
|
|
|
}
|
2020-11-25 22:47:16 +00:00
|
|
|
}
|
|
|
|
if dist_sqrd < MAX_FLEE_DIST.powi(2) {
|
|
|
|
if let Some((bearing, speed)) = chaser.chase(
|
|
|
|
&*terrain,
|
|
|
|
pos.0,
|
|
|
|
vel.0,
|
|
|
|
// Away from the target (ironically)
|
|
|
|
pos.0
|
|
|
|
+ (pos.0 - tgt_pos.0)
|
2020-11-23 19:27:18 +00:00
|
|
|
.try_normalized()
|
2020-11-25 22:47:16 +00:00
|
|
|
.unwrap_or_else(Vec3::unit_y)
|
|
|
|
* 50.0,
|
|
|
|
TraversalConfig {
|
|
|
|
min_tgt_dist: 1.25,
|
|
|
|
..traversal_config
|
|
|
|
},
|
|
|
|
) {
|
|
|
|
inputs.move_dir =
|
|
|
|
bearing.xy().try_normalized().unwrap_or(Vec2::zero())
|
2020-11-23 19:27:18 +00:00
|
|
|
* speed;
|
2020-11-25 22:47:16 +00:00
|
|
|
inputs.jump.set_state(bearing.z > 1.5);
|
|
|
|
inputs.move_z = bearing.z;
|
2020-11-23 19:27:18 +00:00
|
|
|
}
|
2020-11-25 22:47:16 +00:00
|
|
|
} else {
|
|
|
|
do_idle = true;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
// 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 * scale).powi(2) {
|
2020-11-23 19:27:18 +00:00
|
|
|
inputs.primary.set_state(true);
|
2020-11-25 22:47:16 +00:00
|
|
|
inputs.move_dir = Vec2::zero();
|
|
|
|
} else if dist_sqrd < MAX_CHASE_DIST.powi(2)
|
|
|
|
|| (dist_sqrd < SIGHT_DIST.powi(2) && !*been_close)
|
|
|
|
{
|
|
|
|
if dist_sqrd < MAX_CHASE_DIST.powi(2) {
|
|
|
|
*been_close = true;
|
|
|
|
}
|
|
|
|
if let Some((bearing, speed)) = chaser.chase(
|
|
|
|
&*terrain,
|
|
|
|
pos.0,
|
|
|
|
vel.0,
|
|
|
|
tgt_pos.0,
|
|
|
|
TraversalConfig {
|
|
|
|
min_tgt_dist: 1.25,
|
|
|
|
..traversal_config
|
|
|
|
},
|
|
|
|
) {
|
2020-11-23 19:27:18 +00:00
|
|
|
inputs.move_dir = bearing
|
|
|
|
.xy()
|
|
|
|
.try_normalized()
|
|
|
|
.unwrap_or(Vec2::zero())
|
|
|
|
* speed;
|
2020-11-25 22:47:16 +00:00
|
|
|
inputs.jump.set_state(bearing.z > 1.5);
|
|
|
|
inputs.move_z = bearing.z;
|
|
|
|
}
|
|
|
|
|
|
|
|
if dist_sqrd < 16.0f32.powi(2)
|
|
|
|
&& thread_rng().gen::<f32>() < 0.02
|
|
|
|
{
|
|
|
|
inputs.roll.set_state(true);
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
do_idle = true;
|
|
|
|
}
|
|
|
|
},
|
|
|
|
Tactic::Axe => {
|
|
|
|
if dist_sqrd < (MIN_ATTACK_DIST * scale).powi(2) {
|
|
|
|
inputs.move_dir = Vec2::zero();
|
|
|
|
if *powerup > 6.0 {
|
|
|
|
inputs.secondary.set_state(false);
|
|
|
|
*powerup = 0.0;
|
|
|
|
} else if *powerup > 4.0 && energy.current() > 10 {
|
|
|
|
inputs.secondary.set_state(true);
|
|
|
|
*powerup += dt.0;
|
|
|
|
} else if energy.current() > 800
|
|
|
|
&& thread_rng().gen_bool(0.5)
|
|
|
|
{
|
|
|
|
inputs.ability3.set_state(true);
|
|
|
|
*powerup += dt.0;
|
2020-11-23 19:27:18 +00:00
|
|
|
} else {
|
2020-11-25 22:47:16 +00:00
|
|
|
inputs.primary.set_state(true);
|
|
|
|
*powerup += dt.0;
|
|
|
|
}
|
|
|
|
} else if dist_sqrd < MAX_CHASE_DIST.powi(2)
|
|
|
|
|| (dist_sqrd < SIGHT_DIST.powi(2) && !*been_close)
|
|
|
|
{
|
|
|
|
if dist_sqrd < MAX_CHASE_DIST.powi(2) {
|
|
|
|
*been_close = true;
|
|
|
|
}
|
|
|
|
if let Some((bearing, speed)) = chaser.chase(
|
|
|
|
&*terrain,
|
|
|
|
pos.0,
|
|
|
|
vel.0,
|
|
|
|
tgt_pos.0,
|
|
|
|
TraversalConfig {
|
|
|
|
min_tgt_dist: 1.25,
|
|
|
|
..traversal_config
|
|
|
|
},
|
|
|
|
) {
|
2020-11-23 19:27:18 +00:00
|
|
|
inputs.move_dir = bearing
|
|
|
|
.xy()
|
|
|
|
.try_normalized()
|
|
|
|
.unwrap_or(Vec2::zero())
|
|
|
|
* speed;
|
|
|
|
inputs.jump.set_state(bearing.z > 1.5);
|
|
|
|
inputs.move_z = bearing.z;
|
|
|
|
}
|
2020-11-25 22:47:16 +00:00
|
|
|
if dist_sqrd < 16.0f32.powi(2)
|
|
|
|
&& thread_rng().gen::<f32>() < 0.02
|
|
|
|
{
|
|
|
|
inputs.roll.set_state(true);
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
do_idle = true;
|
2020-11-23 19:27:18 +00:00
|
|
|
}
|
2020-11-25 22:47:16 +00:00
|
|
|
},
|
|
|
|
Tactic::Hammer => {
|
|
|
|
if dist_sqrd < (MIN_ATTACK_DIST * scale).powi(2) {
|
|
|
|
inputs.move_dir = Vec2::zero();
|
|
|
|
if *powerup > 4.0 {
|
|
|
|
inputs.secondary.set_state(false);
|
|
|
|
*powerup = 0.0;
|
|
|
|
} else if *powerup > 2.0 {
|
|
|
|
inputs.secondary.set_state(true);
|
|
|
|
*powerup += dt.0;
|
|
|
|
} else if energy.current() > 700 {
|
|
|
|
inputs.ability3.set_state(true);
|
|
|
|
*powerup += dt.0;
|
|
|
|
} else {
|
|
|
|
inputs.primary.set_state(true);
|
|
|
|
*powerup += dt.0;
|
|
|
|
}
|
|
|
|
} else if dist_sqrd < MAX_CHASE_DIST.powi(2)
|
|
|
|
|| (dist_sqrd < SIGHT_DIST.powi(2) && !*been_close)
|
2020-11-23 19:27:18 +00:00
|
|
|
{
|
2020-11-25 22:47:16 +00:00
|
|
|
if dist_sqrd < MAX_CHASE_DIST.powi(2) {
|
|
|
|
*been_close = true;
|
|
|
|
}
|
|
|
|
if let Some((bearing, speed)) = chaser.chase(
|
|
|
|
&*terrain,
|
|
|
|
pos.0,
|
|
|
|
vel.0,
|
|
|
|
tgt_pos.0,
|
|
|
|
TraversalConfig {
|
|
|
|
min_tgt_dist: 1.25,
|
|
|
|
..traversal_config
|
|
|
|
},
|
|
|
|
) {
|
|
|
|
if can_see_tgt(&*terrain, pos, tgt_pos, dist_sqrd) {
|
|
|
|
inputs.move_dir = bearing
|
|
|
|
.xy()
|
|
|
|
.try_normalized()
|
|
|
|
.unwrap_or(Vec2::zero())
|
|
|
|
* speed;
|
|
|
|
if *powerup > 5.0 {
|
|
|
|
inputs.ability3.set_state(true);
|
|
|
|
*powerup = 0.0;
|
|
|
|
} else {
|
|
|
|
*powerup += dt.0;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
inputs.move_dir = bearing
|
|
|
|
.xy()
|
|
|
|
.try_normalized()
|
|
|
|
.unwrap_or(Vec2::zero())
|
|
|
|
* speed;
|
|
|
|
inputs.jump.set_state(bearing.z > 1.5);
|
|
|
|
inputs.move_z = bearing.z;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if dist_sqrd < 16.0f32.powi(2)
|
|
|
|
&& thread_rng().gen::<f32>() < 0.02
|
|
|
|
{
|
|
|
|
inputs.roll.set_state(true);
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
do_idle = true;
|
2020-11-23 19:27:18 +00:00
|
|
|
}
|
2020-11-25 22:47:16 +00:00
|
|
|
},
|
|
|
|
Tactic::Sword => {
|
|
|
|
if dist_sqrd < (MIN_ATTACK_DIST * scale).powi(2) {
|
|
|
|
inputs.move_dir = Vec2::zero();
|
|
|
|
if *powerup < 2.0 && energy.current() > 600 {
|
|
|
|
inputs.ability3.set_state(true);
|
|
|
|
*powerup += dt.0;
|
|
|
|
} else if *powerup > 2.0 {
|
|
|
|
*powerup = 0.0;
|
|
|
|
} else {
|
|
|
|
inputs.primary.set_state(true);
|
|
|
|
*powerup += dt.0;
|
|
|
|
}
|
|
|
|
} else if dist_sqrd < MAX_CHASE_DIST.powi(2)
|
|
|
|
|| (dist_sqrd < SIGHT_DIST.powi(2) && !*been_close)
|
|
|
|
{
|
|
|
|
if dist_sqrd < MAX_CHASE_DIST.powi(2) {
|
|
|
|
*been_close = true;
|
|
|
|
}
|
|
|
|
if let Some((bearing, speed)) = chaser.chase(
|
|
|
|
&*terrain,
|
|
|
|
pos.0,
|
|
|
|
vel.0,
|
|
|
|
tgt_pos.0,
|
|
|
|
TraversalConfig {
|
|
|
|
min_tgt_dist: 1.25,
|
|
|
|
..traversal_config
|
|
|
|
},
|
|
|
|
) {
|
|
|
|
if can_see_tgt(&*terrain, pos, tgt_pos, dist_sqrd) {
|
|
|
|
inputs.move_dir = bearing
|
|
|
|
.xy()
|
|
|
|
.try_normalized()
|
|
|
|
.unwrap_or(Vec2::zero())
|
|
|
|
* speed;
|
|
|
|
if *powerup > 4.0 {
|
|
|
|
inputs.secondary.set_state(true);
|
|
|
|
*powerup = 0.0;
|
|
|
|
} else {
|
|
|
|
*powerup += dt.0;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
inputs.move_dir = bearing
|
|
|
|
.xy()
|
|
|
|
.try_normalized()
|
|
|
|
.unwrap_or(Vec2::zero())
|
|
|
|
* speed;
|
|
|
|
inputs.jump.set_state(bearing.z > 1.5);
|
|
|
|
inputs.move_z = bearing.z;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if dist_sqrd < 16.0f32.powi(2)
|
|
|
|
&& thread_rng().gen::<f32>() < 0.02
|
|
|
|
{
|
|
|
|
inputs.roll.set_state(true);
|
|
|
|
}
|
2020-11-11 04:36:40 +00:00
|
|
|
} else {
|
2020-11-25 22:47:16 +00:00
|
|
|
do_idle = true;
|
2020-11-11 04:36:40 +00:00
|
|
|
}
|
2020-11-25 22:47:16 +00:00
|
|
|
},
|
|
|
|
Tactic::Bow => {
|
|
|
|
if dist_sqrd < (2.0 * MIN_ATTACK_DIST * scale).powi(2) {
|
|
|
|
inputs.roll.set_state(true);
|
|
|
|
} else if dist_sqrd < MAX_CHASE_DIST.powi(2)
|
|
|
|
|| (dist_sqrd < SIGHT_DIST.powi(2) && !*been_close)
|
|
|
|
{
|
|
|
|
if dist_sqrd < MAX_CHASE_DIST.powi(2) {
|
|
|
|
*been_close = true;
|
|
|
|
}
|
|
|
|
if let Some((bearing, speed)) = chaser.chase(
|
|
|
|
&*terrain,
|
|
|
|
pos.0,
|
|
|
|
vel.0,
|
|
|
|
tgt_pos.0,
|
|
|
|
TraversalConfig {
|
|
|
|
min_tgt_dist: 1.25,
|
|
|
|
..traversal_config
|
|
|
|
},
|
|
|
|
) {
|
|
|
|
if can_see_tgt(&*terrain, pos, tgt_pos, dist_sqrd) {
|
|
|
|
inputs.move_dir = bearing
|
|
|
|
.xy()
|
|
|
|
.rotated_z(
|
|
|
|
thread_rng().gen_range(0.5, 1.57),
|
|
|
|
)
|
|
|
|
.try_normalized()
|
|
|
|
.unwrap_or(Vec2::zero())
|
|
|
|
* speed;
|
|
|
|
if *powerup > 4.0 {
|
|
|
|
inputs.secondary.set_state(false);
|
|
|
|
*powerup = 0.0;
|
|
|
|
} else if *powerup > 2.0
|
|
|
|
&& energy.current() > 300
|
|
|
|
{
|
|
|
|
inputs.secondary.set_state(true);
|
|
|
|
*powerup += dt.0;
|
|
|
|
} else if energy.current() > 400
|
|
|
|
&& thread_rng().gen_bool(0.8)
|
|
|
|
{
|
|
|
|
inputs.secondary.set_state(false);
|
|
|
|
inputs.ability3.set_state(true);
|
|
|
|
*powerup += dt.0;
|
|
|
|
} else {
|
|
|
|
inputs.secondary.set_state(false);
|
|
|
|
inputs.primary.set_state(true);
|
|
|
|
*powerup += dt.0;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
inputs.move_dir = bearing
|
|
|
|
.xy()
|
|
|
|
.try_normalized()
|
|
|
|
.unwrap_or(Vec2::zero())
|
|
|
|
* speed;
|
|
|
|
inputs.jump.set_state(bearing.z > 1.5);
|
|
|
|
inputs.move_z = bearing.z;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if dist_sqrd < 16.0f32.powi(2)
|
|
|
|
&& thread_rng().gen::<f32>() < 0.02
|
|
|
|
{
|
|
|
|
inputs.roll.set_state(true);
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
do_idle = true;
|
2020-11-23 19:27:18 +00:00
|
|
|
}
|
2020-11-25 22:47:16 +00:00
|
|
|
},
|
|
|
|
Tactic::Staff => {
|
|
|
|
if dist_sqrd < (MIN_ATTACK_DIST * scale).powi(2) {
|
|
|
|
inputs.roll.set_state(true);
|
|
|
|
} else if dist_sqrd
|
|
|
|
< (5.0 * MIN_ATTACK_DIST * scale).powi(2)
|
|
|
|
{
|
|
|
|
if *powerup < 1.5 {
|
|
|
|
inputs.move_dir = (tgt_pos.0 - pos.0)
|
2020-11-23 19:27:18 +00:00
|
|
|
.xy()
|
2020-11-25 22:47:16 +00:00
|
|
|
.rotated_z(0.47 * PI)
|
2020-11-23 19:27:18 +00:00
|
|
|
.try_normalized()
|
2020-11-25 22:47:16 +00:00
|
|
|
.unwrap_or(Vec2::unit_y());
|
|
|
|
*powerup += dt.0;
|
|
|
|
} else if *powerup < 3.0 {
|
|
|
|
inputs.move_dir = (tgt_pos.0 - pos.0)
|
2020-11-23 19:27:18 +00:00
|
|
|
.xy()
|
2020-11-25 22:47:16 +00:00
|
|
|
.rotated_z(-0.47 * PI)
|
2020-11-23 19:27:18 +00:00
|
|
|
.try_normalized()
|
2020-11-25 22:47:16 +00:00
|
|
|
.unwrap_or(Vec2::unit_y());
|
|
|
|
*powerup += dt.0;
|
|
|
|
} else {
|
|
|
|
*powerup = 0.0;
|
2020-11-23 19:27:18 +00:00
|
|
|
}
|
2020-11-25 22:47:16 +00:00
|
|
|
if energy.current() > 800
|
|
|
|
&& thread_rng().gen::<f32>() > 0.8
|
|
|
|
{
|
|
|
|
inputs.ability3.set_state(true);
|
|
|
|
} else if energy.current() > 10 {
|
|
|
|
inputs.secondary.set_state(true);
|
|
|
|
} else {
|
|
|
|
inputs.primary.set_state(true);
|
|
|
|
}
|
|
|
|
} else if dist_sqrd < MAX_CHASE_DIST.powi(2)
|
|
|
|
|| (dist_sqrd < SIGHT_DIST.powi(2) && !*been_close)
|
2020-11-23 19:27:18 +00:00
|
|
|
{
|
2020-11-25 22:47:16 +00:00
|
|
|
if dist_sqrd < MAX_CHASE_DIST.powi(2) {
|
|
|
|
*been_close = true;
|
|
|
|
}
|
|
|
|
if let Some((bearing, speed)) = chaser.chase(
|
|
|
|
&*terrain,
|
|
|
|
pos.0,
|
|
|
|
vel.0,
|
|
|
|
tgt_pos.0,
|
|
|
|
TraversalConfig {
|
|
|
|
min_tgt_dist: 1.25,
|
|
|
|
..traversal_config
|
|
|
|
},
|
|
|
|
) {
|
|
|
|
if can_see_tgt(&*terrain, pos, tgt_pos, dist_sqrd) {
|
|
|
|
inputs.move_dir = bearing
|
|
|
|
.xy()
|
|
|
|
.rotated_z(
|
|
|
|
thread_rng().gen_range(-1.57, -0.5),
|
|
|
|
)
|
|
|
|
.try_normalized()
|
|
|
|
.unwrap_or(Vec2::zero())
|
|
|
|
* speed;
|
|
|
|
inputs.primary.set_state(true);
|
|
|
|
} else {
|
|
|
|
inputs.move_dir = bearing
|
|
|
|
.xy()
|
|
|
|
.try_normalized()
|
|
|
|
.unwrap_or(Vec2::zero())
|
|
|
|
* speed;
|
|
|
|
inputs.jump.set_state(bearing.z > 1.5);
|
|
|
|
inputs.move_z = bearing.z;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if dist_sqrd < 16.0f32.powi(2)
|
|
|
|
&& thread_rng().gen::<f32>() < 0.02
|
|
|
|
{
|
|
|
|
inputs.roll.set_state(true);
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
do_idle = true;
|
2020-11-23 19:27:18 +00:00
|
|
|
}
|
2020-11-25 22:47:16 +00:00
|
|
|
},
|
|
|
|
Tactic::StoneGolemBoss => {
|
|
|
|
if dist_sqrd < (MIN_ATTACK_DIST * scale).powi(2) {
|
|
|
|
inputs.move_dir = Vec2::zero();
|
|
|
|
inputs.primary.set_state(true);
|
|
|
|
} else if dist_sqrd < MAX_CHASE_DIST.powi(2)
|
|
|
|
|| (dist_sqrd < SIGHT_DIST.powi(2) && !*been_close)
|
|
|
|
{
|
|
|
|
if dist_sqrd < MAX_CHASE_DIST.powi(2) {
|
|
|
|
*been_close = true;
|
|
|
|
}
|
|
|
|
if let Some((bearing, speed)) = chaser.chase(
|
|
|
|
&*terrain,
|
|
|
|
pos.0,
|
|
|
|
vel.0,
|
|
|
|
tgt_pos.0,
|
|
|
|
TraversalConfig {
|
|
|
|
min_tgt_dist: 1.25,
|
|
|
|
..traversal_config
|
|
|
|
},
|
|
|
|
) {
|
|
|
|
if can_see_tgt(&*terrain, pos, tgt_pos, dist_sqrd) {
|
|
|
|
inputs.move_dir = bearing
|
|
|
|
.xy()
|
|
|
|
.try_normalized()
|
|
|
|
.unwrap_or(Vec2::zero())
|
|
|
|
* speed;
|
|
|
|
if *powerup > 5.0 {
|
|
|
|
inputs.secondary.set_state(true);
|
|
|
|
*powerup = 0.0;
|
|
|
|
} else {
|
|
|
|
*powerup += dt.0;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
inputs.move_dir = bearing
|
|
|
|
.xy()
|
|
|
|
.try_normalized()
|
|
|
|
.unwrap_or(Vec2::zero())
|
|
|
|
* speed;
|
|
|
|
inputs.jump.set_state(bearing.z > 1.5);
|
|
|
|
inputs.move_z = bearing.z;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
do_idle = true;
|
2020-11-23 19:27:18 +00:00
|
|
|
}
|
2020-11-25 22:47:16 +00:00
|
|
|
},
|
|
|
|
Tactic::CircleCharge {
|
|
|
|
radius,
|
|
|
|
circle_time,
|
|
|
|
} => {
|
|
|
|
if dist_sqrd < (MIN_ATTACK_DIST * scale).powi(2)
|
|
|
|
&& thread_rng().gen_bool(0.5)
|
|
|
|
{
|
|
|
|
inputs.move_dir = Vec2::zero();
|
|
|
|
inputs.primary.set_state(true);
|
|
|
|
} else if dist_sqrd
|
|
|
|
< (radius as f32 * MIN_ATTACK_DIST * scale).powi(2)
|
|
|
|
{
|
|
|
|
inputs.move_dir = (pos.0 - tgt_pos.0)
|
|
|
|
.xy()
|
|
|
|
.try_normalized()
|
|
|
|
.unwrap_or(Vec2::unit_y());
|
|
|
|
} else if dist_sqrd
|
|
|
|
< ((radius as f32 + 1.0) * MIN_ATTACK_DIST * scale)
|
|
|
|
.powi(2)
|
|
|
|
&& dist_sqrd
|
|
|
|
> (radius as f32 * MIN_ATTACK_DIST * scale).powi(2)
|
|
|
|
{
|
|
|
|
if *powerup < circle_time as f32 {
|
|
|
|
inputs.move_dir = (tgt_pos.0 - pos.0)
|
2020-11-23 19:27:18 +00:00
|
|
|
.xy()
|
2020-11-25 22:47:16 +00:00
|
|
|
.rotated_z(0.47 * PI)
|
2020-11-23 19:27:18 +00:00
|
|
|
.try_normalized()
|
2020-11-25 22:47:16 +00:00
|
|
|
.unwrap_or(Vec2::unit_y());
|
|
|
|
*powerup += dt.0;
|
|
|
|
} else if *powerup < circle_time as f32 + 0.5 {
|
|
|
|
inputs.secondary.set_state(true);
|
|
|
|
*powerup += dt.0;
|
|
|
|
} else if *powerup < 2.0 * circle_time as f32 + 0.5 {
|
|
|
|
inputs.move_dir = (tgt_pos.0 - pos.0)
|
|
|
|
.xy()
|
|
|
|
.rotated_z(-0.47 * PI)
|
|
|
|
.try_normalized()
|
|
|
|
.unwrap_or(Vec2::unit_y());
|
|
|
|
*powerup += dt.0;
|
|
|
|
} else if *powerup < 2.0 * circle_time as f32 + 1.0 {
|
|
|
|
inputs.secondary.set_state(true);
|
|
|
|
*powerup += dt.0;
|
2020-11-23 19:27:18 +00:00
|
|
|
} else {
|
2020-11-25 22:47:16 +00:00
|
|
|
*powerup = 0.0;
|
|
|
|
}
|
|
|
|
} else if dist_sqrd < MAX_CHASE_DIST.powi(2)
|
|
|
|
|| (dist_sqrd < SIGHT_DIST.powi(2) && !*been_close)
|
|
|
|
{
|
|
|
|
if dist_sqrd < MAX_CHASE_DIST.powi(2) {
|
|
|
|
*been_close = true;
|
|
|
|
}
|
|
|
|
if let Some((bearing, speed)) = chaser.chase(
|
|
|
|
&*terrain,
|
|
|
|
pos.0,
|
|
|
|
vel.0,
|
|
|
|
tgt_pos.0,
|
|
|
|
TraversalConfig {
|
|
|
|
min_tgt_dist: 1.25,
|
|
|
|
..traversal_config
|
|
|
|
},
|
|
|
|
) {
|
2020-11-23 19:27:18 +00:00
|
|
|
inputs.move_dir = bearing
|
|
|
|
.xy()
|
|
|
|
.try_normalized()
|
|
|
|
.unwrap_or(Vec2::zero())
|
|
|
|
* speed;
|
|
|
|
inputs.jump.set_state(bearing.z > 1.5);
|
|
|
|
inputs.move_z = bearing.z;
|
|
|
|
}
|
2020-11-25 22:47:16 +00:00
|
|
|
} else {
|
|
|
|
do_idle = true;
|
2020-11-23 19:27:18 +00:00
|
|
|
}
|
2020-11-25 22:47:16 +00:00
|
|
|
},
|
|
|
|
Tactic::QuadLowRanged => {
|
|
|
|
if dist_sqrd < (5.0 * MIN_ATTACK_DIST * scale).powi(2) {
|
2020-11-23 19:27:18 +00:00
|
|
|
inputs.move_dir = (tgt_pos.0 - pos.0)
|
|
|
|
.xy()
|
|
|
|
.try_normalized()
|
|
|
|
.unwrap_or(Vec2::unit_y());
|
2020-11-25 22:47:16 +00:00
|
|
|
inputs.primary.set_state(true);
|
|
|
|
} else if dist_sqrd < MAX_CHASE_DIST.powi(2)
|
|
|
|
|| (dist_sqrd < SIGHT_DIST.powi(2) && !*been_close)
|
2020-11-23 19:27:18 +00:00
|
|
|
{
|
2020-11-25 22:47:16 +00:00
|
|
|
if dist_sqrd < MAX_CHASE_DIST.powi(2) {
|
|
|
|
*been_close = true;
|
|
|
|
}
|
|
|
|
if let Some((bearing, speed)) = chaser.chase(
|
|
|
|
&*terrain,
|
|
|
|
pos.0,
|
|
|
|
vel.0,
|
|
|
|
tgt_pos.0,
|
|
|
|
TraversalConfig {
|
|
|
|
min_tgt_dist: 1.25,
|
|
|
|
..traversal_config
|
|
|
|
},
|
|
|
|
) {
|
|
|
|
if can_see_tgt(&*terrain, pos, tgt_pos, dist_sqrd) {
|
|
|
|
if *powerup > 5.0 {
|
|
|
|
*powerup = 0.0;
|
|
|
|
} else if *powerup > 2.5 {
|
|
|
|
inputs.move_dir = (tgt_pos.0 - pos.0)
|
|
|
|
.xy()
|
|
|
|
.rotated_z(1.75 * PI)
|
|
|
|
.try_normalized()
|
|
|
|
.unwrap_or(Vec2::zero())
|
|
|
|
* speed;
|
|
|
|
*powerup += dt.0;
|
|
|
|
} else {
|
|
|
|
inputs.move_dir = (tgt_pos.0 - pos.0)
|
|
|
|
.xy()
|
|
|
|
.rotated_z(0.25 * PI)
|
|
|
|
.try_normalized()
|
|
|
|
.unwrap_or(Vec2::zero())
|
|
|
|
* speed;
|
|
|
|
*powerup += dt.0;
|
|
|
|
}
|
|
|
|
inputs.secondary.set_state(true);
|
|
|
|
inputs.jump.set_state(bearing.z > 1.5);
|
|
|
|
inputs.move_z = bearing.z;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
}
|
2020-11-11 04:36:40 +00:00
|
|
|
} else {
|
2020-11-25 22:47:16 +00:00
|
|
|
do_idle = true;
|
2020-11-23 19:27:18 +00:00
|
|
|
}
|
2020-11-25 22:47:16 +00:00
|
|
|
},
|
|
|
|
Tactic::TailSlap => {
|
|
|
|
if dist_sqrd < (1.5 * MIN_ATTACK_DIST * scale).powi(2) {
|
|
|
|
if *powerup > 4.0 {
|
|
|
|
inputs.primary.set_state(false);
|
|
|
|
*powerup = 0.0;
|
|
|
|
} else if *powerup > 1.0 {
|
2020-11-23 19:27:18 +00:00
|
|
|
inputs.primary.set_state(true);
|
2020-11-25 22:47:16 +00:00
|
|
|
*powerup += dt.0;
|
2020-11-23 19:27:18 +00:00
|
|
|
} else {
|
2020-11-25 22:47:16 +00:00
|
|
|
inputs.secondary.set_state(true);
|
|
|
|
*powerup += dt.0;
|
|
|
|
}
|
|
|
|
inputs.move_dir = (tgt_pos.0 - pos.0)
|
|
|
|
.xy()
|
|
|
|
.try_normalized()
|
|
|
|
.unwrap_or(Vec2::unit_y())
|
|
|
|
* 0.1;
|
|
|
|
} else if dist_sqrd < MAX_CHASE_DIST.powi(2)
|
|
|
|
|| (dist_sqrd < SIGHT_DIST.powi(2) && !*been_close)
|
|
|
|
{
|
|
|
|
if dist_sqrd < MAX_CHASE_DIST.powi(2) {
|
|
|
|
*been_close = true;
|
|
|
|
}
|
|
|
|
if let Some((bearing, speed)) = chaser.chase(
|
|
|
|
&*terrain,
|
|
|
|
pos.0,
|
|
|
|
vel.0,
|
|
|
|
tgt_pos.0,
|
|
|
|
TraversalConfig {
|
|
|
|
min_tgt_dist: 1.25,
|
|
|
|
..traversal_config
|
|
|
|
},
|
|
|
|
) {
|
2020-11-23 19:27:18 +00:00
|
|
|
inputs.move_dir = bearing
|
|
|
|
.xy()
|
|
|
|
.try_normalized()
|
|
|
|
.unwrap_or(Vec2::zero())
|
|
|
|
* speed;
|
|
|
|
inputs.jump.set_state(bearing.z > 1.5);
|
|
|
|
inputs.move_z = bearing.z;
|
|
|
|
}
|
2020-11-25 22:47:16 +00:00
|
|
|
} else {
|
|
|
|
do_idle = true;
|
2020-11-23 19:27:18 +00:00
|
|
|
}
|
2020-11-25 22:47:16 +00:00
|
|
|
},
|
|
|
|
Tactic::QuadLowQuick => {
|
|
|
|
if dist_sqrd < (1.5 * MIN_ATTACK_DIST * scale).powi(2) {
|
|
|
|
inputs.move_dir = Vec2::zero();
|
|
|
|
inputs.secondary.set_state(true);
|
|
|
|
} else if dist_sqrd
|
|
|
|
< (3.0 * MIN_ATTACK_DIST * scale).powi(2)
|
|
|
|
&& dist_sqrd > (2.0 * MIN_ATTACK_DIST * scale).powi(2)
|
2020-11-23 19:27:18 +00:00
|
|
|
{
|
2020-11-25 22:47:16 +00:00
|
|
|
inputs.primary.set_state(true);
|
|
|
|
inputs.move_dir = (tgt_pos.0 - pos.0)
|
|
|
|
.xy()
|
|
|
|
.rotated_z(-0.47 * PI)
|
|
|
|
.try_normalized()
|
|
|
|
.unwrap_or(Vec2::unit_y());
|
|
|
|
} else if dist_sqrd < MAX_CHASE_DIST.powi(2)
|
|
|
|
|| (dist_sqrd < SIGHT_DIST.powi(2) && !*been_close)
|
|
|
|
{
|
|
|
|
if dist_sqrd < MAX_CHASE_DIST.powi(2) {
|
|
|
|
*been_close = true;
|
|
|
|
}
|
|
|
|
if let Some((bearing, speed)) = chaser.chase(
|
|
|
|
&*terrain,
|
|
|
|
pos.0,
|
|
|
|
vel.0,
|
|
|
|
tgt_pos.0,
|
|
|
|
TraversalConfig {
|
|
|
|
min_tgt_dist: 1.25,
|
|
|
|
..traversal_config
|
|
|
|
},
|
|
|
|
) {
|
2020-11-23 19:27:18 +00:00
|
|
|
inputs.move_dir = bearing
|
|
|
|
.xy()
|
|
|
|
.try_normalized()
|
|
|
|
.unwrap_or(Vec2::zero())
|
|
|
|
* speed;
|
2020-11-25 22:47:16 +00:00
|
|
|
inputs.jump.set_state(bearing.z > 1.5);
|
|
|
|
inputs.move_z = bearing.z;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
do_idle = true;
|
|
|
|
}
|
|
|
|
},
|
|
|
|
Tactic::QuadLowBasic => {
|
|
|
|
if dist_sqrd < (1.5 * MIN_ATTACK_DIST * scale).powi(2) {
|
|
|
|
inputs.move_dir = Vec2::zero();
|
|
|
|
if *powerup > 5.0 {
|
|
|
|
*powerup = 0.0;
|
|
|
|
} else if *powerup > 2.0 {
|
|
|
|
inputs.secondary.set_state(true);
|
|
|
|
*powerup += dt.0;
|
2020-11-23 19:27:18 +00:00
|
|
|
} else {
|
2020-11-25 22:47:16 +00:00
|
|
|
inputs.primary.set_state(true);
|
|
|
|
*powerup += dt.0;
|
|
|
|
}
|
|
|
|
} else if dist_sqrd < MAX_CHASE_DIST.powi(2)
|
|
|
|
|| (dist_sqrd < SIGHT_DIST.powi(2) && !*been_close)
|
|
|
|
{
|
|
|
|
if dist_sqrd < MAX_CHASE_DIST.powi(2) {
|
|
|
|
*been_close = true;
|
|
|
|
}
|
|
|
|
if let Some((bearing, speed)) = chaser.chase(
|
|
|
|
&*terrain,
|
|
|
|
pos.0,
|
|
|
|
vel.0,
|
|
|
|
tgt_pos.0,
|
|
|
|
TraversalConfig {
|
|
|
|
min_tgt_dist: 1.25,
|
|
|
|
..traversal_config
|
|
|
|
},
|
|
|
|
) {
|
2020-11-23 19:27:18 +00:00
|
|
|
inputs.move_dir = bearing
|
|
|
|
.xy()
|
|
|
|
.try_normalized()
|
|
|
|
.unwrap_or(Vec2::zero())
|
|
|
|
* speed;
|
|
|
|
inputs.jump.set_state(bearing.z > 1.5);
|
|
|
|
inputs.move_z = bearing.z;
|
|
|
|
}
|
|
|
|
} else {
|
2020-11-25 22:47:16 +00:00
|
|
|
do_idle = true;
|
2020-11-23 19:27:18 +00:00
|
|
|
}
|
2020-11-25 22:47:16 +00:00
|
|
|
},
|
|
|
|
Tactic::QuadMedJump => {
|
|
|
|
if dist_sqrd < (1.5 * MIN_ATTACK_DIST * scale).powi(2) {
|
|
|
|
inputs.move_dir = Vec2::zero();
|
|
|
|
inputs.secondary.set_state(true);
|
|
|
|
} else if dist_sqrd
|
|
|
|
< (5.0 * MIN_ATTACK_DIST * scale).powi(2)
|
|
|
|
{
|
|
|
|
inputs.ability3.set_state(true);
|
|
|
|
} else if dist_sqrd < MAX_CHASE_DIST.powi(2)
|
|
|
|
|| (dist_sqrd < SIGHT_DIST.powi(2) && !*been_close)
|
|
|
|
{
|
|
|
|
if dist_sqrd < MAX_CHASE_DIST.powi(2) {
|
|
|
|
*been_close = true;
|
|
|
|
}
|
|
|
|
if let Some((bearing, speed)) = chaser.chase(
|
|
|
|
&*terrain,
|
|
|
|
pos.0,
|
|
|
|
vel.0,
|
|
|
|
tgt_pos.0,
|
|
|
|
TraversalConfig {
|
|
|
|
min_tgt_dist: 1.25,
|
|
|
|
..traversal_config
|
|
|
|
},
|
|
|
|
) {
|
|
|
|
if can_see_tgt(&*terrain, pos, tgt_pos, dist_sqrd) {
|
|
|
|
inputs.primary.set_state(true);
|
|
|
|
inputs.move_dir = bearing
|
2020-11-23 19:27:18 +00:00
|
|
|
.xy()
|
|
|
|
.try_normalized()
|
|
|
|
.unwrap_or(Vec2::zero())
|
|
|
|
* speed;
|
|
|
|
} else {
|
2020-11-25 22:47:16 +00:00
|
|
|
inputs.move_dir = bearing
|
2020-11-23 19:27:18 +00:00
|
|
|
.xy()
|
|
|
|
.try_normalized()
|
|
|
|
.unwrap_or(Vec2::zero())
|
|
|
|
* speed;
|
2020-11-25 22:47:16 +00:00
|
|
|
inputs.jump.set_state(bearing.z > 1.5);
|
|
|
|
inputs.move_z = bearing.z;
|
2020-11-23 19:27:18 +00:00
|
|
|
}
|
2020-11-25 22:47:16 +00:00
|
|
|
}
|
|
|
|
} else {
|
|
|
|
do_idle = true;
|
|
|
|
}
|
|
|
|
},
|
|
|
|
Tactic::QuadMedBasic => {
|
|
|
|
if dist_sqrd < (MIN_ATTACK_DIST * scale).powi(2) {
|
|
|
|
inputs.move_dir = Vec2::zero();
|
|
|
|
if *powerup < 2.0 {
|
2020-11-23 19:27:18 +00:00
|
|
|
inputs.secondary.set_state(true);
|
2020-11-25 22:47:16 +00:00
|
|
|
*powerup += dt.0;
|
|
|
|
} else if *powerup < 3.0 {
|
|
|
|
inputs.primary.set_state(true);
|
|
|
|
*powerup += dt.0;
|
|
|
|
} else {
|
|
|
|
*powerup = 0.0;
|
|
|
|
}
|
|
|
|
} else if dist_sqrd < MAX_CHASE_DIST.powi(2)
|
|
|
|
|| (dist_sqrd < SIGHT_DIST.powi(2) && !*been_close)
|
|
|
|
{
|
|
|
|
if dist_sqrd < MAX_CHASE_DIST.powi(2) {
|
|
|
|
*been_close = true;
|
|
|
|
}
|
|
|
|
if let Some((bearing, speed)) = chaser.chase(
|
|
|
|
&*terrain,
|
|
|
|
pos.0,
|
|
|
|
vel.0,
|
|
|
|
tgt_pos.0,
|
|
|
|
TraversalConfig {
|
|
|
|
min_tgt_dist: 1.25,
|
|
|
|
..traversal_config
|
|
|
|
},
|
|
|
|
) {
|
|
|
|
inputs.move_dir = bearing
|
|
|
|
.xy()
|
|
|
|
.try_normalized()
|
|
|
|
.unwrap_or(Vec2::zero())
|
|
|
|
* speed;
|
2020-11-23 19:27:18 +00:00
|
|
|
inputs.jump.set_state(bearing.z > 1.5);
|
|
|
|
inputs.move_z = bearing.z;
|
|
|
|
}
|
|
|
|
} else {
|
2020-11-25 22:47:16 +00:00
|
|
|
do_idle = true;
|
2020-11-23 19:27:18 +00:00
|
|
|
}
|
2020-11-25 22:47:16 +00:00
|
|
|
},
|
|
|
|
Tactic::Lavadrake => {
|
|
|
|
if dist_sqrd < (2.5 * MIN_ATTACK_DIST * scale).powi(2) {
|
|
|
|
inputs.move_dir = Vec2::zero();
|
2020-11-23 19:27:18 +00:00
|
|
|
inputs.secondary.set_state(true);
|
2020-11-25 22:47:16 +00:00
|
|
|
} else if dist_sqrd
|
|
|
|
< (7.0 * MIN_ATTACK_DIST * scale).powi(2)
|
|
|
|
{
|
|
|
|
if *powerup < 2.0 {
|
|
|
|
inputs.move_dir = (tgt_pos.0 - pos.0)
|
|
|
|
.xy()
|
|
|
|
.rotated_z(0.47 * PI)
|
|
|
|
.try_normalized()
|
|
|
|
.unwrap_or(Vec2::unit_y());
|
2020-11-23 19:27:18 +00:00
|
|
|
inputs.primary.set_state(true);
|
2020-11-25 22:47:16 +00:00
|
|
|
*powerup += dt.0;
|
|
|
|
} else if *powerup < 4.0 {
|
|
|
|
inputs.move_dir = (tgt_pos.0 - pos.0)
|
2020-11-13 06:13:37 +00:00
|
|
|
.xy()
|
2020-11-25 22:47:16 +00:00
|
|
|
.rotated_z(-0.47 * PI)
|
2020-11-13 06:13:37 +00:00
|
|
|
.try_normalized()
|
2020-11-25 22:47:16 +00:00
|
|
|
.unwrap_or(Vec2::unit_y());
|
|
|
|
inputs.primary.set_state(true);
|
|
|
|
*powerup += dt.0;
|
|
|
|
} else if *powerup < 6.0 {
|
|
|
|
inputs.ability3.set_state(true);
|
|
|
|
*powerup += dt.0;
|
2020-11-13 06:13:37 +00:00
|
|
|
} else {
|
2020-11-25 22:47:16 +00:00
|
|
|
*powerup = 0.0;
|
|
|
|
}
|
|
|
|
} else if dist_sqrd < MAX_CHASE_DIST.powi(2)
|
|
|
|
|| (dist_sqrd < SIGHT_DIST.powi(2) && !*been_close)
|
|
|
|
{
|
|
|
|
if dist_sqrd < MAX_CHASE_DIST.powi(2) {
|
|
|
|
*been_close = true;
|
|
|
|
}
|
|
|
|
if let Some((bearing, speed)) = chaser.chase(
|
|
|
|
&*terrain,
|
|
|
|
pos.0,
|
|
|
|
vel.0,
|
|
|
|
tgt_pos.0,
|
|
|
|
TraversalConfig {
|
|
|
|
min_tgt_dist: 1.25,
|
|
|
|
..traversal_config
|
|
|
|
},
|
|
|
|
) {
|
2020-11-13 06:13:37 +00:00
|
|
|
inputs.move_dir = bearing
|
|
|
|
.xy()
|
|
|
|
.try_normalized()
|
|
|
|
.unwrap_or(Vec2::zero())
|
|
|
|
* speed;
|
2020-11-23 19:27:18 +00:00
|
|
|
inputs.jump.set_state(bearing.z > 1.5);
|
|
|
|
inputs.move_z = bearing.z;
|
2020-11-13 06:13:37 +00:00
|
|
|
}
|
2020-11-23 19:27:18 +00:00
|
|
|
} else {
|
2020-11-25 22:47:16 +00:00
|
|
|
do_idle = true;
|
2020-11-23 19:27:18 +00:00
|
|
|
}
|
2020-11-25 22:47:16 +00:00
|
|
|
},
|
|
|
|
Tactic::Theropod => {
|
|
|
|
if dist_sqrd < (2.0 * MIN_ATTACK_DIST * scale).powi(2) {
|
|
|
|
inputs.move_dir = Vec2::zero();
|
2020-11-23 19:27:18 +00:00
|
|
|
inputs.primary.set_state(true);
|
2020-11-25 22:47:16 +00:00
|
|
|
} else if dist_sqrd < MAX_CHASE_DIST.powi(2)
|
|
|
|
|| (dist_sqrd < SIGHT_DIST.powi(2) && !*been_close)
|
|
|
|
{
|
|
|
|
if dist_sqrd < MAX_CHASE_DIST.powi(2) {
|
|
|
|
*been_close = true;
|
|
|
|
}
|
|
|
|
if let Some((bearing, speed)) = chaser.chase(
|
|
|
|
&*terrain,
|
|
|
|
pos.0,
|
|
|
|
vel.0,
|
|
|
|
tgt_pos.0,
|
|
|
|
TraversalConfig {
|
|
|
|
min_tgt_dist: 1.25,
|
|
|
|
..traversal_config
|
|
|
|
},
|
|
|
|
) {
|
|
|
|
inputs.move_dir = bearing
|
|
|
|
.xy()
|
|
|
|
.try_normalized()
|
|
|
|
.unwrap_or(Vec2::zero())
|
|
|
|
* speed;
|
|
|
|
inputs.jump.set_state(bearing.z > 1.5);
|
|
|
|
inputs.move_z = bearing.z;
|
|
|
|
}
|
2020-11-23 19:27:18 +00:00
|
|
|
} else {
|
2020-11-25 22:47:16 +00:00
|
|
|
do_idle = true;
|
2020-11-23 19:27:18 +00:00
|
|
|
}
|
2020-11-25 22:47:16 +00:00
|
|
|
},
|
|
|
|
}
|
2020-01-27 16:18:36 +00:00
|
|
|
}
|
2020-11-25 22:47:16 +00:00
|
|
|
} else {
|
|
|
|
do_idle = true;
|
2020-01-25 18:49:47 +00:00
|
|
|
}
|
2020-11-25 22:47:16 +00:00
|
|
|
},
|
|
|
|
}
|
2020-01-25 18:49:47 +00:00
|
|
|
}
|
2020-01-26 00:06:03 +00:00
|
|
|
|
2020-11-25 22:47:16 +00:00
|
|
|
if do_idle {
|
|
|
|
agent.activity = Activity::Idle {
|
|
|
|
bearing: Vec2::zero(),
|
2020-01-27 15:51:07 +00:00
|
|
|
chaser: Chaser::default(),
|
|
|
|
};
|
2020-01-26 00:06:03 +00:00
|
|
|
}
|
|
|
|
|
2020-11-25 22:47:16 +00:00
|
|
|
// Choose a new target to attack: only go out of our way to attack targets we
|
|
|
|
// are hostile toward!
|
|
|
|
if choose_target {
|
|
|
|
// Search for new targets (this looks expensive, but it's only run occasionally)
|
|
|
|
// TODO: Replace this with a better system that doesn't consider *all* entities
|
|
|
|
let closest_entity = (&entities, &positions, &healths, alignments.maybe(), 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;
|
|
|
|
}
|
|
|
|
((e_pos.0.distance_squared(pos.0) < search_dist.powi(2) &&
|
|
|
|
// Within our view
|
|
|
|
(e_pos.0 - pos.0).try_normalized().map(|v| v.dot(*inputs.look_dir) > 0.15).unwrap_or(true))
|
|
|
|
// Within listen distance
|
|
|
|
|| e_pos.0.distance_squared(pos.0) < listen_dist.powi(2))
|
|
|
|
&& *e != entity
|
|
|
|
&& !e_health.is_dead
|
|
|
|
&& alignment
|
|
|
|
.and_then(|a| e_alignment.map(|b| a.hostile_towards(*b)))
|
|
|
|
.unwrap_or(false)
|
|
|
|
})
|
|
|
|
// Can we even see them?
|
|
|
|
.filter(|(_, e_pos, _, _, _)| terrain
|
|
|
|
.ray(pos.0 + Vec3::unit_z(), e_pos.0 + Vec3::unit_z())
|
|
|
|
.until(Block::is_opaque)
|
|
|
|
.cast()
|
|
|
|
.0 >= e_pos.0.distance(pos.0))
|
|
|
|
.min_by_key(|(_, e_pos, _, _, _)| (e_pos.0.distance_squared(pos.0) * 100.0) as i32)
|
|
|
|
.map(|(e, _, _, _, _)| e);
|
2020-01-25 18:49:47 +00:00
|
|
|
|
2020-11-25 22:47:16 +00:00
|
|
|
if let Some(target) = closest_entity {
|
|
|
|
agent.activity = Activity::Attack {
|
|
|
|
target,
|
|
|
|
chaser: Chaser::default(),
|
|
|
|
time: time.0,
|
|
|
|
been_close: false,
|
|
|
|
powerup: 0.0,
|
|
|
|
};
|
|
|
|
}
|
|
|
|
}
|
2020-07-30 19:26:49 +00:00
|
|
|
|
2020-11-25 22:47:16 +00:00
|
|
|
// --- Activity overrides (in reverse order of priority: most important goes
|
|
|
|
// last!) ---
|
|
|
|
|
|
|
|
// Attack a target that's attacking us
|
|
|
|
if let Some(my_health) = healths.get(entity) {
|
|
|
|
// Only if the attack was recent
|
|
|
|
if my_health.last_change.0 < 3.0 {
|
|
|
|
if let comp::HealthSource::Damage { by: Some(by), .. } =
|
|
|
|
my_health.last_change.1.cause
|
|
|
|
{
|
|
|
|
if !agent.activity.is_attack() {
|
|
|
|
if let Some(attacker) = uid_allocator.retrieve_entity_internal(by.id())
|
|
|
|
{
|
|
|
|
if healths.get(attacker).map_or(false, |a| !a.is_dead) {
|
|
|
|
match agent.activity {
|
|
|
|
Activity::Attack { target, .. } if target == attacker => {},
|
|
|
|
_ => {
|
|
|
|
if agent.can_speak {
|
|
|
|
let msg =
|
|
|
|
"npc.speech.villager_under_attack".to_string();
|
|
|
|
event_emitter.emit(ServerEvent::Chat(
|
|
|
|
UnresolvedChatMsg::npc(*uid, msg),
|
|
|
|
));
|
|
|
|
}
|
|
|
|
|
|
|
|
agent.activity = Activity::Attack {
|
|
|
|
target: attacker,
|
|
|
|
chaser: Chaser::default(),
|
|
|
|
time: time.0,
|
|
|
|
been_close: false,
|
|
|
|
powerup: 0.0,
|
|
|
|
};
|
|
|
|
},
|
|
|
|
}
|
2020-05-26 02:45:13 +00:00
|
|
|
}
|
|
|
|
}
|
2020-01-25 18:49:47 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-11-25 22:47:16 +00:00
|
|
|
// Follow owner if we're too far, or if they're under attack
|
|
|
|
if let Some(Alignment::Owned(owner)) = alignment {
|
|
|
|
(|| {
|
|
|
|
let owner = uid_allocator.retrieve_entity_internal(owner.id())?;
|
2020-07-07 00:01:39 +00:00
|
|
|
|
2020-11-25 22:47:16 +00:00
|
|
|
let owner_pos = positions.get(owner)?;
|
|
|
|
let dist_sqrd = pos.0.distance_squared(owner_pos.0);
|
|
|
|
if dist_sqrd > MAX_FOLLOW_DIST.powi(2) && !agent.activity.is_follow() {
|
|
|
|
agent.activity = Activity::Follow {
|
|
|
|
target: owner,
|
|
|
|
chaser: Chaser::default(),
|
|
|
|
};
|
|
|
|
}
|
2020-01-25 18:49:47 +00:00
|
|
|
|
2020-11-25 22:47:16 +00:00
|
|
|
// Attack owner's attacker
|
|
|
|
let owner_health = healths.get(owner)?;
|
|
|
|
if owner_health.last_change.0 < 5.0 && owner_health.last_change.1.amount < 0 {
|
|
|
|
if let comp::HealthSource::Damage { by: Some(by), .. } =
|
|
|
|
owner_health.last_change.1.cause
|
|
|
|
{
|
|
|
|
if !agent.activity.is_attack() {
|
|
|
|
let attacker = uid_allocator.retrieve_entity_internal(by.id())?;
|
2020-07-07 00:01:39 +00:00
|
|
|
|
2020-11-25 22:47:16 +00:00
|
|
|
agent.activity = Activity::Attack {
|
|
|
|
target: attacker,
|
|
|
|
chaser: Chaser::default(),
|
|
|
|
time: time.0,
|
|
|
|
been_close: false,
|
|
|
|
powerup: 0.0,
|
|
|
|
};
|
|
|
|
}
|
2020-01-25 18:49:47 +00:00
|
|
|
}
|
|
|
|
}
|
2020-07-07 00:01:39 +00:00
|
|
|
|
2020-11-25 22:47:16 +00:00
|
|
|
Some(())
|
|
|
|
})();
|
|
|
|
}
|
2020-01-25 18:49:47 +00:00
|
|
|
|
2020-11-25 22:47:16 +00:00
|
|
|
debug_assert!(inputs.move_dir.map(|e| !e.is_nan()).reduce_and());
|
|
|
|
debug_assert!(inputs.look_dir.map(|e| !e.is_nan()).reduce_and());
|
|
|
|
});
|
2020-08-07 01:59:28 +00:00
|
|
|
|
2020-08-25 12:21:25 +00:00
|
|
|
// Process group invites
|
2020-09-03 22:46:32 +00:00
|
|
|
for (_invite, /*alignment,*/ agent, controller) in
|
|
|
|
(&invites, /*&alignments,*/ &mut agents, &mut controllers).join()
|
2020-08-07 01:59:28 +00:00
|
|
|
{
|
2020-12-15 00:43:44 +00:00
|
|
|
let accept = false; // set back to "matches!(alignment, Alignment::Npc)" when we got better NPC recruitment mechanics
|
2020-08-07 01:59:28 +00:00
|
|
|
if accept {
|
|
|
|
// Clear agent comp
|
|
|
|
*agent = Agent::default();
|
|
|
|
controller
|
|
|
|
.events
|
|
|
|
.push(ControlEvent::GroupManip(GroupManip::Accept));
|
|
|
|
} else {
|
|
|
|
controller
|
|
|
|
.events
|
|
|
|
.push(ControlEvent::GroupManip(GroupManip::Decline));
|
|
|
|
}
|
|
|
|
}
|
2020-09-14 12:56:05 +00:00
|
|
|
sys_metrics.agent_ns.store(
|
2020-12-15 23:51:07 +00:00
|
|
|
start_time.elapsed().as_nanos() as u64,
|
2020-09-14 12:56:05 +00:00
|
|
|
std::sync::atomic::Ordering::Relaxed,
|
|
|
|
);
|
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
|
|
|
|
}
|