mirror of
https://gitlab.com/veloren/veloren.git
synced 2024-08-30 18:12:32 +00:00
Basic sword AI
This commit is contained in:
parent
c4154b0160
commit
43d7e9357a
@ -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,
|
||||||
|
@ -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,
|
||||||
|
@ -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)
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user