Merge branch 'sam/block-parry' into 'master'

Blocking/Parrying

Closes  and 

See merge request 
This commit is contained in:
Samuel Keiffer 2021-04-25 18:25:24 +00:00
commit ccc22d4969
41 changed files with 824 additions and 209 deletions

View File

@ -47,6 +47,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Missing translations can be displayed in English.
- New large birds npcs
- Day period dependant wildlife spawns
- You can now block and parry with melee weapons
### Changed

View File

@ -1 +1,7 @@
BasicBlock
BasicBlock(
buildup_duration: 0.1,
recover_duration: 0.1,
max_angle: 90.0,
block_strength: 0.8,
energy_cost: 0.0,
)

View File

@ -1,5 +1,5 @@
SpinMelee(
buildup_duration: 0.6,
buildup_duration: 0.35,
swing_duration: 0.4,
recover_duration: 0.5,
base_damage: 160,

BIN
assets/voxygen/audio/ambient/wind.ogg (Stored with Git LFS)

Binary file not shown.

BIN
assets/voxygen/audio/sfx/character/block_1.ogg (Stored with Git LFS) Normal file

Binary file not shown.

BIN
assets/voxygen/audio/sfx/character/block_2.ogg (Stored with Git LFS) Normal file

Binary file not shown.

BIN
assets/voxygen/audio/sfx/character/block_3.ogg (Stored with Git LFS) Normal file

Binary file not shown.

BIN
assets/voxygen/audio/sfx/character/parry_1.ogg (Stored with Git LFS) Normal file

Binary file not shown.

BIN
assets/voxygen/audio/sfx/character/parry_2.ogg (Stored with Git LFS) Normal file

Binary file not shown.

View File

@ -4,7 +4,8 @@
(
string_map: {
"gameinput.primary": "Basic Attack",
"gameinput.secondary": "Secondary Attack/Block/Aim",
"gameinput.secondary": "Secondary Attack",
"gameinput.block": "Block",
"gameinput.slot1": "Hotbar Slot 1",
"gameinput.slot2": "Hotbar Slot 2",
"gameinput.slot3": "Hotbar Slot 3",

View File

@ -202,7 +202,7 @@ void main() {
attr = Attr(
linear_motion(
normalize(vec3(rand0, rand1, rand3)) * 0.3,
normalize(vec3(rand4, rand5, rand6)) * 2.0 + grav_vel(earth_gravity)
normalize(vec3(rand4, rand5, rand6)) * 4.0 + grav_vel(earth_gravity)
),
vec3(1.0),
vec4(3.5, 3 + rand7, 0, 1),

View File

@ -12,11 +12,12 @@ use crate::{
},
poise::PoiseChange,
skills::SkillGroupKind,
Body, Combo, Energy, EnergyChange, EnergySource, Health, HealthChange, HealthSource,
Inventory, SkillSet, Stats,
Body, CharacterState, Combo, Energy, EnergyChange, EnergySource, Health, HealthChange,
HealthSource, Inventory, Ori, SkillSet, Stats,
},
event::ServerEvent,
outcome::Outcome,
states::utils::StageSection,
uid::Uid,
util::Dir,
};
@ -39,6 +40,15 @@ pub enum GroupTarget {
OutOfGroup,
}
#[derive(Copy, Clone, Debug, Serialize, Deserialize)]
pub enum AttackSource {
Melee,
Projectile,
Beam,
Shockwave,
Explosion,
}
#[cfg(not(target_arch = "wasm32"))]
#[derive(Copy, Clone)]
pub struct AttackerInfo<'a> {
@ -51,10 +61,13 @@ pub struct AttackerInfo<'a> {
#[cfg(not(target_arch = "wasm32"))]
pub struct TargetInfo<'a> {
pub entity: EcsEntity,
pub uid: Uid,
pub inventory: Option<&'a Inventory>,
pub stats: Option<&'a Stats>,
pub health: Option<&'a Health>,
pub pos: Vec3<f32>,
pub ori: Option<&'a Ori>,
pub char_state: Option<&'a CharacterState>,
}
#[cfg(not(target_arch = "wasm32"))]
@ -105,6 +118,43 @@ impl Attack {
pub fn effects(&self) -> impl Iterator<Item = &AttackEffect> { self.effects.iter() }
pub fn compute_damage_reduction(
target: &TargetInfo,
source: AttackSource,
dir: Dir,
mut emit_outcome: impl FnMut(Outcome),
) -> f32 {
let damage_reduction = Damage::compute_damage_reduction(target.inventory, target.stats);
let block_reduction = match source {
AttackSource::Melee => {
if let (Some(CharacterState::BasicBlock(data)), Some(ori)) =
(target.char_state, target.ori)
{
if ori.look_vec().angle_between(-*dir) < data.static_data.max_angle.to_radians()
{
let parry = matches!(data.stage_section, StageSection::Buildup);
emit_outcome(Outcome::Block {
parry,
pos: target.pos,
uid: target.uid,
});
if parry {
1.0
} else {
data.static_data.block_strength
}
} else {
0.0
}
} else {
0.0
}
},
_ => 0.0,
};
1.0 - (1.0 - damage_reduction) * (1.0 - block_reduction)
}
#[allow(clippy::too_many_arguments)]
pub fn apply_attack(
&self,
@ -115,6 +165,7 @@ impl Attack {
target_dodging: bool,
// Currently just modifies damage, maybe look into modifying strength of other effects?
strength_modifier: f32,
attack_source: AttackSource,
mut emit: impl FnMut(ServerEvent),
mut emit_outcome: impl FnMut(Outcome),
) {
@ -126,7 +177,8 @@ impl Attack {
.filter(|d| d.target.map_or(true, |t| t == target_group))
.filter(|d| !(matches!(d.target, Some(GroupTarget::OutOfGroup)) && target_dodging))
{
let damage_reduction = Damage::compute_damage_reduction(target.inventory, target.stats);
let damage_reduction =
Attack::compute_damage_reduction(&target, attack_source, dir, |o| emit_outcome(o));
let change = damage.damage.calculate_health_change(
damage_reduction,
attacker.map(|a| a.uid),

View File

@ -39,7 +39,7 @@ impl From<&CharacterState> for CharacterAbilityType {
CharacterState::BasicRanged(_) => Self::BasicRanged,
CharacterState::Boost(_) => Self::Boost,
CharacterState::DashMelee(data) => Self::DashMelee(data.stage_section),
CharacterState::BasicBlock => Self::BasicBlock,
CharacterState::BasicBlock(_) => Self::BasicBlock,
CharacterState::LeapMelee(data) => Self::LeapMelee(data.stage_section),
CharacterState::ComboMelee(data) => Self::ComboMelee(data.stage_section, data.stage),
CharacterState::SpinMelee(data) => Self::SpinMelee(data.stage_section),
@ -114,7 +114,13 @@ pub enum CharacterAbility {
charge_through: bool,
is_interruptible: bool,
},
BasicBlock,
BasicBlock {
buildup_duration: f32,
recover_duration: f32,
max_angle: f32,
block_strength: f32,
energy_cost: f32,
},
Roll {
energy_cost: f32,
buildup_duration: f32,
@ -305,7 +311,8 @@ impl CharacterAbility {
| CharacterAbility::ChargedRanged { energy_cost, .. }
| CharacterAbility::ChargedMelee { energy_cost, .. }
| CharacterAbility::Shockwave { energy_cost, .. }
| CharacterAbility::BasicAura { energy_cost, .. } => update
| CharacterAbility::BasicAura { energy_cost, .. }
| CharacterAbility::BasicBlock { energy_cost, .. } => update
.energy
.try_change_by(-(*energy_cost as i32), EnergySource::Ability)
.is_ok(),
@ -341,6 +348,16 @@ impl CharacterAbility {
}
}
pub fn default_block() -> CharacterAbility {
CharacterAbility::BasicBlock {
buildup_duration: 0.35,
recover_duration: 0.3,
max_angle: 60.0,
block_strength: 0.5,
energy_cost: 50.0,
}
}
pub fn adjusted_by_stats(mut self, power: f32, poise_strength: f32, speed: f32) -> Self {
use CharacterAbility::*;
match self {
@ -408,7 +425,15 @@ impl CharacterAbility {
*swing_duration /= speed;
*recover_duration /= speed;
},
BasicBlock => {},
BasicBlock {
ref mut buildup_duration,
ref mut recover_duration,
// Block strength explicitly not modified by power, that will be a separate stat
..
} => {
*buildup_duration /= speed;
*recover_duration /= speed;
},
Roll {
ref mut buildup_duration,
ref mut movement_duration,
@ -578,7 +603,8 @@ impl CharacterAbility {
| ChargedRanged { energy_cost, .. }
| Shockwave { energy_cost, .. }
| HealingBeam { energy_cost, .. }
| BasicAura { energy_cost, .. } => *energy_cost as u32,
| BasicAura { energy_cost, .. }
| BasicBlock { energy_cost, .. } => *energy_cost as u32,
BasicBeam { energy_drain, .. } => {
if *energy_drain > f32::EPSILON {
1
@ -586,7 +612,7 @@ impl CharacterAbility {
0
}
},
BasicBlock | Boost { .. } | ComboMelee { .. } | Blink { .. } | BasicSummon { .. } => 0,
Boost { .. } | ComboMelee { .. } | Blink { .. } | BasicSummon { .. } => 0,
}
}
@ -1235,7 +1261,23 @@ impl From<(&CharacterAbility, AbilityInfo)> for CharacterState {
stage_section: StageSection::Buildup,
exhausted: false,
}),
CharacterAbility::BasicBlock => CharacterState::BasicBlock,
CharacterAbility::BasicBlock {
buildup_duration,
recover_duration,
max_angle,
block_strength,
energy_cost: _,
} => CharacterState::BasicBlock(basic_block::Data {
static_data: basic_block::StaticData {
buildup_duration: Duration::from_secs_f32(*buildup_duration),
recover_duration: Duration::from_secs_f32(*recover_duration),
max_angle: *max_angle,
block_strength: *block_strength,
ability_info,
},
timer: Duration::default(),
stage_section: StageSection::Buildup,
}),
CharacterAbility::Roll {
energy_cost: _,
buildup_duration,

View File

@ -55,7 +55,7 @@ pub enum CharacterState {
/// A stunned state
Stunned(stunned::Data),
/// A basic blocking state
BasicBlock,
BasicBlock(basic_block::Data),
/// Player is busy equipping or unequipping weapons
Equipping(equipping::Data),
/// Player is holding a weapon and can perform other actions
@ -110,7 +110,7 @@ impl CharacterState {
| CharacterState::BasicRanged(_)
| CharacterState::DashMelee(_)
| CharacterState::ComboMelee(_)
| CharacterState::BasicBlock
| CharacterState::BasicBlock(_)
| CharacterState::LeapMelee(_)
| CharacterState::SpinMelee(_)
| CharacterState::ChargedMelee(_)
@ -153,7 +153,7 @@ impl CharacterState {
| CharacterState::BasicRanged(_)
| CharacterState::DashMelee(_)
| CharacterState::ComboMelee(_)
| CharacterState::BasicBlock
| CharacterState::BasicBlock(_)
| CharacterState::LeapMelee(_)
| CharacterState::ChargedMelee(_)
| CharacterState::ChargedRanged(_)
@ -180,7 +180,7 @@ impl CharacterState {
)
}
pub fn is_block(&self) -> bool { matches!(self, CharacterState::BasicBlock) }
pub fn is_block(&self) -> bool { matches!(self, CharacterState::BasicBlock(_)) }
pub fn is_dodge(&self) -> bool { matches!(self, CharacterState::Roll(_)) }

View File

@ -149,15 +149,19 @@ impl ControlAction {
pub enum InputKind {
Primary = 0,
Secondary = 1,
Ability(usize) = 2,
Roll = 3,
Jump = 4,
Fly = 5,
Block = 2,
Ability(usize) = 3,
Roll = 4,
Jump = 5,
Fly = 6,
}
impl InputKind {
pub fn is_ability(self) -> bool {
matches!(self, Self::Primary | Self::Secondary | Self::Ability(_))
matches!(
self,
Self::Primary | Self::Secondary | Self::Ability(_) | Self::Block
)
}
}

View File

@ -326,6 +326,17 @@ impl Tool {
Default::default()
}
}
pub fn can_block(&self) -> bool {
matches!(
self.kind,
ToolKind::Sword
| ToolKind::Axe
| ToolKind::Hammer
| ToolKind::Shield
| ToolKind::Dagger
)
}
}
#[derive(Clone, Debug, Serialize, Deserialize)]

View File

@ -59,6 +59,11 @@ pub enum Outcome {
Damage {
pos: Vec3<f32>,
},
Block {
pos: Vec3<f32>,
parry: bool,
uid: Uid,
},
}
impl Outcome {
@ -70,7 +75,8 @@ impl Outcome {
| Outcome::Beam { pos, .. }
| Outcome::SkillPointGain { pos, .. }
| Outcome::SummonedCreature { pos, .. }
| Outcome::Damage { pos, .. } => Some(*pos),
| Outcome::Damage { pos, .. }
| Outcome::Block { pos, .. } => Some(*pos),
Outcome::BreakBlock { pos, .. } => Some(pos.map(|e| e as f32 + 0.5)),
Outcome::ExpChange { .. } | Outcome::ComboChange { .. } => None,
}

View File

@ -1,15 +1,36 @@
use super::utils::*;
use crate::{
comp::StateUpdate,
comp::{CharacterState, InputKind, StateUpdate},
states::behavior::{CharacterBehavior, JoinData},
};
use serde::{Deserialize, Serialize};
use std::time::Duration;
// const BLOCK_ACCEL: f32 = 30.0;
// const BLOCK_SPEED: f32 = 75.0;
/// Separated out to condense update portions of character state
#[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)]
pub struct StaticData {
/// How long until state should deal damage
pub buildup_duration: Duration,
/// How long the state has until exiting
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
pub block_strength: f32,
/// What key is used to press ability
pub ability_info: AbilityInfo,
}
#[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize, Eq, Hash)]
pub struct Data;
#[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)]
pub struct Data {
/// Struct containing data that does not change over the course of the
/// character state
pub static_data: StaticData,
/// Timer for each stage
pub timer: Duration,
/// What section the character stage is in
pub stage_section: StageSection,
}
impl CharacterBehavior for Data {
fn behavior(&self, data: &JoinData) -> StateUpdate {
@ -17,6 +38,71 @@ impl CharacterBehavior for Data {
handle_move(&data, &mut update, 0.4);
match self.stage_section {
StageSection::Buildup => {
if self.timer < self.static_data.buildup_duration {
// Build up
update.character = CharacterState::BasicBlock(Data {
timer: self
.timer
.checked_add(Duration::from_secs_f32(data.dt.0))
.unwrap_or_default(),
..*self
});
} else {
// Transitions to swing section of stage
update.character = CharacterState::BasicBlock(Data {
timer: Duration::default(),
stage_section: StageSection::Swing,
..*self
});
}
},
StageSection::Swing => {
if input_is_pressed(data, InputKind::Block) {
// Block
update.character = CharacterState::BasicBlock(Data {
timer: self
.timer
.checked_add(Duration::from_secs_f32(data.dt.0))
.unwrap_or_default(),
..*self
});
} else {
// Transitions to recover section of stage
update.character = CharacterState::BasicBlock(Data {
timer: Duration::default(),
stage_section: StageSection::Recover,
..*self
});
}
},
StageSection::Recover => {
if self.timer < self.static_data.recover_duration {
// Recovery
update.character = CharacterState::BasicBlock(Data {
timer: self
.timer
.checked_add(Duration::from_secs_f32(data.dt.0))
.unwrap_or_default(),
..*self
});
} else {
// Done
update.character = CharacterState::Wielding;
}
},
_ => {
// If it somehow ends up in an incorrect stage section
update.character = CharacterState::Wielding;
},
}
// At end of state logic so an interrupt isn't overwritten
if !input_is_pressed(data, self.static_data.ability_info.input) {
handle_state_interrupt(data, &mut update, false);
}
update
}
}

View File

@ -538,21 +538,17 @@ pub fn handle_jump(data: &JoinData, update: &mut StateUpdate, strength: f32) ->
}
fn handle_ability(data: &JoinData, update: &mut StateUpdate, input: InputKind) {
let hands = |equip_slot| match data.inventory.equipped(equip_slot).map(|i| i.kind()) {
Some(ItemKind::Tool(tool)) => Some(tool.hands),
_ => None,
};
let hands = get_hands(data);
// Mouse1 and Skill1 always use the MainHand slot
let always_main_hand = matches!(input, InputKind::Primary | InputKind::Ability(0));
let no_main_hand = hands(EquipSlot::Mainhand).is_none();
let no_main_hand = hands.0.is_none();
// skill_index used to select ability for the AbilityKey::Skill2 input
let (equip_slot, skill_index) = if no_main_hand {
(Some(EquipSlot::Offhand), 1)
} else if always_main_hand {
(Some(EquipSlot::Mainhand), 0)
} else {
let hands = (hands(EquipSlot::Mainhand), hands(EquipSlot::Offhand));
match hands {
(Some(Hands::Two), _) => (Some(EquipSlot::Mainhand), 1),
(_, Some(Hands::One)) => (Some(EquipSlot::Offhand), 0),
@ -579,7 +575,7 @@ fn handle_ability(data: &JoinData, update: &mut StateUpdate, input: InputKind) {
.get(skill_index)
.cloned()
.and_then(unlocked),
InputKind::Roll | InputKind::Jump | InputKind::Fly => None,
InputKind::Roll | InputKind::Jump | InputKind::Fly | InputKind::Block => None,
})
.map(|a| {
let tool = unwrap_tool_data(data, equip_slot).map(|t| t.kind);
@ -615,6 +611,7 @@ pub fn handle_input(data: &JoinData, update: &mut StateUpdate, input: InputKind)
InputKind::Jump => {
handle_jump(data, update, 1.0);
},
InputKind::Block => handle_block_input(data, update),
InputKind::Fly => {},
}
}
@ -626,6 +623,24 @@ pub fn attempt_input(data: &JoinData, update: &mut StateUpdate) {
}
}
/// Checks that player can block, then attempts to block
pub fn handle_block_input(data: &JoinData, update: &mut StateUpdate) {
let can_block =
|equip_slot| matches!(unwrap_tool_data(data, equip_slot), Some(tool) if tool.can_block());
let hands = get_hands(data);
if input_is_pressed(data, InputKind::Block)
&& (can_block(EquipSlot::Mainhand) || (hands.0.is_none() && can_block(EquipSlot::Offhand)))
{
let ability = CharacterAbility::default_block();
if ability.requirements_paid(data, update) {
update.character = CharacterState::from((
&ability,
AbilityInfo::from_input(data, false, InputKind::Roll),
));
}
}
}
/// Checks that player can perform a dodge, then
/// attempts to perform their dodge ability
pub fn handle_dodge_input(data: &JoinData, update: &mut StateUpdate) {
@ -662,6 +677,17 @@ pub fn unwrap_tool_data<'a>(data: &'a JoinData, equip_slot: EquipSlot) -> Option
}
}
pub fn get_hands(data: &JoinData) -> (Option<Hands>, Option<Hands>) {
let hand = |slot| {
if let Some(ItemKind::Tool(tool)) = data.inventory.equipped(slot).map(|i| i.kind()) {
Some(tool.hands)
} else {
None
}
};
(hand(EquipSlot::Mainhand), hand(EquipSlot::Offhand))
}
pub fn get_crit_data(data: &JoinData, ai: AbilityInfo) -> (f32, f32) {
const DEFAULT_CRIT_DATA: (f32, f32) = (0.5, 1.3);
use HandInfo::*;

View File

@ -1,8 +1,8 @@
use common::{
combat::{AttackerInfo, TargetInfo},
combat::{AttackSource, AttackerInfo, TargetInfo},
comp::{
Beam, BeamSegment, Body, Combo, Energy, Group, Health, HealthSource, Inventory, Ori, Pos,
Scale, Stats,
Beam, BeamSegment, Body, CharacterState, Combo, Energy, Group, Health, HealthSource,
Inventory, Ori, Pos, Scale, Stats,
},
event::{EventBus, ServerEvent},
outcome::Outcome,
@ -40,6 +40,7 @@ pub struct ReadData<'a> {
energies: ReadStorage<'a, Energy>,
stats: ReadStorage<'a, Stats>,
combos: ReadStorage<'a, Combo>,
character_states: ReadStorage<'a, CharacterState>,
}
/// This system is responsible for handling beams that heal or do damage
@ -184,10 +185,13 @@ impl<'a> System<'a> for Sys {
let target_info = TargetInfo {
entity: target,
uid: *uid_b,
inventory: read_data.inventories.get(target),
stats: read_data.stats.get(target),
health: read_data.healths.get(target),
pos: pos.0,
pos: pos_b.0,
ori: read_data.orientations.get(target),
char_state: read_data.character_states.get(target),
};
beam_segment.properties.attack.apply_attack(
@ -197,6 +201,7 @@ impl<'a> System<'a> for Sys {
ori.look_dir(),
false,
1.0,
AttackSource::Beam,
|e| server_events.push(e),
|o| outcomes.push(o),
);

View File

@ -301,9 +301,7 @@ impl<'a> System<'a> for Sys {
CharacterState::Sneak => {
states::sneak::Data::handle_event(&states::sneak::Data, &j, action)
},
CharacterState::BasicBlock => {
states::basic_block::Data.handle_event(&j, action)
},
CharacterState::BasicBlock(data) => data.handle_event(&j, action),
CharacterState::Roll(data) => data.handle_event(&j, action),
CharacterState::Wielding => states::wielding::Data.handle_event(&j, action),
CharacterState::Equipping(data) => data.handle_event(&j, action),
@ -357,7 +355,7 @@ impl<'a> System<'a> for Sys {
CharacterState::Sit => states::sit::Data::behavior(&states::sit::Data, &j),
CharacterState::Dance => states::dance::Data::behavior(&states::dance::Data, &j),
CharacterState::Sneak => states::sneak::Data::behavior(&states::sneak::Data, &j),
CharacterState::BasicBlock => states::basic_block::Data.behavior(&j),
CharacterState::BasicBlock(data) => data.behavior(&j),
CharacterState::Roll(data) => data.behavior(&j),
CharacterState::Wielding => states::wielding::Data.behavior(&j),
CharacterState::Equipping(data) => data.behavior(&j),

View File

@ -1,5 +1,5 @@
use common::{
combat::{AttackerInfo, TargetInfo},
combat::{AttackSource, AttackerInfo, TargetInfo},
comp::{
Body, CharacterState, Combo, Energy, Group, Health, Inventory, Melee, Ori, Pos, Scale,
Stats,
@ -87,11 +87,12 @@ impl<'a> System<'a> for Sys {
}
// Go through all other entities
for (target, pos_b, health_b, body_b) in (
for (target, pos_b, health_b, body_b, uid_b) in (
&read_data.entities,
&read_data.positions,
&read_data.healths,
&read_data.bodies,
&read_data.uids,
)
.join()
{
@ -143,10 +144,13 @@ impl<'a> System<'a> for Sys {
let target_info = TargetInfo {
entity: target,
uid: *uid_b,
inventory: read_data.inventories.get(target),
stats: read_data.stats.get(target),
health: read_data.healths.get(target),
pos: pos.0,
pos: pos_b.0,
ori: read_data.orientations.get(target),
char_state: read_data.char_states.get(target),
};
melee_attack.attack.apply_attack(
@ -156,6 +160,7 @@ impl<'a> System<'a> for Sys {
dir,
is_dodge,
1.0,
AttackSource::Melee,
|e| server_emitter.emit(e),
|o| outcomes.push(o),
);

View File

@ -1,8 +1,8 @@
use common::{
combat::{AttackerInfo, TargetInfo},
combat::{AttackSource, AttackerInfo, TargetInfo},
comp::{
projectile, Body, Combo, Energy, Group, Health, HealthSource, Inventory, Ori, PhysicsState,
Pos, Projectile, Stats, Vel,
projectile, Body, CharacterState, Combo, Energy, Group, Health, HealthSource, Inventory,
Ori, PhysicsState, Pos, Projectile, Stats, Vel,
},
event::{EventBus, ServerEvent},
outcome::Outcome,
@ -36,6 +36,7 @@ pub struct ReadData<'a> {
combos: ReadStorage<'a, Combo>,
healths: ReadStorage<'a, Health>,
bodies: ReadStorage<'a, Body>,
character_states: ReadStorage<'a, CharacterState>,
}
/// This system is responsible for handling projectile effect triggers
@ -60,11 +61,11 @@ impl<'a> System<'a> for Sys {
let mut server_emitter = read_data.server_bus.emitter();
// Attacks
'projectile_loop: for (entity, pos, physics, ori, mut projectile) in (
'projectile_loop: for (entity, pos, physics, vel, mut projectile) in (
&read_data.entities,
&read_data.positions,
&read_data.physics_states,
&mut orientations,
&read_data.velocities,
&mut projectiles,
)
.join()
@ -110,51 +111,60 @@ impl<'a> System<'a> for Sys {
.uid_allocator
.retrieve_entity_internal(other.into())
{
let owner_entity = projectile.owner.and_then(|u| {
read_data.uid_allocator.retrieve_entity_internal(u.into())
});
let attacker_info =
owner_entity.zip(projectile.owner).map(|(entity, uid)| {
AttackerInfo {
entity,
uid,
energy: read_data.energies.get(entity),
combo: read_data.combos.get(entity),
}
if let (Some(pos), Some(dir)) = (
read_data.positions.get(target),
Dir::from_unnormalized(vel.0),
) {
let owner_entity = projectile.owner.and_then(|u| {
read_data.uid_allocator.retrieve_entity_internal(u.into())
});
let target_info = TargetInfo {
entity: target,
inventory: read_data.inventories.get(target),
stats: read_data.stats.get(target),
health: read_data.healths.get(target),
pos: pos.0,
};
let attacker_info =
owner_entity.zip(projectile.owner).map(|(entity, uid)| {
AttackerInfo {
entity,
uid,
energy: read_data.energies.get(entity),
combo: read_data.combos.get(entity),
}
});
if let Some(&body) = read_data.bodies.get(entity) {
outcomes.push(Outcome::ProjectileHit {
let target_info = TargetInfo {
entity: target,
uid: other,
inventory: read_data.inventories.get(target),
stats: read_data.stats.get(target),
health: read_data.healths.get(target),
pos: pos.0,
body,
vel: read_data
.velocities
.get(entity)
.map_or(Vec3::zero(), |v| v.0),
source: projectile.owner,
target: read_data.uids.get(target).copied(),
});
}
ori: orientations.get(target),
char_state: read_data.character_states.get(target),
};
attack.apply_attack(
target_group,
attacker_info,
target_info,
ori.look_dir(),
false,
1.0,
|e| server_emitter.emit(e),
|o| outcomes.push(o),
);
if let Some(&body) = read_data.bodies.get(entity) {
outcomes.push(Outcome::ProjectileHit {
pos: pos.0,
body,
vel: read_data
.velocities
.get(entity)
.map_or(Vec3::zero(), |v| v.0),
source: projectile.owner,
target: read_data.uids.get(target).copied(),
});
}
attack.apply_attack(
target_group,
attacker_info,
target_info,
dir,
false,
1.0,
AttackSource::Projectile,
|e| server_emitter.emit(e),
|o| outcomes.push(o),
);
}
}
},
projectile::Effect::Explode(e) => {
@ -213,12 +223,10 @@ impl<'a> System<'a> for Sys {
if projectile_vanished {
continue 'projectile_loop;
}
} else if let Some(dir) = read_data
.velocities
.get(entity)
.and_then(|vel| Dir::from_unnormalized(vel.0))
{
*ori = dir.into();
} else if let Some(ori) = orientations.get_mut(entity) {
if let Some(dir) = Dir::from_unnormalized(vel.0) {
*ori = dir.into();
}
}
if projectile.time_left == Duration::default() {

View File

@ -1,8 +1,8 @@
use common::{
combat::{AttackerInfo, TargetInfo},
combat::{AttackSource, AttackerInfo, TargetInfo},
comp::{
Body, Combo, Energy, Group, Health, HealthSource, Inventory, Ori, PhysicsState, Pos, Scale,
Shockwave, ShockwaveHitEntities, Stats,
Body, CharacterState, Combo, Energy, Group, Health, HealthSource, Inventory, Ori,
PhysicsState, Pos, Scale, Shockwave, ShockwaveHitEntities, Stats,
},
event::{EventBus, ServerEvent},
outcome::Outcome,
@ -37,6 +37,7 @@ pub struct ReadData<'a> {
energies: ReadStorage<'a, Energy>,
stats: ReadStorage<'a, Stats>,
combos: ReadStorage<'a, Combo>,
character_states: ReadStorage<'a, CharacterState>,
}
/// This system is responsible for handling accepted inputs like moving or
@ -189,10 +190,13 @@ impl<'a> System<'a> for Sys {
let target_info = TargetInfo {
entity: target,
uid: *uid_b,
inventory: read_data.inventories.get(target),
stats: read_data.stats.get(target),
health: read_data.healths.get(target),
pos: pos.0,
pos: pos_b.0,
ori: read_data.orientations.get(target),
char_state: read_data.character_states.get(target),
};
shockwave.properties.attack.apply_attack(
@ -202,6 +206,7 @@ impl<'a> System<'a> for Sys {
dir,
false,
1.0,
AttackSource::Shockwave,
|e| server_emitter.emit(e),
|o| outcomes.push(o),
);

View File

@ -253,26 +253,11 @@ impl<'a> System<'a> for Sys {
energy.get_mut_unchecked().regen_rate = 0.0
}
},
// recover small amount of passive energy from blocking, and bonus energy from
// blocking attacks?
CharacterState::BasicBlock => {
let res = {
let energy = energy.get_unchecked();
energy.current() < energy.maximum()
};
if res {
energy.get_mut_unchecked().change_by(EnergyChange {
amount: -3,
source: EnergySource::Regen,
});
}
},
// Non-combat abilities that consume energy;
// temporarily stall energy gain, but preserve regen_rate.
// Abilities that temporarily stall energy gain, but preserve regen_rate.
CharacterState::Roll { .. }
| CharacterState::Climb { .. }
| CharacterState::Stunned { .. } => {},
| CharacterState::Stunned { .. }
| CharacterState::BasicBlock { .. } => {},
}
}

View File

@ -672,16 +672,33 @@ pub fn handle_explosion(server: &Server, pos: Vec3<f32>, explosion: Explosion, o
RadiusEffect::Attack(attack) => {
let energies = &ecs.read_storage::<comp::Energy>();
let combos = &ecs.read_storage::<comp::Combo>();
for (entity_b, pos_b, health_b, inventory_b_maybe, stats_b_maybe, body_b_maybe) in (
for (
entity_b,
pos_b,
health_b,
(
body_b_maybe,
inventory_b_maybe,
stats_b_maybe,
ori_b_maybe,
char_state_b_maybe,
uid_b,
),
) in (
&ecs.entities(),
&ecs.read_storage::<comp::Pos>(),
&ecs.read_storage::<comp::Health>(),
ecs.read_storage::<comp::Inventory>().maybe(),
ecs.read_storage::<comp::Stats>().maybe(),
ecs.read_storage::<comp::Body>().maybe(),
(
ecs.read_storage::<comp::Body>().maybe(),
ecs.read_storage::<comp::Inventory>().maybe(),
ecs.read_storage::<comp::Stats>().maybe(),
ecs.read_storage::<comp::Ori>().maybe(),
ecs.read_storage::<comp::CharacterState>().maybe(),
&ecs.read_storage::<Uid>(),
),
)
.join()
.filter(|(_, _, h, _, _, _)| !h.is_dead)
.filter(|(_, _, h, _)| !h.is_dead)
{
// Check if it is a hit
let strength = if let Some(body) = body_b_maybe {
@ -721,10 +738,13 @@ pub fn handle_explosion(server: &Server, pos: Vec3<f32>, explosion: Explosion, o
let target_info = combat::TargetInfo {
entity: entity_b,
uid: *uid_b,
inventory: inventory_b_maybe,
stats: stats_b_maybe,
health: Some(health_b),
pos,
pos: pos_b.0,
ori: ori_b_maybe,
char_state: char_state_b_maybe,
};
let server_eventbus = ecs.read_resource::<EventBus<ServerEvent>>();
@ -736,6 +756,7 @@ pub fn handle_explosion(server: &Server, pos: Vec3<f32>, explosion: Explosion, o
dir,
false,
strength,
combat::AttackSource::Explosion,
|e| server_eventbus.emit_now(e),
|o| outcomes.push(o),
);

View File

@ -166,11 +166,13 @@ impl Animation for AlphaAnimation {
let moveret2 = move2 * pullback;
next.hand_l.position =
Vec3::new(s_a.hhl.0, s_a.hhl.1, s_a.hhl.2 + moveret2 * -7.0);
next.hand_l.orientation =
Quaternion::rotation_x(s_a.hhl.3) * Quaternion::rotation_y(s_a.hhl.4);
next.hand_l.orientation = Quaternion::rotation_x(s_a.hhl.3)
* Quaternion::rotation_y(s_a.hhl.4)
* Quaternion::rotation_z(s_a.hhl.5);
next.hand_r.position = Vec3::new(s_a.hhr.0, s_a.hhr.1, s_a.hhr.2);
next.hand_r.orientation =
Quaternion::rotation_x(s_a.hhr.3) * Quaternion::rotation_y(s_a.hhr.4);
next.hand_r.orientation = Quaternion::rotation_x(s_a.hhr.3)
* Quaternion::rotation_y(s_a.hhr.4)
* Quaternion::rotation_z(s_a.hhr.5);
next.control.position = Vec3::new(
s_a.hc.0 + moveret1 * -13.0 + moveret2 * 3.0,

View File

@ -2,12 +2,24 @@ use super::{
super::{vek::*, Animation},
CharacterSkeleton, SkeletonAttr,
};
use common::comp::item::ToolKind;
use common::{
comp::item::{Hands, ToolKind},
states::utils::StageSection,
};
use std::f32::consts::PI;
pub struct BlockAnimation;
type BlockAnimationDependency = (
(Option<Hands>, Option<Hands>),
Option<ToolKind>,
Option<ToolKind>,
Vec3<f32>,
f32,
Option<StageSection>,
);
impl Animation for BlockAnimation {
type Dependency = (Option<ToolKind>, Option<ToolKind>, f32);
type Dependency = BlockAnimationDependency;
type Skeleton = CharacterSkeleton;
#[cfg(feature = "use-dyn-lib")]
@ -16,24 +28,206 @@ impl Animation for BlockAnimation {
#[cfg_attr(feature = "be-dyn-lib", export_name = "character_block")]
fn update_skeleton_inner(
skeleton: &Self::Skeleton,
(_active_tool_kind, _second_tool_kind, _global_time): Self::Dependency,
_anim_time: f32,
_rate: &mut f32,
(hands, active_tool_kind, second_tool_kind,velocity, _global_time, stage_section): Self::Dependency,
anim_time: f32,
rate: &mut f32,
s_a: &SkeletonAttr,
) -> Self::Skeleton {
*rate = 1.0;
let mut next = (*skeleton).clone();
next.head.position = Vec3::new(0.0, -1.0 + s_a.head.0, s_a.head.1 + 19.5);
next.head.orientation = Quaternion::rotation_x(-0.25);
next.hand_l.position = Vec3::new(s_a.hand.0 - 6.0, s_a.hand.1 + 3.5, s_a.hand.2 + 0.0);
next.hand_l.orientation = Quaternion::rotation_x(-0.3);
next.hand_r.position = Vec3::new(s_a.hand.0 - 6.0, s_a.hand.1 + 3.0, s_a.hand.2 - 2.0);
next.hand_r.orientation = Quaternion::rotation_x(-0.3);
next.main.position = Vec3::new(0.0, 0.0, 0.0);
next.main.orientation = Quaternion::rotation_x(-0.3);
next.main.orientation = Quaternion::rotation_z(0.0);
next.second.position = Vec3::new(0.0, 0.0, 0.0);
next.second.orientation = Quaternion::rotation_z(0.0);
next.torso.position = Vec3::new(0.0, -0.2, 0.1) * s_a.scaler;
let speed = Vec2::<f32>::from(velocity).magnitude();
let (movement1base, move2, movement3) = match stage_section {
Some(StageSection::Buildup) => (anim_time.powf(0.25), 0.0, 0.0),
Some(StageSection::Swing) => (1.0, (anim_time * 10.0).sin(), 0.0),
Some(StageSection::Recover) => (1.0, 1.0, anim_time.powf(4.0)),
_ => (0.0, 0.0, 0.0),
};
let pullback = 1.0 - movement3;
let move1 = movement1base * pullback;
if speed > 0.5 {
} else {
next.chest.position =
Vec3::new(0.0, s_a.chest.0, s_a.chest.1 + move1 * -1.0 + move2 * 0.2);
next.chest.orientation = Quaternion::rotation_x(move1 * -0.15);
next.head.orientation = Quaternion::rotation_x(move1 * 0.25);
next.belt.position = Vec3::new(0.0, s_a.belt.0 + move1 * 0.5, s_a.belt.1 + move1 * 0.5);
next.shorts.position =
Vec3::new(0.0, s_a.shorts.0 + move1 * 1.3, s_a.shorts.1 + move1 * 1.0);
next.belt.orientation = Quaternion::rotation_x(move1 * 0.15);
next.shorts.orientation = Quaternion::rotation_x(move1 * 0.25);
next.foot_l.position = Vec3::new(-s_a.foot.0, s_a.foot.1 + move1 * 2.0, s_a.foot.2);
next.foot_l.orientation = Quaternion::rotation_z(move1 * -0.5);
next.foot_r.position = Vec3::new(s_a.foot.0, s_a.foot.1 + move1 * -2.0, s_a.foot.2);
next.foot_r.orientation = Quaternion::rotation_x(move1 * -0.5);
};
match (hands, active_tool_kind, second_tool_kind) {
((Some(Hands::Two), _), tool, _) | ((None, Some(Hands::Two)), _, tool) => match tool {
Some(ToolKind::Sword) | Some(ToolKind::SwordSimple) => {
next.hand_l.position = Vec3::new(s_a.shl.0, s_a.shl.1, s_a.shl.2);
next.hand_l.orientation =
Quaternion::rotation_x(s_a.shl.3) * Quaternion::rotation_y(s_a.shl.4);
next.hand_r.position = Vec3::new(
s_a.shr.0 + move1 * -2.0,
s_a.shr.1,
s_a.shr.2 + move1 * 20.0,
);
next.hand_r.orientation = Quaternion::rotation_x(s_a.shr.3)
* Quaternion::rotation_y(s_a.shr.4)
* Quaternion::rotation_z(move1 * 1.5);
next.control.position =
Vec3::new(s_a.sc.0 + move1 * -3.0, s_a.sc.1, s_a.sc.2 + move1 * 4.0);
next.control.orientation = Quaternion::rotation_x(s_a.sc.3)
* Quaternion::rotation_y(move1 * 1.1)
* Quaternion::rotation_z(move1 * 1.7);
},
Some(ToolKind::Axe) => {
next.main.position = Vec3::new(0.0, 0.0, 0.0);
next.main.orientation = Quaternion::rotation_x(0.0);
next.hand_l.position = Vec3::new(s_a.ahl.0, s_a.ahl.1, s_a.ahl.2);
next.hand_l.orientation =
Quaternion::rotation_x(s_a.ahl.3) * Quaternion::rotation_y(s_a.ahl.4);
next.hand_r.position = Vec3::new(s_a.ahr.0, s_a.ahr.1, s_a.ahr.2);
next.hand_r.orientation =
Quaternion::rotation_x(s_a.ahr.3) * Quaternion::rotation_z(s_a.ahr.5);
next.control.position = Vec3::new(
s_a.ac.0 + move1 * 13.0,
s_a.ac.1 + move1 * -3.0,
s_a.ac.2 + move1 * 8.0,
);
next.control.orientation = Quaternion::rotation_x(s_a.ac.3 + move1 * -2.0)
* Quaternion::rotation_y(s_a.ac.4 + move1 * -1.8)
* Quaternion::rotation_z(s_a.ac.5 + move1 * 4.0);
},
Some(ToolKind::Hammer) | Some(ToolKind::HammerSimple) | Some(ToolKind::Pick) => {
next.hand_l.position =
Vec3::new(s_a.hhl.0, s_a.hhl.1 + move1 * 6.0, s_a.hhl.2 + move1 * 6.0);
next.hand_l.orientation = Quaternion::rotation_x(s_a.hhl.3 + move1 * -0.5)
* Quaternion::rotation_y(s_a.hhl.4 + move1 * 1.5)
* Quaternion::rotation_z(s_a.hhl.5 + move1 * PI);
next.hand_r.position = Vec3::new(s_a.hhr.0, s_a.hhr.1, s_a.hhr.2);
next.hand_r.orientation = Quaternion::rotation_x(s_a.hhr.3)
* Quaternion::rotation_y(s_a.hhr.4)
* Quaternion::rotation_z(s_a.hhr.5);
next.control.position = Vec3::new(
s_a.hc.0 + move1 * 3.0,
s_a.hc.1 + move1 * 3.0,
s_a.hc.2 + move1 * 10.0,
);
next.control.orientation = Quaternion::rotation_x(s_a.hc.3)
* Quaternion::rotation_y(s_a.hc.4)
* Quaternion::rotation_z(s_a.hc.5 + move1 * -1.0);
},
Some(ToolKind::Staff) | Some(ToolKind::Sceptre) => {
next.hand_r.position = Vec3::new(s_a.sthr.0, s_a.sthr.1, s_a.sthr.2);
next.hand_r.orientation =
Quaternion::rotation_x(s_a.sthr.3) * Quaternion::rotation_y(s_a.sthr.4);
next.control.position = Vec3::new(s_a.stc.0, s_a.stc.1, s_a.stc.2);
next.hand_l.position = Vec3::new(s_a.sthl.0, s_a.sthl.1, s_a.sthl.2);
next.hand_l.orientation = Quaternion::rotation_x(s_a.sthl.3);
next.control.orientation = Quaternion::rotation_x(s_a.stc.3)
* Quaternion::rotation_y(s_a.stc.4)
* Quaternion::rotation_z(s_a.stc.5);
},
Some(ToolKind::Bow) => {
next.main.position = Vec3::new(0.0, 0.0, 0.0);
next.main.orientation = Quaternion::rotation_x(0.0);
next.hand_l.position = Vec3::new(s_a.bhl.0, s_a.bhl.1, s_a.bhl.2);
next.hand_l.orientation = Quaternion::rotation_x(s_a.bhl.3);
next.hand_r.position = Vec3::new(s_a.bhr.0, s_a.bhr.1, s_a.bhr.2);
next.hand_r.orientation = Quaternion::rotation_x(s_a.bhr.3);
next.hold.position = Vec3::new(0.0, -1.0, -5.2);
next.hold.orientation = Quaternion::rotation_x(-1.57);
next.hold.scale = Vec3::one() * 1.0;
next.control.position = Vec3::new(s_a.bc.0, s_a.bc.1, s_a.bc.2);
next.control.orientation = Quaternion::rotation_x(0.0)
* Quaternion::rotation_y(s_a.bc.4)
* Quaternion::rotation_z(s_a.bc.5);
},
Some(ToolKind::Debug) => {
next.hand_l.position = Vec3::new(-7.0, 4.0, 3.0);
next.hand_l.orientation = Quaternion::rotation_x(1.27);
next.main.position = Vec3::new(-5.0, 5.0, 23.0);
next.main.orientation = Quaternion::rotation_x(PI);
},
Some(ToolKind::Farming) => {
next.hand_l.position = Vec3::new(9.0, 1.0, 1.0);
next.hand_l.orientation = Quaternion::rotation_x(1.57);
next.hand_r.position = Vec3::new(9.0, 1.0, 11.0);
next.hand_r.orientation = Quaternion::rotation_x(1.57);
next.main.position = Vec3::new(7.5, 7.5, 13.2);
next.main.orientation = Quaternion::rotation_y(PI);
next.control.position = Vec3::new(-11.0, 1.8, 4.0);
next.control.orientation = Quaternion::rotation_x(0.0)
* Quaternion::rotation_y(0.6)
* Quaternion::rotation_z(0.0);
},
_ => {},
},
((_, _), _, _) => {},
};
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(1.57)
},
(_, _) => {},
};
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(1.57)
},
(_, _) => {},
};
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;
}
next
}

View File

@ -87,11 +87,13 @@ impl Animation for ChargeswingAnimation {
Some(ToolKind::Hammer) | Some(ToolKind::HammerSimple) => {
next.hand_l.position =
Vec3::new(s_a.hhl.0, s_a.hhl.1, s_a.hhl.2 + (move2 * -8.0));
next.hand_l.orientation =
Quaternion::rotation_x(s_a.hhl.3) * Quaternion::rotation_y(s_a.hhl.4);
next.hand_l.orientation = Quaternion::rotation_x(s_a.hhl.3)
* Quaternion::rotation_y(s_a.hhl.4)
* Quaternion::rotation_z(s_a.hhl.5);
next.hand_r.position = Vec3::new(s_a.hhr.0, s_a.hhr.1, s_a.hhr.2);
next.hand_r.orientation =
Quaternion::rotation_x(s_a.hhr.3) * Quaternion::rotation_y(s_a.hhr.4);
next.hand_r.orientation = Quaternion::rotation_x(s_a.hhr.3)
* Quaternion::rotation_y(s_a.hhr.4)
* Quaternion::rotation_z(s_a.hhr.5);
next.control.position = Vec3::new(
s_a.hc.0 + (move1 * -2.0 + move2 * -8.0),

View File

@ -119,9 +119,11 @@ impl Animation for LeapAnimation {
match ability_info.and_then(|a| a.tool) {
Some(ToolKind::Hammer) => {
next.hand_l.position = Vec3::new(s_a.hhl.0, s_a.hhl.1, s_a.hhl.2);
next.hand_l.orientation = Quaternion::rotation_x(s_a.hhl.3);
next.hand_l.orientation =
Quaternion::rotation_x(s_a.hhl.3) * Quaternion::rotation_z(s_a.hhl.5);
next.hand_r.position = Vec3::new(s_a.hhr.0, s_a.hhr.1, s_a.hhr.2);
next.hand_r.orientation = Quaternion::rotation_x(s_a.hhr.3);
next.hand_r.orientation =
Quaternion::rotation_x(s_a.hhr.3) * Quaternion::rotation_z(s_a.hhr.5);
next.main.position = Vec3::new(0.0, 0.0, 0.0);
next.main.orientation =
Quaternion::rotation_y(0.0) * Quaternion::rotation_z(0.0);

View File

@ -291,10 +291,10 @@ impl<'a> From<&'a Body> for SkeletonAttr {
_ => (-7.0, 7.0, 2.0, -0.1, 0.0, 0.0),
},
hhl: match (body.species, body.body_type) {
_ => (-0.5, -1.0, 10.0, 4.71, 0.0, 0.0),
_ => (0.1, 0.0, 11.0, 4.71, 0.0, PI),
},
hhr: match (body.species, body.body_type) {
_ => (0.0, 0.0, 0.0, 4.71, 0.0, 0.0),
_ => (0.0, 0.0, 0.0, 4.71, 0.0, PI),
},
hc: match (body.species, body.body_type) {
_ => (6.0, 7.0, 1.0, -0.3, -1.57, 3.64),

View File

@ -106,10 +106,12 @@ impl Animation for StaggeredAnimation {
Some(ToolKind::Hammer | ToolKind::Pick) => {
next.hand_l.position = Vec3::new(s_a.hhl.0, s_a.hhl.1, s_a.hhl.2);
next.hand_l.orientation = Quaternion::rotation_x(s_a.hhl.3)
* Quaternion::rotation_y(s_a.hhl.4);
* Quaternion::rotation_y(s_a.hhl.4)
* Quaternion::rotation_z(s_a.hhl.5);
next.hand_r.position = Vec3::new(s_a.hhr.0, s_a.hhr.1, s_a.hhr.2);
next.hand_r.orientation = Quaternion::rotation_x(s_a.hhr.3)
* Quaternion::rotation_y(s_a.hhr.4);
* Quaternion::rotation_y(s_a.hhr.4)
* Quaternion::rotation_z(s_a.hhr.5);
next.control.position = Vec3::new(s_a.hc.0, s_a.hc.1, s_a.hc.2);
next.control.orientation = Quaternion::rotation_x(s_a.hc.3)

View File

@ -101,11 +101,13 @@ impl Animation for StunnedAnimation {
},
Some(ToolKind::Hammer | ToolKind::Pick) => {
next.hand_l.position = Vec3::new(s_a.hhl.0, s_a.hhl.1, s_a.hhl.2);
next.hand_l.orientation =
Quaternion::rotation_x(s_a.hhl.3) * Quaternion::rotation_y(s_a.hhl.4);
next.hand_l.orientation = Quaternion::rotation_x(s_a.hhl.3)
* Quaternion::rotation_y(s_a.hhl.4)
* Quaternion::rotation_z(s_a.hhl.5);
next.hand_r.position = Vec3::new(s_a.hhr.0, s_a.hhr.1, s_a.hhr.2);
next.hand_r.orientation =
Quaternion::rotation_x(s_a.hhr.3) * Quaternion::rotation_y(s_a.hhr.4);
next.hand_r.orientation = Quaternion::rotation_x(s_a.hhr.3)
* Quaternion::rotation_y(s_a.hhr.4)
* Quaternion::rotation_z(s_a.hhr.5);
next.control.position = Vec3::new(s_a.hc.0, s_a.hc.1, s_a.hc.2);
next.control.orientation = Quaternion::rotation_x(s_a.hc.3)

View File

@ -166,19 +166,6 @@ impl Animation for WieldAnimation {
next.control.orientation = Quaternion::rotation_x(s_a.sc.3 + u_slow * 0.15)
* Quaternion::rotation_z(u_slowalt * 0.08);
},
Some(ToolKind::Dagger) => {
next.control.position = Vec3::new(0.0, 0.0, 0.0);
next.hand_l.position = Vec3::new(0.0, 0.0, 0.0);
next.hand_l.orientation = Quaternion::rotation_x(0.0);
next.control_l.position = Vec3::new(-7.0, 0.0, 0.0);
next.hand_r.position = Vec3::new(0.0, 0.0, 0.0);
next.hand_r.orientation = Quaternion::rotation_x(0.0);
next.control_r.position = Vec3::new(7.0, 0.0, 0.0);
},
Some(ToolKind::Axe) => {
next.main.position = Vec3::new(0.0, 0.0, 0.0);
next.main.orientation = Quaternion::rotation_x(0.0);
@ -215,11 +202,13 @@ impl Animation for WieldAnimation {
},
Some(ToolKind::Hammer) | Some(ToolKind::HammerSimple) | Some(ToolKind::Pick) => {
next.hand_l.position = Vec3::new(s_a.hhl.0, s_a.hhl.1, s_a.hhl.2);
next.hand_l.orientation =
Quaternion::rotation_x(s_a.hhl.3) * Quaternion::rotation_y(s_a.hhl.4);
next.hand_l.orientation = Quaternion::rotation_x(s_a.hhl.3)
* Quaternion::rotation_y(s_a.hhl.4)
* Quaternion::rotation_z(s_a.hhl.5);
next.hand_r.position = Vec3::new(s_a.hhr.0, s_a.hhr.1, s_a.hhr.2);
next.hand_r.orientation =
Quaternion::rotation_x(s_a.hhr.3) * Quaternion::rotation_y(s_a.hhr.4);
next.hand_r.orientation = Quaternion::rotation_x(s_a.hhr.3)
* Quaternion::rotation_y(s_a.hhr.4)
* Quaternion::rotation_z(s_a.hhr.5);
next.control.position = Vec3::new(
s_a.hc.0,

View File

@ -407,9 +407,6 @@ impl SfxMgr {
let file_ref = "voxygen.audio.sfx.footsteps.stone_step_1";
audio.play_sfx(file_ref, pos.map(|e| e as f32 + 0.5), Some(3.0));
},
Outcome::ExpChange { .. }
| Outcome::ComboChange { .. }
| Outcome::SummonedCreature { .. } => {},
Outcome::Damage { pos, .. } => {
let file_ref = vec![
"voxygen.audio.sfx.character.hit_1",
@ -419,6 +416,27 @@ impl SfxMgr {
][rand::thread_rng().gen_range(1..4)];
audio.play_sfx(file_ref, *pos, None);
},
Outcome::Block { pos, parry, .. } => {
let block_sfx = vec![
"voxygen.audio.sfx.character.block_1",
"voxygen.audio.sfx.character.block_2",
"voxygen.audio.sfx.character.block_3",
];
let parry_sfx = vec![
"voxygen.audio.sfx.character.parry_1",
"voxygen.audio.sfx.character.parry_2",
];
if *parry {
let file_ref = parry_sfx[rand::thread_rng().gen_range(1..parry_sfx.len())];
audio.play_sfx(file_ref, *pos, Some(2.0));
} else {
let file_ref = block_sfx[rand::thread_rng().gen_range(1..block_sfx.len())];
audio.play_sfx(file_ref, *pos, Some(2.0));
}
},
Outcome::ExpChange { .. }
| Outcome::ComboChange { .. }
| Outcome::SummonedCreature { .. } => {},
}
}

View File

@ -329,6 +329,11 @@ pub struct ComboFloater {
pub timer: f64,
}
pub struct BlockFloater {
pub owner: Uid,
pub timer: f32,
}
pub struct DebugInfo {
pub tps: f64,
pub frame_time: Duration,
@ -739,6 +744,13 @@ impl PromptDialogSettings {
}
}
pub struct Floaters {
pub exp_floaters: Vec<ExpFloater>,
pub skill_point_displays: Vec<SkillPointGain>,
pub combo_floaters: VecDeque<ComboFloater>,
pub block_floaters: Vec<BlockFloater>,
}
pub struct Hud {
ui: Ui,
ids: Ids,
@ -765,9 +777,7 @@ pub struct Hud {
hotbar: hotbar::State,
events: Vec<Event>,
crosshair_opacity: f32,
exp_floaters: Vec<ExpFloater>,
skill_point_displays: Vec<SkillPointGain>,
combo_floaters: VecDeque<ComboFloater>,
floaters: Floaters,
}
impl Hud {
@ -878,9 +888,12 @@ impl Hud {
hotbar: hotbar_state,
events: Vec::new(),
crosshair_opacity: 0.0,
exp_floaters: Vec::new(),
skill_point_displays: Vec::new(),
combo_floaters: VecDeque::new(),
floaters: Floaters {
exp_floaters: Vec::new(),
skill_point_displays: Vec::new(),
combo_floaters: VecDeque::new(),
block_floaters: Vec::new(),
},
}
}
@ -1174,9 +1187,18 @@ impl Hud {
}
}
// EXP Numbers
self.exp_floaters.retain(|f| f.timer > 0_f32);
self.floaters
.exp_floaters
.iter_mut()
.for_each(|f| f.timer -= dt.as_secs_f32());
self.floaters.exp_floaters.retain(|f| f.timer > 0_f32);
if let Some(uid) = uids.get(me) {
for floater in self.exp_floaters.iter_mut().filter(|f| f.owner == *uid) {
for floater in self
.floaters
.exp_floaters
.iter_mut()
.filter(|f| f.owner == *uid)
{
let number_speed = 50.0; // Number Speed for Single EXP
let player_sct_bg_id = player_sct_bg_id_walker.next(
&mut self.ids.player_sct_bgs,
@ -1221,13 +1243,19 @@ impl Hud {
)
.set(player_sct_id, ui_widgets);
}
floater.timer -= dt.as_secs_f32();
}
}
// Skill points
self.skill_point_displays.retain(|d| d.timer > 0_f32);
self.floaters
.skill_point_displays
.iter_mut()
.for_each(|f| f.timer -= dt.as_secs_f32());
self.floaters
.skill_point_displays
.retain(|d| d.timer > 0_f32);
if let Some(uid) = uids.get(me) {
if let Some(display) = self
.floaters
.skill_point_displays
.iter_mut()
.find(|d| d.owner == *uid)
@ -1311,8 +1339,54 @@ impl Hud {
.left_from(self.ids.player_rank_up_txt_1_bg, 5.0)
.color(Some(Color::Rgba(1.0, 1.0, 1.0, fade)))
.set(self.ids.player_rank_up_icon, ui_widgets);
}
}
// Scrolling Combat Text for Parrying an attack
self.floaters
.block_floaters
.iter_mut()
.for_each(|f| f.timer -= dt.as_secs_f32());
self.floaters.block_floaters.retain(|f| f.timer > 0_f32);
if let Some(uid) = uids.get(me) {
for floater in self
.floaters
.block_floaters
.iter_mut()
.filter(|f| f.owner == *uid)
{
let number_speed = 50.0;
let player_sct_bg_id = player_sct_bg_id_walker.next(
&mut self.ids.player_sct_bgs,
&mut ui_widgets.widget_id_generator(),
);
let player_sct_id = player_sct_id_walker.next(
&mut self.ids.player_scts,
&mut ui_widgets.widget_id_generator(),
);
let font_size = 30;
let y = floater.timer as f64 * number_speed; // Timer sets the widget offset
// text transparency
let fade = if floater.timer < 0.25 {
floater.timer as f32 / 0.25
} else {
1.0
};
display.timer -= dt.as_secs_f32();
Text::new(&i18n.get("hud.sct.block"))
.font_size(font_size)
.font_id(self.fonts.cyri.conrod_id)
.color(Color::Rgba(0.0, 0.0, 0.0, fade))
.x_y(
ui_widgets.win_w * (0.0),
ui_widgets.win_h * (-0.3) + y - 3.0,
)
.set(player_sct_bg_id, ui_widgets);
Text::new(&i18n.get("hud.sct.block"))
.font_size(font_size)
.font_id(self.fonts.cyri.conrod_id)
.color(Color::Rgba(0.69, 0.82, 0.88, fade))
.x_y(ui_widgets.win_w * 0.0, ui_widgets.win_h * -0.3 + y)
.set(player_sct_id, ui_widgets);
}
}
}
@ -2273,12 +2347,14 @@ impl Hud {
let ability_map = ecs.fetch::<comp::item::tool::AbilityMap>();
let bodies = ecs.read_storage::<comp::Body>();
// Combo floater stuffs
for combo_floater in self.combo_floaters.iter_mut() {
combo_floater.timer -= dt.as_secs_f64();
}
self.combo_floaters.retain(|f| f.timer > 0_f64);
self.floaters
.combo_floaters
.iter_mut()
.for_each(|f| f.timer -= dt.as_secs_f64());
self.floaters.combo_floaters.retain(|f| f.timer > 0_f64);
let combo = if let Some(uid) = ecs.read_storage::<Uid>().get(entity) {
self.combo_floaters
self.floaters
.combo_floaters
.iter()
.find(|c| c.owner == *uid)
.copied()
@ -3425,7 +3501,7 @@ impl Hud {
pub fn handle_outcome(&mut self, outcome: &Outcome) {
match outcome {
Outcome::ExpChange { uid, exp } => self.exp_floaters.push(ExpFloater {
Outcome::ExpChange { uid, exp } => self.floaters.exp_floaters.push(ExpFloater {
owner: *uid,
exp_change: *exp,
timer: 4.0,
@ -3436,17 +3512,25 @@ impl Hud {
skill_tree,
total_points,
..
} => self.skill_point_displays.push(SkillPointGain {
} => self.floaters.skill_point_displays.push(SkillPointGain {
owner: *uid,
skill_tree: *skill_tree,
total_points: *total_points,
timer: 5.0,
}),
Outcome::ComboChange { uid, combo } => self.combo_floaters.push_front(ComboFloater {
owner: *uid,
combo: *combo,
timer: comp::combo::COMBO_DECAY_START,
}),
Outcome::ComboChange { uid, combo } => {
self.floaters.combo_floaters.push_front(ComboFloater {
owner: *uid,
combo: *combo,
timer: comp::combo::COMBO_DECAY_START,
})
},
Outcome::Block { uid, parry, .. } if *parry => {
self.floaters.block_floaters.push(BlockFloater {
owner: *uid,
timer: 1.0,
})
},
_ => {},
}
}
@ -3568,8 +3652,8 @@ pub fn get_buff_desc(buff: BuffKind, localized_strings: &Localization) -> &str {
pub fn get_buff_time(buff: BuffInfo) -> String {
if let Some(dur) = buff.dur {
format!("Remaining: {:.0}s", dur.as_secs_f32())
format!("{:.0}s", dur.as_secs_f32())
} else {
"Permanent".to_string()
"".to_string()
}
}

View File

@ -1385,11 +1385,29 @@ impl FigureMgr {
),
}
},
CharacterState::BasicBlock { .. } => {
CharacterState::BasicBlock(s) => {
let stage_time = s.timer.as_secs_f32();
let stage_progress = match s.stage_section {
StageSection::Buildup => {
stage_time / s.static_data.buildup_duration.as_secs_f32()
},
StageSection::Swing => stage_time,
StageSection::Recover => {
stage_time / s.static_data.recover_duration.as_secs_f32()
},
_ => 0.0,
};
anim::character::BlockAnimation::update_skeleton(
&CharacterSkeleton::new(holding_lantern),
(active_tool_kind, second_tool_kind, time),
state.state_time,
&target_base,
(
hands,
active_tool_kind,
second_tool_kind,
rel_vel,
time,
Some(s.stage_section),
),
stage_progress,
&mut state_animation_rate,
skeleton_attr,
)

View File

@ -203,6 +203,18 @@ impl ParticleMgr {
});
}
},
Outcome::Block { pos, parry, .. } => {
if *parry {
self.particles.resize_with(self.particles.len() + 10, || {
Particle::new(
Duration::from_millis(200),
time,
ParticleMode::GunPowderSpark,
*pos + Vec3::unit_z(),
)
});
}
},
Outcome::ProjectileShot { .. }
| Outcome::Beam { .. }
| Outcome::ExpChange { .. }

View File

@ -418,6 +418,15 @@ impl PlayState for SessionState {
);
}
},
GameInput::Block => {
let mut client = self.client.borrow_mut();
client.handle_input(
InputKind::Block,
state,
select_pos,
target_entity.map(|t| t.0),
);
},
GameInput::Roll => {
let mut client = self.client.borrow_mut();
if can_build {

View File

@ -111,6 +111,7 @@ impl ControlSettings {
match game_input {
GameInput::Primary => KeyMouse::Mouse(MouseButton::Left),
GameInput::Secondary => KeyMouse::Mouse(MouseButton::Right),
GameInput::Block => KeyMouse::Key(VirtualKeyCode::LAlt),
GameInput::ToggleCursor => KeyMouse::Key(VirtualKeyCode::Comma),
GameInput::Escape => KeyMouse::Key(VirtualKeyCode::Escape),
GameInput::Chat => KeyMouse::Key(VirtualKeyCode::Return),

View File

@ -21,6 +21,7 @@ use winit::monitor::VideoMode;
pub enum GameInput {
Primary,
Secondary,
Block,
Slot1,
Slot2,
Slot3,
@ -85,6 +86,7 @@ impl GameInput {
match *self {
GameInput::Primary => "gameinput.primary",
GameInput::Secondary => "gameinput.secondary",
GameInput::Block => "gameinput.block",
GameInput::ToggleCursor => "gameinput.togglecursor",
GameInput::MoveForward => "gameinput.moveforward",
GameInput::MoveLeft => "gameinput.moveleft",
@ -149,6 +151,7 @@ impl GameInput {
[
GameInput::Primary,
GameInput::Secondary,
GameInput::Block,
GameInput::ToggleCursor,
GameInput::MoveForward,
GameInput::MoveLeft,