Updates from review

This commit is contained in:
danielkenji83 2024-02-04 01:32:27 -03:00
parent 5c401215cf
commit 81cfb26059
13 changed files with 260 additions and 123 deletions

View File

@ -2,7 +2,7 @@ BasicBlock(
buildup_duration: 0.25,
recover_duration: 0.2,
max_angle: 60.0,
block_strength: 0.5,
block_strength: 1.0,
parry_window: (
buildup: true,
recover: false,
@ -10,6 +10,7 @@ BasicBlock(
energy_cost: 5,
energy_regen: 2.5,
can_hold: true,
can_use_block_priority: true,
blocked_attacks: (
melee: true,
projectiles: false,

View File

@ -2,7 +2,7 @@ BasicBlock(
buildup_duration: 0.25,
recover_duration: 0.2,
max_angle: 60.0,
block_strength: 0.5,
block_strength: 1.0,
parry_window: (
buildup: true,
recover: false,
@ -10,6 +10,7 @@ BasicBlock(
energy_cost: 5,
energy_regen: 2.5,
can_hold: true,
can_use_block_priority: true,
blocked_attacks: (
melee: true,
projectiles: false,

View File

@ -2,6 +2,7 @@ BasicBlock(
buildup_duration: 0.25,
recover_duration: 0.25,
max_angle: 90.0,
block_strength: 1.0,
parry_window: (
buildup: false,
recover: false,
@ -9,6 +10,7 @@ BasicBlock(
energy_cost: 0,
energy_regen: 0,
can_hold: true,
can_use_block_priority: true,
blocked_attacks: (
melee: true,
projectiles: true,

View File

@ -2,7 +2,7 @@ BasicBlock(
buildup_duration: 0.45,
recover_duration: 0.2,
max_angle: 90.0,
block_strength: 0.8,
block_strength: 1.0,
parry_window: (
buildup: true,
recover: false,
@ -10,6 +10,7 @@ BasicBlock(
energy_cost: 5.0,
energy_regen: 5.0,
can_hold: true,
can_use_block_priority: true,
blocked_attacks: (
melee: true,
projectiles: true,

View File

@ -2,7 +2,7 @@ BasicBlock(
buildup_duration: 0.25,
recover_duration: 0.2,
max_angle: 60.0,
block_strength: 0.5,
block_strength: 1.0,
parry_window: (
buildup: true,
recover: false,
@ -10,6 +10,7 @@ BasicBlock(
energy_cost: 5,
energy_regen: 2.5,
can_hold: true,
can_use_block_priority: true,
blocked_attacks: (
melee: true,
projectiles: false,

View File

@ -2,6 +2,7 @@ BasicBlock(
buildup_duration: 0.4,
recover_duration: 0.2,
max_angle: 45.0,
block_strength: 1.0,
parry_window: (
buildup: true,
recover: false,
@ -9,6 +10,7 @@ BasicBlock(
energy_cost: 2.5,
energy_regen: 17.5,
can_hold: false,
can_use_block_priority: false,
blocked_attacks: (
melee: false,
projectiles: true,

View File

@ -2,7 +2,7 @@ BasicBlock(
buildup_duration: 0.4,
recover_duration: 0.15,
max_angle: 60.0,
block_strength: 0.75,
block_strength: 1.1,
parry_window: (
buildup: true,
recover: false,
@ -10,6 +10,7 @@ BasicBlock(
energy_cost: 2.5,
energy_regen: 17.5,
can_hold: true,
can_use_block_priority: true,
blocked_attacks: (
melee: true,
projectiles: false,

View File

@ -20,7 +20,7 @@ use crate::{
},
outcome::Outcome,
resources::{Secs, Time},
states::utils::{HandInfo, StageSection},
states::utils::StageSection,
uid::{IdMaps, Uid},
util::Dir,
};
@ -58,6 +58,8 @@ pub const MAX_HEADSHOT_PRECISION: f32 = 1.0;
pub const MAX_TOP_HEADSHOT_PRECISION: f32 = 0.5;
pub const MAX_BEAM_DUR_PRECISION: f32 = 0.25;
pub const MAX_MELEE_POISE_PRECISION: f32 = 0.5;
pub const MAX_BLOCK_POISE_COST: f32 = 25.0;
pub const PARRY_BONUS_MULTIPLIER: f32 = 2.0;
#[derive(Copy, Clone)]
pub struct AttackerInfo<'a> {
@ -140,7 +142,7 @@ impl Attack {
pub fn effects(&self) -> impl Iterator<Item = &AttackEffect> { self.effects.iter() }
pub fn compute_block_damage_reduction(
pub fn compute_block_damage_decrement(
attacker: Option<&AttackerInfo>,
target: &TargetInfo,
source: AttackSource,
@ -152,27 +154,24 @@ impl Attack {
mut emit_outcome: impl FnMut(Outcome),
) -> f32 {
if damage.value > 0.0 {
let poise_reduction = 1.0
- (Poise::compute_poise_damage_reduction(
target.inventory,
msm,
target.char_state,
target.stats,
));
if let (Some(char_state), Some(ori), Some(inventory), Some(stats)) = (
target.char_state,
target.ori,
target.inventory,
target.stats,
) {
if ori.look_vec().angle_between(-dir.with_z(0.0)) < char_state.block_angle() {
let poise = stats.original_body.base_poise() as f32 / 4.0;
if let (Some(char_state), Some(ori), Some(inventory)) =
(target.char_state, target.ori, target.inventory)
{
let block_strength = block_strength(inventory, char_state);
if ori.look_vec().angle_between(-dir.with_z(0.0)) < char_state.block_angle()
&& block_strength > 0.0
{
if char_state.is_parry(source) {
let block_strength =
f32::max(1.0, block_strength(inventory, char_state) * 2.0);
let poise_cost =
(damage.value.min(block_strength) * poise * poise_reduction)
/ block_strength;
let final_block_strength = block_strength * PARRY_BONUS_MULTIPLIER;
let poise_change = Poise::apply_poise_reduction(
(damage.value.min(final_block_strength) * MAX_BLOCK_POISE_COST)
/ final_block_strength,
target.inventory,
msm,
target.char_state,
target.stats,
);
emit_outcome(Outcome::Block {
parry: true,
pos: target.pos,
@ -186,19 +185,24 @@ impl Attack {
emit(ServerEvent::PoiseChange {
entity: target.entity,
change: PoiseChange {
amount: -poise_cost,
amount: -poise_change,
impulse: *dir,
by: attacker.map(|x| (*x).into()),
cause: Some(damage.source),
time,
},
});
damage.value.min(block_strength)
damage.value.min(final_block_strength)
} else if char_state.is_block(source) {
let block_strength = f32::max(1.0, block_strength(inventory, char_state));
let poise_cost =
(damage.value.min(block_strength) * poise * poise_reduction)
/ block_strength;
let final_block_strength = block_strength;
let poise_change = Poise::apply_poise_reduction(
(damage.value.min(final_block_strength) * MAX_BLOCK_POISE_COST)
/ final_block_strength,
target.inventory,
msm,
target.char_state,
target.stats,
);
emit_outcome(Outcome::Block {
parry: false,
pos: target.pos,
@ -207,29 +211,7 @@ impl Attack {
emit(ServerEvent::PoiseChange {
entity: target.entity,
change: PoiseChange {
amount: -poise_cost,
impulse: *dir,
by: attacker.map(|x| (*x).into()),
cause: Some(damage.source),
time,
},
});
damage.value.min(block_strength)
} else if char_state.is_half_block(source) {
let block_strength =
f32::max(1.0, block_strength(inventory, char_state) / 2.0);
let poise_cost =
(damage.value.min(block_strength) * poise * poise_reduction)
/ block_strength;
emit_outcome(Outcome::Block {
parry: false,
pos: target.pos,
uid: target.uid,
});
emit(ServerEvent::PoiseChange {
entity: target.entity,
change: PoiseChange {
amount: -poise_cost,
amount: -poise_change,
impulse: *dir,
by: attacker.map(|x| (*x).into()),
cause: Some(damage.source),
@ -251,7 +233,7 @@ impl Attack {
}
}
pub fn compute_armor_damage_reduction(
pub fn compute_damage_reduction(
attacker: Option<&AttackerInfo>,
target: &TargetInfo,
damage: Damage,
@ -337,14 +319,10 @@ impl Attack {
let damage_instance = damage.instance + damage_instance_offset;
is_applied = true;
let armor_damage_reduction = Attack::compute_armor_damage_reduction(
attacker.as_ref(),
target,
damage.damage,
msm,
);
let damage_reduction =
Attack::compute_damage_reduction(attacker.as_ref(), target, damage.damage, msm);
let block_damage_reduction = Attack::compute_block_damage_reduction(
let block_damage_decrement = Attack::compute_block_damage_decrement(
attacker.as_ref(),
target,
attack_source,
@ -357,8 +335,8 @@ impl Attack {
);
let change = damage.damage.calculate_health_change(
armor_damage_reduction,
block_damage_reduction,
damage_reduction,
block_damage_decrement,
attacker.map(|x| x.into()),
precision_mult,
self.precision_multiplier,
@ -408,8 +386,8 @@ impl Attack {
// of damage that was reduced by target's protection
// Damage reduction should never equal 1 here as otherwise the check above
// that health change amount is greater than 0 would fail.
let reduced_damage = applied_damage * armor_damage_reduction
/ (1.0 - armor_damage_reduction);
let reduced_damage =
applied_damage * damage_reduction / (1.0 - damage_reduction);
let poise = reduced_damage
* CRUSHING_POISE_FRACTION
* attacker
@ -1127,8 +1105,8 @@ impl Damage {
pub fn calculate_health_change(
self,
armor_damage_reduction: f32,
block_damage_reduction: f32,
damage_reduction: f32,
block_damage_decrement: f32,
damage_contributor: Option<DamageContributor>,
precision_mult: Option<f32>,
precision_power: f32,
@ -1147,9 +1125,9 @@ impl Damage {
// Precise hit
damage += precise_damage;
// Block
damage -= block_damage_reduction;
damage -= block_damage_decrement;
// Armor
damage *= 1.0 - armor_damage_reduction;
damage *= 1.0 - damage_reduction;
HealthChange {
amount: -damage,
@ -1162,7 +1140,7 @@ impl Damage {
},
DamageSource::Falling => {
// Armor
if (armor_damage_reduction - 1.0).abs() < f32::EPSILON {
if (damage_reduction - 1.0).abs() < f32::EPSILON {
damage = 0.0;
}
HealthChange {
@ -1586,23 +1564,22 @@ pub fn precision_mult_from_flank(attack_dir: Vec3<f32>, target_ori: Option<&Ori>
}
pub fn block_strength(inventory: &Inventory, char_state: &CharacterState) -> f32 {
fn get_equip_slot(hand: Option<HandInfo>) -> EquipSlot {
match hand {
Some(HandInfo::OffHand) => EquipSlot::ActiveOffhand,
_ => EquipSlot::ActiveMainhand,
let equip_slot = if let CharacterState::BasicBlock(data) = char_state {
if data.static_data.can_use_block_priority {
get_block_equip_slot_by_priority(Some(inventory))
} else {
get_block_equip_slot_by_tool(Some(inventory), data.static_data.ability_info.tool)
}
}
let equip_slot = match char_state {
CharacterState::BasicBlock(data) => get_equip_slot(data.static_data.ability_info.hand),
CharacterState::RiposteMelee(data) => get_equip_slot(data.static_data.ability_info.hand),
CharacterState::ComboMelee2(data) => get_equip_slot(data.static_data.ability_info.hand),
_ => EquipSlot::ActiveMainhand,
} else {
get_block_equip_slot_by_tool(
Some(inventory),
char_state.ability_info().and_then(|t| t.tool),
)
};
inventory
.equipped(equip_slot)
.map_or(0.0, |item| match &*item.kind() {
.map(|item| match &*item.kind() {
ItemKind::Tool(tool) => {
tool.stats(item.stats_durability_multiplier())
.block_strength
@ -1610,17 +1587,103 @@ pub fn block_strength(inventory: &Inventory, char_state: &CharacterState) -> f32
},
_ => 0.0,
})
.map_or(0.0, |weapon_block_strength| match char_state {
CharacterState::BasicBlock(data) => {
weapon_block_strength * data.static_data.block_strength
},
CharacterState::ComboMelee2(data) => {
let capabilities = data.static_data.ability_info.ability_meta.capabilities;
if capabilities.contains(Capability::PARRIES)
|| capabilities.contains(Capability::PARRIES_MELEE)
{
return weapon_block_strength;
}
if capabilities.contains(Capability::BLOCKS) {
return weapon_block_strength * 0.5;
}
0.0
},
CharacterState::RiposteMelee(_) => weapon_block_strength,
CharacterState::Idle(_)
| CharacterState::Climb(_)
| CharacterState::Sit
| CharacterState::Dance
| CharacterState::Talk
| CharacterState::Glide(_)
| CharacterState::GlideWield(_)
| CharacterState::Stunned(_)
| CharacterState::Equipping(_)
| CharacterState::Wielding(_)
| CharacterState::Roll(_)
| CharacterState::BasicMelee(_)
| CharacterState::BasicRanged(_)
| CharacterState::Boost(_)
| CharacterState::DashMelee(_)
| CharacterState::ComboMeleeDeprecated(_)
| CharacterState::LeapMelee(_)
| CharacterState::LeapShockwave(_)
| CharacterState::ChargedRanged(_)
| CharacterState::ChargedMelee(_)
| CharacterState::RepeaterRanged(_)
| CharacterState::Shockwave(_)
| CharacterState::BasicBeam(_)
| CharacterState::BasicAura(_)
| CharacterState::Blink(_)
| CharacterState::BasicSummon(_)
| CharacterState::SelfBuff(_)
| CharacterState::SpriteSummon(_)
| CharacterState::UseItem(_)
| CharacterState::SpriteInteract(_)
| CharacterState::Wallrun(_)
| CharacterState::Skate(_)
| CharacterState::Music(_)
| CharacterState::FinisherMelee(_)
| CharacterState::DiveMelee(_)
| CharacterState::RapidMelee(_) => 0.0,
})
}
pub fn get_block_equip_slot(inventory: Option<&Inventory>) -> EquipSlot {
pub fn get_block_equip_slot_by_priority(inventory: Option<&Inventory>) -> EquipSlot {
inventory
.and_then(|inv| inv.equipped(EquipSlot::ActiveOffhand))
.and_then(|item| match &*item.kind() {
ItemKind::Tool(tool) => Some(tool.kind),
_ => None,
})
.map_or(EquipSlot::ActiveMainhand, |item| match item {
ToolKind::Shield => EquipSlot::ActiveOffhand,
_ => EquipSlot::ActiveMainhand,
})
.map(get_weapon_kinds)
.map_or(
EquipSlot::ActiveOffhand,
|weapon_kinds| match weapon_kinds {
(Some(mainhand), Some(offhand)) => {
if mainhand.block_priority() >= offhand.block_priority() {
EquipSlot::ActiveMainhand
} else {
EquipSlot::ActiveOffhand
}
},
(Some(_), None) => EquipSlot::ActiveMainhand,
(None, Some(_)) => EquipSlot::ActiveOffhand,
(None, None) => EquipSlot::ActiveOffhand,
},
)
}
pub fn get_block_equip_slot_by_tool(
inventory: Option<&Inventory>,
tool_kind: Option<ToolKind>,
) -> EquipSlot {
inventory
.map(get_weapon_kinds)
.map_or(
EquipSlot::ActiveOffhand,
|weapon_kinds| match weapon_kinds {
(mainhand, Some(_)) => {
if mainhand == tool_kind {
EquipSlot::ActiveMainhand
} else {
EquipSlot::ActiveOffhand
}
},
(Some(_), None) => EquipSlot::ActiveMainhand,
(None, None) => EquipSlot::ActiveOffhand,
},
)
}

View File

@ -196,7 +196,7 @@ impl ActiveAbilities {
match ability {
Ability::ToolGuard => {
let equip_slot = combat::get_block_equip_slot(inv);
let equip_slot = combat::get_block_equip_slot_by_priority(inv);
ability_set(equip_slot)
.and_then(|abilities| {
abilities
@ -205,7 +205,7 @@ impl ActiveAbilities {
})
.map(|(ability, i)| (scale_ability(ability, equip_slot), true, spec_ability(i)))
.or_else(|| {
ability_set(EquipSlot::ActiveOffhand)
ability_set(EquipSlot::ActiveMainhand)
.and_then(|abilities| {
abilities
.guard(Some(skill_set), context)
@ -213,7 +213,7 @@ impl ActiveAbilities {
})
.map(|(ability, i)| {
(
scale_ability(ability, EquipSlot::ActiveOffhand),
scale_ability(ability, EquipSlot::ActiveMainhand),
false,
spec_ability(i),
)
@ -393,7 +393,7 @@ impl Ability {
};
match self {
Ability::ToolGuard => ability_set(combat::get_block_equip_slot(inv))
Ability::ToolGuard => ability_set(combat::get_block_equip_slot_by_priority(inv))
.and_then(|abilities| {
abilities
.guard(skillset, context)
@ -406,7 +406,7 @@ impl Ability {
})
})
.or_else(|| {
ability_set(EquipSlot::ActiveOffhand).and_then(|abilities| {
ability_set(EquipSlot::ActiveMainhand).and_then(|abilities| {
abilities
.guard(skillset, context)
.map(|a| a.0.id.as_str())
@ -521,10 +521,10 @@ impl SpecifiedAbility {
ability_set(EquipSlot::ActiveMainhand)
.map(|abilities| ability_id(self, &abilities.secondary))
}),
Ability::ToolGuard => ability_set(combat::get_block_equip_slot(inv))
Ability::ToolGuard => ability_set(combat::get_block_equip_slot_by_priority(inv))
.and_then(|abilities| abilities.guard.as_ref().map(|a| ability_id(self, a)))
.or_else(|| {
ability_set(EquipSlot::ActiveOffhand)
ability_set(EquipSlot::ActiveMainhand)
.and_then(|abilities| abilities.guard.as_ref().map(|a| ability_id(self, a)))
}),
Ability::SpeciesMovement => None, // TODO: Make not None
@ -756,6 +756,7 @@ pub enum CharacterAbility {
energy_cost: f32,
energy_regen: f32,
can_hold: bool,
can_use_block_priority: bool,
blocked_attacks: AttackFilters,
#[serde(default)]
meta: AbilityMeta,
@ -1282,6 +1283,7 @@ impl CharacterAbility {
ref mut energy_cost,
energy_regen: _,
can_hold: _,
can_use_block_priority: _,
blocked_attacks: _,
meta: _,
} => {
@ -2346,6 +2348,7 @@ impl From<(&CharacterAbility, AbilityInfo, &JoinData<'_>)> for CharacterState {
energy_cost,
energy_regen,
can_hold,
can_use_block_priority,
blocked_attacks,
meta: _,
} => CharacterState::BasicBlock(basic_block::Data {
@ -2358,6 +2361,7 @@ impl From<(&CharacterAbility, AbilityInfo, &JoinData<'_>)> for CharacterState {
energy_cost: *energy_cost,
energy_regen: *energy_regen,
can_hold: *can_hold,
can_use_block_priority: *can_use_block_priority,
blocked_attacks: *blocked_attacks,
ability_info,
},
@ -3000,6 +3004,8 @@ bitflags::bitflags! {
#[derive(Copy, Clone, Debug, PartialEq, Eq, Default, Serialize, Deserialize)]
// If more are ever needed, first check if any not used anymore, as some were only used in intermediary stages so may be free
pub struct Capability: u8 {
// The ability will parry all blockable attacks in the buildup portion
const PARRIES = 0b01000000;
// There used to be a capability here, to keep ordering the same below this is now a placeholder
const PLACEHOLDER = 0b00000001;
// Allows blocking to interrupt the ability at any point
@ -3012,8 +3018,6 @@ bitflags::bitflags! {
const KNOCKBACK_RESISTANT = 0b00010000;
// The ability will parry melee attacks in the buildup portion
const PARRIES_MELEE = 0b00100000;
// The ability will parry all blockable attacks in the buildup portion
const PARRIES = 0b01000000;
}
}

View File

@ -352,25 +352,62 @@ impl CharacterState {
match self {
CharacterState::BasicBlock(data) => {
data.static_data.blocked_attacks.applies(attack_source)
&& matches!(
self.stage_section(),
Some(StageSection::Buildup | StageSection::Action),
)
},
_ => false,
CharacterState::ComboMelee2(data) => {
data.static_data
.ability_info
.ability_meta
.capabilities
.contains(Capability::BLOCKS)
&& matches!(
self.stage_section(),
Some(StageSection::Buildup | StageSection::Action),
)
},
CharacterState::Idle(_)
| CharacterState::Climb(_)
| CharacterState::Sit
| CharacterState::Dance
| CharacterState::Talk
| CharacterState::Glide(_)
| CharacterState::GlideWield(_)
| CharacterState::Stunned(_)
| CharacterState::Equipping(_)
| CharacterState::Wielding(_)
| CharacterState::Roll(_)
| CharacterState::BasicMelee(_)
| CharacterState::BasicRanged(_)
| CharacterState::Boost(_)
| CharacterState::DashMelee(_)
| CharacterState::ComboMeleeDeprecated(_)
| CharacterState::LeapMelee(_)
| CharacterState::LeapShockwave(_)
| CharacterState::ChargedRanged(_)
| CharacterState::ChargedMelee(_)
| CharacterState::RepeaterRanged(_)
| CharacterState::Shockwave(_)
| CharacterState::BasicBeam(_)
| CharacterState::BasicAura(_)
| CharacterState::Blink(_)
| CharacterState::BasicSummon(_)
| CharacterState::SelfBuff(_)
| CharacterState::SpriteSummon(_)
| CharacterState::UseItem(_)
| CharacterState::SpriteInteract(_)
| CharacterState::Wallrun(_)
| CharacterState::Skate(_)
| CharacterState::Music(_)
| CharacterState::FinisherMelee(_)
| CharacterState::DiveMelee(_)
| CharacterState::RiposteMelee(_)
| CharacterState::RapidMelee(_) => false,
}
}
pub fn is_half_block(&self, attack_source: AttackSource) -> bool {
matches!(attack_source, AttackSource::Melee)
&& self
.ability_info()
.map(|a| a.ability_meta.capabilities)
.map_or(false, |c| {
c.contains(Capability::BLOCKS)
&& matches!(
self.stage_section(),
Some(StageSection::Buildup | StageSection::Action)
)
})
}
/// In radians
pub fn block_angle(&self) -> f32 {
match self {

View File

@ -97,6 +97,28 @@ impl ToolKind {
| ToolKind::Dagger
)
}
pub fn block_priority(&self) -> i32 {
match self {
ToolKind::Debug => 0,
ToolKind::Blowgun => 1,
ToolKind::Bow => 2,
ToolKind::Staff => 3,
ToolKind::Sceptre => 4,
ToolKind::Empty => 5,
ToolKind::Natural => 6,
ToolKind::Instrument => 7,
ToolKind::Farming => 8,
ToolKind::Shovel => 9,
ToolKind::Pick => 10,
ToolKind::Dagger => 11,
ToolKind::Spear => 12,
ToolKind::Hammer => 13,
ToolKind::Axe => 14,
ToolKind::Sword => 15,
ToolKind::Shield => 16,
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]

View File

@ -25,7 +25,7 @@ pub struct StaticData {
pub recover_duration: Duration,
/// Max angle (45.0 will give you a 90.0 angle window)
pub max_angle: f32,
/// What percentage incoming damage is reduced by
/// What percentage of block strength is effective
pub block_strength: f32,
/// What durations are considered a parry
pub parry_window: ParryWindow,
@ -37,6 +37,8 @@ pub struct StaticData {
pub energy_regen: f32,
/// Whether block can be held
pub can_hold: bool,
/// Whether can choose higher priority weapon block
pub can_use_block_priority: bool,
/// What kinds of attacks the block applies to
pub blocked_attacks: AttackFilters,
}

View File

@ -685,7 +685,7 @@ impl<'a> Widget for ItemTooltip<'a> {
// Block Strength
stat_text(
format!(
"{} : {:+.0}",
"{} : {:.1}",
i18n.get_msg("common-stats-block_strength"),
stats.block_strength * 10.0
),
@ -1215,13 +1215,13 @@ impl<'a> Widget for ItemTooltip<'a> {
// Block Strength
let block_str_text = if is_primary {
format!(
"{} : {:.0}",
"{} : {:.1}",
i18n.get_msg("common-stats-block_strength"),
stats.block_strength * 10.0
)
} else {
format!(
"{} : x{:.2}",
"{} : {:.2}",
i18n.get_msg("common-stats-block_strength"),
stats.block_strength
)