Ice creature AIs

This commit is contained in:
Sam 2023-03-29 22:23:56 -04:00
parent c46ea4ae91
commit 71d7a3b780
10 changed files with 187 additions and 45 deletions

View File

@ -13,7 +13,7 @@ ComboMelee2(
damage_effect: Some(Buff(( damage_effect: Some(Buff((
kind: Frozen, kind: Frozen,
dur_secs: 10.0, dur_secs: 10.0,
strength: DamageFraction(0.5), strength: Value(0.5),
chance: 1.0, chance: 1.0,
))), ))),
), ),

View File

@ -10,12 +10,6 @@ ComboMelee2(
), ),
range: 2.5, range: 2.5,
angle: 30.0, angle: 30.0,
damage_effect: Some(Buff((
kind: Frozen,
dur_secs: 10.0,
strength: DamageFraction(0.5),
chance: 1.0,
))),
), ),
buildup_duration: 0.9, buildup_duration: 0.9,
swing_duration: 0.07, swing_duration: 0.07,
@ -36,20 +30,11 @@ ComboMelee2(
), ),
range: 2.5, range: 2.5,
angle: 30.0, angle: 30.0,
damage_effect: Some(Buff((
kind: Frozen,
dur_secs: 10.0,
strength: DamageFraction(0.5),
chance: 1.0,
))),
), ),
buildup_duration: 0.8, buildup_duration: 0.8,
swing_duration: 0.07, swing_duration: 0.07,
hit_timing: 0.5, hit_timing: 0.5,
recover_duration: 0.4, recover_duration: 0.4,
movement: (
swing: Some(Forward(0.0)),
),
ori_modifier: 0.7, ori_modifier: 0.7,
), ),
( (
@ -62,12 +47,6 @@ ComboMelee2(
), ),
range: 2.5, range: 2.5,
angle: 30.0, angle: 30.0,
damage_effect: Some(Buff((
kind: Frozen,
dur_secs: 10.0,
strength: DamageFraction(0.5),
chance: 1.0,
))),
), ),
buildup_duration: 0.8, buildup_duration: 0.8,
swing_duration: 0.07, swing_duration: 0.07,
@ -80,4 +59,5 @@ ComboMelee2(
), ),
], ],
energy_cost_per_strike: 0, energy_cost_per_strike: 0,
auto_progress: true,
) )

View File

@ -4,12 +4,12 @@ BasicBeam(
beam_duration: 0.5, beam_duration: 0.5,
damage: 10.0, damage: 10.0,
tick_rate: 3.0, tick_rate: 3.0,
range: 25.0, range: 15.0,
max_angle: 22.5, max_angle: 22.5,
damage_effect: Some(Buff(( damage_effect: Some(Buff((
kind: Frozen, kind: Frozen,
dur_secs: 10.0, dur_secs: 10.0,
strength: DamageFraction(0.25), strength: Value(0.25),
chance: 0.25, chance: 0.25,
))), ))),
energy_regen: 0, energy_regen: 0,

View File

@ -13,7 +13,7 @@ ComboMelee2(
damage_effect: Some(Buff(( damage_effect: Some(Buff((
kind: Frozen, kind: Frozen,
dur_secs: 10.0, dur_secs: 10.0,
strength: DamageFraction(0.5), strength: Value(0.5),
chance: 1.0, chance: 1.0,
))), ))),
), ),

View File

@ -16,5 +16,5 @@ LeapMelee(
multi_target: Some(Normal), multi_target: Some(Normal),
), ),
forward_leap_strength: 20.0, forward_leap_strength: 20.0,
vertical_leap_strength: 8.0, vertical_leap_strength: 10.0,
) )

View File

