Skiing and ice skating

This commit is contained in:
Christof Petig 2022-05-27 17:19:52 +00:00 committed by Isse
parent ca1a27bd11
commit 2bf8e1865f
33 changed files with 461 additions and 90 deletions

View File

@ -25,6 +25,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Water caves
- Modular weapons
- Added Thai translation
- Skiing and ice skating
### Changed

View File

@ -0,0 +1,15 @@
ItemDef(
name: "Ice Skates",
description: "Best used on a frozen lake.",
kind: Armor((
kind: Foot,
stats: (
protection: Some(Normal(3.0)),
ground_contact: Skate,
),
)),
quality: Moderate,
tags: [
Material(Steel),
],
)

View File

@ -0,0 +1,16 @@
ItemDef(
name: "Wooden skis",
description: "Best used downhill on snow.",
kind: Armor((
kind: Foot,
stats: (
protection: Some(Normal(3.0)),
ground_contact: Ski,
),
)),
quality: Moderate,
tags: [
Material(Wood),
// SalvageInto(Twigs),
],
)

View File

@ -2699,6 +2699,14 @@
"voxel.armor.misc.pants.grayscale",
(0.0, 1.0, 0.0), (-120.0, 210.0,15.0), 0.9,
),
Simple("common.items.armor.misc.foot.ski"): VoxTrans(
"voxel.armor.misc.foot.ski",
(0.0, 0.0, 0.0), (-120.0, 210.0,15.0), 0.9,
),
Simple("common.items.armor.misc.foot.iceskate"): VoxTrans(
"voxel.armor.misc.foot.iceskate",
(0.0, 0.0, 0.0), (-120.0, 210.0,15.0), 0.9,
),
// Backs
Simple("common.items.armor.misc.back.short_0"): VoxTrans(
"voxel.armor.misc.back.short-0",

BIN
assets/voxygen/voxel/armor/misc/foot/iceskate.vox (Stored with Git LFS) Normal file

Binary file not shown.

BIN
assets/voxygen/voxel/armor/misc/foot/ski.vox (Stored with Git LFS) Normal file

Binary file not shown.

View File

@ -164,5 +164,13 @@
vox_spec: ("armor.merchant.foot", (-2.5, -3.5, -2.0)),
color: None
),
"common.items.armor.misc.foot.ski": (
vox_spec: ("armor.misc.foot.ski", (-2.5, -15.5, -2.0)),
color: None
),
"common.items.armor.misc.foot.iceskate": (
vox_spec: ("armor.misc.foot.iceskate", (-2.5, -3.5, -2.0)),
color: None
),
},
))

View File

@ -402,6 +402,7 @@ impl From<&CharacterState> for CharacterAbilityType {
| CharacterState::SpriteSummon(_)
| CharacterState::UseItem(_)
| CharacterState::SpriteInteract(_)
| CharacterState::Skate(_)
| CharacterState::Wallrun(_) => Self::Other,
}
}

View File

