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 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,
|
||||
|
@ -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,
|
||||
|
@ -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)
|
||||
},
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user