Merge branch 'james/aggro-radius' into 'master'

Allow NPCs to path towards far away enemies

See merge request veloren/veloren!2323
This commit is contained in:
Imbris 2021-05-21 03:14:45 +00:00
commit 88ba97b35a
2 changed files with 213 additions and 523 deletions

View File

@ -134,6 +134,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Squirrels are no longer immune to arrows at some angles.
- /spawn command's auto-complete now works for species names
- Mindflayer AI now correctly summons husks at certain HP thresholds.
- Far away NPCs respond to being damaged by a projectile
## [0.9.0] - 2021-03-20

View File

@ -147,13 +147,13 @@ pub struct ReadData<'a> {
}
// This is 3.1 to last longer than the last damage timer (3.0 seconds)
const DAMAGE_MEMORY_DURATION: f64 = 0.1;
const DAMAGE_MEMORY_DURATION: f64 = 3.1;
const FLEE_DURATION: f32 = 3.0;
const MAX_FOLLOW_DIST: f32 = 12.0;
const MAX_CHASE_DIST: f32 = 250.0;
const MAX_PATH_DIST: f32 = 170.0;
const PARTIAL_PATH_DIST: f32 = 50.0;
const MAX_FLEE_DIST: f32 = 20.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;
const RETARGETING_THRESHOLD_SECONDS: f64 = 10.0;
@ -513,10 +513,10 @@ impl<'a> System<'a> for Sys {
&read_data,
);
// Remember this encounter if an RtSim entity
if let Some(tgt_stats) =
read_data.stats.get(attacker)
if let Some(tgt_stats) = data
.rtsim_entity
.and_then(|_| read_data.stats.get(attacker))
{
if data.rtsim_entity.is_some() {
agent.rtsim_controller.events.push(
RtSimEvent::AddMemory(Memory {
item: MemoryItem::CharacterFight {
@ -528,7 +528,6 @@ impl<'a> System<'a> for Sys {
);
}
}
}
} else {
agent.target = None;
data.idle_tree(
@ -698,16 +697,14 @@ impl<'a> AgentData<'a> {
&& matches!(self.alignment, Some(Alignment::Enemy))
{
self.choose_target(agent, controller, &read_data, event_emitter);
} else if dist_sqrd < SIGHT_DIST.powi(2) {
} else {
// TODO Add utility for attacking vs leaving target alone
let target_data = TargetData {
pos: tgt_pos,
body: read_data.bodies.get(target),
scale: read_data.scales.get(target),
};
self.attack(agent, controller, &target_data, &read_data);
} else {
agent.target = None;
self.idle(agent, controller, &read_data);
}
}
}
@ -1538,18 +1535,6 @@ impl<'a> AgentData<'a> {
});
}
fn jump_if(&self, controller: &mut Controller, condition: bool) {
if condition {
controller
.actions
.push(ControlAction::basic_input(InputKind::Jump));
} else {
controller
.actions
.push(ControlAction::CancelInput(InputKind::Jump))
}
}
fn attack(
&self,
agent: &mut Agent,
@ -1851,22 +1836,8 @@ impl<'a> AgentData<'a> {
.actions
.push(ControlAction::basic_input(InputKind::Primary));
controller.inputs.move_dir = Vec2::zero();
} else if attack_data.dist_sqrd < MAX_CHASE_DIST.powi(2) {
if let Some((bearing, speed)) = agent.chaser.chase(
&*read_data.terrain,
self.pos.0,
self.vel.0,
tgt_data.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;
self.jump_if(controller, bearing.z > 1.5);
controller.inputs.move_z = bearing.z;
}
} else if attack_data.dist_sqrd < MAX_PATH_DIST.powi(2) {
self.path_toward_target(agent, controller, tgt_data, read_data, true, None);
if self.body.map(|b| b.is_humanoid()).unwrap_or(false)
&& attack_data.dist_sqrd < 16.0f32.powi(2)
@ -1877,7 +1848,7 @@ impl<'a> AgentData<'a> {
.push(ControlAction::basic_input(InputKind::Roll));
}
} else {
agent.target = None;
self.path_toward_target(agent, controller, tgt_data, read_data, false, None);
}
}
@ -1915,22 +1886,8 @@ impl<'a> AgentData<'a> {
.push(ControlAction::basic_input(InputKind::Primary));
agent.action_state.timer += read_data.dt.0;
}
} else if attack_data.dist_sqrd < MAX_CHASE_DIST.powi(2) {
if let Some((bearing, speed)) = agent.chaser.chase(
&*read_data.terrain,
self.pos.0,
self.vel.0,
tgt_data.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;
self.jump_if(controller, bearing.z > 1.5);
controller.inputs.move_z = bearing.z;
}
} else if attack_data.dist_sqrd < MAX_PATH_DIST.powi(2) {
self.path_toward_target(agent, controller, tgt_data, read_data, true, None);
if self.body.map(|b| b.is_humanoid()).unwrap_or(false)
&& attack_data.dist_sqrd < 16.0f32.powi(2)
&& thread_rng().gen::<f32>() < 0.02
@ -1940,7 +1897,7 @@ impl<'a> AgentData<'a> {
.push(ControlAction::basic_input(InputKind::Roll));
}
} else {
agent.target = None;
self.path_toward_target(agent, controller, tgt_data, read_data, false, None);
}
}
@ -1980,26 +1937,16 @@ impl<'a> AgentData<'a> {
.push(ControlAction::basic_input(InputKind::Primary));
agent.action_state.timer += read_data.dt.0;
}
} else if attack_data.dist_sqrd < MAX_CHASE_DIST.powi(2) {
if let Some((bearing, speed)) = agent.chaser.chase(
&*read_data.terrain,
self.pos.0,
self.vel.0,
tgt_data.pos.0,
TraversalConfig {
min_tgt_dist: 1.25,
..self.traversal_config
},
) {
if can_see_tgt(
} else if attack_data.dist_sqrd < MAX_PATH_DIST.powi(2) {
if self.path_toward_target(agent, controller, tgt_data, read_data, true, None)
&& can_see_tgt(
&*read_data.terrain,
self.pos,
tgt_data.pos,
attack_data.dist_sqrd,
) && attack_data.angle < 45.0
)
&& attack_data.angle < 45.0
{
controller.inputs.move_dir =
bearing.xy().try_normalized().unwrap_or_else(Vec2::zero) * speed;
if self
.skill_set
.has_skill(Skill::Hammer(HammerSkill::UnlockLeap))
@ -2012,12 +1959,6 @@ impl<'a> AgentData<'a> {
} else {
agent.action_state.timer += read_data.dt.0;
}
} else {
controller.inputs.move_dir =
bearing.xy().try_normalized().unwrap_or_else(Vec2::zero) * speed;
self.jump_if(controller, bearing.z > 1.5);
controller.inputs.move_z = bearing.z;
}
}
if self.body.map(|b| b.is_humanoid()).unwrap_or(false)
&& attack_data.dist_sqrd < 16.0f32.powi(2)
@ -2028,7 +1969,7 @@ impl<'a> AgentData<'a> {
.push(ControlAction::basic_input(InputKind::Roll));
}
} else {
agent.target = None;
self.path_toward_target(agent, controller, tgt_data, read_data, false, None);
}
}
@ -2060,25 +2001,15 @@ impl<'a> AgentData<'a> {
.push(ControlAction::basic_input(InputKind::Primary));
agent.action_state.timer += read_data.dt.0;
}
} else if attack_data.dist_sqrd < MAX_CHASE_DIST.powi(2) {
if let Some((bearing, speed)) = agent.chaser.chase(
&*read_data.terrain,
self.pos.0,
self.vel.0,
tgt_data.pos.0,
TraversalConfig {
min_tgt_dist: 1.25,
..self.traversal_config
},
) {
if can_see_tgt(
} else if attack_data.dist_sqrd < MAX_PATH_DIST.powi(2) {
if self.path_toward_target(agent, controller, tgt_data, read_data, true, None)
&& can_see_tgt(
&*read_data.terrain,
self.pos,
tgt_data.pos,
attack_data.dist_sqrd,
) {
controller.inputs.move_dir =
bearing.xy().try_normalized().unwrap_or_else(Vec2::zero) * speed;
)
{
if agent.action_state.timer > 4.0 && attack_data.angle < 45.0 {
controller
.actions
@ -2087,12 +2018,6 @@ impl<'a> AgentData<'a> {
} else {
agent.action_state.timer += read_data.dt.0;
}
} else {
controller.inputs.move_dir =
bearing.xy().try_normalized().unwrap_or_else(Vec2::zero) * speed;
self.jump_if(controller, bearing.z > 1.5);
controller.inputs.move_z = bearing.z;
}
}
if self.body.map(|b| b.is_humanoid()).unwrap_or(false)
&& attack_data.dist_sqrd < 16.0f32.powi(2)
@ -2103,7 +2028,7 @@ impl<'a> AgentData<'a> {
.push(ControlAction::basic_input(InputKind::Roll));
}
} else {
agent.target = None;
self.path_toward_target(agent, controller, tgt_data, read_data, false, None);
}
}
@ -2123,33 +2048,14 @@ impl<'a> AgentData<'a> {
.actions
.push(ControlAction::basic_input(InputKind::Roll));
} else {
if let Some((bearing, speed)) = agent.chaser.chase(
&*read_data.terrain,
self.pos.0,
self.vel.0,
// Away from the target (ironically)
self.pos.0
+ (self.pos.0 - tgt_data.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;
self.jump_if(controller, bearing.z > 1.5);
controller.inputs.move_z = bearing.z;
}
self.path_toward_target(agent, controller, tgt_data, read_data, true, None);
if attack_data.angle < 15.0 {
controller
.actions
.push(ControlAction::basic_input(InputKind::Primary));
}
}
} else if attack_data.dist_sqrd < MAX_CHASE_DIST.powi(2) {
} else if attack_data.dist_sqrd < MAX_PATH_DIST.powi(2) {
if let Some((bearing, speed)) = agent.chaser.chase(
&*read_data.terrain,
self.pos.0,
@ -2220,29 +2126,8 @@ impl<'a> AgentData<'a> {
.actions
.push(ControlAction::basic_input(InputKind::Roll));
}
} else if can_see_tgt(
&*read_data.terrain,
self.pos,
tgt_data.pos,
attack_data.dist_sqrd,
) {
if let Some((bearing, speed)) = agent.chaser.chase(
&*read_data.terrain,
self.pos.0,
self.vel.0,
tgt_data.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;
self.jump_if(controller, bearing.z > 1.5);
controller.inputs.move_z = bearing.z;
}
} else {
agent.target = None;
self.path_toward_target(agent, controller, tgt_data, read_data, false, None);
}
}
@ -2298,7 +2183,7 @@ impl<'a> AgentData<'a> {
.actions
.push(ControlAction::basic_input(InputKind::Primary));
}
} else if attack_data.dist_sqrd < MAX_CHASE_DIST.powi(2) {
} else if attack_data.dist_sqrd < MAX_PATH_DIST.powi(2) {
if let Some((bearing, speed)) = agent.chaser.chase(
&*read_data.terrain,
self.pos.0,
@ -2340,29 +2225,8 @@ impl<'a> AgentData<'a> {
.actions
.push(ControlAction::basic_input(InputKind::Roll));
}
} else if can_see_tgt(
&*read_data.terrain,
self.pos,
tgt_data.pos,
attack_data.dist_sqrd,
) {
if let Some((bearing, speed)) = agent.chaser.chase(
&*read_data.terrain,
self.pos.0,
self.vel.0,
tgt_data.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;
self.jump_if(controller, bearing.z > 1.5);
controller.inputs.move_z = bearing.z;
}
} else {
agent.target = None;
self.path_toward_target(agent, controller, tgt_data, read_data, false, None);
}
}
@ -2380,31 +2244,21 @@ impl<'a> AgentData<'a> {
.actions
.push(ControlAction::basic_input(InputKind::Primary));
//controller.inputs.primary.set_state(true);
} else if attack_data.dist_sqrd < MAX_CHASE_DIST.powi(2) {
} else if attack_data.dist_sqrd < MAX_PATH_DIST.powi(2) {
if self.vel.0.is_approx_zero() {
controller
.actions
.push(ControlAction::basic_input(InputKind::Ability(0)));
}
if let Some((bearing, speed)) = agent.chaser.chase(
&*read_data.terrain,
self.pos.0,
self.vel.0,
tgt_data.pos.0,
TraversalConfig {
min_tgt_dist: 1.25,
..self.traversal_config
},
) {
if can_see_tgt(
if self.path_toward_target(agent, controller, tgt_data, read_data, true, None)
&& can_see_tgt(
&*read_data.terrain,
self.pos,
tgt_data.pos,
attack_data.dist_sqrd,
) && attack_data.angle < 90.0
)
&& attack_data.angle < 90.0
{
controller.inputs.move_dir =
bearing.xy().try_normalized().unwrap_or_else(Vec2::zero) * speed;
if agent.action_state.timer > 5.0 {
controller
.actions
@ -2413,15 +2267,9 @@ impl<'a> AgentData<'a> {
} else {
agent.action_state.timer += read_data.dt.0;
}
} else {
controller.inputs.move_dir =
bearing.xy().try_normalized().unwrap_or_else(Vec2::zero) * speed;
self.jump_if(controller, bearing.z > 1.5);
controller.inputs.move_z = bearing.z;
}
}
} else {
agent.target = None;
self.path_toward_target(agent, controller, tgt_data, read_data, false, None);
}
}
@ -2509,24 +2357,10 @@ impl<'a> AgentData<'a> {
} else {
agent.action_state.timer = 0.0;
}
} else if attack_data.dist_sqrd < MAX_CHASE_DIST.powi(2) {
if let Some((bearing, speed)) = agent.chaser.chase(
&*read_data.terrain,
self.pos.0,
self.vel.0,
tgt_data.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;
self.jump_if(controller, bearing.z > 1.5);
controller.inputs.move_z = bearing.z;
}
} else if attack_data.dist_sqrd < MAX_PATH_DIST.powi(2) {
self.path_toward_target(agent, controller, tgt_data, read_data, true, None);
} else {
agent.target = None;
self.path_toward_target(agent, controller, tgt_data, read_data, false, None);
}
}
@ -2548,7 +2382,7 @@ impl<'a> AgentData<'a> {
controller
.actions
.push(ControlAction::basic_input(InputKind::Primary));
} else if attack_data.dist_sqrd < MAX_CHASE_DIST.powi(2) {
} else if attack_data.dist_sqrd < MAX_PATH_DIST.powi(2) {
if let Some((bearing, speed)) = agent.chaser.chase(
&*read_data.terrain,
self.pos.0,
@ -2601,7 +2435,7 @@ impl<'a> AgentData<'a> {
agent.target = None;
}
} else {
agent.target = None;
self.path_toward_target(agent, controller, tgt_data, read_data, false, None);
}
}
@ -2637,24 +2471,10 @@ impl<'a> AgentData<'a> {
.try_normalized()
.unwrap_or_else(Vec2::unit_y)
* 0.1;
} else if attack_data.dist_sqrd < MAX_CHASE_DIST.powi(2) {
if let Some((bearing, speed)) = agent.chaser.chase(
&*read_data.terrain,
self.pos.0,
self.vel.0,
tgt_data.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;
self.jump_if(controller, bearing.z > 1.5);
controller.inputs.move_z = bearing.z;
}
} else if attack_data.dist_sqrd < MAX_PATH_DIST.powi(2) {
self.path_toward_target(agent, controller, tgt_data, read_data, true, None);
} else {
agent.target = None;
self.path_toward_target(agent, controller, tgt_data, read_data, false, None);
}
}
@ -2685,24 +2505,10 @@ impl<'a> AgentData<'a> {
.rotated_z(-0.47 * PI)
.try_normalized()
.unwrap_or_else(Vec2::unit_y);
} else if attack_data.dist_sqrd < MAX_CHASE_DIST.powi(2) {
if let Some((bearing, speed)) = agent.chaser.chase(
&*read_data.terrain,
self.pos.0,
self.vel.0,
tgt_data.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;
self.jump_if(controller, bearing.z > 1.5);
controller.inputs.move_z = bearing.z;
}
} else if attack_data.dist_sqrd < MAX_PATH_DIST.powi(2) {
self.path_toward_target(agent, controller, tgt_data, read_data, true, None);
} else {
agent.target = None;
self.path_toward_target(agent, controller, tgt_data, read_data, false, None);
}
}
@ -2731,24 +2537,10 @@ impl<'a> AgentData<'a> {
.push(ControlAction::basic_input(InputKind::Primary));
agent.action_state.timer += read_data.dt.0;
}
} else if attack_data.dist_sqrd < MAX_CHASE_DIST.powi(2) {
if let Some((bearing, speed)) = agent.chaser.chase(
&*read_data.terrain,
self.pos.0,
self.vel.0,
tgt_data.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;
self.jump_if(controller, bearing.z > 1.5);
controller.inputs.move_z = bearing.z;
}
} else if attack_data.dist_sqrd < MAX_PATH_DIST.powi(2) {
self.path_toward_target(agent, controller, tgt_data, read_data, true, None);
} else {
agent.target = None;
self.path_toward_target(agent, controller, tgt_data, read_data, false, None);
}
}
@ -2773,18 +2565,9 @@ impl<'a> AgentData<'a> {
controller
.actions
.push(ControlAction::basic_input(InputKind::Ability(0)));
} else if attack_data.dist_sqrd < MAX_CHASE_DIST.powi(2) {
if let Some((bearing, speed)) = agent.chaser.chase(
&*read_data.terrain,
self.pos.0,
self.vel.0,
tgt_data.pos.0,
TraversalConfig {
min_tgt_dist: 1.25,
..self.traversal_config
},
) {
if attack_data.angle < 15.0
} else if attack_data.dist_sqrd < MAX_PATH_DIST.powi(2) {
if self.path_toward_target(agent, controller, tgt_data, read_data, true, None)
&& attack_data.angle < 15.0
&& can_see_tgt(
&*read_data.terrain,
self.pos,
@ -2796,13 +2579,8 @@ impl<'a> AgentData<'a> {
.actions
.push(ControlAction::basic_input(InputKind::Primary));
}
controller.inputs.move_dir =
bearing.xy().try_normalized().unwrap_or_else(Vec2::zero) * speed;
self.jump_if(controller, bearing.z > 1.5);
controller.inputs.move_z = bearing.z;
}
} else {
agent.target = None;
self.path_toward_target(agent, controller, tgt_data, read_data, false, None);
}
}
@ -2829,24 +2607,10 @@ impl<'a> AgentData<'a> {
} else {
agent.action_state.timer = 0.0;
}
} else if attack_data.dist_sqrd < MAX_CHASE_DIST.powi(2) {
if let Some((bearing, speed)) = agent.chaser.chase(
&*read_data.terrain,
self.pos.0,
self.vel.0,
tgt_data.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;
self.jump_if(controller, bearing.z > 1.5);
controller.inputs.move_z = bearing.z;
}
} else if attack_data.dist_sqrd < MAX_PATH_DIST.powi(2) {
self.path_toward_target(agent, controller, tgt_data, read_data, true, None);
} else {
agent.target = None;
self.path_toward_target(agent, controller, tgt_data, read_data, false, None);
}
}
@ -2896,24 +2660,10 @@ impl<'a> AgentData<'a> {
} else {
agent.action_state.timer = 0.0;
}
} else if attack_data.dist_sqrd < MAX_CHASE_DIST.powi(2) {
if let Some((bearing, speed)) = agent.chaser.chase(
&*read_data.terrain,
self.pos.0,
self.vel.0,
tgt_data.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;
self.jump_if(controller, bearing.z > 1.5);
controller.inputs.move_z = bearing.z;
}
} else if attack_data.dist_sqrd < MAX_PATH_DIST.powi(2) {
self.path_toward_target(agent, controller, tgt_data, read_data, true, None);
} else {
agent.target = None;
self.path_toward_target(agent, controller, tgt_data, read_data, false, None);
}
}
@ -2925,31 +2675,15 @@ impl<'a> AgentData<'a> {
tgt_data: &TargetData,
read_data: &ReadData,
) {
if attack_data.angle < 90.0
&& attack_data.dist_sqrd < (2.0 * attack_data.min_attack_dist).powi(2)
{
if attack_data.angle < 90.0 && attack_data.dist_sqrd < attack_data.min_attack_dist.powi(2) {
controller.inputs.move_dir = Vec2::zero();
controller
.actions
.push(ControlAction::basic_input(InputKind::Primary));
} else if attack_data.dist_sqrd < MAX_CHASE_DIST.powi(2) {
if let Some((bearing, speed)) = agent.chaser.chase(
&*read_data.terrain,
self.pos.0,
self.vel.0,
tgt_data.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;
self.jump_if(controller, bearing.z > 1.5);
controller.inputs.move_z = bearing.z;
}
} else if attack_data.dist_sqrd < MAX_PATH_DIST.powi(2) {
self.path_toward_target(agent, controller, tgt_data, read_data, true, None);
} else {
agent.target = None;
self.path_toward_target(agent, controller, tgt_data, read_data, false, None);
}
}
@ -3046,7 +2780,7 @@ impl<'a> AgentData<'a> {
agent.action_state.counter = 1.0 - MINION_SUMMON_THRESHOLD;
agent.action_state.condition = true;
}
let mindflayer_is_far = attack_data.dist_sqrd > MINDFLAYER_ATTACK_DIST.powi(2);
if agent.action_state.counter > health_fraction {
// Summon minions at particular thresholds of health
controller
@ -3057,36 +2791,13 @@ impl<'a> AgentData<'a> {
{
agent.action_state.counter -= MINION_SUMMON_THRESHOLD;
}
} else if mindflayer_is_far {
// If too far from target, throw a random number of necrotic spheres at them and
// then blink to them.
let num_fireballs = &mut agent.action_state.int_counter;
if *num_fireballs == 0 {
controller.actions.push(ControlAction::StartInput {
input: InputKind::Ability(0),
target_entity: agent
.target
.as_ref()
.and_then(|t| read_data.uids.get(t.target))
.copied(),
select_pos: None,
});
if matches!(self.char_state, CharacterState::Blink(_)) {
*num_fireballs = rand::random::<u8>() % 4;
}
} else if matches!(self.char_state, CharacterState::Wielding) {
*num_fireballs -= 1;
controller.actions.push(ControlAction::StartInput {
input: InputKind::Ability(1),
target_entity: agent
.target
.as_ref()
.and_then(|t| read_data.uids.get(t.target))
.copied(),
select_pos: None,
});
}
} else {
} else if attack_data.dist_sqrd < MINDFLAYER_ATTACK_DIST.powi(2) {
if can_see_tgt(
&*read_data.terrain,
self.pos,
tgt_data.pos,
attack_data.dist_sqrd,
) {
// If close to target, use either primary or secondary ability
if matches!(self.char_state, CharacterState::BasicBeam(c) if c.timer < Duration::from_secs(10) && !matches!(c.stage_section, StageSection::Recover))
{
@ -3112,23 +2823,41 @@ impl<'a> AgentData<'a> {
.actions
.push(ControlAction::basic_input(InputKind::Secondary));
}
} else {
self.path_toward_target(agent, controller, tgt_data, read_data, true, None);
}
// Move towards target
if let Some((bearing, speed)) = agent.chaser.chase(
&*read_data.terrain,
self.pos.0,
self.vel.0,
tgt_data.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;
self.jump_if(controller, bearing.z > 1.5);
controller.inputs.move_z = bearing.z;
} else if attack_data.dist_sqrd < MAX_PATH_DIST.powi(2) {
// If too far from target, throw a random number of necrotic spheres at them and
// then blink to them.
let num_fireballs = &mut agent.action_state.int_counter;
if *num_fireballs == 0 {
controller.actions.push(ControlAction::StartInput {
input: InputKind::Ability(0),
target_entity: agent
.target
.as_ref()
.and_then(|t| read_data.uids.get(t.target))
.copied(),
select_pos: None,
});
if matches!(self.char_state, CharacterState::Blink(_)) {
*num_fireballs = rand::random::<u8>() % 4;
}
} else {
*num_fireballs -= 1;
controller.actions.push(ControlAction::StartInput {
input: InputKind::Ability(1),
target_entity: agent
.target
.as_ref()
.and_then(|t| read_data.uids.get(t.target))
.copied(),
select_pos: None,
});
}
self.path_toward_target(agent, controller, tgt_data, read_data, true, None);
} else {
self.path_toward_target(agent, controller, tgt_data, read_data, false, None);
}
}
@ -3244,23 +2973,8 @@ impl<'a> AgentData<'a> {
}
// If further than 2.5 blocks and random chance
else if attack_data.dist_sqrd > (2.5 * attack_data.min_attack_dist).powi(2) {
// If some target
if let Some((bearing, speed)) = agent.chaser.chase(
&*read_data.terrain,
self.pos.0,
self.vel.0,
tgt_data.pos.0,
TraversalConfig {
min_tgt_dist: 1.25,
..self.traversal_config
},
) {
// Walk to target
controller.inputs.move_dir =
bearing.xy().try_normalized().unwrap_or_else(Vec2::zero) * speed;
self.jump_if(controller, bearing.z > 1.5);
controller.inputs.move_z = bearing.z;
}
self.path_toward_target(agent, controller, tgt_data, read_data, true, None);
}
// If energy higher than 600 and random chance
else if self.energy.current() > 600 && thread_rng().gen_bool(0.4) {
@ -3275,22 +2989,7 @@ impl<'a> AgentData<'a> {
.push(ControlAction::basic_input(InputKind::Secondary));
} else {
// Target is behind us. Turn around and chase target
if let Some((bearing, speed)) = agent.chaser.chase(
&*read_data.terrain,
self.pos.0,
self.vel.0,
tgt_data.pos.0,
TraversalConfig {
min_tgt_dist: 1.25,
..self.traversal_config
},
) {
// Walk to target
controller.inputs.move_dir =
bearing.xy().try_normalized().unwrap_or_else(Vec2::zero) * speed;
self.jump_if(controller, bearing.z > 1.5);
controller.inputs.move_z = bearing.z;
}
self.path_toward_target(agent, controller, tgt_data, read_data, true, None);
}
}
@ -3390,21 +3089,7 @@ impl<'a> AgentData<'a> {
.push(ControlAction::basic_input(InputKind::Jump));
controller.inputs.move_z = 1.0;
} else if attack_data.dist_sqrd > (3.0 * attack_data.min_attack_dist).powi(2) {
if let Some((bearing, speed)) = agent.chaser.chase(
&*read_data.terrain,
self.pos.0,
self.vel.0,
tgt_data.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;
self.jump_if(controller, bearing.z > 1.5);
controller.inputs.move_z = bearing.z;
}
self.path_toward_target(agent, controller, tgt_data, read_data, true, None);
} else if self.energy.current() > 600
&& agent.action_state.timer < 3.0
&& attack_data.angle < 15.0
@ -3414,21 +3099,7 @@ impl<'a> AgentData<'a> {
.actions
.push(ControlAction::basic_input(InputKind::Ability(0)));
// Move towards the target slowly
if let Some((bearing, speed)) = agent.chaser.chase(
&*read_data.terrain,
self.pos.0,
self.vel.0,
tgt_data.pos.0,
TraversalConfig {
min_tgt_dist: 1.25,
..self.traversal_config
},
) {
controller.inputs.move_dir =
bearing.xy().try_normalized().unwrap_or_else(Vec2::zero) * 0.5 * speed;
self.jump_if(controller, bearing.z > 1.5);
controller.inputs.move_z = bearing.z;
}
self.path_toward_target(agent, controller, tgt_data, read_data, true, Some(0.5));
agent.action_state.timer += read_data.dt.0;
} else if agent.action_state.timer < 6.0
&& attack_data.angle < 90.0
@ -3443,22 +3114,7 @@ impl<'a> AgentData<'a> {
// Reset timer
agent.action_state.timer = 0.0;
// Target is behind us or the timer needs to be reset. Chase target
if let Some((bearing, speed)) = agent.chaser.chase(
&*read_data.terrain,
self.pos.0,
self.vel.0,
tgt_data.pos.0,
TraversalConfig {
min_tgt_dist: 1.25,
..self.traversal_config
},
) {
// Walk to target
controller.inputs.move_dir =
bearing.xy().try_normalized().unwrap_or_else(Vec2::zero) * speed;
self.jump_if(controller, bearing.z > 1.5);
controller.inputs.move_z = bearing.z;
}
self.path_toward_target(agent, controller, tgt_data, read_data, true, None);
}
}
@ -3526,19 +3182,7 @@ impl<'a> AgentData<'a> {
}
}
// Make minotaur move towards target
if let Some((bearing, speed)) = agent.chaser.chase(
&*read_data.terrain,
self.pos.0,
self.vel.0,
tgt_data.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;
}
self.path_toward_target(agent, controller, tgt_data, read_data, true, None);
}
fn handle_clay_golem_attack(
@ -3621,19 +3265,7 @@ impl<'a> AgentData<'a> {
}
}
// Make clay golem move towards target
if let Some((bearing, speed)) = agent.chaser.chase(
&*read_data.terrain,
self.pos.0,
self.vel.0,
tgt_data.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;
}
self.path_toward_target(agent, controller, tgt_data, read_data, true, None);
}
fn follow(
@ -3703,6 +3335,63 @@ impl<'a> AgentData<'a> {
}
}
}
/// Directs the entity to path and move toward the target
/// If full_path is false, the entity will path to a location 50 units along
/// the vector between the entity and the target. The speed multiplier
/// multiplies the movement speed by a value less than 1.0.
/// A `None` value implies a multiplier of 1.0.
/// Returns `false` if the pathfinding algorithm fails to return a path
fn path_toward_target(
&self,
agent: &mut Agent,
controller: &mut Controller,
tgt_data: &TargetData,
read_data: &ReadData,
full_path: bool,
speed_multiplier: Option<f32>,
) -> bool {
let pathing_pos = if full_path {
tgt_data.pos.0
} else {
self.pos.0
+ PARTIAL_PATH_DIST
* (tgt_data.pos.0 - self.pos.0)
.try_normalized()
.unwrap_or_else(Vec3::zero)
};
let speed_multiplier = speed_multiplier.unwrap_or(1.0).min(1.0);
if let Some((bearing, speed)) = agent.chaser.chase(
&*read_data.terrain,
self.pos.0,
self.vel.0,
pathing_pos,
TraversalConfig {
min_tgt_dist: 1.25,
..self.traversal_config
},
) {
controller.inputs.move_dir =
bearing.xy().try_normalized().unwrap_or_else(Vec2::zero) * speed * speed_multiplier;
self.jump_if(controller, bearing.z > 1.5);
controller.inputs.move_z = bearing.z;
true
} else {
false
}
}
fn jump_if(&self, controller: &mut Controller, condition: bool) {
if condition {
controller
.actions
.push(ControlAction::basic_input(InputKind::Jump));
} else {
controller
.actions
.push(ControlAction::CancelInput(InputKind::Jump))
}
}
}
fn can_see_tgt(terrain: &TerrainGrid, pos: &Pos, tgt_pos: &Pos, dist_sqrd: f32) -> bool {