@ -1,6 +1,7 @@
use crate::{
comp::{
item::ConsumableKind, ControlAction, Density, Energy, InputAttr, InputKind, Ori, Pos, Vel,
inventory::item::armor::Friction, item::ConsumableKind, ControlAction, Density, Energy,
InputAttr, InputKind, Ori, Pos, Vel,
},
event::{LocalEvent, ServerEvent},
states::{
@ -123,6 +124,8 @@ pub enum CharacterState {
SpriteInteract(sprite_interact::Data),
/// Runs on the wall
Wallrun(wallrun::Data),
/// Ice skating or skiing
Skate(skate::Data),
}
impl CharacterState {
@ -153,15 +156,16 @@ impl CharacterState {
pub fn is_stealthy(&self) -> bool {
matches!(
self,
CharacterState::Idle(idle::Data { is_sneaking: true })
| CharacterState::Wielding(wielding::Data {
is_sneaking: true,
..
})
| CharacterState::Roll(roll::Data {
is_sneaking: true,
..
})
CharacterState::Idle(idle::Data {
is_sneaking: true,
footwear: _
}) | CharacterState::Wielding(wielding::Data {
is_sneaking: true,
..
}) | CharacterState::Roll(roll::Data {
is_sneaking: true,
..
})
)
}
@ -227,6 +231,8 @@ impl CharacterState {
pub fn is_glide(&self) -> bool { matches!(self, CharacterState::Glide(_)) }
pub fn is_skate(&self) -> bool { matches!(self, CharacterState::Skate(_)) }
pub fn is_melee_dodge(&self) -> bool {
matches!(self, CharacterState::Roll(d) if d.static_data.immune_melee)
}
@ -329,6 +335,7 @@ impl CharacterState {
CharacterState::SpriteSummon(data) => data.behavior(j, output_events),
CharacterState::UseItem(data) => data.behavior(j, output_events),
CharacterState::SpriteInteract(data) => data.behavior(j, output_events),
CharacterState::Skate(data) => data.behavior(j, output_events),
}
}
@ -375,12 +382,26 @@ impl CharacterState {
CharacterState::SpriteSummon(data) => data.handle_event(j, output_events, action),
CharacterState::UseItem(data) => data.handle_event(j, output_events, action),
CharacterState::SpriteInteract(data) => data.handle_event(j, output_events, action),
CharacterState::Skate(data) => data.handle_event(j, output_events, action),
}
}
pub fn footwear(&self) -> Option<Friction> {
match &self {
CharacterState::Idle(data) => data.footwear,
CharacterState::Skate(data) => Some(data.footwear),
_ => None,
}
}
}
impl Default for CharacterState {
fn default() -> Self { Self::Idle(idle::Data { is_sneaking: false }) }
fn default() -> Self {
Self::Idle(idle::Data {
is_sneaking: false,
footwear: None,
})
}
}
impl Component for CharacterState {

View File

@ -1,3 +1,7 @@
use crate::{
comp::item::Rgb,
terrain::{Block, BlockKind},
};
use serde::{Deserialize, Serialize};
use std::{cmp::Ordering, ops::Sub};
@ -26,6 +30,45 @@ impl Armor {
}
}
/// longitudinal and lateral friction, only meaningful for footwear
#[derive(Clone, Copy, Debug, PartialEq, Serialize, Deserialize)]
pub enum Friction {
Normal,
Ski,
Skate,
// Snowshoe,
// Spikes,
}
impl Default for Friction {
fn default() -> Self { Self::Normal }
}
impl Friction {
pub fn can_skate_on(&self, b: BlockKind) -> bool {
match self {
Friction::Ski => matches!(b, BlockKind::Snow | BlockKind::Ice | BlockKind::Air),
Friction::Skate => b == BlockKind::Ice,
_ => false,
}
}
/// longitudinal (forward) and lateral (side) friction
pub fn get_friction(&self, b: BlockKind) -> (f32, f32) {
match (self, b) {
(Friction::Ski, BlockKind::Snow) => (0.01, 0.95),
(Friction::Ski, BlockKind::Ice) => (0.001, 0.5),
(Friction::Ski, BlockKind::Water) => (0.1, 0.7),
(Friction::Ski, BlockKind::Air) => (0.0, 0.0),
(Friction::Skate, BlockKind::Ice) => (0.001, 0.99),
_ => {
let non_directional_friction = Block::new(b, Rgb::new(0, 0, 0)).get_friction();
(non_directional_friction, non_directional_friction)
},
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Serialize, Deserialize)]
pub struct Stats {
/// Protection is non-linearly transformed (following summation) to a damage
@ -46,6 +89,9 @@ pub struct Stats {
/// Stealth is summed along with the base stealth bonus (2.0), and then
/// the agent's perception distance is divided by this value
stealth: Option<f32>,
/// Ground contact type, mostly for shoes
#[serde(default)]
ground_contact: Friction,
}
impl Stats {
@ -66,6 +112,7 @@ impl Stats {
energy_reward,
crit_power,
stealth,
ground_contact: Friction::Normal,
}
}
@ -80,6 +127,8 @@ impl Stats {
pub fn crit_power(&self) -> Option<f32> { self.crit_power }
pub fn stealth(&self) -> Option<f32> { self.stealth }
pub fn ground_contact(&self) -> Friction { self.ground_contact }
}
impl Sub<Stats> for Stats {
@ -99,6 +148,7 @@ impl Sub<Stats> for Stats {
.map(|(a, b)| a - b),
crit_power: self.crit_power.zip(other.crit_power).map(|(a, b)| a - b),
stealth: self.stealth.zip(other.stealth).map(|(a, b)| a - b),
ground_contact: Friction::Normal,
}
}
}
@ -159,6 +209,8 @@ impl Armor {
pub fn stealth(&self) -> Option<f32> { self.stats.stealth }
pub fn ground_contact(&self) -> Friction { self.stats.ground_contact }
#[cfg(test)]
pub fn test_armor(
kind: ArmorKind,
@ -174,6 +226,7 @@ impl Armor {
energy_reward: None,
crit_power: None,
stealth: None,
ground_contact: Friction::Normal,
},
}
}

View File

@ -1,6 +1,9 @@
use super::{Fluid, Ori};
use crate::{
comp::body::ship::figuredata::VoxelCollider, consts::WATER_DENSITY, terrain::Block, uid::Uid,
comp::{body::ship::figuredata::VoxelCollider, inventory::item::armor::Friction},
consts::WATER_DENSITY,
terrain::Block,
uid::Uid,
};
use hashbrown::HashSet;
use serde::{Deserialize, Serialize};
@ -182,6 +185,9 @@ pub struct PhysicsState {
pub touch_entities: HashSet<Uid>,
pub in_fluid: Option<Fluid>,
pub ground_vel: Vec3<f32>,
pub footwear: Friction,
pub skating_last_height: f32,
pub skating_active: bool,
}
impl PhysicsState {

View File

@ -79,7 +79,7 @@ impl CharacterBehavior for Data {
CLIMB_BOOST_JUMP_FACTOR * impulse / data.mass.0,
));
};
update.character = CharacterState::Idle(idle::Data { is_sneaking: false });
update.character = CharacterState::Idle(idle::Data::default());
return update;
};
// Move player
@ -103,7 +103,7 @@ impl CharacterBehavior for Data {
.try_change_by(-energy_use * data.dt.0)
.is_err()
{
update.character = CharacterState::Idle(idle::Data { is_sneaking: false });
update.character = CharacterState::Idle(idle::Data::default());
}
// Set orientation direction based on wall direction

View File

@ -20,7 +20,7 @@ impl CharacterBehavior for Data {
// Try to Fall/Stand up/Move
if data.physics.on_ground.is_none() || data.inputs.move_dir.magnitude_squared() > 0.0 {
update.character = CharacterState::Idle(idle::Data { is_sneaking: false });
update.character = CharacterState::Idle(idle::Data::default());
}
update
@ -52,7 +52,7 @@ impl CharacterBehavior for Data {
fn stand(&self, data: &JoinData, _: &mut OutputEvents) -> StateUpdate {
let mut update = StateUpdate::from(data);
// Try to Fall/Stand up/Move
update.character = CharacterState::Idle(idle::Data { is_sneaking: false });
update.character = CharacterState::Idle(idle::Data::default());
update
}
}

View File

@ -89,7 +89,7 @@ impl CharacterBehavior for Data {
.and_then(|inv| inv.equipped(EquipSlot::Glider))
.is_none()
{
update.character = CharacterState::Idle(idle::Data { is_sneaking: false });
update.character = CharacterState::Idle(idle::Data::default());
} else if !handle_climb(data, &mut update) {
let air_flow = data
.physics
@ -207,7 +207,7 @@ impl CharacterBehavior for Data {
pos: data.pos.0,
wielded: false,
}));
update.character = CharacterState::Idle(idle::Data { is_sneaking: false });
update.character = CharacterState::Idle(idle::Data::default());
update
}
}

View File

@ -74,7 +74,7 @@ impl CharacterBehavior for Data {
..*self
})
} else {
CharacterState::Idle(idle::Data { is_sneaking: false })
CharacterState::Idle(idle::Data::default())
};
}
@ -98,7 +98,7 @@ impl CharacterBehavior for Data {
pos: data.pos.0,
wielded: false,
}));
update.character = CharacterState::Idle(idle::Data { is_sneaking: false });
update.character = CharacterState::Idle(idle::Data::default());
update
}

