diff --git a/CHANGELOG.md b/CHANGELOG.md index b369b19bf7..74995d905c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -48,6 +48,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Gave weapons critical strike {chance, multiplier} stats - A system to add glow and reflection effects to figures (i.e: characters, armour, weapons, etc.) - Merchants will trade wares with players +- Airships that can be mounted and flown, and also walked on (`/airship` admin command) ### Changed diff --git a/Cargo.lock b/Cargo.lock index 3e6de98ee3..c16404c034 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5619,6 +5619,7 @@ dependencies = [ "hashbrown", "serde", "specs", + "specs-idvs", "sum_type", "tracing", "vek 0.14.1", diff --git a/assets/common/npc_names.ron b/assets/common/npc_names.ron index 49ceb5c023..aeefb77fee 100644 --- a/assets/common/npc_names.ron +++ b/assets/common/npc_names.ron @@ -969,6 +969,15 @@ ), species: () ), + ship: ( + body: ( + keyword: "ship", + names_0: [ + "Boaty McBoatface", + ], + ), + species: (), + ), biped_small: ( body: ( keyword: "biped_small", diff --git a/assets/server/manifests/ship_manifest.ron b/assets/server/manifests/ship_manifest.ron new file mode 100644 index 0000000000..817d579120 --- /dev/null +++ b/assets/server/manifests/ship_manifest.ron @@ -0,0 +1,23 @@ +({ + DefaultAirship: ( + bone0: ( + //offset: (3.0, 7.0, 1.0), + //offset: (-20.75, -34.75, 1.25), + //offset: (0.0, 0.0, 0.0), + offset: (-17.5, -35.0, 1.0), + //phys_offset: (0.25, 0.25, 0.25), + phys_offset: (0.0, 0.0, 0.0), + central: ("Human_Airship"), + ), + bone1: ( + offset: (0.0, 40.0, -8.0), + phys_offset: (0.0, 0.0, 0.0), + central: ("propeller-l"), + ), + bone2: ( + offset: (0.0, 0.0, -4.0), + phys_offset: (0.0, 0.0, 0.0), + central: ("propeller-r"), + ), + ), +}) diff --git a/assets/server/voxel/Human_Airship.vox b/assets/server/voxel/Human_Airship.vox new file mode 100644 index 0000000000..36e3758d03 --- /dev/null +++ b/assets/server/voxel/Human_Airship.vox @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ba02746d73ebf853c0511b673510c09bd47e3ab0fff13d936feb181a8378ebd9 +size 78024 diff --git a/assets/server/voxel/airship.vox b/assets/server/voxel/airship.vox new file mode 100644 index 0000000000..06bebaa938 --- /dev/null +++ b/assets/server/voxel/airship.vox @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:86f317298900ea98f95c6a33192b25fbbcbd3ce5f105cad58ad3c595a7a7d9ee +size 70176 diff --git a/assets/server/voxel/propeller-l.vox b/assets/server/voxel/propeller-l.vox new file mode 100644 index 0000000000..a193fa89ee --- /dev/null +++ b/assets/server/voxel/propeller-l.vox @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:09ef4bad2557abcc5a2b938f21053babc7770ebe2333039aef9b98ba930b7ec7 +size 1584 diff --git a/assets/server/voxel/propeller-r.vox b/assets/server/voxel/propeller-r.vox new file mode 100644 index 0000000000..5b940751e6 --- /dev/null +++ b/assets/server/voxel/propeller-r.vox @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e4947977524b88bc5adfa934d9061a3499e94b960abb3bcf0a3e2aca482096dc +size 1584 diff --git a/client/src/lib.rs b/client/src/lib.rs index 588ab0ae8b..27bf2f7c90 100644 --- a/client/src/lib.rs +++ b/client/src/lib.rs @@ -33,6 +33,7 @@ use common::{ grid::Grid, outcome::Outcome, recipe::RecipeBook, + resources::PlayerEntity, terrain::{block::Block, neighbors, BiomeKind, SitesKind, TerrainChunk, TerrainChunkSize}, trade::{PendingTrade, TradeAction, TradeId, TradeResult}, uid::{Uid, UidAllocator}, @@ -281,6 +282,7 @@ impl Client { let entity = state.ecs_mut().apply_entity_package(entity_package); *state.ecs_mut().write_resource() = time_of_day; + *state.ecs_mut().write_resource() = PlayerEntity(Some(entity)); state.ecs_mut().insert(material_stats); state.ecs_mut().insert(ability_map); @@ -1458,6 +1460,7 @@ impl Client { ServerGeneral::SetPlayerEntity(uid) => { if let Some(entity) = self.state.ecs().entity_from_uid(uid.0) { self.entity = entity; + *self.state.ecs_mut().write_resource() = PlayerEntity(Some(entity)); } else { return Err(Error::Other("Failed to find entity from uid.".to_owned())); } diff --git a/common/Cargo.toml b/common/Cargo.toml index 9001b753b1..ae126f3ed1 100644 --- a/common/Cargo.toml +++ b/common/Cargo.toml @@ -15,6 +15,7 @@ default = ["simd"] [dependencies] common-base = { package = "veloren-common-base", path = "base" } +#inline_tweak = "1.0.2" # Serde serde = { version = "1.0.110", features = ["derive", "rc"] } diff --git a/common/net/Cargo.toml b/common/net/Cargo.toml index 227ae093f9..5d8773a164 100644 --- a/common/net/Cargo.toml +++ b/common/net/Cargo.toml @@ -12,6 +12,7 @@ default = ["simd"] [dependencies] common = {package = "veloren-common", path = "../../common"} +#inline_tweak = "1.0.2" sum_type = "0.2.0" vek = { version = "=0.14.1", features = ["serde"] } @@ -25,6 +26,7 @@ authc = { git = "https://gitlab.com/veloren/auth.git", rev = "fb3dcbc4962b367253 # ECS specs = { git = "https://github.com/amethyst/specs.git", features = ["serde", "storage-event-control"], rev = "5a9b71035007be0e3574f35184acac1cd4530496" } +specs-idvs = { git = "https://gitlab.com/veloren/specs-idvs.git", rev = "b65fb220e94f5d3c9bc30074a076149763795556" } # Serde -serde = { version = "1.0.110", features = ["derive"] } \ No newline at end of file +serde = { version = "1.0.110", features = ["derive"] } diff --git a/common/net/src/msg/ecs_packet.rs b/common/net/src/msg/ecs_packet.rs index 1ee77e127d..86049f7103 100644 --- a/common/net/src/msg/ecs_packet.rs +++ b/common/net/src/msg/ecs_packet.rs @@ -100,9 +100,9 @@ impl sync::CompPacket for EcsCompPacket { EcsCompPacket::Gravity(comp) => sync::handle_insert(comp, entity, world), EcsCompPacket::Sticky(comp) => sync::handle_insert(comp, entity, world), EcsCompPacket::CharacterState(comp) => sync::handle_insert(comp, entity, world), - EcsCompPacket::Pos(comp) => sync::handle_insert(comp, entity, world), - EcsCompPacket::Vel(comp) => sync::handle_insert(comp, entity, world), - EcsCompPacket::Ori(comp) => sync::handle_insert(comp, entity, world), + EcsCompPacket::Pos(comp) => sync::handle_interp_insert(comp, entity, world), + EcsCompPacket::Vel(comp) => sync::handle_interp_insert(comp, entity, world), + EcsCompPacket::Ori(comp) => sync::handle_interp_insert(comp, entity, world), EcsCompPacket::Shockwave(comp) => sync::handle_insert(comp, entity, world), EcsCompPacket::BeamSegment(comp) => sync::handle_insert(comp, entity, world), } @@ -132,9 +132,9 @@ impl sync::CompPacket for EcsCompPacket { EcsCompPacket::Gravity(comp) => sync::handle_modify(comp, entity, world), EcsCompPacket::Sticky(comp) => sync::handle_modify(comp, entity, world), EcsCompPacket::CharacterState(comp) => sync::handle_modify(comp, entity, world), - EcsCompPacket::Pos(comp) => sync::handle_modify(comp, entity, world), - EcsCompPacket::Vel(comp) => sync::handle_modify(comp, entity, world), - EcsCompPacket::Ori(comp) => sync::handle_modify(comp, entity, world), + EcsCompPacket::Pos(comp) => sync::handle_interp_modify(comp, entity, world), + EcsCompPacket::Vel(comp) => sync::handle_interp_modify(comp, entity, world), + EcsCompPacket::Ori(comp) => sync::handle_interp_modify(comp, entity, world), EcsCompPacket::Shockwave(comp) => sync::handle_modify(comp, entity, world), EcsCompPacket::BeamSegment(comp) => sync::handle_modify(comp, entity, world), } @@ -168,9 +168,9 @@ impl sync::CompPacket for EcsCompPacket { EcsCompPhantom::CharacterState(_) => { sync::handle_remove::(entity, world) }, - EcsCompPhantom::Pos(_) => sync::handle_remove::(entity, world), - EcsCompPhantom::Vel(_) => sync::handle_remove::(entity, world), - EcsCompPhantom::Ori(_) => sync::handle_remove::(entity, world), + EcsCompPhantom::Pos(_) => sync::handle_interp_remove::(entity, world), + EcsCompPhantom::Vel(_) => sync::handle_interp_remove::(entity, world), + EcsCompPhantom::Ori(_) => sync::handle_interp_remove::(entity, world), EcsCompPhantom::Shockwave(_) => sync::handle_remove::(entity, world), EcsCompPhantom::BeamSegment(_) => sync::handle_remove::(entity, world), } diff --git a/common/net/src/sync/interpolation.rs b/common/net/src/sync/interpolation.rs new file mode 100644 index 0000000000..2b2207ad09 --- /dev/null +++ b/common/net/src/sync/interpolation.rs @@ -0,0 +1,155 @@ +// impls of `InterpolatableComponent` on things defined in `common`, since +// `common_net` is downstream of `common`, and an `InterpolationSystem` that +// applies them +use super::InterpolatableComponent; +use common::comp::{Ori, Pos, Vel}; +use specs::Component; +use specs_idvs::IdvStorage; +use tracing::warn; +use vek::ops::{Lerp, Slerp}; + +#[derive(Debug, Default)] +pub struct InterpBuffer { + pub buf: [(f64, T); 4], + pub i: usize, +} + +impl InterpBuffer { + fn push(&mut self, time: f64, x: T) { + let InterpBuffer { + ref mut buf, + ref mut i, + } = self; + *i += 1; + *i %= buf.len(); + buf[*i] = (time, x); + } +} + +impl Component for InterpBuffer { + type Storage = IdvStorage; +} + +// 0 is pure physics, 1 is pure extrapolation +const PHYSICS_VS_EXTRAPOLATION_FACTOR: f32 = 0.2; +const POSITION_INTERP_SANITY: Option = None; +const VELOCITY_INTERP_SANITY: Option = None; +const ENABLE_POSITION_HERMITE: bool = false; + +impl InterpolatableComponent for Pos { + type InterpData = InterpBuffer; + type ReadData = InterpBuffer; + + fn update_component(&self, interp_data: &mut Self::InterpData, time: f64) { + interp_data.push(time, *self); + } + + fn interpolate(self, interp_data: &Self::InterpData, t2: f64, vel: &InterpBuffer) -> Self { + // lerp to test interface, do hermite spline later + let InterpBuffer { ref buf, ref i } = interp_data; + let (t0, p0) = buf[(i + buf.len() - 1) % buf.len()]; + let (t1, p1) = buf[i % buf.len()]; + if (t1 - t0).abs() < f64::EPSILON { + return self; + } + if POSITION_INTERP_SANITY + .map_or(false, |limit| p0.0.distance_squared(p1.0) > limit.powf(2.0)) + { + warn!("position delta exceeded sanity check, clamping"); + return p1; + } + let (t0prime, m0) = vel.buf[(i + vel.buf.len() - 1) % vel.buf.len()]; + let (t1prime, m1) = vel.buf[i % vel.buf.len()]; + let mut out; + let t = (t2 - t0) / (t1 - t0); + if ENABLE_POSITION_HERMITE + && ((t0 - t0prime).abs() < f64::EPSILON && (t1 - t1prime).abs() < f64::EPSILON) + { + let h00 = |t: f64| (2.0 * t.powf(3.0) - 3.0 * t.powf(2.0) + 1.0) as f32; + let h10 = |t: f64| (t.powf(3.0) - 2.0 * t.powf(2.0) + t) as f32; + let h01 = |t: f64| (-2.0 * t.powf(3.0) + 3.0 * t.powf(2.0)) as f32; + let h11 = |t: f64| (t.powf(3.0) - t.powf(2.0)) as f32; + let dt = (t1 - t0) as f32; + out = h00(t) * p0.0 + h10(t) * dt * m0.0 + h01(t) * p1.0 + h11(t) * dt * m1.0; + } else { + if ENABLE_POSITION_HERMITE { + warn!( + "timestamps for pos and vel don't match ({:?}, {:?}), falling back to lerp", + interp_data, vel + ); + } + out = Lerp::lerp_unclamped(p0.0, p1.0, t as f32); + } + + if out.map(|x| x.is_nan()).reduce_or() { + warn!("interpolation output is nan: {}, {}, {:?}", t2, t, buf); + out = p1.0; + } + + Pos(Lerp::lerp(self.0, out, PHYSICS_VS_EXTRAPOLATION_FACTOR)) + } +} + +impl InterpolatableComponent for Vel { + type InterpData = InterpBuffer; + type ReadData = (); + + fn update_component(&self, interp_data: &mut Self::InterpData, time: f64) { + interp_data.push(time, *self); + } + + fn interpolate(self, interp_data: &Self::InterpData, t2: f64, _: &()) -> Self { + let InterpBuffer { ref buf, ref i } = interp_data; + let (t0, p0) = buf[(i + buf.len() - 1) % buf.len()]; + let (t1, p1) = buf[i % buf.len()]; + if (t1 - t0).abs() < f64::EPSILON { + return self; + } + if VELOCITY_INTERP_SANITY + .map_or(false, |limit| p0.0.distance_squared(p1.0) > limit.powf(2.0)) + { + warn!("velocity delta exceeded sanity check, clamping"); + return p1; + } + let lerp_factor = 1.0 + ((t2 - t1) / (t1 - t0)) as f32; + let mut out = Lerp::lerp_unclamped(p0.0, p1.0, lerp_factor); + if out.map(|x| x.is_nan()).reduce_or() { + warn!( + "interpolation output is nan: {}, {}, {:?}", + t2, lerp_factor, buf + ); + out = p1.0; + } + + Vel(Lerp::lerp(self.0, out, PHYSICS_VS_EXTRAPOLATION_FACTOR)) + } +} + +impl InterpolatableComponent for Ori { + type InterpData = InterpBuffer; + type ReadData = (); + + fn update_component(&self, interp_data: &mut Self::InterpData, time: f64) { + interp_data.push(time, *self); + } + + fn interpolate(self, interp_data: &Self::InterpData, t2: f64, _: &()) -> Self { + let InterpBuffer { ref buf, ref i } = interp_data; + let (t0, p0) = buf[(i + buf.len() - 1) % buf.len()]; + let (t1, p1) = buf[i % buf.len()]; + if (t1 - t0).abs() < f64::EPSILON { + return self; + } + let lerp_factor = 1.0 + ((t2 - t1) / (t1 - t0)) as f32; + let mut out = Slerp::slerp_unclamped(p0.to_quat(), p1.to_quat(), lerp_factor); + if out.into_vec4().map(|x| x.is_nan()).reduce_or() { + warn!( + "interpolation output is nan: {}, {}, {:?}", + t2, lerp_factor, buf + ); + out = p1.to_quat(); + } + + Ori::new(Slerp::slerp(self.to_quat(), out, PHYSICS_VS_EXTRAPOLATION_FACTOR).normalized()) + } +} diff --git a/common/net/src/sync/mod.rs b/common/net/src/sync/mod.rs index 2b25c0db43..7997437db5 100644 --- a/common/net/src/sync/mod.rs +++ b/common/net/src/sync/mod.rs @@ -1,5 +1,6 @@ // Note: Currently only one-way sync is supported until a usecase for two-way // sync arises +pub mod interpolation; mod packet; mod sync_ext; mod track; @@ -7,8 +8,9 @@ mod track; // Reexports pub use common::uid::{Uid, UidAllocator}; pub use packet::{ - handle_insert, handle_modify, handle_remove, CompPacket, CompSyncPackage, EntityPackage, - EntitySyncPackage, StatePackage, + handle_insert, handle_interp_insert, handle_interp_modify, handle_interp_remove, handle_modify, + handle_remove, CompPacket, CompSyncPackage, EntityPackage, EntitySyncPackage, + InterpolatableComponent, StatePackage, }; pub use sync_ext::WorldSyncExt; pub use track::UpdateTracker; diff --git a/common/net/src/sync/packet.rs b/common/net/src/sync/packet.rs index e86dfb30fa..152b7946d6 100644 --- a/common/net/src/sync/packet.rs +++ b/common/net/src/sync/packet.rs @@ -1,5 +1,5 @@ use super::track::UpdateTracker; -use common::uid::Uid; +use common::{resources::Time, uid::Uid}; use serde::{de::DeserializeOwned, Deserialize, Serialize}; use specs::{Component, Entity, Join, ReadStorage, World, WorldExt}; use std::{ @@ -9,6 +9,10 @@ use std::{ }; use tracing::error; +// TODO: apply_{insert,modify,remove} all take the entity and call +// `write_storage` once per entity per component, instead of once per update +// batch(e.g. in a system-like memory access pattern); if sync ends up being a +// bottleneck, try optimizing this /// Implemented by type that carries component data for insertion and /// modification The assocatied `Phantom` type only carries information about /// which component type is of interest and is used to transmit deletion events @@ -42,6 +46,44 @@ pub fn handle_remove(entity: Entity, world: &World) { world.write_storage::().remove(entity); } +pub trait InterpolatableComponent: Component { + type InterpData: Component + Default; + type ReadData; + + fn update_component(&self, data: &mut Self::InterpData, time: f64); + fn interpolate(self, data: &Self::InterpData, time: f64, read_data: &Self::ReadData) -> Self; +} + +pub fn handle_interp_insert(comp: C, entity: Entity, world: &World) { + let mut interp_data = C::InterpData::default(); + let time = world.read_resource::