Allow NPCs to path towards far away enemies

This commit is contained in:
James Melkonian 2021-05-21 03:14:45 +00:00 committed by Imbris
parent cf4203da54
commit 27885e00b2
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. - Squirrels are no longer immune to arrows at some angles.
- /spawn command's auto-complete now works for species names - /spawn command's auto-complete now works for species names
- Mindflayer AI now correctly summons husks at certain HP thresholds. - 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 ## [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) // 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 FLEE_DURATION: f32 = 3.0;
const MAX_FOLLOW_DIST: f32 = 12.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 MAX_FLEE_DIST: f32 = 20.0;
const SEARCH_DIST: f32 = 48.0; const SEARCH_DIST: f32 = 48.0;
const SIGHT_DIST: f32 = 80.0;
const SNEAK_COEFFICIENT: f32 = 0.25; const SNEAK_COEFFICIENT: f32 = 0.25;
const AVG_FOLLOW_DIST: f32 = 6.0; const AVG_FOLLOW_DIST: f32 = 6.0;
const RETARGETING_THRESHOLD_SECONDS: f64 = 10.0; const RETARGETING_THRESHOLD_SECONDS: f64 = 10.0;
@ -513,10 +513,10 @@ impl<'a> System<'a> for Sys {
&read_data, &read_data,
); );
// Remember this encounter if an RtSim entity // Remember this encounter if an RtSim entity
if let Some(tgt_stats) = if let Some(tgt_stats) = data
read_data.stats.get(attacker) .rtsim_entity
.and_then(|_| read_data.stats.get(attacker))
{ {
if data.rtsim_entity.is_some() {
agent.rtsim_controller.events.push( agent.rtsim_controller.events.push(
RtSimEvent::AddMemory(Memory { RtSimEvent::AddMemory(Memory {
item: MemoryItem::CharacterFight { item: MemoryItem::CharacterFight {
@ -528,7 +528,6 @@ impl<'a> System<'a> for Sys {
); );
} }
} }
}
} else { } else {
agent.target = None; agent.target = None;
data.idle_tree( data.idle_tree(
@ -698,16 +697,14 @@ impl<'a> AgentData<'a> {
&& matches!(self.alignment, Some(Alignment::Enemy)) && matches!(self.alignment, Some(Alignment::Enemy))
{ {
self.choose_target(agent, controller, &read_data, event_emitter); 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 { let target_data = TargetData {
pos: tgt_pos, pos: tgt_pos,
body: read_data.bodies.get(target), body: read_data.bodies.get(target),
scale: read_data.scales.get(target), scale: read_data.scales.get(target),
}; };
self.attack(agent, controller, &target_data, &read_data); 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( fn attack(
&self, &self,
agent: &mut Agent, agent: &mut Agent,
@ -1851,22 +1836,8 @@ impl<'a> AgentData<'a> {
.actions .actions
.push(ControlAction::basic_input(InputKind::Primary)); .push(ControlAction::basic_input(InputKind::Primary));
controller.inputs.move_dir = Vec2::zero(); controller.inputs.move_dir = Vec2::zero();
} 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( self.path_toward_target(agent, controller, tgt_data, read_data, true, None);
&*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;
}
if self.body.map(|b| b.is_humanoid()).unwrap_or(false) if self.body.map(|b| b.is_humanoid()).unwrap_or(false)
&& attack_data.dist_sqrd < 16.0f32.powi(2) && attack_data.dist_sqrd < 16.0f32.powi(2)
@ -1877,7 +1848,7 @@ impl<'a> AgentData<'a> {
.push(ControlAction::basic_input(InputKind::Roll)); .push(ControlAction::basic_input(InputKind::Roll));
} }
} else { } 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)); .push(ControlAction::basic_input(InputKind::Primary));
agent.action_state.timer += read_data.dt.0; agent.action_state.timer += read_data.dt.0;
} }
} 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( self.path_toward_target(agent, controller, tgt_data, read_data, true, None);
&*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;
}
if self.body.map(|b| b.is_humanoid()).unwrap_or(false) if self.body.map(|b| b.is_humanoid()).unwrap_or(false)
&& attack_data.dist_sqrd < 16.0f32.powi(2) && attack_data.dist_sqrd < 16.0f32.powi(2)
&& thread_rng().gen::<f32>() < 0.02 && thread_rng().gen::<f32>() < 0.02
@ -1940,7 +1897,7 @@ impl<'a> AgentData<'a> {
.push(ControlAction::basic_input(InputKind::Roll)); .push(ControlAction::basic_input(InputKind::Roll));
} }
} else { } 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)); .push(ControlAction::basic_input(InputKind::Primary));
agent.action_state.timer += read_data.dt.0; agent.action_state.timer += read_data.dt.0;
} }
} 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( if self.path_toward_target(agent, controller, tgt_data, read_data, true, None)
&*read_data.terrain, && can_see_tgt(
self.pos.0,
self.vel.0,
tgt_data.pos.0,
TraversalConfig {
min_tgt_dist: 1.25,
..self.traversal_config
},
) {
if can_see_tgt(
&*read_data.terrain, &*read_data.terrain,
self.pos, self.pos,
tgt_data.pos, tgt_data.pos,
attack_data.dist_sqrd, 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 if self
.skill_set .skill_set
.has_skill(Skill::Hammer(HammerSkill::UnlockLeap)) .has_skill(Skill::Hammer(HammerSkill::UnlockLeap))
@ -2012,12 +1959,6 @@ impl<'a> AgentData<'a> {
} else { } else {
agent.action_state.timer += read_data.dt.0; 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) if self.body.map(|b| b.is_humanoid()).unwrap_or(false)
&& attack_data.dist_sqrd < 16.0f32.powi(2) && attack_data.dist_sqrd < 16.0f32.powi(2)
@ -2028,7 +1969,7 @@ impl<'a> AgentData<'a> {
.push(ControlAction::basic_input(InputKind::Roll)); .push(ControlAction::basic_input(InputKind::Roll));
} }
} else { } 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)); .push(ControlAction::basic_input(InputKind::Primary));
agent.action_state.timer += read_data.dt.0; agent.action_state.timer += read_data.dt.0;
} }
} 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( if self.path_toward_target(agent, controller, tgt_data, read_data, true, None)
&*read_data.terrain, && can_see_tgt(
self.pos.0,
self.vel.0,
tgt_data.pos.0,
TraversalConfig {
min_tgt_dist: 1.25,
..self.traversal_config
},
) {
if can_see_tgt(
&*read_data.terrain, &*read_data.terrain,
self.pos, self.pos,
tgt_data.pos, tgt_data.pos,
attack_data.dist_sqrd, 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 { if agent.action_state.timer > 4.0 && attack_data.angle < 45.0 {
controller controller
.actions .actions
@ -2087,12 +2018,6 @@ impl<'a> AgentData<'a> {
} else { } else {
agent.action_state.timer += read_data.dt.0; 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) if self.body.map(|b| b.is_humanoid()).unwrap_or(false)
&& attack_data.dist_sqrd < 16.0f32.powi(2) && attack_data.dist_sqrd < 16.0f32.powi(2)
@ -2103,7 +2028,7 @@ impl<'a> AgentData<'a> {
.push(ControlAction::basic_input(InputKind::Roll)); .push(ControlAction::basic_input(InputKind::Roll));
} }
} else { } 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 .actions
.push(ControlAction::basic_input(InputKind::Roll)); .push(ControlAction::basic_input(InputKind::Roll));
} else { } else {
if let Some((bearing, speed)) = agent.chaser.chase( self.path_toward_target(agent, controller, tgt_data, read_data, true, None);
&*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;
}
if attack_data.angle < 15.0 { if attack_data.angle < 15.0 {
controller controller
.actions .actions
.push(ControlAction::basic_input(InputKind::Primary)); .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( if let Some((bearing, speed)) = agent.chaser.chase(
&*read_data.terrain, &*read_data.terrain,
self.pos.0, self.pos.0,
@ -2220,29 +2126,8 @@ impl<'a> AgentData<'a> {
.actions .actions
.push(ControlAction::basic_input(InputKind::Roll)); .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 { } 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 .actions
.push(ControlAction::basic_input(InputKind::Primary)); .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( if let Some((bearing, speed)) = agent.chaser.chase(
&*read_data.terrain, &*read_data.terrain,
self.pos.0, self.pos.0,
@ -2340,29 +2225,8 @@ impl<'a> AgentData<'a> {
.actions .actions
.push(ControlAction::basic_input(InputKind::Roll)); .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 { } 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 .actions
.push(ControlAction::basic_input(InputKind::Primary)); .push(ControlAction::basic_input(InputKind::Primary));
//controller.inputs.primary.set_state(true); //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() { if self.vel.0.is_approx_zero() {
controller controller
.actions .actions
.push(ControlAction::basic_input(InputKind::Ability(0))); .push(ControlAction::basic_input(InputKind::Ability(0)));
} }
if let Some((bearing, speed)) = agent.chaser.chase( if self.path_toward_target(agent, controller, tgt_data, read_data, true, None)
&*read_data.terrain, && can_see_tgt(
self.pos.0,
self.vel.0,
tgt_data.pos.0,
TraversalConfig {
min_tgt_dist: 1.25,
..self.traversal_config
},
) {
if can_see_tgt(
&*read_data.terrain, &*read_data.terrain,
self.pos, self.pos,
tgt_data.pos, tgt_data.pos,
attack_data.dist_sqrd, 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 { if agent.action_state.timer > 5.0 {
controller controller
.actions .actions
@ -2413,15 +2267,9 @@ impl<'a> AgentData<'a> {
} else { } else {
agent.action_state.timer += read_data.dt.0; 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 { } 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 { } else {
agent.action_state.timer = 0.0; agent.action_state.timer = 0.0;
} }
} 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( self.path_toward_target(agent, controller, tgt_data, read_data, true, None);
&*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 { } 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 controller
.actions .actions
.push(ControlAction::basic_input(InputKind::Primary)); .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( if let Some((bearing, speed)) = agent.chaser.chase(
&*read_data.terrain, &*read_data.terrain,
self.pos.0, self.pos.0,
@ -2601,7 +2435,7 @@ impl<'a> AgentData<'a> {
agent.target = None; agent.target = None;
} }
} else { } 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() .try_normalized()
.unwrap_or_else(Vec2::unit_y) .unwrap_or_else(Vec2::unit_y)
* 0.1; * 0.1;
} 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( self.path_toward_target(agent, controller, tgt_data, read_data, true, None);
&*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 { } 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) .rotated_z(-0.47 * PI)
.try_normalized() .try_normalized()
.unwrap_or_else(Vec2::unit_y); .unwrap_or_else(Vec2::unit_y);
} 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( self.path_toward_target(agent, controller, tgt_data, read_data, true, None);
&*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 { } 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)); .push(ControlAction::basic_input(InputKind::Primary));
agent.action_state.timer += read_data.dt.0; agent.action_state.timer += read_data.dt.0;
} }
} 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( self.path_toward_target(agent, controller, tgt_data, read_data, true, None);
&*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 { } 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 controller
.actions .actions
.push(ControlAction::basic_input(InputKind::Ability(0))); .push(ControlAction::basic_input(InputKind::Ability(0)));
} 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( if self.path_toward_target(agent, controller, tgt_data, read_data, true, None)
&*read_data.terrain, && attack_data.angle < 15.0
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
&& can_see_tgt( && can_see_tgt(
&*read_data.terrain, &*read_data.terrain,
self.pos, self.pos,
@ -2796,13 +2579,8 @@ impl<'a> AgentData<'a> {
.actions .actions
.push(ControlAction::basic_input(InputKind::Primary)); .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 { } 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 { } else {
agent.action_state.timer = 0.0; agent.action_state.timer = 0.0;
} }
} 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( self.path_toward_target(agent, controller, tgt_data, read_data, true, None);
&*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 { } 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 { } else {
agent.action_state.timer = 0.0; agent.action_state.timer = 0.0;
} }
} 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( self.path_toward_target(agent, controller, tgt_data, read_data, true, None);
&*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 { } 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, tgt_data: &TargetData,
read_data: &ReadData, read_data: &ReadData,
) { ) {
if attack_data.angle < 90.0 if attack_data.angle < 90.0 && attack_data.dist_sqrd < attack_data.min_attack_dist.powi(2) {
&& attack_data.dist_sqrd < (2.0 * attack_data.min_attack_dist).powi(2)
{
controller.inputs.move_dir = Vec2::zero(); controller.inputs.move_dir = Vec2::zero();
controller controller
.actions .actions
.push(ControlAction::basic_input(InputKind::Primary)); .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( self.path_toward_target(agent, controller, tgt_data, read_data, true, None);
&*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 { } 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.counter = 1.0 - MINION_SUMMON_THRESHOLD;
agent.action_state.condition = true; 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 { if agent.action_state.counter > health_fraction {
// Summon minions at particular thresholds of health // Summon minions at particular thresholds of health
controller controller
@ -3057,36 +2791,13 @@ impl<'a> AgentData<'a> {
{ {
agent.action_state.counter -= MINION_SUMMON_THRESHOLD; agent.action_state.counter -= MINION_SUMMON_THRESHOLD;
} }
} else if mindflayer_is_far { } else if attack_data.dist_sqrd < MINDFLAYER_ATTACK_DIST.powi(2) {
// If too far from target, throw a random number of necrotic spheres at them and if can_see_tgt(
// then blink to them. &*read_data.terrain,
let num_fireballs = &mut agent.action_state.int_counter; self.pos,
if *num_fireballs == 0 { tgt_data.pos,
controller.actions.push(ControlAction::StartInput { attack_data.dist_sqrd,
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 {
// If close to target, use either primary or secondary ability // 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)) 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 .actions
.push(ControlAction::basic_input(InputKind::Secondary)); .push(ControlAction::basic_input(InputKind::Secondary));
} }
} else {
self.path_toward_target(agent, controller, tgt_data, read_data, true, None);
} }
} else if attack_data.dist_sqrd < MAX_PATH_DIST.powi(2) {
// Move towards target // If too far from target, throw a random number of necrotic spheres at them and
if let Some((bearing, speed)) = agent.chaser.chase( // then blink to them.
&*read_data.terrain, let num_fireballs = &mut agent.action_state.int_counter;
self.pos.0, if *num_fireballs == 0 {
self.vel.0, controller.actions.push(ControlAction::StartInput {
tgt_data.pos.0, input: InputKind::Ability(0),
TraversalConfig { target_entity: agent
min_tgt_dist: 1.25, .target
..self.traversal_config .as_ref()
}, .and_then(|t| read_data.uids.get(t.target))
) { .copied(),
controller.inputs.move_dir = select_pos: None,
bearing.xy().try_normalized().unwrap_or_else(Vec2::zero) * speed; });
self.jump_if(controller, bearing.z > 1.5); if matches!(self.char_state, CharacterState::Blink(_)) {
controller.inputs.move_z = bearing.z; *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 // If further than 2.5 blocks and random chance
else if attack_data.dist_sqrd > (2.5 * attack_data.min_attack_dist).powi(2) { 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 // Walk to target
controller.inputs.move_dir = self.path_toward_target(agent, controller, tgt_data, read_data, true, None);
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 energy higher than 600 and random chance // If energy higher than 600 and random chance
else if self.energy.current() > 600 && thread_rng().gen_bool(0.4) { 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)); .push(ControlAction::basic_input(InputKind::Secondary));
} else { } else {
// Target is behind us. Turn around and chase target // Target is behind us. Turn around and chase target
if let Some((bearing, speed)) = agent.chaser.chase( self.path_toward_target(agent, controller, tgt_data, read_data, true, None);
&*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;
}
} }
} }
@ -3390,21 +3089,7 @@ impl<'a> AgentData<'a> {
.push(ControlAction::basic_input(InputKind::Jump)); .push(ControlAction::basic_input(InputKind::Jump));
controller.inputs.move_z = 1.0; controller.inputs.move_z = 1.0;
} else if attack_data.dist_sqrd > (3.0 * attack_data.min_attack_dist).powi(2) { } else if attack_data.dist_sqrd > (3.0 * attack_data.min_attack_dist).powi(2) {
if let Some((bearing, speed)) = agent.chaser.chase( self.path_toward_target(agent, controller, tgt_data, read_data, true, None);
&*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 self.energy.current() > 600 } else if self.energy.current() > 600
&& agent.action_state.timer < 3.0 && agent.action_state.timer < 3.0
&& attack_data.angle < 15.0 && attack_data.angle < 15.0
@ -3414,21 +3099,7 @@ impl<'a> AgentData<'a> {
.actions .actions
.push(ControlAction::basic_input(InputKind::Ability(0))); .push(ControlAction::basic_input(InputKind::Ability(0)));
// Move towards the target slowly // Move towards the target slowly
if let Some((bearing, speed)) = agent.chaser.chase( self.path_toward_target(agent, controller, tgt_data, read_data, true, Some(0.5));
&*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;
}
agent.action_state.timer += read_data.dt.0; agent.action_state.timer += read_data.dt.0;
} else if agent.action_state.timer < 6.0 } else if agent.action_state.timer < 6.0
&& attack_data.angle < 90.0 && attack_data.angle < 90.0
@ -3443,22 +3114,7 @@ impl<'a> AgentData<'a> {
// Reset timer // Reset timer
agent.action_state.timer = 0.0; agent.action_state.timer = 0.0;
// Target is behind us or the timer needs to be reset. Chase target // Target is behind us or the timer needs to be reset. Chase target
if let Some((bearing, speed)) = agent.chaser.chase( self.path_toward_target(agent, controller, tgt_data, read_data, true, None);
&*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;
}
} }
} }
@ -3526,19 +3182,7 @@ impl<'a> AgentData<'a> {
} }
} }
// Make minotaur move towards target // Make minotaur move towards target
if let Some((bearing, speed)) = agent.chaser.chase( self.path_toward_target(agent, controller, tgt_data, read_data, true, None);
&*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;
}
} }
fn handle_clay_golem_attack( fn handle_clay_golem_attack(
@ -3621,19 +3265,7 @@ impl<'a> AgentData<'a> {
} }
} }
// Make clay golem move towards target // Make clay golem move towards target
if let Some((bearing, speed)) = agent.chaser.chase( self.path_toward_target(agent, controller, tgt_data, read_data, true, None);
&*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;
}
} }
fn follow( 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 { fn can_see_tgt(terrain: &TerrainGrid, pos: &Pos, tgt_pos: &Pos, dist_sqrd: f32) -> bool {