Merge branch 'danielkenji83/block-based-on-poise' into 'master'

Block based on poise

See merge request veloren/veloren!4283
This commit is contained in:
Samuel Keiffer 2024-02-21 16:53:24 +00:00
commit 94f6c3350c
48 changed files with 582 additions and 222 deletions

View File

@ -90,6 +90,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Balance changes; Smoother entity progression consisting of larger variety in (effective) health pools, dps, entity flee_health alterations, and minor weight distribution changes for entity spawns.
- Made power of weapon tiers scale non-linearly.
- Sword Changes; Pommel Strike has been nerfed -> increased energy cost, increased _durations, and decreased poise damage. Heavy Sweep has been nerfed -> decreased poise damage and stun vulnerability damage. Pillar Thrust has been altered -> decreased maximum base damage with an increase in stun vulnerability damage.
- Weapons block are based on poise
- Wooden Shield recipe
### Removed
- Medium and large potions from all loot tables

View File

@ -351,8 +351,9 @@
abilities: [],
),
Tool(Shield): (
primary: Simple(None, "common.abilities.shield.tempbasic"),
secondary: Simple(None, "common.abilities.shield.block"),
guard: Some(Simple(None, "common.abilities.shield.basic_guard")),
primary: Simple(None, "common.abilities.shield.singlestrike"),
secondary: Simple(None, "common.abilities.shield.power_guard"),
abilities: [],
),
Custom("Stone Golem"): (

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: 5.0,
parry_window: (
buildup: true,
recover: 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: 5.0,
parry_window: (
buildup: true,
recover: false,

View File

@ -2,7 +2,7 @@ BasicBlock(
buildup_duration: 0.25,
recover_duration: 0.25,
max_angle: 90.0,
block_strength: 0.5,
block_strength: 5.0,
parry_window: (
buildup: false,
recover: false,

View File

@ -3,6 +3,7 @@ RiposteMelee(
buildup_duration: 0.7,
swing_duration: 0.3,
recover_duration: 0.2,
block_strength: 5.0,
melee_constructor: (
kind: Slash(
damage: 30,

View File

@ -1,14 +1,14 @@
BasicBlock(
buildup_duration: 0.1,
recover_duration: 0.1,
buildup_duration: 0.45,
recover_duration: 0.2,
max_angle: 90.0,
block_strength: 0.8,
block_strength: 10.0,
parry_window: (
buildup: true,
recover: false,
),
energy_cost: 0.0,
energy_regen: 0.0,
energy_cost: 5.0,
energy_regen: 2.5,
can_hold: true,
blocked_attacks: (
melee: true,

View File

@ -0,0 +1,21 @@
BasicBlock(
buildup_duration: 0.5,
recover_duration: 0.25,
max_angle: 90.0,
block_strength: 15.0,
parry_window: (
buildup: true,
recover: false,
),
energy_cost: 15.0,
energy_regen: 2.5,
can_hold: true,
blocked_attacks: (
melee: true,
projectiles: true,
beams: true,
ground_shockwaves: false,
air_shockwaves: true,
explosions: true,
),
)

View File

@ -0,0 +1,32 @@
ComboMelee2(
strikes: [
(
melee_constructor: (
kind: Bash(
damage: 6,
poise: 5,
knockback: 0,
energy_regen: 5,
),
range: 1.5,
angle: 45.0,
damage_effect: Some(BuffsVulnerable(0.5, Parried)),
),
buildup_duration: 0.15,
swing_duration: 0.2,
hit_timing: 0.1,
recover_duration: 0.15,
ori_modifier: 0.1,
movement: (
buildup: Some(Forward(0.1)),
swing: None,
recover: None,
),
),
],
energy_cost_per_strike: 0,
meta: (
// The ability will parry all blockable attacks in the buildup portion
capabilities: ("PARRIES"),
),
)

View File

@ -1,18 +0,0 @@
BasicMelee(
energy_cost: 0,
buildup_duration: 0.1,
swing_duration: 0.1,
hit_timing: 0.0,
recover_duration: 0.3,
melee_constructor: (
kind: Bash(
damage: 4.0,
poise: 0.0,
knockback: 0.0,
energy_regen: 0.0,
),
range: 3.0,
angle: 90.0,
),
ori_modifier: 1.0,
)

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: 5.0,
parry_window: (
buildup: true,
recover: false,

View File

@ -22,6 +22,6 @@ ComboMelee2(
energy_cost_per_strike: 5,
meta: (
// The ability will parry melee attacks in the buildup & swing portion
capabilities: ("PARRIES"),
capabilities: ("PARRIES_MELEE"),
),
)

View File

@ -22,6 +22,6 @@ ComboMelee2(
energy_cost_per_strike: 5,
meta: (
// The ability will parry melee attacks in the buildup & swing portion
capabilities: ("PARRIES"),
capabilities: ("PARRIES_MELEE"),
),
)

View File

@ -41,6 +41,6 @@ ComboMelee2(
auto_progress: true,
meta: (
// The ability will parry melee attacks in the buildup & swing portion
capabilities: ("PARRIES"),
capabilities: ("PARRIES_MELEE"),
),
)

View File

@ -2,7 +2,7 @@ BasicBlock(
buildup_duration: 0.4,
recover_duration: 0.2,
max_angle: 45.0,
block_strength: 0.75,
block_strength: 7.5,
parry_window: (
buildup: true,
recover: false,

View File

@ -23,6 +23,6 @@ ComboMelee2(
energy_cost_per_strike: 5,
meta: (
// The ability will parry melee attacks in the buildup & swing portion
capabilities: ("PARRIES"),
capabilities: ("PARRIES_MELEE"),
),
)

View File

@ -22,6 +22,6 @@ ComboMelee2(
energy_cost_per_strike: 5,
meta: (
// The ability will parry melee attacks in the buildup & swing portion
capabilities: ("PARRIES"),
capabilities: ("PARRIES_MELEE"),
),
)

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: 7.5,
parry_window: (
buildup: true,
recover: false,

View File

@ -3,6 +3,7 @@ RiposteMelee(
buildup_duration: 0.4,
swing_duration: 0.1,
recover_duration: 0.2,
block_strength: 5.0,
melee_constructor: (
kind: Slash(
damage: 18,

View File

@ -27,6 +27,6 @@ ComboMelee2(
energy_cost_per_strike: 5,
meta: (
// The ability will parry melee attacks in the buildup & swing portion
capabilities: ("PARRIES"),
capabilities: ("PARRIES_MELEE"),
),
)

View File

@ -708,6 +708,9 @@
Simple(
"common.items.weapons.shield.shield_1",
): "weapon-shield-wood-0",
Simple(
"common.items.weapons.shield.starter_shield",
): "weapon-shield-starter",
Simple(
"common.items.weapons.dagger.basic_0",
): "weapon-dagger-dagger_basic-0",

View File

@ -0,0 +1,20 @@
ItemDef(
legacy_name: "Wooden Shield",
legacy_description: "May not withstand fire",
kind: Tool((
kind: Shield,
hands: One,
stats: (
equip_time_secs: 0.3,
power: 0.5,
effect_power: 1.0,
speed: 1.0,
range: 1.0,
energy_efficiency: 1.0,
buff_strength: 1.0,
),
)),
quality: Common,
tags: [],
ability_spec: None,
)

View File

@ -5,4 +5,5 @@
(1.0, Item("common.items.weapons.axe.starter_axe")),
(1.0, Item("common.items.weapons.staff.starter_staff")),
(1.0, Item("common.items.weapons.sceptre.starter_sceptre")),
(1.0, Item("common.items.weapons.shield.starter_shield")),
]

View File

@ -2357,4 +2357,12 @@
],
craft_sprite: Some(Cauldron),
),
"shield": (
output: ("common.items.weapons.shield.starter_shield", 1),
inputs: [
(Item("common.items.log.wood"), 5, false),
(Item("common.items.crafting_ing.leather.leather_strips"), 2, false)
],
craft_sprite: Some(CraftingBench),
),
}

View File

@ -8,6 +8,7 @@ recipes: {
ItemDefId("common.items.weapons.staff.starter_staff"): ( inputs: [], ),
ItemDefId("common.items.weapons.sceptre.starter_sceptre"): ( inputs: [], ),
ItemDefId("common.items.weapons.sword_1h.starter"): ( inputs: [], ),
ItemDefId("common.items.weapons.shield.starter_shield"): ( inputs: [], ),
ModularWeapon(material: "common.items.mineral.ingot.bronze"): (
inputs: [
(Item("common.items.mineral.ingot.bronze"), 1),

View File

@ -954,7 +954,7 @@
threshold: 0.5,
subtitle: "subtitle-unwield_shield",
),
Attack(BasicMelee(Action), Shield): (
Attack(ComboMelee2(Action), Shield): (
files: [
"voxygen.audio.sfx.abilities.swing",
],

View File

@ -1,6 +1,9 @@
weapon-shield-wood-0 = A Tattered Targe
.desc = Should withstand a few more hits, hopefully...
weapon-shield-starter = Wooden Shield
.desc = May not withstand fire
weapon-dagger-dagger_basic-0 = Suspicious Paper Knife
.desc = Opens letters quickly.

View File

@ -1554,7 +1554,11 @@
// Shields
Simple("common.items.weapons.shield.shield_1"): VoxTrans(
"voxel.weapon.shield.wood-0",
(0.0, 0.0, 0.0), (-90.0, 90.0, 0.0), 2.4,
(0.0, 0.0, 0.0), (-135.0, 90.0, 0.0), 1.0,
),
Simple("common.items.weapons.shield.starter_shield"): VoxTrans(
"voxel.weapon.shield.wood-1",
(0.0, 0.0, 0.0), (-135.0, 90.0, 0.0), 1.0,
),
// Lanterns
Simple("common.items.lantern.black_0"): VoxTrans(

View File

@ -899,7 +899,11 @@
),
// Shields
Tool("common.items.weapons.shield.shield_1"): (
vox_spec: ("weapon.shield.wood-0", (-2.5, -5.5, -5.5)),
vox_spec: ("weapon.shield.wood-0", (-2.5, -7.5, -5.5)),
color: None
),
Tool("common.items.weapons.shield.starter_shield"): (
vox_spec: ("weapon.shield.wood-1", (-3.5, -7.5, -5.5)),
color: None
),
// Bows

View File

@ -376,6 +376,7 @@
Simple("common.items.weapons.dagger.cultist_0"): "voxel.weapon.dagger.dagger_cult-0",
// Shields
Simple("common.items.weapons.shield.shield_1"): "voxel.weapon.shield.wood-0",
Simple("common.items.weapons.shield.starter_shield"): "voxel.weapon.shield.wood-1",
// Lanterns
Simple("common.items.lantern.black_0"): "voxel.lantern.black-0",
Simple("common.items.lantern.green_0"): "voxel.lantern.green-0",

BIN
assets/voxygen/voxel/weapon/shield/wood-1.vox (Stored with Git LFS) Normal file

Binary file not shown.

View File

@ -58,6 +58,9 @@ 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 = 50.0;
pub const PARRY_BONUS_MULTIPLIER: f32 = 2.0;
pub const FALLBACK_BLOCK_STRENGTH: f32 = 5.0;
#[derive(Copy, Clone)]
pub struct AttackerInfo<'a> {
@ -140,15 +143,84 @@ impl Attack {
pub fn effects(&self) -> impl Iterator<Item = &AttackEffect> { self.effects.iter() }
pub fn compute_damage_reduction(
pub fn compute_block_damage_decrement(
attacker: Option<&AttackerInfo>,
damage_reduction: f32,
target: &TargetInfo,
source: AttackSource,
dir: Dir,
damage: Damage,
msm: &MaterialStatManifest,
emitters: &mut impl EmitExt<ParryHookEvent>,
time: Time,
emitters: &mut (impl EmitExt<ParryHookEvent> + EmitExt<PoiseChangeEvent>),
mut emit_outcome: impl FnMut(Outcome),
) -> f32 {
if damage.value > 0.0 {
if let (Some(char_state), Some(ori), Some(inventory)) =
(target.char_state, target.ori, target.inventory)
{
let is_parry = char_state.is_parry(source);
let is_block = char_state.is_block(source);
let damage_value = damage.value * (1.0 - damage_reduction);
let mut block_strength = block_strength(inventory, char_state);
if ori.look_vec().angle_between(-dir.with_z(0.0)) < char_state.block_angle()
&& (is_parry || is_block)
&& block_strength > 0.0
{
if is_parry {
block_strength *= PARRY_BONUS_MULTIPLIER;
emitters.emit(ParryHookEvent {
defender: target.entity,
attacker: attacker.map(|a| a.entity),
source,
});
}
let poise_cost =
(damage_value / block_strength).min(1.0) * MAX_BLOCK_POISE_COST;
let poise_change = Poise::apply_poise_reduction(
poise_cost,
target.inventory,
msm,
target.char_state,
target.stats,
);
emit_outcome(Outcome::Block {
parry: is_parry,
pos: target.pos,
uid: target.uid,
});
emitters.emit(PoiseChangeEvent {
entity: target.entity,
change: PoiseChange {
amount: -poise_change,
impulse: *dir,
by: attacker.map(|x| (*x).into()),
cause: Some(damage.source),
time,
},
});
block_strength
} else {
0.0
}
} else {
0.0
}
} else {
0.0
}
}
pub fn compute_damage_reduction(
attacker: Option<&AttackerInfo>,
target: &TargetInfo,
damage: Damage,
msm: &MaterialStatManifest,
) -> f32 {
if damage.value > 0.0 {
let attacker_penetration = attacker
@ -157,39 +229,7 @@ impl Attack {
.clamp(0.0, 1.0);
let raw_damage_reduction =
Damage::compute_damage_reduction(Some(damage), target.inventory, target.stats, msm);
let damage_reduction = (1.0 - attacker_penetration) * raw_damage_reduction;
let block_reduction =
if let (Some(char_state), Some(ori)) = (target.char_state, target.ori) {
if ori.look_vec().angle_between(-dir.with_z(0.0)) < char_state.block_angle() {
if char_state.is_parry(source) {
emit_outcome(Outcome::Block {
parry: true,
pos: target.pos,
uid: target.uid,
});
emitters.emit(ParryHookEvent {
defender: target.entity,
attacker: attacker.map(|a| a.entity),
source,
});
1.0
} else if let Some(block_strength) = char_state.block_strength(source) {
emit_outcome(Outcome::Block {
parry: false,
pos: target.pos,
uid: target.uid,
});
block_strength
} else {
0.0
}
} else {
0.0
}
} else {
0.0
};
1.0 - (1.0 - damage_reduction) * (1.0 - block_reduction)
(1.0 - attacker_penetration) * raw_damage_reduction
} else {
0.0
}
@ -260,18 +300,26 @@ impl Attack {
{
let damage_instance = damage.instance + damage_instance_offset;
is_applied = true;
let damage_reduction = Attack::compute_damage_reduction(
let damage_reduction =
Attack::compute_damage_reduction(attacker.as_ref(), target, damage.damage, msm);
let block_damage_decrement = Attack::compute_block_damage_decrement(
attacker.as_ref(),
damage_reduction,
target,
attack_source,
dir,
damage.damage,
msm,
time,
emitters,
&mut emit_outcome,
);
let change = damage.damage.calculate_health_change(
damage_reduction,
block_damage_decrement,
attacker.map(|x| x.into()),
precision_mult,
self.precision_multiplier,
@ -1046,6 +1094,7 @@ impl Damage {
pub fn calculate_health_change(
self,
damage_reduction: f32,
block_damage_decrement: f32,
damage_contributor: Option<DamageContributor>,
precision_mult: Option<f32>,
precision_power: f32,
@ -1065,6 +1114,8 @@ impl Damage {
damage += precise_damage;
// Armor
damage *= 1.0 - damage_reduction;
// Block
damage = f32::max(damage - block_damage_decrement, 0.0);
HealthChange {
amount: -damage,
@ -1496,3 +1547,55 @@ pub fn precision_mult_from_flank(attack_dir: Vec3<f32>, target_ori: Option<&Ori>
Some(_) | None => None,
}
}
pub fn block_strength(inventory: &Inventory, char_state: &CharacterState) -> f32 {
match char_state {
CharacterState::BasicBlock(data) => data.static_data.block_strength,
CharacterState::RiposteMelee(data) => data.static_data.block_strength,
_ => char_state
.ability_info()
.map(|ability| (ability.ability_meta.capabilities, ability.hand))
.map(|(capabilities, hand)| {
(
if capabilities.contains(Capability::PARRIES)
|| capabilities.contains(Capability::PARRIES_MELEE)
|| capabilities.contains(Capability::BLOCKS)
{
FALLBACK_BLOCK_STRENGTH
} else {
0.0
},
hand.and_then(|hand| inventory.equipped(hand.to_equip_slot()))
.map_or(1.0, |item| match &*item.kind() {
ItemKind::Tool(tool) => {
tool.stats(item.stats_durability_multiplier()).power
},
_ => 1.0,
}),
)
})
.map_or(0.0, |(capability_strength, tool_block_strength)| {
capability_strength * tool_block_strength
}),
}
}
pub fn get_equip_slot_by_block_priority(inventory: Option<&Inventory>) -> EquipSlot {
inventory
.map(get_weapon_kinds)
.map_or(
EquipSlot::ActiveMainhand,
|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::ActiveMainhand,
},
)
}

View File

@ -195,34 +195,22 @@ impl ActiveAbilities {
};
match ability {
Ability::ToolGuard => ability_set(EquipSlot::ActiveMainhand)
.and_then(|abilities| {
abilities
.guard(Some(skill_set), context)
.map(|(a, i)| (a.ability.clone(), i))
})
.map(|(ability, i)| {
(
scale_ability(ability, EquipSlot::ActiveMainhand),
true,
spec_ability(i),
)
})
.or_else(|| {
ability_set(EquipSlot::ActiveOffhand)
.and_then(|abilities| {
abilities
.guard(Some(skill_set), context)
.map(|(a, i)| (a.ability.clone(), i))
})
.map(|(ability, i)| {
(
scale_ability(ability, EquipSlot::ActiveOffhand),
false,
spec_ability(i),
)
})
}),
Ability::ToolGuard => {
let equip_slot = combat::get_equip_slot_by_block_priority(inv);
ability_set(equip_slot)
.and_then(|abilities| {
abilities
.guard(Some(skill_set), context)
.map(|(a, i)| (a.ability.clone(), i))
})
.map(|(ability, i)| {
(
scale_ability(ability, equip_slot),
matches!(equip_slot, EquipSlot::ActiveOffhand),
spec_ability(i),
)
})
},
Ability::ToolPrimary => ability_set(EquipSlot::ActiveMainhand)
.and_then(|abilities| {
abilities
@ -396,7 +384,7 @@ impl Ability {
};
match self {
Ability::ToolGuard => ability_set(EquipSlot::ActiveMainhand)
Ability::ToolGuard => ability_set(combat::get_equip_slot_by_block_priority(inv))
.and_then(|abilities| {
abilities
.guard(skillset, context)
@ -407,19 +395,6 @@ impl Ability {
.as_ref()
.and_then(|g| contextual_id(Some(g)))
})
})
.or_else(|| {
ability_set(EquipSlot::ActiveOffhand).and_then(|abilities| {
abilities
.guard(skillset, context)
.map(|a| a.0.id.as_str())
.or_else(|| {
abilities
.guard
.as_ref()
.and_then(|g| contextual_id(Some(g)))
})
})
}),
Ability::ToolPrimary => ability_set(EquipSlot::ActiveMainhand).and_then(|abilities| {
abilities
@ -524,12 +499,8 @@ impl SpecifiedAbility {
ability_set(EquipSlot::ActiveMainhand)
.map(|abilities| ability_id(self, &abilities.secondary))
}),
Ability::ToolGuard => ability_set(EquipSlot::ActiveMainhand)
.and_then(|abilities| abilities.guard.as_ref().map(|a| ability_id(self, a)))
.or_else(|| {
ability_set(EquipSlot::ActiveOffhand)
.and_then(|abilities| abilities.guard.as_ref().map(|a| ability_id(self, a)))
}),
Ability::ToolGuard => ability_set(combat::get_equip_slot_by_block_priority(inv))
.and_then(|abilities| abilities.guard.as_ref().map(|a| ability_id(self, a))),
Ability::SpeciesMovement => None, // TODO: Make not None
Ability::MainWeaponAux(index) => ability_set(EquipSlot::ActiveMainhand)
.and_then(|abilities| abilities.abilities.get(index).map(|a| ability_id(self, a))),
@ -1006,6 +977,7 @@ pub enum CharacterAbility {
buildup_duration: f32,
swing_duration: f32,
recover_duration: f32,
block_strength: f32,
melee_constructor: MeleeConstructor,
#[serde(default)]
meta: AbilityMeta,
@ -1279,8 +1251,7 @@ impl CharacterAbility {
ref mut recover_duration,
// Do we want angle to be adjusted by range?
max_angle: _,
// Block strength explicitly not modified by power, that will be a separate stat
block_strength: _,
ref mut block_strength,
parry_window: _,
ref mut energy_cost,
energy_regen: _,
@ -1291,6 +1262,7 @@ impl CharacterAbility {
*buildup_duration /= stats.speed;
*recover_duration /= stats.speed;
*energy_cost /= stats.energy_efficiency;
*block_strength *= stats.power;
},
Roll {
ref mut energy_cost,
@ -1660,6 +1632,7 @@ impl CharacterAbility {
ref mut buildup_duration,
ref mut swing_duration,
ref mut recover_duration,
ref mut block_strength,
ref mut melee_constructor,
meta: _,
} => {
@ -1667,6 +1640,7 @@ impl CharacterAbility {
*swing_duration /= stats.speed;
*recover_duration /= stats.speed;
*energy_cost /= stats.energy_efficiency;
*block_strength *= stats.power;
*melee_constructor = melee_constructor.adjusted_by_stats(stats);
},
RapidMelee {
@ -2914,6 +2888,7 @@ impl From<(&CharacterAbility, AbilityInfo, &JoinData<'_>)> for CharacterState {
buildup_duration,
swing_duration,
recover_duration,
block_strength,
melee_constructor,
meta: _,
} => CharacterState::RiposteMelee(riposte_melee::Data {
@ -2921,6 +2896,7 @@ impl From<(&CharacterAbility, AbilityInfo, &JoinData<'_>)> for CharacterState {
buildup_duration: Duration::from_secs_f32(*buildup_duration),
swing_duration: Duration::from_secs_f32(*swing_duration),
recover_duration: Duration::from_secs_f32(*recover_duration),
block_strength: *block_strength,
melee_constructor: *melee_constructor,
ability_info,
},
@ -3003,18 +2979,18 @@ 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 {
// There used to be a capability here, to keep ordering the same below this is now a placeholder
const PLACEHOLDER = 0b00000001;
// The ability will parry all blockable attacks in the buildup portion
const PARRIES = 0b00000001;
// Allows blocking to interrupt the ability at any point
const BLOCK_INTERRUPT = 0b00000010;
// When the ability is in the buildup section, it counts as a block with 50% DR
// The ability will block melee attacks in the buildup portion
const BLOCKS = 0b00000100;
// When in the ability, an entity only receives half as much poise damage
const POISE_RESISTANT = 0b00001000;
// WHen in the ability, an entity only receives half as much knockback
const KNOCKBACK_RESISTANT = 0b00010000;
// The ability will parry melee attacks in the buildup portion
const PARRIES = 0b00100000;
const PARRIES_MELEE = 0b00100000;
}
}

View File

@ -308,53 +308,36 @@ impl CharacterState {
)
}
pub fn block_strength(&self, attack_source: AttackSource) -> Option<f32> {
let from_capability = if let AttackSource::Melee = attack_source {
if let Some(capabilities) = self
.ability_info()
.map(|a| a.ability_meta)
.map(|m| m.capabilities)
{
(capabilities.contains(Capability::BLOCKS)
&& matches!(
self.stage_section(),
Some(StageSection::Buildup | StageSection::Action)
))
.then_some(0.5)
} else {
None
}
} else {
None
};
let from_state = match self {
CharacterState::BasicBlock(c) => c
.static_data
.blocked_attacks
.applies(attack_source)
.then_some(c.static_data.block_strength),
_ => None,
};
match (from_capability, from_state) {
(Some(a), Some(b)) => Some(a.max(b)),
(Some(a), None) | (None, Some(a)) => Some(a),
(None, None) => None,
}
}
pub fn is_parry(&self, attack_source: AttackSource) -> bool {
let melee = matches!(attack_source, AttackSource::Melee);
let from_capability = melee
let from_capability_melee = melee
&& self
.ability_info()
.map(|a| a.ability_meta.capabilities)
.map_or(false, |c| {
c.contains(Capability::PARRIES)
c.contains(Capability::PARRIES_MELEE)
&& matches!(
self.stage_section(),
Some(StageSection::Buildup | StageSection::Action)
)
});
let from_capability = matches!(
attack_source,
AttackSource::Melee
| AttackSource::Projectile
| AttackSource::Beam
| AttackSource::AirShockwave
| AttackSource::Explosion
) && self
.ability_info()
.map(|a| a.ability_meta.capabilities)
.map_or(false, |c| {
c.contains(Capability::PARRIES)
&& matches!(
self.stage_section(),
Some(StageSection::Buildup | StageSection::Action)
)
});
let from_state = match self {
CharacterState::BasicBlock(c) => c.is_parry(attack_source),
CharacterState::RiposteMelee(c) => {
@ -366,7 +349,30 @@ impl CharacterState {
},
_ => false,
};
from_capability || from_state
from_capability_melee || from_capability || from_state
}
pub fn is_block(&self, attack_source: AttackSource) -> bool {
match self {
CharacterState::BasicBlock(data) => {
data.static_data.blocked_attacks.applies(attack_source)
&& matches!(
self.stage_section(),
Some(StageSection::Buildup | StageSection::Action)
)
},
_ => self
.ability_info()
.map(|ability| ability.ability_meta.capabilities)
.map_or(false, |capabilities| {
capabilities.contains(Capability::BLOCKS)
&& matches!(
self.stage_section(),
Some(StageSection::Buildup | StageSection::Action)
)
&& matches!(attack_source, AttackSource::Melee)
}),
}
}
/// In radians

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,8 @@ 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
/// Base value that incoming damage is reduced by and converted to poise
/// damage
pub block_strength: f32,
/// What durations are considered a parry
pub parry_window: ParryWindow,

View File

@ -18,6 +18,9 @@ pub struct StaticData {
pub swing_duration: Duration,
/// How long the state has until exiting
pub recover_duration: Duration,
/// Base value that incoming damage is reduced by and converted to poise
/// damage
pub block_strength: f32,
/// Used to construct the Melee attack
pub melee_constructor: MeleeConstructor,
/// What key is used to press ability

View File

@ -1411,10 +1411,7 @@ pub fn get_hands(data: &JoinData<'_>) -> (Option<Hands>, Option<Hands>) {
pub fn get_tool_stats(data: &JoinData<'_>, ai: AbilityInfo) -> tool::Stats {
ai.hand
.map(|hand| match hand {
HandInfo::TwoHanded | HandInfo::MainHand => EquipSlot::ActiveMainhand,
HandInfo::OffHand => EquipSlot::ActiveOffhand,
})
.map(|hand| hand.to_equip_slot())
.and_then(|slot| data.inventory.and_then(|inv| inv.equipped(slot)))
.and_then(|item| {
if let ItemKind::Tool(tool) = &*item.kind() {
@ -1616,6 +1613,13 @@ impl HandInfo {
},
}
}
pub fn to_equip_slot(&self) -> EquipSlot {
match self {
HandInfo::TwoHanded | HandInfo::MainHand => EquipSlot::ActiveMainhand,
HandInfo::OffHand => EquipSlot::ActiveOffhand,
}
}
}
pub fn leave_stance(data: &JoinData<'_>, output_events: &mut OutputEvents) {

View File

@ -819,6 +819,7 @@ impl ServerEvent for LandOnGroundEvent {
);
let change = damage.calculate_health_change(
damage_reduction,
0.0,
None,
None,
0.0,
@ -1401,6 +1402,7 @@ pub fn emit_effect_events(
common::effect::Effect::Damage(damage) => {
let change = damage.calculate_health_change(
combat::Damage::compute_damage_reduction(Some(damage), inventory, stats, msm),
0.0,
damage_contributor,
None,
0.0,
@ -1677,7 +1679,7 @@ impl ServerEvent for ParryHookEvent {
entity: ev.defender,
change: c.static_data.energy_regen,
});
true
false
},
_ => false,
};

View File

@ -202,6 +202,7 @@ impl StateExt for State {
stats.get(entity),
&msm,
),
0.0,
damage_contributor,
None,
0.0,

View File

@ -55,7 +55,9 @@ impl Animation for BlockAnimation {
| Some("common.abilities.sword.basic_guard")
| Some("common.abilities.axe.basic_guard")
| Some("common.abilities.hammer.basic_guard")
| Some("common.abilities.sword.defensive_guard") => {
| Some("common.abilities.sword.defensive_guard")
| Some("common.abilities.shield.basic_guard")
| Some("common.abilities.shield.power_guard") => {
let speed = Vec2::<f32>::from(velocity).magnitude();
let (movement1base, move2, movement3) = match stage_section {
@ -215,53 +217,95 @@ impl Animation for BlockAnimation {
* Quaternion::rotation_y(0.6)
* Quaternion::rotation_z(0.0);
},
Some(ToolKind::Shield) => {
next.control.position = Vec3::new(2.0, 9.0, 8.0);
next.control.orientation =
Quaternion::rotation_x(0.25) * Quaternion::rotation_z(-1.5);
next.hand_l.position = Vec3::new(0.0, -2.0, 0.0);
next.hand_l.orientation = Quaternion::rotation_x(PI / 2.0);
next.hand_r.position = Vec3::new(0.0, 0.0, 0.0);
next.hand_r.orientation = Quaternion::rotation_x(PI / 2.0)
* Quaternion::rotation_y(PI / 2.0);
},
_ => {},
}
},
((Some(Hands::One), Some(Hands::One)), _, Some(ToolKind::Shield)) => {
next.control_r.position = Vec3::new(-1.5, 8.0, 4.0 + move1 * 3.0);
next.control_r.orientation = Quaternion::rotation_x(0.25)
* Quaternion::rotation_y(0.0)
* Quaternion::rotation_z(1.5);
next.hand_r.position = Vec3::new(0.0, -2.0, 0.0);
next.hand_r.orientation = Quaternion::rotation_x(PI / 2.0);
next.control_l.position = Vec3::new(-9.0, -5.0, 0.0);
next.control_l.orientation =
Quaternion::rotation_x(-1.75) * Quaternion::rotation_y(-0.3);
next.hand_l.position = Vec3::new(0.0, -0.5, 0.0);
next.hand_l.orientation = Quaternion::rotation_x(PI / 2.0);
},
((Some(Hands::One), _), Some(ToolKind::Shield), _) => {
next.control_l.position = Vec3::new(1.5, 8.0, 4.0 + move1 * 3.0);
next.control_l.orientation = Quaternion::rotation_x(0.25)
* Quaternion::rotation_y(0.0)
* Quaternion::rotation_z(-1.5);
next.hand_l.position = Vec3::new(0.0, -2.0, 0.0);
next.hand_l.orientation = Quaternion::rotation_x(PI / 2.0);
next.control_r.position = Vec3::new(9.0, -5.0, 0.0);
next.control_r.orientation =
Quaternion::rotation_x(-1.75) * Quaternion::rotation_y(0.3);
next.hand_r.position = Vec3::new(0.0, -0.5, 0.0);
next.hand_r.orientation = Quaternion::rotation_x(PI / 2.0);
},
((Some(Hands::One), _), _, _) => {
match hands {
(Some(Hands::One), _) => {
next.control_l.position =
Vec3::new(-7.0, 8.0 + move1 * 3.0, 2.0 + move1 * 3.0);
next.control_l.orientation = Quaternion::rotation_x(-0.3)
* Quaternion::rotation_y(move1 * 1.0);
next.hand_l.position = Vec3::new(0.0, -0.5, 0.0);
next.hand_l.orientation = Quaternion::rotation_x(PI / 2.0)
},
(_, _) => {},
};
match hands {
(None | Some(Hands::One), Some(Hands::One)) => {
next.control_r.position =
Vec3::new(7.0, 8.0 + move1 * 3.0, 2.0 + move1 * 3.0);
next.control_r.orientation = Quaternion::rotation_x(-0.3)
* Quaternion::rotation_y(move1 * -1.0);
next.hand_r.position = Vec3::new(0.0, -0.5, 0.0);
next.hand_r.orientation = Quaternion::rotation_x(PI / 2.0)
},
(_, _) => {},
};
match hands {
(None, None) | (None, Some(Hands::One)) => {
next.hand_l.position = Vec3::new(-4.5, 8.0, 5.0);
next.hand_l.orientation =
Quaternion::rotation_x(1.9) * Quaternion::rotation_y(-0.5)
},
(_, _) => {},
};
match hands {
(None, None) | (Some(Hands::One), None) => {
next.hand_r.position = Vec3::new(4.5, 8.0, 5.0);
next.hand_r.orientation =
Quaternion::rotation_x(1.9) * Quaternion::rotation_y(0.5)
},
(_, _) => {},
};
if let (None, Some(Hands::Two)) = hands {
next.second = next.main;
}
},
((_, _), _, _) => {},
};
match hands {
(Some(Hands::One), _) => {
next.control_l.position =
Vec3::new(-7.0, 8.0 + move1 * 3.0, 2.0 + move1 * 3.0);
next.control_l.orientation =
Quaternion::rotation_x(-0.3) * Quaternion::rotation_y(move1 * 1.0);
next.hand_l.position = Vec3::new(0.0, -0.5, 0.0);
next.hand_l.orientation = Quaternion::rotation_x(PI / 2.0)
},
(_, _) => {},
};
match hands {
(None | Some(Hands::One), Some(Hands::One)) => {
next.control_r.position =
Vec3::new(7.0, 8.0 + move1 * 3.0, 2.0 + move1 * 3.0);
next.control_r.orientation =
Quaternion::rotation_x(-0.3) * Quaternion::rotation_y(move1 * -1.0);
next.hand_r.position = Vec3::new(0.0, -0.5, 0.0);
next.hand_r.orientation = Quaternion::rotation_x(PI / 2.0)
},
(_, _) => {},
};
match hands {
(None, None) | (None, Some(Hands::One)) => {
next.hand_l.position = Vec3::new(-4.5, 8.0, 5.0);
next.hand_l.orientation =
Quaternion::rotation_x(1.9) * Quaternion::rotation_y(-0.5)
},
(_, _) => {},
};
match hands {
(None, None) | (Some(Hands::One), None) => {
next.hand_r.position = Vec3::new(4.5, 8.0, 5.0);
next.hand_r.orientation =
Quaternion::rotation_x(1.9) * Quaternion::rotation_y(0.5)
},
(_, _) => {},
};
if let (None, Some(Hands::Two)) = hands {
next.second = next.main;
}
},
Some("common.abilities.sword.defensive_deflect") => {
let (move1, move2, move3) = match stage_section {

View File

@ -2,7 +2,7 @@ use super::{
super::{vek::*, Animation},
CharacterSkeleton, SkeletonAttr,
};
use common::states::utils::{AbilityInfo, StageSection};
use common::states::utils::{AbilityInfo, HandInfo, StageSection};
use core::f32::consts::{PI, TAU};
pub struct ComboAnimation;
@ -1133,6 +1133,81 @@ impl Animation for ComboAnimation {
next.control.orientation.rotate_x(move2 * -1.2);
next.control.position += Vec3::new(move2 * 8.0, 0.0, 0.0);
},
Some("common.abilities.shield.singlestrike") => {
let move1 = if strike == current_strike {
match stage_section {
Some(StageSection::Buildup) => anim_time,
Some(StageSection::Action) => 1.0,
Some(StageSection::Recover) => 1.0,
_ => 0.0,
}
} else {
1.0
};
let move1 = move1 * multi_strike_pullback;
if let Some(ability_info) = _ability_info {
match ability_info.hand {
Some(HandInfo::TwoHanded) => {
next.main.orientation = Quaternion::rotation_x(0.0);
next.chest.orientation = Quaternion::rotation_z(move1 * -0.3);
next.torso.orientation = Quaternion::rotation_z(move1 * -1.0);
next.head.orientation = Quaternion::rotation_z(move1 * 0.75);
next.head.position = Vec3::new(0.5, s_a.head.0 + 0.5, s_a.head.1);
next.control.position = Vec3::new(move1 * -10.0, 6.0, move1 * 6.0);
next.control.orientation = Quaternion::rotation_z(-0.25);
next.hand_l.position = Vec3::new(0.0, -2.0, 0.0);
next.hand_l.orientation = Quaternion::rotation_x(PI / 2.0);
next.hand_r.position = Vec3::new(0.0, 0.0, 0.0);
next.hand_r.orientation = Quaternion::rotation_x(PI / 2.0)
* Quaternion::rotation_y(PI / 2.0);
},
Some(HandInfo::MainHand) => {
next.main.orientation = Quaternion::rotation_x(0.0);
next.chest.orientation = Quaternion::rotation_z(move1 * -0.3);
next.torso.orientation = Quaternion::rotation_z(move1 * -1.2);
next.head.orientation = Quaternion::rotation_z(move1 * 0.75);
next.head.position = Vec3::new(0.5, s_a.head.0 + 0.5, s_a.head.1);
next.control_l.position =
Vec3::new(move1 * -12.0, 4.0, move1 * 6.0);
next.control_l.orientation = Quaternion::rotation_x(move1 * 0.0)
* Quaternion::rotation_y(0.0)
* Quaternion::rotation_z(-0.25);
next.hand_l.position = Vec3::new(0.0, -1.5, 0.0);
next.hand_l.orientation = Quaternion::rotation_x(PI / 2.0);
next.control_r.position = Vec3::new(9.0, -1.0, 0.0);
next.control_r.orientation = Quaternion::rotation_x(-1.75);
next.hand_r.position = Vec3::new(0.0, 0.5, 0.0);
next.hand_r.orientation = Quaternion::rotation_x(PI / 2.0);
},
Some(HandInfo::OffHand) => {
next.main.orientation = Quaternion::rotation_x(0.0);
next.chest.orientation = Quaternion::rotation_z(move1 * 0.3);
next.torso.orientation = Quaternion::rotation_z(move1 * 1.2);
next.head.orientation = Quaternion::rotation_z(move1 * -0.75);
next.head.position = Vec3::new(-0.5, s_a.head.0 + -0.5, s_a.head.1);
next.control_r.position = Vec3::new(move1 * 12.0, 4.0, move1 * 6.0);
next.control_r.orientation = Quaternion::rotation_x(move1 * 0.0)
* Quaternion::rotation_y(0.0)
* Quaternion::rotation_z(0.25);
next.hand_r.position = Vec3::new(0.0, -1.5, 0.0);
next.hand_r.orientation = Quaternion::rotation_x(PI / 2.0);
next.control_l.position = Vec3::new(-9.0, -1.0, 0.0);
next.control_l.orientation = Quaternion::rotation_x(-1.75);
next.hand_l.position = Vec3::new(0.0, 0.5, 0.0);
next.hand_l.orientation = Quaternion::rotation_x(PI / 2.0);
},
_ => {},
}
}
},
_ => {},
}
}

View File

@ -360,6 +360,11 @@ impl CharacterSkeleton {
self.main.orientation =
Quaternion::rotation_y(-0.5) * Quaternion::rotation_z(PI / 2.0);
},
Some(ToolKind::Shield) => {
self.main.position = Vec3::new(-2.0, -3.0 - self.back_carry_offset, 1.0);
self.main.orientation =
Quaternion::rotation_y(-0.75) * Quaternion::rotation_z(PI / 2.0);
},
_ => {
self.main.position = Vec3::new(-7.0, -5.0 - self.back_carry_offset, 15.0);
self.main.orientation =
@ -382,7 +387,7 @@ impl CharacterSkeleton {
Quaternion::rotation_y(2.5) * Quaternion::rotation_z(PI / 2.0);
},
Some(ToolKind::Shield) => {
self.main.position = Vec3::new(-0.0, -4.0 - self.back_carry_offset, 3.0);
self.main.position = Vec3::new(-2.0, -4.0 - self.back_carry_offset, 3.0);
self.main.orientation =
Quaternion::rotation_y(0.25 * PI) * Quaternion::rotation_z(-1.5 * PI);
},
@ -403,7 +408,7 @@ impl CharacterSkeleton {
Quaternion::rotation_y(-2.5) * Quaternion::rotation_z(-PI / 2.0);
},
Some(ToolKind::Shield) => {
self.second.position = Vec3::new(0.0, -4.0 - self.back_carry_offset, 3.0);
self.second.position = Vec3::new(1.5, -4.0 - self.back_carry_offset, 3.0);
self.second.orientation =
Quaternion::rotation_y(-0.25 * PI) * Quaternion::rotation_z(1.5 * PI);
},

View File

@ -167,6 +167,18 @@ impl Animation for RollAnimation {
next.control.position = Vec3::new(-11.0, 1.8, 4.0);
},
Some(ToolKind::Shield) => {
next.hand_l.position = Vec3::new(0.0, -1.5, 0.0);
next.hand_l.orientation = Quaternion::rotation_x(PI / 2.0);
next.hand_r.position = Vec3::new(0.0, 0.0, 0.0);
next.hand_r.orientation =
Quaternion::rotation_x(PI / 2.0) * Quaternion::rotation_y(2.0);
next.control.position = Vec3::new(0.0, 7.0, 4.0);
next.control.orientation =
Quaternion::rotation_y(-0.5) * Quaternion::rotation_z(-1.25);
},
_ => {},
},
(_, _) => {},

View File

@ -497,6 +497,18 @@ impl Animation for WieldAnimation {
}
}
},
Some(ToolKind::Shield) => {
next.hand_l.position = Vec3::new(0.0, -2.0, 0.0);
next.hand_l.orientation = Quaternion::rotation_x(PI / 2.0);
next.hand_r.position = Vec3::new(0.0, 0.0, 0.0);
next.hand_r.orientation =
Quaternion::rotation_x(PI / 2.0) * Quaternion::rotation_y(2.0);
next.control.position = Vec3::new(0.0, 7.0, 4.0);
next.control.orientation =
Quaternion::rotation_y(-0.5) * Quaternion::rotation_z(-1.25);
},
_ => {},
},
((_, _), _, _) => {},

View File

@ -145,7 +145,12 @@ impl CombatEventMapper {
previous_state: &PreviousEntityState,
inventory: &Inventory,
) -> SfxEvent {
if let Some(item) = inventory.equipped(EquipSlot::ActiveMainhand) {
let equip_slot = character_state
.ability_info()
.and_then(|ability| ability.hand)
.map_or(EquipSlot::ActiveMainhand, |hand| hand.to_equip_slot());
if let Some(item) = inventory.equipped(equip_slot) {
if let ItemKind::Tool(data) = &*item.kind() {
if character_state.is_attack() {
return SfxEvent::Attack(

View File

@ -1131,6 +1131,7 @@ impl<'a> Widget for Crafting<'a> {
RecipeKind::Component(ToolKind::Bow) => self.imgs.icon_log,
RecipeKind::Component(ToolKind::Staff) => self.imgs.icon_log,
RecipeKind::Component(ToolKind::Sceptre) => self.imgs.icon_log,
RecipeKind::Component(ToolKind::Shield) => self.imgs.icon_ingot,
_ => self.imgs.not_found,
};

View File

@ -563,8 +563,8 @@ pub fn ability_image(imgs: &img_ids::Imgs, ability_id: &str) -> image::Id {
"common.abilities.sceptre.healingaura" => imgs.skill_sceptre_heal,
"common.abilities.sceptre.wardingaura" => imgs.skill_sceptre_aura,
// Shield
"common.abilities.shield.tempbasic" => imgs.onehshield_m1,
"common.abilities.shield.block" => imgs.onehshield_m2,
"common.abilities.shield.singlestrike" => imgs.onehshield_m1,
"common.abilities.shield.power_guard" => imgs.onehshield_m1,
// Dagger
"common.abilities.dagger.tempbasic" => imgs.onehdagger_m1,
// Pickaxe