Airship progress: now possessable, and physics kind of works (desyncs from the visuals by a shift + scale, and rotation isn't done at all yet, but the contours are correct).

This commit is contained in:
Avi Weinstock 2021-03-11 17:27:03 -05:00
parent 1d0600851b
commit 6d35e7c6d0
10 changed files with 359 additions and 287 deletions

View File

@ -1,16 +1,17 @@
({ ({
DefaultAirship: ( DefaultAirship: (
bone0: ( bone0: (
offset: (-20.0, -35.0, 1.0), //offset: (-20.0, -35.0, 1.0),
offset: (3.0, 7.0, 1.0),
central: ("object.Human_Airship"), central: ("object.Human_Airship"),
), ),
bone1: ( bone1: (
offset: (0.0, 0.0, 0.0), offset: (0.0, 40.0, -8.0),
central: ("propeller-l"), central: ("object.propeller-l"),
), ),
bone2: ( bone2: (
offset: (0.0, 0.0, 0.0), offset: (0.0, 0.0, -4.0),
central: ("propeller-r"), central: ("object.propeller-r"),
), ),
), ),
}) })

BIN
assets/server/voxel/Human_Airship.vox (Stored with Git LFS)

Binary file not shown.

Binary file not shown.

View File

@ -0,0 +1 @@
../../../server/voxel/Human_Airship.vox

View File

@ -1,6 +1,4 @@
use crate::{ use crate::make_case_elim;
make_case_elim
};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
make_case_elim!( make_case_elim!(
@ -17,22 +15,29 @@ impl From<Body> for super::Body {
} }
impl Body { impl Body {
pub fn manifest_id(&self) -> &'static str { pub fn manifest_entry(&self) -> &'static str {
match self { match self {
Body::DefaultAirship => "server.manifests.ship_manifest", Body::DefaultAirship => "object.Human_Airship",
} }
} }
} }
/// Duplicate of some of the things defined in `voxygen::scene::figure::load` to avoid having to /// Duplicate of some of the things defined in `voxygen::scene::figure::load` to
/// refactor all of that to `common` for using voxels as collider geometry /// avoid having to refactor all of that to `common` for using voxels as
/// collider geometry
pub mod figuredata { pub mod figuredata {
use crate::{ use crate::{
assets::{self, AssetExt, AssetHandle, DotVoxAsset, Ron}, assets::{self, AssetExt, AssetHandle, DotVoxAsset, Ron},
volumes::dyna::Dyna, figure::cell::Cell,
terrain::{
block::{Block, BlockKind},
sprite::SpriteKind,
},
volumes::dyna::{ColumnAccess, Dyna},
}; };
use serde::Deserialize;
use hashbrown::HashMap; use hashbrown::HashMap;
use lazy_static::lazy_static;
use serde::Deserialize;
#[derive(Deserialize)] #[derive(Deserialize)]
pub struct VoxSimple(pub String); pub struct VoxSimple(pub String);
@ -57,13 +62,43 @@ pub mod figuredata {
#[derive(Clone)] #[derive(Clone)]
pub struct ShipSpec { pub struct ShipSpec {
pub central: AssetHandle<Ron<ShipCentralSpec>>, pub central: AssetHandle<Ron<ShipCentralSpec>>,
pub voxes: HashMap<String, Dyna<Block, (), ColumnAccess>>,
} }
impl assets::Compound for ShipSpec { impl assets::Compound for ShipSpec {
fn load<S: assets::source::Source>(_: &assets::AssetCache<S>, _: &str) -> Result<Self, assets::Error> { 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 voxes = HashMap::new();
for (_, spec) in (manifest.read().0).0.iter() {
for bone in [&spec.bone0, &spec.bone1, &spec.bone2].iter() {
// TODO: avoid the requirement for symlinks in "voxygen.voxel.object.", and load
// the models from "server.voxel." instead
let vox =
cache.load::<DotVoxAsset>(&["voxygen.voxel.", &bone.central.0].concat())?;
let dyna = Dyna::<Cell, (), ColumnAccess>::from_vox(&vox.read().0, false);
voxes.insert(bone.central.0.clone(), dyna.map_into(|cell| {
if let Some(rgb) = cell.get_color() {
Block::new(BlockKind::Misc, rgb)
} else {
Block::air(SpriteKind::Empty)
}
}));
}
}
Ok(ShipSpec { Ok(ShipSpec {
central: AssetExt::load("server.manifests.ship_manifest")? central: manifest,
voxes,
}) })
} }
} }
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: ShipSpec = AssetExt::load_expect_cloned("server.manifests.ship_manifest");
}
} }

