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 serde::{Deserialize, Serialize};
use std::{ use std::{
f32::consts::PI, f32::consts::PI,
ops::{Add, Div}, ops::{Add, Div, Mul},
time::Duration, time::Duration,
}; };
use strum::Display; 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)] #[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub enum MovementDirection { pub enum MovementDirection {
Look, Look,

View File

@ -569,9 +569,9 @@ impl<'a> AgentData<'a> {
// &mut tactics, // &mut tactics,
// ); // );
// } // }
// if tactics.is_empty() { if tactics.is_empty() {
// try_tactic(SwordSkill::CrescentSlash, SwordTactics::Basic, &mut tactics); try_tactic(SwordSkill::CrescentSlash, SwordTactics::Basic, &mut tactics);
// } }
if tactics.is_empty() { if tactics.is_empty() {
tactics.push(SwordTactics::Unskilled); tactics.push(SwordTactics::Unskilled);
} }
@ -753,7 +753,7 @@ impl<'a> AgentData<'a> {
if let Some(health) = self.health { if let Some(health) = self.health {
agent.action_state.int_counters[IntCounters::ActionMode as usize] = 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; agent.action_state.positions[Positions::GuardedCover as usize] = None;
ActionMode::Fleeing as u8 ActionMode::Fleeing as u8
} else if health.fraction() < 0.9 { } else if health.fraction() < 0.9 {
@ -778,7 +778,7 @@ impl<'a> AgentData<'a> {
} }
enum Conditions { enum Conditions {
GuardedAttack = 0, GuardedDefend = 0,
RollingBreakThrough = 1, RollingBreakThrough = 1,
TacticMisc = 2, TacticMisc = 2,
} }
@ -816,15 +816,15 @@ impl<'a> AgentData<'a> {
{ {
agent.action_state.timers[Timers::GuardedCycle as usize] = 0.0; agent.action_state.timers[Timers::GuardedCycle as usize] = 0.0;
agent.action_state.counters[FloatCounters::GuardedTimer as usize] = agent.action_state.counters[FloatCounters::GuardedTimer as usize] =
rng.gen_range(3.0..8.0); rng.gen_range(3.0..6.0);
agent.action_state.conditions[Conditions::GuardedAttack as usize] ^= true; agent.action_state.conditions[Conditions::GuardedDefend as usize] ^= true;
} }
if let Some(pos) = agent.action_state.positions[Positions::GuardedCover as usize] { if let Some(pos) = agent.action_state.positions[Positions::GuardedCover as usize] {
if pos.distance_squared(self.pos.0) < 3_f32.powi(2) { if pos.distance_squared(self.pos.0) < 3_f32.powi(2) {
agent.action_state.positions[Positions::GuardedCover as usize] = None; 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; agent.action_state.positions[Positions::GuardedCover as usize] = None;
true true
} else { } else {
@ -1046,18 +1046,25 @@ impl<'a> AgentData<'a> {
_ => 255, _ => 255,
} }
} }
let could_use_input = |input| match input { let could_use_input = |input, misc_data| match input {
InputKind::Primary => primary.map_or(false, |p| p.could_use(attack_data, self)), InputKind::Primary => primary
InputKind::Secondary => secondary.map_or(false, |s| s.could_use(attack_data, self)), .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] InputKind::Ability(x) => abilities[x]
.as_ref() .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, _ => false,
}; };
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],
) { ) {
SwordTactics::Unskilled => { SwordTactics::Unskilled => {
let misc_data = MiscData {
desired_energy: 15.0,
};
let current_input = self.char_state.ability_info().map(|ai| ai.input); let current_input = self.char_state.ability_info().map(|ai| ai.input);
let mut next_input = None; let mut next_input = None;
if let Some(input) = current_input { if let Some(input) = current_input {
@ -1086,7 +1093,52 @@ impl<'a> AgentData<'a> {
} }
}; };
if let Some(input) = next_input { 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); controller.push_basic_input(input);
false false
} else { } else {
@ -1096,7 +1148,6 @@ impl<'a> AgentData<'a> {
true true
} }
}, },
SwordTactics::Basic => true,
SwordTactics::HeavySimple => true, SwordTactics::HeavySimple => true,
SwordTactics::AgileSimple => true, SwordTactics::AgileSimple => true,
SwordTactics::DefensiveSimple => true, SwordTactics::DefensiveSimple => true,
@ -1112,7 +1163,7 @@ impl<'a> AgentData<'a> {
false false
}; };
if attack_failed { if attack_failed && attack_data.dist_sqrd > 1.5_f32.powi(2) {
self.path_toward_target( self.path_toward_target(
agent, agent,
controller, controller,

View File

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