Merge branch 'master' of gitlab.com:veloren/veloren into sharp/small-fixes

This commit is contained in:
Joshua Yanovski 2020-07-12 18:47:00 +02:00
commit 6332cbe006
44 changed files with 876 additions and 243 deletions

View File

@ -44,8 +44,6 @@ coverage:
- cargo build --release - cargo build --release
- cp -r target/release/veloren-server-cli $CI_PROJECT_DIR - cp -r target/release/veloren-server-cli $CI_PROJECT_DIR
- cp -r target/release/veloren-voxygen $CI_PROJECT_DIR - cp -r target/release/veloren-voxygen $CI_PROJECT_DIR
- strip --strip-all veloren-server-cli
- strip --strip-all veloren-voxygen
artifacts: artifacts:
paths: paths:
- veloren-server-cli - veloren-server-cli

View File

@ -40,6 +40,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Better pathfinding - Better pathfinding
- Bombs - Bombs
- Training dummy items - Training dummy items
- Added spin attack for axe
- Creature specific stats
### Changed ### Changed

2
Cargo.lock generated
View File

@ -3717,7 +3717,7 @@ dependencies = [
[[package]] [[package]]
name = "specs-idvs" name = "specs-idvs"
version = "0.1.0" version = "0.1.0"
source = "git+https://gitlab.com/veloren/specs-idvs.git#530ebb00b60685d6d1f47a3bc16c5b1f25c5e530" source = "git+https://gitlab.com/veloren/specs-idvs.git?branch=specs-git#fcb0b2306b571f62f9f85d89e79e087454d95efd"
dependencies = [ dependencies = [
"specs", "specs",
] ]

View File

@ -72,7 +72,3 @@ debug = false
[profile.releasedebuginfo] [profile.releasedebuginfo]
inherits = 'release' inherits = 'release'
debug = 1 debug = 1
# living on the edge
[patch.crates-io]
specs = { git = "https://github.com/amethyst/specs.git", rev = "7a2e348ab2223818bad487695c66c43db88050a5" }

BIN
assets/voxygen/element/icons/2haxe_m1.png (Stored with Git LFS)

Binary file not shown.

BIN
assets/voxygen/element/icons/skill_axespin.png (Stored with Git LFS) Normal file

Binary file not shown.

Binary file not shown.

View File

@ -19,7 +19,7 @@ num = "0.2.0"
num_cpus = "1.10.1" num_cpus = "1.10.1"
tracing = { version = "0.1", default-features = false } tracing = { version = "0.1", default-features = false }
rayon = "^1.3.0" rayon = "^1.3.0"
specs = "0.16.1" specs = { git = "https://github.com/amethyst/specs.git", rev = "7a2e348ab2223818bad487695c66c43db88050a5" }
vek = { version = "0.11.0", features = ["serde"] } vek = { version = "0.11.0", features = ["serde"] }
hashbrown = { version = "0.7.2", features = ["rayon", "serde", "nightly"] } hashbrown = { version = "0.7.2", features = ["rayon", "serde", "nightly"] }
authc = { git = "https://gitlab.com/veloren/auth.git", rev = "223a4097f7ebc8d451936dccb5e6517194bbf086" } authc = { git = "https://gitlab.com/veloren/auth.git", rev = "223a4097f7ebc8d451936dccb5e6517194bbf086" }

View File

@ -39,6 +39,7 @@ use network::{Address, Network, Participant, Pid, Stream, PROMISES_CONSISTENCY,
use num::traits::FloatConst; use num::traits::FloatConst;
use rayon::prelude::*; use rayon::prelude::*;
use std::{ use std::{
collections::VecDeque,
net::SocketAddr, net::SocketAddr,
sync::Arc, sync::Arc,
time::{Duration, Instant}, time::{Duration, Instant},
@ -58,6 +59,7 @@ const SERVER_TIMEOUT: f64 = 20.0;
// After this duration has elapsed, the user will begin getting kick warnings in // After this duration has elapsed, the user will begin getting kick warnings in
// their chat window // their chat window
const SERVER_TIMEOUT_GRACE_PERIOD: f64 = 14.0; const SERVER_TIMEOUT_GRACE_PERIOD: f64 = 14.0;
const PING_ROLLING_AVERAGE_SECS: usize = 10;
pub enum Event { pub enum Event {
Chat(comp::ChatMsg), Chat(comp::ChatMsg),
@ -102,6 +104,7 @@ pub struct Client {
last_server_ping: f64, last_server_ping: f64,
last_server_pong: f64, last_server_pong: f64,
last_ping_delta: f64, last_ping_delta: f64,
ping_deltas: VecDeque<f64>,
tick: u64, tick: u64,
state: State, state: State,
@ -344,6 +347,7 @@ impl Client {
last_server_ping: 0.0, last_server_ping: 0.0,
last_server_pong: 0.0, last_server_pong: 0.0,
last_ping_delta: 0.0, last_ping_delta: 0.0,
ping_deltas: VecDeque::new(),
tick: 0, tick: 0,
state, state,
@ -1030,8 +1034,15 @@ impl Client {
}, },
ServerMsg::Pong => { ServerMsg::Pong => {
self.last_server_pong = self.state.get_time(); self.last_server_pong = self.state.get_time();
self.last_ping_delta = self.state.get_time() - self.last_server_ping;
self.last_ping_delta = (self.state.get_time() - self.last_server_ping).round(); // Maintain the correct number of deltas for calculating the rolling average
// ping. The client sends a ping to the server every second so we should be
// receiving a pong reply roughly every second.
while self.ping_deltas.len() > PING_ROLLING_AVERAGE_SECS - 1 {
self.ping_deltas.pop_front();
}
self.ping_deltas.push_back(self.last_ping_delta);
}, },
ServerMsg::ChatMsg(m) => frontend_events.push(Event::Chat(m)), ServerMsg::ChatMsg(m) => frontend_events.push(Event::Chat(m)),
ServerMsg::SetPlayerEntity(uid) => { ServerMsg::SetPlayerEntity(uid) => {
@ -1174,6 +1185,22 @@ impl Client {
pub fn get_ping_ms(&self) -> f64 { self.last_ping_delta * 1000.0 } pub fn get_ping_ms(&self) -> f64 { self.last_ping_delta * 1000.0 }
pub fn get_ping_ms_rolling_avg(&self) -> f64 {
let mut total_weight = 0.;
let pings = self.ping_deltas.len() as f64;
(self
.ping_deltas
.iter()
.enumerate()
.fold(0., |acc, (i, ping)| {
let weight = i as f64 + 1. / pings;
total_weight += weight;
acc + (weight * ping)
})
/ total_weight)
* 1000.0
}
/// Get a reference to the client's worker thread pool. This pool should be /// Get a reference to the client's worker thread pool. This pool should be
/// used for any computationally expensive operations that run outside /// used for any computationally expensive operations that run outside
/// of the main thread (i.e., threads that block on I/O operations are /// of the main thread (i.e., threads that block on I/O operations are
@ -1197,6 +1224,18 @@ impl Client {
.collect() .collect()
} }
/// Return true if this client is an admin on the server
pub fn is_admin(&self) -> bool {
let client_uid = self
.state
.read_component_cloned::<Uid>(self.entity)
.expect("Client doesn't have a Uid!!!");
self.player_list
.get(&client_uid)
.map_or(false, |info| info.is_admin)
}
/// Clean client ECS state /// Clean client ECS state
fn clean_state(&mut self) { fn clean_state(&mut self) {
let client_uid = self let client_uid = self

View File

@ -8,9 +8,9 @@ edition = "2018"
no-assets = [] no-assets = []
[dependencies] [dependencies]
specs-idvs = { git = "https://gitlab.com/veloren/specs-idvs.git" } specs-idvs = { git = "https://gitlab.com/veloren/specs-idvs.git", branch = "specs-git" }
specs = { version = "0.16.1", features = ["serde", "storage-event-control"] } specs = { git = "https://github.com/amethyst/specs.git", features = ["serde", "storage-event-control"], rev = "7a2e348ab2223818bad487695c66c43db88050a5" }
vek = { version = "0.11.0", features = ["serde"] } vek = { version = "0.11.0", features = ["serde"] }
dot_vox = "4.0" dot_vox = "4.0"
image = { version = "0.22.5", default-features = false, features = ["png"] } image = { version = "0.22.5", default-features = false, features = ["png"] }

View File

@ -20,6 +20,7 @@ pub enum CharacterAbilityType {
BasicBlock, BasicBlock,
TripleStrike(Stage), TripleStrike(Stage),
LeapMelee, LeapMelee,
SpinMelee,
} }
impl From<&CharacterState> for CharacterAbilityType { impl From<&CharacterState> for CharacterAbilityType {
@ -32,6 +33,7 @@ impl From<&CharacterState> for CharacterAbilityType {
CharacterState::BasicBlock => Self::BasicBlock, CharacterState::BasicBlock => Self::BasicBlock,
CharacterState::LeapMelee(_) => Self::LeapMelee, CharacterState::LeapMelee(_) => Self::LeapMelee,
CharacterState::TripleStrike(data) => Self::TripleStrike(data.stage), CharacterState::TripleStrike(data) => Self::TripleStrike(data.stage),
CharacterState::SpinMelee(_) => Self::SpinMelee,
_ => Self::BasicMelee, _ => Self::BasicMelee,
} }
} }
@ -80,6 +82,12 @@ pub enum CharacterAbility {
recover_duration: Duration, recover_duration: Duration,
base_damage: u32, base_damage: u32,
}, },
SpinMelee {
energy_cost: u32,
buildup_duration: Duration,
recover_duration: Duration,
base_damage: u32,
},
} }
impl CharacterAbility { impl CharacterAbility {
@ -117,6 +125,10 @@ impl CharacterAbility {
.energy .energy
.try_change_by(-(*energy_cost as i32), EnergySource::Ability) .try_change_by(-(*energy_cost as i32), EnergySource::Ability)
.is_ok(), .is_ok(),
CharacterAbility::SpinMelee { energy_cost, .. } => update
.energy
.try_change_by(-(*energy_cost as i32), EnergySource::Ability)
.is_ok(),
_ => true, _ => true,
} }
} }
@ -239,6 +251,26 @@ impl From<&CharacterAbility> for CharacterState {
recover_duration: *recover_duration, recover_duration: *recover_duration,
base_damage: *base_damage, base_damage: *base_damage,
}), }),
CharacterAbility::SpinMelee {
energy_cost,
buildup_duration,
recover_duration,
base_damage,
} => CharacterState::SpinMelee(spin_melee::Data {
exhausted: false,
energy_cost: *energy_cost,
buildup_duration: *buildup_duration,
buildup_duration_default: *buildup_duration,
recover_duration: *recover_duration,
recover_duration_default: *recover_duration,
base_damage: *base_damage,
// This isn't needed for it's continuous implementation, but is left in should this
// skill be moved to the skillbar
hits_remaining: 1,
hits_remaining_default: 1, /* Should be the same value as hits_remaining, also
* this value can be removed if ability moved to
* skillbar */
}),
} }
} }
} }

