Don't try to mount unmountable entities, clippy fixes and fmt

This commit is contained in:
Joshua Barretto 2022-01-16 17:06:35 +00:00
parent 504e2a38d5
commit 043016a433
24 changed files with 351 additions and 253 deletions

View File

@ -34,6 +34,8 @@ use common::{
},
event::{EventBus, LocalEvent},
grid::Grid,
link::Is,
mounting::Rider,
outcome::Outcome,
recipe::RecipeBook,
resources::{PlayerEntity, TimeOfDay},
@ -44,8 +46,6 @@ use common::{
trade::{PendingTrade, SitePrices, TradeAction, TradeId, TradeResult},
uid::{Uid, UidAllocator},
vol::RectVolSize,
mounting::Rider,
link::Is,
};
use common_base::{prof_span, span};
use common_net::{

View File

@ -1,9 +1,9 @@
use crate::sync;
use common::{
comp,
resources::Time,
mounting::{Mount, Rider},
link::Is,
mounting::{Mount, Rider},
resources::Time,
};
use serde::{Deserialize, Serialize};
use specs::WorldExt;
@ -215,7 +215,9 @@ impl sync::CompPacket for EcsCompPacket {
EcsCompPhantom::Vel(_) => sync::handle_interp_remove::<comp::Vel>(entity, world),
EcsCompPhantom::Ori(_) => sync::handle_interp_remove::<comp::Ori>(entity, world),
EcsCompPhantom::Shockwave(_) => sync::handle_remove::<comp::Shockwave>(entity, world),
EcsCompPhantom::BeamSegment(_) => sync::handle_remove::<comp::BeamSegment>(entity, world),
EcsCompPhantom::BeamSegment(_) => {
sync::handle_remove::<comp::BeamSegment>(entity, world)
},
EcsCompPhantom::Alignment(_) => sync::handle_remove::<comp::Alignment>(entity, world),
}
}

View File

@ -8,8 +8,8 @@ use crate::{
trade::{PendingTrade, ReducedInventory, SiteId, SitePrices, TradeId, TradeResult},
uid::Uid,
};
use serde::{Serialize, Deserialize};
use specs::{Component, Entity as EcsEntity, DerefFlaggedStorage};
use serde::{Deserialize, Serialize};
use specs::{Component, DerefFlaggedStorage, Entity as EcsEntity};
use specs_idvs::IdvStorage;
use std::{collections::VecDeque, fmt};
use strum::IntoEnumIterator;

View File

