mirror of
https://gitlab.com/veloren/veloren.git
synced 2024-08-30 18:12:32 +00:00
Non-attack potion of sword AI
This commit is contained in:
parent
ca9fdfa291
commit
387ea16598
@ -40,6 +40,8 @@ const ACTIONSTATE_NUMBER_OF_CONCURRENT_INT_COUNTERS: usize = 5;
|
||||
/// The number of booleans that a single Action node can track concurrently
|
||||
/// Define constants within a given action node to index between them.
|
||||
const ACTIONSTATE_NUMBER_OF_CONCURRENT_CONDITIONS: usize = 5;
|
||||
/// The number of positions that can be remembered by an agent
|
||||
const ACTIONSTATE_NUMBER_OF_CONCURRENT_POSITIONS: usize = 5;
|
||||
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub enum Alignment {
|
||||
@ -630,6 +632,7 @@ pub struct ActionState {
|
||||
pub counters: [f32; ACTIONSTATE_NUMBER_OF_CONCURRENT_COUNTERS],
|
||||
pub conditions: [bool; ACTIONSTATE_NUMBER_OF_CONCURRENT_CONDITIONS],
|
||||
pub int_counters: [u8; ACTIONSTATE_NUMBER_OF_CONCURRENT_INT_COUNTERS],
|
||||
pub positions: [Option<Vec3<f32>>; ACTIONSTATE_NUMBER_OF_CONCURRENT_POSITIONS],
|
||||
pub initialized: bool,
|
||||
}
|
||||
|
||||
|
@ -1,12 +1,12 @@
|
||||
use crate::{consts::MAX_PATH_DIST, data::*, util::entities_have_line_of_sight};
|
||||
use common::{
|
||||
comp::{
|
||||
ability::{self, Ability, ActiveAbilities, AuxiliaryAbility, Capability},
|
||||
ability::{ActiveAbilities, AuxiliaryAbility},
|
||||
buff::BuffKind,
|
||||
item::tool::AbilityContext,
|
||||
skills::{AxeSkill, BowSkill, HammerSkill, SceptreSkill, Skill, StaffSkill, SwordSkill},
|
||||
AbilityInput, Agent, CharacterAbility, CharacterState, ControlAction, ControlEvent,
|
||||
Controller, InputKind, Stance,
|
||||
Controller, InputKind,
|
||||
},
|
||||
path::TraversalConfig,
|
||||
states::{self_buff, sprite_summon, utils::StageSection},
|
||||
@ -15,7 +15,6 @@ use common::{
|
||||
vol::ReadVol,
|
||||
};
|
||||
use rand::{prelude::SliceRandom, Rng};
|
||||
use specs::saveload::MarkerAllocator;
|
||||
use std::{f32::consts::PI, time::Duration};
|
||||
use vek::*;
|
||||
|
||||
@ -510,11 +509,6 @@ impl<'a> AgentData<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
enum IntCounters {
|
||||
Tactics = 0,
|
||||
ActionMode = 1,
|
||||
}
|
||||
|
||||
if !agent.action_state.initialized {
|
||||
let available_tactics = {
|
||||
let mut tactics = Vec::new();
|
||||
@ -737,7 +731,7 @@ impl<'a> AgentData<'a> {
|
||||
}
|
||||
|
||||
agent.action_state.int_counters[IntCounters::ActionMode as usize] =
|
||||
ActionMode::Guarded as u8;
|
||||
ActionMode::Reckless as u8;
|
||||
}
|
||||
|
||||
enum ActionMode {
|
||||
@ -757,15 +751,282 @@ impl<'a> AgentData<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(health) = self.health {
|
||||
agent.action_state.int_counters[IntCounters::ActionMode as usize] =
|
||||
if health.fraction() < 0.25 {
|
||||
agent.action_state.positions[Positions::GuardedCover as usize] = None;
|
||||
ActionMode::Fleeing as u8
|
||||
} else if health.fraction() < 0.75 {
|
||||
agent.action_state.positions[Positions::Flee as usize] = None;
|
||||
ActionMode::Guarded as u8
|
||||
} else {
|
||||
agent.action_state.positions[Positions::GuardedCover as usize] = None;
|
||||
agent.action_state.positions[Positions::Flee as usize] = None;
|
||||
ActionMode::Reckless as u8
|
||||
};
|
||||
}
|
||||
|
||||
enum IntCounters {
|
||||
Tactics = 0,
|
||||
ActionMode = 1,
|
||||
}
|
||||
|
||||
enum Timers {
|
||||
GuardedCycle = 0,
|
||||
PosTimeOut = 1,
|
||||
}
|
||||
|
||||
enum Conditions {
|
||||
GuardedAttack = 0,
|
||||
RollingBreakThrough = 1,
|
||||
}
|
||||
|
||||
enum FloatCounters {
|
||||
GuardedTimer = 0,
|
||||
}
|
||||
|
||||
enum Positions {
|
||||
GuardedCover = 0,
|
||||
Flee = 1,
|
||||
}
|
||||
|
||||
if self.vel.0.magnitude_squared() < 1_f32.powi(2) {
|
||||
agent.action_state.timers[Timers::PosTimeOut as usize] += read_data.dt.0;
|
||||
} else {
|
||||
agent.action_state.timers[Timers::PosTimeOut as usize] = 0.0;
|
||||
}
|
||||
|
||||
if agent.action_state.timers[Timers::PosTimeOut as usize] > 2.0 {
|
||||
agent.action_state.positions.iter_mut().for_each(|pos| {
|
||||
*pos = None;
|
||||
});
|
||||
agent.action_state.timers[Timers::PosTimeOut as usize] = 0.0;
|
||||
}
|
||||
|
||||
let attempt_attack = match ActionMode::from_u8(
|
||||
agent.action_state.int_counters[IntCounters::ActionMode as usize],
|
||||
) {
|
||||
ActionMode::Reckless => true,
|
||||
ActionMode::Guarded => true,
|
||||
ActionMode::Fleeing => false,
|
||||
ActionMode::Guarded => {
|
||||
agent.action_state.timers[Timers::GuardedCycle as usize] += read_data.dt.0;
|
||||
if agent.action_state.timers[Timers::GuardedCycle as usize]
|
||||
> agent.action_state.counters[FloatCounters::GuardedTimer as usize]
|
||||
{
|
||||
agent.action_state.timers[Timers::GuardedCycle as usize] = 0.0;
|
||||
agent.action_state.counters[FloatCounters::GuardedTimer as usize] =
|
||||
rng.gen_range(3.0..8.0);
|
||||
agent.action_state.conditions[Conditions::GuardedAttack as usize] ^= true;
|
||||
}
|
||||
if let Some(pos) = agent.action_state.positions[Positions::GuardedCover as usize] {
|
||||
if pos.distance_squared(self.pos.0) < 3_f32.powi(2) {
|
||||
agent.action_state.positions[Positions::GuardedCover as usize] = None;
|
||||
}
|
||||
}
|
||||
if agent.action_state.conditions[Conditions::GuardedAttack as usize] {
|
||||
agent.action_state.positions[Positions::GuardedCover as usize] = None;
|
||||
true
|
||||
} else {
|
||||
if attack_data.dist_sqrd > 10_f32.powi(2) {
|
||||
// Choose random point to either side when looking at target and move
|
||||
// towards it
|
||||
if let Some(pos) =
|
||||
agent.action_state.positions[Positions::GuardedCover as usize]
|
||||
{
|
||||
if pos.distance_squared(self.pos.0) < 5_f32.powi(2) {
|
||||
agent.action_state.positions[Positions::GuardedCover as usize] =
|
||||
None;
|
||||
}
|
||||
self.path_toward_target(
|
||||
agent,
|
||||
controller,
|
||||
pos,
|
||||
read_data,
|
||||
Path::Separate,
|
||||
None,
|
||||
);
|
||||
} else {
|
||||
agent.action_state.positions[Positions::GuardedCover as usize] = {
|
||||
let rand_dir = {
|
||||
let dir = (tgt_data.pos.0 - self.pos.0)
|
||||
.try_normalized()
|
||||
.unwrap_or(Vec3::unit_x())
|
||||
.xy();
|
||||
if rng.gen_bool(0.5) {
|
||||
dir.rotated_z(PI / 2.0 + rng.gen_range(-0.75..0.0))
|
||||
} else {
|
||||
dir.rotated_z(-PI / 2.0 + rng.gen_range(-0.0..0.75))
|
||||
}
|
||||
};
|
||||
let attempted_dist = rng.gen_range(6.0..16.0);
|
||||
let actual_dist = read_data
|
||||
.terrain
|
||||
.ray(
|
||||
self.pos.0 + Vec3::unit_z() * 0.5,
|
||||
self.pos.0
|
||||
+ Vec3::unit_z() * 0.5
|
||||
+ rand_dir * attempted_dist,
|
||||
)
|
||||
.until(Block::is_solid)
|
||||
.cast()
|
||||
.0
|
||||
- 1.0;
|
||||
Some(self.pos.0 + rand_dir * actual_dist)
|
||||
};
|
||||
}
|
||||
} else {
|
||||
if let Some(pos) =
|
||||
agent.action_state.positions[Positions::GuardedCover as usize]
|
||||
{
|
||||
self.path_toward_target(
|
||||
agent,
|
||||
controller,
|
||||
pos,
|
||||
read_data,
|
||||
Path::Separate,
|
||||
None,
|
||||
);
|
||||
if agent.action_state.conditions
|
||||
[Conditions::RollingBreakThrough as usize]
|
||||
{
|
||||
controller.push_basic_input(InputKind::Roll);
|
||||
agent.action_state.conditions
|
||||
[Conditions::RollingBreakThrough as usize] = false;
|
||||
}
|
||||
if tgt_data.char_state.map_or(false, |cs| cs.is_melee_attack()) {
|
||||
controller.push_basic_input(InputKind::Block);
|
||||
}
|
||||
} else {
|
||||
agent.action_state.positions[Positions::GuardedCover as usize] = {
|
||||
let backwards = (self.pos.0 - tgt_data.pos.0)
|
||||
.try_normalized()
|
||||
.unwrap_or(Vec3::unit_x())
|
||||
.xy();
|
||||
let pos = if read_data
|
||||
.terrain
|
||||
.ray(
|
||||
self.pos.0 + Vec3::unit_z() * 0.5,
|
||||
self.pos.0 + Vec3::unit_z() * 0.5 + backwards * 6.0,
|
||||
)
|
||||
.until(Block::is_solid)
|
||||
.cast()
|
||||
.0
|
||||
> 5.0
|
||||
{
|
||||
self.pos.0 + backwards * 5.0
|
||||
} else {
|
||||
agent.action_state.conditions
|
||||
[Conditions::RollingBreakThrough as usize] = true;
|
||||
self.pos.0
|
||||
- backwards
|
||||
* read_data
|
||||
.terrain
|
||||
.ray(
|
||||
self.pos.0 + Vec3::unit_z() * 0.5,
|
||||
self.pos.0 + Vec3::unit_z() * 0.5
|
||||
- backwards * 10.0,
|
||||
)
|
||||
.until(Block::is_solid)
|
||||
.cast()
|
||||
.0
|
||||
- 1.0
|
||||
};
|
||||
Some(pos)
|
||||
}
|
||||
}
|
||||
}
|
||||
false
|
||||
}
|
||||
},
|
||||
ActionMode::Fleeing => {
|
||||
if agent.action_state.conditions[Conditions::RollingBreakThrough as usize] {
|
||||
controller.push_basic_input(InputKind::Roll);
|
||||
agent.action_state.conditions[Conditions::RollingBreakThrough as usize] = false;
|
||||
}
|
||||
if let Some(pos) = agent.action_state.positions[Positions::Flee as usize] {
|
||||
if let Some(dir) = Dir::from_unnormalized(pos - self.pos.0) {
|
||||
controller.inputs.look_dir = dir;
|
||||
}
|
||||
if pos.distance_squared(self.pos.0) < 5_f32.powi(2) {
|
||||
agent.action_state.positions[Positions::Flee as usize] = None;
|
||||
}
|
||||
self.path_toward_target(
|
||||
agent,
|
||||
controller,
|
||||
pos,
|
||||
read_data,
|
||||
Path::Separate,
|
||||
None,
|
||||
);
|
||||
} else {
|
||||
agent.action_state.positions[Positions::Flee as usize] = {
|
||||
let rand_dir = {
|
||||
let dir = (self.pos.0 - tgt_data.pos.0)
|
||||
.try_normalized()
|
||||
.unwrap_or(Vec3::unit_x())
|
||||
.xy();
|
||||
dir.rotated_z(rng.gen_range(-0.75..0.75))
|
||||
};
|
||||
let attempted_dist = rng.gen_range(16.0..26.0);
|
||||
let actual_dist = read_data
|
||||
.terrain
|
||||
.ray(
|
||||
self.pos.0 + Vec3::unit_z() * 0.5,
|
||||
self.pos.0 + Vec3::unit_z() * 0.5 + rand_dir * attempted_dist,
|
||||
)
|
||||
.until(Block::is_solid)
|
||||
.cast()
|
||||
.0
|
||||
- 1.0;
|
||||
if actual_dist < 10.0 {
|
||||
let dist = read_data
|
||||
.terrain
|
||||
.ray(
|
||||
self.pos.0 + Vec3::unit_z() * 0.5,
|
||||
self.pos.0 + Vec3::unit_z() * 0.5 - rand_dir * attempted_dist,
|
||||
)
|
||||
.until(Block::is_solid)
|
||||
.cast()
|
||||
.0
|
||||
- 1.0;
|
||||
agent.action_state.conditions
|
||||
[Conditions::RollingBreakThrough as usize] = true;
|
||||
Some(self.pos.0 - rand_dir * actual_dist)
|
||||
} else {
|
||||
Some(self.pos.0 + rand_dir * actual_dist)
|
||||
}
|
||||
};
|
||||
}
|
||||
false
|
||||
},
|
||||
};
|
||||
|
||||
let attack_failed = if attempt_attack {
|
||||
let context = AbilityContext::from(self.stance);
|
||||
let extract_ability = |input: AbilityInput| {
|
||||
AbilityData::from_ability(
|
||||
&self
|
||||
.active_abilities
|
||||
.activate_ability(
|
||||
input,
|
||||
Some(self.inventory),
|
||||
self.skill_set,
|
||||
self.body,
|
||||
Some(self.char_state),
|
||||
context,
|
||||
)
|
||||
.unwrap_or_default()
|
||||
.0,
|
||||
)
|
||||
};
|
||||
let primary = extract_ability(AbilityInput::Primary);
|
||||
let secondary = extract_ability(AbilityInput::Secondary);
|
||||
let abilities = [
|
||||
extract_ability(AbilityInput::Auxiliary(0)),
|
||||
extract_ability(AbilityInput::Auxiliary(1)),
|
||||
extract_ability(AbilityInput::Auxiliary(2)),
|
||||
extract_ability(AbilityInput::Auxiliary(3)),
|
||||
extract_ability(AbilityInput::Auxiliary(4)),
|
||||
];
|
||||
match SwordTactics::from_u8(
|
||||
agent.action_state.int_counters[IntCounters::Tactics as usize],
|
||||
) {
|
||||
|
@ -1,5 +1,6 @@
|
||||
use common::{
|
||||
comp::{
|
||||
ability::CharacterAbility,
|
||||
buff::{BuffKind, Buffs},
|
||||
group,
|
||||
item::MaterialStatManifest,
|
||||
@ -193,153 +194,191 @@ pub enum Path {
|
||||
Partial,
|
||||
}
|
||||
|
||||
pub struct ComboMeleeData {
|
||||
pub range: f32,
|
||||
pub angle: f32,
|
||||
pub energy: f32,
|
||||
pub enum AbilityData {
|
||||
ComboMelee {
|
||||
range: f32,
|
||||
angle: f32,
|
||||
energy_per_strike: f32,
|
||||
},
|
||||
FinisherMelee {
|
||||
range: f32,
|
||||
angle: f32,
|
||||
energy: f32,
|
||||
combo: u32,
|
||||
},
|
||||
SelfBuff {
|
||||
buff: BuffKind,
|
||||
energy: f32,
|
||||
},
|
||||
DiveMelee {
|
||||
range: f32,
|
||||
angle: f32,
|
||||
energy: f32,
|
||||
},
|
||||
DashMelee {
|
||||
range: f32,
|
||||
angle: f32,
|
||||
initial_energy: f32,
|
||||
energy_drain: f32,
|
||||
speed: f32,
|
||||
charge_dur: f32,
|
||||
},
|
||||
RapidMelee {
|
||||
range: f32,
|
||||
angle: f32,
|
||||
energy_per_strike: f32,
|
||||
strikes: u32,
|
||||
combo: u32,
|
||||
},
|
||||
}
|
||||
|
||||
impl ComboMeleeData {
|
||||
pub fn could_use(&self, attack_data: &AttackData, agent_data: &AgentData) -> bool {
|
||||
attack_data.dist_sqrd
|
||||
< (self.range + agent_data.body.map_or(0.0, |b| b.max_radius())).powi(2)
|
||||
&& attack_data.angle < self.angle
|
||||
&& agent_data.energy.current() >= self.energy
|
||||
impl AbilityData {
|
||||
pub fn from_ability(ability: &CharacterAbility) -> Option<Self> {
|
||||
use CharacterAbility::*;
|
||||
let inner = match ability {
|
||||
ComboMelee2 {
|
||||
strikes,
|
||||
energy_cost_per_strike,
|
||||
..
|
||||
} => {
|
||||
let (range, angle) = strikes
|
||||
.iter()
|
||||
.map(|s| (s.melee_constructor.range, s.melee_constructor.angle))
|
||||
.fold(
|
||||
(100.0, 360.0),
|
||||
|(r1, a1): (f32, f32), (r2, a2): (f32, f32)| (r1.min(r2), a1.min(a2)),
|
||||
);
|
||||
Self::ComboMelee {
|
||||
range,
|
||||
angle,
|
||||
energy_per_strike: *energy_cost_per_strike,
|
||||
}
|
||||
},
|
||||
FinisherMelee {
|
||||
energy_cost,
|
||||
melee_constructor,
|
||||
minimum_combo,
|
||||
..
|
||||
} => Self::FinisherMelee {
|
||||
energy: *energy_cost,
|
||||
range: melee_constructor.range,
|
||||
angle: melee_constructor.angle,
|
||||
combo: *minimum_combo,
|
||||
},
|
||||
SelfBuff {
|
||||
buff_kind,
|
||||
energy_cost,
|
||||
..
|
||||
} => Self::SelfBuff {
|
||||
buff: *buff_kind,
|
||||
energy: *energy_cost,
|
||||
},
|
||||
DiveMelee {
|
||||
energy_cost,
|
||||
melee_constructor,
|
||||
..
|
||||
} => Self::DiveMelee {
|
||||
energy: *energy_cost,
|
||||
range: melee_constructor.range,
|
||||
angle: melee_constructor.angle,
|
||||
},
|
||||
DashMelee {
|
||||
energy_cost,
|
||||
energy_drain,
|
||||
forward_speed,
|
||||
melee_constructor,
|
||||
charge_duration,
|
||||
..
|
||||
} => Self::DashMelee {
|
||||
initial_energy: *energy_cost,
|
||||
energy_drain: *energy_drain,
|
||||
range: melee_constructor.range,
|
||||
angle: melee_constructor.angle,
|
||||
charge_dur: *charge_duration,
|
||||
speed: *forward_speed,
|
||||
},
|
||||
RapidMelee {
|
||||
energy_cost,
|
||||
max_strikes,
|
||||
minimum_combo,
|
||||
melee_constructor,
|
||||
..
|
||||
} => Self::RapidMelee {
|
||||
energy_per_strike: *energy_cost,
|
||||
range: melee_constructor.range,
|
||||
angle: melee_constructor.angle,
|
||||
strikes: max_strikes.unwrap_or(100),
|
||||
combo: *minimum_combo,
|
||||
},
|
||||
_ => return None,
|
||||
};
|
||||
Some(inner)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct FinisherMeleeData {
|
||||
pub range: f32,
|
||||
pub angle: f32,
|
||||
pub energy: f32,
|
||||
pub combo: u32,
|
||||
}
|
||||
|
||||
impl FinisherMeleeData {
|
||||
pub fn could_use(&self, attack_data: &AttackData, agent_data: &AgentData) -> bool {
|
||||
attack_data.dist_sqrd
|
||||
< (self.range + agent_data.body.map_or(0.0, |b| b.max_radius())).powi(2)
|
||||
&& attack_data.angle < self.angle
|
||||
&& agent_data.energy.current() >= self.energy
|
||||
&& agent_data
|
||||
.combo
|
||||
.map_or(false, |c| c.counter() >= self.combo)
|
||||
}
|
||||
|
||||
pub fn use_desirable(&self, tgt_data: &TargetData, agent_data: &AgentData) -> bool {
|
||||
let combo_factor =
|
||||
agent_data.combo.map_or(0, |c| c.counter()) as f32 / self.combo as f32 * 2.0;
|
||||
let tgt_health_factor = tgt_data.health.map_or(0.0, |h| h.current()) / 50.0;
|
||||
let self_health_factor = agent_data.health.map_or(0.0, |h| h.current()) / 50.0;
|
||||
// Use becomes more desirable if either self or target is close to death
|
||||
combo_factor > tgt_health_factor.min(self_health_factor)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct SelfBuffData {
|
||||
pub buff: BuffKind,
|
||||
pub energy: f32,
|
||||
}
|
||||
|
||||
impl SelfBuffData {
|
||||
pub fn could_use(&self, agent_data: &AgentData) -> bool {
|
||||
agent_data.energy.current() >= self.energy
|
||||
}
|
||||
|
||||
pub fn use_desirable(&self, agent_data: &AgentData) -> bool {
|
||||
agent_data
|
||||
.buffs
|
||||
.map_or(false, |buffs| !buffs.contains(self.buff))
|
||||
}
|
||||
}
|
||||
|
||||
pub struct DiveMeleeData {
|
||||
pub range: f32,
|
||||
pub angle: f32,
|
||||
pub energy: f32,
|
||||
}
|
||||
|
||||
impl DiveMeleeData {
|
||||
// Hack here refers to agents using the mildly unintended method of roll jumping
|
||||
// to achieve the required downwards vertical speed to enter dive melee when on
|
||||
// flat ground.
|
||||
pub fn npc_should_use_hack(&self, agent_data: &AgentData, tgt_data: &TargetData) -> bool {
|
||||
let dist_sqrd_2d = agent_data.pos.0.xy().distance_squared(tgt_data.pos.0.xy());
|
||||
agent_data.energy.current() > self.energy
|
||||
&& agent_data.physics_state.on_ground.is_some()
|
||||
&& agent_data.pos.0.z >= tgt_data.pos.0.z
|
||||
&& dist_sqrd_2d
|
||||
> ((self.range + agent_data.body.map_or(0.0, |b| b.max_radius())) / 2.0).powi(2)
|
||||
&& dist_sqrd_2d
|
||||
< (self.range + agent_data.body.map_or(0.0, |b| b.max_radius()) + 5.0).powi(2)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct BlockData {
|
||||
pub angle: f32,
|
||||
// Should probably just always use 5 or so unless riposte melee
|
||||
pub range: f32,
|
||||
pub energy: f32,
|
||||
}
|
||||
|
||||
impl BlockData {
|
||||
pub fn could_use(&self, attack_data: &AttackData, agent_data: &AgentData) -> bool {
|
||||
attack_data.dist_sqrd
|
||||
< (self.range + agent_data.body.map_or(0.0, |b| b.max_radius())).powi(2)
|
||||
&& attack_data.angle < self.angle
|
||||
&& agent_data.energy.current() >= self.energy
|
||||
}
|
||||
}
|
||||
|
||||
pub struct DashMeleeData {
|
||||
pub range: f32,
|
||||
pub angle: f32,
|
||||
pub initial_energy: f32,
|
||||
pub energy_drain: f32,
|
||||
pub speed: f32,
|
||||
pub charge_dur: f32,
|
||||
}
|
||||
|
||||
impl DashMeleeData {
|
||||
// TODO: Maybe figure out better way of pulling in base accel from body and
|
||||
// accounting for friction?
|
||||
const BASE_SPEED: f32 = 3.0;
|
||||
const ORI_RATE: f32 = 30.0;
|
||||
|
||||
pub fn could_use(&self, attack_data: &AttackData, agent_data: &AgentData) -> bool {
|
||||
let charge_dur = self.charge_dur(agent_data);
|
||||
let charge_dist = charge_dur * self.speed * Self::BASE_SPEED;
|
||||
let attack_dist =
|
||||
charge_dist + self.range + agent_data.body.map_or(0.0, |b| b.max_radius());
|
||||
let ori_gap = Self::ORI_RATE * charge_dur;
|
||||
attack_data.dist_sqrd < attack_dist.powi(2)
|
||||
&& attack_data.angle < self.angle + ori_gap
|
||||
&& agent_data.energy.current() > self.initial_energy
|
||||
}
|
||||
|
||||
pub fn use_desirable(&self, attack_data: &AttackData, agent_data: &AgentData) -> bool {
|
||||
let charge_dist = self.charge_dur(agent_data) * self.speed * Self::BASE_SPEED;
|
||||
attack_data.dist_sqrd / charge_dist.powi(2) > 0.75_f32.powi(2)
|
||||
}
|
||||
|
||||
fn charge_dur(&self, agent_data: &AgentData) -> f32 {
|
||||
((agent_data.energy.current() - self.initial_energy) / self.energy_drain)
|
||||
.clamp(0.0, self.charge_dur)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct RapidMeleeData {
|
||||
pub range: f32,
|
||||
pub angle: f32,
|
||||
pub energy: f32,
|
||||
pub strikes: u32,
|
||||
}
|
||||
|
||||
impl RapidMeleeData {
|
||||
pub fn could_use(&self, attack_data: &AttackData, agent_data: &AgentData) -> bool {
|
||||
attack_data.dist_sqrd
|
||||
< (self.range + agent_data.body.map_or(0.0, |b| b.max_radius())).powi(2)
|
||||
&& attack_data.angle < self.angle
|
||||
&& agent_data.energy.current() > self.energy * self.strikes as f32
|
||||
let melee_check = |range: f32, angle| {
|
||||
attack_data.dist_sqrd
|
||||
< (range + agent_data.body.map_or(0.0, |b| b.max_radius())).powi(2)
|
||||
&& attack_data.angle < angle
|
||||
};
|
||||
let energy_check = |energy| agent_data.energy.current() >= energy;
|
||||
let combo_check = |combo| agent_data.combo.map_or(false, |c| c.counter() >= combo);
|
||||
use AbilityData::*;
|
||||
match self {
|
||||
ComboMelee {
|
||||
range,
|
||||
angle,
|
||||
energy_per_strike,
|
||||
} => melee_check(*range, *angle) && energy_check(*energy_per_strike),
|
||||
FinisherMelee {
|
||||
range,
|
||||
angle,
|
||||
energy,
|
||||
combo,
|
||||
} => melee_check(*range, *angle) && energy_check(*energy) && combo_check(*combo),
|
||||
SelfBuff { buff, energy } => {
|
||||
energy_check(*energy)
|
||||
&& agent_data
|
||||
.buffs
|
||||
.map_or(false, |buffs| !buffs.contains(*buff))
|
||||
},
|
||||
DiveMelee {
|
||||
range,
|
||||
angle,
|
||||
energy,
|
||||
} => melee_check(*range, *angle) && energy_check(*energy),
|
||||
DashMelee {
|
||||
range,
|
||||
angle,
|
||||
initial_energy,
|
||||
energy_drain,
|
||||
speed,
|
||||
charge_dur,
|
||||
} => {
|
||||
// TODO: Maybe figure out better way of pulling in base accel from body and
|
||||
// accounting for friction?
|
||||
const BASE_SPEED: f32 = 3.0;
|
||||
const ORI_RATE: f32 = 30.0;
|
||||
let charge_dur = ((agent_data.energy.current() - initial_energy) / energy_drain)
|
||||
.clamp(0.0, *charge_dur);
|
||||
let charge_dist = charge_dur * speed * BASE_SPEED;
|
||||
let attack_dist = charge_dist + range;
|
||||
let ori_gap = ORI_RATE * charge_dur;
|
||||
melee_check(attack_dist, angle + ori_gap)
|
||||
&& energy_check(*initial_energy)
|
||||
&& attack_data.dist_sqrd / charge_dist.powi(2) > 0.75_f32.powi(2)
|
||||
},
|
||||
RapidMelee {
|
||||
range,
|
||||
angle,
|
||||
energy_per_strike,
|
||||
strikes,
|
||||
combo,
|
||||
} => {
|
||||
melee_check(*range, *angle)
|
||||
&& energy_check(*energy_per_strike * *strikes as f32)
|
||||
&& combo_check(*combo)
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user