Addressed review comments

This commit is contained in:
Sam 2022-10-23 13:10:37 -04:00
parent a44c36432e
commit b5682c4682
22 changed files with 246 additions and 256 deletions

View File

@ -2123,7 +2123,7 @@ impl From<(&CharacterAbility, AbilityInfo, &JoinData<'_>)> for CharacterState {
// If ability is a stance, enter the stance without beginning a strike, otherwise
// immediately begin the strike
stage_section: if *is_stance {
Some(StageSection::Charge)
Some(StageSection::Ready)
} else {
Some(StageSection::Buildup)
},
@ -2611,7 +2611,7 @@ bitflags::bitflags! {
const ROLL_INTERRUPT = 0b00000001;
// Allows blocking to interrupt the ability at any point
const BLOCK_INTERRUPT = 0b00000010;
// When the ability is in the buyildup section, it counts as a parry
// When the ability is in the buildup section, it counts as a parry
const BUILDUP_PARRIES = 0b00000100;
// When in the ability, an entity only receives half as much poise damage
const POISE_RESISTANT = 0b00001000;

View File

@ -647,6 +647,10 @@ impl CharacterState {
buildup: Some(strike.buildup_duration),
action: Some(strike.swing_duration),
recover: Some(strike.recover_duration),
ready: data
.static_data
.is_stance
.then_some(combo_melee2::STANCE_ENTER_TIME),
..Default::default()
})
},
@ -847,6 +851,7 @@ pub struct DurationsInfo {
pub recover: Option<Duration>,
pub movement: Option<Duration>,
pub charge: Option<Duration>,
pub ready: Option<Duration>,
}
impl Default for CharacterState {

View File

@ -337,8 +337,7 @@ pub enum AbilityContext {
}
impl AbilityContext {
// TODO: Come up with better name for function
pub fn yeet(char_state: Option<&CharacterState>) -> Option<Self> {
pub fn try_from(char_state: Option<&CharacterState>) -> Option<Self> {
if let Some(AbilityKind::Sword(stance)) = char_state
.and_then(|cs| cs.ability_info())
.and_then(|info| info.ability_meta)

View File

@ -27,6 +27,10 @@ pub struct Melee {
#[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)]
pub enum MultiTarget {
Normal,
/// Applies scaling to the power of the attack based on how many consecutive
/// enemies have been hit. First enemy hit will be at a power of 1.0, second
/// enemy hit will be at a power of `1.0 + scaling`, nth enemy hit will be
/// at a power of `1.0 + (n - 1) * scaling`.
Scaling(f32),
}

View File

@ -1,6 +1,6 @@
use crate::{
comp::{
character_state::OutputEvents, tool::ToolKind, CharacterState, Melee, MeleeConstructor,
character_state::OutputEvents, tool::ToolKind, CharacterState, MeleeConstructor,
StateUpdate,
},
states::{
@ -121,15 +121,13 @@ impl CharacterBehavior for Data {
if self.static_data.ability_info.input.map_or(false, |input| input_is_pressed(data, input)) {
reset_state(self, data, output_events, &mut update);
} else {
end_ability(data, &mut update);
end_melee_ability(data, &mut update);
}
}
},
_ => {
// If it somehow ends up in an incorrect stage section
end_ability(data, &mut update);
// Make sure attack component is removed
data.updater.remove::<Melee>(data.entity);
end_melee_ability(data, &mut update);
},
}

View File

@ -1,5 +1,5 @@
use crate::{
comp::{character_state::OutputEvents, CharacterState, Melee, MeleeConstructor, StateUpdate},
comp::{character_state::OutputEvents, CharacterState, MeleeConstructor, StateUpdate},
event::LocalEvent,
outcome::Outcome,
states::{
@ -156,16 +156,12 @@ impl CharacterBehavior for Data {
});
} else {
// Done
end_ability(data, &mut update);
// Make sure attack component is removed
data.updater.remove::<Melee>(data.entity);
end_melee_ability(data, &mut update);
}
},
_ => {
// If it somehow ends up in an incorrect stage section
end_ability(data, &mut update);
// Make sure attack component is removed
data.updater.remove::<Melee>(data.entity);
end_melee_ability(data, &mut update);
},
}

View File

@ -344,15 +344,13 @@ impl CharacterBehavior for Data {
if self.static_data.ability_info.input.map_or(false, |input| input_is_pressed(data, input)) {
reset_state(self, data, output_events, &mut update);
} else {
end_ability(data, &mut update);
end_melee_ability(data, &mut update);
}
}
},
_ => {
// If it somehow ends up in an incorrect stage section
end_ability(data, &mut update);
// Make sure attack component is removed
data.updater.remove::<Melee>(data.entity);
end_melee_ability(data, &mut update);
},
}

View File

@ -1,6 +1,6 @@
use crate::{
comp::{
character_state::OutputEvents, tool::Stats, CharacterState, InputKind, Melee,
character_state::OutputEvents, tool::Stats, CharacterState, InputKind,
MeleeConstructor, StateUpdate, InputAttr, InventoryAction, slot::{Slot, EquipSlot},
},
states::{
@ -132,7 +132,7 @@ impl CharacterBehavior for Data {
let strike_data = self.strike_data();
match self.stage_section {
Some(StageSection::Charge) => {
Some(StageSection::Ready) => {
// Adds a small duration to entering a stance to discourage spam swapping stances for ability activation benefits of matching stance
if self.timer < STANCE_ENTER_TIME {
if let CharacterState::ComboMelee2(c) = &mut update.character {
@ -225,17 +225,13 @@ impl CharacterBehavior for Data {
}
} else {
// Return to wielding
end_ability(data, &mut update);
// Make sure attack component is removed
data.updater.remove::<Melee>(data.entity);
end_melee_ability(data, &mut update);
}
}
},
Some(_) => {
// If it somehow ends up in an incorrect stage section
end_ability(data, &mut update);
// Make sure attack component is removed
data.updater.remove::<Melee>(data.entity);
end_melee_ability(data, &mut update);
},
None => {
if self.timer < STANCE_LEAVE_TIME {
@ -244,9 +240,7 @@ impl CharacterBehavior for Data {
}
} else {
// Done
end_ability(data, &mut update);
// Make sure melee component is removed
data.updater.remove::<Melee>(data.entity);
end_melee_ability(data, &mut update);
}
handle_climb(data, &mut update);
@ -273,7 +267,7 @@ impl CharacterBehavior for Data {
let mut update = StateUpdate::from(data);
if matches!(data.character, CharacterState::ComboMelee2(data) if data.static_data.ability_info.input == Some(input) && input != InputKind::Primary && data.stage_section.is_none()) {
end_ability(data, &mut update);
end_melee_ability(data, &mut update);
} else {
update.queued_inputs.insert(input, InputAttr {
select_pos,

View File

@ -247,16 +247,12 @@ impl CharacterBehavior for Data {
});
} else {
// Done
end_ability(data, &mut update);
// Make sure attack component is removed
data.updater.remove::<Melee>(data.entity);
end_melee_ability(data, &mut update);
}
},
_ => {
// If it somehow ends up in an incorrect stage section
end_ability(data, &mut update);
// Make sure attack component is removed
data.updater.remove::<Melee>(data.entity);
end_melee_ability(data, &mut update);
},
}

View File

@ -1,5 +1,5 @@
use crate::{
comp::{character_state::OutputEvents, CharacterState, Melee, MeleeConstructor, StateUpdate},
comp::{character_state::OutputEvents, CharacterState, MeleeConstructor, StateUpdate},
states::{
behavior::{CharacterBehavior, JoinData},
utils::{StageSection, *},
@ -112,16 +112,12 @@ impl CharacterBehavior for Data {
}
} else {
// Done
end_ability(data, &mut update);
// Make sure attack component is removed
data.updater.remove::<Melee>(data.entity);
end_melee_ability(data, &mut update);
}
},
_ => {
// If it somehow ends up in an incorrect stage section
end_ability(data, &mut update);
// Make sure attack component is removed
data.updater.remove::<Melee>(data.entity);
end_melee_ability(data, &mut update);
},
}

View File

@ -1,6 +1,6 @@
use crate::{
combat::{CombatBuff, CombatEffect},
comp::{character_state::OutputEvents, CharacterState, Melee, MeleeConstructor, StateUpdate},
comp::{character_state::OutputEvents, CharacterState, MeleeConstructor, StateUpdate},
event::ServerEvent,
states::{
behavior::{CharacterBehavior, JoinData},
@ -131,16 +131,12 @@ impl CharacterBehavior for Data {
}
} else {
// Done
end_ability(data, &mut update);
// Make sure attack component is removed
data.updater.remove::<Melee>(data.entity);
end_melee_ability(data, &mut update);
}
},
_ => {
// If it somehow ends up in an incorrect stage section
end_ability(data, &mut update);
// Make sure attack component is removed
data.updater.remove::<Melee>(data.entity);
end_melee_ability(data, &mut update);
},
}

View File

@ -1,5 +1,5 @@
use crate::{
comp::{character_state::OutputEvents, CharacterState, Melee, MeleeConstructor, StateUpdate},
comp::{character_state::OutputEvents, CharacterState, MeleeConstructor, StateUpdate},
states::{
behavior::{CharacterBehavior, JoinData},
utils::{StageSection, *},
@ -139,16 +139,12 @@ impl CharacterBehavior for Data {
});
} else {
// Done
end_ability(data, &mut update);
// Make sure attack component is removed
data.updater.remove::<Melee>(data.entity);
end_melee_ability(data, &mut update);
}
},
_ => {
// If it somehow ends up in an incorrect stage section
end_ability(data, &mut update);
// Make sure attack component is removed
data.updater.remove::<Melee>(data.entity);
end_melee_ability(data, &mut update);
},
}

View File

@ -1,5 +1,5 @@
use crate::{
comp::{character_state::OutputEvents, CharacterState, Melee, StateUpdate},
comp::{character_state::OutputEvents, CharacterState, StateUpdate},
states::{
behavior::{CharacterBehavior, JoinData},
utils::*,
@ -66,8 +66,6 @@ impl CharacterBehavior for Data {
_ => {
// If it somehow ends up in an incorrect stage section
end_ability(data, &mut update);
// Make sure attack component is removed
data.updater.remove::<Melee>(data.entity);
},
}

View File

@ -1,5 +1,5 @@
use crate::{
comp::{character_state::OutputEvents, CharacterState, Melee, MeleeConstructor, StateUpdate},
comp::{character_state::OutputEvents, CharacterState, MeleeConstructor, StateUpdate},
states::{
behavior::{CharacterBehavior, JoinData},
utils::*,
@ -113,16 +113,12 @@ impl CharacterBehavior for Data {
}
} else {
// Done
end_ability(data, &mut update);
// Make sure attack component is removed
data.updater.remove::<Melee>(data.entity);
end_melee_ability(data, &mut update);
}
},
_ => {
// If it somehow ends up in an incorrect stage section
end_ability(data, &mut update);
// Make sure attack component is removed
data.updater.remove::<Melee>(data.entity);
end_melee_ability(data, &mut update);
},
}

View File

@ -1,5 +1,5 @@
use crate::{
comp::{character_state::OutputEvents, CharacterState, Melee, MeleeConstructor, StateUpdate},
comp::{character_state::OutputEvents, CharacterState, MeleeConstructor, StateUpdate},
states::{
behavior::{CharacterBehavior, JoinData},
utils::*,
@ -94,16 +94,12 @@ impl CharacterBehavior for Data {
}
} else {
// Done
end_ability(data, &mut update);
// Make sure attack component is removed
data.updater.remove::<Melee>(data.entity);
end_melee_ability(data, &mut update);
}
},
_ => {
// If it somehow ends up in an incorrect stage section
end_ability(data, &mut update);
// Make sure attack component is removed
data.updater.remove::<Melee>(data.entity);
end_melee_ability(data, &mut update);
},
}

View File

@ -151,16 +151,12 @@ impl CharacterBehavior for Data {
});
} else {
// Done
end_ability(data, &mut update);
// Make sure attack component is removed
data.updater.remove::<Melee>(data.entity);
end_melee_ability(data, &mut update);
}
},
_ => {
// If it somehow ends up in an incorrect stage section
end_ability(data, &mut update);
// Make sure attack component is removed
data.updater.remove::<Melee>(data.entity);
end_melee_ability(data, &mut update);
},
}

