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. - 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. - 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. - 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 ### Removed
- Medium and large potions from all loot tables - Medium and large potions from all loot tables

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,14 +1,14 @@
BasicBlock( BasicBlock(
buildup_duration: 0.1, buildup_duration: 0.45,
recover_duration: 0.1, recover_duration: 0.2,
max_angle: 90.0, max_angle: 90.0,
block_strength: 0.8, block_strength: 10.0,
parry_window: ( parry_window: (
buildup: true, buildup: true,
recover: false, recover: false,
), ),
energy_cost: 0.0, energy_cost: 5.0,
energy_regen: 0.0, energy_regen: 2.5,
can_hold: true, can_hold: true,
blocked_attacks: ( blocked_attacks: (
melee: true, 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, buildup_duration: 0.25,
recover_duration: 0.2, recover_duration: 0.2,
max_angle: 60.0, max_angle: 60.0,
block_strength: 0.5, block_strength: 5.0,
parry_window: ( parry_window: (
buildup: true, buildup: true,
recover: false, recover: false,

View File

@ -22,6 +22,6 @@ ComboMelee2(
energy_cost_per_strike: 5, energy_cost_per_strike: 5,
meta: ( meta: (
// The ability will parry melee attacks in the buildup & swing portion // 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, energy_cost_per_strike: 5,
meta: ( meta: (
// The ability will parry melee attacks in the buildup & swing portion // 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, auto_progress: true,
meta: ( meta: (
// The ability will parry melee attacks in the buildup & swing portion // 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, buildup_duration: 0.4,
recover_duration: 0.2, recover_duration: 0.2,
max_angle: 45.0, max_angle: 45.0,
block_strength: 0.75, block_strength: 7.5,
parry_window: ( parry_window: (
buildup: true, buildup: true,
recover: false, recover: false,

View File

@ -23,6 +23,6 @@ ComboMelee2(
energy_cost_per_strike: 5, energy_cost_per_strike: 5,
meta: ( meta: (
// The ability will parry melee attacks in the buildup & swing portion // 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, energy_cost_per_strike: 5,
meta: ( meta: (
// The ability will parry melee attacks in the buildup & swing portion // 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, buildup_duration: 0.4,
recover_duration: 0.15, recover_duration: 0.15,
max_angle: 60.0, max_angle: 60.0,
block_strength: 0.75, block_strength: 7.5,
parry_window: ( parry_window: (
buildup: true, buildup: true,
recover: false, recover: false,

View File

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

View File

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

View File

@ -708,6 +708,9 @@
Simple( Simple(
"common.items.weapons.shield.shield_1", "common.items.weapons.shield.shield_1",
): "weapon-shield-wood-0", ): "weapon-shield-wood-0",
Simple(
"common.items.weapons.shield.starter_shield",
): "weapon-shield-starter",
Simple( Simple(
"common.items.weapons.dagger.basic_0", "common.items.weapons.dagger.basic_0",
): "weapon-dagger-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.axe.starter_axe")),
(1.0, Item("common.items.weapons.staff.starter_staff")), (1.0, Item("common.items.weapons.staff.starter_staff")),
(1.0, Item("common.items.weapons.sceptre.starter_sceptre")), (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), 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.staff.starter_staff"): ( inputs: [], ),
ItemDefId("common.items.weapons.sceptre.starter_sceptre"): ( inputs: [], ), ItemDefId("common.items.weapons.sceptre.starter_sceptre"): ( inputs: [], ),
ItemDefId("common.items.weapons.sword_1h.starter"): ( inputs: [], ), ItemDefId("common.items.weapons.sword_1h.starter"): ( inputs: [], ),
ItemDefId("common.items.weapons.shield.starter_shield"): ( inputs: [], ),
ModularWeapon(material: "common.items.mineral.ingot.bronze"): ( ModularWeapon(material: "common.items.mineral.ingot.bronze"): (
inputs: [ inputs: [
(Item("common.items.mineral.ingot.bronze"), 1), (Item("common.items.mineral.ingot.bronze"), 1),

View File

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

View File

@ -1,6 +1,9 @@
weapon-shield-wood-0 = A Tattered Targe weapon-shield-wood-0 = A Tattered Targe
.desc = Should withstand a few more hits, hopefully... .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 weapon-dagger-dagger_basic-0 = Suspicious Paper Knife
.desc = Opens letters quickly. .desc = Opens letters quickly.

View File

@ -1554,7 +1554,11 @@
// Shields // Shields
Simple("common.items.weapons.shield.shield_1"): VoxTrans( Simple("common.items.weapons.shield.shield_1"): VoxTrans(
"voxel.weapon.shield.wood-0", "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 // Lanterns
Simple("common.items.lantern.black_0"): VoxTrans( Simple("common.items.lantern.black_0"): VoxTrans(

View File

@ -899,7 +899,11 @@
), ),
// Shields // Shields
Tool("common.items.weapons.shield.shield_1"): ( 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 color: None
), ),
// Bows // Bows

View File

@ -376,6 +376,7 @@
Simple("common.items.weapons.dagger.cultist_0"): "voxel.weapon.dagger.dagger_cult-0", Simple("common.items.weapons.dagger.cultist_0"): "voxel.weapon.dagger.dagger_cult-0",
// Shields // Shields
Simple("common.items.weapons.shield.shield_1"): "voxel.weapon.shield.wood-0", 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 // Lanterns
Simple("common.items.lantern.black_0"): "voxel.lantern.black-0", Simple("common.items.lantern.black_0"): "voxel.lantern.black-0",
Simple("common.items.lantern.green_0"): "voxel.lantern.green-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_TOP_HEADSHOT_PRECISION: f32 = 0.5;
pub const MAX_BEAM_DUR_PRECISION: f32 = 0.25; pub const MAX_BEAM_DUR_PRECISION: f32 = 0.25;
pub const MAX_MELEE_POISE_PRECISION: f32 = 0.5; 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)] #[derive(Copy, Clone)]
pub struct AttackerInfo<'a> { pub struct AttackerInfo<'a> {
@ -140,15 +143,84 @@ impl Attack {
pub fn effects(&self) -> impl Iterator<Item = &AttackEffect> { self.effects.iter() } 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>, attacker: Option<&AttackerInfo>,
damage_reduction: f32,
target: &TargetInfo, target: &TargetInfo,
source: AttackSource, source: AttackSource,
dir: Dir, dir: Dir,
damage: Damage, damage: Damage,
msm: &MaterialStatManifest, msm: &MaterialStatManifest,
emitters: &mut impl EmitExt<ParryHookEvent>, time: Time,
emitters: &mut (impl EmitExt<ParryHookEvent> + EmitExt<PoiseChangeEvent>),
mut emit_outcome: impl FnMut(Outcome), 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 { ) -> f32 {
if damage.value > 0.0 { if damage.value > 0.0 {
let attacker_penetration = attacker let attacker_penetration = attacker
@ -157,39 +229,7 @@ impl Attack {
.clamp(0.0, 1.0); .clamp(0.0, 1.0);
let raw_damage_reduction = let raw_damage_reduction =
Damage::compute_damage_reduction(Some(damage), target.inventory, target.stats, msm); Damage::compute_damage_reduction(Some(damage), target.inventory, target.stats, msm);
let damage_reduction = (1.0 - attacker_penetration) * raw_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)
} else { } else {
0.0 0.0
} }
@ -260,18 +300,26 @@ impl Attack {
{ {
let damage_instance = damage.instance + damage_instance_offset; let damage_instance = damage.instance + damage_instance_offset;
is_applied = true; 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(), attacker.as_ref(),
damage_reduction,
target, target,
attack_source, attack_source,
dir, dir,
damage.damage, damage.damage,
msm, msm,
time,
emitters, emitters,
&mut emit_outcome, &mut emit_outcome,
); );
let change = damage.damage.calculate_health_change( let change = damage.damage.calculate_health_change(
damage_reduction, damage_reduction,
block_damage_decrement,
attacker.map(|x| x.into()), attacker.map(|x| x.into()),
precision_mult, precision_mult,
self.precision_multiplier, self.precision_multiplier,
@ -1046,6 +1094,7 @@ impl Damage {
pub fn calculate_health_change( pub fn calculate_health_change(
self, self,
damage_reduction: f32, damage_reduction: f32,
block_damage_decrement: f32,
damage_contributor: Option<DamageContributor>, damage_contributor: Option<DamageContributor>,
precision_mult: Option<f32>, precision_mult: Option<f32>,
precision_power: f32, precision_power: f32,
@ -1065,6 +1114,8 @@ impl Damage {
damage += precise_damage; damage += precise_damage;
// Armor // Armor
damage *= 1.0 - damage_reduction; damage *= 1.0 - damage_reduction;
// Block
damage = f32::max(damage - block_damage_decrement, 0.0);
HealthChange { HealthChange {
amount: -damage, amount: -damage,
@ -1496,3 +1547,55 @@ pub fn precision_mult_from_flank(attack_dir: Vec3<f32>, target_ori: Option<&Ori>
Some(_) | None => None, 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 { match ability {
Ability::ToolGuard => ability_set(EquipSlot::ActiveMainhand) Ability::ToolGuard => {
.and_then(|abilities| { let equip_slot = combat::get_equip_slot_by_block_priority(inv);
abilities ability_set(equip_slot)
.guard(Some(skill_set), context) .and_then(|abilities| {
.map(|(a, i)| (a.ability.clone(), i)) abilities
}) .guard(Some(skill_set), context)
.map(|(ability, i)| { .map(|(a, i)| (a.ability.clone(), i))
( })
scale_ability(ability, EquipSlot::ActiveMainhand), .map(|(ability, i)| {
true, (
spec_ability(i), scale_ability(ability, equip_slot),
) matches!(equip_slot, EquipSlot::ActiveOffhand),
}) 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::ToolPrimary => ability_set(EquipSlot::ActiveMainhand) Ability::ToolPrimary => ability_set(EquipSlot::ActiveMainhand)
.and_then(|abilities| { .and_then(|abilities| {
abilities abilities
@ -396,7 +384,7 @@ impl Ability {
}; };
match self { match self {
Ability::ToolGuard => ability_set(EquipSlot::ActiveMainhand) Ability::ToolGuard => ability_set(combat::get_equip_slot_by_block_priority(inv))
.and_then(|abilities| { .and_then(|abilities| {
abilities abilities
.guard(skillset, context) .guard(skillset, context)
@ -407,19 +395,6 @@ impl Ability {
.as_ref() .as_ref()
.and_then(|g| contextual_id(Some(g))) .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| { Ability::ToolPrimary => ability_set(EquipSlot::ActiveMainhand).and_then(|abilities| {
abilities abilities
@ -524,12 +499,8 @@ impl SpecifiedAbility {
ability_set(EquipSlot::ActiveMainhand) ability_set(EquipSlot::ActiveMainhand)
.map(|abilities| ability_id(self, &abilities.secondary)) .map(|abilities| ability_id(self, &abilities.secondary))
}), }),
Ability::ToolGuard => ability_set(EquipSlot::ActiveMainhand) 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))) .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::SpeciesMovement => None, // TODO: Make not None Ability::SpeciesMovement => None, // TODO: Make not None
Ability::MainWeaponAux(index) => ability_set(EquipSlot::ActiveMainhand) Ability::MainWeaponAux(index) => ability_set(EquipSlot::ActiveMainhand)
.and_then(|abilities| abilities.abilities.get(index).map(|a| ability_id(self, a))), .and_then(|abilities| abilities.abilities.get(index).map(|a| ability_id(self, a))),
@ -1006,6 +977,7 @@ pub enum CharacterAbility {
buildup_duration: f32, buildup_duration: f32,
swing_duration: f32, swing_duration: f32,
recover_duration: f32, recover_duration: f32,
block_strength: f32,
melee_constructor: MeleeConstructor, melee_constructor: MeleeConstructor,
#[serde(default)] #[serde(default)]
meta: AbilityMeta, meta: AbilityMeta,
@ -1279,8 +1251,7 @@ impl CharacterAbility {
ref mut recover_duration, ref mut recover_duration,
// Do we want angle to be adjusted by range? // Do we want angle to be adjusted by range?
max_angle: _, max_angle: _,
// Block strength explicitly not modified by power, that will be a separate stat ref mut block_strength,
block_strength: _,
parry_window: _, parry_window: _,
ref mut energy_cost, ref mut energy_cost,
energy_regen: _, energy_regen: _,
@ -1291,6 +1262,7 @@ impl CharacterAbility {
*buildup_duration /= stats.speed; *buildup_duration /= stats.speed;
*recover_duration /= stats.speed; *recover_duration /= stats.speed;
*energy_cost /= stats.energy_efficiency; *energy_cost /= stats.energy_efficiency;
*block_strength *= stats.power;
}, },
Roll { Roll {
ref mut energy_cost, ref mut energy_cost,
@ -1660,6 +1632,7 @@ impl CharacterAbility {
ref mut buildup_duration, ref mut buildup_duration,
ref mut swing_duration, ref mut swing_duration,
ref mut recover_duration, ref mut recover_duration,
ref mut block_strength,
ref mut melee_constructor, ref mut melee_constructor,
meta: _, meta: _,
} => { } => {
@ -1667,6 +1640,7 @@ impl CharacterAbility {
*swing_duration /= stats.speed; *swing_duration /= stats.speed;
*recover_duration /= stats.speed; *recover_duration /= stats.speed;
*energy_cost /= stats.energy_efficiency; *energy_cost /= stats.energy_efficiency;
*block_strength *= stats.power;
*melee_constructor = melee_constructor.adjusted_by_stats(stats); *melee_constructor = melee_constructor.adjusted_by_stats(stats);
}, },
RapidMelee { RapidMelee {
@ -2914,6 +2888,7 @@ impl From<(&CharacterAbility, AbilityInfo, &JoinData<'_>)> for CharacterState {
buildup_duration, buildup_duration,
swing_duration, swing_duration,
recover_duration, recover_duration,
block_strength,
melee_constructor, melee_constructor,
meta: _, meta: _,
} => CharacterState::RiposteMelee(riposte_melee::Data { } => CharacterState::RiposteMelee(riposte_melee::Data {
@ -2921,6 +2896,7 @@ impl From<(&CharacterAbility, AbilityInfo, &JoinData<'_>)> for CharacterState {
buildup_duration: Duration::from_secs_f32(*buildup_duration), buildup_duration: Duration::from_secs_f32(*buildup_duration),
swing_duration: Duration::from_secs_f32(*swing_duration), swing_duration: Duration::from_secs_f32(*swing_duration),
recover_duration: Duration::from_secs_f32(*recover_duration), recover_duration: Duration::from_secs_f32(*recover_duration),
block_strength: *block_strength,
melee_constructor: *melee_constructor, melee_constructor: *melee_constructor,
ability_info, ability_info,
}, },
@ -3003,18 +2979,18 @@ bitflags::bitflags! {
#[derive(Copy, Clone, Debug, PartialEq, Eq, Default, Serialize, Deserialize)] #[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 // 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 { pub struct Capability: u8 {
// There used to be a capability here, to keep ordering the same below this is now a placeholder // The ability will parry all blockable attacks in the buildup portion
const PLACEHOLDER = 0b00000001; const PARRIES = 0b00000001;
// Allows blocking to interrupt the ability at any point // Allows blocking to interrupt the ability at any point
const BLOCK_INTERRUPT = 0b00000010; 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; const BLOCKS = 0b00000100;
// When in the ability, an entity only receives half as much poise damage // When in the ability, an entity only receives half as much poise damage
const POISE_RESISTANT = 0b00001000; const POISE_RESISTANT = 0b00001000;
// WHen in the ability, an entity only receives half as much knockback // WHen in the ability, an entity only receives half as much knockback
const KNOCKBACK_RESISTANT = 0b00010000; const KNOCKBACK_RESISTANT = 0b00010000;
// The ability will parry melee attacks in the buildup portion // 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 { pub fn is_parry(&self, attack_source: AttackSource) -> bool {
let melee = matches!(attack_source, AttackSource::Melee); let melee = matches!(attack_source, AttackSource::Melee);
let from_capability = melee let from_capability_melee = melee
&& self && self
.ability_info() .ability_info()
.map(|a| a.ability_meta.capabilities) .map(|a| a.ability_meta.capabilities)
.map_or(false, |c| { .map_or(false, |c| {
c.contains(Capability::PARRIES) c.contains(Capability::PARRIES_MELEE)
&& matches!( && matches!(
self.stage_section(), self.stage_section(),
Some(StageSection::Buildup | StageSection::Action) 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 { let from_state = match self {
CharacterState::BasicBlock(c) => c.is_parry(attack_source), CharacterState::BasicBlock(c) => c.is_parry(attack_source),
CharacterState::RiposteMelee(c) => { CharacterState::RiposteMelee(c) => {
@ -366,7 +349,30 @@ impl CharacterState {
}, },
_ => false, _ => 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 /// In radians

View File

@ -97,6 +97,28 @@ impl ToolKind {
| ToolKind::Dagger | 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)] #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]

View File

@ -25,7 +25,8 @@ pub struct StaticData {
pub recover_duration: Duration, pub recover_duration: Duration,
/// Max angle (45.0 will give you a 90.0 angle window) /// Max angle (45.0 will give you a 90.0 angle window)
pub max_angle: f32, 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, pub block_strength: f32,
/// What durations are considered a parry /// What durations are considered a parry
pub parry_window: ParryWindow, pub parry_window: ParryWindow,

View File

@ -18,6 +18,9 @@ pub struct StaticData {
pub swing_duration: Duration, pub swing_duration: Duration,
/// How long the state has until exiting /// How long the state has until exiting
pub recover_duration: Duration, 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 /// Used to construct the Melee attack
pub melee_constructor: MeleeConstructor, pub melee_constructor: MeleeConstructor,
/// What key is used to press ability /// 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 { pub fn get_tool_stats(data: &JoinData<'_>, ai: AbilityInfo) -> tool::Stats {
ai.hand ai.hand
.map(|hand| match hand { .map(|hand| hand.to_equip_slot())
HandInfo::TwoHanded | HandInfo::MainHand => EquipSlot::ActiveMainhand,
HandInfo::OffHand => EquipSlot::ActiveOffhand,
})
.and_then(|slot| data.inventory.and_then(|inv| inv.equipped(slot))) .and_then(|slot| data.inventory.and_then(|inv| inv.equipped(slot)))
.and_then(|item| { .and_then(|item| {
if let ItemKind::Tool(tool) = &*item.kind() { 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) { 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( let change = damage.calculate_health_change(
damage_reduction, damage_reduction,
0.0,
None, None,
None, None,
0.0, 0.0,
@ -1401,6 +1402,7 @@ pub fn emit_effect_events(
common::effect::Effect::Damage(damage) => { common::effect::Effect::Damage(damage) => {
let change = damage.calculate_health_change( let change = damage.calculate_health_change(
combat::Damage::compute_damage_reduction(Some(damage), inventory, stats, msm), combat::Damage::compute_damage_reduction(Some(damage), inventory, stats, msm),
0.0,
damage_contributor, damage_contributor,
None, None,
0.0, 0.0,
@ -1677,7 +1679,7 @@ impl ServerEvent for ParryHookEvent {
entity: ev.defender, entity: ev.defender,
change: c.static_data.energy_regen, change: c.static_data.energy_regen,
}); });
true false
}, },
_ => false, _ => false,
}; };

View File

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

View File

@ -55,7 +55,9 @@ impl Animation for BlockAnimation {
| Some("common.abilities.sword.basic_guard") | Some("common.abilities.sword.basic_guard")
| Some("common.abilities.axe.basic_guard") | Some("common.abilities.axe.basic_guard")
| Some("common.abilities.hammer.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 speed = Vec2::<f32>::from(velocity).magnitude();
let (movement1base, move2, movement3) = match stage_section { let (movement1base, move2, movement3) = match stage_section {
@ -215,53 +217,95 @@ impl Animation for BlockAnimation {
* Quaternion::rotation_y(0.6) * Quaternion::rotation_y(0.6)
* Quaternion::rotation_z(0.0); * 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") => { Some("common.abilities.sword.defensive_deflect") => {
let (move1, move2, move3) = match stage_section { let (move1, move2, move3) = match stage_section {

View File

@ -2,7 +2,7 @@ use super::{
super::{vek::*, Animation}, super::{vek::*, Animation},
CharacterSkeleton, SkeletonAttr, CharacterSkeleton, SkeletonAttr,
}; };
use common::states::utils::{AbilityInfo, StageSection}; use common::states::utils::{AbilityInfo, HandInfo, StageSection};
use core::f32::consts::{PI, TAU}; use core::f32::consts::{PI, TAU};
pub struct ComboAnimation; pub struct ComboAnimation;
@ -1133,6 +1133,81 @@ impl Animation for ComboAnimation {
next.control.orientation.rotate_x(move2 * -1.2); next.control.orientation.rotate_x(move2 * -1.2);
next.control.position += Vec3::new(move2 * 8.0, 0.0, 0.0); 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 = self.main.orientation =
Quaternion::rotation_y(-0.5) * Quaternion::rotation_z(PI / 2.0); 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.position = Vec3::new(-7.0, -5.0 - self.back_carry_offset, 15.0);
self.main.orientation = self.main.orientation =
@ -382,7 +387,7 @@ impl CharacterSkeleton {
Quaternion::rotation_y(2.5) * Quaternion::rotation_z(PI / 2.0); Quaternion::rotation_y(2.5) * Quaternion::rotation_z(PI / 2.0);
}, },
Some(ToolKind::Shield) => { 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 = self.main.orientation =
Quaternion::rotation_y(0.25 * PI) * Quaternion::rotation_z(-1.5 * PI); 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); Quaternion::rotation_y(-2.5) * Quaternion::rotation_z(-PI / 2.0);
}, },
Some(ToolKind::Shield) => { 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 = self.second.orientation =
Quaternion::rotation_y(-0.25 * PI) * Quaternion::rotation_z(1.5 * PI); 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); 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, previous_state: &PreviousEntityState,
inventory: &Inventory, inventory: &Inventory,
) -> SfxEvent { ) -> 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 let ItemKind::Tool(data) = &*item.kind() {
if character_state.is_attack() { if character_state.is_attack() {
return SfxEvent::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::Bow) => self.imgs.icon_log,
RecipeKind::Component(ToolKind::Staff) => self.imgs.icon_log, RecipeKind::Component(ToolKind::Staff) => self.imgs.icon_log,
RecipeKind::Component(ToolKind::Sceptre) => self.imgs.icon_log, RecipeKind::Component(ToolKind::Sceptre) => self.imgs.icon_log,
RecipeKind::Component(ToolKind::Shield) => self.imgs.icon_ingot,
_ => self.imgs.not_found, _ => 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.healingaura" => imgs.skill_sceptre_heal,
"common.abilities.sceptre.wardingaura" => imgs.skill_sceptre_aura, "common.abilities.sceptre.wardingaura" => imgs.skill_sceptre_aura,
// Shield // Shield
"common.abilities.shield.tempbasic" => imgs.onehshield_m1, "common.abilities.shield.singlestrike" => imgs.onehshield_m1,
"common.abilities.shield.block" => imgs.onehshield_m2, "common.abilities.shield.power_guard" => imgs.onehshield_m1,
// Dagger // Dagger
"common.abilities.dagger.tempbasic" => imgs.onehdagger_m1, "common.abilities.dagger.tempbasic" => imgs.onehdagger_m1,
// Pickaxe // Pickaxe