View File

@ -1,19 +1,25 @@
use super::utils::*;
use crate::{
comp::{character_state::OutputEvents, CharacterState, InventoryAction, StateUpdate},
comp::{
character_state::OutputEvents, inventory::item::armor::Friction, CharacterState,
InventoryAction, StateUpdate,
},
states::behavior::{CharacterBehavior, JoinData},
};
use serde::{Deserialize, Serialize};
#[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)]
#[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize, Default)]
pub struct Data {
pub is_sneaking: bool,
// None means unknown
pub(crate) footwear: Option<Friction>,
}
impl CharacterBehavior for Data {
fn behavior(&self, data: &JoinData, output_events: &mut OutputEvents) -> StateUpdate {
let mut update = StateUpdate::from(data);
handle_skating(data, &mut update);
handle_orientation(data, &mut update, 1.0, None);
handle_move(data, &mut update, if self.is_sneaking { 0.4 } else { 1.0 });
handle_jump(data, output_events, &mut update, 1.0);
@ -26,7 +32,10 @@ impl CharacterBehavior for Data {
if self.is_sneaking
&& (data.physics.on_ground.is_none() || data.physics.in_liquid().is_some())
{
update.character = CharacterState::Idle(Data { is_sneaking: false });
update.character = CharacterState::Idle(Data {
is_sneaking: false,
footwear: self.footwear,
});
}
update
@ -75,13 +84,16 @@ impl CharacterBehavior for Data {
fn sneak(&self, data: &JoinData, _: &mut OutputEvents) -> StateUpdate {
let mut update = StateUpdate::from(data);
update.character = CharacterState::Idle(Data { is_sneaking: true });
update.character = CharacterState::Idle(Data {
is_sneaking: true,
footwear: self.footwear,
});
update
}
fn stand(&self, data: &JoinData, _: &mut OutputEvents) -> StateUpdate {
let mut update = StateUpdate::from(data);
update.character = CharacterState::Idle(Data { is_sneaking: false });
update.character = CharacterState::Idle(Data::default());
update
}

View File

@ -23,6 +23,7 @@ pub mod roll;
pub mod self_buff;
pub mod shockwave;
pub mod sit;
pub mod skate;
pub mod spin_melee;
pub mod sprite_interact;
pub mod sprite_summon;

View File

@ -142,6 +142,7 @@ impl CharacterBehavior for Data {
} else {
CharacterState::Idle(idle::Data {
is_sneaking: self.is_sneaking,
footwear: None,
})
}
}
@ -151,6 +152,7 @@ impl CharacterBehavior for Data {
// If it somehow ends up in an incorrect stage section
update.character = CharacterState::Idle(idle::Data {
is_sneaking: self.is_sneaking,
footwear: None,
});
},
}

View File

@ -20,7 +20,7 @@ impl CharacterBehavior for Data {
// Try to Fall/Stand up/Move
if data.physics.on_ground.is_none() || data.inputs.move_dir.magnitude_squared() > 0.0 {
update.character = CharacterState::Idle(idle::Data { is_sneaking: false });
update.character = CharacterState::Idle(idle::Data::default());
}
update
@ -52,7 +52,7 @@ impl CharacterBehavior for Data {
fn stand(&self, data: &JoinData, _: &mut OutputEvents) -> StateUpdate {
let mut update = StateUpdate::from(data);
// Try to Fall/Stand up/Move
update.character = CharacterState::Idle(idle::Data { is_sneaking: false });
update.character = CharacterState::Idle(idle::Data::default());
update
}
}

118
common/src/states/skate.rs Normal file
View File

@ -0,0 +1,118 @@
use super::utils::*;
use crate::{
comp::{
character_state::OutputEvents, item::armor::Friction, CharacterState, InventoryAction,
StateUpdate,
},
states::{
behavior::{CharacterBehavior, JoinData},
idle,
},
};
use serde::{Deserialize, Serialize};
#[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)]
pub struct Data {
pub(crate) footwear: Friction,
// hints for animation
pub turn: f32, // negative to left, positive to right, 1.0=45°
pub accelerate: f32, // negative to brake
pub sidewalk: f32, // negative to left
}
impl Data {
pub fn new(_: &JoinData, footwear: Friction) -> Self {
Self {
footwear,
turn: Default::default(),
accelerate: Default::default(),
sidewalk: Default::default(),
}
}
}
impl CharacterBehavior for Data {
fn behavior(&self, data: &JoinData, output_events: &mut OutputEvents) -> StateUpdate {
let mut update = StateUpdate::from(data);
handle_wield(data, &mut update);
handle_jump(data, output_events, &mut update, 1.0);
if !data.physics.skating_active {
update.character = CharacterState::Idle(idle::Data {
is_sneaking: false,
footwear: Some(self.footwear),
});
} else {
let plane_ori = data.inputs.look_dir.xy();
let orthogonal = vek::Vec2::new(plane_ori.y, -plane_ori.x);
update.ori = vek::Vec3::new(plane_ori.x, plane_ori.y, 0.0).into();
let current_planar_velocity = data.vel.0.xy().magnitude();
let long_input = data.inputs.move_dir.dot(plane_ori);
let lat_input = data.inputs.move_dir.dot(orthogonal);
let acceleration = if long_input.abs() > lat_input.abs() {
if long_input > 0.0 {
if let CharacterState::Skate(data) = &mut update.character {
data.accelerate = 1.0;
data.sidewalk = 0.0;
}
// forward, max at 8u/s
(data.dt.0 * 3.0)
.min(8.0 - current_planar_velocity)
.max(0.0)
} else {
if let CharacterState::Skate(data) = &mut update.character {
data.accelerate = -1.0;
data.sidewalk = 0.0;
}
//brake up to 4u/s², but never backwards
(data.dt.0 * 4.0).min(current_planar_velocity)
}
} else {
if let CharacterState::Skate(data) = &mut update.character {
data.accelerate = 0.0;
data.sidewalk = lat_input;
}
// sideways: constant speed
(0.5 - current_planar_velocity).max(0.0)
};
if let CharacterState::Skate(skate_data) = &mut update.character {
skate_data.turn = orthogonal.dot(data.vel.0.xy());
}
let delta_vel = acceleration * data.inputs.move_dir;
update.vel.0 += vek::Vec3::new(delta_vel.x, delta_vel.y, 0.0);
}
update
}
fn manipulate_loadout(
&self,
data: &JoinData,
output_events: &mut OutputEvents,
inv_action: InventoryAction,
) -> StateUpdate {
let mut update = StateUpdate::from(data);
handle_manipulate_loadout(data, output_events, &mut update, inv_action);
update
}
fn wield(&self, data: &JoinData, _: &mut OutputEvents) -> StateUpdate {
let mut update = StateUpdate::from(data);
attempt_wield(data, &mut update);
update
}
fn sit(&self, data: &JoinData, _: &mut OutputEvents) -> StateUpdate {
let mut update = StateUpdate::from(data);
attempt_sit(data, &mut update);
update
}
fn stand(&self, data: &JoinData, _: &mut OutputEvents) -> StateUpdate {
let mut update = StateUpdate::from(data);
// Try to Fall/Stand up/Move
update.character = CharacterState::Idle(idle::Data::default());
update
}
}

View File

@ -106,13 +106,14 @@ impl CharacterBehavior for Data {
} else {
CharacterState::Idle(idle::Data {
is_sneaking: self.static_data.was_sneak,
footwear: None,
})
}
}
},
_ => {
// If it somehow ends up in an incorrect stage section
update.character = CharacterState::Idle(idle::Data { is_sneaking: false });
update.character = CharacterState::Idle(idle::Data::default());
},
}

