mirror of
https://gitlab.com/veloren/veloren.git
synced 2024-08-30 18:12:32 +00:00
Sword AI for offensive abilities
This commit is contained in:
parent
4ab3abfc07
commit
7dcb3582e6
@ -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,
|
||||
|
@ -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,
|
||||
|
@ -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,
|
||||
|
@ -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,
|
||||
|
@ -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())
|
||||
{
|
||||
|
@ -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);
|
||||
|
@ -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),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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));
|
||||
|
Loading…
Reference in New Issue
Block a user