@ -73,8 +73,7 @@ pub use self::{
combo::Combo,
controller::{
Climb, ControlAction, ControlEvent, Controller, ControllerInputs, GroupManip, InputAttr,
InputKind, InventoryAction, InventoryEvent, InventoryManip,
UtteranceKind,
InputKind, InventoryAction, InventoryEvent, InventoryManip, UtteranceKind,
},
energy::Energy,
fluid_dynamics::Fluid,

View File

@ -48,8 +48,11 @@ pub mod figure;
#[cfg(not(target_arch = "wasm32"))]
pub mod generation;
#[cfg(not(target_arch = "wasm32"))] pub mod grid;
#[cfg(not(target_arch = "wasm32"))] pub mod link;
#[cfg(not(target_arch = "wasm32"))]
pub mod lottery;
#[cfg(not(target_arch = "wasm32"))]
pub mod mounting;
#[cfg(not(target_arch = "wasm32"))] pub mod npc;
#[cfg(not(target_arch = "wasm32"))]
pub mod outcome;
@ -80,10 +83,6 @@ pub mod uid;
#[cfg(not(target_arch = "wasm32"))] pub mod vol;
#[cfg(not(target_arch = "wasm32"))]
pub mod volumes;
#[cfg(not(target_arch = "wasm32"))]
pub mod link;
#[cfg(not(target_arch = "wasm32"))]
pub mod mounting;
#[cfg(not(target_arch = "wasm32"))]
pub use cached_spatial_grid::CachedSpatialGrid;

View File

@ -1,14 +1,13 @@
use serde::{Deserialize, Serialize};
use specs::{SystemData, Component, DerefFlaggedStorage};
use specs::{Component, DerefFlaggedStorage, SystemData};
use specs_idvs::IdvStorage;
use std::{
ops::Deref,
sync::Arc,
};
use std::{ops::Deref, sync::Arc};
pub trait Link: Sized + Send + Sync + 'static {
type Error;
type CreateData<'a>: SystemData<'a>;
fn create(this: &LinkHandle<Self>, data: Self::CreateData<'_>) -> Result<(), ()>;
fn create(this: &LinkHandle<Self>, data: Self::CreateData<'_>) -> Result<(), Self::Error>;
type PersistData<'a>: SystemData<'a>;
fn persist(this: &LinkHandle<Self>, data: Self::PersistData<'_>) -> bool;
@ -35,16 +34,22 @@ impl<R: Role> Is<R> {
}
impl<R: Role> Clone for Is<R> {
fn clone(&self) -> Self { Self { link: self.link.clone() } }
fn clone(&self) -> Self {
Self {
link: self.link.clone(),
}
}
}
impl<R: Role> Deref for Is<R> {
type Target = R::Link;
fn deref(&self) -> &Self::Target { &self.link }
}
impl<R: Role + 'static> Component for Is<R>
where R::Link: Send + Sync + 'static
where
R::Link: Send + Sync + 'static,
{
type Storage = DerefFlaggedStorage<Self, IdvStorage<Self>>;
}
@ -56,21 +61,24 @@ pub struct LinkHandle<L: Link> {
impl<L: Link> Clone for LinkHandle<L> {
fn clone(&self) -> Self {
Self { link: self.link.clone() }
Self {
link: Arc::clone(&self.link),
}
}
}
impl<L: Link> LinkHandle<L> {
pub fn from_link(link: L) -> Self {
Self { link: Arc::new(link) }
Self {
link: Arc::new(link),
}
}
pub fn make_role<R: Role<Link = L>>(&self) -> Is<R> {
Is { link: self.clone() }
}
pub fn make_role<R: Role<Link = L>>(&self) -> Is<R> { Is { link: self.clone() } }
}
impl<L: Link> Deref for LinkHandle<L> {
type Target = L;
fn deref(&self) -> &Self::Target { &self.link }
}

View File

@ -1,12 +1,11 @@
use crate::{
comp,
link::{Is, Link, Role, LinkHandle},
uid::{Uid, UidAllocator},
link::{Is, Link, LinkHandle, Role},
terrain::TerrainGrid,
uid::{Uid, UidAllocator},
};
use specs::{Entities, ReadStorage, Read, ReadExpect, WriteStorage};
use specs::saveload::MarkerAllocator;
use serde::{Deserialize, Serialize};
use specs::{saveload::MarkerAllocator, Entities, Read, ReadExpect, ReadStorage, WriteStorage};
use vek::*;
#[derive(Serialize, Deserialize, Debug)]
@ -29,35 +28,26 @@ pub struct Mounting {
pub rider: Uid,
}
pub enum MountingError {
NoSuchEntity,
NotMountable,
}
impl Link for Mounting {
type CreateData<'a> = (
Read<'a, UidAllocator>,
WriteStorage<'a, Is<Mount>>,
WriteStorage<'a, Is<Rider>>,
);
fn create(this: &LinkHandle<Self>, (uid_allocator, mut is_mounts, mut is_riders): Self::CreateData<'_>) -> Result<(), ()> {
let entity = |uid: Uid| uid_allocator
.retrieve_entity_internal(uid.into());
if this.mount == this.rider {
// Forbid self-mounting
Err(())
} 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();
// Ensure that neither mount or rider are already part of a mounting relationship
if can_mount_with(mount) && can_mount_with(rider) {
let _ = is_mounts.insert(mount, this.make_role());
let _ = is_riders.insert(rider, this.make_role());
Ok(())
} else {
Err(())
}
} else {
Err(())
}
}
type DeleteData<'a> = (
Read<'a, UidAllocator>,
WriteStorage<'a, Is<Mount>>,
WriteStorage<'a, Is<Rider>>,
WriteStorage<'a, comp::Pos>,
WriteStorage<'a, comp::ForceUpdate>,
ReadExpect<'a, TerrainGrid>,
);
type Error = MountingError;
type PersistData<'a> = (
Read<'a, UidAllocator>,
Entities<'a>,
@ -65,12 +55,44 @@ impl Link for Mounting {
ReadStorage<'a, Is<Mount>>,
ReadStorage<'a, Is<Rider>>,
);
fn persist(this: &LinkHandle<Self>, (uid_allocator, entities, healths, is_mounts, is_riders): Self::PersistData<'_>) -> bool {
let entity = |uid: Uid| uid_allocator
.retrieve_entity_internal(uid.into());
fn create(
this: &LinkHandle<Self>,
(uid_allocator, mut is_mounts, mut is_riders): Self::CreateData<'_>,
) -> Result<(), Self::Error> {
let entity = |uid: Uid| uid_allocator.retrieve_entity_internal(uid.into());
if this.mount == this.rider {
// 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();
// Ensure that neither mount or rider are already part of a mounting
// relationship
if can_mount_with(mount) && can_mount_with(rider) {
let _ = is_mounts.insert(mount, this.make_role());
let _ = is_riders.insert(rider, this.make_role());
Ok(())
} else {
Err(MountingError::NotMountable)
}
} else {
Err(MountingError::NoSuchEntity)
}
}
fn persist(
this: &LinkHandle<Self>,
(uid_allocator, entities, healths, is_mounts, is_riders): Self::PersistData<'_>,
) -> bool {
let entity = |uid: Uid| uid_allocator.retrieve_entity_internal(uid.into());
if let Some((mount, rider)) = entity(this.mount).zip(entity(this.rider)) {
let is_alive = |entity| entities.is_alive(entity) && healths.get(entity).map_or(true, |h| !h.is_dead);
let is_alive = |entity| {
entities.is_alive(entity) && healths.get(entity).map_or(true, |h| !h.is_dead)
};
// Ensure that both entities are alive and that they continue to be linked
is_alive(mount)
@ -82,17 +104,11 @@ impl Link for Mounting {
}
}
type DeleteData<'a> = (
Read<'a, UidAllocator>,
WriteStorage<'a, Is<Mount>>,
WriteStorage<'a, Is<Rider>>,
WriteStorage<'a, comp::Pos>,
WriteStorage<'a, comp::ForceUpdate>,
ReadExpect<'a, TerrainGrid>,
);
fn delete(this: &LinkHandle<Self>, (uid_allocator, mut is_mounts, mut is_riders, mut positions, mut force_update, terrain): Self::DeleteData<'_>) {
let entity = |uid: Uid| uid_allocator
.retrieve_entity_internal(uid.into());
fn delete(
this: &LinkHandle<Self>,
(uid_allocator, mut is_mounts, mut is_riders, mut positions, mut force_update, terrain): Self::DeleteData<'_>,
) {
let entity = |uid: Uid| uid_allocator.retrieve_entity_internal(uid.into());
let mount = entity(this.mount);
let rider = entity(this.rider);
@ -105,9 +121,15 @@ impl Link for Mounting {
let safe_pos = rider
.and_then(|rider| positions.get(rider).copied())
.filter(|rider_pos| terrain.is_space(rider_pos.0.map(|e| e.floor() as i32)))
.or_else(|| mount
.and_then(|mount| positions.get(mount).copied())
.filter(|mount_pos| terrain.is_space((mount_pos.0 + Vec3::unit_z() * 0.1).map(|e| e.floor() as i32))));
.or_else(|| {
mount
.and_then(|mount| positions.get(mount).copied())
.filter(|mount_pos| {
terrain.is_space(
(mount_pos.0 + Vec3::unit_z() * 0.1).map(|e| e.floor() as i32),
)
})
});
rider
.and_then(|rider| Some(rider).zip(positions.get_mut(rider)))
.map(|(rider, pos)| {
@ -115,7 +137,7 @@ impl Link for Mounting {
pos.0 = safe_pos
.map(|p| p.0.map(|e| e.floor()))
.unwrap_or_else(|| terrain.find_space(old_pos).map(|e| e as f32))
+ Vec3::new(0.5, 0.5, 0.0);
+ Vec3::new(0.5, 0.5, 0.0);
let _ = force_update.insert(rider, comp::ForceUpdate);
});
}

View File

@ -174,9 +174,11 @@ impl TerrainGrid {
(0..SEARCH_DIST * 2 + 1)
.map(|i| if i % 2 == 0 { i } else { -i } / 2)
.map(|z_diff| pos + Vec3::unit_z() * z_diff)
.find(|pos| self.get(pos - Vec3::unit_z())
.map_or(false, |b| b.is_filled())
&& self.is_space(*pos))
.find(|pos| {
self.get(pos - Vec3::unit_z())
.map_or(false, |b| b.is_filled())
&& self.is_space(*pos)
})
}
}

View File

@ -8,6 +8,8 @@ use common::{
calendar::Calendar,
comp,
event::{EventBus, LocalEvent, ServerEvent},
link::Is,
mounting::{Mount, Rider},
outcome::Outcome,
region::RegionMap,
resources::{
@ -19,8 +21,6 @@ use common::{
time::DayPeriod,
trade::Trades,
vol::{ReadVol, WriteVol},
link::Is,
mounting::{Mount, Rider},
};
use common_base::span;
use common_ecs::{PhysicsMetrics, SysMetrics};
@ -34,10 +34,7 @@ use specs::{
storage::{MaskedStorage as EcsMaskedStorage, Storage as EcsStorage},
Component, DispatcherBuilder, Entity as EcsEntity, WorldExt,
};
use std::{
ops::{Deref, DerefMut},
sync::Arc,
};
use std::sync::Arc;
use vek::*;
/// How much faster should an in-game day be compared to a real day?

View File

@ -11,6 +11,8 @@ use common::{
StateUpdate, Stats, Vel,
},
event::{EventBus, LocalEvent, ServerEvent},
link::Is,
mounting::Rider,
outcome::Outcome,
resources::{DeltaTime, Time},
states::{
@ -19,8 +21,6 @@ use common::{
},
terrain::TerrainGrid,
uid::Uid,
mounting::Rider,
link::Is,
};
use common_ecs::{Job, Origin, Phase, System};

View File

@ -1,14 +1,13 @@
use common::{
comp::{Body, Controller, Ori, Pos, Vel, ForceUpdate},
uid::UidAllocator,
mounting::{Mount, Rider},
comp::{Body, Controller, Ori, Pos, Vel},
link::Is,
uid::Uid,
mounting::Mount,
uid::UidAllocator,
};
use common_ecs::{Job, Origin, Phase, System};
use specs::{
saveload::{Marker, MarkerAllocator},
Entities, Join, Read, ReadStorage, WriteStorage, WriteExpect,
Entities, Join, Read, ReadStorage, WriteStorage,
};
use vek::*;
@ -20,14 +19,11 @@ impl<'a> System<'a> for Sys {
type SystemData = (
Read<'a, UidAllocator>,
Entities<'a>,
ReadStorage<'a, Uid>,
WriteStorage<'a, Controller>,
ReadStorage<'a, Is<Rider>>,
ReadStorage<'a, Is<Mount>>,
WriteStorage<'a, Pos>,
WriteStorage<'a, Vel>,
WriteStorage<'a, Ori>,
ReadStorage<'a, ForceUpdate>,
ReadStorage<'a, Body>,
);
@ -40,14 +36,11 @@ impl<'a> System<'a> for Sys {
(
uid_allocator,
entities,
uids,
mut controllers,
is_riders,
is_mounts,
mut positions,
mut velocities,
mut orientations,
force_updates,
bodies,
): Self::SystemData,
) {
@ -71,8 +64,7 @@ impl<'a> System<'a> for Sys {
let mounter_body = bodies.get(rider);
let mounting_offset = body.map_or(Vec3::unit_z(), Body::mount_offset)
+ mounter_body.map_or(Vec3::zero(), Body::rider_offset);
let _ = positions
.insert(rider, Pos(pos.0 + ori.to_quat() * mounting_offset));
let _ = positions.insert(rider, Pos(pos.0 + ori.to_quat() * mounting_offset));
let _ = orientations.insert(rider, ori);
let _ = velocities.insert(rider, vel);
}

View File

@ -2,11 +2,13 @@ use common::{
comp::{
body::ship::figuredata::{VoxelCollider, VOXEL_COLLIDER_MANIFEST},
fluid_dynamics::{Fluid, LiquidKind, Wings},
Body, CharacterState, Collider, Density, Mass, Ori, PhysicsState, Pos,
PosVelOriDefer, PreviousPhysCache, Projectile, Scale, Stats, Sticky, Vel,
Body, CharacterState, Collider, Density, Mass, Ori, PhysicsState, Pos, PosVelOriDefer,
PreviousPhysCache, Projectile, Scale, Stats, Sticky, Vel,
},
consts::{AIR_DENSITY, FRIC_GROUND, GRAVITY},
event::{EventBus, ServerEvent},
link::Is,
mounting::Rider,
outcome::Outcome,
resources::DeltaTime,
states,
@ -14,8 +16,6 @@ use common::{
uid::Uid,
util::{Projection, SpatialGrid},
vol::{BaseVol, ReadVol},
mounting::Rider,
link::Is,
};
use common_base::{prof_span, span};
use common_ecs::{Job, Origin, ParMode, Phase, PhysicsMetrics, System};
@ -1851,7 +1851,8 @@ fn resolve_e2e_collision(
//
// This allows using e2e pushback to gain speed by jumping out of a roll
// while in the middle of a collider, this is an intentional combat mechanic.
let forced_movement = matches!(char_state_maybe, Some(cs) if cs.is_forced_movement()) || is_riding;
let forced_movement =
matches!(char_state_maybe, Some(cs) if cs.is_forced_movement()) || is_riding;
// Don't apply repulsive force to projectiles,
// or if we're colliding with a terrain-like entity,
@ -1873,7 +1874,14 @@ fn resolve_e2e_collision(
let diff = diff.normalized();
*vel_delta += Vec3::from(diff) * force * step_delta * vel.0.xy().try_normalized().map_or(1.0, |dir| diff.dot(-dir).max(0.025));
*vel_delta += Vec3::from(diff)
* force
* step_delta
* vel
.0
.xy()
.try_normalized()
.map_or(1.0, |dir| diff.dot(-dir).max(0.025));
}
*collision_registered = true;

View File

@ -34,14 +34,14 @@ use common::{
effect::Effect,
event::{EventBus, ServerEvent},
generation::{EntityConfig, EntityInfo},
link::Is,
mounting::Rider,
npc::{self, get_npc_name},
resources::{BattleMode, PlayerPhysicsSettings, Time, TimeOfDay},
terrain::{Block, BlockKind, SpriteKind, TerrainChunkSize},
uid::{Uid, UidAllocator},
vol::{ReadVol, RectVolSize},
Damage, DamageKind, DamageSource, Explosion, LoadoutBuilder, RadiusEffect,
link::Is,
mounting::Rider,
};
use common_net::{
msg::{DisconnectReason, Notification, PlayerListUpdate, ServerGeneral},
@ -52,7 +52,9 @@ use core::{cmp::Ordering, convert::TryFrom, time::Duration};
use hashbrown::{HashMap, HashSet};
use humantime::Duration as HumanDuration;
use rand::Rng;
use specs::{storage::StorageEntry, Builder, Entity as EcsEntity, Join, WorldExt, saveload::MarkerAllocator};
use specs::{
saveload::MarkerAllocator, storage::StorageEntry, Builder, Entity as EcsEntity, Join, WorldExt,
};
use std::{str::FromStr, sync::Arc};
use vek::*;
use wiring::{Circuit, Wire, WiringAction, WiringActionEffect, WiringElement};
@ -203,24 +205,30 @@ fn position_mut<T>(
descriptor: &str,
f: impl for<'a> FnOnce(&'a mut comp::Pos) -> T,
) -> CmdResult<T> {
let entity = server.state
let entity = server
.state
.ecs()
.read_storage::<Is<Rider>>()
.get(entity)
.and_then(|is_rider| server.state
.ecs()
.read_resource::<UidAllocator>()
.retrieve_entity_internal(is_rider.mount.into()))
.and_then(|is_rider| {
server
.state
.ecs()
.read_resource::<UidAllocator>()
.retrieve_entity_internal(is_rider.mount.into())
})
.unwrap_or(entity);
let res = server.state
let res = server
.state
.ecs()
.write_storage::<comp::Pos>()
.get_mut(entity)
.map(f)
.ok_or_else(|| format!("Cannot get position for {:?}!", descriptor));
if res.is_ok() {
let _ = server.state
let _ = server
.state
.ecs()
.write_storage::<comp::ForceUpdate>()
.insert(entity, comp::ForceUpdate);

View File

@ -15,12 +15,12 @@ use common::{
Inventory, Pos, SkillGroupKind,
},
consts::{MAX_MOUNT_RANGE, SOUND_TRAVEL_DIST_PER_VOLUME},
link::Is,
mounting::{Mount, Mounting, Rider},
outcome::Outcome,
terrain::{Block, SpriteKind},
uid::Uid,
vol::ReadVol,
mounting::{Mount, Rider, Mounting},
link::Is,
};
use common_net::{msg::ServerGeneral, sync::WorldSyncExt};
@ -102,17 +102,8 @@ pub fn handle_npc_interaction(server: &mut Server, interactor: EcsEntity, npc_en
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();
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::<comp::Pos>();
@ -126,10 +117,13 @@ pub fn handle_mount(server: &mut Server, rider: EcsEntity, mount: EcsEntity) {
if let (Some(rider_uid), Some(mount_uid)) =
(uids.get(rider).copied(), uids.get(mount).copied())
{
let is_pet = match state.ecs().read_storage::<comp::Alignment>().get(mount) {
Some(comp::Alignment::Owned(owner)) if *owner == rider_uid => true,
_ => false,
};
let is_pet = matches!(
state
.ecs()
.read_storage::<comp::Alignment>()
.get(mount),
Some(comp::Alignment::Owned(owner)) if *owner == rider_uid,
);
if is_pet {
drop(uids);
@ -146,10 +140,7 @@ pub fn handle_mount(server: &mut Server, rider: EcsEntity, mount: EcsEntity) {
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<Rider>>().remove(rider);
}
/// FIXME: This code is dangerous and needs to be refactored. We can't just

View File

@ -1,6 +1,9 @@
use super::*;
use common::{
comp::inventory::{loadout_builder::{make_potion_bag, make_food_bag}, slot::ArmorSlot},
comp::inventory::{
loadout_builder::{make_food_bag, make_potion_bag},
slot::ArmorSlot,
},
resources::Time,
rtsim::{Memory, MemoryItem},
store::Id,
@ -142,9 +145,8 @@ impl Entity {
// give potions to traveler humanoids or return loadout as is otherwise
match (body, kind) {
(comp::Body::Humanoid(_), RtSimEntityKind::Random) => {
|l, _| l
.bag(ArmorSlot::Bag1, Some(make_potion_bag(100)))
(comp::Body::Humanoid(_), RtSimEntityKind::Random) => |l, _| {
l.bag(ArmorSlot::Bag1, Some(make_potion_bag(100)))
.bag(ArmorSlot::Bag2, Some(make_food_bag(100)))
},
(_, RtSimEntityKind::Merchant) => {

View File

@ -18,11 +18,11 @@ use common::{
Group, Inventory, Poise,
},
effect::Effect,
link::{Link, LinkHandle},
mounting::Mounting,
resources::{Time, TimeOfDay},
slowjob::SlowJobPool,
uid::{Uid, UidAllocator},
link::{Is, Link, Role, LinkHandle},
mounting::Mounting,
};
use common_net::{
msg::{CharacterInfo, PlayerListUpdate, PresenceKind, ServerGeneral},
@ -32,7 +32,7 @@ use common_state::State;
use rand::prelude::*;
use specs::{
saveload::MarkerAllocator, Builder, Entity as EcsEntity, EntityBuilder as EcsEntityBuilder,
Join, WorldExt, Component,
Join, WorldExt,
};
use std::time::Duration;
use tracing::{trace, warn};
@ -112,8 +112,9 @@ pub trait StateExt {
fn send_chat(&self, msg: comp::UnresolvedChatMsg);
fn notify_players(&self, msg: ServerGeneral);
fn notify_in_game_clients(&self, msg: ServerGeneral);
/// Create a new link between entities (see [`common::mounting`] for an example).
fn link<L: Link>(&mut self, link: L) -> Result<(), ()>;
/// Create a new link between entities (see [`common::mounting`] for an
/// example).
fn link<L: Link>(&mut self, link: L) -> Result<(), L::Error>;
/// Maintain active links between entities
fn maintain_links(&mut self);
/// Delete an entity, recording the deletion in [`DeletedEntities`]
@ -277,7 +278,7 @@ impl StateExt for State {
mountable: bool,
) -> EcsEntityBuilder {
let body = comp::Body::Ship(ship);
let mut builder = self
let builder = self
.ecs_mut()
.create_entity_synced()
.with(pos)
@ -831,13 +832,10 @@ impl StateExt for State {
}
}
fn link<L: Link>(&mut self, link: L) -> Result<(), ()> {
fn link<L: Link>(&mut self, link: L) -> Result<(), L::Error> {
let linker = LinkHandle::from_link(link);
L::create(
&linker,
self.ecs().system_data(),
)?;
L::create(&linker, self.ecs().system_data())?;
self.ecs_mut()
.entry::<Vec<LinkHandle<L>>>()
@ -850,11 +848,13 @@ impl StateExt for State {
fn maintain_links(&mut self) {
fn maintain_link<L: Link>(state: &State) {
if let Some(mut handles) = state.ecs().try_fetch_mut::<Vec<LinkHandle<L>>>() {
handles.retain(|link| if L::persist(link, state.ecs().system_data()) {
true
} else {
L::delete(link, state.ecs().system_data());
false
handles.retain(|link| {
if L::persist(link, state.ecs().system_data()) {
true
} else {
L::delete(link, state.ecs().system_data());
false
}
});
}
}

View File

@ -27,6 +27,8 @@ use common::{
consts::GRAVITY,
effect::{BuffEffect, Effect},
event::{Emitter, EventBus, ServerEvent},
link::Is,
mounting::Mount,
path::TraversalConfig,
resources::{DeltaTime, Time, TimeOfDay},
rtsim::{Memory, MemoryItem, RtSimEntity, RtSimEvent},
@ -37,8 +39,6 @@ use common::{
uid::{Uid, UidAllocator},
util::Dir,
vol::ReadVol,
mounting::Mount,
link::Is,
};
use common_base::prof_span;
use common_ecs::{Job, Origin, ParMode, Phase, System};
@ -1428,18 +1428,16 @@ impl<'a> AgentData<'a> {
/// Attempt to consume a healing item, and return whether any healing items
/// were queued. Callers should use this to implement a delay so that
/// the healing isn't interrupted. If `relaxed` is `true`, we allow eating food and prioritise healing.
/// the healing isn't interrupted. If `relaxed` is `true`, we allow eating
/// food and prioritise healing.
fn heal_self(&self, _agent: &mut Agent, controller: &mut Controller, relaxed: bool) -> bool {
let healing_value = |item: &Item| {
let mut value = 0.0;
if let ItemKind::Consumable {
kind,
effects,
..
} = &item.kind
{
if matches!(kind, ConsumableKind::Drink) || (relaxed && matches!(kind, ConsumableKind::Food)) {
if let ItemKind::Consumable { kind, effects, .. } = &item.kind {
if matches!(kind, ConsumableKind::Drink)
|| (relaxed && matches!(kind, ConsumableKind::Food))
{
for effect in effects.iter() {
use BuffKind::*;
match effect {
@ -1449,8 +1447,8 @@ impl<'a> AgentData<'a> {
Effect::Buff(BuffEffect { kind, data, .. })
if matches!(kind, Regeneration | Saturation | Potion) =>
{
value +=
data.strength * data.duration.map_or(0.0, |d| d.as_secs() as f32);
value += data.strength
* data.duration.map_or(0.0, |d| d.as_secs() as f32);
},
_ => {},
}
@ -1469,10 +1467,12 @@ impl<'a> AgentData<'a> {
})
.collect();
consumables.sort_by_key(|(_, item)| if relaxed {
-healing_value(item)
} else {
healing_value(item)
consumables.sort_by_key(|(_, item)| {
if relaxed {
-healing_value(item)
} else {
healing_value(item)
}
});
if let Some((id, _)) = consumables.last() {

View File

@ -7,14 +7,14 @@ use crate::{
use common::{
calendar::Calendar,
comp::{Collider, ForceUpdate, Inventory, InventoryUpdate, Last, Ori, Player, Pos, Vel},
link::Is,
mounting::Rider,
outcome::Outcome,
region::{Event as RegionEvent, RegionMap},
resources::{PlayerPhysicsSettings, TimeOfDay},
terrain::TerrainChunkSize,
uid::Uid,
vol::RectVolSize,
link::Is,
mounting::Rider,
};
use common_ecs::{Job, Origin, Phase, System};
use common_net::{msg::ServerGeneral, sync::CompSyncPackage};

View File

@ -2,13 +2,13 @@
use common::{
comp::{
item::{tool::AbilityMap, MaterialStatManifest},
ActiveAbilities, Auras, BeamSegment, Body, Buffs, CanBuild, CharacterState, Collider,
Combo, Density, Energy, Group, Health, Inventory, Item, LightEmitter, Mass,
Ori, Player, Poise, Pos, Scale, Shockwave, SkillSet, Stats, Sticky, Vel, Alignment,
ActiveAbilities, Alignment, Auras, BeamSegment, Body, Buffs, CanBuild, CharacterState,
Collider, Combo, Density, Energy, Group, Health, Inventory, Item, LightEmitter, Mass, Ori,
Player, Poise, Pos, Scale, Shockwave, SkillSet, Stats, Sticky, Vel,
},
uid::Uid,
mounting::{Mount, Rider},
link::Is,
mounting::{Mount, Rider},
uid::Uid,
};
use common_ecs::{Job, Origin, Phase, System};
use common_net::{

View File

@ -83,6 +83,8 @@ use common::{
BuffData, BuffKind, Item,
},
consts::MAX_PICKUP_RANGE,
link::Is,
mounting::Mount,
outcome::Outcome,
slowjob::SlowJobPool,
terrain::{SpriteKind, TerrainChunk},
@ -90,8 +92,6 @@ use common::{
uid::Uid,
util::{srgba_to_linear, Dir},
vol::RectRasterableVol,
mounting::Mount,
link::Is,
};
use common_base::{prof_span, span};
use common_net::{
@ -1668,32 +1668,33 @@ impl Hud {
let mut sct_bg_walker = self.ids.sct_bgs.walk();
let pulse = self.pulse;
let make_overitem = |item: &Item, pos, distance, properties, fonts, interaction_options| {
let text = if item.amount() > 1 {
format!("{} x {}", item.amount(), item.name())
} else {
item.name().to_string()
let make_overitem =
|item: &Item, pos, distance, properties, fonts, interaction_options| {
let text = if item.amount() > 1 {
format!("{} x {}", item.amount(), item.name())
} else {
item.name().to_string()
};
let quality = get_quality_col(item);
// Item
overitem::Overitem::new(
text.into(),
quality,
distance,
fonts,
i18n,
&global_state.settings.controls,
properties,
pulse,
&global_state.window.key_layout,
interaction_options,
)
.x_y(0.0, 100.0)
.position_ingame(pos)
};
let quality = get_quality_col(item);
// Item
overitem::Overitem::new(
text.into(),
quality,
distance,
fonts,
i18n,
&global_state.settings.controls,
properties,
pulse,
&global_state.window.key_layout,
interaction_options,
)
.x_y(0.0, 100.0)
.position_ingame(pos)
};
self.failed_block_pickups
.retain(|_, t| pulse - *t < overitem::PICKUP_FAILED_FADE_OUT_TIME);
self.failed_entity_pickups
@ -1788,7 +1789,22 @@ impl Hud {
let speech_bubbles = &self.speech_bubbles;
// Render overhead name tags and health bars
for (entity, pos, info, bubble, _, _, health, _, height_offset, hpfl, in_group, dist_sqr, alignment, is_mount) in (
for (
entity,
pos,
info,
bubble,
_,
_,
health,
_,
height_offset,
hpfl,
in_group,
dist_sqr,
alignment,
is_mount,
) in (
&entities,
&pos,
interpolated.maybe(),
@ -1932,16 +1948,23 @@ impl Hud {
&global_state.window.key_layout,
match alignment {
// TODO: Don't use `MAX_MOUNT_RANGE` here, add dedicated interaction range
Some(comp::Alignment::Npc) if dist_sqr < common::consts::MAX_MOUNT_RANGE.powi(2)
&& interactable.as_ref().and_then(|i| i.entity()) == Some(entity) =>
Some(comp::Alignment::Npc)
if dist_sqr < common::consts::MAX_MOUNT_RANGE.powi(2)
&& interactable.as_ref().and_then(|i| i.entity())
== Some(entity) =>
{
vec![
(GameInput::Interact, i18n.get("hud.talk").to_string()),
(GameInput::Trade, i18n.get("hud.trade").to_string()),
],
Some(comp::Alignment::Owned(owner)) if Some(*owner) == client.uid()
&& is_mount.is_none()
&& dist_sqr < common::consts::MAX_MOUNT_RANGE.powi(2) =>
vec![(GameInput::Mount, i18n.get("hud.mount").to_string())],
]
},
Some(comp::Alignment::Owned(owner))
if Some(*owner) == client.uid()
&& is_mount.is_none()
&& dist_sqr < common::consts::MAX_MOUNT_RANGE.powi(2) =>
{
vec![(GameInput::Mount, i18n.get("hud.mount").to_string())]
},
_ => Vec::new(),
},
)

View File

@ -9,15 +9,15 @@ use crate::{
settings::{ControlSettings, InterfaceSettings},
ui::{fonts::Fonts, Ingameable},
};
use keyboard_keynames::key_layout::KeyLayout;
use common::comp::{Buffs, Energy, Health, SpeechBubble, SpeechBubbleType};
use conrod_core::{
color,
position::Align,
widget::{self, Image, Rectangle, Text, RoundedRectangle},
widget::{self, Image, Rectangle, RoundedRectangle, Text},
widget_ids, Color, Colorable, Positionable, Sizeable, Widget, WidgetCommon,
};
use i18n::Localization;
use keyboard_keynames::key_layout::KeyLayout;
const MAX_BUBBLE_WIDTH: f64 = 250.0;
widget_ids! {
@ -101,6 +101,7 @@ pub struct Overhead<'a> {
}
impl<'a> Overhead<'a> {
#[allow(clippy::too_many_arguments)]
pub fn new(
info: Option<Info<'a>>,
bubble: Option<&'a SpeechBubble>,
@ -467,25 +468,30 @@ impl<'a> Widget for Overhead<'a> {
// Interaction hints
if !self.interaction_options.is_empty() {
let text = self.interaction_options
let text = self
.interaction_options
.iter()
.filter_map(|(input, action)| Some((self
.controls
.get_binding(*input)?, action)))
.map(|(input, action)| format!("{} {}", input.display_string(self.key_layout).as_str(), action))
.filter_map(|(input, action)| {
Some((self.controls.get_binding(*input)?, action))
})
.map(|(input, action)| {
format!(
"{} {}",
input.display_string(self.key_layout).as_str(),
action
)
})
.collect::<Vec<_>>()
.join("\n");
let scale = 30.0;
let btn_rect_size = scale * 0.8;
let btn_font_size = scale * 0.6;
let btn_rect_pos_y = 0.0;
let btn_text_pos_y = btn_rect_pos_y + ((btn_rect_size - btn_font_size) * 0.5);
let btn_radius = btn_rect_size / 5.0;
let btn_color = Color::Rgba(0.0, 0.0, 0.0, 0.8);
// RoundedRectangle::fill_with([btn_rect_size, btn_rect_size], btn_radius, btn_color)
// .x_y(0.0, btn_rect_pos_y)
// RoundedRectangle::fill_with([btn_rect_size, btn_rect_size], btn_radius,
// btn_color) .x_y(0.0, btn_rect_pos_y)
// .depth(self.distance_from_player_sqr + 2.0)
// .parent(id)
// .set(state.ids.btn_bg, ui);
@ -494,11 +500,20 @@ impl<'a> Widget for Overhead<'a> {
.font_size(btn_font_size as u32)
.color(TEXT_COLOR)
.parent(id)
.down_from(self.info.map_or(state.ids.name, |info| if info.health.map_or(false, should_show_healthbar) {
if info.energy.is_some() { state.ids.mana_bar } else { state.ids.health_bar }
} else {
state.ids.name
}), 12.0)
.down_from(
self.info.map_or(state.ids.name, |info| {
if info.health.map_or(false, should_show_healthbar) {
if info.energy.is_some() {
state.ids.mana_bar
} else {
state.ids.health_bar
}
} else {
state.ids.name
}
}),
12.0,
)
.align_middle_x_of(state.ids.name)
.depth(1.0);
@ -506,12 +521,16 @@ impl<'a> Widget for Overhead<'a> {
hints_text.set(state.ids.interaction_hints, ui);
RoundedRectangle::fill_with([w + btn_radius * 2.0, h + btn_radius * 2.0], btn_radius, btn_color)
.depth(2.0)
.middle_of(state.ids.interaction_hints)
.align_middle_y_of(state.ids.interaction_hints)
.parent(id)
.set(state.ids.interaction_hints_bg, ui);
RoundedRectangle::fill_with(
[w + btn_radius * 2.0, h + btn_radius * 2.0],
btn_radius,
btn_color,
)
.depth(2.0)
.middle_of(state.ids.interaction_hints)
.align_middle_y_of(state.ids.interaction_hints)
.parent(id)
.set(state.ids.interaction_hints_bg, ui);
}
}
// Speech bubble

View File

@ -6,7 +6,7 @@ use crate::{
use conrod_core::{
color,
widget::{self, RoundedRectangle, Text},
widget_ids, Color, Colorable, Positionable, Widget, WidgetCommon, Sizeable,
widget_ids, Color, Colorable, Positionable, Sizeable, Widget, WidgetCommon,
};
use i18n::Localization;
use std::borrow::Cow;
@ -172,13 +172,24 @@ impl<'a> Widget for Overitem<'a> {
// Interaction hints
if !self.interaction_options.is_empty() {
let text = self.interaction_options
let text = self
.interaction_options
.iter()
.filter_map(|(input, action)| Some((self
.controls
.get_binding(*input)
.filter(|_| self.properties.active)?, action)))
.map(|(input, action)| format!("{} {}", input.display_string(self.key_layout).as_str(), action))
.filter_map(|(input, action)| {
Some((
self.controls
.get_binding(*input)
.filter(|_| self.properties.active)?,
action,
))
})
.map(|(input, action)| {
format!(
"{} {}",
input.display_string(self.key_layout).as_str(),
action
)
})
.collect::<Vec<_>>()
.join("\n");
@ -194,11 +205,15 @@ impl<'a> Widget for Overitem<'a> {
hints_text.set(state.ids.btn, ui);
RoundedRectangle::fill_with([w + btn_radius * 2.0, h + btn_radius * 2.0], btn_radius, btn_color)
.x_y(0.0, btn_rect_pos_y)
.depth(self.distance_from_player_sqr + 2.0)
.parent(id)
.set(state.ids.btn_bg, ui);
RoundedRectangle::fill_with(
[w + btn_radius * 2.0, h + btn_radius * 2.0],
btn_radius,
btn_color,
)
.x_y(0.0, btn_rect_pos_y)
.depth(self.distance_from_player_sqr + 2.0)
.parent(id)
.set(state.ids.btn_bg, ui);
}
if let Some(time) = self.properties.pickup_failed_pulse {
//should never exceed 1.0, but just in case

View File

@ -35,13 +35,13 @@ use common::{
Body, CharacterState, Collider, Controller, Health, Inventory, Item, Last, LightAnimation,
LightEmitter, Ori, PhysicsState, PoiseState, Pos, Scale, Vel,
},
link::Is,
mounting::Rider,
resources::{DeltaTime, Time},
states::{equipping, idle, utils::StageSection, wielding},
terrain::TerrainChunk,
uid::UidAllocator,
vol::RectRasterableVol,
link::Is,
mounting::Rider,
};
use common_base::span;
use common_state::State;
@ -489,8 +489,15 @@ impl FigureMgr {
.and_then(|body| self.states.get_mut(body, &entity))
.and_then(|state| {
// Calculate the correct lantern position
let pos = anim::vek::Vec3::from(interpolated.map(|i| i.pos).unwrap_or(pos.0).into_array());
Some(state.mount_world_pos + state.mount_transform.orientation * anim::vek::Vec3::from(state.lantern_offset?.into_array()) - pos)
let pos = anim::vek::Vec3::from(
interpolated.map(|i| i.pos).unwrap_or(pos.0).into_array(),
);
Some(
state.mount_world_pos
+ state.mount_transform.orientation
* anim::vek::Vec3::from(state.lantern_offset?.into_array())
- pos,
)
})
{
light_anim.offset = vek::Vec3::from(lantern_offset);
@ -739,7 +746,9 @@ impl FigureMgr {
let (in_frustum, lpindex) = if let Some(ref mut meta) = state {
let (in_frustum, lpindex) = BoundingSphere::new(pos.0.into_array(), radius)
.coherent_test_against_frustum(frustum, meta.lpindex);
let in_frustum = in_frustum || matches!(body, Body::Ship(_));
let in_frustum = in_frustum
|| matches!(body, Body::Ship(_))
|| pos.0.distance_squared(focus_pos) < 32.0f32.powi(2);
meta.visible = in_frustum;
meta.lpindex = lpindex;
if in_frustum {

View File

@ -22,13 +22,13 @@ use common::{
ChatMsg, ChatType, InputKind, InventoryUpdateEvent, Pos, Stats, UtteranceKind, Vel,
},
consts::MAX_MOUNT_RANGE,
link::Is,
mounting::Mount,
outcome::Outcome,
terrain::{Block, BlockKind},
trade::TradeResult,
util::{Dir, Plane},
vol::ReadVol,
link::Is,
mounting::{Mounting, Mount},
};
use common_base::{prof_span, span};
use common_net::{
@ -687,10 +687,12 @@ impl PlayState for SessionState {
&client.state().ecs().read_storage::<comp::Pos>(),
// TODO: More cleverly filter by things that can actually be mounted
!&client.state().ecs().read_storage::<Is<Mount>>(),
client.state().ecs().read_storage::<comp::Alignment>().maybe(),
)
.join()
.filter(|(entity, _, _)| *entity != client.entity())
.map(|(entity, pos, _)| {
.filter(|(entity, _, _, _)| *entity != client.entity())
.filter(|(_, _, _, alignment)| matches!(alignment, Some(comp::Alignment::Owned(owner)) if Some(*owner) == client.uid()))
.map(|(entity, pos, _, _)| {
(entity, player_pos.0.distance_squared(pos.0))
})
.filter(|(_, dist_sqr)| {