Basic sword AI

This commit is contained in:
Sam 2023-01-22 13:50:06 -05:00
parent c4154b0160
commit 43d7e9357a
3 changed files with 132 additions and 31 deletions

View File

@ -26,7 +26,7 @@ use fxhash::FxHasher64;
use serde::{Deserialize, Serialize};
use std::{
f32::consts::PI,
ops::{Add, Div},
ops::{Add, Div, Mul},
time::Duration,
};
use strum::Display;
@ -1350,6 +1350,23 @@ pub enum ForcedMovement {
},
}
impl Mul<f32> for ForcedMovement {
type Output = Self;
fn mul(self, scalar: f32) -> Self {
use ForcedMovement::*;
match self {
Forward(x) => Forward(x * scalar),
Reverse(x) => Reverse(x * scalar),
Sideways(x) => Sideways(x * scalar),
DirectedReverse(x) => DirectedReverse(x * scalar),
AntiDirectedForward(x) => AntiDirectedForward(x * scalar),
Leap { vertical, forward, progress, direction } => Leap { vertical: vertical * scalar, forward: forward * scalar, progress, direction },
Hover { move_input } => Hover { move_input },
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub enum MovementDirection {
Look,

View File

@ -569,9 +569,9 @@ impl<'a> AgentData<'a> {
// &mut tactics,
// );
// }
// if tactics.is_empty() {
// try_tactic(SwordSkill::CrescentSlash, SwordTactics::Basic, &mut tactics);
// }
if tactics.is_empty() {
try_tactic(SwordSkill::CrescentSlash, SwordTactics::Basic, &mut tactics);
}
if tactics.is_empty() {
tactics.push(SwordTactics::Unskilled);
}
@ -753,7 +753,7 @@ impl<'a> AgentData<'a> {
if let Some(health) = self.health {
agent.action_state.int_counters[IntCounters::ActionMode as usize] =
if health.fraction() < 0.2 {
if health.fraction() < 0.1 {
agent.action_state.positions[Positions::GuardedCover as usize] = None;
ActionMode::Fleeing as u8
} else if health.fraction() < 0.9 {
@ -778,7 +778,7 @@ impl<'a> AgentData<'a> {
}
enum Conditions {
GuardedAttack = 0,
GuardedDefend = 0,
RollingBreakThrough = 1,
TacticMisc = 2,
}
@ -816,15 +816,15 @@ impl<'a> AgentData<'a> {
{
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;
rng.gen_range(3.0..6.0);
agent.action_state.conditions[Conditions::GuardedDefend 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] {
if !agent.action_state.conditions[Conditions::GuardedDefend as usize] {
agent.action_state.positions[Positions::GuardedCover as usize] = None;
true
} else {
@ -1046,18 +1046,25 @@ impl<'a> AgentData<'a> {
_ => 255,
}
}
let could_use_input = |input| match input {
InputKind::Primary => primary.map_or(false, |p| p.could_use(attack_data, self)),
InputKind::Secondary => secondary.map_or(false, |s| s.could_use(attack_data, self)),
let could_use_input = |input, misc_data| match input {
InputKind::Primary => primary
.as_ref()
.map_or(false, |p| p.could_use(attack_data, self, misc_data)),
InputKind::Secondary => secondary
.as_ref()
.map_or(false, |s| s.could_use(attack_data, self, misc_data)),
InputKind::Ability(x) => abilities[x]
.as_ref()
.map_or(false, |a| a.could_use(attack_data, self)),
.map_or(false, |a| a.could_use(attack_data, self, misc_data)),
_ => false,
};
match SwordTactics::from_u8(
agent.action_state.int_counters[IntCounters::Tactics as usize],
) {
SwordTactics::Unskilled => {
let misc_data = MiscData {
desired_energy: 15.0,
};
let current_input = self.char_state.ability_info().map(|ai| ai.input);
let mut next_input = None;
if let Some(input) = current_input {
@ -1086,7 +1093,52 @@ impl<'a> AgentData<'a> {
}
};
if let Some(input) = next_input {
if could_use_input(input) {
if could_use_input(input, &misc_data) {
controller.push_basic_input(input);
false
} else {
true
}
} else {
true
}
},
SwordTactics::Basic => {
let misc_data = MiscData {
desired_energy: 25.0,
};
let current_input = self.char_state.ability_info().map(|ai| ai.input);
let mut next_input = None;
if let Some(input) = current_input {
if let input = InputKind::Secondary {
let charging = matches!(
self.char_state.stage_section(),
Some(StageSection::Charge)
);
let charged = self
.char_state
.durations()
.and_then(|durs| durs.charge)
.zip(self.char_state.timer())
.map_or(false, |(dur, timer)| timer > dur);
if !(charging && charged) {
next_input = Some(InputKind::Secondary);
}
} else {
next_input = Some(input);
}
} else {
let attempt_ability = InputKind::Ability(rng.gen_range(0..5));
if could_use_input(attempt_ability, &misc_data) {
next_input = Some(attempt_ability);
} else if rng.gen_bool(0.5) {
next_input = Some(InputKind::Primary);
} else {
next_input = Some(InputKind::Secondary);
}
};
if let Some(input) = next_input {
if could_use_input(input, &misc_data) {
controller.push_basic_input(input);
false
} else {
@ -1096,7 +1148,6 @@ impl<'a> AgentData<'a> {
true
}
},
SwordTactics::Basic => true,
SwordTactics::HeavySimple => true,
SwordTactics::AgileSimple => true,
SwordTactics::DefensiveSimple => true,
@ -1112,7 +1163,7 @@ impl<'a> AgentData<'a> {
false
};
if attack_failed {
if attack_failed && attack_data.dist_sqrd > 1.5_f32.powi(2) {
self.path_toward_target(
agent,
controller,

View File

@ -13,6 +13,7 @@ use common::{
path::TraversalConfig,
resources::{DeltaTime, Time, TimeOfDay},
rtsim::RtSimEntity,
states::utils::ForcedMovement,
terrain::TerrainGrid,
uid::{Uid, UidAllocator},
};
@ -199,6 +200,7 @@ pub enum AbilityData {
range: f32,
angle: f32,
energy_per_strike: f32,
forced_movement: Option<ForcedMovement>,
},
FinisherMelee {
range: f32,
@ -239,6 +241,10 @@ pub enum AbilityData {
},
}
pub struct MiscData {
pub desired_energy: f32,
}
impl AbilityData {
pub fn from_ability(ability: &CharacterAbility) -> Option<Self> {
use CharacterAbility::*;
@ -248,17 +254,27 @@ impl AbilityData {
energy_cost_per_strike,
..
} => {
let (range, angle) = strikes
let (range, angle, forced_movement) = strikes
.iter()
.map(|s| (s.melee_constructor.range, s.melee_constructor.angle))
.map(|s| {
(
s.melee_constructor.range,
s.melee_constructor.angle,
s.movement.buildup.map(|m| m * s.buildup_duration),
)
})
.fold(
(100.0, 360.0),
|(r1, a1): (f32, f32), (r2, a2): (f32, f32)| (r1.min(r2), a1.min(a2)),
(100.0, 360.0, None),
|(r1, a1, m1): (f32, f32, Option<ForcedMovement>),
(r2, a2, m2): (f32, f32, Option<ForcedMovement>)| {
(r1.min(r2), a1.min(a2), m1.or(m2))
},
);
Self::ComboMelee {
range,
angle,
energy_per_strike: *energy_cost_per_strike,
forced_movement,
}
},
FinisherMelee {
@ -335,13 +351,28 @@ impl AbilityData {
Some(inner)
}
pub fn could_use(&self, attack_data: &AttackData, agent_data: &AgentData) -> bool {
let melee_check = |range: f32, angle| {
attack_data.dist_sqrd
< (range + agent_data.body.map_or(0.0, |b| b.max_radius())).powi(2)
pub fn could_use(
&self,
attack_data: &AttackData,
agent_data: &AgentData,
misc_data: &MiscData,
) -> bool {
let melee_check = |range: f32, angle, forced_movement: Option<ForcedMovement>| {
let range_inc = forced_movement.map_or(0.0, |fm| match fm {
ForcedMovement::Forward(speed) => speed * 15.0,
ForcedMovement::Reverse(speed) => -speed,
_ => 0.0,
});
let body_rad = agent_data.body.map_or(0.0, |b| b.max_radius());
attack_data.dist_sqrd < (range + range_inc + body_rad).powi(2)
&& attack_data.angle < angle
&& attack_data.dist_sqrd > range_inc.powi(2)
};
let energy_check = |energy: f32| {
agent_data.energy.current() >= energy
&& (energy < f32::EPSILON
|| agent_data.energy.current() >= misc_data.desired_energy)
};
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 {
@ -349,13 +380,14 @@ impl AbilityData {
range,
angle,
energy_per_strike,
} => melee_check(*range, *angle) && energy_check(*energy_per_strike),
forced_movement,
} => melee_check(*range, *angle, *forced_movement) && energy_check(*energy_per_strike),
FinisherMelee {
range,
angle,
energy,
combo,
} => melee_check(*range, *angle) && energy_check(*energy) && combo_check(*combo),
} => melee_check(*range, *angle, None) && energy_check(*energy) && combo_check(*combo),
SelfBuff { buff, energy } => {
energy_check(*energy)
&& agent_data
@ -366,7 +398,7 @@ impl AbilityData {
range,
angle,
energy,
} => melee_check(*range, *angle) && energy_check(*energy),
} => melee_check(*range, *angle, None) && energy_check(*energy),
DashMelee {
range,
angle,
@ -384,7 +416,8 @@ impl AbilityData {
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)
// TODO: Replace None with actual forced movement later
melee_check(attack_dist, angle + ori_gap, None)
&& energy_check(*initial_energy)
&& attack_data.dist_sqrd / charge_dist.powi(2) > 0.75_f32.powi(2)
},
@ -395,7 +428,7 @@ impl AbilityData {
strikes,
combo,
} => {
melee_check(*range, *angle)
melee_check(*range, *angle, None)
&& energy_check(*energy_per_strike * *strikes as f32)
&& combo_check(*combo)
},
@ -406,7 +439,7 @@ impl AbilityData {
energy_drain,
charge_dur,
} => {
melee_check(*range, *angle)
melee_check(*range, *angle, None)
&& energy_check(*initial_energy + *energy_drain * *charge_dur)
},
}