mirror of
https://gitlab.com/veloren/veloren.git
synced 2024-08-30 18:12:32 +00:00
intercat with sprites on ships
This commit is contained in:
parent
7fbe2cd5ec
commit
6674cce2cc
@ -19,6 +19,9 @@
|
||||
|
||||
custom_indices: {
|
||||
1: Air(ChairSingle, 4),
|
||||
2: Air(Helm, 0),
|
||||
3: Air(Door, 4),
|
||||
8: Air(Door, 0),
|
||||
},
|
||||
),
|
||||
AirBalloon: (
|
||||
|
BIN
assets/common/voxel/airship_human/structure.vox
(Stored with Git LFS)
BIN
assets/common/voxel/airship_human/structure.vox
(Stored with Git LFS)
Binary file not shown.
BIN
assets/voxygen/voxel/sprite/furniture/helm.vox
(Stored with Git LFS)
Normal file
BIN
assets/voxygen/voxel/sprite/furniture/helm.vox
(Stored with Git LFS)
Normal file
Binary file not shown.
@ -1924,6 +1924,17 @@ ChairDouble: Some((
|
||||
],
|
||||
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: Some((
|
||||
variations: [
|
||||
|
@ -37,7 +37,7 @@ use common::{
|
||||
grid::Grid,
|
||||
link::Is,
|
||||
lod,
|
||||
mounting::Rider,
|
||||
mounting::{Rider, VolumePos, VolumeRider},
|
||||
outcome::Outcome,
|
||||
recipe::{ComponentRecipeBook, RecipeBook, RepairRecipeBook},
|
||||
resources::{GameMode, PlayerEntity, Time, TimeOfDay},
|
||||
@ -1185,7 +1185,7 @@ impl Client {
|
||||
&mut self,
|
||||
recipe: &str,
|
||||
slots: Vec<(u32, InvSlotId)>,
|
||||
craft_sprite: Option<(Vec3<i32>, SpriteKind)>,
|
||||
craft_sprite: Option<(VolumePos, SpriteKind)>,
|
||||
amount: u32,
|
||||
) -> bool {
|
||||
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
|
||||
/// 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);
|
||||
if is_salvageable {
|
||||
self.send_msg(ClientGeneral::ControlEvent(ControlEvent::InventoryEvent(
|
||||
@ -1239,7 +1239,7 @@ impl Client {
|
||||
&mut self,
|
||||
primary_component: InvSlotId,
|
||||
secondary_component: InvSlotId,
|
||||
sprite_pos: Option<Vec3<i32>>,
|
||||
sprite_pos: Option<VolumePos>,
|
||||
) -> bool {
|
||||
let inventories = self.inventories();
|
||||
let inventory = inventories.get(self.entity());
|
||||
@ -1288,7 +1288,7 @@ impl Client {
|
||||
material: InvSlotId,
|
||||
modifier: Option<InvSlotId>,
|
||||
slots: Vec<(u32, InvSlotId)>,
|
||||
sprite_pos: Option<Vec3<i32>>,
|
||||
sprite_pos: Option<VolumePos>,
|
||||
) {
|
||||
self.send_msg(ClientGeneral::ControlEvent(ControlEvent::InventoryEvent(
|
||||
InventoryEvent::CraftRecipe {
|
||||
@ -1309,7 +1309,7 @@ impl Client {
|
||||
&mut self,
|
||||
item: Slot,
|
||||
slots: Vec<(u32, InvSlotId)>,
|
||||
sprite_pos: Vec3<i32>,
|
||||
sprite_pos: VolumePos,
|
||||
) -> bool {
|
||||
let is_repairable = {
|
||||
let inventories = self.inventories();
|
||||
@ -1448,6 +1448,12 @@ impl Client {
|
||||
.read_storage::<Is<Rider>>()
|
||||
.get(self.entity())
|
||||
.is_some()
|
||||
|| self
|
||||
.state
|
||||
.ecs()
|
||||
.read_storage::<Is<VolumeRider>>()
|
||||
.get(self.entity())
|
||||
.is_some()
|
||||
}
|
||||
|
||||
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 respawn(&mut self) {
|
||||
@ -1530,7 +1542,7 @@ impl Client {
|
||||
.ecs()
|
||||
.read_storage::<CharacterState>()
|
||||
.get(self.entity())
|
||||
.map(|cs| matches!(cs, CharacterState::Sit | CharacterState::MountSprite(_)));
|
||||
.map(|cs| matches!(cs, CharacterState::Sit));
|
||||
|
||||
match is_sitting {
|
||||
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) {
|
||||
let is_dancing = self
|
||||
.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) {
|
||||
let auxiliary_key = self
|
||||
.inventories()
|
||||
|
@ -34,6 +34,7 @@ macro_rules! synced_components {
|
||||
group: Group,
|
||||
is_mount: IsMount,
|
||||
is_rider: IsRider,
|
||||
is_volume_rider: IsVolumeRider,
|
||||
mass: Mass,
|
||||
density: Density,
|
||||
collider: Collider,
|
||||
@ -72,7 +73,7 @@ macro_rules! reexport_comps {
|
||||
mod inner {
|
||||
pub use common::comp::*;
|
||||
use common::link::Is;
|
||||
use common::mounting::{Mount, Rider};
|
||||
use common::mounting::{Mount, Rider, VolumeRider};
|
||||
|
||||
// We alias these because the identifier used for the
|
||||
// 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`.
|
||||
pub type IsMount = Is<Mount>;
|
||||
pub type IsRider = Is<Rider>;
|
||||
pub type IsVolumeRider = Is<VolumeRider>;
|
||||
}
|
||||
|
||||
// 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;
|
||||
}
|
||||
|
||||
impl NetSync for IsVolumeRider {
|
||||
const SYNC_FROM: SyncFrom = SyncFrom::AnyEntity;
|
||||
}
|
||||
|
||||
impl NetSync for Mass {
|
||||
const SYNC_FROM: SyncFrom = SyncFrom::AnyEntity;
|
||||
}
|
||||
|
@ -470,7 +470,6 @@ impl From<&CharacterState> for CharacterAbilityType {
|
||||
| CharacterState::SpriteSummon(_)
|
||||
| CharacterState::UseItem(_)
|
||||
| CharacterState::SpriteInteract(_)
|
||||
| CharacterState::MountSprite(_)
|
||||
| CharacterState::Skate(_)
|
||||
| CharacterState::Wallrun(_) => Self::Other,
|
||||
}
|
||||
|
@ -130,8 +130,6 @@ pub enum CharacterState {
|
||||
/// Handles logic for interacting with a sprite, e.g. using a chest or
|
||||
/// picking a plant
|
||||
SpriteInteract(sprite_interact::Data),
|
||||
// Mounted to a sprite
|
||||
MountSprite(mount_sprite::Data),
|
||||
/// Runs on the wall
|
||||
Wallrun(wallrun::Data),
|
||||
/// Ice skating or skiing
|
||||
@ -472,7 +470,6 @@ impl CharacterState {
|
||||
CharacterState::SpriteSummon(data) => data.behavior(j, output_events),
|
||||
CharacterState::UseItem(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::Music(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::UseItem(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::Music(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::UseItem(_) => None,
|
||||
CharacterState::SpriteInteract(_) => None,
|
||||
CharacterState::MountSprite(_) => None,
|
||||
CharacterState::FinisherMelee(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),
|
||||
@ -628,7 +623,6 @@ impl CharacterState {
|
||||
CharacterState::SpriteSummon(data) => Some(data.stage_section),
|
||||
CharacterState::UseItem(data) => Some(data.stage_section),
|
||||
CharacterState::SpriteInteract(data) => Some(data.stage_section),
|
||||
CharacterState::MountSprite(_) => None,
|
||||
CharacterState::FinisherMelee(data) => Some(data.stage_section),
|
||||
CharacterState::Music(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),
|
||||
..Default::default()
|
||||
}),
|
||||
CharacterState::MountSprite(_) => None,
|
||||
CharacterState::FinisherMelee(data) => Some(DurationsInfo {
|
||||
buildup: Some(data.static_data.buildup_duration),
|
||||
action: Some(data.static_data.swing_duration),
|
||||
@ -869,7 +862,6 @@ impl CharacterState {
|
||||
CharacterState::SpriteSummon(data) => Some(data.timer),
|
||||
CharacterState::UseItem(data) => Some(data.timer),
|
||||
CharacterState::SpriteInteract(data) => Some(data.timer),
|
||||
CharacterState::MountSprite(_) => None,
|
||||
CharacterState::FinisherMelee(data) => Some(data.timer),
|
||||
CharacterState::Music(data) => Some(data.timer),
|
||||
CharacterState::DiveMelee(data) => Some(data.timer),
|
||||
@ -889,7 +881,6 @@ impl CharacterState {
|
||||
CharacterState::GlideWield(_) => None,
|
||||
CharacterState::Stunned(_) => None,
|
||||
CharacterState::Sit => None,
|
||||
CharacterState::MountSprite(_) => None,
|
||||
CharacterState::Dance => None,
|
||||
CharacterState::BasicBlock(_) => None,
|
||||
CharacterState::Roll(_) => None,
|
||||
|
@ -9,6 +9,7 @@ use crate::{
|
||||
invite::{InviteKind, InviteResponse},
|
||||
BuffKind,
|
||||
},
|
||||
mounting::VolumePos,
|
||||
trade::{TradeAction, TradeId},
|
||||
uid::Uid,
|
||||
util::Dir,
|
||||
@ -28,7 +29,7 @@ pub enum InventoryEvent {
|
||||
Sort,
|
||||
CraftRecipe {
|
||||
craft_event: CraftEvent,
|
||||
craft_sprite: Option<Vec3<i32>>,
|
||||
craft_sprite: Option<VolumePos>,
|
||||
},
|
||||
}
|
||||
|
||||
@ -57,7 +58,7 @@ pub enum InventoryManip {
|
||||
Sort,
|
||||
CraftRecipe {
|
||||
craft_event: CraftEvent,
|
||||
craft_sprite: Option<Vec3<i32>>,
|
||||
craft_sprite: Option<VolumePos>,
|
||||
},
|
||||
SwapEquippedWeapons,
|
||||
}
|
||||
@ -143,6 +144,7 @@ pub enum ControlEvent {
|
||||
InviteResponse(InviteResponse),
|
||||
PerformTradeAction(TradeId, TradeAction),
|
||||
Mount(Uid),
|
||||
MountVolume(VolumePos),
|
||||
Unmount,
|
||||
InventoryEvent(InventoryEvent),
|
||||
GroupManip(GroupManip),
|
||||
@ -165,7 +167,6 @@ pub enum ControlAction {
|
||||
GlideWield,
|
||||
Unwield,
|
||||
Sit,
|
||||
MountSprite(Vec3<i32>),
|
||||
Dance,
|
||||
Sneak,
|
||||
Stand,
|
||||
|
@ -1,4 +1,4 @@
|
||||
use super::{Fluid, Ori};
|
||||
use super::{ship::figuredata::ShipSpec, Fluid, Ori};
|
||||
use crate::{
|
||||
comp::{body::ship::figuredata::VoxelCollider, inventory::item::armor::Friction},
|
||||
consts::WATER_DENSITY,
|
||||
@ -127,6 +127,14 @@ pub enum Collider {
|
||||
impl Collider {
|
||||
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 {
|
||||
match self {
|
||||
Collider::Voxel { .. } | Collider::Volume(_) => 1.0,
|
||||
|
@ -1,6 +1,7 @@
|
||||
// The limit on distance between the entity and a collectible (squared)
|
||||
pub const MAX_PICKUP_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 GRAVITY: f32 = 25.0;
|
||||
|
@ -8,6 +8,7 @@ use crate::{
|
||||
DisconnectReason, Ori, Pos,
|
||||
},
|
||||
lottery::LootSpec,
|
||||
mounting::VolumePos,
|
||||
outcome::Outcome,
|
||||
rtsim::{RtSimEntity, RtSimVehicle},
|
||||
terrain::SpriteKind,
|
||||
@ -195,6 +196,7 @@ pub enum ServerEvent {
|
||||
InitiateInvite(EcsEntity, Uid, InviteKind),
|
||||
ProcessTradeAction(EcsEntity, TradeId, TradeAction),
|
||||
Mount(EcsEntity, EcsEntity),
|
||||
MountVolume(EcsEntity, VolumePos),
|
||||
Unmount(EcsEntity),
|
||||
Possess(Uid, Uid),
|
||||
/// Inserts default components for a character when loading into the game
|
||||
|
@ -1,11 +1,18 @@
|
||||
use crate::{
|
||||
comp,
|
||||
comp::{self, ship::figuredata::VOXEL_COLLIDER_MANIFEST},
|
||||
link::{Is, Link, LinkHandle, Role},
|
||||
terrain::TerrainGrid,
|
||||
terrain::{Block, TerrainGrid},
|
||||
uid::{Uid, UidAllocator},
|
||||
vol::ReadVol,
|
||||
};
|
||||
use hashbrown::HashSet;
|
||||
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::*;
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
@ -39,6 +46,7 @@ impl Link for Mounting {
|
||||
Read<'a, UidAllocator>,
|
||||
WriteStorage<'a, Is<Mount>>,
|
||||
WriteStorage<'a, Is<Rider>>,
|
||||
ReadStorage<'a, Is<VolumeRider>>,
|
||||
);
|
||||
type DeleteData<'a> = (
|
||||
Read<'a, UidAllocator>,
|
||||
@ -59,7 +67,7 @@ impl Link for Mounting {
|
||||
|
||||
fn create(
|
||||
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> {
|
||||
let entity = |uid: Uid| uid_allocator.retrieve_entity_internal(uid.into());
|
||||
|
||||
@ -67,8 +75,11 @@ impl Link for Mounting {
|
||||
// Forbid self-mounting
|
||||
Err(MountingError::NotMountable)
|
||||
} else if let Some((mount, rider)) = entity(this.mount).zip(entity(this.rider)) {
|
||||
let can_mount_with =
|
||||
|entity| is_mounts.get(entity).is_none() && is_riders.get(entity).is_none();
|
||||
let can_mount_with = |entity| {
|
||||
!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
|
||||
// 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -9,7 +9,7 @@ use crate::{
|
||||
Stats, Vel,
|
||||
},
|
||||
link::Is,
|
||||
mounting::Rider,
|
||||
mounting::{Rider, VolumeRider},
|
||||
resources::{DeltaTime, Time},
|
||||
terrain::TerrainGrid,
|
||||
uid::Uid,
|
||||
@ -59,14 +59,6 @@ pub trait CharacterBehavior {
|
||||
fn talk(&self, data: &JoinData, _output_events: &mut OutputEvents) -> StateUpdate {
|
||||
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
|
||||
// may also need to be modified when changes are made here: ComboMelee2
|
||||
fn start_input(
|
||||
@ -105,7 +97,7 @@ pub trait CharacterBehavior {
|
||||
ControlAction::Sit => self.sit(data, output_events),
|
||||
ControlAction::Dance => self.dance(data, output_events),
|
||||
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)
|
||||
} else {
|
||||
self.stand(data, output_events)
|
||||
@ -119,7 +111,6 @@ pub trait CharacterBehavior {
|
||||
select_pos,
|
||||
} => self.start_input(data, input, target_entity, select_pos),
|
||||
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 terrain: &'a TerrainGrid,
|
||||
pub mount_data: Option<&'a Is<Rider>>,
|
||||
pub volume_mount_data: Option<&'a Is<VolumeRider>>,
|
||||
pub stance: Option<&'a Stance>,
|
||||
}
|
||||
|
||||
@ -185,6 +177,7 @@ pub struct JoinStruct<'a> {
|
||||
pub alignment: Option<&'a comp::Alignment>,
|
||||
pub terrain: &'a TerrainGrid,
|
||||
pub mount_data: Option<&'a Is<Rider>>,
|
||||
pub volume_mount_data: Option<&'a Is<VolumeRider>>,
|
||||
pub stance: Option<&'a Stance>,
|
||||
}
|
||||
|
||||
@ -228,6 +221,7 @@ impl<'a> JoinData<'a> {
|
||||
terrain: j.terrain,
|
||||
active_abilities: j.active_abilities,
|
||||
mount_data: j.mount_data,
|
||||
volume_mount_data: j.volume_mount_data,
|
||||
stance: j.stance,
|
||||
}
|
||||
}
|
||||
|
@ -7,7 +7,6 @@ use crate::{
|
||||
},
|
||||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use vek::Vec3;
|
||||
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize, Eq, Hash)]
|
||||
pub struct Data;
|
||||
@ -51,12 +50,6 @@ impl CharacterBehavior for Data {
|
||||
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 {
|
||||
let mut update = StateUpdate::from(data);
|
||||
// Try to Fall/Stand up/Move
|
||||
|
@ -12,7 +12,6 @@ use crate::{
|
||||
},
|
||||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use vek::Vec3;
|
||||
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)]
|
||||
pub struct Data {
|
||||
@ -111,12 +110,6 @@ impl CharacterBehavior for Data {
|
||||
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 {
|
||||
let mut update = StateUpdate::from(data);
|
||||
attempt_dance(data, &mut update);
|
||||
|
@ -8,7 +8,6 @@ use crate::{
|
||||
states::behavior::{CharacterBehavior, JoinData},
|
||||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use vek::Vec3;
|
||||
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize, Default)]
|
||||
pub struct Data {
|
||||
@ -87,12 +86,6 @@ impl CharacterBehavior for Data {
|
||||
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 {
|
||||
let mut update = StateUpdate::from(data);
|
||||
attempt_dance(data, &mut update);
|
||||
|
@ -22,7 +22,6 @@ pub mod glide_wield;
|
||||
pub mod idle;
|
||||
pub mod leap_melee;
|
||||
pub mod leap_shockwave;
|
||||
pub mod mount_sprite;
|
||||
pub mod music;
|
||||
pub mod rapid_melee;
|
||||
pub mod repeater_ranged;
|
||||
|
@ -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
|
||||
}
|
||||
}
|
@ -10,7 +10,6 @@ use crate::{
|
||||
},
|
||||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use vek::Vec3;
|
||||
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)]
|
||||
pub struct Data {
|
||||
@ -109,12 +108,6 @@ impl CharacterBehavior for Data {
|
||||
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 {
|
||||
let mut update = StateUpdate::from(data);
|
||||
// Try to Fall/Stand up/Move
|
||||
|
@ -7,7 +7,6 @@ use crate::{
|
||||
},
|
||||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use vek::Vec3;
|
||||
|
||||
const TURN_RATE: f32 = 40.0;
|
||||
|
||||
@ -48,13 +47,6 @@ impl CharacterBehavior for Data {
|
||||
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 {
|
||||
let mut update = StateUpdate::from(data);
|
||||
update.character = CharacterState::Idle(idle::Data::default());
|
||||
|
@ -21,13 +21,12 @@ use crate::{
|
||||
event::{LocalEvent, ServerEvent},
|
||||
outcome::Outcome,
|
||||
states::{behavior::JoinData, utils::CharacterState::Idle, *},
|
||||
terrain::{SpriteKind, TerrainGrid, UnlockKind},
|
||||
terrain::{TerrainGrid, UnlockKind},
|
||||
util::Dir,
|
||||
vol::ReadVol,
|
||||
};
|
||||
use core::hash::BuildHasherDefault;
|
||||
use fxhash::FxHasher64;
|
||||
use ordered_float::OrderedFloat;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::{
|
||||
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) {
|
||||
if data.physics.on_ground.is_some() && data.body.is_humanoid() {
|
||||
update.character = CharacterState::Dance;
|
||||
|
@ -11,7 +11,6 @@ use crate::{
|
||||
},
|
||||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use vek::Vec3;
|
||||
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub struct Data {
|
||||
@ -94,12 +93,6 @@ impl CharacterBehavior for Data {
|
||||
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 {
|
||||
let mut update = StateUpdate::from(data);
|
||||
attempt_dance(data, &mut update);
|
||||
|
@ -428,7 +428,16 @@ impl Block {
|
||||
}
|
||||
|
||||
#[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]
|
||||
pub fn is_bonkable(&self) -> bool {
|
||||
|
@ -242,6 +242,7 @@ make_case_elim!(
|
||||
KeyDoor = 0xD8,
|
||||
CommonLockedChest = 0xD9,
|
||||
RepairBench = 0xDA,
|
||||
Helm = 0xDB,
|
||||
}
|
||||
);
|
||||
|
||||
@ -475,47 +476,40 @@ impl SpriteKind {
|
||||
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]
|
||||
pub fn mount_offsets(&self) -> &'static [(Vec3<f32>, Vec3<f32>)] {
|
||||
const UNIT_Y: Vec3<f32> = Vec3 {
|
||||
x: 0.0,
|
||||
y: -1.0,
|
||||
z: 0.0,
|
||||
};
|
||||
pub fn mount_offset(&self) -> Option<(Vec3<f32>, Vec3<f32>)> {
|
||||
match self {
|
||||
SpriteKind::ChairSingle => &[(
|
||||
SpriteKind::ChairSingle | SpriteKind::ChairDouble => Some((
|
||||
Vec3 {
|
||||
x: 0.0,
|
||||
y: 0.0,
|
||||
z: 0.5,
|
||||
},
|
||||
UNIT_Y,
|
||||
)],
|
||||
SpriteKind::ChairDouble => &[
|
||||
(
|
||||
Vec3 {
|
||||
x: -0.5,
|
||||
y: 0.0,
|
||||
z: 0.6,
|
||||
},
|
||||
UNIT_Y,
|
||||
),
|
||||
(
|
||||
Vec3 {
|
||||
x: 0.5,
|
||||
y: -0.1,
|
||||
z: 0.6,
|
||||
},
|
||||
UNIT_Y,
|
||||
),
|
||||
],
|
||||
_ => &[],
|
||||
-Vec3::unit_y(),
|
||||
)),
|
||||
SpriteKind::Helm => Some((
|
||||
Vec3 {
|
||||
x: 0.0,
|
||||
y: -0.6,
|
||||
z: 0.2,
|
||||
},
|
||||
Vec3::unit_y(),
|
||||
)),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
#[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?
|
||||
#[inline]
|
||||
@ -646,6 +640,7 @@ impl SpriteKind {
|
||||
| SpriteKind::Grave
|
||||
| SpriteKind::Gravestone
|
||||
| SpriteKind::MagicalBarrier
|
||||
| SpriteKind::Helm,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -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)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
@ -9,7 +9,7 @@ use common::{
|
||||
comp,
|
||||
event::{EventBus, LocalEvent, ServerEvent},
|
||||
link::Is,
|
||||
mounting::{Mount, Rider},
|
||||
mounting::{Mount, Rider, VolumeRider, VolumeRiders},
|
||||
outcome::Outcome,
|
||||
region::RegionMap,
|
||||
resources::{
|
||||
@ -201,6 +201,7 @@ impl State {
|
||||
ecs.register::<comp::Scale>();
|
||||
ecs.register::<Is<Mount>>();
|
||||
ecs.register::<Is<Rider>>();
|
||||
ecs.register::<Is<VolumeRider>>();
|
||||
ecs.register::<comp::Mass>();
|
||||
ecs.register::<comp::Density>();
|
||||
ecs.register::<comp::Collider>();
|
||||
@ -260,6 +261,7 @@ impl State {
|
||||
ecs.register::<comp::invite::Invite>();
|
||||
ecs.register::<comp::invite::PendingInvites>();
|
||||
ecs.register::<comp::Beam>();
|
||||
ecs.register::<VolumeRiders>();
|
||||
|
||||
// Register synced resources used by the ECS.
|
||||
ecs.insert(TimeOfDay(0.0));
|
||||
@ -294,6 +296,7 @@ impl State {
|
||||
ecs.insert(PhysicsMetrics::default());
|
||||
ecs.insert(Trades::default());
|
||||
ecs.insert(PlayerPhysicsSettings::default());
|
||||
ecs.insert(VolumeRiders::default());
|
||||
|
||||
// Load plugins from asset directory
|
||||
#[cfg(feature = "plugins")]
|
||||
|
@ -14,7 +14,7 @@ use common::{
|
||||
},
|
||||
event::{EventBus, LocalEvent, ServerEvent},
|
||||
link::Is,
|
||||
mounting::Rider,
|
||||
mounting::{Rider, VolumeRider},
|
||||
outcome::Outcome,
|
||||
resources::{DeltaTime, Time},
|
||||
states::{
|
||||
@ -43,6 +43,7 @@ pub struct ReadData<'a> {
|
||||
beams: ReadStorage<'a, Beam>,
|
||||
uids: ReadStorage<'a, Uid>,
|
||||
is_riders: ReadStorage<'a, Is<Rider>>,
|
||||
is_volume_riders: ReadStorage<'a, Is<VolumeRider>>,
|
||||
stats: ReadStorage<'a, Stats>,
|
||||
skill_sets: ReadStorage<'a, SkillSet>,
|
||||
active_abilities: ReadStorage<'a, ActiveAbilities>,
|
||||
@ -207,6 +208,7 @@ impl<'a> System<'a> for Sys {
|
||||
alignment: read_data.alignments.get(entity),
|
||||
terrain: &read_data.terrain,
|
||||
mount_data: read_data.is_riders.get(entity),
|
||||
volume_mount_data: read_data.is_volume_riders.get(entity),
|
||||
stance: read_data.stances.get(entity),
|
||||
};
|
||||
|
||||
|
@ -2,16 +2,17 @@ use common::{
|
||||
comp::{
|
||||
ability::Stance,
|
||||
agent::{Sound, SoundKind},
|
||||
Body, BuffChange, ControlEvent, Controller, Pos, Scale,
|
||||
Body, BuffChange, Collider, ControlEvent, Controller, Pos, Scale,
|
||||
},
|
||||
event::{EventBus, ServerEvent},
|
||||
terrain::TerrainGrid,
|
||||
uid::UidAllocator,
|
||||
};
|
||||
use common_ecs::{Job, Origin, Phase, System};
|
||||
use specs::{
|
||||
saveload::{Marker, MarkerAllocator},
|
||||
shred::ResourceId,
|
||||
Entities, Join, Read, ReadStorage, SystemData, World, WriteStorage,
|
||||
Entities, Join, Read, ReadExpect, ReadStorage, SystemData, World, WriteStorage,
|
||||
};
|
||||
use vek::*;
|
||||
|
||||
@ -20,9 +21,11 @@ pub struct ReadData<'a> {
|
||||
entities: Entities<'a>,
|
||||
uid_allocator: Read<'a, UidAllocator>,
|
||||
server_bus: Read<'a, EventBus<ServerEvent>>,
|
||||
terrain_grid: ReadExpect<'a, TerrainGrid>,
|
||||
positions: ReadStorage<'a, Pos>,
|
||||
bodies: ReadStorage<'a, Body>,
|
||||
scales: ReadStorage<'a, Scale>,
|
||||
colliders: ReadStorage<'a, Collider>,
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
@ -53,6 +56,17 @@ impl<'a> System<'a> for Sys {
|
||||
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) => {
|
||||
server_emitter.emit(ServerEvent::Buff {
|
||||
entity,
|
||||
|
@ -1,14 +1,16 @@
|
||||
use common::{
|
||||
comp::{Body, ControlAction, Controller, InputKind, Ori, Pos, Scale, Vel},
|
||||
comp::{Body, Collider, ControlAction, Controller, InputKind, Ori, Pos, Scale, Vel},
|
||||
link::Is,
|
||||
mounting::Mount,
|
||||
mounting::{Mount, VolumeRider},
|
||||
terrain::TerrainGrid,
|
||||
uid::UidAllocator,
|
||||
};
|
||||
use common_ecs::{Job, Origin, Phase, System};
|
||||
use specs::{
|
||||
saveload::{Marker, MarkerAllocator},
|
||||
Entities, Join, Read, ReadStorage, WriteStorage,
|
||||
Entities, Join, Read, ReadExpect, ReadStorage, WriteStorage,
|
||||
};
|
||||
use tracing::error;
|
||||
use vek::*;
|
||||
|
||||
/// This system is responsible for controlling mounts
|
||||
@ -17,14 +19,17 @@ pub struct Sys;
|
||||
impl<'a> System<'a> for Sys {
|
||||
type SystemData = (
|
||||
Read<'a, UidAllocator>,
|
||||
ReadExpect<'a, TerrainGrid>,
|
||||
Entities<'a>,
|
||||
WriteStorage<'a, Controller>,
|
||||
ReadStorage<'a, Is<Mount>>,
|
||||
ReadStorage<'a, Is<VolumeRider>>,
|
||||
WriteStorage<'a, Pos>,
|
||||
WriteStorage<'a, Vel>,
|
||||
WriteStorage<'a, Ori>,
|
||||
ReadStorage<'a, Body>,
|
||||
ReadStorage<'a, Scale>,
|
||||
ReadStorage<'a, Collider>,
|
||||
);
|
||||
|
||||
const NAME: &'static str = "mount";
|
||||
@ -35,14 +40,17 @@ impl<'a> System<'a> for Sys {
|
||||
_job: &mut Job<Self>,
|
||||
(
|
||||
uid_allocator,
|
||||
terrain,
|
||||
entities,
|
||||
mut controllers,
|
||||
is_mounts,
|
||||
is_volume_riders,
|
||||
mut positions,
|
||||
mut velocities,
|
||||
mut orientations,
|
||||
bodies,
|
||||
scales,
|
||||
colliders,
|
||||
): Self::SystemData,
|
||||
) {
|
||||
// For each mount...
|
||||
@ -84,5 +92,87 @@ impl<'a> System<'a> for Sys {
|
||||
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 => {},
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -9,7 +9,7 @@ use common::{
|
||||
consts::{AIR_DENSITY, FRIC_GROUND, GRAVITY},
|
||||
event::{EventBus, ServerEvent},
|
||||
link::Is,
|
||||
mounting::Rider,
|
||||
mounting::{Rider, VolumeRider},
|
||||
outcome::Outcome,
|
||||
resources::DeltaTime,
|
||||
states,
|
||||
@ -122,6 +122,7 @@ pub struct PhysicsRead<'a> {
|
||||
masses: ReadStorage<'a, Mass>,
|
||||
colliders: ReadStorage<'a, Collider>,
|
||||
is_ridings: ReadStorage<'a, Is<Rider>>,
|
||||
is_volume_ridings: ReadStorage<'a, Is<VolumeRider>>,
|
||||
projectiles: ReadStorage<'a, Projectile>,
|
||||
char_states: ReadStorage<'a, CharacterState>,
|
||||
bodies: ReadStorage<'a, Body>,
|
||||
@ -335,6 +336,7 @@ impl<'a> PhysicsData<'a> {
|
||||
&read.masses,
|
||||
&read.colliders,
|
||||
read.is_ridings.maybe(),
|
||||
read.is_volume_ridings.maybe(),
|
||||
read.stickies.maybe(),
|
||||
read.immovables.maybe(),
|
||||
&mut write.physics_states,
|
||||
@ -359,6 +361,7 @@ impl<'a> PhysicsData<'a> {
|
||||
mass,
|
||||
collider,
|
||||
is_riding,
|
||||
is_volume_riding,
|
||||
sticky,
|
||||
immovable,
|
||||
physics,
|
||||
@ -366,8 +369,7 @@ impl<'a> PhysicsData<'a> {
|
||||
char_state_maybe,
|
||||
)| {
|
||||
let is_sticky = sticky.is_some();
|
||||
let is_immovable = immovable.is_some()
|
||||
|| matches!(char_state_maybe, Some(CharacterState::MountSprite(_)));
|
||||
let is_immovable = immovable.is_some();
|
||||
let is_mid_air = physics.on_surface().is_none();
|
||||
let mut entity_entity_collision_checks = 0;
|
||||
let mut entity_entity_collisions = 0;
|
||||
@ -492,7 +494,9 @@ impl<'a> PhysicsData<'a> {
|
||||
mass: *mass_other,
|
||||
},
|
||||
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()
|
||||
{
|
||||
let vol = match collider {
|
||||
Collider::Voxel { id } => voxel_colliders_manifest.colliders.get(id),
|
||||
Collider::Volume(vol) => Some(&**vol),
|
||||
_ => None,
|
||||
};
|
||||
let vol = collider.get_vol(&voxel_colliders_manifest);
|
||||
|
||||
if let Some(vol) = vol {
|
||||
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.previous_phys_cache.mask(),
|
||||
!&read.is_ridings,
|
||||
!&read.is_volume_ridings,
|
||||
)
|
||||
.join()
|
||||
.map(|t| (t.0, *t.2, *t.3, *t.4))
|
||||
@ -620,9 +621,9 @@ impl<'a> PhysicsData<'a> {
|
||||
&read.densities,
|
||||
read.scales.maybe(),
|
||||
!&read.is_ridings,
|
||||
!&read.is_volume_ridings,
|
||||
)
|
||||
.par_join()
|
||||
.filter(|tuple| !matches!(tuple.4, Some(CharacterState::MountSprite(_))))
|
||||
.for_each_init(
|
||||
|| {
|
||||
prof_span!(guard, "velocity update rayon job");
|
||||
@ -640,6 +641,7 @@ impl<'a> PhysicsData<'a> {
|
||||
density,
|
||||
scale,
|
||||
_,
|
||||
_,
|
||||
)| {
|
||||
let in_loaded_chunk = read
|
||||
.terrain
|
||||
@ -751,12 +753,10 @@ impl<'a> PhysicsData<'a> {
|
||||
&mut write.pos_vel_ori_defers,
|
||||
previous_phys_cache,
|
||||
!&read.is_ridings,
|
||||
!&read.is_volume_ridings,
|
||||
)
|
||||
.par_join()
|
||||
.filter(|tuple| {
|
||||
tuple.3.is_voxel() == terrain_like_entities
|
||||
&& !matches!(tuple.8, Some(CharacterState::MountSprite(_)))
|
||||
})
|
||||
.filter(|tuple| tuple.3.is_voxel() == terrain_like_entities)
|
||||
.map_init(
|
||||
|| {
|
||||
prof_span!(guard, "physics e<>t rayon job");
|
||||
@ -777,6 +777,7 @@ impl<'a> PhysicsData<'a> {
|
||||
pos_vel_ori_defer,
|
||||
previous_cache,
|
||||
_,
|
||||
_,
|
||||
)| {
|
||||
let mut land_on_ground = None;
|
||||
let mut outcomes = Vec::new();
|
||||
@ -1066,13 +1067,8 @@ impl<'a> PhysicsData<'a> {
|
||||
return;
|
||||
}
|
||||
|
||||
let voxel_collider = match collider_other {
|
||||
Collider::Voxel { id } => {
|
||||
voxel_colliders_manifest.colliders.get(id)
|
||||
},
|
||||
Collider::Volume(vol) => Some(&**vol),
|
||||
_ => None,
|
||||
};
|
||||
let voxel_collider =
|
||||
collider_other.get_vol(&voxel_colliders_manifest);
|
||||
|
||||
// use bounding cylinder regardless of our collider
|
||||
// TODO: extract point-terrain collision above to its own
|
||||
|
@ -145,7 +145,7 @@ impl<'a> System<'a> for Sys {
|
||||
{
|
||||
match character_state {
|
||||
// Sitting accelerates recharging energy the most
|
||||
CharacterState::Sit | CharacterState::MountSprite(_) => {
|
||||
CharacterState::Sit => {
|
||||
if energy.needs_regen() {
|
||||
energy.regen(SIT_ENERGY_REGEN_ACCEL, dt);
|
||||
}
|
||||
|
@ -10,7 +10,7 @@ use common::{
|
||||
SkillSet, Stance, Stats, Vel,
|
||||
},
|
||||
link::Is,
|
||||
mounting::{Mount, Rider},
|
||||
mounting::{Mount, Rider, VolumeRider},
|
||||
path::TraversalConfig,
|
||||
resources::{DeltaTime, Time, TimeOfDay},
|
||||
rtsim::{Actor, RtSimEntity},
|
||||
@ -236,6 +236,7 @@ pub struct ReadData<'a> {
|
||||
pub bodies: ReadStorage<'a, Body>,
|
||||
pub is_mounts: ReadStorage<'a, Is<Mount>>,
|
||||
pub is_riders: ReadStorage<'a, Is<Rider>>,
|
||||
pub is_volume_riders: ReadStorage<'a, Is<VolumeRider>>,
|
||||
pub time_of_day: Read<'a, TimeOfDay>,
|
||||
pub light_emitter: ReadStorage<'a, LightEmitter>,
|
||||
#[cfg(feature = "worldgen")]
|
||||
|
@ -229,6 +229,7 @@ fn position_mut<T>(
|
||||
descriptor: &str,
|
||||
f: impl for<'a> FnOnce(&'a mut comp::Pos) -> T,
|
||||
) -> CmdResult<T> {
|
||||
// TODO: Handle volume mount
|
||||
let entity = server
|
||||
.state
|
||||
.ecs()
|
||||
|
@ -14,10 +14,10 @@ use common::{
|
||||
tool::{AbilityMap, ToolKind},
|
||||
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,
|
||||
link::Is,
|
||||
mounting::{Mount, Mounting, Rider},
|
||||
mounting::{Mounting, Rider, VolumeMounting, VolumePos, VolumeRider},
|
||||
outcome::Outcome,
|
||||
terrain::{Block, SpriteKind},
|
||||
uid::Uid,
|
||||
@ -103,53 +103,78 @@ pub fn handle_npc_interaction(
|
||||
pub fn handle_mount(server: &mut Server, rider: EcsEntity, mount: EcsEntity) {
|
||||
let state = server.state_mut();
|
||||
|
||||
if state.ecs().read_storage::<Is<Rider>>().get(rider).is_none() {
|
||||
let not_mounting_yet = state.ecs().read_storage::<Is<Mount>>().get(mount).is_none();
|
||||
let within_range = {
|
||||
let positions = state.ecs().read_storage::<Pos>();
|
||||
within_mounting_range(positions.get(rider), positions.get(mount))
|
||||
};
|
||||
|
||||
let within_range = || {
|
||||
let positions = state.ecs().read_storage::<Pos>();
|
||||
within_mounting_range(positions.get(rider), positions.get(mount))
|
||||
};
|
||||
let healths = state.ecs().read_storage::<comp::Health>();
|
||||
let alive = |e| healths.get(e).map_or(true, |h| !h.is_dead);
|
||||
|
||||
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
|
||||
if within_range {
|
||||
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()
|
||||
.get(mount)
|
||||
.map_or(false, |mount_body| {
|
||||
is_mountable(mount_body, state.ecs().read_storage().get(rider))
|
||||
});
|
||||
.read_storage::<comp::Alignment>()
|
||||
.get(mount),
|
||||
Some(comp::Alignment::Owned(owner)) if *owner == rider_uid,
|
||||
);
|
||||
|
||||
if is_pet && can_ride {
|
||||
drop(uids);
|
||||
drop(healths);
|
||||
let _ = state.link(Mounting {
|
||||
mount: mount_uid,
|
||||
rider: rider_uid,
|
||||
});
|
||||
}
|
||||
let can_ride = state
|
||||
.ecs()
|
||||
.read_storage()
|
||||
.get(mount)
|
||||
.map_or(false, |mount_body| {
|
||||
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) {
|
||||
let state = server.state_mut();
|
||||
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 {
|
||||
|
@ -21,7 +21,7 @@ use common::{
|
||||
trade::Trades,
|
||||
uid::Uid,
|
||||
util::find_dist::{self, FindDist},
|
||||
vol::ReadVol,
|
||||
vol::ReadVol, mounting::VolumePos,
|
||||
};
|
||||
use common_net::sync::WorldSyncExt;
|
||||
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 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
|
||||
.filter(|pos| {
|
||||
let entity_cylinder = get_cylinder(state, entity);
|
||||
let in_range = within_pickup_range(entity_cylinder, || {
|
||||
Some(find_dist::Cube {
|
||||
min: pos.as_(),
|
||||
side_length: 1.0,
|
||||
})
|
||||
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)))
|
||||
});
|
||||
if !in_range {
|
||||
debug!(
|
||||
?entity_cylinder,
|
||||
"Failed to craft recipe as not within range of required sprite, \
|
||||
sprite pos: {}",
|
||||
sprite pos: {:?}",
|
||||
pos
|
||||
);
|
||||
}
|
||||
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())
|
||||
};
|
||||
|
||||
|
@ -1,5 +1,7 @@
|
||||
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,
|
||||
};
|
||||
use common::event::{EventBus, ServerEvent, ServerEventDiscriminants};
|
||||
@ -136,6 +138,9 @@ impl Server {
|
||||
handle_process_trade_action(self, entity, trade_id, action);
|
||||
},
|
||||
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::Possess(possessor_uid, possesse_uid) => {
|
||||
handle_possess(self, possessor_uid, possesse_uid)
|
||||
|
@ -23,7 +23,7 @@ use common::{
|
||||
},
|
||||
effect::Effect,
|
||||
link::{Link, LinkHandle},
|
||||
mounting::Mounting,
|
||||
mounting::{Mounting, VolumeMounting},
|
||||
resources::{Secs, Time, TimeOfDay},
|
||||
rtsim::{Actor, RtSimEntity},
|
||||
slowjob::SlowJobPool,
|
||||
@ -1099,6 +1099,7 @@ impl StateExt for State {
|
||||
}
|
||||
|
||||
maintain_link::<Mounting>(self);
|
||||
maintain_link::<VolumeMounting>(self);
|
||||
}
|
||||
|
||||
fn delete_entity_recorded(
|
||||
|
@ -12,6 +12,7 @@ use common::{
|
||||
Controller, Health, InputKind, Scale,
|
||||
},
|
||||
event::{EventBus, ServerEvent},
|
||||
mounting::Volume,
|
||||
path::TraversalConfig,
|
||||
};
|
||||
use common_base::prof_span;
|
||||
@ -69,6 +70,7 @@ impl<'a> System<'a> for Sys {
|
||||
read_data.rtsim_entities.maybe(),
|
||||
!&read_data.is_mounts,
|
||||
read_data.is_riders.maybe(),
|
||||
read_data.is_volume_riders.maybe(),
|
||||
)
|
||||
.par_join()
|
||||
.for_each_init(
|
||||
@ -93,6 +95,7 @@ impl<'a> System<'a> for Sys {
|
||||
rtsim_entity,
|
||||
_,
|
||||
is_rider,
|
||||
is_volume_rider,
|
||||
)| {
|
||||
let mut event_emitter = event_bus.emitter();
|
||||
let mut rng = thread_rng();
|
||||
@ -104,6 +107,16 @@ impl<'a> System<'a> for Sys {
|
||||
.uid_allocator
|
||||
.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);
|
||||
|
||||
let moving_body = read_data.bodies.get(moving_entity);
|
||||
|
@ -8,7 +8,7 @@ use common::{
|
||||
},
|
||||
event::{EventBus, ServerEvent},
|
||||
link::Is,
|
||||
mounting::Rider,
|
||||
mounting::{Rider, VolumeRider},
|
||||
resources::{PlayerPhysicsSetting, PlayerPhysicsSettings},
|
||||
slowjob::SlowJobPool,
|
||||
terrain::TerrainGrid,
|
||||
@ -52,6 +52,7 @@ impl Sys {
|
||||
terrain: &ReadExpect<'_, TerrainGrid>,
|
||||
can_build: &ReadStorage<'_, CanBuild>,
|
||||
is_rider: &ReadStorage<'_, Is<Rider>>,
|
||||
is_volume_rider: &ReadStorage<'_, Is<VolumeRider>>,
|
||||
force_updates: &ReadStorage<'_, ForceUpdate>,
|
||||
skill_set: &mut Option<Cow<'_, SkillSet>>,
|
||||
healths: &ReadStorage<'_, Health>,
|
||||
@ -126,6 +127,7 @@ impl Sys {
|
||||
&& force_updates.get(entity).map_or(true, |force_update| force_update.counter() == force_counter)
|
||||
&& healths.get(entity).map_or(true, |h| !h.is_dead)
|
||||
&& is_rider.get(entity).is_none()
|
||||
&& is_volume_rider.get(entity).is_none()
|
||||
&& player_physics_setting
|
||||
.as_ref()
|
||||
.map_or(true, |s| s.client_authoritative())
|
||||
@ -322,6 +324,7 @@ impl<'a> System<'a> for Sys {
|
||||
ReadStorage<'a, CanBuild>,
|
||||
ReadStorage<'a, ForceUpdate>,
|
||||
ReadStorage<'a, Is<Rider>>,
|
||||
ReadStorage<'a, Is<VolumeRider>>,
|
||||
WriteStorage<'a, SkillSet>,
|
||||
ReadStorage<'a, Health>,
|
||||
Write<'a, BlockChange>,
|
||||
@ -353,6 +356,7 @@ impl<'a> System<'a> for Sys {
|
||||
can_build,
|
||||
force_updates,
|
||||
is_rider,
|
||||
is_volume_rider,
|
||||
mut skill_sets,
|
||||
healths,
|
||||
mut block_changes,
|
||||
@ -430,6 +434,7 @@ impl<'a> System<'a> for Sys {
|
||||
&terrain,
|
||||
&can_build,
|
||||
&is_rider,
|
||||
&is_volume_rider,
|
||||
&force_updates,
|
||||
&mut skill_set,
|
||||
&healths,
|
||||
|
@ -25,6 +25,7 @@ use common::{
|
||||
slot::{InvSlotId, Slot},
|
||||
Inventory,
|
||||
},
|
||||
mounting::VolumePos,
|
||||
recipe::{ComponentKey, Recipe, RecipeInput},
|
||||
terrain::SpriteKind,
|
||||
};
|
||||
@ -123,7 +124,7 @@ pub enum Event {
|
||||
pub struct CraftingShow {
|
||||
pub crafting_tab: CraftingTab,
|
||||
pub crafting_search_key: Option<String>,
|
||||
pub craft_sprite: Option<(Vec3<i32>, SpriteKind)>,
|
||||
pub craft_sprite: Option<(VolumePos, SpriteKind)>,
|
||||
pub salvage: bool,
|
||||
pub initialize_repair: bool,
|
||||
// TODO: Maybe try to do something that doesn't need to allocate?
|
||||
|
@ -107,7 +107,7 @@ use common::{
|
||||
},
|
||||
consts::MAX_PICKUP_RANGE,
|
||||
link::Is,
|
||||
mounting::Mount,
|
||||
mounting::{Mount, VolumePos},
|
||||
outcome::Outcome,
|
||||
resources::{Secs, Time},
|
||||
slowjob::SlowJobPool,
|
||||
@ -710,27 +710,27 @@ pub enum Event {
|
||||
|
||||
CraftRecipe {
|
||||
recipe_name: String,
|
||||
craft_sprite: Option<(Vec3<i32>, SpriteKind)>,
|
||||
craft_sprite: Option<(VolumePos, SpriteKind)>,
|
||||
amount: u32,
|
||||
},
|
||||
SalvageItem {
|
||||
slot: InvSlotId,
|
||||
salvage_pos: Vec3<i32>,
|
||||
salvage_pos: VolumePos,
|
||||
},
|
||||
CraftModularWeapon {
|
||||
primary_slot: InvSlotId,
|
||||
secondary_slot: InvSlotId,
|
||||
craft_sprite: Option<Vec3<i32>>,
|
||||
craft_sprite: Option<VolumePos>,
|
||||
},
|
||||
CraftModularWeaponComponent {
|
||||
toolkind: ToolKind,
|
||||
material: InvSlotId,
|
||||
modifier: Option<InvSlotId>,
|
||||
craft_sprite: Option<Vec3<i32>>,
|
||||
craft_sprite: Option<VolumePos>,
|
||||
},
|
||||
RepairItem {
|
||||
item: Slot,
|
||||
sprite_pos: Vec3<i32>,
|
||||
sprite_pos: VolumePos,
|
||||
},
|
||||
InviteMember(Uid),
|
||||
AcceptInvite,
|
||||
@ -995,7 +995,7 @@ impl Show {
|
||||
pub fn open_crafting_tab(
|
||||
&mut self,
|
||||
tab: CraftingTab,
|
||||
craft_sprite: Option<(Vec3<i32>, SpriteKind)>,
|
||||
craft_sprite: Option<(VolumePos, SpriteKind)>,
|
||||
) {
|
||||
self.selected_crafting_tab(tab);
|
||||
self.crafting(true);
|
||||
@ -1289,7 +1289,7 @@ pub struct Hud {
|
||||
item_imgs: ItemImgs,
|
||||
fonts: Fonts,
|
||||
rot_imgs: ImgsRot,
|
||||
failed_block_pickups: HashMap<Vec3<i32>, CollectFailedData>,
|
||||
failed_block_pickups: HashMap<VolumePos, CollectFailedData>,
|
||||
failed_entity_pickups: HashMap<EcsEntity, CollectFailedData>,
|
||||
new_loot_messages: VecDeque<LootMessage>,
|
||||
new_messages: VecDeque<comp::ChatMsg>,
|
||||
@ -2040,7 +2040,8 @@ impl Hud {
|
||||
}
|
||||
|
||||
// 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(
|
||||
&mut self.ids.overitems,
|
||||
&mut ui_widgets.widget_id_generator(),
|
||||
@ -2050,7 +2051,8 @@ impl Hud {
|
||||
active: true,
|
||||
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 interaction_text = || match interaction {
|
||||
@ -2107,10 +2109,9 @@ impl Hud {
|
||||
}
|
||||
}
|
||||
},
|
||||
BlockInteraction::Mount(_) => vec![(
|
||||
Some(GameInput::Interact),
|
||||
i18n.get_msg("hud-sit").to_string(),
|
||||
)],
|
||||
BlockInteraction::Mount => {
|
||||
vec![(Some(GameInput::Mount), i18n.get_msg("hud-sit").to_string())]
|
||||
},
|
||||
};
|
||||
|
||||
// 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
|
||||
.insert(pos, CollectFailedData::new(self.pulse, reason));
|
||||
}
|
||||
@ -4730,16 +4731,31 @@ impl Hud {
|
||||
.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.filter(|(pos, _)| {
|
||||
self.show.crafting
|
||||
&& if let Some(player_pos) = client.position() {
|
||||
pos.map(|e| e as f32 + 0.5).distance(player_pos) < MAX_PICKUP_RANGE
|
||||
} else {
|
||||
false
|
||||
}
|
||||
});
|
||||
self.show
|
||||
.crafting_fields
|
||||
.craft_sprite
|
||||
.filter(|(pos, sprite)| {
|
||||
self.show.crafting
|
||||
&& 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.
|
||||
if !self.show.ui {
|
||||
|
@ -43,7 +43,7 @@ use common::{
|
||||
Scale, SkillSet, Stance, Vel,
|
||||
},
|
||||
link::Is,
|
||||
mounting::Rider,
|
||||
mounting::{Rider, VolumeRider},
|
||||
resources::{DeltaTime, Time},
|
||||
states::{equipping, idle, utils::StageSection, wielding},
|
||||
terrain::{Block, TerrainChunk, TerrainGrid},
|
||||
@ -830,7 +830,7 @@ impl FigureMgr {
|
||||
inventory,
|
||||
item,
|
||||
light_emitter,
|
||||
(is_rider, collider, stance, skillset),
|
||||
(is_rider, is_volume_rider, collider, stance, skillset),
|
||||
),
|
||||
) in (
|
||||
&ecs.entities(),
|
||||
@ -850,6 +850,7 @@ impl FigureMgr {
|
||||
ecs.read_storage::<LightEmitter>().maybe(),
|
||||
(
|
||||
ecs.read_storage::<Is<Rider>>().maybe(),
|
||||
ecs.read_storage::<Is<VolumeRider>>().maybe(),
|
||||
ecs.read_storage::<Collider>().maybe(),
|
||||
ecs.read_storage::<Stance>().maybe(),
|
||||
ecs.read_storage::<SkillSet>().maybe(),
|
||||
@ -1029,11 +1030,15 @@ impl FigureMgr {
|
||||
|
||||
// If a mount exists, get its animated mounting transform and its position
|
||||
let mount_transform_pos = (|| -> Option<_> {
|
||||
let mount = is_rider?.mount;
|
||||
let mount = uid_allocator.retrieve_entity_internal(mount.into())?;
|
||||
let body = *bodies.get(mount)?;
|
||||
let meta = self.states.get_mut(&body, &mount)?;
|
||||
Some((meta.mount_transform, meta.mount_world_pos))
|
||||
if let Some(is_rider) = is_rider {
|
||||
let mount = is_rider.mount;
|
||||
let mount = uid_allocator.retrieve_entity_internal(mount.into())?;
|
||||
let body = *bodies.get(mount)?;
|
||||
let meta = self.states.get_mut(&body, &mount)?;
|
||||
Some((meta.mount_transform, meta.mount_world_pos))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})();
|
||||
|
||||
let body = *body;
|
||||
@ -1111,7 +1116,7 @@ impl FigureMgr {
|
||||
physics.on_ground.is_some(),
|
||||
rel_vel.magnitude_squared() > 0.01, // Moving
|
||||
physics.in_liquid().is_some(), // In water
|
||||
is_rider.is_some(),
|
||||
is_rider.is_some() || is_volume_rider.is_some(),
|
||||
physics.skating_active,
|
||||
) {
|
||||
// Standing or Skating
|
||||
@ -2059,7 +2064,7 @@ impl FigureMgr {
|
||||
skeleton_attr,
|
||||
)
|
||||
},
|
||||
CharacterState::Sit { .. } | CharacterState::MountSprite(_) => {
|
||||
CharacterState::Sit { .. } => {
|
||||
anim::character::SitAnimation::update_skeleton(
|
||||
&target_base,
|
||||
(active_tool_kind, second_tool_kind, time),
|
||||
@ -6099,7 +6104,7 @@ impl FigureMgr {
|
||||
pos.0.into(),
|
||||
ori.into_vec4().into(),
|
||||
vk,
|
||||
Arc::clone(vol),
|
||||
Arc::clone(&vol),
|
||||
tick,
|
||||
&slow_jobs,
|
||||
terrain,
|
||||
|
@ -1,20 +1,17 @@
|
||||
use crate::hud::CraftingTab;
|
||||
use common::{
|
||||
states::utils::sprite_mount_points,
|
||||
terrain::{Block, BlockKind, SpriteKind},
|
||||
};
|
||||
use common::terrain::{Block, BlockKind, SpriteKind};
|
||||
use common_base::span;
|
||||
use rand::prelude::*;
|
||||
use rand_chacha::ChaCha8Rng;
|
||||
use vek::*;
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub enum Interaction {
|
||||
/// This covers mining, unlocking, and regular collectable things (e.g.
|
||||
/// twigs).
|
||||
Collect,
|
||||
Craft(CraftingTab),
|
||||
Mount(Vec<Vec3<f32>>),
|
||||
Mount,
|
||||
}
|
||||
|
||||
pub enum FireplaceType {
|
||||
@ -175,21 +172,7 @@ impl BlocksOfInterest {
|
||||
Some(SpriteKind::RepairBench) => {
|
||||
interactables.push((pos, Interaction::Craft(CraftingTab::All)))
|
||||
},
|
||||
Some(
|
||||
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(),
|
||||
),
|
||||
)),
|
||||
_ if block.is_mountable() => interactables.push((pos, Interaction::Mount)),
|
||||
_ => {},
|
||||
},
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
use ordered_float::OrderedFloat;
|
||||
use specs::{Join, WorldExt};
|
||||
use specs::{Join, ReadStorage, WorldExt};
|
||||
use vek::*;
|
||||
|
||||
use super::{
|
||||
@ -9,12 +9,12 @@ use super::{
|
||||
use client::Client;
|
||||
use common::{
|
||||
comp,
|
||||
comp::{tool::ToolKind, CharacterState},
|
||||
consts::MAX_PICKUP_RANGE,
|
||||
comp::{ship::figuredata::VOXEL_COLLIDER_MANIFEST, tool::ToolKind, Collider},
|
||||
consts::{MAX_PICKUP_RANGE, MAX_SPRITE_MOUNT_RANGE},
|
||||
link::Is,
|
||||
mounting::Mount,
|
||||
states::utils::SPRITE_MOUNT_RANGE_SQR,
|
||||
mounting::{Mount, VolumePos, VolumeRider},
|
||||
terrain::{Block, TerrainGrid, UnlockKind},
|
||||
uid::{Uid, UidAllocator},
|
||||
util::find_dist::{Cube, Cylinder, FindDist},
|
||||
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
|
||||
// to have them here, will see how things turn out
|
||||
Mine(ToolKind),
|
||||
Mount(Vec<Vec3<f32>>),
|
||||
Mount,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum Interactable {
|
||||
Block(Block, Vec3<i32>, BlockInteraction),
|
||||
Block(Block, VolumePos, BlockInteraction),
|
||||
Entity(specs::Entity),
|
||||
}
|
||||
|
||||
@ -52,28 +52,34 @@ impl Interactable {
|
||||
|
||||
fn from_block_pos(
|
||||
terrain: &TerrainGrid,
|
||||
pos: Vec3<i32>,
|
||||
uid_allocator: &UidAllocator,
|
||||
colliders: &ReadStorage<Collider>,
|
||||
volume_pos: VolumePos,
|
||||
interaction: Interaction,
|
||||
) -> 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 {
|
||||
Interaction::Collect => {
|
||||
// Check if this is an unlockable sprite
|
||||
let unlock = block.get_sprite().and_then(|sprite| {
|
||||
let Some(chunk) = terrain.pos_chunk(pos) else { return None };
|
||||
let sprite_chunk_pos = TerrainGrid::chunk_offs(pos);
|
||||
let sprite_cfg = chunk.meta().sprite_cfg_at(sprite_chunk_pos);
|
||||
let unlock_condition = sprite.unlock_condition(sprite_cfg.cloned());
|
||||
// HACK: No other way to distinguish between things that should be unlockable
|
||||
// and regular sprites with the current unlock_condition method so we hack
|
||||
// around that by saying that it is a regular collectible sprite if
|
||||
// `unlock_condition` returns UnlockKind::Free and the cfg was `None`.
|
||||
if sprite_cfg.is_some() || !matches!(&unlock_condition, UnlockKind::Free) {
|
||||
Some(unlock_condition)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
});
|
||||
let unlock = match volume_pos.kind {
|
||||
common::mounting::Volume::Terrain => block.get_sprite().and_then(|sprite| {
|
||||
let Some(chunk) = terrain.pos_chunk(volume_pos.pos) else { return None };
|
||||
let sprite_chunk_pos = TerrainGrid::chunk_offs(volume_pos.pos);
|
||||
let sprite_cfg = chunk.meta().sprite_cfg_at(sprite_chunk_pos);
|
||||
let unlock_condition = sprite.unlock_condition(sprite_cfg.cloned());
|
||||
// HACK: No other way to distinguish between things that should be
|
||||
// unlockable and regular sprites with the current
|
||||
// unlock_condition method so we hack around that by
|
||||
// saying that it is a regular collectible sprite if
|
||||
// `unlock_condition` returns UnlockKind::Free and the cfg was `None`.
|
||||
if sprite_cfg.is_some() || !matches!(&unlock_condition, UnlockKind::Free) {
|
||||
Some(unlock_condition)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}),
|
||||
common::mounting::Volume::Entity(_) => None,
|
||||
};
|
||||
|
||||
if let Some(unlock) = unlock {
|
||||
BlockInteraction::Unlock(unlock)
|
||||
@ -84,9 +90,9 @@ impl Interactable {
|
||||
}
|
||||
},
|
||||
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| {
|
||||
if Some(t.distance) == nearest_dist {
|
||||
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 {
|
||||
None
|
||||
@ -149,7 +159,7 @@ pub(super) fn select_interactable(
|
||||
if let Some(mine_tool) = b.mine_tool() && b.is_air() {
|
||||
Some(Interactable::Block(
|
||||
b,
|
||||
t.position_int(),
|
||||
VolumePos::terrain(t.position_int()),
|
||||
BlockInteraction::Mine(mine_tool),
|
||||
))
|
||||
} else {
|
||||
@ -232,6 +242,49 @@ pub(super) fn select_interactable(
|
||||
});
|
||||
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
|
||||
// TODO: consider doing this one first?
|
||||
let closest_interactable_block_pos = Spiral2d::new()
|
||||
@ -252,39 +305,36 @@ pub(super) fn select_interactable(
|
||||
.interactables
|
||||
.iter()
|
||||
.map(move |(block_offset, interaction)| (chunk_pos + block_offset, interaction))
|
||||
.filter_map(|(pos, interaction)| {
|
||||
if matches!(player_char_state, Some(CharacterState::MountSprite(_))) && matches!(interaction, Interaction::Mount(_) | Interaction::Collect) {
|
||||
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(|(pos, interaction)| {
|
||||
(pos.as_::<f32>() + 0.5, VolumePos::terrain(pos), interaction)
|
||||
})
|
||||
})
|
||||
.map(|((block_pos, dis), interaction)| (
|
||||
block_pos,
|
||||
dis,
|
||||
interaction,
|
||||
))
|
||||
.min_by_key(|(_, dist_sqr, _)| OrderedFloat(*dist_sqr))
|
||||
.map(|(block_pos, _, interaction)| (block_pos, interaction));
|
||||
.chain(volumes)
|
||||
.filter(|(wpos, volume_pos, interaction)| {
|
||||
match 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(),
|
||||
_ => true,
|
||||
}
|
||||
})
|
||||
.min_by_key(|(wpos, _, _)| OrderedFloat(wpos.distance_squared(player_pos)));
|
||||
|
||||
// Return the closest of the 2 closest
|
||||
closest_interactable_block_pos
|
||||
.filter(|(block_pos, _)| {
|
||||
.filter(|(wpos, _, _)| {
|
||||
player_cylinder.min_distance(Cube {
|
||||
min: block_pos.as_(),
|
||||
min: *wpos,
|
||||
side_length: 1.0,
|
||||
}) < search_dist
|
||||
})
|
||||
.and_then(|(block_pos, interaction)| {
|
||||
Interactable::from_block_pos(&terrain, block_pos, interaction.clone())
|
||||
.and_then(|(_, block_pos, interaction)| {
|
||||
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)))
|
||||
}
|
||||
|
@ -25,7 +25,7 @@ use common::{
|
||||
consts::MAX_MOUNT_RANGE,
|
||||
event::UpdateCharacterMetadata,
|
||||
link::Is,
|
||||
mounting::Mount,
|
||||
mounting::{Mount, VolumePos},
|
||||
outcome::Outcome,
|
||||
recipe,
|
||||
terrain::{Block, BlockKind},
|
||||
@ -350,7 +350,8 @@ impl SessionState {
|
||||
match inv_event {
|
||||
InventoryUpdateEvent::BlockCollectFailed { pos, reason } => {
|
||||
self.hud.add_failed_block_pickup(
|
||||
pos,
|
||||
// TODO: Support volumes.
|
||||
VolumePos::terrain(pos),
|
||||
HudCollectFailedReason::from_server_reason(
|
||||
&reason,
|
||||
client.state().ecs(),
|
||||
@ -887,14 +888,12 @@ impl PlayState for SessionState {
|
||||
let mut client = self.client.borrow_mut();
|
||||
if client.is_riding() {
|
||||
client.unmount();
|
||||
} else if client.stand_if_mounted() {
|
||||
} else {
|
||||
if let Some(interactable) = &self.interactable {
|
||||
match interactable {
|
||||
Interactable::Block(_, pos, interaction) => {
|
||||
if matches!(interaction, BlockInteraction::Mount(_))
|
||||
{
|
||||
client.mount_sprite(*pos)
|
||||
if matches!(interaction, BlockInteraction::Mount) {
|
||||
client.mount_volume(*pos)
|
||||
}
|
||||
},
|
||||
Interactable::Entity(entity) => client.mount(*entity),
|
||||
@ -941,18 +940,25 @@ impl PlayState for SessionState {
|
||||
BlockInteraction::Collect
|
||||
| BlockInteraction::Unlock(_) => {
|
||||
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) => {
|
||||
self.hud.show.open_crafting_tab(
|
||||
*tab,
|
||||
block.get_sprite().map(|s| (*pos, s)),
|
||||
block
|
||||
.get_sprite()
|
||||
.map(|s| (*pos, s)),
|
||||
)
|
||||
},
|
||||
BlockInteraction::Mount(_) => {
|
||||
BlockInteraction::Mount => {
|
||||
if block.is_mountable() {
|
||||
client.mount_sprite(*pos);
|
||||
client.mount_volume(*pos)
|
||||
}
|
||||
},
|
||||
BlockInteraction::Mine(_) => {},
|
||||
|
Loading…
Reference in New Issue
Block a user