mirror of
https://gitlab.com/veloren/veloren.git
synced 2024-08-30 18:12:32 +00:00
Merge branch 'master' of gitlab.com:veloren/veloren into sharp/small-fixes
This commit is contained in:
commit
6332cbe006
@ -44,8 +44,6 @@ coverage:
|
||||
- cargo build --release
|
||||
- cp -r target/release/veloren-server-cli $CI_PROJECT_DIR
|
||||
- cp -r target/release/veloren-voxygen $CI_PROJECT_DIR
|
||||
- strip --strip-all veloren-server-cli
|
||||
- strip --strip-all veloren-voxygen
|
||||
artifacts:
|
||||
paths:
|
||||
- veloren-server-cli
|
||||
|
@ -40,6 +40,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
- Better pathfinding
|
||||
- Bombs
|
||||
- Training dummy items
|
||||
- Added spin attack for axe
|
||||
- Creature specific stats
|
||||
|
||||
### Changed
|
||||
|
||||
|
2
Cargo.lock
generated
2
Cargo.lock
generated
@ -3717,7 +3717,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "specs-idvs"
|
||||
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 = [
|
||||
"specs",
|
||||
]
|
||||
|
@ -72,7 +72,3 @@ debug = false
|
||||
[profile.releasedebuginfo]
|
||||
inherits = 'release'
|
||||
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)
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
BIN
assets/voxygen/element/icons/skill_axespin.png
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
assets/voxygen/voxel/weapon/axe/rusty_2h.vox
(Stored with Git LFS)
BIN
assets/voxygen/voxel/weapon/axe/rusty_2h.vox
(Stored with Git LFS)
Binary file not shown.
@ -19,7 +19,7 @@ num = "0.2.0"
|
||||
num_cpus = "1.10.1"
|
||||
tracing = { version = "0.1", default-features = false }
|
||||
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"] }
|
||||
hashbrown = { version = "0.7.2", features = ["rayon", "serde", "nightly"] }
|
||||
authc = { git = "https://gitlab.com/veloren/auth.git", rev = "223a4097f7ebc8d451936dccb5e6517194bbf086" }
|
||||
|
@ -39,6 +39,7 @@ use network::{Address, Network, Participant, Pid, Stream, PROMISES_CONSISTENCY,
|
||||
use num::traits::FloatConst;
|
||||
use rayon::prelude::*;
|
||||
use std::{
|
||||
collections::VecDeque,
|
||||
net::SocketAddr,
|
||||
sync::Arc,
|
||||
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
|
||||
// their chat window
|
||||
const SERVER_TIMEOUT_GRACE_PERIOD: f64 = 14.0;
|
||||
const PING_ROLLING_AVERAGE_SECS: usize = 10;
|
||||
|
||||
pub enum Event {
|
||||
Chat(comp::ChatMsg),
|
||||
@ -102,6 +104,7 @@ pub struct Client {
|
||||
last_server_ping: f64,
|
||||
last_server_pong: f64,
|
||||
last_ping_delta: f64,
|
||||
ping_deltas: VecDeque<f64>,
|
||||
|
||||
tick: u64,
|
||||
state: State,
|
||||
@ -344,6 +347,7 @@ impl Client {
|
||||
last_server_ping: 0.0,
|
||||
last_server_pong: 0.0,
|
||||
last_ping_delta: 0.0,
|
||||
ping_deltas: VecDeque::new(),
|
||||
|
||||
tick: 0,
|
||||
state,
|
||||
@ -1030,8 +1034,15 @@ impl Client {
|
||||
},
|
||||
ServerMsg::Pong => {
|
||||
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::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_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
|
||||
/// used for any computationally expensive operations that run outside
|
||||
/// of the main thread (i.e., threads that block on I/O operations are
|
||||
@ -1197,6 +1224,18 @@ impl Client {
|
||||
.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
|
||||
fn clean_state(&mut self) {
|
||||
let client_uid = self
|
||||
|
@ -8,9 +8,9 @@ edition = "2018"
|
||||
no-assets = []
|
||||
|
||||
[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"] }
|
||||
dot_vox = "4.0"
|
||||
image = { version = "0.22.5", default-features = false, features = ["png"] }
|
||||
|
@ -20,6 +20,7 @@ pub enum CharacterAbilityType {
|
||||
BasicBlock,
|
||||
TripleStrike(Stage),
|
||||
LeapMelee,
|
||||
SpinMelee,
|
||||
}
|
||||
|
||||
impl From<&CharacterState> for CharacterAbilityType {
|
||||
@ -32,6 +33,7 @@ impl From<&CharacterState> for CharacterAbilityType {
|
||||
CharacterState::BasicBlock => Self::BasicBlock,
|
||||
CharacterState::LeapMelee(_) => Self::LeapMelee,
|
||||
CharacterState::TripleStrike(data) => Self::TripleStrike(data.stage),
|
||||
CharacterState::SpinMelee(_) => Self::SpinMelee,
|
||||
_ => Self::BasicMelee,
|
||||
}
|
||||
}
|
||||
@ -80,6 +82,12 @@ pub enum CharacterAbility {
|
||||
recover_duration: Duration,
|
||||
base_damage: u32,
|
||||
},
|
||||
SpinMelee {
|
||||
energy_cost: u32,
|
||||
buildup_duration: Duration,
|
||||
recover_duration: Duration,
|
||||
base_damage: u32,
|
||||
},
|
||||
}
|
||||
|
||||
impl CharacterAbility {
|
||||
@ -117,6 +125,10 @@ impl CharacterAbility {
|
||||
.energy
|
||||
.try_change_by(-(*energy_cost as i32), EnergySource::Ability)
|
||||
.is_ok(),
|
||||
CharacterAbility::SpinMelee { energy_cost, .. } => update
|
||||
.energy
|
||||
.try_change_by(-(*energy_cost as i32), EnergySource::Ability)
|
||||
.is_ok(),
|
||||
_ => true,
|
||||
}
|
||||
}
|
||||
@ -239,6 +251,26 @@ impl From<&CharacterAbility> for CharacterState {
|
||||
recover_duration: *recover_duration,
|
||||
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 */
|
||||
}),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -64,6 +64,8 @@ pub enum CharacterState {
|
||||
TripleStrike(triple_strike::Data),
|
||||
/// A leap followed by a small aoe ground attack
|
||||
LeapMelee(leap_melee::Data),
|
||||
/// Spin around, dealing damage to enemies surrounding you
|
||||
SpinMelee(spin_melee::Data),
|
||||
}
|
||||
|
||||
impl CharacterState {
|
||||
@ -75,7 +77,8 @@ impl CharacterState {
|
||||
| CharacterState::DashMelee(_)
|
||||
| CharacterState::TripleStrike(_)
|
||||
| CharacterState::BasicBlock
|
||||
| CharacterState::LeapMelee(_) => true,
|
||||
| CharacterState::LeapMelee(_)
|
||||
| CharacterState::SpinMelee(_) => true,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
@ -86,7 +89,8 @@ impl CharacterState {
|
||||
| CharacterState::BasicRanged(_)
|
||||
| CharacterState::DashMelee(_)
|
||||
| CharacterState::TripleStrike(_)
|
||||
| CharacterState::LeapMelee(_) => true,
|
||||
| CharacterState::LeapMelee(_)
|
||||
| CharacterState::SpinMelee(_) => true,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
@ -221,13 +221,11 @@ impl Tool {
|
||||
base_damage: 7,
|
||||
needs_timing: true,
|
||||
},
|
||||
BasicMelee {
|
||||
SpinMelee {
|
||||
energy_cost: 100,
|
||||
buildup_duration: Duration::from_millis(700),
|
||||
recover_duration: Duration::from_millis(100),
|
||||
base_healthchange: -12,
|
||||
range: 3.5,
|
||||
max_angle: 30.0,
|
||||
buildup_duration: Duration::from_millis(125),
|
||||
recover_duration: Duration::from_millis(125),
|
||||
base_damage: 5,
|
||||
},
|
||||
],
|
||||
Hammer(_) => vec![
|
||||
|
@ -131,6 +131,81 @@ pub struct Stats {
|
||||
pub fitness: u32,
|
||||
pub willpower: u32,
|
||||
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 {
|
||||
@ -143,7 +218,10 @@ impl Stats {
|
||||
}
|
||||
|
||||
// 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 {
|
||||
@ -187,9 +265,10 @@ impl Stats {
|
||||
fitness,
|
||||
willpower,
|
||||
is_dead: false,
|
||||
body_type: body,
|
||||
};
|
||||
|
||||
stats.update_max_hp();
|
||||
stats.update_max_hp(body);
|
||||
stats
|
||||
.health
|
||||
.set_to(stats.health.maximum(), HealthSource::Revive);
|
||||
|
@ -2,7 +2,7 @@ use crate::{
|
||||
assets,
|
||||
comp::{
|
||||
item::{Item, ItemKind},
|
||||
CharacterAbility, ItemConfig, Loadout,
|
||||
Body, CharacterAbility, ItemConfig, Loadout,
|
||||
},
|
||||
};
|
||||
use std::time::Duration;
|
||||
@ -24,6 +24,44 @@ use std::time::Duration;
|
||||
/// ```
|
||||
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 {
|
||||
#[allow(clippy::new_without_default)] // TODO: Pending review in #587
|
||||
pub fn new() -> Self {
|
||||
@ -63,7 +101,7 @@ impl LoadoutBuilder {
|
||||
}
|
||||
|
||||
/// Default animal configuration
|
||||
pub fn animal() -> Self {
|
||||
pub fn animal(body: Body) -> Self {
|
||||
Self(Loadout {
|
||||
active_item: Some(ItemConfig {
|
||||
item: assets::load_expect_cloned("common.items.weapons.empty"),
|
||||
@ -71,8 +109,8 @@ impl LoadoutBuilder {
|
||||
energy_cost: 10,
|
||||
buildup_duration: Duration::from_millis(600),
|
||||
recover_duration: Duration::from_millis(100),
|
||||
base_healthchange: -6,
|
||||
range: 5.0,
|
||||
base_healthchange: -(body.base_dmg() as i32),
|
||||
range: body.base_range(),
|
||||
max_angle: 80.0,
|
||||
}),
|
||||
ability2: None,
|
||||
|
@ -12,6 +12,7 @@ pub mod idle;
|
||||
pub mod leap_melee;
|
||||
pub mod roll;
|
||||
pub mod sit;
|
||||
pub mod spin_melee;
|
||||
pub mod triple_strike;
|
||||
pub mod utils;
|
||||
pub mod wielding;
|
||||
|
146
common/src/states/spin_melee.rs
Normal file
146
common/src/states/spin_melee.rs
Normal 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
|
||||
}
|
||||
}
|
@ -244,6 +244,7 @@ impl<'a> System<'a> for Sys {
|
||||
CharacterState::Boost(data) => data.handle_event(&j, action),
|
||||
CharacterState::DashMelee(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);
|
||||
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::DashMelee(data) => data.behavior(&j),
|
||||
CharacterState::LeapMelee(data) => data.behavior(&j),
|
||||
CharacterState::SpinMelee(data) => data.behavior(&j),
|
||||
};
|
||||
|
||||
local_emitter.append(&mut state_update.local_events);
|
||||
|
@ -1,5 +1,8 @@
|
||||
use crate::{
|
||||
comp::{CharacterState, ControlEvent, Controller},
|
||||
comp::{
|
||||
slot::{EquipSlot, Slot},
|
||||
CharacterState, ControlEvent, Controller, InventoryManip,
|
||||
},
|
||||
event::{EventBus, LocalEvent, ServerEvent},
|
||||
state::DeltaTime,
|
||||
sync::{Uid, UidAllocator},
|
||||
@ -79,7 +82,18 @@ impl<'a> System<'a> for Sys {
|
||||
server_emitter.emit(ServerEvent::ToggleLantern(entity))
|
||||
},
|
||||
ControlEvent::InventoryManip(manip) => {
|
||||
*character_state = CharacterState::Idle;
|
||||
// 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;
|
||||
},
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
server_emitter.emit(ServerEvent::InventoryManip(entity, manip))
|
||||
},
|
||||
ControlEvent::Respawn => server_emitter.emit(ServerEvent::Respawn(entity)),
|
||||
|
@ -22,7 +22,7 @@ impl<'a> System<'a> for Sys {
|
||||
|
||||
fn run(
|
||||
&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();
|
||||
|
||||
@ -62,7 +62,7 @@ impl<'a> System<'a> for Sys {
|
||||
server_event_emitter.emit(ServerEvent::LevelUp(entity, stat.level.level()));
|
||||
}
|
||||
|
||||
stat.update_max_hp();
|
||||
stat.update_max_hp(stat.body_type);
|
||||
stat.health
|
||||
.set_to(stat.health.maximum(), HealthSource::LevelUp);
|
||||
}
|
||||
@ -103,6 +103,7 @@ impl<'a> System<'a> for Sys {
|
||||
CharacterState::BasicMelee { .. }
|
||||
| CharacterState::DashMelee { .. }
|
||||
| CharacterState::LeapMelee { .. }
|
||||
| CharacterState::SpinMelee { .. }
|
||||
| CharacterState::TripleStrike { .. }
|
||||
| CharacterState::BasicRanged { .. } => {
|
||||
if energy.get_unchecked().regen_rate != 0.0 {
|
||||
|
@ -13,10 +13,10 @@ common = { package = "veloren-common", path = "../common" }
|
||||
world = { package = "veloren-world", path = "../world" }
|
||||
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"
|
||||
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"] }
|
||||
uvth = "3.1.1"
|
||||
futures-util = "0.3"
|
||||
|
@ -544,7 +544,7 @@ fn handle_spawn(
|
||||
.create_npc(
|
||||
pos,
|
||||
comp::Stats::new(get_npc_name(id).into(), body),
|
||||
LoadoutBuilder::animal().build(),
|
||||
LoadoutBuilder::animal(body).build(),
|
||||
body,
|
||||
)
|
||||
.with(comp::Vel(vel))
|
||||
@ -1568,7 +1568,7 @@ fn handle_set_level(
|
||||
{
|
||||
stats.level.set_level(lvl);
|
||||
|
||||
stats.update_max_hp();
|
||||
stats.update_max_hp(stats.body_type);
|
||||
stats
|
||||
.health
|
||||
.set_to(stats.health.maximum(), comp::HealthSource::LevelUp);
|
||||
|
@ -63,9 +63,12 @@ pub fn handle_destroy(server: &mut Server, entity: EcsEntity, cause: HealthSourc
|
||||
if let Some(attacker_stats) = stats.get_mut(attacker) {
|
||||
// TODO: Discuss whether we should give EXP by Player
|
||||
// Killing or not.
|
||||
attacker_stats
|
||||
.exp
|
||||
.change_by((entity_stats.level.level() * 10) as i64);
|
||||
attacker_stats.exp.change_by(
|
||||
(entity_stats.body_type.base_exp()
|
||||
+ entity_stats.level.level()
|
||||
* entity_stats.body_type.base_exp_increase())
|
||||
as i64,
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
@ -0,0 +1 @@
|
||||
-- Nothing to undo since up.sql only creates missing inventory/loadout records
|
@ -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;
|
@ -0,0 +1 @@
|
||||
-- Nothing to undo since up.sql only fixes corrupt JSON in loadouts
|
@ -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%';
|
@ -236,71 +236,35 @@ impl Drop for CharacterLoader {
|
||||
fn load_character_data(player_uuid: &str, character_id: i32, db_dir: &str) -> CharacterDataResult {
|
||||
let connection = establish_connection(db_dir);
|
||||
|
||||
let (character_data, body_data, stats_data, maybe_inventory, maybe_loadout) =
|
||||
schema::character::dsl::character
|
||||
.filter(schema::character::id.eq(character_id))
|
||||
.filter(schema::character::player_uuid.eq(player_uuid))
|
||||
.inner_join(schema::body::table)
|
||||
.inner_join(schema::stats::table)
|
||||
.left_join(schema::inventory::table)
|
||||
.left_join(schema::loadout::table)
|
||||
.first::<(Character, Body, Stats, Option<Inventory>, Option<Loadout>)>(&connection)?;
|
||||
let result = schema::character::dsl::character
|
||||
.filter(schema::character::id.eq(character_id))
|
||||
.filter(schema::character::player_uuid.eq(player_uuid))
|
||||
.inner_join(schema::body::table)
|
||||
.inner_join(schema::stats::table)
|
||||
.inner_join(schema::inventory::table)
|
||||
.inner_join(schema::loadout::table)
|
||||
.first::<(Character, Body, Stats, Inventory, Loadout)>(&connection);
|
||||
|
||||
Ok((
|
||||
comp::Body::from(&body_data),
|
||||
comp::Stats::from(StatsJoinData {
|
||||
alias: &character_data.alias,
|
||||
body: &comp::Body::from(&body_data),
|
||||
stats: &stats_data,
|
||||
}),
|
||||
maybe_inventory.map_or_else(
|
||||
|| {
|
||||
// If no inventory record was found for the character, create it now
|
||||
let row = Inventory::from((character_data.id, comp::Inventory::default()));
|
||||
|
||||
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,
|
||||
?char_id,
|
||||
"Failed to create an loadout record for character",
|
||||
)
|
||||
}
|
||||
|
||||
default_loadout
|
||||
},
|
||||
|data| comp::Loadout::from(&data),
|
||||
),
|
||||
))
|
||||
match result {
|
||||
Ok((character_data, body_data, stats_data, inventory, loadout)) => Ok((
|
||||
comp::Body::from(&body_data),
|
||||
comp::Stats::from(StatsJoinData {
|
||||
alias: &character_data.alias,
|
||||
body: &comp::Body::from(&body_data),
|
||||
stats: &stats_data,
|
||||
}),
|
||||
comp::Inventory::from(inventory),
|
||||
comp::Loadout::from(&loadout),
|
||||
)),
|
||||
Err(e) => {
|
||||
error!(
|
||||
?e,
|
||||
?character_id,
|
||||
"Failed to load character data for character"
|
||||
);
|
||||
Err(Error::CharacterDataError)
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
/// Loads a list of characters belonging to the player. This data is a small
|
||||
@ -311,40 +275,36 @@ 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
|
||||
/// returned.
|
||||
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))
|
||||
.order(schema::character::id.desc())
|
||||
.inner_join(schema::body::table)
|
||||
.inner_join(schema::stats::table)
|
||||
.left_join(schema::loadout::table)
|
||||
.load::<(Character, Body, Stats, Option<Loadout>)>(&establish_connection(db_dir))?;
|
||||
.inner_join(schema::loadout::table)
|
||||
.load::<(Character, Body, Stats, Loadout)>(&establish_connection(db_dir));
|
||||
|
||||
Ok(data
|
||||
.iter()
|
||||
.map(|(character_data, body_data, stats_data, maybe_loadout)| {
|
||||
let character = CharacterData::from(character_data);
|
||||
let body = comp::Body::from(body_data);
|
||||
let level = stats_data.level as usize;
|
||||
let loadout = maybe_loadout.as_ref().map_or_else(
|
||||
|| {
|
||||
LoadoutBuilder::new()
|
||||
.defaults()
|
||||
.active_item(LoadoutBuilder::default_item_config_from_str(
|
||||
character.tool.as_deref(),
|
||||
))
|
||||
.build()
|
||||
},
|
||||
comp::Loadout::from,
|
||||
);
|
||||
match result {
|
||||
Ok(data) => Ok(data
|
||||
.iter()
|
||||
.map(|(character_data, body_data, stats_data, loadout)| {
|
||||
let character = CharacterData::from(character_data);
|
||||
let body = comp::Body::from(body_data);
|
||||
let level = stats_data.level as usize;
|
||||
let loadout = comp::Loadout::from(loadout);
|
||||
|
||||
CharacterItem {
|
||||
character,
|
||||
body,
|
||||
level,
|
||||
loadout,
|
||||
}
|
||||
})
|
||||
.collect())
|
||||
CharacterItem {
|
||||
character,
|
||||
body,
|
||||
level,
|
||||
loadout,
|
||||
}
|
||||
})
|
||||
.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.
|
||||
|
@ -2,7 +2,7 @@ extern crate serde_json;
|
||||
|
||||
use super::schema::{body, character, inventory, loadout, stats};
|
||||
use crate::comp;
|
||||
use common::{character::Character as CharacterData, LoadoutBuilder};
|
||||
use common::character::Character as CharacterData;
|
||||
use diesel::sql_types::Text;
|
||||
use serde::{Deserialize, Serialize};
|
||||
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.update_max_hp();
|
||||
base_stats.update_max_hp(base_stats.body_type);
|
||||
base_stats
|
||||
.health
|
||||
.set_to(base_stats.health.maximum(), comp::HealthSource::Revive);
|
||||
@ -212,14 +212,7 @@ where
|
||||
bytes: Option<&<DB as diesel::backend::Backend>::RawValue>,
|
||||
) -> diesel::deserialize::Result<Self> {
|
||||
let t = String::from_sql(bytes)?;
|
||||
|
||||
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()))
|
||||
},
|
||||
}
|
||||
serde_json::from_str(&t).map_err(Box::from)
|
||||
}
|
||||
}
|
||||
|
||||
@ -298,23 +291,7 @@ where
|
||||
bytes: Option<&<DB as diesel::backend::Backend>::RawValue>,
|
||||
) -> diesel::deserialize::Result<Self> {
|
||||
let t = String::from_sql(bytes)?;
|
||||
|
||||
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))
|
||||
},
|
||||
}
|
||||
serde_json::from_str(&t).map_err(Box::from)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -215,7 +215,7 @@ impl<'a> System<'a> for Sys {
|
||||
head: None,
|
||||
tabard: None,
|
||||
},
|
||||
_ => LoadoutBuilder::animal().build(),
|
||||
_ => LoadoutBuilder::animal(entity.body).build(),
|
||||
};
|
||||
|
||||
let mut scale = entity.scale;
|
||||
@ -296,7 +296,7 @@ impl<'a> System<'a> for Sys {
|
||||
scale = 2.0 + rand::random::<f32>();
|
||||
}
|
||||
|
||||
stats.update_max_hp();
|
||||
stats.update_max_hp(stats.body_type);
|
||||
|
||||
stats
|
||||
.health
|
||||
|
@ -34,8 +34,8 @@ conrod_winit = { git = "https://gitlab.com/veloren/conrod.git", branch = "pre-wi
|
||||
euc = { git = "https://github.com/zesterer/euc.git" }
|
||||
|
||||
# ECS
|
||||
specs = "0.16.1"
|
||||
specs-idvs = { git = "https://gitlab.com/veloren/specs-idvs.git" }
|
||||
specs = { git = "https://github.com/amethyst/specs.git", rev = "7a2e348ab2223818bad487695c66c43db88050a5" }
|
||||
specs-idvs = { git = "https://gitlab.com/veloren/specs-idvs.git", branch = "specs-git" }
|
||||
|
||||
# Mathematics
|
||||
vek = { version = "0.11.0", features = [
|
||||
|
@ -17,6 +17,7 @@ pub mod run;
|
||||
pub mod shoot;
|
||||
pub mod sit;
|
||||
pub mod spin;
|
||||
pub mod spinmelee;
|
||||
pub mod stand;
|
||||
pub mod swim;
|
||||
pub mod wield;
|
||||
@ -28,8 +29,8 @@ pub use self::{
|
||||
dance::DanceAnimation, dash::DashAnimation, equip::EquipAnimation,
|
||||
glidewield::GlideWieldAnimation, gliding::GlidingAnimation, idle::IdleAnimation,
|
||||
jump::JumpAnimation, leapmelee::LeapAnimation, roll::RollAnimation, run::RunAnimation,
|
||||
shoot::ShootAnimation, sit::SitAnimation, spin::SpinAnimation, stand::StandAnimation,
|
||||
swim::SwimAnimation, wield::WieldAnimation,
|
||||
shoot::ShootAnimation, sit::SitAnimation, spin::SpinAnimation, spinmelee::SpinMeleeAnimation,
|
||||
stand::StandAnimation, swim::SwimAnimation, wield::WieldAnimation,
|
||||
};
|
||||
|
||||
use super::{Bone, FigureBoneData, Skeleton};
|
||||
|
170
voxygen/src/anim/src/character/spinmelee.rs
Normal file
170
voxygen/src/anim/src/character/spinmelee.rs
Normal 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
|
||||
}
|
||||
}
|
@ -131,7 +131,7 @@ impl Animation for WieldAnimation {
|
||||
let hand_scale = 1.12;
|
||||
|
||||
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_z(u_slowalt * 0.08);
|
||||
// next.control.scale = Vec3::one();
|
||||
@ -172,26 +172,52 @@ impl Animation for WieldAnimation {
|
||||
// next.r_control.scale = Vec3::one();
|
||||
},
|
||||
Some(ToolKind::Axe(_)) => {
|
||||
next.l_hand.offset = Vec3::new(-4.0, 3.0, 6.0);
|
||||
next.l_hand.ori = Quaternion::rotation_x(-0.3)
|
||||
* Quaternion::rotation_z(3.14 - 0.3)
|
||||
* Quaternion::rotation_y(-0.8);
|
||||
if velocity < 0.5 {
|
||||
next.head.offset = Vec3::new(
|
||||
0.0,
|
||||
-3.5 + skeleton_attr.head.0,
|
||||
skeleton_attr.head.1 + u_slow * 0.1,
|
||||
);
|
||||
next.head.ori = Quaternion::rotation_z(head_look.x)
|
||||
* Quaternion::rotation_x(0.35 + head_look.y.abs());
|
||||
next.head.scale = Vec3::one() * skeleton_attr.head_scale;
|
||||
next.chest.ori = Quaternion::rotation_x(-0.35)
|
||||
* Quaternion::rotation_y(u_slowalt * 0.04)
|
||||
* Quaternion::rotation_z(0.15);
|
||||
next.belt.offset =
|
||||
Vec3::new(0.0, 1.0 + skeleton_attr.belt.0, skeleton_attr.belt.1);
|
||||
next.belt.ori = Quaternion::rotation_x(0.15)
|
||||
* Quaternion::rotation_y(u_slowalt * 0.03)
|
||||
* Quaternion::rotation_z(0.15);
|
||||
next.shorts.offset =
|
||||
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();
|
||||
} 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(-2.5, 9.0, 4.0);
|
||||
next.r_hand.ori = Quaternion::rotation_x(-0.3)
|
||||
* Quaternion::rotation_z(3.14 - 0.3)
|
||||
* Quaternion::rotation_y(-0.8);
|
||||
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(-6.0, 10.0, -1.0);
|
||||
next.main.ori = Quaternion::rotation_x(1.27)
|
||||
* Quaternion::rotation_y(-0.3)
|
||||
* Quaternion::rotation_z(-0.8);
|
||||
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, 0.0, 0.0);
|
||||
next.control.ori = Quaternion::rotation_x(u_slowalt * 0.1 + 0.2)
|
||||
* Quaternion::rotation_y(-0.3)
|
||||
* Quaternion::rotation_z(u_slow * 0.1 + 0.0);
|
||||
next.control.scale = Vec3::one();
|
||||
next.control.offset = Vec3::new(-3.0, 11.0, 3.0);
|
||||
},
|
||||
Some(ToolKind::Hammer(_)) => {
|
||||
next.l_hand.offset = Vec3::new(-12.0, 0.0, 0.0);
|
||||
|
@ -117,6 +117,7 @@ image_ids! {
|
||||
flyingrod_m2: "voxygen.element.icons.debug_wand_m2",
|
||||
charge: "voxygen.element.icons.skill_charge_3",
|
||||
hammerleap: "voxygen.element.icons.skill_hammerleap",
|
||||
axespin: "voxygen.element.icons.skill_axespin",
|
||||
|
||||
// Skillbar
|
||||
level_up: "voxygen.element.misc_bg.level_up",
|
||||
|
@ -708,7 +708,7 @@ impl<'a> Widget for Skillbar<'a> {
|
||||
Some(ToolKind::Dagger(_)) => self.imgs.onehdagger_m2,
|
||||
Some(ToolKind::Shield(_)) => self.imgs.onehshield_m2,
|
||||
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::Staff(StaffKind::Sceptre)) => self.imgs.heal_0,
|
||||
Some(ToolKind::Staff(_)) => self.imgs.staff_m2,
|
||||
|
@ -8,6 +8,7 @@ pub const FAR_PLANE: f32 = 100000.0;
|
||||
|
||||
const FIRST_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;
|
||||
pub const MIN_ZOOM: f32 = 0.1;
|
||||
|
||||
@ -16,6 +17,7 @@ pub const MIN_ZOOM: f32 = 0.1;
|
||||
pub enum CameraMode {
|
||||
FirstPerson = 0,
|
||||
ThirdPerson = 1,
|
||||
Freefly = 2,
|
||||
}
|
||||
|
||||
impl Default for CameraMode {
|
||||
@ -75,15 +77,7 @@ impl Camera {
|
||||
/// and position of the camera.
|
||||
pub fn compute_dependents(&mut self, terrain: &impl ReadVol) {
|
||||
let dist = {
|
||||
let (start, end) = (
|
||||
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,
|
||||
);
|
||||
let (start, end) = (self.focus - self.forward() * self.dist, self.focus);
|
||||
|
||||
match terrain
|
||||
.ray(start, end)
|
||||
@ -161,13 +155,10 @@ impl Camera {
|
||||
|
||||
/// Zoom the camera by the given delta, limiting the input accordingly.
|
||||
pub fn zoom_by(&mut self, delta: f32) {
|
||||
match self.mode {
|
||||
CameraMode::ThirdPerson => {
|
||||
// Clamp camera dist to the 2 <= x <= infinity range
|
||||
self.tgt_dist = (self.tgt_dist + delta).max(2.0);
|
||||
},
|
||||
CameraMode::FirstPerson => {},
|
||||
};
|
||||
if self.mode == CameraMode::ThirdPerson {
|
||||
// Clamp camera dist to the 2 <= x <= infinity range
|
||||
self.tgt_dist = (self.tgt_dist + delta).max(2.0);
|
||||
}
|
||||
}
|
||||
|
||||
/// Zoom with the ability to switch between first and third-person mode.
|
||||
@ -187,6 +178,7 @@ impl Camera {
|
||||
self.set_mode(CameraMode::ThirdPerson);
|
||||
self.tgt_dist = MIN_THIRD_PERSON;
|
||||
},
|
||||
_ => {},
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -250,6 +242,7 @@ impl Camera {
|
||||
match self.mode {
|
||||
CameraMode::FirstPerson => FIRST_PERSON_INTERP_TIME,
|
||||
CameraMode::ThirdPerson => THIRD_PERSON_INTERP_TIME,
|
||||
CameraMode::Freefly => FREEFLY_INTERP_TIME,
|
||||
}
|
||||
}
|
||||
|
||||
@ -293,6 +286,9 @@ impl Camera {
|
||||
CameraMode::FirstPerson => {
|
||||
self.set_distance(MIN_ZOOM);
|
||||
},
|
||||
CameraMode::Freefly => {
|
||||
self.zoom_by(0.0);
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -307,4 +303,45 @@ impl Camera {
|
||||
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)) }
|
||||
}
|
||||
|
@ -122,42 +122,50 @@ impl<Skel: Skeleton> FigureModelCache<Skel> {
|
||||
|
||||
[
|
||||
match camera_mode {
|
||||
CameraMode::ThirdPerson => Some(
|
||||
CameraMode::ThirdPerson | CameraMode::Freefly => Some(
|
||||
humanoid_head_spec
|
||||
.mesh_head(&body, |segment, offset| generate_mesh(segment, offset)),
|
||||
),
|
||||
CameraMode::FirstPerson => None,
|
||||
},
|
||||
match camera_mode {
|
||||
CameraMode::ThirdPerson => Some(humanoid_armor_chest_spec.mesh_chest(
|
||||
&body,
|
||||
loadout,
|
||||
|segment, offset| generate_mesh(segment, offset),
|
||||
)),
|
||||
CameraMode::ThirdPerson | CameraMode::Freefly => {
|
||||
Some(humanoid_armor_chest_spec.mesh_chest(
|
||||
&body,
|
||||
loadout,
|
||||
|segment, offset| generate_mesh(segment, offset),
|
||||
))
|
||||
},
|
||||
CameraMode::FirstPerson => None,
|
||||
},
|
||||
match camera_mode {
|
||||
CameraMode::ThirdPerson => Some(humanoid_armor_belt_spec.mesh_belt(
|
||||
&body,
|
||||
loadout,
|
||||
|segment, offset| generate_mesh(segment, offset),
|
||||
)),
|
||||
CameraMode::ThirdPerson | CameraMode::Freefly => {
|
||||
Some(humanoid_armor_belt_spec.mesh_belt(
|
||||
&body,
|
||||
loadout,
|
||||
|segment, offset| generate_mesh(segment, offset),
|
||||
))
|
||||
},
|
||||
CameraMode::FirstPerson => None,
|
||||
},
|
||||
match camera_mode {
|
||||
CameraMode::ThirdPerson => Some(humanoid_armor_back_spec.mesh_back(
|
||||
&body,
|
||||
loadout,
|
||||
|segment, offset| generate_mesh(segment, offset),
|
||||
)),
|
||||
CameraMode::ThirdPerson | CameraMode::Freefly => {
|
||||
Some(humanoid_armor_back_spec.mesh_back(
|
||||
&body,
|
||||
loadout,
|
||||
|segment, offset| generate_mesh(segment, offset),
|
||||
))
|
||||
},
|
||||
CameraMode::FirstPerson => None,
|
||||
},
|
||||
match camera_mode {
|
||||
CameraMode::ThirdPerson => Some(humanoid_armor_pants_spec.mesh_pants(
|
||||
&body,
|
||||
loadout,
|
||||
|segment, offset| generate_mesh(segment, offset),
|
||||
)),
|
||||
CameraMode::ThirdPerson | CameraMode::Freefly => {
|
||||
Some(humanoid_armor_pants_spec.mesh_pants(
|
||||
&body,
|
||||
loadout,
|
||||
|segment, offset| generate_mesh(segment, offset),
|
||||
))
|
||||
},
|
||||
CameraMode::FirstPerson => None,
|
||||
},
|
||||
Some(humanoid_armor_hand_spec.mesh_left_hand(
|
||||
@ -181,7 +189,7 @@ impl<Skel: Skeleton> FigureModelCache<Skel> {
|
||||
|segment, offset| generate_mesh(segment, offset),
|
||||
)),
|
||||
match camera_mode {
|
||||
CameraMode::ThirdPerson => {
|
||||
CameraMode::ThirdPerson | CameraMode::Freefly => {
|
||||
Some(humanoid_armor_shoulder_spec.mesh_left_shoulder(
|
||||
&body,
|
||||
loadout,
|
||||
@ -191,7 +199,7 @@ impl<Skel: Skeleton> FigureModelCache<Skel> {
|
||||
CameraMode::FirstPerson => None,
|
||||
},
|
||||
match camera_mode {
|
||||
CameraMode::ThirdPerson => {
|
||||
CameraMode::ThirdPerson | CameraMode::Freefly => {
|
||||
Some(humanoid_armor_shoulder_spec.mesh_right_shoulder(
|
||||
&body,
|
||||
loadout,
|
||||
|
@ -715,6 +715,15 @@ impl FigureMgr {
|
||||
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 {
|
||||
triple_strike::Stage::First => {
|
||||
anim::character::AlphaAnimation::update_skeleton(
|
||||
|
@ -395,10 +395,17 @@ impl Scene {
|
||||
},
|
||||
CameraMode::ThirdPerson if scene_data.is_aiming => player_scale * 2.1,
|
||||
CameraMode::ThirdPerson => player_scale * 1.65,
|
||||
CameraMode::Freefly => 0.0,
|
||||
};
|
||||
|
||||
self.camera
|
||||
.set_focus_pos(player_pos + Vec3::unit_z() * (up - tilt.min(0.0).sin() * dist * 0.6));
|
||||
match self.camera.get_mode() {
|
||||
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.
|
||||
self.camera.update(
|
||||
|
@ -177,7 +177,9 @@ impl PlayState for SessionState {
|
||||
)
|
||||
.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 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 {
|
||||
AnalogGameInput::MovementX(v) => {
|
||||
self.key_state.analog_matrix.x = v;
|
||||
@ -552,17 +562,60 @@ impl PlayState for SessionState {
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
// Calculate the movement input vector of the player from the current key
|
||||
// presses and the camera direction.
|
||||
let unit_vecs = (
|
||||
Vec2::new(ori[0].cos(), -ori[0].sin()),
|
||||
Vec2::new(ori[0].sin(), ori[0].cos()),
|
||||
);
|
||||
let dir_vec = self.key_state.dir_vec();
|
||||
self.inputs.move_dir = unit_vecs.0 * dir_vec[0] + unit_vecs.1 * dir_vec[1];
|
||||
|
||||
// Get the current state of movement related inputs
|
||||
let input_vec = self.key_state.dir_vec();
|
||||
let (axis_right, axis_up) = (input_vec[0], input_vec[1]);
|
||||
|
||||
match self.scene.camera().get_mode() {
|
||||
camera::CameraMode::FirstPerson | camera::CameraMode::ThirdPerson => {
|
||||
// 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();
|
||||
|
||||
@ -597,7 +650,7 @@ impl PlayState for SessionState {
|
||||
global_state,
|
||||
DebugInfo {
|
||||
tps: clock.get_tps(),
|
||||
ping_ms: self.client.borrow().get_ping_ms(),
|
||||
ping_ms: self.client.borrow().get_ping_ms_rolling_avg(),
|
||||
coordinates: self
|
||||
.client
|
||||
.borrow()
|
||||
|
@ -142,6 +142,7 @@ impl ControlSettings {
|
||||
//GameInput::Charge => KeyMouse::Key(VirtualKeyCode::Key1),
|
||||
GameInput::FreeLook => KeyMouse::Key(VirtualKeyCode::L),
|
||||
GameInput::AutoWalk => KeyMouse::Key(VirtualKeyCode::Period),
|
||||
GameInput::CycleCamera => KeyMouse::Key(VirtualKeyCode::Key0),
|
||||
GameInput::Slot1 => KeyMouse::Key(VirtualKeyCode::Key1),
|
||||
GameInput::Slot2 => KeyMouse::Key(VirtualKeyCode::Key2),
|
||||
GameInput::Slot3 => KeyMouse::Key(VirtualKeyCode::Key3),
|
||||
@ -204,6 +205,7 @@ impl Default for ControlSettings {
|
||||
//GameInput::Charge,
|
||||
GameInput::FreeLook,
|
||||
GameInput::AutoWalk,
|
||||
GameInput::CycleCamera,
|
||||
GameInput::Slot1,
|
||||
GameInput::Slot2,
|
||||
GameInput::Slot3,
|
||||
|
@ -47,14 +47,14 @@ impl Singleplayer {
|
||||
let paused = Arc::new(AtomicBool::new(false));
|
||||
let paused1 = paused.clone();
|
||||
|
||||
let server = Server::new(settings2).expect("Failed to create server instance!");
|
||||
|
||||
let server = match thread_pool {
|
||||
Some(pool) => server.with_thread_pool(pool),
|
||||
None => server,
|
||||
};
|
||||
|
||||
let thread = thread::spawn(move || {
|
||||
let server = Server::new(settings2).expect("Failed to create server instance!");
|
||||
|
||||
let server = match thread_pool {
|
||||
Some(pool) => server.with_thread_pool(pool),
|
||||
None => server,
|
||||
};
|
||||
|
||||
run_server(server, receiver, paused1);
|
||||
});
|
||||
|
||||
|
@ -65,6 +65,7 @@ pub enum GameInput {
|
||||
SwapLoadout,
|
||||
FreeLook,
|
||||
AutoWalk,
|
||||
CycleCamera,
|
||||
}
|
||||
|
||||
impl GameInput {
|
||||
@ -89,6 +90,7 @@ impl GameInput {
|
||||
GameInput::Mount => "gameinput.mount",
|
||||
GameInput::Enter => "gameinput.enter",
|
||||
GameInput::Command => "gameinput.command",
|
||||
GameInput::CycleCamera => "gameinput.cyclecamera",
|
||||
GameInput::Escape => "gameinput.escape",
|
||||
GameInput::Map => "gameinput.map",
|
||||
GameInput::Bag => "gameinput.bag",
|
||||
|
Loading…
Reference in New Issue
Block a user