View File

@ -72,7 +72,7 @@ impl CharacterBehavior for Data {
update.character =
CharacterState::Wielding(wielding::Data { is_sneaking: false });
} else {
update.character = CharacterState::Idle(idle::Data { is_sneaking: false });
update.character = CharacterState::Idle(idle::Data::default());
}
}
},
@ -82,7 +82,7 @@ impl CharacterBehavior for Data {
update.character =
CharacterState::Wielding(wielding::Data { is_sneaking: false });
} else {
update.character = CharacterState::Idle(idle::Data { is_sneaking: false });
update.character = CharacterState::Idle(idle::Data::default());
}
},
}

View File

@ -42,14 +42,14 @@ impl CharacterBehavior for Data {
fn sit(&self, data: &JoinData, _: &mut OutputEvents) -> StateUpdate {
let mut update = StateUpdate::from(data);
update.character = CharacterState::Idle(idle::Data { is_sneaking: false });
update.character = CharacterState::Idle(idle::Data::default());
attempt_sit(data, &mut update);
update
}
fn dance(&self, data: &JoinData, _: &mut OutputEvents) -> StateUpdate {
let mut update = StateUpdate::from(data);
update.character = CharacterState::Idle(idle::Data { is_sneaking: false });
update.character = CharacterState::Idle(idle::Data::default());
attempt_dance(data, &mut update);
update
}
@ -57,7 +57,7 @@ impl CharacterBehavior for Data {
fn stand(&self, data: &JoinData, _: &mut OutputEvents) -> StateUpdate {
let mut update = StateUpdate::from(data);
// Try to Fall/Stand up/Move
update.character = CharacterState::Idle(idle::Data { is_sneaking: false });
update.character = CharacterState::Idle(idle::Data::default());
update
}
}

View File

@ -132,13 +132,14 @@ impl CharacterBehavior for Data {
} else {
CharacterState::Idle(idle::Data {
is_sneaking: self.static_data.was_sneak,
footwear: None,
})
}
}
},
_ => {
// If it somehow ends up in an incorrect stage section
update.character = CharacterState::Idle(idle::Data { is_sneaking: false });
update.character = CharacterState::Idle(idle::Data::default());
},
}