@ -54,6 +54,17 @@ pub struct Data {
pub charge_amount: f32, 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 { impl CharacterBehavior for Data {
fn behavior(&self, data: &JoinData, output_events: &mut OutputEvents) -> StateUpdate { fn behavior(&self, data: &JoinData, output_events: &mut OutputEvents) -> StateUpdate {
let mut update = StateUpdate::from(data); let mut update = StateUpdate::from(data);

View File

@ -14,6 +14,7 @@ use common::{
combat::perception_dist_multiplier_from_stealth, combat::perception_dist_multiplier_from_stealth,
comp::{ comp::{
self, self,
ability::MAX_ABILITIES,
agent::{Sound, SoundKind, Target}, agent::{Sound, SoundKind, Target},
inventory::slot::EquipSlot, inventory::slot::EquipSlot,
item::{ item::{
@ -1018,6 +1019,16 @@ impl<'a> AgentData<'a> {
"Adlet Icepicker" => Tactic::AdletIcepicker, "Adlet Icepicker" => Tactic::AdletIcepicker,
"Adlet Tracker" => Tactic::AdletTracker, "Adlet Tracker" => Tactic::AdletTracker,
"Ice Drake" => Tactic::IceDrake, "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, _ => Tactic::SimpleMelee,
}, },
AbilitySpec::Tool(tool_kind) => tool_tactic(*tool_kind), AbilitySpec::Tool(tool_kind) => tool_tactic(*tool_kind),
@ -1473,6 +1484,21 @@ impl<'a> AgentData<'a> {
Tactic::IceDrake => { Tactic::IceDrake => {
self.handle_icedrake(agent, controller, &attack_data, tgt_data, read_data, rng) 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,
),
} }
} }

View File

@ -5,7 +5,7 @@ use crate::{
}; };
use common::{ use common::{
comp::{ comp::{
ability::{ActiveAbilities, AuxiliaryAbility, Stance, SwordStance}, ability::{ActiveAbilities, AuxiliaryAbility, Stance, SwordStance, MAX_ABILITIES},
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},
@ -4744,12 +4744,15 @@ impl<'a> AgentData<'a> {
_ => false, _ => 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) => { Some(input @ InputKind::Primary) => {
if !matches!(self.char_state.stage_section(), Some(StageSection::Recover)) if !matches!(self.char_state.stage_section(), Some(StageSection::Recover))
&& could_use_input(input) && could_use_input(input)
{ {
controller.push_basic_input(input) controller.push_basic_input(input);
true
} else {
false
} }
}, },
Some(input @ InputKind::Ability(1)) => { Some(input @ InputKind::Ability(1)) => {
@ -4759,13 +4762,17 @@ impl<'a> AgentData<'a> {
.map_or(false, |t| t.as_secs_f32() < 3.0) .map_or(false, |t| t.as_secs_f32() < 3.0)
&& could_use_input(input) && could_use_input(input)
{ {
controller.push_basic_input(input) controller.push_basic_input(input);
true
} else {
false
} }
}, },
_ => {}, _ => false,
} };
let move_forwards = if could_use_input(InputKind::Primary) && rng.gen_bool(0.4) { let move_forwards = if !continued_attack {
if could_use_input(InputKind::Primary) && rng.gen_bool(0.4) {
controller.push_basic_input(InputKind::Primary); controller.push_basic_input(InputKind::Primary);
false false
} else if could_use_input(InputKind::Secondary) && rng.gen_bool(0.8) { } else if could_use_input(InputKind::Secondary) && rng.gen_bool(0.8) {
@ -4779,9 +4786,118 @@ impl<'a> AgentData<'a> {
true true
} else { } else {
true true
}
} else {
false
}; };
if move_forwards && attack_data.dist_sqrd > 3_f32.powi(2) { 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::<u8>()) as f64)
.max(0.01);
let secondary_chance = secondary_weight as f64
/ ((secondary_weight + ability_weights.iter().sum::<u8>()) 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::<u8>() 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(primary_chance) {
controller.push_basic_input(InputKind::Primary);
false
} 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(0)) && rng.gen_bool(ability_chances[0]) {
controller.push_basic_input(InputKind::Ability(0));
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 {
self.path_toward_target( self.path_toward_target(
agent, agent,
controller, controller,

View File

@ -1,7 +1,7 @@
use crate::util::*; use crate::util::*;
use common::{ use common::{
comp::{ comp::{
ability::CharacterAbility, ability::{CharacterAbility, MAX_ABILITIES},
buff::{BuffKind, Buffs}, buff::{BuffKind, Buffs},
character_state::AttackFilters, character_state::AttackFilters,
group, group,
@ -122,6 +122,12 @@ pub enum Tactic {
FixedTurret, FixedTurret,
RotatingTurret, RotatingTurret,
RadialTurret, 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 // Tool specific tactics
Axe, Axe,
@ -134,7 +140,10 @@ pub enum Tactic {
SwordSimple, SwordSimple,
// Broad creature tactics // Broad creature tactics
CircleCharge { radius: u32, circle_time: u32 }, CircleCharge {
radius: u32,
circle_time: u32,
},
QuadLowRanged, QuadLowRanged,
TailSlap, TailSlap,
QuadLowQuick, QuadLowQuick,
@ -549,9 +558,9 @@ impl AbilityData {
vertical, forward, .. vertical, forward, ..
} => { } => {
let dur = vertical * 2.0 / GRAVITY; let dur = vertical * 2.0 / GRAVITY;
// 0.8 factor to allow for fact that agent looks down as they approach, so won't // 0.75 factor to allow for fact that agent looks down as they approach, so
// go as far // won't go as far
forward * dur * 0.8 forward * dur * 0.75
}, },
_ => 0.0, _ => 0.0,
}); });

View File

@ -176,7 +176,7 @@ pub fn positions_have_line_of_sight(pos_a: &Pos, pos_b: &Pos, read_data: &ReadDa
.cast() .cast()
.0 .0
.powi(2) .powi(2)
>= dist_sqrd >= (dist_sqrd - 0.01)
} }
pub fn is_dressed_as_cultist(entity: EcsEntity, read_data: &ReadData) -> bool { pub fn is_dressed_as_cultist(entity: EcsEntity, read_data: &ReadData) -> bool {