mirror of
https://gitlab.com/veloren/veloren.git
synced 2024-08-30 18:12:32 +00:00
Merge branch 'aweinstock/airship-mvp-rebased' into 'master'
Airships See merge request veloren/veloren!1888
This commit is contained in:
commit
5b21ee7200
@ -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
|
||||
|
||||
|
1
Cargo.lock
generated
1
Cargo.lock
generated
@ -5619,6 +5619,7 @@ dependencies = [
|
||||
"hashbrown",
|
||||
"serde",
|
||||
"specs",
|
||||
"specs-idvs",
|
||||
"sum_type",
|
||||
"tracing",
|
||||
"vek 0.14.1",
|
||||
|
@ -969,6 +969,15 @@
|
||||
),
|
||||
species: ()
|
||||
),
|
||||
ship: (
|
||||
body: (
|
||||
keyword: "ship",
|
||||
names_0: [
|
||||
"Boaty McBoatface",
|
||||
],
|
||||
),
|
||||
species: (),
|
||||
),
|
||||
biped_small: (
|
||||
body: (
|
||||
keyword: "biped_small",
|
||||
|
23
assets/server/manifests/ship_manifest.ron
Normal file
23
assets/server/manifests/ship_manifest.ron
Normal file
@ -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"),
|
||||
),
|
||||
),
|
||||
})
|
BIN
assets/server/voxel/Human_Airship.vox
(Stored with Git LFS)
Normal file
BIN
assets/server/voxel/Human_Airship.vox
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
assets/server/voxel/airship.vox
(Stored with Git LFS)
Normal file
BIN
assets/server/voxel/airship.vox
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
assets/server/voxel/propeller-l.vox
(Stored with Git LFS)
Normal file
BIN
assets/server/voxel/propeller-l.vox
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
assets/server/voxel/propeller-r.vox
(Stored with Git LFS)
Normal file
BIN
assets/server/voxel/propeller-r.vox
(Stored with Git LFS)
Normal file
Binary file not shown.
@ -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()));
|
||||
}
|
||||
|
@ -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"] }
|
||||
|
@ -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"] }
|
||||
serde = { version = "1.0.110", features = ["derive"] }
|
||||
|
@ -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::<comp::CharacterState>(entity, world)
|
||||
},
|
||||
EcsCompPhantom::Pos(_) => sync::handle_remove::<comp::Pos>(entity, world),
|
||||
EcsCompPhantom::Vel(_) => sync::handle_remove::<comp::Vel>(entity, world),
|
||||
EcsCompPhantom::Ori(_) => sync::handle_remove::<comp::Ori>(entity, world),
|
||||
EcsCompPhantom::Pos(_) => sync::handle_interp_remove::<comp::Pos>(entity, world),
|
||||
EcsCompPhantom::Vel(_) => sync::handle_interp_remove::<comp::Vel>(entity, world),
|
||||
EcsCompPhantom::Ori(_) => sync::handle_interp_remove::<comp::Ori>(entity, world),
|
||||
EcsCompPhantom::Shockwave(_) => sync::handle_remove::<comp::Shockwave>(entity, world),
|
||||
EcsCompPhantom::BeamSegment(_) => sync::handle_remove::<comp::Ori>(entity, world),
|
||||
}
|
||||
|
155
common/net/src/sync/interpolation.rs
Normal file
155
common/net/src/sync/interpolation.rs
Normal file
@ -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<T> {
|
||||
pub buf: [(f64, T); 4],
|
||||
pub i: usize,
|
||||
}
|
||||
|
||||
impl<T> InterpBuffer<T> {
|
||||
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<T: 'static + Send + Sync> Component for InterpBuffer<T> {
|
||||
type Storage = IdvStorage<Self>;
|
||||
}
|
||||
|
||||
// 0 is pure physics, 1 is pure extrapolation
|
||||
const PHYSICS_VS_EXTRAPOLATION_FACTOR: f32 = 0.2;
|
||||
const POSITION_INTERP_SANITY: Option<f32> = None;
|
||||
const VELOCITY_INTERP_SANITY: Option<f32> = None;
|
||||
const ENABLE_POSITION_HERMITE: bool = false;
|
||||
|
||||
impl InterpolatableComponent for Pos {
|
||||
type InterpData = InterpBuffer<Pos>;
|
||||
type ReadData = InterpBuffer<Vel>;
|
||||
|
||||
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<Vel>) -> 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<Vel>;
|
||||
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<Ori>;
|
||||
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())
|
||||
}
|
||||
}
|
@ -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;
|
||||
|
@ -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<C: Component>(entity: Entity, world: &World) {
|
||||
world.write_storage::<C>().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<C: InterpolatableComponent>(comp: C, entity: Entity, world: &World) {
|
||||
let mut interp_data = C::InterpData::default();
|
||||
let time = world.read_resource::<Time>().0;
|
||||
comp.update_component(&mut interp_data, time);
|
||||
handle_insert(comp, entity, world);
|
||||
handle_insert(interp_data, entity, world);
|
||||
}
|
||||
|
||||
pub fn handle_interp_modify<C: InterpolatableComponent + Debug>(
|
||||
comp: C,
|
||||
entity: Entity,
|
||||
world: &World,
|
||||
) {
|
||||
if let Some(mut interp_data) = world.write_storage::<C::InterpData>().get_mut(entity) {
|
||||
let time = world.read_resource::<Time>().0;
|
||||
comp.update_component(&mut interp_data, time);
|
||||
handle_modify(comp, entity, world);
|
||||
} else {
|
||||
error!(
|
||||
?comp,
|
||||
"Error modifying interpolation data for synced component, it doesn't seem to exist"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn handle_interp_remove<C: InterpolatableComponent>(entity: Entity, world: &World) {
|
||||
handle_remove::<C>(entity, world);
|
||||
handle_remove::<C::InterpData>(entity, world);
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, Serialize, Deserialize)]
|
||||
pub enum CompUpdateKind<P: CompPacket> {
|
||||
Inserted(P),
|
||||
|
@ -36,6 +36,7 @@ impl ChatCommandData {
|
||||
#[derive(Copy, Clone)]
|
||||
pub enum ChatCommand {
|
||||
Adminify,
|
||||
Airship,
|
||||
Alias,
|
||||
Ban,
|
||||
Build,
|
||||
@ -89,6 +90,7 @@ pub enum ChatCommand {
|
||||
// Thank you for keeping this sorted alphabetically :-)
|
||||
pub static CHAT_COMMANDS: &[ChatCommand] = &[
|
||||
ChatCommand::Adminify,
|
||||
ChatCommand::Airship,
|
||||
ChatCommand::Alias,
|
||||
ChatCommand::Ban,
|
||||
ChatCommand::Build,
|
||||
@ -222,6 +224,11 @@ impl ChatCommand {
|
||||
"Temporarily gives a player admin permissions or removes them",
|
||||
Admin,
|
||||
),
|
||||
ChatCommand::Airship => cmd(
|
||||
vec![Float("destination_degrees_ccw_of_east", 90.0, Optional)],
|
||||
"Spawns an airship",
|
||||
Admin,
|
||||
),
|
||||
ChatCommand::Alias => cmd(vec![Any("name", Required)], "Change your alias", NoAdmin),
|
||||
ChatCommand::Ban => cmd(
|
||||
vec![Any("username", Required), Message(Optional)],
|
||||
@ -449,6 +456,7 @@ impl ChatCommand {
|
||||
pub fn keyword(&self) -> &'static str {
|
||||
match self {
|
||||
ChatCommand::Adminify => "adminify",
|
||||
ChatCommand::Airship => "airship",
|
||||
ChatCommand::Alias => "alias",
|
||||
ChatCommand::Ban => "ban",
|
||||
ChatCommand::Build => "build",
|
||||
|
@ -168,6 +168,7 @@ impl<'a> From<&'a Body> for Psyche {
|
||||
Body::Golem(_) => 1.0,
|
||||
Body::Theropod(_) => 1.0,
|
||||
Body::Dragon(_) => 1.0,
|
||||
Body::Ship(_) => 1.0,
|
||||
},
|
||||
}
|
||||
}
|
||||
@ -221,6 +222,15 @@ impl Agent {
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_destination(pos: Vec3<f32>) -> Self {
|
||||
Self {
|
||||
can_speak: true,
|
||||
psyche: Psyche { aggro: 1.0 },
|
||||
rtsim_controller: RtSimController::with_destination(pos),
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new(
|
||||
patrol_origin: Option<Vec3<f32>>,
|
||||
can_speak: bool,
|
||||
|
@ -11,6 +11,7 @@ pub mod object;
|
||||
pub mod quadruped_low;
|
||||
pub mod quadruped_medium;
|
||||
pub mod quadruped_small;
|
||||
pub mod ship;
|
||||
pub mod theropod;
|
||||
|
||||
use crate::{
|
||||
@ -44,6 +45,7 @@ make_case_elim!(
|
||||
Golem(body: golem::Body) = 11,
|
||||
Theropod(body: theropod::Body) = 12,
|
||||
QuadrupedLow(body: quadruped_low::Body) = 13,
|
||||
Ship(body: ship::Body) = 14,
|
||||
}
|
||||
);
|
||||
|
||||
@ -78,6 +80,7 @@ pub struct AllBodies<BodyMeta, SpeciesMeta> {
|
||||
pub golem: BodyData<BodyMeta, golem::AllSpecies<SpeciesMeta>>,
|
||||
pub theropod: BodyData<BodyMeta, theropod::AllSpecies<SpeciesMeta>>,
|
||||
pub quadruped_low: BodyData<BodyMeta, quadruped_low::AllSpecies<SpeciesMeta>>,
|
||||
pub ship: BodyData<BodyMeta, ()>,
|
||||
}
|
||||
|
||||
/// Can only retrieve body metadata by direct index.
|
||||
@ -124,6 +127,7 @@ impl<'a, BodyMeta, SpeciesMeta> core::ops::Index<&'a Body> for AllBodies<BodyMet
|
||||
Body::Golem(_) => &self.golem.body,
|
||||
Body::Theropod(_) => &self.theropod.body,
|
||||
Body::QuadrupedLow(_) => &self.quadruped_low.body,
|
||||
Body::Ship(_) => &self.ship.body,
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -218,6 +222,7 @@ impl Body {
|
||||
Body::Golem(_) => 2.5,
|
||||
Body::BipedSmall(_) => 0.75,
|
||||
Body::Object(_) => 0.4,
|
||||
Body::Ship(_) => 1.0,
|
||||
}
|
||||
}
|
||||
|
||||
@ -294,6 +299,7 @@ impl Body {
|
||||
object::Body::Crossbow => 1.7,
|
||||
_ => 1.0,
|
||||
},
|
||||
Body::Ship(_) => 1.0,
|
||||
}
|
||||
}
|
||||
|
||||
@ -416,6 +422,7 @@ impl Body {
|
||||
quadruped_low::Species::Deadwood => 600,
|
||||
_ => 200,
|
||||
},
|
||||
Body::Ship(_) => 10000,
|
||||
}
|
||||
}
|
||||
|
||||
@ -508,12 +515,23 @@ impl Body {
|
||||
quadruped_low::Species::Deadwood => 30,
|
||||
_ => 20,
|
||||
},
|
||||
Body::Ship(_) => 500,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn flying_height(&self) -> f32 {
|
||||
match self {
|
||||
Body::BirdSmall(_) => 30.0,
|
||||
Body::BirdMedium(_) => 40.0,
|
||||
Body::Dragon(_) => 60.0,
|
||||
Body::Ship(ship::Body::DefaultAirship) => 60.0,
|
||||
_ => 0.0,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn immune_to(&self, buff: BuffKind) -> bool {
|
||||
match buff {
|
||||
BuffKind::Bleeding => matches!(self, Body::Object(_) | Body::Golem(_)),
|
||||
BuffKind::Bleeding => matches!(self, Body::Object(_) | Body::Golem(_) | Body::Ship(_)),
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
@ -521,7 +539,13 @@ impl Body {
|
||||
/// Returns a multiplier representing increased difficulty not accounted for
|
||||
/// due to AI or not using an actual weapon
|
||||
// TODO: Match on species
|
||||
pub fn combat_multiplier(&self) -> f32 { if let Body::Object(_) = self { 0.0 } else { 1.0 } }
|
||||
pub fn combat_multiplier(&self) -> f32 {
|
||||
if let Body::Object(_) | Body::Ship(_) = self {
|
||||
0.0
|
||||
} else {
|
||||
1.0
|
||||
}
|
||||
}
|
||||
|
||||
pub fn base_poise(&self) -> u32 {
|
||||
match self {
|
||||
@ -549,6 +573,13 @@ impl Body {
|
||||
Body::Humanoid(_) | Body::BipedSmall(_) | Body::BipedLarge(_)
|
||||
)
|
||||
}
|
||||
|
||||
pub fn mounting_offset(&self) -> Vec3<f32> {
|
||||
match self {
|
||||
Body::Ship(ship::Body::DefaultAirship) => Vec3::from([0.0, 0.0, 10.0]),
|
||||
_ => Vec3::unit_z(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Component for Body {
|
||||
|
126
common/src/comp/body/ship.rs
Normal file
126
common/src/comp/body/ship.rs
Normal file
@ -0,0 +1,126 @@
|
||||
use crate::make_case_elim;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
make_case_elim!(
|
||||
body,
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
|
||||
#[repr(u32)]
|
||||
pub enum Body {
|
||||
DefaultAirship = 0,
|
||||
}
|
||||
);
|
||||
|
||||
impl From<Body> for super::Body {
|
||||
fn from(body: Body) -> Self { super::Body::Ship(body) }
|
||||
}
|
||||
|
||||
impl Body {
|
||||
pub fn manifest_entry(&self) -> &'static str {
|
||||
match self {
|
||||
Body::DefaultAirship => "Human_Airship",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Terrain is 11.0 scale relative to small-scale voxels, and all figures get
|
||||
/// multiplied by 0.8 in rendering. For now, have a constant in `comp::Scale`
|
||||
/// that compensates for both of these, but there might be a more elegant way
|
||||
/// (e.g. using `Scale(0.8)` for everything else and not having a magic number
|
||||
/// in figure rendering, and multiplying terrain models by 11.0 in animation).
|
||||
pub const AIRSHIP_SCALE: f32 = 11.0 / 0.8;
|
||||
|
||||
/// Duplicate of some of the things defined in `voxygen::scene::figure::load` to
|
||||
/// avoid having to refactor all of that to `common` for using voxels as
|
||||
/// collider geometry
|
||||
pub mod figuredata {
|
||||
use crate::{
|
||||
assets::{self, AssetExt, AssetHandle, DotVoxAsset, Ron},
|
||||
figure::cell::Cell,
|
||||
terrain::{
|
||||
block::{Block, BlockKind},
|
||||
sprite::SpriteKind,
|
||||
},
|
||||
volumes::dyna::{ColumnAccess, Dyna},
|
||||
};
|
||||
use hashbrown::HashMap;
|
||||
use lazy_static::lazy_static;
|
||||
use serde::Deserialize;
|
||||
use vek::Vec3;
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct VoxSimple(pub String);
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct ShipCentralSpec(pub HashMap<super::Body, SidedShipCentralVoxSpec>);
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct SidedShipCentralVoxSpec {
|
||||
pub bone0: ShipCentralSubSpec,
|
||||
pub bone1: ShipCentralSubSpec,
|
||||
pub bone2: ShipCentralSubSpec,
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct ShipCentralSubSpec {
|
||||
pub offset: [f32; 3],
|
||||
pub phys_offset: [f32; 3],
|
||||
pub central: VoxSimple,
|
||||
}
|
||||
|
||||
/// manual instead of through `make_vox_spec!` so that it can be in `common`
|
||||
#[derive(Clone)]
|
||||
pub struct ShipSpec {
|
||||
pub central: AssetHandle<Ron<ShipCentralSpec>>,
|
||||
pub colliders: HashMap<String, VoxelCollider>,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct VoxelCollider {
|
||||
pub dyna: Dyna<Block, (), ColumnAccess>,
|
||||
pub translation: Vec3<f32>,
|
||||
}
|
||||
|
||||
impl assets::Compound for ShipSpec {
|
||||
fn load<S: assets::source::Source>(
|
||||
cache: &assets::AssetCache<S>,
|
||||
_: &str,
|
||||
) -> Result<Self, assets::Error> {
|
||||
let manifest: AssetHandle<Ron<ShipCentralSpec>> =
|
||||
AssetExt::load("server.manifests.ship_manifest")?;
|
||||
let mut colliders = HashMap::new();
|
||||
for (_, spec) in (manifest.read().0).0.iter() {
|
||||
for bone in [&spec.bone0, &spec.bone1, &spec.bone2].iter() {
|
||||
// TODO: Currently both client and server load models and manifests from
|
||||
// "server.voxel.". In order to support CSG procedural airships, we probably
|
||||
// need to load them in the server and sync them as an ECS resource.
|
||||
let vox =
|
||||
cache.load::<DotVoxAsset>(&["server.voxel.", &bone.central.0].concat())?;
|
||||
let dyna = Dyna::<Cell, (), ColumnAccess>::from_vox(&vox.read().0, false);
|
||||
let dyna = dyna.map_into(|cell| {
|
||||
if let Some(rgb) = cell.get_color() {
|
||||
Block::new(BlockKind::Misc, rgb)
|
||||
} else {
|
||||
Block::air(SpriteKind::Empty)
|
||||
}
|
||||
});
|
||||
let collider = VoxelCollider {
|
||||
dyna,
|
||||
translation: Vec3::from(bone.offset) + Vec3::from(bone.phys_offset),
|
||||
};
|
||||
colliders.insert(bone.central.0.clone(), collider);
|
||||
}
|
||||
}
|
||||
Ok(ShipSpec {
|
||||
central: manifest,
|
||||
colliders,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
lazy_static! {
|
||||
// TODO: Load this from the ECS as a resource, and maybe make it more general than ships
|
||||
// (although figuring out how to keep the figure bones in sync with the terrain offsets seems
|
||||
// like a hard problem if they're not the same manifest)
|
||||
pub static ref VOXEL_COLLIDER_MANIFEST: AssetHandle<ShipSpec> = AssetExt::load_expect("server.manifests.ship_manifest");
|
||||
}
|
||||
}
|
@ -48,8 +48,8 @@ pub use self::{
|
||||
beam::{Beam, BeamSegment},
|
||||
body::{
|
||||
biped_large, biped_small, bird_medium, bird_small, dragon, fish_medium, fish_small, golem,
|
||||
humanoid, object, quadruped_low, quadruped_medium, quadruped_small, theropod, AllBodies,
|
||||
Body, BodyData,
|
||||
humanoid, object, quadruped_low, quadruped_medium, quadruped_small, ship, theropod,
|
||||
AllBodies, Body, BodyData,
|
||||
},
|
||||
buff::{
|
||||
Buff, BuffCategory, BuffChange, BuffData, BuffEffect, BuffId, BuffKind, BuffSource, Buffs,
|
||||
|
@ -33,6 +33,7 @@ pub struct PreviousPhysCache {
|
||||
pub collision_boundary: f32,
|
||||
pub scale: f32,
|
||||
pub scaled_radius: f32,
|
||||
pub ori: Quaternion<f32>,
|
||||
}
|
||||
|
||||
impl Component for PreviousPhysCache {
|
||||
@ -55,9 +56,12 @@ impl Component for Mass {
|
||||
type Storage = DerefFlaggedStorage<Self, IdvStorage<Self>>;
|
||||
}
|
||||
|
||||
// Mass
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)]
|
||||
// Collider
|
||||
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
|
||||
pub enum Collider {
|
||||
// TODO: pass the map from ids -> voxel data to get_radius and get_z_limits to compute a
|
||||
// bounding cylinder
|
||||
Voxel { id: String },
|
||||
Box { radius: f32, z_min: f32, z_max: f32 },
|
||||
Point,
|
||||
}
|
||||
@ -65,6 +69,7 @@ pub enum Collider {
|
||||
impl Collider {
|
||||
pub fn get_radius(&self) -> f32 {
|
||||
match self {
|
||||
Collider::Voxel { .. } => 1.0,
|
||||
Collider::Box { radius, .. } => *radius,
|
||||
Collider::Point => 0.0,
|
||||
}
|
||||
@ -72,6 +77,7 @@ impl Collider {
|
||||
|
||||
pub fn get_z_limits(&self, modifier: f32) -> (f32, f32) {
|
||||
match self {
|
||||
Collider::Voxel { .. } => (0.0, 1.0),
|
||||
Collider::Box { z_min, z_max, .. } => (*z_min * modifier, *z_max * modifier),
|
||||
Collider::Point => (0.0, 0.0),
|
||||
}
|
||||
@ -104,6 +110,7 @@ pub struct PhysicsState {
|
||||
pub on_wall: Option<Vec3<f32>>,
|
||||
pub touch_entities: Vec<Uid>,
|
||||
pub in_liquid: Option<f32>, // Depth
|
||||
pub ground_vel: Vec3<f32>,
|
||||
}
|
||||
|
||||
impl PhysicsState {
|
||||
@ -113,6 +120,8 @@ impl PhysicsState {
|
||||
touch_entities.clear();
|
||||
*self = Self {
|
||||
touch_entities,
|
||||
ground_vel: self.ground_vel, /* Preserved, since it's the velocity of the last
|
||||
* contact point */
|
||||
..Self::default()
|
||||
}
|
||||
}
|
||||
|
@ -20,17 +20,12 @@ pub type SiteId = u64;
|
||||
|
||||
pub enum LocalEvent {
|
||||
/// Applies upward force to entity's `Vel`
|
||||
Jump(EcsEntity),
|
||||
Jump(EcsEntity, f32),
|
||||
/// Applies the `impulse` to `entity`'s `Vel`
|
||||
ApplyImpulse {
|
||||
entity: EcsEntity,
|
||||
impulse: Vec3<f32>,
|
||||
},
|
||||
/// Applies leaping force to `entity`'s `Vel` away from `wall_dir` direction
|
||||
WallLeap {
|
||||
entity: EcsEntity,
|
||||
wall_dir: Vec3<f32>,
|
||||
},
|
||||
/// Applies `vel` velocity to `entity`
|
||||
Boost { entity: EcsEntity, vel: Vec3<f32> },
|
||||
}
|
||||
|
@ -1,4 +1,5 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
use specs::Entity;
|
||||
|
||||
/// A resource that stores the time of day.
|
||||
#[derive(Copy, Clone, Debug, Serialize, Deserialize, Default)]
|
||||
@ -26,3 +27,8 @@ pub enum GameMode {
|
||||
// To be used later when we no longer start up an entirely new server for singleplayer
|
||||
Singleplayer,
|
||||
}
|
||||
|
||||
/// A resource that stores the player's entity (on the client), and None on the
|
||||
/// server
|
||||
#[derive(Copy, Clone, Default, Debug)]
|
||||
pub struct PlayerEntity(pub Option<Entity>);
|
||||
|
@ -46,4 +46,11 @@ impl Default for RtSimController {
|
||||
|
||||
impl RtSimController {
|
||||
pub fn reset(&mut self) { *self = Self::default(); }
|
||||
|
||||
pub fn with_destination(pos: Vec3<f32>) -> Self {
|
||||
Self {
|
||||
travel_to: Some((pos, format!("{:0.1?}", pos))),
|
||||
speed_factor: 0.25,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -9,12 +9,9 @@ use crate::{
|
||||
util::Dir,
|
||||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use vek::{
|
||||
vec::{Vec2, Vec3},
|
||||
Lerp,
|
||||
};
|
||||
use vek::*;
|
||||
|
||||
const HUMANOID_CLIMB_ACCEL: f32 = 5.0;
|
||||
const HUMANOID_CLIMB_ACCEL: f32 = 24.0;
|
||||
const CLIMB_SPEED: f32 = 5.0;
|
||||
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize, Eq, Hash)]
|
||||
@ -36,7 +33,7 @@ impl CharacterBehavior for Data {
|
||||
// They've climbed atop something, give them a boost
|
||||
update
|
||||
.local_events
|
||||
.push_front(LocalEvent::Jump(data.entity));
|
||||
.push_front(LocalEvent::Jump(data.entity, BASE_JUMP_IMPULSE * 0.5));
|
||||
}
|
||||
update.character = CharacterState::Idle {};
|
||||
return update;
|
||||
@ -76,26 +73,10 @@ impl CharacterBehavior for Data {
|
||||
};
|
||||
|
||||
// Apply Vertical Climbing Movement
|
||||
if update.vel.0.z <= CLIMB_SPEED {
|
||||
match climb {
|
||||
Climb::Down => {
|
||||
update.vel.0 -=
|
||||
data.dt.0 * update.vel.0.map(|e| e.abs().powf(1.5) * e.signum() * 1.0);
|
||||
},
|
||||
Climb::Up => {
|
||||
update.vel.0.z = (update.vel.0.z + data.dt.0 * GRAVITY * 1.25).min(CLIMB_SPEED);
|
||||
},
|
||||
Climb::Hold => {
|
||||
// Antigrav
|
||||
update.vel.0.z =
|
||||
(update.vel.0.z + data.dt.0 * GRAVITY * 1.075).min(CLIMB_SPEED);
|
||||
update.vel.0 = Lerp::lerp(
|
||||
update.vel.0,
|
||||
Vec3::zero(),
|
||||
30.0 * data.dt.0 / (1.0 - update.vel.0.z.min(0.0) * 5.0),
|
||||
);
|
||||
},
|
||||
}
|
||||
match climb {
|
||||
Climb::Down => update.vel.0.z += data.dt.0 * (GRAVITY - HUMANOID_CLIMB_ACCEL),
|
||||
Climb::Up => update.vel.0.z += data.dt.0 * (GRAVITY + HUMANOID_CLIMB_ACCEL),
|
||||
Climb::Hold => update.vel.0.z += data.dt.0 * GRAVITY,
|
||||
}
|
||||
|
||||
update
|
||||
|
@ -9,8 +9,7 @@ use vek::Vec2;
|
||||
|
||||
// Gravity is 9.81 * 4, so this makes gravity equal to .15
|
||||
const GLIDE_ANTIGRAV: f32 = crate::consts::GRAVITY * 0.90;
|
||||
const GLIDE_ACCEL: f32 = 12.0;
|
||||
const GLIDE_SPEED: f32 = 45.0;
|
||||
const GLIDE_ACCEL: f32 = 5.0;
|
||||
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize, Eq, Hash)]
|
||||
pub struct Data;
|
||||
@ -40,13 +39,7 @@ impl CharacterBehavior for Data {
|
||||
handle_climb(&data, &mut update);
|
||||
|
||||
// Move player according to movement direction vector
|
||||
update.vel.0 += Vec2::broadcast(data.dt.0)
|
||||
* data.inputs.move_dir
|
||||
* if data.vel.0.magnitude_squared() < GLIDE_SPEED.powi(2) {
|
||||
GLIDE_ACCEL
|
||||
} else {
|
||||
0.0
|
||||
};
|
||||
update.vel.0 += Vec2::broadcast(data.dt.0) * data.inputs.move_dir * GLIDE_ACCEL;
|
||||
|
||||
// Determine orientation vector from movement direction vector
|
||||
let horiz_vel = Vec2::<f32>::from(update.vel.0);
|
||||
@ -56,7 +49,7 @@ impl CharacterBehavior for Data {
|
||||
|
||||
// Apply Glide antigrav lift
|
||||
let horiz_speed_sq = horiz_vel.magnitude_squared();
|
||||
if horiz_speed_sq < GLIDE_SPEED.powi(2) && update.vel.0.z < 0.0 {
|
||||
if update.vel.0.z < 0.0 {
|
||||
let lift = (GLIDE_ANTIGRAV + update.vel.0.z.powi(2) * 0.15)
|
||||
* (horiz_speed_sq * f32::powf(0.075, 2.0)).clamp(0.2, 1.0);
|
||||
|
||||
|
@ -3,7 +3,7 @@ use crate::{
|
||||
biped_large, biped_small,
|
||||
inventory::slot::EquipSlot,
|
||||
item::{Hands, ItemKind, Tool, ToolKind},
|
||||
quadruped_low, quadruped_medium, quadruped_small,
|
||||
quadruped_low, quadruped_medium, quadruped_small, ship,
|
||||
skills::Skill,
|
||||
theropod, Body, CharacterAbility, CharacterState, InputKind, InventoryAction, StateUpdate,
|
||||
},
|
||||
@ -17,10 +17,11 @@ use std::time::Duration;
|
||||
use vek::*;
|
||||
|
||||
pub const MOVEMENT_THRESHOLD_VEL: f32 = 3.0;
|
||||
const BASE_HUMANOID_AIR_ACCEL: f32 = 8.0;
|
||||
const BASE_FLIGHT_ACCEL: f32 = 16.0;
|
||||
const BASE_HUMANOID_AIR_ACCEL: f32 = 2.0;
|
||||
const BASE_FLIGHT_ACCEL: f32 = 2.0;
|
||||
const BASE_HUMANOID_WATER_ACCEL: f32 = 150.0;
|
||||
const BASE_HUMANOID_WATER_SPEED: f32 = 180.0;
|
||||
pub const BASE_JUMP_IMPULSE: f32 = 16.0;
|
||||
// const BASE_HUMANOID_CLIMB_ACCEL: f32 = 10.0;
|
||||
// const ROLL_SPEED: f32 = 17.0;
|
||||
// const CHARGE_SPEED: f32 = 20.0;
|
||||
@ -117,6 +118,7 @@ impl Body {
|
||||
quadruped_low::Species::Basilisk => 120.0,
|
||||
quadruped_low::Species::Deadwood => 140.0,
|
||||
},
|
||||
Body::Ship(_) => 30.0,
|
||||
}
|
||||
}
|
||||
|
||||
@ -168,14 +170,24 @@ impl Body {
|
||||
quadruped_low::Species::Lavadrake => 4.0,
|
||||
_ => 6.0,
|
||||
},
|
||||
Body::Ship(_) => 0.175,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn can_fly(&self) -> bool {
|
||||
matches!(
|
||||
self,
|
||||
Body::BirdMedium(_) | Body::Dragon(_) | Body::BirdSmall(_)
|
||||
)
|
||||
/// Returns flying speed if the body type can fly, otherwise None
|
||||
pub fn can_fly(&self) -> Option<f32> {
|
||||
match self {
|
||||
Body::BirdMedium(_) | Body::Dragon(_) | Body::BirdSmall(_) => Some(1.0),
|
||||
Body::Ship(ship::Body::DefaultAirship) => Some(1.0),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn jump_impulse(&self) -> Option<f32> {
|
||||
match self {
|
||||
Body::Object(_) | Body::Ship(_) => None,
|
||||
_ => Some(BASE_JUMP_IMPULSE),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn can_climb(&self) -> bool { matches!(self, Body::Humanoid(_)) }
|
||||
@ -186,10 +198,18 @@ pub fn handle_move(data: &JoinData, update: &mut StateUpdate, efficiency: f32) {
|
||||
if let Some(depth) = data.physics.in_liquid {
|
||||
swim_move(data, update, efficiency, depth);
|
||||
} else if input_is_pressed(data, InputKind::Fly)
|
||||
&& !data.physics.on_ground
|
||||
&& data.body.can_fly()
|
||||
&& (!data.physics.on_ground || data.body.jump_impulse().is_none())
|
||||
&& data.body.can_fly().is_some()
|
||||
{
|
||||
fly_move(data, update, efficiency);
|
||||
fly_move(
|
||||
data,
|
||||
update,
|
||||
efficiency
|
||||
* data
|
||||
.body
|
||||
.can_fly()
|
||||
.expect("can_fly is_some right above this"),
|
||||
);
|
||||
} else {
|
||||
basic_move(data, update, efficiency);
|
||||
}
|
||||
@ -289,7 +309,11 @@ fn swim_move(data: &JoinData, update: &mut StateUpdate, efficiency: f32, depth:
|
||||
}
|
||||
* efficiency;
|
||||
|
||||
handle_orientation(data, update, if data.physics.on_ground { 9.0 } else { 2.0 });
|
||||
handle_orientation(
|
||||
data,
|
||||
update,
|
||||
data.body.base_ori_rate() * if data.physics.on_ground { 0.5 } else { 0.1 },
|
||||
);
|
||||
|
||||
// Swim
|
||||
update.vel.0.z = (update.vel.0.z
|
||||
@ -306,7 +330,6 @@ fn swim_move(data: &JoinData, update: &mut StateUpdate, efficiency: f32, depth:
|
||||
/// Updates components to move entity as if it's flying
|
||||
fn fly_move(data: &JoinData, update: &mut StateUpdate, efficiency: f32) {
|
||||
// Update velocity (counteract gravity with lift)
|
||||
// TODO: Do this better
|
||||
update.vel.0 += Vec3::unit_z() * data.dt.0 * GRAVITY
|
||||
+ Vec3::new(
|
||||
data.inputs.move_dir.x,
|
||||
@ -316,7 +339,7 @@ fn fly_move(data: &JoinData, update: &mut StateUpdate, efficiency: f32) {
|
||||
* BASE_FLIGHT_ACCEL
|
||||
* efficiency;
|
||||
|
||||
handle_orientation(data, update, 1.0);
|
||||
handle_orientation(data, update, data.body.base_ori_rate());
|
||||
}
|
||||
|
||||
/// Checks if an input related to an attack is held. If one is, moves entity
|
||||
@ -429,10 +452,12 @@ pub fn handle_jump(data: &JoinData, update: &mut StateUpdate) {
|
||||
.in_liquid
|
||||
.map(|depth| depth > 1.0)
|
||||
.unwrap_or(false)
|
||||
&& data.body.jump_impulse().is_some()
|
||||
{
|
||||
update
|
||||
.local_events
|
||||
.push_front(LocalEvent::Jump(data.entity));
|
||||
update.local_events.push_front(LocalEvent::Jump(
|
||||
data.entity,
|
||||
data.body.jump_impulse().unwrap(),
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -35,11 +35,11 @@ impl Cylinder {
|
||||
pub fn from_components(
|
||||
pos: Vec3<f32>,
|
||||
scale: Option<crate::comp::Scale>,
|
||||
collider: Option<crate::comp::Collider>,
|
||||
collider: Option<&crate::comp::Collider>,
|
||||
char_state: Option<&crate::comp::CharacterState>,
|
||||
) -> Self {
|
||||
let scale = scale.map_or(1.0, |s| s.0);
|
||||
let radius = collider.map_or(0.5, |c| c.get_radius()) * scale;
|
||||
let radius = collider.as_ref().map_or(0.5, |c| c.get_radius()) * scale;
|
||||
let z_limit_modifier = char_state
|
||||
.filter(|char_state| char_state.is_dodge())
|
||||
.map_or(1.0, |_| 0.5)
|
||||
|
@ -44,6 +44,21 @@ impl<V, M, A: Access> Dyna<V, M, A> {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
pub fn map_into<W, F: FnMut(V) -> W>(self, f: F) -> Dyna<W, M, A> {
|
||||
let Dyna {
|
||||
vox,
|
||||
meta,
|
||||
sz,
|
||||
_phantom,
|
||||
} = self;
|
||||
Dyna {
|
||||
vox: vox.into_iter().map(f).collect(),
|
||||
meta,
|
||||
sz,
|
||||
_phantom,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<V, M, A: Access> BaseVol for Dyna<V, M, A> {
|
||||
|
@ -153,15 +153,6 @@ impl<'a> System<'a> for Sys {
|
||||
// Do nothing
|
||||
continue;
|
||||
}
|
||||
// If mounted, character state is controlled by mount
|
||||
// TODO: Make mounting a state
|
||||
if let Some(Mounting(_)) = read_data.mountings.get(entity) {
|
||||
let sit_state = CharacterState::Sit {};
|
||||
if char_state.get_unchecked() != &sit_state {
|
||||
*char_state.get_mut_unchecked() = sit_state;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
// Enter stunned state if poise damage is enough
|
||||
if let Some(mut poise) = poises.get_mut(entity) {
|
||||
@ -317,6 +308,17 @@ impl<'a> System<'a> for Sys {
|
||||
incorporate_update(&mut join_struct, state_update);
|
||||
}
|
||||
|
||||
// Mounted occurs after control actions have been handled
|
||||
// If mounted, character state is controlled by mount
|
||||
// TODO: Make mounting a state
|
||||
if let Some(Mounting(_)) = read_data.mountings.get(entity) {
|
||||
let sit_state = CharacterState::Sit {};
|
||||
if join_struct.char_state.get_unchecked() != &sit_state {
|
||||
*join_struct.char_state.get_mut_unchecked() = sit_state;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
let j = JoinData::new(
|
||||
&join_struct,
|
||||
&read_data.lazy_update,
|
||||
|
63
common/sys/src/interpolation.rs
Normal file
63
common/sys/src/interpolation.rs
Normal file
@ -0,0 +1,63 @@
|
||||
use common::{
|
||||
comp::{Ori, Pos, Vel},
|
||||
resources::{PlayerEntity, Time},
|
||||
};
|
||||
use common_ecs::{Job, Origin, Phase, System};
|
||||
use common_net::sync::InterpolatableComponent;
|
||||
use specs::{
|
||||
prelude::ParallelIterator, shred::ResourceId, Entities, ParJoin, Read, ReadStorage, SystemData,
|
||||
World, WriteStorage,
|
||||
};
|
||||
|
||||
#[derive(SystemData)]
|
||||
pub struct InterpolationSystemData<'a> {
|
||||
time: Read<'a, Time>,
|
||||
player: Read<'a, PlayerEntity>,
|
||||
entities: Entities<'a>,
|
||||
pos: WriteStorage<'a, Pos>,
|
||||
pos_interpdata: ReadStorage<'a, <Pos as InterpolatableComponent>::InterpData>,
|
||||
vel: WriteStorage<'a, Vel>,
|
||||
vel_interpdata: ReadStorage<'a, <Vel as InterpolatableComponent>::InterpData>,
|
||||
ori: WriteStorage<'a, Ori>,
|
||||
ori_interpdata: ReadStorage<'a, <Ori as InterpolatableComponent>::InterpData>,
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct InterpolationSystem;
|
||||
|
||||
impl<'a> System<'a> for InterpolationSystem {
|
||||
type SystemData = InterpolationSystemData<'a>;
|
||||
|
||||
const NAME: &'static str = "interpolation";
|
||||
const ORIGIN: Origin = Origin::Common;
|
||||
const PHASE: Phase = Phase::Apply;
|
||||
|
||||
fn run(_job: &mut Job<Self>, mut data: InterpolationSystemData<'a>) {
|
||||
let time = data.time.0;
|
||||
let player = data.player.0;
|
||||
|
||||
(
|
||||
&data.entities,
|
||||
&mut data.pos,
|
||||
&data.pos_interpdata,
|
||||
&data.vel_interpdata,
|
||||
)
|
||||
.par_join()
|
||||
.filter(|(e, _, _, _)| Some(e) != player.as_ref())
|
||||
.for_each(|(_, pos, interp, vel)| {
|
||||
*pos = pos.interpolate(interp, time, vel);
|
||||
});
|
||||
(&data.entities, &mut data.vel, &data.vel_interpdata)
|
||||
.par_join()
|
||||
.filter(|(e, _, _)| Some(e) != player.as_ref())
|
||||
.for_each(|(_, vel, interp)| {
|
||||
*vel = vel.interpolate(interp, time, &());
|
||||
});
|
||||
(&data.entities, &mut data.ori, &data.ori_interpdata)
|
||||
.par_join()
|
||||
.filter(|(e, _, _)| Some(e) != player.as_ref())
|
||||
.for_each(|(_, ori, interp)| {
|
||||
*ori = ori.interpolate(interp, time, &());
|
||||
});
|
||||
}
|
||||
}
|
@ -5,6 +5,7 @@ mod beam;
|
||||
mod buff;
|
||||
pub mod character_behavior;
|
||||
pub mod controller;
|
||||
mod interpolation;
|
||||
pub mod melee;
|
||||
mod mount;
|
||||
pub mod phys;
|
||||
|
@ -1,11 +1,11 @@
|
||||
use common::{
|
||||
comp::{Controller, MountState, Mounting, Ori, Pos, Vel},
|
||||
comp::{Body, Controller, MountState, Mounting, Ori, Pos, Vel},
|
||||
uid::UidAllocator,
|
||||
};
|
||||
use common_ecs::{Job, Origin, Phase, System};
|
||||
use specs::{
|
||||
saveload::{Marker, MarkerAllocator},
|
||||
Entities, Join, Read, WriteStorage,
|
||||
Entities, Join, Read, ReadStorage, WriteStorage,
|
||||
};
|
||||
use vek::*;
|
||||
|
||||
@ -23,6 +23,7 @@ impl<'a> System<'a> for Sys {
|
||||
WriteStorage<'a, Pos>,
|
||||
WriteStorage<'a, Vel>,
|
||||
WriteStorage<'a, Ori>,
|
||||
ReadStorage<'a, Body>,
|
||||
);
|
||||
|
||||
const NAME: &'static str = "mount";
|
||||
@ -40,21 +41,24 @@ impl<'a> System<'a> for Sys {
|
||||
mut positions,
|
||||
mut velocities,
|
||||
mut orientations,
|
||||
bodies,
|
||||
): Self::SystemData,
|
||||
) {
|
||||
// Mounted entities.
|
||||
for (entity, mut mount_states) in (&entities, &mut mount_state.restrict_mut()).join() {
|
||||
for (entity, mut mount_states, body) in
|
||||
(&entities, &mut mount_state.restrict_mut(), bodies.maybe()).join()
|
||||
{
|
||||
match mount_states.get_unchecked() {
|
||||
MountState::Unmounted => {},
|
||||
MountState::MountedBy(mounter_uid) => {
|
||||
// Note: currently controller events are not passed through since none of them
|
||||
// are currently relevant to controlling the mounted entity
|
||||
if let Some((inputs, mounter)) = uid_allocator
|
||||
if let Some((inputs, queued_inputs, mounter)) = uid_allocator
|
||||
.retrieve_entity_internal(mounter_uid.id())
|
||||
.and_then(|mounter| {
|
||||
controllers
|
||||
.get(mounter)
|
||||
.map(|c| (c.inputs.clone(), mounter))
|
||||
.map(|c| (c.inputs.clone(), c.queued_inputs.clone(), mounter))
|
||||
})
|
||||
{
|
||||
// TODO: consider joining on these? (remember we can use .maybe())
|
||||
@ -62,13 +66,16 @@ impl<'a> System<'a> for Sys {
|
||||
let ori = orientations.get(entity).copied();
|
||||
let vel = velocities.get(entity).copied();
|
||||
if let (Some(pos), Some(ori), Some(vel)) = (pos, ori, vel) {
|
||||
let _ = positions.insert(mounter, Pos(pos.0 + Vec3::unit_z() * 1.0));
|
||||
let mounting_offset =
|
||||
body.map_or(Vec3::unit_z(), Body::mounting_offset);
|
||||
let _ = positions.insert(mounter, Pos(pos.0 + mounting_offset));
|
||||
let _ = orientations.insert(mounter, ori);
|
||||
let _ = velocities.insert(mounter, vel);
|
||||
}
|
||||
if let Some(controller) = controllers.get_mut(entity) {
|
||||
*controller = Controller {
|
||||
inputs,
|
||||
queued_inputs,
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -6,7 +6,7 @@ use common::{
|
||||
comp,
|
||||
event::{EventBus, LocalEvent, ServerEvent},
|
||||
region::RegionMap,
|
||||
resources::{DeltaTime, GameMode, Time, TimeOfDay},
|
||||
resources::{DeltaTime, GameMode, PlayerEntity, Time, TimeOfDay},
|
||||
terrain::{Block, TerrainChunk, TerrainGrid},
|
||||
time::DayPeriod,
|
||||
trade::Trades,
|
||||
@ -14,8 +14,8 @@ use common::{
|
||||
vol::{ReadVol, WriteVol},
|
||||
};
|
||||
use common_base::span;
|
||||
use common_ecs::{PhysicsMetrics, SysMetrics};
|
||||
use common_net::sync::WorldSyncExt;
|
||||
use common_ecs::{run_now, PhysicsMetrics, SysMetrics};
|
||||
use common_net::sync::{interpolation as sync_interp, WorldSyncExt};
|
||||
use hashbrown::{HashMap, HashSet};
|
||||
use rayon::{ThreadPool, ThreadPoolBuilder};
|
||||
use specs::{
|
||||
@ -36,7 +36,6 @@ const DAY_CYCLE_FACTOR: f64 = 24.0 * 2.0;
|
||||
/// this value, the game's physics will begin to produce time lag. Ideally, we'd
|
||||
/// avoid such a situation.
|
||||
const MAX_DELTA_TIME: f32 = 1.0;
|
||||
const HUMANOID_JUMP_ACCEL: f32 = 16.0;
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct BlockChange {
|
||||
@ -165,6 +164,9 @@ impl State {
|
||||
// Register client-local components
|
||||
// TODO: only register on the client
|
||||
ecs.register::<comp::LightAnimation>();
|
||||
ecs.register::<sync_interp::InterpBuffer<comp::Pos>>();
|
||||
ecs.register::<sync_interp::InterpBuffer<comp::Vel>>();
|
||||
ecs.register::<sync_interp::InterpBuffer<comp::Ori>>();
|
||||
|
||||
// Register server-local components
|
||||
// TODO: only register on the server
|
||||
@ -194,6 +196,7 @@ impl State {
|
||||
// Register unsynced resources used by the ECS.
|
||||
ecs.insert(Time(0.0));
|
||||
ecs.insert(DeltaTime(0.0));
|
||||
ecs.insert(PlayerEntity(None));
|
||||
ecs.insert(TerrainGrid::new().unwrap());
|
||||
ecs.insert(BlockChange::default());
|
||||
ecs.insert(TerrainChanges::default());
|
||||
@ -438,6 +441,7 @@ impl State {
|
||||
let mut dispatcher = dispatch_builder.build();
|
||||
drop(guard);
|
||||
span!(guard, "run systems");
|
||||
run_now::<crate::interpolation::InterpolationSystem>(&self.ecs);
|
||||
dispatcher.dispatch(&self.ecs);
|
||||
drop(guard);
|
||||
|
||||
@ -454,11 +458,11 @@ impl State {
|
||||
let events = self.ecs.read_resource::<EventBus<LocalEvent>>().recv_all();
|
||||
for event in events {
|
||||
let mut velocities = self.ecs.write_storage::<comp::Vel>();
|
||||
let mut controllers = self.ecs.write_storage::<comp::Controller>();
|
||||
let physics = self.ecs.read_storage::<comp::PhysicsState>();
|
||||
match event {
|
||||
LocalEvent::Jump(entity) => {
|
||||
LocalEvent::Jump(entity, impulse) => {
|
||||
if let Some(vel) = velocities.get_mut(entity) {
|
||||
vel.0.z = HUMANOID_JUMP_ACCEL;
|
||||
vel.0.z = impulse + physics.get(entity).map_or(0.0, |ps| ps.ground_vel.z);
|
||||
}
|
||||
},
|
||||
LocalEvent::ApplyImpulse { entity, impulse } => {
|
||||
@ -466,21 +470,6 @@ impl State {
|
||||
vel.0 = impulse;
|
||||
}
|
||||
},
|
||||
LocalEvent::WallLeap { entity, wall_dir } => {
|
||||
if let (Some(vel), Some(_controller)) =
|
||||
(velocities.get_mut(entity), controllers.get_mut(entity))
|
||||
{
|
||||
let hspeed = Vec2::<f32>::from(vel.0).magnitude();
|
||||
if hspeed > 0.001 && hspeed < 0.5 {
|
||||
vel.0 += vel.0.normalized()
|
||||
* Vec3::new(1.0, 1.0, 0.0)
|
||||
* HUMANOID_JUMP_ACCEL
|
||||
* 1.5
|
||||
- wall_dir * 0.03;
|
||||
vel.0.z = HUMANOID_JUMP_ACCEL * 0.5;
|
||||
}
|
||||
}
|
||||
},
|
||||
LocalEvent::Boost {
|
||||
entity,
|
||||
vel: extra_vel,
|
||||
|
@ -77,6 +77,7 @@ type CommandHandler = fn(&mut Server, EcsEntity, EcsEntity, String, &ChatCommand
|
||||
fn get_handler(cmd: &ChatCommand) -> CommandHandler {
|
||||
match cmd {
|
||||
ChatCommand::Adminify => handle_adminify,
|
||||
ChatCommand::Airship => handle_spawn_airship,
|
||||
ChatCommand::Alias => handle_alias,
|
||||
ChatCommand::Ban => handle_ban,
|
||||
ChatCommand::Build => handle_build,
|
||||
@ -984,6 +985,51 @@ fn handle_spawn_training_dummy(
|
||||
}
|
||||
}
|
||||
|
||||
fn handle_spawn_airship(
|
||||
server: &mut Server,
|
||||
client: EcsEntity,
|
||||
target: EcsEntity,
|
||||
args: String,
|
||||
action: &ChatCommand,
|
||||
) {
|
||||
let angle = scan_fmt!(&args, &action.arg_fmt(), f32).ok();
|
||||
match server.state.read_component_copied::<comp::Pos>(target) {
|
||||
Some(mut pos) => {
|
||||
pos.0.z += 50.0;
|
||||
const DESTINATION_RADIUS: f32 = 2000.0;
|
||||
let angle = angle.map(|a| a * std::f32::consts::PI / 180.0);
|
||||
let destination = angle.map(|a| {
|
||||
pos.0
|
||||
+ Vec3::new(
|
||||
DESTINATION_RADIUS * a.cos(),
|
||||
DESTINATION_RADIUS * a.sin(),
|
||||
200.0,
|
||||
)
|
||||
});
|
||||
server
|
||||
.state
|
||||
.create_ship(pos, comp::ship::Body::DefaultAirship, 1, destination)
|
||||
.with(comp::Scale(comp::ship::AIRSHIP_SCALE))
|
||||
.with(LightEmitter {
|
||||
col: Rgb::new(1.0, 0.65, 0.2),
|
||||
strength: 2.0,
|
||||
flicker: 1.0,
|
||||
animated: true,
|
||||
})
|
||||
.build();
|
||||
|
||||
server.notify_client(
|
||||
client,
|
||||
ServerGeneral::server_msg(ChatType::CommandInfo, "Spawned an airship"),
|
||||
);
|
||||
},
|
||||
None => server.notify_client(
|
||||
client,
|
||||
ServerGeneral::server_msg(ChatType::CommandError, "You have no position!"),
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
fn handle_spawn_campfire(
|
||||
server: &mut Server,
|
||||
client: EcsEntity,
|
||||
|
@ -129,7 +129,12 @@ pub fn handle_shoot(
|
||||
return;
|
||||
};
|
||||
|
||||
let vel = *dir * speed;
|
||||
let vel = *dir * speed
|
||||
+ state
|
||||
.ecs()
|
||||
.read_storage::<Vel>()
|
||||
.get(entity)
|
||||
.map_or(Vec3::zero(), |v| v.0);
|
||||
|
||||
// Add an outcome
|
||||
state
|
||||
|
@ -69,7 +69,7 @@ pub fn handle_inventory(server: &mut Server, entity: EcsEntity, manip: comp::Inv
|
||||
find_dist::Cylinder::from_components(
|
||||
p.0,
|
||||
scales.get(entity).copied(),
|
||||
colliders.get(entity).copied(),
|
||||
colliders.get(entity),
|
||||
char_states.get(entity),
|
||||
)
|
||||
})
|
||||
|
@ -27,7 +27,10 @@ impl Entity {
|
||||
|
||||
pub fn get_body(&self) -> comp::Body {
|
||||
match self.rng(PERM_GENUS).gen::<f32>() {
|
||||
//we want 50% birds, 50% humans for now
|
||||
// we want 5% airships, 45% birds, 50% humans
|
||||
// TODO: uncomment this to re-enable RtSim airships once physics is interpolated well
|
||||
// in multiplayer.
|
||||
//x if x < 0.05 => comp::Body::Ship(comp::ship::Body::DefaultAirship),
|
||||
x if x < 0.50 => {
|
||||
let species = *(&comp::bird_medium::ALL_SPECIES)
|
||||
.choose(&mut self.rng(PERM_SPECIES))
|
||||
@ -53,6 +56,7 @@ impl Entity {
|
||||
comp::Body::BirdSmall(_) => "Warbler".to_string(),
|
||||
comp::Body::Dragon(b) => get_npc_name(&npc_names.dragon, b.species).to_string(),
|
||||
comp::Body::Humanoid(b) => get_npc_name(&npc_names.humanoid, b.species).to_string(),
|
||||
comp::Body::Ship(_) => "Veloren Air".to_string(),
|
||||
//TODO: finish match as necessary
|
||||
_ => unimplemented!(),
|
||||
}
|
||||
@ -131,6 +135,7 @@ impl Entity {
|
||||
.iter()
|
||||
.filter(|s| match self.get_body() {
|
||||
comp::Body::Humanoid(_) => s.1.is_settlement() | s.1.is_castle(),
|
||||
comp::Body::Ship(_) => s.1.is_settlement(),
|
||||
_ => s.1.is_dungeon(),
|
||||
})
|
||||
.filter(|_| thread_rng().gen_range(0i32..4) == 0)
|
||||
|
@ -122,7 +122,10 @@ impl<'a> System<'a> for Sys {
|
||||
comp::Body::Humanoid(_) => comp::Alignment::Npc,
|
||||
_ => comp::Alignment::Wild,
|
||||
},
|
||||
scale: comp::Scale(1.0),
|
||||
scale: match body {
|
||||
comp::Body::Ship(_) => comp::Scale(comp::ship::AIRSHIP_SCALE),
|
||||
_ => comp::Scale(1.0),
|
||||
},
|
||||
drop_item: None,
|
||||
home_chunk: None,
|
||||
rtsim_entity: Some(RtSimEntity(id)),
|
||||
|
@ -41,6 +41,13 @@ pub trait StateExt {
|
||||
) -> EcsEntityBuilder;
|
||||
/// Build a static object entity
|
||||
fn create_object(&mut self, pos: comp::Pos, object: comp::object::Body) -> EcsEntityBuilder;
|
||||
fn create_ship(
|
||||
&mut self,
|
||||
pos: comp::Pos,
|
||||
ship: comp::ship::Body,
|
||||
level: u16,
|
||||
destination: Option<Vec3<f32>>,
|
||||
) -> EcsEntityBuilder;
|
||||
/// Build a projectile
|
||||
fn create_projectile(
|
||||
&mut self,
|
||||
@ -172,10 +179,15 @@ impl StateExt for State {
|
||||
))
|
||||
.unwrap_or_default(),
|
||||
)
|
||||
.with(comp::Collider::Box {
|
||||
radius: body.radius(),
|
||||
z_min: 0.0,
|
||||
z_max: body.height(),
|
||||
.with(match body {
|
||||
comp::Body::Ship(ship) => comp::Collider::Voxel {
|
||||
id: ship.manifest_entry().to_string(),
|
||||
},
|
||||
_ => comp::Collider::Box {
|
||||
radius: body.radius(),
|
||||
z_min: 0.0,
|
||||
z_max: body.height(),
|
||||
},
|
||||
})
|
||||
.with(comp::Controller::default())
|
||||
.with(body)
|
||||
@ -215,6 +227,42 @@ impl StateExt for State {
|
||||
.with(comp::Gravity(1.0))
|
||||
}
|
||||
|
||||
fn create_ship(
|
||||
&mut self,
|
||||
pos: comp::Pos,
|
||||
ship: comp::ship::Body,
|
||||
level: u16,
|
||||
destination: Option<Vec3<f32>>,
|
||||
) -> EcsEntityBuilder {
|
||||
let mut builder = self
|
||||
.ecs_mut()
|
||||
.create_entity_synced()
|
||||
.with(pos)
|
||||
.with(comp::Vel(Vec3::zero()))
|
||||
.with(comp::Ori::default())
|
||||
.with(comp::Mass(50.0))
|
||||
.with(comp::Collider::Voxel {
|
||||
id: ship.manifest_entry().to_string(),
|
||||
})
|
||||
.with(comp::Body::Ship(ship))
|
||||
.with(comp::Gravity(1.0))
|
||||
.with(comp::Controller::default())
|
||||
.with(comp::inventory::Inventory::new_empty())
|
||||
.with(comp::CharacterState::default())
|
||||
// TODO: some of these are required in order for the character_behavior system to
|
||||
// recognize a possesed airship; that system should be refactored to use `.maybe()`
|
||||
.with(comp::Energy::new(ship.into(), level))
|
||||
.with(comp::Health::new(ship.into(), level))
|
||||
.with(comp::Stats::new("Airship".to_string()))
|
||||
.with(comp::Buffs::default())
|
||||
.with(comp::MountState::Unmounted)
|
||||
.with(comp::Combo::default());
|
||||
if let Some(pos) = destination {
|
||||
builder = builder.with(comp::Agent::with_destination(pos))
|
||||
}
|
||||
builder
|
||||
}
|
||||
|
||||
fn create_projectile(
|
||||
&mut self,
|
||||
pos: comp::Pos,
|
||||
|
@ -34,7 +34,7 @@ use specs::{
|
||||
Entities, Entity as EcsEntity, Join, ParJoin, Read, ReadExpect, ReadStorage, SystemData, World,
|
||||
Write, WriteStorage,
|
||||
};
|
||||
use std::f32::consts::PI;
|
||||
use std::{f32::consts::PI, sync::Arc};
|
||||
use vek::*;
|
||||
|
||||
struct AgentData<'a> {
|
||||
@ -82,6 +82,7 @@ pub struct ReadData<'a> {
|
||||
mount_states: ReadStorage<'a, MountState>,
|
||||
time_of_day: Read<'a, TimeOfDay>,
|
||||
light_emitter: ReadStorage<'a, LightEmitter>,
|
||||
world: ReadExpect<'a, Arc<world::World>>,
|
||||
}
|
||||
|
||||
// This is 3.1 to last longer than the last damage timer (3.0 seconds)
|
||||
@ -213,7 +214,7 @@ impl<'a> System<'a> for Sys {
|
||||
in_liquid: physics_state.in_liquid.is_some(),
|
||||
min_tgt_dist: 1.0,
|
||||
can_climb: body.map(|b| b.can_climb()).unwrap_or(false),
|
||||
can_fly: body.map(|b| b.can_fly()).unwrap_or(false),
|
||||
can_fly: body.map(|b| b.can_fly().is_some()).unwrap_or(false),
|
||||
};
|
||||
|
||||
let flees = alignment
|
||||
@ -619,19 +620,43 @@ impl<'a> AgentData<'a> {
|
||||
|
||||
controller.inputs.move_z = bearing.z
|
||||
+ if self.traversal_config.can_fly {
|
||||
if read_data
|
||||
let obstacle_ahead = read_data
|
||||
.terrain
|
||||
.ray(
|
||||
self.pos.0 + Vec3::unit_z(),
|
||||
self.pos.0
|
||||
+ bearing.try_normalized().unwrap_or_else(Vec3::unit_y) * 60.0
|
||||
+ bearing.try_normalized().unwrap_or_else(Vec3::unit_y) * 80.0
|
||||
+ Vec3::unit_z(),
|
||||
)
|
||||
.until(Block::is_solid)
|
||||
.cast()
|
||||
.1
|
||||
.map_or(true, |b| b.is_some())
|
||||
{
|
||||
.map_or(true, |b| b.is_some());
|
||||
let ground_too_close = self
|
||||
.body
|
||||
.map(|body| {
|
||||
let height_approx = self.pos.0.y
|
||||
- read_data
|
||||
.world
|
||||
.sim()
|
||||
.get_alt_approx(self.pos.0.xy().map(|x: f32| x as i32))
|
||||
.unwrap_or(0.0);
|
||||
|
||||
height_approx < body.flying_height()
|
||||
|| read_data
|
||||
.terrain
|
||||
.ray(
|
||||
self.pos.0,
|
||||
self.pos.0 - body.flying_height() * Vec3::unit_z(),
|
||||
)
|
||||
.until(|b: &Block| b.is_solid() || b.is_liquid())
|
||||
.cast()
|
||||
.1
|
||||
.map_or(false, |b| b.is_some())
|
||||
})
|
||||
.unwrap_or(false);
|
||||
|
||||
if obstacle_ahead || ground_too_close {
|
||||
1.0 //fly up when approaching obstacles
|
||||
} else {
|
||||
-0.1
|
||||
|
@ -249,9 +249,18 @@ impl<'a> System<'a> for Sys {
|
||||
{
|
||||
let mut comp_sync_package = CompSyncPackage::new();
|
||||
let mut throttle = true;
|
||||
|
||||
// Extrapolation depends on receiving several frames indicating that something
|
||||
// has stopped in order for the extrapolated value to have
|
||||
// stopped
|
||||
const SEND_UNCHANGED_PHYSICS_DATA: bool = true;
|
||||
|
||||
// TODO: An entity that stopped moving on a tick that it wasn't sent to the
|
||||
// player will never have its position updated
|
||||
match last_pos.get(entity).map(|&l| l.0 != pos) {
|
||||
match last_pos
|
||||
.get(entity)
|
||||
.map(|&l| l.0 != pos || SEND_UNCHANGED_PHYSICS_DATA)
|
||||
{
|
||||
Some(false) => {},
|
||||
Some(true) => {
|
||||
let _ = last_pos.insert(entity, Last(pos));
|
||||
@ -265,7 +274,10 @@ impl<'a> System<'a> for Sys {
|
||||
}
|
||||
|
||||
if let Some(&vel) = maybe_vel {
|
||||
match last_vel.get(entity).map(|&l| l.0 != vel) {
|
||||
match last_vel
|
||||
.get(entity)
|
||||
.map(|&l| l.0 != vel || SEND_UNCHANGED_PHYSICS_DATA)
|
||||
{
|
||||
Some(false) => {},
|
||||
Some(true) => {
|
||||
let _ = last_vel.insert(entity, Last(vel));
|
||||
@ -286,7 +298,10 @@ impl<'a> System<'a> for Sys {
|
||||
}
|
||||
|
||||
if let Some(&ori) = maybe_ori {
|
||||
match last_ori.get(entity).map(|&l| l.0 != ori) {
|
||||
match last_ori
|
||||
.get(entity)
|
||||
.map(|&l| l.0 != ori || SEND_UNCHANGED_PHYSICS_DATA)
|
||||
{
|
||||
Some(false) => {},
|
||||
Some(true) => {
|
||||
let _ = last_ori.insert(entity, Last(ori));
|
||||
|
@ -140,7 +140,7 @@ impl<'a> TrackedComps<'a> {
|
||||
self.mass.get(entity).copied().map(|c| comps.push(c.into()));
|
||||
self.collider
|
||||
.get(entity)
|
||||
.copied()
|
||||
.cloned()
|
||||
.map(|c| comps.push(c.into()));
|
||||
self.sticky
|
||||
.get(entity)
|
||||
|
@ -54,6 +54,7 @@ pub mod object;
|
||||
pub mod quadruped_low;
|
||||
pub mod quadruped_medium;
|
||||
pub mod quadruped_small;
|
||||
pub mod ship;
|
||||
pub mod theropod;
|
||||
pub mod vek;
|
||||
|
||||
|
33
voxygen/anim/src/ship/idle.rs
Normal file
33
voxygen/anim/src/ship/idle.rs
Normal file
@ -0,0 +1,33 @@
|
||||
use super::{
|
||||
super::{vek::*, Animation},
|
||||
ShipSkeleton, SkeletonAttr,
|
||||
};
|
||||
use common::comp::item::ToolKind;
|
||||
|
||||
pub struct IdleAnimation;
|
||||
|
||||
impl Animation for IdleAnimation {
|
||||
type Dependency = (Option<ToolKind>, Option<ToolKind>, f32);
|
||||
type Skeleton = ShipSkeleton;
|
||||
|
||||
#[cfg(feature = "use-dyn-lib")]
|
||||
const UPDATE_FN: &'static [u8] = b"ship_idle\0";
|
||||
|
||||
#[cfg_attr(feature = "be-dyn-lib", export_name = "ship_idle")]
|
||||
#[allow(clippy::approx_constant)] // TODO: Pending review in #587
|
||||
fn update_skeleton_inner(
|
||||
skeleton: &Self::Skeleton,
|
||||
(_active_tool_kind, _second_tool_kind, _global_time): Self::Dependency,
|
||||
_anim_time: f32,
|
||||
_rate: &mut f32,
|
||||
s_a: &SkeletonAttr,
|
||||
) -> Self::Skeleton {
|
||||
let mut next = (*skeleton).clone();
|
||||
|
||||
next.bone0.position = Vec3::new(s_a.bone0.0, s_a.bone0.1, s_a.bone0.2) / 11.0;
|
||||
|
||||
next.bone1.position = Vec3::new(s_a.bone1.0, s_a.bone1.1, s_a.bone1.2) / 11.0;
|
||||
|
||||
next
|
||||
}
|
||||
}
|
68
voxygen/anim/src/ship/mod.rs
Normal file
68
voxygen/anim/src/ship/mod.rs
Normal file
@ -0,0 +1,68 @@
|
||||
pub mod idle;
|
||||
|
||||
// Reexports
|
||||
pub use self::idle::IdleAnimation;
|
||||
|
||||
use super::{make_bone, vek::*, FigureBoneData, Skeleton};
|
||||
use common::comp::{self};
|
||||
use core::convert::TryFrom;
|
||||
|
||||
pub type Body = comp::ship::Body;
|
||||
|
||||
skeleton_impls!(struct ShipSkeleton {
|
||||
+ bone0,
|
||||
+ bone1,
|
||||
});
|
||||
|
||||
impl Skeleton for ShipSkeleton {
|
||||
type Attr = SkeletonAttr;
|
||||
type Body = Body;
|
||||
|
||||
const BONE_COUNT: usize = 2;
|
||||
#[cfg(feature = "use-dyn-lib")]
|
||||
const COMPUTE_FN: &'static [u8] = b"ship_compute_mats\0";
|
||||
|
||||
#[cfg_attr(feature = "be-dyn-lib", export_name = "ship_compute_mats")]
|
||||
fn compute_matrices_inner(
|
||||
&self,
|
||||
base_mat: Mat4<f32>,
|
||||
buf: &mut [FigureBoneData; super::MAX_BONE_COUNT],
|
||||
) -> Vec3<f32> {
|
||||
let bone0_mat = base_mat * Mat4::<f32>::from(self.bone0);
|
||||
|
||||
*(<&mut [_; Self::BONE_COUNT]>::try_from(&mut buf[0..Self::BONE_COUNT]).unwrap()) = [
|
||||
make_bone(bone0_mat * Mat4::scaling_3d(1.0 / 11.0)),
|
||||
make_bone(Mat4::<f32>::from(self.bone1) * Mat4::scaling_3d(1.0 / 11.0)), /* Decorellated from ori */
|
||||
];
|
||||
Vec3::unit_z() * 0.5
|
||||
}
|
||||
}
|
||||
|
||||
pub struct SkeletonAttr {
|
||||
bone0: (f32, f32, f32),
|
||||
bone1: (f32, f32, f32),
|
||||
}
|
||||
|
||||
impl<'a> std::convert::TryFrom<&'a comp::Body> for SkeletonAttr {
|
||||
type Error = ();
|
||||
|
||||
fn try_from(body: &'a comp::Body) -> Result<Self, Self::Error> {
|
||||
match body {
|
||||
comp::Body::Ship(body) => Ok(SkeletonAttr::from(body)),
|
||||
_ => Err(()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for SkeletonAttr {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
bone0: (0.0, 0.0, 0.0),
|
||||
bone1: (0.0, 0.0, 0.0),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> From<&'a Body> for SkeletonAttr {
|
||||
fn from(_: &'a Body) -> Self { Self::default() }
|
||||
}
|
@ -16,13 +16,17 @@ use common::{
|
||||
quadruped_low::{self, BodyType as QLBodyType, Species as QLSpecies},
|
||||
quadruped_medium::{self, BodyType as QMBodyType, Species as QMSpecies},
|
||||
quadruped_small::{self, BodyType as QSBodyType, Species as QSSpecies},
|
||||
ship::{
|
||||
self,
|
||||
figuredata::{ShipCentralSubSpec, ShipSpec},
|
||||
},
|
||||
theropod::{self, BodyType as TBodyType, Species as TSpecies},
|
||||
},
|
||||
figure::{DynaUnionizer, MatSegment, Material, Segment},
|
||||
};
|
||||
use hashbrown::HashMap;
|
||||
use serde::Deserialize;
|
||||
use std::sync::Arc;
|
||||
use std::{fmt, hash::Hash, sync::Arc};
|
||||
use tracing::{error, warn};
|
||||
use vek::*;
|
||||
|
||||
@ -34,6 +38,9 @@ fn load_segment(mesh_name: &str) -> Segment {
|
||||
}
|
||||
fn graceful_load_vox(mesh_name: &str) -> AssetHandle<DotVoxAsset> {
|
||||
let full_specifier: String = ["voxygen.voxel.", mesh_name].concat();
|
||||
graceful_load_vox_fullspec(&full_specifier)
|
||||
}
|
||||
fn graceful_load_vox_fullspec(full_specifier: &str) -> AssetHandle<DotVoxAsset> {
|
||||
match DotVoxAsset::load(&full_specifier) {
|
||||
Ok(dot_vox) => dot_vox,
|
||||
Err(_) => {
|
||||
@ -45,6 +52,9 @@ fn graceful_load_vox(mesh_name: &str) -> AssetHandle<DotVoxAsset> {
|
||||
fn graceful_load_segment(mesh_name: &str) -> Segment {
|
||||
Segment::from(&graceful_load_vox(mesh_name).read().0)
|
||||
}
|
||||
fn graceful_load_segment_fullspec(full_specifier: &str) -> Segment {
|
||||
Segment::from(&graceful_load_vox_fullspec(full_specifier).read().0)
|
||||
}
|
||||
fn graceful_load_segment_flipped(mesh_name: &str, flipped: bool) -> Segment {
|
||||
Segment::from_vox(&graceful_load_vox(mesh_name).read().0, flipped)
|
||||
}
|
||||
@ -4171,3 +4181,53 @@ impl ObjectCentralSpec {
|
||||
(central, Vec3::from(spec.bone1.offset))
|
||||
}
|
||||
}
|
||||
|
||||
fn mesh_ship_bone<K: fmt::Debug + Eq + Hash, V, F: Fn(&V) -> &ShipCentralSubSpec>(
|
||||
map: &HashMap<K, V>,
|
||||
obj: &K,
|
||||
f: F,
|
||||
) -> BoneMeshes {
|
||||
let spec = match map.get(&obj) {
|
||||
Some(spec) => spec,
|
||||
None => {
|
||||
error!("No specification exists for {:?}", obj);
|
||||
return load_mesh("not_found", Vec3::new(-5.0, -5.0, -2.5));
|
||||
},
|
||||
};
|
||||
let bone = f(spec);
|
||||
let central = graceful_load_segment_fullspec(&["server.voxel.", &bone.central.0].concat());
|
||||
|
||||
(central, Vec3::from(bone.offset))
|
||||
}
|
||||
|
||||
impl BodySpec for ship::Body {
|
||||
type Spec = ShipSpec;
|
||||
|
||||
#[allow(unused_variables)]
|
||||
fn load_spec() -> Result<AssetHandle<Self::Spec>, assets::Error> { Self::Spec::load("") }
|
||||
|
||||
fn bone_meshes(
|
||||
FigureKey { body, .. }: &FigureKey<Self>,
|
||||
spec: &Self::Spec,
|
||||
) -> [Option<BoneMeshes>; anim::MAX_BONE_COUNT] {
|
||||
let map = &(spec.central.read().0).0;
|
||||
[
|
||||
Some(mesh_ship_bone(map, body, |spec| &spec.bone0)),
|
||||
Some(mesh_ship_bone(map, body, |spec| &spec.bone1)),
|
||||
Some(mesh_ship_bone(map, body, |spec| &spec.bone2)),
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
]
|
||||
}
|
||||
}
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -199,6 +199,7 @@ impl Scene {
|
||||
&camera,
|
||||
&mut buf,
|
||||
None,
|
||||
Vec3::zero(),
|
||||
);
|
||||
(model, state)
|
||||
}),
|
||||
@ -378,6 +379,7 @@ impl Scene {
|
||||
&self.camera,
|
||||
&mut buf,
|
||||
None,
|
||||
Vec3::zero(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -1562,7 +1562,7 @@ fn under_cursor(
|
||||
let player_cylinder = Cylinder::from_components(
|
||||
player_pos,
|
||||
scales.get(player_entity).copied(),
|
||||
colliders.get(player_entity).copied(),
|
||||
colliders.get(player_entity),
|
||||
char_states.get(player_entity),
|
||||
);
|
||||
let terrain = client.state().terrain();
|
||||
@ -1643,7 +1643,7 @@ fn under_cursor(
|
||||
let target_cylinder = Cylinder::from_components(
|
||||
p,
|
||||
scales.get(*e).copied(),
|
||||
colliders.get(*e).copied(),
|
||||
colliders.get(*e),
|
||||
char_states.get(*e),
|
||||
);
|
||||
|
||||
@ -1706,7 +1706,7 @@ fn select_interactable(
|
||||
let player_cylinder = Cylinder::from_components(
|
||||
player_pos,
|
||||
scales.get(player_entity).copied(),
|
||||
colliders.get(player_entity).copied(),
|
||||
colliders.get(player_entity),
|
||||
char_states.get(player_entity),
|
||||
);
|
||||
|
||||
@ -1720,7 +1720,7 @@ fn select_interactable(
|
||||
.join()
|
||||
.filter(|(e, _, _, _, _)| *e != player_entity)
|
||||
.map(|(e, p, s, c, cs)| {
|
||||
let cylinder = Cylinder::from_components(p.0, s.copied(), c.copied(), cs);
|
||||
let cylinder = Cylinder::from_components(p.0, s.copied(), c, cs);
|
||||
(e, cylinder)
|
||||
})
|
||||
// Roughly filter out entities farther than interaction distance
|
||||
|
Loading…
Reference in New Issue
Block a user