Simple AI for each of the stances.

This commit is contained in:
Sam 2023-01-23 22:06:20 -05:00
parent 43d7e9357a
commit 1edd064611
3 changed files with 424 additions and 101 deletions

View File

@ -795,7 +795,7 @@ impl<'a> AgentData<'a> {
e e
) )
}); });
controller.events.reserve(32);
attack_fn(self, agent, controller, tgt_data, read_data); attack_fn(self, agent, controller, tgt_data, read_data);
} }
} }

View File

@ -1,7 +1,7 @@
use crate::{consts::MAX_PATH_DIST, data::*, util::entities_have_line_of_sight}; use crate::{consts::MAX_PATH_DIST, data::*, util::entities_have_line_of_sight};
use common::{ use common::{
comp::{ comp::{
ability::{ActiveAbilities, AuxiliaryAbility}, ability::{ActiveAbilities, AuxiliaryAbility, Stance, SwordStance},
buff::BuffKind, buff::BuffKind,
item::tool::AbilityContext, item::tool::AbilityContext,
skills::{AxeSkill, BowSkill, HammerSkill, SceptreSkill, Skill, StaffSkill, SwordSkill}, skills::{AxeSkill, BowSkill, HammerSkill, SceptreSkill, Skill, StaffSkill, SwordSkill},
@ -510,6 +510,7 @@ impl<'a> AgentData<'a> {
} }
if !agent.action_state.initialized { if !agent.action_state.initialized {
agent.action_state.initialized = true;
let available_tactics = { let available_tactics = {
let mut tactics = Vec::new(); let mut tactics = Vec::new();
let try_tactic = |skill, tactic, tactics: &mut Vec<SwordTactics>| { let try_tactic = |skill, tactic, tactics: &mut Vec<SwordTactics>| {
@ -542,33 +543,33 @@ impl<'a> AgentData<'a> {
// SwordTactics::CleavingAdvanced, // SwordTactics::CleavingAdvanced,
// &mut tactics, // &mut tactics,
// ); // );
// if tactics.is_empty() { if tactics.is_empty() {
// try_tactic( try_tactic(
// SwordSkill::HeavyWindmillSlash, SwordSkill::HeavyWindmillSlash,
// SwordTactics::HeavySimple, SwordTactics::HeavySimple,
// &mut tactics, &mut tactics,
// ); );
// try_tactic( try_tactic(
// SwordSkill::AgileQuickDraw, SwordSkill::AgileQuickDraw,
// SwordTactics::AgileSimple, SwordTactics::AgileSimple,
// &mut tactics, &mut tactics,
// ); );
// try_tactic( try_tactic(
// SwordSkill::DefensiveDisengage, SwordSkill::DefensiveDisengage,
// SwordTactics::DefensiveSimple, SwordTactics::DefensiveSimple,
// &mut tactics, &mut tactics,
// ); );
// try_tactic( try_tactic(
// SwordSkill::CripplingGouge, SwordSkill::CripplingGouge,
// SwordTactics::CripplingSimple, SwordTactics::CripplingSimple,
// &mut tactics, &mut tactics,
// ); );
// try_tactic( try_tactic(
// SwordSkill::CleavingWhirlwindSlice, SwordSkill::CleavingWhirlwindSlice,
// SwordTactics::CleavingSimple, SwordTactics::CleavingSimple,
// &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);
} }
@ -586,147 +587,147 @@ impl<'a> AgentData<'a> {
agent.action_state.int_counters[IntCounters::Tactics as usize] = tactic as u8; agent.action_state.int_counters[IntCounters::Tactics as usize] = tactic as u8;
let auxiliary_key = ActiveAbilities::active_auxiliary_key(Some(self.inventory)); let auxiliary_key = ActiveAbilities::active_auxiliary_key(Some(self.inventory));
let mut set_sword_ability = |slot, skill| { let mut set_sword_ability = |controller: &mut Controller, slot, skill| {
controller.push_event(ControlEvent::ChangeAbility { controller.push_event(ControlEvent::ChangeAbility {
slot, slot,
auxiliary_key, auxiliary_key,
new_ability: AuxiliaryAbility::MainWeapon(skill), new_ability: AuxiliaryAbility::MainWeapon(skill),
}) });
}; };
match tactic { match tactic {
SwordTactics::Unskilled => {}, SwordTactics::Unskilled => {},
SwordTactics::Basic => { SwordTactics::Basic => {
// Crescent slash // Crescent slash
set_sword_ability(0, 0); set_sword_ability(controller, 0, 0);
// Fell strike // Fell strike
set_sword_ability(1, 1); set_sword_ability(controller, 1, 1);
// Skewer // Skewer
set_sword_ability(2, 2); set_sword_ability(controller, 2, 2);
// Cascade // Cascade
set_sword_ability(3, 3); set_sword_ability(controller, 3, 3);
// Cross cut // Cross cut
set_sword_ability(4, 4); set_sword_ability(controller, 4, 4);
}, },
SwordTactics::HeavySimple => { SwordTactics::HeavySimple => {
// Finisher // Finisher
set_sword_ability(0, 5); set_sword_ability(controller, 0, 5);
// Crescent slash // Crescent slash
set_sword_ability(1, 0); set_sword_ability(controller, 1, 0);
// Cascade // Cascade
set_sword_ability(2, 3); set_sword_ability(controller, 2, 3);
// Windmill slash // Windmill slash
set_sword_ability(3, 6); set_sword_ability(controller, 3, 6);
// Pommel strike // Pommel strike
set_sword_ability(4, 7); set_sword_ability(controller, 4, 7);
}, },
SwordTactics::AgileSimple => { SwordTactics::AgileSimple => {
// Finisher // Finisher
set_sword_ability(0, 5); set_sword_ability(controller, 0, 5);
// Skewer // Skewer
set_sword_ability(1, 2); set_sword_ability(controller, 1, 2);
// Cross cut // Cross cut
set_sword_ability(2, 4); set_sword_ability(controller, 2, 4);
// Quick draw // Quick draw
set_sword_ability(3, 8); set_sword_ability(controller, 3, 8);
// Feint // Feint
set_sword_ability(4, 9); set_sword_ability(controller, 4, 9);
}, },
SwordTactics::DefensiveSimple => { SwordTactics::DefensiveSimple => {
// Finisher // Finisher
set_sword_ability(0, 5); set_sword_ability(controller, 0, 5);
// Crescent slash // Crescent slash
set_sword_ability(1, 0); set_sword_ability(controller, 1, 0);
// Fell strike // Fell strike
set_sword_ability(2, 1); set_sword_ability(controller, 2, 1);
// Riposte // Riposte
set_sword_ability(3, 10); set_sword_ability(controller, 3, 10);
// Disengage // Disengage
set_sword_ability(4, 11); set_sword_ability(controller, 4, 11);
}, },
SwordTactics::CripplingSimple => { SwordTactics::CripplingSimple => {
// Finisher // Finisher
set_sword_ability(0, 5); set_sword_ability(controller, 0, 5);
// Fell strike // Fell strike
set_sword_ability(1, 1); set_sword_ability(controller, 1, 1);
// Skewer // Skewer
set_sword_ability(2, 2); set_sword_ability(controller, 2, 2);
// Gouge // Gouge
set_sword_ability(3, 12); set_sword_ability(controller, 3, 12);
// Hamstring // Hamstring
set_sword_ability(4, 13); set_sword_ability(controller, 4, 13);
}, },
SwordTactics::CleavingSimple => { SwordTactics::CleavingSimple => {
// Finisher // Finisher
set_sword_ability(0, 5); set_sword_ability(controller, 0, 5);
// Cascade // Cascade
set_sword_ability(1, 3); set_sword_ability(controller, 1, 3);
// Cross cut // Cross cut
set_sword_ability(2, 4); set_sword_ability(controller, 2, 4);
// Whirlwind slice // Whirlwind slice
set_sword_ability(3, 14); set_sword_ability(controller, 3, 14);
// Earth splitter // Earth splitter
set_sword_ability(4, 15); set_sword_ability(controller, 4, 15);
}, },
SwordTactics::HeavyAdvanced => { SwordTactics::HeavyAdvanced => {
// Finisher // Finisher
set_sword_ability(0, 5); set_sword_ability(controller, 0, 5);
// Windmill slash // Windmill slash
set_sword_ability(1, 6); set_sword_ability(controller, 1, 6);
// Pommel strike // Pommel strike
set_sword_ability(2, 7); set_sword_ability(controller, 2, 7);
// Fortitude // Fortitude
set_sword_ability(3, 16); set_sword_ability(controller, 3, 16);
// Pillar Thrust // Pillar Thrust
set_sword_ability(4, 17); set_sword_ability(controller, 4, 17);
}, },
SwordTactics::AgileAdvanced => { SwordTactics::AgileAdvanced => {
// Finisher // Finisher
set_sword_ability(0, 5); set_sword_ability(controller, 0, 5);
// Quick draw // Quick draw
set_sword_ability(1, 8); set_sword_ability(controller, 1, 8);
// Feint // Feint
set_sword_ability(2, 9); set_sword_ability(controller, 2, 9);
// Dancing edge // Dancing edge
set_sword_ability(3, 18); set_sword_ability(controller, 3, 18);
// Flurry // Flurry
set_sword_ability(4, 19); set_sword_ability(controller, 4, 19);
}, },
SwordTactics::DefensiveAdvanced => { SwordTactics::DefensiveAdvanced => {
// Finisher // Finisher
set_sword_ability(0, 5); set_sword_ability(controller, 0, 5);
// Riposte // Riposte
set_sword_ability(1, 10); set_sword_ability(controller, 1, 10);
// Disengage // Disengage
set_sword_ability(2, 11); set_sword_ability(controller, 2, 11);
// Stalwart sword // Stalwart sword
set_sword_ability(3, 20); set_sword_ability(controller, 3, 20);
// Deflect // Deflect
set_sword_ability(4, 21); set_sword_ability(controller, 4, 21);
}, },
SwordTactics::CripplingAdvanced => { SwordTactics::CripplingAdvanced => {
// Finisher // Finisher
set_sword_ability(0, 5); set_sword_ability(controller, 0, 5);
// Gouge // Gouge
set_sword_ability(1, 12); set_sword_ability(controller, 1, 12);
// Hamstring // Hamstring
set_sword_ability(2, 13); set_sword_ability(controller, 2, 13);
// Eviscerate // Eviscerate
set_sword_ability(3, 22); set_sword_ability(controller, 3, 22);
// Bloody gash // Bloody gash
set_sword_ability(4, 23); set_sword_ability(controller, 4, 23);
}, },
SwordTactics::CleavingAdvanced => { SwordTactics::CleavingAdvanced => {
// Finisher // Finisher
set_sword_ability(0, 5); set_sword_ability(controller, 0, 5);
// Whirlwind slice // Whirlwind slice
set_sword_ability(1, 14); set_sword_ability(controller, 1, 14);
// Earth splitter // Earth splitter
set_sword_ability(2, 15); set_sword_ability(controller, 2, 15);
// Blade fever // Blade fever
set_sword_ability(3, 24); set_sword_ability(controller, 3, 24);
// Sky splitter // Sky splitter
set_sword_ability(4, 25); set_sword_ability(controller, 4, 25);
}, },
} }
@ -1047,15 +1048,15 @@ impl<'a> AgentData<'a> {
} }
} }
let could_use_input = |input, misc_data| match input { let could_use_input = |input, misc_data| match input {
InputKind::Primary => primary InputKind::Primary => primary.as_ref().map_or(false, |p| {
.as_ref() p.could_use(attack_data, self, misc_data, tgt_data)
.map_or(false, |p| p.could_use(attack_data, self, misc_data)), }),
InputKind::Secondary => secondary InputKind::Secondary => secondary.as_ref().map_or(false, |s| {
.as_ref() s.could_use(attack_data, self, misc_data, tgt_data)
.map_or(false, |s| s.could_use(attack_data, self, misc_data)), }),
InputKind::Ability(x) => abilities[x] InputKind::Ability(x) => abilities[x].as_ref().map_or(false, |a| {
.as_ref() a.could_use(attack_data, self, misc_data, tgt_data)
.map_or(false, |a| a.could_use(attack_data, self, misc_data)), }),
_ => false, _ => false,
}; };
match SwordTactics::from_u8( match SwordTactics::from_u8(
@ -1148,11 +1149,298 @@ impl<'a> AgentData<'a> {
true true
} }
}, },
SwordTactics::HeavySimple => true, SwordTactics::HeavySimple => {
SwordTactics::AgileSimple => true, let misc_data = MiscData {
SwordTactics::DefensiveSimple => true, desired_energy: 25.0,
SwordTactics::CripplingSimple => true, };
SwordTactics::CleavingSimple => true, 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 stance_ability = InputKind::Ability(rng.gen_range(3..5));
let random_ability = InputKind::Ability(rng.gen_range(1..5));
if !matches!(self.stance, Some(Stance::Sword(SwordStance::Heavy))) {
if could_use_input(stance_ability, &misc_data) {
next_input = Some(stance_ability);
} else if rng.gen_bool(0.5) {
next_input = Some(InputKind::Primary);
} else {
next_input = Some(InputKind::Secondary);
}
} else {
if could_use_input(InputKind::Ability(0), &misc_data) {
next_input = Some(InputKind::Ability(0));
} else if could_use_input(random_ability, &misc_data) {
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, &misc_data) {
controller.push_basic_input(input);
false
} else {
true
}
} else {
true
}
},
SwordTactics::AgileSimple => {
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 stance_ability = InputKind::Ability(rng.gen_range(3..5));
let random_ability = InputKind::Ability(rng.gen_range(1..5));
if !matches!(self.stance, Some(Stance::Sword(SwordStance::Agile))) {
if could_use_input(stance_ability, &misc_data) {
next_input = Some(stance_ability);
} else if rng.gen_bool(0.5) {
next_input = Some(InputKind::Primary);
} else {
next_input = Some(InputKind::Secondary);
}
} else {
if could_use_input(InputKind::Ability(0), &misc_data) {
next_input = Some(InputKind::Ability(0));
} else if could_use_input(random_ability, &misc_data) {
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, &misc_data) {
controller.push_basic_input(input);
false
} else {
true
}
} else {
true
}
},
SwordTactics::DefensiveSimple => {
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 stance_ability = InputKind::Ability(rng.gen_range(3..5));
let random_ability = InputKind::Ability(rng.gen_range(1..5));
if !matches!(self.stance, Some(Stance::Sword(SwordStance::Defensive))) {
if could_use_input(stance_ability, &misc_data) {
next_input = Some(stance_ability);
} else if rng.gen_bool(0.5) {
next_input = Some(InputKind::Primary);
} else {
next_input = Some(InputKind::Secondary);
}
} else {
if could_use_input(InputKind::Ability(0), &misc_data) {
next_input = Some(InputKind::Ability(0));
} else if could_use_input(InputKind::Ability(3), &misc_data) {
next_input = Some(InputKind::Ability(3));
} else if could_use_input(random_ability, &misc_data) {
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, &misc_data) {
controller.push_basic_input(input);
false
} else {
true
}
} else {
true
}
},
SwordTactics::CripplingSimple => {
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 stance_ability = InputKind::Ability(rng.gen_range(3..5));
let random_ability = InputKind::Ability(rng.gen_range(1..5));
if !matches!(self.stance, Some(Stance::Sword(SwordStance::Crippling))) {
if could_use_input(stance_ability, &misc_data) {
next_input = Some(stance_ability);
} else if rng.gen_bool(0.5) {
next_input = Some(InputKind::Primary);
} else {
next_input = Some(InputKind::Secondary);
}
} else {
if could_use_input(InputKind::Ability(0), &misc_data) {
next_input = Some(InputKind::Ability(0));
} else if could_use_input(random_ability, &misc_data) {
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, &misc_data) {
controller.push_basic_input(input);
false
} else {
true
}
} else {
true
}
},
SwordTactics::CleavingSimple => {
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 stance_ability = InputKind::Ability(rng.gen_range(3..5));
let random_ability = InputKind::Ability(rng.gen_range(1..5));
if !matches!(self.stance, Some(Stance::Sword(SwordStance::Cleaving))) {
if could_use_input(stance_ability, &misc_data) {
next_input = Some(stance_ability);
} else if rng.gen_bool(0.5) {
next_input = Some(InputKind::Primary);
} else {
next_input = Some(InputKind::Secondary);
}
} else {
if could_use_input(InputKind::Ability(0), &misc_data) {
next_input = Some(InputKind::Ability(0));
} else if could_use_input(random_ability, &misc_data) {
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, &misc_data) {
controller.push_basic_input(input);
false
} else {
true
}
} else {
true
}
},
SwordTactics::HeavyAdvanced => true, SwordTactics::HeavyAdvanced => true,
SwordTactics::AgileAdvanced => true, SwordTactics::AgileAdvanced => true,
SwordTactics::DefensiveAdvanced => true, SwordTactics::DefensiveAdvanced => true,

View File

@ -13,7 +13,7 @@ use common::{
path::TraversalConfig, path::TraversalConfig,
resources::{DeltaTime, Time, TimeOfDay}, resources::{DeltaTime, Time, TimeOfDay},
rtsim::RtSimEntity, rtsim::RtSimEntity,
states::utils::ForcedMovement, states::utils::{ForcedMovement, StageSection},
terrain::TerrainGrid, terrain::TerrainGrid,
uid::{Uid, UidAllocator}, uid::{Uid, UidAllocator},
}; };
@ -195,6 +195,7 @@ pub enum Path {
Partial, Partial,
} }
#[derive(Copy, Clone, Debug)]
pub enum AbilityData { pub enum AbilityData {
ComboMelee { ComboMelee {
range: f32, range: f32,
@ -239,6 +240,11 @@ pub enum AbilityData {
energy_drain: f32, energy_drain: f32,
charge_dur: f32, charge_dur: f32,
}, },
RiposteMelee {
range: f32,
angle: f32,
energy: f32,
},
} }
pub struct MiscData { pub struct MiscData {
@ -346,6 +352,15 @@ impl AbilityData {
range: melee_constructor.range, range: melee_constructor.range,
angle: melee_constructor.angle, angle: melee_constructor.angle,
}, },
RiposteMelee {
energy_cost,
melee_constructor,
..
} => Self::RiposteMelee {
energy: *energy_cost,
range: melee_constructor.range,
angle: melee_constructor.angle,
},
_ => return None, _ => return None,
}; };
Some(inner) Some(inner)
@ -356,6 +371,7 @@ impl AbilityData {
attack_data: &AttackData, attack_data: &AttackData,
agent_data: &AgentData, agent_data: &AgentData,
misc_data: &MiscData, misc_data: &MiscData,
tgt_data: &TargetData,
) -> bool { ) -> bool {
let melee_check = |range: f32, angle, forced_movement: Option<ForcedMovement>| { let melee_check = |range: f32, angle, forced_movement: Option<ForcedMovement>| {
let range_inc = forced_movement.map_or(0.0, |fm| match fm { let range_inc = forced_movement.map_or(0.0, |fm| match fm {
@ -442,6 +458,25 @@ impl AbilityData {
melee_check(*range, *angle, None) melee_check(*range, *angle, None)
&& energy_check(*initial_energy + *energy_drain * *charge_dur) && energy_check(*initial_energy + *energy_drain * *charge_dur)
}, },
RiposteMelee {
energy,
range,
angle,
} => {
melee_check(*range, *angle, None)
&& energy_check(*energy)
&& tgt_data.char_state.map_or(false, |cs| {
cs.is_melee_attack()
&& matches!(
cs.stage_section(),
Some(
StageSection::Buildup
| StageSection::Charge
| StageSection::Movement
)
)
})
},
} }
} }
} }