View File

@ -4,8 +4,8 @@ use crate::{
comp::{
arthropod, biped_large, biped_small,
character_state::OutputEvents,
inventory::slot::{EquipSlot, Slot},
item::{Hands, ItemKind, ToolKind},
inventory::slot::{ArmorSlot, EquipSlot, Slot},
item::{armor::Friction, Hands, ItemKind, ToolKind},
quadruped_low, quadruped_medium, quadruped_small,
skills::{Skill, SwimSkill, SKILL_MODIFIERS},
theropod, Body, CharacterAbility, CharacterState, Density, InputAttr, InputKind,
@ -14,7 +14,7 @@ use crate::{
consts::{FRIC_GROUND, GRAVITY, MAX_PICKUP_RANGE},
event::{LocalEvent, ServerEvent},
outcome::Outcome,
states::{behavior::JoinData, *},
states::{behavior::JoinData, utils::CharacterState::Idle, *},
util::Dir,
vol::ReadVol,
};
@ -301,6 +301,35 @@ impl Body {
}
}
/// set footwear in idle data and potential state change to Skate
pub fn handle_skating(data: &JoinData, update: &mut StateUpdate) {
if let Idle(crate::states::idle::Data {
is_sneaking,
mut footwear,
}) = data.character
{
if footwear.is_none() {
footwear = data.inventory.and_then(|inv| {
inv.equipped(EquipSlot::Armor(ArmorSlot::Feet))
.map(|armor| match armor.kind().as_ref() {
ItemKind::Armor(a) => a.ground_contact(),
_ => crate::comp::inventory::item::armor::Friction::Normal,
})
});
update.character = Idle(crate::states::idle::Data {
is_sneaking: *is_sneaking,
footwear,
});
}
if data.physics.skating_active {
update.character = CharacterState::Skate(crate::states::skate::Data::new(
data,
footwear.unwrap_or(Friction::Normal),
));
}
}
}
/// Handles updating `Components` to move player based on state of `JoinData`
pub fn handle_move(data: &JoinData<'_>, update: &mut StateUpdate, efficiency: f32) {
let submersion = data
@ -635,7 +664,10 @@ pub fn attempt_talk(data: &JoinData<'_>, update: &mut StateUpdate) {
pub fn attempt_sneak(data: &JoinData<'_>, update: &mut StateUpdate) {
if data.physics.on_ground.is_some() && data.body.is_humanoid() {
update.character = CharacterState::Idle(idle::Data { is_sneaking: true });
update.character = CharacterState::Idle(idle::Data {
is_sneaking: true,
footwear: data.character.footwear(),
});
}
}

View File

@ -38,7 +38,7 @@ impl CharacterBehavior for Data {
|| data.physics.on_ground.is_some()
|| data.physics.in_liquid().is_some()
{
update.character = CharacterState::Idle(idle::Data { is_sneaking: false });
update.character = CharacterState::Idle(idle::Data::default());
}
update

View File

@ -58,6 +58,7 @@ impl CharacterBehavior for Data {
) => {
update.character = CharacterState::Idle(idle::Data {
is_sneaking: self.is_sneaking,
footwear: None,
});
},
_ => (),
@ -76,6 +77,7 @@ impl CharacterBehavior for Data {
let mut update = StateUpdate::from(data);
update.character = CharacterState::Idle(idle::Data {
is_sneaking: self.is_sneaking,
footwear: None,
});
update
}

View File

@ -213,7 +213,7 @@ impl<'a> System<'a> for Sys {
// If mounted, character state is controlled by mount
if is_rider.is_some() && !join_struct.char_state.can_perform_mounted() {
// TODO: A better way to swap between mount inputs and rider inputs
*join_struct.char_state = CharacterState::Idle(idle::Data { is_sneaking: false });
*join_struct.char_state = CharacterState::Idle(idle::Data::default());
continue;
}

View File

@ -2,6 +2,7 @@ use common::{
comp::{
body::ship::figuredata::{VoxelCollider, VOXEL_COLLIDER_MANIFEST},
fluid_dynamics::{Fluid, LiquidKind, Wings},
inventory::item::armor::Friction,
Body, CharacterState, Collider, Density, Immovable, Mass, Ori, PhysicsState, Pos,
PosVelOriDefer, PreviousPhysCache, Projectile, Scale, Stats, Sticky, Vel,
},
@ -12,7 +13,7 @@ use common::{
outcome::Outcome,
resources::DeltaTime,
states,
terrain::{Block, TerrainGrid},
terrain::{Block, BlockKind, TerrainGrid},
uid::Uid,
util::{Projection, SpatialGrid},
vol::{BaseVol, ReadVol},
@ -780,6 +781,13 @@ impl<'a> PhysicsData<'a> {
1.0
};
if let Some(state) = character_state {
let footwear = state.footwear().unwrap_or(Friction::Normal);
if footwear != physics_state.footwear {
physics_state.footwear = footwear;
}
}
let in_loaded_chunk = read
.terrain
.get_key(read.terrain.pos_key(pos.0.map(|e| e.floor() as i32)))
@ -846,6 +854,7 @@ impl<'a> PhysicsData<'a> {
climbing,
|entity, vel| land_on_ground = Some((entity, vel)),
read,
&ori,
);
tgt_pos = cpos.0;
},
@ -878,6 +887,7 @@ impl<'a> PhysicsData<'a> {
climbing,
|entity, vel| land_on_ground = Some((entity, vel)),
read,
&ori,
);
// Sticky things shouldn't move when on a surface
@ -1142,6 +1152,7 @@ impl<'a> PhysicsData<'a> {
Some((entity, Vel(ori_from.mul_direction(vel.0))));
},
read,
&ori,
);
cpos.0 = transform_from.mul_point(cpos.0) + wpos;
@ -1339,6 +1350,7 @@ fn box_voxel_collision<'a, T: BaseVol<Vox = Block> + ReadVol>(
climbing: bool,
mut land_on_ground: impl FnMut(Entity, Vel),
read: &PhysicsRead,
ori: &Ori,
) {
// FIXME: Review these
#![allow(
@ -1700,19 +1712,84 @@ fn box_voxel_collision<'a, T: BaseVol<Vox = Block> + ReadVol>(
physics_state.on_wall = on_wall;
let fric_mod = read.stats.get(entity).map_or(1.0, |s| s.friction_modifier);
let ground_fric = physics_state
.on_ground
.map(|b| b.get_friction())
.unwrap_or(0.0);
let wall_fric = if physics_state.on_wall.is_some() && climbing {
FRIC_GROUND
// skating (ski)
if !vel.0.xy().is_approx_zero()
&& physics_state
.on_ground
.map_or(false, |g| physics_state.footwear.can_skate_on(g.kind()))
{
const DT_SCALE: f32 = 1.0; // other areas use 60.0???
const POTENTIAL_TO_KINETIC: f32 = 8.0; // * 2.0 * GRAVITY;
let kind = physics_state.on_ground.map_or(BlockKind::Air, |g| g.kind());
let (longitudinal_friction, lateral_friction) = physics_state.footwear.get_friction(kind);
// the amount of longitudinal speed preserved
let longitudinal_friction_factor_squared =
(1.0 - longitudinal_friction).powf(dt.0 * DT_SCALE * 2.0);
let lateral_friction_factor = (1.0 - lateral_friction).powf(dt.0 * DT_SCALE);
let groundplane_velocity = vel.0.xy();
let mut longitudinal_dir = ori.look_vec().xy();
if longitudinal_dir.is_approx_zero() {
// fall back to travelling dir (in case we look up)
longitudinal_dir = groundplane_velocity;
}
let longitudinal_dir = longitudinal_dir.normalized();
let lateral_dir = Vec2::new(longitudinal_dir.y, -longitudinal_dir.x);
let squared_velocity = groundplane_velocity.magnitude_squared();
// if we crossed an edge up or down accelerate in travelling direction,
// as potential energy is converted into kinetic energy we compare it with the
// square of velocity
let vertical_difference = physics_state.skating_last_height - pos.0.z;
// might become negative when skating slowly uphill
let height_factor_squared = if vertical_difference != 0.0 {
// E=½mv², we scale both energies by ½m
let kinetic = squared_velocity;
// positive accelerate, negative decelerate, ΔE=mgΔh
let delta_potential = vertical_difference.max(-1.0).min(2.0) * POTENTIAL_TO_KINETIC;
let new_energy = kinetic + delta_potential;
physics_state.skating_last_height = pos.0.z;
new_energy / kinetic
} else {
1.0
};
// we calculate these squared as we need to combined them Euclidianly anyway,
// skiing: separate speed into longitudinal and lateral component
let long_speed = groundplane_velocity.dot(longitudinal_dir);
let lat_speed = groundplane_velocity.dot(lateral_dir);
let long_speed_squared = long_speed.powi(2);
// lateral speed is reduced by lateral_friction,
let new_lateral = lat_speed * lateral_friction_factor;
let lateral_speed_reduction = lat_speed - new_lateral;
// we convert this reduction partically (by the cosine of the angle) into
// longitudinal (elastic collision) and the remainder into heat
let cosine_squared_aoa = long_speed_squared / squared_velocity;
let converted_lateral_squared = cosine_squared_aoa * lateral_speed_reduction.powi(2);
let new_longitudinal_squared = longitudinal_friction_factor_squared
* (long_speed_squared + converted_lateral_squared)
* height_factor_squared;
let new_longitudinal =
new_longitudinal_squared.signum() * new_longitudinal_squared.abs().sqrt();
let new_ground_speed = new_longitudinal * longitudinal_dir + new_lateral * lateral_dir;
physics_state.skating_active = true;
vel.0 = Vec3::new(new_ground_speed.x, new_ground_speed.y, 0.0);
} else {
0.0
};
let fric = ground_fric.max(wall_fric);
if fric > 0.0 {
vel.0 *= (1.0 - fric.min(1.0) * fric_mod).powf(dt.0 * 60.0);
physics_state.ground_vel = ground_vel;
let ground_fric = physics_state
.on_ground
.map(|b| b.get_friction())
.unwrap_or(0.0);
let wall_fric = if physics_state.on_wall.is_some() && climbing {
FRIC_GROUND
} else {
0.0
};
let fric = ground_fric.max(wall_fric);
if fric > 0.0 {
vel.0 *= (1.0 - fric.min(1.0) * fric_mod).powf(dt.0 * 60.0);
physics_state.ground_vel = ground_vel;
}
physics_state.skating_active = false;
}
physics_state.in_fluid = liquid

View File

@ -147,6 +147,7 @@ impl<'a> System<'a> for Sys {
| CharacterState::Sit { .. }
| CharacterState::Dance { .. }
| CharacterState::Glide { .. }
| CharacterState::Skate { .. }
| CharacterState::GlideWield { .. }
| CharacterState::Wielding { .. }
| CharacterState::Equipping { .. }

View File

@ -11,7 +11,7 @@ mod widgets;
use client::{Client, Join, World, WorldExt};
use common::{
comp,
comp::{Poise, PoiseState},
comp::{inventory::item::armor::Friction, Poise, PoiseState},
};
use core::mem;
use egui::{
@ -708,6 +708,7 @@ fn selected_entity_window(
Some(Fluid::Liquid { depth, kind, .. }) => format!("{:?} (Depth: {:.1})", kind, depth),
_ => "None".to_owned() });
});
two_col_row(ui, "Footwear", match physics_state.footwear{ Friction::Ski => "Ski", Friction::Skate => "Skate", /* Friction::Snowshoe => "Snowshoe", Friction::Spikes => "Spikes", */ Friction::Normal=>"Normal",}.to_string());
});
}
});

