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
|
/// The number of booleans that a single Action node can track concurrently
|
||||||
/// Define constants within a given action node to index between them.
|
/// Define constants within a given action node to index between them.
|
||||||
const ACTIONSTATE_NUMBER_OF_CONCURRENT_CONDITIONS: usize = 5;
|
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)]
|
#[derive(Copy, Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
|
||||||
pub enum Alignment {
|
pub enum Alignment {
|
||||||
@ -630,6 +632,7 @@ pub struct ActionState {
|
|||||||
pub counters: [f32; ACTIONSTATE_NUMBER_OF_CONCURRENT_COUNTERS],
|
pub counters: [f32; ACTIONSTATE_NUMBER_OF_CONCURRENT_COUNTERS],
|
||||||
pub conditions: [bool; ACTIONSTATE_NUMBER_OF_CONCURRENT_CONDITIONS],
|
pub conditions: [bool; ACTIONSTATE_NUMBER_OF_CONCURRENT_CONDITIONS],
|
||||||
pub int_counters: [u8; ACTIONSTATE_NUMBER_OF_CONCURRENT_INT_COUNTERS],
|
pub int_counters: [u8; ACTIONSTATE_NUMBER_OF_CONCURRENT_INT_COUNTERS],
|
||||||
|
pub positions: [Option<Vec3<f32>>; ACTIONSTATE_NUMBER_OF_CONCURRENT_POSITIONS],
|
||||||
pub initialized: bool,
|
pub initialized: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,12 +1,12 @@
|
|||||||
use crate::{consts::MAX_PATH_DIST, data::*, util::entities_have_line_of_sight};
|
use crate::{consts::MAX_PATH_DIST, data::*, util::entities_have_line_of_sight};
|
||||||
use common::{
|
use common::{
|
||||||
comp::{
|
comp::{
|
||||||
ability::{self, Ability, ActiveAbilities, AuxiliaryAbility, Capability},
|
ability::{ActiveAbilities, AuxiliaryAbility},
|
||||||
buff::BuffKind,
|
buff::BuffKind,
|
||||||
item::tool::AbilityContext,
|
item::tool::AbilityContext,
|
||||||
skills::{AxeSkill, BowSkill, HammerSkill, SceptreSkill, Skill, StaffSkill, SwordSkill},
|
skills::{AxeSkill, BowSkill, HammerSkill, SceptreSkill, Skill, StaffSkill, SwordSkill},
|
||||||
AbilityInput, Agent, CharacterAbility, CharacterState, ControlAction, ControlEvent,
|
AbilityInput, Agent, CharacterAbility, CharacterState, ControlAction, ControlEvent,
|
||||||
Controller, InputKind, Stance,
|
Controller, InputKind,
|
||||||
},
|
},
|
||||||
path::TraversalConfig,
|
path::TraversalConfig,
|
||||||
states::{self_buff, sprite_summon, utils::StageSection},
|
states::{self_buff, sprite_summon, utils::StageSection},
|
||||||
@ -15,7 +15,6 @@ use common::{
|
|||||||
vol::ReadVol,
|
vol::ReadVol,
|
||||||
};
|
};
|
||||||
use rand::{prelude::SliceRandom, Rng};
|
use rand::{prelude::SliceRandom, Rng};
|
||||||
use specs::saveload::MarkerAllocator;
|
|
||||||
use std::{f32::consts::PI, time::Duration};
|
use std::{f32::consts::PI, time::Duration};
|
||||||
use vek::*;
|
use vek::*;
|
||||||
|
|
||||||
@ -510,11 +509,6 @@ impl<'a> AgentData<'a> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
enum IntCounters {
|
|
||||||
Tactics = 0,
|
|
||||||
ActionMode = 1,
|
|
||||||
}
|
|
||||||
|
|
||||||
if !agent.action_state.initialized {
|
if !agent.action_state.initialized {
|
||||||
let available_tactics = {
|
let available_tactics = {
|
||||||
let mut tactics = Vec::new();
|
let mut tactics = Vec::new();
|
||||||
@ -737,7 +731,7 @@ impl<'a> AgentData<'a> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
agent.action_state.int_counters[IntCounters::ActionMode as usize] =
|
agent.action_state.int_counters[IntCounters::ActionMode as usize] =
|
||||||
ActionMode::Guarded as u8;
|
ActionMode::Reckless as u8;
|
||||||
}
|
}
|
||||||
|
|
||||||
enum ActionMode {
|
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(
|
let attempt_attack = match ActionMode::from_u8(
|
||||||
agent.action_state.int_counters[IntCounters::ActionMode as usize],
|
agent.action_state.int_counters[IntCounters::ActionMode as usize],
|
||||||
) {
|
) {
|
||||||
ActionMode::Reckless => true,
|
ActionMode::Reckless => true,
|
||||||
ActionMode::Guarded => true,
|
ActionMode::Guarded => {
|
||||||
ActionMode::Fleeing => false,
|
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 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(
|
match SwordTactics::from_u8(
|
||||||
agent.action_state.int_counters[IntCounters::Tactics as usize],
|
agent.action_state.int_counters[IntCounters::Tactics as usize],
|
||||||
) {
|
) {
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
use common::{
|
use common::{
|
||||||
comp::{
|
comp::{
|
||||||
|
ability::CharacterAbility,
|
||||||
buff::{BuffKind, Buffs},
|
buff::{BuffKind, Buffs},
|
||||||
group,
|
group,
|
||||||
item::MaterialStatManifest,
|
item::MaterialStatManifest,
|
||||||
@ -193,153 +194,191 @@ pub enum Path {
|
|||||||
Partial,
|
Partial,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct ComboMeleeData {
|
pub enum AbilityData {
|
||||||
pub range: f32,
|
ComboMelee {
|
||||||
pub angle: f32,
|
range: f32,
|
||||||
pub energy: 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 {
|
impl AbilityData {
|
||||||
pub fn could_use(&self, attack_data: &AttackData, agent_data: &AgentData) -> bool {
|
pub fn from_ability(ability: &CharacterAbility) -> Option<Self> {
|
||||||
attack_data.dist_sqrd
|
use CharacterAbility::*;
|
||||||
< (self.range + agent_data.body.map_or(0.0, |b| b.max_radius())).powi(2)
|
let inner = match ability {
|
||||||
&& attack_data.angle < self.angle
|
ComboMelee2 {
|
||||||
&& agent_data.energy.current() >= self.energy
|
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 {
|
pub fn could_use(&self, attack_data: &AttackData, agent_data: &AgentData) -> bool {
|
||||||
let charge_dur = self.charge_dur(agent_data);
|
let melee_check = |range: f32, angle| {
|
||||||
let charge_dist = charge_dur * self.speed * Self::BASE_SPEED;
|
attack_data.dist_sqrd
|
||||||
let attack_dist =
|
< (range + agent_data.body.map_or(0.0, |b| b.max_radius())).powi(2)
|
||||||
charge_dist + self.range + agent_data.body.map_or(0.0, |b| b.max_radius());
|
&& attack_data.angle < angle
|
||||||
let ori_gap = Self::ORI_RATE * charge_dur;
|
};
|
||||||
attack_data.dist_sqrd < attack_dist.powi(2)
|
let energy_check = |energy| agent_data.energy.current() >= energy;
|
||||||
&& attack_data.angle < self.angle + ori_gap
|
let combo_check = |combo| agent_data.combo.map_or(false, |c| c.counter() >= combo);
|
||||||
&& agent_data.energy.current() > self.initial_energy
|
use AbilityData::*;
|
||||||
}
|
match self {
|
||||||
|
ComboMelee {
|
||||||
pub fn use_desirable(&self, attack_data: &AttackData, agent_data: &AgentData) -> bool {
|
range,
|
||||||
let charge_dist = self.charge_dur(agent_data) * self.speed * Self::BASE_SPEED;
|
angle,
|
||||||
attack_data.dist_sqrd / charge_dist.powi(2) > 0.75_f32.powi(2)
|
energy_per_strike,
|
||||||
}
|
} => melee_check(*range, *angle) && energy_check(*energy_per_strike),
|
||||||
|
FinisherMelee {
|
||||||
fn charge_dur(&self, agent_data: &AgentData) -> f32 {
|
range,
|
||||||
((agent_data.energy.current() - self.initial_energy) / self.energy_drain)
|
angle,
|
||||||
.clamp(0.0, self.charge_dur)
|
energy,
|
||||||
}
|
combo,
|
||||||
}
|
} => melee_check(*range, *angle) && energy_check(*energy) && combo_check(*combo),
|
||||||
|
SelfBuff { buff, energy } => {
|
||||||
pub struct RapidMeleeData {
|
energy_check(*energy)
|
||||||
pub range: f32,
|
&& agent_data
|
||||||
pub angle: f32,
|
.buffs
|
||||||
pub energy: f32,
|
.map_or(false, |buffs| !buffs.contains(*buff))
|
||||||
pub strikes: u32,
|
},
|
||||||
}
|
DiveMelee {
|
||||||
|
range,
|
||||||
impl RapidMeleeData {
|
angle,
|
||||||
pub fn could_use(&self, attack_data: &AttackData, agent_data: &AgentData) -> bool {
|
energy,
|
||||||
attack_data.dist_sqrd
|
} => melee_check(*range, *angle) && energy_check(*energy),
|
||||||
< (self.range + agent_data.body.map_or(0.0, |b| b.max_radius())).powi(2)
|
DashMelee {
|
||||||
&& attack_data.angle < self.angle
|
range,
|
||||||
&& agent_data.energy.current() > self.energy * self.strikes as f32
|
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