From 71d7a3b78051044eb42b2d581749165b151125e0 Mon Sep 17 00:00:00 2001 From: Sam Date: Wed, 29 Mar 2023 22:23:56 -0400 Subject: [PATCH] Ice creature AIs --- .../custom/frostfang/singlestrike.ron | 2 +- .../custom/frostfang/triplestrike.ron | 22 +-- .../abilities/custom/icedrake/icebreath.ron | 4 +- .../abilities/custom/icedrake/icy_bite.ron | 2 +- .../custom/tursus/tusk_bash_leap.ron | 2 +- common/src/states/charged_melee.rs | 11 ++ server/agent/src/action_nodes.rs | 26 ++++ server/agent/src/attack.rs | 142 ++++++++++++++++-- server/agent/src/data.rs | 19 ++- server/agent/src/util.rs | 2 +- 10 files changed, 187 insertions(+), 45 deletions(-) diff --git a/assets/common/abilities/custom/frostfang/singlestrike.ron b/assets/common/abilities/custom/frostfang/singlestrike.ron index 863b5eafc4..3f926554d9 100644 --- a/assets/common/abilities/custom/frostfang/singlestrike.ron +++ b/assets/common/abilities/custom/frostfang/singlestrike.ron @@ -13,7 +13,7 @@ ComboMelee2( damage_effect: Some(Buff(( kind: Frozen, dur_secs: 10.0, - strength: DamageFraction(0.5), + strength: Value(0.5), chance: 1.0, ))), ), diff --git a/assets/common/abilities/custom/frostfang/triplestrike.ron b/assets/common/abilities/custom/frostfang/triplestrike.ron index 67a7006045..3874146167 100644 --- a/assets/common/abilities/custom/frostfang/triplestrike.ron +++ b/assets/common/abilities/custom/frostfang/triplestrike.ron @@ -10,12 +10,6 @@ ComboMelee2( ), range: 2.5, angle: 30.0, - damage_effect: Some(Buff(( - kind: Frozen, - dur_secs: 10.0, - strength: DamageFraction(0.5), - chance: 1.0, - ))), ), buildup_duration: 0.9, swing_duration: 0.07, @@ -36,20 +30,11 @@ ComboMelee2( ), range: 2.5, angle: 30.0, - damage_effect: Some(Buff(( - kind: Frozen, - dur_secs: 10.0, - strength: DamageFraction(0.5), - chance: 1.0, - ))), ), buildup_duration: 0.8, swing_duration: 0.07, hit_timing: 0.5, recover_duration: 0.4, - movement: ( - swing: Some(Forward(0.0)), - ), ori_modifier: 0.7, ), ( @@ -62,12 +47,6 @@ ComboMelee2( ), range: 2.5, angle: 30.0, - damage_effect: Some(Buff(( - kind: Frozen, - dur_secs: 10.0, - strength: DamageFraction(0.5), - chance: 1.0, - ))), ), buildup_duration: 0.8, swing_duration: 0.07, @@ -80,4 +59,5 @@ ComboMelee2( ), ], energy_cost_per_strike: 0, + auto_progress: true, ) \ No newline at end of file diff --git a/assets/common/abilities/custom/icedrake/icebreath.ron b/assets/common/abilities/custom/icedrake/icebreath.ron index 925202541f..b4181ffab2 100644 --- a/assets/common/abilities/custom/icedrake/icebreath.ron +++ b/assets/common/abilities/custom/icedrake/icebreath.ron @@ -4,12 +4,12 @@ BasicBeam( beam_duration: 0.5, damage: 10.0, tick_rate: 3.0, - range: 25.0, + range: 15.0, max_angle: 22.5, damage_effect: Some(Buff(( kind: Frozen, dur_secs: 10.0, - strength: DamageFraction(0.25), + strength: Value(0.25), chance: 0.25, ))), energy_regen: 0, diff --git a/assets/common/abilities/custom/icedrake/icy_bite.ron b/assets/common/abilities/custom/icedrake/icy_bite.ron index eeed78c0b2..e04d5b0467 100644 --- a/assets/common/abilities/custom/icedrake/icy_bite.ron +++ b/assets/common/abilities/custom/icedrake/icy_bite.ron @@ -13,7 +13,7 @@ ComboMelee2( damage_effect: Some(Buff(( kind: Frozen, dur_secs: 10.0, - strength: DamageFraction(0.5), + strength: Value(0.5), chance: 1.0, ))), ), diff --git a/assets/common/abilities/custom/tursus/tusk_bash_leap.ron b/assets/common/abilities/custom/tursus/tusk_bash_leap.ron index 8e24f89553..0aaa8b8749 100644 --- a/assets/common/abilities/custom/tursus/tusk_bash_leap.ron +++ b/assets/common/abilities/custom/tursus/tusk_bash_leap.ron @@ -16,5 +16,5 @@ LeapMelee( multi_target: Some(Normal), ), forward_leap_strength: 20.0, - vertical_leap_strength: 8.0, + vertical_leap_strength: 10.0, ) diff --git a/common/src/states/charged_melee.rs b/common/src/states/charged_melee.rs index 74a8c61e5a..7f11212b0d 100644 --- a/common/src/states/charged_melee.rs +++ b/common/src/states/charged_melee.rs @@ -54,6 +54,17 @@ pub struct Data { pub charge_amount: f32, } +impl Data { + /// How complete the charge is, on a scale of 0.0 to 1.0 + pub fn charge_frac(&self) -> f32 { + if let StageSection::Charge = self.stage_section { + (self.timer.as_secs_f32() / self.static_data.charge_duration.as_secs_f32()).min(1.0) + } else { + 0.0 + } + } +} + impl CharacterBehavior for Data { fn behavior(&self, data: &JoinData, output_events: &mut OutputEvents) -> StateUpdate { let mut update = StateUpdate::from(data); diff --git a/server/agent/src/action_nodes.rs b/server/agent/src/action_nodes.rs index 1d55dac9d2..c96bfb32b1 100644 --- a/server/agent/src/action_nodes.rs +++ b/server/agent/src/action_nodes.rs @@ -14,6 +14,7 @@ use common::{ combat::perception_dist_multiplier_from_stealth, comp::{ self, + ability::MAX_ABILITIES, agent::{Sound, SoundKind, Target}, inventory::slot::EquipSlot, item::{ @@ -1018,6 +1019,16 @@ impl<'a> AgentData<'a> { "Adlet Icepicker" => Tactic::AdletIcepicker, "Adlet Tracker" => Tactic::AdletTracker, "Ice Drake" => Tactic::IceDrake, + "Frostfang" => Tactic::RandomAbilities { + primary: 1, + secondary: 3, + abilities: [0; MAX_ABILITIES], + }, + "Tursus Claws" => Tactic::RandomAbilities { + primary: 2, + secondary: 1, + abilities: [4, 0, 0, 0, 0], + }, _ => Tactic::SimpleMelee, }, AbilitySpec::Tool(tool_kind) => tool_tactic(*tool_kind), @@ -1473,6 +1484,21 @@ impl<'a> AgentData<'a> { Tactic::IceDrake => { self.handle_icedrake(agent, controller, &attack_data, tgt_data, read_data, rng) }, + Tactic::RandomAbilities { + primary, + secondary, + abilities, + } => self.handle_random_abilities( + agent, + controller, + &attack_data, + tgt_data, + read_data, + rng, + primary, + secondary, + abilities, + ), } } diff --git a/server/agent/src/attack.rs b/server/agent/src/attack.rs index ee9a85522c..8ebb4ec7f2 100644 --- a/server/agent/src/attack.rs +++ b/server/agent/src/attack.rs @@ -5,7 +5,7 @@ use crate::{ }; use common::{ comp::{ - ability::{ActiveAbilities, AuxiliaryAbility, Stance, SwordStance}, + ability::{ActiveAbilities, AuxiliaryAbility, Stance, SwordStance, MAX_ABILITIES}, buff::BuffKind, item::tool::AbilityContext, skills::{AxeSkill, BowSkill, HammerSkill, SceptreSkill, Skill, StaffSkill, SwordSkill}, @@ -4744,12 +4744,15 @@ impl<'a> AgentData<'a> { _ => false, }; - match self.char_state.ability_info().map(|ai| ai.input) { + let continued_attack = match self.char_state.ability_info().map(|ai| ai.input) { Some(input @ InputKind::Primary) => { if !matches!(self.char_state.stage_section(), Some(StageSection::Recover)) && could_use_input(input) { - controller.push_basic_input(input) + controller.push_basic_input(input); + true + } else { + false } }, Some(input @ InputKind::Ability(1)) => { @@ -4759,29 +4762,142 @@ impl<'a> AgentData<'a> { .map_or(false, |t| t.as_secs_f32() < 3.0) && could_use_input(input) { - controller.push_basic_input(input) + controller.push_basic_input(input); + true + } else { + false } }, - _ => {}, + _ => false, + }; + + let move_forwards = if !continued_attack { + if could_use_input(InputKind::Primary) && rng.gen_bool(0.4) { + controller.push_basic_input(InputKind::Primary); + false + } else if could_use_input(InputKind::Secondary) && rng.gen_bool(0.8) { + controller.push_basic_input(InputKind::Secondary); + false + } else if could_use_input(InputKind::Ability(1)) && rng.gen_bool(0.9) { + controller.push_basic_input(InputKind::Ability(1)); + true + } else if could_use_input(InputKind::Ability(0)) { + controller.push_basic_input(InputKind::Ability(0)); + true + } else { + true + } + } else { + false + }; + + if move_forwards { + self.path_toward_target( + agent, + controller, + tgt_data.pos.0, + read_data, + Path::Separate, + None, + ); + } + } + + pub fn handle_random_abilities( + &self, + agent: &mut Agent, + controller: &mut Controller, + attack_data: &AttackData, + tgt_data: &TargetData, + read_data: &ReadData, + rng: &mut impl Rng, + primary_weight: u8, + secondary_weight: u8, + ability_weights: [u8; MAX_ABILITIES], + ) { + 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| match input { + InputKind::Primary => primary.as_ref().map_or(false, |p| { + p.could_use(attack_data, self, tgt_data, read_data, 0.0) + }), + InputKind::Secondary => secondary.as_ref().map_or(false, |s| { + s.could_use(attack_data, self, tgt_data, read_data, 0.0) + }), + InputKind::Ability(x) => abilities[x].as_ref().map_or(false, |a| { + a.could_use(attack_data, self, tgt_data, read_data, 0.0) + }), + _ => false, + }; + + let primary_chance = primary_weight as f64 + / ((primary_weight + secondary_weight + ability_weights.iter().sum::()) as f64) + .max(0.01); + let secondary_chance = secondary_weight as f64 + / ((secondary_weight + ability_weights.iter().sum::()) as f64).max(0.01); + let ability_chances = { + let mut chances = [0.0; MAX_ABILITIES]; + chances.iter_mut().enumerate().for_each(|(i, chance)| { + *chance = ability_weights[i] as f64 + / (ability_weights + .iter() + .enumerate() + .filter_map(|(j, weight)| if j >= i { Some(weight) } else { None }) + .sum::() as f64) + .max(0.01) + }); + chances + }; + + if let Some(input) = self.char_state.ability_info().map(|ai| ai.input) { + match self.char_state { + CharacterState::ChargedMelee(c) => { + if c.charge_frac() < 1.0 && could_use_input(input) { + controller.push_basic_input(input); + } + }, + CharacterState::ChargedRanged(c) => { + if c.charge_frac() < 1.0 && could_use_input(input) { + controller.push_basic_input(input); + } + }, + _ => {}, + } } - let move_forwards = if could_use_input(InputKind::Primary) && rng.gen_bool(0.4) { + let move_forwards = if could_use_input(InputKind::Primary) && rng.gen_bool(primary_chance) { controller.push_basic_input(InputKind::Primary); false - } else if could_use_input(InputKind::Secondary) && rng.gen_bool(0.8) { + } else if could_use_input(InputKind::Secondary) && rng.gen_bool(secondary_chance) { controller.push_basic_input(InputKind::Secondary); false - } else if could_use_input(InputKind::Ability(1)) && rng.gen_bool(0.9) { - controller.push_basic_input(InputKind::Ability(1)); - true - } else if could_use_input(InputKind::Ability(0)) { + } else if could_use_input(InputKind::Ability(0)) && rng.gen_bool(ability_chances[0]) { controller.push_basic_input(InputKind::Ability(0)); - true + false + } else if could_use_input(InputKind::Ability(1)) && rng.gen_bool(ability_chances[1]) { + controller.push_basic_input(InputKind::Ability(1)); + false + } else if could_use_input(InputKind::Ability(2)) && rng.gen_bool(ability_chances[2]) { + controller.push_basic_input(InputKind::Ability(2)); + false + } else if could_use_input(InputKind::Ability(3)) && rng.gen_bool(ability_chances[3]) { + controller.push_basic_input(InputKind::Ability(3)); + false + } else if could_use_input(InputKind::Ability(4)) && rng.gen_bool(ability_chances[4]) { + controller.push_basic_input(InputKind::Ability(4)); + false } else { true }; - if move_forwards && attack_data.dist_sqrd > 3_f32.powi(2) { + if move_forwards { self.path_toward_target( agent, controller, diff --git a/server/agent/src/data.rs b/server/agent/src/data.rs index d1a6eccb93..c7bd0ecf1c 100644 --- a/server/agent/src/data.rs +++ b/server/agent/src/data.rs @@ -1,7 +1,7 @@ use crate::util::*; use common::{ comp::{ - ability::CharacterAbility, + ability::{CharacterAbility, MAX_ABILITIES}, buff::{BuffKind, Buffs}, character_state::AttackFilters, group, @@ -122,6 +122,12 @@ pub enum Tactic { FixedTurret, RotatingTurret, RadialTurret, + // u8s are weights that each ability gets used, if it can be used + RandomAbilities { + primary: u8, + secondary: u8, + abilities: [u8; MAX_ABILITIES], + }, // Tool specific tactics Axe, @@ -134,7 +140,10 @@ pub enum Tactic { SwordSimple, // Broad creature tactics - CircleCharge { radius: u32, circle_time: u32 }, + CircleCharge { + radius: u32, + circle_time: u32, + }, QuadLowRanged, TailSlap, QuadLowQuick, @@ -549,9 +558,9 @@ impl AbilityData { vertical, forward, .. } => { let dur = vertical * 2.0 / GRAVITY; - // 0.8 factor to allow for fact that agent looks down as they approach, so won't - // go as far - forward * dur * 0.8 + // 0.75 factor to allow for fact that agent looks down as they approach, so + // won't go as far + forward * dur * 0.75 }, _ => 0.0, }); diff --git a/server/agent/src/util.rs b/server/agent/src/util.rs index ec8bb8c3e1..e486ed2207 100644 --- a/server/agent/src/util.rs +++ b/server/agent/src/util.rs @@ -176,7 +176,7 @@ pub fn positions_have_line_of_sight(pos_a: &Pos, pos_b: &Pos, read_data: &ReadDa .cast() .0 .powi(2) - >= dist_sqrd + >= (dist_sqrd - 0.01) } pub fn is_dressed_as_cultist(entity: EcsEntity, read_data: &ReadData) -> bool {