View File

@ -93,7 +93,7 @@ fn same_previous_event_elapsed_emits() {
#[test]
fn maps_idle() {
let result = MovementEventMapper::map_movement_event(
&CharacterState::Idle(common::states::idle::Data { is_sneaking: false }),
&CharacterState::Idle(common::states::idle::Data::default()),
&PhysicsState {
on_ground: Some(Block::empty()),
..Default::default()
@ -115,7 +115,7 @@ fn maps_idle() {
#[test]
fn maps_run_with_sufficient_velocity() {
let result = MovementEventMapper::map_movement_event(
&CharacterState::Idle(common::states::idle::Data { is_sneaking: false }),
&CharacterState::Idle(common::states::idle::Data::default()),
&PhysicsState {
on_ground: Some(Block::empty()),
..Default::default()
@ -137,7 +137,7 @@ fn maps_run_with_sufficient_velocity() {
#[test]
fn does_not_map_run_with_insufficient_velocity() {
let result = MovementEventMapper::map_movement_event(
&CharacterState::Idle(common::states::idle::Data { is_sneaking: false }),
&CharacterState::Idle(common::states::idle::Data::default()),
&PhysicsState {
on_ground: Some(Block::empty()),
..Default::default()
@ -159,7 +159,7 @@ fn does_not_map_run_with_insufficient_velocity() {
#[test]
fn does_not_map_run_with_sufficient_velocity_but_not_on_ground() {
let result = MovementEventMapper::map_movement_event(
&CharacterState::Idle(common::states::idle::Data { is_sneaking: false }),
&CharacterState::Idle(common::states::idle::Data::default()),
&Default::default(),
&PreviousEntityState {
event: SfxEvent::Idle,
@ -214,7 +214,7 @@ fn maps_roll() {
#[test]
fn maps_land_on_ground_to_run() {
let result = MovementEventMapper::map_movement_event(
&CharacterState::Idle(common::states::idle::Data { is_sneaking: false }),
&CharacterState::Idle(common::states::idle::Data::default()),
&PhysicsState {
on_ground: Some(Block::empty()),
..Default::default()

View File

@ -976,9 +976,10 @@ impl FigureMgr {
rel_vel.magnitude_squared() > 0.01, // Moving
physics.in_liquid().is_some(), // In water
is_rider.is_some(),
physics.skating_active,
) {
// Standing
(true, false, false, false) => {
// Standing or Skating
(true, false, false, false, _) | (_, _, false, false, true) => {
anim::character::StandAnimation::update_skeleton(
&CharacterSkeleton::new(holding_lantern),
(
@ -997,7 +998,7 @@ impl FigureMgr {
)
},
// Running
(true, true, false, false) => {
(true, true, false, false, _) => {
anim::character::RunAnimation::update_skeleton(
&CharacterSkeleton::new(holding_lantern),
(
@ -1019,7 +1020,7 @@ impl FigureMgr {
)
},
// In air
(false, _, false, false) => {
(false, _, false, false, _) => {
anim::character::JumpAnimation::update_skeleton(
&CharacterSkeleton::new(holding_lantern),
(
@ -1038,7 +1039,7 @@ impl FigureMgr {
)
},
// Swim
(_, _, true, false) => anim::character::SwimAnimation::update_skeleton(
(_, _, true, false, _) => anim::character::SwimAnimation::update_skeleton(
&CharacterSkeleton::new(holding_lantern),
(
active_tool_kind,
@ -1056,7 +1057,7 @@ impl FigureMgr {
skeleton_attr,
),
// Mount
(_, _, _, true) => anim::character::MountAnimation::update_skeleton(
(_, _, _, true, _) => anim::character::MountAnimation::update_skeleton(
&CharacterSkeleton::new(holding_lantern),
(
active_tool_kind,
@ -1260,7 +1261,9 @@ impl FigureMgr {
skeleton_attr,
)
},
CharacterState::Idle(idle::Data { is_sneaking: true }) => {
CharacterState::Idle(idle::Data {
is_sneaking: true, ..
}) => {
anim::character::SneakAnimation::update_skeleton(
&target_base,
(
@ -5257,16 +5260,11 @@ impl FigureMgr {
// Average velocity relative to the current ground
let _rel_avg_vel = state.avg_vel - physics.ground_vel;
let idlestate = CharacterState::Idle(common::states::idle::Data::default());
let last = Last(idlestate.clone());
let (character, last_character) = match (character, last_character) {
(Some(c), Some(l)) => (c, l),
_ => (
&CharacterState::Idle(common::states::idle::Data {
is_sneaking: false,
}),
&Last(CharacterState::Idle(common::states::idle::Data {
is_sneaking: false,
})),
),
_ => (&idlestate, &last),
};
if !character.same_variant(&last_character.0) {
@ -5388,16 +5386,11 @@ impl FigureMgr {
// Average velocity relative to the current ground
let _rel_avg_vel = state.avg_vel - physics.ground_vel;
let idle_state = CharacterState::Idle(common::states::idle::Data::default());
let last = Last(idle_state.clone());
let (character, last_character) = match (character, last_character) {
(Some(c), Some(l)) => (c, l),
_ => (
&CharacterState::Idle(common::states::idle::Data {
is_sneaking: false,
}),
&Last(CharacterState::Idle(common::states::idle::Data {
is_sneaking: false,
})),
),
_ => (&idle_state, &last),
};
if !character.same_variant(&last_character.0) {
@ -5486,16 +5479,11 @@ impl FigureMgr {
// Average velocity relative to the current ground
let _rel_avg_vel = state.avg_vel - physics.ground_vel;
let idlestate = CharacterState::Idle(common::states::idle::Data::default());
let last = Last(idlestate.clone());
let (character, last_character) = match (character, last_character) {
(Some(c), Some(l)) => (c, l),
_ => (
&CharacterState::Idle(common::states::idle::Data {
is_sneaking: false,
}),
&Last(CharacterState::Idle(common::states::idle::Data {
is_sneaking: false,
})),
),
_ => (&idlestate, &last),
};
if !character.same_variant(&last_character.0) {