Sword AI for offensive abilities

This commit is contained in:
Sam 2022-10-05 21:25:24 -04:00
parent 4ab3abfc07
commit 7dcb3582e6
8 changed files with 163 additions and 53 deletions

View File

@ -9,7 +9,7 @@ ComboMelee2(
energy_regen: 10,
),
range: 6.0,
angle: 5.0,
angle: 30.0,
),
buildup_duration: 0.2,
swing_duration: 0.1,

View File

@ -235,7 +235,7 @@ pub enum AbilityInput {
Auxiliary(usize),
}
#[derive(Copy, Clone, Serialize, Deserialize, Debug, PartialEq)]
#[derive(Copy, Clone, Serialize, Deserialize, Debug, PartialEq, Eq)]
pub enum Ability {
ToolPrimary,
ToolSecondary,
@ -2581,7 +2581,7 @@ impl From<(&CharacterAbility, AbilityInfo, &JoinData<'_>)> for CharacterState {
}
}
#[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize, Default)]
#[derive(Copy, Clone, Debug, PartialEq, Eq, Serialize, Deserialize, Default)]
#[serde(deny_unknown_fields)]
pub struct AbilityMeta {
pub kind: Option<AbilityKind>,
@ -2591,12 +2591,12 @@ pub struct AbilityMeta {
// Only extend this if it is needed to control certain functionality of
// abilities
#[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)]
#[derive(Copy, Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub enum AbilityKind {
Sword(SwordStance),
}
#[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)]
#[derive(Copy, Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub enum SwordStance {
Balanced,
Offensive,

View File

@ -8,7 +8,7 @@ use crate::{
use serde::{Deserialize, Serialize};
use std::time::Duration;
#[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)]
#[derive(Copy, Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub struct ParryWindow {
pub buildup: bool,
pub recover: bool,

View File

@ -148,20 +148,20 @@ impl CharacterBehavior for Data {
}
}
#[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)]
#[derive(Copy, Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub enum ScalingTarget {
Attack,
Buff,
}
#[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)]
#[derive(Copy, Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub enum ScalingKind {
// Reaches a scaling of 1 when at minimum combo, and a scaling of 2 when at double minimum
// combo
Linear,
}
#[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)]
#[derive(Copy, Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub struct Scaling {
pub target: ScalingTarget,
pub kind: ScalingKind,

View File

