Tidal warrior AI.

This commit is contained in:
Sam 2021-05-25 21:20:27 -05:00
parent c81e1534f7
commit ac2f097d80
12 changed files with 150 additions and 93 deletions

View File

@ -108,7 +108,7 @@
secondary: "common.abilities.custom.wendigomagic.singlestrike", secondary: "common.abilities.custom.wendigomagic.singlestrike",
abilities: [], abilities: [],
), ),
Custom("Tidal Claws"): ( Custom("Tidal Warrior"): (
primary: "common.abilities.custom.tidalwarrior.pincer", primary: "common.abilities.custom.tidalwarrior.pincer",
secondary: "common.abilities.custom.tidalwarrior.scuttle", secondary: "common.abilities.custom.tidalwarrior.scuttle",
abilities: [ abilities: [

View File

@ -10,7 +10,7 @@ BasicBeam(
kind: Wet, kind: Wet,
dur_secs: 15.0, dur_secs: 15.0,
strength: Value(4.5), strength: Value(4.5),
chance: 0.25, chance: 1.0,
))), ))),
energy_regen: 0, energy_regen: 0,
energy_drain: 0, energy_drain: 0,

View File

@ -14,7 +14,7 @@ DashMelee(
charge_duration: 2.0, charge_duration: 2.0,
swing_duration: 0.1, swing_duration: 0.1,
recover_duration: 0.5, recover_duration: 0.5,
charge_through: false, charge_through: true,
is_interruptible: false, is_interruptible: false,
damage_kind: Crushing, damage_kind: Crushing,
) )

View File

@ -1,18 +1,14 @@
BasicMelee( BasicSummon(
energy_cost: 0, buildup_duration: 0.5,
buildup_duration: 0.3, cast_duration: 1.0,
swing_duration: 0.1, recover_duration: 0.5,
recover_duration: 0.6, summon_amount: 6,
base_damage: 50.0, summon_info: (
base_poise_damage: 100.0, // If this is still HaniwaSentry, code reviewers open a comment. I'll know what to do.
knockback: ( strength: 50.0, direction: Towards), body: Object(HaniwaSentry),
range: 5.0, scale: None,
max_angle: 60.0, health_scaling: 20,
damage_effect: Some(Buff(( loadout_config: None,
kind: Crippled, skillset_config: None,
dur_secs: 15.0, ),
strength: Value(0.5),
chance: 1.0,
))),
damage_kind: Slashing,
) )

View File

@ -15,5 +15,5 @@ ItemDef(
)), )),
quality: Low, quality: Low,
tags: [], tags: [],
ability_spec: Some(Custom("Tidal Claws")), ability_spec: Some(Custom("Tidal Warrior")),
) )

View File

