mirror of
https://gitlab.com/veloren/veloren.git
synced 2024-08-30 18:12:32 +00:00
Hammer AI
This commit is contained in:
parent
0dc261c70a
commit
aeb887963e
2
Cargo.lock
generated
2
Cargo.lock
generated
@ -7151,8 +7151,10 @@ dependencies = [
|
||||
"lazy_static",
|
||||
"rand 0.8.5",
|
||||
"specs",
|
||||
"tracing",
|
||||
"vek 0.16.1",
|
||||
"veloren-common",
|
||||
"veloren-common-base",
|
||||
"veloren-common-dynlib",
|
||||
"veloren-rtsim",
|
||||
]
|
||||
|
@ -8,13 +8,13 @@
|
||||
loadout: Inline((
|
||||
inherit: Asset("common.loadout.dungeon.cultist.cultist"),
|
||||
active_hands: InHands((Choice([
|
||||
(2, ModularWeapon(tool: Axe, material: Orichalcum, hands: One)),
|
||||
(4, Item("common.items.weapons.sword.cultist")),
|
||||
(2, Item("common.items.weapons.staff.cultist_staff")),
|
||||
// (2, ModularWeapon(tool: Axe, material: Orichalcum, hands: One)),
|
||||
// (4, Item("common.items.weapons.sword.cultist")),
|
||||
// (2, Item("common.items.weapons.staff.cultist_staff")),
|
||||
(2, Item("common.items.weapons.hammer.cultist_purp_2h-0")),
|
||||
(2, ModularWeapon(tool: Hammer, material: Orichalcum, hands: One)),
|
||||
(2, Item("common.items.weapons.bow.velorite")),
|
||||
(1, Item("common.items.weapons.sceptre.sceptre_velorite_0")),
|
||||
// (2, ModularWeapon(tool: Hammer, material: Orichalcum, hands: One)),
|
||||
// (2, Item("common.items.weapons.bow.velorite")),
|
||||
// (1, Item("common.items.weapons.sceptre.sceptre_velorite_0")),
|
||||
]), None)),
|
||||
)),
|
||||
items: [
|
||||
|
@ -62,6 +62,7 @@ pub const MAX_MELEE_POISE_PRECISION: f32 = 0.5;
|
||||
pub const MAX_BLOCK_POISE_COST: f32 = 25.0;
|
||||
pub const PARRY_BONUS_MULTIPLIER: f32 = 2.0;
|
||||
pub const FALLBACK_BLOCK_STRENGTH: f32 = 5.0;
|
||||
pub const BEHIND_TARGET_ANGLE: f32 = 45.0;
|
||||
|
||||
#[derive(Copy, Clone)]
|
||||
pub struct AttackerInfo<'a> {
|
||||
@ -658,9 +659,8 @@ impl Attack {
|
||||
target.char_state.map_or(false, |cs| cs.is_stunned())
|
||||
},
|
||||
CombatRequirement::BehindTarget => {
|
||||
const REQUIRED_ANGLE: f32 = 45.0;
|
||||
if let Some(ori) = target.ori {
|
||||
ori.look_vec().angle_between(dir.with_z(0.0)) < REQUIRED_ANGLE
|
||||
ori.look_vec().angle_between(dir.with_z(0.0)) < BEHIND_TARGET_ANGLE
|
||||
} else {
|
||||
false
|
||||
}
|
||||
|
@ -10,6 +10,7 @@ be-dyn-lib = []
|
||||
|
||||
[dependencies]
|
||||
common = { package = "veloren-common", path = "../../common"}
|
||||
common-base = { package = "veloren-common-base", path = "../../common/base" }
|
||||
common-dynlib = { package = "veloren-common-dynlib", path = "../../common/dynlib", optional = true}
|
||||
rtsim = { package = "veloren-rtsim", path = "../../rtsim" }
|
||||
|
||||
@ -18,3 +19,4 @@ vek = { workspace = true }
|
||||
rand = { workspace = true, features = ["small_rng"] }
|
||||
itertools = { workspace = true }
|
||||
lazy_static = { workspace = true }
|
||||
tracing = { workspace = true }
|
||||
|
@ -4,11 +4,12 @@ use crate::{
|
||||
util::{entities_have_line_of_sight, handle_attack_aggression},
|
||||
};
|
||||
use common::{
|
||||
combat::{self, AttackSource},
|
||||
comp::{
|
||||
ability::{ActiveAbilities, AuxiliaryAbility, Stance, SwordStance, BASE_ABILITY_LIMIT},
|
||||
buff::BuffKind,
|
||||
item::tool::AbilityContext,
|
||||
skills::{AxeSkill, BowSkill, SceptreSkill, Skill, StaffSkill, SwordSkill},
|
||||
skills::{AxeSkill, BowSkill, HammerSkill, SceptreSkill, Skill, StaffSkill, SwordSkill},
|
||||
Ability, AbilityInput, Agent, CharacterAbility, CharacterState, ControlAction,
|
||||
ControlEvent, Controller, Fluid, InputKind,
|
||||
},
|
||||
@ -271,14 +272,391 @@ impl<'a> AgentData<'a> {
|
||||
|
||||
pub fn handle_hammer_attack(
|
||||
&self,
|
||||
_agent: &mut Agent,
|
||||
_controller: &mut Controller,
|
||||
_attack_data: &AttackData,
|
||||
_tgt_data: &TargetData,
|
||||
_read_data: &ReadData,
|
||||
_rng: &mut impl Rng,
|
||||
agent: &mut Agent,
|
||||
controller: &mut Controller,
|
||||
attack_data: &AttackData,
|
||||
tgt_data: &TargetData,
|
||||
read_data: &ReadData,
|
||||
rng: &mut impl Rng,
|
||||
) {
|
||||
// TODO
|
||||
if !agent.combat_state.initialized {
|
||||
agent.combat_state.initialized = true;
|
||||
let available_tactics = {
|
||||
let mut tactics = Vec::new();
|
||||
let try_tactic = |skill, tactic, tactics: &mut Vec<HammerTactics>| {
|
||||
if self.skill_set.has_skill(Skill::Hammer(skill)) {
|
||||
tactics.push(tactic);
|
||||
}
|
||||
};
|
||||
try_tactic(
|
||||
HammerSkill::Thunderclap,
|
||||
HammerTactics::AttackExpert,
|
||||
&mut tactics,
|
||||
);
|
||||
try_tactic(
|
||||
HammerSkill::Judgement,
|
||||
HammerTactics::SupportExpert,
|
||||
&mut tactics,
|
||||
);
|
||||
if tactics.is_empty() {
|
||||
try_tactic(
|
||||
HammerSkill::IronTempest,
|
||||
HammerTactics::AttackAdvanced,
|
||||
&mut tactics,
|
||||
);
|
||||
try_tactic(
|
||||
HammerSkill::Rampart,
|
||||
HammerTactics::SupportAdvanced,
|
||||
&mut tactics,
|
||||
);
|
||||
}
|
||||
if tactics.is_empty() {
|
||||
try_tactic(
|
||||
HammerSkill::Retaliate,
|
||||
HammerTactics::AttackIntermediate,
|
||||
&mut tactics,
|
||||
);
|
||||
try_tactic(
|
||||
HammerSkill::PileDriver,
|
||||
HammerTactics::SupportIntermediate,
|
||||
&mut tactics,
|
||||
);
|
||||
}
|
||||
if tactics.is_empty() {
|
||||
try_tactic(
|
||||
HammerSkill::Tremor,
|
||||
HammerTactics::AttackSimple,
|
||||
&mut tactics,
|
||||
);
|
||||
try_tactic(
|
||||
HammerSkill::HeavyWhorl,
|
||||
HammerTactics::SupportSimple,
|
||||
&mut tactics,
|
||||
);
|
||||
}
|
||||
if tactics.is_empty() {
|
||||
try_tactic(
|
||||
HammerSkill::ScornfulSwipe,
|
||||
HammerTactics::Simple,
|
||||
&mut tactics,
|
||||
);
|
||||
}
|
||||
if tactics.is_empty() {
|
||||
tactics.push(HammerTactics::Unskilled);
|
||||
}
|
||||
tactics
|
||||
};
|
||||
|
||||
let tactic = available_tactics
|
||||
.choose(rng)
|
||||
.copied()
|
||||
.unwrap_or(HammerTactics::Unskilled);
|
||||
|
||||
agent.combat_state.int_counters[IntCounters::Tactic as usize] = tactic as u8;
|
||||
|
||||
let auxiliary_key = ActiveAbilities::active_auxiliary_key(Some(self.inventory));
|
||||
let set_ability = |controller: &mut Controller, slot, skill| {
|
||||
controller.push_event(ControlEvent::ChangeAbility {
|
||||
slot,
|
||||
auxiliary_key,
|
||||
new_ability: AuxiliaryAbility::MainWeapon(skill),
|
||||
});
|
||||
};
|
||||
let mut set_random = |controller: &mut Controller, slot, options: &mut Vec<usize>| {
|
||||
if options.is_empty() {
|
||||
return;
|
||||
}
|
||||
let i = rng.gen_range(0..options.len());
|
||||
set_ability(controller, slot, options.swap_remove(i));
|
||||
};
|
||||
|
||||
match tactic {
|
||||
HammerTactics::Unskilled => {},
|
||||
HammerTactics::Simple => {
|
||||
// Scornful swipe
|
||||
set_ability(controller, 0, 0);
|
||||
},
|
||||
HammerTactics::AttackSimple => {
|
||||
// Scornful swipe
|
||||
set_ability(controller, 0, 0);
|
||||
// Tremor or vigorous bash
|
||||
set_ability(controller, 1, rng.gen_range(1..3));
|
||||
},
|
||||
HammerTactics::AttackIntermediate => {
|
||||
// Scornful swipe
|
||||
set_ability(controller, 0, 0);
|
||||
// Tremor or vigorous bash
|
||||
set_ability(controller, 1, rng.gen_range(1..3));
|
||||
// Retaliate, spine cracker, or breach
|
||||
set_ability(controller, 2, rng.gen_range(3..6));
|
||||
},
|
||||
HammerTactics::AttackAdvanced => {
|
||||
// Scornful swipe, tremor, vigorous bash, retaliate, spine cracker, or breach
|
||||
let mut options = vec![0, 1, 2, 3, 4, 5];
|
||||
set_random(controller, 0, &mut options);
|
||||
set_random(controller, 1, &mut options);
|
||||
set_random(controller, 2, &mut options);
|
||||
set_ability(controller, 3, rng.gen_range(6..8));
|
||||
},
|
||||
HammerTactics::AttackExpert => {
|
||||
// Scornful swipe, tremor, vigorous bash, retaliate, spine cracker, breach, iron
|
||||
// tempest, or upheaval
|
||||
let mut options = vec![0, 1, 2, 3, 4, 5, 6, 7];
|
||||
set_random(controller, 0, &mut options);
|
||||
set_random(controller, 1, &mut options);
|
||||
set_random(controller, 2, &mut options);
|
||||
set_random(controller, 3, &mut options);
|
||||
set_ability(controller, 4, rng.gen_range(8..10));
|
||||
},
|
||||
HammerTactics::SupportSimple => {
|
||||
// Scornful swipe
|
||||
set_ability(controller, 0, 0);
|
||||
// Heavy whorl or intercept
|
||||
set_ability(controller, 1, rng.gen_range(10..12));
|
||||
},
|
||||
HammerTactics::SupportIntermediate => {
|
||||
// Scornful swipe
|
||||
set_ability(controller, 0, 0);
|
||||
// Heavy whorl or intercept
|
||||
set_ability(controller, 1, rng.gen_range(10..12));
|
||||
// Retaliate, spine cracker, or breach
|
||||
set_ability(controller, 2, rng.gen_range(12..15));
|
||||
},
|
||||
HammerTactics::SupportAdvanced => {
|
||||
// Scornful swipe, heavy whorl, intercept, pile driver, lung pummel, or helm
|
||||
// crusher
|
||||
let mut options = vec![0, 10, 11, 12, 13, 14];
|
||||
set_random(controller, 0, &mut options);
|
||||
set_random(controller, 1, &mut options);
|
||||
set_random(controller, 2, &mut options);
|
||||
set_ability(controller, 3, rng.gen_range(15..17));
|
||||
},
|
||||
HammerTactics::SupportExpert => {
|
||||
// Scornful swipe, heavy whorl, intercept, pile driver, lung pummel, helm
|
||||
// crusher, rampart, or tenacity
|
||||
let mut options = vec![0, 10, 11, 12, 13, 14, 15, 16];
|
||||
set_random(controller, 0, &mut options);
|
||||
set_random(controller, 1, &mut options);
|
||||
set_random(controller, 2, &mut options);
|
||||
set_random(controller, 3, &mut options);
|
||||
set_ability(controller, 4, rng.gen_range(17..19));
|
||||
},
|
||||
}
|
||||
|
||||
agent.combat_state.int_counters[IntCounters::ActionMode as usize] =
|
||||
ActionMode::Reckless as u8;
|
||||
}
|
||||
|
||||
enum IntCounters {
|
||||
Tactic = 0,
|
||||
ActionMode = 1,
|
||||
}
|
||||
|
||||
enum Timers {
|
||||
GuardedCycle = 0,
|
||||
PosTimeOut = 1,
|
||||
}
|
||||
|
||||
enum Conditions {
|
||||
GuardedDefend = 0,
|
||||
RollingBreakThrough = 1,
|
||||
}
|
||||
|
||||
enum FloatCounters {
|
||||
GuardedTimer = 0,
|
||||
}
|
||||
|
||||
enum Positions {
|
||||
GuardedCover = 0,
|
||||
Flee = 1,
|
||||
}
|
||||
|
||||
let attempt_attack = handle_attack_aggression(
|
||||
self,
|
||||
agent,
|
||||
controller,
|
||||
attack_data,
|
||||
tgt_data,
|
||||
read_data,
|
||||
rng,
|
||||
Timers::PosTimeOut as usize,
|
||||
Timers::GuardedCycle as usize,
|
||||
FloatCounters::GuardedTimer as usize,
|
||||
IntCounters::ActionMode as usize,
|
||||
Conditions::GuardedDefend as usize,
|
||||
Conditions::RollingBreakThrough as usize,
|
||||
Positions::GuardedCover as usize,
|
||||
Positions::Flee as usize,
|
||||
);
|
||||
|
||||
let attack_failed = if attempt_attack {
|
||||
let primary = self.extract_ability(AbilityInput::Primary);
|
||||
let secondary = self.extract_ability(AbilityInput::Secondary);
|
||||
let abilities = [
|
||||
self.extract_ability(AbilityInput::Auxiliary(0)),
|
||||
self.extract_ability(AbilityInput::Auxiliary(1)),
|
||||
self.extract_ability(AbilityInput::Auxiliary(2)),
|
||||
self.extract_ability(AbilityInput::Auxiliary(3)),
|
||||
self.extract_ability(AbilityInput::Auxiliary(4)),
|
||||
];
|
||||
let could_use_input = |input, desired_energy| match input {
|
||||
InputKind::Primary => primary.as_ref().map_or(false, |p| {
|
||||
p.could_use(attack_data, self, tgt_data, read_data, desired_energy)
|
||||
}),
|
||||
InputKind::Secondary => secondary.as_ref().map_or(false, |s| {
|
||||
s.could_use(attack_data, self, tgt_data, read_data, desired_energy)
|
||||
}),
|
||||
InputKind::Ability(x) => abilities[x].as_ref().map_or(false, |a| {
|
||||
let ability = self.active_abilities.get_ability(
|
||||
AbilityInput::Auxiliary(x),
|
||||
Some(self.inventory),
|
||||
Some(self.skill_set),
|
||||
self.stats,
|
||||
);
|
||||
let additional_conditions = match ability {
|
||||
Ability::MainWeaponAux(0) => self
|
||||
.buffs
|
||||
.map_or(false, |buffs| !buffs.contains(BuffKind::ScornfulTaunt)),
|
||||
Ability::MainWeaponAux(2) => {
|
||||
tgt_data.char_state.map_or(false, |cs| cs.is_stunned())
|
||||
},
|
||||
Ability::MainWeaponAux(4) => tgt_data.ori.map_or(false, |ori| {
|
||||
ori.look_vec().angle_between(tgt_data.pos.0 - self.pos.0)
|
||||
< combat::BEHIND_TARGET_ANGLE
|
||||
}),
|
||||
Ability::MainWeaponAux(5) => tgt_data.char_state.map_or(false, |cs| {
|
||||
cs.is_block(AttackSource::Melee) || cs.is_parry(AttackSource::Melee)
|
||||
}),
|
||||
Ability::MainWeaponAux(7) => tgt_data
|
||||
.buffs
|
||||
.map_or(false, |buffs| !buffs.contains(BuffKind::Staggered)),
|
||||
Ability::MainWeaponAux(12) => tgt_data
|
||||
.buffs
|
||||
.map_or(false, |buffs| !buffs.contains(BuffKind::Rooted)),
|
||||
Ability::MainWeaponAux(13) => tgt_data
|
||||
.buffs
|
||||
.map_or(false, |buffs| !buffs.contains(BuffKind::Winded)),
|
||||
Ability::MainWeaponAux(14) => tgt_data
|
||||
.buffs
|
||||
.map_or(false, |buffs| !buffs.contains(BuffKind::Concussion)),
|
||||
Ability::MainWeaponAux(15) => self
|
||||
.buffs
|
||||
.map_or(false, |buffs| !buffs.contains(BuffKind::ProtectingWard)),
|
||||
_ => true,
|
||||
};
|
||||
a.could_use(attack_data, self, tgt_data, read_data, desired_energy)
|
||||
&& additional_conditions
|
||||
}),
|
||||
_ => false,
|
||||
};
|
||||
let continue_current_input = |current_input, next_input: &mut Option<InputKind>| {
|
||||
if matches!(current_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(current_input);
|
||||
}
|
||||
};
|
||||
let current_input = self.char_state.ability_info().map(|ai| ai.input);
|
||||
let ability_preferences = AbilityPreferences {
|
||||
desired_energy: 40.0,
|
||||
combo_scaling_buildup: 0,
|
||||
};
|
||||
let mut next_input = None;
|
||||
if let Some(input) = current_input {
|
||||
continue_current_input(input, &mut next_input);
|
||||
} else {
|
||||
match HammerTactics::from_u8(
|
||||
agent.combat_state.int_counters[IntCounters::Tactic as usize],
|
||||
) {
|
||||
HammerTactics::Unskilled => {
|
||||
if rng.gen_bool(0.5) {
|
||||
next_input = Some(InputKind::Primary);
|
||||
} else {
|
||||
next_input = Some(InputKind::Secondary);
|
||||
}
|
||||
},
|
||||
HammerTactics::Simple => {
|
||||
if rng.gen_bool(0.5) {
|
||||
next_input = Some(InputKind::Primary);
|
||||
} else {
|
||||
next_input = Some(InputKind::Secondary);
|
||||
}
|
||||
},
|
||||
HammerTactics::AttackSimple | HammerTactics::SupportSimple => {
|
||||
if could_use_input(InputKind::Ability(0), ability_preferences) {
|
||||
next_input = Some(InputKind::Ability(0));
|
||||
} else if rng.gen_bool(0.5) {
|
||||
next_input = Some(InputKind::Primary);
|
||||
} else {
|
||||
next_input = Some(InputKind::Secondary);
|
||||
}
|
||||
},
|
||||
HammerTactics::AttackIntermediate | HammerTactics::SupportIntermediate => {
|
||||
let random_ability = InputKind::Ability(rng.gen_range(0..3));
|
||||
if could_use_input(random_ability, ability_preferences) {
|
||||
next_input = Some(random_ability);
|
||||
} else if rng.gen_bool(0.5) {
|
||||
next_input = Some(InputKind::Primary);
|
||||
} else {
|
||||
next_input = Some(InputKind::Secondary);
|
||||
}
|
||||
},
|
||||
HammerTactics::AttackAdvanced | HammerTactics::SupportAdvanced => {
|
||||
let random_ability = InputKind::Ability(rng.gen_range(0..5));
|
||||
if could_use_input(random_ability, ability_preferences) {
|
||||
next_input = Some(random_ability);
|
||||
} else if rng.gen_bool(0.5) {
|
||||
next_input = Some(InputKind::Primary);
|
||||
} else {
|
||||
next_input = Some(InputKind::Secondary);
|
||||
}
|
||||
},
|
||||
HammerTactics::AttackExpert | HammerTactics::SupportExpert => {
|
||||
let random_ability = InputKind::Ability(rng.gen_range(0..5));
|
||||
if could_use_input(random_ability, ability_preferences) {
|
||||
next_input = Some(random_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, ability_preferences) {
|
||||
controller.push_basic_input(input);
|
||||
false
|
||||
} else {
|
||||
true
|
||||
}
|
||||
} else {
|
||||
true
|
||||
}
|
||||
} else {
|
||||
false
|
||||
};
|
||||
|
||||
if attack_failed && attack_data.dist_sqrd > 1.5_f32.powi(2) {
|
||||
self.path_toward_target(
|
||||
agent,
|
||||
controller,
|
||||
tgt_data.pos.0,
|
||||
read_data,
|
||||
Path::Separate,
|
||||
None,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn handle_sword_attack(
|
||||
|
@ -27,6 +27,7 @@ use common::{
|
||||
terrain::TerrainGrid,
|
||||
uid::{IdMaps, Uid},
|
||||
};
|
||||
use common_base::dev_panic;
|
||||
use specs::{shred, Entities, Entity as EcsEntity, Read, ReadExpect, ReadStorage, SystemData};
|
||||
|
||||
event_emitters! {
|
||||
@ -72,6 +73,7 @@ pub struct AgentData<'a> {
|
||||
|
||||
pub struct TargetData<'a> {
|
||||
pub pos: &'a Pos,
|
||||
pub ori: Option<&'a Ori>,
|
||||
pub body: Option<&'a Body>,
|
||||
pub scale: Option<&'a Scale>,
|
||||
pub char_state: Option<&'a CharacterState>,
|
||||
@ -84,6 +86,7 @@ impl<'a> TargetData<'a> {
|
||||
pub fn new(pos: &'a Pos, target: EcsEntity, read_data: &'a ReadData) -> Self {
|
||||
Self {
|
||||
pos,
|
||||
ori: read_data.orientations.get(target),
|
||||
body: read_data.bodies.get(target),
|
||||
scale: read_data.scales.get(target),
|
||||
char_state: read_data.char_states.get(target),
|
||||
@ -342,6 +345,39 @@ impl AxeTactics {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
pub enum HammerTactics {
|
||||
Unskilled = 0,
|
||||
Simple = 1,
|
||||
AttackSimple = 2,
|
||||
SupportSimple = 3,
|
||||
AttackIntermediate = 4,
|
||||
SupportIntermediate = 5,
|
||||
AttackAdvanced = 6,
|
||||
SupportAdvanced = 7,
|
||||
AttackExpert = 8,
|
||||
SupportExpert = 9,
|
||||
}
|
||||
|
||||
impl HammerTactics {
|
||||
pub fn from_u8(x: u8) -> Self {
|
||||
use HammerTactics::*;
|
||||
match x {
|
||||
0 => Unskilled,
|
||||
1 => Simple,
|
||||
2 => AttackSimple,
|
||||
3 => SupportSimple,
|
||||
4 => AttackIntermediate,
|
||||
5 => SupportIntermediate,
|
||||
6 => AttackAdvanced,
|
||||
7 => SupportAdvanced,
|
||||
8 => AttackExpert,
|
||||
9 => SupportExpert,
|
||||
_ => Unskilled,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(SystemData)]
|
||||
pub struct ReadData<'a> {
|
||||
pub entities: Entities<'a>,
|
||||
@ -481,6 +517,17 @@ pub enum AbilityData {
|
||||
angle: f32,
|
||||
ori_rate: f32,
|
||||
},
|
||||
Shockwave {
|
||||
energy: f32,
|
||||
angle: f32,
|
||||
range: f32,
|
||||
combo: u32,
|
||||
},
|
||||
// Note, buff check not done as auras could be non-buff and auras could target either in or
|
||||
// out of group
|
||||
StaticAura {
|
||||
energy: f32,
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, Default)]
|
||||
@ -659,7 +706,29 @@ impl AbilityData {
|
||||
ori_rate: *ori_rate,
|
||||
energy_drain: *energy_drain,
|
||||
},
|
||||
_ => return None,
|
||||
Shockwave {
|
||||
energy_cost,
|
||||
shockwave_angle,
|
||||
shockwave_speed,
|
||||
shockwave_duration,
|
||||
minimum_combo,
|
||||
..
|
||||
} => Self::Shockwave {
|
||||
energy: *energy_cost,
|
||||
angle: *shockwave_angle,
|
||||
range: *shockwave_speed * *shockwave_duration,
|
||||
combo: *minimum_combo,
|
||||
},
|
||||
StaticAura { energy_cost, .. } => Self::StaticAura {
|
||||
energy: *energy_cost,
|
||||
},
|
||||
_ => {
|
||||
dev_panic!(
|
||||
"Agent tried to use ability with a character state they haven't learned to \
|
||||
understand"
|
||||
);
|
||||
return None;
|
||||
},
|
||||
};
|
||||
Some(inner)
|
||||
}
|
||||
@ -888,6 +957,17 @@ impl AbilityData {
|
||||
angle,
|
||||
ori_rate,
|
||||
} => beam_check(*range, *angle, *ori_rate) && energy_check(*energy_drain * 3.0),
|
||||
Shockwave {
|
||||
energy,
|
||||
range,
|
||||
angle,
|
||||
combo,
|
||||
} => {
|
||||
melee_check(*range, *angle, None)
|
||||
&& energy_check(*energy)
|
||||
&& combo_check(*combo, false)
|
||||
},
|
||||
StaticAura { energy } => energy_check(*energy),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user