@ -1351,12 +1351,7 @@ impl<'a> AgentData<'a> {
self.idle(agent, controller, read_data, rng);
} else {
let target_data = TargetData::new(
tgt_pos,
read_data.bodies.get(target),
read_data.scales.get(target),
read_data.char_states.get(target),
);
let target_data = TargetData::new(tgt_pos, target, read_data);
if let Some(tgt_name) =
read_data.stats.get(target).map(|stats| stats.name.clone())
{

View File

@ -5,7 +5,7 @@ use crate::{
};
use common::{
comp::{
ability::{self, ActiveAbilities, AuxiliaryAbility},
ability::{self, AbilityKind, ActiveAbilities, AuxiliaryAbility},
buff::BuffKind,
skills::{AxeSkill, BowSkill, HammerSkill, SceptreSkill, Skill, StaffSkill, SwordSkill},
AbilityInput, Agent, CharacterAbility, CharacterState, ControlAction, ControlEvent,
@ -420,6 +420,20 @@ impl<'a> AgentData<'a> {
read_data: &ReadData,
rng: &mut impl Rng,
) {
struct ComboMeleeData {
min_range: f32,
max_range: f32,
angle: f32,
energy: f32,
}
impl ComboMeleeData {
fn could_use(&self, attack_data: &AttackData, agent_data: &AgentData) -> bool {
attack_data.dist_sqrd < self.max_range.powi(2)
&& attack_data.dist_sqrd > self.min_range.powi(2)
&& attack_data.angle < self.angle
&& agent_data.energy.current() >= self.energy
}
}
struct FinisherMeleeData {
range: f32,
angle: f32,
@ -435,6 +449,15 @@ impl<'a> AgentData<'a> {
.combo
.map_or(false, |c| c.counter() >= self.combo)
}
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)
}
}
use ability::SwordStance;
let stance = |stance| match stance {
@ -482,10 +505,10 @@ impl<'a> AgentData<'a> {
SwordStance::Offensive => {
// Offensive combo
set_sword_ability(0, 1);
// Offensive finisher
set_sword_ability(1, 2);
// Offensive advance
set_sword_ability(2, 3);
set_sword_ability(1, 3);
// Offensive finisher
set_sword_ability(2, 2);
// Balanced finisher
set_sword_ability(3, 0);
},
@ -590,12 +613,20 @@ impl<'a> AgentData<'a> {
// ability. Only contains tactics for using M1 and M2
let fallback_rng_1 = rng.gen::<f32>() < 0.67;
let fallback_tactics = |agent: &mut Agent, controller: &mut Controller| {
const BALANCED_COMBO_RANGE: f32 = 2.5;
const BALANCED_COMBO_ANGLE: f32 = 40.0;
const BALANCED_THRUST_RANGE: f32 = 4.0;
const BALANCED_THRUST_ANGLE: f32 = 8.0;
let balanced_thrust_targetable = attack_data.dist_sqrd < BALANCED_THRUST_RANGE.powi(2)
&& attack_data.angle < BALANCED_THRUST_ANGLE;
const BALANCED_COMBO: ComboMeleeData = ComboMeleeData {
min_range: 0.0,
max_range: 2.5,
angle: 40.0,
energy: 0.0,
};
const BALANCED_THRUST: ComboMeleeData = ComboMeleeData {
min_range: 0.0,
max_range: 4.0,
angle: 8.0,
energy: 0.0,
};
let balanced_thrust_targetable = BALANCED_THRUST.could_use(attack_data, self);
if (matches!(self.char_state, CharacterState::ChargedMelee(c) if c.charge_amount < 0.95)
|| matches!(
@ -608,27 +639,37 @@ impl<'a> AgentData<'a> {
// that needs charging), thrust
// Or if already thrusting, keep thrussting
controller.push_basic_input(InputKind::Secondary);
} else if attack_data.dist_sqrd < BALANCED_COMBO_RANGE.powi(2)
&& attack_data.angle < BALANCED_COMBO_ANGLE
&& fallback_rng_1
{
} else if BALANCED_COMBO.could_use(attack_data, self) && fallback_rng_1 {
controller.push_basic_input(InputKind::Primary);
} else if balanced_thrust_targetable {
controller.push_basic_input(InputKind::Secondary);
}
advance(agent, controller, 1.5, BALANCED_COMBO_ANGLE);
advance(agent, controller, 1.5, BALANCED_COMBO.angle);
};
let in_stance = |stance| {
if let CharacterState::ComboMelee2(c) = self.char_state {
c.static_data.is_stance
&& c.static_data
.ability_info
.ability_meta
.and_then(|meta| meta.kind)
.map_or(false, |kind| AbilityKind::Sword(stance) == kind)
} else {
false
}
};
const BALANCED_FINISHER: FinisherMeleeData = FinisherMeleeData {
range: 2.5,
angle: 12.5,
energy: 30.0,
combo: 10,
};
match stance(agent.action_state.int_counter) {
SwordStance::Balanced => {
const BALANCED_FINISHER: FinisherMeleeData = FinisherMeleeData {
range: 2.5,
angle: 12.5,
energy: 30.0,
combo: 10,
};
if self
.skill_set
.has_skill(Skill::Sword(SwordSkill::BalancedFinisher))
@ -646,7 +687,89 @@ impl<'a> AgentData<'a> {
}
},
SwordStance::Offensive => {
fallback_tactics(agent, controller);
const OFFENSIVE_COMBO: ComboMeleeData = ComboMeleeData {
min_range: 0.0,
max_range: 2.0,
angle: 30.0,
energy: 5.0,
};
const OFFENSIVE_ADVANCE: ComboMeleeData = ComboMeleeData {
min_range: 6.0,
max_range: 10.0,
angle: 20.0,
energy: 10.0,
};
const OFFENSIVE_FINISHER: FinisherMeleeData = FinisherMeleeData {
range: 2.0,
angle: 10.0,
energy: 40.0,
combo: 10,
};
const DESIRED_ENERGY: f32 = 50.0;
if self.energy.current() < DESIRED_ENERGY {
fallback_tactics(agent, controller);
} else if !in_stance(SwordStance::Offensive) {
controller.push_basic_input(InputKind::Ability(0));
} else {
let used_finisher = if self
.skill_set
.has_skill(Skill::Sword(SwordSkill::OffensiveFinisher))
{
if OFFENSIVE_FINISHER.could_use(attack_data, self)
&& OFFENSIVE_FINISHER.use_desirable(tgt_data, self)
{
controller.push_basic_input(InputKind::Ability(2));
advance(
agent,
controller,
OFFENSIVE_FINISHER.range,
OFFENSIVE_FINISHER.angle,
);
true
} else {
false
}
} else if BALANCED_FINISHER.could_use(attack_data, self) {
controller.push_basic_input(InputKind::Ability(3));
advance(
agent,
controller,
BALANCED_FINISHER.range,
BALANCED_FINISHER.angle,
);
true
} else {
false
};
if !used_finisher {
if OFFENSIVE_COMBO.could_use(attack_data, self) {
controller.push_basic_input(InputKind::Primary);
advance(
agent,
controller,
OFFENSIVE_COMBO.max_range,
OFFENSIVE_COMBO.angle,
);
} else if OFFENSIVE_ADVANCE.could_use(attack_data, self) {
controller.push_basic_input(InputKind::Ability(1));
advance(
agent,
controller,
OFFENSIVE_ADVANCE.max_range,
OFFENSIVE_ADVANCE.angle,
);
} else {
advance(
agent,
controller,
OFFENSIVE_COMBO.max_range,
OFFENSIVE_COMBO.angle,
);
}
}
}
},
SwordStance::Defensive => {
fallback_tactics(agent, controller);

View File

@ -51,20 +51,17 @@ pub struct TargetData<'a> {
pub body: Option<&'a Body>,
pub scale: Option<&'a Scale>,
pub char_state: Option<&'a CharacterState>,
pub health: Option<&'a Health>,
}
impl<'a> TargetData<'a> {
pub fn new(
pos: &'a Pos,
body: Option<&'a Body>,
scale: Option<&'a Scale>,
char_state: Option<&'a CharacterState>,
) -> Self {
pub fn new(pos: &'a Pos, target: EcsEntity, read_data: &'a ReadData) -> Self {
Self {
pos,
body,
scale,
char_state,
body: read_data.bodies.get(target),
scale: read_data.scales.get(target),
char_state: read_data.char_states.get(target),
health: read_data.healths.get(target),
}
}
}

View File

@ -615,12 +615,7 @@ fn do_combat(bdata: &mut BehaviorData) -> bool {
}
if aggro_on {
let target_data = TargetData::new(
tgt_pos,
read_data.bodies.get(target),
read_data.scales.get(target),
read_data.char_states.get(target),
);
let target_data = TargetData::new(tgt_pos, target, read_data);
let tgt_name = read_data.stats.get(target).map(|stats| stats.name.clone());
tgt_name.map(|tgt_name| agent.add_fight_to_memory(&tgt_name, read_data.time.0));