@ -235,14 +235,9 @@ impl CharacterBehavior for Data {
handle_orientation(data, &mut update, 0.4 * self.static_data.ori_modifier); handle_orientation(data, &mut update, 0.4 * self.static_data.ori_modifier);
// Forward movement // Forward movement
handle_forced_movement( handle_forced_movement(data, &mut update, ForcedMovement::Forward {
data,
&mut update,
ForcedMovement::Forward {
strength: self.static_data.stage_data[stage_index].forward_movement, strength: self.static_data.stage_data[stage_index].forward_movement,
}, });
0.3,
);
// Swings // Swings
update.character = CharacterState::ComboMelee(Data { update.character = CharacterState::ComboMelee(Data {

View File

@ -106,14 +106,9 @@ impl CharacterBehavior for Data {
.min(1.0); .min(1.0);
handle_orientation(data, &mut update, 0.6); handle_orientation(data, &mut update, 0.6);
handle_forced_movement( handle_forced_movement(data, &mut update, ForcedMovement::Forward {
data,
&mut update,
ForcedMovement::Forward {
strength: self.static_data.forward_speed * charge_frac.sqrt(), strength: self.static_data.forward_speed * charge_frac.sqrt(),
}, });
0.1,
);
// This logic basically just decides if a charge should end, and prevents the // This logic basically just decides if a charge should end, and prevents the
// character state spamming attacks while checking if it has hit something // character state spamming attacks while checking if it has hit something

View File

@ -86,17 +86,12 @@ impl CharacterBehavior for Data {
let progress = 1.0 let progress = 1.0
- self.timer.as_secs_f32() - self.timer.as_secs_f32()
/ self.static_data.movement_duration.as_secs_f32(); / self.static_data.movement_duration.as_secs_f32();
handle_forced_movement( handle_forced_movement(data, &mut update, ForcedMovement::Leap {
data,
&mut update,
ForcedMovement::Leap {
vertical: self.static_data.vertical_leap_strength, vertical: self.static_data.vertical_leap_strength,
forward: self.static_data.forward_leap_strength, forward: self.static_data.forward_leap_strength,
progress, progress,
direction: MovementDirection::Look, direction: MovementDirection::Look,
}, });
0.15,
);
// Increment duration // Increment duration
// If we were to set a timeout for state, this would be // If we were to set a timeout for state, this would be

View File

@ -78,19 +78,14 @@ impl CharacterBehavior for Data {
}, },
StageSection::Movement => { StageSection::Movement => {
// Update velocity // Update velocity
handle_forced_movement( handle_forced_movement(data, &mut update, ForcedMovement::Forward {
data,
&mut update,
ForcedMovement::Forward {
strength: self.static_data.roll_strength strength: self.static_data.roll_strength
* ((1.0 * ((1.0
- self.timer.as_secs_f32() - self.timer.as_secs_f32()
/ self.static_data.movement_duration.as_secs_f32()) / self.static_data.movement_duration.as_secs_f32())
/ 2.0 / 2.0
+ 0.5), + 0.5),
}, });
0.0,
);
if self.timer < self.static_data.movement_duration { if self.timer < self.static_data.movement_duration {
// Movement // Movement

View File

@ -167,14 +167,9 @@ impl CharacterBehavior for Data {
self.static_data.movement_behavior, self.static_data.movement_behavior,
MovementBehavior::ForwardGround MovementBehavior::ForwardGround
) { ) {
handle_forced_movement( handle_forced_movement(data, &mut update, ForcedMovement::Forward {
data,
&mut update,
ForcedMovement::Forward {
strength: self.static_data.forward_speed, strength: self.static_data.forward_speed,
}, });
0.1,
);
} }
// Swings // Swings

View File

@ -263,21 +263,15 @@ fn basic_move(data: &JoinData, update: &mut StateUpdate, efficiency: f32) {
} }
/// Handles forced movement /// Handles forced movement
pub fn handle_forced_movement( pub fn handle_forced_movement(data: &JoinData, update: &mut StateUpdate, movement: ForcedMovement) {
data: &JoinData,
update: &mut StateUpdate,
movement: ForcedMovement,
efficiency: f32,
) {
let efficiency = efficiency * data.stats.move_speed_modifier * data.stats.friction_modifier;
match movement { match movement {
ForcedMovement::Forward { strength } => { ForcedMovement::Forward { strength } => {
let strength = strength * data.stats.move_speed_modifier * data.stats.friction_modifier;
if let Some(accel) = data.physics.on_ground.then_some(data.body.base_accel()) { if let Some(accel) = data.physics.on_ground.then_some(data.body.base_accel()) {
update.vel.0 += Vec2::broadcast(data.dt.0) update.vel.0 += Vec2::broadcast(data.dt.0)
* accel * accel
* (data.inputs.move_dir + Vec2::from(update.ori) * strength) * (data.inputs.move_dir + Vec2::from(update.ori))
* efficiency; * strength;
} }
}, },
ForcedMovement::Leap { ForcedMovement::Leap {

View File

@ -87,6 +87,10 @@ struct AttackData {
angle: f32, angle: f32,
} }
impl AttackData {
fn in_min_range(&self) -> bool { self.dist_sqrd < self.min_attack_dist.powi(2) }
}
#[derive(Eq, PartialEq)] #[derive(Eq, PartialEq)]
pub enum Tactic { pub enum Tactic {
Melee, Melee,
@ -113,6 +117,7 @@ pub enum Tactic {
BirdLargeFire, BirdLargeFire,
Minotaur, Minotaur,
ClayGolem, ClayGolem,
TidalWarrior,
} }
#[derive(SystemData)] #[derive(SystemData)]
@ -1601,6 +1606,7 @@ impl<'a> AgentData<'a> {
"Mindflayer" => Tactic::Mindflayer, "Mindflayer" => Tactic::Mindflayer,
"Minotaur" => Tactic::Minotaur, "Minotaur" => Tactic::Minotaur,
"Clay Golem" => Tactic::ClayGolem, "Clay Golem" => Tactic::ClayGolem,
"Tidal Warrior" => Tactic::TidalWarrior,
_ => Tactic::Melee, _ => Tactic::Melee,
}, },
AbilitySpec::Tool(tool_kind) => tool_tactic(*tool_kind), AbilitySpec::Tool(tool_kind) => tool_tactic(*tool_kind),
@ -1843,6 +1849,13 @@ impl<'a> AgentData<'a> {
&tgt_data, &tgt_data,
&read_data, &read_data,
), ),
Tactic::TidalWarrior => self.handle_tidal_warrior_attack(
agent,
controller,
&attack_data,
&tgt_data,
&read_data,
),
} }
} }
@ -1854,7 +1867,7 @@ impl<'a> AgentData<'a> {
tgt_data: &TargetData, tgt_data: &TargetData,
read_data: &ReadData, read_data: &ReadData,
) { ) {
if attack_data.dist_sqrd < attack_data.min_attack_dist.powi(2) && attack_data.angle < 45.0 { if attack_data.in_min_range() && attack_data.angle < 45.0 {
controller controller
.actions .actions
.push(ControlAction::basic_input(InputKind::Primary)); .push(ControlAction::basic_input(InputKind::Primary));
@ -1883,7 +1896,7 @@ impl<'a> AgentData<'a> {
tgt_data: &TargetData, tgt_data: &TargetData,
read_data: &ReadData, read_data: &ReadData,
) { ) {
if attack_data.dist_sqrd < attack_data.min_attack_dist.powi(2) && attack_data.angle < 45.0 { if attack_data.in_min_range() && attack_data.angle < 45.0 {
controller.inputs.move_dir = Vec2::zero(); controller.inputs.move_dir = Vec2::zero();
if agent.action_state.timer > 6.0 { if agent.action_state.timer > 6.0 {
controller controller
@ -1932,7 +1945,7 @@ impl<'a> AgentData<'a> {
tgt_data: &TargetData, tgt_data: &TargetData,
read_data: &ReadData, read_data: &ReadData,
) { ) {
if attack_data.dist_sqrd < attack_data.min_attack_dist.powi(2) && attack_data.angle < 45.0 { if attack_data.in_min_range() && attack_data.angle < 45.0 {
controller.inputs.move_dir = Vec2::zero(); controller.inputs.move_dir = Vec2::zero();
if agent.action_state.timer > 4.0 { if agent.action_state.timer > 4.0 {
controller controller
@ -2004,7 +2017,7 @@ impl<'a> AgentData<'a> {
tgt_data: &TargetData, tgt_data: &TargetData,
read_data: &ReadData, read_data: &ReadData,
) { ) {
if attack_data.dist_sqrd < attack_data.min_attack_dist.powi(2) && attack_data.angle < 45.0 { if attack_data.in_min_range() && attack_data.angle < 45.0 {
controller.inputs.move_dir = Vec2::zero(); controller.inputs.move_dir = Vec2::zero();
if self if self
.skill_set .skill_set
@ -2226,9 +2239,7 @@ impl<'a> AgentData<'a> {
tgt_data: &TargetData, tgt_data: &TargetData,
read_data: &ReadData, read_data: &ReadData,
) { ) {
if self.body.map(|b| b.is_humanoid()).unwrap_or(false) if self.body.map(|b| b.is_humanoid()).unwrap_or(false) && attack_data.in_min_range() {
&& attack_data.dist_sqrd < attack_data.min_attack_dist.powi(2)
{
controller controller
.actions .actions
.push(ControlAction::basic_input(InputKind::Roll)); .push(ControlAction::basic_input(InputKind::Roll));
@ -2325,7 +2336,7 @@ impl<'a> AgentData<'a> {
tgt_data: &TargetData, tgt_data: &TargetData,
read_data: &ReadData, read_data: &ReadData,
) { ) {
if attack_data.dist_sqrd < attack_data.min_attack_dist.powi(2) && attack_data.angle < 90.0 { if attack_data.in_min_range() && attack_data.angle < 90.0 {
controller.inputs.move_dir = Vec2::zero(); controller.inputs.move_dir = Vec2::zero();
controller controller
.actions .actions
@ -2371,8 +2382,7 @@ impl<'a> AgentData<'a> {
radius: u32, radius: u32,
circle_time: u32, circle_time: u32,
) { ) {
if attack_data.dist_sqrd < attack_data.min_attack_dist.powi(2) && thread_rng().gen_bool(0.5) if attack_data.in_min_range() && thread_rng().gen_bool(0.5) {
{
controller.inputs.move_dir = Vec2::zero(); controller.inputs.move_dir = Vec2::zero();
controller controller
.actions .actions
@ -2679,7 +2689,7 @@ impl<'a> AgentData<'a> {
tgt_data: &TargetData, tgt_data: &TargetData,
read_data: &ReadData, read_data: &ReadData,
) { ) {
if attack_data.angle < 90.0 && attack_data.dist_sqrd < attack_data.min_attack_dist.powi(2) { if attack_data.angle < 90.0 && attack_data.in_min_range() {
controller.inputs.move_dir = Vec2::zero(); controller.inputs.move_dir = Vec2::zero();
if agent.action_state.timer < 2.0 { if agent.action_state.timer < 2.0 {
controller controller
@ -2762,7 +2772,7 @@ impl<'a> AgentData<'a> {
tgt_data: &TargetData, tgt_data: &TargetData,
read_data: &ReadData, read_data: &ReadData,
) { ) {
if attack_data.angle < 90.0 && attack_data.dist_sqrd < attack_data.min_attack_dist.powi(2) { if attack_data.angle < 90.0 && attack_data.in_min_range() {
controller.inputs.move_dir = Vec2::zero(); controller.inputs.move_dir = Vec2::zero();
controller controller
.actions .actions
@ -3190,7 +3200,7 @@ impl<'a> AgentData<'a> {
agent.action_state.timer += read_data.dt.0; agent.action_state.timer += read_data.dt.0;
} else if agent.action_state.timer < 6.0 } else if agent.action_state.timer < 6.0
&& attack_data.angle < 90.0 && attack_data.angle < 90.0
&& attack_data.dist_sqrd < attack_data.min_attack_dist.powi(2) && attack_data.in_min_range()
{ {
// Triplestrike // Triplestrike
controller controller
@ -3355,6 +3365,88 @@ impl<'a> AgentData<'a> {
self.path_toward_target(agent, controller, tgt_data, read_data, true, None); self.path_toward_target(agent, controller, tgt_data, read_data, true, None);
} }
fn handle_tidal_warrior_attack(
&self,
agent: &mut Agent,
controller: &mut Controller,
attack_data: &AttackData,
tgt_data: &TargetData,
read_data: &ReadData,
) {
const SCUTTLE_RANGE: f32 = 40.0;
const BUBBLE_RANGE: f32 = 20.0;
const MINION_SUMMON_THRESHOLD: f32 = 0.20;
let health_fraction = self.health.map_or(0.5, |h| h.fraction());
// Sets counter at start of combat, using `condition` to keep track of whether
// it was already intitialized
if !agent.action_state.condition {
agent.action_state.counter = 1.0 - MINION_SUMMON_THRESHOLD;
agent.action_state.condition = true;
}
if agent.action_state.counter > health_fraction {
// Summon minions at particular thresholds of health
controller
.actions
.push(ControlAction::basic_input(InputKind::Ability(1)));
if matches!(self.char_state, CharacterState::BasicSummon(c) if matches!(c.stage_section, StageSection::Recover))
{
agent.action_state.counter -= MINION_SUMMON_THRESHOLD;
}
} else if attack_data.dist_sqrd < SCUTTLE_RANGE.powi(2) {
if matches!(self.char_state, CharacterState::DashMelee(c) if !matches!(c.stage_section, StageSection::Recover))
{
// Keep scuttling if already in dash melee and not in recover
controller
.actions
.push(ControlAction::basic_input(InputKind::Secondary));
} else if attack_data.dist_sqrd < BUBBLE_RANGE.powi(2) {
if matches!(self.char_state, CharacterState::BasicBeam(c) if !matches!(c.stage_section, StageSection::Recover) && c.timer < Duration::from_secs(10))
{
// Keep shooting bubbles at them if already in basic beam and not in recover and
// have not been bubbling too long
controller
.actions
.push(ControlAction::basic_input(InputKind::Ability(0)));
} else if attack_data.in_min_range() && attack_data.angle < 60.0 {
// Pincer them if they're in range and angle
controller
.actions
.push(ControlAction::basic_input(InputKind::Primary));
} else if attack_data.angle < 30.0
&& can_see_tgt(
&*read_data.terrain,
self.pos,
tgt_data.pos,
attack_data.dist_sqrd,
)
{
// Start bubbling them if not close enough to do something else and in angle and
// can see target
controller
.actions
.push(ControlAction::basic_input(InputKind::Ability(0)));
}
} else if attack_data.angle < 90.0
&& can_see_tgt(
&*read_data.terrain,
self.pos,
tgt_data.pos,
attack_data.dist_sqrd,
)
{
// Start scuttling if not close enough to do something else and in angle and can
// see target
controller
.actions
.push(ControlAction::basic_input(InputKind::Secondary));
}
}
// Always attempt to path towards target
self.path_toward_target(agent, controller, tgt_data, read_data, false, None);
}
fn follow( fn follow(
&self, &self,
agent: &mut Agent, agent: &mut Agent,