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((
kind: Frozen,
dur_secs: 10.0,
strength: DamageFraction(0.5),
strength: Value(0.5),
chance: 1.0,
))),
),

View File

@ -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,
)

View File

@ -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,

View File

@ -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,
))),
),

View File

@ -16,5 +16,5 @@ LeapMelee(
multi_target: Some(Normal),
),
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,
}
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);

View File

@ -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,
),
}
}

View File

@ -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::<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(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,

View File

@ -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,
});

View File

@ -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 {