mirror of
https://gitlab.com/veloren/veloren.git
synced 2024-08-30 18:12:32 +00:00
Ice creature AIs
This commit is contained in:
parent
c46ea4ae91
commit
71d7a3b780
@ -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,
|
||||||
))),
|
))),
|
||||||
),
|
),
|
||||||
|
@ -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,
|
||||||
)
|
)
|
@ -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,
|
||||||
|
@ -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,
|
||||||
))),
|
))),
|
||||||
),
|
),
|
||||||
|
@ -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,
|
||||||
)
|
)
|
||||||
|
@ -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);
|
||||||
|
@ -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,
|
||||||
|
),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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,29 +4762,142 @@ 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 !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);
|
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(secondary_chance) {
|
||||||
controller.push_basic_input(InputKind::Secondary);
|
controller.push_basic_input(InputKind::Secondary);
|
||||||
false
|
false
|
||||||
} else if could_use_input(InputKind::Ability(1)) && rng.gen_bool(0.9) {
|
} else if could_use_input(InputKind::Ability(0)) && rng.gen_bool(ability_chances[0]) {
|
||||||
controller.push_basic_input(InputKind::Ability(1));
|
|
||||||
true
|
|
||||||
} else if could_use_input(InputKind::Ability(0)) {
|
|
||||||
controller.push_basic_input(InputKind::Ability(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 {
|
} else {
|
||||||
true
|
true
|
||||||
};
|
};
|
||||||
|
|
||||||
if move_forwards && attack_data.dist_sqrd > 3_f32.powi(2) {
|
if move_forwards {
|
||||||
self.path_toward_target(
|
self.path_toward_target(
|
||||||
agent,
|
agent,
|
||||||
controller,
|
controller,
|
||||||
|
@ -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,
|
||||||
});
|
});
|
||||||
|
@ -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 {
|
||||||
|
Loading…
Reference in New Issue
Block a user