View File

@ -10,7 +10,7 @@ use crate::{
quadruped_low, quadruped_medium, quadruped_small,
skills::{Skill, SwimSkill, SKILL_MODIFIERS},
theropod, Body, CharacterAbility, CharacterState, Density, InputAttr, InputKind,
InventoryAction, StateUpdate,
InventoryAction, StateUpdate, Melee,
},
consts::{FRIC_GROUND, GRAVITY, MAX_PICKUP_RANGE},
event::{LocalEvent, ServerEvent},
@ -975,7 +975,7 @@ pub fn handle_jump(
}
fn handle_ability(data: &JoinData<'_>, update: &mut StateUpdate, input: InputKind) -> bool {
let context = AbilityContext::yeet(Some(data.character));
let context = AbilityContext::try_from(Some(data.character));
if let Some(ability_input) = input.into() {
if let Some((ability, from_offhand)) = data
.active_abilities
@ -1259,6 +1259,7 @@ pub enum StageSection {
Charge,
Movement,
Action,
Ready,
}
#[derive(Clone, Copy, Debug, PartialEq, Serialize, Deserialize)]
@ -1387,6 +1388,11 @@ pub fn end_ability(data: &JoinData<'_>, update: &mut StateUpdate) {
}
}
pub fn end_melee_ability(data: &JoinData<'_>, update: &mut StateUpdate) {
end_ability(data, update);
data.updater.remove::<Melee>(data.entity);
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub enum HandInfo {
TwoHanded,

View File

@ -1,8 +1,4 @@
use crate::{
consts::MAX_PATH_DIST,
data::{AgentData, AttackData, Path, ReadData, TargetData},
util::entities_have_line_of_sight,
};
use crate::{consts::MAX_PATH_DIST, data::*, util::entities_have_line_of_sight};
use common::{
comp::{
ability::{self, Ability, AbilityKind, ActiveAbilities, AuxiliaryAbility, Capability},
@ -18,7 +14,7 @@ use common::{
util::Dir,
vol::ReadVol,
};
use rand::Rng;
use rand::{prelude::SliceRandom, Rng};
use specs::saveload::MarkerAllocator;
use std::{f32::consts::PI, time::Duration};
use vek::*;
@ -422,145 +418,16 @@ impl<'a> AgentData<'a> {
read_data: &ReadData,
rng: &mut impl Rng,
) {
struct ComboMeleeData {
min_range: f32,
max_range: f32,
angle: f32,
energy: f32,
}
impl ComboMeleeData {
fn could_use(&self, attack_data: &AttackData, agent_data: &AgentData) -> bool {
attack_data.dist_sqrd < self.max_range.powi(2)
&& attack_data.dist_sqrd > self.min_range.powi(2)
&& attack_data.angle < self.angle
&& agent_data.energy.current() >= self.energy
}
}
struct FinisherMeleeData {
range: f32,
angle: f32,
energy: f32,
combo: u32,
}
impl FinisherMeleeData {
fn could_use(&self, attack_data: &AttackData, agent_data: &AgentData) -> bool {
attack_data.dist_sqrd < self.range.powi(2)
&& attack_data.angle < self.angle
&& agent_data.energy.current() >= self.energy
&& agent_data
.combo
.map_or(false, |c| c.counter() >= self.combo)
}
fn use_desirable(&self, tgt_data: &TargetData, agent_data: &AgentData) -> bool {
let combo_factor =
agent_data.combo.map_or(0, |c| c.counter()) as f32 / self.combo as f32 * 2.0;
let tgt_health_factor = tgt_data.health.map_or(0.0, |h| h.current()) / 50.0;
let self_health_factor = agent_data.health.map_or(0.0, |h| h.current()) / 50.0;
// Use becomes more desirable if either self or target is close to death
combo_factor > tgt_health_factor.min(self_health_factor)
}
}
struct SelfBuffData {
buff: BuffKind,
energy: f32,
}
impl SelfBuffData {
fn could_use(&self, agent_data: &AgentData) -> bool {
agent_data.energy.current() >= self.energy
}
fn use_desirable(&self, agent_data: &AgentData) -> bool {
agent_data
.buffs
.map_or(false, |buffs| !buffs.contains(self.buff))
}
}
struct DiveMeleeData {
range: f32,
angle: f32,
energy: f32,
}
impl DiveMeleeData {
fn npc_should_use_hack(&self, agent_data: &AgentData, tgt_data: &TargetData) -> bool {
let dist_sqrd_2d = agent_data.pos.0.xy().distance_squared(tgt_data.pos.0.xy());
agent_data.energy.current() > self.energy
&& agent_data.physics_state.on_ground.is_some()
&& agent_data.pos.0.z >= tgt_data.pos.0.z
&& dist_sqrd_2d > (self.range / 2.0).powi(2)
&& dist_sqrd_2d < (self.range + 5.0).powi(2)
}
}
struct BlockData {
angle: f32,
// Should probably just always use 5 or so unless riposte melee
range: f32,
energy: f32,
}
impl BlockData {
fn could_use(&self, attack_data: &AttackData, agent_data: &AgentData) -> bool {
attack_data.dist_sqrd < self.range.powi(2)
&& attack_data.angle < self.angle
&& agent_data.energy.current() >= self.energy
}
}
struct DashMeleeData {
range: f32,
angle: f32,
initial_energy: f32,
energy_drain: f32,
speed: f32,
charge_dur: f32,
}
impl DashMeleeData {
// TODO: Maybe figure out better way of pulling in base accel from body and
// accounting for friction?
const BASE_SPEED: f32 = 3.0;
const ORI_RATE: f32 = 30.0;
fn could_use(&self, attack_data: &AttackData, agent_data: &AgentData) -> bool {
let charge_dur = self.charge_dur(agent_data);
let charge_dist = charge_dur * self.speed * Self::BASE_SPEED;
let attack_dist = charge_dist + self.range;
let ori_gap = Self::ORI_RATE * charge_dur;
attack_data.dist_sqrd < attack_dist.powi(2)
&& attack_data.angle < self.angle + ori_gap
&& agent_data.energy.current() > self.initial_energy
}
fn use_desirable(&self, attack_data: &AttackData, agent_data: &AgentData) -> bool {
let charge_dist = self.charge_dur(agent_data) * self.speed * Self::BASE_SPEED;
attack_data.dist_sqrd / charge_dist.powi(2) > 0.75_f32.powi(2)
}
fn charge_dur(&self, agent_data: &AgentData) -> f32 {
((agent_data.energy.current() - self.initial_energy) / self.energy_drain)
.clamp(0.0, self.charge_dur)
}
}
struct RapidMeleeData {
range: f32,
angle: f32,
energy: f32,
strikes: u32,
}
impl RapidMeleeData {
fn could_use(&self, attack_data: &AttackData, agent_data: &AgentData) -> bool {
attack_data.dist_sqrd < self.range.powi(2)
&& attack_data.angle < self.angle
&& agent_data.energy.current() > self.energy * self.strikes as f32
}
}
use ability::SwordStance;
let stance = |stance| match stance {
1 => SwordStance::Offensive,
2 => SwordStance::Defensive,
3 => SwordStance::Mobility,
4 => SwordStance::Crippling,
5 => SwordStance::Parrying,
6 => SwordStance::Heavy,
7 => SwordStance::Reaching,
8 => SwordStance::Cleaving,
5 => SwordStance::Cleaving,
6 => SwordStance::Parrying,
7 => SwordStance::Heavy,
8 => SwordStance::Reaching,
_ => SwordStance::Balanced,
};
if !agent.action_state.initialized {
@ -575,7 +442,9 @@ impl<'a> AgentData<'a> {
// Hack to make cleaving stance come up less often because agents cannot use it
// effectively due to only considering a single target
// Remove when agents properly consider multiple targets
4 + rng.gen_range(0..13) / 3
// (stance, weight)
let options = [(4, 3), (5, 1), (6, 3), (7, 3), (8, 3)];
options.choose_weighted(rng, |x| x.1).map_or(0, |x| x.0)
// rng.gen_range(4..9)
} else if self
.skill_set
@ -952,23 +821,32 @@ impl<'a> AgentData<'a> {
};
const DESIRED_ENERGY: f32 = 50.0;
let mut try_roll = || {
let mut try_roll = |controller: &mut Controller| {
if let Some(char_state) = tgt_data.char_state {
matches!(char_state.stage_section(), Some(StageSection::Buildup))
&& char_state.is_melee_attack()
&& char_state
.timer()
.map_or(false, |timer| rng.gen::<f32>() < timer.as_secs_f32() * 4.0)
&& matches!(
self.char_state.stage_section(),
Some(StageSection::Recover)
)
&& self
.char_state
.ability_info()
.and_then(|a| a.ability_meta)
.map(|m| m.capabilities)
.map_or(false, |c| c.contains(Capability::ROLL_INTERRUPT))
let try_roll =
matches!(char_state.stage_section(), Some(StageSection::Buildup))
&& char_state.is_melee_attack()
&& char_state.timer().map_or(false, |timer| {
rng.gen::<f32>() < timer.as_secs_f32() * 4.0
})
&& matches!(
self.char_state.stage_section(),
Some(StageSection::Recover)
)
&& self
.char_state
.ability_info()
.and_then(|a| a.ability_meta)
.map(|m| m.capabilities)
.map_or(false, |c| c.contains(Capability::ROLL_INTERRUPT));
if try_roll {
controller.inputs.move_dir = (tgt_data.pos.0 - self.pos.0)
.xy()
.try_normalized()
.unwrap_or_default();
controller.push_basic_input(InputKind::Roll);
}
try_roll
} else {
false
}
@ -984,12 +862,8 @@ impl<'a> AgentData<'a> {
controller.push_basic_input(InputKind::Ability(0));
} else if MOBILITY_AGILITY.could_use(self) && MOBILITY_AGILITY.use_desirable(self) {
controller.push_basic_input(InputKind::Ability(2));
} else if try_roll() {
controller.inputs.move_dir = (tgt_data.pos.0 - self.pos.0)
.xy()
.try_normalized()
.unwrap_or_default();
controller.push_basic_input(InputKind::Roll);
} else if try_roll(controller) {
// Nothing here, rolling handled in try_roll function
} else if MOBILITY_FEINT.could_use(attack_data, self) && rng.gen::<f32>() < 0.3 {
controller.push_basic_input(InputKind::Ability(1));
advance(
@ -1641,7 +1515,7 @@ impl<'a> AgentData<'a> {
enum ActionStateConditions {
ConditionStaffCanShockwave = 0,
}
let context = AbilityContext::yeet(Some(self.char_state));
let context = AbilityContext::try_from(Some(self.char_state));
let extract_ability = |input: AbilityInput| {
self.active_abilities
.activate_ability(

View File

@ -1,8 +1,10 @@
use common::{
comp::{
buff::Buffs, group, item::MaterialStatManifest, ActiveAbilities, Alignment, Body,
CharacterState, Combo, Energy, Health, Inventory, LightEmitter, LootOwner, Ori,
PhysicsState, Poise, Pos, Scale, SkillSet, Stats, Vel,
buff::{BuffKind, Buffs},
group,
item::MaterialStatManifest,
ActiveAbilities, Alignment, Body, CharacterState, Combo, Energy, Health, Inventory,
LightEmitter, LootOwner, Ori, PhysicsState, Poise, Pos, Scale, SkillSet, Stats, Vel,
},
link::Is,
mounting::Mount,
@ -180,3 +182,149 @@ pub enum Path {
Separate,
Partial,
}
pub struct ComboMeleeData {
pub min_range: f32,
pub max_range: f32,
pub angle: f32,
pub energy: f32,
}
impl ComboMeleeData {
pub fn could_use(&self, attack_data: &AttackData, agent_data: &AgentData) -> bool {
attack_data.dist_sqrd < self.max_range.powi(2)
&& attack_data.dist_sqrd > self.min_range.powi(2)
&& attack_data.angle < self.angle
&& agent_data.energy.current() >= self.energy
}
}
pub struct FinisherMeleeData {
pub range: f32,
pub angle: f32,
pub energy: f32,
pub combo: u32,
}
impl FinisherMeleeData {
pub fn could_use(&self, attack_data: &AttackData, agent_data: &AgentData) -> bool {
attack_data.dist_sqrd < self.range.powi(2)
&& attack_data.angle < self.angle
&& agent_data.energy.current() >= self.energy
&& agent_data
.combo
.map_or(false, |c| c.counter() >= self.combo)
}
pub fn use_desirable(&self, tgt_data: &TargetData, agent_data: &AgentData) -> bool {
let combo_factor =
agent_data.combo.map_or(0, |c| c.counter()) as f32 / self.combo as f32 * 2.0;
let tgt_health_factor = tgt_data.health.map_or(0.0, |h| h.current()) / 50.0;
let self_health_factor = agent_data.health.map_or(0.0, |h| h.current()) / 50.0;
// Use becomes more desirable if either self or target is close to death
combo_factor > tgt_health_factor.min(self_health_factor)
}
}
pub struct SelfBuffData {
pub buff: BuffKind,
pub energy: f32,
}
impl SelfBuffData {
pub fn could_use(&self, agent_data: &AgentData) -> bool {
agent_data.energy.current() >= self.energy
}
pub fn use_desirable(&self, agent_data: &AgentData) -> bool {
agent_data
.buffs
.map_or(false, |buffs| !buffs.contains(self.buff))
}
}
pub struct DiveMeleeData {
pub range: f32,
pub angle: f32,
pub energy: f32,
}
impl DiveMeleeData {
// Hack here refers to agents using the mildly unintended method of roll jumping
// to achieve the required downwards vertical speed to enter dive melee when on
// flat ground.
pub fn npc_should_use_hack(&self, agent_data: &AgentData, tgt_data: &TargetData) -> bool {
let dist_sqrd_2d = agent_data.pos.0.xy().distance_squared(tgt_data.pos.0.xy());
agent_data.energy.current() > self.energy
&& agent_data.physics_state.on_ground.is_some()
&& agent_data.pos.0.z >= tgt_data.pos.0.z
&& dist_sqrd_2d > (self.range / 2.0).powi(2)
&& dist_sqrd_2d < (self.range + 5.0).powi(2)
}
}
pub struct BlockData {
pub angle: f32,
// Should probably just always use 5 or so unless riposte melee
pub range: f32,
pub energy: f32,
}
impl BlockData {
pub fn could_use(&self, attack_data: &AttackData, agent_data: &AgentData) -> bool {
attack_data.dist_sqrd < self.range.powi(2)
&& attack_data.angle < self.angle
&& agent_data.energy.current() >= self.energy
}
}
pub struct DashMeleeData {
pub range: f32,
pub angle: f32,
pub initial_energy: f32,
pub energy_drain: f32,
pub speed: f32,
pub charge_dur: f32,
}
impl DashMeleeData {
// TODO: Maybe figure out better way of pulling in base accel from body and
// accounting for friction?
const BASE_SPEED: f32 = 3.0;
const ORI_RATE: f32 = 30.0;
pub fn could_use(&self, attack_data: &AttackData, agent_data: &AgentData) -> bool {
let charge_dur = self.charge_dur(agent_data);
let charge_dist = charge_dur * self.speed * Self::BASE_SPEED;
let attack_dist = charge_dist + self.range;
let ori_gap = Self::ORI_RATE * charge_dur;
attack_data.dist_sqrd < attack_dist.powi(2)
&& attack_data.angle < self.angle + ori_gap
&& agent_data.energy.current() > self.initial_energy
}
pub fn use_desirable(&self, attack_data: &AttackData, agent_data: &AgentData) -> bool {
let charge_dist = self.charge_dur(agent_data) * self.speed * Self::BASE_SPEED;
attack_data.dist_sqrd / charge_dist.powi(2) > 0.75_f32.powi(2)
}
fn charge_dur(&self, agent_data: &AgentData) -> f32 {
((agent_data.energy.current() - self.initial_energy) / self.energy_drain)
.clamp(0.0, self.charge_dur)
}
}
pub struct RapidMeleeData {
pub range: f32,
pub angle: f32,
pub energy: f32,
pub strikes: u32,
}
impl RapidMeleeData {
pub fn could_use(&self, attack_data: &AttackData, agent_data: &AgentData) -> bool {
attack_data.dist_sqrd < self.range.powi(2)
&& attack_data.angle < self.angle
&& agent_data.energy.current() > self.energy * self.strikes as f32
}
}

View File

@ -2885,7 +2885,6 @@ fn skill_strings(skill: Skill) -> SkillStrings<'static> {
Skill::General(s) => general_skill_strings(s),
Skill::UnlockGroup(s) => unlock_skill_strings(s),
// weapon trees
Skill::Sword(s) => sword_skill_strings(s),
Skill::Axe(s) => axe_skill_strings(s),
Skill::Hammer(s) => hammer_skill_strings(s),
Skill::Bow(s) => bow_skill_strings(s),
@ -2897,6 +2896,7 @@ fn skill_strings(skill: Skill) -> SkillStrings<'static> {
Skill::Swim(s) => swim_skill_strings(s),
// mining
Skill::Pick(s) => mining_skill_strings(s),
_ => SkillStrings::plain("", ""),
}
}
@ -2954,8 +2954,6 @@ fn unlock_skill_strings(group: SkillGroupKind) -> SkillStrings<'static> {
}
}
fn sword_skill_strings(_skill: SwordSkill) -> SkillStrings<'static> { SkillStrings::plain("", "") }
fn axe_skill_strings(skill: AxeSkill) -> SkillStrings<'static> {
let modifiers = SKILL_MODIFIERS.axe_tree;
match skill {

View File

@ -2912,7 +2912,7 @@ impl Hud {
skillsets.get(entity),
bodies.get(entity),
) {
let context = AbilityContext::yeet(char_states.get(entity));
let context = AbilityContext::try_from(char_states.get(entity));
Skillbar::new(
client,
&info,
@ -3343,7 +3343,7 @@ impl Hud {
bodies.get(entity),
poises.get(entity),
) {
let context = AbilityContext::yeet(char_states.get(entity));
let context = AbilityContext::try_from(char_states.get(entity));
for event in Diary::new(
&self.show,
client,

View File

@ -917,7 +917,7 @@ impl FigureMgr {
let second_tool_spec = second_tool_spec.as_deref();
let hands = (active_tool_hand, second_tool_hand);
let context = AbilityContext::yeet(character);
let context = AbilityContext::try_from(character);
let ability_id = character.and_then(|c| {
c.ability_info()