2022-10-23 17:10:37 +00:00
|
|
|
use crate::{consts::MAX_PATH_DIST, data::*, util::entities_have_line_of_sight};
|
2022-01-18 03:02:43 +00:00
|
|
|
use common::{
|
|
|
|
comp::{
|
2022-10-12 19:53:26 +00:00
|
|
|
ability::{self, Ability, AbilityKind, ActiveAbilities, AuxiliaryAbility, Capability},
|
2022-01-18 03:02:43 +00:00
|
|
|
buff::BuffKind,
|
2022-10-22 18:36:41 +00:00
|
|
|
item::tool::AbilityContext,
|
2022-09-18 02:11:41 +00:00
|
|
|
skills::{AxeSkill, BowSkill, HammerSkill, SceptreSkill, Skill, StaffSkill, SwordSkill},
|
2022-09-14 01:58:53 +00:00
|
|
|
AbilityInput, Agent, CharacterAbility, CharacterState, ControlAction, ControlEvent,
|
|
|
|
Controller, InputKind,
|
2022-01-18 03:02:43 +00:00
|
|
|
},
|
|
|
|
path::TraversalConfig,
|
2022-04-23 14:54:01 +00:00
|
|
|
states::{self_buff, sprite_summon, utils::StageSection},
|
2022-01-18 03:02:43 +00:00
|
|
|
terrain::Block,
|
|
|
|
util::Dir,
|
|
|
|
vol::ReadVol,
|
|
|
|
};
|
2022-10-23 17:10:37 +00:00
|
|
|
use rand::{prelude::SliceRandom, Rng};
|
2022-10-09 07:17:29 +00:00
|
|
|
use specs::saveload::MarkerAllocator;
|
2022-01-18 03:02:43 +00:00
|
|
|
use std::{f32::consts::PI, time::Duration};
|
|
|
|
use vek::*;
|
|
|
|
|
|
|
|
impl<'a> AgentData<'a> {
|
2022-01-20 01:15:50 +00:00
|
|
|
// Intended for any agent that has one attack, that attack is a melee attack,
|
|
|
|
// and the agent is able to freely walk around
|
2022-01-21 23:28:15 +00:00
|
|
|
pub fn handle_simple_melee(
|
2022-01-18 03:02:43 +00:00
|
|
|
&self,
|
|
|
|
agent: &mut Agent,
|
|
|
|
controller: &mut Controller,
|
|
|
|
attack_data: &AttackData,
|
|
|
|
tgt_data: &TargetData,
|
|
|
|
read_data: &ReadData,
|
|
|
|
rng: &mut impl Rng,
|
|
|
|
) {
|
2022-01-20 01:15:50 +00:00
|
|
|
if attack_data.in_min_range() && attack_data.angle < 30.0 {
|
2022-01-26 19:09:59 +00:00
|
|
|
controller.push_basic_input(InputKind::Primary);
|
2022-01-18 03:02:43 +00:00
|
|
|
controller.inputs.move_dir = Vec2::zero();
|
2022-02-27 06:56:36 +00:00
|
|
|
} else {
|
2022-03-01 21:54:44 +00:00
|
|
|
self.path_toward_target(
|
|
|
|
agent,
|
|
|
|
controller,
|
|
|
|
tgt_data.pos.0,
|
|
|
|
read_data,
|
|
|
|
Path::Full,
|
|
|
|
None,
|
|
|
|
);
|
2022-03-05 08:53:28 +00:00
|
|
|
if self.body.map(|b| b.is_humanoid()).unwrap_or(false)
|
2022-01-18 03:02:43 +00:00
|
|
|
&& attack_data.dist_sqrd < 16.0f32.powi(2)
|
|
|
|
&& rng.gen::<f32>() < 0.02
|
|
|
|
{
|
2022-01-26 19:09:59 +00:00
|
|
|
controller.push_basic_input(InputKind::Roll);
|
2022-01-18 03:02:43 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-01-20 01:15:50 +00:00
|
|
|
// Intended for any agent that has one attack, that attack is a melee attack,
|
2022-10-23 01:18:23 +00:00
|
|
|
// and the agent is able to freely fly around
|
|
|
|
pub fn handle_simple_flying_melee(
|
|
|
|
&self,
|
2022-10-23 19:04:04 +00:00
|
|
|
_agent: &mut Agent,
|
2022-10-23 01:18:23 +00:00
|
|
|
controller: &mut Controller,
|
|
|
|
attack_data: &AttackData,
|
|
|
|
tgt_data: &TargetData,
|
|
|
|
read_data: &ReadData,
|
2022-10-23 19:04:04 +00:00
|
|
|
_rng: &mut impl Rng,
|
2022-10-23 01:18:23 +00:00
|
|
|
) {
|
|
|
|
// Fly to target
|
|
|
|
let dir_to_target = ((tgt_data.pos.0 + Vec3::unit_z() * 1.5) - self.pos.0)
|
|
|
|
.try_normalized()
|
|
|
|
.unwrap_or_else(Vec3::zero);
|
|
|
|
let speed = 1.0;
|
|
|
|
controller.inputs.move_dir = dir_to_target.xy() * speed;
|
|
|
|
|
|
|
|
// Always fly! If the floor can't touch you, it can't hurt you...
|
|
|
|
controller.push_basic_input(InputKind::Fly);
|
|
|
|
// Flee from the ground! The internet told me it was lava!
|
|
|
|
// If on the ground, jump with every last ounce of energy, holding onto all that
|
|
|
|
// is dear in life and straining for the wide open skies.
|
|
|
|
if self.physics_state.on_ground.is_some() {
|
|
|
|
controller.push_basic_input(InputKind::Jump);
|
|
|
|
} else {
|
|
|
|
// Only fly down if close enough to target in the xy plane
|
|
|
|
// Otherwise fly towards the target bouncing around a 5 block altitude
|
|
|
|
let mut maintain_altitude = |altitude| {
|
|
|
|
if read_data
|
|
|
|
.terrain
|
|
|
|
.ray(self.pos.0, self.pos.0 - (Vec3::unit_z() * altitude))
|
|
|
|
.until(Block::is_solid)
|
|
|
|
.cast()
|
|
|
|
.1
|
|
|
|
.map_or(true, |b| b.is_some())
|
|
|
|
{
|
|
|
|
// Fly up
|
|
|
|
controller.inputs.move_z = 1.0;
|
|
|
|
} else {
|
|
|
|
// Fly down
|
|
|
|
controller.inputs.move_z = -1.0;
|
|
|
|
}
|
|
|
|
};
|
|
|
|
if (tgt_data.pos.0 - self.pos.0).xy().magnitude_squared() > (5.0_f32).powi(2) {
|
|
|
|
// If above 5 blocks, fly down
|
|
|
|
maintain_altitude(5.0);
|
|
|
|
} else {
|
|
|
|
maintain_altitude(2.0);
|
|
|
|
|
|
|
|
// Attack if in range
|
|
|
|
if attack_data.dist_sqrd < 3.5_f32.powi(2) && attack_data.angle < 150.0 {
|
|
|
|
controller.push_basic_input(InputKind::Primary);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-01-20 01:15:50 +00:00
|
|
|
// Intended for any agent that has one attack, that attack is a melee attack,
|
|
|
|
// the agent is able to freely walk around, and the agent is trying to attack
|
|
|
|
// from behind its target
|
2022-01-21 23:28:15 +00:00
|
|
|
pub fn handle_simple_backstab(
|
2022-01-20 01:15:50 +00:00
|
|
|
&self,
|
|
|
|
agent: &mut Agent,
|
|
|
|
controller: &mut Controller,
|
|
|
|
attack_data: &AttackData,
|
|
|
|
tgt_data: &TargetData,
|
|
|
|
read_data: &ReadData,
|
|
|
|
) {
|
|
|
|
// Handle attacking of agent
|
|
|
|
if attack_data.in_min_range() && attack_data.angle < 30.0 {
|
|
|
|
controller.push_basic_input(InputKind::Primary);
|
|
|
|
controller.inputs.move_dir = Vec2::zero();
|
|
|
|
}
|
|
|
|
|
|
|
|
// Handle movement of agent
|
|
|
|
let target_ori = agent
|
|
|
|
.target
|
|
|
|
.and_then(|t| read_data.orientations.get(t.target))
|
|
|
|
.map(|ori| ori.look_vec())
|
|
|
|
.unwrap_or_default();
|
2022-01-21 00:56:27 +00:00
|
|
|
let dist = attack_data.dist_sqrd.sqrt();
|
|
|
|
|
2022-03-01 21:54:44 +00:00
|
|
|
let in_front_of_target = target_ori.dot(self.pos.0 - tgt_data.pos.0) > 0.0;
|
2022-01-20 01:15:50 +00:00
|
|
|
if attack_data.dist_sqrd < MAX_PATH_DIST.powi(2) {
|
|
|
|
// If in front of the target, circle to try and get behind, else just make a
|
|
|
|
// beeline for the back of the agent
|
2022-03-01 21:54:44 +00:00
|
|
|
let vec_to_target = (tgt_data.pos.0 - self.pos.0).xy();
|
2022-01-20 01:15:50 +00:00
|
|
|
if in_front_of_target {
|
2022-01-21 00:56:27 +00:00
|
|
|
let theta = (PI / 2. - dist * 0.1).max(0.0);
|
2022-01-20 01:15:50 +00:00
|
|
|
// Checks both CW and CCW rotation
|
|
|
|
let potential_move_dirs = [
|
|
|
|
vec_to_target
|
2022-01-21 00:56:27 +00:00
|
|
|
.rotated_z(theta)
|
2022-01-20 01:15:50 +00:00
|
|
|
.try_normalized()
|
|
|
|
.unwrap_or_default(),
|
|
|
|
vec_to_target
|
2022-01-21 00:56:27 +00:00
|
|
|
.rotated_z(-theta)
|
2022-01-20 01:15:50 +00:00
|
|
|
.try_normalized()
|
|
|
|
.unwrap_or_default(),
|
|
|
|
];
|
|
|
|
// Finds shortest path to get behind
|
|
|
|
if let Some(move_dir) = potential_move_dirs
|
|
|
|
.iter()
|
|
|
|
.find(|move_dir| target_ori.xy().dot(**move_dir) < 0.0)
|
|
|
|
{
|
|
|
|
controller.inputs.move_dir = *move_dir;
|
|
|
|
}
|
|
|
|
} else {
|
2022-01-21 00:56:27 +00:00
|
|
|
// Aim for a point a given distance behind the target to prevent sideways
|
|
|
|
// movement
|
2022-03-01 21:54:44 +00:00
|
|
|
let move_target = tgt_data.pos.0.xy() - dist / 2. * target_ori.xy();
|
2022-01-21 00:56:27 +00:00
|
|
|
controller.inputs.move_dir = (move_target - self.pos.0)
|
|
|
|
.try_normalized()
|
|
|
|
.unwrap_or_default();
|
2022-01-20 01:15:50 +00:00
|
|
|
}
|
|
|
|
} else {
|
2022-03-01 21:54:44 +00:00
|
|
|
self.path_toward_target(
|
|
|
|
agent,
|
|
|
|
controller,
|
|
|
|
tgt_data.pos.0,
|
|
|
|
read_data,
|
|
|
|
Path::Full,
|
|
|
|
None,
|
|
|
|
);
|
2022-01-20 01:15:50 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-01-21 23:28:15 +00:00
|
|
|
pub fn handle_elevated_ranged(
|
|
|
|
&self,
|
|
|
|
agent: &mut Agent,
|
|
|
|
controller: &mut Controller,
|
|
|
|
attack_data: &AttackData,
|
|
|
|
tgt_data: &TargetData,
|
|
|
|
read_data: &ReadData,
|
|
|
|
) {
|
2022-04-24 20:15:30 +00:00
|
|
|
let line_of_sight_with_target = || {
|
2022-04-26 23:34:29 +00:00
|
|
|
entities_have_line_of_sight(self.pos, self.body, tgt_data.pos, tgt_data.body, read_data)
|
2022-04-24 20:15:30 +00:00
|
|
|
};
|
2022-04-22 01:32:56 +00:00
|
|
|
|
2022-03-01 21:54:44 +00:00
|
|
|
let elevation = self.pos.0.z - tgt_data.pos.0.z;
|
2022-01-21 23:28:15 +00:00
|
|
|
const PREF_DIST: f32 = 30_f32;
|
|
|
|
if attack_data.angle_xy < 30.0
|
|
|
|
&& (elevation > 10.0 || attack_data.dist_sqrd > PREF_DIST.powi(2))
|
2022-04-24 20:15:30 +00:00
|
|
|
&& line_of_sight_with_target()
|
2022-01-21 23:28:15 +00:00
|
|
|
{
|
|
|
|
controller.push_basic_input(InputKind::Primary);
|
|
|
|
} else if attack_data.dist_sqrd < (PREF_DIST / 2.).powi(2) {
|
|
|
|
// Attempt to move quickly away from target if too close
|
|
|
|
if let Some((bearing, _)) = agent.chaser.chase(
|
|
|
|
&*read_data.terrain,
|
|
|
|
self.pos.0,
|
|
|
|
self.vel.0,
|
2022-03-01 21:54:44 +00:00
|
|
|
tgt_data.pos.0,
|
2022-01-21 23:28:15 +00:00
|
|
|
TraversalConfig {
|
|
|
|
min_tgt_dist: 1.25,
|
|
|
|
..self.traversal_config
|
|
|
|
},
|
|
|
|
) {
|
2022-02-13 01:52:01 +00:00
|
|
|
let flee_dir = -bearing.xy().try_normalized().unwrap_or_else(Vec2::zero);
|
|
|
|
let pos = self.pos.0.xy().with_z(self.pos.0.z + 1.5);
|
|
|
|
if read_data
|
|
|
|
.terrain
|
|
|
|
.ray(pos, pos + flee_dir * 2.0)
|
|
|
|
.until(|b| b.is_solid() || b.get_sprite().is_none())
|
|
|
|
.cast()
|
|
|
|
.0
|
|
|
|
> 1.0
|
|
|
|
{
|
|
|
|
// If able to flee, flee
|
|
|
|
controller.inputs.move_dir = flee_dir;
|
|
|
|
if !self.char_state.is_attack() {
|
|
|
|
controller.inputs.look_dir = -controller.inputs.look_dir;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
// Otherwise, fight to the death
|
|
|
|
controller.push_basic_input(InputKind::Primary);
|
2022-01-21 23:28:15 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
} else if attack_data.dist_sqrd < PREF_DIST.powi(2) {
|
|
|
|
// Attempt to move away from target if too close, while still attacking
|
|
|
|
if let Some((bearing, _)) = agent.chaser.chase(
|
|
|
|
&*read_data.terrain,
|
|
|
|
self.pos.0,
|
|
|
|
self.vel.0,
|
2022-03-01 21:54:44 +00:00
|
|
|
tgt_data.pos.0,
|
2022-01-21 23:28:15 +00:00
|
|
|
TraversalConfig {
|
|
|
|
min_tgt_dist: 1.25,
|
|
|
|
..self.traversal_config
|
|
|
|
},
|
|
|
|
) {
|
2022-04-24 20:15:30 +00:00
|
|
|
if line_of_sight_with_target() {
|
2022-01-21 23:28:15 +00:00
|
|
|
controller.push_basic_input(InputKind::Primary);
|
|
|
|
}
|
|
|
|
controller.inputs.move_dir =
|
|
|
|
-bearing.xy().try_normalized().unwrap_or_else(Vec2::zero);
|
|
|
|
}
|
|
|
|
} else {
|
2022-03-01 21:54:44 +00:00
|
|
|
self.path_toward_target(
|
|
|
|
agent,
|
|
|
|
controller,
|
|
|
|
tgt_data.pos.0,
|
|
|
|
read_data,
|
|
|
|
Path::Full,
|
|
|
|
None,
|
|
|
|
);
|
2022-01-21 23:28:15 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-01-18 03:02:43 +00:00
|
|
|
pub fn handle_axe_attack(
|
|
|
|
&self,
|
|
|
|
agent: &mut Agent,
|
|
|
|
controller: &mut Controller,
|
|
|
|
attack_data: &AttackData,
|
|
|
|
tgt_data: &TargetData,
|
|
|
|
read_data: &ReadData,
|
|
|
|
rng: &mut impl Rng,
|
|
|
|
) {
|
2022-10-25 02:52:37 +00:00
|
|
|
enum ActionStateTimers {
|
2022-10-17 02:48:27 +00:00
|
|
|
TimerHandleAxeAttack = 0,
|
|
|
|
}
|
2022-01-18 03:02:43 +00:00
|
|
|
let has_leap = || self.skill_set.has_skill(Skill::Axe(AxeSkill::UnlockLeap));
|
|
|
|
let has_energy = |need| self.energy.current() > need;
|
|
|
|
let use_leap = |controller: &mut Controller| {
|
2022-01-26 19:09:59 +00:00
|
|
|
controller.push_basic_input(InputKind::Ability(0));
|
2022-01-18 03:02:43 +00:00
|
|
|
};
|
2022-04-22 01:32:56 +00:00
|
|
|
|
2022-01-18 03:02:43 +00:00
|
|
|
if attack_data.in_min_range() && attack_data.angle < 45.0 {
|
|
|
|
controller.inputs.move_dir = Vec2::zero();
|
2022-10-17 03:13:03 +00:00
|
|
|
if agent.action_state.timers[ActionStateTimers::TimerHandleAxeAttack as usize] > 5.0 {
|
2022-01-26 19:15:40 +00:00
|
|
|
controller.push_cancel_input(InputKind::Secondary);
|
2022-10-17 03:13:03 +00:00
|
|
|
agent.action_state.timers[ActionStateTimers::TimerHandleAxeAttack as usize] = 0.0;
|
2022-10-25 02:52:37 +00:00
|
|
|
} else if agent.action_state.timers[ActionStateTimers::TimerHandleAxeAttack as usize]
|
|
|
|
> 2.5
|
|
|
|
&& has_energy(10.0)
|
|
|
|
{
|
2022-01-26 19:09:59 +00:00
|
|
|
controller.push_basic_input(InputKind::Secondary);
|
2022-10-25 02:52:37 +00:00
|
|
|
agent.action_state.timers[ActionStateTimers::TimerHandleAxeAttack as usize] +=
|
|
|
|
read_data.dt.0;
|
2022-01-18 03:02:43 +00:00
|
|
|
} else if has_leap() && has_energy(45.0) && rng.gen_bool(0.5) {
|
2022-01-26 19:09:59 +00:00
|
|
|
controller.push_basic_input(InputKind::Ability(0));
|
2022-10-25 02:52:37 +00:00
|
|
|
agent.action_state.timers[ActionStateTimers::TimerHandleAxeAttack as usize] +=
|
|
|
|
read_data.dt.0;
|
2022-01-18 03:02:43 +00:00
|
|
|
} else {
|
2022-01-26 19:09:59 +00:00
|
|
|
controller.push_basic_input(InputKind::Primary);
|
2022-10-25 02:52:37 +00:00
|
|
|
agent.action_state.timers[ActionStateTimers::TimerHandleAxeAttack as usize] +=
|
|
|
|
read_data.dt.0;
|
2022-01-18 03:02:43 +00:00
|
|
|
}
|
|
|
|
} else {
|
2022-03-01 21:54:44 +00:00
|
|
|
self.path_toward_target(
|
|
|
|
agent,
|
|
|
|
controller,
|
|
|
|
tgt_data.pos.0,
|
|
|
|
read_data,
|
|
|
|
Path::Separate,
|
|
|
|
None,
|
|
|
|
);
|
2022-03-05 08:53:28 +00:00
|
|
|
|
|
|
|
if attack_data.dist_sqrd < 32.0f32.powi(2)
|
|
|
|
&& has_leap()
|
|
|
|
&& has_energy(50.0)
|
2022-04-26 23:34:29 +00:00
|
|
|
&& entities_have_line_of_sight(
|
|
|
|
self.pos,
|
|
|
|
self.body,
|
|
|
|
tgt_data.pos,
|
|
|
|
tgt_data.body,
|
|
|
|
read_data,
|
|
|
|
)
|
2022-03-05 08:53:28 +00:00
|
|
|
{
|
|
|
|
use_leap(controller);
|
|
|
|
}
|
|
|
|
if self.body.map(|b| b.is_humanoid()).unwrap_or(false)
|
|
|
|
&& attack_data.dist_sqrd < 16.0f32.powi(2)
|
|
|
|
&& rng.gen::<f32>() < 0.02
|
|
|
|
{
|
|
|
|
controller.push_basic_input(InputKind::Roll);
|
2022-02-27 06:56:36 +00:00
|
|
|
}
|
2022-01-18 03:02:43 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn handle_hammer_attack(
|
|
|
|
&self,
|
|
|
|
agent: &mut Agent,
|
|
|
|
controller: &mut Controller,
|
|
|
|
attack_data: &AttackData,
|
|
|
|
tgt_data: &TargetData,
|
|
|
|
read_data: &ReadData,
|
|
|
|
rng: &mut impl Rng,
|
|
|
|
) {
|
2022-10-25 02:52:37 +00:00
|
|
|
enum ActionStateTimers {
|
2022-10-17 02:48:27 +00:00
|
|
|
TimerHandleHammerAttack = 0,
|
|
|
|
}
|
2022-01-18 03:02:43 +00:00
|
|
|
let has_leap = || {
|
|
|
|
self.skill_set
|
|
|
|
.has_skill(Skill::Hammer(HammerSkill::UnlockLeap))
|
|
|
|
};
|
|
|
|
|
|
|
|
let has_energy = |need| self.energy.current() > need;
|
|
|
|
|
|
|
|
let use_leap = |controller: &mut Controller| {
|
2022-01-26 19:09:59 +00:00
|
|
|
controller.push_basic_input(InputKind::Ability(0));
|
2022-01-18 03:02:43 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
if attack_data.in_min_range() && attack_data.angle < 45.0 {
|
|
|
|
controller.inputs.move_dir = Vec2::zero();
|
2022-10-25 02:52:37 +00:00
|
|
|
if agent.action_state.timers[ActionStateTimers::TimerHandleHammerAttack as usize] > 4.0
|
|
|
|
{
|
2022-01-26 19:15:40 +00:00
|
|
|
controller.push_cancel_input(InputKind::Secondary);
|
2022-10-25 02:52:37 +00:00
|
|
|
agent.action_state.timers[ActionStateTimers::TimerHandleHammerAttack as usize] =
|
|
|
|
0.0;
|
|
|
|
} else if agent.action_state.timers[ActionStateTimers::TimerHandleHammerAttack as usize]
|
|
|
|
> 3.0
|
|
|
|
{
|
2022-01-26 19:09:59 +00:00
|
|
|
controller.push_basic_input(InputKind::Secondary);
|
2022-10-25 02:52:37 +00:00
|
|
|
agent.action_state.timers[ActionStateTimers::TimerHandleHammerAttack as usize] +=
|
|
|
|
read_data.dt.0;
|
2022-01-18 03:02:43 +00:00
|
|
|
} else if has_leap() && has_energy(50.0) && rng.gen_bool(0.9) {
|
|
|
|
use_leap(controller);
|
2022-10-25 02:52:37 +00:00
|
|
|
agent.action_state.timers[ActionStateTimers::TimerHandleHammerAttack as usize] +=
|
|
|
|
read_data.dt.0;
|
2022-01-18 03:02:43 +00:00
|
|
|
} else {
|
2022-01-26 19:09:59 +00:00
|
|
|
controller.push_basic_input(InputKind::Primary);
|
2022-10-25 02:52:37 +00:00
|
|
|
agent.action_state.timers[ActionStateTimers::TimerHandleHammerAttack as usize] +=
|
|
|
|
read_data.dt.0;
|
2022-01-18 03:02:43 +00:00
|
|
|
}
|
|
|
|
} else {
|
2022-03-01 21:54:44 +00:00
|
|
|
self.path_toward_target(
|
|
|
|
agent,
|
|
|
|
controller,
|
|
|
|
tgt_data.pos.0,
|
|
|
|
read_data,
|
|
|
|
Path::Separate,
|
|
|
|
None,
|
|
|
|
);
|
2022-02-27 06:56:36 +00:00
|
|
|
|
2022-03-05 08:53:28 +00:00
|
|
|
if attack_data.dist_sqrd < 32.0f32.powi(2)
|
|
|
|
&& has_leap()
|
|
|
|
&& has_energy(50.0)
|
2022-04-26 23:34:29 +00:00
|
|
|
&& entities_have_line_of_sight(
|
|
|
|
self.pos,
|
|
|
|
self.body,
|
|
|
|
tgt_data.pos,
|
|
|
|
tgt_data.body,
|
|
|
|
read_data,
|
|
|
|
)
|
2022-03-05 08:53:28 +00:00
|
|
|
{
|
|
|
|
use_leap(controller);
|
|
|
|
}
|
|
|
|
if self.body.map(|b| b.is_humanoid()).unwrap_or(false)
|
|
|
|
&& attack_data.dist_sqrd < 16.0f32.powi(2)
|
|
|
|
&& rng.gen::<f32>() < 0.02
|
|
|
|
{
|
|
|
|
controller.push_basic_input(InputKind::Roll);
|
2022-02-27 06:56:36 +00:00
|
|
|
}
|
2022-01-18 03:02:43 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn handle_sword_attack(
|
|
|
|
&self,
|
|
|
|
agent: &mut Agent,
|
|
|
|
controller: &mut Controller,
|
|
|
|
attack_data: &AttackData,
|
|
|
|
tgt_data: &TargetData,
|
|
|
|
read_data: &ReadData,
|
|
|
|
rng: &mut impl Rng,
|
|
|
|
) {
|
2022-10-26 00:30:33 +00:00
|
|
|
const INT_COUNTER_STANCE: usize = 0;
|
2022-09-14 01:58:53 +00:00
|
|
|
use ability::SwordStance;
|
|
|
|
let stance = |stance| match stance {
|
|
|
|
1 => SwordStance::Offensive,
|
|
|
|
2 => SwordStance::Defensive,
|
|
|
|
3 => SwordStance::Mobility,
|
|
|
|
4 => SwordStance::Crippling,
|
2022-10-23 17:10:37 +00:00
|
|
|
5 => SwordStance::Cleaving,
|
|
|
|
6 => SwordStance::Parrying,
|
|
|
|
7 => SwordStance::Heavy,
|
|
|
|
8 => SwordStance::Reaching,
|
2022-09-14 01:58:53 +00:00
|
|
|
_ => SwordStance::Balanced,
|
|
|
|
};
|
|
|
|
if !agent.action_state.initialized {
|
2022-09-18 02:11:41 +00:00
|
|
|
// TODO: Don't always assume that if they have skill checked for, they have
|
|
|
|
// entire set of necessary skills to take full advantage of AI. Make sure to
|
|
|
|
// change this to properly query for all required skills when AI dynamically get
|
|
|
|
// new skills and don't have pre-created skill sets.
|
2022-10-26 00:30:33 +00:00
|
|
|
agent.action_state.int_counters[INT_COUNTER_STANCE] = if self
|
2022-09-18 02:11:41 +00:00
|
|
|
.skill_set
|
|
|
|
.has_skill(Skill::Sword(SwordSkill::CripplingFinisher))
|
|
|
|
{
|
2022-10-09 07:17:29 +00:00
|
|
|
// Hack to make cleaving stance come up less often because agents cannot use it
|
|
|
|
// effectively due to only considering a single target
|
|
|
|
// Remove when agents properly consider multiple targets
|
2022-10-23 17:10:37 +00:00
|
|
|
// (stance, weight)
|
|
|
|
let options = [(4, 3), (5, 1), (6, 3), (7, 3), (8, 3)];
|
|
|
|
options.choose_weighted(rng, |x| x.1).map_or(0, |x| x.0)
|
2022-10-09 00:45:37 +00:00
|
|
|
// rng.gen_range(4..9)
|
2022-09-18 02:11:41 +00:00
|
|
|
} else if self
|
|
|
|
.skill_set
|
|
|
|
.has_skill(Skill::Sword(SwordSkill::OffensiveFinisher))
|
|
|
|
{
|
|
|
|
rng.gen_range(1..4)
|
|
|
|
} else {
|
|
|
|
0
|
|
|
|
};
|
2022-09-14 01:58:53 +00:00
|
|
|
let auxiliary_key = ActiveAbilities::active_auxiliary_key(Some(self.inventory));
|
|
|
|
let mut set_sword_ability = |slot, skill| {
|
|
|
|
controller.push_event(ControlEvent::ChangeAbility {
|
|
|
|
slot,
|
|
|
|
auxiliary_key,
|
|
|
|
new_ability: AuxiliaryAbility::MainWeapon(skill),
|
|
|
|
})
|
|
|
|
};
|
2022-10-26 00:30:33 +00:00
|
|
|
match stance(agent.action_state.int_counters[INT_COUNTER_STANCE]) {
|
2022-09-14 01:58:53 +00:00
|
|
|
SwordStance::Balanced => {
|
|
|
|
// Balanced finisher
|
2022-10-22 19:59:25 +00:00
|
|
|
set_sword_ability(0, 8);
|
2022-09-14 01:58:53 +00:00
|
|
|
},
|
|
|
|
SwordStance::Offensive => {
|
|
|
|
// Offensive combo
|
2022-10-22 19:59:25 +00:00
|
|
|
set_sword_ability(0, 0);
|
2022-09-14 01:58:53 +00:00
|
|
|
// Offensive advance
|
2022-10-22 19:59:25 +00:00
|
|
|
set_sword_ability(1, 9);
|
2022-10-06 01:25:24 +00:00
|
|
|
// Offensive finisher
|
2022-10-22 19:59:25 +00:00
|
|
|
set_sword_ability(2, 8);
|
2022-09-14 01:58:53 +00:00
|
|
|
},
|
|
|
|
SwordStance::Defensive => {
|
|
|
|
// Defensive combo
|
2022-10-22 19:59:25 +00:00
|
|
|
set_sword_ability(0, 3);
|
2022-09-14 01:58:53 +00:00
|
|
|
// Defensive retreat
|
2022-10-22 19:59:25 +00:00
|
|
|
set_sword_ability(1, 9);
|
2022-10-08 18:46:32 +00:00
|
|
|
// Defensive bulwark
|
2022-10-22 19:59:25 +00:00
|
|
|
set_sword_ability(2, 10);
|
2022-09-14 01:58:53 +00:00
|
|
|
},
|
|
|
|
SwordStance::Mobility => {
|
|
|
|
// Mobility combo
|
2022-10-22 19:59:25 +00:00
|
|
|
set_sword_ability(0, 6);
|
2022-09-14 01:58:53 +00:00
|
|
|
// Mobility feint
|
2022-10-22 19:59:25 +00:00
|
|
|
set_sword_ability(1, 9);
|
2022-09-14 01:58:53 +00:00
|
|
|
// Mobility agility
|
2022-10-22 19:59:25 +00:00
|
|
|
set_sword_ability(2, 10);
|
2022-09-14 01:58:53 +00:00
|
|
|
},
|
|
|
|
SwordStance::Crippling => {
|
|
|
|
// Crippling combo
|
2022-10-22 19:59:25 +00:00
|
|
|
set_sword_ability(0, 1);
|
2022-09-14 01:58:53 +00:00
|
|
|
// Crippling finisher
|
2022-10-22 19:59:25 +00:00
|
|
|
set_sword_ability(1, 8);
|
2022-09-14 01:58:53 +00:00
|
|
|
// Crippling strike
|
2022-10-22 19:59:25 +00:00
|
|
|
set_sword_ability(2, 9);
|
2022-09-14 01:58:53 +00:00
|
|
|
// Crippling gouge
|
2022-10-22 19:59:25 +00:00
|
|
|
set_sword_ability(3, 10);
|
2022-09-14 01:58:53 +00:00
|
|
|
},
|
|
|
|
SwordStance::Cleaving => {
|
|
|
|
// Cleaving combo
|
2022-10-22 19:59:25 +00:00
|
|
|
set_sword_ability(0, 2);
|
2022-09-14 01:58:53 +00:00
|
|
|
// Cleaving finisher
|
2022-10-22 19:59:25 +00:00
|
|
|
set_sword_ability(1, 8);
|
2022-09-14 01:58:53 +00:00
|
|
|
// Cleaving spin
|
|
|
|
set_sword_ability(2, 10);
|
|
|
|
// Cleaving dive
|
2022-10-22 19:59:25 +00:00
|
|
|
set_sword_ability(3, 9);
|
2022-09-14 01:58:53 +00:00
|
|
|
},
|
|
|
|
SwordStance::Parrying => {
|
|
|
|
// Parrying combo
|
2022-10-22 19:59:25 +00:00
|
|
|
set_sword_ability(0, 4);
|
2022-09-14 01:58:53 +00:00
|
|
|
// Parrying parry
|
2022-10-22 19:59:25 +00:00
|
|
|
set_sword_ability(1, 10);
|
2022-09-14 01:58:53 +00:00
|
|
|
// Parrying riposte
|
2022-10-22 19:59:25 +00:00
|
|
|
set_sword_ability(2, 9);
|
2022-09-14 01:58:53 +00:00
|
|
|
// Parrying counter
|
2022-10-22 19:59:25 +00:00
|
|
|
set_sword_ability(3, 8);
|
2022-09-14 01:58:53 +00:00
|
|
|
},
|
|
|
|
SwordStance::Heavy => {
|
|
|
|
// Heavy combo
|
2022-10-22 19:59:25 +00:00
|
|
|
set_sword_ability(0, 5);
|
2022-09-14 01:58:53 +00:00
|
|
|
// Heavy finisher
|
2022-10-22 19:59:25 +00:00
|
|
|
set_sword_ability(1, 8);
|
2022-09-14 01:58:53 +00:00
|
|
|
// Heavy pommelstrike
|
2022-10-22 19:59:25 +00:00
|
|
|
set_sword_ability(2, 10);
|
2022-09-14 01:58:53 +00:00
|
|
|
// Heavy fortitude
|
2022-10-22 19:59:25 +00:00
|
|
|
set_sword_ability(3, 9);
|
2022-09-14 01:58:53 +00:00
|
|
|
},
|
|
|
|
SwordStance::Reaching => {
|
|
|
|
// Reaching combo
|
2022-10-22 19:59:25 +00:00
|
|
|
set_sword_ability(0, 7);
|
2022-09-14 01:58:53 +00:00
|
|
|
// Reaching charge
|
2022-10-22 19:59:25 +00:00
|
|
|
set_sword_ability(1, 9);
|
2022-09-14 01:58:53 +00:00
|
|
|
// Reaching flurry
|
2022-10-22 19:59:25 +00:00
|
|
|
set_sword_ability(2, 8);
|
2022-09-14 01:58:53 +00:00
|
|
|
// Reaching skewer
|
2022-10-22 19:59:25 +00:00
|
|
|
set_sword_ability(3, 10);
|
2022-09-14 01:58:53 +00:00
|
|
|
},
|
|
|
|
}
|
|
|
|
agent.action_state.initialized = true;
|
|
|
|
}
|
|
|
|
|
2022-09-18 02:11:41 +00:00
|
|
|
let advance = |agent: &mut Agent, controller: &mut Controller, dist: f32, angle: f32| {
|
2022-10-26 02:00:45 +00:00
|
|
|
if attack_data.dist_sqrd > (dist + self.body.map_or(0.0, |b| b.max_radius())).powi(2)
|
|
|
|
|| attack_data.angle > angle
|
|
|
|
{
|
2022-09-18 02:11:41 +00:00
|
|
|
self.path_toward_target(
|
|
|
|
agent,
|
|
|
|
controller,
|
|
|
|
tgt_data.pos.0,
|
|
|
|
read_data,
|
|
|
|
Path::Separate,
|
|
|
|
None,
|
|
|
|
);
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2022-10-26 02:00:45 +00:00
|
|
|
const AVERAGE_ROLL_FREQUENCY: f32 = 12.0;
|
|
|
|
const TIMER_LAST_ROLL: usize = 0;
|
|
|
|
const CONDITION_RANDOM_ROLL: usize = 0;
|
|
|
|
const MIN_ENERGY_FOR_ROLL: f32 = 30.0;
|
|
|
|
if self.energy.current() > MIN_ENERGY_FOR_ROLL {
|
|
|
|
agent.action_state.timers[TIMER_LAST_ROLL] += read_data.dt.0;
|
|
|
|
}
|
|
|
|
if (-agent.action_state.timers[TIMER_LAST_ROLL] / AVERAGE_ROLL_FREQUENCY).exp()
|
|
|
|
< rng.gen::<f32>() / std::f32::consts::E
|
|
|
|
{
|
|
|
|
agent.action_state.conditions[CONDITION_RANDOM_ROLL] = true;
|
|
|
|
}
|
|
|
|
if agent.action_state.conditions[CONDITION_RANDOM_ROLL] {
|
|
|
|
controller.push_basic_input(InputKind::Roll);
|
|
|
|
advance(agent, controller, 1.0, 30.0);
|
|
|
|
let random_angle = rng.gen_range(-PI..PI) / 4.0;
|
|
|
|
controller.inputs.move_dir.rotated_z(random_angle);
|
|
|
|
}
|
|
|
|
if matches!(self.char_state, CharacterState::Roll(c) if c.stage_section == StageSection::Recover)
|
|
|
|
{
|
|
|
|
agent.action_state.timers[TIMER_LAST_ROLL] = 0.0;
|
|
|
|
agent.action_state.conditions[CONDITION_RANDOM_ROLL] = false;
|
|
|
|
}
|
|
|
|
|
2022-09-18 02:11:41 +00:00
|
|
|
// Called when out of energy, or the situation is not right to use another
|
|
|
|
// ability. Only contains tactics for using M1 and M2
|
|
|
|
let fallback_rng_1 = rng.gen::<f32>() < 0.67;
|
|
|
|
let fallback_tactics = |agent: &mut Agent, controller: &mut Controller| {
|
2022-10-06 01:25:24 +00:00
|
|
|
const BALANCED_COMBO: ComboMeleeData = ComboMeleeData {
|
|
|
|
min_range: 0.0,
|
|
|
|
max_range: 2.5,
|
|
|
|
angle: 40.0,
|
|
|
|
energy: 0.0,
|
|
|
|
};
|
|
|
|
const BALANCED_THRUST: ComboMeleeData = ComboMeleeData {
|
|
|
|
min_range: 0.0,
|
|
|
|
max_range: 4.0,
|
|
|
|
angle: 8.0,
|
|
|
|
energy: 0.0,
|
|
|
|
};
|
|
|
|
|
|
|
|
let balanced_thrust_targetable = BALANCED_THRUST.could_use(attack_data, self);
|
2022-09-18 02:11:41 +00:00
|
|
|
|
|
|
|
if (matches!(self.char_state, CharacterState::ChargedMelee(c) if c.charge_amount < 0.95)
|
|
|
|
|| matches!(
|
|
|
|
tgt_data.char_state.and_then(|cs| cs.stage_section()),
|
|
|
|
Some(StageSection::Buildup)
|
|
|
|
))
|
|
|
|
&& balanced_thrust_targetable
|
|
|
|
{
|
|
|
|
// If target in buildup (and therefore likely to still be around for an attack
|
|
|
|
// that needs charging), thrust
|
|
|
|
// Or if already thrusting, keep thrussting
|
|
|
|
controller.push_basic_input(InputKind::Secondary);
|
2022-10-06 01:25:24 +00:00
|
|
|
} else if BALANCED_COMBO.could_use(attack_data, self) && fallback_rng_1 {
|
2022-09-18 02:11:41 +00:00
|
|
|
controller.push_basic_input(InputKind::Primary);
|
|
|
|
} else if balanced_thrust_targetable {
|
|
|
|
controller.push_basic_input(InputKind::Secondary);
|
|
|
|
}
|
|
|
|
|
2022-10-06 01:25:24 +00:00
|
|
|
advance(agent, controller, 1.5, BALANCED_COMBO.angle);
|
|
|
|
};
|
|
|
|
|
|
|
|
let in_stance = |stance| {
|
|
|
|
if let CharacterState::ComboMelee2(c) = self.char_state {
|
|
|
|
c.static_data.is_stance
|
|
|
|
&& c.static_data
|
|
|
|
.ability_info
|
|
|
|
.ability_meta
|
|
|
|
.and_then(|meta| meta.kind)
|
|
|
|
.map_or(false, |kind| AbilityKind::Sword(stance) == kind)
|
|
|
|
} else {
|
|
|
|
false
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2022-10-26 00:30:33 +00:00
|
|
|
match stance(agent.action_state.int_counters[INT_COUNTER_STANCE]) {
|
2022-09-14 01:58:53 +00:00
|
|
|
SwordStance::Balanced => {
|
2022-10-22 19:59:25 +00:00
|
|
|
const BALANCED_FINISHER: FinisherMeleeData = FinisherMeleeData {
|
|
|
|
range: 2.5,
|
|
|
|
angle: 12.5,
|
|
|
|
energy: 30.0,
|
|
|
|
combo: 10,
|
|
|
|
};
|
|
|
|
|
2022-09-18 02:11:41 +00:00
|
|
|
if self
|
|
|
|
.skill_set
|
|
|
|
.has_skill(Skill::Sword(SwordSkill::BalancedFinisher))
|
|
|
|
&& BALANCED_FINISHER.could_use(attack_data, self)
|
|
|
|
{
|
|
|
|
controller.push_basic_input(InputKind::Ability(0));
|
|
|
|
advance(
|
|
|
|
agent,
|
|
|
|
controller,
|
|
|
|
BALANCED_FINISHER.range,
|
|
|
|
BALANCED_FINISHER.angle,
|
|
|
|
);
|
|
|
|
} else {
|
|
|
|
fallback_tactics(agent, controller);
|
|
|
|
}
|
2022-09-14 01:58:53 +00:00
|
|
|
},
|
|
|
|
SwordStance::Offensive => {
|
2022-10-06 01:25:24 +00:00
|
|
|
const OFFENSIVE_COMBO: ComboMeleeData = ComboMeleeData {
|
|
|
|
min_range: 0.0,
|
|
|
|
max_range: 2.0,
|
|
|
|
angle: 30.0,
|
2022-10-20 19:49:57 +00:00
|
|
|
energy: 4.0,
|
2022-10-06 01:25:24 +00:00
|
|
|
};
|
2022-10-22 19:59:25 +00:00
|
|
|
const OFFENSIVE_ADVANCE: ComboMeleeData = ComboMeleeData {
|
|
|
|
min_range: 6.0,
|
|
|
|
max_range: 10.0,
|
|
|
|
angle: 20.0,
|
|
|
|
energy: 10.0,
|
|
|
|
};
|
2022-10-06 01:25:24 +00:00
|
|
|
const OFFENSIVE_FINISHER: FinisherMeleeData = FinisherMeleeData {
|
|
|
|
range: 2.0,
|
|
|
|
angle: 10.0,
|
|
|
|
energy: 40.0,
|
|
|
|
combo: 10,
|
|
|
|
};
|
|
|
|
const DESIRED_ENERGY: f32 = 50.0;
|
|
|
|
|
|
|
|
if self.energy.current() < DESIRED_ENERGY {
|
|
|
|
fallback_tactics(agent, controller);
|
|
|
|
} else if !in_stance(SwordStance::Offensive) {
|
|
|
|
controller.push_basic_input(InputKind::Ability(0));
|
|
|
|
} else {
|
2022-10-22 19:59:25 +00:00
|
|
|
let used_finisher = if OFFENSIVE_FINISHER.could_use(attack_data, self)
|
|
|
|
&& OFFENSIVE_FINISHER.use_desirable(tgt_data, self)
|
2022-10-06 01:25:24 +00:00
|
|
|
{
|
2022-10-22 19:59:25 +00:00
|
|
|
controller.push_basic_input(InputKind::Ability(2));
|
2022-10-06 01:25:24 +00:00
|
|
|
advance(
|
|
|
|
agent,
|
|
|
|
controller,
|
2022-10-22 19:59:25 +00:00
|
|
|
OFFENSIVE_FINISHER.range,
|
|
|
|
OFFENSIVE_FINISHER.angle,
|
2022-10-06 01:25:24 +00:00
|
|
|
);
|
|
|
|
true
|
|
|
|
} else {
|
|
|
|
false
|
|
|
|
};
|
|
|
|
|
|
|
|
if !used_finisher {
|
|
|
|
if OFFENSIVE_COMBO.could_use(attack_data, self) {
|
|
|
|
controller.push_basic_input(InputKind::Primary);
|
|
|
|
advance(
|
|
|
|
agent,
|
|
|
|
controller,
|
|
|
|
OFFENSIVE_COMBO.max_range,
|
|
|
|
OFFENSIVE_COMBO.angle,
|
|
|
|
);
|
|
|
|
} else if OFFENSIVE_ADVANCE.could_use(attack_data, self) {
|
|
|
|
controller.push_basic_input(InputKind::Ability(1));
|
|
|
|
advance(
|
|
|
|
agent,
|
|
|
|
controller,
|
|
|
|
OFFENSIVE_ADVANCE.max_range,
|
|
|
|
OFFENSIVE_ADVANCE.angle,
|
|
|
|
);
|
|
|
|
} else {
|
|
|
|
advance(
|
|
|
|
agent,
|
|
|
|
controller,
|
|
|
|
OFFENSIVE_COMBO.max_range,
|
|
|
|
OFFENSIVE_COMBO.angle,
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2022-09-14 01:58:53 +00:00
|
|
|
},
|
|
|
|
SwordStance::Defensive => {
|
2022-10-08 18:46:32 +00:00
|
|
|
const DEFENSIVE_COMBO: ComboMeleeData = ComboMeleeData {
|
|
|
|
min_range: 0.0,
|
|
|
|
max_range: 2.5,
|
|
|
|
angle: 35.0,
|
2022-10-20 19:49:57 +00:00
|
|
|
energy: 3.0,
|
2022-10-08 18:46:32 +00:00
|
|
|
};
|
2022-10-22 19:59:25 +00:00
|
|
|
const DEFENSIVE_RETREAT: ComboMeleeData = ComboMeleeData {
|
|
|
|
min_range: 0.0,
|
|
|
|
max_range: 3.0,
|
|
|
|
angle: 35.0,
|
|
|
|
energy: 10.0,
|
|
|
|
};
|
|
|
|
const DEFENSIVE_BULWARK: SelfBuffData = SelfBuffData {
|
|
|
|
buff: BuffKind::ProtectingWard,
|
|
|
|
energy: 40.0,
|
|
|
|
};
|
2022-10-12 19:19:52 +00:00
|
|
|
const BASIC_BLOCK: BlockData = BlockData {
|
|
|
|
angle: 55.0,
|
|
|
|
range: 5.0,
|
|
|
|
energy: 2.5,
|
|
|
|
};
|
2022-10-08 18:46:32 +00:00
|
|
|
const DESIRED_ENERGY: f32 = 50.0;
|
2022-10-26 02:00:45 +00:00
|
|
|
const CONDITION_HOLD: usize = 1;
|
|
|
|
const COUNTER_ANGLE: usize = 1;
|
|
|
|
const TIMER_HOLD_TIMEOUT: usize = 1;
|
|
|
|
const HOLD_TIMEOUT: f32 = 3.0;
|
2022-10-08 18:46:32 +00:00
|
|
|
|
|
|
|
let mut try_block = || {
|
|
|
|
if let Some(char_state) = tgt_data.char_state {
|
|
|
|
matches!(char_state.stage_section(), Some(StageSection::Buildup))
|
|
|
|
&& char_state.is_melee_attack()
|
2022-10-12 19:19:52 +00:00
|
|
|
&& BASIC_BLOCK.could_use(attack_data, self)
|
2022-10-08 18:46:32 +00:00
|
|
|
&& char_state
|
|
|
|
.timer()
|
|
|
|
.map_or(false, |timer| rng.gen::<f32>() < timer.as_secs_f32() * 4.0)
|
|
|
|
&& self
|
|
|
|
.char_state
|
|
|
|
.ability_info()
|
|
|
|
.and_then(|a| a.ability_meta)
|
|
|
|
.map(|m| m.capabilities)
|
|
|
|
.map_or(false, |c| c.contains(Capability::BLOCK_INTERRUPT))
|
|
|
|
} else {
|
|
|
|
false
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2022-10-26 02:00:45 +00:00
|
|
|
if agent.action_state.conditions[CONDITION_HOLD] {
|
|
|
|
agent.action_state.timers[TIMER_HOLD_TIMEOUT] += read_data.dt.0;
|
|
|
|
}
|
|
|
|
|
2022-10-08 18:46:32 +00:00
|
|
|
if read_data.time.0
|
|
|
|
- self
|
|
|
|
.health
|
|
|
|
.map_or(read_data.time.0, |h| h.last_change.time.0)
|
|
|
|
< read_data.dt.0 as f64 * 2.0
|
2022-10-26 02:00:45 +00:00
|
|
|
|| agent.action_state.timers[TIMER_HOLD_TIMEOUT] > HOLD_TIMEOUT
|
2022-10-08 18:46:32 +00:00
|
|
|
{
|
|
|
|
// If attacked in last couple ticks, stop standing still
|
2022-10-26 00:30:33 +00:00
|
|
|
agent.action_state.conditions[CONDITION_HOLD] = false;
|
2022-10-26 02:00:45 +00:00
|
|
|
agent.action_state.timers[TIMER_HOLD_TIMEOUT] = 0.0;
|
2022-10-08 18:46:32 +00:00
|
|
|
} else if matches!(
|
|
|
|
self.char_state.ability_info().and_then(|info| info.input),
|
|
|
|
Some(InputKind::Ability(1))
|
|
|
|
) {
|
|
|
|
// If used defensive retreat, stand still for a little bit to bait people
|
|
|
|
// forward
|
2022-10-26 00:30:33 +00:00
|
|
|
agent.action_state.conditions[CONDITION_HOLD] = true;
|
2022-10-08 18:46:32 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
if self.energy.current() < DESIRED_ENERGY {
|
|
|
|
fallback_tactics(agent, controller);
|
|
|
|
} else if !in_stance(SwordStance::Defensive) {
|
|
|
|
controller.push_basic_input(InputKind::Ability(0));
|
|
|
|
} else if DEFENSIVE_BULWARK.could_use(self) && DEFENSIVE_BULWARK.use_desirable(self)
|
|
|
|
{
|
|
|
|
controller.push_basic_input(InputKind::Ability(2));
|
|
|
|
} else if try_block() {
|
|
|
|
controller.push_basic_input(InputKind::Block);
|
|
|
|
} else if DEFENSIVE_RETREAT.could_use(attack_data, self) && rng.gen::<f32>() < 0.2 {
|
|
|
|
controller.push_basic_input(InputKind::Ability(1));
|
2022-10-26 00:30:33 +00:00
|
|
|
if !agent.action_state.conditions[CONDITION_HOLD] {
|
2022-10-08 18:46:32 +00:00
|
|
|
advance(
|
|
|
|
agent,
|
|
|
|
controller,
|
|
|
|
DEFENSIVE_RETREAT.max_range,
|
|
|
|
DEFENSIVE_RETREAT.angle,
|
|
|
|
);
|
|
|
|
}
|
|
|
|
} else if DEFENSIVE_COMBO.could_use(attack_data, self) {
|
|
|
|
controller.push_basic_input(InputKind::Primary);
|
2022-10-26 00:30:33 +00:00
|
|
|
if !agent.action_state.conditions[CONDITION_HOLD] {
|
2022-10-08 18:46:32 +00:00
|
|
|
advance(
|
|
|
|
agent,
|
|
|
|
controller,
|
|
|
|
DEFENSIVE_COMBO.max_range,
|
|
|
|
DEFENSIVE_COMBO.angle,
|
|
|
|
);
|
|
|
|
}
|
2022-10-26 02:00:45 +00:00
|
|
|
} else if agent.action_state.conditions[CONDITION_HOLD] {
|
|
|
|
agent.action_state.counters[COUNTER_ANGLE] += rng.gen::<f32>() * read_data.dt.0;
|
|
|
|
controller.inputs.move_dir =
|
|
|
|
Vec2::unit_x().rotated_z(agent.action_state.counters[COUNTER_ANGLE]) * 0.25;
|
|
|
|
} else {
|
2022-10-08 18:46:32 +00:00
|
|
|
advance(
|
|
|
|
agent,
|
|
|
|
controller,
|
|
|
|
DEFENSIVE_COMBO.max_range,
|
|
|
|
DEFENSIVE_COMBO.angle,
|
|
|
|
);
|
|
|
|
}
|
2022-09-14 01:58:53 +00:00
|
|
|
},
|
|
|
|
SwordStance::Mobility => {
|
2022-10-08 22:38:19 +00:00
|
|
|
const MOBILITY_COMBO: ComboMeleeData = ComboMeleeData {
|
|
|
|
min_range: 0.0,
|
|
|
|
max_range: 2.5,
|
|
|
|
angle: 35.0,
|
2022-10-20 19:49:57 +00:00
|
|
|
energy: 3.0,
|
2022-10-08 22:38:19 +00:00
|
|
|
};
|
|
|
|
const MOBILITY_FEINT: ComboMeleeData = ComboMeleeData {
|
|
|
|
min_range: 0.0,
|
|
|
|
max_range: 3.0,
|
|
|
|
angle: 35.0,
|
|
|
|
energy: 10.0,
|
|
|
|
};
|
2022-10-22 19:59:25 +00:00
|
|
|
const MOBILITY_AGILITY: SelfBuffData = SelfBuffData {
|
|
|
|
buff: BuffKind::Hastened,
|
|
|
|
energy: 40.0,
|
|
|
|
};
|
2022-10-08 22:38:19 +00:00
|
|
|
const DESIRED_ENERGY: f32 = 50.0;
|
|
|
|
|
2022-10-23 17:10:37 +00:00
|
|
|
let mut try_roll = |controller: &mut Controller| {
|
2022-10-08 22:38:19 +00:00
|
|
|
if let Some(char_state) = tgt_data.char_state {
|
2022-10-23 17:10:37 +00:00
|
|
|
let try_roll =
|
|
|
|
matches!(char_state.stage_section(), Some(StageSection::Buildup))
|
|
|
|
&& char_state.is_melee_attack()
|
|
|
|
&& char_state.timer().map_or(false, |timer| {
|
|
|
|
rng.gen::<f32>() < timer.as_secs_f32() * 4.0
|
|
|
|
})
|
|
|
|
&& matches!(
|
|
|
|
self.char_state.stage_section(),
|
|
|
|
Some(StageSection::Recover)
|
|
|
|
)
|
|
|
|
&& self
|
|
|
|
.char_state
|
|
|
|
.ability_info()
|
|
|
|
.and_then(|a| a.ability_meta)
|
|
|
|
.map(|m| m.capabilities)
|
|
|
|
.map_or(false, |c| c.contains(Capability::ROLL_INTERRUPT));
|
|
|
|
if try_roll {
|
|
|
|
controller.inputs.move_dir = (tgt_data.pos.0 - self.pos.0)
|
|
|
|
.xy()
|
|
|
|
.try_normalized()
|
|
|
|
.unwrap_or_default();
|
|
|
|
controller.push_basic_input(InputKind::Roll);
|
|
|
|
}
|
|
|
|
try_roll
|
2022-10-08 22:38:19 +00:00
|
|
|
} else {
|
|
|
|
false
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
if matches!(self.char_state, CharacterState::Roll(_)) {
|
|
|
|
controller.push_basic_input(InputKind::Jump);
|
|
|
|
}
|
|
|
|
|
|
|
|
if self.energy.current() < DESIRED_ENERGY {
|
|
|
|
fallback_tactics(agent, controller);
|
|
|
|
} else if !in_stance(SwordStance::Mobility) {
|
|
|
|
controller.push_basic_input(InputKind::Ability(0));
|
|
|
|
} else if MOBILITY_AGILITY.could_use(self) && MOBILITY_AGILITY.use_desirable(self) {
|
|
|
|
controller.push_basic_input(InputKind::Ability(2));
|
2022-10-23 17:10:37 +00:00
|
|
|
} else if try_roll(controller) {
|
|
|
|
// Nothing here, rolling handled in try_roll function
|
2022-10-08 22:38:19 +00:00
|
|
|
} else if MOBILITY_FEINT.could_use(attack_data, self) && rng.gen::<f32>() < 0.3 {
|
|
|
|
controller.push_basic_input(InputKind::Ability(1));
|
|
|
|
advance(
|
|
|
|
agent,
|
|
|
|
controller,
|
|
|
|
MOBILITY_FEINT.max_range,
|
|
|
|
MOBILITY_FEINT.angle,
|
|
|
|
);
|
|
|
|
} else if MOBILITY_COMBO.could_use(attack_data, self) {
|
|
|
|
controller.push_basic_input(InputKind::Primary);
|
|
|
|
advance(
|
|
|
|
agent,
|
|
|
|
controller,
|
|
|
|
MOBILITY_COMBO.max_range,
|
|
|
|
MOBILITY_COMBO.angle,
|
|
|
|
);
|
|
|
|
} else {
|
|
|
|
advance(
|
|
|
|
agent,
|
|
|
|
controller,
|
|
|
|
MOBILITY_COMBO.max_range,
|
|
|
|
MOBILITY_COMBO.angle,
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2022-10-26 02:00:45 +00:00
|
|
|
const CONDITION_FEINT_DIR: usize = 1;
|
2022-10-26 00:30:33 +00:00
|
|
|
|
2022-10-08 22:38:19 +00:00
|
|
|
if rng.gen::<f32>() < read_data.dt.0 {
|
2022-10-26 00:30:33 +00:00
|
|
|
agent.action_state.conditions[CONDITION_FEINT_DIR] =
|
|
|
|
!agent.action_state.conditions[CONDITION_FEINT_DIR];
|
2022-10-08 22:38:19 +00:00
|
|
|
}
|
2022-10-26 00:30:33 +00:00
|
|
|
let dir = if agent.action_state.conditions[CONDITION_FEINT_DIR] {
|
2022-10-08 22:38:19 +00:00
|
|
|
1.0
|
|
|
|
} else {
|
|
|
|
-1.0
|
|
|
|
};
|
|
|
|
controller.inputs.move_dir.rotated_z(PI / 4.0 * dir);
|
2022-09-14 01:58:53 +00:00
|
|
|
},
|
|
|
|
SwordStance::Crippling => {
|
2022-10-09 00:45:37 +00:00
|
|
|
const CRIPPLING_COMBO: ComboMeleeData = ComboMeleeData {
|
|
|
|
min_range: 0.0,
|
|
|
|
max_range: 2.5,
|
|
|
|
angle: 35.0,
|
2022-10-20 19:49:57 +00:00
|
|
|
energy: 5.0,
|
2022-10-09 00:45:37 +00:00
|
|
|
};
|
|
|
|
const CRIPPLING_GOUGE: ComboMeleeData = ComboMeleeData {
|
|
|
|
min_range: 0.0,
|
|
|
|
max_range: 3.0,
|
|
|
|
angle: 35.0,
|
2022-10-20 19:49:57 +00:00
|
|
|
energy: 25.0,
|
2022-10-09 00:45:37 +00:00
|
|
|
};
|
|
|
|
const CRIPPLING_STRIKE: ComboMeleeData = ComboMeleeData {
|
|
|
|
min_range: 0.0,
|
|
|
|
max_range: 3.0,
|
|
|
|
angle: 35.0,
|
2022-10-20 19:49:57 +00:00
|
|
|
energy: 25.0,
|
2022-10-09 00:45:37 +00:00
|
|
|
};
|
|
|
|
const CRIPPLING_FINISHER: FinisherMeleeData = FinisherMeleeData {
|
|
|
|
range: 2.5,
|
|
|
|
angle: 10.0,
|
|
|
|
energy: 40.0,
|
|
|
|
combo: 10,
|
|
|
|
};
|
|
|
|
const DESIRED_ENERGY: f32 = 50.0;
|
|
|
|
|
|
|
|
if self.energy.current() < DESIRED_ENERGY {
|
|
|
|
fallback_tactics(agent, controller);
|
|
|
|
} else if !in_stance(SwordStance::Crippling) {
|
|
|
|
controller.push_basic_input(InputKind::Ability(0));
|
|
|
|
} else if CRIPPLING_FINISHER.could_use(attack_data, self)
|
|
|
|
&& CRIPPLING_FINISHER.use_desirable(tgt_data, self)
|
|
|
|
{
|
|
|
|
controller.push_basic_input(InputKind::Ability(1));
|
|
|
|
advance(
|
|
|
|
agent,
|
|
|
|
controller,
|
|
|
|
CRIPPLING_FINISHER.range,
|
|
|
|
CRIPPLING_FINISHER.angle,
|
|
|
|
);
|
|
|
|
} else if tgt_data
|
|
|
|
.buffs
|
|
|
|
.map_or(false, |buffs| !buffs.contains(BuffKind::Crippled))
|
|
|
|
&& CRIPPLING_STRIKE.could_use(attack_data, self)
|
|
|
|
{
|
|
|
|
controller.push_basic_input(InputKind::Ability(2));
|
|
|
|
advance(
|
|
|
|
agent,
|
|
|
|
controller,
|
|
|
|
CRIPPLING_STRIKE.max_range,
|
|
|
|
CRIPPLING_STRIKE.angle,
|
|
|
|
);
|
|
|
|
} else if CRIPPLING_GOUGE.could_use(attack_data, self) && rng.gen::<f32>() < 0.3 {
|
|
|
|
controller.push_basic_input(InputKind::Ability(3));
|
|
|
|
advance(
|
|
|
|
agent,
|
|
|
|
controller,
|
|
|
|
CRIPPLING_GOUGE.max_range,
|
|
|
|
CRIPPLING_GOUGE.angle,
|
|
|
|
);
|
|
|
|
} else if CRIPPLING_COMBO.could_use(attack_data, self) {
|
|
|
|
controller.push_basic_input(InputKind::Primary);
|
|
|
|
advance(
|
|
|
|
agent,
|
|
|
|
controller,
|
|
|
|
CRIPPLING_COMBO.max_range,
|
|
|
|
CRIPPLING_COMBO.angle,
|
|
|
|
);
|
|
|
|
} else {
|
|
|
|
advance(
|
|
|
|
agent,
|
|
|
|
controller,
|
|
|
|
CRIPPLING_COMBO.max_range,
|
|
|
|
CRIPPLING_COMBO.angle,
|
|
|
|
);
|
|
|
|
}
|
2022-09-14 01:58:53 +00:00
|
|
|
},
|
|
|
|
SwordStance::Cleaving => {
|
2022-10-09 07:17:29 +00:00
|
|
|
// TODO: Rewrite cleaving stance tactics when agents can consider multiple
|
|
|
|
// targets at once. Remove hack to make cleaving AI appear less frequently above
|
|
|
|
// when doing so.
|
|
|
|
const CLEAVING_COMBO: ComboMeleeData = ComboMeleeData {
|
|
|
|
min_range: 0.0,
|
|
|
|
max_range: 2.5,
|
|
|
|
angle: 35.0,
|
2022-10-20 19:49:57 +00:00
|
|
|
energy: 6.0,
|
2022-10-09 07:17:29 +00:00
|
|
|
};
|
|
|
|
const CLEAVING_FINISHER: FinisherMeleeData = FinisherMeleeData {
|
|
|
|
range: 2.0,
|
|
|
|
angle: 10.0,
|
|
|
|
energy: 40.0,
|
|
|
|
combo: 10,
|
|
|
|
};
|
|
|
|
const CLEAVING_SPIN: ComboMeleeData = ComboMeleeData {
|
|
|
|
min_range: 0.0,
|
|
|
|
max_range: 5.0,
|
|
|
|
angle: 360.0,
|
2022-10-20 19:49:57 +00:00
|
|
|
energy: 20.0,
|
2022-10-09 07:17:29 +00:00
|
|
|
};
|
|
|
|
const CLEAVING_DIVE: DiveMeleeData = DiveMeleeData {
|
|
|
|
range: 5.0,
|
|
|
|
angle: 10.0,
|
2022-10-20 19:49:57 +00:00
|
|
|
energy: 25.0,
|
2022-10-09 07:17:29 +00:00
|
|
|
};
|
|
|
|
const DESIRED_ENERGY: f32 = 50.0;
|
|
|
|
|
|
|
|
// TODO: Remove when agents actually have multiple targets
|
|
|
|
// Hacky check for multiple nearby melee range targets
|
|
|
|
let are_nearby_targets = || {
|
|
|
|
if let Some(health) = self.health {
|
|
|
|
health
|
|
|
|
.recent_damagers()
|
|
|
|
.filter(|(_, time)| read_data.time.0 - time.0 < 5.0)
|
|
|
|
.filter_map(|(uid, _)| {
|
|
|
|
read_data.uid_allocator.retrieve_entity_internal(uid.0)
|
|
|
|
})
|
|
|
|
.filter(|e| {
|
|
|
|
read_data.positions.get(*e).map_or(false, |pos| {
|
|
|
|
pos.0.distance_squared(self.pos.0) < 10_f32.powi(2)
|
|
|
|
})
|
|
|
|
})
|
|
|
|
.count()
|
|
|
|
> 1
|
|
|
|
} else {
|
|
|
|
false
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2022-10-26 02:00:45 +00:00
|
|
|
const CONDITION_SELF_ROLLING: usize = 1;
|
|
|
|
const TIMER_DIVE_TIMEOUT: usize = 1;
|
2022-10-26 00:30:33 +00:00
|
|
|
|
2022-10-09 07:17:29 +00:00
|
|
|
if matches!(self.char_state, CharacterState::Roll(_)) {
|
2022-10-26 00:30:33 +00:00
|
|
|
agent.action_state.conditions[CONDITION_SELF_ROLLING] = true;
|
2022-10-09 07:17:29 +00:00
|
|
|
}
|
|
|
|
|
2022-10-26 00:30:33 +00:00
|
|
|
if agent.action_state.conditions[CONDITION_SELF_ROLLING] {
|
2022-10-09 07:17:29 +00:00
|
|
|
if self.physics_state.on_ground.is_some() {
|
|
|
|
controller.push_basic_input(InputKind::Jump);
|
|
|
|
} else {
|
|
|
|
controller.push_basic_input(InputKind::Ability(3));
|
|
|
|
}
|
2022-10-26 00:30:33 +00:00
|
|
|
agent.action_state.timers[TIMER_DIVE_TIMEOUT] += read_data.dt.0;
|
|
|
|
if agent.action_state.timers[TIMER_DIVE_TIMEOUT] > 2.0 {
|
|
|
|
agent.action_state.timers[TIMER_DIVE_TIMEOUT] = 0.0;
|
|
|
|
agent.action_state.conditions[CONDITION_SELF_ROLLING] = false;
|
2022-10-09 07:17:29 +00:00
|
|
|
}
|
|
|
|
advance(agent, controller, CLEAVING_DIVE.range, CLEAVING_DIVE.angle);
|
|
|
|
} else if self.energy.current() < DESIRED_ENERGY {
|
|
|
|
fallback_tactics(agent, controller);
|
|
|
|
} else if !in_stance(SwordStance::Cleaving) {
|
|
|
|
controller.push_basic_input(InputKind::Ability(0));
|
|
|
|
} else if CLEAVING_FINISHER.could_use(attack_data, self)
|
|
|
|
&& CLEAVING_FINISHER.use_desirable(tgt_data, self)
|
|
|
|
{
|
|
|
|
controller.push_basic_input(InputKind::Ability(1));
|
|
|
|
advance(
|
|
|
|
agent,
|
|
|
|
controller,
|
|
|
|
CLEAVING_FINISHER.range,
|
|
|
|
CLEAVING_FINISHER.angle,
|
|
|
|
);
|
|
|
|
} else if CLEAVING_SPIN.could_use(attack_data, self) && are_nearby_targets() {
|
|
|
|
controller.push_basic_input(InputKind::Ability(2));
|
|
|
|
advance(
|
|
|
|
agent,
|
|
|
|
controller,
|
|
|
|
CLEAVING_SPIN.max_range,
|
|
|
|
CLEAVING_SPIN.angle,
|
|
|
|
);
|
|
|
|
} else if CLEAVING_DIVE.npc_should_use_hack(self, tgt_data) {
|
|
|
|
controller.push_basic_input(InputKind::Roll);
|
|
|
|
advance(agent, controller, CLEAVING_DIVE.range, CLEAVING_DIVE.angle);
|
|
|
|
} else if CLEAVING_COMBO.could_use(attack_data, self) {
|
|
|
|
controller.push_basic_input(InputKind::Primary);
|
|
|
|
advance(
|
|
|
|
agent,
|
|
|
|
controller,
|
|
|
|
CLEAVING_COMBO.max_range,
|
|
|
|
CLEAVING_COMBO.angle,
|
|
|
|
);
|
|
|
|
} else {
|
|
|
|
advance(
|
|
|
|
agent,
|
|
|
|
controller,
|
|
|
|
CLEAVING_COMBO.max_range,
|
|
|
|
CLEAVING_COMBO.angle,
|
|
|
|
);
|
|
|
|
}
|
2022-09-14 01:58:53 +00:00
|
|
|
},
|
|
|
|
SwordStance::Parrying => {
|
2022-10-12 19:19:52 +00:00
|
|
|
const PARRYING_COMBO: ComboMeleeData = ComboMeleeData {
|
|
|
|
min_range: 0.0,
|
|
|
|
max_range: 2.5,
|
|
|
|
angle: 35.0,
|
2022-10-20 19:49:57 +00:00
|
|
|
energy: 6.0,
|
2022-10-12 19:19:52 +00:00
|
|
|
};
|
|
|
|
const PARRYING_PARRY: BlockData = BlockData {
|
|
|
|
angle: 40.0,
|
|
|
|
range: 5.0,
|
|
|
|
energy: 10.0,
|
|
|
|
};
|
|
|
|
const PARRYING_RIPOSTE: BlockData = BlockData {
|
|
|
|
angle: 15.0,
|
|
|
|
range: 3.5,
|
2022-10-20 19:49:57 +00:00
|
|
|
energy: 20.0,
|
2022-10-12 19:19:52 +00:00
|
|
|
};
|
|
|
|
const PARRYING_COUNTER: ComboMeleeData = ComboMeleeData {
|
|
|
|
min_range: 0.0,
|
|
|
|
max_range: 2.5,
|
|
|
|
angle: 25.0,
|
|
|
|
energy: 15.0,
|
|
|
|
};
|
|
|
|
const DESIRED_ENERGY: f32 = 50.0;
|
|
|
|
|
|
|
|
let try_parry = |pregen_rng_1: f32| {
|
|
|
|
if let Some(char_state) = tgt_data.char_state {
|
|
|
|
matches!(char_state.stage_section(), Some(StageSection::Buildup))
|
|
|
|
&& char_state.is_melee_attack()
|
|
|
|
&& char_state
|
|
|
|
.timer()
|
|
|
|
.map_or(false, |timer| pregen_rng_1 < timer.as_secs_f32() * 4.0)
|
|
|
|
} else {
|
|
|
|
false
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
if self.energy.current() < DESIRED_ENERGY {
|
|
|
|
fallback_tactics(agent, controller);
|
|
|
|
} else if !in_stance(SwordStance::Parrying) {
|
|
|
|
controller.push_basic_input(InputKind::Ability(0));
|
|
|
|
} else if matches!(
|
|
|
|
tgt_data.char_state.and_then(|cs| cs.stage_section()),
|
|
|
|
Some(StageSection::Buildup)
|
|
|
|
) && PARRYING_COUNTER.could_use(attack_data, self)
|
|
|
|
&& rng.gen::<f32>() < self.health.map_or(0.0, |h| h.fraction())
|
|
|
|
{
|
|
|
|
controller.push_basic_input(InputKind::Ability(3));
|
|
|
|
advance(
|
|
|
|
agent,
|
|
|
|
controller,
|
|
|
|
PARRYING_COUNTER.max_range,
|
|
|
|
PARRYING_COUNTER.angle,
|
|
|
|
);
|
|
|
|
} else if try_parry(rng.gen::<f32>()) {
|
|
|
|
if PARRYING_RIPOSTE.could_use(attack_data, self)
|
|
|
|
&& rng.gen::<f32>() > self.health.map_or(0.0, |h| h.fraction())
|
|
|
|
{
|
|
|
|
controller.push_basic_input(InputKind::Ability(2));
|
|
|
|
advance(
|
|
|
|
agent,
|
|
|
|
controller,
|
|
|
|
PARRYING_RIPOSTE.range,
|
|
|
|
PARRYING_RIPOSTE.angle,
|
|
|
|
);
|
|
|
|
} else if PARRYING_PARRY.could_use(attack_data, self) {
|
|
|
|
controller.push_basic_input(InputKind::Ability(1));
|
|
|
|
advance(
|
|
|
|
agent,
|
|
|
|
controller,
|
|
|
|
PARRYING_PARRY.range,
|
|
|
|
PARRYING_PARRY.angle,
|
|
|
|
);
|
|
|
|
} else if PARRYING_COMBO.could_use(attack_data, self) {
|
|
|
|
controller.push_basic_input(InputKind::Primary);
|
|
|
|
advance(
|
|
|
|
agent,
|
|
|
|
controller,
|
|
|
|
PARRYING_COMBO.max_range,
|
|
|
|
PARRYING_COMBO.angle,
|
|
|
|
);
|
2022-10-22 19:59:25 +00:00
|
|
|
} else {
|
2022-10-12 19:19:52 +00:00
|
|
|
advance(
|
|
|
|
agent,
|
|
|
|
controller,
|
|
|
|
PARRYING_COMBO.max_range,
|
|
|
|
PARRYING_COMBO.angle,
|
|
|
|
);
|
|
|
|
}
|
2022-10-22 19:59:25 +00:00
|
|
|
} else if PARRYING_COMBO.could_use(attack_data, self) {
|
|
|
|
controller.push_basic_input(InputKind::Primary);
|
|
|
|
advance(
|
|
|
|
agent,
|
|
|
|
controller,
|
|
|
|
PARRYING_COMBO.max_range,
|
|
|
|
PARRYING_COMBO.angle,
|
|
|
|
);
|
2022-10-12 19:19:52 +00:00
|
|
|
} else {
|
|
|
|
advance(
|
|
|
|
agent,
|
|
|
|
controller,
|
|
|
|
PARRYING_COMBO.max_range,
|
|
|
|
PARRYING_COMBO.angle,
|
|
|
|
);
|
|
|
|
}
|
2022-09-14 01:58:53 +00:00
|
|
|
},
|
|
|
|
SwordStance::Heavy => {
|
2022-10-12 19:53:26 +00:00
|
|
|
const HEAVY_COMBO: ComboMeleeData = ComboMeleeData {
|
|
|
|
min_range: 0.0,
|
|
|
|
max_range: 2.5,
|
|
|
|
angle: 35.0,
|
|
|
|
energy: 5.0,
|
|
|
|
};
|
|
|
|
const HEAVY_FINISHER: FinisherMeleeData = FinisherMeleeData {
|
|
|
|
range: 2.5,
|
|
|
|
angle: 10.0,
|
|
|
|
energy: 40.0,
|
|
|
|
combo: 10,
|
|
|
|
};
|
|
|
|
const HEAVY_POMMELSTRIKE: ComboMeleeData = ComboMeleeData {
|
|
|
|
min_range: 0.0,
|
|
|
|
max_range: 3.0,
|
|
|
|
angle: 3.0,
|
2022-10-20 19:49:57 +00:00
|
|
|
energy: 15.0,
|
2022-10-12 19:53:26 +00:00
|
|
|
};
|
|
|
|
const HEAVY_FORTITUDE: SelfBuffData = SelfBuffData {
|
|
|
|
buff: BuffKind::Fortitude,
|
|
|
|
energy: 40.0,
|
|
|
|
};
|
|
|
|
const DESIRED_ENERGY: f32 = 50.0;
|
|
|
|
|
2022-10-26 02:00:45 +00:00
|
|
|
const CONDITION_POISE_DMG: usize = 1;
|
|
|
|
const TIMER_POMMELSTRIKE: usize = 1;
|
2022-10-26 00:30:33 +00:00
|
|
|
|
|
|
|
agent.action_state.conditions[CONDITION_POISE_DMG] = self
|
2022-10-12 19:53:26 +00:00
|
|
|
.poise
|
|
|
|
.map_or(false, |p| p.current() < p.maximum() * 0.8);
|
|
|
|
if matches!(
|
|
|
|
self.char_state.ability_info().and_then(|info| info.ability),
|
|
|
|
Some(Ability::MainWeaponAux(21))
|
|
|
|
) {
|
2022-10-26 00:30:33 +00:00
|
|
|
agent.action_state.timers[TIMER_POMMELSTRIKE] = 0.0;
|
2022-10-12 19:53:26 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if self.energy.current() < DESIRED_ENERGY {
|
|
|
|
fallback_tactics(agent, controller);
|
|
|
|
} else if !in_stance(SwordStance::Heavy) {
|
|
|
|
controller.push_basic_input(InputKind::Ability(0));
|
|
|
|
} else if HEAVY_FORTITUDE.could_use(self)
|
|
|
|
&& HEAVY_FORTITUDE.use_desirable(self)
|
2022-10-26 00:30:33 +00:00
|
|
|
&& agent.action_state.conditions[CONDITION_POISE_DMG]
|
2022-10-12 19:53:26 +00:00
|
|
|
{
|
|
|
|
controller.push_basic_input(InputKind::Ability(3));
|
|
|
|
} else if HEAVY_FINISHER.could_use(attack_data, self)
|
|
|
|
&& HEAVY_FINISHER.use_desirable(tgt_data, self)
|
|
|
|
{
|
|
|
|
controller.push_basic_input(InputKind::Ability(1));
|
|
|
|
advance(
|
|
|
|
agent,
|
|
|
|
controller,
|
|
|
|
HEAVY_FINISHER.range,
|
|
|
|
HEAVY_FINISHER.angle,
|
|
|
|
);
|
|
|
|
} else if HEAVY_POMMELSTRIKE.could_use(attack_data, self)
|
|
|
|
&& rng.gen::<f32>()
|
2022-10-26 00:30:33 +00:00
|
|
|
< agent.action_state.timers[TIMER_POMMELSTRIKE]
|
|
|
|
* self.poise.map_or(0.5, |p| p.fraction())
|
2022-10-12 19:53:26 +00:00
|
|
|
{
|
|
|
|
controller.push_basic_input(InputKind::Ability(2));
|
|
|
|
advance(
|
|
|
|
agent,
|
|
|
|
controller,
|
|
|
|
HEAVY_POMMELSTRIKE.max_range,
|
|
|
|
HEAVY_POMMELSTRIKE.angle,
|
|
|
|
);
|
|
|
|
} else if HEAVY_COMBO.could_use(attack_data, self) {
|
|
|
|
controller.push_basic_input(InputKind::Primary);
|
|
|
|
advance(agent, controller, HEAVY_COMBO.max_range, HEAVY_COMBO.angle);
|
2022-10-26 00:30:33 +00:00
|
|
|
agent.action_state.timers[TIMER_POMMELSTRIKE] += read_data.dt.0;
|
2022-10-12 19:53:26 +00:00
|
|
|
} else {
|
|
|
|
advance(agent, controller, HEAVY_COMBO.max_range, HEAVY_COMBO.angle);
|
|
|
|
}
|
2022-09-14 01:58:53 +00:00
|
|
|
},
|
|
|
|
SwordStance::Reaching => {
|
2022-10-20 17:12:17 +00:00
|
|
|
const REACHING_COMBO: ComboMeleeData = ComboMeleeData {
|
|
|
|
min_range: 0.0,
|
|
|
|
max_range: 4.5,
|
|
|
|
angle: 6.0,
|
|
|
|
energy: 5.0,
|
|
|
|
};
|
|
|
|
const REACHING_CHARGE: DashMeleeData = DashMeleeData {
|
|
|
|
range: 3.5,
|
|
|
|
angle: 12.5,
|
|
|
|
initial_energy: 10.0,
|
|
|
|
energy_drain: 20.0,
|
|
|
|
speed: 3.0,
|
|
|
|
charge_dur: 1.0,
|
|
|
|
};
|
|
|
|
const REACHING_FLURRY: RapidMeleeData = RapidMeleeData {
|
|
|
|
range: 4.0,
|
|
|
|
angle: 7.5,
|
|
|
|
energy: 10.0,
|
|
|
|
strikes: 6,
|
|
|
|
};
|
|
|
|
const REACHING_SKEWER: ComboMeleeData = ComboMeleeData {
|
|
|
|
min_range: 1.0,
|
|
|
|
max_range: 7.5,
|
|
|
|
angle: 7.5,
|
2022-10-20 19:49:57 +00:00
|
|
|
energy: 15.0,
|
2022-10-20 17:12:17 +00:00
|
|
|
};
|
|
|
|
const DESIRED_ENERGY: f32 = 50.0;
|
|
|
|
|
|
|
|
if self.energy.current() < DESIRED_ENERGY {
|
|
|
|
fallback_tactics(agent, controller);
|
|
|
|
} else if !in_stance(SwordStance::Reaching) {
|
|
|
|
controller.push_basic_input(InputKind::Ability(0));
|
|
|
|
} else if matches!(self.char_state, CharacterState::DashMelee(s) if s.stage_section != StageSection::Recover)
|
|
|
|
|| (REACHING_CHARGE.could_use(attack_data, self)
|
|
|
|
&& REACHING_CHARGE.use_desirable(attack_data, self))
|
|
|
|
{
|
|
|
|
controller.push_basic_input(InputKind::Ability(1));
|
|
|
|
advance(
|
|
|
|
agent,
|
|
|
|
controller,
|
|
|
|
REACHING_CHARGE.range,
|
|
|
|
REACHING_CHARGE.angle,
|
|
|
|
);
|
|
|
|
} else if REACHING_FLURRY.could_use(attack_data, self)
|
|
|
|
&& matches!(
|
|
|
|
tgt_data.char_state.and_then(|cs| cs.stage_section()),
|
|
|
|
Some(StageSection::Buildup)
|
|
|
|
)
|
|
|
|
&& rng.gen::<f32>() < 0.5
|
|
|
|
{
|
|
|
|
controller.push_basic_input(InputKind::Ability(2));
|
|
|
|
advance(
|
|
|
|
agent,
|
|
|
|
controller,
|
|
|
|
REACHING_FLURRY.range,
|
|
|
|
REACHING_FLURRY.angle,
|
|
|
|
);
|
|
|
|
} else if REACHING_SKEWER.could_use(attack_data, self) && rng.gen::<f32>() < 0.33 {
|
|
|
|
controller.push_basic_input(InputKind::Ability(3));
|
|
|
|
advance(
|
|
|
|
agent,
|
|
|
|
controller,
|
|
|
|
REACHING_SKEWER.max_range,
|
|
|
|
REACHING_SKEWER.angle,
|
|
|
|
);
|
|
|
|
} else if REACHING_COMBO.could_use(attack_data, self) {
|
|
|
|
controller.push_basic_input(InputKind::Primary);
|
|
|
|
advance(
|
|
|
|
agent,
|
|
|
|
controller,
|
|
|
|
REACHING_COMBO.max_range,
|
|
|
|
REACHING_COMBO.angle,
|
|
|
|
);
|
|
|
|
} else {
|
|
|
|
advance(
|
|
|
|
agent,
|
|
|
|
controller,
|
|
|
|
REACHING_COMBO.max_range,
|
|
|
|
REACHING_COMBO.angle,
|
|
|
|
);
|
|
|
|
}
|
2022-09-14 01:58:53 +00:00
|
|
|
},
|
|
|
|
}
|
2022-10-09 00:45:37 +00:00
|
|
|
|
|
|
|
if self.active_abilities.auxiliary_sets.is_empty() {
|
|
|
|
agent.action_state.initialized = false;
|
|
|
|
}
|
2022-01-18 03:02:43 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
pub fn handle_bow_attack(
|
|
|
|
&self,
|
|
|
|
agent: &mut Agent,
|
|
|
|
controller: &mut Controller,
|
|
|
|
attack_data: &AttackData,
|
|
|
|
tgt_data: &TargetData,
|
|
|
|
read_data: &ReadData,
|
|
|
|
rng: &mut impl Rng,
|
|
|
|
) {
|
|
|
|
const MIN_CHARGE_FRAC: f32 = 0.5;
|
|
|
|
const OPTIMAL_TARGET_VELOCITY: f32 = 5.0;
|
|
|
|
const DESIRED_ENERGY_LEVEL: f32 = 50.0;
|
2022-04-22 01:32:56 +00:00
|
|
|
|
2022-04-26 23:34:29 +00:00
|
|
|
let line_of_sight_with_target = || {
|
|
|
|
entities_have_line_of_sight(self.pos, self.body, tgt_data.pos, tgt_data.body, read_data)
|
2022-04-24 20:15:30 +00:00
|
|
|
};
|
2022-04-22 01:32:56 +00:00
|
|
|
|
2022-01-18 03:02:43 +00:00
|
|
|
// Logic to use abilities
|
|
|
|
if let CharacterState::ChargedRanged(c) = self.char_state {
|
|
|
|
if !matches!(c.stage_section, StageSection::Recover) {
|
|
|
|
// Don't even bother with this logic if in recover
|
|
|
|
let target_speed_sqd = agent
|
|
|
|
.target
|
|
|
|
.as_ref()
|
|
|
|
.map(|t| t.target)
|
|
|
|
.and_then(|e| read_data.velocities.get(e))
|
|
|
|
.map_or(0.0, |v| v.0.magnitude_squared());
|
|
|
|
if c.charge_frac() < MIN_CHARGE_FRAC
|
|
|
|
|| (target_speed_sqd > OPTIMAL_TARGET_VELOCITY.powi(2) && c.charge_frac() < 1.0)
|
|
|
|
{
|
|
|
|
// If haven't charged to desired level, or target is moving too fast and haven't
|
|
|
|
// fully charged, keep charging
|
2022-01-26 19:09:59 +00:00
|
|
|
controller.push_basic_input(InputKind::Primary);
|
2022-01-18 03:02:43 +00:00
|
|
|
}
|
|
|
|
// Else don't send primary input to release the shot
|
|
|
|
}
|
|
|
|
} else if matches!(self.char_state, CharacterState::RepeaterRanged(c) if self.energy.current() > 5.0 && !matches!(c.stage_section, StageSection::Recover))
|
|
|
|
{
|
|
|
|
// If in repeater ranged, have enough energy, and aren't in recovery, try to
|
|
|
|
// keep firing
|
|
|
|
if attack_data.dist_sqrd > attack_data.min_attack_dist.powi(2)
|
2022-04-26 23:34:29 +00:00
|
|
|
&& line_of_sight_with_target()
|
2022-01-18 03:02:43 +00:00
|
|
|
{
|
|
|
|
// Only keep firing if not in melee range or if can see target
|
2022-01-26 19:09:59 +00:00
|
|
|
controller.push_basic_input(InputKind::Secondary);
|
2022-01-18 03:02:43 +00:00
|
|
|
}
|
|
|
|
} else if attack_data.dist_sqrd < (2.0 * attack_data.min_attack_dist).powi(2) {
|
|
|
|
if self
|
|
|
|
.skill_set
|
|
|
|
.has_skill(Skill::Bow(BowSkill::UnlockShotgun))
|
|
|
|
&& self.energy.current() > 45.0
|
|
|
|
&& rng.gen_bool(0.5)
|
|
|
|
{
|
|
|
|
// Use shotgun if target close and have sufficient energy
|
2022-01-26 19:09:59 +00:00
|
|
|
controller.push_basic_input(InputKind::Ability(0));
|
2022-01-18 03:02:43 +00:00
|
|
|
} else if self.body.map(|b| b.is_humanoid()).unwrap_or(false)
|
2022-10-24 01:12:59 +00:00
|
|
|
&& self.energy.current() > CharacterAbility::default_roll().energy_cost()
|
2022-01-18 03:02:43 +00:00
|
|
|
&& !matches!(self.char_state, CharacterState::BasicRanged(c) if !matches!(c.stage_section, StageSection::Recover))
|
|
|
|
{
|
|
|
|
// Else roll away if can roll and have enough energy, and not using shotgun
|
|
|
|
// (other 2 attacks have interrupt handled above) unless in recover
|
2022-01-26 19:09:59 +00:00
|
|
|
controller.push_basic_input(InputKind::Roll);
|
2022-01-18 03:02:43 +00:00
|
|
|
} else {
|
2022-02-25 20:35:35 +00:00
|
|
|
self.path_toward_target(
|
|
|
|
agent,
|
|
|
|
controller,
|
2022-03-01 21:54:44 +00:00
|
|
|
tgt_data.pos.0,
|
2022-02-25 20:35:35 +00:00
|
|
|
read_data,
|
|
|
|
Path::Separate,
|
|
|
|
None,
|
|
|
|
);
|
2022-01-18 03:02:43 +00:00
|
|
|
if attack_data.angle < 15.0 {
|
2022-01-26 19:09:59 +00:00
|
|
|
controller.push_basic_input(InputKind::Primary);
|
2022-01-18 03:02:43 +00:00
|
|
|
}
|
|
|
|
}
|
2022-04-26 23:34:29 +00:00
|
|
|
} else if attack_data.dist_sqrd < MAX_PATH_DIST.powi(2) && line_of_sight_with_target() {
|
2022-01-18 03:02:43 +00:00
|
|
|
// If not really far, and can see target, attempt to shoot bow
|
|
|
|
if self.energy.current() < DESIRED_ENERGY_LEVEL {
|
|
|
|
// If low on energy, use primary to attempt to regen energy
|
2022-01-26 19:09:59 +00:00
|
|
|
controller.push_basic_input(InputKind::Primary);
|
2022-01-18 03:02:43 +00:00
|
|
|
} else {
|
|
|
|
// Else we have enough energy, use repeater
|
2022-01-26 19:09:59 +00:00
|
|
|
controller.push_basic_input(InputKind::Secondary);
|
2022-01-18 03:02:43 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
// Logic to move. Intentionally kept separate from ability logic so duplicated
|
|
|
|
// work is less necessary.
|
|
|
|
if attack_data.dist_sqrd < (2.0 * attack_data.min_attack_dist).powi(2) {
|
|
|
|
// Attempt to move away from target if too close
|
|
|
|
if let Some((bearing, speed)) = agent.chaser.chase(
|
|
|
|
&*read_data.terrain,
|
|
|
|
self.pos.0,
|
|
|
|
self.vel.0,
|
2022-03-01 21:54:44 +00:00
|
|
|
tgt_data.pos.0,
|
2022-01-18 03:02:43 +00:00
|
|
|
TraversalConfig {
|
|
|
|
min_tgt_dist: 1.25,
|
|
|
|
..self.traversal_config
|
|
|
|
},
|
|
|
|
) {
|
|
|
|
controller.inputs.move_dir =
|
|
|
|
-bearing.xy().try_normalized().unwrap_or_else(Vec2::zero) * speed;
|
|
|
|
}
|
|
|
|
} else if attack_data.dist_sqrd < MAX_PATH_DIST.powi(2) {
|
|
|
|
// Else attempt to circle target if neither too close nor too far
|
|
|
|
if let Some((bearing, speed)) = agent.chaser.chase(
|
|
|
|
&*read_data.terrain,
|
|
|
|
self.pos.0,
|
|
|
|
self.vel.0,
|
2022-03-01 21:54:44 +00:00
|
|
|
tgt_data.pos.0,
|
2022-01-18 03:02:43 +00:00
|
|
|
TraversalConfig {
|
|
|
|
min_tgt_dist: 1.25,
|
|
|
|
..self.traversal_config
|
|
|
|
},
|
|
|
|
) {
|
2022-04-26 23:34:29 +00:00
|
|
|
if line_of_sight_with_target() && attack_data.angle < 45.0 {
|
2022-01-18 03:02:43 +00:00
|
|
|
controller.inputs.move_dir = bearing
|
|
|
|
.xy()
|
|
|
|
.rotated_z(rng.gen_range(0.5..1.57))
|
|
|
|
.try_normalized()
|
|
|
|
.unwrap_or_else(Vec2::zero)
|
|
|
|
* speed;
|
|
|
|
} else {
|
|
|
|
// Unless cannot see target, then move towards them
|
|
|
|
controller.inputs.move_dir =
|
|
|
|
bearing.xy().try_normalized().unwrap_or_else(Vec2::zero) * speed;
|
2022-03-30 21:54:03 +00:00
|
|
|
self.jump_if(bearing.z > 1.5, controller);
|
2022-01-18 03:02:43 +00:00
|
|
|
controller.inputs.move_z = bearing.z;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// Sometimes try to roll
|
|
|
|
if self.body.map(|b| b.is_humanoid()).unwrap_or(false)
|
|
|
|
&& attack_data.dist_sqrd < 16.0f32.powi(2)
|
|
|
|
&& rng.gen::<f32>() < 0.01
|
|
|
|
{
|
2022-01-26 19:09:59 +00:00
|
|
|
controller.push_basic_input(InputKind::Roll);
|
2022-01-18 03:02:43 +00:00
|
|
|
}
|
|
|
|
} else {
|
|
|
|
// If too far, move towards target
|
2022-03-01 21:54:44 +00:00
|
|
|
self.path_toward_target(
|
|
|
|
agent,
|
|
|
|
controller,
|
|
|
|
tgt_data.pos.0,
|
|
|
|
read_data,
|
|
|
|
Path::Partial,
|
|
|
|
None,
|
|
|
|
);
|
2022-01-18 03:02:43 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn handle_staff_attack(
|
|
|
|
&self,
|
|
|
|
agent: &mut Agent,
|
|
|
|
controller: &mut Controller,
|
|
|
|
attack_data: &AttackData,
|
|
|
|
tgt_data: &TargetData,
|
|
|
|
read_data: &ReadData,
|
|
|
|
rng: &mut impl Rng,
|
|
|
|
) {
|
2022-10-17 02:48:27 +00:00
|
|
|
enum ActionStateConditions {
|
|
|
|
ConditionStaffCanShockwave = 0,
|
|
|
|
}
|
2022-10-23 17:10:37 +00:00
|
|
|
let context = AbilityContext::try_from(Some(self.char_state));
|
2022-01-18 03:02:43 +00:00
|
|
|
let extract_ability = |input: AbilityInput| {
|
|
|
|
self.active_abilities
|
2022-10-22 18:36:41 +00:00
|
|
|
.activate_ability(
|
|
|
|
input,
|
|
|
|
Some(self.inventory),
|
|
|
|
self.skill_set,
|
|
|
|
self.body,
|
|
|
|
context,
|
|
|
|
)
|
2022-01-18 03:02:43 +00:00
|
|
|
.unwrap_or_default()
|
|
|
|
.0
|
|
|
|
};
|
|
|
|
let (flamethrower, shockwave) = (
|
|
|
|
extract_ability(AbilityInput::Secondary),
|
|
|
|
extract_ability(AbilityInput::Auxiliary(0)),
|
|
|
|
);
|
|
|
|
let flamethrower_range = match flamethrower {
|
|
|
|
CharacterAbility::BasicBeam { range, .. } => range,
|
|
|
|
_ => 20.0_f32,
|
|
|
|
};
|
2022-10-24 01:12:59 +00:00
|
|
|
let shockwave_cost = shockwave.energy_cost();
|
2022-01-18 03:02:43 +00:00
|
|
|
if self.body.map_or(false, |b| b.is_humanoid())
|
|
|
|
&& attack_data.in_min_range()
|
2022-10-24 01:12:59 +00:00
|
|
|
&& self.energy.current() > CharacterAbility::default_roll().energy_cost()
|
2022-01-18 03:02:43 +00:00
|
|
|
&& !matches!(self.char_state, CharacterState::Shockwave(_))
|
|
|
|
{
|
|
|
|
// if a humanoid, have enough stamina, not in shockwave, and in melee range,
|
|
|
|
// emergency roll
|
2022-01-26 19:09:59 +00:00
|
|
|
controller.push_basic_input(InputKind::Roll);
|
2022-01-18 03:02:43 +00:00
|
|
|
} else if matches!(self.char_state, CharacterState::Shockwave(_)) {
|
2022-10-25 02:52:37 +00:00
|
|
|
agent.action_state.conditions
|
|
|
|
[ActionStateConditions::ConditionStaffCanShockwave as usize] = false;
|
|
|
|
} else if agent.action_state.conditions
|
|
|
|
[ActionStateConditions::ConditionStaffCanShockwave as usize]
|
2022-01-18 03:02:43 +00:00
|
|
|
&& matches!(self.char_state, CharacterState::Wielding(_))
|
|
|
|
{
|
2022-01-26 19:09:59 +00:00
|
|
|
controller.push_basic_input(InputKind::Ability(0));
|
2022-01-18 03:02:43 +00:00
|
|
|
} else if !matches!(self.char_state, CharacterState::Shockwave(c) if !matches!(c.stage_section, StageSection::Recover))
|
|
|
|
{
|
|
|
|
// only try to use another ability unless in shockwave or recover
|
|
|
|
let target_approaching_speed = -agent
|
|
|
|
.target
|
|
|
|
.as_ref()
|
|
|
|
.map(|t| t.target)
|
|
|
|
.and_then(|e| read_data.velocities.get(e))
|
|
|
|
.map_or(0.0, |v| v.0.dot(self.ori.look_vec()));
|
|
|
|
if self
|
|
|
|
.skill_set
|
|
|
|
.has_skill(Skill::Staff(StaffSkill::UnlockShockwave))
|
|
|
|
&& target_approaching_speed > 12.0
|
|
|
|
&& self.energy.current() > shockwave_cost
|
|
|
|
{
|
|
|
|
// if enemy is closing distance quickly, use shockwave to knock back
|
|
|
|
if matches!(self.char_state, CharacterState::Wielding(_)) {
|
2022-01-26 19:09:59 +00:00
|
|
|
controller.push_basic_input(InputKind::Ability(0));
|
2022-01-18 03:02:43 +00:00
|
|
|
} else {
|
2022-10-25 02:52:37 +00:00
|
|
|
agent.action_state.conditions
|
|
|
|
[ActionStateConditions::ConditionStaffCanShockwave as usize] = true;
|
2022-01-18 03:02:43 +00:00
|
|
|
}
|
|
|
|
} else if self.energy.current()
|
2022-10-24 01:12:59 +00:00
|
|
|
> shockwave_cost + CharacterAbility::default_roll().energy_cost()
|
2022-01-18 03:02:43 +00:00
|
|
|
&& attack_data.dist_sqrd < flamethrower_range.powi(2)
|
|
|
|
{
|
2022-01-26 19:09:59 +00:00
|
|
|
controller.push_basic_input(InputKind::Secondary);
|
2022-01-18 03:02:43 +00:00
|
|
|
} else {
|
2022-01-26 19:09:59 +00:00
|
|
|
controller.push_basic_input(InputKind::Primary);
|
2022-01-18 03:02:43 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
// Logic to move. Intentionally kept separate from ability logic so duplicated
|
|
|
|
// work is less necessary.
|
|
|
|
if attack_data.dist_sqrd < (2.0 * attack_data.min_attack_dist).powi(2) {
|
|
|
|
// Attempt to move away from target if too close
|
|
|
|
if let Some((bearing, speed)) = agent.chaser.chase(
|
|
|
|
&*read_data.terrain,
|
|
|
|
self.pos.0,
|
|
|
|
self.vel.0,
|
2022-03-01 21:54:44 +00:00
|
|
|
tgt_data.pos.0,
|
2022-01-18 03:02:43 +00:00
|
|
|
TraversalConfig {
|
|
|
|
min_tgt_dist: 1.25,
|
|
|
|
..self.traversal_config
|
|
|
|
},
|
|
|
|
) {
|
|
|
|
controller.inputs.move_dir =
|
|
|
|
-bearing.xy().try_normalized().unwrap_or_else(Vec2::zero) * speed;
|
|
|
|
}
|
|
|
|
} else if attack_data.dist_sqrd < MAX_PATH_DIST.powi(2) {
|
|
|
|
// Else attempt to circle target if neither too close nor too far
|
|
|
|
if let Some((bearing, speed)) = agent.chaser.chase(
|
|
|
|
&*read_data.terrain,
|
|
|
|
self.pos.0,
|
|
|
|
self.vel.0,
|
2022-03-01 21:54:44 +00:00
|
|
|
tgt_data.pos.0,
|
2022-01-18 03:02:43 +00:00
|
|
|
TraversalConfig {
|
|
|
|
min_tgt_dist: 1.25,
|
|
|
|
..self.traversal_config
|
|
|
|
},
|
|
|
|
) {
|
2022-04-26 23:34:29 +00:00
|
|
|
if entities_have_line_of_sight(
|
|
|
|
self.pos,
|
|
|
|
self.body,
|
|
|
|
tgt_data.pos,
|
|
|
|
tgt_data.body,
|
|
|
|
read_data,
|
|
|
|
) && attack_data.angle < 45.0
|
2022-01-18 03:02:43 +00:00
|
|
|
{
|
|
|
|
controller.inputs.move_dir = bearing
|
|
|
|
.xy()
|
|
|
|
.rotated_z(rng.gen_range(-1.57..-0.5))
|
|
|
|
.try_normalized()
|
|
|
|
.unwrap_or_else(Vec2::zero)
|
|
|
|
* speed;
|
|
|
|
} else {
|
|
|
|
// Unless cannot see target, then move towards them
|
|
|
|
controller.inputs.move_dir =
|
|
|
|
bearing.xy().try_normalized().unwrap_or_else(Vec2::zero) * speed;
|
2022-03-30 21:54:03 +00:00
|
|
|
self.jump_if(bearing.z > 1.5, controller);
|
2022-01-18 03:02:43 +00:00
|
|
|
controller.inputs.move_z = bearing.z;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// Sometimes try to roll
|
|
|
|
if self.body.map_or(false, |b| b.is_humanoid())
|
|
|
|
&& attack_data.dist_sqrd < 16.0f32.powi(2)
|
|
|
|
&& !matches!(self.char_state, CharacterState::Shockwave(_))
|
|
|
|
&& rng.gen::<f32>() < 0.02
|
|
|
|
{
|
2022-01-26 19:09:59 +00:00
|
|
|
controller.push_basic_input(InputKind::Roll);
|
2022-01-18 03:02:43 +00:00
|
|
|
}
|
|
|
|
} else {
|
|
|
|
// If too far, move towards target
|
2022-03-01 21:54:44 +00:00
|
|
|
self.path_toward_target(
|
|
|
|
agent,
|
|
|
|
controller,
|
|
|
|
tgt_data.pos.0,
|
|
|
|
read_data,
|
|
|
|
Path::Partial,
|
|
|
|
None,
|
|
|
|
);
|
2022-01-18 03:02:43 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn handle_sceptre_attack(
|
|
|
|
&self,
|
|
|
|
agent: &mut Agent,
|
|
|
|
controller: &mut Controller,
|
|
|
|
attack_data: &AttackData,
|
|
|
|
tgt_data: &TargetData,
|
|
|
|
read_data: &ReadData,
|
|
|
|
rng: &mut impl Rng,
|
|
|
|
) {
|
|
|
|
const DESIRED_ENERGY_LEVEL: f32 = 50.0;
|
|
|
|
const DESIRED_COMBO_LEVEL: u32 = 8;
|
2022-04-22 01:32:56 +00:00
|
|
|
|
2022-04-26 23:34:29 +00:00
|
|
|
let line_of_sight_with_target = || {
|
|
|
|
entities_have_line_of_sight(self.pos, self.body, tgt_data.pos, tgt_data.body, read_data)
|
2022-04-24 20:15:30 +00:00
|
|
|
};
|
2022-04-22 01:32:56 +00:00
|
|
|
|
2022-01-18 03:02:43 +00:00
|
|
|
// Logic to use abilities
|
2022-04-24 20:15:30 +00:00
|
|
|
if attack_data.dist_sqrd > attack_data.min_attack_dist.powi(2)
|
2022-04-26 23:34:29 +00:00
|
|
|
&& line_of_sight_with_target()
|
2022-01-18 03:02:43 +00:00
|
|
|
{
|
|
|
|
// If far enough away, and can see target, check which skill is appropriate to
|
|
|
|
// use
|
|
|
|
if self.energy.current() > DESIRED_ENERGY_LEVEL
|
|
|
|
&& read_data
|
|
|
|
.combos
|
|
|
|
.get(*self.entity)
|
|
|
|
.map_or(false, |c| c.counter() >= DESIRED_COMBO_LEVEL)
|
|
|
|
&& !read_data.buffs.get(*self.entity).iter().any(|buff| {
|
|
|
|
buff.iter_kind(BuffKind::Regeneration)
|
|
|
|
.peekable()
|
|
|
|
.peek()
|
|
|
|
.is_some()
|
|
|
|
})
|
|
|
|
{
|
|
|
|
// If have enough energy and combo to use healing aura, do so
|
2022-01-26 19:09:59 +00:00
|
|
|
controller.push_basic_input(InputKind::Secondary);
|
2022-01-18 03:02:43 +00:00
|
|
|
} else if self
|
|
|
|
.skill_set
|
|
|
|
.has_skill(Skill::Sceptre(SceptreSkill::UnlockAura))
|
|
|
|
&& self.energy.current() > DESIRED_ENERGY_LEVEL
|
|
|
|
&& !read_data.buffs.get(*self.entity).iter().any(|buff| {
|
|
|
|
buff.iter_kind(BuffKind::ProtectingWard)
|
|
|
|
.peekable()
|
|
|
|
.peek()
|
|
|
|
.is_some()
|
|
|
|
})
|
|
|
|
{
|
|
|
|
// Use ward if target is far enough away, self is not buffed, and have
|
|
|
|
// sufficient energy
|
2022-01-26 19:09:59 +00:00
|
|
|
controller.push_basic_input(InputKind::Ability(0));
|
2022-01-18 03:02:43 +00:00
|
|
|
} else {
|
|
|
|
// If low on energy, use primary to attempt to regen energy
|
|
|
|
// Or if at desired energy level but not able/willing to ward, just attack
|
2022-01-26 19:09:59 +00:00
|
|
|
controller.push_basic_input(InputKind::Primary);
|
2022-01-18 03:02:43 +00:00
|
|
|
}
|
|
|
|
} else if attack_data.dist_sqrd < (2.0 * attack_data.min_attack_dist).powi(2) {
|
|
|
|
if self.body.map_or(false, |b| b.is_humanoid())
|
2022-10-24 01:12:59 +00:00
|
|
|
&& self.energy.current() > CharacterAbility::default_roll().energy_cost()
|
2022-01-18 03:02:43 +00:00
|
|
|
&& !matches!(self.char_state, CharacterState::BasicAura(c) if !matches!(c.stage_section, StageSection::Recover))
|
|
|
|
{
|
|
|
|
// Else roll away if can roll and have enough energy, and not using aura or in
|
|
|
|
// recover
|
2022-01-26 19:09:59 +00:00
|
|
|
controller.push_basic_input(InputKind::Roll);
|
2022-01-18 03:02:43 +00:00
|
|
|
} else if attack_data.angle < 15.0 {
|
2022-01-26 19:09:59 +00:00
|
|
|
controller.push_basic_input(InputKind::Primary);
|
2022-01-18 03:02:43 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
// Logic to move. Intentionally kept separate from ability logic where possible
|
|
|
|
// so duplicated work is less necessary.
|
|
|
|
if attack_data.dist_sqrd < (2.0 * attack_data.min_attack_dist).powi(2) {
|
|
|
|
// Attempt to move away from target if too close
|
|
|
|
if let Some((bearing, speed)) = agent.chaser.chase(
|
|
|
|
&*read_data.terrain,
|
|
|
|
self.pos.0,
|
|
|
|
self.vel.0,
|
2022-03-01 21:54:44 +00:00
|
|
|
tgt_data.pos.0,
|
2022-01-18 03:02:43 +00:00
|
|
|
TraversalConfig {
|
|
|
|
min_tgt_dist: 1.25,
|
|
|
|
..self.traversal_config
|
|
|
|
},
|
|
|
|
) {
|
|
|
|
controller.inputs.move_dir =
|
|
|
|
-bearing.xy().try_normalized().unwrap_or_else(Vec2::zero) * speed;
|
|
|
|
}
|
|
|
|
} else if attack_data.dist_sqrd < MAX_PATH_DIST.powi(2) {
|
|
|
|
// Else attempt to circle target if neither too close nor too far
|
|
|
|
if let Some((bearing, speed)) = agent.chaser.chase(
|
|
|
|
&*read_data.terrain,
|
|
|
|
self.pos.0,
|
|
|
|
self.vel.0,
|
2022-03-01 21:54:44 +00:00
|
|
|
tgt_data.pos.0,
|
2022-01-18 03:02:43 +00:00
|
|
|
TraversalConfig {
|
|
|
|
min_tgt_dist: 1.25,
|
|
|
|
..self.traversal_config
|
|
|
|
},
|
|
|
|
) {
|
2022-04-26 23:34:29 +00:00
|
|
|
if line_of_sight_with_target() && attack_data.angle < 45.0 {
|
2022-01-18 03:02:43 +00:00
|
|
|
controller.inputs.move_dir = bearing
|
|
|
|
.xy()
|
|
|
|
.rotated_z(rng.gen_range(0.5..1.57))
|
|
|
|
.try_normalized()
|
|
|
|
.unwrap_or_else(Vec2::zero)
|
|
|
|
* speed;
|
|
|
|
} else {
|
|
|
|
// Unless cannot see target, then move towards them
|
|
|
|
controller.inputs.move_dir =
|
|
|
|
bearing.xy().try_normalized().unwrap_or_else(Vec2::zero) * speed;
|
2022-03-30 21:54:03 +00:00
|
|
|
self.jump_if(bearing.z > 1.5, controller);
|
2022-01-18 03:02:43 +00:00
|
|
|
controller.inputs.move_z = bearing.z;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// Sometimes try to roll
|
|
|
|
if self.body.map(|b| b.is_humanoid()).unwrap_or(false)
|
|
|
|
&& !matches!(self.char_state, CharacterState::BasicAura(_))
|
|
|
|
&& attack_data.dist_sqrd < 16.0f32.powi(2)
|
|
|
|
&& rng.gen::<f32>() < 0.01
|
|
|
|
{
|
2022-01-26 19:09:59 +00:00
|
|
|
controller.push_basic_input(InputKind::Roll);
|
2022-01-18 03:02:43 +00:00
|
|
|
}
|
|
|
|
} else {
|
|
|
|
// If too far, move towards target
|
2022-03-01 21:54:44 +00:00
|
|
|
self.path_toward_target(
|
|
|
|
agent,
|
|
|
|
controller,
|
|
|
|
tgt_data.pos.0,
|
|
|
|
read_data,
|
|
|
|
Path::Partial,
|
|
|
|
None,
|
|
|
|
);
|
2022-01-18 03:02:43 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn handle_stone_golem_attack(
|
|
|
|
&self,
|
|
|
|
agent: &mut Agent,
|
|
|
|
controller: &mut Controller,
|
|
|
|
attack_data: &AttackData,
|
|
|
|
tgt_data: &TargetData,
|
|
|
|
read_data: &ReadData,
|
|
|
|
) {
|
2022-10-17 02:48:27 +00:00
|
|
|
enum ActionStateTimers {
|
|
|
|
TimerHandleStoneGolemAttack = 0, //Timer 0
|
|
|
|
}
|
|
|
|
|
2022-01-18 03:02:43 +00:00
|
|
|
if attack_data.in_min_range() && attack_data.angle < 90.0 {
|
|
|
|
controller.inputs.move_dir = Vec2::zero();
|
2022-01-26 19:09:59 +00:00
|
|
|
controller.push_basic_input(InputKind::Primary);
|
2022-01-18 03:02:43 +00:00
|
|
|
//controller.inputs.primary.set_state(true);
|
|
|
|
} else if attack_data.dist_sqrd < MAX_PATH_DIST.powi(2) {
|
|
|
|
if self.vel.0.is_approx_zero() {
|
2022-01-26 19:09:59 +00:00
|
|
|
controller.push_basic_input(InputKind::Ability(0));
|
2022-01-18 03:02:43 +00:00
|
|
|
}
|
2022-03-01 21:54:44 +00:00
|
|
|
if self.path_toward_target(
|
|
|
|
agent,
|
|
|
|
controller,
|
|
|
|
tgt_data.pos.0,
|
|
|
|
read_data,
|
|
|
|
Path::Separate,
|
|
|
|
None,
|
2022-04-26 23:34:29 +00:00
|
|
|
) && entities_have_line_of_sight(
|
|
|
|
self.pos,
|
|
|
|
self.body,
|
|
|
|
tgt_data.pos,
|
|
|
|
tgt_data.body,
|
|
|
|
read_data,
|
|
|
|
) && attack_data.angle < 90.0
|
2022-01-18 03:02:43 +00:00
|
|
|
{
|
2022-10-25 02:52:37 +00:00
|
|
|
if agent.action_state.timers
|
|
|
|
[ActionStateTimers::TimerHandleStoneGolemAttack as usize]
|
|
|
|
> 5.0
|
|
|
|
{
|
2022-01-26 19:09:59 +00:00
|
|
|
controller.push_basic_input(InputKind::Secondary);
|
2022-10-25 02:52:37 +00:00
|
|
|
agent.action_state.timers
|
|
|
|
[ActionStateTimers::TimerHandleStoneGolemAttack as usize] = 0.0;
|
2022-01-18 03:02:43 +00:00
|
|
|
} else {
|
2022-10-25 02:52:37 +00:00
|
|
|
agent.action_state.timers
|
|
|
|
[ActionStateTimers::TimerHandleStoneGolemAttack as usize] += read_data.dt.0;
|
2022-01-18 03:02:43 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
} else {
|
2022-03-01 21:54:44 +00:00
|
|
|
self.path_toward_target(
|
|
|
|
agent,
|
|
|
|
controller,
|
|
|
|
tgt_data.pos.0,
|
|
|
|
read_data,
|
|
|
|
Path::Partial,
|
|
|
|
None,
|
|
|
|
);
|
2022-01-18 03:02:43 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#[allow(clippy::too_many_arguments)]
|
|
|
|
pub fn handle_circle_charge_attack(
|
|
|
|
&self,
|
|
|
|
agent: &mut Agent,
|
|
|
|
controller: &mut Controller,
|
|
|
|
attack_data: &AttackData,
|
|
|
|
tgt_data: &TargetData,
|
|
|
|
read_data: &ReadData,
|
|
|
|
radius: u32,
|
|
|
|
circle_time: u32,
|
|
|
|
rng: &mut impl Rng,
|
|
|
|
) {
|
2022-10-25 02:52:37 +00:00
|
|
|
enum ActionStateCountersF {
|
2022-10-17 02:48:27 +00:00
|
|
|
CounterFHandleCircleChargeAttack = 0,
|
|
|
|
}
|
|
|
|
|
2022-10-25 02:52:37 +00:00
|
|
|
enum ActionStateCountersI {
|
2022-10-17 02:48:27 +00:00
|
|
|
CounterIHandleCircleChargeAttack = 0,
|
|
|
|
}
|
|
|
|
|
2022-10-25 02:52:37 +00:00
|
|
|
if agent.action_state.counters
|
|
|
|
[ActionStateCountersF::CounterFHandleCircleChargeAttack as usize]
|
|
|
|
>= circle_time as f32
|
|
|
|
{
|
2022-01-18 03:02:43 +00:00
|
|
|
// if circle charge is in progress and time hasn't expired, continue charging
|
2022-01-26 19:09:59 +00:00
|
|
|
controller.push_basic_input(InputKind::Secondary);
|
2022-01-18 03:02:43 +00:00
|
|
|
}
|
|
|
|
if attack_data.in_min_range() {
|
2022-10-25 02:52:37 +00:00
|
|
|
if agent.action_state.counters
|
|
|
|
[ActionStateCountersF::CounterFHandleCircleChargeAttack as usize]
|
|
|
|
> 0.0
|
|
|
|
{
|
2022-01-18 03:02:43 +00:00
|
|
|
// set timer and rotation counter to zero if in minimum range
|
2022-10-25 02:52:37 +00:00
|
|
|
agent.action_state.counters
|
|
|
|
[ActionStateCountersF::CounterFHandleCircleChargeAttack as usize] = 0.0;
|
|
|
|
agent.action_state.int_counters
|
|
|
|
[ActionStateCountersI::CounterIHandleCircleChargeAttack as usize] = 0;
|
2022-01-18 03:02:43 +00:00
|
|
|
} else {
|
|
|
|
// melee attack
|
2022-01-26 19:09:59 +00:00
|
|
|
controller.push_basic_input(InputKind::Primary);
|
2022-01-18 03:02:43 +00:00
|
|
|
controller.inputs.move_dir = Vec2::zero();
|
|
|
|
}
|
|
|
|
} else if attack_data.dist_sqrd < (radius as f32 + attack_data.min_attack_dist).powi(2) {
|
|
|
|
// if in range to charge, circle, then charge
|
2022-10-25 02:52:37 +00:00
|
|
|
if agent.action_state.int_counters
|
|
|
|
[ActionStateCountersI::CounterIHandleCircleChargeAttack as usize]
|
|
|
|
== 0
|
|
|
|
{
|
2022-01-18 03:02:43 +00:00
|
|
|
// if you haven't chosen a direction to go in, choose now
|
2022-10-25 02:52:37 +00:00
|
|
|
agent.action_state.int_counters
|
|
|
|
[ActionStateCountersI::CounterIHandleCircleChargeAttack as usize] =
|
|
|
|
1 + rng.gen_bool(0.5) as u8;
|
2022-01-18 03:02:43 +00:00
|
|
|
}
|
2022-10-25 02:52:37 +00:00
|
|
|
if agent.action_state.counters
|
|
|
|
[ActionStateCountersF::CounterFHandleCircleChargeAttack as usize]
|
|
|
|
< circle_time as f32
|
|
|
|
{
|
2022-01-18 03:02:43 +00:00
|
|
|
// circle if circle timer not ready
|
2022-10-25 02:52:37 +00:00
|
|
|
let move_dir = match agent.action_state.int_counters
|
|
|
|
[ActionStateCountersI::CounterIHandleCircleChargeAttack as usize]
|
|
|
|
{
|
2022-01-18 03:02:43 +00:00
|
|
|
1 =>
|
|
|
|
// circle left if counter is 1
|
|
|
|
{
|
2022-03-01 21:54:44 +00:00
|
|
|
(tgt_data.pos.0 - self.pos.0)
|
2022-01-18 03:02:43 +00:00
|
|
|
.xy()
|
|
|
|
.rotated_z(0.47 * PI)
|
|
|
|
.try_normalized()
|
|
|
|
.unwrap_or_else(Vec2::unit_y)
|
|
|
|
},
|
|
|
|
2 =>
|
|
|
|
// circle right if counter is 2
|
|
|
|
{
|
2022-03-01 21:54:44 +00:00
|
|
|
(tgt_data.pos.0 - self.pos.0)
|
2022-01-18 03:02:43 +00:00
|
|
|
.xy()
|
|
|
|
.rotated_z(-0.47 * PI)
|
|
|
|
.try_normalized()
|
|
|
|
.unwrap_or_else(Vec2::unit_y)
|
|
|
|
},
|
|
|
|
_ =>
|
|
|
|
// if some illegal value slipped in, get zero vector
|
|
|
|
{
|
2022-07-15 12:08:04 +00:00
|
|
|
Vec2::zero()
|
2022-01-18 03:02:43 +00:00
|
|
|
},
|
|
|
|
};
|
|
|
|
let obstacle = read_data
|
|
|
|
.terrain
|
|
|
|
.ray(
|
|
|
|
self.pos.0 + Vec3::unit_z(),
|
|
|
|
self.pos.0 + move_dir.with_z(0.0) * 2.0 + Vec3::unit_z(),
|
|
|
|
)
|
|
|
|
.until(Block::is_solid)
|
|
|
|
.cast()
|
|
|
|
.1
|
|
|
|
.map_or(true, |b| b.is_some());
|
|
|
|
if obstacle {
|
|
|
|
// if obstacle detected, stop circling
|
2022-10-25 02:52:37 +00:00
|
|
|
agent.action_state.counters
|
|
|
|
[ActionStateCountersF::CounterFHandleCircleChargeAttack as usize] =
|
|
|
|
circle_time as f32;
|
2022-01-18 03:02:43 +00:00
|
|
|
}
|
|
|
|
controller.inputs.move_dir = move_dir;
|
|
|
|
// use counter as timer since timer may be modified in other parts of the code
|
2022-10-25 02:52:37 +00:00
|
|
|
agent.action_state.counters
|
|
|
|
[ActionStateCountersF::CounterFHandleCircleChargeAttack as usize] +=
|
|
|
|
read_data.dt.0;
|
2022-01-18 03:02:43 +00:00
|
|
|
}
|
|
|
|
// activating charge once circle timer expires is handled above
|
|
|
|
} else {
|
2022-02-27 06:56:36 +00:00
|
|
|
let path = if attack_data.dist_sqrd < MAX_PATH_DIST.powi(2) {
|
|
|
|
// if too far away from target, move towards them
|
|
|
|
Path::Separate
|
|
|
|
} else {
|
|
|
|
Path::Partial
|
|
|
|
};
|
2022-03-01 21:54:44 +00:00
|
|
|
self.path_toward_target(agent, controller, tgt_data.pos.0, read_data, path, None);
|
2022-01-18 03:02:43 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn handle_quadlow_ranged_attack(
|
|
|
|
&self,
|
|
|
|
agent: &mut Agent,
|
|
|
|
controller: &mut Controller,
|
|
|
|
attack_data: &AttackData,
|
|
|
|
tgt_data: &TargetData,
|
|
|
|
read_data: &ReadData,
|
|
|
|
) {
|
2022-10-17 03:13:03 +00:00
|
|
|
enum ActionStateTimers {
|
2022-10-17 02:48:27 +00:00
|
|
|
TimerHandleQuadLowRanged = 0,
|
|
|
|
}
|
|
|
|
|
2022-01-18 03:02:43 +00:00
|
|
|
if attack_data.dist_sqrd < (3.0 * attack_data.min_attack_dist).powi(2)
|
|
|
|
&& attack_data.angle < 90.0
|
|
|
|
{
|
2022-03-01 21:54:44 +00:00
|
|
|
controller.inputs.move_dir = (tgt_data.pos.0 - self.pos.0)
|
2022-01-18 03:02:43 +00:00
|
|
|
.xy()
|
|
|
|
.try_normalized()
|
|
|
|
.unwrap_or_else(Vec2::unit_y);
|
2022-01-26 19:09:59 +00:00
|
|
|
controller.push_basic_input(InputKind::Primary);
|
2022-01-18 03:02:43 +00:00
|
|
|
} 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,
|
|
|
|
self.vel.0,
|
2022-03-01 21:54:44 +00:00
|
|
|
tgt_data.pos.0,
|
2022-01-18 03:02:43 +00:00
|
|
|
TraversalConfig {
|
|
|
|
min_tgt_dist: 1.25,
|
|
|
|
..self.traversal_config
|
|
|
|
},
|
|
|
|
) {
|
|
|
|
if attack_data.angle < 15.0
|
2022-04-26 23:34:29 +00:00
|
|
|
&& entities_have_line_of_sight(
|
|
|
|
self.pos,
|
|
|
|
self.body,
|
|
|
|
tgt_data.pos,
|
|
|
|
tgt_data.body,
|
|
|
|
read_data,
|
|
|
|
)
|
2022-01-18 03:02:43 +00:00
|
|
|
{
|
2022-10-25 02:52:37 +00:00
|
|
|
if agent.action_state.timers
|
|
|
|
[ActionStateTimers::TimerHandleQuadLowRanged as usize]
|
|
|
|
> 5.0
|
|
|
|
{
|
|
|
|
agent.action_state.timers
|
|
|
|
[ActionStateTimers::TimerHandleQuadLowRanged as usize] = 0.0;
|
|
|
|
} else if agent.action_state.timers
|
|
|
|
[ActionStateTimers::TimerHandleQuadLowRanged as usize]
|
|
|
|
> 2.5
|
|
|
|
{
|
2022-03-01 21:54:44 +00:00
|
|
|
controller.inputs.move_dir = (tgt_data.pos.0 - self.pos.0)
|
2022-01-18 03:02:43 +00:00
|
|
|
.xy()
|
|
|
|
.rotated_z(1.75 * PI)
|
|
|
|
.try_normalized()
|
|
|
|
.unwrap_or_else(Vec2::zero)
|
|
|
|
* speed;
|
2022-10-25 02:52:37 +00:00
|
|
|
agent.action_state.timers
|
|
|
|
[ActionStateTimers::TimerHandleQuadLowRanged as usize] +=
|
|
|
|
read_data.dt.0;
|
2022-01-18 03:02:43 +00:00
|
|
|
} else {
|
2022-03-01 21:54:44 +00:00
|
|
|
controller.inputs.move_dir = (tgt_data.pos.0 - self.pos.0)
|
2022-01-18 03:02:43 +00:00
|
|
|
.xy()
|
|
|
|
.rotated_z(0.25 * PI)
|
|
|
|
.try_normalized()
|
|
|
|
.unwrap_or_else(Vec2::zero)
|
|
|
|
* speed;
|
2022-10-25 02:52:37 +00:00
|
|
|
agent.action_state.timers
|
|
|
|
[ActionStateTimers::TimerHandleQuadLowRanged as usize] +=
|
|
|
|
read_data.dt.0;
|
2022-01-18 03:02:43 +00:00
|
|
|
}
|
2022-01-26 19:09:59 +00:00
|
|
|
controller.push_basic_input(InputKind::Secondary);
|
2022-03-30 21:54:03 +00:00
|
|
|
self.jump_if(bearing.z > 1.5, controller);
|
2022-01-18 03:02:43 +00:00
|
|
|
controller.inputs.move_z = bearing.z;
|
|
|
|
} else {
|
|
|
|
controller.inputs.move_dir =
|
|
|
|
bearing.xy().try_normalized().unwrap_or_else(Vec2::zero) * speed;
|
2022-03-30 21:54:03 +00:00
|
|
|
self.jump_if(bearing.z > 1.5, controller);
|
2022-01-18 03:02:43 +00:00
|
|
|
controller.inputs.move_z = bearing.z;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
agent.target = None;
|
|
|
|
}
|
|
|
|
} else {
|
2022-03-01 21:54:44 +00:00
|
|
|
self.path_toward_target(
|
|
|
|
agent,
|
|
|
|
controller,
|
|
|
|
tgt_data.pos.0,
|
|
|
|
read_data,
|
|
|
|
Path::Partial,
|
|
|
|
None,
|
|
|
|
);
|
2022-01-18 03:02:43 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn handle_tail_slap_attack(
|
|
|
|
&self,
|
|
|
|
agent: &mut Agent,
|
|
|
|
controller: &mut Controller,
|
|
|
|
attack_data: &AttackData,
|
|
|
|
tgt_data: &TargetData,
|
|
|
|
read_data: &ReadData,
|
|
|
|
) {
|
2022-10-17 03:13:03 +00:00
|
|
|
enum ActionStateTimers {
|
2022-10-17 02:48:27 +00:00
|
|
|
TimerTailSlap = 0,
|
|
|
|
}
|
|
|
|
|
2022-01-18 03:02:43 +00:00
|
|
|
if attack_data.angle < 90.0
|
|
|
|
&& attack_data.dist_sqrd < (1.5 * attack_data.min_attack_dist).powi(2)
|
|
|
|
{
|
2022-10-17 03:13:03 +00:00
|
|
|
if agent.action_state.timers[ActionStateTimers::TimerTailSlap as usize] > 4.0 {
|
2022-01-26 19:15:40 +00:00
|
|
|
controller.push_cancel_input(InputKind::Primary);
|
2022-10-17 03:13:03 +00:00
|
|
|
agent.action_state.timers[ActionStateTimers::TimerTailSlap as usize] = 0.0;
|
|
|
|
} else if agent.action_state.timers[ActionStateTimers::TimerTailSlap as usize] > 1.0 {
|
2022-01-26 19:09:59 +00:00
|
|
|
controller.push_basic_input(InputKind::Primary);
|
2022-10-25 02:52:37 +00:00
|
|
|
agent.action_state.timers[ActionStateTimers::TimerTailSlap as usize] +=
|
|
|
|
read_data.dt.0;
|
2022-01-18 03:02:43 +00:00
|
|
|
} else {
|
2022-01-26 19:09:59 +00:00
|
|
|
controller.push_basic_input(InputKind::Secondary);
|
2022-10-25 02:52:37 +00:00
|
|
|
agent.action_state.timers[ActionStateTimers::TimerTailSlap as usize] +=
|
|
|
|
read_data.dt.0;
|
2022-01-18 03:02:43 +00:00
|
|
|
}
|
2022-03-01 21:54:44 +00:00
|
|
|
controller.inputs.move_dir = (tgt_data.pos.0 - self.pos.0)
|
2022-01-18 03:02:43 +00:00
|
|
|
.xy()
|
|
|
|
.try_normalized()
|
|
|
|
.unwrap_or_else(Vec2::unit_y)
|
|
|
|
* 0.1;
|
|
|
|
} else if attack_data.dist_sqrd < MAX_PATH_DIST.powi(2) {
|
2022-03-01 21:54:44 +00:00
|
|
|
self.path_toward_target(
|
|
|
|
agent,
|
|
|
|
controller,
|
|
|
|
tgt_data.pos.0,
|
|
|
|
read_data,
|
|
|
|
Path::Separate,
|
|
|
|
None,
|
|
|
|
);
|
2022-01-18 03:02:43 +00:00
|
|
|
} else {
|
2022-03-01 21:54:44 +00:00
|
|
|
self.path_toward_target(
|
|
|
|
agent,
|
|
|
|
controller,
|
|
|
|
tgt_data.pos.0,
|
|
|
|
read_data,
|
|
|
|
Path::Partial,
|
|
|
|
None,
|
|
|
|
);
|
2022-01-18 03:02:43 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn handle_quadlow_quick_attack(
|
|
|
|
&self,
|
|
|
|
agent: &mut Agent,
|
|
|
|
controller: &mut Controller,
|
|
|
|
attack_data: &AttackData,
|
|
|
|
tgt_data: &TargetData,
|
|
|
|
read_data: &ReadData,
|
|
|
|
) {
|
|
|
|
if attack_data.angle < 90.0
|
|
|
|
&& attack_data.dist_sqrd < (1.5 * attack_data.min_attack_dist).powi(2)
|
|
|
|
{
|
|
|
|
controller.inputs.move_dir = Vec2::zero();
|
2022-01-26 19:09:59 +00:00
|
|
|
controller.push_basic_input(InputKind::Secondary);
|
2022-01-18 03:02:43 +00:00
|
|
|
} else if attack_data.dist_sqrd < (3.0 * attack_data.min_attack_dist).powi(2)
|
|
|
|
&& attack_data.dist_sqrd > (2.0 * attack_data.min_attack_dist).powi(2)
|
|
|
|
&& attack_data.angle < 90.0
|
|
|
|
{
|
2022-01-26 19:09:59 +00:00
|
|
|
controller.push_basic_input(InputKind::Primary);
|
2022-03-01 21:54:44 +00:00
|
|
|
controller.inputs.move_dir = (tgt_data.pos.0 - self.pos.0)
|
2022-01-18 03:02:43 +00:00
|
|
|
.xy()
|
|
|
|
.rotated_z(-0.47 * PI)
|
|
|
|
.try_normalized()
|
|
|
|
.unwrap_or_else(Vec2::unit_y);
|
|
|
|
} else if attack_data.dist_sqrd < MAX_PATH_DIST.powi(2) {
|
2022-03-01 21:54:44 +00:00
|
|
|
self.path_toward_target(
|
|
|
|
agent,
|
|
|
|
controller,
|
|
|
|
tgt_data.pos.0,
|
|
|
|
read_data,
|
|
|
|
Path::Separate,
|
|
|
|
None,
|
|
|
|
);
|
2022-01-18 03:02:43 +00:00
|
|
|
} else {
|
2022-03-01 21:54:44 +00:00
|
|
|
self.path_toward_target(
|
|
|
|
agent,
|
|
|
|
controller,
|
|
|
|
tgt_data.pos.0,
|
|
|
|
read_data,
|
|
|
|
Path::Partial,
|
|
|
|
None,
|
|
|
|
);
|
2022-01-18 03:02:43 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn handle_quadlow_basic_attack(
|
|
|
|
&self,
|
|
|
|
agent: &mut Agent,
|
|
|
|
controller: &mut Controller,
|
|
|
|
attack_data: &AttackData,
|
|
|
|
tgt_data: &TargetData,
|
|
|
|
read_data: &ReadData,
|
|
|
|
) {
|
2022-10-17 03:13:03 +00:00
|
|
|
enum ActionStateTimers {
|
2022-10-17 02:48:27 +00:00
|
|
|
TimerQuadLowBasic = 0,
|
|
|
|
}
|
|
|
|
|
2022-01-18 03:02:43 +00:00
|
|
|
if attack_data.angle < 70.0
|
|
|
|
&& attack_data.dist_sqrd < (1.3 * attack_data.min_attack_dist).powi(2)
|
|
|
|
{
|
|
|
|
controller.inputs.move_dir = Vec2::zero();
|
2022-10-17 03:13:03 +00:00
|
|
|
if agent.action_state.timers[ActionStateTimers::TimerQuadLowBasic as usize] > 5.0 {
|
|
|
|
agent.action_state.timers[ActionStateTimers::TimerQuadLowBasic as usize] = 0.0;
|
2022-10-25 02:52:37 +00:00
|
|
|
} else if agent.action_state.timers[ActionStateTimers::TimerQuadLowBasic as usize] > 2.0
|
|
|
|
{
|
2022-01-26 19:09:59 +00:00
|
|
|
controller.push_basic_input(InputKind::Secondary);
|
2022-10-25 02:52:37 +00:00
|
|
|
agent.action_state.timers[ActionStateTimers::TimerQuadLowBasic as usize] +=
|
|
|
|
read_data.dt.0;
|
2022-01-18 03:02:43 +00:00
|
|
|
} else {
|
2022-01-26 19:09:59 +00:00
|
|
|
controller.push_basic_input(InputKind::Primary);
|
2022-10-25 02:52:37 +00:00
|
|
|
agent.action_state.timers[ActionStateTimers::TimerQuadLowBasic as usize] +=
|
|
|
|
read_data.dt.0;
|
2022-01-18 03:02:43 +00:00
|
|
|
}
|
|
|
|
} else {
|
2022-02-27 06:56:36 +00:00
|
|
|
let path = if attack_data.dist_sqrd < MAX_PATH_DIST.powi(2) {
|
|
|
|
Path::Separate
|
|
|
|
} else {
|
|
|
|
Path::Partial
|
|
|
|
};
|
2022-03-01 21:54:44 +00:00
|
|
|
self.path_toward_target(agent, controller, tgt_data.pos.0, read_data, path, None);
|
2022-01-18 03:02:43 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn handle_quadmed_jump_attack(
|
|
|
|
&self,
|
|
|
|
agent: &mut Agent,
|
|
|
|
controller: &mut Controller,
|
|
|
|
attack_data: &AttackData,
|
|
|
|
tgt_data: &TargetData,
|
|
|
|
read_data: &ReadData,
|
|
|
|
) {
|
|
|
|
if attack_data.angle < 90.0
|
|
|
|
&& attack_data.dist_sqrd < (1.5 * attack_data.min_attack_dist).powi(2)
|
|
|
|
{
|
|
|
|
controller.inputs.move_dir = Vec2::zero();
|
2022-01-26 19:09:59 +00:00
|
|
|
controller.push_basic_input(InputKind::Secondary);
|
2022-01-18 03:02:43 +00:00
|
|
|
} else if attack_data.angle < 15.0
|
|
|
|
&& attack_data.dist_sqrd < (5.0 * attack_data.min_attack_dist).powi(2)
|
|
|
|
{
|
2022-01-26 19:09:59 +00:00
|
|
|
controller.push_basic_input(InputKind::Ability(0));
|
2022-01-18 03:02:43 +00:00
|
|
|
} else if attack_data.dist_sqrd < MAX_PATH_DIST.powi(2) {
|
2022-03-01 21:54:44 +00:00
|
|
|
if self.path_toward_target(
|
|
|
|
agent,
|
|
|
|
controller,
|
|
|
|
tgt_data.pos.0,
|
|
|
|
read_data,
|
|
|
|
Path::Separate,
|
|
|
|
None,
|
|
|
|
) && attack_data.angle < 15.0
|
2022-04-26 23:34:29 +00:00
|
|
|
&& entities_have_line_of_sight(
|
|
|
|
self.pos,
|
|
|
|
self.body,
|
|
|
|
tgt_data.pos,
|
|
|
|
tgt_data.body,
|
|
|
|
read_data,
|
|
|
|
)
|
2022-01-18 03:02:43 +00:00
|
|
|
{
|
2022-01-26 19:09:59 +00:00
|
|
|
controller.push_basic_input(InputKind::Primary);
|
2022-01-18 03:02:43 +00:00
|
|
|
}
|
|
|
|
} else {
|
2022-03-01 21:54:44 +00:00
|
|
|
self.path_toward_target(
|
|
|
|
agent,
|
|
|
|
controller,
|
|
|
|
tgt_data.pos.0,
|
|
|
|
read_data,
|
|
|
|
Path::Partial,
|
|
|
|
None,
|
|
|
|
);
|
2022-01-18 03:02:43 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn handle_quadmed_basic_attack(
|
|
|
|
&self,
|
|
|
|
agent: &mut Agent,
|
|
|
|
controller: &mut Controller,
|
|
|
|
attack_data: &AttackData,
|
|
|
|
tgt_data: &TargetData,
|
|
|
|
read_data: &ReadData,
|
|
|
|
) {
|
2022-10-17 03:13:03 +00:00
|
|
|
enum ActionStateTimers {
|
2022-10-17 02:48:27 +00:00
|
|
|
TimerQuadMedBasic = 0,
|
|
|
|
}
|
|
|
|
|
2022-01-18 03:02:43 +00:00
|
|
|
if attack_data.angle < 90.0 && attack_data.in_min_range() {
|
|
|
|
controller.inputs.move_dir = Vec2::zero();
|
2022-10-17 03:13:03 +00:00
|
|
|
if agent.action_state.timers[ActionStateTimers::TimerQuadMedBasic as usize] < 2.0 {
|
2022-01-26 19:09:59 +00:00
|
|
|
controller.push_basic_input(InputKind::Secondary);
|
2022-10-25 02:52:37 +00:00
|
|
|
agent.action_state.timers[ActionStateTimers::TimerQuadMedBasic as usize] +=
|
|
|
|
read_data.dt.0;
|
|
|
|
} else if agent.action_state.timers[ActionStateTimers::TimerQuadMedBasic as usize] < 3.0
|
|
|
|
{
|
2022-01-26 19:09:59 +00:00
|
|
|
controller.push_basic_input(InputKind::Primary);
|
2022-10-25 02:52:37 +00:00
|
|
|
agent.action_state.timers[ActionStateTimers::TimerQuadMedBasic as usize] +=
|
|
|
|
read_data.dt.0;
|
2022-01-18 03:02:43 +00:00
|
|
|
} else {
|
2022-10-17 03:13:03 +00:00
|
|
|
agent.action_state.timers[ActionStateTimers::TimerQuadMedBasic as usize] = 0.0;
|
2022-01-18 03:02:43 +00:00
|
|
|
}
|
|
|
|
} else if attack_data.dist_sqrd < MAX_PATH_DIST.powi(2) {
|
2022-03-01 21:54:44 +00:00
|
|
|
self.path_toward_target(
|
|
|
|
agent,
|
|
|
|
controller,
|
|
|
|
tgt_data.pos.0,
|
|
|
|
read_data,
|
|
|
|
Path::Separate,
|
|
|
|
None,
|
|
|
|
);
|
2022-01-18 03:02:43 +00:00
|
|
|
} else {
|
2022-03-01 21:54:44 +00:00
|
|
|
self.path_toward_target(
|
|
|
|
agent,
|
|
|
|
controller,
|
|
|
|
tgt_data.pos.0,
|
|
|
|
read_data,
|
|
|
|
Path::Partial,
|
|
|
|
None,
|
|
|
|
);
|
2022-01-18 03:02:43 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn handle_quadlow_beam_attack(
|
|
|
|
&self,
|
|
|
|
agent: &mut Agent,
|
|
|
|
controller: &mut Controller,
|
|
|
|
attack_data: &AttackData,
|
|
|
|
tgt_data: &TargetData,
|
|
|
|
read_data: &ReadData,
|
|
|
|
) {
|
2022-10-25 02:52:37 +00:00
|
|
|
enum ActionStateTimers {
|
2022-10-17 02:48:27 +00:00
|
|
|
TimerQuadLowBeam = 0,
|
|
|
|
}
|
2022-01-18 03:02:43 +00:00
|
|
|
if attack_data.angle < 90.0
|
|
|
|
&& attack_data.dist_sqrd < (2.5 * attack_data.min_attack_dist).powi(2)
|
|
|
|
{
|
|
|
|
controller.inputs.move_dir = Vec2::zero();
|
2022-01-26 19:09:59 +00:00
|
|
|
controller.push_basic_input(InputKind::Secondary);
|
2022-01-18 03:02:43 +00:00
|
|
|
} else if attack_data.dist_sqrd < (7.0 * attack_data.min_attack_dist).powi(2)
|
|
|
|
&& attack_data.angle < 15.0
|
|
|
|
{
|
2022-10-17 03:13:03 +00:00
|
|
|
if agent.action_state.timers[ActionStateTimers::TimerQuadLowBeam as usize] < 2.0 {
|
2022-03-01 21:54:44 +00:00
|
|
|
controller.inputs.move_dir = (tgt_data.pos.0 - self.pos.0)
|
2022-01-18 03:02:43 +00:00
|
|
|
.xy()
|
|
|
|
.rotated_z(0.47 * PI)
|
|
|
|
.try_normalized()
|
|
|
|
.unwrap_or_else(Vec2::unit_y);
|
2022-01-26 19:09:59 +00:00
|
|
|
controller.push_basic_input(InputKind::Primary);
|
2022-10-25 02:52:37 +00:00
|
|
|
agent.action_state.timers[ActionStateTimers::TimerQuadLowBeam as usize] +=
|
|
|
|
read_data.dt.0;
|
|
|
|
} else if agent.action_state.timers[ActionStateTimers::TimerQuadLowBeam as usize] < 4.0
|
|
|
|
&& attack_data.angle < 15.0
|
|
|
|
{
|
2022-03-01 21:54:44 +00:00
|
|
|
controller.inputs.move_dir = (tgt_data.pos.0 - self.pos.0)
|
2022-01-18 03:02:43 +00:00
|
|
|
.xy()
|
|
|
|
.rotated_z(-0.47 * PI)
|
|
|
|
.try_normalized()
|
|
|
|
.unwrap_or_else(Vec2::unit_y);
|
2022-01-26 19:09:59 +00:00
|
|
|
controller.push_basic_input(InputKind::Primary);
|
2022-10-25 02:52:37 +00:00
|
|
|
agent.action_state.timers[ActionStateTimers::TimerQuadLowBeam as usize] +=
|
|
|
|
read_data.dt.0;
|
|
|
|
} else if agent.action_state.timers[ActionStateTimers::TimerQuadLowBeam as usize] < 6.0
|
|
|
|
&& attack_data.angle < 15.0
|
|
|
|
{
|
2022-01-26 19:09:59 +00:00
|
|
|
controller.push_basic_input(InputKind::Ability(0));
|
2022-10-25 02:52:37 +00:00
|
|
|
agent.action_state.timers[ActionStateTimers::TimerQuadLowBeam as usize] +=
|
|
|
|
read_data.dt.0;
|
2022-01-18 03:02:43 +00:00
|
|
|
} else {
|
2022-10-17 03:13:03 +00:00
|
|
|
agent.action_state.timers[ActionStateTimers::TimerQuadLowBeam as usize] = 0.0;
|
2022-01-18 03:02:43 +00:00
|
|
|
}
|
|
|
|
} else if attack_data.dist_sqrd < MAX_PATH_DIST.powi(2) {
|
2022-03-01 21:54:44 +00:00
|
|
|
self.path_toward_target(
|
|
|
|
agent,
|
|
|
|
controller,
|
|
|
|
tgt_data.pos.0,
|
|
|
|
read_data,
|
|
|
|
Path::Separate,
|
|
|
|
None,
|
|
|
|
);
|
2022-01-18 03:02:43 +00:00
|
|
|
} else {
|
2022-03-01 21:54:44 +00:00
|
|
|
self.path_toward_target(
|
|
|
|
agent,
|
|
|
|
controller,
|
|
|
|
tgt_data.pos.0,
|
|
|
|
read_data,
|
|
|
|
Path::Partial,
|
|
|
|
None,
|
|
|
|
);
|
2022-01-18 03:02:43 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-08-24 21:50:53 +00:00
|
|
|
pub fn handle_organ_aura_attack(
|
|
|
|
&self,
|
|
|
|
agent: &mut Agent,
|
|
|
|
controller: &mut Controller,
|
|
|
|
attack_data: &AttackData,
|
|
|
|
_tgt_data: &TargetData,
|
|
|
|
read_data: &ReadData,
|
|
|
|
) {
|
2022-10-17 03:13:03 +00:00
|
|
|
enum ActionStateTimers {
|
2022-10-17 02:48:27 +00:00
|
|
|
TimerOrganAura = 0,
|
|
|
|
}
|
|
|
|
|
2022-08-24 21:50:53 +00:00
|
|
|
const ORGAN_AURA_DURATION: f32 = 34.75;
|
|
|
|
if attack_data.dist_sqrd < (7.0 * attack_data.min_attack_dist).powi(2) {
|
2022-10-25 02:52:37 +00:00
|
|
|
if agent.action_state.timers[ActionStateTimers::TimerOrganAura as usize]
|
|
|
|
> ORGAN_AURA_DURATION
|
|
|
|
{
|
2022-10-17 03:13:03 +00:00
|
|
|
agent.action_state.timers[ActionStateTimers::TimerOrganAura as usize] = 0.0;
|
|
|
|
} else if agent.action_state.timers[ActionStateTimers::TimerOrganAura as usize] < 1.0 {
|
2022-08-24 21:50:53 +00:00
|
|
|
controller
|
|
|
|
.actions
|
|
|
|
.push(ControlAction::basic_input(InputKind::Primary));
|
2022-10-25 02:52:37 +00:00
|
|
|
agent.action_state.timers[ActionStateTimers::TimerOrganAura as usize] +=
|
|
|
|
read_data.dt.0;
|
2022-08-24 21:50:53 +00:00
|
|
|
} else {
|
2022-10-25 02:52:37 +00:00
|
|
|
agent.action_state.timers[ActionStateTimers::TimerOrganAura as usize] +=
|
|
|
|
read_data.dt.0;
|
2022-08-24 21:50:53 +00:00
|
|
|
}
|
|
|
|
} else {
|
|
|
|
agent.target = None;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-01-18 03:02:43 +00:00
|
|
|
pub fn handle_theropod_attack(
|
|
|
|
&self,
|
|
|
|
agent: &mut Agent,
|
|
|
|
controller: &mut Controller,
|
|
|
|
attack_data: &AttackData,
|
|
|
|
tgt_data: &TargetData,
|
|
|
|
read_data: &ReadData,
|
|
|
|
) {
|
|
|
|
if attack_data.angle < 90.0 && attack_data.in_min_range() {
|
|
|
|
controller.inputs.move_dir = Vec2::zero();
|
2022-01-26 19:09:59 +00:00
|
|
|
controller.push_basic_input(InputKind::Primary);
|
2022-01-18 03:02:43 +00:00
|
|
|
} else if attack_data.dist_sqrd < MAX_PATH_DIST.powi(2) {
|
2022-03-01 21:54:44 +00:00
|
|
|
self.path_toward_target(
|
|
|
|
agent,
|
|
|
|
controller,
|
|
|
|
tgt_data.pos.0,
|
|
|
|
read_data,
|
|
|
|
Path::Separate,
|
|
|
|
None,
|
|
|
|
);
|
2022-01-18 03:02:43 +00:00
|
|
|
} else {
|
2022-03-01 21:54:44 +00:00
|
|
|
self.path_toward_target(
|
|
|
|
agent,
|
|
|
|
controller,
|
|
|
|
tgt_data.pos.0,
|
|
|
|
read_data,
|
|
|
|
Path::Partial,
|
|
|
|
None,
|
|
|
|
);
|
2022-01-18 03:02:43 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn handle_turret_attack(
|
|
|
|
&self,
|
|
|
|
agent: &mut Agent,
|
|
|
|
controller: &mut Controller,
|
|
|
|
attack_data: &AttackData,
|
2022-04-26 23:34:29 +00:00
|
|
|
tgt_data: &TargetData,
|
2022-01-18 03:02:43 +00:00
|
|
|
read_data: &ReadData,
|
|
|
|
) {
|
2022-04-26 23:34:29 +00:00
|
|
|
if entities_have_line_of_sight(self.pos, self.body, tgt_data.pos, tgt_data.body, read_data)
|
|
|
|
&& attack_data.angle < 15.0
|
2022-01-18 03:02:43 +00:00
|
|
|
{
|
2022-01-26 19:09:59 +00:00
|
|
|
controller.push_basic_input(InputKind::Primary);
|
2022-01-18 03:02:43 +00:00
|
|
|
} else {
|
|
|
|
agent.target = None;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn handle_fixed_turret_attack(
|
|
|
|
&self,
|
|
|
|
agent: &mut Agent,
|
|
|
|
controller: &mut Controller,
|
|
|
|
attack_data: &AttackData,
|
2022-04-26 23:34:29 +00:00
|
|
|
tgt_data: &TargetData,
|
2022-01-18 03:02:43 +00:00
|
|
|
read_data: &ReadData,
|
|
|
|
) {
|
|
|
|
controller.inputs.look_dir = self.ori.look_dir();
|
2022-04-26 23:34:29 +00:00
|
|
|
if entities_have_line_of_sight(self.pos, self.body, tgt_data.pos, tgt_data.body, read_data)
|
|
|
|
&& attack_data.angle < 15.0
|
2022-01-18 03:02:43 +00:00
|
|
|
{
|
2022-01-26 19:09:59 +00:00
|
|
|
controller.push_basic_input(InputKind::Primary);
|
2022-01-18 03:02:43 +00:00
|
|
|
} else {
|
|
|
|
agent.target = None;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn handle_rotating_turret_attack(
|
|
|
|
&self,
|
|
|
|
agent: &mut Agent,
|
|
|
|
controller: &mut Controller,
|
2022-04-26 23:34:29 +00:00
|
|
|
tgt_data: &TargetData,
|
2022-01-18 03:02:43 +00:00
|
|
|
read_data: &ReadData,
|
|
|
|
) {
|
|
|
|
controller.inputs.look_dir = Dir::new(
|
|
|
|
Quaternion::from_xyzw(self.ori.look_dir().x, self.ori.look_dir().y, 0.0, 0.0)
|
|
|
|
.rotated_z(6.0 * read_data.dt.0 as f32)
|
|
|
|
.into_vec3()
|
|
|
|
.try_normalized()
|
|
|
|
.unwrap_or_default(),
|
|
|
|
);
|
2022-04-26 23:34:29 +00:00
|
|
|
if entities_have_line_of_sight(self.pos, self.body, tgt_data.pos, tgt_data.body, read_data)
|
|
|
|
{
|
2022-01-26 19:09:59 +00:00
|
|
|
controller.push_basic_input(InputKind::Primary);
|
2022-01-18 03:02:43 +00:00
|
|
|
} else {
|
|
|
|
agent.target = None;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-04-22 01:32:56 +00:00
|
|
|
pub fn handle_radial_turret_attack(&self, controller: &mut Controller) {
|
2022-02-09 01:23:23 +00:00
|
|
|
controller.push_basic_input(InputKind::Primary);
|
2022-01-18 03:02:43 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
pub fn handle_mindflayer_attack(
|
|
|
|
&self,
|
|
|
|
agent: &mut Agent,
|
|
|
|
controller: &mut Controller,
|
|
|
|
attack_data: &AttackData,
|
|
|
|
tgt_data: &TargetData,
|
|
|
|
read_data: &ReadData,
|
|
|
|
rng: &mut impl Rng,
|
|
|
|
) {
|
2022-10-17 03:13:03 +00:00
|
|
|
enum ActionStateFCounters {
|
2022-10-17 02:48:27 +00:00
|
|
|
FCounterHealthThreshold = 0,
|
|
|
|
}
|
|
|
|
|
2022-10-17 03:13:03 +00:00
|
|
|
enum ActionStateICounters {
|
2022-10-17 02:48:27 +00:00
|
|
|
ICounterNumFireballs = 0,
|
|
|
|
}
|
|
|
|
|
2022-10-17 03:13:03 +00:00
|
|
|
enum ActionStateConditions {
|
2022-10-17 02:48:27 +00:00
|
|
|
ConditionCounterInit = 0,
|
|
|
|
}
|
|
|
|
|
2022-01-18 03:02:43 +00:00
|
|
|
const MINDFLAYER_ATTACK_DIST: f32 = 16.0;
|
|
|
|
const MINION_SUMMON_THRESHOLD: f32 = 0.20;
|
|
|
|
let health_fraction = self.health.map_or(0.5, |h| h.fraction());
|
|
|
|
// Sets counter at start of combat, using `condition` to keep track of whether
|
2022-07-15 16:59:37 +00:00
|
|
|
// it was already initialized
|
2022-10-17 03:13:03 +00:00
|
|
|
if !agent.action_state.conditions[ActionStateConditions::ConditionCounterInit as usize] {
|
2022-10-25 02:52:37 +00:00
|
|
|
agent.action_state.counters[ActionStateFCounters::FCounterHealthThreshold as usize] =
|
|
|
|
1.0 - MINION_SUMMON_THRESHOLD;
|
|
|
|
agent.action_state.conditions[ActionStateConditions::ConditionCounterInit as usize] =
|
|
|
|
true;
|
2022-01-18 03:02:43 +00:00
|
|
|
}
|
|
|
|
|
2022-10-25 02:52:37 +00:00
|
|
|
if agent.action_state.counters[ActionStateFCounters::FCounterHealthThreshold as usize]
|
|
|
|
> health_fraction
|
|
|
|
{
|
2022-01-18 03:02:43 +00:00
|
|
|
// Summon minions at particular thresholds of health
|
2022-01-26 19:09:59 +00:00
|
|
|
controller.push_basic_input(InputKind::Ability(2));
|
2022-01-18 03:02:43 +00:00
|
|
|
|
|
|
|
if matches!(self.char_state, CharacterState::BasicSummon(c) if matches!(c.stage_section, StageSection::Recover))
|
|
|
|
{
|
2022-10-25 02:52:37 +00:00
|
|
|
agent.action_state.counters
|
|
|
|
[ActionStateFCounters::FCounterHealthThreshold as usize] -=
|
|
|
|
MINION_SUMMON_THRESHOLD;
|
2022-01-18 03:02:43 +00:00
|
|
|
}
|
|
|
|
} else if attack_data.dist_sqrd < MINDFLAYER_ATTACK_DIST.powi(2) {
|
2022-04-26 23:34:29 +00:00
|
|
|
if entities_have_line_of_sight(
|
|
|
|
self.pos,
|
|
|
|
self.body,
|
|
|
|
tgt_data.pos,
|
|
|
|
tgt_data.body,
|
|
|
|
read_data,
|
|
|
|
) {
|
2022-01-18 03:02:43 +00:00
|
|
|
// 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 already using primary, keep using primary until 10 consecutive seconds
|
2022-01-26 19:09:59 +00:00
|
|
|
controller.push_basic_input(InputKind::Primary);
|
2022-01-18 03:02:43 +00:00
|
|
|
} else if matches!(self.char_state, CharacterState::SpinMelee(c) if c.consecutive_spins < 50 && !matches!(c.stage_section, StageSection::Recover))
|
|
|
|
{
|
|
|
|
// If already using secondary, keep using secondary until 10 consecutive
|
|
|
|
// seconds
|
2022-01-26 19:09:59 +00:00
|
|
|
controller.push_basic_input(InputKind::Secondary);
|
2022-01-18 03:02:43 +00:00
|
|
|
} else if rng.gen_bool(health_fraction.into()) {
|
|
|
|
// Else if at high health, use primary
|
2022-01-26 19:09:59 +00:00
|
|
|
controller.push_basic_input(InputKind::Primary);
|
2022-01-18 03:02:43 +00:00
|
|
|
} else {
|
|
|
|
// Else use secondary
|
2022-01-26 19:09:59 +00:00
|
|
|
controller.push_basic_input(InputKind::Secondary);
|
2022-01-18 03:02:43 +00:00
|
|
|
}
|
|
|
|
} else {
|
2022-02-25 20:35:35 +00:00
|
|
|
self.path_toward_target(
|
|
|
|
agent,
|
|
|
|
controller,
|
2022-03-01 21:54:44 +00:00
|
|
|
tgt_data.pos.0,
|
2022-02-25 20:35:35 +00:00
|
|
|
read_data,
|
|
|
|
Path::Separate,
|
|
|
|
None,
|
|
|
|
);
|
2022-01-18 03:02:43 +00:00
|
|
|
}
|
|
|
|
} 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.
|
2022-10-25 02:52:37 +00:00
|
|
|
let num_fireballs = &mut agent.action_state.int_counters
|
|
|
|
[ActionStateICounters::ICounterNumFireballs as usize];
|
2022-01-18 03:02:43 +00:00
|
|
|
if *num_fireballs == 0 {
|
2022-01-26 18:52:19 +00:00
|
|
|
controller.push_action(ControlAction::StartInput {
|
2022-01-18 03:02:43 +00:00
|
|
|
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;
|
2022-01-26 18:52:19 +00:00
|
|
|
controller.push_action(ControlAction::StartInput {
|
2022-01-18 03:02:43 +00:00
|
|
|
input: InputKind::Ability(1),
|
|
|
|
target_entity: agent
|
|
|
|
.target
|
|
|
|
.as_ref()
|
|
|
|
.and_then(|t| read_data.uids.get(t.target))
|
|
|
|
.copied(),
|
|
|
|
select_pos: None,
|
|
|
|
});
|
|
|
|
}
|
2022-03-01 21:54:44 +00:00
|
|
|
self.path_toward_target(
|
|
|
|
agent,
|
|
|
|
controller,
|
|
|
|
tgt_data.pos.0,
|
|
|
|
read_data,
|
|
|
|
Path::Separate,
|
|
|
|
None,
|
|
|
|
);
|
2022-01-18 03:02:43 +00:00
|
|
|
} else {
|
2022-03-01 21:54:44 +00:00
|
|
|
self.path_toward_target(
|
|
|
|
agent,
|
|
|
|
controller,
|
|
|
|
tgt_data.pos.0,
|
|
|
|
read_data,
|
|
|
|
Path::Partial,
|
|
|
|
None,
|
|
|
|
);
|
2022-01-18 03:02:43 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn handle_birdlarge_fire_attack(
|
|
|
|
&self,
|
|
|
|
agent: &mut Agent,
|
|
|
|
controller: &mut Controller,
|
|
|
|
attack_data: &AttackData,
|
|
|
|
tgt_data: &TargetData,
|
|
|
|
read_data: &ReadData,
|
|
|
|
rng: &mut impl Rng,
|
|
|
|
) {
|
|
|
|
if attack_data.dist_sqrd > 30.0_f32.powi(2) {
|
|
|
|
let small_chance = rng.gen_bool(0.05);
|
|
|
|
|
|
|
|
if small_chance
|
2022-04-26 23:34:29 +00:00
|
|
|
&& entities_have_line_of_sight(
|
|
|
|
self.pos,
|
|
|
|
self.body,
|
|
|
|
tgt_data.pos,
|
|
|
|
tgt_data.body,
|
|
|
|
read_data,
|
|
|
|
)
|
2022-01-18 03:02:43 +00:00
|
|
|
&& attack_data.angle < 15.0
|
|
|
|
{
|
|
|
|
// Fireball
|
2022-01-26 19:09:59 +00:00
|
|
|
controller.push_basic_input(InputKind::Primary);
|
2022-01-18 03:02:43 +00:00
|
|
|
}
|
|
|
|
// If some target
|
|
|
|
if let Some((bearing, speed)) = agent.chaser.chase(
|
|
|
|
&*read_data.terrain,
|
|
|
|
self.pos.0,
|
|
|
|
self.vel.0,
|
2022-03-01 21:54:44 +00:00
|
|
|
tgt_data.pos.0,
|
2022-01-18 03:02:43 +00:00
|
|
|
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;
|
|
|
|
// If less than 20 blocks higher than target
|
2022-03-01 21:54:44 +00:00
|
|
|
if (self.pos.0.z - tgt_data.pos.0.z) < 20.0 {
|
2022-01-18 03:02:43 +00:00
|
|
|
// Fly upward
|
2022-01-26 19:09:59 +00:00
|
|
|
controller.push_basic_input(InputKind::Fly);
|
2022-01-18 03:02:43 +00:00
|
|
|
controller.inputs.move_z = 1.0;
|
|
|
|
} else {
|
|
|
|
// Jump
|
2022-03-30 21:54:03 +00:00
|
|
|
self.jump_if(bearing.z > 1.5, controller);
|
2022-01-18 03:02:43 +00:00
|
|
|
controller.inputs.move_z = bearing.z;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// If higher than 2 blocks
|
|
|
|
else if !read_data
|
|
|
|
.terrain
|
|
|
|
.ray(self.pos.0, self.pos.0 - (Vec3::unit_z() * 2.0))
|
|
|
|
.until(Block::is_solid)
|
|
|
|
.cast()
|
|
|
|
.1
|
|
|
|
.map_or(true, |b| b.is_some())
|
|
|
|
{
|
|
|
|
// Do not increment the timer during this movement
|
|
|
|
// The next stage shouldn't trigger until the entity
|
|
|
|
// is on the ground
|
|
|
|
// Fly to target
|
2022-01-26 19:09:59 +00:00
|
|
|
controller.push_basic_input(InputKind::Fly);
|
2022-03-01 21:54:44 +00:00
|
|
|
let move_dir = tgt_data.pos.0 - self.pos.0;
|
2022-01-18 03:02:43 +00:00
|
|
|
controller.inputs.move_dir =
|
|
|
|
move_dir.xy().try_normalized().unwrap_or_else(Vec2::zero) * 2.0;
|
|
|
|
controller.inputs.move_z = move_dir.z - 0.5;
|
|
|
|
// If further than 4 blocks and random chance
|
|
|
|
if rng.gen_bool(0.05)
|
|
|
|
&& attack_data.dist_sqrd > (4.0 * attack_data.min_attack_dist).powi(2)
|
|
|
|
&& attack_data.angle < 15.0
|
|
|
|
{
|
|
|
|
// Fireball
|
2022-01-26 19:09:59 +00:00
|
|
|
controller.push_basic_input(InputKind::Primary);
|
2022-01-18 03:02:43 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
// If further than 4 blocks and random chance
|
|
|
|
else if rng.gen_bool(0.05)
|
|
|
|
&& attack_data.dist_sqrd > (4.0 * attack_data.min_attack_dist).powi(2)
|
|
|
|
&& attack_data.angle < 15.0
|
|
|
|
{
|
|
|
|
// Fireball
|
2022-01-26 19:09:59 +00:00
|
|
|
controller.push_basic_input(InputKind::Primary);
|
2022-01-18 03:02:43 +00:00
|
|
|
}
|
|
|
|
// If random chance and less than 20 blocks higher than target and further than 4
|
|
|
|
// blocks
|
|
|
|
else if rng.gen_bool(0.5)
|
2022-03-01 21:54:44 +00:00
|
|
|
&& (self.pos.0.z - tgt_data.pos.0.z) < 15.0
|
2022-01-18 03:02:43 +00:00
|
|
|
&& attack_data.dist_sqrd > (4.0 * attack_data.min_attack_dist).powi(2)
|
|
|
|
{
|
2022-01-26 19:09:59 +00:00
|
|
|
controller.push_basic_input(InputKind::Fly);
|
2022-01-18 03:02:43 +00:00
|
|
|
controller.inputs.move_z = 1.0;
|
|
|
|
}
|
|
|
|
// If further than 2.5 blocks and random chance
|
|
|
|
else if attack_data.dist_sqrd > (2.5 * attack_data.min_attack_dist).powi(2) {
|
|
|
|
// Walk to target
|
2022-03-01 21:54:44 +00:00
|
|
|
self.path_toward_target(
|
|
|
|
agent,
|
|
|
|
controller,
|
|
|
|
tgt_data.pos.0,
|
|
|
|
read_data,
|
|
|
|
Path::Separate,
|
|
|
|
None,
|
|
|
|
);
|
2022-01-18 03:02:43 +00:00
|
|
|
}
|
|
|
|
// If energy higher than 600 and random chance
|
|
|
|
else if self.energy.current() > 60.0 && rng.gen_bool(0.4) {
|
|
|
|
// Shockwave
|
2022-01-26 19:09:59 +00:00
|
|
|
controller.push_basic_input(InputKind::Ability(0));
|
2022-01-18 03:02:43 +00:00
|
|
|
} else if attack_data.angle < 90.0 {
|
|
|
|
// Triple strike
|
2022-01-26 19:09:59 +00:00
|
|
|
controller.push_basic_input(InputKind::Secondary);
|
2022-01-18 03:02:43 +00:00
|
|
|
} else {
|
|
|
|
// Target is behind us. Turn around and chase target
|
2022-03-01 21:54:44 +00:00
|
|
|
self.path_toward_target(
|
|
|
|
agent,
|
|
|
|
controller,
|
|
|
|
tgt_data.pos.0,
|
|
|
|
read_data,
|
|
|
|
Path::Separate,
|
|
|
|
None,
|
|
|
|
);
|
2022-01-18 03:02:43 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn handle_birdlarge_breathe_attack(
|
|
|
|
&self,
|
|
|
|
agent: &mut Agent,
|
|
|
|
controller: &mut Controller,
|
|
|
|
attack_data: &AttackData,
|
|
|
|
tgt_data: &TargetData,
|
|
|
|
read_data: &ReadData,
|
|
|
|
rng: &mut impl Rng,
|
|
|
|
) {
|
2022-10-17 03:13:03 +00:00
|
|
|
enum ActionStateTimers {
|
2022-10-17 02:48:27 +00:00
|
|
|
TimerBirdLargeBreathe = 0,
|
|
|
|
}
|
|
|
|
|
2022-01-18 03:02:43 +00:00
|
|
|
// Set fly to false
|
2022-01-26 19:15:40 +00:00
|
|
|
controller.push_cancel_input(InputKind::Fly);
|
2022-01-18 03:02:43 +00:00
|
|
|
if attack_data.dist_sqrd > 30.0_f32.powi(2) {
|
|
|
|
if rng.gen_bool(0.05)
|
2022-04-26 23:34:29 +00:00
|
|
|
&& entities_have_line_of_sight(
|
|
|
|
self.pos,
|
|
|
|
self.body,
|
|
|
|
tgt_data.pos,
|
|
|
|
tgt_data.body,
|
|
|
|
read_data,
|
|
|
|
)
|
2022-01-18 03:02:43 +00:00
|
|
|
&& attack_data.angle < 15.0
|
|
|
|
{
|
2022-01-26 19:09:59 +00:00
|
|
|
controller.push_basic_input(InputKind::Primary);
|
2022-01-18 03:02:43 +00:00
|
|
|
}
|
|
|
|
if let Some((bearing, speed)) = agent.chaser.chase(
|
|
|
|
&*read_data.terrain,
|
|
|
|
self.pos.0,
|
|
|
|
self.vel.0,
|
2022-03-01 21:54:44 +00:00
|
|
|
tgt_data.pos.0,
|
2022-01-18 03:02:43 +00:00
|
|
|
TraversalConfig {
|
|
|
|
min_tgt_dist: 1.25,
|
|
|
|
..self.traversal_config
|
|
|
|
},
|
|
|
|
) {
|
|
|
|
controller.inputs.move_dir =
|
|
|
|
bearing.xy().try_normalized().unwrap_or_else(Vec2::zero) * speed;
|
2022-03-01 21:54:44 +00:00
|
|
|
if (self.pos.0.z - tgt_data.pos.0.z) < 20.0 {
|
2022-01-26 19:09:59 +00:00
|
|
|
controller.push_basic_input(InputKind::Fly);
|
2022-01-18 03:02:43 +00:00
|
|
|
controller.inputs.move_z = 1.0;
|
|
|
|
} else {
|
2022-03-30 21:54:03 +00:00
|
|
|
self.jump_if(bearing.z > 1.5, controller);
|
2022-01-18 03:02:43 +00:00
|
|
|
controller.inputs.move_z = bearing.z;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} else if !read_data
|
|
|
|
.terrain
|
|
|
|
.ray(self.pos.0, self.pos.0 - (Vec3::unit_z() * 2.0))
|
|
|
|
.until(Block::is_solid)
|
|
|
|
.cast()
|
|
|
|
.1
|
|
|
|
.map_or(true, |b| b.is_some())
|
|
|
|
{
|
|
|
|
// Do not increment the timer during this movement
|
|
|
|
// The next stage shouldn't trigger until the entity
|
|
|
|
// is on the ground
|
2022-01-26 19:09:59 +00:00
|
|
|
controller.push_basic_input(InputKind::Fly);
|
2022-03-01 21:54:44 +00:00
|
|
|
let move_dir = tgt_data.pos.0 - self.pos.0;
|
2022-01-18 03:02:43 +00:00
|
|
|
controller.inputs.move_dir =
|
|
|
|
move_dir.xy().try_normalized().unwrap_or_else(Vec2::zero) * 2.0;
|
|
|
|
controller.inputs.move_z = move_dir.z - 0.5;
|
|
|
|
if rng.gen_bool(0.05)
|
|
|
|
&& attack_data.dist_sqrd > (4.0 * attack_data.min_attack_dist).powi(2)
|
|
|
|
&& attack_data.angle < 15.0
|
|
|
|
{
|
2022-01-26 19:09:59 +00:00
|
|
|
controller.push_basic_input(InputKind::Primary);
|
2022-01-18 03:02:43 +00:00
|
|
|
}
|
|
|
|
} else if rng.gen_bool(0.05)
|
|
|
|
&& attack_data.dist_sqrd > (4.0 * attack_data.min_attack_dist).powi(2)
|
|
|
|
&& attack_data.angle < 15.0
|
|
|
|
{
|
2022-01-26 19:09:59 +00:00
|
|
|
controller.push_basic_input(InputKind::Primary);
|
2022-01-18 03:02:43 +00:00
|
|
|
} else if rng.gen_bool(0.5)
|
2022-03-01 21:54:44 +00:00
|
|
|
&& (self.pos.0.z - tgt_data.pos.0.z) < 15.0
|
2022-01-18 03:02:43 +00:00
|
|
|
&& attack_data.dist_sqrd > (4.0 * attack_data.min_attack_dist).powi(2)
|
|
|
|
{
|
2022-01-26 19:09:59 +00:00
|
|
|
controller.push_basic_input(InputKind::Fly);
|
2022-01-18 03:02:43 +00:00
|
|
|
controller.inputs.move_z = 1.0;
|
|
|
|
} else if attack_data.dist_sqrd > (3.0 * attack_data.min_attack_dist).powi(2) {
|
2022-03-01 21:54:44 +00:00
|
|
|
self.path_toward_target(
|
|
|
|
agent,
|
|
|
|
controller,
|
|
|
|
tgt_data.pos.0,
|
|
|
|
read_data,
|
|
|
|
Path::Separate,
|
|
|
|
None,
|
|
|
|
);
|
2022-01-18 03:02:43 +00:00
|
|
|
} else if self.energy.current() > 60.0
|
2022-10-17 03:13:03 +00:00
|
|
|
&& agent.action_state.timers[ActionStateTimers::TimerBirdLargeBreathe as usize] < 3.0
|
2022-01-18 03:02:43 +00:00
|
|
|
&& attack_data.angle < 15.0
|
|
|
|
{
|
|
|
|
// Fire breath attack
|
2022-01-26 19:09:59 +00:00
|
|
|
controller.push_basic_input(InputKind::Ability(0));
|
2022-01-18 03:02:43 +00:00
|
|
|
// Move towards the target slowly
|
|
|
|
self.path_toward_target(
|
|
|
|
agent,
|
|
|
|
controller,
|
2022-03-01 21:54:44 +00:00
|
|
|
tgt_data.pos.0,
|
2022-01-18 03:02:43 +00:00
|
|
|
read_data,
|
2022-02-25 20:35:35 +00:00
|
|
|
Path::Separate,
|
2022-01-18 03:02:43 +00:00
|
|
|
Some(0.5),
|
|
|
|
);
|
2022-10-25 02:52:37 +00:00
|
|
|
agent.action_state.timers[ActionStateTimers::TimerBirdLargeBreathe as usize] +=
|
|
|
|
read_data.dt.0;
|
2022-10-17 03:13:03 +00:00
|
|
|
} else if agent.action_state.timers[ActionStateTimers::TimerBirdLargeBreathe as usize] < 6.0
|
2022-01-18 03:02:43 +00:00
|
|
|
&& attack_data.angle < 90.0
|
|
|
|
&& attack_data.in_min_range()
|
|
|
|
{
|
2022-07-15 16:59:37 +00:00
|
|
|
// Triple strike
|
2022-01-26 19:09:59 +00:00
|
|
|
controller.push_basic_input(InputKind::Secondary);
|
2022-10-25 02:52:37 +00:00
|
|
|
agent.action_state.timers[ActionStateTimers::TimerBirdLargeBreathe as usize] +=
|
|
|
|
read_data.dt.0;
|
2022-01-18 03:02:43 +00:00
|
|
|
} else {
|
|
|
|
// Reset timer
|
2022-10-17 03:13:03 +00:00
|
|
|
agent.action_state.timers[ActionStateTimers::TimerBirdLargeBreathe as usize] = 0.0;
|
2022-01-18 03:02:43 +00:00
|
|
|
// Target is behind us or the timer needs to be reset. Chase target
|
2022-03-01 21:54:44 +00:00
|
|
|
self.path_toward_target(
|
|
|
|
agent,
|
|
|
|
controller,
|
|
|
|
tgt_data.pos.0,
|
|
|
|
read_data,
|
|
|
|
Path::Separate,
|
|
|
|
None,
|
|
|
|
);
|
2022-01-18 03:02:43 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn handle_birdlarge_basic_attack(
|
|
|
|
&self,
|
|
|
|
agent: &mut Agent,
|
|
|
|
controller: &mut Controller,
|
|
|
|
attack_data: &AttackData,
|
|
|
|
tgt_data: &TargetData,
|
|
|
|
read_data: &ReadData,
|
|
|
|
) {
|
2022-10-17 03:13:03 +00:00
|
|
|
enum ActionStateTimers {
|
2022-10-17 02:48:27 +00:00
|
|
|
TimerBirdLargeBasic = 0,
|
|
|
|
}
|
|
|
|
|
2022-10-17 03:13:03 +00:00
|
|
|
enum ActionStateConditions {
|
2022-10-25 02:52:37 +00:00
|
|
|
ConditionBirdLargeBasic = 0, /* FIXME: Not sure what this represents. This name
|
|
|
|
* should be reflective of the condition... */
|
2022-10-17 02:48:27 +00:00
|
|
|
}
|
|
|
|
|
2022-01-18 03:02:43 +00:00
|
|
|
const BIRD_ATTACK_RANGE: f32 = 4.0;
|
|
|
|
const BIRD_CHARGE_DISTANCE: f32 = 15.0;
|
|
|
|
let bird_attack_distance = self.body.map_or(0.0, |b| b.max_radius()) + BIRD_ATTACK_RANGE;
|
|
|
|
// Increase action timer
|
2022-10-25 02:52:37 +00:00
|
|
|
agent.action_state.timers[ActionStateTimers::TimerBirdLargeBasic as usize] +=
|
|
|
|
read_data.dt.0;
|
2022-01-18 03:02:43 +00:00
|
|
|
// If higher than 2 blocks
|
|
|
|
if !read_data
|
|
|
|
.terrain
|
|
|
|
.ray(self.pos.0, self.pos.0 - (Vec3::unit_z() * 2.0))
|
|
|
|
.until(Block::is_solid)
|
|
|
|
.cast()
|
|
|
|
.1
|
|
|
|
.map_or(true, |b| b.is_some())
|
|
|
|
{
|
|
|
|
// Fly to target and land
|
2022-01-26 19:09:59 +00:00
|
|
|
controller.push_basic_input(InputKind::Fly);
|
2022-03-01 21:54:44 +00:00
|
|
|
let move_dir = tgt_data.pos.0 - self.pos.0;
|
2022-01-18 03:02:43 +00:00
|
|
|
controller.inputs.move_dir =
|
|
|
|
move_dir.xy().try_normalized().unwrap_or_else(Vec2::zero) * 2.0;
|
|
|
|
controller.inputs.move_z = move_dir.z - 0.5;
|
2022-10-17 03:13:03 +00:00
|
|
|
} else if agent.action_state.timers[ActionStateTimers::TimerBirdLargeBasic as usize] > 8.0 {
|
2022-01-18 03:02:43 +00:00
|
|
|
// If action timer higher than 8, make bird summon tornadoes
|
2022-01-26 19:09:59 +00:00
|
|
|
controller.push_basic_input(InputKind::Secondary);
|
2022-01-18 03:02:43 +00:00
|
|
|
if matches!(self.char_state, CharacterState::BasicSummon(c) if matches!(c.stage_section, StageSection::Recover))
|
|
|
|
{
|
|
|
|
// Reset timer
|
2022-10-17 03:13:03 +00:00
|
|
|
agent.action_state.timers[ActionStateTimers::TimerBirdLargeBasic as usize] = 0.0;
|
2022-01-18 03:02:43 +00:00
|
|
|
}
|
|
|
|
} else if matches!(self.char_state, CharacterState::DashMelee(c) if !matches!(c.stage_section, StageSection::Recover))
|
|
|
|
{
|
|
|
|
// If already in dash, keep dashing if not in recover
|
2022-01-26 19:09:59 +00:00
|
|
|
controller.push_basic_input(InputKind::Ability(0));
|
2022-01-18 03:02:43 +00:00
|
|
|
} else if matches!(self.char_state, CharacterState::ComboMelee(c) if matches!(c.stage_section, StageSection::Recover))
|
|
|
|
{
|
|
|
|
// If already in combo keep comboing if not in recover
|
2022-01-26 19:09:59 +00:00
|
|
|
controller.push_basic_input(InputKind::Primary);
|
2022-01-18 03:02:43 +00:00
|
|
|
} else if attack_data.dist_sqrd > BIRD_CHARGE_DISTANCE.powi(2) {
|
|
|
|
// Charges at target if they are far enough away
|
|
|
|
if attack_data.angle < 60.0 {
|
2022-01-26 19:09:59 +00:00
|
|
|
controller.push_basic_input(InputKind::Ability(0));
|
2022-01-18 03:02:43 +00:00
|
|
|
}
|
|
|
|
} else if attack_data.dist_sqrd < bird_attack_distance.powi(2) {
|
|
|
|
// Combo melee target
|
2022-01-26 19:09:59 +00:00
|
|
|
controller.push_basic_input(InputKind::Primary);
|
2022-10-25 02:52:37 +00:00
|
|
|
agent.action_state.conditions
|
|
|
|
[ActionStateConditions::ConditionBirdLargeBasic as usize] = true;
|
2022-01-18 03:02:43 +00:00
|
|
|
}
|
|
|
|
// Make bird move towards target
|
2022-03-01 21:54:44 +00:00
|
|
|
self.path_toward_target(
|
|
|
|
agent,
|
|
|
|
controller,
|
|
|
|
tgt_data.pos.0,
|
|
|
|
read_data,
|
|
|
|
Path::Separate,
|
|
|
|
None,
|
|
|
|
);
|
2022-01-18 03:02:43 +00:00
|
|
|
}
|
|
|
|
|
2021-12-23 14:13:57 +00:00
|
|
|
pub fn handle_arthropod_ranged_attack(
|
|
|
|
&self,
|
|
|
|
agent: &mut Agent,
|
|
|
|
controller: &mut Controller,
|
|
|
|
attack_data: &AttackData,
|
|
|
|
tgt_data: &TargetData,
|
|
|
|
read_data: &ReadData,
|
|
|
|
) {
|
2022-10-25 02:52:37 +00:00
|
|
|
enum ActionStateTimers {
|
2022-10-17 02:48:27 +00:00
|
|
|
TimerArthropodRanged = 0,
|
|
|
|
}
|
|
|
|
|
2022-10-25 02:52:37 +00:00
|
|
|
agent.action_state.timers[ActionStateTimers::TimerArthropodRanged as usize] +=
|
|
|
|
read_data.dt.0;
|
2022-10-17 03:13:03 +00:00
|
|
|
if agent.action_state.timers[ActionStateTimers::TimerArthropodRanged as usize] > 6.0
|
2022-04-23 14:54:01 +00:00
|
|
|
&& attack_data.dist_sqrd < (1.5 * attack_data.min_attack_dist).powi(2)
|
2021-12-23 14:13:57 +00:00
|
|
|
{
|
|
|
|
controller.inputs.move_dir = Vec2::zero();
|
2022-01-26 19:09:59 +00:00
|
|
|
controller.push_basic_input(InputKind::Secondary);
|
2021-12-23 14:13:57 +00:00
|
|
|
// Reset timer
|
2022-04-23 14:54:01 +00:00
|
|
|
if matches!(self.char_state,
|
|
|
|
CharacterState::SpriteSummon(sprite_summon::Data { stage_section, .. })
|
|
|
|
| CharacterState::SelfBuff(self_buff::Data { stage_section, .. })
|
|
|
|
if matches!(stage_section, StageSection::Recover))
|
2021-12-23 14:13:57 +00:00
|
|
|
{
|
2022-10-17 03:13:03 +00:00
|
|
|
agent.action_state.timers[ActionStateTimers::TimerArthropodRanged as usize] = 0.0;
|
2021-12-23 14:13:57 +00:00
|
|
|
}
|
2022-04-23 14:54:01 +00:00
|
|
|
} else if attack_data.dist_sqrd < (2.5 * attack_data.min_attack_dist).powi(2)
|
2021-12-23 14:13:57 +00:00
|
|
|
&& attack_data.angle < 90.0
|
|
|
|
{
|
2022-03-01 21:54:44 +00:00
|
|
|
controller.inputs.move_dir = (tgt_data.pos.0 - self.pos.0)
|
2021-12-23 14:13:57 +00:00
|
|
|
.xy()
|
|
|
|
.try_normalized()
|
|
|
|
.unwrap_or_else(Vec2::unit_y);
|
2022-01-26 19:09:59 +00:00
|
|
|
controller.push_basic_input(InputKind::Primary);
|
2021-12-23 14:13:57 +00:00
|
|
|
} 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,
|
|
|
|
self.vel.0,
|
2022-03-01 21:54:44 +00:00
|
|
|
tgt_data.pos.0,
|
2021-12-23 14:13:57 +00:00
|
|
|
TraversalConfig {
|
|
|
|
min_tgt_dist: 1.25,
|
|
|
|
..self.traversal_config
|
|
|
|
},
|
|
|
|
) {
|
|
|
|
if attack_data.angle < 15.0
|
2022-04-26 23:34:29 +00:00
|
|
|
&& entities_have_line_of_sight(
|
|
|
|
self.pos,
|
|
|
|
self.body,
|
|
|
|
tgt_data.pos,
|
|
|
|
tgt_data.body,
|
|
|
|
read_data,
|
|
|
|
)
|
2021-12-23 14:13:57 +00:00
|
|
|
{
|
2022-10-25 02:52:37 +00:00
|
|
|
if agent.action_state.timers[ActionStateTimers::TimerArthropodRanged as usize]
|
|
|
|
> 5.0
|
|
|
|
{
|
|
|
|
agent.action_state.timers
|
|
|
|
[ActionStateTimers::TimerArthropodRanged as usize] = 0.0;
|
|
|
|
} else if agent.action_state.timers
|
|
|
|
[ActionStateTimers::TimerArthropodRanged as usize]
|
|
|
|
> 2.5
|
|
|
|
{
|
2022-03-01 21:54:44 +00:00
|
|
|
controller.inputs.move_dir = (tgt_data.pos.0 - self.pos.0)
|
2021-12-23 14:13:57 +00:00
|
|
|
.xy()
|
|
|
|
.rotated_z(1.75 * PI)
|
|
|
|
.try_normalized()
|
|
|
|
.unwrap_or_else(Vec2::zero)
|
|
|
|
* speed;
|
2022-10-25 02:52:37 +00:00
|
|
|
agent.action_state.timers
|
|
|
|
[ActionStateTimers::TimerArthropodRanged as usize] += read_data.dt.0;
|
2021-12-23 14:13:57 +00:00
|
|
|
} else {
|
2022-03-01 21:54:44 +00:00
|
|
|
controller.inputs.move_dir = (tgt_data.pos.0 - self.pos.0)
|
2021-12-23 14:13:57 +00:00
|
|
|
.xy()
|
|
|
|
.rotated_z(0.25 * PI)
|
|
|
|
.try_normalized()
|
|
|
|
.unwrap_or_else(Vec2::zero)
|
|
|
|
* speed;
|
2022-10-25 02:52:37 +00:00
|
|
|
agent.action_state.timers
|
|
|
|
[ActionStateTimers::TimerArthropodRanged as usize] += read_data.dt.0;
|
2021-12-23 14:13:57 +00:00
|
|
|
}
|
2022-01-26 19:09:59 +00:00
|
|
|
controller.push_basic_input(InputKind::Ability(0));
|
2022-03-30 21:54:03 +00:00
|
|
|
self.jump_if(bearing.z > 1.5, controller);
|
2021-12-23 14:13:57 +00:00
|
|
|
controller.inputs.move_z = bearing.z;
|
|
|
|
} else {
|
|
|
|
controller.inputs.move_dir =
|
|
|
|
bearing.xy().try_normalized().unwrap_or_else(Vec2::zero) * speed;
|
2022-03-30 21:54:03 +00:00
|
|
|
self.jump_if(bearing.z > 1.5, controller);
|
2021-12-23 14:13:57 +00:00
|
|
|
controller.inputs.move_z = bearing.z;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
agent.target = None;
|
|
|
|
}
|
|
|
|
} else {
|
2022-03-01 21:54:44 +00:00
|
|
|
self.path_toward_target(
|
|
|
|
agent,
|
|
|
|
controller,
|
|
|
|
tgt_data.pos.0,
|
|
|
|
read_data,
|
|
|
|
Path::Partial,
|
|
|
|
None,
|
|
|
|
);
|
2021-12-23 14:13:57 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-04-23 14:54:01 +00:00
|
|
|
pub fn handle_arthropod_ambush_attack(
|
2021-12-23 14:13:57 +00:00
|
|
|
&self,
|
|
|
|
agent: &mut Agent,
|
|
|
|
controller: &mut Controller,
|
|
|
|
attack_data: &AttackData,
|
|
|
|
tgt_data: &TargetData,
|
|
|
|
read_data: &ReadData,
|
|
|
|
rng: &mut impl Rng,
|
2021-09-06 23:43:10 +00:00
|
|
|
) {
|
2022-10-17 03:13:03 +00:00
|
|
|
enum ActionStateTimers {
|
2022-10-17 02:48:27 +00:00
|
|
|
TimersArthropodAmbush = 0,
|
|
|
|
}
|
|
|
|
|
2022-10-25 02:52:37 +00:00
|
|
|
agent.action_state.timers[ActionStateTimers::TimersArthropodAmbush as usize] +=
|
|
|
|
read_data.dt.0;
|
2022-10-17 03:13:03 +00:00
|
|
|
if agent.action_state.timers[ActionStateTimers::TimersArthropodAmbush as usize] > 12.0
|
2021-09-06 23:43:10 +00:00
|
|
|
&& attack_data.dist_sqrd < (1.5 * attack_data.min_attack_dist).powi(2)
|
|
|
|
{
|
|
|
|
controller.inputs.move_dir = Vec2::zero();
|
2022-01-26 19:09:59 +00:00
|
|
|
controller.push_basic_input(InputKind::Secondary);
|
2021-10-27 19:58:08 +00:00
|
|
|
// Reset timer
|
2022-04-23 14:54:01 +00:00
|
|
|
if matches!(self.char_state,
|
|
|
|
CharacterState::SpriteSummon(sprite_summon::Data { stage_section, .. })
|
|
|
|
| CharacterState::SelfBuff(self_buff::Data { stage_section, .. })
|
|
|
|
if matches!(stage_section, StageSection::Recover))
|
2021-12-23 14:13:57 +00:00
|
|
|
{
|
2022-10-17 03:13:03 +00:00
|
|
|
agent.action_state.timers[ActionStateTimers::TimersArthropodAmbush as usize] = 0.0;
|
2021-12-23 14:13:57 +00:00
|
|
|
}
|
2021-09-06 23:43:10 +00:00
|
|
|
} else if attack_data.angle < 90.0
|
2022-04-23 14:54:01 +00:00
|
|
|
&& attack_data.dist_sqrd < attack_data.min_attack_dist.powi(2)
|
2021-09-06 23:43:10 +00:00
|
|
|
{
|
|
|
|
controller.inputs.move_dir = Vec2::zero();
|
2022-01-26 19:09:59 +00:00
|
|
|
controller.push_basic_input(InputKind::Primary);
|
2021-12-23 14:13:57 +00:00
|
|
|
} else if rng.gen_bool(0.01)
|
2022-04-23 14:54:01 +00:00
|
|
|
&& attack_data.angle < 60.0
|
|
|
|
&& attack_data.dist_sqrd > (2.0 * attack_data.min_attack_dist).powi(2)
|
2021-09-06 23:43:10 +00:00
|
|
|
{
|
2022-01-26 19:09:59 +00:00
|
|
|
controller.push_basic_input(InputKind::Ability(0));
|
2021-09-06 23:43:10 +00:00
|
|
|
} else {
|
2022-03-01 21:54:44 +00:00
|
|
|
self.path_toward_target(
|
|
|
|
agent,
|
|
|
|
controller,
|
|
|
|
tgt_data.pos.0,
|
|
|
|
read_data,
|
|
|
|
Path::Partial,
|
|
|
|
None,
|
|
|
|
);
|
2021-09-06 23:43:10 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-04-23 14:54:01 +00:00
|
|
|
pub fn handle_arthropod_melee_attack(
|
2021-09-02 23:57:17 +00:00
|
|
|
&self,
|
|
|
|
agent: &mut Agent,
|
|
|
|
controller: &mut Controller,
|
|
|
|
attack_data: &AttackData,
|
|
|
|
tgt_data: &TargetData,
|
|
|
|
read_data: &ReadData,
|
|
|
|
) {
|
2022-10-17 03:13:03 +00:00
|
|
|
enum ActionStateTimers {
|
2022-10-17 02:48:27 +00:00
|
|
|
TimersArthropodMelee = 0,
|
|
|
|
}
|
2022-10-25 02:52:37 +00:00
|
|
|
agent.action_state.timers[ActionStateTimers::TimersArthropodMelee as usize] +=
|
|
|
|
read_data.dt.0;
|
2021-10-11 21:04:42 +00:00
|
|
|
if matches!(self.char_state, CharacterState::DashMelee(c) if !matches!(c.stage_section, StageSection::Recover))
|
2021-09-02 23:57:17 +00:00
|
|
|
{
|
2021-10-11 21:04:42 +00:00
|
|
|
// If already charging, keep charging if not in recover
|
2022-01-26 19:09:59 +00:00
|
|
|
controller.push_basic_input(InputKind::Secondary);
|
2022-04-23 14:54:01 +00:00
|
|
|
} else if attack_data.dist_sqrd > (2.5 * attack_data.min_attack_dist).powi(2) {
|
2021-10-11 21:04:42 +00:00
|
|
|
// Charges at target if they are far enough away
|
|
|
|
if attack_data.angle < 60.0 {
|
2022-01-26 19:09:59 +00:00
|
|
|
controller.push_basic_input(InputKind::Secondary);
|
2021-09-02 23:57:17 +00:00
|
|
|
}
|
2021-09-02 23:57:55 +00:00
|
|
|
} else if attack_data.angle < 90.0
|
2022-04-23 14:54:01 +00:00
|
|
|
&& attack_data.dist_sqrd < attack_data.min_attack_dist.powi(2)
|
2021-09-02 23:57:55 +00:00
|
|
|
{
|
|
|
|
controller.inputs.move_dir = Vec2::zero();
|
2022-01-26 19:09:59 +00:00
|
|
|
controller.push_basic_input(InputKind::Primary);
|
2021-09-02 23:57:17 +00:00
|
|
|
} else {
|
2022-03-01 21:54:44 +00:00
|
|
|
self.path_toward_target(
|
|
|
|
agent,
|
|
|
|
controller,
|
|
|
|
tgt_data.pos.0,
|
|
|
|
read_data,
|
|
|
|
Path::Partial,
|
|
|
|
None,
|
|
|
|
);
|
2021-09-02 23:57:17 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-01-18 03:02:43 +00:00
|
|
|
pub fn handle_minotaur_attack(
|
|
|
|
&self,
|
|
|
|
agent: &mut Agent,
|
|
|
|
controller: &mut Controller,
|
|
|
|
attack_data: &AttackData,
|
|
|
|
tgt_data: &TargetData,
|
|
|
|
read_data: &ReadData,
|
|
|
|
) {
|
|
|
|
const MINOTAUR_FRENZY_THRESHOLD: f32 = 0.5;
|
|
|
|
const MINOTAUR_ATTACK_RANGE: f32 = 5.0;
|
|
|
|
const MINOTAUR_CHARGE_DISTANCE: f32 = 15.0;
|
2022-10-17 02:48:27 +00:00
|
|
|
|
2022-10-17 03:13:03 +00:00
|
|
|
enum ActionStateFCounters {
|
2022-10-17 02:48:27 +00:00
|
|
|
FCounterMinotaurAttack = 0,
|
|
|
|
}
|
|
|
|
|
2022-10-17 03:13:03 +00:00
|
|
|
enum ActionStateConditions {
|
2022-10-17 02:48:27 +00:00
|
|
|
ConditionJustCrippledOrCleaved = 0,
|
|
|
|
}
|
|
|
|
|
2022-01-18 03:02:43 +00:00
|
|
|
let minotaur_attack_distance =
|
|
|
|
self.body.map_or(0.0, |b| b.max_radius()) + MINOTAUR_ATTACK_RANGE;
|
|
|
|
let health_fraction = self.health.map_or(1.0, |h| h.fraction());
|
|
|
|
// Sets action counter at start of combat
|
2022-10-25 02:52:37 +00:00
|
|
|
if agent.action_state.counters[ActionStateFCounters::FCounterMinotaurAttack as usize]
|
|
|
|
< MINOTAUR_FRENZY_THRESHOLD
|
2022-01-18 03:02:43 +00:00
|
|
|
&& health_fraction > MINOTAUR_FRENZY_THRESHOLD
|
|
|
|
{
|
2022-10-25 02:52:37 +00:00
|
|
|
agent.action_state.counters[ActionStateFCounters::FCounterMinotaurAttack as usize] =
|
|
|
|
MINOTAUR_FRENZY_THRESHOLD;
|
2022-01-18 03:02:43 +00:00
|
|
|
}
|
2022-10-25 02:52:37 +00:00
|
|
|
if health_fraction
|
|
|
|
< agent.action_state.counters[ActionStateFCounters::FCounterMinotaurAttack as usize]
|
|
|
|
{
|
2022-01-18 03:02:43 +00:00
|
|
|
// Makes minotaur buff itself with frenzy
|
2022-01-26 19:09:59 +00:00
|
|
|
controller.push_basic_input(InputKind::Ability(1));
|
2022-01-18 03:02:43 +00:00
|
|
|
if matches!(self.char_state, CharacterState::SelfBuff(c) if matches!(c.stage_section, StageSection::Recover))
|
|
|
|
{
|
2022-10-25 02:52:37 +00:00
|
|
|
agent.action_state.counters
|
|
|
|
[ActionStateFCounters::FCounterMinotaurAttack as usize] = 0.0;
|
2022-01-18 03:02:43 +00:00
|
|
|
}
|
|
|
|
} else if matches!(self.char_state, CharacterState::DashMelee(c) if !matches!(c.stage_section, StageSection::Recover))
|
|
|
|
{
|
|
|
|
// If already charging, keep charging if not in recover
|
2022-01-26 19:09:59 +00:00
|
|
|
controller.push_basic_input(InputKind::Ability(0));
|
2022-01-18 03:02:43 +00:00
|
|
|
} else if matches!(self.char_state, CharacterState::ChargedMelee(c) if matches!(c.stage_section, StageSection::Charge) && c.timer < c.static_data.charge_duration)
|
|
|
|
{
|
|
|
|
// If already charging a melee attack, keep charging it if charging
|
2022-01-26 19:09:59 +00:00
|
|
|
controller.push_basic_input(InputKind::Primary);
|
2022-01-18 03:02:43 +00:00
|
|
|
} else if attack_data.dist_sqrd > MINOTAUR_CHARGE_DISTANCE.powi(2) {
|
|
|
|
// Charges at target if they are far enough away
|
|
|
|
if attack_data.angle < 60.0 {
|
2022-01-26 19:09:59 +00:00
|
|
|
controller.push_basic_input(InputKind::Ability(0));
|
2022-01-18 03:02:43 +00:00
|
|
|
}
|
|
|
|
} else if attack_data.dist_sqrd < minotaur_attack_distance.powi(2) {
|
2022-10-25 02:52:37 +00:00
|
|
|
if agent.action_state.conditions
|
|
|
|
[ActionStateConditions::ConditionJustCrippledOrCleaved as usize]
|
|
|
|
&& !self.char_state.is_attack()
|
|
|
|
{
|
2022-01-18 03:02:43 +00:00
|
|
|
// Cripple target if not just used cripple
|
2022-01-26 19:09:59 +00:00
|
|
|
controller.push_basic_input(InputKind::Secondary);
|
2022-10-25 02:52:37 +00:00
|
|
|
agent.action_state.conditions
|
|
|
|
[ActionStateConditions::ConditionJustCrippledOrCleaved as usize] = false;
|
2022-01-18 03:02:43 +00:00
|
|
|
} else if !self.char_state.is_attack() {
|
|
|
|
// Cleave target if not just used cleave
|
2022-01-26 19:09:59 +00:00
|
|
|
controller.push_basic_input(InputKind::Primary);
|
2022-10-25 02:52:37 +00:00
|
|
|
agent.action_state.conditions
|
|
|
|
[ActionStateConditions::ConditionJustCrippledOrCleaved as usize] = true;
|
2022-01-18 03:02:43 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
// Make minotaur move towards target
|
2022-03-01 21:54:44 +00:00
|
|
|
self.path_toward_target(
|
|
|
|
agent,
|
|
|
|
controller,
|
|
|
|
tgt_data.pos.0,
|
|
|
|
read_data,
|
|
|
|
Path::Separate,
|
|
|
|
None,
|
|
|
|
);
|
2022-01-18 03:02:43 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
pub fn handle_clay_golem_attack(
|
|
|
|
&self,
|
|
|
|
agent: &mut Agent,
|
|
|
|
controller: &mut Controller,
|
|
|
|
attack_data: &AttackData,
|
|
|
|
tgt_data: &TargetData,
|
|
|
|
read_data: &ReadData,
|
|
|
|
) {
|
|
|
|
const GOLEM_MELEE_RANGE: f32 = 4.0;
|
|
|
|
const GOLEM_LASER_RANGE: f32 = 30.0;
|
|
|
|
const GOLEM_LONG_RANGE: f32 = 50.0;
|
|
|
|
const GOLEM_TARGET_SPEED: f32 = 8.0;
|
2022-10-17 02:48:27 +00:00
|
|
|
|
2022-10-17 03:13:03 +00:00
|
|
|
enum ActionStateFCounters {
|
2022-10-17 02:48:27 +00:00
|
|
|
FCounterGlayGolemAttack = 0,
|
|
|
|
}
|
|
|
|
|
2022-01-18 03:02:43 +00:00
|
|
|
let golem_melee_range = self.body.map_or(0.0, |b| b.max_radius()) + GOLEM_MELEE_RANGE;
|
|
|
|
// Fraction of health, used for activation of shockwave
|
|
|
|
// If golem don't have health for some reason, assume it's full
|
|
|
|
let health_fraction = self.health.map_or(1.0, |h| h.fraction());
|
|
|
|
// Magnitude squared of cross product of target velocity with golem orientation
|
|
|
|
let target_speed_cross_sqd = agent
|
|
|
|
.target
|
|
|
|
.as_ref()
|
|
|
|
.map(|t| t.target)
|
|
|
|
.and_then(|e| read_data.velocities.get(e))
|
|
|
|
.map_or(0.0, |v| v.0.cross(self.ori.look_vec()).magnitude_squared());
|
2022-04-26 23:34:29 +00:00
|
|
|
let line_of_sight_with_target = || {
|
|
|
|
entities_have_line_of_sight(self.pos, self.body, tgt_data.pos, tgt_data.body, read_data)
|
2022-04-24 20:15:30 +00:00
|
|
|
};
|
2022-04-22 01:32:56 +00:00
|
|
|
|
2022-01-18 03:02:43 +00:00
|
|
|
if attack_data.dist_sqrd < golem_melee_range.powi(2) {
|
2022-10-25 02:52:37 +00:00
|
|
|
if agent.action_state.counters[ActionStateFCounters::FCounterGlayGolemAttack as usize]
|
|
|
|
< 7.5
|
|
|
|
{
|
2022-01-18 03:02:43 +00:00
|
|
|
// If target is close, whack them
|
2022-01-26 19:09:59 +00:00
|
|
|
controller.push_basic_input(InputKind::Primary);
|
2022-10-25 02:52:37 +00:00
|
|
|
agent.action_state.counters
|
|
|
|
[ActionStateFCounters::FCounterGlayGolemAttack as usize] += read_data.dt.0;
|
2022-01-18 03:02:43 +00:00
|
|
|
} else {
|
|
|
|
// If whacked for too long, nuke them
|
2022-01-26 19:09:59 +00:00
|
|
|
controller.push_basic_input(InputKind::Ability(1));
|
2022-01-18 03:02:43 +00:00
|
|
|
if matches!(self.char_state, CharacterState::BasicRanged(c) if matches!(c.stage_section, StageSection::Recover))
|
|
|
|
{
|
2022-10-25 02:52:37 +00:00
|
|
|
agent.action_state.counters
|
|
|
|
[ActionStateFCounters::FCounterGlayGolemAttack as usize] = 0.0;
|
2022-01-18 03:02:43 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
} else if attack_data.dist_sqrd < GOLEM_LASER_RANGE.powi(2) {
|
|
|
|
if matches!(self.char_state, CharacterState::BasicBeam(c) if c.timer < Duration::from_secs(5))
|
|
|
|
|| target_speed_cross_sqd < GOLEM_TARGET_SPEED.powi(2)
|
2022-04-26 23:34:29 +00:00
|
|
|
&& line_of_sight_with_target()
|
2022-01-18 03:02:43 +00:00
|
|
|
&& attack_data.angle < 45.0
|
|
|
|
{
|
|
|
|
// If target in range threshold and haven't been lasering for more than 5
|
|
|
|
// seconds already or if target is moving slow-ish, laser them
|
2022-01-26 19:09:59 +00:00
|
|
|
controller.push_basic_input(InputKind::Secondary);
|
2022-01-18 03:02:43 +00:00
|
|
|
} else if health_fraction < 0.7 {
|
|
|
|
// Else target moving too fast for laser, shockwave time.
|
|
|
|
// But only if damaged enough
|
2022-01-26 19:09:59 +00:00
|
|
|
controller.push_basic_input(InputKind::Ability(0));
|
2022-01-18 03:02:43 +00:00
|
|
|
}
|
|
|
|
} else if attack_data.dist_sqrd < GOLEM_LONG_RANGE.powi(2) {
|
2022-04-26 23:34:29 +00:00
|
|
|
if target_speed_cross_sqd < GOLEM_TARGET_SPEED.powi(2) && line_of_sight_with_target() {
|
2022-01-18 03:02:43 +00:00
|
|
|
// If target is far-ish and moving slow-ish, rocket them
|
2022-01-26 19:09:59 +00:00
|
|
|
controller.push_basic_input(InputKind::Ability(1));
|
2022-01-18 03:02:43 +00:00
|
|
|
} else if health_fraction < 0.7 {
|
|
|
|
// Else target moving too fast for laser, shockwave time.
|
|
|
|
// But only if damaged enough
|
2022-01-26 19:09:59 +00:00
|
|
|
controller.push_basic_input(InputKind::Ability(0));
|
2022-01-18 03:02:43 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
// Make clay golem move towards target
|
2022-03-01 21:54:44 +00:00
|
|
|
self.path_toward_target(
|
|
|
|
agent,
|
|
|
|
controller,
|
|
|
|
tgt_data.pos.0,
|
|
|
|
read_data,
|
|
|
|
Path::Separate,
|
|
|
|
None,
|
|
|
|
);
|
2022-01-18 03:02:43 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
pub fn handle_tidal_warrior_attack(
|
|
|
|
&self,
|
|
|
|
agent: &mut Agent,
|
|
|
|
controller: &mut Controller,
|
|
|
|
attack_data: &AttackData,
|
|
|
|
tgt_data: &TargetData,
|
|
|
|
read_data: &ReadData,
|
|
|
|
) {
|
|
|
|
const SCUTTLE_RANGE: f32 = 40.0;
|
|
|
|
const BUBBLE_RANGE: f32 = 20.0;
|
|
|
|
const MINION_SUMMON_THRESHOLD: f32 = 0.20;
|
2022-10-17 02:48:27 +00:00
|
|
|
|
2022-10-25 02:52:37 +00:00
|
|
|
enum ActionStateConditions {
|
2022-10-17 02:48:27 +00:00
|
|
|
ConditionCounterInitialized = 0,
|
|
|
|
}
|
|
|
|
|
2022-10-25 02:52:37 +00:00
|
|
|
enum ActionStateFCounters {
|
2022-10-17 02:48:27 +00:00
|
|
|
FCounterMinionSummonThreshold = 0,
|
|
|
|
}
|
|
|
|
|
2022-01-18 03:02:43 +00:00
|
|
|
let health_fraction = self.health.map_or(0.5, |h| h.fraction());
|
2022-04-26 23:34:29 +00:00
|
|
|
let line_of_sight_with_target = || {
|
|
|
|
entities_have_line_of_sight(self.pos, self.body, tgt_data.pos, tgt_data.body, read_data)
|
2022-04-24 20:15:30 +00:00
|
|
|
};
|
2022-04-22 01:32:56 +00:00
|
|
|
|
2022-01-18 03:02:43 +00:00
|
|
|
// Sets counter at start of combat, using `condition` to keep track of whether
|
2022-07-15 16:59:37 +00:00
|
|
|
// it was already initialized
|
2022-10-25 02:52:37 +00:00
|
|
|
if !agent.action_state.conditions
|
|
|
|
[ActionStateConditions::ConditionCounterInitialized as usize]
|
|
|
|
{
|
|
|
|
agent.action_state.counters
|
|
|
|
[ActionStateFCounters::FCounterMinionSummonThreshold as usize] =
|
|
|
|
1.0 - MINION_SUMMON_THRESHOLD;
|
|
|
|
agent.action_state.conditions
|
|
|
|
[ActionStateConditions::ConditionCounterInitialized as usize] = true;
|
2022-01-18 03:02:43 +00:00
|
|
|
}
|
|
|
|
|
2022-10-25 02:52:37 +00:00
|
|
|
if agent.action_state.counters[ActionStateFCounters::FCounterMinionSummonThreshold as usize]
|
|
|
|
> health_fraction
|
|
|
|
{
|
2022-01-18 03:02:43 +00:00
|
|
|
// Summon minions at particular thresholds of health
|
2022-01-26 19:09:59 +00:00
|
|
|
controller.push_basic_input(InputKind::Ability(1));
|
2022-01-18 03:02:43 +00:00
|
|
|
|
|
|
|
if matches!(self.char_state, CharacterState::BasicSummon(c) if matches!(c.stage_section, StageSection::Recover))
|
|
|
|
{
|
2022-10-25 02:52:37 +00:00
|
|
|
agent.action_state.counters
|
|
|
|
[ActionStateFCounters::FCounterMinionSummonThreshold as usize] -=
|
|
|
|
MINION_SUMMON_THRESHOLD;
|
2022-01-18 03:02:43 +00:00
|
|
|
}
|
|
|
|
} else if attack_data.dist_sqrd < SCUTTLE_RANGE.powi(2) {
|
|
|
|
if matches!(self.char_state, CharacterState::DashMelee(c) if !matches!(c.stage_section, StageSection::Recover))
|
|
|
|
{
|
|
|
|
// Keep scuttling if already in dash melee and not in recover
|
2022-01-26 19:09:59 +00:00
|
|
|
controller.push_basic_input(InputKind::Secondary);
|
2022-01-18 03:02:43 +00:00
|
|
|
} else if attack_data.dist_sqrd < BUBBLE_RANGE.powi(2) {
|
|
|
|
if matches!(self.char_state, CharacterState::BasicBeam(c) if !matches!(c.stage_section, StageSection::Recover) && c.timer < Duration::from_secs(10))
|
|
|
|
{
|
|
|
|
// Keep shooting bubbles at them if already in basic beam and not in recover and
|
|
|
|
// have not been bubbling too long
|
2022-01-26 19:09:59 +00:00
|
|
|
controller.push_basic_input(InputKind::Ability(0));
|
2022-01-18 03:02:43 +00:00
|
|
|
} else if attack_data.in_min_range() && attack_data.angle < 60.0 {
|
|
|
|
// Pincer them if they're in range and angle
|
2022-01-26 19:09:59 +00:00
|
|
|
controller.push_basic_input(InputKind::Primary);
|
2022-04-26 23:34:29 +00:00
|
|
|
} else if attack_data.angle < 30.0 && line_of_sight_with_target() {
|
2022-01-18 03:02:43 +00:00
|
|
|
// Start bubbling them if not close enough to do something else and in angle and
|
|
|
|
// can see target
|
2022-01-26 19:09:59 +00:00
|
|
|
controller.push_basic_input(InputKind::Ability(0));
|
2022-01-18 03:02:43 +00:00
|
|
|
}
|
2022-04-26 23:34:29 +00:00
|
|
|
} else if attack_data.angle < 90.0 && line_of_sight_with_target() {
|
2022-01-18 03:02:43 +00:00
|
|
|
// Start scuttling if not close enough to do something else and in angle and can
|
|
|
|
// see target
|
2022-01-26 19:09:59 +00:00
|
|
|
controller.push_basic_input(InputKind::Secondary);
|
2022-01-18 03:02:43 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
// Always attempt to path towards target
|
2022-03-01 21:54:44 +00:00
|
|
|
self.path_toward_target(
|
|
|
|
agent,
|
|
|
|
controller,
|
|
|
|
tgt_data.pos.0,
|
|
|
|
read_data,
|
|
|
|
Path::Partial,
|
|
|
|
None,
|
|
|
|
);
|
2022-01-18 03:02:43 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
pub fn handle_yeti_attack(
|
|
|
|
&self,
|
|
|
|
agent: &mut Agent,
|
|
|
|
controller: &mut Controller,
|
|
|
|
attack_data: &AttackData,
|
|
|
|
tgt_data: &TargetData,
|
|
|
|
read_data: &ReadData,
|
|
|
|
) {
|
|
|
|
const ICE_SPIKES_RANGE: f32 = 15.0;
|
|
|
|
const ICE_BREATH_RANGE: f32 = 10.0;
|
|
|
|
const ICE_BREATH_TIMER: f32 = 10.0;
|
|
|
|
const SNOWBALL_MAX_RANGE: f32 = 50.0;
|
|
|
|
|
2022-10-25 02:52:37 +00:00
|
|
|
enum ActionStateFCounters {
|
2022-10-17 02:48:27 +00:00
|
|
|
FCounterYetiAttack = 0,
|
|
|
|
}
|
|
|
|
|
2022-10-25 02:52:37 +00:00
|
|
|
agent.action_state.counters[ActionStateFCounters::FCounterYetiAttack as usize] +=
|
|
|
|
read_data.dt.0;
|
2022-01-18 03:02:43 +00:00
|
|
|
|
|
|
|
if attack_data.dist_sqrd < ICE_BREATH_RANGE.powi(2) {
|
|
|
|
if matches!(self.char_state, CharacterState::BasicBeam(c) if c.timer < Duration::from_secs(2))
|
|
|
|
{
|
|
|
|
// Keep using ice breath for 2 second
|
2022-01-26 19:09:59 +00:00
|
|
|
controller.push_basic_input(InputKind::Ability(0));
|
2022-10-25 02:52:37 +00:00
|
|
|
} else if agent.action_state.counters[ActionStateFCounters::FCounterYetiAttack as usize]
|
|
|
|
> ICE_BREATH_TIMER
|
|
|
|
{
|
2022-01-18 03:02:43 +00:00
|
|
|
// Use ice breath if timer has gone for long enough
|
2022-01-26 19:09:59 +00:00
|
|
|
controller.push_basic_input(InputKind::Ability(0));
|
2022-01-18 03:02:43 +00:00
|
|
|
|
|
|
|
if matches!(self.char_state, CharacterState::BasicBeam(_)) {
|
|
|
|
// Resets action counter when using beam
|
2022-10-25 02:52:37 +00:00
|
|
|
agent.action_state.counters
|
|
|
|
[ActionStateFCounters::FCounterYetiAttack as usize] = 0.0;
|
2022-01-18 03:02:43 +00:00
|
|
|
}
|
|
|
|
} else if attack_data.in_min_range() {
|
|
|
|
// Basic attack if on top of them
|
2022-01-26 19:09:59 +00:00
|
|
|
controller.push_basic_input(InputKind::Primary);
|
2022-01-18 03:02:43 +00:00
|
|
|
} else {
|
|
|
|
// Use ice spikes if too far for other abilities
|
2022-01-26 19:09:59 +00:00
|
|
|
controller.push_basic_input(InputKind::Secondary);
|
2022-01-18 03:02:43 +00:00
|
|
|
}
|
|
|
|
} else if attack_data.dist_sqrd < ICE_SPIKES_RANGE.powi(2) && attack_data.angle < 60.0 {
|
|
|
|
// Use ice spikes if in range
|
2022-01-26 19:09:59 +00:00
|
|
|
controller.push_basic_input(InputKind::Secondary);
|
2022-01-18 03:02:43 +00:00
|
|
|
} else if attack_data.dist_sqrd < SNOWBALL_MAX_RANGE.powi(2) && attack_data.angle < 60.0 {
|
|
|
|
// Otherwise, chuck all the snowballs
|
2022-01-26 19:09:59 +00:00
|
|
|
controller.push_basic_input(InputKind::Ability(1));
|
2022-01-18 03:02:43 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Always attempt to path towards target
|
2022-03-01 21:54:44 +00:00
|
|
|
self.path_toward_target(
|
|
|
|
agent,
|
|
|
|
controller,
|
|
|
|
tgt_data.pos.0,
|
|
|
|
read_data,
|
|
|
|
Path::Partial,
|
|
|
|
None,
|
|
|
|
);
|
2022-01-18 03:02:43 +00:00
|
|
|
}
|
|
|
|
|
2022-11-10 12:53:12 +00:00
|
|
|
pub fn handle_roshwalr_attack(
|
|
|
|
&self,
|
|
|
|
agent: &mut Agent,
|
|
|
|
controller: &mut Controller,
|
|
|
|
attack_data: &AttackData,
|
|
|
|
tgt_data: &TargetData,
|
|
|
|
read_data: &ReadData,
|
|
|
|
) {
|
|
|
|
const SLOW_CHARGE_RANGE: f32 = 20.0;
|
|
|
|
const SHOCKWAVE_RANGE: f32 = 12.5;
|
|
|
|
const SHOCKWAVE_TIMER: f32 = 10.0;
|
|
|
|
|
|
|
|
enum ActionStateFCounters {
|
|
|
|
FCounterRoshwalrAttack = 0,
|
|
|
|
}
|
|
|
|
|
|
|
|
agent.action_state.counters[ActionStateFCounters::FCounterRoshwalrAttack as usize] +=
|
|
|
|
read_data.dt.0;
|
|
|
|
if matches!(self.char_state, CharacterState::DashMelee(c) if !matches!(c.stage_section, StageSection::Recover))
|
|
|
|
{
|
|
|
|
// If already charging, keep charging if not in recover
|
|
|
|
controller.push_basic_input(InputKind::Ability(0));
|
|
|
|
} else if attack_data.dist_sqrd < SHOCKWAVE_RANGE.powi(2) {
|
|
|
|
if agent.action_state.counters[ActionStateFCounters::FCounterRoshwalrAttack as usize]
|
|
|
|
> SHOCKWAVE_TIMER
|
|
|
|
{
|
|
|
|
// Use shockwave if timer has gone for long enough
|
|
|
|
controller.push_basic_input(InputKind::Ability(0));
|
|
|
|
|
|
|
|
if matches!(self.char_state, CharacterState::Shockwave(_)) {
|
|
|
|
// Resets action counter when using shockwave
|
|
|
|
agent.action_state.counters
|
|
|
|
[ActionStateFCounters::FCounterRoshwalrAttack as usize] = 0.0;
|
|
|
|
}
|
|
|
|
} else if attack_data.in_min_range() {
|
|
|
|
// Basic attack if on top of them
|
|
|
|
controller.push_basic_input(InputKind::Primary);
|
|
|
|
} else {
|
|
|
|
// Use slow charge if too far for other abilities
|
|
|
|
controller.push_basic_input(InputKind::Secondary);
|
|
|
|
}
|
|
|
|
} else if attack_data.dist_sqrd < SLOW_CHARGE_RANGE.powi(2) {
|
|
|
|
// Use slow charge if in range
|
|
|
|
controller.push_basic_input(InputKind::Secondary);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Always attempt to path towards target
|
|
|
|
self.path_toward_target(
|
|
|
|
agent,
|
|
|
|
controller,
|
|
|
|
tgt_data.pos.0,
|
|
|
|
read_data,
|
|
|
|
Path::Partial,
|
|
|
|
None,
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2022-01-18 03:02:43 +00:00
|
|
|
pub fn handle_harvester_attack(
|
|
|
|
&self,
|
|
|
|
agent: &mut Agent,
|
|
|
|
controller: &mut Controller,
|
|
|
|
attack_data: &AttackData,
|
|
|
|
tgt_data: &TargetData,
|
|
|
|
read_data: &ReadData,
|
|
|
|
) {
|
|
|
|
const VINE_CREATION_THRESHOLD: f32 = 0.50;
|
|
|
|
const FIRE_BREATH_RANGE: f32 = 20.0;
|
|
|
|
const MAX_PUMPKIN_RANGE: f32 = 50.0;
|
2022-10-17 02:48:27 +00:00
|
|
|
|
2022-10-17 03:13:03 +00:00
|
|
|
enum ActionStateConditions {
|
2022-10-17 02:48:27 +00:00
|
|
|
ConditionHasSummonedVines = 0,
|
|
|
|
}
|
|
|
|
|
2022-01-18 03:02:43 +00:00
|
|
|
let health_fraction = self.health.map_or(0.5, |h| h.fraction());
|
2022-04-26 23:34:29 +00:00
|
|
|
let line_of_sight_with_target = || {
|
|
|
|
entities_have_line_of_sight(self.pos, self.body, tgt_data.pos, tgt_data.body, read_data)
|
2022-04-24 20:15:30 +00:00
|
|
|
};
|
2022-01-18 03:02:43 +00:00
|
|
|
|
2022-10-25 02:52:37 +00:00
|
|
|
if health_fraction < VINE_CREATION_THRESHOLD
|
|
|
|
&& !agent.action_state.conditions
|
|
|
|
[ActionStateConditions::ConditionHasSummonedVines as usize]
|
|
|
|
{
|
2022-01-18 03:02:43 +00:00
|
|
|
// Summon vines when reach threshold of health
|
2022-01-26 19:09:59 +00:00
|
|
|
controller.push_basic_input(InputKind::Ability(0));
|
2022-01-18 03:02:43 +00:00
|
|
|
|
|
|
|
if matches!(self.char_state, CharacterState::SpriteSummon(c) if matches!(c.stage_section, StageSection::Recover))
|
|
|
|
{
|
2022-10-25 02:52:37 +00:00
|
|
|
agent.action_state.conditions
|
|
|
|
[ActionStateConditions::ConditionHasSummonedVines as usize] = true;
|
2022-01-18 03:02:43 +00:00
|
|
|
}
|
|
|
|
} else if attack_data.dist_sqrd < FIRE_BREATH_RANGE.powi(2) {
|
|
|
|
if matches!(self.char_state, CharacterState::BasicBeam(c) if c.timer < Duration::from_secs(5))
|
2022-04-26 23:34:29 +00:00
|
|
|
&& line_of_sight_with_target()
|
2022-01-18 03:02:43 +00:00
|
|
|
{
|
|
|
|
// Keep breathing fire if close enough, can see target, and have not been
|
|
|
|
// breathing for more than 5 seconds
|
2022-01-26 19:09:59 +00:00
|
|
|
controller.push_basic_input(InputKind::Secondary);
|
2022-01-18 03:02:43 +00:00
|
|
|
} else if attack_data.in_min_range() && attack_data.angle < 60.0 {
|
|
|
|
// Scythe them if they're in range and angle
|
2022-01-26 19:09:59 +00:00
|
|
|
controller.push_basic_input(InputKind::Primary);
|
2022-04-26 23:34:29 +00:00
|
|
|
} else if attack_data.angle < 30.0 && line_of_sight_with_target() {
|
2022-01-18 03:02:43 +00:00
|
|
|
// Start breathing fire at them if close enough, in angle, and can see target
|
2022-01-26 19:09:59 +00:00
|
|
|
controller.push_basic_input(InputKind::Secondary);
|
2022-01-18 03:02:43 +00:00
|
|
|
}
|
2022-04-26 23:34:29 +00:00
|
|
|
} else if attack_data.dist_sqrd < MAX_PUMPKIN_RANGE.powi(2) && line_of_sight_with_target() {
|
2022-01-18 03:02:43 +00:00
|
|
|
// Throw a pumpkin at them if close enough and can see them
|
2022-01-26 19:09:59 +00:00
|
|
|
controller.push_basic_input(InputKind::Ability(1));
|
2022-01-18 03:02:43 +00:00
|
|
|
}
|
|
|
|
// Always attempt to path towards target
|
2022-03-01 21:54:44 +00:00
|
|
|
self.path_toward_target(
|
|
|
|
agent,
|
|
|
|
controller,
|
|
|
|
tgt_data.pos.0,
|
|
|
|
read_data,
|
|
|
|
Path::Partial,
|
|
|
|
None,
|
|
|
|
);
|
2022-01-18 03:02:43 +00:00
|
|
|
}
|
2022-01-30 23:04:09 +00:00
|
|
|
|
2022-08-24 21:50:53 +00:00
|
|
|
pub fn handle_cardinal_attack(
|
|
|
|
&self,
|
|
|
|
agent: &mut Agent,
|
|
|
|
controller: &mut Controller,
|
|
|
|
attack_data: &AttackData,
|
|
|
|
tgt_data: &TargetData,
|
|
|
|
read_data: &ReadData,
|
|
|
|
rng: &mut impl Rng,
|
|
|
|
) {
|
|
|
|
const DESIRED_ENERGY_LEVEL: f32 = 50.0;
|
|
|
|
const DESIRED_COMBO_LEVEL: u32 = 8;
|
|
|
|
const MINION_SUMMON_THRESHOLD: f32 = 0.10;
|
2022-10-17 02:48:27 +00:00
|
|
|
|
2022-10-17 03:13:03 +00:00
|
|
|
enum ActionStateConditions {
|
2022-10-17 02:48:27 +00:00
|
|
|
ConditionCounterInitialized = 0,
|
|
|
|
}
|
|
|
|
|
2022-10-25 02:52:37 +00:00
|
|
|
enum ActionStateFCounters {
|
2022-10-17 02:48:27 +00:00
|
|
|
FCounterHealthThreshold = 0,
|
|
|
|
}
|
|
|
|
|
2022-08-24 21:50:53 +00:00
|
|
|
let health_fraction = self.health.map_or(0.5, |h| h.fraction());
|
|
|
|
// Sets counter at start of combat, using `condition` to keep track of whether
|
|
|
|
// it was already intitialized
|
2022-10-25 02:52:37 +00:00
|
|
|
if !agent.action_state.conditions
|
|
|
|
[ActionStateConditions::ConditionCounterInitialized as usize]
|
|
|
|
{
|
|
|
|
agent.action_state.counters[ActionStateFCounters::FCounterHealthThreshold as usize] =
|
|
|
|
1.0 - MINION_SUMMON_THRESHOLD;
|
|
|
|
agent.action_state.conditions
|
|
|
|
[ActionStateConditions::ConditionCounterInitialized as usize] = true;
|
2022-08-24 21:50:53 +00:00
|
|
|
}
|
|
|
|
|
2022-10-25 02:52:37 +00:00
|
|
|
if agent.action_state.counters[ActionStateFCounters::FCounterHealthThreshold as usize]
|
|
|
|
> health_fraction
|
|
|
|
{
|
2022-08-24 21:50:53 +00:00
|
|
|
// Summon minions at particular thresholds of health
|
|
|
|
controller.push_basic_input(InputKind::Ability(1));
|
|
|
|
|
|
|
|
if matches!(self.char_state, CharacterState::BasicSummon(c) if matches!(c.stage_section, StageSection::Recover))
|
|
|
|
{
|
2022-10-25 02:52:37 +00:00
|
|
|
agent.action_state.counters
|
|
|
|
[ActionStateFCounters::FCounterHealthThreshold as usize] -=
|
|
|
|
MINION_SUMMON_THRESHOLD;
|
2022-08-24 21:50:53 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
// Logic to use abilities
|
|
|
|
else if attack_data.dist_sqrd > attack_data.min_attack_dist.powi(2)
|
|
|
|
&& entities_have_line_of_sight(
|
|
|
|
self.pos,
|
|
|
|
self.body,
|
|
|
|
tgt_data.pos,
|
|
|
|
tgt_data.body,
|
|
|
|
read_data,
|
|
|
|
)
|
|
|
|
{
|
|
|
|
// If far enough away, and can see target, check which skill is appropriate to
|
|
|
|
// use
|
|
|
|
if self.energy.current() > DESIRED_ENERGY_LEVEL
|
|
|
|
&& read_data
|
|
|
|
.combos
|
|
|
|
.get(*self.entity)
|
|
|
|
.map_or(false, |c| c.counter() >= DESIRED_COMBO_LEVEL)
|
|
|
|
&& !read_data.buffs.get(*self.entity).iter().any(|buff| {
|
|
|
|
buff.iter_kind(BuffKind::Regeneration)
|
|
|
|
.peekable()
|
|
|
|
.peek()
|
|
|
|
.is_some()
|
|
|
|
})
|
|
|
|
{
|
|
|
|
// If have enough energy and combo to use healing aura, do so
|
|
|
|
controller.push_basic_input(InputKind::Secondary);
|
|
|
|
} else if self
|
|
|
|
.skill_set
|
|
|
|
.has_skill(Skill::Sceptre(SceptreSkill::UnlockAura))
|
|
|
|
&& self.energy.current() > DESIRED_ENERGY_LEVEL
|
|
|
|
&& !read_data.buffs.get(*self.entity).iter().any(|buff| {
|
|
|
|
buff.iter_kind(BuffKind::ProtectingWard)
|
|
|
|
.peekable()
|
|
|
|
.peek()
|
|
|
|
.is_some()
|
|
|
|
})
|
|
|
|
{
|
2022-09-22 01:04:01 +00:00
|
|
|
// Use steam beam if target is far enough away, self is not buffed, and have
|
2022-08-24 21:50:53 +00:00
|
|
|
// sufficient energy
|
|
|
|
controller.push_basic_input(InputKind::Ability(0));
|
|
|
|
} else {
|
|
|
|
// If low on energy, use primary to attempt to regen energy
|
|
|
|
// Or if at desired energy level but not able/willing to ward, just attack
|
|
|
|
controller.push_basic_input(InputKind::Primary);
|
|
|
|
}
|
|
|
|
} else if attack_data.dist_sqrd < (2.0 * attack_data.min_attack_dist).powi(2) {
|
|
|
|
if self.body.map_or(false, |b| b.is_humanoid())
|
2022-10-24 01:12:59 +00:00
|
|
|
&& self.energy.current() > CharacterAbility::default_roll().energy_cost()
|
2022-08-24 21:50:53 +00:00
|
|
|
&& !matches!(self.char_state, CharacterState::BasicAura(c) if !matches!(c.stage_section, StageSection::Recover))
|
|
|
|
{
|
2022-09-22 01:04:01 +00:00
|
|
|
// Else use steam beam
|
|
|
|
controller.push_basic_input(InputKind::Ability(0));
|
2022-08-24 21:50:53 +00:00
|
|
|
} else if attack_data.angle < 15.0 {
|
|
|
|
controller.push_basic_input(InputKind::Primary);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// Logic to move. Intentionally kept separate from ability logic where possible
|
|
|
|
// so duplicated work is less necessary.
|
|
|
|
if attack_data.dist_sqrd < (2.0 * attack_data.min_attack_dist).powi(2) {
|
|
|
|
// Attempt to move away from target if too close
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
} else if attack_data.dist_sqrd < MAX_PATH_DIST.powi(2) {
|
|
|
|
// Else attempt to circle target if neither too close nor too far
|
|
|
|
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 entities_have_line_of_sight(
|
|
|
|
self.pos,
|
|
|
|
self.body,
|
|
|
|
tgt_data.pos,
|
|
|
|
tgt_data.body,
|
|
|
|
read_data,
|
|
|
|
) && attack_data.angle < 45.0
|
|
|
|
{
|
|
|
|
controller.inputs.move_dir = bearing
|
|
|
|
.xy()
|
|
|
|
.rotated_z(rng.gen_range(0.5..1.57))
|
|
|
|
.try_normalized()
|
|
|
|
.unwrap_or_else(Vec2::zero)
|
|
|
|
* speed;
|
|
|
|
} else {
|
|
|
|
// Unless cannot see target, then move towards them
|
|
|
|
controller.inputs.move_dir =
|
|
|
|
bearing.xy().try_normalized().unwrap_or_else(Vec2::zero) * speed;
|
|
|
|
self.jump_if(bearing.z > 1.5, controller);
|
|
|
|
controller.inputs.move_z = bearing.z;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// Sometimes try to roll
|
|
|
|
if self.body.map(|b| b.is_humanoid()).unwrap_or(false)
|
|
|
|
&& !matches!(self.char_state, CharacterState::BasicAura(_))
|
|
|
|
&& attack_data.dist_sqrd < 16.0f32.powi(2)
|
|
|
|
&& rng.gen::<f32>() < 0.01
|
|
|
|
{
|
|
|
|
controller.push_basic_input(InputKind::Roll);
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
// If too far, move towards target
|
|
|
|
self.path_toward_target(
|
|
|
|
agent,
|
|
|
|
controller,
|
|
|
|
tgt_data.pos.0,
|
|
|
|
read_data,
|
|
|
|
Path::Partial,
|
|
|
|
None,
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn handle_dagon_attack(
|
|
|
|
&self,
|
|
|
|
agent: &mut Agent,
|
|
|
|
controller: &mut Controller,
|
|
|
|
attack_data: &AttackData,
|
|
|
|
tgt_data: &TargetData,
|
|
|
|
read_data: &ReadData,
|
|
|
|
) {
|
2022-10-25 02:52:37 +00:00
|
|
|
enum ActionStateTimers {
|
2022-10-17 02:48:27 +00:00
|
|
|
TimerDagon = 0,
|
|
|
|
}
|
|
|
|
|
2022-10-17 03:13:03 +00:00
|
|
|
if agent.action_state.timers[ActionStateTimers::TimerDagon as usize] > 2.5 {
|
|
|
|
agent.action_state.timers[ActionStateTimers::TimerDagon as usize] = 0.0;
|
2022-09-22 01:04:01 +00:00
|
|
|
}
|
|
|
|
// if close to target lay out sea urchins, use steambeam and shoot dagon bombs
|
|
|
|
if attack_data.dist_sqrd < (1.3 * attack_data.min_attack_dist).powi(2) {
|
2022-08-24 21:50:53 +00:00
|
|
|
controller.inputs.move_dir = Vec2::zero();
|
2022-10-17 03:13:03 +00:00
|
|
|
if agent.action_state.timers[ActionStateTimers::TimerDagon as usize] > 2.0 {
|
2022-08-24 21:50:53 +00:00
|
|
|
controller.push_basic_input(InputKind::Primary);
|
2022-10-17 03:13:03 +00:00
|
|
|
agent.action_state.timers[ActionStateTimers::TimerDagon as usize] += read_data.dt.0;
|
|
|
|
} else if agent.action_state.timers[ActionStateTimers::TimerDagon as usize] > 1.0 {
|
2022-09-22 01:04:01 +00:00
|
|
|
controller.push_basic_input(InputKind::Ability(1));
|
2022-08-24 21:50:53 +00:00
|
|
|
} else {
|
|
|
|
controller.push_basic_input(InputKind::Secondary);
|
2022-10-17 03:13:03 +00:00
|
|
|
agent.action_state.timers[ActionStateTimers::TimerDagon as usize] += read_data.dt.0;
|
2022-08-24 21:50:53 +00:00
|
|
|
}
|
2022-09-22 01:04:01 +00:00
|
|
|
} else if attack_data.dist_sqrd > (3.0 * attack_data.min_attack_dist).powi(2) {
|
|
|
|
// if enemy is far, heal
|
|
|
|
controller.push_basic_input(InputKind::Ability(2));
|
2022-10-17 03:13:03 +00:00
|
|
|
agent.action_state.timers[ActionStateTimers::TimerDagon as usize] += read_data.dt.0;
|
2022-09-22 01:04:01 +00:00
|
|
|
} else if entities_have_line_of_sight(
|
|
|
|
self.pos,
|
|
|
|
self.body,
|
|
|
|
tgt_data.pos,
|
|
|
|
tgt_data.body,
|
|
|
|
read_data,
|
|
|
|
) {
|
|
|
|
// if in range shoot dagon bombs and steamwave
|
2022-10-17 03:13:03 +00:00
|
|
|
if agent.action_state.timers[ActionStateTimers::TimerDagon as usize] > 1.0 {
|
2022-09-22 01:04:01 +00:00
|
|
|
controller.push_basic_input(InputKind::Primary);
|
2022-10-17 03:13:03 +00:00
|
|
|
agent.action_state.timers[ActionStateTimers::TimerDagon as usize] += read_data.dt.0;
|
2022-09-22 01:04:01 +00:00
|
|
|
} else {
|
|
|
|
controller.push_basic_input(InputKind::Ability(0));
|
2022-10-17 03:13:03 +00:00
|
|
|
agent.action_state.timers[ActionStateTimers::TimerDagon as usize] += read_data.dt.0;
|
2022-09-22 01:04:01 +00:00
|
|
|
}
|
2022-08-24 21:50:53 +00:00
|
|
|
}
|
|
|
|
// chase
|
|
|
|
let path = if attack_data.dist_sqrd < MAX_PATH_DIST.powi(2) {
|
|
|
|
Path::Separate
|
|
|
|
} else {
|
|
|
|
Path::Partial
|
|
|
|
};
|
|
|
|
self.path_toward_target(agent, controller, tgt_data.pos.0, read_data, path, None);
|
|
|
|
}
|
|
|
|
|
2022-01-30 23:04:09 +00:00
|
|
|
pub fn handle_deadwood(
|
|
|
|
&self,
|
|
|
|
agent: &mut Agent,
|
|
|
|
controller: &mut Controller,
|
|
|
|
attack_data: &AttackData,
|
|
|
|
tgt_data: &TargetData,
|
|
|
|
read_data: &ReadData,
|
|
|
|
) {
|
|
|
|
const BEAM_RANGE: f32 = 20.0;
|
|
|
|
const BEAM_TIME: Duration = Duration::from_secs(3);
|
|
|
|
// action_state.condition controls whether or not deadwood should beam or dash
|
|
|
|
if matches!(self.char_state, CharacterState::DashMelee(s) if s.stage_section != StageSection::Recover)
|
|
|
|
{
|
|
|
|
// If already dashing, keep dashing and have move_dir set to forward
|
|
|
|
controller.push_basic_input(InputKind::Secondary);
|
|
|
|
controller.inputs.move_dir = self.ori.look_vec().xy();
|
2022-03-27 20:02:38 +00:00
|
|
|
} else if attack_data.in_min_range() && attack_data.angle_xy < 10.0 {
|
2022-01-30 23:04:09 +00:00
|
|
|
// If near target, dash at them and through them to get away
|
|
|
|
controller.push_basic_input(InputKind::Secondary);
|
|
|
|
} else if matches!(self.char_state, CharacterState::BasicBeam(s) if s.stage_section != StageSection::Recover && s.timer < BEAM_TIME)
|
|
|
|
{
|
|
|
|
// If already beaming, keep beaming if not beaming for over 5 seconds
|
|
|
|
controller.push_basic_input(InputKind::Primary);
|
|
|
|
} else if attack_data.dist_sqrd < BEAM_RANGE.powi(2) {
|
|
|
|
// Else if in beam range, beam them
|
2022-03-27 20:02:38 +00:00
|
|
|
if attack_data.angle_xy < 5.0 {
|
2022-01-30 23:04:09 +00:00
|
|
|
controller.push_basic_input(InputKind::Primary);
|
|
|
|
} else {
|
|
|
|
// If not in angle, apply slight movement so deadwood orients itself correctly
|
2022-03-01 21:54:44 +00:00
|
|
|
controller.inputs.move_dir = (tgt_data.pos.0 - self.pos.0)
|
2022-01-30 23:04:09 +00:00
|
|
|
.xy()
|
|
|
|
.try_normalized()
|
|
|
|
.unwrap_or_else(Vec2::zero)
|
|
|
|
* 0.01;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
// Otherwise too far, move towards target
|
2022-03-01 21:54:44 +00:00
|
|
|
self.path_toward_target(
|
|
|
|
agent,
|
|
|
|
controller,
|
|
|
|
tgt_data.pos.0,
|
|
|
|
read_data,
|
|
|
|
Path::Partial,
|
|
|
|
None,
|
|
|
|
);
|
2022-01-30 23:04:09 +00:00
|
|
|
}
|
|
|
|
}
|
2022-02-02 05:17:06 +00:00
|
|
|
|
|
|
|
pub fn handle_mandragora(
|
|
|
|
&self,
|
|
|
|
agent: &mut Agent,
|
|
|
|
controller: &mut Controller,
|
|
|
|
attack_data: &AttackData,
|
|
|
|
tgt_data: &TargetData,
|
|
|
|
read_data: &ReadData,
|
|
|
|
) {
|
|
|
|
const SCREAM_RANGE: f32 = 10.0;
|
|
|
|
|
2022-10-25 02:52:37 +00:00
|
|
|
enum ActionStateFCounters {
|
2022-10-17 02:48:27 +00:00
|
|
|
FCounterHealthThreshold = 0,
|
|
|
|
}
|
|
|
|
|
2022-10-25 02:52:37 +00:00
|
|
|
enum ActionStateConditions {
|
2022-10-17 02:48:27 +00:00
|
|
|
ConditionHasScreamed = 0,
|
|
|
|
}
|
|
|
|
|
2022-02-02 05:17:06 +00:00
|
|
|
if !agent.action_state.initialized {
|
2022-10-25 02:52:37 +00:00
|
|
|
agent.action_state.counters[ActionStateFCounters::FCounterHealthThreshold as usize] =
|
|
|
|
self.health.map_or(0.0, |h| h.maximum());
|
2022-02-02 05:17:06 +00:00
|
|
|
agent.action_state.initialized = true;
|
|
|
|
}
|
|
|
|
|
2022-10-17 03:13:03 +00:00
|
|
|
if !agent.action_state.conditions[ActionStateConditions::ConditionHasScreamed as usize] {
|
2022-02-02 05:17:06 +00:00
|
|
|
// If mandragora is still "sleeping" and hasn't screamed yet, do nothing until
|
|
|
|
// target in range or until it's taken damage
|
2022-10-25 02:52:37 +00:00
|
|
|
if self.health.map_or(false, |h| {
|
|
|
|
h.current()
|
|
|
|
< agent.action_state.counters
|
|
|
|
[ActionStateFCounters::FCounterHealthThreshold as usize]
|
|
|
|
}) || attack_data.dist_sqrd < SCREAM_RANGE.powi(2)
|
2022-02-02 05:17:06 +00:00
|
|
|
{
|
2022-10-25 02:52:37 +00:00
|
|
|
agent.action_state.conditions
|
|
|
|
[ActionStateConditions::ConditionHasScreamed as usize] = true;
|
2022-02-02 05:17:06 +00:00
|
|
|
controller.push_basic_input(InputKind::Secondary);
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
// Once mandragora has woken, move towards target and attack
|
|
|
|
if attack_data.in_min_range() {
|
|
|
|
controller.push_basic_input(InputKind::Primary);
|
|
|
|
} else if attack_data.dist_sqrd < MAX_PATH_DIST.powi(2)
|
2022-04-26 23:34:29 +00:00
|
|
|
&& entities_have_line_of_sight(
|
|
|
|
self.pos,
|
|
|
|
self.body,
|
|
|
|
tgt_data.pos,
|
|
|
|
tgt_data.body,
|
|
|
|
read_data,
|
|
|
|
)
|
2022-02-02 05:17:06 +00:00
|
|
|
{
|
|
|
|
// If in pathing range and can see target, move towards them
|
2022-03-01 21:54:44 +00:00
|
|
|
self.path_toward_target(
|
|
|
|
agent,
|
|
|
|
controller,
|
|
|
|
tgt_data.pos.0,
|
|
|
|
read_data,
|
|
|
|
Path::Partial,
|
|
|
|
None,
|
|
|
|
);
|
2022-02-02 05:17:06 +00:00
|
|
|
} else {
|
|
|
|
// Otherwise, go back to sleep
|
2022-10-25 02:52:37 +00:00
|
|
|
agent.action_state.conditions
|
|
|
|
[ActionStateConditions::ConditionHasScreamed as usize] = false;
|
|
|
|
agent.action_state.counters
|
|
|
|
[ActionStateFCounters::FCounterHealthThreshold as usize] =
|
|
|
|
self.health.map_or(0.0, |h| h.maximum());
|
2022-02-02 05:17:06 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2022-02-03 01:45:43 +00:00
|
|
|
|
|
|
|
pub fn handle_wood_golem(
|
|
|
|
&self,
|
|
|
|
agent: &mut Agent,
|
|
|
|
controller: &mut Controller,
|
|
|
|
attack_data: &AttackData,
|
|
|
|
tgt_data: &TargetData,
|
|
|
|
read_data: &ReadData,
|
|
|
|
) {
|
|
|
|
const SHOCKWAVE_RANGE: f32 = 25.0;
|
|
|
|
const SHOCKWAVE_WAIT_TIME: f32 = 7.5;
|
|
|
|
const SPIN_WAIT_TIME: f32 = 3.0;
|
|
|
|
|
2022-10-25 02:52:37 +00:00
|
|
|
enum ActionStateTimers {
|
2022-10-17 02:48:27 +00:00
|
|
|
TimerSpinWait = 0,
|
|
|
|
TimerShockwaveWait,
|
|
|
|
}
|
|
|
|
|
2022-02-03 01:45:43 +00:00
|
|
|
// After spinning, reset timer
|
|
|
|
if matches!(self.char_state, CharacterState::SpinMelee(s) if s.stage_section == StageSection::Recover)
|
|
|
|
{
|
2022-10-17 03:13:03 +00:00
|
|
|
agent.action_state.timers[ActionStateTimers::TimerSpinWait as usize] = 0.0;
|
2022-02-03 01:45:43 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if attack_data.in_min_range() {
|
|
|
|
// If in minimum range
|
2022-10-25 02:52:37 +00:00
|
|
|
if agent.action_state.timers[ActionStateTimers::TimerSpinWait as usize] > SPIN_WAIT_TIME
|
|
|
|
{
|
2022-02-03 01:45:43 +00:00
|
|
|
// If it's been too long since able to hit target, spin
|
|
|
|
controller.push_basic_input(InputKind::Secondary);
|
|
|
|
} else if attack_data.angle < 30.0 {
|
|
|
|
// Else if in angle to strike, strike
|
|
|
|
controller.push_basic_input(InputKind::Primary);
|
|
|
|
} else {
|
|
|
|
// Else increment spin timer
|
2022-10-25 02:52:37 +00:00
|
|
|
agent.action_state.timers[ActionStateTimers::TimerSpinWait as usize] +=
|
|
|
|
read_data.dt.0;
|
2022-02-03 01:45:43 +00:00
|
|
|
// If not in angle, apply slight movement so golem orients itself correctly
|
2022-03-01 21:54:44 +00:00
|
|
|
controller.inputs.move_dir = (tgt_data.pos.0 - self.pos.0)
|
2022-02-03 01:45:43 +00:00
|
|
|
.xy()
|
|
|
|
.try_normalized()
|
|
|
|
.unwrap_or_else(Vec2::zero)
|
|
|
|
* 0.01;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
// Else if too far for melee
|
|
|
|
if attack_data.dist_sqrd < SHOCKWAVE_RANGE.powi(2) && attack_data.angle < 45.0 {
|
|
|
|
// Shockwave if close enough and haven't shockwaved too recently
|
2022-10-25 02:52:37 +00:00
|
|
|
if agent.action_state.timers[ActionStateTimers::TimerSpinWait as usize]
|
|
|
|
> SHOCKWAVE_WAIT_TIME
|
|
|
|
{
|
2022-02-03 01:45:43 +00:00
|
|
|
controller.push_basic_input(InputKind::Ability(0));
|
|
|
|
}
|
|
|
|
if matches!(self.char_state, CharacterState::Shockwave(_)) {
|
2022-10-17 03:13:03 +00:00
|
|
|
agent.action_state.timers[ActionStateTimers::TimerShockwaveWait as usize] = 0.0;
|
2022-02-03 01:45:43 +00:00
|
|
|
} else {
|
2022-10-25 02:52:37 +00:00
|
|
|
agent.action_state.timers[ActionStateTimers::TimerShockwaveWait as usize] +=
|
|
|
|
read_data.dt.0;
|
2022-02-03 01:45:43 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
// And always try to path towards target
|
2022-03-01 21:54:44 +00:00
|
|
|
self.path_toward_target(
|
|
|
|
agent,
|
|
|
|
controller,
|
|
|
|
tgt_data.pos.0,
|
|
|
|
read_data,
|
|
|
|
Path::Partial,
|
|
|
|
None,
|
|
|
|
);
|
2022-02-03 01:45:43 +00:00
|
|
|
}
|
|
|
|
}
|
2022-02-09 01:23:23 +00:00
|
|
|
|
|
|
|
pub fn handle_gnarling_chieftain(
|
|
|
|
&self,
|
|
|
|
agent: &mut Agent,
|
|
|
|
controller: &mut Controller,
|
|
|
|
attack_data: &AttackData,
|
|
|
|
tgt_data: &TargetData,
|
|
|
|
read_data: &ReadData,
|
|
|
|
rng: &mut impl Rng,
|
|
|
|
) {
|
2022-02-11 06:23:09 +00:00
|
|
|
const TOTEM_TIMER: f32 = 10.0;
|
|
|
|
const HEAVY_ATTACK_WAIT_TIME: f32 = 15.0;
|
2022-02-09 01:23:23 +00:00
|
|
|
|
2022-10-17 03:13:03 +00:00
|
|
|
enum ActionStateTimers {
|
2022-10-17 02:48:27 +00:00
|
|
|
TimerSummonTotem = 0,
|
|
|
|
TimerShockwave,
|
|
|
|
}
|
2022-02-09 01:23:23 +00:00
|
|
|
// Handle timers
|
2022-10-17 03:13:03 +00:00
|
|
|
agent.action_state.timers[ActionStateTimers::TimerSummonTotem as usize] += read_data.dt.0;
|
2022-02-09 01:23:23 +00:00
|
|
|
match self.char_state {
|
2022-10-25 02:52:37 +00:00
|
|
|
CharacterState::BasicSummon(_) => {
|
|
|
|
agent.action_state.timers[ActionStateTimers::TimerSummonTotem as usize] = 0.0
|
|
|
|
},
|
2022-02-09 01:23:23 +00:00
|
|
|
CharacterState::Shockwave(_) | CharacterState::BasicRanged(_) => {
|
2022-10-17 03:13:03 +00:00
|
|
|
agent.action_state.counters[ActionStateTimers::TimerShockwave as usize] = 0.0
|
2022-02-09 01:23:23 +00:00
|
|
|
},
|
|
|
|
_ => {},
|
|
|
|
}
|
|
|
|
|
2022-02-11 06:23:09 +00:00
|
|
|
if !agent.action_state.initialized {
|
|
|
|
// If not initialized yet, start out by summoning green totem
|
|
|
|
controller.push_basic_input(InputKind::Ability(2));
|
2022-02-11 07:07:15 +00:00
|
|
|
if matches!(self.char_state, CharacterState::BasicSummon(s) if s.stage_section == StageSection::Recover)
|
|
|
|
{
|
2022-02-11 06:23:09 +00:00
|
|
|
agent.action_state.initialized = true;
|
|
|
|
}
|
2022-10-25 02:52:37 +00:00
|
|
|
} else if agent.action_state.timers[ActionStateTimers::TimerSummonTotem as usize]
|
|
|
|
> TOTEM_TIMER
|
|
|
|
{
|
2022-02-11 06:23:09 +00:00
|
|
|
// If time to summon a totem, do it
|
2022-02-09 01:23:23 +00:00
|
|
|
let input = rng.gen_range(1..=3);
|
2022-02-13 01:52:01 +00:00
|
|
|
let buff_kind = match input {
|
|
|
|
2 => Some(BuffKind::Regeneration),
|
|
|
|
3 => Some(BuffKind::Hastened),
|
|
|
|
_ => None,
|
|
|
|
};
|
|
|
|
if buff_kind.map_or(true, |b| self.has_buff(read_data, b))
|
|
|
|
&& matches!(self.char_state, CharacterState::Wielding { .. })
|
|
|
|
{
|
|
|
|
// If already under effects of buff from totem that would be summoned, don't
|
|
|
|
// summon totem (doesn't work for red totems since that applies debuff to
|
|
|
|
// enemies instead)
|
2022-10-17 03:13:03 +00:00
|
|
|
agent.action_state.timers[ActionStateTimers::TimerSummonTotem as usize] = 0.0;
|
2022-02-13 01:52:01 +00:00
|
|
|
} else {
|
|
|
|
controller.push_basic_input(InputKind::Ability(input));
|
|
|
|
}
|
2022-10-25 02:52:37 +00:00
|
|
|
} else if agent.action_state.counters[ActionStateTimers::TimerShockwave as usize]
|
|
|
|
> HEAVY_ATTACK_WAIT_TIME
|
|
|
|
{
|
2022-02-09 01:23:23 +00:00
|
|
|
// Else if time for a heavy attack
|
|
|
|
if attack_data.in_min_range() {
|
|
|
|
// If in range, shockwave
|
|
|
|
controller.push_basic_input(InputKind::Ability(0));
|
2022-04-26 23:34:29 +00:00
|
|
|
} else if entities_have_line_of_sight(
|
|
|
|
self.pos,
|
|
|
|
self.body,
|
|
|
|
tgt_data.pos,
|
|
|
|
tgt_data.body,
|
|
|
|
read_data,
|
|
|
|
) {
|
2022-02-09 01:23:23 +00:00
|
|
|
// Else if in sight, barrage
|
|
|
|
controller.push_basic_input(InputKind::Secondary);
|
|
|
|
}
|
|
|
|
} else if attack_data.in_min_range() {
|
|
|
|
// Else if not time to use anything fancy, if in range and angle, strike them
|
|
|
|
if attack_data.angle < 20.0 {
|
|
|
|
controller.push_basic_input(InputKind::Primary);
|
2022-10-25 02:52:37 +00:00
|
|
|
agent.action_state.counters[ActionStateTimers::TimerShockwave as usize] +=
|
|
|
|
read_data.dt.0;
|
2022-02-09 01:23:23 +00:00
|
|
|
} else {
|
|
|
|
// If not in angle, charge heavy attack faster
|
2022-10-25 02:52:37 +00:00
|
|
|
agent.action_state.counters[ActionStateTimers::TimerShockwave as usize] +=
|
|
|
|
read_data.dt.0 * 5.0;
|
2022-02-09 01:23:23 +00:00
|
|
|
}
|
|
|
|
} else {
|
|
|
|
// If not in range, charge heavy attack faster
|
2022-10-25 02:52:37 +00:00
|
|
|
agent.action_state.counters[ActionStateTimers::TimerShockwave as usize] +=
|
|
|
|
read_data.dt.0 * 3.3;
|
2022-02-09 01:23:23 +00:00
|
|
|
}
|
|
|
|
|
2022-03-01 21:54:44 +00:00
|
|
|
self.path_toward_target(
|
|
|
|
agent,
|
|
|
|
controller,
|
|
|
|
tgt_data.pos.0,
|
|
|
|
read_data,
|
|
|
|
Path::Full,
|
|
|
|
None,
|
|
|
|
);
|
2022-02-09 01:23:23 +00:00
|
|
|
}
|
2022-01-18 03:02:43 +00:00
|
|
|
}
|