View File

@ -64,6 +64,8 @@ pub enum CharacterState {
TripleStrike(triple_strike::Data), TripleStrike(triple_strike::Data),
/// A leap followed by a small aoe ground attack /// A leap followed by a small aoe ground attack
LeapMelee(leap_melee::Data), LeapMelee(leap_melee::Data),
/// Spin around, dealing damage to enemies surrounding you
SpinMelee(spin_melee::Data),
} }
impl CharacterState { impl CharacterState {
@ -75,7 +77,8 @@ impl CharacterState {
| CharacterState::DashMelee(_) | CharacterState::DashMelee(_)
| CharacterState::TripleStrike(_) | CharacterState::TripleStrike(_)
| CharacterState::BasicBlock | CharacterState::BasicBlock
| CharacterState::LeapMelee(_) => true, | CharacterState::LeapMelee(_)
| CharacterState::SpinMelee(_) => true,
_ => false, _ => false,
} }
} }
@ -86,7 +89,8 @@ impl CharacterState {
| CharacterState::BasicRanged(_) | CharacterState::BasicRanged(_)
| CharacterState::DashMelee(_) | CharacterState::DashMelee(_)
| CharacterState::TripleStrike(_) | CharacterState::TripleStrike(_)
| CharacterState::LeapMelee(_) => true, | CharacterState::LeapMelee(_)
| CharacterState::SpinMelee(_) => true,
_ => false, _ => false,
} }
} }

View File

