intercat with sprites on ships

This commit is contained in:
Isse 2023-04-17 15:38:41 +02:00
parent 7fbe2cd5ec
commit 6674cce2cc
46 changed files with 796 additions and 441 deletions

View File

@ -19,6 +19,9 @@
custom_indices: { custom_indices: {
1: Air(ChairSingle, 4), 1: Air(ChairSingle, 4),
2: Air(Helm, 0),
3: Air(Door, 4),
8: Air(Door, 0),
}, },
), ),
AirBalloon: ( AirBalloon: (

Binary file not shown.

BIN
assets/voxygen/voxel/sprite/furniture/helm.vox (Stored with Git LFS) Normal file

Binary file not shown.

View File

@ -1924,6 +1924,17 @@ ChairDouble: Some((
], ],
wind_sway: 0.0, wind_sway: 0.0,
)), )),
// Helm
Helm: Some((
variations: [
(
model: "voxygen.voxel.sprite.furniture.helm",
offset: (-5.5, -5.5, 0.0),
lod_axes: (1.0, 1.0, 1.0),
),
],
wind_sway: 0.0,
)),
// CoatRack // CoatRack
CoatRack: Some(( CoatRack: Some((
variations: [ variations: [

View File

@ -37,7 +37,7 @@ use common::{
grid::Grid, grid::Grid,
link::Is, link::Is,
lod, lod,
mounting::Rider, mounting::{Rider, VolumePos, VolumeRider},
outcome::Outcome, outcome::Outcome,
recipe::{ComponentRecipeBook, RecipeBook, RepairRecipeBook}, recipe::{ComponentRecipeBook, RecipeBook, RepairRecipeBook},
resources::{GameMode, PlayerEntity, Time, TimeOfDay}, resources::{GameMode, PlayerEntity, Time, TimeOfDay},
@ -1185,7 +1185,7 @@ impl Client {
&mut self, &mut self,
recipe: &str, recipe: &str,
slots: Vec<(u32, InvSlotId)>, slots: Vec<(u32, InvSlotId)>,
craft_sprite: Option<(Vec3<i32>, SpriteKind)>, craft_sprite: Option<(VolumePos, SpriteKind)>,
amount: u32, amount: u32,
) -> bool { ) -> bool {
let (can_craft, required_sprite) = self.can_craft_recipe(recipe, amount); let (can_craft, required_sprite) = self.can_craft_recipe(recipe, amount);
@ -1217,7 +1217,7 @@ impl Client {
/// Salvage the item in the given inventory slot. `salvage_pos` should be /// Salvage the item in the given inventory slot. `salvage_pos` should be
/// the location of a relevant crafting station within range of the player. /// the location of a relevant crafting station within range of the player.
pub fn salvage_item(&mut self, slot: InvSlotId, salvage_pos: Vec3<i32>) -> bool { pub fn salvage_item(&mut self, slot: InvSlotId, salvage_pos: VolumePos) -> bool {
let is_salvageable = self.can_salvage_item(slot); let is_salvageable = self.can_salvage_item(slot);
if is_salvageable { if is_salvageable {
self.send_msg(ClientGeneral::ControlEvent(ControlEvent::InventoryEvent( self.send_msg(ClientGeneral::ControlEvent(ControlEvent::InventoryEvent(
@ -1239,7 +1239,7 @@ impl Client {
&mut self, &mut self,
primary_component: InvSlotId, primary_component: InvSlotId,
secondary_component: InvSlotId, secondary_component: InvSlotId,
sprite_pos: Option<Vec3<i32>>, sprite_pos: Option<VolumePos>,
) -> bool { ) -> bool {
let inventories = self.inventories(); let inventories = self.inventories();
let inventory = inventories.get(self.entity()); let inventory = inventories.get(self.entity());
@ -1288,7 +1288,7 @@ impl Client {
material: InvSlotId, material: InvSlotId,
modifier: Option<InvSlotId>, modifier: Option<InvSlotId>,
slots: Vec<(u32, InvSlotId)>, slots: Vec<(u32, InvSlotId)>,
sprite_pos: Option<Vec3<i32>>, sprite_pos: Option<VolumePos>,
) { ) {
self.send_msg(ClientGeneral::ControlEvent(ControlEvent::InventoryEvent( self.send_msg(ClientGeneral::ControlEvent(ControlEvent::InventoryEvent(
InventoryEvent::CraftRecipe { InventoryEvent::CraftRecipe {
@ -1309,7 +1309,7 @@ impl Client {
&mut self, &mut self,
item: Slot, item: Slot,
slots: Vec<(u32, InvSlotId)>, slots: Vec<(u32, InvSlotId)>,
sprite_pos: Vec3<i32>, sprite_pos: VolumePos,
) -> bool { ) -> bool {
let is_repairable = { let is_repairable = {
let inventories = self.inventories(); let inventories = self.inventories();
@ -1448,6 +1448,12 @@ impl Client {
.read_storage::<Is<Rider>>() .read_storage::<Is<Rider>>()
.get(self.entity()) .get(self.entity())
.is_some() .is_some()
|| self
.state
.ecs()
.read_storage::<Is<VolumeRider>>()
.get(self.entity())
.is_some()
} }
pub fn is_lantern_enabled(&self) -> bool { pub fn is_lantern_enabled(&self) -> bool {
@ -1464,6 +1470,12 @@ impl Client {
} }
} }
pub fn mount_volume(&mut self, volume_pos: VolumePos) {
self.send_msg(ClientGeneral::ControlEvent(ControlEvent::MountVolume(
volume_pos,
)));
}
pub fn unmount(&mut self) { self.send_msg(ClientGeneral::ControlEvent(ControlEvent::Unmount)); } pub fn unmount(&mut self) { self.send_msg(ClientGeneral::ControlEvent(ControlEvent::Unmount)); }
pub fn respawn(&mut self) { pub fn respawn(&mut self) {
@ -1530,7 +1542,7 @@ impl Client {
.ecs() .ecs()
.read_storage::<CharacterState>() .read_storage::<CharacterState>()
.get(self.entity()) .get(self.entity())
.map(|cs| matches!(cs, CharacterState::Sit | CharacterState::MountSprite(_))); .map(|cs| matches!(cs, CharacterState::Sit));
match is_sitting { match is_sitting {
Some(true) => self.control_action(ControlAction::Stand), Some(true) => self.control_action(ControlAction::Stand),
@ -1539,22 +1551,6 @@ impl Client {
} }
} }
pub fn stand_if_mounted(&mut self) -> bool {
let is_sitting = self
.state
.ecs()
.read_storage::<CharacterState>()
.get(self.entity())
.map(|cs| matches!(cs, CharacterState::MountSprite(_)));
match is_sitting {
Some(true) => self.control_action(ControlAction::Stand),
Some(false) => {},
None => warn!("Can't stand, client entity doesn't have a `CharacterState`"),
}
is_sitting.unwrap_or(false)
}
pub fn toggle_dance(&mut self) { pub fn toggle_dance(&mut self) {
let is_dancing = self let is_dancing = self
.state .state
@ -1738,10 +1734,6 @@ impl Client {
))); )));
} }
pub fn mount_sprite(&mut self, pos: Vec3<i32>) {
self.control_action(ControlAction::MountSprite(pos));
}
pub fn change_ability(&mut self, slot: usize, new_ability: comp::ability::AuxiliaryAbility) { pub fn change_ability(&mut self, slot: usize, new_ability: comp::ability::AuxiliaryAbility) {
let auxiliary_key = self let auxiliary_key = self
.inventories() .inventories()

View File

@ -34,6 +34,7 @@ macro_rules! synced_components {
group: Group, group: Group,
is_mount: IsMount, is_mount: IsMount,
is_rider: IsRider, is_rider: IsRider,
is_volume_rider: IsVolumeRider,
mass: Mass, mass: Mass,
density: Density, density: Density,
collider: Collider, collider: Collider,
@ -72,7 +73,7 @@ macro_rules! reexport_comps {
mod inner { mod inner {
pub use common::comp::*; pub use common::comp::*;
use common::link::Is; use common::link::Is;
use common::mounting::{Mount, Rider}; use common::mounting::{Mount, Rider, VolumeRider};
// We alias these because the identifier used for the // We alias these because the identifier used for the
// component's type is reused as an enum variant name // component's type is reused as an enum variant name
@ -82,6 +83,7 @@ macro_rules! reexport_comps {
// we can't just re-export all the types directly from `common::comp`. // we can't just re-export all the types directly from `common::comp`.
pub type IsMount = Is<Mount>; pub type IsMount = Is<Mount>;
pub type IsRider = Is<Rider>; pub type IsRider = Is<Rider>;
pub type IsVolumeRider = Is<VolumeRider>;
} }
// Re-export all the component types. So that uses of `synced_components!` outside this // Re-export all the component types. So that uses of `synced_components!` outside this
@ -178,6 +180,10 @@ impl NetSync for IsRider {
const SYNC_FROM: SyncFrom = SyncFrom::AnyEntity; const SYNC_FROM: SyncFrom = SyncFrom::AnyEntity;
} }
impl NetSync for IsVolumeRider {
const SYNC_FROM: SyncFrom = SyncFrom::AnyEntity;
}
impl NetSync for Mass { impl NetSync for Mass {
const SYNC_FROM: SyncFrom = SyncFrom::AnyEntity; const SYNC_FROM: SyncFrom = SyncFrom::AnyEntity;
} }

View File

@ -470,7 +470,6 @@ impl From<&CharacterState> for CharacterAbilityType {
| CharacterState::SpriteSummon(_) | CharacterState::SpriteSummon(_)
| CharacterState::UseItem(_) | CharacterState::UseItem(_)
| CharacterState::SpriteInteract(_) | CharacterState::SpriteInteract(_)
| CharacterState::MountSprite(_)
| CharacterState::Skate(_) | CharacterState::Skate(_)
| CharacterState::Wallrun(_) => Self::Other, | CharacterState::Wallrun(_) => Self::Other,
} }

View File

@ -130,8 +130,6 @@ pub enum CharacterState {
/// Handles logic for interacting with a sprite, e.g. using a chest or /// Handles logic for interacting with a sprite, e.g. using a chest or
/// picking a plant /// picking a plant
SpriteInteract(sprite_interact::Data), SpriteInteract(sprite_interact::Data),
// Mounted to a sprite
MountSprite(mount_sprite::Data),
/// Runs on the wall /// Runs on the wall
Wallrun(wallrun::Data), Wallrun(wallrun::Data),
/// Ice skating or skiing /// Ice skating or skiing
@ -472,7 +470,6 @@ impl CharacterState {
CharacterState::SpriteSummon(data) => data.behavior(j, output_events), CharacterState::SpriteSummon(data) => data.behavior(j, output_events),
CharacterState::UseItem(data) => data.behavior(j, output_events), CharacterState::UseItem(data) => data.behavior(j, output_events),
CharacterState::SpriteInteract(data) => data.behavior(j, output_events), CharacterState::SpriteInteract(data) => data.behavior(j, output_events),
CharacterState::MountSprite(data) => data.behavior(j, output_events),
CharacterState::Skate(data) => data.behavior(j, output_events), CharacterState::Skate(data) => data.behavior(j, output_events),
CharacterState::Music(data) => data.behavior(j, output_events), CharacterState::Music(data) => data.behavior(j, output_events),
CharacterState::FinisherMelee(data) => data.behavior(j, output_events), CharacterState::FinisherMelee(data) => data.behavior(j, output_events),
@ -527,7 +524,6 @@ impl CharacterState {
CharacterState::SpriteSummon(data) => data.handle_event(j, output_events, action), CharacterState::SpriteSummon(data) => data.handle_event(j, output_events, action),
CharacterState::UseItem(data) => data.handle_event(j, output_events, action), CharacterState::UseItem(data) => data.handle_event(j, output_events, action),
CharacterState::SpriteInteract(data) => data.handle_event(j, output_events, action), CharacterState::SpriteInteract(data) => data.handle_event(j, output_events, action),
CharacterState::MountSprite(data) => data.handle_event(j, output_events, action),
CharacterState::Skate(data) => data.handle_event(j, output_events, action), CharacterState::Skate(data) => data.handle_event(j, output_events, action),
CharacterState::Music(data) => data.handle_event(j, output_events, action), CharacterState::Music(data) => data.handle_event(j, output_events, action),
CharacterState::FinisherMelee(data) => data.handle_event(j, output_events, action), CharacterState::FinisherMelee(data) => data.handle_event(j, output_events, action),
@ -582,7 +578,6 @@ impl CharacterState {
CharacterState::SpriteSummon(data) => Some(data.static_data.ability_info), CharacterState::SpriteSummon(data) => Some(data.static_data.ability_info),
CharacterState::UseItem(_) => None, CharacterState::UseItem(_) => None,
CharacterState::SpriteInteract(_) => None, CharacterState::SpriteInteract(_) => None,
CharacterState::MountSprite(_) => None,
CharacterState::FinisherMelee(data) => Some(data.static_data.ability_info), CharacterState::FinisherMelee(data) => Some(data.static_data.ability_info),
CharacterState::Music(data) => Some(data.static_data.ability_info), CharacterState::Music(data) => Some(data.static_data.ability_info),
CharacterState::DiveMelee(data) => Some(data.static_data.ability_info), CharacterState::DiveMelee(data) => Some(data.static_data.ability_info),
@ -628,7 +623,6 @@ impl CharacterState {
CharacterState::SpriteSummon(data) => Some(data.stage_section), CharacterState::SpriteSummon(data) => Some(data.stage_section),
CharacterState::UseItem(data) => Some(data.stage_section), CharacterState::UseItem(data) => Some(data.stage_section),
CharacterState::SpriteInteract(data) => Some(data.stage_section), CharacterState::SpriteInteract(data) => Some(data.stage_section),
CharacterState::MountSprite(_) => None,
CharacterState::FinisherMelee(data) => Some(data.stage_section), CharacterState::FinisherMelee(data) => Some(data.stage_section),
CharacterState::Music(data) => Some(data.stage_section), CharacterState::Music(data) => Some(data.stage_section),
CharacterState::DiveMelee(data) => Some(data.stage_section), CharacterState::DiveMelee(data) => Some(data.stage_section),
@ -800,7 +794,6 @@ impl CharacterState {
recover: Some(data.static_data.recover_duration), recover: Some(data.static_data.recover_duration),
..Default::default() ..Default::default()
}), }),
CharacterState::MountSprite(_) => None,
CharacterState::FinisherMelee(data) => Some(DurationsInfo { CharacterState::FinisherMelee(data) => Some(DurationsInfo {
buildup: Some(data.static_data.buildup_duration), buildup: Some(data.static_data.buildup_duration),
action: Some(data.static_data.swing_duration), action: Some(data.static_data.swing_duration),
@ -869,7 +862,6 @@ impl CharacterState {
CharacterState::SpriteSummon(data) => Some(data.timer), CharacterState::SpriteSummon(data) => Some(data.timer),
CharacterState::UseItem(data) => Some(data.timer), CharacterState::UseItem(data) => Some(data.timer),
CharacterState::SpriteInteract(data) => Some(data.timer), CharacterState::SpriteInteract(data) => Some(data.timer),
CharacterState::MountSprite(_) => None,
CharacterState::FinisherMelee(data) => Some(data.timer), CharacterState::FinisherMelee(data) => Some(data.timer),
CharacterState::Music(data) => Some(data.timer), CharacterState::Music(data) => Some(data.timer),
CharacterState::DiveMelee(data) => Some(data.timer), CharacterState::DiveMelee(data) => Some(data.timer),
@ -889,7 +881,6 @@ impl CharacterState {
CharacterState::GlideWield(_) => None, CharacterState::GlideWield(_) => None,
CharacterState::Stunned(_) => None, CharacterState::Stunned(_) => None,
CharacterState::Sit => None, CharacterState::Sit => None,
CharacterState::MountSprite(_) => None,
CharacterState::Dance => None, CharacterState::Dance => None,
CharacterState::BasicBlock(_) => None, CharacterState::BasicBlock(_) => None,
CharacterState::Roll(_) => None, CharacterState::Roll(_) => None,

View File

@ -9,6 +9,7 @@ use crate::{
invite::{InviteKind, InviteResponse}, invite::{InviteKind, InviteResponse},
BuffKind, BuffKind,
}, },
mounting::VolumePos,
trade::{TradeAction, TradeId}, trade::{TradeAction, TradeId},
uid::Uid, uid::Uid,
util::Dir, util::Dir,
@ -28,7 +29,7 @@ pub enum InventoryEvent {
Sort, Sort,
CraftRecipe { CraftRecipe {
craft_event: CraftEvent, craft_event: CraftEvent,
craft_sprite: Option<Vec3<i32>>, craft_sprite: Option<VolumePos>,
}, },
} }
@ -57,7 +58,7 @@ pub enum InventoryManip {
Sort, Sort,
CraftRecipe { CraftRecipe {
craft_event: CraftEvent, craft_event: CraftEvent,
craft_sprite: Option<Vec3<i32>>, craft_sprite: Option<VolumePos>,
}, },
SwapEquippedWeapons, SwapEquippedWeapons,
} }
@ -143,6 +144,7 @@ pub enum ControlEvent {
InviteResponse(InviteResponse), InviteResponse(InviteResponse),
PerformTradeAction(TradeId, TradeAction), PerformTradeAction(TradeId, TradeAction),
Mount(Uid), Mount(Uid),
MountVolume(VolumePos),
Unmount, Unmount,
InventoryEvent(InventoryEvent), InventoryEvent(InventoryEvent),
GroupManip(GroupManip), GroupManip(GroupManip),
@ -165,7 +167,6 @@ pub enum ControlAction {
GlideWield, GlideWield,
Unwield, Unwield,
Sit, Sit,
MountSprite(Vec3<i32>),
Dance, Dance,
Sneak, Sneak,
Stand, Stand,

View File

@ -1,4 +1,4 @@
use super::{Fluid, Ori}; use super::{ship::figuredata::ShipSpec, Fluid, Ori};
use crate::{ use crate::{
comp::{body::ship::figuredata::VoxelCollider, inventory::item::armor::Friction}, comp::{body::ship::figuredata::VoxelCollider, inventory::item::armor::Friction},
consts::WATER_DENSITY, consts::WATER_DENSITY,
@ -127,6 +127,14 @@ pub enum Collider {
impl Collider { impl Collider {
pub fn is_voxel(&self) -> bool { matches!(self, Collider::Voxel { .. } | Collider::Volume(_)) } pub fn is_voxel(&self) -> bool { matches!(self, Collider::Voxel { .. } | Collider::Volume(_)) }
pub fn get_vol<'a>(&'a self, ship_spec: &'a ShipSpec) -> Option<&'a VoxelCollider> {
match self {
Collider::Voxel { id } => ship_spec.colliders.get(id),
Collider::Volume(vol) => Some(&**vol),
_ => None,
}
}
pub fn bounding_radius(&self) -> f32 { pub fn bounding_radius(&self) -> f32 {
match self { match self {
Collider::Voxel { .. } | Collider::Volume(_) => 1.0, Collider::Voxel { .. } | Collider::Volume(_) => 1.0,

View File

@ -1,6 +1,7 @@
// The limit on distance between the entity and a collectible (squared) // The limit on distance between the entity and a collectible (squared)
pub const MAX_PICKUP_RANGE: f32 = 5.0; pub const MAX_PICKUP_RANGE: f32 = 5.0;
pub const MAX_MOUNT_RANGE: f32 = 5.0; pub const MAX_MOUNT_RANGE: f32 = 5.0;
pub const MAX_SPRITE_MOUNT_RANGE: f32 = 1.5;
pub const MAX_TRADE_RANGE: f32 = 20.0; pub const MAX_TRADE_RANGE: f32 = 20.0;
pub const GRAVITY: f32 = 25.0; pub const GRAVITY: f32 = 25.0;

View File

@ -8,6 +8,7 @@ use crate::{
DisconnectReason, Ori, Pos, DisconnectReason, Ori, Pos,
}, },
lottery::LootSpec, lottery::LootSpec,
mounting::VolumePos,
outcome::Outcome, outcome::Outcome,
rtsim::{RtSimEntity, RtSimVehicle}, rtsim::{RtSimEntity, RtSimVehicle},
terrain::SpriteKind, terrain::SpriteKind,
@ -195,6 +196,7 @@ pub enum ServerEvent {
InitiateInvite(EcsEntity, Uid, InviteKind), InitiateInvite(EcsEntity, Uid, InviteKind),
ProcessTradeAction(EcsEntity, TradeId, TradeAction), ProcessTradeAction(EcsEntity, TradeId, TradeAction),
Mount(EcsEntity, EcsEntity), Mount(EcsEntity, EcsEntity),
MountVolume(EcsEntity, VolumePos),
Unmount(EcsEntity), Unmount(EcsEntity),
Possess(Uid, Uid), Possess(Uid, Uid),
/// Inserts default components for a character when loading into the game /// Inserts default components for a character when loading into the game

View File

@ -1,11 +1,18 @@
use crate::{ use crate::{
comp, comp::{self, ship::figuredata::VOXEL_COLLIDER_MANIFEST},
link::{Is, Link, LinkHandle, Role}, link::{Is, Link, LinkHandle, Role},
terrain::TerrainGrid, terrain::{Block, TerrainGrid},
uid::{Uid, UidAllocator}, uid::{Uid, UidAllocator},
vol::ReadVol,
}; };
use hashbrown::HashSet;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use specs::{saveload::MarkerAllocator, Entities, Read, ReadExpect, ReadStorage, WriteStorage}; use specs::{
saveload::MarkerAllocator,
storage::{GenericWriteStorage, MaskedStorage},
Component, DenseVecStorage, Entities, Read, ReadExpect, ReadStorage, Storage, Write,
WriteStorage,
};
use vek::*; use vek::*;
#[derive(Serialize, Deserialize, Debug)] #[derive(Serialize, Deserialize, Debug)]
@ -39,6 +46,7 @@ impl Link for Mounting {
Read<'a, UidAllocator>, Read<'a, UidAllocator>,
WriteStorage<'a, Is<Mount>>, WriteStorage<'a, Is<Mount>>,
WriteStorage<'a, Is<Rider>>, WriteStorage<'a, Is<Rider>>,
ReadStorage<'a, Is<VolumeRider>>,
); );
type DeleteData<'a> = ( type DeleteData<'a> = (
Read<'a, UidAllocator>, Read<'a, UidAllocator>,
@ -59,7 +67,7 @@ impl Link for Mounting {
fn create( fn create(
this: &LinkHandle<Self>, this: &LinkHandle<Self>,
(uid_allocator, mut is_mounts, mut is_riders): Self::CreateData<'_>, (uid_allocator, mut is_mounts, mut is_riders, is_volume_rider): Self::CreateData<'_>,
) -> Result<(), Self::Error> { ) -> Result<(), Self::Error> {
let entity = |uid: Uid| uid_allocator.retrieve_entity_internal(uid.into()); let entity = |uid: Uid| uid_allocator.retrieve_entity_internal(uid.into());
@ -67,8 +75,11 @@ impl Link for Mounting {
// Forbid self-mounting // Forbid self-mounting
Err(MountingError::NotMountable) Err(MountingError::NotMountable)
} else if let Some((mount, rider)) = entity(this.mount).zip(entity(this.rider)) { } else if let Some((mount, rider)) = entity(this.mount).zip(entity(this.rider)) {
let can_mount_with = let can_mount_with = |entity| {
|entity| is_mounts.get(entity).is_none() && is_riders.get(entity).is_none(); !is_mounts.contains(entity)
&& !is_riders.contains(entity)
&& !is_volume_rider.contains(entity)
};
// Ensure that neither mount or rider are already part of a mounting // Ensure that neither mount or rider are already part of a mounting
// relationship // relationship
@ -145,3 +156,257 @@ impl Link for Mounting {
}); });
} }
} }
#[derive(Serialize, Deserialize, Debug)]
pub struct VolumeRider;
impl Role for VolumeRider {
type Link = VolumeMounting;
}
#[derive(Serialize, Deserialize, Clone, Copy, PartialEq, Eq, Debug, Hash)]
pub enum Volume {
Terrain,
Entity(Uid),
}
#[derive(Serialize, Deserialize, Clone, Copy, PartialEq, Eq, Debug, Hash)]
pub struct VolumePos {
pub kind: Volume,
pub pos: Vec3<i32>,
}
impl VolumePos {
pub fn terrain(block_pos: Vec3<i32>) -> Self {
Self {
kind: Volume::Terrain,
pos: block_pos,
}
}
pub fn entity(block_pos: Vec3<i32>, uid: Uid) -> Self {
Self {
kind: Volume::Entity(uid),
pos: block_pos,
}
}
}
impl VolumePos {
pub fn get_block_and_transform(
&self,
terrain: &TerrainGrid,
uid_allocator: &UidAllocator,
positions: &Storage<comp::Pos, impl std::ops::Deref<Target = MaskedStorage<comp::Pos>>>,
orientations: &Storage<comp::Ori, impl std::ops::Deref<Target = MaskedStorage<comp::Ori>>>,
colliders: &ReadStorage<comp::Collider>,
) -> Option<(Mat4<f32>, Block)> {
match self.kind {
Volume::Terrain => Some((
Mat4::translation_3d(self.pos.as_()),
*terrain.get(self.pos).ok()?,
)),
Volume::Entity(uid) => {
uid_allocator
.retrieve_entity_internal(uid.0)
.and_then(|entity| {
let collider = colliders.get(entity)?;
let pos = positions.get(entity)?;
let ori = orientations.get(entity)?;
let voxel_colliders_manifest = VOXEL_COLLIDER_MANIFEST.read();
let voxel_collider = collider.get_vol(&voxel_colliders_manifest)?;
let block = *voxel_collider.volume().get(self.pos).ok()?;
let local_translation = voxel_collider.translation + self.pos.as_();
let trans = Mat4::from(ori.to_quat()).translated_3d(pos.0)
* Mat4::<f32>::translation_3d(local_translation);
Some((trans, block))
})
},
}
}
pub fn get_block(
&self,
terrain: &TerrainGrid,
uid_allocator: &UidAllocator,
colliders: &ReadStorage<comp::Collider>,
) -> Option<Block> {
match self.kind {
Volume::Terrain => Some(*terrain.get(self.pos).ok()?),
Volume::Entity(uid) => {
uid_allocator
.retrieve_entity_internal(uid.0)
.and_then(|entity| {
let collider = colliders.get(entity)?;
let voxel_colliders_manifest = VOXEL_COLLIDER_MANIFEST.read();
let voxel_collider = collider.get_vol(&voxel_colliders_manifest)?;
let block = *voxel_collider.volume().get(self.pos).ok()?;
Some(block)
})
},
}
}
}
#[derive(Default)]
pub struct VolumeRiders {
riders: HashSet<Vec3<i32>>,
}
impl Component for VolumeRiders {
type Storage = DenseVecStorage<Self>;
}
#[derive(Serialize, Deserialize, Debug)]
pub struct VolumeMounting {
pub pos: VolumePos,
pub block: Block,
pub rider: Uid,
}
impl Link for VolumeMounting {
type CreateData<'a> = (
Write<'a, VolumeRiders>,
WriteStorage<'a, VolumeRiders>,
WriteStorage<'a, Is<VolumeRider>>,
ReadStorage<'a, Is<Rider>>,
ReadStorage<'a, Is<Mount>>,
ReadExpect<'a, TerrainGrid>,
Read<'a, UidAllocator>,
ReadStorage<'a, comp::Collider>,
);
type DeleteData<'a> = (
Write<'a, VolumeRiders>,
WriteStorage<'a, VolumeRiders>,
WriteStorage<'a, Is<VolumeRider>>,
Read<'a, UidAllocator>,
);
type Error = MountingError;
type PersistData<'a> = (
Entities<'a>,
ReadStorage<'a, comp::Health>,
Read<'a, VolumeRiders>,
ReadStorage<'a, VolumeRiders>,
ReadStorage<'a, Is<VolumeRider>>,
ReadExpect<'a, TerrainGrid>,
Read<'a, UidAllocator>,
ReadStorage<'a, comp::Collider>,
);
fn create(
this: &LinkHandle<Self>,
(
mut terrain_riders,
mut volume_riders,
mut is_volume_riders,
is_riders,
is_mounts,
terrain_grid,
uid_allocator,
colliders,
): Self::CreateData<'_>,
) -> Result<(), Self::Error> {
let entity = |uid: Uid| uid_allocator.retrieve_entity_internal(uid.into());
let riders = match this.pos.kind {
Volume::Terrain => &mut *terrain_riders,
Volume::Entity(uid) => entity(uid)
.and_then(|entity| volume_riders.get_mut_or_default(entity))
.ok_or(MountingError::NoSuchEntity)?,
};
let rider = entity(this.rider).ok_or(MountingError::NoSuchEntity)?;
if !riders.riders.contains(&this.pos.pos)
&& !is_volume_riders.contains(rider)
&& !is_volume_riders.contains(rider)
&& !is_riders.contains(rider)
&& !is_mounts.contains(rider)
{
let block = this
.pos
.get_block(&terrain_grid, &uid_allocator, &colliders)
.ok_or(MountingError::NoSuchEntity)?;
if block == this.block {
let _ = is_volume_riders.insert(rider, this.make_role());
riders.riders.insert(this.pos.pos);
Ok(())
} else {
Err(MountingError::NotMountable)
}
} else {
Err(MountingError::NotMountable)
}
}
fn persist(
this: &LinkHandle<Self>,
(
entities,
healths,
terrain_riders,
volume_riders,
is_volume_riders,
terrain_grid,
uid_allocator,
colliders,
): Self::PersistData<'_>,
) -> bool {
let entity = |uid: Uid| uid_allocator.retrieve_entity_internal(uid.into());
let is_alive =
|entity| entities.is_alive(entity) && healths.get(entity).map_or(true, |h| !h.is_dead);
let riders = match this.pos.kind {
Volume::Terrain => &*terrain_riders,
Volume::Entity(uid) => {
let Some(riders) = entity(uid)
.filter(|entity| is_alive(*entity))
.and_then(|entity| volume_riders.get(entity)) else {
return false;
};
riders
},
};
let rider_exists = entity(this.rider).map_or(false, |rider| {
is_volume_riders.contains(rider) && is_alive(rider)
});
let mount_spot_exists = riders.riders.contains(&this.pos.pos);
let block_exists = this
.pos
.get_block(&terrain_grid, &uid_allocator, &colliders)
.map_or(false, |block| block == this.block);
rider_exists && mount_spot_exists && block_exists
}
fn delete(
this: &LinkHandle<Self>,
(mut terrain_riders, mut volume_riders, mut is_rider, uid_allocator): Self::DeleteData<'_>,
) {
let entity = |uid: Uid| uid_allocator.retrieve_entity_internal(uid.into());
let riders = match this.pos.kind {
Volume::Terrain => Some(&mut *terrain_riders),
Volume::Entity(uid) => {
entity(uid).and_then(|entity| volume_riders.get_mut_or_default(entity))
},
};
if let Some(riders) = riders {
riders.riders.remove(&this.pos.pos);
}
if let Some(entity) = entity(this.rider) {
is_rider.remove(entity);
}
}
}

View File

@ -9,7 +9,7 @@ use crate::{
Stats, Vel, Stats, Vel,
}, },
link::Is, link::Is,
mounting::Rider, mounting::{Rider, VolumeRider},
resources::{DeltaTime, Time}, resources::{DeltaTime, Time},
terrain::TerrainGrid, terrain::TerrainGrid,
uid::Uid, uid::Uid,
@ -59,14 +59,6 @@ pub trait CharacterBehavior {
fn talk(&self, data: &JoinData, _output_events: &mut OutputEvents) -> StateUpdate { fn talk(&self, data: &JoinData, _output_events: &mut OutputEvents) -> StateUpdate {
StateUpdate::from(data) StateUpdate::from(data)
} }
fn mount_sprite(
&self,
data: &JoinData,
_output_events: &mut OutputEvents,
_pos: Vec3<i32>,
) -> StateUpdate {
StateUpdate::from(data)
}
// start_input has custom implementation in the following character states that // start_input has custom implementation in the following character states that
// may also need to be modified when changes are made here: ComboMelee2 // may also need to be modified when changes are made here: ComboMelee2
fn start_input( fn start_input(
@ -105,7 +97,7 @@ pub trait CharacterBehavior {
ControlAction::Sit => self.sit(data, output_events), ControlAction::Sit => self.sit(data, output_events),
ControlAction::Dance => self.dance(data, output_events), ControlAction::Dance => self.dance(data, output_events),
ControlAction::Sneak => { ControlAction::Sneak => {
if data.mount_data.is_none() { if data.mount_data.is_none() && data.volume_mount_data.is_none() {
self.sneak(data, output_events) self.sneak(data, output_events)
} else { } else {
self.stand(data, output_events) self.stand(data, output_events)
@ -119,7 +111,6 @@ pub trait CharacterBehavior {
select_pos, select_pos,
} => self.start_input(data, input, target_entity, select_pos), } => self.start_input(data, input, target_entity, select_pos),
ControlAction::CancelInput(input) => self.cancel_input(data, input), ControlAction::CancelInput(input) => self.cancel_input(data, input),
ControlAction::MountSprite(pos) => self.mount_sprite(data, output_events, pos),
} }
} }
} }
@ -156,6 +147,7 @@ pub struct JoinData<'a> {
pub alignment: Option<&'a comp::Alignment>, pub alignment: Option<&'a comp::Alignment>,
pub terrain: &'a TerrainGrid, pub terrain: &'a TerrainGrid,
pub mount_data: Option<&'a Is<Rider>>, pub mount_data: Option<&'a Is<Rider>>,
pub volume_mount_data: Option<&'a Is<VolumeRider>>,
pub stance: Option<&'a Stance>, pub stance: Option<&'a Stance>,
} }
@ -185,6 +177,7 @@ pub struct JoinStruct<'a> {
pub alignment: Option<&'a comp::Alignment>, pub alignment: Option<&'a comp::Alignment>,
pub terrain: &'a TerrainGrid, pub terrain: &'a TerrainGrid,
pub mount_data: Option<&'a Is<Rider>>, pub mount_data: Option<&'a Is<Rider>>,
pub volume_mount_data: Option<&'a Is<VolumeRider>>,
pub stance: Option<&'a Stance>, pub stance: Option<&'a Stance>,
} }
@ -228,6 +221,7 @@ impl<'a> JoinData<'a> {
terrain: j.terrain, terrain: j.terrain,
active_abilities: j.active_abilities, active_abilities: j.active_abilities,
mount_data: j.mount_data, mount_data: j.mount_data,
volume_mount_data: j.volume_mount_data,
stance: j.stance, stance: j.stance,
} }
} }

View File

@ -7,7 +7,6 @@ use crate::{
}, },
}; };
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use vek::Vec3;
#[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize, Eq, Hash)] #[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize, Eq, Hash)]
pub struct Data; pub struct Data;
@ -51,12 +50,6 @@ impl CharacterBehavior for Data {
update update
} }
fn mount_sprite(&self, data: &JoinData, _: &mut OutputEvents, pos: Vec3<i32>) -> StateUpdate {
let mut update = StateUpdate::from(data);
attempt_mount_sprite(data, &mut update, pos);
update
}
fn stand(&self, data: &JoinData, _: &mut OutputEvents) -> StateUpdate { fn stand(&self, data: &JoinData, _: &mut OutputEvents) -> StateUpdate {
let mut update = StateUpdate::from(data); let mut update = StateUpdate::from(data);
// Try to Fall/Stand up/Move // Try to Fall/Stand up/Move

View File

@ -12,7 +12,6 @@ use crate::{
}, },
}; };
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use vek::Vec3;
#[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)] #[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)]
pub struct Data { pub struct Data {
@ -111,12 +110,6 @@ impl CharacterBehavior for Data {
update update
} }
fn mount_sprite(&self, data: &JoinData, _: &mut OutputEvents, pos: Vec3<i32>) -> StateUpdate {
let mut update = StateUpdate::from(data);
attempt_mount_sprite(data, &mut update, pos);
update
}
fn dance(&self, data: &JoinData, _: &mut OutputEvents) -> StateUpdate { fn dance(&self, data: &JoinData, _: &mut OutputEvents) -> StateUpdate {
let mut update = StateUpdate::from(data); let mut update = StateUpdate::from(data);
attempt_dance(data, &mut update); attempt_dance(data, &mut update);

View File

@ -8,7 +8,6 @@ use crate::{
states::behavior::{CharacterBehavior, JoinData}, states::behavior::{CharacterBehavior, JoinData},
}; };
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use vek::Vec3;
#[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize, Default)] #[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize, Default)]
pub struct Data { pub struct Data {
@ -87,12 +86,6 @@ impl CharacterBehavior for Data {
update update
} }
fn mount_sprite(&self, data: &JoinData, _: &mut OutputEvents, pos: Vec3<i32>) -> StateUpdate {
let mut update = StateUpdate::from(data);
attempt_mount_sprite(data, &mut update, pos);
update
}
fn dance(&self, data: &JoinData, _: &mut OutputEvents) -> StateUpdate { fn dance(&self, data: &JoinData, _: &mut OutputEvents) -> StateUpdate {
let mut update = StateUpdate::from(data); let mut update = StateUpdate::from(data);
attempt_dance(data, &mut update); attempt_dance(data, &mut update);

View File

@ -22,7 +22,6 @@ pub mod glide_wield;
pub mod idle; pub mod idle;
pub mod leap_melee; pub mod leap_melee;
pub mod leap_shockwave; pub mod leap_shockwave;
pub mod mount_sprite;
pub mod music; pub mod music;
pub mod rapid_melee; pub mod rapid_melee;
pub mod repeater_ranged; pub mod repeater_ranged;

View File

@ -1,60 +0,0 @@
use serde::{Deserialize, Serialize};
use vek::Vec3;
use crate::{
comp::{character_state::OutputEvents, CharacterState, StateUpdate},
util::Dir,
};
use super::{
behavior::{CharacterBehavior, JoinData},
idle,
utils::{end_ability, handle_orientation, handle_wield},
};
#[derive(Clone, Copy, Debug, PartialEq, Serialize, Deserialize)]
pub struct StaticData {
pub mount_pos: Vec3<f32>,
pub mount_dir: Vec3<f32>,
/// Position sprite is located at
pub sprite_pos: Vec3<i32>,
}
#[derive(Clone, Copy, Debug, PartialEq, Serialize, Deserialize)]
pub struct Data {
/// Struct containing data that does not change over the course of the
/// character state
pub static_data: StaticData,
}
impl CharacterBehavior for Data {
fn behavior(&self, data: &JoinData, _output_events: &mut OutputEvents) -> StateUpdate {
let mut update = StateUpdate::from(data);
update.pos.0 = self.static_data.mount_pos;
update.vel.0 = Vec3::zero();
handle_orientation(
data,
&mut update,
1.0,
Some(Dir::new(self.static_data.mount_dir)),
);
handle_wield(data, &mut update);
// Try to Fall/Stand up/Move
if data.physics.on_ground.is_none() || data.inputs.move_dir.magnitude_squared() > 0.0 {
update.character = CharacterState::Idle(idle::Data::default());
}
update
}
fn stand(&self, data: &JoinData, _output_events: &mut OutputEvents) -> StateUpdate {
let mut update = StateUpdate::from(data);
end_ability(data, &mut update);
update
}
}

View File

@ -10,7 +10,6 @@ use crate::{
}, },
}; };
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use vek::Vec3;
#[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)] #[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)]
pub struct Data { pub struct Data {
@ -109,12 +108,6 @@ impl CharacterBehavior for Data {
update update
} }
fn mount_sprite(&self, data: &JoinData, _: &mut OutputEvents, pos: Vec3<i32>) -> StateUpdate {
let mut update = StateUpdate::from(data);
attempt_mount_sprite(data, &mut update, pos);
update
}
fn stand(&self, data: &JoinData, _: &mut OutputEvents) -> StateUpdate { fn stand(&self, data: &JoinData, _: &mut OutputEvents) -> StateUpdate {
let mut update = StateUpdate::from(data); let mut update = StateUpdate::from(data);
// Try to Fall/Stand up/Move // Try to Fall/Stand up/Move

View File

@ -7,7 +7,6 @@ use crate::{
}, },
}; };
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use vek::Vec3;
const TURN_RATE: f32 = 40.0; const TURN_RATE: f32 = 40.0;
@ -48,13 +47,6 @@ impl CharacterBehavior for Data {
update update
} }
fn mount_sprite(&self, data: &JoinData, _: &mut OutputEvents, pos: Vec3<i32>) -> StateUpdate {
let mut update = StateUpdate::from(data);
update.character = CharacterState::Idle(idle::Data::default());
attempt_mount_sprite(data, &mut update, pos);
update
}
fn dance(&self, data: &JoinData, _: &mut OutputEvents) -> StateUpdate { fn dance(&self, data: &JoinData, _: &mut OutputEvents) -> StateUpdate {
let mut update = StateUpdate::from(data); let mut update = StateUpdate::from(data);
update.character = CharacterState::Idle(idle::Data::default()); update.character = CharacterState::Idle(idle::Data::default());

View File

@ -21,13 +21,12 @@ use crate::{
event::{LocalEvent, ServerEvent}, event::{LocalEvent, ServerEvent},
outcome::Outcome, outcome::Outcome,
states::{behavior::JoinData, utils::CharacterState::Idle, *}, states::{behavior::JoinData, utils::CharacterState::Idle, *},
terrain::{SpriteKind, TerrainGrid, UnlockKind}, terrain::{TerrainGrid, UnlockKind},
util::Dir, util::Dir,
vol::ReadVol, vol::ReadVol,
}; };
use core::hash::BuildHasherDefault; use core::hash::BuildHasherDefault;
use fxhash::FxHasher64; use fxhash::FxHasher64;
use ordered_float::OrderedFloat;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use std::{ use std::{
f32::consts::PI, f32::consts::PI,
@ -785,52 +784,6 @@ pub fn attempt_sit(data: &JoinData<'_>, update: &mut StateUpdate) {
} }
} }
pub fn sprite_mount_points(
sprite_kind: SpriteKind,
pos: Vec3<i32>,
ori: u8,
) -> impl ExactSizeIterator<Item = (Vec3<f32>, Vec3<f32>)> {
let mat = Mat4::identity()
.rotated_z(std::f32::consts::PI * 0.25 * ori as f32)
.translated_3d(pos.map(|e| e as f32) + Vec3::new(0.5, 0.5, 0.0));
sprite_kind
.mount_offsets()
.iter()
.map(move |(pos, dir)| ((mat * pos.with_w(1.0)).xyz(), (mat * dir.with_w(0.0)).xyz()))
}
const SPRITE_MOUNT_RANGE: f32 = 2.0;
pub const SPRITE_MOUNT_RANGE_SQR: f32 = SPRITE_MOUNT_RANGE * SPRITE_MOUNT_RANGE;
pub fn attempt_mount_sprite(data: &JoinData<'_>, update: &mut StateUpdate, pos: Vec3<i32>) {
if let Some((kind, ori)) = data
.terrain
.get(pos)
.ok()
.and_then(|block| block.get_sprite().zip(block.get_ori()))
{
if let Some((mount_pos, mount_dir)) = sprite_mount_points(kind, pos, ori)
.min_by_key(|(pos, _)| OrderedFloat(data.pos.0.distance_squared(*pos)))
{
if reach_block(
data.pos.0,
pos,
SPRITE_MOUNT_RANGE_SQR,
data.body,
data.terrain,
) {
update.character = CharacterState::MountSprite(mount_sprite::Data {
static_data: mount_sprite::StaticData {
mount_pos,
mount_dir,
sprite_pos: pos,
},
});
}
}
}
}
pub fn attempt_dance(data: &JoinData<'_>, update: &mut StateUpdate) { pub fn attempt_dance(data: &JoinData<'_>, update: &mut StateUpdate) {
if data.physics.on_ground.is_some() && data.body.is_humanoid() { if data.physics.on_ground.is_some() && data.body.is_humanoid() {
update.character = CharacterState::Dance; update.character = CharacterState::Dance;

View File

@ -11,7 +11,6 @@ use crate::{
}, },
}; };
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use vek::Vec3;
#[derive(Copy, Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] #[derive(Copy, Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub struct Data { pub struct Data {
@ -94,12 +93,6 @@ impl CharacterBehavior for Data {
update update
} }
fn mount_sprite(&self, data: &JoinData, _: &mut OutputEvents, pos: Vec3<i32>) -> StateUpdate {
let mut update = StateUpdate::from(data);
attempt_mount_sprite(data, &mut update, pos);
update
}
fn dance(&self, data: &JoinData, _: &mut OutputEvents) -> StateUpdate { fn dance(&self, data: &JoinData, _: &mut OutputEvents) -> StateUpdate {
let mut update = StateUpdate::from(data); let mut update = StateUpdate::from(data);
attempt_dance(data, &mut update); attempt_dance(data, &mut update);

View File

@ -428,7 +428,16 @@ impl Block {
} }
#[inline] #[inline]
pub fn is_mountable(&self) -> bool { self.get_sprite().map_or(false, |s| s.is_mountable()) } pub fn is_mountable(&self) -> bool { self.mount_offset().is_some() }
/// Get the position and direction to mount this block if any.
pub fn mount_offset(&self) -> Option<(Vec3<f32>, Vec3<f32>)> {
self.get_sprite().and_then(|sprite| sprite.mount_offset())
}
pub fn is_controller(&self) -> bool {
self.get_sprite().map_or(false, |sprite| sprite.is_controller())
}
#[inline] #[inline]
pub fn is_bonkable(&self) -> bool { pub fn is_bonkable(&self) -> bool {

View File

@ -242,6 +242,7 @@ make_case_elim!(
KeyDoor = 0xD8, KeyDoor = 0xD8,
CommonLockedChest = 0xD9, CommonLockedChest = 0xD9,
RepairBench = 0xDA, RepairBench = 0xDA,
Helm = 0xDB,
} }
); );
@ -475,47 +476,40 @@ impl SpriteKind {
matches!(self.collectible_id(), Some(Some(LootSpec::LootTable(_)))) matches!(self.collectible_id(), Some(Some(LootSpec::LootTable(_))))
} }
/// Is the sprite a container that will emit a mystery item? /// Get the position and direction to mount this sprite if any.
#[inline] #[inline]
pub fn mount_offsets(&self) -> &'static [(Vec3<f32>, Vec3<f32>)] { pub fn mount_offset(&self) -> Option<(Vec3<f32>, Vec3<f32>)> {
const UNIT_Y: Vec3<f32> = Vec3 {
x: 0.0,
y: -1.0,
z: 0.0,
};
match self { match self {
SpriteKind::ChairSingle => &[( SpriteKind::ChairSingle | SpriteKind::ChairDouble => Some((
Vec3 { Vec3 {
x: 0.0, x: 0.0,
y: 0.0, y: 0.0,
z: 0.5, z: 0.5,
}, },
UNIT_Y, -Vec3::unit_y(),
)], )),
SpriteKind::ChairDouble => &[ SpriteKind::Helm => Some((
( Vec3 {
Vec3 { x: 0.0,
x: -0.5, y: -0.6,
y: 0.0, z: 0.2,
z: 0.6, },
}, Vec3::unit_y(),
UNIT_Y, )),
), _ => None,
(
Vec3 {
x: 0.5,
y: -0.1,
z: 0.6,
},
UNIT_Y,
),
],
_ => &[],
} }
} }
#[inline] #[inline]
pub fn is_mountable(&self) -> bool { !self.mount_offsets().is_empty() } pub fn is_mountable(&self) -> bool { self.mount_offset().is_some() }
#[inline]
pub fn is_controller(&self) -> bool {
match self {
SpriteKind::Helm => true,
_ => false,
}
}
/// Which tool (if any) is needed to collect this sprite? /// Which tool (if any) is needed to collect this sprite?
#[inline] #[inline]
@ -646,6 +640,7 @@ impl SpriteKind {
| SpriteKind::Grave | SpriteKind::Grave
| SpriteKind::Gravestone | SpriteKind::Gravestone
| SpriteKind::MagicalBarrier | SpriteKind::MagicalBarrier
| SpriteKind::Helm,
) )
} }
} }

View File

@ -149,6 +149,18 @@ impl FindDist<Vec3<f32>> for Cylinder {
} }
} }
impl FindDist<Cylinder> for Vec3<f32> {
#[inline]
fn approx_in_range(self, other: Cylinder, range: f32) -> bool {
other.approx_in_range(self, range)
}
#[inline]
fn min_distance(self, other: Cylinder) -> f32 {
other.min_distance(self)
}
}
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;

View File

@ -9,7 +9,7 @@ use common::{
comp, comp,
event::{EventBus, LocalEvent, ServerEvent}, event::{EventBus, LocalEvent, ServerEvent},
link::Is, link::Is,
mounting::{Mount, Rider}, mounting::{Mount, Rider, VolumeRider, VolumeRiders},
outcome::Outcome, outcome::Outcome,
region::RegionMap, region::RegionMap,
resources::{ resources::{
@ -201,6 +201,7 @@ impl State {
ecs.register::<comp::Scale>(); ecs.register::<comp::Scale>();
ecs.register::<Is<Mount>>(); ecs.register::<Is<Mount>>();
ecs.register::<Is<Rider>>(); ecs.register::<Is<Rider>>();
ecs.register::<Is<VolumeRider>>();
ecs.register::<comp::Mass>(); ecs.register::<comp::Mass>();
ecs.register::<comp::Density>(); ecs.register::<comp::Density>();
ecs.register::<comp::Collider>(); ecs.register::<comp::Collider>();
@ -260,6 +261,7 @@ impl State {
ecs.register::<comp::invite::Invite>(); ecs.register::<comp::invite::Invite>();
ecs.register::<comp::invite::PendingInvites>(); ecs.register::<comp::invite::PendingInvites>();
ecs.register::<comp::Beam>(); ecs.register::<comp::Beam>();
ecs.register::<VolumeRiders>();
// Register synced resources used by the ECS. // Register synced resources used by the ECS.
ecs.insert(TimeOfDay(0.0)); ecs.insert(TimeOfDay(0.0));
@ -294,6 +296,7 @@ impl State {
ecs.insert(PhysicsMetrics::default()); ecs.insert(PhysicsMetrics::default());
ecs.insert(Trades::default()); ecs.insert(Trades::default());
ecs.insert(PlayerPhysicsSettings::default()); ecs.insert(PlayerPhysicsSettings::default());
ecs.insert(VolumeRiders::default());
// Load plugins from asset directory // Load plugins from asset directory
#[cfg(feature = "plugins")] #[cfg(feature = "plugins")]

View File

@ -14,7 +14,7 @@ use common::{
}, },
event::{EventBus, LocalEvent, ServerEvent}, event::{EventBus, LocalEvent, ServerEvent},
link::Is, link::Is,
mounting::Rider, mounting::{Rider, VolumeRider},
outcome::Outcome, outcome::Outcome,
resources::{DeltaTime, Time}, resources::{DeltaTime, Time},
states::{ states::{
@ -43,6 +43,7 @@ pub struct ReadData<'a> {
beams: ReadStorage<'a, Beam>, beams: ReadStorage<'a, Beam>,
uids: ReadStorage<'a, Uid>, uids: ReadStorage<'a, Uid>,
is_riders: ReadStorage<'a, Is<Rider>>, is_riders: ReadStorage<'a, Is<Rider>>,
is_volume_riders: ReadStorage<'a, Is<VolumeRider>>,
stats: ReadStorage<'a, Stats>, stats: ReadStorage<'a, Stats>,
skill_sets: ReadStorage<'a, SkillSet>, skill_sets: ReadStorage<'a, SkillSet>,
active_abilities: ReadStorage<'a, ActiveAbilities>, active_abilities: ReadStorage<'a, ActiveAbilities>,
@ -207,6 +208,7 @@ impl<'a> System<'a> for Sys {
alignment: read_data.alignments.get(entity), alignment: read_data.alignments.get(entity),
terrain: &read_data.terrain, terrain: &read_data.terrain,
mount_data: read_data.is_riders.get(entity), mount_data: read_data.is_riders.get(entity),
volume_mount_data: read_data.is_volume_riders.get(entity),
stance: read_data.stances.get(entity), stance: read_data.stances.get(entity),
}; };

View File

@ -2,16 +2,17 @@ use common::{
comp::{ comp::{
ability::Stance, ability::Stance,
agent::{Sound, SoundKind}, agent::{Sound, SoundKind},
Body, BuffChange, ControlEvent, Controller, Pos, Scale, Body, BuffChange, Collider, ControlEvent, Controller, Pos, Scale,
}, },
event::{EventBus, ServerEvent}, event::{EventBus, ServerEvent},
terrain::TerrainGrid,
uid::UidAllocator, uid::UidAllocator,
}; };
use common_ecs::{Job, Origin, Phase, System}; use common_ecs::{Job, Origin, Phase, System};
use specs::{ use specs::{
saveload::{Marker, MarkerAllocator}, saveload::{Marker, MarkerAllocator},
shred::ResourceId, shred::ResourceId,
Entities, Join, Read, ReadStorage, SystemData, World, WriteStorage, Entities, Join, Read, ReadExpect, ReadStorage, SystemData, World, WriteStorage,
}; };
use vek::*; use vek::*;
@ -20,9 +21,11 @@ pub struct ReadData<'a> {
entities: Entities<'a>, entities: Entities<'a>,
uid_allocator: Read<'a, UidAllocator>, uid_allocator: Read<'a, UidAllocator>,
server_bus: Read<'a, EventBus<ServerEvent>>, server_bus: Read<'a, EventBus<ServerEvent>>,
terrain_grid: ReadExpect<'a, TerrainGrid>,
positions: ReadStorage<'a, Pos>, positions: ReadStorage<'a, Pos>,
bodies: ReadStorage<'a, Body>, bodies: ReadStorage<'a, Body>,
scales: ReadStorage<'a, Scale>, scales: ReadStorage<'a, Scale>,
colliders: ReadStorage<'a, Collider>,
} }
#[derive(Default)] #[derive(Default)]
@ -53,6 +56,17 @@ impl<'a> System<'a> for Sys {
server_emitter.emit(ServerEvent::Mount(entity, mountee_entity)); server_emitter.emit(ServerEvent::Mount(entity, mountee_entity));
} }
}, },
ControlEvent::MountVolume(volume) => {
if let Some(block) = volume.get_block(
&read_data.terrain_grid,
&read_data.uid_allocator,
&read_data.colliders,
) {
if block.is_mountable() {
server_emitter.emit(ServerEvent::MountVolume(entity, volume));
}
}
},
ControlEvent::RemoveBuff(buff_id) => { ControlEvent::RemoveBuff(buff_id) => {
server_emitter.emit(ServerEvent::Buff { server_emitter.emit(ServerEvent::Buff {
entity, entity,

View File

@ -1,14 +1,16 @@
use common::{ use common::{
comp::{Body, ControlAction, Controller, InputKind, Ori, Pos, Scale, Vel}, comp::{Body, Collider, ControlAction, Controller, InputKind, Ori, Pos, Scale, Vel},
link::Is, link::Is,
mounting::Mount, mounting::{Mount, VolumeRider},
terrain::TerrainGrid,
uid::UidAllocator, uid::UidAllocator,
}; };
use common_ecs::{Job, Origin, Phase, System}; use common_ecs::{Job, Origin, Phase, System};
use specs::{ use specs::{
saveload::{Marker, MarkerAllocator}, saveload::{Marker, MarkerAllocator},
Entities, Join, Read, ReadStorage, WriteStorage, Entities, Join, Read, ReadExpect, ReadStorage, WriteStorage,
}; };
use tracing::error;
use vek::*; use vek::*;
/// This system is responsible for controlling mounts /// This system is responsible for controlling mounts
@ -17,14 +19,17 @@ pub struct Sys;
impl<'a> System<'a> for Sys { impl<'a> System<'a> for Sys {
type SystemData = ( type SystemData = (
Read<'a, UidAllocator>, Read<'a, UidAllocator>,
ReadExpect<'a, TerrainGrid>,
Entities<'a>, Entities<'a>,
WriteStorage<'a, Controller>, WriteStorage<'a, Controller>,
ReadStorage<'a, Is<Mount>>, ReadStorage<'a, Is<Mount>>,
ReadStorage<'a, Is<VolumeRider>>,
WriteStorage<'a, Pos>, WriteStorage<'a, Pos>,
WriteStorage<'a, Vel>, WriteStorage<'a, Vel>,
WriteStorage<'a, Ori>, WriteStorage<'a, Ori>,
ReadStorage<'a, Body>, ReadStorage<'a, Body>,
ReadStorage<'a, Scale>, ReadStorage<'a, Scale>,
ReadStorage<'a, Collider>,
); );
const NAME: &'static str = "mount"; const NAME: &'static str = "mount";
@ -35,14 +40,17 @@ impl<'a> System<'a> for Sys {
_job: &mut Job<Self>, _job: &mut Job<Self>,
( (
uid_allocator, uid_allocator,
terrain,
entities, entities,
mut controllers, mut controllers,
is_mounts, is_mounts,
is_volume_riders,
mut positions, mut positions,
mut velocities, mut velocities,
mut orientations, mut orientations,
bodies, bodies,
scales, scales,
colliders,
): Self::SystemData, ): Self::SystemData,
) { ) {
// For each mount... // For each mount...
@ -84,5 +92,87 @@ impl<'a> System<'a> for Sys {
controller.actions = actions; controller.actions = actions;
} }
} }
// For each volume rider.
for (entity, is_volume_rider) in (&entities, &is_volume_riders).join() {
if let Some((mut mat, _)) = is_volume_rider.pos.get_block_and_transform(
&terrain,
&uid_allocator,
&positions,
&orientations,
&colliders,
) {
let Some((mount_offset, mount_dir)) = is_volume_rider.block.mount_offset() else {
error!("Mounted on unmountable block");
continue;
};
if let Some(ori) = is_volume_rider.block.get_ori() {
mat *= Mat4::identity()
.translated_3d(mount_offset)
.rotated_z(std::f32::consts::PI * 0.25 * ori as f32)
.translated_3d(Vec3::new(0.5, 0.5, 0.0));
} else {
mat *= Mat4::identity().translated_3d(mount_offset + Vec3::new(0.5, 0.5, 0.0));
}
if let Some(pos) = positions.get_mut(entity) {
pos.0 = mat.mul_point(Vec3::zero());
}
if let Some(ori) = orientations.get_mut(entity) {
*ori = Ori::from_unnormalized_vec(mat.mul_direction(mount_dir))
.unwrap_or_default();
}
}
let v = match is_volume_rider.pos.kind {
common::mounting::Volume::Terrain => Vec3::zero(),
common::mounting::Volume::Entity(uid) => {
if let Some(v) = uid_allocator
.retrieve_entity_internal(uid.into())
.and_then(|e| velocities.get(e))
{
v.0
} else {
Vec3::zero()
}
},
};
if let Some(vel) = velocities.get_mut(entity) {
vel.0 = v;
}
let inputs = controllers.get_mut(entity).map(|c| {
let actions: Vec<_> = c
.actions
.drain_filter(|action| match action {
ControlAction::StartInput { input: i, .. }
| ControlAction::CancelInput(i) => {
matches!(i, InputKind::Jump | InputKind::Fly | InputKind::Roll)
},
_ => false,
})
.collect();
let inputs = c.inputs.clone();
(actions, inputs)
});
if is_volume_rider.block.is_controller() {
if let Some((actions, inputs)) = inputs {
match is_volume_rider.pos.kind {
common::mounting::Volume::Entity(uid) => {
if let Some(controller) = uid_allocator
.retrieve_entity_internal(uid.into())
.and_then(|e| controllers.get_mut(e))
{
controller.inputs = inputs;
controller.actions = actions;
}
},
common::mounting::Volume::Terrain => {},
}
}
}
}
} }
} }

View File

@ -9,7 +9,7 @@ use common::{
consts::{AIR_DENSITY, FRIC_GROUND, GRAVITY}, consts::{AIR_DENSITY, FRIC_GROUND, GRAVITY},
event::{EventBus, ServerEvent}, event::{EventBus, ServerEvent},
link::Is, link::Is,
mounting::Rider, mounting::{Rider, VolumeRider},
outcome::Outcome, outcome::Outcome,
resources::DeltaTime, resources::DeltaTime,
states, states,
@ -122,6 +122,7 @@ pub struct PhysicsRead<'a> {
masses: ReadStorage<'a, Mass>, masses: ReadStorage<'a, Mass>,
colliders: ReadStorage<'a, Collider>, colliders: ReadStorage<'a, Collider>,
is_ridings: ReadStorage<'a, Is<Rider>>, is_ridings: ReadStorage<'a, Is<Rider>>,
is_volume_ridings: ReadStorage<'a, Is<VolumeRider>>,
projectiles: ReadStorage<'a, Projectile>, projectiles: ReadStorage<'a, Projectile>,
char_states: ReadStorage<'a, CharacterState>, char_states: ReadStorage<'a, CharacterState>,
bodies: ReadStorage<'a, Body>, bodies: ReadStorage<'a, Body>,
@ -335,6 +336,7 @@ impl<'a> PhysicsData<'a> {
&read.masses, &read.masses,
&read.colliders, &read.colliders,
read.is_ridings.maybe(), read.is_ridings.maybe(),
read.is_volume_ridings.maybe(),
read.stickies.maybe(), read.stickies.maybe(),
read.immovables.maybe(), read.immovables.maybe(),
&mut write.physics_states, &mut write.physics_states,
@ -359,6 +361,7 @@ impl<'a> PhysicsData<'a> {
mass, mass,
collider, collider,
is_riding, is_riding,
is_volume_riding,
sticky, sticky,
immovable, immovable,
physics, physics,
@ -366,8 +369,7 @@ impl<'a> PhysicsData<'a> {
char_state_maybe, char_state_maybe,
)| { )| {
let is_sticky = sticky.is_some(); let is_sticky = sticky.is_some();
let is_immovable = immovable.is_some() let is_immovable = immovable.is_some();
|| matches!(char_state_maybe, Some(CharacterState::MountSprite(_)));
let is_mid_air = physics.on_surface().is_none(); let is_mid_air = physics.on_surface().is_none();
let mut entity_entity_collision_checks = 0; let mut entity_entity_collision_checks = 0;
let mut entity_entity_collisions = 0; let mut entity_entity_collisions = 0;
@ -492,7 +494,9 @@ impl<'a> PhysicsData<'a> {
mass: *mass_other, mass: *mass_other,
}, },
vel, vel,
is_riding.is_some() || other_is_riding_maybe.is_some(), is_riding.is_some()
|| is_volume_riding.is_some()
|| other_is_riding_maybe.is_some(),
); );
} }
}, },
@ -547,11 +551,7 @@ impl<'a> PhysicsData<'a> {
) )
.join() .join()
{ {
let vol = match collider { let vol = collider.get_vol(&voxel_colliders_manifest);
Collider::Voxel { id } => voxel_colliders_manifest.colliders.get(id),
Collider::Volume(vol) => Some(&**vol),
_ => None,
};
if let Some(vol) = vol { if let Some(vol) = vol {
let sphere = voxel_collider_bounding_sphere(vol, pos, ori); let sphere = voxel_collider_bounding_sphere(vol, pos, ori);
@ -588,6 +588,7 @@ impl<'a> PhysicsData<'a> {
!&write.pos_vel_ori_defers, // This is the one we are adding !&write.pos_vel_ori_defers, // This is the one we are adding
write.previous_phys_cache.mask(), write.previous_phys_cache.mask(),
!&read.is_ridings, !&read.is_ridings,
!&read.is_volume_ridings,
) )
.join() .join()
.map(|t| (t.0, *t.2, *t.3, *t.4)) .map(|t| (t.0, *t.2, *t.3, *t.4))
@ -620,9 +621,9 @@ impl<'a> PhysicsData<'a> {
&read.densities, &read.densities,
read.scales.maybe(), read.scales.maybe(),
!&read.is_ridings, !&read.is_ridings,
!&read.is_volume_ridings,
) )
.par_join() .par_join()
.filter(|tuple| !matches!(tuple.4, Some(CharacterState::MountSprite(_))))
.for_each_init( .for_each_init(
|| { || {
prof_span!(guard, "velocity update rayon job"); prof_span!(guard, "velocity update rayon job");
@ -640,6 +641,7 @@ impl<'a> PhysicsData<'a> {
density, density,
scale, scale,
_, _,
_,
)| { )| {
let in_loaded_chunk = read let in_loaded_chunk = read
.terrain .terrain
@ -751,12 +753,10 @@ impl<'a> PhysicsData<'a> {
&mut write.pos_vel_ori_defers, &mut write.pos_vel_ori_defers,
previous_phys_cache, previous_phys_cache,
!&read.is_ridings, !&read.is_ridings,
!&read.is_volume_ridings,
) )
.par_join() .par_join()
.filter(|tuple| { .filter(|tuple| tuple.3.is_voxel() == terrain_like_entities)
tuple.3.is_voxel() == terrain_like_entities
&& !matches!(tuple.8, Some(CharacterState::MountSprite(_)))
})
.map_init( .map_init(
|| { || {
prof_span!(guard, "physics e<>t rayon job"); prof_span!(guard, "physics e<>t rayon job");
@ -777,6 +777,7 @@ impl<'a> PhysicsData<'a> {
pos_vel_ori_defer, pos_vel_ori_defer,
previous_cache, previous_cache,
_, _,
_,
)| { )| {
let mut land_on_ground = None; let mut land_on_ground = None;
let mut outcomes = Vec::new(); let mut outcomes = Vec::new();
@ -1066,13 +1067,8 @@ impl<'a> PhysicsData<'a> {
return; return;
} }
let voxel_collider = match collider_other { let voxel_collider =
Collider::Voxel { id } => { collider_other.get_vol(&voxel_colliders_manifest);
voxel_colliders_manifest.colliders.get(id)
},
Collider::Volume(vol) => Some(&**vol),
_ => None,
};
// use bounding cylinder regardless of our collider // use bounding cylinder regardless of our collider
// TODO: extract point-terrain collision above to its own // TODO: extract point-terrain collision above to its own

View File

@ -145,7 +145,7 @@ impl<'a> System<'a> for Sys {
{ {
match character_state { match character_state {
// Sitting accelerates recharging energy the most // Sitting accelerates recharging energy the most
CharacterState::Sit | CharacterState::MountSprite(_) => { CharacterState::Sit => {
if energy.needs_regen() { if energy.needs_regen() {
energy.regen(SIT_ENERGY_REGEN_ACCEL, dt); energy.regen(SIT_ENERGY_REGEN_ACCEL, dt);
} }

View File

@ -10,7 +10,7 @@ use common::{
SkillSet, Stance, Stats, Vel, SkillSet, Stance, Stats, Vel,
}, },
link::Is, link::Is,
mounting::{Mount, Rider}, mounting::{Mount, Rider, VolumeRider},
path::TraversalConfig, path::TraversalConfig,
resources::{DeltaTime, Time, TimeOfDay}, resources::{DeltaTime, Time, TimeOfDay},
rtsim::{Actor, RtSimEntity}, rtsim::{Actor, RtSimEntity},
@ -236,6 +236,7 @@ pub struct ReadData<'a> {
pub bodies: ReadStorage<'a, Body>, pub bodies: ReadStorage<'a, Body>,
pub is_mounts: ReadStorage<'a, Is<Mount>>, pub is_mounts: ReadStorage<'a, Is<Mount>>,
pub is_riders: ReadStorage<'a, Is<Rider>>, pub is_riders: ReadStorage<'a, Is<Rider>>,
pub is_volume_riders: ReadStorage<'a, Is<VolumeRider>>,
pub time_of_day: Read<'a, TimeOfDay>, pub time_of_day: Read<'a, TimeOfDay>,
pub light_emitter: ReadStorage<'a, LightEmitter>, pub light_emitter: ReadStorage<'a, LightEmitter>,
#[cfg(feature = "worldgen")] #[cfg(feature = "worldgen")]

View File

@ -229,6 +229,7 @@ fn position_mut<T>(
descriptor: &str, descriptor: &str,
f: impl for<'a> FnOnce(&'a mut comp::Pos) -> T, f: impl for<'a> FnOnce(&'a mut comp::Pos) -> T,
) -> CmdResult<T> { ) -> CmdResult<T> {
// TODO: Handle volume mount
let entity = server let entity = server
.state .state
.ecs() .ecs()

View File

@ -14,10 +14,10 @@ use common::{
tool::{AbilityMap, ToolKind}, tool::{AbilityMap, ToolKind},
Inventory, LootOwner, Pos, SkillGroupKind, Inventory, LootOwner, Pos, SkillGroupKind,
}, },
consts::{MAX_MOUNT_RANGE, SOUND_TRAVEL_DIST_PER_VOLUME}, consts::{MAX_MOUNT_RANGE, MAX_SPRITE_MOUNT_RANGE, SOUND_TRAVEL_DIST_PER_VOLUME},
event::EventBus, event::EventBus,
link::Is, link::Is,
mounting::{Mount, Mounting, Rider}, mounting::{Mounting, Rider, VolumeMounting, VolumePos, VolumeRider},
outcome::Outcome, outcome::Outcome,
terrain::{Block, SpriteKind}, terrain::{Block, SpriteKind},
uid::Uid, uid::Uid,
@ -103,53 +103,78 @@ pub fn handle_npc_interaction(
pub fn handle_mount(server: &mut Server, rider: EcsEntity, mount: EcsEntity) { pub fn handle_mount(server: &mut Server, rider: EcsEntity, mount: EcsEntity) {
let state = server.state_mut(); let state = server.state_mut();
if state.ecs().read_storage::<Is<Rider>>().get(rider).is_none() { let within_range = {
let not_mounting_yet = state.ecs().read_storage::<Is<Mount>>().get(mount).is_none(); let positions = state.ecs().read_storage::<Pos>();
within_mounting_range(positions.get(rider), positions.get(mount))
};
let within_range = || { if within_range {
let positions = state.ecs().read_storage::<Pos>(); let uids = state.ecs().read_storage::<Uid>();
within_mounting_range(positions.get(rider), positions.get(mount)) if let (Some(rider_uid), Some(mount_uid)) =
}; (uids.get(rider).copied(), uids.get(mount).copied())
let healths = state.ecs().read_storage::<comp::Health>(); {
let alive = |e| healths.get(e).map_or(true, |h| !h.is_dead); let is_pet = matches!(
state
if not_mounting_yet && within_range() && alive(rider) && alive(mount) {
let uids = state.ecs().read_storage::<Uid>();
if let (Some(rider_uid), Some(mount_uid)) =
(uids.get(rider).copied(), uids.get(mount).copied())
{
let is_pet = matches!(
state
.ecs()
.read_storage::<comp::Alignment>()
.get(mount),
Some(comp::Alignment::Owned(owner)) if *owner == rider_uid,
);
let can_ride = state
.ecs() .ecs()
.read_storage() .read_storage::<comp::Alignment>()
.get(mount) .get(mount),
.map_or(false, |mount_body| { Some(comp::Alignment::Owned(owner)) if *owner == rider_uid,
is_mountable(mount_body, state.ecs().read_storage().get(rider)) );
});
if is_pet && can_ride { let can_ride = state
drop(uids); .ecs()
drop(healths); .read_storage()
let _ = state.link(Mounting { .get(mount)
mount: mount_uid, .map_or(false, |mount_body| {
rider: rider_uid, is_mountable(mount_body, state.ecs().read_storage().get(rider))
}); });
}
if is_pet && can_ride {
drop(uids);
let _ = state.link(Mounting {
mount: mount_uid,
rider: rider_uid,
});
} }
} }
} }
} }
pub fn handle_mount_volume(server: &mut Server, rider: EcsEntity, volume_pos: VolumePos) {
let state = server.state_mut();
let block_transform = volume_pos.get_block_and_transform(
&state.ecs().read_resource(),
&state.ecs().read_resource(),
&state.ecs().read_storage(),
&state.ecs().read_storage(),
&state.ecs().read_storage(),
);
if let Some((transform, block)) = block_transform
&& let Some(mount_offset) = block.mount_offset() {
let mount_pos = (Mat4::from(transform) * mount_offset.0.with_w(1.0)).xyz();
let within_range = {
let positions = state.ecs().read_storage::<Pos>();
positions.get(rider).map_or(false, |pos| pos.0.distance_squared(mount_pos) < MAX_SPRITE_MOUNT_RANGE * MAX_SPRITE_MOUNT_RANGE)
};
let maybe_uid = state.ecs().read_storage::<Uid>().get(rider).copied();
if let Some(rider) = maybe_uid && within_range {
let _ = state.link(VolumeMounting {
pos: volume_pos,
block,
rider,
});
}
}
}
pub fn handle_unmount(server: &mut Server, rider: EcsEntity) { pub fn handle_unmount(server: &mut Server, rider: EcsEntity) {
let state = server.state_mut(); let state = server.state_mut();
state.ecs().write_storage::<Is<Rider>>().remove(rider); state.ecs().write_storage::<Is<Rider>>().remove(rider);
state.ecs().write_storage::<Is<VolumeRider>>().remove(rider);
} }
fn within_mounting_range(player_position: Option<&Pos>, mount_position: Option<&Pos>) -> bool { fn within_mounting_range(player_position: Option<&Pos>, mount_position: Option<&Pos>) -> bool {

View File

@ -21,7 +21,7 @@ use common::{
trade::Trades, trade::Trades,
uid::Uid, uid::Uid,
util::find_dist::{self, FindDist}, util::find_dist::{self, FindDist},
vol::ReadVol, vol::ReadVol, mounting::VolumePos,
}; };
use common_net::sync::WorldSyncExt; use common_net::sync::WorldSyncExt;
use common_state::State; use common_state::State;
@ -745,27 +745,24 @@ pub fn handle_inventory(server: &mut Server, entity: EcsEntity, manip: comp::Inv
let ability_map = &state.ecs().read_resource::<AbilityMap>(); let ability_map = &state.ecs().read_resource::<AbilityMap>();
let msm = state.ecs().read_resource::<MaterialStatManifest>(); let msm = state.ecs().read_resource::<MaterialStatManifest>();
let get_craft_sprite = |state, sprite_pos: Option<Vec3<i32>>| { let get_craft_sprite = |state, sprite_pos: Option<VolumePos>| {
sprite_pos sprite_pos
.filter(|pos| { .filter(|pos| {
let entity_cylinder = get_cylinder(state, entity); let entity_cylinder = get_cylinder(state, entity);
let in_range = within_pickup_range(entity_cylinder, || { let in_range = within_pickup_range(entity_cylinder, || {
Some(find_dist::Cube { pos.get_block_and_transform(&state.terrain(), &state.ecs().read_resource(), &state.read_storage(), &state.read_storage(), &state.read_storage()).map(|(transform, _)| transform.mul_point(Vec3::broadcast(0.5)))
min: pos.as_(),
side_length: 1.0,
})
}); });
if !in_range { if !in_range {
debug!( debug!(
?entity_cylinder, ?entity_cylinder,
"Failed to craft recipe as not within range of required sprite, \ "Failed to craft recipe as not within range of required sprite, \
sprite pos: {}", sprite pos: {:?}",
pos pos
); );
} }
in_range in_range
}) })
.and_then(|pos| state.terrain().get(pos).ok().copied()) .and_then(|pos| pos.get_block(&state.terrain(), &state.ecs().read_resource(), &state.read_storage()))
.and_then(|block| block.get_sprite()) .and_then(|block| block.get_sprite())
}; };

View File

@ -1,5 +1,7 @@
use crate::{ use crate::{
events::interaction::handle_tame_pet, persistence::PersistedComponents, state_ext::StateExt, events::interaction::{handle_mount_volume, handle_tame_pet},
persistence::PersistedComponents,
state_ext::StateExt,
Server, Server,
}; };
use common::event::{EventBus, ServerEvent, ServerEventDiscriminants}; use common::event::{EventBus, ServerEvent, ServerEventDiscriminants};
@ -136,6 +138,9 @@ impl Server {
handle_process_trade_action(self, entity, trade_id, action); handle_process_trade_action(self, entity, trade_id, action);
}, },
ServerEvent::Mount(mounter, mountee) => handle_mount(self, mounter, mountee), ServerEvent::Mount(mounter, mountee) => handle_mount(self, mounter, mountee),
ServerEvent::MountVolume(mounter, volume) => {
handle_mount_volume(self, mounter, volume)
},
ServerEvent::Unmount(mounter) => handle_unmount(self, mounter), ServerEvent::Unmount(mounter) => handle_unmount(self, mounter),
ServerEvent::Possess(possessor_uid, possesse_uid) => { ServerEvent::Possess(possessor_uid, possesse_uid) => {
handle_possess(self, possessor_uid, possesse_uid) handle_possess(self, possessor_uid, possesse_uid)

View File

@ -23,7 +23,7 @@ use common::{
}, },
effect::Effect, effect::Effect,
link::{Link, LinkHandle}, link::{Link, LinkHandle},
mounting::Mounting, mounting::{Mounting, VolumeMounting},
resources::{Secs, Time, TimeOfDay}, resources::{Secs, Time, TimeOfDay},
rtsim::{Actor, RtSimEntity}, rtsim::{Actor, RtSimEntity},
slowjob::SlowJobPool, slowjob::SlowJobPool,
@ -1099,6 +1099,7 @@ impl StateExt for State {
} }
maintain_link::<Mounting>(self); maintain_link::<Mounting>(self);
maintain_link::<VolumeMounting>(self);
} }
fn delete_entity_recorded( fn delete_entity_recorded(

View File

@ -12,6 +12,7 @@ use common::{
Controller, Health, InputKind, Scale, Controller, Health, InputKind, Scale,
}, },
event::{EventBus, ServerEvent}, event::{EventBus, ServerEvent},
mounting::Volume,
path::TraversalConfig, path::TraversalConfig,
}; };
use common_base::prof_span; use common_base::prof_span;
@ -69,6 +70,7 @@ impl<'a> System<'a> for Sys {
read_data.rtsim_entities.maybe(), read_data.rtsim_entities.maybe(),
!&read_data.is_mounts, !&read_data.is_mounts,
read_data.is_riders.maybe(), read_data.is_riders.maybe(),
read_data.is_volume_riders.maybe(),
) )
.par_join() .par_join()
.for_each_init( .for_each_init(
@ -93,6 +95,7 @@ impl<'a> System<'a> for Sys {
rtsim_entity, rtsim_entity,
_, _,
is_rider, is_rider,
is_volume_rider,
)| { )| {
let mut event_emitter = event_bus.emitter(); let mut event_emitter = event_bus.emitter();
let mut rng = thread_rng(); let mut rng = thread_rng();
@ -104,6 +107,16 @@ impl<'a> System<'a> for Sys {
.uid_allocator .uid_allocator
.retrieve_entity_internal(is_rider.mount.into()) .retrieve_entity_internal(is_rider.mount.into())
}) })
.or_else(|| {
is_volume_rider.and_then(|is_volume_rider| {
match is_volume_rider.pos.kind {
Volume::Terrain => None,
Volume::Entity(uid) => {
read_data.uid_allocator.retrieve_entity_internal(uid.into())
},
}
})
})
.unwrap_or(entity); .unwrap_or(entity);
let moving_body = read_data.bodies.get(moving_entity); let moving_body = read_data.bodies.get(moving_entity);

View File

@ -8,7 +8,7 @@ use common::{
}, },
event::{EventBus, ServerEvent}, event::{EventBus, ServerEvent},
link::Is, link::Is,
mounting::Rider, mounting::{Rider, VolumeRider},
resources::{PlayerPhysicsSetting, PlayerPhysicsSettings}, resources::{PlayerPhysicsSetting, PlayerPhysicsSettings},
slowjob::SlowJobPool, slowjob::SlowJobPool,
terrain::TerrainGrid, terrain::TerrainGrid,
@ -52,6 +52,7 @@ impl Sys {
terrain: &ReadExpect<'_, TerrainGrid>, terrain: &ReadExpect<'_, TerrainGrid>,
can_build: &ReadStorage<'_, CanBuild>, can_build: &ReadStorage<'_, CanBuild>,
is_rider: &ReadStorage<'_, Is<Rider>>, is_rider: &ReadStorage<'_, Is<Rider>>,
is_volume_rider: &ReadStorage<'_, Is<VolumeRider>>,
force_updates: &ReadStorage<'_, ForceUpdate>, force_updates: &ReadStorage<'_, ForceUpdate>,
skill_set: &mut Option<Cow<'_, SkillSet>>, skill_set: &mut Option<Cow<'_, SkillSet>>,
healths: &ReadStorage<'_, Health>, healths: &ReadStorage<'_, Health>,
@ -126,6 +127,7 @@ impl Sys {
&& force_updates.get(entity).map_or(true, |force_update| force_update.counter() == force_counter) && force_updates.get(entity).map_or(true, |force_update| force_update.counter() == force_counter)
&& healths.get(entity).map_or(true, |h| !h.is_dead) && healths.get(entity).map_or(true, |h| !h.is_dead)
&& is_rider.get(entity).is_none() && is_rider.get(entity).is_none()
&& is_volume_rider.get(entity).is_none()
&& player_physics_setting && player_physics_setting
.as_ref() .as_ref()
.map_or(true, |s| s.client_authoritative()) .map_or(true, |s| s.client_authoritative())
@ -322,6 +324,7 @@ impl<'a> System<'a> for Sys {
ReadStorage<'a, CanBuild>, ReadStorage<'a, CanBuild>,
ReadStorage<'a, ForceUpdate>, ReadStorage<'a, ForceUpdate>,
ReadStorage<'a, Is<Rider>>, ReadStorage<'a, Is<Rider>>,
ReadStorage<'a, Is<VolumeRider>>,
WriteStorage<'a, SkillSet>, WriteStorage<'a, SkillSet>,
ReadStorage<'a, Health>, ReadStorage<'a, Health>,
Write<'a, BlockChange>, Write<'a, BlockChange>,
@ -353,6 +356,7 @@ impl<'a> System<'a> for Sys {
can_build, can_build,
force_updates, force_updates,
is_rider, is_rider,
is_volume_rider,
mut skill_sets, mut skill_sets,
healths, healths,
mut block_changes, mut block_changes,
@ -430,6 +434,7 @@ impl<'a> System<'a> for Sys {
&terrain, &terrain,
&can_build, &can_build,
&is_rider, &is_rider,
&is_volume_rider,
&force_updates, &force_updates,
&mut skill_set, &mut skill_set,
&healths, &healths,

View File

@ -25,6 +25,7 @@ use common::{
slot::{InvSlotId, Slot}, slot::{InvSlotId, Slot},
Inventory, Inventory,
}, },
mounting::VolumePos,
recipe::{ComponentKey, Recipe, RecipeInput}, recipe::{ComponentKey, Recipe, RecipeInput},
terrain::SpriteKind, terrain::SpriteKind,
}; };
@ -123,7 +124,7 @@ pub enum Event {
pub struct CraftingShow { pub struct CraftingShow {
pub crafting_tab: CraftingTab, pub crafting_tab: CraftingTab,
pub crafting_search_key: Option<String>, pub crafting_search_key: Option<String>,
pub craft_sprite: Option<(Vec3<i32>, SpriteKind)>, pub craft_sprite: Option<(VolumePos, SpriteKind)>,
pub salvage: bool, pub salvage: bool,
pub initialize_repair: bool, pub initialize_repair: bool,
// TODO: Maybe try to do something that doesn't need to allocate? // TODO: Maybe try to do something that doesn't need to allocate?

View File

@ -107,7 +107,7 @@ use common::{
}, },
consts::MAX_PICKUP_RANGE, consts::MAX_PICKUP_RANGE,
link::Is, link::Is,
mounting::Mount, mounting::{Mount, VolumePos},
outcome::Outcome, outcome::Outcome,
resources::{Secs, Time}, resources::{Secs, Time},
slowjob::SlowJobPool, slowjob::SlowJobPool,
@ -710,27 +710,27 @@ pub enum Event {
CraftRecipe { CraftRecipe {
recipe_name: String, recipe_name: String,
craft_sprite: Option<(Vec3<i32>, SpriteKind)>, craft_sprite: Option<(VolumePos, SpriteKind)>,
amount: u32, amount: u32,
}, },
SalvageItem { SalvageItem {
slot: InvSlotId, slot: InvSlotId,
salvage_pos: Vec3<i32>, salvage_pos: VolumePos,
}, },
CraftModularWeapon { CraftModularWeapon {
primary_slot: InvSlotId, primary_slot: InvSlotId,
secondary_slot: InvSlotId, secondary_slot: InvSlotId,
craft_sprite: Option<Vec3<i32>>, craft_sprite: Option<VolumePos>,
}, },
CraftModularWeaponComponent { CraftModularWeaponComponent {
toolkind: ToolKind, toolkind: ToolKind,
material: InvSlotId, material: InvSlotId,
modifier: Option<InvSlotId>, modifier: Option<InvSlotId>,
craft_sprite: Option<Vec3<i32>>, craft_sprite: Option<VolumePos>,
}, },
RepairItem { RepairItem {
item: Slot, item: Slot,
sprite_pos: Vec3<i32>, sprite_pos: VolumePos,
}, },
InviteMember(Uid), InviteMember(Uid),
AcceptInvite, AcceptInvite,
@ -995,7 +995,7 @@ impl Show {
pub fn open_crafting_tab( pub fn open_crafting_tab(
&mut self, &mut self,
tab: CraftingTab, tab: CraftingTab,
craft_sprite: Option<(Vec3<i32>, SpriteKind)>, craft_sprite: Option<(VolumePos, SpriteKind)>,
) { ) {
self.selected_crafting_tab(tab); self.selected_crafting_tab(tab);
self.crafting(true); self.crafting(true);
@ -1289,7 +1289,7 @@ pub struct Hud {
item_imgs: ItemImgs, item_imgs: ItemImgs,
fonts: Fonts, fonts: Fonts,
rot_imgs: ImgsRot, rot_imgs: ImgsRot,
failed_block_pickups: HashMap<Vec3<i32>, CollectFailedData>, failed_block_pickups: HashMap<VolumePos, CollectFailedData>,
failed_entity_pickups: HashMap<EcsEntity, CollectFailedData>, failed_entity_pickups: HashMap<EcsEntity, CollectFailedData>,
new_loot_messages: VecDeque<LootMessage>, new_loot_messages: VecDeque<LootMessage>,
new_messages: VecDeque<comp::ChatMsg>, new_messages: VecDeque<comp::ChatMsg>,
@ -2040,7 +2040,8 @@ impl Hud {
} }
// Render overtime for an interactable block // Render overtime for an interactable block
if let Some(Interactable::Block(block, pos, interaction)) = interactable { if let Some(Interactable::Block(block, pos, interaction)) = interactable
&& let Some((mat, _)) = pos.get_block_and_transform(&ecs.read_resource(), &ecs.read_resource(), &ecs.read_storage(), &ecs.read_storage(), &ecs.read_storage()) {
let overitem_id = overitem_walker.next( let overitem_id = overitem_walker.next(
&mut self.ids.overitems, &mut self.ids.overitems,
&mut ui_widgets.widget_id_generator(), &mut ui_widgets.widget_id_generator(),
@ -2050,7 +2051,8 @@ impl Hud {
active: true, active: true,
pickup_failed_pulse: self.failed_block_pickups.get(pos).cloned(), pickup_failed_pulse: self.failed_block_pickups.get(pos).cloned(),
}; };
let pos = pos.map(|e| e as f32 + 0.5);
let pos = mat.mul_point(Vec3::broadcast(0.5));
let over_pos = pos + Vec3::unit_z() * 0.7; let over_pos = pos + Vec3::unit_z() * 0.7;
let interaction_text = || match interaction { let interaction_text = || match interaction {
@ -2107,10 +2109,9 @@ impl Hud {
} }
} }
}, },
BlockInteraction::Mount(_) => vec![( BlockInteraction::Mount => {
Some(GameInput::Interact), vec![(Some(GameInput::Mount), i18n.get_msg("hud-sit").to_string())]
i18n.get_msg("hud-sit").to_string(), },
)],
}; };
// This is only done once per frame, so it's not a performance issue // This is only done once per frame, so it's not a performance issue
@ -4349,7 +4350,7 @@ impl Hud {
} }
} }
pub fn add_failed_block_pickup(&mut self, pos: Vec3<i32>, reason: HudCollectFailedReason) { pub fn add_failed_block_pickup(&mut self, pos: VolumePos, reason: HudCollectFailedReason) {
self.failed_block_pickups self.failed_block_pickups
.insert(pos, CollectFailedData::new(self.pulse, reason)); .insert(pos, CollectFailedData::new(self.pulse, reason));
} }
@ -4730,16 +4731,31 @@ impl Hud {
.handle_event(conrod_core::event::Input::Text("\t".to_string())); .handle_event(conrod_core::event::Input::Text("\t".to_string()));
} }
// Stop selecting a sprite to perform crafting with when out of range // Stop selecting a sprite to perform crafting with when out of range or sprite
// has been removed
self.show.crafting_fields.craft_sprite = self.show.crafting_fields.craft_sprite =
self.show.crafting_fields.craft_sprite.filter(|(pos, _)| { self.show
self.show.crafting .crafting_fields
&& if let Some(player_pos) = client.position() { .craft_sprite
pos.map(|e| e as f32 + 0.5).distance(player_pos) < MAX_PICKUP_RANGE .filter(|(pos, sprite)| {
} else { self.show.crafting
false && if let Some(player_pos) = client.position() {
} pos.get_block_and_transform(
}); &client.state().terrain(),
&client.state().ecs().read_resource(),
&client.state().read_storage(),
&client.state().read_storage(),
&client.state().read_storage(),
)
.map_or(false, |(mat, block)| {
block.get_sprite() == Some(*sprite)
&& mat.mul_point(Vec3::broadcast(0.5)).distance(player_pos)
< MAX_PICKUP_RANGE
})
} else {
false
}
});
// Optimization: skip maintaining UI when it's off. // Optimization: skip maintaining UI when it's off.
if !self.show.ui { if !self.show.ui {

View File

@ -43,7 +43,7 @@ use common::{
Scale, SkillSet, Stance, Vel, Scale, SkillSet, Stance, Vel,
}, },
link::Is, link::Is,
mounting::Rider, mounting::{Rider, VolumeRider},
resources::{DeltaTime, Time}, resources::{DeltaTime, Time},
states::{equipping, idle, utils::StageSection, wielding}, states::{equipping, idle, utils::StageSection, wielding},
terrain::{Block, TerrainChunk, TerrainGrid}, terrain::{Block, TerrainChunk, TerrainGrid},
@ -830,7 +830,7 @@ impl FigureMgr {
inventory, inventory,
item, item,
light_emitter, light_emitter,
(is_rider, collider, stance, skillset), (is_rider, is_volume_rider, collider, stance, skillset),
), ),
) in ( ) in (
&ecs.entities(), &ecs.entities(),
@ -850,6 +850,7 @@ impl FigureMgr {
ecs.read_storage::<LightEmitter>().maybe(), ecs.read_storage::<LightEmitter>().maybe(),
( (
ecs.read_storage::<Is<Rider>>().maybe(), ecs.read_storage::<Is<Rider>>().maybe(),
ecs.read_storage::<Is<VolumeRider>>().maybe(),
ecs.read_storage::<Collider>().maybe(), ecs.read_storage::<Collider>().maybe(),
ecs.read_storage::<Stance>().maybe(), ecs.read_storage::<Stance>().maybe(),
ecs.read_storage::<SkillSet>().maybe(), ecs.read_storage::<SkillSet>().maybe(),
@ -1029,11 +1030,15 @@ impl FigureMgr {
// If a mount exists, get its animated mounting transform and its position // If a mount exists, get its animated mounting transform and its position
let mount_transform_pos = (|| -> Option<_> { let mount_transform_pos = (|| -> Option<_> {
let mount = is_rider?.mount; if let Some(is_rider) = is_rider {
let mount = uid_allocator.retrieve_entity_internal(mount.into())?; let mount = is_rider.mount;
let body = *bodies.get(mount)?; let mount = uid_allocator.retrieve_entity_internal(mount.into())?;
let meta = self.states.get_mut(&body, &mount)?; let body = *bodies.get(mount)?;
Some((meta.mount_transform, meta.mount_world_pos)) let meta = self.states.get_mut(&body, &mount)?;
Some((meta.mount_transform, meta.mount_world_pos))
} else {
None
}
})(); })();
let body = *body; let body = *body;
@ -1111,7 +1116,7 @@ impl FigureMgr {
physics.on_ground.is_some(), physics.on_ground.is_some(),
rel_vel.magnitude_squared() > 0.01, // Moving rel_vel.magnitude_squared() > 0.01, // Moving
physics.in_liquid().is_some(), // In water physics.in_liquid().is_some(), // In water
is_rider.is_some(), is_rider.is_some() || is_volume_rider.is_some(),
physics.skating_active, physics.skating_active,
) { ) {
// Standing or Skating // Standing or Skating
@ -2059,7 +2064,7 @@ impl FigureMgr {
skeleton_attr, skeleton_attr,
) )
}, },
CharacterState::Sit { .. } | CharacterState::MountSprite(_) => { CharacterState::Sit { .. } => {
anim::character::SitAnimation::update_skeleton( anim::character::SitAnimation::update_skeleton(
&target_base, &target_base,
(active_tool_kind, second_tool_kind, time), (active_tool_kind, second_tool_kind, time),
@ -6099,7 +6104,7 @@ impl FigureMgr {
pos.0.into(), pos.0.into(),
ori.into_vec4().into(), ori.into_vec4().into(),
vk, vk,
Arc::clone(vol), Arc::clone(&vol),
tick, tick,
&slow_jobs, &slow_jobs,
terrain, terrain,

View File

@ -1,20 +1,17 @@
use crate::hud::CraftingTab; use crate::hud::CraftingTab;
use common::{ use common::terrain::{Block, BlockKind, SpriteKind};
states::utils::sprite_mount_points,
terrain::{Block, BlockKind, SpriteKind},
};
use common_base::span; use common_base::span;
use rand::prelude::*; use rand::prelude::*;
use rand_chacha::ChaCha8Rng; use rand_chacha::ChaCha8Rng;
use vek::*; use vek::*;
#[derive(Clone, Debug)] #[derive(Clone, Copy, Debug)]
pub enum Interaction { pub enum Interaction {
/// This covers mining, unlocking, and regular collectable things (e.g. /// This covers mining, unlocking, and regular collectable things (e.g.
/// twigs). /// twigs).
Collect, Collect,
Craft(CraftingTab), Craft(CraftingTab),
Mount(Vec<Vec3<f32>>), Mount,
} }
pub enum FireplaceType { pub enum FireplaceType {
@ -175,21 +172,7 @@ impl BlocksOfInterest {
Some(SpriteKind::RepairBench) => { Some(SpriteKind::RepairBench) => {
interactables.push((pos, Interaction::Craft(CraftingTab::All))) interactables.push((pos, Interaction::Craft(CraftingTab::All)))
}, },
Some( _ if block.is_mountable() => interactables.push((pos, Interaction::Mount)),
sprite_kind @ SpriteKind::ChairSingle
| sprite_kind @ SpriteKind::ChairDouble,
) => interactables.push((
pos,
Interaction::Mount(
sprite_mount_points(
sprite_kind,
Vec3::zero(),
block.get_ori().unwrap_or(0),
)
.map(|(pos, _)| pos)
.collect(),
),
)),
_ => {}, _ => {},
}, },
} }

View File

@ -1,5 +1,5 @@
use ordered_float::OrderedFloat; use ordered_float::OrderedFloat;
use specs::{Join, WorldExt}; use specs::{Join, ReadStorage, WorldExt};
use vek::*; use vek::*;
use super::{ use super::{
@ -9,12 +9,12 @@ use super::{
use client::Client; use client::Client;
use common::{ use common::{
comp, comp,
comp::{tool::ToolKind, CharacterState}, comp::{ship::figuredata::VOXEL_COLLIDER_MANIFEST, tool::ToolKind, Collider},
consts::MAX_PICKUP_RANGE, consts::{MAX_PICKUP_RANGE, MAX_SPRITE_MOUNT_RANGE},
link::Is, link::Is,
mounting::Mount, mounting::{Mount, VolumePos, VolumeRider},
states::utils::SPRITE_MOUNT_RANGE_SQR,
terrain::{Block, TerrainGrid, UnlockKind}, terrain::{Block, TerrainGrid, UnlockKind},
uid::{Uid, UidAllocator},
util::find_dist::{Cube, Cylinder, FindDist}, util::find_dist::{Cube, Cylinder, FindDist},
vol::ReadVol, vol::ReadVol,
}; };
@ -33,12 +33,12 @@ pub enum BlockInteraction {
// TODO: mining blocks don't use the interaction key, so it might not be the best abstraction // TODO: mining blocks don't use the interaction key, so it might not be the best abstraction
// to have them here, will see how things turn out // to have them here, will see how things turn out
Mine(ToolKind), Mine(ToolKind),
Mount(Vec<Vec3<f32>>), Mount,
} }
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub enum Interactable { pub enum Interactable {
Block(Block, Vec3<i32>, BlockInteraction), Block(Block, VolumePos, BlockInteraction),
Entity(specs::Entity), Entity(specs::Entity),
} }
@ -52,28 +52,34 @@ impl Interactable {
fn from_block_pos( fn from_block_pos(
terrain: &TerrainGrid, terrain: &TerrainGrid,
pos: Vec3<i32>, uid_allocator: &UidAllocator,
colliders: &ReadStorage<Collider>,
volume_pos: VolumePos,
interaction: Interaction, interaction: Interaction,
) -> Option<Self> { ) -> Option<Self> {
let Ok(&block) = terrain.get(pos) else { return None }; let Some(block) = volume_pos.get_block(terrain, uid_allocator, colliders) else { return None };
let block_interaction = match interaction { let block_interaction = match interaction {
Interaction::Collect => { Interaction::Collect => {
// Check if this is an unlockable sprite // Check if this is an unlockable sprite
let unlock = block.get_sprite().and_then(|sprite| { let unlock = match volume_pos.kind {
let Some(chunk) = terrain.pos_chunk(pos) else { return None }; common::mounting::Volume::Terrain => block.get_sprite().and_then(|sprite| {
let sprite_chunk_pos = TerrainGrid::chunk_offs(pos); let Some(chunk) = terrain.pos_chunk(volume_pos.pos) else { return None };
let sprite_cfg = chunk.meta().sprite_cfg_at(sprite_chunk_pos); let sprite_chunk_pos = TerrainGrid::chunk_offs(volume_pos.pos);
let unlock_condition = sprite.unlock_condition(sprite_cfg.cloned()); let sprite_cfg = chunk.meta().sprite_cfg_at(sprite_chunk_pos);
// HACK: No other way to distinguish between things that should be unlockable let unlock_condition = sprite.unlock_condition(sprite_cfg.cloned());
// and regular sprites with the current unlock_condition method so we hack // HACK: No other way to distinguish between things that should be
// around that by saying that it is a regular collectible sprite if // unlockable and regular sprites with the current
// `unlock_condition` returns UnlockKind::Free and the cfg was `None`. // unlock_condition method so we hack around that by
if sprite_cfg.is_some() || !matches!(&unlock_condition, UnlockKind::Free) { // saying that it is a regular collectible sprite if
Some(unlock_condition) // `unlock_condition` returns UnlockKind::Free and the cfg was `None`.
} else { if sprite_cfg.is_some() || !matches!(&unlock_condition, UnlockKind::Free) {
None Some(unlock_condition)
} } else {
}); None
}
}),
common::mounting::Volume::Entity(_) => None,
};
if let Some(unlock) = unlock { if let Some(unlock) = unlock {
BlockInteraction::Unlock(unlock) BlockInteraction::Unlock(unlock)
@ -84,9 +90,9 @@ impl Interactable {
} }
}, },
Interaction::Craft(tab) => BlockInteraction::Craft(tab), Interaction::Craft(tab) => BlockInteraction::Craft(tab),
Interaction::Mount(points) => BlockInteraction::Mount(points), Interaction::Mount => BlockInteraction::Mount,
}; };
Some(Self::Block(block, pos, block_interaction)) Some(Self::Block(block, volume_pos, block_interaction))
} }
} }
@ -130,7 +136,11 @@ pub(super) fn select_interactable(
collect_target.and_then(|t| { collect_target.and_then(|t| {
if Some(t.distance) == nearest_dist { if Some(t.distance) == nearest_dist {
terrain.get(t.position_int()).ok().map(|&b| { terrain.get(t.position_int()).ok().map(|&b| {
Interactable::Block(b, t.position_int(), BlockInteraction::Collect) Interactable::Block(
b,
VolumePos::terrain(t.position_int()),
BlockInteraction::Collect,
)
}) })
} else { } else {
None None
@ -149,7 +159,7 @@ pub(super) fn select_interactable(
if let Some(mine_tool) = b.mine_tool() && b.is_air() { if let Some(mine_tool) = b.mine_tool() && b.is_air() {
Some(Interactable::Block( Some(Interactable::Block(
b, b,
t.position_int(), VolumePos::terrain(t.position_int()),
BlockInteraction::Mine(mine_tool), BlockInteraction::Mine(mine_tool),
)) ))
} else { } else {
@ -232,6 +242,49 @@ pub(super) fn select_interactable(
}); });
let scene_terrain = scene.terrain(); let scene_terrain = scene.terrain();
let voxel_colliders_manifest = VOXEL_COLLIDER_MANIFEST.read();
let volumes_data = (
&ecs.entities(),
&ecs.read_storage::<Uid>(),
&ecs.read_storage::<comp::Body>(),
&ecs.read_storage::<comp::Pos>(),
&ecs.read_storage::<comp::Ori>(),
&ecs.read_storage::<comp::Collider>(),
);
let volumes = volumes_data
.join()
.filter_map(|(entity, uid, body, pos, ori, collider)| {
let vol = collider.get_vol(&voxel_colliders_manifest)?;
let mat = Mat4::from(ori.to_quat()).translated_3d(pos.0)
* Mat4::translation_3d(vol.translation);
let p = mat.inverted().mul_point(player_pos);
let aabb = Aabb {
min: Vec3::zero(),
max: vol.volume().sz.as_(),
};
if aabb.contains_point(p) || aabb.distance_to_point(p) < search_dist {
scene
.figure_mgr()
.get_blocks_of_interest(entity, body, Some(collider))
.map(move |(blocks_of_interest, _)| {
blocks_of_interest.interactables.iter().map(
move |(block_offset, interaction)| {
let wpos = mat.mul_point(block_offset.as_() + 0.5);
(wpos, VolumePos::entity(*block_offset, *uid), interaction)
},
)
})
} else {
None
}
})
.flatten();
let is_volume_rider = ecs.read_storage::<Is<VolumeRider>>();
// Find closest interactable block // Find closest interactable block
// TODO: consider doing this one first? // TODO: consider doing this one first?
let closest_interactable_block_pos = Spiral2d::new() let closest_interactable_block_pos = Spiral2d::new()
@ -252,39 +305,36 @@ pub(super) fn select_interactable(
.interactables .interactables
.iter() .iter()
.map(move |(block_offset, interaction)| (chunk_pos + block_offset, interaction)) .map(move |(block_offset, interaction)| (chunk_pos + block_offset, interaction))
.filter_map(|(pos, interaction)| { .map(|(pos, interaction)| {
if matches!(player_char_state, Some(CharacterState::MountSprite(_))) && matches!(interaction, Interaction::Mount(_) | Interaction::Collect) { (pos.as_::<f32>() + 0.5, VolumePos::terrain(pos), interaction)
None
} else if let Interaction::Mount(mount_points) = interaction {
mount_points.iter()
.map(|p| (p + pos.as_()).distance_squared(player_pos))
.filter(|dist| *dist < SPRITE_MOUNT_RANGE_SQR)
.min_by_key(|dist| OrderedFloat(*dist))
.map(|dist| (pos, dist))
.zip(Some(interaction))
} else {
Some(((pos, (pos.as_() + 0.5).distance_squared(player_pos)), interaction))
}
}) })
}) })
.map(|((block_pos, dis), interaction)| ( .chain(volumes)
block_pos, .filter(|(wpos, volume_pos, interaction)| {
dis, match interaction {
interaction, Interaction::Mount => !is_volume_rider.contains(player_entity) && wpos.distance_squared(player_pos) < MAX_SPRITE_MOUNT_RANGE * MAX_SPRITE_MOUNT_RANGE &&
)) is_volume_rider.join().find(|is_volume_rider| is_volume_rider.pos == *volume_pos).is_none(),
.min_by_key(|(_, dist_sqr, _)| OrderedFloat(*dist_sqr)) _ => true,
.map(|(block_pos, _, interaction)| (block_pos, interaction)); }
})
.min_by_key(|(wpos, _, _)| OrderedFloat(wpos.distance_squared(player_pos)));
// Return the closest of the 2 closest // Return the closest of the 2 closest
closest_interactable_block_pos closest_interactable_block_pos
.filter(|(block_pos, _)| { .filter(|(wpos, _, _)| {
player_cylinder.min_distance(Cube { player_cylinder.min_distance(Cube {
min: block_pos.as_(), min: *wpos,
side_length: 1.0, side_length: 1.0,
}) < search_dist }) < search_dist
}) })
.and_then(|(block_pos, interaction)| { .and_then(|(_, block_pos, interaction)| {
Interactable::from_block_pos(&terrain, block_pos, interaction.clone()) Interactable::from_block_pos(
&terrain,
&ecs.read_resource::<UidAllocator>(),
&ecs.read_storage(),
block_pos,
interaction.clone(),
)
}) })
.or_else(|| closest_interactable_entity.map(|(e, _)| Interactable::Entity(e))) .or_else(|| closest_interactable_entity.map(|(e, _)| Interactable::Entity(e)))
} }

View File

@ -25,7 +25,7 @@ use common::{
consts::MAX_MOUNT_RANGE, consts::MAX_MOUNT_RANGE,
event::UpdateCharacterMetadata, event::UpdateCharacterMetadata,
link::Is, link::Is,
mounting::Mount, mounting::{Mount, VolumePos},
outcome::Outcome, outcome::Outcome,
recipe, recipe,
terrain::{Block, BlockKind}, terrain::{Block, BlockKind},
@ -350,7 +350,8 @@ impl SessionState {
match inv_event { match inv_event {
InventoryUpdateEvent::BlockCollectFailed { pos, reason } => { InventoryUpdateEvent::BlockCollectFailed { pos, reason } => {
self.hud.add_failed_block_pickup( self.hud.add_failed_block_pickup(
pos, // TODO: Support volumes.
VolumePos::terrain(pos),
HudCollectFailedReason::from_server_reason( HudCollectFailedReason::from_server_reason(
&reason, &reason,
client.state().ecs(), client.state().ecs(),
@ -887,14 +888,12 @@ impl PlayState for SessionState {
let mut client = self.client.borrow_mut(); let mut client = self.client.borrow_mut();
if client.is_riding() { if client.is_riding() {
client.unmount(); client.unmount();
} else if client.stand_if_mounted() {
} else { } else {
if let Some(interactable) = &self.interactable { if let Some(interactable) = &self.interactable {
match interactable { match interactable {
Interactable::Block(_, pos, interaction) => { Interactable::Block(_, pos, interaction) => {
if matches!(interaction, BlockInteraction::Mount(_)) if matches!(interaction, BlockInteraction::Mount) {
{ client.mount_volume(*pos)
client.mount_sprite(*pos)
} }
}, },
Interactable::Entity(entity) => client.mount(*entity), Interactable::Entity(entity) => client.mount(*entity),
@ -941,18 +940,25 @@ impl PlayState for SessionState {
BlockInteraction::Collect BlockInteraction::Collect
| BlockInteraction::Unlock(_) => { | BlockInteraction::Unlock(_) => {
if block.is_collectible() { if block.is_collectible() {
client.collect_block(*pos); match pos.kind {
common::mounting::Volume::Terrain => {
client.collect_block(pos.pos);
}
common::mounting::Volume::Entity(_) => todo!(),
}
} }
}, },
BlockInteraction::Craft(tab) => { BlockInteraction::Craft(tab) => {
self.hud.show.open_crafting_tab( self.hud.show.open_crafting_tab(
*tab, *tab,
block.get_sprite().map(|s| (*pos, s)), block
.get_sprite()
.map(|s| (*pos, s)),
) )
}, },
BlockInteraction::Mount(_) => { BlockInteraction::Mount => {
if block.is_mountable() { if block.is_mountable() {
client.mount_sprite(*pos); client.mount_volume(*pos)
} }
}, },
BlockInteraction::Mine(_) => {}, BlockInteraction::Mine(_) => {},