View File

@ -66,7 +66,7 @@ pub enum Collider {
impl Collider { impl Collider {
pub fn get_radius(&self) -> f32 { pub fn get_radius(&self) -> f32 {
match self { match self {
Collider::Voxel { .. } => 0.0, Collider::Voxel { .. } => 1.0,
Collider::Box { radius, .. } => *radius, Collider::Box { radius, .. } => *radius,
Collider::Point => 0.0, Collider::Point => 0.0,
} }
@ -74,7 +74,7 @@ impl Collider {
pub fn get_z_limits(&self, modifier: f32) -> (f32, f32) { pub fn get_z_limits(&self, modifier: f32) -> (f32, f32) {
match self { match self {
Collider::Voxel { .. } => (0.0, 0.0), Collider::Voxel { .. } => (0.0, 1.0),
Collider::Box { z_min, z_max, .. } => (*z_min * modifier, *z_max * modifier), Collider::Box { z_min, z_max, .. } => (*z_min * modifier, *z_max * modifier),
Collider::Point => (0.0, 0.0), Collider::Point => (0.0, 0.0),
} }

View File

@ -44,6 +44,16 @@ impl<V, M, A: Access> Dyna<V, M, A> {
None 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> { impl<V, M, A: Access> BaseVol for Dyna<V, M, A> {

View File

@ -1,7 +1,7 @@
use common::{ use common::{
comp::{ comp::{
BeamSegment, CharacterState, Collider, Gravity, Mass, Mounting, Ori, PhysicsState, Pos, BeamSegment, CharacterState, Collider, Gravity, Mass, Mounting, Ori, PhysicsState, Pos,
PreviousPhysCache, Projectile, Scale, Shockwave, Sticky, Vel, PreviousPhysCache, Projectile, Scale, Shockwave, Sticky, Vel, body::ship::figuredata::VOXEL_COLLIDER_MANIFEST,
}, },
consts::{FRIC_GROUND, GRAVITY}, consts::{FRIC_GROUND, GRAVITY},
event::{EventBus, ServerEvent}, event::{EventBus, ServerEvent},
@ -15,8 +15,9 @@ use common_ecs::{Job, Origin, ParMode, Phase, PhysicsMetrics, System};
use hashbrown::HashMap; use hashbrown::HashMap;
use rayon::iter::ParallelIterator; use rayon::iter::ParallelIterator;
use specs::{ use specs::{
shred::{World, ResourceId}, shred::{ResourceId, World},
Entities, Entity, Join, ParJoin, Read, ReadExpect, ReadStorage, WriteExpect, WriteStorage, SystemData, Entities, Entity, Join, ParJoin, Read, ReadExpect, ReadStorage, SystemData, WriteExpect,
WriteStorage,
}; };
use std::ops::Range; use std::ops::Range;
use vek::*; use vek::*;
@ -114,7 +115,9 @@ impl<'a> PhysicsSystemData<'a> {
) )
.join() .join()
{ {
let _ = self.w.physics_states let _ = self
.w
.physics_states
.entry(entity) .entry(entity)
.map(|e| e.or_insert_with(Default::default)); .map(|e| e.or_insert_with(Default::default));
} }
@ -137,13 +140,16 @@ impl<'a> PhysicsSystemData<'a> {
.map(|(e, _, _, _, _, _, _)| e) .map(|(e, _, _, _, _, _, _)| e)
.collect::<Vec<_>>() .collect::<Vec<_>>()
{ {
let _ = self.w.previous_phys_cache.insert(entity, PreviousPhysCache { let _ = self
velocity_dt: Vec3::zero(), .w
center: Vec3::zero(), .previous_phys_cache
collision_boundary: 0.0, .insert(entity, PreviousPhysCache {
scale: 0.0, velocity_dt: Vec3::zero(),
scaled_radius: 0.0, center: Vec3::zero(),
}); collision_boundary: 0.0,
scale: 0.0,
scaled_radius: 0.0,
});
} }
//Update PreviousPhysCache //Update PreviousPhysCache
@ -180,10 +186,14 @@ impl<'a> PhysicsSystemData<'a> {
} }
drop(guard); drop(guard);
} }
fn apply_pushback(&mut self, job: &mut Job<Sys>) { fn apply_pushback(&mut self, job: &mut Job<Sys>) {
span!(guard, "Apply pushback"); span!(guard, "Apply pushback");
job.cpu_stats.measure(ParMode::Rayon); job.cpu_stats.measure(ParMode::Rayon);
let PhysicsSystemData { r: ref psdr, w: ref mut psdw } = self; let PhysicsSystemData {
r: ref psdr,
w: ref mut psdw,
} = self;
let (positions, previous_phys_cache) = (&psdw.positions, &psdw.previous_phys_cache); let (positions, previous_phys_cache) = (&psdw.positions, &psdw.previous_phys_cache);
let metrics = ( let metrics = (
&psdr.entities, &psdr.entities,
@ -310,8 +320,13 @@ impl<'a> PhysicsSystemData<'a> {
metrics.entity_entity_collisions += 1; metrics.entity_entity_collisions += 1;
} }
// Don't apply repulsive force to projectiles // Don't apply repulsive force to projectiles or if we're colliding
if diff.magnitude_squared() > 0.0 && !is_projectile { // with a terrain-like entity, or if we are a terrain-like entity
if diff.magnitude_squared() > 0.0
&& !is_projectile
&& !matches!(collider_other, Some(Collider::Voxel { .. }))
&& !matches!(collider, Some(Collider::Voxel { .. }))
{
let force = let force =
400.0 * (collision_dist - diff.magnitude()) * mass_other 400.0 * (collision_dist - diff.magnitude()) * mass_other
/ (mass + mass_other); / (mass + mass_other);
@ -335,265 +350,273 @@ impl<'a> PhysicsSystemData<'a> {
entity_entity_collisions: old.entity_entity_collisions entity_entity_collisions: old.entity_entity_collisions
+ new.entity_entity_collisions, + new.entity_entity_collisions,
}); });
psdw.physics_metrics.entity_entity_collision_checks = metrics.entity_entity_collision_checks; psdw.physics_metrics.entity_entity_collision_checks =
metrics.entity_entity_collision_checks;
psdw.physics_metrics.entity_entity_collisions = metrics.entity_entity_collisions; psdw.physics_metrics.entity_entity_collisions = metrics.entity_entity_collisions;
drop(guard); drop(guard);
} }
fn handle_movement_and_terrain(&mut self, job: &mut Job<Sys>) { fn handle_movement_and_terrain(&mut self, job: &mut Job<Sys>) {
let PhysicsSystemData { r: ref psdr, w: ref mut psdw } = self; let PhysicsSystemData {
r: ref psdr,
w: ref mut psdw,
} = self;
// Apply movement inputs // Apply movement inputs
span!(guard, "Apply movement and terrain collision"); span!(guard, "Apply movement and terrain collision");
let (positions, previous_phys_cache) = (&psdw.positions, &psdw.previous_phys_cache); let (positions, previous_phys_cache) = (&psdw.positions, &psdw.previous_phys_cache);
let (pos_writes, land_on_grounds) = let (pos_writes, land_on_grounds) = (
( &psdr.entities,
&psdr.entities, psdr.scales.maybe(),
psdr.scales.maybe(), psdr.stickies.maybe(),
psdr.stickies.maybe(), &psdr.colliders,
&psdr.colliders, positions,
positions, &mut psdw.velocities,
&mut psdw.velocities, &psdw.orientations,
&psdw.orientations, &mut psdw.physics_states,
&mut psdw.physics_states, previous_phys_cache,
previous_phys_cache, !&psdr.mountings,
!&psdr.mountings, )
) .par_join()
.par_join() .fold(
.fold( || (Vec::new(), Vec::new()),
|| (Vec::new(), Vec::new()), |(mut pos_writes, mut land_on_grounds),
|(mut pos_writes, mut land_on_grounds), (
( entity,
entity, scale,
scale, sticky,
sticky, collider,
collider, pos,
pos, mut vel,
mut vel, _ori,
_ori, mut physics_state,
mut physics_state, previous_cache,
previous_cache, _,
_, )| {
)| { // defer the writes of positions to allow an inner loop over terrain-like
// defer the writes of positions to allow an inner loop over terrain-like // entities
// entities let old_pos = *pos;
let old_pos = *pos; let mut pos = *pos;
let mut pos = *pos; if sticky.is_some() && physics_state.on_surface().is_some() {
if sticky.is_some() && physics_state.on_surface().is_some() { vel.0 = Vec3::zero();
vel.0 = Vec3::zero(); return (pos_writes, land_on_grounds);
return (pos_writes, land_on_grounds); }
}
let scale = if let Collider::Voxel { .. } = collider { let scale = if let Collider::Voxel { .. } = collider {
scale.map(|s| s.0).unwrap_or(1.0) scale.map(|s| s.0).unwrap_or(1.0)
} else {
// TODO: Use scale & actual proportions when pathfinding is good
// enough to manage irregular entity sizes
1.0
};
let old_vel = *vel;
// Integrate forces
// Friction is assumed to be a constant dependent on location
let friction = FRIC_AIR
.max(if physics_state.on_ground {
FRIC_GROUND
} else { } else {
// TODO: Use scale & actual proportions when pathfinding is good 0.0
// enough to manage irregular entity sizes })
1.0 .max(if physics_state.in_liquid.is_some() {
}; FRIC_FLUID
let old_vel = *vel;
// Integrate forces
// Friction is assumed to be a constant dependent on location
let friction = FRIC_AIR
.max(if physics_state.on_ground {
FRIC_GROUND
} else {
0.0
})
.max(if physics_state.in_liquid.is_some() {
FRIC_FLUID
} else {
0.0
});
let in_loaded_chunk = psdr.terrain
.get_key(psdr.terrain.pos_key(pos.0.map(|e| e.floor() as i32)))
.is_some();
let downward_force =
if !in_loaded_chunk {
0.0 // No gravity in unloaded chunks
} else if physics_state
.in_liquid
.map(|depth| depth > 0.75)
.unwrap_or(false)
{
(1.0 - BOUYANCY) * GRAVITY
} else {
GRAVITY
} * psdr.gravities.get(entity).map(|g| g.0).unwrap_or_default();
vel.0 = integrate_forces(psdr.dt.0, vel.0, downward_force, friction);
// Don't move if we're not in a loaded chunk
let pos_delta = if in_loaded_chunk {
// this is an approximation that allows most framerates to
// behave in a similar manner.
let dt_lerp = 0.2;
(vel.0 * dt_lerp + old_vel.0 * (1.0 - dt_lerp)) * psdr.dt.0
} else { } else {
Vec3::zero() 0.0
}; });
let in_loaded_chunk = psdr
match &*collider { .terrain
Collider::Voxel { .. } => { .get_key(psdr.terrain.pos_key(pos.0.map(|e| e.floor() as i32)))
// for now, treat entities with voxel colliders as their bounding .is_some();
// cylinders for the purposes of colliding them with terrain let downward_force =
let radius = collider.get_radius() * scale; if !in_loaded_chunk {
let (z_min, z_max) = collider.get_z_limits(scale); 0.0 // No gravity in unloaded chunks
} else if physics_state
let cylinder = (radius, z_min, z_max); .in_liquid
cylinder_voxel_collision( .map(|depth| depth > 0.75)
cylinder, .unwrap_or(false)
&*psdr.terrain,
entity,
&mut pos,
pos_delta,
vel,
&mut physics_state,
&mut land_on_grounds,
);
},
Collider::Box {
radius,
z_min,
z_max,
} => {
// Scale collider
let radius = radius.min(0.45) * scale;
let z_min = *z_min * scale;
let z_max = z_max.clamped(1.2, 1.95) * scale;
let cylinder = (radius, z_min, z_max);
cylinder_voxel_collision(
cylinder,
&*psdr.terrain,
entity,
&mut pos,
pos_delta,
vel,
&mut physics_state,
&mut land_on_grounds,
);
},
Collider::Point => {
let (dist, block) = psdr.terrain
.ray(pos.0, pos.0 + pos_delta)
.until(|block: &Block| block.is_filled())
.ignore_error()
.cast();
pos.0 += pos_delta.try_normalized().unwrap_or(Vec3::zero()) * dist;
// Can't fail since we do ignore_error above
if block.unwrap().is_some() {
let block_center = pos.0.map(|e| e.floor()) + 0.5;
let block_rpos = (pos.0 - block_center)
.try_normalized()
.unwrap_or(Vec3::zero());
// See whether we're on the top/bottom of a block, or the side
if block_rpos.z.abs()
> block_rpos.xy().map(|e| e.abs()).reduce_partial_max()
{
if block_rpos.z > 0.0 {
physics_state.on_ground = true;
} else {
physics_state.on_ceiling = true;
}
vel.0.z = 0.0;
} else {
physics_state.on_wall =
Some(if block_rpos.x.abs() > block_rpos.y.abs() {
vel.0.x = 0.0;
Vec3::unit_x() * -block_rpos.x.signum()
} else {
vel.0.y = 0.0;
Vec3::unit_y() * -block_rpos.y.signum()
});
}
}
physics_state.in_liquid = psdr.terrain
.get(pos.0.map(|e| e.floor() as i32))
.ok()
.and_then(|vox| vox.is_liquid().then_some(1.0));
},
}
// Collide with terrain-like entities
for (
entity_other,
other,
pos_other,
previous_cache_other,
mass_other,
collider_other,
_,
_,
_,
_,
char_state_other_maybe,
) in (
&psdr.entities,
&psdr.uids,
positions,
previous_phys_cache,
psdr.masses.maybe(),
&psdr.colliders,
!&psdr.projectiles,
!&psdr.mountings,
!&psdr.beams,
!&psdr.shockwaves,
psdr.char_states.maybe(),
)
.join()
{ {
let collision_boundary = previous_cache.collision_boundary (1.0 - BOUYANCY) * GRAVITY
+ previous_cache_other.collision_boundary; } else {
if previous_cache GRAVITY
.center } * psdr.gravities.get(entity).map(|g| g.0).unwrap_or_default();
.distance_squared(previous_cache_other.center) vel.0 = integrate_forces(psdr.dt.0, vel.0, downward_force, friction);
> collision_boundary.powi(2)
|| entity == entity_other // Don't move if we're not in a loaded chunk
{ let pos_delta = if in_loaded_chunk {
continue; // this is an approximation that allows most framerates to
// behave in a similar manner.
let dt_lerp = 0.2;
(vel.0 * dt_lerp + old_vel.0 * (1.0 - dt_lerp)) * psdr.dt.0
} else {
Vec3::zero()
};
match &*collider {
Collider::Voxel { .. } => {
// for now, treat entities with voxel colliders as their bounding
// cylinders for the purposes of colliding them with terrain
let radius = collider.get_radius() * scale;
let (z_min, z_max) = collider.get_z_limits(scale);
let cylinder = (radius, z_min, z_max);
cylinder_voxel_collision(
cylinder,
&*psdr.terrain,
entity,
&mut pos,
pos_delta,
vel,
&mut physics_state,
&mut land_on_grounds,
);
},
Collider::Box {
radius,
z_min,
z_max,
} => {
// Scale collider
let radius = radius.min(0.45) * scale;
let z_min = *z_min * scale;
let z_max = z_max.clamped(1.2, 1.95) * scale;
let cylinder = (radius, z_min, z_max);
cylinder_voxel_collision(
cylinder,
&*psdr.terrain,
entity,
&mut pos,
pos_delta,
vel,
&mut physics_state,
&mut land_on_grounds,
);
},
Collider::Point => {
let (dist, block) = psdr
.terrain
.ray(pos.0, pos.0 + pos_delta)
.until(|block: &Block| block.is_filled())
.ignore_error()
.cast();
pos.0 += pos_delta.try_normalized().unwrap_or(Vec3::zero()) * dist;
// Can't fail since we do ignore_error above
if block.unwrap().is_some() {
let block_center = pos.0.map(|e| e.floor()) + 0.5;
let block_rpos = (pos.0 - block_center)
.try_normalized()
.unwrap_or(Vec3::zero());
// See whether we're on the top/bottom of a block, or the side
if block_rpos.z.abs()
> block_rpos.xy().map(|e| e.abs()).reduce_partial_max()
{
if block_rpos.z > 0.0 {
physics_state.on_ground = true;
} else {
physics_state.on_ceiling = true;
}
vel.0.z = 0.0;
} else {
physics_state.on_wall =
Some(if block_rpos.x.abs() > block_rpos.y.abs() {
vel.0.x = 0.0;
Vec3::unit_x() * -block_rpos.x.signum()
} else {
vel.0.y = 0.0;
Vec3::unit_y() * -block_rpos.y.signum()
});
}
} }
if let Collider::Voxel { id } = collider_other { physics_state.in_liquid = psdr
// use bounding cylinder regardless of our collider .terrain
// TODO: extract point-terrain collision above to its own function .get(pos.0.map(|e| e.floor() as i32))
let radius = collider.get_radius() * scale; .ok()
let (z_min, z_max) = collider.get_z_limits(scale); .and_then(|vox| vox.is_liquid().then_some(1.0));
},
}
let cylinder = (radius, z_min, z_max); // Collide with terrain-like entities
// TODO: load .vox into a Dyna, and use it (appropriately rotated) for (
// as the terrain entity_other,
/*cylinder_voxel_collision( other,
pos_other,
previous_cache_other,
mass_other,
collider_other,
_,
_,
_,
_,
char_state_other_maybe,
) in (
&psdr.entities,
&psdr.uids,
positions,
previous_phys_cache,
psdr.masses.maybe(),
&psdr.colliders,
!&psdr.projectiles,
!&psdr.mountings,
!&psdr.beams,
!&psdr.shockwaves,
psdr.char_states.maybe(),
)
.join()
{
/*let collision_boundary = previous_cache.collision_boundary
+ previous_cache_other.collision_boundary;
if previous_cache
.center
.distance_squared(previous_cache_other.center)
> collision_boundary.powi(2)
|| entity == entity_other
{
continue;
}*/
if let Collider::Voxel { id } = collider_other {
// use bounding cylinder regardless of our collider
// TODO: extract point-terrain collision above to its own function
let radius = collider.get_radius() * scale;
let (z_min, z_max) = collider.get_z_limits(scale);
pos.0 -= pos_other.0;
let cylinder = (radius, z_min, z_max);
if let Some(dyna) = VOXEL_COLLIDER_MANIFEST.voxes.get(id) {
cylinder_voxel_collision(
cylinder, cylinder,
&*psdr.terrain, &*dyna,
entity, entity,
&mut pos, &mut pos,
pos_delta, pos_delta,
vel, vel,
&mut physics_state, &mut physics_state,
&mut land_on_grounds, &mut land_on_grounds,
);*/ );
} }
pos.0 += pos_other.0;
} }
if pos != old_pos { }
pos_writes.push((entity, pos)); if pos != old_pos {
} pos_writes.push((entity, pos));
}
(pos_writes, land_on_grounds) (pos_writes, land_on_grounds)
}, },
) )
.reduce( .reduce(
|| (Vec::new(), Vec::new()), || (Vec::new(), Vec::new()),
|(mut pos_writes_a, mut land_on_grounds_a), |(mut pos_writes_a, mut land_on_grounds_a),
(mut pos_writes_b, mut land_on_grounds_b)| { (mut pos_writes_b, mut land_on_grounds_b)| {
pos_writes_a.append(&mut pos_writes_b); pos_writes_a.append(&mut pos_writes_b);
land_on_grounds_a.append(&mut land_on_grounds_b); land_on_grounds_a.append(&mut land_on_grounds_b);
(pos_writes_a, land_on_grounds_a) (pos_writes_a, land_on_grounds_a)
}, },
); );
drop(guard); drop(guard);
job.cpu_stats.measure(ParMode::Single); job.cpu_stats.measure(ParMode::Single);
@ -620,10 +643,7 @@ impl<'a> System<'a> for Sys {
#[allow(clippy::or_fun_call)] // TODO: Pending review in #587 #[allow(clippy::or_fun_call)] // TODO: Pending review in #587
#[allow(clippy::blocks_in_if_conditions)] // TODO: Pending review in #587 #[allow(clippy::blocks_in_if_conditions)] // TODO: Pending review in #587
fn run( fn run(job: &mut Job<Self>, mut psd: Self::SystemData) {
job: &mut Job<Self>,
mut psd: Self::SystemData,
) {
psd.reset(); psd.reset();
// Apply pushback // Apply pushback
@ -642,7 +662,6 @@ impl<'a> System<'a> for Sys {
psd.maintain_pushback_cache(); psd.maintain_pushback_cache();
psd.apply_pushback(job); psd.apply_pushback(job);
psd.handle_movement_and_terrain(job); psd.handle_movement_and_terrain(job);
} }
} }

View File

@ -966,11 +966,12 @@ fn handle_spawn_airship(
_action: &ChatCommand, _action: &ChatCommand,
) { ) {
match server.state.read_component_copied::<comp::Pos>(target) { match server.state.read_component_copied::<comp::Pos>(target) {
Some(pos) => { Some(mut pos) => {
pos.0.z += 50.0;
server server
.state .state
.create_ship(pos, comp::ship::Body::DefaultAirship) .create_ship(pos, comp::ship::Body::DefaultAirship, 1)
.with(comp::Scale(50.0)) .with(comp::Scale(11.0))
.with(LightEmitter { .with(LightEmitter {
col: Rgb::new(1.0, 0.65, 0.2), col: Rgb::new(1.0, 0.65, 0.2),
strength: 2.0, strength: 2.0,

View File

@ -41,7 +41,7 @@ pub trait StateExt {
) -> EcsEntityBuilder; ) -> EcsEntityBuilder;
/// Build a static object entity /// Build a static object entity
fn create_object(&mut self, pos: comp::Pos, object: comp::object::Body) -> EcsEntityBuilder; fn create_object(&mut self, pos: comp::Pos, object: comp::object::Body) -> EcsEntityBuilder;
fn create_ship(&mut self, pos: comp::Pos, object: comp::ship::Body) -> EcsEntityBuilder; fn create_ship(&mut self, pos: comp::Pos, object: comp::ship::Body, level: u16) -> EcsEntityBuilder;
/// Build a projectile /// Build a projectile
fn create_projectile( fn create_projectile(
&mut self, &mut self,
@ -203,16 +203,24 @@ impl StateExt for State {
.with(comp::Gravity(1.0)) .with(comp::Gravity(1.0))
} }
fn create_ship(&mut self, pos: comp::Pos, object: comp::ship::Body) -> EcsEntityBuilder { fn create_ship(&mut self, pos: comp::Pos, ship: comp::ship::Body, level: u16) -> EcsEntityBuilder {
self.ecs_mut() self.ecs_mut()
.create_entity_synced() .create_entity_synced()
.with(pos) .with(pos)
.with(comp::Vel(Vec3::zero())) .with(comp::Vel(Vec3::zero()))
.with(comp::Ori::default()) .with(comp::Ori::default())
.with(comp::Mass(50.0)) .with(comp::Mass(50.0))
.with(comp::Collider::Voxel { id: object.manifest_id().to_string() }) .with(comp::Collider::Voxel { id: ship.manifest_entry().to_string() })
.with(comp::Body::Ship(object)) .with(comp::Body::Ship(ship))
.with(comp::Gravity(1.0)) .with(comp::Gravity(1.0))
.with(comp::Controller::default())
.with(comp::inventory::Inventory::new_empty())
.with(comp::CharacterState::default())
.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::Combo::default())
} }
fn create_projectile( fn create_projectile(

View File

@ -4120,7 +4120,7 @@ impl FigureMgr {
.join() .join()
// Don't render dead entities // Don't render dead entities
.filter(|(_, _, _, _, health, _, _)| health.map_or(true, |h| !h.is_dead)) .filter(|(_, _, _, _, health, _, _)| health.map_or(true, |h| !h.is_dead))
.for_each(|(entity, pos, _, body, _, inventory, _)| { .for_each(|(entity, pos, _, body, _, inventory, scale)| {
if let Some((locals, bone_consts, model, _)) = self.get_model_for_render( if let Some((locals, bone_consts, model, _)) = self.get_model_for_render(
tick, tick,
camera, camera,
@ -4130,7 +4130,7 @@ impl FigureMgr {
inventory, inventory,
false, false,
pos.0, pos.0,
figure_lod_render_distance, figure_lod_render_distance * scale.map_or(1.0, |s| s.0),
|state| state.can_shadow_sun(), |state| state.can_shadow_sun(),
) { ) {
renderer.render_figure_shadow_directed( renderer.render_figure_shadow_directed(
@ -4162,7 +4162,7 @@ impl FigureMgr {
let character_state_storage = state.read_storage::<common::comp::CharacterState>(); let character_state_storage = state.read_storage::<common::comp::CharacterState>();
let character_state = character_state_storage.get(player_entity); let character_state = character_state_storage.get(player_entity);
for (entity, pos, _, body, _, inventory, _) in ( for (entity, pos, _, body, _, inventory, scale) in (
&ecs.entities(), &ecs.entities(),
&ecs.read_storage::<Pos>(), &ecs.read_storage::<Pos>(),
ecs.read_storage::<Ori>().maybe(), ecs.read_storage::<Ori>().maybe(),
@ -4187,7 +4187,7 @@ impl FigureMgr {
inventory, inventory,
false, false,
pos.0, pos.0,
figure_lod_render_distance, figure_lod_render_distance * scale.map_or(1.0, |s| s.0),
|state| state.visible(), |state| state.visible(),
) { ) {
renderer.render_figure(model, &col_lights, global, locals, bone_consts, lod); renderer.render_figure(model, &col_lights, global, locals, bone_consts, lod);