@ -221,13 +221,11 @@ impl Tool {
base_damage: 7, base_damage: 7,
needs_timing: true, needs_timing: true,
}, },
BasicMelee { SpinMelee {
energy_cost: 100, energy_cost: 100,
buildup_duration: Duration::from_millis(700), buildup_duration: Duration::from_millis(125),
recover_duration: Duration::from_millis(100), recover_duration: Duration::from_millis(125),
base_healthchange: -12, base_damage: 5,
range: 3.5,
max_angle: 30.0,
}, },
], ],
Hammer(_) => vec![ Hammer(_) => vec![

View File

@ -131,6 +131,81 @@ pub struct Stats {
pub fitness: u32, pub fitness: u32,
pub willpower: u32, pub willpower: u32,
pub is_dead: bool, pub is_dead: bool,
pub body_type: Body,
}
impl Body {
pub fn base_health(&self) -> u32 {
match self {
Body::Humanoid(_) => 52,
Body::QuadrupedSmall(_) => 44,
Body::QuadrupedMedium(_) => 72,
Body::BirdMedium(_) => 36,
Body::FishMedium(_) => 32,
Body::Dragon(_) => 256,
Body::BirdSmall(_) => 24,
Body::FishSmall(_) => 20,
Body::BipedLarge(_) => 144,
Body::Object(_) => 100,
Body::Golem(_) => 168,
Body::Critter(_) => 32,
Body::QuadrupedLow(_) => 64,
}
}
pub fn base_health_increase(&self) -> u32 {
match self {
Body::Humanoid(_) => 5,
Body::QuadrupedSmall(_) => 4,
Body::QuadrupedMedium(_) => 7,
Body::BirdMedium(_) => 4,
Body::FishMedium(_) => 3,
Body::Dragon(_) => 26,
Body::BirdSmall(_) => 2,
Body::FishSmall(_) => 2,
Body::BipedLarge(_) => 14,
Body::Object(_) => 0,
Body::Golem(_) => 17,
Body::Critter(_) => 3,
Body::QuadrupedLow(_) => 6,
}
}
pub fn base_exp(&self) -> u32 {
match self {
Body::Humanoid(_) => 15,
Body::QuadrupedSmall(_) => 12,
Body::QuadrupedMedium(_) => 28,
Body::BirdMedium(_) => 10,
Body::FishMedium(_) => 8,
Body::Dragon(_) => 160,
Body::BirdSmall(_) => 5,
Body::FishSmall(_) => 4,
Body::BipedLarge(_) => 75,
Body::Object(_) => 0,
Body::Golem(_) => 75,
Body::Critter(_) => 8,
Body::QuadrupedLow(_) => 24,
}
}
pub fn base_exp_increase(&self) -> u32 {
match self {
Body::Humanoid(_) => 3,
Body::QuadrupedSmall(_) => 2,
Body::QuadrupedMedium(_) => 6,
Body::BirdMedium(_) => 2,
Body::FishMedium(_) => 2,
Body::Dragon(_) => 32,
Body::BirdSmall(_) => 1,
Body::FishSmall(_) => 1,
Body::BipedLarge(_) => 15,
Body::Object(_) => 0,
Body::Golem(_) => 15,
Body::Critter(_) => 2,
Body::QuadrupedLow(_) => 5,
}
}
} }
impl Stats { impl Stats {
@ -143,7 +218,10 @@ impl Stats {
} }
// TODO: Delete this once stat points will be a thing // TODO: Delete this once stat points will be a thing
pub fn update_max_hp(&mut self) { self.health.set_maximum(52 + 3 * self.level.amount); } pub fn update_max_hp(&mut self, body: Body) {
self.health
.set_maximum(body.base_health() + body.base_health_increase() * self.level.amount);
}
} }
impl Stats { impl Stats {
@ -187,9 +265,10 @@ impl Stats {
fitness, fitness,
willpower, willpower,
is_dead: false, is_dead: false,
body_type: body,
}; };
stats.update_max_hp(); stats.update_max_hp(body);
stats stats
.health .health
.set_to(stats.health.maximum(), HealthSource::Revive); .set_to(stats.health.maximum(), HealthSource::Revive);

View File

@ -2,7 +2,7 @@ use crate::{
assets, assets,
comp::{ comp::{
item::{Item, ItemKind}, item::{Item, ItemKind},
CharacterAbility, ItemConfig, Loadout, Body, CharacterAbility, ItemConfig, Loadout,
}, },
}; };
use std::time::Duration; use std::time::Duration;
@ -24,6 +24,44 @@ use std::time::Duration;
/// ``` /// ```
pub struct LoadoutBuilder(Loadout); pub struct LoadoutBuilder(Loadout);
impl Body {
pub fn base_dmg(&self) -> u32 {
match self {
Body::Humanoid(_) => 8,
Body::QuadrupedSmall(_) => 7,
Body::QuadrupedMedium(_) => 10,
Body::BirdMedium(_) => 6,
Body::FishMedium(_) => 5,
Body::Dragon(_) => 75,
Body::BirdSmall(_) => 4,
Body::FishSmall(_) => 3,
Body::BipedLarge(_) => 30,
Body::Object(_) => 0,
Body::Golem(_) => 30,
Body::Critter(_) => 6,
Body::QuadrupedLow(_) => 9,
}
}
pub fn base_range(&self) -> f32 {
match self {
Body::Humanoid(_) => 5.0,
Body::QuadrupedSmall(_) => 4.5,
Body::QuadrupedMedium(_) => 5.5,
Body::BirdMedium(_) => 3.5,
Body::FishMedium(_) => 3.5,
Body::Dragon(_) => 12.5,
Body::BirdSmall(_) => 3.0,
Body::FishSmall(_) => 3.0,
Body::BipedLarge(_) => 10.0,
Body::Object(_) => 3.0,
Body::Golem(_) => 7.5,
Body::Critter(_) => 3.0,
Body::QuadrupedLow(_) => 4.5,
}
}
}
impl LoadoutBuilder { impl LoadoutBuilder {
#[allow(clippy::new_without_default)] // TODO: Pending review in #587 #[allow(clippy::new_without_default)] // TODO: Pending review in #587
pub fn new() -> Self { pub fn new() -> Self {
@ -63,7 +101,7 @@ impl LoadoutBuilder {
} }
/// Default animal configuration /// Default animal configuration
pub fn animal() -> Self { pub fn animal(body: Body) -> Self {
Self(Loadout { Self(Loadout {
active_item: Some(ItemConfig { active_item: Some(ItemConfig {
item: assets::load_expect_cloned("common.items.weapons.empty"), item: assets::load_expect_cloned("common.items.weapons.empty"),
@ -71,8 +109,8 @@ impl LoadoutBuilder {
energy_cost: 10, energy_cost: 10,
buildup_duration: Duration::from_millis(600), buildup_duration: Duration::from_millis(600),
recover_duration: Duration::from_millis(100), recover_duration: Duration::from_millis(100),
base_healthchange: -6, base_healthchange: -(body.base_dmg() as i32),
range: 5.0, range: body.base_range(),
max_angle: 80.0, max_angle: 80.0,
}), }),
ability2: None, ability2: None,

View File

@ -12,6 +12,7 @@ pub mod idle;
pub mod leap_melee; pub mod leap_melee;
pub mod roll; pub mod roll;
pub mod sit; pub mod sit;
pub mod spin_melee;
pub mod triple_strike; pub mod triple_strike;
pub mod utils; pub mod utils;
pub mod wielding; pub mod wielding;

View File

@ -0,0 +1,146 @@
use crate::{
comp::{Attacking, CharacterState, EnergySource, StateUpdate},
sys::character_behavior::*,
};
use serde::{Deserialize, Serialize};
use std::time::Duration;
use vek::Vec3;
#[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize, Eq, Hash)]
pub struct Data {
/// How long until the state attacks
pub buildup_duration: Duration,
/// Allows for buildup_duration to be reset to default value
pub buildup_duration_default: Duration,
/// How long until state ends
pub recover_duration: Duration,
/// Allows for recover_duration to be reset to default value
pub recover_duration_default: Duration,
/// Base damage
pub base_damage: u32,
/// Whether the attack can deal more damage
pub exhausted: bool,
/// How many hits it can do before ending
pub hits_remaining: u32,
/// Allows for hits_remaining to be reset to default value
pub hits_remaining_default: u32,
/// Energy cost per attack
pub energy_cost: u32,
}
const MOVE_SPEED: f32 = 5.0;
impl CharacterBehavior for Data {
fn behavior(&self, data: &JoinData) -> StateUpdate {
let mut update = StateUpdate::from(data);
if self.buildup_duration != Duration::default() {
// Allows for moving
update.vel.0 =
Vec3::new(data.inputs.move_dir.x, data.inputs.move_dir.y, 0.0) * MOVE_SPEED;
update.character = CharacterState::SpinMelee(Data {
buildup_duration: self
.buildup_duration
.checked_sub(Duration::from_secs_f32(data.dt.0))
.unwrap_or_default(),
buildup_duration_default: self.buildup_duration_default,
recover_duration: self.recover_duration,
recover_duration_default: self.recover_duration_default,
base_damage: self.base_damage,
exhausted: self.exhausted,
hits_remaining: self.hits_remaining,
hits_remaining_default: self.hits_remaining_default,
energy_cost: self.energy_cost,
});
} else if !self.exhausted {
//Hit attempt
data.updater.insert(data.entity, Attacking {
base_healthchange: -(self.base_damage as i32),
range: 3.5,
max_angle: 360_f32.to_radians(),
applied: false,
hit_count: 0,
knockback: 0.0,
});
update.character = CharacterState::SpinMelee(Data {
buildup_duration: self.buildup_duration,
buildup_duration_default: self.buildup_duration_default,
recover_duration: self.recover_duration,
recover_duration_default: self.recover_duration_default,
base_damage: self.base_damage,
exhausted: true,
hits_remaining: self.hits_remaining - 1,
hits_remaining_default: self.hits_remaining_default,
energy_cost: self.energy_cost,
});
} else if self.recover_duration != Duration::default() {
// Allows for moving
update.vel.0 =
Vec3::new(data.inputs.move_dir.x, data.inputs.move_dir.y, 0.0) * MOVE_SPEED;
update.character = CharacterState::SpinMelee(Data {
buildup_duration: self.buildup_duration,
buildup_duration_default: self.buildup_duration_default,
recover_duration: self
.recover_duration
.checked_sub(Duration::from_secs_f32(data.dt.0))
.unwrap_or_default(),
recover_duration_default: self.recover_duration_default,
base_damage: self.base_damage,
exhausted: self.exhausted,
hits_remaining: self.hits_remaining,
hits_remaining_default: self.hits_remaining_default,
energy_cost: self.energy_cost,
});
} else if self.hits_remaining != 0 {
// Allows for one ability usage to have multiple hits
// This isn't needed for it's continuous implementation, but is left in should
// this skill be moved to the skillbar
update.character = CharacterState::SpinMelee(Data {
buildup_duration: self.buildup_duration_default,
buildup_duration_default: self.buildup_duration_default,
recover_duration: self.recover_duration_default,
recover_duration_default: self.recover_duration_default,
base_damage: self.base_damage,
exhausted: false,
hits_remaining: self.hits_remaining,
hits_remaining_default: self.hits_remaining_default,
energy_cost: self.energy_cost,
});
} else if update.energy.current() >= self.energy_cost && data.inputs.secondary.is_pressed()
{
update.character = CharacterState::SpinMelee(Data {
buildup_duration: self.buildup_duration_default,
buildup_duration_default: self.buildup_duration_default,
recover_duration: self.recover_duration_default,
recover_duration_default: self.recover_duration_default,
base_damage: self.base_damage,
exhausted: false,
hits_remaining: self.hits_remaining_default,
hits_remaining_default: self.hits_remaining_default,
energy_cost: self.energy_cost,
});
// Consumes energy if there's enough left and RMB is held down
update
.energy
.change_by(-(self.energy_cost as i32), EnergySource::Ability);
} else {
// Done
update.character = CharacterState::Wielding;
// Make sure attack component is removed
data.updater.remove::<Attacking>(data.entity);
}
// Grant energy on successful hit
if let Some(attack) = data.attacking {
if attack.applied && attack.hit_count > 0 {
data.updater.remove::<Attacking>(data.entity);
update.energy.change_by(10, EnergySource::HitEnemy);
}
}
update
}
}

View File

@ -244,6 +244,7 @@ impl<'a> System<'a> for Sys {
CharacterState::Boost(data) => data.handle_event(&j, action), CharacterState::Boost(data) => data.handle_event(&j, action),
CharacterState::DashMelee(data) => data.handle_event(&j, action), CharacterState::DashMelee(data) => data.handle_event(&j, action),
CharacterState::LeapMelee(data) => data.handle_event(&j, action), CharacterState::LeapMelee(data) => data.handle_event(&j, action),
CharacterState::SpinMelee(data) => data.handle_event(&j, action),
}; };
local_emitter.append(&mut state_update.local_events); local_emitter.append(&mut state_update.local_events);
server_emitter.append(&mut state_update.server_events); server_emitter.append(&mut state_update.server_events);
@ -269,6 +270,7 @@ impl<'a> System<'a> for Sys {
CharacterState::Boost(data) => data.behavior(&j), CharacterState::Boost(data) => data.behavior(&j),
CharacterState::DashMelee(data) => data.behavior(&j), CharacterState::DashMelee(data) => data.behavior(&j),
CharacterState::LeapMelee(data) => data.behavior(&j), CharacterState::LeapMelee(data) => data.behavior(&j),
CharacterState::SpinMelee(data) => data.behavior(&j),
}; };
local_emitter.append(&mut state_update.local_events); local_emitter.append(&mut state_update.local_events);

View File

@ -1,5 +1,8 @@
use crate::{ use crate::{
comp::{CharacterState, ControlEvent, Controller}, comp::{
slot::{EquipSlot, Slot},
CharacterState, ControlEvent, Controller, InventoryManip,
},
event::{EventBus, LocalEvent, ServerEvent}, event::{EventBus, LocalEvent, ServerEvent},
state::DeltaTime, state::DeltaTime,
sync::{Uid, UidAllocator}, sync::{Uid, UidAllocator},
@ -79,7 +82,18 @@ impl<'a> System<'a> for Sys {
server_emitter.emit(ServerEvent::ToggleLantern(entity)) server_emitter.emit(ServerEvent::ToggleLantern(entity))
}, },
ControlEvent::InventoryManip(manip) => { ControlEvent::InventoryManip(manip) => {
// Unwield if a wielded equipment slot is being modified, to avoid entering
// a barehanded wielding state.
if character_state.is_wield() {
match manip {
InventoryManip::Drop(Slot::Equip(EquipSlot::Mainhand))
| InventoryManip::Swap(_, Slot::Equip(EquipSlot::Mainhand))
| InventoryManip::Swap(Slot::Equip(EquipSlot::Mainhand), _) => {
*character_state = CharacterState::Idle; *character_state = CharacterState::Idle;
},
_ => (),
}
}
server_emitter.emit(ServerEvent::InventoryManip(entity, manip)) server_emitter.emit(ServerEvent::InventoryManip(entity, manip))
}, },
ControlEvent::Respawn => server_emitter.emit(ServerEvent::Respawn(entity)), ControlEvent::Respawn => server_emitter.emit(ServerEvent::Respawn(entity)),

View File

@ -22,7 +22,7 @@ impl<'a> System<'a> for Sys {
fn run( fn run(
&mut self, &mut self,
(entities, dt, server_event_bus, character_states, mut stats,mut energies): Self::SystemData, (entities, dt, server_event_bus, character_states, mut stats, mut energies): Self::SystemData,
) { ) {
let mut server_event_emitter = server_event_bus.emitter(); let mut server_event_emitter = server_event_bus.emitter();
@ -62,7 +62,7 @@ impl<'a> System<'a> for Sys {
server_event_emitter.emit(ServerEvent::LevelUp(entity, stat.level.level())); server_event_emitter.emit(ServerEvent::LevelUp(entity, stat.level.level()));
} }
stat.update_max_hp(); stat.update_max_hp(stat.body_type);
stat.health stat.health
.set_to(stat.health.maximum(), HealthSource::LevelUp); .set_to(stat.health.maximum(), HealthSource::LevelUp);
} }
@ -103,6 +103,7 @@ impl<'a> System<'a> for Sys {
CharacterState::BasicMelee { .. } CharacterState::BasicMelee { .. }
| CharacterState::DashMelee { .. } | CharacterState::DashMelee { .. }
| CharacterState::LeapMelee { .. } | CharacterState::LeapMelee { .. }
| CharacterState::SpinMelee { .. }
| CharacterState::TripleStrike { .. } | CharacterState::TripleStrike { .. }
| CharacterState::BasicRanged { .. } => { | CharacterState::BasicRanged { .. } => {
if energy.get_unchecked().regen_rate != 0.0 { if energy.get_unchecked().regen_rate != 0.0 {

View File

@ -13,10 +13,10 @@ common = { package = "veloren-common", path = "../common" }
world = { package = "veloren-world", path = "../world" } world = { package = "veloren-world", path = "../world" }
network = { package = "veloren_network", path = "../network", default-features = false } network = { package = "veloren_network", path = "../network", default-features = false }
specs-idvs = { git = "https://gitlab.com/veloren/specs-idvs.git" } specs-idvs = { git = "https://gitlab.com/veloren/specs-idvs.git", branch = "specs-git" }
tracing = "0.1" tracing = "0.1"
specs = { version = "0.16.1", features = ["shred-derive"] } specs = { git = "https://github.com/amethyst/specs.git", features = ["shred-derive"], rev = "7a2e348ab2223818bad487695c66c43db88050a5" }
vek = { version = "0.11.0", features = ["serde"] } vek = { version = "0.11.0", features = ["serde"] }
uvth = "3.1.1" uvth = "3.1.1"
futures-util = "0.3" futures-util = "0.3"

View File

@ -544,7 +544,7 @@ fn handle_spawn(
.create_npc( .create_npc(
pos, pos,
comp::Stats::new(get_npc_name(id).into(), body), comp::Stats::new(get_npc_name(id).into(), body),
LoadoutBuilder::animal().build(), LoadoutBuilder::animal(body).build(),
body, body,
) )
.with(comp::Vel(vel)) .with(comp::Vel(vel))
@ -1568,7 +1568,7 @@ fn handle_set_level(
{ {
stats.level.set_level(lvl); stats.level.set_level(lvl);
stats.update_max_hp(); stats.update_max_hp(stats.body_type);
stats stats
.health .health
.set_to(stats.health.maximum(), comp::HealthSource::LevelUp); .set_to(stats.health.maximum(), comp::HealthSource::LevelUp);

View File

@ -63,9 +63,12 @@ pub fn handle_destroy(server: &mut Server, entity: EcsEntity, cause: HealthSourc
if let Some(attacker_stats) = stats.get_mut(attacker) { if let Some(attacker_stats) = stats.get_mut(attacker) {
// TODO: Discuss whether we should give EXP by Player // TODO: Discuss whether we should give EXP by Player
// Killing or not. // Killing or not.
attacker_stats attacker_stats.exp.change_by(
.exp (entity_stats.body_type.base_exp()
.change_by((entity_stats.level.level() * 10) as i64); + entity_stats.level.level()
* entity_stats.body_type.base_exp_increase())
as i64,
);
} }
}); });
} }

View File

@ -0,0 +1 @@
-- Nothing to undo since up.sql only creates missing inventory/loadout records

View File

@ -0,0 +1,15 @@
-- This migration was added in conjunction with a change that no longer creates missing inventory/loadout records
-- for characters that don't have one, to prevent such characters from being unable to log in.
-- Create a default loadout for any characters that don't have one
INSERT INTO loadout (character_id, items)
SELECT c."id", "{""active_item"":{""item"":{""name"":""Battered Sword"",""description"":""Two-Hand Sword\n\nPower: 2-10\n\nHeld together by Rust and hope.\n\n<Right-Click to use>"",""kind"":{""Tool"":{""kind"":{""Sword"":""BasicSword""},""equip_time_millis"":300}}},""ability1"":{""TripleStrike"":{""base_damage"":5,""needs_timing"":false}},""ability2"":{""DashMelee"":{""energy_cost"":700,""buildup_duration"":{""secs"":0,""nanos"":500000000},""recover_duration"":{""secs"":0,""nanos"":500000000},""base_damage"":10}},""ability3"":null,""block_ability"":""BasicBlock"",""dodge_ability"":""Roll""},""second_item"":null,""shoulder"":null,""chest"":{""name"":""Rugged Shirt"",""description"":""Chest\n\nArmor: 0\n\nSmells like Adventure.\n\n<Right-Click to use>"",""kind"":{""Armor"":{""kind"":{""Chest"":""Rugged0""},""stats"":20}}},""belt"":null,""hand"":null,""pants"":{""name"":""Rugged Commoner's Pants"",""description"":""Legs\n\nArmor: 0\n\nThey remind you of the old days.\n\n<Right-Click to use>"",""kind"":{""Armor"":{""kind"":{""Pants"":""Rugged0""},""stats"":20}}},""foot"":{""name"":""Worn out Sandals"",""description"":""Feet\n\nArmor: 0\n\nLoyal companions.\n\n<Right-Click to use>"",""kind"":{""Armor"":{""kind"":{""Foot"":""Sandal0""},""stats"":20}}},""back"":null,""ring"":null,""neck"":null,""lantern"":{""name"":""Black Lantern"",""description"":""Used by city guards."",""kind"":{""Lantern"":{""kind"":""Black0"",""color"":{""r"":255,""g"":190,""b"":75},""strength_thousandths"":3000,""flicker_thousandths"":300}}},""head"":null,""tabard"":null}"
FROM character c
WHERE (SELECT COUNT(1) FROM loadout WHERE character_id = c.id) = 0;
-- Create a default inventory for any characters that don't have one
INSERT INTO inventory (character_id, items)
SELECT c."id", "{""slots"":[{""name"":""Dwarven Cheese"",""description"":""Restores 15 Health\n\nAromatic and nutritious\n\n<Right-Click to use>"",""kind"":{""Consumable"":{""kind"":""Cheese"",""effect"":{""Health"":{""amount"":15,""cause"":""Item""}},""amount"":1}}},{""name"":""Apple"",""description"":""Restores 20 Health\n\nRed and juicy\n\n<Right-Click to use>"",""kind"":{""Consumable"":{""kind"":""Apple"",""effect"":{""Health"":{""amount"":20,""cause"":""Item""}},""amount"":1}}},null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null],""amount"":2}"
FROM character c
WHERE (SELECT COUNT(1) FROM inventory WHERE character_id = c.id) = 0;

View File

@ -0,0 +1 @@
-- Nothing to undo since up.sql only fixes corrupt JSON in loadouts

View File

@ -0,0 +1,7 @@
-- This is a migration to fix loadouts that contain old versions of items with the DashMelee skill before the
-- energy_cost field was added to it in https://gitlab.com/veloren/veloren/-/merge_requests/1140
-- This missing field in the JSON prevents accounts with affected characters from being able log in due to JSON
-- deserialization failure.
UPDATE loadout
SET items = REPLACE(items, '{"DashMelee":{"buildup_duration"','{"DashMelee":{"energy_cost":700,"buildup_duration"')
WHERE items LIKE '%DashMelee%';

View File

@ -236,71 +236,35 @@ impl Drop for CharacterLoader {
fn load_character_data(player_uuid: &str, character_id: i32, db_dir: &str) -> CharacterDataResult { fn load_character_data(player_uuid: &str, character_id: i32, db_dir: &str) -> CharacterDataResult {
let connection = establish_connection(db_dir); let connection = establish_connection(db_dir);
let (character_data, body_data, stats_data, maybe_inventory, maybe_loadout) = let result = schema::character::dsl::character
schema::character::dsl::character
.filter(schema::character::id.eq(character_id)) .filter(schema::character::id.eq(character_id))
.filter(schema::character::player_uuid.eq(player_uuid)) .filter(schema::character::player_uuid.eq(player_uuid))
.inner_join(schema::body::table) .inner_join(schema::body::table)
.inner_join(schema::stats::table) .inner_join(schema::stats::table)
.left_join(schema::inventory::table) .inner_join(schema::inventory::table)
.left_join(schema::loadout::table) .inner_join(schema::loadout::table)
.first::<(Character, Body, Stats, Option<Inventory>, Option<Loadout>)>(&connection)?; .first::<(Character, Body, Stats, Inventory, Loadout)>(&connection);
Ok(( match result {
Ok((character_data, body_data, stats_data, inventory, loadout)) => Ok((
comp::Body::from(&body_data), comp::Body::from(&body_data),
comp::Stats::from(StatsJoinData { comp::Stats::from(StatsJoinData {
alias: &character_data.alias, alias: &character_data.alias,
body: &comp::Body::from(&body_data), body: &comp::Body::from(&body_data),
stats: &stats_data, stats: &stats_data,
}), }),
maybe_inventory.map_or_else( comp::Inventory::from(inventory),
|| { comp::Loadout::from(&loadout),
// If no inventory record was found for the character, create it now )),
let row = Inventory::from((character_data.id, comp::Inventory::default())); Err(e) => {
error!(
if let Err(error) = diesel::insert_into(schema::inventory::table)
.values(&row)
.execute(&connection)
{
warn!(
"Failed to create an inventory record for character {}: {}",
&character_data.id, error
)
}
comp::Inventory::default()
},
comp::Inventory::from,
),
maybe_loadout.map_or_else(
|| {
// Create if no record was found
let default_loadout = LoadoutBuilder::new()
.defaults()
.active_item(LoadoutBuilder::default_item_config_from_str(
character_data.tool.as_deref(),
))
.build();
let row = NewLoadout::from((character_data.id, &default_loadout));
if let Err(e) = diesel::insert_into(schema::loadout::table)
.values(&row)
.execute(&connection)
{
let char_id = character_data.id;
warn!(
?e, ?e,
?char_id, ?character_id,
"Failed to create an loadout record for character", "Failed to load character data for character"
) );
} Err(Error::CharacterDataError)
default_loadout
}, },
|data| comp::Loadout::from(&data), }
),
))
} }
/// Loads a list of characters belonging to the player. This data is a small /// Loads a list of characters belonging to the player. This data is a small
@ -311,31 +275,22 @@ fn load_character_data(player_uuid: &str, character_id: i32, db_dir: &str) -> Ch
/// stats, body, etc...) the character is skipped, and no entry will be /// stats, body, etc...) the character is skipped, and no entry will be
/// returned. /// returned.
fn load_character_list(player_uuid: &str, db_dir: &str) -> CharacterListResult { fn load_character_list(player_uuid: &str, db_dir: &str) -> CharacterListResult {
let data = schema::character::dsl::character let result = schema::character::dsl::character
.filter(schema::character::player_uuid.eq(player_uuid)) .filter(schema::character::player_uuid.eq(player_uuid))
.order(schema::character::id.desc()) .order(schema::character::id.desc())
.inner_join(schema::body::table) .inner_join(schema::body::table)
.inner_join(schema::stats::table) .inner_join(schema::stats::table)
.left_join(schema::loadout::table) .inner_join(schema::loadout::table)
.load::<(Character, Body, Stats, Option<Loadout>)>(&establish_connection(db_dir))?; .load::<(Character, Body, Stats, Loadout)>(&establish_connection(db_dir));
Ok(data match result {
Ok(data) => Ok(data
.iter() .iter()
.map(|(character_data, body_data, stats_data, maybe_loadout)| { .map(|(character_data, body_data, stats_data, loadout)| {
let character = CharacterData::from(character_data); let character = CharacterData::from(character_data);
let body = comp::Body::from(body_data); let body = comp::Body::from(body_data);
let level = stats_data.level as usize; let level = stats_data.level as usize;
let loadout = maybe_loadout.as_ref().map_or_else( let loadout = comp::Loadout::from(loadout);
|| {
LoadoutBuilder::new()
.defaults()
.active_item(LoadoutBuilder::default_item_config_from_str(
character.tool.as_deref(),
))
.build()
},
comp::Loadout::from,
);
CharacterItem { CharacterItem {
character, character,
@ -344,7 +299,12 @@ fn load_character_list(player_uuid: &str, db_dir: &str) -> CharacterListResult {
loadout, loadout,
} }
}) })
.collect()) .collect()),
Err(e) => {
error!(?e, ?player_uuid, "Failed to load character list for player");
Err(Error::CharacterDataError)
},
}
} }
/// Create a new character with provided comp::Character and comp::Body data. /// Create a new character with provided comp::Character and comp::Body data.

View File

@ -2,7 +2,7 @@ extern crate serde_json;
use super::schema::{body, character, inventory, loadout, stats}; use super::schema::{body, character, inventory, loadout, stats};
use crate::comp; use crate::comp;
use common::{character::Character as CharacterData, LoadoutBuilder}; use common::character::Character as CharacterData;
use diesel::sql_types::Text; use diesel::sql_types::Text;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use tracing::warn; use tracing::warn;
@ -106,7 +106,7 @@ impl From<StatsJoinData<'_>> for comp::Stats {
base_stats.exp.set_current(data.stats.exp as u32); base_stats.exp.set_current(data.stats.exp as u32);
base_stats.update_max_hp(); base_stats.update_max_hp(base_stats.body_type);
base_stats base_stats
.health .health
.set_to(base_stats.health.maximum(), comp::HealthSource::Revive); .set_to(base_stats.health.maximum(), comp::HealthSource::Revive);
@ -212,14 +212,7 @@ where
bytes: Option<&<DB as diesel::backend::Backend>::RawValue>, bytes: Option<&<DB as diesel::backend::Backend>::RawValue>,
) -> diesel::deserialize::Result<Self> { ) -> diesel::deserialize::Result<Self> {
let t = String::from_sql(bytes)?; let t = String::from_sql(bytes)?;
serde_json::from_str(&t).map_err(Box::from)
match serde_json::from_str(&t) {
Ok(data) => Ok(Self(data)),
Err(e) => {
warn!(?e, "Failed to deserialise inventory data");
Ok(Self(comp::Inventory::default()))
},
}
} }
} }
@ -298,23 +291,7 @@ where
bytes: Option<&<DB as diesel::backend::Backend>::RawValue>, bytes: Option<&<DB as diesel::backend::Backend>::RawValue>,
) -> diesel::deserialize::Result<Self> { ) -> diesel::deserialize::Result<Self> {
let t = String::from_sql(bytes)?; let t = String::from_sql(bytes)?;
serde_json::from_str(&t).map_err(Box::from)
match serde_json::from_str(&t) {
Ok(data) => Ok(Self(data)),
Err(e) => {
warn!(?e, "Failed to deserialise loadout data");
// We don't have a weapon reference here, so we default to sword
let loadout = LoadoutBuilder::new()
.defaults()
.active_item(LoadoutBuilder::default_item_config_from_str(Some(
"common.items.weapons.sword.starter_sword",
)))
.build();
Ok(Self(loadout))
},
}
} }
} }

View File

@ -215,7 +215,7 @@ impl<'a> System<'a> for Sys {
head: None, head: None,
tabard: None, tabard: None,
}, },
_ => LoadoutBuilder::animal().build(), _ => LoadoutBuilder::animal(entity.body).build(),
}; };
let mut scale = entity.scale; let mut scale = entity.scale;
@ -296,7 +296,7 @@ impl<'a> System<'a> for Sys {
scale = 2.0 + rand::random::<f32>(); scale = 2.0 + rand::random::<f32>();
} }
stats.update_max_hp(); stats.update_max_hp(stats.body_type);
stats stats
.health .health

View File

@ -34,8 +34,8 @@ conrod_winit = { git = "https://gitlab.com/veloren/conrod.git", branch = "pre-wi
euc = { git = "https://github.com/zesterer/euc.git" } euc = { git = "https://github.com/zesterer/euc.git" }
# ECS # ECS
specs = "0.16.1" specs = { git = "https://github.com/amethyst/specs.git", rev = "7a2e348ab2223818bad487695c66c43db88050a5" }
specs-idvs = { git = "https://gitlab.com/veloren/specs-idvs.git" } specs-idvs = { git = "https://gitlab.com/veloren/specs-idvs.git", branch = "specs-git" }
# Mathematics # Mathematics
vek = { version = "0.11.0", features = [ vek = { version = "0.11.0", features = [

View File

@ -17,6 +17,7 @@ pub mod run;
pub mod shoot; pub mod shoot;
pub mod sit; pub mod sit;
pub mod spin; pub mod spin;
pub mod spinmelee;
pub mod stand; pub mod stand;
pub mod swim; pub mod swim;
pub mod wield; pub mod wield;
@ -28,8 +29,8 @@ pub use self::{
dance::DanceAnimation, dash::DashAnimation, equip::EquipAnimation, dance::DanceAnimation, dash::DashAnimation, equip::EquipAnimation,
glidewield::GlideWieldAnimation, gliding::GlidingAnimation, idle::IdleAnimation, glidewield::GlideWieldAnimation, gliding::GlidingAnimation, idle::IdleAnimation,
jump::JumpAnimation, leapmelee::LeapAnimation, roll::RollAnimation, run::RunAnimation, jump::JumpAnimation, leapmelee::LeapAnimation, roll::RollAnimation, run::RunAnimation,
shoot::ShootAnimation, sit::SitAnimation, spin::SpinAnimation, stand::StandAnimation, shoot::ShootAnimation, sit::SitAnimation, spin::SpinAnimation, spinmelee::SpinMeleeAnimation,
swim::SwimAnimation, wield::WieldAnimation, stand::StandAnimation, swim::SwimAnimation, wield::WieldAnimation,
}; };
use super::{Bone, FigureBoneData, Skeleton}; use super::{Bone, FigureBoneData, Skeleton};

View File

@ -0,0 +1,170 @@
use super::{super::Animation, CharacterSkeleton, SkeletonAttr};
use common::comp::item::{Hands, ToolKind};
use std::f32::consts::PI;
use vek::*;
pub struct SpinMeleeAnimation;
impl Animation for SpinMeleeAnimation {
type Dependency = (Option<ToolKind>, Option<ToolKind>, Vec3<f32>, f64);
type Skeleton = CharacterSkeleton;
#[cfg(feature = "use-dyn-lib")]
const UPDATE_FN: &'static [u8] = b"character_spinmelee\0";
#[cfg_attr(feature = "be-dyn-lib", export_name = "character_spinmelee")]
#[allow(clippy::approx_constant)] // TODO: Pending review in #587
fn update_skeleton_inner(
skeleton: &Self::Skeleton,
(active_tool_kind, second_tool_kind, velocity, _global_time): Self::Dependency,
anim_time: f64,
rate: &mut f32,
skeleton_attr: &SkeletonAttr,
) -> Self::Skeleton {
*rate = 1.0;
let lab = 1.0;
let speed = Vec2::<f32>::from(velocity).magnitude();
let mut next = (*skeleton).clone();
//torso movement
let xshift = if velocity.z.abs() < 0.1 {
((anim_time as f32 - 1.1) * lab as f32 * 3.0).sin()
} else {
0.0
};
let yshift = if velocity.z.abs() < 0.1 {
((anim_time as f32 - 1.1) * lab as f32 * 3.0 + PI / 2.0).sin()
} else {
0.0
};
let spin = if anim_time < 1.1 && velocity.z.abs() < 0.1 {
0.5 * ((anim_time as f32).powf(2.0))
} else {
lab as f32 * anim_time as f32 * 0.9
};
//feet
let slowersmooth = (anim_time as f32 * lab as f32 * 4.0).sin();
let quick = (anim_time as f32 * lab as f32 * 8.0).sin();
if let Some(ToolKind::Axe(_)) = active_tool_kind {
next.l_hand.offset = Vec3::new(-0.5, 0.0, 4.0);
next.l_hand.ori = Quaternion::rotation_x(PI / 2.0)
* Quaternion::rotation_z(0.0)
* Quaternion::rotation_y(PI);
next.l_hand.scale = Vec3::one() * 1.08;
next.r_hand.offset = Vec3::new(0.5, 0.0, -2.5);
next.r_hand.ori = Quaternion::rotation_x(PI / 2.0)
* Quaternion::rotation_z(0.0)
* Quaternion::rotation_y(0.0);
next.r_hand.scale = Vec3::one() * 1.06;
next.main.offset = Vec3::new(-0.0, -2.0, -1.0);
next.main.ori = Quaternion::rotation_x(0.0)
* Quaternion::rotation_y(0.0)
* Quaternion::rotation_z(0.0);
next.control.offset = Vec3::new(0.0, 16.0, 3.0);
next.control.ori = Quaternion::rotation_x(-1.4)
* Quaternion::rotation_y(0.0)
* Quaternion::rotation_z(1.4);
next.control.scale = Vec3::one();
next.head.offset = Vec3::new(0.0, skeleton_attr.head.0, skeleton_attr.head.1);
next.head.ori = Quaternion::rotation_z(0.0)
* Quaternion::rotation_x(-0.15)
* Quaternion::rotation_y(0.08);
next.chest.offset = Vec3::new(
0.0,
skeleton_attr.chest.0 - 3.0,
skeleton_attr.chest.1 - 2.0,
);
next.chest.ori = Quaternion::rotation_z(0.0)
* Quaternion::rotation_x(-0.1)
* Quaternion::rotation_y(0.3);
next.chest.scale = Vec3::one();
next.belt.offset = Vec3::new(0.0, 1.0, -1.0);
next.belt.ori = Quaternion::rotation_z(0.0)
* Quaternion::rotation_x(0.4)
* Quaternion::rotation_y(0.0);
next.belt.scale = Vec3::one() * 0.98;
next.shorts.offset = Vec3::new(0.0, 3.0, -2.5);
next.shorts.ori = Quaternion::rotation_z(0.0)
* Quaternion::rotation_x(0.7)
* Quaternion::rotation_y(0.0);
next.shorts.scale = Vec3::one();
next.torso.offset = Vec3::new(
-xshift * (anim_time as f32).min(0.6),
-yshift * (anim_time as f32).min(0.6),
0.0,
) * skeleton_attr.scaler;
next.torso.ori = Quaternion::rotation_z(spin * -16.0)
* Quaternion::rotation_x(0.0)
* Quaternion::rotation_y(0.0);
next.torso.scale = Vec3::one() / 11.0 * skeleton_attr.scaler;
}
if velocity.z.abs() > 0.1 {
next.l_foot.offset = Vec3::new(-skeleton_attr.foot.0, 8.0, skeleton_attr.foot.2 + 2.0);
next.l_foot.ori = Quaternion::rotation_x(1.0) * Quaternion::rotation_z(0.0);
next.l_foot.scale = Vec3::one();
next.r_foot.offset = Vec3::new(skeleton_attr.foot.0, 8.0, skeleton_attr.foot.2 + 2.0);
next.r_foot.ori = Quaternion::rotation_x(1.0);
next.r_foot.scale = Vec3::one();
} else if speed < 0.5 {
next.l_foot.offset = Vec3::new(
-skeleton_attr.foot.0,
2.0 + quick * -6.0,
skeleton_attr.foot.2,
);
next.l_foot.ori =
Quaternion::rotation_x(0.5 + slowersmooth * 0.2) * Quaternion::rotation_z(0.0);
next.l_foot.scale = Vec3::one();
next.r_foot.offset = Vec3::new(skeleton_attr.foot.0, 4.0, skeleton_attr.foot.2);
next.r_foot.ori =
Quaternion::rotation_x(0.5 - slowersmooth * 0.2) * Quaternion::rotation_y(-0.4);
next.r_foot.scale = Vec3::one();
} else {
next.l_foot.offset = Vec3::new(
-skeleton_attr.foot.0,
2.0 + quick * -6.0,
skeleton_attr.foot.2,
);
next.l_foot.ori =
Quaternion::rotation_x(0.5 + slowersmooth * 0.2) * Quaternion::rotation_z(0.0);
next.l_foot.scale = Vec3::one();
next.r_foot.offset = Vec3::new(
skeleton_attr.foot.0,
2.0 + quick * 6.0,
skeleton_attr.foot.2,
);
next.r_foot.ori =
Quaternion::rotation_x(0.5 - slowersmooth * 0.2) * Quaternion::rotation_z(0.0);
next.r_foot.scale = Vec3::one();
};
next.lantern.offset = Vec3::new(
skeleton_attr.lantern.0,
skeleton_attr.lantern.1,
skeleton_attr.lantern.2,
);
next.lantern.ori = Quaternion::rotation_z(0.0)
* Quaternion::rotation_x(0.7)
* Quaternion::rotation_y(-0.8);
next.glider.offset = Vec3::new(0.0, 0.0, 10.0);
next.glider.scale = Vec3::one() * 0.0;
next.l_control.scale = Vec3::one();
next.r_control.scale = Vec3::one();
next.second.scale = match (
active_tool_kind.map(|tk| tk.into_hands()),
second_tool_kind.map(|tk| tk.into_hands()),
) {
(Some(Hands::OneHand), Some(Hands::OneHand)) => Vec3::one(),
(_, _) => Vec3::zero(),
};
next
}
}

View File

@ -131,7 +131,7 @@ impl Animation for WieldAnimation {
let hand_scale = 1.12; let hand_scale = 1.12;
next.control.offset = Vec3::new(0.0, 0.0, 0.0); next.control.offset = Vec3::new(0.0, 0.0, 0.0);
// next.control.ori = Quaternion::rotation_x(u_slow * 0.15 + 1.0) //next.control.ori = Quaternion::rotation_x(slow * 1.0);
// * Quaternion::rotation_y(0.0) // * Quaternion::rotation_y(0.0)
// * Quaternion::rotation_z(u_slowalt * 0.08); // * Quaternion::rotation_z(u_slowalt * 0.08);
// next.control.scale = Vec3::one(); // next.control.scale = Vec3::one();
@ -172,26 +172,52 @@ impl Animation for WieldAnimation {
// next.r_control.scale = Vec3::one(); // next.r_control.scale = Vec3::one();
}, },
Some(ToolKind::Axe(_)) => { Some(ToolKind::Axe(_)) => {
next.l_hand.offset = Vec3::new(-4.0, 3.0, 6.0); if velocity < 0.5 {
next.l_hand.ori = Quaternion::rotation_x(-0.3) next.head.offset = Vec3::new(
* Quaternion::rotation_z(3.14 - 0.3) 0.0,
* Quaternion::rotation_y(-0.8); -3.5 + skeleton_attr.head.0,
next.l_hand.scale = Vec3::one() * 1.08; skeleton_attr.head.1 + u_slow * 0.1,
next.r_hand.offset = Vec3::new(-2.5, 9.0, 4.0); );
next.r_hand.ori = Quaternion::rotation_x(-0.3) next.head.ori = Quaternion::rotation_z(head_look.x)
* Quaternion::rotation_z(3.14 - 0.3) * Quaternion::rotation_x(0.35 + head_look.y.abs());
* Quaternion::rotation_y(-0.8); next.head.scale = Vec3::one() * skeleton_attr.head_scale;
next.r_hand.scale = Vec3::one() * 1.06; next.chest.ori = Quaternion::rotation_x(-0.35)
next.main.offset = Vec3::new(-6.0, 10.0, -1.0); * Quaternion::rotation_y(u_slowalt * 0.04)
next.main.ori = Quaternion::rotation_x(1.27) * Quaternion::rotation_z(0.15);
* Quaternion::rotation_y(-0.3) next.belt.offset =
* Quaternion::rotation_z(-0.8); Vec3::new(0.0, 1.0 + skeleton_attr.belt.0, skeleton_attr.belt.1);
next.belt.ori = Quaternion::rotation_x(0.15)
next.control.offset = Vec3::new(0.0, 0.0, 0.0); * Quaternion::rotation_y(u_slowalt * 0.03)
next.control.ori = Quaternion::rotation_x(u_slowalt * 0.1 + 0.2) * Quaternion::rotation_z(0.15);
* Quaternion::rotation_y(-0.3) next.shorts.offset =
* Quaternion::rotation_z(u_slow * 0.1 + 0.0); Vec3::new(0.0, 1.0 + skeleton_attr.shorts.0, skeleton_attr.shorts.1);
next.shorts.ori = Quaternion::rotation_x(0.15) * Quaternion::rotation_z(0.25);
next.control.ori = Quaternion::rotation_x(1.8)
* Quaternion::rotation_y(-0.5)
* Quaternion::rotation_z(PI - 0.2);
next.control.scale = Vec3::one(); next.control.scale = Vec3::one();
} else {
next.control.ori = Quaternion::rotation_x(2.1)
* Quaternion::rotation_y(-0.4)
* Quaternion::rotation_z(PI - 0.2);
next.control.scale = Vec3::one();
}
next.l_hand.offset = Vec3::new(-0.5, 0.0, 4.0);
next.l_hand.ori = Quaternion::rotation_x(PI / 2.0)
* Quaternion::rotation_z(0.0)
* Quaternion::rotation_y(0.0);
next.l_hand.scale = Vec3::one() * 1.08;
next.r_hand.offset = Vec3::new(0.5, 0.0, -2.5);
next.r_hand.ori = Quaternion::rotation_x(PI / 2.0)
* Quaternion::rotation_z(0.0)
* Quaternion::rotation_y(0.0);
next.r_hand.scale = Vec3::one() * 1.06;
next.main.offset = Vec3::new(-0.0, -2.0, -1.0);
next.main.ori = Quaternion::rotation_x(0.0)
* Quaternion::rotation_y(0.0)
* Quaternion::rotation_z(0.0);
next.control.offset = Vec3::new(-3.0, 11.0, 3.0);
}, },
Some(ToolKind::Hammer(_)) => { Some(ToolKind::Hammer(_)) => {
next.l_hand.offset = Vec3::new(-12.0, 0.0, 0.0); next.l_hand.offset = Vec3::new(-12.0, 0.0, 0.0);

View File

@ -117,6 +117,7 @@ image_ids! {
flyingrod_m2: "voxygen.element.icons.debug_wand_m2", flyingrod_m2: "voxygen.element.icons.debug_wand_m2",
charge: "voxygen.element.icons.skill_charge_3", charge: "voxygen.element.icons.skill_charge_3",
hammerleap: "voxygen.element.icons.skill_hammerleap", hammerleap: "voxygen.element.icons.skill_hammerleap",
axespin: "voxygen.element.icons.skill_axespin",
// Skillbar // Skillbar
level_up: "voxygen.element.misc_bg.level_up", level_up: "voxygen.element.misc_bg.level_up",

View File

@ -708,7 +708,7 @@ impl<'a> Widget for Skillbar<'a> {
Some(ToolKind::Dagger(_)) => self.imgs.onehdagger_m2, Some(ToolKind::Dagger(_)) => self.imgs.onehdagger_m2,
Some(ToolKind::Shield(_)) => self.imgs.onehshield_m2, Some(ToolKind::Shield(_)) => self.imgs.onehshield_m2,
Some(ToolKind::Hammer(_)) => self.imgs.hammerleap, Some(ToolKind::Hammer(_)) => self.imgs.hammerleap,
Some(ToolKind::Axe(_)) => self.imgs.nothing, Some(ToolKind::Axe(_)) => self.imgs.axespin,
Some(ToolKind::Bow(_)) => self.imgs.bow_m2, Some(ToolKind::Bow(_)) => self.imgs.bow_m2,
Some(ToolKind::Staff(StaffKind::Sceptre)) => self.imgs.heal_0, Some(ToolKind::Staff(StaffKind::Sceptre)) => self.imgs.heal_0,
Some(ToolKind::Staff(_)) => self.imgs.staff_m2, Some(ToolKind::Staff(_)) => self.imgs.staff_m2,

View File

@ -8,6 +8,7 @@ pub const FAR_PLANE: f32 = 100000.0;
const FIRST_PERSON_INTERP_TIME: f32 = 0.1; const FIRST_PERSON_INTERP_TIME: f32 = 0.1;
const THIRD_PERSON_INTERP_TIME: f32 = 0.1; const THIRD_PERSON_INTERP_TIME: f32 = 0.1;
const FREEFLY_INTERP_TIME: f32 = 0.0;
const LERP_ORI_RATE: f32 = 15.0; const LERP_ORI_RATE: f32 = 15.0;
pub const MIN_ZOOM: f32 = 0.1; pub const MIN_ZOOM: f32 = 0.1;
@ -16,6 +17,7 @@ pub const MIN_ZOOM: f32 = 0.1;
pub enum CameraMode { pub enum CameraMode {
FirstPerson = 0, FirstPerson = 0,
ThirdPerson = 1, ThirdPerson = 1,
Freefly = 2,
} }
impl Default for CameraMode { impl Default for CameraMode {
@ -75,15 +77,7 @@ impl Camera {
/// and position of the camera. /// and position of the camera.
pub fn compute_dependents(&mut self, terrain: &impl ReadVol) { pub fn compute_dependents(&mut self, terrain: &impl ReadVol) {
let dist = { let dist = {
let (start, end) = ( let (start, end) = (self.focus - self.forward() * self.dist, self.focus);
self.focus
+ (Vec3::new(
-f32::sin(self.ori.x) * f32::cos(self.ori.y),
-f32::cos(self.ori.x) * f32::cos(self.ori.y),
f32::sin(self.ori.y),
) * self.dist),
self.focus,
);
match terrain match terrain
.ray(start, end) .ray(start, end)
@ -161,13 +155,10 @@ impl Camera {
/// Zoom the camera by the given delta, limiting the input accordingly. /// Zoom the camera by the given delta, limiting the input accordingly.
pub fn zoom_by(&mut self, delta: f32) { pub fn zoom_by(&mut self, delta: f32) {
match self.mode { if self.mode == CameraMode::ThirdPerson {
CameraMode::ThirdPerson => {
// Clamp camera dist to the 2 <= x <= infinity range // Clamp camera dist to the 2 <= x <= infinity range
self.tgt_dist = (self.tgt_dist + delta).max(2.0); self.tgt_dist = (self.tgt_dist + delta).max(2.0);
}, }
CameraMode::FirstPerson => {},
};
} }
/// Zoom with the ability to switch between first and third-person mode. /// Zoom with the ability to switch between first and third-person mode.
@ -187,6 +178,7 @@ impl Camera {
self.set_mode(CameraMode::ThirdPerson); self.set_mode(CameraMode::ThirdPerson);
self.tgt_dist = MIN_THIRD_PERSON; self.tgt_dist = MIN_THIRD_PERSON;
}, },
_ => {},
} }
} }
} }
@ -250,6 +242,7 @@ impl Camera {
match self.mode { match self.mode {
CameraMode::FirstPerson => FIRST_PERSON_INTERP_TIME, CameraMode::FirstPerson => FIRST_PERSON_INTERP_TIME,
CameraMode::ThirdPerson => THIRD_PERSON_INTERP_TIME, CameraMode::ThirdPerson => THIRD_PERSON_INTERP_TIME,
CameraMode::Freefly => FREEFLY_INTERP_TIME,
} }
} }
@ -293,6 +286,9 @@ impl Camera {
CameraMode::FirstPerson => { CameraMode::FirstPerson => {
self.set_distance(MIN_ZOOM); self.set_distance(MIN_ZOOM);
}, },
CameraMode::Freefly => {
self.zoom_by(0.0);
},
} }
} }
} }
@ -307,4 +303,45 @@ impl Camera {
mode => mode, mode => mode,
} }
} }
/// Cycle the camera to its next valid mode. If is_admin is false then only
/// modes which are accessible without admin access will be cycled to.
pub fn next_mode(&mut self, is_admin: bool) {
self.set_mode(match self.mode {
CameraMode::ThirdPerson => CameraMode::FirstPerson,
CameraMode::FirstPerson => {
if is_admin {
CameraMode::Freefly
} else {
CameraMode::ThirdPerson
}
},
CameraMode::Freefly => CameraMode::ThirdPerson,
});
}
/// Return a unit vector in the forward direction for the current camera
/// orientation
pub fn forward(&self) -> Vec3<f32> {
Vec3::new(
f32::sin(self.ori.x) * f32::cos(self.ori.y),
f32::cos(self.ori.x) * f32::cos(self.ori.y),
-f32::sin(self.ori.y),
)
}
/// Return a unit vector in the right direction for the current camera
/// orientation
pub fn right(&self) -> Vec3<f32> {
const UP: Vec3<f32> = Vec3::new(0.0, 0.0, 1.0);
self.forward().cross(UP).normalized()
}
/// Return a unit vector in the forward direction on the XY plane for
/// the current camera orientation
pub fn forward_xy(&self) -> Vec2<f32> { Vec2::new(f32::sin(self.ori.x), f32::cos(self.ori.x)) }
/// Return a unit vector in the right direction on the XY plane for
/// the current camera orientation
pub fn right_xy(&self) -> Vec2<f32> { Vec2::new(f32::cos(self.ori.x), -f32::sin(self.ori.x)) }
} }

View File

@ -122,42 +122,50 @@ impl<Skel: Skeleton> FigureModelCache<Skel> {
[ [
match camera_mode { match camera_mode {
CameraMode::ThirdPerson => Some( CameraMode::ThirdPerson | CameraMode::Freefly => Some(
humanoid_head_spec humanoid_head_spec
.mesh_head(&body, |segment, offset| generate_mesh(segment, offset)), .mesh_head(&body, |segment, offset| generate_mesh(segment, offset)),
), ),
CameraMode::FirstPerson => None, CameraMode::FirstPerson => None,
}, },
match camera_mode { match camera_mode {
CameraMode::ThirdPerson => Some(humanoid_armor_chest_spec.mesh_chest( CameraMode::ThirdPerson | CameraMode::Freefly => {
Some(humanoid_armor_chest_spec.mesh_chest(
&body, &body,
loadout, loadout,
|segment, offset| generate_mesh(segment, offset), |segment, offset| generate_mesh(segment, offset),
)), ))
},
CameraMode::FirstPerson => None, CameraMode::FirstPerson => None,
}, },
match camera_mode { match camera_mode {
CameraMode::ThirdPerson => Some(humanoid_armor_belt_spec.mesh_belt( CameraMode::ThirdPerson | CameraMode::Freefly => {
Some(humanoid_armor_belt_spec.mesh_belt(
&body, &body,
loadout, loadout,
|segment, offset| generate_mesh(segment, offset), |segment, offset| generate_mesh(segment, offset),
)), ))
},
CameraMode::FirstPerson => None, CameraMode::FirstPerson => None,
}, },
match camera_mode { match camera_mode {
CameraMode::ThirdPerson => Some(humanoid_armor_back_spec.mesh_back( CameraMode::ThirdPerson | CameraMode::Freefly => {
Some(humanoid_armor_back_spec.mesh_back(
&body, &body,
loadout, loadout,
|segment, offset| generate_mesh(segment, offset), |segment, offset| generate_mesh(segment, offset),
)), ))
},
CameraMode::FirstPerson => None, CameraMode::FirstPerson => None,
}, },
match camera_mode { match camera_mode {
CameraMode::ThirdPerson => Some(humanoid_armor_pants_spec.mesh_pants( CameraMode::ThirdPerson | CameraMode::Freefly => {
Some(humanoid_armor_pants_spec.mesh_pants(
&body, &body,
loadout, loadout,
|segment, offset| generate_mesh(segment, offset), |segment, offset| generate_mesh(segment, offset),
)), ))
},
CameraMode::FirstPerson => None, CameraMode::FirstPerson => None,
}, },
Some(humanoid_armor_hand_spec.mesh_left_hand( Some(humanoid_armor_hand_spec.mesh_left_hand(
@ -181,7 +189,7 @@ impl<Skel: Skeleton> FigureModelCache<Skel> {
|segment, offset| generate_mesh(segment, offset), |segment, offset| generate_mesh(segment, offset),
)), )),
match camera_mode { match camera_mode {
CameraMode::ThirdPerson => { CameraMode::ThirdPerson | CameraMode::Freefly => {
Some(humanoid_armor_shoulder_spec.mesh_left_shoulder( Some(humanoid_armor_shoulder_spec.mesh_left_shoulder(
&body, &body,
loadout, loadout,
@ -191,7 +199,7 @@ impl<Skel: Skeleton> FigureModelCache<Skel> {
CameraMode::FirstPerson => None, CameraMode::FirstPerson => None,
}, },
match camera_mode { match camera_mode {
CameraMode::ThirdPerson => { CameraMode::ThirdPerson | CameraMode::Freefly => {
Some(humanoid_armor_shoulder_spec.mesh_right_shoulder( Some(humanoid_armor_shoulder_spec.mesh_right_shoulder(
&body, &body,
loadout, loadout,

View File

@ -715,6 +715,15 @@ impl FigureMgr {
skeleton_attr, skeleton_attr,
) )
}, },
CharacterState::SpinMelee(_) => {
anim::character::SpinMeleeAnimation::update_skeleton(
&target_base,
(active_tool_kind, second_tool_kind, vel.0, time),
state.state_time,
&mut state_animation_rate,
skeleton_attr,
)
},
CharacterState::TripleStrike(s) => match s.stage { CharacterState::TripleStrike(s) => match s.stage {
triple_strike::Stage::First => { triple_strike::Stage::First => {
anim::character::AlphaAnimation::update_skeleton( anim::character::AlphaAnimation::update_skeleton(

View File

@ -395,10 +395,17 @@ impl Scene {
}, },
CameraMode::ThirdPerson if scene_data.is_aiming => player_scale * 2.1, CameraMode::ThirdPerson if scene_data.is_aiming => player_scale * 2.1,
CameraMode::ThirdPerson => player_scale * 1.65, CameraMode::ThirdPerson => player_scale * 1.65,
CameraMode::Freefly => 0.0,
}; };
self.camera match self.camera.get_mode() {
.set_focus_pos(player_pos + Vec3::unit_z() * (up - tilt.min(0.0).sin() * dist * 0.6)); CameraMode::FirstPerson | CameraMode::ThirdPerson => {
self.camera.set_focus_pos(
player_pos + Vec3::unit_z() * (up - tilt.min(0.0).sin() * dist * 0.6),
);
},
CameraMode::Freefly => {},
};
// Tick camera for interpolation. // Tick camera for interpolation.
self.camera.update( self.camera.update(

View File

@ -177,7 +177,9 @@ impl PlayState for SessionState {
) )
.unwrap(); .unwrap();
let mut ori = self.scene.camera().get_orientation(); let mut walk_forward_dir = self.scene.camera().forward_xy();
let mut walk_right_dir = self.scene.camera().right_xy();
let mut freefly_vel = Vec3::zero();
let mut free_look = false; let mut free_look = false;
let mut auto_walk = false; let mut auto_walk = false;
@ -526,6 +528,14 @@ impl PlayState for SessionState {
_ => {}, _ => {},
} }
}, },
Event::InputUpdate(GameInput::CycleCamera, true) => {
// Prevent accessing camera modes which aren't available in multiplayer
// unless you are an admin. This is an easily bypassed clientside check.
// The server should do its own filtering of which entities are sent to
// clients to prevent abuse.
let camera = self.scene.camera_mut();
camera.next_mode(self.client.borrow().is_admin());
},
Event::AnalogGameInput(input) => match input { Event::AnalogGameInput(input) => match input {
AnalogGameInput::MovementX(v) => { AnalogGameInput::MovementX(v) => {
self.key_state.analog_matrix.x = v; self.key_state.analog_matrix.x = v;
@ -552,17 +562,60 @@ impl PlayState for SessionState {
} }
if !free_look { if !free_look {
ori = self.scene.camera().get_orientation(); walk_forward_dir = self.scene.camera().forward_xy();
walk_right_dir = self.scene.camera().right_xy();
self.inputs.look_dir = Dir::from_unnormalized(cam_dir + aim_dir_offset).unwrap(); self.inputs.look_dir = Dir::from_unnormalized(cam_dir + aim_dir_offset).unwrap();
} }
// Calculate the movement input vector of the player from the current key
// presses and the camera direction. // Get the current state of movement related inputs
let unit_vecs = ( let input_vec = self.key_state.dir_vec();
Vec2::new(ori[0].cos(), -ori[0].sin()), let (axis_right, axis_up) = (input_vec[0], input_vec[1]);
Vec2::new(ori[0].sin(), ori[0].cos()),
); match self.scene.camera().get_mode() {
let dir_vec = self.key_state.dir_vec(); camera::CameraMode::FirstPerson | camera::CameraMode::ThirdPerson => {
self.inputs.move_dir = unit_vecs.0 * dir_vec[0] + unit_vecs.1 * dir_vec[1]; // Move the player character based on their walking direction.
// This could be different from the camera direction if free look is enabled.
self.inputs.move_dir = walk_right_dir * axis_right + walk_forward_dir * axis_up;
freefly_vel = Vec3::zero();
},
camera::CameraMode::Freefly => {
// Move the camera freely in 3d space. Apply acceleration so that
// the movement feels more natural and controlled.
const FREEFLY_ACCEL: f32 = 120.0;
const FREEFLY_DAMPING: f32 = 80.0;
const FREEFLY_MAX_SPEED: f32 = 50.0;
let forward = self.scene.camera().forward();
let right = self.scene.camera().right();
let dir = right * axis_right + forward * axis_up;
let dt = clock.get_last_delta().as_secs_f32();
if freefly_vel.magnitude_squared() > 0.01 {
let new_vel =
freefly_vel - freefly_vel.normalized() * (FREEFLY_DAMPING * dt);
if freefly_vel.dot(new_vel) > 0.0 {
freefly_vel = new_vel;
} else {
freefly_vel = Vec3::zero();
}
}
if dir.magnitude_squared() > 0.01 {
freefly_vel += dir * (FREEFLY_ACCEL * dt);
if freefly_vel.magnitude() > FREEFLY_MAX_SPEED {
freefly_vel = freefly_vel.normalized() * FREEFLY_MAX_SPEED;
}
}
let pos = self.scene.camera().get_focus_pos();
self.scene
.camera_mut()
.set_focus_pos(pos + freefly_vel * dt);
// Do not apply any movement to the player character
self.inputs.move_dir = Vec2::zero();
},
};
self.inputs.climb = self.key_state.climb(); self.inputs.climb = self.key_state.climb();
@ -597,7 +650,7 @@ impl PlayState for SessionState {
global_state, global_state,
DebugInfo { DebugInfo {
tps: clock.get_tps(), tps: clock.get_tps(),
ping_ms: self.client.borrow().get_ping_ms(), ping_ms: self.client.borrow().get_ping_ms_rolling_avg(),
coordinates: self coordinates: self
.client .client
.borrow() .borrow()

View File

@ -142,6 +142,7 @@ impl ControlSettings {
//GameInput::Charge => KeyMouse::Key(VirtualKeyCode::Key1), //GameInput::Charge => KeyMouse::Key(VirtualKeyCode::Key1),
GameInput::FreeLook => KeyMouse::Key(VirtualKeyCode::L), GameInput::FreeLook => KeyMouse::Key(VirtualKeyCode::L),
GameInput::AutoWalk => KeyMouse::Key(VirtualKeyCode::Period), GameInput::AutoWalk => KeyMouse::Key(VirtualKeyCode::Period),
GameInput::CycleCamera => KeyMouse::Key(VirtualKeyCode::Key0),
GameInput::Slot1 => KeyMouse::Key(VirtualKeyCode::Key1), GameInput::Slot1 => KeyMouse::Key(VirtualKeyCode::Key1),
GameInput::Slot2 => KeyMouse::Key(VirtualKeyCode::Key2), GameInput::Slot2 => KeyMouse::Key(VirtualKeyCode::Key2),
GameInput::Slot3 => KeyMouse::Key(VirtualKeyCode::Key3), GameInput::Slot3 => KeyMouse::Key(VirtualKeyCode::Key3),
@ -204,6 +205,7 @@ impl Default for ControlSettings {
//GameInput::Charge, //GameInput::Charge,
GameInput::FreeLook, GameInput::FreeLook,
GameInput::AutoWalk, GameInput::AutoWalk,
GameInput::CycleCamera,
GameInput::Slot1, GameInput::Slot1,
GameInput::Slot2, GameInput::Slot2,
GameInput::Slot3, GameInput::Slot3,

View File

@ -47,6 +47,7 @@ impl Singleplayer {
let paused = Arc::new(AtomicBool::new(false)); let paused = Arc::new(AtomicBool::new(false));
let paused1 = paused.clone(); let paused1 = paused.clone();
let thread = thread::spawn(move || {
let server = Server::new(settings2).expect("Failed to create server instance!"); let server = Server::new(settings2).expect("Failed to create server instance!");
let server = match thread_pool { let server = match thread_pool {
@ -54,7 +55,6 @@ impl Singleplayer {
None => server, None => server,
}; };
let thread = thread::spawn(move || {
run_server(server, receiver, paused1); run_server(server, receiver, paused1);
}); });

View File

@ -65,6 +65,7 @@ pub enum GameInput {
SwapLoadout, SwapLoadout,
FreeLook, FreeLook,
AutoWalk, AutoWalk,
CycleCamera,
} }
impl GameInput { impl GameInput {
@ -89,6 +90,7 @@ impl GameInput {
GameInput::Mount => "gameinput.mount", GameInput::Mount => "gameinput.mount",
GameInput::Enter => "gameinput.enter", GameInput::Enter => "gameinput.enter",
GameInput::Command => "gameinput.command", GameInput::Command => "gameinput.command",
GameInput::CycleCamera => "gameinput.cyclecamera",
GameInput::Escape => "gameinput.escape", GameInput::Escape => "gameinput.escape",
GameInput::Map => "gameinput.map", GameInput::Map => "gameinput.map",
GameInput::Bag => "gameinput.bag", GameInput::Bag => "gameinput.bag",