mirror of
https://gitlab.com/veloren/veloren.git
synced 2024-08-30 18:12:32 +00:00
Merge branch 'zesterer/mount-everest' into 'master'
Mount Everest See merge request veloren/veloren!3101
This commit is contained in:
commit
9c565dadc5
@ -36,6 +36,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
- Crushing damage now does poise damage to a target equal to the amount mitigated by armor
|
||||
- UI to select abilities and assign to hotbar
|
||||
- Position of abilities on hotbar is now persisted through the server
|
||||
- Interation hints now appear for sprites and entities
|
||||
- Players can now mount and ride pets
|
||||
|
||||
### Changed
|
||||
|
||||
|
@ -26,7 +26,7 @@
|
||||
"hud.tutorial_elements": r#"Crafting"#,
|
||||
|
||||
"hud.temp_quest_headline": r#"Greetings Traveller!"#,
|
||||
"hud.temp_quest_text": r#"To begin your journey you could start looking through this village and gather some supplies.
|
||||
"hud.temp_quest_text": r#"To begin your journey you could start looking through this village and gather some supplies.
|
||||
|
||||
You are welcome to take whatever you need on your journey!
|
||||
|
||||
@ -46,6 +46,14 @@ Whenever you feel ready, try to get even better equipment from the many challeng
|
||||
"hud.free_look_indicator": "Free look active. Press {key} to disable.",
|
||||
"hud.camera_clamp_indicator": "Camera vertical clamp active. Press {key} to disable.",
|
||||
"hud.auto_walk_indicator": "Auto walk/swim active",
|
||||
"hud.collect": "Collect",
|
||||
"hud.pick_up": "Pick up",
|
||||
"hud.open": "Open",
|
||||
"hud.use": "Use",
|
||||
"hud.mine": "Mine",
|
||||
"hud.talk": "Talk",
|
||||
"hud.trade": "Trade",
|
||||
"hud.mount": "Mount",
|
||||
},
|
||||
|
||||
|
||||
|
@ -34,6 +34,8 @@ use common::{
|
||||
},
|
||||
event::{EventBus, LocalEvent},
|
||||
grid::Grid,
|
||||
link::Is,
|
||||
mounting::Rider,
|
||||
outcome::Outcome,
|
||||
recipe::RecipeBook,
|
||||
resources::{PlayerEntity, TimeOfDay},
|
||||
@ -1152,10 +1154,10 @@ impl Client {
|
||||
)));
|
||||
}
|
||||
|
||||
pub fn is_mounted(&self) -> bool {
|
||||
pub fn is_riding(&self) -> bool {
|
||||
self.state
|
||||
.ecs()
|
||||
.read_storage::<comp::Mounting>()
|
||||
.read_storage::<Is<Rider>>()
|
||||
.get(self.entity())
|
||||
.is_some()
|
||||
}
|
||||
|
@ -1,5 +1,10 @@
|
||||
use crate::sync;
|
||||
use common::{comp, resources::Time};
|
||||
use common::{
|
||||
comp,
|
||||
link::Is,
|
||||
mounting::{Mount, Rider},
|
||||
resources::Time,
|
||||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use specs::WorldExt;
|
||||
use std::marker::PhantomData;
|
||||
@ -27,8 +32,8 @@ sum_type! {
|
||||
Item(comp::Item),
|
||||
Scale(comp::Scale),
|
||||
Group(comp::Group),
|
||||
MountState(comp::MountState),
|
||||
Mounting(comp::Mounting),
|
||||
IsMount(Is<Mount>),
|
||||
IsRider(Is<Rider>),
|
||||
Mass(comp::Mass),
|
||||
Density(comp::Density),
|
||||
Collider(comp::Collider),
|
||||
@ -39,6 +44,7 @@ sum_type! {
|
||||
Ori(comp::Ori),
|
||||
Shockwave(comp::Shockwave),
|
||||
BeamSegment(comp::BeamSegment),
|
||||
Alignment(comp::Alignment),
|
||||
}
|
||||
}
|
||||
// Automatically derive From<T> for EcsCompPhantom
|
||||
@ -63,8 +69,8 @@ sum_type! {
|
||||
Item(PhantomData<comp::Item>),
|
||||
Scale(PhantomData<comp::Scale>),
|
||||
Group(PhantomData<comp::Group>),
|
||||
MountState(PhantomData<comp::MountState>),
|
||||
Mounting(PhantomData<comp::Mounting>),
|
||||
IsMount(PhantomData<Is<Mount>>),
|
||||
IsRider(PhantomData<Is<Rider>>),
|
||||
Mass(PhantomData<comp::Mass>),
|
||||
Density(PhantomData<comp::Density>),
|
||||
Collider(PhantomData<comp::Collider>),
|
||||
@ -75,6 +81,7 @@ sum_type! {
|
||||
Ori(PhantomData<comp::Ori>),
|
||||
Shockwave(PhantomData<comp::Shockwave>),
|
||||
BeamSegment(PhantomData<comp::BeamSegment>),
|
||||
Alignment(PhantomData<comp::Alignment>),
|
||||
}
|
||||
}
|
||||
impl sync::CompPacket for EcsCompPacket {
|
||||
@ -104,8 +111,8 @@ impl sync::CompPacket for EcsCompPacket {
|
||||
EcsCompPacket::Item(comp) => sync::handle_insert(comp, entity, world),
|
||||
EcsCompPacket::Scale(comp) => sync::handle_insert(comp, entity, world),
|
||||
EcsCompPacket::Group(comp) => sync::handle_insert(comp, entity, world),
|
||||
EcsCompPacket::MountState(comp) => sync::handle_insert(comp, entity, world),
|
||||
EcsCompPacket::Mounting(comp) => sync::handle_insert(comp, entity, world),
|
||||
EcsCompPacket::IsMount(comp) => sync::handle_insert(comp, entity, world),
|
||||
EcsCompPacket::IsRider(comp) => sync::handle_insert(comp, entity, world),
|
||||
EcsCompPacket::Mass(comp) => sync::handle_insert(comp, entity, world),
|
||||
EcsCompPacket::Density(comp) => sync::handle_insert(comp, entity, world),
|
||||
EcsCompPacket::Collider(comp) => sync::handle_insert(comp, entity, world),
|
||||
@ -122,6 +129,7 @@ impl sync::CompPacket for EcsCompPacket {
|
||||
},
|
||||
EcsCompPacket::Shockwave(comp) => sync::handle_insert(comp, entity, world),
|
||||
EcsCompPacket::BeamSegment(comp) => sync::handle_insert(comp, entity, world),
|
||||
EcsCompPacket::Alignment(comp) => sync::handle_insert(comp, entity, world),
|
||||
}
|
||||
}
|
||||
|
||||
@ -149,8 +157,8 @@ impl sync::CompPacket for EcsCompPacket {
|
||||
EcsCompPacket::Item(comp) => sync::handle_modify(comp, entity, world),
|
||||
EcsCompPacket::Scale(comp) => sync::handle_modify(comp, entity, world),
|
||||
EcsCompPacket::Group(comp) => sync::handle_modify(comp, entity, world),
|
||||
EcsCompPacket::MountState(comp) => sync::handle_modify(comp, entity, world),
|
||||
EcsCompPacket::Mounting(comp) => sync::handle_modify(comp, entity, world),
|
||||
EcsCompPacket::IsMount(comp) => sync::handle_modify(comp, entity, world),
|
||||
EcsCompPacket::IsRider(comp) => sync::handle_modify(comp, entity, world),
|
||||
EcsCompPacket::Mass(comp) => sync::handle_modify(comp, entity, world),
|
||||
EcsCompPacket::Density(comp) => sync::handle_modify(comp, entity, world),
|
||||
EcsCompPacket::Collider(comp) => sync::handle_modify(comp, entity, world),
|
||||
@ -167,6 +175,7 @@ impl sync::CompPacket for EcsCompPacket {
|
||||
},
|
||||
EcsCompPacket::Shockwave(comp) => sync::handle_modify(comp, entity, world),
|
||||
EcsCompPacket::BeamSegment(comp) => sync::handle_modify(comp, entity, world),
|
||||
EcsCompPacket::Alignment(comp) => sync::handle_modify(comp, entity, world),
|
||||
}
|
||||
}
|
||||
|
||||
@ -193,8 +202,8 @@ impl sync::CompPacket for EcsCompPacket {
|
||||
EcsCompPhantom::Item(_) => sync::handle_remove::<comp::Item>(entity, world),
|
||||
EcsCompPhantom::Scale(_) => sync::handle_remove::<comp::Scale>(entity, world),
|
||||
EcsCompPhantom::Group(_) => sync::handle_remove::<comp::Group>(entity, world),
|
||||
EcsCompPhantom::MountState(_) => sync::handle_remove::<comp::MountState>(entity, world),
|
||||
EcsCompPhantom::Mounting(_) => sync::handle_remove::<comp::Mounting>(entity, world),
|
||||
EcsCompPhantom::IsMount(_) => sync::handle_remove::<Is<Mount>>(entity, world),
|
||||
EcsCompPhantom::IsRider(_) => sync::handle_remove::<Is<Rider>>(entity, world),
|
||||
EcsCompPhantom::Mass(_) => sync::handle_remove::<comp::Mass>(entity, world),
|
||||
EcsCompPhantom::Density(_) => sync::handle_remove::<comp::Density>(entity, world),
|
||||
EcsCompPhantom::Collider(_) => sync::handle_remove::<comp::Collider>(entity, world),
|
||||
@ -206,7 +215,10 @@ 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::Ori>(entity, world),
|
||||
EcsCompPhantom::BeamSegment(_) => {
|
||||
sync::handle_remove::<comp::BeamSegment>(entity, world)
|
||||
},
|
||||
EcsCompPhantom::Alignment(_) => sync::handle_remove::<comp::Alignment>(entity, world),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -8,8 +8,8 @@ use crate::{
|
||||
trade::{PendingTrade, ReducedInventory, SiteId, SitePrices, TradeId, TradeResult},
|
||||
uid::Uid,
|
||||
};
|
||||
use serde::Deserialize;
|
||||
use specs::{Component, Entity as EcsEntity};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use specs::{Component, DerefFlaggedStorage, Entity as EcsEntity};
|
||||
use specs_idvs::IdvStorage;
|
||||
use std::{collections::VecDeque, fmt};
|
||||
use strum::IntoEnumIterator;
|
||||
@ -21,7 +21,7 @@ use super::dialogue::Subject;
|
||||
pub const DEFAULT_INTERACTION_TIME: f32 = 3.0;
|
||||
pub const TRADE_INTERACTION_TIME: f32 = 300.0;
|
||||
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Deserialize)]
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)]
|
||||
pub enum Alignment {
|
||||
/// Wild animals and gentle giants
|
||||
Wild,
|
||||
@ -79,7 +79,7 @@ impl Alignment {
|
||||
}
|
||||
|
||||
impl Component for Alignment {
|
||||
type Storage = IdvStorage<Self>;
|
||||
type Storage = DerefFlaggedStorage<Self, IdvStorage<Self>>;
|
||||
}
|
||||
|
||||
bitflags::bitflags! {
|
||||
|
@ -730,45 +730,45 @@ impl Body {
|
||||
)
|
||||
}
|
||||
|
||||
/// Component of the mounting offset specific to the mountee
|
||||
pub fn mountee_offset(&self) -> Vec3<f32> {
|
||||
/// Component of the mounting offset specific to the mount
|
||||
pub fn mount_offset(&self) -> Vec3<f32> {
|
||||
match self {
|
||||
Body::QuadrupedMedium(quadruped_medium) => {
|
||||
match (quadruped_medium.species, quadruped_medium.body_type) {
|
||||
(quadruped_medium::Species::Grolgar, _) => [0.5, 0.5, 1.8],
|
||||
(quadruped_medium::Species::Saber, _) => [0.3, 0.3, 1.3],
|
||||
(quadruped_medium::Species::Tiger, _) => [0.2, 0.2, 1.4],
|
||||
(quadruped_medium::Species::Tuskram, _) => [-0.5, -0.5, 1.5],
|
||||
(quadruped_medium::Species::Lion, _) => [0.3, 0.3, 1.5],
|
||||
(quadruped_medium::Species::Tarasque, _) => [0.6, 0.6, 2.0],
|
||||
(quadruped_medium::Species::Wolf, _) => [0.5, 0.5, 1.3],
|
||||
(quadruped_medium::Species::Frostfang, _) => [0.5, 0.5, 1.2],
|
||||
(quadruped_medium::Species::Mouflon, _) => [0.3, 0.3, 1.2],
|
||||
(quadruped_medium::Species::Grolgar, _) => [0.0, 0.5, 1.8],
|
||||
(quadruped_medium::Species::Saber, _) => [0.0, 0.3, 1.3],
|
||||
(quadruped_medium::Species::Tiger, _) => [0.0, 0.2, 1.4],
|
||||
(quadruped_medium::Species::Tuskram, _) => [0.0, -0.5, 1.5],
|
||||
(quadruped_medium::Species::Lion, _) => [0.0, 0.3, 1.5],
|
||||
(quadruped_medium::Species::Tarasque, _) => [0.0, 0.6, 2.0],
|
||||
(quadruped_medium::Species::Wolf, _) => [0.0, 0.5, 1.3],
|
||||
(quadruped_medium::Species::Frostfang, _) => [0.0, 0.5, 1.2],
|
||||
(quadruped_medium::Species::Mouflon, _) => [0.0, 0.3, 1.2],
|
||||
(quadruped_medium::Species::Catoblepas, _) => [0.0, 0.0, 2.0],
|
||||
(quadruped_medium::Species::Bonerattler, _) => [0.5, 0.5, 1.2],
|
||||
(quadruped_medium::Species::Deer, _) => [0.2, 0.2, 1.3],
|
||||
(quadruped_medium::Species::Bonerattler, _) => [0.0, 0.5, 1.2],
|
||||
(quadruped_medium::Species::Deer, _) => [0.0, 0.2, 1.3],
|
||||
(quadruped_medium::Species::Hirdrasil, _) => [0.0, 0.0, 1.4],
|
||||
(quadruped_medium::Species::Roshwalr, _) => [0.5, 0.5, 1.8],
|
||||
(quadruped_medium::Species::Donkey, _) => [0.5, 0.5, 1.5],
|
||||
(quadruped_medium::Species::Camel, _) => [-0.1, -0.1, 2.8],
|
||||
(quadruped_medium::Species::Zebra, _) => [0.5, 0.5, 1.8],
|
||||
(quadruped_medium::Species::Antelope, _) => [0.3, 0.3, 1.4],
|
||||
(quadruped_medium::Species::Kelpie, _) => [0.5, 0.5, 1.9],
|
||||
(quadruped_medium::Species::Roshwalr, _) => [0.0, 0.5, 1.8],
|
||||
(quadruped_medium::Species::Donkey, _) => [0.0, 0.5, 1.5],
|
||||
(quadruped_medium::Species::Camel, _) => [0.0, -0.1, 2.8],
|
||||
(quadruped_medium::Species::Zebra, _) => [0.0, 0.5, 1.8],
|
||||
(quadruped_medium::Species::Antelope, _) => [0.0, 0.3, 1.4],
|
||||
(quadruped_medium::Species::Kelpie, _) => [0.0, 0.5, 1.9],
|
||||
(quadruped_medium::Species::Horse, _) => [0.0, 0.0, 2.0],
|
||||
(quadruped_medium::Species::Barghest, _) => [0.5, 0.5, 2.2],
|
||||
(quadruped_medium::Species::Barghest, _) => [0.0, 0.5, 2.2],
|
||||
(quadruped_medium::Species::Cattle, quadruped_medium::BodyType::Male) => {
|
||||
[0.5, 0.5, 2.6]
|
||||
[0.0, 0.5, 2.6]
|
||||
},
|
||||
(quadruped_medium::Species::Cattle, quadruped_medium::BodyType::Female) => {
|
||||
[0.7, 0.7, 2.2]
|
||||
[0.0, 0.7, 2.2]
|
||||
},
|
||||
(quadruped_medium::Species::Darkhound, _) => [0.5, 0.5, 1.4],
|
||||
(quadruped_medium::Species::Highland, _) => [0.5, 0.5, 2.3],
|
||||
(quadruped_medium::Species::Darkhound, _) => [0.0, 0.5, 1.4],
|
||||
(quadruped_medium::Species::Highland, _) => [0.0, 0.5, 2.3],
|
||||
(quadruped_medium::Species::Yak, _) => [0.0, 0.0, 3.0],
|
||||
(quadruped_medium::Species::Panda, _) => [-0.2, -0.2, 1.4],
|
||||
(quadruped_medium::Species::Bear, _) => [-0.4, -0.4, 2.5],
|
||||
(quadruped_medium::Species::Dreadhorn, _) => [0.2, 0.2, 3.5],
|
||||
(quadruped_medium::Species::Moose, _) => [-0.6, -0.6, 2.1],
|
||||
(quadruped_medium::Species::Panda, _) => [0.0, -0.2, 1.4],
|
||||
(quadruped_medium::Species::Bear, _) => [0.0, -0.4, 2.5],
|
||||
(quadruped_medium::Species::Dreadhorn, _) => [0.0, 0.2, 3.5],
|
||||
(quadruped_medium::Species::Moose, _) => [0.0, -0.6, 2.1],
|
||||
(quadruped_medium::Species::Snowleopard, _) => [-0.5, -0.5, 1.4],
|
||||
(quadruped_medium::Species::Mammoth, _) => [0.0, 4.9, 7.2],
|
||||
(quadruped_medium::Species::Ngoubou, _) => [0.0, 0.3, 2.0],
|
||||
@ -789,8 +789,8 @@ impl Body {
|
||||
.into()
|
||||
}
|
||||
|
||||
/// Component of the mounting offset specific to the mounter
|
||||
pub fn mounter_offset(&self) -> Vec3<f32> {
|
||||
/// Component of the mounting offset specific to the rider
|
||||
pub fn rider_offset(&self) -> Vec3<f32> {
|
||||
match self {
|
||||
Body::Humanoid(_) => [0.0, 0.0, 0.0],
|
||||
_ => [0.0, 0.0, 0.0],
|
||||
|
@ -243,6 +243,32 @@ impl CharacterState {
|
||||
|| matches!(self, CharacterState::Roll(s) if s.stage_section == StageSection::Movement)
|
||||
}
|
||||
|
||||
pub fn can_perform_mounted(&self) -> bool {
|
||||
matches!(
|
||||
self,
|
||||
CharacterState::Idle(_)
|
||||
| CharacterState::Sit
|
||||
| CharacterState::Talk
|
||||
| CharacterState::GlideWield(_)
|
||||
| CharacterState::Stunned(_)
|
||||
| CharacterState::BasicBlock(_)
|
||||
| CharacterState::Equipping(_)
|
||||
| CharacterState::Wielding(_)
|
||||
| CharacterState::BasicMelee(_)
|
||||
| CharacterState::BasicRanged(_)
|
||||
| CharacterState::ComboMelee(_)
|
||||
| CharacterState::ChargedRanged(_)
|
||||
| CharacterState::RepeaterRanged(_)
|
||||
| CharacterState::BasicBeam(_)
|
||||
| CharacterState::BasicAura(_)
|
||||
| CharacterState::BasicSummon(_)
|
||||
| CharacterState::SelfBuff(_)
|
||||
| CharacterState::SpriteSummon(_)
|
||||
| CharacterState::UseItem(_)
|
||||
| CharacterState::SpriteInteract(_)
|
||||
)
|
||||
}
|
||||
|
||||
pub fn is_sitting(&self) -> bool {
|
||||
use use_item::{Data, ItemUseKind, StaticData};
|
||||
matches!(
|
||||
|
@ -10,7 +10,7 @@ use crate::{
|
||||
util::Dir,
|
||||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use specs::{Component, DerefFlaggedStorage};
|
||||
use specs::Component;
|
||||
use specs_idvs::IdvStorage;
|
||||
use std::collections::BTreeMap;
|
||||
use vek::*;
|
||||
@ -284,20 +284,3 @@ impl Controller {
|
||||
impl Component for Controller {
|
||||
type Storage = IdvStorage<Self>;
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
|
||||
pub enum MountState {
|
||||
Unmounted,
|
||||
MountedBy(Uid),
|
||||
}
|
||||
|
||||
impl Component for MountState {
|
||||
type Storage = DerefFlaggedStorage<Self, IdvStorage<Self>>;
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
|
||||
pub struct Mounting(pub Uid);
|
||||
|
||||
impl Component for Mounting {
|
||||
type Storage = DerefFlaggedStorage<Self, IdvStorage<Self>>;
|
||||
}
|
||||
|
@ -153,6 +153,19 @@ pub fn make_potion_bag(quantity: u32) -> Item {
|
||||
bag
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn make_food_bag(quantity: u32) -> Item {
|
||||
let mut bag = Item::new_from_asset_expect("common.items.armor.misc.bag.tiny_leather_pouch");
|
||||
if let Some(i) = bag.slots_mut().iter_mut().next() {
|
||||
let mut food = Item::new_from_asset_expect("common.items.food.apple_stick");
|
||||
if let Err(e) = food.set_amount(quantity) {
|
||||
warn!("Failed to set food quantity: {:?}", e);
|
||||
}
|
||||
*i = Some(food);
|
||||
}
|
||||
bag
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
// We have many species so this function is long
|
||||
// Also we are using default tools for un-specified species so
|
||||
|
@ -73,8 +73,7 @@ pub use self::{
|
||||
combo::Combo,
|
||||
controller::{
|
||||
Climb, ControlAction, ControlEvent, Controller, ControllerInputs, GroupManip, InputAttr,
|
||||
InputKind, InventoryAction, InventoryEvent, InventoryManip, MountState, Mounting,
|
||||
UtteranceKind,
|
||||
InputKind, InventoryAction, InventoryEvent, InventoryManip, UtteranceKind,
|
||||
},
|
||||
energy::Energy,
|
||||
fluid_dynamics::Fluid,
|
||||
|
@ -1,6 +1,6 @@
|
||||
// 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 = 14.0;
|
||||
pub const MAX_MOUNT_RANGE: f32 = 5.0;
|
||||
pub const MAX_TRADE_RANGE: f32 = 20.0;
|
||||
|
||||
pub const GRAVITY: f32 = 25.0;
|
||||
|
@ -12,7 +12,9 @@
|
||||
trait_alias,
|
||||
type_alias_impl_trait,
|
||||
extend_one,
|
||||
arbitrary_enum_discriminant
|
||||
arbitrary_enum_discriminant,
|
||||
generic_associated_types,
|
||||
arbitrary_self_types
|
||||
)]
|
||||
#![feature(hash_drain_filter)]
|
||||
|
||||
@ -46,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;
|
||||
|
84
common/src/link.rs
Normal file
84
common/src/link.rs
Normal file
@ -0,0 +1,84 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
use specs::{Component, DerefFlaggedStorage, SystemData};
|
||||
use specs_idvs::IdvStorage;
|
||||
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<(), Self::Error>;
|
||||
|
||||
type PersistData<'a>: SystemData<'a>;
|
||||
fn persist(this: &LinkHandle<Self>, data: Self::PersistData<'_>) -> bool;
|
||||
|
||||
type DeleteData<'a>: SystemData<'a>;
|
||||
fn delete(this: &LinkHandle<Self>, data: Self::DeleteData<'_>);
|
||||
}
|
||||
|
||||
pub trait Role {
|
||||
type Link: Link;
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
pub struct Is<R: Role> {
|
||||
#[serde(bound(serialize = "R::Link: Serialize"))]
|
||||
#[serde(bound(deserialize = "R::Link: Deserialize<'de>"))]
|
||||
link: LinkHandle<R::Link>,
|
||||
}
|
||||
|
||||
impl<R: Role> Is<R> {
|
||||
pub fn delete(&self, data: <R::Link as Link>::DeleteData<'_>) {
|
||||
R::Link::delete(&self.link, data)
|
||||
}
|
||||
}
|
||||
|
||||
impl<R: Role> Clone for Is<R> {
|
||||
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,
|
||||
{
|
||||
type Storage = DerefFlaggedStorage<Self, IdvStorage<Self>>;
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
pub struct LinkHandle<L: Link> {
|
||||
link: Arc<L>,
|
||||
}
|
||||
|
||||
impl<L: Link> Clone for LinkHandle<L> {
|
||||
fn clone(&self) -> Self {
|
||||
Self {
|
||||
link: Arc::clone(&self.link),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<L: Link> LinkHandle<L> {
|
||||
pub fn from_link(link: L) -> Self {
|
||||
Self {
|
||||
link: Arc::new(link),
|
||||
}
|
||||
}
|
||||
|
||||
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 }
|
||||
}
|
144
common/src/mounting.rs
Normal file
144
common/src/mounting.rs
Normal file
@ -0,0 +1,144 @@
|
||||
use crate::{
|
||||
comp,
|
||||
link::{Is, Link, LinkHandle, Role},
|
||||
terrain::TerrainGrid,
|
||||
uid::{Uid, UidAllocator},
|
||||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use specs::{saveload::MarkerAllocator, Entities, Read, ReadExpect, ReadStorage, WriteStorage};
|
||||
use vek::*;
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
pub struct Rider;
|
||||
|
||||
impl Role for Rider {
|
||||
type Link = Mounting;
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
pub struct Mount;
|
||||
|
||||
impl Role for Mount {
|
||||
type Link = Mounting;
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
pub struct Mounting {
|
||||
pub mount: Uid,
|
||||
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>>,
|
||||
);
|
||||
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>,
|
||||
ReadStorage<'a, comp::Health>,
|
||||
ReadStorage<'a, Is<Mount>>,
|
||||
ReadStorage<'a, Is<Rider>>,
|
||||
);
|
||||
|
||||
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)
|
||||
};
|
||||
|
||||
// Ensure that both entities are alive and that they continue to be linked
|
||||
is_alive(mount)
|
||||
&& is_alive(rider)
|
||||
&& is_mounts.get(mount).is_some()
|
||||
&& is_riders.get(rider).is_some()
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
// Delete link components
|
||||
mount.map(|mount| is_mounts.remove(mount));
|
||||
rider.map(|rider| is_riders.remove(rider));
|
||||
|
||||
// Try to move the rider to a safe place when dismounting
|
||||
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),
|
||||
)
|
||||
})
|
||||
});
|
||||
rider
|
||||
.and_then(|rider| Some(rider).zip(positions.get_mut(rider)))
|
||||
.map(|(rider, pos)| {
|
||||
let old_pos = pos.0.map(|e| e.floor() as i32);
|
||||
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);
|
||||
let _ = force_update.insert(rider, comp::ForceUpdate);
|
||||
});
|
||||
}
|
||||
}
|
@ -158,7 +158,7 @@ impl Body {
|
||||
Body::Humanoid(_) => 3.5,
|
||||
Body::QuadrupedSmall(_) => 3.0,
|
||||
Body::QuadrupedMedium(quadruped_medium) => match quadruped_medium.species {
|
||||
quadruped_medium::Species::Mammoth => 2.2,
|
||||
quadruped_medium::Species::Mammoth => 1.0,
|
||||
_ => 2.8,
|
||||
},
|
||||
Body::BirdMedium(_) => 6.0,
|
||||
@ -421,7 +421,14 @@ pub fn handle_orientation(
|
||||
// Angle factor used to keep turning rate approximately constant by
|
||||
// counteracting slerp turning more with a larger angle
|
||||
let angle_factor = 2.0 / (1.0 - update.ori.dot(target_ori)).sqrt();
|
||||
data.body.base_ori_rate() * efficiency * angle_factor
|
||||
data.body.base_ori_rate()
|
||||
* efficiency
|
||||
* angle_factor
|
||||
* if data.physics.on_ground.is_some() {
|
||||
1.0
|
||||
} else {
|
||||
0.2
|
||||
}
|
||||
};
|
||||
update.ori = update
|
||||
.ori
|
||||
|
@ -162,18 +162,22 @@ impl TerrainGrid {
|
||||
self.try_find_space(pos).unwrap_or(pos)
|
||||
}
|
||||
|
||||
pub fn is_space(&self, pos: Vec3<i32>) -> bool {
|
||||
(0..2).all(|z| {
|
||||
self.get(pos + Vec3::unit_z() * z)
|
||||
.map_or(true, |b| !b.is_solid())
|
||||
})
|
||||
}
|
||||
|
||||
pub fn try_find_space(&self, pos: Vec3<i32>) -> Option<Vec3<i32>> {
|
||||
const SEARCH_DIST: i32 = 63;
|
||||
(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(|test_pos| {
|
||||
self.get(test_pos - Vec3::unit_z())
|
||||
.find(|pos| {
|
||||
self.get(pos - Vec3::unit_z())
|
||||
.map_or(false, |b| b.is_filled())
|
||||
&& (0..2).all(|z| {
|
||||
self.get(test_pos + Vec3::unit_z() * z)
|
||||
.map_or(true, |b| !b.is_solid())
|
||||
})
|
||||
&& self.is_space(*pos)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -8,6 +8,8 @@ use common::{
|
||||
calendar::Calendar,
|
||||
comp,
|
||||
event::{EventBus, LocalEvent, ServerEvent},
|
||||
link::Is,
|
||||
mounting::{Mount, Rider},
|
||||
outcome::Outcome,
|
||||
region::RegionMap,
|
||||
resources::{
|
||||
@ -141,8 +143,8 @@ impl State {
|
||||
ecs.register::<comp::LightEmitter>();
|
||||
ecs.register::<comp::Item>();
|
||||
ecs.register::<comp::Scale>();
|
||||
ecs.register::<comp::Mounting>();
|
||||
ecs.register::<comp::MountState>();
|
||||
ecs.register::<Is<Mount>>();
|
||||
ecs.register::<Is<Rider>>();
|
||||
ecs.register::<comp::Mass>();
|
||||
ecs.register::<comp::Density>();
|
||||
ecs.register::<comp::Collider>();
|
||||
@ -153,6 +155,7 @@ impl State {
|
||||
ecs.register::<comp::Shockwave>();
|
||||
ecs.register::<comp::ShockwaveHitEntities>();
|
||||
ecs.register::<comp::BeamSegment>();
|
||||
ecs.register::<comp::Alignment>();
|
||||
|
||||
// Register components send from clients -> server
|
||||
ecs.register::<comp::Controller>();
|
||||
@ -182,7 +185,6 @@ impl State {
|
||||
ecs.register::<comp::Last<comp::Pos>>();
|
||||
ecs.register::<comp::Last<comp::Vel>>();
|
||||
ecs.register::<comp::Last<comp::Ori>>();
|
||||
ecs.register::<comp::Alignment>();
|
||||
ecs.register::<comp::Agent>();
|
||||
ecs.register::<comp::WaypointArea>();
|
||||
ecs.register::<comp::ForceUpdate>();
|
||||
|
@ -7,10 +7,12 @@ use common::{
|
||||
comp::{
|
||||
self, character_state::OutputEvents, inventory::item::MaterialStatManifest,
|
||||
ActiveAbilities, Beam, Body, CharacterState, Combo, Controller, Density, Energy, Health,
|
||||
Inventory, InventoryManip, Mass, Melee, Mounting, Ori, PhysicsState, Poise, Pos, SkillSet,
|
||||
Inventory, InventoryManip, Mass, Melee, Ori, PhysicsState, Poise, Pos, SkillSet,
|
||||
StateUpdate, Stats, Vel,
|
||||
},
|
||||
event::{EventBus, LocalEvent, ServerEvent},
|
||||
link::Is,
|
||||
mounting::Rider,
|
||||
outcome::Outcome,
|
||||
resources::{DeltaTime, Time},
|
||||
states::{
|
||||
@ -37,7 +39,7 @@ pub struct ReadData<'a> {
|
||||
melee_attacks: ReadStorage<'a, Melee>,
|
||||
beams: ReadStorage<'a, Beam>,
|
||||
uids: ReadStorage<'a, Uid>,
|
||||
mountings: ReadStorage<'a, Mounting>,
|
||||
is_riders: ReadStorage<'a, Is<Rider>>,
|
||||
stats: ReadStorage<'a, Stats>,
|
||||
skill_sets: ReadStorage<'a, SkillSet>,
|
||||
active_abilities: ReadStorage<'a, ActiveAbilities>,
|
||||
@ -110,7 +112,7 @@ impl<'a> System<'a> for Sys {
|
||||
health,
|
||||
body,
|
||||
physics,
|
||||
(stat, skill_set, active_abilities),
|
||||
(stat, skill_set, active_abilities, is_rider),
|
||||
combo,
|
||||
) in (
|
||||
&read_data.entities,
|
||||
@ -131,6 +133,7 @@ impl<'a> System<'a> for Sys {
|
||||
&read_data.stats,
|
||||
&read_data.skill_sets,
|
||||
&read_data.active_abilities,
|
||||
read_data.is_riders.maybe(),
|
||||
),
|
||||
&read_data.combos,
|
||||
)
|
||||
@ -207,11 +210,9 @@ impl<'a> System<'a> for Sys {
|
||||
|
||||
// Mounted occurs after control actions have been handled
|
||||
// If mounted, character state is controlled by mount
|
||||
if let Some(Mounting(_)) = read_data.mountings.get(entity) {
|
||||
let idle_state = CharacterState::Idle(idle::Data { is_sneaking: false });
|
||||
if *join_struct.char_state != idle_state {
|
||||
*join_struct.char_state = idle_state;
|
||||
}
|
||||
if is_rider.is_some() && !join_struct.char_state.can_perform_mounted() {
|
||||
// TODO: A better way to swap between mount inputs and rider inputs
|
||||
*join_struct.char_state = CharacterState::Idle(idle::Data { is_sneaking: false });
|
||||
continue;
|
||||
}
|
||||
|
||||
|
@ -1,4 +1,4 @@
|
||||
#![feature(bool_to_option)]
|
||||
#![feature(bool_to_option, let_else, btree_drain_filter)]
|
||||
#![allow(clippy::option_map_unit_fn)]
|
||||
|
||||
mod aura;
|
||||
|
@ -1,5 +1,7 @@
|
||||
use common::{
|
||||
comp::{Body, Controller, MountState, Mounting, Ori, Pos, Vel},
|
||||
comp::{Body, Controller, InputKind, Ori, Pos, Vel},
|
||||
link::Is,
|
||||
mounting::Mount,
|
||||
uid::UidAllocator,
|
||||
};
|
||||
use common_ecs::{Job, Origin, Phase, System};
|
||||
@ -18,8 +20,7 @@ impl<'a> System<'a> for Sys {
|
||||
Read<'a, UidAllocator>,
|
||||
Entities<'a>,
|
||||
WriteStorage<'a, Controller>,
|
||||
WriteStorage<'a, MountState>,
|
||||
WriteStorage<'a, Mounting>,
|
||||
ReadStorage<'a, Is<Mount>>,
|
||||
WriteStorage<'a, Pos>,
|
||||
WriteStorage<'a, Vel>,
|
||||
WriteStorage<'a, Ori>,
|
||||
@ -36,69 +37,51 @@ impl<'a> System<'a> for Sys {
|
||||
uid_allocator,
|
||||
entities,
|
||||
mut controllers,
|
||||
mut mount_state,
|
||||
mut mountings,
|
||||
is_mounts,
|
||||
mut positions,
|
||||
mut velocities,
|
||||
mut orientations,
|
||||
bodies,
|
||||
): Self::SystemData,
|
||||
) {
|
||||
// Mounted entities.
|
||||
for (entity, mut mount_states, body) in (&entities, &mut mount_state, bodies.maybe()).join()
|
||||
{
|
||||
match *mount_states {
|
||||
MountState::Unmounted => {},
|
||||
MountState::MountedBy(mounter_uid) => {
|
||||
// Note: currently controller events are not passed through since none of them
|
||||
// are currently relevant to controlling the mounted entity
|
||||
if let Some((inputs, queued_inputs, mounter)) = uid_allocator
|
||||
.retrieve_entity_internal(mounter_uid.id())
|
||||
.and_then(|mounter| {
|
||||
controllers
|
||||
.get(mounter)
|
||||
.map(|c| (c.inputs.clone(), c.queued_inputs.clone(), mounter))
|
||||
// For each mount...
|
||||
for (entity, is_mount, body) in (&entities, &is_mounts, bodies.maybe()).join() {
|
||||
// ...find the rider...
|
||||
let Some((inputs, queued_inputs, rider)) = uid_allocator
|
||||
.retrieve_entity_internal(is_mount.rider.id())
|
||||
.and_then(|rider| {
|
||||
controllers
|
||||
.get_mut(rider)
|
||||
.map(|c| {
|
||||
let queued_inputs = c.queued_inputs
|
||||
// TODO: Formalise ways to pass inputs to mounts
|
||||
.drain_filter(|i, _| matches!(i, InputKind::Jump | InputKind::Fly | InputKind::Roll))
|
||||
.collect();
|
||||
(c.inputs.clone(), queued_inputs, rider)
|
||||
})
|
||||
{
|
||||
// TODO: consider joining on these? (remember we can use .maybe())
|
||||
let pos = positions.get(entity).copied();
|
||||
let ori = orientations.get(entity).copied();
|
||||
let vel = velocities.get(entity).copied();
|
||||
if let (Some(pos), Some(ori), Some(vel)) = (pos, ori, vel) {
|
||||
let mounter_body = bodies.get(mounter);
|
||||
let mounting_offset = body.map_or(Vec3::unit_z(), Body::mountee_offset)
|
||||
+ mounter_body.map_or(Vec3::zero(), Body::mounter_offset);
|
||||
let _ = positions
|
||||
.insert(mounter, Pos(pos.0 + ori.to_quat() * mounting_offset));
|
||||
let _ = orientations.insert(mounter, ori);
|
||||
let _ = velocities.insert(mounter, vel);
|
||||
}
|
||||
if let Some(controller) = controllers.get_mut(entity) {
|
||||
*controller = Controller {
|
||||
inputs,
|
||||
queued_inputs,
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
} else {
|
||||
*mount_states = MountState::Unmounted;
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
})
|
||||
else { continue };
|
||||
|
||||
let mut to_unmount = Vec::new();
|
||||
for (entity, Mounting(mountee_uid)) in (&entities, &mountings).join() {
|
||||
if uid_allocator
|
||||
.retrieve_entity_internal(mountee_uid.id())
|
||||
.filter(|mountee| entities.is_alive(*mountee))
|
||||
.is_none()
|
||||
{
|
||||
to_unmount.push(entity);
|
||||
// ...apply the mount's position/ori/velocity to the rider...
|
||||
let pos = positions.get(entity).copied();
|
||||
let ori = orientations.get(entity).copied();
|
||||
let vel = velocities.get(entity).copied();
|
||||
if let (Some(pos), Some(ori), Some(vel)) = (pos, ori, vel) {
|
||||
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 _ = orientations.insert(rider, ori);
|
||||
let _ = velocities.insert(rider, vel);
|
||||
}
|
||||
// ...and apply the rider's inputs to the mount's controller.
|
||||
if let Some(controller) = controllers.get_mut(entity) {
|
||||
*controller = Controller {
|
||||
inputs,
|
||||
queued_inputs,
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
}
|
||||
for entity in to_unmount {
|
||||
mountings.remove(entity);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -2,11 +2,13 @@ use common::{
|
||||
comp::{
|
||||
body::ship::figuredata::{VoxelCollider, VOXEL_COLLIDER_MANIFEST},
|
||||
fluid_dynamics::{Fluid, LiquidKind, Wings},
|
||||
Body, CharacterState, Collider, Density, Mass, Mounting, 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,
|
||||
@ -111,7 +113,7 @@ pub struct PhysicsRead<'a> {
|
||||
stickies: ReadStorage<'a, Sticky>,
|
||||
masses: ReadStorage<'a, Mass>,
|
||||
colliders: ReadStorage<'a, Collider>,
|
||||
mountings: ReadStorage<'a, Mounting>,
|
||||
is_ridings: ReadStorage<'a, Is<Rider>>,
|
||||
projectiles: ReadStorage<'a, Projectile>,
|
||||
char_states: ReadStorage<'a, CharacterState>,
|
||||
bodies: ReadStorage<'a, Body>,
|
||||
@ -169,10 +171,9 @@ impl<'a> PhysicsData<'a> {
|
||||
&self.write.velocities,
|
||||
&self.write.positions,
|
||||
!&self.write.previous_phys_cache,
|
||||
!&self.read.mountings,
|
||||
)
|
||||
.join()
|
||||
.map(|(e, _, _, _, _, _)| e)
|
||||
.map(|(e, _, _, _, _)| e)
|
||||
.collect::<Vec<_>>()
|
||||
{
|
||||
let _ = self
|
||||
@ -192,7 +193,7 @@ impl<'a> PhysicsData<'a> {
|
||||
}
|
||||
|
||||
// Update PreviousPhysCache
|
||||
for (_, vel, position, ori, mut phys_cache, collider, scale, cs, _) in (
|
||||
for (_, vel, position, ori, mut phys_cache, collider, scale, cs) in (
|
||||
&self.read.entities,
|
||||
&self.write.velocities,
|
||||
&self.write.positions,
|
||||
@ -201,7 +202,6 @@ impl<'a> PhysicsData<'a> {
|
||||
&self.read.colliders,
|
||||
self.read.scales.maybe(),
|
||||
self.read.char_states.maybe(),
|
||||
!&self.read.mountings,
|
||||
)
|
||||
.join()
|
||||
{
|
||||
@ -292,14 +292,12 @@ impl<'a> PhysicsData<'a> {
|
||||
let lg2_large_cell_size = 6;
|
||||
let radius_cutoff = 8;
|
||||
let mut spatial_grid = SpatialGrid::new(lg2_cell_size, lg2_large_cell_size, radius_cutoff);
|
||||
for (entity, pos, phys_cache, _, _, _, _) in (
|
||||
for (entity, pos, phys_cache, _, _) in (
|
||||
&read.entities,
|
||||
&write.positions,
|
||||
&write.previous_phys_cache,
|
||||
write.velocities.mask(),
|
||||
!&read.projectiles, // Not needed because they are skipped in the inner loop below
|
||||
!&read.mountings,
|
||||
read.colliders.mask(),
|
||||
)
|
||||
.join()
|
||||
{
|
||||
@ -328,7 +326,7 @@ impl<'a> PhysicsData<'a> {
|
||||
previous_phys_cache,
|
||||
&read.masses,
|
||||
&read.colliders,
|
||||
!&read.mountings,
|
||||
read.is_ridings.maybe(),
|
||||
read.stickies.maybe(),
|
||||
&mut write.physics_states,
|
||||
// TODO: if we need to avoid collisions for other things consider
|
||||
@ -338,9 +336,6 @@ impl<'a> PhysicsData<'a> {
|
||||
read.char_states.maybe(),
|
||||
)
|
||||
.par_join()
|
||||
.map(|(e, p, v, vd, m, c, _, sticky, ph, pr, c_s)| {
|
||||
(e, p, v, vd, m, c, sticky, ph, pr, c_s)
|
||||
})
|
||||
.map_init(
|
||||
|| {
|
||||
prof_span!(guard, "physics e<>e rayon job");
|
||||
@ -354,6 +349,7 @@ impl<'a> PhysicsData<'a> {
|
||||
previous_cache,
|
||||
mass,
|
||||
collider,
|
||||
is_riding,
|
||||
sticky,
|
||||
physics,
|
||||
projectile,
|
||||
@ -404,6 +400,7 @@ impl<'a> PhysicsData<'a> {
|
||||
mass,
|
||||
collider,
|
||||
read.char_states.get(entity),
|
||||
read.is_ridings.get(entity),
|
||||
))
|
||||
})
|
||||
.for_each(
|
||||
@ -415,6 +412,7 @@ impl<'a> PhysicsData<'a> {
|
||||
mass_other,
|
||||
collider_other,
|
||||
char_state_other_maybe,
|
||||
other_is_riding_maybe,
|
||||
)| {
|
||||
let collision_boundary = previous_cache.collision_boundary
|
||||
+ previous_cache_other.collision_boundary;
|
||||
@ -476,6 +474,8 @@ impl<'a> PhysicsData<'a> {
|
||||
collider_other,
|
||||
*mass,
|
||||
*mass_other,
|
||||
vel,
|
||||
is_riding.is_some() || other_is_riding_maybe.is_some(),
|
||||
);
|
||||
}
|
||||
},
|
||||
@ -570,7 +570,7 @@ impl<'a> PhysicsData<'a> {
|
||||
write.physics_states.mask(),
|
||||
!&write.pos_vel_ori_defers, // This is the one we are adding
|
||||
write.previous_phys_cache.mask(),
|
||||
!&read.mountings,
|
||||
!&read.is_ridings,
|
||||
)
|
||||
.join()
|
||||
.map(|t| (t.0, *t.2, *t.3, *t.4))
|
||||
@ -601,7 +601,7 @@ impl<'a> PhysicsData<'a> {
|
||||
&write.physics_states,
|
||||
&read.masses,
|
||||
&read.densities,
|
||||
!&read.mountings,
|
||||
!&read.is_ridings,
|
||||
)
|
||||
.par_join()
|
||||
.for_each_init(
|
||||
@ -730,7 +730,7 @@ impl<'a> PhysicsData<'a> {
|
||||
&mut write.physics_states,
|
||||
&mut write.pos_vel_ori_defers,
|
||||
previous_phys_cache,
|
||||
!&read.mountings,
|
||||
!&read.is_ridings,
|
||||
)
|
||||
.par_join()
|
||||
.filter(|tuple| tuple.3.is_voxel() == terrain_like_entities)
|
||||
@ -1792,6 +1792,8 @@ fn resolve_e2e_collision(
|
||||
collider_other: &Collider,
|
||||
mass: Mass,
|
||||
mass_other: Mass,
|
||||
vel: &Vel,
|
||||
is_riding: bool,
|
||||
) -> bool {
|
||||
// Find the distance betwen our collider and
|
||||
// collider we collide with and get vector of pushback.
|
||||
@ -1849,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());
|
||||
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,
|
||||
@ -1869,7 +1872,16 @@ fn resolve_e2e_collision(
|
||||
let distance_coefficient = collision_dist - diff.magnitude();
|
||||
let force = ELASTIC_FORCE_COEFFICIENT * distance_coefficient * mass_coefficient;
|
||||
|
||||
*vel_delta += Vec3::from(diff.normalized()) * force * step_delta;
|
||||
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));
|
||||
}
|
||||
|
||||
*collision_registered = true;
|
||||
|
@ -34,10 +34,12 @@ 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,
|
||||
uid::{Uid, UidAllocator},
|
||||
vol::{ReadVol, RectVolSize},
|
||||
Damage, DamageKind, DamageSource, Explosion, LoadoutBuilder, RadiusEffect,
|
||||
};
|
||||
@ -50,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};
|
||||
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};
|
||||
@ -201,11 +205,35 @@ fn position_mut<T>(
|
||||
descriptor: &str,
|
||||
f: impl for<'a> FnOnce(&'a mut comp::Pos) -> T,
|
||||
) -> CmdResult<T> {
|
||||
let mut pos_storage = server.state.ecs_mut().write_storage::<comp::Pos>();
|
||||
pos_storage
|
||||
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())
|
||||
})
|
||||
.unwrap_or(entity);
|
||||
|
||||
let res = server
|
||||
.state
|
||||
.ecs()
|
||||
.write_storage::<comp::Pos>()
|
||||
.get_mut(entity)
|
||||
.map(f)
|
||||
.ok_or_else(|| format!("Cannot get position for {:?}!", descriptor))
|
||||
.ok_or_else(|| format!("Cannot get position for {:?}!", descriptor));
|
||||
if res.is_ok() {
|
||||
let _ = server
|
||||
.state
|
||||
.ecs()
|
||||
.write_storage::<comp::ForceUpdate>()
|
||||
.insert(entity, comp::ForceUpdate);
|
||||
}
|
||||
res
|
||||
}
|
||||
|
||||
fn insert_or_replace_component<C: specs::Component>(
|
||||
@ -622,8 +650,7 @@ fn handle_make_npc(
|
||||
.create_npc(pos, stats, skill_set, health, poise, inventory, body)
|
||||
.with(alignment)
|
||||
.with(scale)
|
||||
.with(comp::Vel(Vec3::new(0.0, 0.0, 0.0)))
|
||||
.with(comp::MountState::Unmounted);
|
||||
.with(comp::Vel(Vec3::new(0.0, 0.0, 0.0)));
|
||||
|
||||
if let Some(agent) = agent {
|
||||
entity_builder = entity_builder.with(agent);
|
||||
@ -767,8 +794,7 @@ fn handle_jump(
|
||||
if let (Some(x), Some(y), Some(z)) = parse_args!(args, f32, f32, f32) {
|
||||
position_mut(server, target, "target", |current_pos| {
|
||||
current_pos.0 += Vec3::new(x, y, z)
|
||||
})?;
|
||||
insert_or_replace_component(server, target, comp::ForceUpdate, "target")
|
||||
})
|
||||
} else {
|
||||
Err(action.help_string())
|
||||
}
|
||||
@ -784,8 +810,7 @@ fn handle_goto(
|
||||
if let (Some(x), Some(y), Some(z)) = parse_args!(args, f32, f32, f32) {
|
||||
position_mut(server, target, "target", |current_pos| {
|
||||
current_pos.0 = Vec3::new(x, y, z)
|
||||
})?;
|
||||
insert_or_replace_component(server, target, comp::ForceUpdate, "target")
|
||||
})
|
||||
} else {
|
||||
Err(action.help_string())
|
||||
}
|
||||
@ -820,8 +845,7 @@ fn handle_site(
|
||||
|
||||
position_mut(server, target, "target", |current_pos| {
|
||||
current_pos.0 = site_pos
|
||||
})?;
|
||||
insert_or_replace_component(server, target, comp::ForceUpdate, "target")
|
||||
})
|
||||
} else {
|
||||
Err(action.help_string())
|
||||
}
|
||||
@ -848,8 +872,7 @@ fn handle_home(
|
||||
target,
|
||||
comp::Waypoint::temp_new(home_pos, time),
|
||||
"target",
|
||||
)?;
|
||||
insert_or_replace_component(server, target, comp::ForceUpdate, "target")
|
||||
)
|
||||
}
|
||||
|
||||
fn handle_kill(
|
||||
@ -1119,8 +1142,7 @@ fn handle_tp(
|
||||
let player_pos = position(server, player, "player")?;
|
||||
position_mut(server, target, "target", |target_pos| {
|
||||
*target_pos = player_pos
|
||||
})?;
|
||||
insert_or_replace_component(server, target, comp::ForceUpdate, "target")
|
||||
})
|
||||
}
|
||||
|
||||
fn handle_spawn(
|
||||
@ -1168,7 +1190,6 @@ fn handle_spawn(
|
||||
body,
|
||||
)
|
||||
.with(comp::Vel(vel))
|
||||
.with(comp::MountState::Unmounted)
|
||||
.with(alignment);
|
||||
|
||||
if ai {
|
||||
@ -1251,7 +1272,6 @@ fn handle_spawn_training_dummy(
|
||||
body,
|
||||
)
|
||||
.with(comp::Vel(vel))
|
||||
.with(comp::MountState::Unmounted)
|
||||
.build();
|
||||
|
||||
server.notify_client(
|
||||
|
@ -15,6 +15,8 @@ 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,
|
||||
@ -97,62 +99,48 @@ pub fn handle_npc_interaction(server: &mut Server, interactor: EcsEntity, npc_en
|
||||
}
|
||||
}
|
||||
|
||||
/// FIXME: Make mounting more robust, avoid bidirectional links.
|
||||
pub fn handle_mount(server: &mut Server, mounter: EcsEntity, mountee: EcsEntity) {
|
||||
pub fn handle_mount(server: &mut Server, rider: EcsEntity, mount: EcsEntity) {
|
||||
let state = server.state_mut();
|
||||
|
||||
if state
|
||||
.ecs()
|
||||
.read_storage::<comp::Mounting>()
|
||||
.get(mounter)
|
||||
.is_none()
|
||||
{
|
||||
let not_mounting_yet = matches!(
|
||||
state.ecs().read_storage::<comp::MountState>().get(mountee),
|
||||
Some(comp::MountState::Unmounted)
|
||||
);
|
||||
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>();
|
||||
within_mounting_range(positions.get(mounter), positions.get(mountee))
|
||||
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(mounter) && alive(mountee) {
|
||||
if not_mounting_yet && within_range() && alive(rider) && alive(mount) {
|
||||
let uids = state.ecs().read_storage::<Uid>();
|
||||
if let (Some(mounter_uid), Some(mountee_uid)) =
|
||||
(uids.get(mounter).copied(), uids.get(mountee).copied())
|
||||
if let (Some(rider_uid), Some(mount_uid)) =
|
||||
(uids.get(rider).copied(), uids.get(mount).copied())
|
||||
{
|
||||
drop(uids);
|
||||
drop(healths);
|
||||
// We know the entities must exist to be able to look up their UIDs, so these
|
||||
// are guaranteed to work; hence we can ignore possible errors here.
|
||||
state.write_component_ignore_entity_dead(
|
||||
mountee,
|
||||
comp::MountState::MountedBy(mounter_uid),
|
||||
let is_pet = matches!(
|
||||
state
|
||||
.ecs()
|
||||
.read_storage::<comp::Alignment>()
|
||||
.get(mount),
|
||||
Some(comp::Alignment::Owned(owner)) if *owner == rider_uid,
|
||||
);
|
||||
state.write_component_ignore_entity_dead(mounter, comp::Mounting(mountee_uid));
|
||||
|
||||
if is_pet {
|
||||
drop(uids);
|
||||
drop(healths);
|
||||
let _ = state.link(Mounting {
|
||||
mount: mount_uid,
|
||||
rider: rider_uid,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn handle_unmount(server: &mut Server, mounter: EcsEntity) {
|
||||
pub fn handle_unmount(server: &mut Server, rider: EcsEntity) {
|
||||
let state = server.state_mut();
|
||||
let mountee_entity = state
|
||||
.ecs()
|
||||
.write_storage::<comp::Mounting>()
|
||||
.get(mounter)
|
||||
.and_then(|mountee| state.ecs().entity_from_uid(mountee.0.into()));
|
||||
if let Some(mountee_entity) = mountee_entity {
|
||||
state
|
||||
.ecs()
|
||||
.write_storage::<comp::MountState>()
|
||||
.get_mut(mountee_entity)
|
||||
.map(|mut ms| *ms = comp::MountState::Unmounted);
|
||||
}
|
||||
state.delete_component::<comp::Mounting>(mounter);
|
||||
state.ecs().write_storage::<Is<Rider>>().remove(rider);
|
||||
}
|
||||
|
||||
/// FIXME: This code is dangerous and needs to be refactored. We can't just
|
||||
|
@ -8,7 +8,8 @@
|
||||
drain_filter,
|
||||
never_type,
|
||||
option_zip,
|
||||
unwrap_infallible
|
||||
unwrap_infallible,
|
||||
let_else
|
||||
)]
|
||||
#![cfg_attr(not(feature = "worldgen"), feature(const_panic))]
|
||||
|
||||
@ -663,6 +664,9 @@ impl Server {
|
||||
// will be processed once handle_events() is called below
|
||||
let disconnect_type = self.disconnect_all_clients_if_requested();
|
||||
|
||||
// Handle entity links (such as mounting)
|
||||
self.state.maintain_links();
|
||||
|
||||
// Handle game events
|
||||
frontend_events.append(&mut self.handle_events());
|
||||
|
||||
|
@ -1,6 +1,9 @@
|
||||
use super::*;
|
||||
use common::{
|
||||
comp::inventory::{loadout_builder::make_potion_bag, slot::ArmorSlot},
|
||||
comp::inventory::{
|
||||
loadout_builder::{make_food_bag, make_potion_bag},
|
||||
slot::ArmorSlot,
|
||||
},
|
||||
resources::Time,
|
||||
rtsim::{Memory, MemoryItem},
|
||||
store::Id,
|
||||
@ -142,8 +145,9 @@ 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) => {
|
||||
|l, trade| l.with_creator(world::site::settlement::merchant_loadout, trade)
|
||||
@ -163,7 +167,9 @@ impl Entity {
|
||||
.iter()
|
||||
.filter(|s| s.1.is_settlement() || s.1.is_castle())
|
||||
.min_by_key(|(_, site)| {
|
||||
let wpos = site.center * TerrainChunk::RECT_SIZE.map(|e| e as i32);
|
||||
let wpos = site.center.map2(TerrainChunk::RECT_SIZE, |e, sz| {
|
||||
e * sz as i32 + sz as i32 / 2
|
||||
});
|
||||
wpos.map(|e| e as f32).distance_squared(self.pos.xy()) as u32
|
||||
})
|
||||
.map(|(id, _)| id)
|
||||
@ -173,7 +179,9 @@ impl Entity {
|
||||
// with at least one path, we need to get them to a town that does.
|
||||
let nearest_site = &world.civs().sites[nearest_site_id];
|
||||
let site_wpos =
|
||||
nearest_site.center * TerrainChunk::RECT_SIZE.map(|e| e as i32);
|
||||
nearest_site.center.map2(TerrainChunk::RECT_SIZE, |e, sz| {
|
||||
e * sz as i32 + sz as i32 / 2
|
||||
});
|
||||
let dist =
|
||||
site_wpos.map(|e| e as f32).distance_squared(self.pos.xy()) as u32;
|
||||
if dist < 64_u32.pow(2) {
|
||||
@ -204,7 +212,9 @@ impl Entity {
|
||||
})
|
||||
.filter(|_| thread_rng().gen_range(0i32..4) == 0)
|
||||
.min_by_key(|(_, site)| {
|
||||
let wpos = site.center * TerrainChunk::RECT_SIZE.map(|e| e as i32);
|
||||
let wpos = site.center.map2(TerrainChunk::RECT_SIZE, |e, sz| {
|
||||
e * sz as i32 + sz as i32 / 2
|
||||
});
|
||||
let dist =
|
||||
wpos.map(|e| e as f32).distance_squared(self.pos.xy()) as u32;
|
||||
dist + if dist < 96_u32.pow(2) { 100_000_000 } else { 0 }
|
||||
@ -215,8 +225,10 @@ impl Entity {
|
||||
(Normal::new(0.0, 64.0), Normal::new(0.0, 256.0))
|
||||
{
|
||||
let mut path = Vec::<Vec2<i32>>::default();
|
||||
let target_site_pos = site.center.map(|e| e as f32)
|
||||
* TerrainChunk::RECT_SIZE.map(|e| e as f32);
|
||||
let target_site_pos =
|
||||
site.center.map2(TerrainChunk::RECT_SIZE, |e, sz| {
|
||||
(e * sz as i32 + sz as i32 / 2) as f32
|
||||
});
|
||||
let offset_site_pos =
|
||||
target_site_pos.map(|v| v + normalpos.sample(&mut rng));
|
||||
let offset_dir = (offset_site_pos - self.pos.xy()).normalized();
|
||||
@ -252,7 +264,9 @@ impl Entity {
|
||||
})
|
||||
.filter(|_| thread_rng().gen_range(0i32..4) == 0)
|
||||
.min_by_key(|(_, site)| {
|
||||
let wpos = site.center * TerrainChunk::RECT_SIZE.map(|e| e as i32);
|
||||
let wpos = site.center.map2(TerrainChunk::RECT_SIZE, |e, sz| {
|
||||
e * sz as i32 + sz as i32 / 2
|
||||
});
|
||||
let dist =
|
||||
wpos.map(|e| e as f32).distance_squared(self.pos.xy()) as u32;
|
||||
dist + if dist < 96_u32.pow(2) { 100_000 } else { 0 }
|
||||
@ -275,7 +289,9 @@ impl Entity {
|
||||
.neighbors(site_id)
|
||||
.filter(|sid| {
|
||||
let site = world.civs().sites.get(*sid);
|
||||
let wpos = site.center * TerrainChunk::RECT_SIZE.map(|e| e as i32);
|
||||
let wpos = site.center.map2(TerrainChunk::RECT_SIZE, |e, sz| {
|
||||
e * sz as i32 + sz as i32 / 2
|
||||
});
|
||||
let dist = wpos.map(|e| e as f32).distance_squared(self.pos.xy()) as u32;
|
||||
dist > 96_u32.pow(2)
|
||||
})
|
||||
@ -310,7 +326,9 @@ impl Entity {
|
||||
.filter(|s| s.1.is_settlement() | s.1.is_castle())
|
||||
.filter(|_| thread_rng().gen_range(0i32..4) == 0)
|
||||
.min_by_key(|(_, site)| {
|
||||
let wpos = site.center * TerrainChunk::RECT_SIZE.map(|e| e as i32);
|
||||
let wpos = site.center.map2(TerrainChunk::RECT_SIZE, |e, sz| {
|
||||
e * sz as i32 + sz as i32 / 2
|
||||
});
|
||||
let dist = wpos.map(|e| e as f32).distance_squared(self.pos.xy()) as u32;
|
||||
dist + if dist < 96_u32.pow(2) { 100_000 } else { 0 }
|
||||
})
|
||||
@ -333,7 +351,9 @@ impl Entity {
|
||||
.site_tmp
|
||||
.map_or("".to_string(), |id| index.sites[id].name().to_string());
|
||||
|
||||
let wpos = site.center * TerrainChunk::RECT_SIZE.map(|e| e as i32);
|
||||
let wpos = site.center.map2(TerrainChunk::RECT_SIZE, |e, sz| {
|
||||
e * sz as i32 + sz as i32 / 2
|
||||
});
|
||||
let dist = wpos.map(|e| e as f32).distance_squared(self.pos.xy()) as u32;
|
||||
|
||||
if dist < 64_u32.pow(2) {
|
||||
@ -435,7 +455,9 @@ impl Entity {
|
||||
};
|
||||
|
||||
if let Some(sim_pos) = track.path().iter().nth(nth) {
|
||||
let chunkpos = sim_pos * TerrainChunk::RECT_SIZE.map(|e| e as i32);
|
||||
let chunkpos = sim_pos.map2(TerrainChunk::RECT_SIZE, |e, sz| {
|
||||
e * sz as i32 + sz as i32 / 2
|
||||
});
|
||||
let wpos = if let Some(pathdata) = world.sim().get_nearest_path(chunkpos) {
|
||||
pathdata.1.map(|e| e as i32)
|
||||
} else {
|
||||
@ -523,7 +545,9 @@ impl Entity {
|
||||
.site_tmp
|
||||
.map_or("".to_string(), |id| index.sites[id].name().to_string());
|
||||
|
||||
let wpos = dest_site.center * TerrainChunk::RECT_SIZE.map(|e| e as i32);
|
||||
let wpos = dest_site.center.map2(TerrainChunk::RECT_SIZE, |e, sz| {
|
||||
e * sz as i32 + sz as i32 / 2
|
||||
});
|
||||
let dist = wpos.map(|e| e as f32).distance_squared(self.pos.xy()) as u32;
|
||||
|
||||
// Once at site, stay for a bit, then move to other site
|
||||
|
@ -18,6 +18,8 @@ use common::{
|
||||
Group, Inventory, Poise,
|
||||
},
|
||||
effect::Effect,
|
||||
link::{Link, LinkHandle},
|
||||
mounting::Mounting,
|
||||
resources::{Time, TimeOfDay},
|
||||
slowjob::SlowJobPool,
|
||||
uid::{Uid, UidAllocator},
|
||||
@ -110,6 +112,11 @@ 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<(), L::Error>;
|
||||
/// Maintain active links between entities
|
||||
fn maintain_links(&mut self);
|
||||
/// Delete an entity, recording the deletion in [`DeletedEntities`]
|
||||
fn delete_entity_recorded(
|
||||
&mut self,
|
||||
@ -271,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)
|
||||
@ -294,7 +301,7 @@ impl StateExt for State {
|
||||
.with(comp::Combo::default());
|
||||
|
||||
if mountable {
|
||||
builder = builder.with(comp::MountState::Unmounted);
|
||||
// TODO: Re-add mounting check
|
||||
}
|
||||
builder
|
||||
}
|
||||
@ -825,6 +832,36 @@ impl StateExt for State {
|
||||
}
|
||||
}
|
||||
|
||||
fn link<L: Link>(&mut self, link: L) -> Result<(), L::Error> {
|
||||
let linker = LinkHandle::from_link(link);
|
||||
|
||||
L::create(&linker, self.ecs().system_data())?;
|
||||
|
||||
self.ecs_mut()
|
||||
.entry::<Vec<LinkHandle<L>>>()
|
||||
.or_insert_with(Vec::new)
|
||||
.push(linker);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
maintain_link::<Mounting>(self);
|
||||
}
|
||||
|
||||
fn delete_entity_recorded(
|
||||
&mut self,
|
||||
entity: EcsEntity,
|
||||
|
@ -21,12 +21,14 @@ use common::{
|
||||
skills::{AxeSkill, BowSkill, HammerSkill, SceptreSkill, Skill, StaffSkill, SwordSkill},
|
||||
AbilityInput, ActiveAbilities, Agent, Alignment, BehaviorCapability, BehaviorState, Body,
|
||||
CharacterAbility, CharacterState, Combo, ControlAction, ControlEvent, Controller, Energy,
|
||||
Health, HealthChange, InputKind, Inventory, InventoryAction, LightEmitter, MountState, Ori,
|
||||
Health, HealthChange, InputKind, Inventory, InventoryAction, LightEmitter, Ori,
|
||||
PhysicsState, Pos, Scale, SkillSet, Stats, UnresolvedChatMsg, UtteranceKind, Vel,
|
||||
},
|
||||
consts::GRAVITY,
|
||||
effect::{BuffEffect, Effect},
|
||||
event::{Emitter, EventBus, ServerEvent},
|
||||
link::Is,
|
||||
mounting::Mount,
|
||||
path::TraversalConfig,
|
||||
resources::{DeltaTime, Time, TimeOfDay},
|
||||
rtsim::{Memory, MemoryItem, RtSimEntity, RtSimEvent},
|
||||
@ -152,7 +154,7 @@ pub struct ReadData<'a> {
|
||||
terrain: ReadExpect<'a, TerrainGrid>,
|
||||
alignments: ReadStorage<'a, Alignment>,
|
||||
bodies: ReadStorage<'a, Body>,
|
||||
mount_states: ReadStorage<'a, MountState>,
|
||||
is_mounts: ReadStorage<'a, Is<Mount>>,
|
||||
time_of_day: Read<'a, TimeOfDay>,
|
||||
light_emitter: ReadStorage<'a, LightEmitter>,
|
||||
#[cfg(feature = "worldgen")]
|
||||
@ -174,6 +176,7 @@ const MAX_FLEE_DIST: f32 = 20.0;
|
||||
const AVG_FOLLOW_DIST: f32 = 6.0;
|
||||
const RETARGETING_THRESHOLD_SECONDS: f64 = 10.0;
|
||||
const HEALING_ITEM_THRESHOLD: f32 = 0.5;
|
||||
const IDLE_HEALING_ITEM_THRESHOLD: f32 = 0.999;
|
||||
const DEFAULT_ATTACK_RANGE: f32 = 2.0;
|
||||
const AWARENESS_INVESTIGATE_THRESHOLD: f32 = 1.0;
|
||||
const AWARENESS_DECREMENT_CONSTANT: f32 = 0.07;
|
||||
@ -224,15 +227,9 @@ impl<'a> System<'a> for Sys {
|
||||
&mut controllers,
|
||||
read_data.light_emitter.maybe(),
|
||||
read_data.groups.maybe(),
|
||||
read_data.mount_states.maybe(),
|
||||
!&read_data.is_mounts,
|
||||
)
|
||||
.par_join()
|
||||
.filter(|(_, _, _, _, _, _, _, _, _, _, _, _, mount_state)| {
|
||||
// Skip mounted entities
|
||||
mount_state
|
||||
.map(|ms| *ms == MountState::Unmounted)
|
||||
.unwrap_or(true)
|
||||
})
|
||||
.for_each_init(
|
||||
|| {
|
||||
prof_span!(guard, "agent rayon job");
|
||||
@ -675,7 +672,7 @@ impl<'a> AgentData<'a> {
|
||||
read_data: &ReadData,
|
||||
event_emitter: &mut Emitter<'_, ServerEvent>,
|
||||
) {
|
||||
if self.damage < HEALING_ITEM_THRESHOLD && self.heal_self(agent, controller) {
|
||||
if self.damage < HEALING_ITEM_THRESHOLD && self.heal_self(agent, controller, false) {
|
||||
agent.action_state.timer = 0.01;
|
||||
return;
|
||||
}
|
||||
@ -813,7 +810,7 @@ impl<'a> AgentData<'a> {
|
||||
}
|
||||
};
|
||||
|
||||
if self.damage < HEALING_ITEM_THRESHOLD && self.heal_self(agent, controller) {
|
||||
if self.damage < IDLE_HEALING_ITEM_THRESHOLD && self.heal_self(agent, controller, true) {
|
||||
agent.action_state.timer = 0.01;
|
||||
return;
|
||||
}
|
||||
@ -1431,53 +1428,57 @@ 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.
|
||||
fn heal_self(&self, _agent: &mut Agent, controller: &mut Controller) -> bool {
|
||||
/// 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: ConsumableKind::Drink,
|
||||
effects,
|
||||
..
|
||||
} = &item.kind
|
||||
{
|
||||
for effect in effects.iter() {
|
||||
use BuffKind::*;
|
||||
match effect {
|
||||
Effect::Health(HealthChange { amount, .. }) => {
|
||||
value += *amount;
|
||||
},
|
||||
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);
|
||||
},
|
||||
_ => {},
|
||||
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 {
|
||||
Effect::Health(HealthChange { amount, .. }) => {
|
||||
value += *amount;
|
||||
},
|
||||
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 as i32
|
||||
};
|
||||
|
||||
let mut consumables: Vec<_> = self
|
||||
let item = self
|
||||
.inventory
|
||||
.slots_with_id()
|
||||
.filter_map(|(id, slot)| match slot {
|
||||
Some(item) if healing_value(item) > 0 => Some((id, item)),
|
||||
_ => None,
|
||||
})
|
||||
.collect();
|
||||
.max_by_key(|(_, item)| {
|
||||
if relaxed {
|
||||
-healing_value(item)
|
||||
} else {
|
||||
healing_value(item)
|
||||
}
|
||||
});
|
||||
|
||||
consumables.sort_by_key(|(_, item)| healing_value(item));
|
||||
|
||||
if let Some((id, _)) = consumables.last() {
|
||||
if let Some((id, _)) = item {
|
||||
use comp::inventory::slot::Slot;
|
||||
controller
|
||||
.actions
|
||||
.push(ControlAction::InventoryAction(InventoryAction::Use(
|
||||
Slot::Inventory(*id),
|
||||
Slot::Inventory(id),
|
||||
)));
|
||||
true
|
||||
} else {
|
||||
|
@ -7,6 +7,8 @@ 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},
|
||||
@ -49,6 +51,7 @@ impl<'a> System<'a> for Sys {
|
||||
Write<'a, DeletedEntities>,
|
||||
Write<'a, Vec<Outcome>>,
|
||||
Read<'a, PlayerPhysicsSettings>,
|
||||
ReadStorage<'a, Is<Rider>>,
|
||||
ReadStorage<'a, Player>,
|
||||
TrackedComps<'a>,
|
||||
ReadTrackers<'a>,
|
||||
@ -83,6 +86,7 @@ impl<'a> System<'a> for Sys {
|
||||
mut deleted_entities,
|
||||
mut outcomes,
|
||||
player_physics_settings,
|
||||
is_rider,
|
||||
players,
|
||||
tracked_comps,
|
||||
trackers,
|
||||
@ -248,7 +252,9 @@ impl<'a> System<'a> for Sys {
|
||||
// Don't send client physics updates about itself unless force update is
|
||||
// set or the client is subject to
|
||||
// server-authoritative physics
|
||||
force_update.is_some() || player_physics_setting.server_authoritative()
|
||||
force_update.is_some()
|
||||
|| player_physics_setting.server_authoritative()
|
||||
|| is_rider.get(entity).is_some()
|
||||
} else if matches!(collider, Some(Collider::Voxel { .. })) {
|
||||
// Things with a voxel collider (airships, etc.) need to have very
|
||||
// stable physics so we always send updated
|
||||
|
@ -7,6 +7,8 @@ use common::{
|
||||
Vel,
|
||||
},
|
||||
event::{EventBus, ServerEvent},
|
||||
link::Is,
|
||||
mounting::Rider,
|
||||
resources::PlayerPhysicsSettings,
|
||||
terrain::TerrainGrid,
|
||||
vol::ReadVol,
|
||||
@ -32,6 +34,7 @@ impl Sys {
|
||||
maybe_presence: &mut Option<&mut Presence>,
|
||||
terrain: &ReadExpect<'_, TerrainGrid>,
|
||||
can_build: &ReadStorage<'_, CanBuild>,
|
||||
is_rider: &ReadStorage<'_, Is<Rider>>,
|
||||
force_updates: &ReadStorage<'_, ForceUpdate>,
|
||||
skill_sets: &mut WriteStorage<'_, SkillSet>,
|
||||
healths: &ReadStorage<'_, Health>,
|
||||
@ -119,6 +122,7 @@ impl Sys {
|
||||
if matches!(presence.kind, PresenceKind::Character(_))
|
||||
&& force_updates.get(entity).is_none()
|
||||
&& healths.get(entity).map_or(true, |h| !h.is_dead)
|
||||
&& is_rider.get(entity).is_none()
|
||||
&& player_physics_setting
|
||||
.as_ref()
|
||||
.map_or(true, |s| s.client_authoritative())
|
||||
@ -307,6 +311,7 @@ impl<'a> System<'a> for Sys {
|
||||
ReadExpect<'a, TerrainGrid>,
|
||||
ReadStorage<'a, CanBuild>,
|
||||
ReadStorage<'a, ForceUpdate>,
|
||||
ReadStorage<'a, Is<Rider>>,
|
||||
WriteStorage<'a, SkillSet>,
|
||||
ReadStorage<'a, Health>,
|
||||
Write<'a, BlockChange>,
|
||||
@ -336,6 +341,7 @@ impl<'a> System<'a> for Sys {
|
||||
terrain,
|
||||
can_build,
|
||||
force_updates,
|
||||
is_rider,
|
||||
mut skill_sets,
|
||||
healths,
|
||||
mut block_changes,
|
||||
@ -372,6 +378,7 @@ impl<'a> System<'a> for Sys {
|
||||
&mut maybe_presence.as_deref_mut(),
|
||||
&terrain,
|
||||
&can_build,
|
||||
&is_rider,
|
||||
&force_updates,
|
||||
&mut skill_sets,
|
||||
&healths,
|
||||
|
@ -2,10 +2,12 @@
|
||||
use common::{
|
||||
comp::{
|
||||
item::{tool::AbilityMap, MaterialStatManifest},
|
||||
ActiveAbilities, Auras, BeamSegment, Body, Buffs, CanBuild, CharacterState, Collider,
|
||||
Combo, Density, Energy, Group, Health, Inventory, Item, LightEmitter, Mass, MountState,
|
||||
Mounting, Ori, Player, Poise, Pos, Scale, Shockwave, SkillSet, Stats, Sticky, Vel,
|
||||
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,
|
||||
},
|
||||
link::Is,
|
||||
mounting::{Mount, Rider},
|
||||
uid::Uid,
|
||||
};
|
||||
use common_ecs::{Job, Origin, Phase, System};
|
||||
@ -56,8 +58,8 @@ pub struct TrackedComps<'a> {
|
||||
pub light_emitter: ReadStorage<'a, LightEmitter>,
|
||||
pub item: ReadStorage<'a, Item>,
|
||||
pub scale: ReadStorage<'a, Scale>,
|
||||
pub mounting: ReadStorage<'a, Mounting>,
|
||||
pub mount_state: ReadStorage<'a, MountState>,
|
||||
pub is_mount: ReadStorage<'a, Is<Mount>>,
|
||||
pub is_rider: ReadStorage<'a, Is<Rider>>,
|
||||
pub group: ReadStorage<'a, Group>,
|
||||
pub mass: ReadStorage<'a, Mass>,
|
||||
pub density: ReadStorage<'a, Density>,
|
||||
@ -67,6 +69,8 @@ pub struct TrackedComps<'a> {
|
||||
pub character_state: ReadStorage<'a, CharacterState>,
|
||||
pub shockwave: ReadStorage<'a, Shockwave>,
|
||||
pub beam_segment: ReadStorage<'a, BeamSegment>,
|
||||
pub alignment: ReadStorage<'a, Alignment>,
|
||||
|
||||
pub ability_map: ReadExpect<'a, AbilityMap>,
|
||||
pub msm: ReadExpect<'a, MaterialStatManifest>,
|
||||
}
|
||||
@ -137,11 +141,11 @@ impl<'a> TrackedComps<'a> {
|
||||
.get(entity)
|
||||
.copied()
|
||||
.map(|c| comps.push(c.into()));
|
||||
self.mounting
|
||||
self.is_mount
|
||||
.get(entity)
|
||||
.cloned()
|
||||
.map(|c| comps.push(c.into()));
|
||||
self.mount_state
|
||||
self.is_rider
|
||||
.get(entity)
|
||||
.cloned()
|
||||
.map(|c| comps.push(c.into()));
|
||||
@ -178,6 +182,10 @@ impl<'a> TrackedComps<'a> {
|
||||
.get(entity)
|
||||
.cloned()
|
||||
.map(|c| comps.push(c.into()));
|
||||
self.alignment
|
||||
.get(entity)
|
||||
.cloned()
|
||||
.map(|c| comps.push(c.into()));
|
||||
// Add untracked comps
|
||||
pos.map(|c| comps.push(c.into()));
|
||||
vel.map(|c| comps.push(c.into()));
|
||||
@ -205,8 +213,8 @@ pub struct ReadTrackers<'a> {
|
||||
pub inventory: ReadExpect<'a, UpdateTracker<Inventory>>,
|
||||
pub item: ReadExpect<'a, UpdateTracker<Item>>,
|
||||
pub scale: ReadExpect<'a, UpdateTracker<Scale>>,
|
||||
pub mounting: ReadExpect<'a, UpdateTracker<Mounting>>,
|
||||
pub mount_state: ReadExpect<'a, UpdateTracker<MountState>>,
|
||||
pub is_mount: ReadExpect<'a, UpdateTracker<Is<Mount>>>,
|
||||
pub is_rider: ReadExpect<'a, UpdateTracker<Is<Rider>>>,
|
||||
pub group: ReadExpect<'a, UpdateTracker<Group>>,
|
||||
pub mass: ReadExpect<'a, UpdateTracker<Mass>>,
|
||||
pub density: ReadExpect<'a, UpdateTracker<Density>>,
|
||||
@ -215,6 +223,7 @@ pub struct ReadTrackers<'a> {
|
||||
pub character_state: ReadExpect<'a, UpdateTracker<CharacterState>>,
|
||||
pub shockwave: ReadExpect<'a, UpdateTracker<Shockwave>>,
|
||||
pub beam_segment: ReadExpect<'a, UpdateTracker<BeamSegment>>,
|
||||
pub alignment: ReadExpect<'a, UpdateTracker<Alignment>>,
|
||||
}
|
||||
impl<'a> ReadTrackers<'a> {
|
||||
pub fn create_sync_packages(
|
||||
@ -251,8 +260,8 @@ impl<'a> ReadTrackers<'a> {
|
||||
)
|
||||
.with_component(&comps.uid, &*self.item, &comps.item, filter)
|
||||
.with_component(&comps.uid, &*self.scale, &comps.scale, filter)
|
||||
.with_component(&comps.uid, &*self.mounting, &comps.mounting, filter)
|
||||
.with_component(&comps.uid, &*self.mount_state, &comps.mount_state, filter)
|
||||
.with_component(&comps.uid, &*self.is_mount, &comps.is_mount, filter)
|
||||
.with_component(&comps.uid, &*self.is_rider, &comps.is_rider, filter)
|
||||
.with_component(&comps.uid, &*self.group, &comps.group, filter)
|
||||
.with_component(&comps.uid, &*self.mass, &comps.mass, filter)
|
||||
.with_component(&comps.uid, &*self.density, &comps.density, filter)
|
||||
@ -266,7 +275,8 @@ impl<'a> ReadTrackers<'a> {
|
||||
filter,
|
||||
)
|
||||
.with_component(&comps.uid, &*self.shockwave, &comps.shockwave, filter)
|
||||
.with_component(&comps.uid, &*self.beam_segment, &comps.beam_segment, filter);
|
||||
.with_component(&comps.uid, &*self.beam_segment, &comps.beam_segment, filter)
|
||||
.with_component(&comps.uid, &*self.alignment, &comps.alignment, filter);
|
||||
|
||||
(entity_sync_package, comp_sync_package)
|
||||
}
|
||||
@ -290,8 +300,8 @@ pub struct WriteTrackers<'a> {
|
||||
light_emitter: WriteExpect<'a, UpdateTracker<LightEmitter>>,
|
||||
item: WriteExpect<'a, UpdateTracker<Item>>,
|
||||
scale: WriteExpect<'a, UpdateTracker<Scale>>,
|
||||
mounting: WriteExpect<'a, UpdateTracker<Mounting>>,
|
||||
mount_state: WriteExpect<'a, UpdateTracker<MountState>>,
|
||||
is_mounts: WriteExpect<'a, UpdateTracker<Is<Mount>>>,
|
||||
is_riders: WriteExpect<'a, UpdateTracker<Is<Rider>>>,
|
||||
group: WriteExpect<'a, UpdateTracker<Group>>,
|
||||
mass: WriteExpect<'a, UpdateTracker<Mass>>,
|
||||
density: WriteExpect<'a, UpdateTracker<Density>>,
|
||||
@ -301,6 +311,7 @@ pub struct WriteTrackers<'a> {
|
||||
character_state: WriteExpect<'a, UpdateTracker<CharacterState>>,
|
||||
shockwave: WriteExpect<'a, UpdateTracker<Shockwave>>,
|
||||
beam: WriteExpect<'a, UpdateTracker<BeamSegment>>,
|
||||
alignment: WriteExpect<'a, UpdateTracker<Alignment>>,
|
||||
}
|
||||
|
||||
fn record_changes(comps: &TrackedComps, trackers: &mut WriteTrackers) {
|
||||
@ -323,8 +334,8 @@ fn record_changes(comps: &TrackedComps, trackers: &mut WriteTrackers) {
|
||||
trackers.light_emitter.record_changes(&comps.light_emitter);
|
||||
trackers.item.record_changes(&comps.item);
|
||||
trackers.scale.record_changes(&comps.scale);
|
||||
trackers.mounting.record_changes(&comps.mounting);
|
||||
trackers.mount_state.record_changes(&comps.mount_state);
|
||||
trackers.is_mounts.record_changes(&comps.is_mount);
|
||||
trackers.is_riders.record_changes(&comps.is_rider);
|
||||
trackers.group.record_changes(&comps.group);
|
||||
trackers.mass.record_changes(&comps.mass);
|
||||
trackers.density.record_changes(&comps.density);
|
||||
@ -336,6 +347,7 @@ fn record_changes(comps: &TrackedComps, trackers: &mut WriteTrackers) {
|
||||
.record_changes(&comps.character_state);
|
||||
trackers.shockwave.record_changes(&comps.shockwave);
|
||||
trackers.beam.record_changes(&comps.beam_segment);
|
||||
trackers.alignment.record_changes(&comps.alignment);
|
||||
// Debug how many updates are being sent
|
||||
/*
|
||||
macro_rules! log_counts {
|
||||
@ -366,8 +378,8 @@ fn record_changes(comps: &TrackedComps, trackers: &mut WriteTrackers) {
|
||||
log_counts!(light_emitter, "Light emitters");
|
||||
log_counts!(item, "Items");
|
||||
log_counts!(scale, "Scales");
|
||||
log_counts!(mounting, "Mountings");
|
||||
log_counts!(mount_state, "Mount States");
|
||||
log_counts!(is_mounts, "mounts");
|
||||
log_counts!(is_riders, "riders");
|
||||
log_counts!(mass, "Masses");
|
||||
log_counts!(mass, "Densities");
|
||||
log_counts!(collider, "Colliders");
|
||||
@ -376,6 +388,7 @@ fn record_changes(comps: &TrackedComps, trackers: &mut WriteTrackers) {
|
||||
log_counts!(character_state, "Character States");
|
||||
log_counts!(shockwave, "Shockwaves");
|
||||
log_counts!(beam, "Beams");
|
||||
log_counts!(alignment, "Alignments");
|
||||
*/
|
||||
}
|
||||
|
||||
@ -396,8 +409,8 @@ pub fn register_trackers(world: &mut World) {
|
||||
world.register_tracker::<LightEmitter>();
|
||||
world.register_tracker::<Item>();
|
||||
world.register_tracker::<Scale>();
|
||||
world.register_tracker::<Mounting>();
|
||||
world.register_tracker::<MountState>();
|
||||
world.register_tracker::<Is<Mount>>();
|
||||
world.register_tracker::<Is<Rider>>();
|
||||
world.register_tracker::<Group>();
|
||||
world.register_tracker::<Mass>();
|
||||
world.register_tracker::<Density>();
|
||||
@ -407,6 +420,7 @@ pub fn register_trackers(world: &mut World) {
|
||||
world.register_tracker::<CharacterState>();
|
||||
world.register_tracker::<Shockwave>();
|
||||
world.register_tracker::<BeamSegment>();
|
||||
world.register_tracker::<Alignment>();
|
||||
}
|
||||
|
||||
/// Deleted entities grouped by region
|
||||
|
@ -31,10 +31,11 @@ tracy = ["profiling", "profiling/profile-with-tracy", "common-frontend/tracy", "
|
||||
tracy-memory = ["tracy"] # enables heap profiling with tracy
|
||||
plugins = ["client/plugins"]
|
||||
egui-ui = ["voxygen-egui", "egui", "egui_wgpu_backend", "egui_winit_platform"]
|
||||
shaderc-from-source = ["shaderc/build-from-source"]
|
||||
|
||||
# We don't ship egui with published release builds so a separate feature is required that excludes it.
|
||||
default-publish = ["singleplayer", "native-dialog", "plugins", "simd"]
|
||||
default = ["default-publish", "egui-ui", "hot-reloading"]
|
||||
default = ["default-publish", "egui-ui", "hot-reloading", "shaderc-from-source"]
|
||||
|
||||
[dependencies]
|
||||
client = {package = "veloren-client", path = "../client"}
|
||||
|
@ -132,7 +132,7 @@ impl Skeleton for BipedLargeSkeleton {
|
||||
// TODO: see quadruped_medium for how to animate this
|
||||
mount_bone: Transform {
|
||||
position: common::comp::Body::BipedLarge(body)
|
||||
.mountee_offset()
|
||||
.mount_offset()
|
||||
.into_tuple()
|
||||
.into(),
|
||||
..Default::default()
|
||||
|
@ -73,7 +73,7 @@ impl Skeleton for BipedSmallSkeleton {
|
||||
// TODO: see quadruped_medium for how to animate this
|
||||
mount_bone: Transform {
|
||||
position: common::comp::Body::BipedSmall(body)
|
||||
.mountee_offset()
|
||||
.mount_offset()
|
||||
.into_tuple()
|
||||
.into(),
|
||||
..Default::default()
|
||||
|
@ -101,7 +101,7 @@ impl Skeleton for BirdLargeSkeleton {
|
||||
// TODO: see quadruped_medium for how to animate this
|
||||
mount_bone: Transform {
|
||||
position: common::comp::Body::BirdLarge(body)
|
||||
.mountee_offset()
|
||||
.mount_offset()
|
||||
.into_tuple()
|
||||
.into(),
|
||||
..Default::default()
|
||||
|
@ -55,7 +55,7 @@ impl Skeleton for BirdMediumSkeleton {
|
||||
// TODO: see quadruped_medium for how to animate this
|
||||
mount_bone: Transform {
|
||||
position: common::comp::Body::BirdMedium(body)
|
||||
.mountee_offset()
|
||||
.mount_offset()
|
||||
.into_tuple()
|
||||
.into(),
|
||||
..Default::default()
|
||||
|
@ -149,7 +149,7 @@ impl Skeleton for CharacterSkeleton {
|
||||
// TODO: see quadruped_medium for how to animate this
|
||||
mount_bone: Transform {
|
||||
position: common::comp::Body::Humanoid(body)
|
||||
.mountee_offset()
|
||||
.mount_offset()
|
||||
.into_tuple()
|
||||
.into(),
|
||||
..Default::default()
|
||||
|
@ -43,10 +43,6 @@ impl Animation for MountAnimation {
|
||||
) -> Self::Skeleton {
|
||||
let mut next = (*skeleton).clone();
|
||||
|
||||
let slow = (anim_time * 1.0).sin();
|
||||
let slowa = (anim_time * 1.0 + PI / 2.0).sin();
|
||||
let stop = (anim_time * 3.0).min(PI / 2.0).sin();
|
||||
|
||||
let head_look = Vec2::new(
|
||||
(global_time * 0.05 + anim_time / 15.0)
|
||||
.floor()
|
||||
@ -65,7 +61,7 @@ impl Animation for MountAnimation {
|
||||
let speed = (Vec2::<f32>::from(velocity).magnitude()).min(24.0);
|
||||
let canceler = (speed / 24.0).powf(0.6);
|
||||
let _x_tilt = avg_vel.z.atan2(avg_vel.xy().magnitude()) * canceler;
|
||||
let _tilt = if ::vek::Vec2::new(ori, last_ori)
|
||||
let tilt = if ::vek::Vec2::new(ori, last_ori)
|
||||
.map(|o| o.magnitude_squared())
|
||||
.map(|m| m > 0.001 && m.is_finite())
|
||||
.reduce_and()
|
||||
@ -82,49 +78,41 @@ impl Animation for MountAnimation {
|
||||
next.hand_l.scale = Vec3::one() * 1.04;
|
||||
next.hand_r.scale = Vec3::one() * 1.04;
|
||||
next.back.scale = Vec3::one() * 1.02;
|
||||
next.belt.scale = Vec3::one() * 1.02;
|
||||
next.hold.scale = Vec3::one() * 0.0;
|
||||
next.lantern.scale = Vec3::one() * 0.65;
|
||||
next.shoulder_l.scale = Vec3::one() * 1.1;
|
||||
next.shoulder_r.scale = Vec3::one() * 1.1;
|
||||
|
||||
next.head.position = Vec3::new(0.0, s_a.head.0, s_a.head.1 + slow * 0.1 + stop * -0.8);
|
||||
next.head.orientation = Quaternion::rotation_z(head_look.x + slow * 0.2 - slow * 0.1)
|
||||
* Quaternion::rotation_x((0.4 + slowa * -0.1 + slow * 0.1 + head_look.y).abs());
|
||||
next.head.position = Vec3::new(0.0, s_a.head.0, s_a.head.1);
|
||||
next.head.orientation = Quaternion::rotation_z(head_look.x + tilt * -2.0)
|
||||
* Quaternion::rotation_x((0.35 + head_look.y + tilt.abs() * 1.2).abs());
|
||||
|
||||
next.chest.position = Vec3::new(0.0, s_a.chest.0, s_a.chest.1);
|
||||
next.chest.orientation = Quaternion::rotation_x(-0.6 + stop * 0.15);
|
||||
next.chest.orientation =
|
||||
Quaternion::rotation_x(-0.4 + tilt.abs() * -1.5) * Quaternion::rotation_y(tilt * 2.0);
|
||||
|
||||
next.belt.position = Vec3::new(0.0, s_a.belt.0 + stop * 1.2, s_a.belt.1);
|
||||
next.belt.orientation = Quaternion::rotation_x(stop * 0.3);
|
||||
next.belt.position = Vec3::new(0.0, s_a.belt.0 + 0.5, s_a.belt.1 + 0.5);
|
||||
next.belt.orientation = Quaternion::rotation_x(0.2) * Quaternion::rotation_y(tilt * -0.5);
|
||||
|
||||
next.back.position = Vec3::new(0.0, s_a.back.0, s_a.back.1);
|
||||
|
||||
next.shorts.position = Vec3::new(0.0, s_a.shorts.0 + stop * 2.5, s_a.shorts.1 + stop * 0.6);
|
||||
next.shorts.orientation = Quaternion::rotation_x(stop * 0.6);
|
||||
next.shorts.position = Vec3::new(0.0, s_a.shorts.0 + 1.0, s_a.shorts.1 + 1.0);
|
||||
next.shorts.orientation = Quaternion::rotation_x(0.3) * Quaternion::rotation_y(tilt * -1.0);
|
||||
|
||||
next.hand_l.position = Vec3::new(
|
||||
-s_a.hand.0 + 4.0,
|
||||
s_a.hand.1 + slowa * 0.15 + stop * 8.0,
|
||||
s_a.hand.2 + slow * 0.7 + stop * 4.0,
|
||||
);
|
||||
next.hand_l.position = Vec3::new(-s_a.hand.0 + 3.0, s_a.hand.1 + 9.0, s_a.hand.2 + 4.0);
|
||||
next.hand_l.orientation =
|
||||
Quaternion::rotation_x(PI / 2.0) * Quaternion::rotation_z(-PI / 2.0);
|
||||
Quaternion::rotation_x(PI / 2.0) * Quaternion::rotation_z(-PI / 2.0 + 0.5);
|
||||
|
||||
next.hand_r.position = Vec3::new(
|
||||
s_a.hand.0 - 4.0,
|
||||
s_a.hand.1 + slowa * 0.15 + stop * 8.0,
|
||||
s_a.hand.2 + slow * 0.7 + stop * 4.0,
|
||||
);
|
||||
next.hand_r.position = Vec3::new(s_a.hand.0 - 3.0, s_a.hand.1 + 9.0, s_a.hand.2 + 4.0);
|
||||
next.hand_r.orientation =
|
||||
Quaternion::rotation_x(PI / 2.0) * Quaternion::rotation_z(PI / 2.0);
|
||||
Quaternion::rotation_x(PI / 2.0) * Quaternion::rotation_z(PI / 2.0 - 0.5);
|
||||
|
||||
next.foot_l.position = Vec3::new(-s_a.foot.0 - 2.0, 4.0 + s_a.foot.1, s_a.foot.2);
|
||||
next.foot_l.orientation = Quaternion::rotation_x(slow * 0.1 + stop * 0.4 + slow * 0.1)
|
||||
* Quaternion::rotation_y(0.5);
|
||||
next.foot_l.orientation = Quaternion::rotation_x(0.5) * Quaternion::rotation_y(0.5);
|
||||
|
||||
next.foot_r.position = Vec3::new(s_a.foot.0 + 2.0, 4.0 + s_a.foot.1, s_a.foot.2);
|
||||
next.foot_r.orientation = Quaternion::rotation_x(slowa * 0.1 + stop * 0.4 + slowa * 0.1)
|
||||
* Quaternion::rotation_y(-0.5);
|
||||
next.foot_r.orientation = Quaternion::rotation_x(0.5) * Quaternion::rotation_y(-0.5);
|
||||
|
||||
next.shoulder_l.position = Vec3::new(-s_a.shoulder.0, s_a.shoulder.1, s_a.shoulder.2);
|
||||
next.shoulder_l.orientation = Quaternion::rotation_x(0.0);
|
||||
@ -132,8 +120,6 @@ impl Animation for MountAnimation {
|
||||
next.shoulder_r.position = Vec3::new(s_a.shoulder.0, s_a.shoulder.1, s_a.shoulder.2);
|
||||
next.shoulder_r.orientation = Quaternion::rotation_x(0.0);
|
||||
|
||||
next.torso.position = Vec3::new(0.0, 0.0, stop * -1.76);
|
||||
|
||||
if skeleton.holding_lantern {
|
||||
next.hand_r.position = Vec3::new(
|
||||
s_a.hand.0 + 1.0 - head_look.x * 8.0,
|
||||
@ -152,7 +138,9 @@ impl Animation for MountAnimation {
|
||||
next.lantern.orientation = next.hand_r.orientation.inverse()
|
||||
* Quaternion::rotation_x(fast * 0.1)
|
||||
* Quaternion::rotation_y(fast2 * 0.1);
|
||||
}
|
||||
} else {
|
||||
next.lantern.position = Vec3::new(s_a.lantern.0, s_a.lantern.1, s_a.lantern.2);
|
||||
};
|
||||
|
||||
next.glider.position = Vec3::new(0.0, 0.0, 10.0);
|
||||
next.glider.scale = Vec3::one() * 0.0;
|
||||
|
@ -5,6 +5,7 @@ use super::{
|
||||
use common::{
|
||||
comp::item::{Hands, ToolKind},
|
||||
states::utils::{AbilityInfo, StageSection},
|
||||
util::Dir,
|
||||
};
|
||||
use core::f32::consts::PI;
|
||||
|
||||
@ -16,6 +17,8 @@ impl Animation for RepeaterAnimation {
|
||||
Option<AbilityInfo>,
|
||||
(Option<Hands>, Option<Hands>),
|
||||
Vec3<f32>,
|
||||
Dir,
|
||||
Vec3<f32>,
|
||||
f32,
|
||||
Option<StageSection>,
|
||||
);
|
||||
@ -27,7 +30,7 @@ impl Animation for RepeaterAnimation {
|
||||
#[cfg_attr(feature = "be-dyn-lib", export_name = "character_repeater")]
|
||||
fn update_skeleton_inner<'a>(
|
||||
skeleton: &Self::Skeleton,
|
||||
(ability_info, hands, velocity, _global_time, stage_section): Self::Dependency<'a>,
|
||||
(ability_info, hands, orientation,look_dir, velocity, _global_time, stage_section): Self::Dependency<'a>,
|
||||
anim_time: f32,
|
||||
rate: &mut f32,
|
||||
s_a: &SkeletonAttr,
|
||||
@ -35,7 +38,9 @@ impl Animation for RepeaterAnimation {
|
||||
*rate = 1.0;
|
||||
let mut next = (*skeleton).clone();
|
||||
let speed = Vec2::<f32>::from(velocity).magnitude();
|
||||
|
||||
let ori_angle = orientation.y.atan2(orientation.x);
|
||||
let lookdir_angle = look_dir.y.atan2(look_dir.x);
|
||||
let swivel = lookdir_angle - ori_angle;
|
||||
let (move1base, move2base, move3base, move4) = match stage_section {
|
||||
Some(StageSection::Movement) => (anim_time, 0.0, 0.0, 0.0),
|
||||
Some(StageSection::Buildup) => (1.0, anim_time, 0.0, 0.0),
|
||||
@ -60,6 +65,10 @@ impl Animation for RepeaterAnimation {
|
||||
next.hold.position = Vec3::new(0.0, -1.0 + move3 * 2.0, -5.2);
|
||||
next.hold.orientation = Quaternion::rotation_x(-PI / 2.0) * Quaternion::rotation_z(0.0);
|
||||
next.hold.scale = Vec3::one() * (1.0);
|
||||
|
||||
next.chest.orientation = Quaternion::rotation_z(swivel * 0.8);
|
||||
next.torso.orientation = Quaternion::rotation_z(swivel * 0.2);
|
||||
|
||||
if speed < 0.5 {
|
||||
next.foot_l.position = Vec3::new(
|
||||
-s_a.foot.0 + move1 * -0.75,
|
||||
|
@ -46,7 +46,7 @@ impl Animation for ShootAnimation {
|
||||
s_a: &SkeletonAttr,
|
||||
) -> Self::Skeleton {
|
||||
*rate = 1.0;
|
||||
let speed = Vec2::<f32>::from(velocity).magnitude();
|
||||
let _speed = Vec2::<f32>::from(velocity).magnitude();
|
||||
|
||||
let mut next = (*skeleton).clone();
|
||||
|
||||
@ -64,7 +64,9 @@ impl Animation for ShootAnimation {
|
||||
} else {
|
||||
0.0
|
||||
} * 1.3;
|
||||
|
||||
let ori_angle = orientation.y.atan2(orientation.x);
|
||||
let lookdir_angle = look_dir.y.atan2(look_dir.x);
|
||||
let swivel = lookdir_angle - ori_angle;
|
||||
match ability_info.and_then(|a| a.tool) {
|
||||
Some(ToolKind::Staff) | Some(ToolKind::Sceptre) => {
|
||||
let (move1, move2, move3) = match stage_section {
|
||||
@ -96,29 +98,14 @@ impl Animation for ShootAnimation {
|
||||
* Quaternion::rotation_z(
|
||||
s_a.stc.5 - (0.2 + move1 * -0.5 + move2 * 0.8) * (1.0 - move3),
|
||||
);
|
||||
next.chest.orientation =
|
||||
Quaternion::rotation_z((move1 * 0.3 + move2 * 0.2) * (1.0 - move3));
|
||||
|
||||
next.head.position = Vec3::new(0.0, s_a.head.0, s_a.head.1);
|
||||
next.head.orientation = Quaternion::rotation_x(look_dir.z * 0.7)
|
||||
* Quaternion::rotation_z(
|
||||
tilt * -2.5 + (move1 * -0.2 + move2 * -0.4) * (1.0 - move3),
|
||||
);
|
||||
|
||||
if speed < 0.5 {
|
||||
next.belt.orientation =
|
||||
Quaternion::rotation_x(0.07) * Quaternion::rotation_z(0.0);
|
||||
|
||||
next.shorts.orientation =
|
||||
Quaternion::rotation_x(0.08) * Quaternion::rotation_z(0.0);
|
||||
|
||||
next.foot_l.position = Vec3::new(-s_a.foot.0, s_a.foot.1 - 5.0, s_a.foot.2);
|
||||
next.foot_l.orientation = Quaternion::rotation_x(-0.5);
|
||||
|
||||
next.foot_r.position = Vec3::new(s_a.foot.0, s_a.foot.1 + 3.0, s_a.foot.2);
|
||||
next.foot_r.orientation =
|
||||
Quaternion::rotation_x(0.5) * Quaternion::rotation_z(0.3);
|
||||
} else {
|
||||
};
|
||||
next.chest.orientation = Quaternion::rotation_z(swivel * 0.8);
|
||||
next.torso.orientation = Quaternion::rotation_z(swivel * 0.2);
|
||||
},
|
||||
Some(ToolKind::Bow) => {
|
||||
let (_move1, move2, _move3) = match stage_section {
|
||||
@ -148,28 +135,18 @@ impl Animation for ShootAnimation {
|
||||
s_a.bc.1 + 2.0 + (look_dir.z * -5.0).min(-2.0) + move2 * -1.0,
|
||||
s_a.bc.2 + 8.0 + (look_dir.z * 15.0).max(-8.0),
|
||||
);
|
||||
next.control.orientation = Quaternion::rotation_x(look_dir.z + move2 * -0.0)
|
||||
* Quaternion::rotation_y(-look_dir.z + s_a.bc.4 - 1.25 + move2 * -0.0)
|
||||
next.control.orientation = Quaternion::rotation_x(look_dir.z)
|
||||
* Quaternion::rotation_y(-look_dir.z + s_a.bc.4 - 1.25)
|
||||
* Quaternion::rotation_z(s_a.bc.5 - 0.2 + move2 * -0.1);
|
||||
next.chest.orientation = Quaternion::rotation_z(0.8 + move2 * 0.5);
|
||||
next.head.position = Vec3::new(0.0 - 2.0, s_a.head.0, s_a.head.1);
|
||||
|
||||
next.head.orientation = Quaternion::rotation_x(look_dir.z * 0.7)
|
||||
* Quaternion::rotation_z(tilt * -2.5 - 0.5 + (move2 * -0.2).sin());
|
||||
next.chest.orientation = Quaternion::rotation_z(0.8 + move2 * 0.2);
|
||||
next.belt.orientation = Quaternion::rotation_z(move2 * 0.3);
|
||||
next.shorts.orientation = Quaternion::rotation_z(move2 * 0.5);
|
||||
next.head.position = Vec3::new(0.0, s_a.head.0, s_a.head.1);
|
||||
|
||||
next.head.orientation =
|
||||
Quaternion::rotation_x(look_dir.z * 0.7) * Quaternion::rotation_z(tilt * -0.0);
|
||||
next.chest.orientation = Quaternion::rotation_z(swivel * 0.8 + 0.8 + move2 * 0.5);
|
||||
next.torso.orientation = Quaternion::rotation_z(swivel * 0.2);
|
||||
|
||||
next.shoulder_l.orientation = Quaternion::rotation_x(move2 * 0.5);
|
||||
|
||||
if speed < 0.5 {
|
||||
next.foot_l.position = Vec3::new(-s_a.foot.0, s_a.foot.1 - 5.0, s_a.foot.2);
|
||||
next.foot_l.orientation = Quaternion::rotation_x(-0.5);
|
||||
|
||||
next.foot_r.position = Vec3::new(s_a.foot.0, s_a.foot.1 + 3.0, s_a.foot.2);
|
||||
next.foot_r.orientation =
|
||||
Quaternion::rotation_x(0.5) * Quaternion::rotation_z(0.3);
|
||||
} else {
|
||||
};
|
||||
},
|
||||
_ => {},
|
||||
}
|
||||
|
@ -76,7 +76,7 @@ impl Skeleton for DragonSkeleton {
|
||||
// TODO: see quadruped_medium for how to animate this
|
||||
mount_bone: Transform {
|
||||
position: common::comp::Body::Dragon(body)
|
||||
.mountee_offset()
|
||||
.mount_offset()
|
||||
.into_tuple()
|
||||
.into(),
|
||||
..Default::default()
|
||||
|
@ -55,7 +55,7 @@ impl Skeleton for FishMediumSkeleton {
|
||||
// TODO: see quadruped_medium for how to animate this
|
||||
mount_bone: Transform {
|
||||
position: common::comp::Body::FishMedium(body)
|
||||
.mountee_offset()
|
||||
.mount_offset()
|
||||
.into_tuple()
|
||||
.into(),
|
||||
..Default::default()
|
||||
|
@ -46,7 +46,7 @@ impl Skeleton for FishSmallSkeleton {
|
||||
// TODO: see quadruped_medium for how to animate this
|
||||
mount_bone: Transform {
|
||||
position: common::comp::Body::FishSmall(body)
|
||||
.mountee_offset()
|
||||
.mount_offset()
|
||||
.into_tuple()
|
||||
.into(),
|
||||
..Default::default()
|
||||
|
@ -78,7 +78,7 @@ impl Skeleton for GolemSkeleton {
|
||||
// TODO: see quadruped_medium for how to animate this
|
||||
mount_bone: Transform {
|
||||
position: common::comp::Body::Golem(body)
|
||||
.mountee_offset()
|
||||
.mount_offset()
|
||||
.into_tuple()
|
||||
.into(),
|
||||
..Default::default()
|
||||
|
@ -44,7 +44,7 @@ impl Skeleton for ObjectSkeleton {
|
||||
// TODO: see quadruped_medium for how to animate this
|
||||
mount_bone: Transform {
|
||||
position: common::comp::Body::Object(body)
|
||||
.mountee_offset()
|
||||
.mount_offset()
|
||||
.into_tuple()
|
||||
.into(),
|
||||
..Default::default()
|
||||
|
@ -33,6 +33,7 @@ skeleton_impls!(struct QuadrupedLowSkeleton {
|
||||
+ foot_fr,
|
||||
+ foot_bl,
|
||||
+ foot_br,
|
||||
mount,
|
||||
});
|
||||
|
||||
impl Skeleton for QuadrupedLowSkeleton {
|
||||
@ -69,15 +70,32 @@ impl Skeleton for QuadrupedLowSkeleton {
|
||||
make_bone(chest_mat * Mat4::<f32>::from(self.foot_bl)),
|
||||
make_bone(chest_mat * Mat4::<f32>::from(self.foot_br)),
|
||||
];
|
||||
//let (mount_bone_mat, mount_bone_ori) = (chest_mat, self.chest.orientation);
|
||||
// Offset from the mounted bone's origin.
|
||||
// Note: This could be its own bone if we need to animate it independently.
|
||||
|
||||
// NOTE: We apply the ori from base_mat externally so we don't need to worry
|
||||
// about it here for now.
|
||||
|
||||
use comp::quadruped_low::Species::*;
|
||||
let (mount_bone_mat, mount_bone_ori) = match (body.species, body.body_type) {
|
||||
(Maneater, _) => (
|
||||
head_upper_mat,
|
||||
self.chest.orientation * self.head_lower.orientation * self.head_upper.orientation,
|
||||
),
|
||||
_ => (chest_mat, self.chest.orientation),
|
||||
};
|
||||
let mount_position = (mount_bone_mat * Vec4::from_point(mount_point(&body)))
|
||||
.homogenized()
|
||||
.xyz();
|
||||
let mount_orientation = mount_bone_ori;
|
||||
|
||||
Offsets {
|
||||
lantern: None,
|
||||
// TODO: see quadruped_medium for how to animate this
|
||||
mount_bone: Transform {
|
||||
position: common::comp::Body::QuadrupedLow(body)
|
||||
.mountee_offset()
|
||||
.into_tuple()
|
||||
.into(),
|
||||
..Default::default()
|
||||
position: mount_position,
|
||||
orientation: mount_orientation,
|
||||
scale: Vec3::one(),
|
||||
},
|
||||
}
|
||||
}
|
||||
@ -311,3 +329,25 @@ impl<'a> From<&'a Body> for SkeletonAttr {
|
||||
}
|
||||
}
|
||||
}
|
||||
fn mount_point(body: &Body) -> Vec3<f32> {
|
||||
use comp::quadruped_low::{BodyType::*, Species::*};
|
||||
match (body.species, body.body_type) {
|
||||
(Crocodile, _) => (0.0, 4.5, -2.0),
|
||||
(Alligator, _) => (0.0, 4.25, -2.0),
|
||||
(Salamander, Male) => (0.0, 5.0, -1.0),
|
||||
(Salamander, Female) => (0.0, 5.0, -1.0),
|
||||
(Monitor, _) => (0.0, 2.0, -2.0),
|
||||
(Asp, _) => (0.0, 2.0, 0.0),
|
||||
(Tortoise, _) => (0.0, -7.0, -1.0),
|
||||
(Rocksnapper, _) => (0.0, -7.0, 4.5),
|
||||
(Pangolin, _) => (0.0, -6.5, -2.0),
|
||||
(Maneater, _) => (0.0, 4.0, -11.5),
|
||||
(Sandshark, _) => (0.0, -4.0, -2.0),
|
||||
(Hakulaq, _) => (0.0, 4.0, -4.5),
|
||||
(Lavadrake, _) => (0.0, 2.0, -2.5),
|
||||
(Icedrake, _) => (0.0, -8.0, 2.5),
|
||||
(Basilisk, _) => (0.0, -2.0, 2.0),
|
||||
(Deadwood, _) => (0.0, -2.0, -3.0),
|
||||
}
|
||||
.into()
|
||||
}
|
||||
|
@ -6,7 +6,7 @@ use super::{
|
||||
pub struct JumpAnimation;
|
||||
|
||||
impl Animation for JumpAnimation {
|
||||
type Dependency<'a> = f32;
|
||||
type Dependency<'a> = (f32, Vec3<f32>, Vec3<f32>);
|
||||
type Skeleton = QuadrupedMediumSkeleton;
|
||||
|
||||
#[cfg(feature = "use-dyn-lib")]
|
||||
@ -15,7 +15,7 @@ impl Animation for JumpAnimation {
|
||||
#[cfg_attr(feature = "be-dyn-lib", export_name = "quadruped_medium_jump")]
|
||||
fn update_skeleton_inner<'a>(
|
||||
skeleton: &Self::Skeleton,
|
||||
_global_time: Self::Dependency<'a>,
|
||||
(_global_time, velocity, avg_vel): Self::Dependency<'a>,
|
||||
_anim_time: f32,
|
||||
_rate: &mut f32,
|
||||
s_a: &SkeletonAttr,
|
||||
@ -33,38 +33,61 @@ impl Animation for JumpAnimation {
|
||||
next.foot_bl.scale = Vec3::one() * 0.96;
|
||||
next.foot_br.scale = Vec3::one() * 0.96;
|
||||
next.ears.scale = Vec3::one() * 1.02;
|
||||
let speed = Vec2::<f32>::from(velocity).magnitude();
|
||||
let velocityalt = speed.max(3.0);
|
||||
let normalize = velocityalt / 22.0;
|
||||
|
||||
let x_tilt = (avg_vel.z.atan2(avg_vel.xy().magnitude()) * normalize).max(-0.28);
|
||||
let x_tilt = if velocityalt < 3.5 {
|
||||
x_tilt.abs()
|
||||
} else {
|
||||
x_tilt
|
||||
};
|
||||
|
||||
next.head.position = Vec3::new(0.0, s_a.head.0, s_a.head.1);
|
||||
next.head.orientation = Quaternion::rotation_x(x_tilt * -0.5);
|
||||
|
||||
next.neck.position = Vec3::new(0.0, s_a.neck.0, s_a.neck.1);
|
||||
next.neck.orientation = Quaternion::rotation_x(x_tilt * -1.0);
|
||||
|
||||
next.jaw.position = Vec3::new(0.0, s_a.jaw.0, s_a.jaw.1);
|
||||
next.jaw.orientation = Quaternion::rotation_x(0.0);
|
||||
|
||||
next.tail.position = Vec3::new(0.0, s_a.tail.0, s_a.tail.1);
|
||||
next.tail.orientation = Quaternion::rotation_x(-0.6 * normalize + x_tilt * 2.0);
|
||||
|
||||
next.torso_front.position = Vec3::new(0.0, s_a.torso_front.0, s_a.torso_front.1);
|
||||
next.torso_front.orientation = Quaternion::rotation_y(0.0);
|
||||
next.torso_front.orientation = Quaternion::rotation_x(x_tilt * 3.5);
|
||||
|
||||
next.torso_back.position = Vec3::new(0.0, s_a.torso_back.0, s_a.torso_back.1);
|
||||
next.torso_back.orientation = Quaternion::rotation_x(x_tilt * -1.2);
|
||||
|
||||
next.ears.position = Vec3::new(0.0, s_a.ears.0, s_a.ears.1);
|
||||
next.ears.orientation = Quaternion::rotation_x(x_tilt * 1.5);
|
||||
|
||||
next.leg_fl.position = Vec3::new(-s_a.leg_f.0, s_a.leg_f.1, s_a.leg_f.2);
|
||||
next.leg_fl.orientation = Quaternion::rotation_x(1.2 * normalize + x_tilt.abs() * 0.8);
|
||||
|
||||
next.leg_fr.position = Vec3::new(s_a.leg_f.0, s_a.leg_f.1, s_a.leg_f.2);
|
||||
next.leg_fr.orientation = Quaternion::rotation_x(1.2 * normalize + x_tilt.abs() * 0.8);
|
||||
|
||||
next.leg_bl.position = Vec3::new(-s_a.leg_b.0, s_a.leg_b.1, s_a.leg_b.2);
|
||||
next.leg_bl.orientation = Quaternion::rotation_x(-0.8 * normalize + x_tilt * -0.8);
|
||||
|
||||
next.leg_br.position = Vec3::new(s_a.leg_b.0, s_a.leg_b.1, s_a.leg_b.2);
|
||||
next.leg_br.orientation = Quaternion::rotation_x(-0.8 * normalize + x_tilt * -0.8);
|
||||
|
||||
next.foot_fl.position = Vec3::new(-s_a.feet_f.0, s_a.feet_f.1, s_a.feet_f.2);
|
||||
next.foot_fl.orientation = Quaternion::rotation_x(-0.4 * normalize + x_tilt * -0.4);
|
||||
|
||||
next.foot_fr.position = Vec3::new(s_a.feet_f.0, s_a.feet_f.1, s_a.feet_f.2);
|
||||
next.foot_fr.orientation = Quaternion::rotation_x(-0.4 * normalize + x_tilt * -0.4);
|
||||
|
||||
next.foot_bl.position = Vec3::new(-s_a.feet_b.0, s_a.feet_b.1, s_a.feet_b.2);
|
||||
next.foot_bl.orientation = Quaternion::rotation_x(-0.4 * normalize + x_tilt * -0.4);
|
||||
|
||||
next.foot_br.position = Vec3::new(s_a.feet_b.0, s_a.feet_b.1, s_a.feet_b.2);
|
||||
next.foot_br.orientation = Quaternion::rotation_x(-0.4 * normalize + x_tilt * -0.4);
|
||||
|
||||
next
|
||||
}
|
||||
|
@ -701,42 +701,42 @@ impl<'a> From<&'a Body> for SkeletonAttr {
|
||||
fn mount_point(body: &Body) -> Vec3<f32> {
|
||||
use comp::quadruped_medium::{BodyType::*, Species::*};
|
||||
match (body.species, body.body_type) {
|
||||
(Grolgar, _) => (0.0, -6.0, 6.0),
|
||||
(Saber, _) => (0.0, -12.0, 4.0),
|
||||
(Tuskram, _) => (0.0, -17.0, 2.0),
|
||||
(Lion, _) => (0.0, -8.0, 4.0),
|
||||
(Tarasque, _) => (0.0, -6.0, 4.0),
|
||||
(Tiger, _) => (0.0, -8.0, 4.0),
|
||||
(Wolf, _) => (0.0, -7.0, 3.0),
|
||||
(Frostfang, _) => (0.0, -3.0, 4.0),
|
||||
(Mouflon, _) => (0.0, -8.0, 2.0),
|
||||
(Catoblepas, _) => (0.0, -8.0, 2.0),
|
||||
(Bonerattler, _) => (0.0, -1.0, 4.0),
|
||||
(Deer, _) => (0.0, -9.0, 3.0),
|
||||
(Hirdrasil, _) => (0.0, -11.0, 3.0),
|
||||
(Roshwalr, _) => (0.0, -1.0, 7.0),
|
||||
(Donkey, _) => (0.0, -5.0, 2.0),
|
||||
(Camel, _) => (0.0, -13.0, 5.0),
|
||||
(Zebra, _) => (0.0, -6.0, 3.0),
|
||||
(Antelope, _) => (0.0, -8.0, 3.0),
|
||||
(Kelpie, _) => (0.0, -6.0, 3.0),
|
||||
(Horse, _) => (0.0, -8.0, 3.0),
|
||||
(Barghest, _) => (0.0, -8.0, 5.0),
|
||||
(Cattle, Male) => (0.0, -3.0, 8.0),
|
||||
(Cattle, Female) => (0.0, -2.0, 6.0),
|
||||
(Darkhound, _) => (0.0, -2.0, 3.0),
|
||||
(Highland, _) => (0.0, -3.0, 8.0),
|
||||
(Yak, _) => (0.0, -8.0, 9.0),
|
||||
(Panda, _) => (0.0, -10.0, 5.0),
|
||||
(Bear, _) => (0.0, -11.0, 6.0),
|
||||
(Dreadhorn, _) => (0.0, 0.0, 10.0),
|
||||
(Moose, _) => (0.0, -9.0, 6.0),
|
||||
(Snowleopard, _) => (0.0, -9.0, 4.0),
|
||||
(Mammoth, _) => (0.0, 5.0, 8.0),
|
||||
(Ngoubou, _) => (0.0, -7.0, 6.0),
|
||||
(Llama, _) => (0.0, -6.0, 5.0),
|
||||
(Alpaca, _) => (0.0, -9.0, 3.0),
|
||||
(Akhlut, _) => (0.0, -6.0, 4.0),
|
||||
(Grolgar, _) => (0.0, -6.0, 3.0),
|
||||
(Saber, _) => (0.0, -12.0, 1.0),
|
||||
(Tuskram, _) => (0.0, -17.0, -1.0),
|
||||
(Lion, _) => (0.0, -8.0, 1.0),
|
||||
(Tarasque, _) => (0.0, -6.0, 1.0),
|
||||
(Tiger, _) => (0.0, -8.0, 1.0),
|
||||
(Wolf, _) => (0.0, -9.0, 0.0),
|
||||
(Frostfang, _) => (0.0, -6.0, -1.0),
|
||||
(Mouflon, _) => (0.0, -8.0, -1.0),
|
||||
(Catoblepas, _) => (0.0, -8.0, -1.0),
|
||||
(Bonerattler, _) => (0.0, -1.0, 1.0),
|
||||
(Deer, _) => (0.0, -9.0, 0.0),
|
||||
(Hirdrasil, _) => (0.0, -11.0, 0.0),
|
||||
(Roshwalr, _) => (0.0, -1.0, 4.0),
|
||||
(Donkey, _) => (0.0, -5.0, -1.0),
|
||||
(Camel, _) => (0.0, -13.0, 2.0),
|
||||
(Zebra, _) => (0.0, -6.0, 0.0),
|
||||
(Antelope, _) => (0.0, -8.0, 0.0),
|
||||
(Kelpie, _) => (0.0, -6.0, 0.0),
|
||||
(Horse, _) => (0.0, -8.0, 0.0),
|
||||
(Barghest, _) => (0.0, -8.0, 2.0),
|
||||
(Cattle, Male) => (0.0, -3.0, 5.0),
|
||||
(Cattle, Female) => (0.0, -2.0, 3.0),
|
||||
(Darkhound, _) => (0.0, -2.0, 0.0),
|
||||
(Highland, _) => (0.0, -3.0, 5.0),
|
||||
(Yak, _) => (0.0, -8.0, 6.0),
|
||||
(Panda, _) => (0.0, -10.0, 2.0),
|
||||
(Bear, _) => (0.0, -11.0, 3.0),
|
||||
(Dreadhorn, _) => (0.0, 0.0, 7.0),
|
||||
(Moose, _) => (0.0, -9.0, 3.0),
|
||||
(Snowleopard, _) => (0.0, -9.0, 1.0),
|
||||
(Mammoth, _) => (0.0, 5.0, 5.0),
|
||||
(Ngoubou, _) => (0.0, -7.0, 3.0),
|
||||
(Llama, _) => (0.0, -6.0, 2.0),
|
||||
(Alpaca, _) => (0.0, -9.0, 0.0),
|
||||
(Akhlut, _) => (0.0, -6.0, 1.0),
|
||||
}
|
||||
.into()
|
||||
}
|
||||
|
@ -25,6 +25,7 @@ skeleton_impls!(struct QuadrupedSmallSkeleton {
|
||||
+ leg_bl,
|
||||
+ leg_br,
|
||||
+ tail,
|
||||
mount,
|
||||
});
|
||||
|
||||
impl Skeleton for QuadrupedSmallSkeleton {
|
||||
@ -45,9 +46,10 @@ impl Skeleton for QuadrupedSmallSkeleton {
|
||||
let chest_mat = base_mat
|
||||
* Mat4::scaling_3d(SkeletonAttr::from(&body).scaler / 11.0)
|
||||
* Mat4::<f32>::from(self.chest);
|
||||
let head_mat = chest_mat * Mat4::<f32>::from(self.head);
|
||||
|
||||
*(<&mut [_; Self::BONE_COUNT]>::try_from(&mut buf[0..Self::BONE_COUNT]).unwrap()) = [
|
||||
make_bone(chest_mat * Mat4::<f32>::from(self.head)),
|
||||
make_bone(head_mat),
|
||||
make_bone(chest_mat),
|
||||
make_bone(chest_mat * Mat4::<f32>::from(self.leg_fl)),
|
||||
make_bone(chest_mat * Mat4::<f32>::from(self.leg_fr)),
|
||||
@ -55,15 +57,22 @@ impl Skeleton for QuadrupedSmallSkeleton {
|
||||
make_bone(chest_mat * Mat4::<f32>::from(self.leg_br)),
|
||||
make_bone(chest_mat * Mat4::<f32>::from(self.tail)),
|
||||
];
|
||||
use comp::quadruped_small::Species::*;
|
||||
let (mount_bone_mat, mount_bone_ori) = match (body.species, body.body_type) {
|
||||
(Dodarock, _) => (head_mat, self.chest.orientation * self.head.orientation),
|
||||
_ => (chest_mat, self.chest.orientation),
|
||||
};
|
||||
let mount_position = (mount_bone_mat * Vec4::from_point(mount_point(&body)))
|
||||
.homogenized()
|
||||
.xyz();
|
||||
let mount_orientation = mount_bone_ori;
|
||||
|
||||
Offsets {
|
||||
lantern: None,
|
||||
// TODO: see quadruped_medium for how to animate this
|
||||
mount_bone: Transform {
|
||||
position: common::comp::Body::QuadrupedSmall(body)
|
||||
.mountee_offset()
|
||||
.into_tuple()
|
||||
.into(),
|
||||
..Default::default()
|
||||
position: mount_position,
|
||||
orientation: mount_orientation,
|
||||
scale: Vec3::one(),
|
||||
},
|
||||
}
|
||||
}
|
||||
@ -392,3 +401,37 @@ impl<'a> From<&'a Body> for SkeletonAttr {
|
||||
}
|
||||
}
|
||||
}
|
||||
fn mount_point(body: &Body) -> Vec3<f32> {
|
||||
use comp::quadruped_small::{BodyType::*, Species::*};
|
||||
match (body.species, body.body_type) {
|
||||
(Pig, _) => (0.0, -2.0, -2.5),
|
||||
(Fox, _) => (0.0, -4.0, -3.5),
|
||||
(Sheep, _) => (0.0, -4.0, -3.5),
|
||||
(Boar, _) => (0.0, -2.0, -3.5),
|
||||
(Jackalope, _) => (0.0, -4.0, -3.5),
|
||||
(Skunk, _) => (0.0, -4.0, -3.5),
|
||||
(Cat, _) => (0.0, -5.0, -4.0),
|
||||
(Batfox, _) => (0.0, -4.0, -3.0),
|
||||
(Raccoon, _) => (0.0, -4.0, -2.5),
|
||||
(Quokka, _) => (0.0, -3.0, -3.5),
|
||||
(Dodarock, _) => (0.0, 0.0, 2.5),
|
||||
(Holladon, _) => (0.0, -2.0, -2.5),
|
||||
(Hyena, _) => (0.0, -4.0, -3.5),
|
||||
(Rabbit, _) => (0.0, -4.0, -3.5),
|
||||
(Truffler, _) => (0.0, -6.0, 6.5),
|
||||
(Frog, _) => (0.0, -4.0, -4.5),
|
||||
(Rat, _) => (0.0, -4.0, -4.5),
|
||||
(Axolotl, _) => (0.0, -4.0, -4.5),
|
||||
(Gecko, _) => (0.0, -4.0, -4.5),
|
||||
(Turtle, _) => (0.0, -4.0, -4.5),
|
||||
(Squirrel, _) => (0.0, -4.0, -4.5),
|
||||
(Fungome, _) => (0.0, -4.0, -4.5),
|
||||
(Porcupine, _) => (0.0, -4.0, -3.5),
|
||||
(Beaver, _) => (0.0, -2.0, -3.5),
|
||||
(Hare, Male) => (0.0, -4.0, -4.5),
|
||||
(Hare, Female) => (0.0, -4.0, -4.5),
|
||||
(Dog, _) => (0.0, -4.0, -2.5),
|
||||
(Goat, _) => (0.0, -4.0, -3.5),
|
||||
}
|
||||
.into()
|
||||
}
|
||||
|
@ -47,7 +47,7 @@ impl Skeleton for ShipSkeleton {
|
||||
mount_bone: Transform {
|
||||
position: (base_mat * scale_mat).mul_point(
|
||||
common::comp::Body::Ship(body)
|
||||
.mountee_offset()
|
||||
.mount_offset()
|
||||
.into_tuple()
|
||||
.into(),
|
||||
),
|
||||
|
@ -79,7 +79,7 @@ impl Skeleton for TheropodSkeleton {
|
||||
// TODO: see quadruped_medium for how to animate this
|
||||
mount_bone: Transform {
|
||||
position: common::comp::Body::Theropod(body)
|
||||
.mountee_offset()
|
||||
.mount_offset()
|
||||
.into_tuple()
|
||||
.into(),
|
||||
..Default::default()
|
||||
|
@ -57,7 +57,10 @@ use crate::{
|
||||
game_input::GameInput,
|
||||
hud::{img_ids::ImgsRot, prompt_dialog::DialogOutcomeEvent},
|
||||
render::UiDrawer,
|
||||
scene::camera::{self, Camera},
|
||||
scene::{
|
||||
camera::{self, Camera},
|
||||
terrain::Interaction,
|
||||
},
|
||||
session::{
|
||||
interactable::Interactable,
|
||||
settings_change::{Chat as ChatChange, Interface as InterfaceChange, SettingsChange},
|
||||
@ -83,6 +86,8 @@ use common::{
|
||||
BuffData, BuffKind, Item,
|
||||
},
|
||||
consts::MAX_PICKUP_RANGE,
|
||||
link::Is,
|
||||
mounting::Mount,
|
||||
outcome::Outcome,
|
||||
slowjob::SlowJobPool,
|
||||
terrain::{SpriteKind, TerrainChunk},
|
||||
@ -1140,6 +1145,8 @@ impl Hud {
|
||||
let entities = ecs.entities();
|
||||
let me = client.entity();
|
||||
let poises = ecs.read_storage::<comp::Poise>();
|
||||
let alignments = ecs.read_storage::<comp::Alignment>();
|
||||
let is_mount = ecs.read_storage::<Is<Mount>>();
|
||||
|
||||
// Check if there was a persistence load error of the skillset, and if so
|
||||
// display a dialog prompt
|
||||
@ -1664,31 +1671,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| {
|
||||
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,
|
||||
)
|
||||
.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
|
||||
@ -1714,12 +1723,13 @@ impl Hud {
|
||||
pickup_failed_pulse: self.failed_entity_pickups.get(&entity).copied(),
|
||||
},
|
||||
&self.fonts,
|
||||
vec![(GameInput::Interact, i18n.get("hud.pick_up").to_string())],
|
||||
)
|
||||
.set(overitem_id, ui_widgets);
|
||||
}
|
||||
|
||||
// Render overtime for an interactable block
|
||||
if let Some(Interactable::Block(block, pos, _)) = interactable {
|
||||
if let Some(Interactable::Block(block, pos, interaction)) = interactable {
|
||||
let overitem_id = overitem_walker.next(
|
||||
&mut self.ids.overitems,
|
||||
&mut ui_widgets.widget_id_generator(),
|
||||
@ -1733,9 +1743,9 @@ impl Hud {
|
||||
let over_pos = pos + Vec3::unit_z() * 0.7;
|
||||
|
||||
// This is only done once per frame, so it's not a performance issue
|
||||
if block.get_sprite().map_or(false, |s| s.is_container()) {
|
||||
if let Some(sprite) = block.get_sprite().filter(|s| s.is_container()) {
|
||||
overitem::Overitem::new(
|
||||
"???".into(),
|
||||
format!("{:?}", sprite).into(),
|
||||
overitem::TEXT_COLOR,
|
||||
pos.distance_squared(player_pos),
|
||||
&self.fonts,
|
||||
@ -1744,6 +1754,7 @@ impl Hud {
|
||||
overitem_properties,
|
||||
self.pulse,
|
||||
&global_state.window.key_layout,
|
||||
vec![(GameInput::Interact, i18n.get("hud.open").to_string())],
|
||||
)
|
||||
.x_y(0.0, 100.0)
|
||||
.position_ingame(over_pos)
|
||||
@ -1755,6 +1766,17 @@ impl Hud {
|
||||
pos.distance_squared(player_pos),
|
||||
overitem_properties,
|
||||
&self.fonts,
|
||||
match interaction {
|
||||
Interaction::Collect => {
|
||||
vec![(GameInput::Interact, i18n.get("hud.collect").to_string())]
|
||||
},
|
||||
Interaction::Craft(_) => {
|
||||
vec![(GameInput::Interact, i18n.get("hud.use").to_string())]
|
||||
},
|
||||
Interaction::Mine => {
|
||||
vec![(GameInput::Primary, i18n.get("hud.mine").to_string())]
|
||||
},
|
||||
},
|
||||
)
|
||||
.set(overitem_id, ui_widgets);
|
||||
} else if let Some(desc) = block.get_sprite().and_then(|s| get_sprite_desc(s, i18n))
|
||||
@ -1769,6 +1791,7 @@ impl Hud {
|
||||
overitem_properties,
|
||||
self.pulse,
|
||||
&global_state.window.key_layout,
|
||||
vec![(GameInput::Interact, i18n.get("hud.use").to_string())],
|
||||
)
|
||||
.x_y(0.0, 100.0)
|
||||
.position_ingame(over_pos)
|
||||
@ -1779,7 +1802,22 @@ impl Hud {
|
||||
let speech_bubbles = &self.speech_bubbles;
|
||||
|
||||
// Render overhead name tags and health bars
|
||||
for (pos, info, bubble, _, _, health, _, height_offset, hpfl, in_group) in (
|
||||
for (
|
||||
entity,
|
||||
pos,
|
||||
info,
|
||||
bubble,
|
||||
_,
|
||||
_,
|
||||
health,
|
||||
_,
|
||||
height_offset,
|
||||
hpfl,
|
||||
in_group,
|
||||
dist_sqr,
|
||||
alignment,
|
||||
is_mount,
|
||||
) in (
|
||||
&entities,
|
||||
&pos,
|
||||
interpolated.maybe(),
|
||||
@ -1795,6 +1833,7 @@ impl Hud {
|
||||
&inventories,
|
||||
players.maybe(),
|
||||
poises.maybe(),
|
||||
(alignments.maybe(), is_mount.maybe()),
|
||||
)
|
||||
.join()
|
||||
.filter(|t| {
|
||||
@ -1818,6 +1857,7 @@ impl Hud {
|
||||
inventory,
|
||||
player,
|
||||
poise,
|
||||
(alignment, is_mount),
|
||||
)| {
|
||||
// Use interpolated position if available
|
||||
let pos = interpolated.map_or(pos.0, |i| i.pos);
|
||||
@ -1879,6 +1919,7 @@ impl Hud {
|
||||
|
||||
(info.is_some() || bubble.is_some()).then(|| {
|
||||
(
|
||||
entity,
|
||||
pos,
|
||||
info,
|
||||
bubble,
|
||||
@ -1889,6 +1930,9 @@ impl Hud {
|
||||
body.height() * scale.map_or(1.0, |s| s.0) + 0.5,
|
||||
hpfl,
|
||||
in_group,
|
||||
dist_sqr,
|
||||
alignment,
|
||||
is_mount,
|
||||
)
|
||||
})
|
||||
},
|
||||
@ -1911,8 +1955,32 @@ impl Hud {
|
||||
&global_state.settings.interface,
|
||||
self.pulse,
|
||||
i18n,
|
||||
&global_state.settings.controls,
|
||||
&self.imgs,
|
||||
&self.fonts,
|
||||
&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) =>
|
||||
{
|
||||
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()
|
||||
&& !client.is_riding()
|
||||
&& is_mount.is_none()
|
||||
&& dist_sqr < common::consts::MAX_MOUNT_RANGE.powi(2) =>
|
||||
{
|
||||
vec![(GameInput::Mount, i18n.get("hud.mount").to_string())]
|
||||
},
|
||||
_ => Vec::new(),
|
||||
},
|
||||
)
|
||||
.x_y(0.0, 100.0)
|
||||
.position_ingame(ingame_pos)
|
||||
|
@ -4,18 +4,20 @@ use super::{
|
||||
TEXT_BG, TEXT_COLOR,
|
||||
};
|
||||
use crate::{
|
||||
game_input::GameInput,
|
||||
hud::{get_buff_image, get_buff_info},
|
||||
settings::InterfaceSettings,
|
||||
settings::{ControlSettings, InterfaceSettings},
|
||||
ui::{fonts::Fonts, Ingameable},
|
||||
};
|
||||
use common::comp::{Buffs, Energy, Health, SpeechBubble, SpeechBubbleType};
|
||||
use conrod_core::{
|
||||
color,
|
||||
position::Align,
|
||||
widget::{self, Image, Rectangle, Text},
|
||||
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! {
|
||||
@ -53,6 +55,10 @@ widget_ids! {
|
||||
buffs_align,
|
||||
buffs[],
|
||||
buff_timers[],
|
||||
|
||||
// Interaction hints
|
||||
interaction_hints,
|
||||
interaction_hints_bg,
|
||||
}
|
||||
}
|
||||
|
||||
@ -84,14 +90,18 @@ pub struct Overhead<'a> {
|
||||
settings: &'a InterfaceSettings,
|
||||
pulse: f32,
|
||||
i18n: &'a Localization,
|
||||
controls: &'a ControlSettings,
|
||||
imgs: &'a Imgs,
|
||||
fonts: &'a Fonts,
|
||||
key_layout: &'a Option<KeyLayout>,
|
||||
interaction_options: Vec<(GameInput, String)>,
|
||||
|
||||
#[conrod(common_builder)]
|
||||
common: widget::CommonBuilder,
|
||||
}
|
||||
|
||||
impl<'a> Overhead<'a> {
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub fn new(
|
||||
info: Option<Info<'a>>,
|
||||
bubble: Option<&'a SpeechBubble>,
|
||||
@ -99,8 +109,11 @@ impl<'a> Overhead<'a> {
|
||||
settings: &'a InterfaceSettings,
|
||||
pulse: f32,
|
||||
i18n: &'a Localization,
|
||||
controls: &'a ControlSettings,
|
||||
imgs: &'a Imgs,
|
||||
fonts: &'a Fonts,
|
||||
key_layout: &'a Option<KeyLayout>,
|
||||
interaction_options: Vec<(GameInput, String)>,
|
||||
) -> Self {
|
||||
Self {
|
||||
info,
|
||||
@ -109,8 +122,11 @@ impl<'a> Overhead<'a> {
|
||||
settings,
|
||||
pulse,
|
||||
i18n,
|
||||
controls,
|
||||
imgs,
|
||||
fonts,
|
||||
key_layout,
|
||||
interaction_options,
|
||||
common: widget::CommonBuilder::default(),
|
||||
}
|
||||
}
|
||||
@ -157,6 +173,7 @@ impl<'a> Ingameable for Overhead<'a> {
|
||||
} else {
|
||||
0
|
||||
}
|
||||
+ (!self.interaction_options.is_empty()) as usize * 2
|
||||
}) + if self.bubble.is_some() { 13 } else { 0 }
|
||||
}
|
||||
}
|
||||
@ -448,6 +465,68 @@ impl<'a> Widget for Overhead<'a> {
|
||||
},
|
||||
_ => {},
|
||||
}
|
||||
|
||||
// Interaction hints
|
||||
if !self.interaction_options.is_empty() {
|
||||
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
|
||||
)
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
.join("\n");
|
||||
|
||||
let scale = 30.0;
|
||||
let btn_rect_size = scale * 0.8;
|
||||
let btn_font_size = scale * 0.6;
|
||||
let btn_radius = btn_rect_size / 5.0;
|
||||
let btn_color = Color::Rgba(0.0, 0.0, 0.0, 0.8);
|
||||
|
||||
let hints_text = Text::new(&text)
|
||||
.font_id(self.fonts.cyri.conrod_id)
|
||||
.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,
|
||||
)
|
||||
.align_middle_x_of(state.ids.name)
|
||||
.depth(1.0);
|
||||
|
||||
let [w, h] = hints_text.get_wh(ui).unwrap_or([btn_rect_size; 2]);
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
// Speech bubble
|
||||
if let Some(bubble) = self.bubble {
|
||||
|
@ -6,7 +6,7 @@ use crate::{
|
||||
use conrod_core::{
|
||||
color,
|
||||
widget::{self, RoundedRectangle, Text},
|
||||
widget_ids, Color, Colorable, Positionable, Widget, WidgetCommon,
|
||||
widget_ids, Color, Colorable, Positionable, Sizeable, Widget, WidgetCommon,
|
||||
};
|
||||
use i18n::Localization;
|
||||
use std::borrow::Cow;
|
||||
@ -21,7 +21,7 @@ widget_ids! {
|
||||
// Name
|
||||
name_bg,
|
||||
name,
|
||||
// Key
|
||||
// Interaction hints
|
||||
btn_bg,
|
||||
btn,
|
||||
// Inventory full
|
||||
@ -45,6 +45,7 @@ pub struct Overitem<'a> {
|
||||
properties: OveritemProperties,
|
||||
pulse: f32,
|
||||
key_layout: &'a Option<KeyLayout>,
|
||||
interaction_options: Vec<(GameInput, String)>,
|
||||
}
|
||||
|
||||
impl<'a> Overitem<'a> {
|
||||
@ -58,6 +59,7 @@ impl<'a> Overitem<'a> {
|
||||
properties: OveritemProperties,
|
||||
pulse: f32,
|
||||
key_layout: &'a Option<KeyLayout>,
|
||||
interaction_options: Vec<(GameInput, String)>,
|
||||
) -> Self {
|
||||
Self {
|
||||
name,
|
||||
@ -70,6 +72,7 @@ impl<'a> Overitem<'a> {
|
||||
properties,
|
||||
pulse,
|
||||
key_layout,
|
||||
interaction_options,
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -120,7 +123,7 @@ impl<'a> Widget for Overitem<'a> {
|
||||
fn update(self, args: widget::UpdateArgs<Self>) -> Self::Event {
|
||||
let widget::UpdateArgs { id, state, ui, .. } = args;
|
||||
|
||||
let btn_color = Color::Rgba(0.0, 0.0, 0.0, 0.4);
|
||||
let btn_color = Color::Rgba(0.0, 0.0, 0.0, 0.8);
|
||||
|
||||
// Example:
|
||||
// MUSHROOM
|
||||
@ -167,25 +170,50 @@ impl<'a> Widget for Overitem<'a> {
|
||||
.parent(id)
|
||||
.set(state.ids.name, ui);
|
||||
|
||||
// Pickup Button
|
||||
if let Some(key_button) = self
|
||||
.controls
|
||||
.get_binding(GameInput::Interact)
|
||||
.filter(|_| self.properties.active)
|
||||
{
|
||||
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 + 1.0)
|
||||
.parent(id)
|
||||
.set(state.ids.btn_bg, ui);
|
||||
Text::new(key_button.display_string(self.key_layout).as_str())
|
||||
// Interaction hints
|
||||
if !self.interaction_options.is_empty() {
|
||||
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
|
||||
)
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
.join("\n");
|
||||
|
||||
let hints_text = Text::new(&text)
|
||||
.font_id(self.fonts.cyri.conrod_id)
|
||||
.font_size(btn_font_size as u32)
|
||||
.color(TEXT_COLOR)
|
||||
.x_y(0.0, btn_text_pos_y)
|
||||
.depth(self.distance_from_player_sqr + 2.0)
|
||||
.parent(id)
|
||||
.set(state.ids.btn, ui);
|
||||
.depth(self.distance_from_player_sqr + 1.0)
|
||||
.parent(id);
|
||||
|
||||
let [w, h] = hints_text.get_wh(ui).unwrap_or([btn_rect_size; 2]);
|
||||
|
||||
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);
|
||||
}
|
||||
if let Some(time) = self.properties.pickup_failed_pulse {
|
||||
//should never exceed 1.0, but just in case
|
||||
|
@ -33,8 +33,10 @@ use common::{
|
||||
inventory::slot::EquipSlot,
|
||||
item::{Hands, ItemKind, ToolKind},
|
||||
Body, CharacterState, Collider, Controller, Health, Inventory, Item, Last, LightAnimation,
|
||||
LightEmitter, Mounting, Ori, PhysicsState, PoiseState, Pos, Scale, Vel,
|
||||
LightEmitter, Ori, PhysicsState, PoiseState, Pos, Scale, Vel,
|
||||
},
|
||||
link::Is,
|
||||
mounting::Rider,
|
||||
resources::{DeltaTime, Time},
|
||||
states::{equipping, idle, utils::StageSection, wielding},
|
||||
terrain::TerrainChunk,
|
||||
@ -458,9 +460,11 @@ impl FigureMgr {
|
||||
}
|
||||
let dt = ecs.fetch::<DeltaTime>().0;
|
||||
let updater = ecs.read_resource::<LazyUpdate>();
|
||||
for (entity, light_emitter_opt, body, mut light_anim) in (
|
||||
for (entity, light_emitter_opt, interpolated, pos, body, mut light_anim) in (
|
||||
&ecs.entities(),
|
||||
ecs.read_storage::<LightEmitter>().maybe(),
|
||||
ecs.read_storage::<crate::ecs::comp::Interpolated>().maybe(),
|
||||
&ecs.read_storage::<Pos>(),
|
||||
ecs.read_storage::<Body>().maybe(),
|
||||
&mut ecs.write_storage::<LightAnimation>(),
|
||||
)
|
||||
@ -483,7 +487,18 @@ impl FigureMgr {
|
||||
};
|
||||
if let Some(lantern_offset) = body
|
||||
.and_then(|body| self.states.get_mut(body, &entity))
|
||||
.and_then(|state| state.lantern_offset)
|
||||
.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,
|
||||
)
|
||||
})
|
||||
{
|
||||
light_anim.offset = vek::Vec3::from(lantern_offset);
|
||||
} else if let Some(body) = body {
|
||||
@ -626,7 +641,7 @@ impl FigureMgr {
|
||||
inventory,
|
||||
item,
|
||||
light_emitter,
|
||||
mountings,
|
||||
is_rider,
|
||||
collider,
|
||||
),
|
||||
) in (
|
||||
@ -644,7 +659,7 @@ impl FigureMgr {
|
||||
ecs.read_storage::<Inventory>().maybe(),
|
||||
ecs.read_storage::<Item>().maybe(),
|
||||
ecs.read_storage::<LightEmitter>().maybe(),
|
||||
ecs.read_storage::<Mounting>().maybe(),
|
||||
ecs.read_storage::<Is<Rider>>().maybe(),
|
||||
ecs.read_storage::<Collider>().maybe(),
|
||||
)
|
||||
.join()
|
||||
@ -731,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 {
|
||||
@ -791,12 +808,12 @@ impl FigureMgr {
|
||||
|
||||
let hands = (active_tool_hand, second_tool_hand);
|
||||
|
||||
// If a mountee exists, get its animated mounting transform and its position
|
||||
// If a mount exists, get its animated mounting transform and its position
|
||||
let mount_transform_pos = (|| -> Option<_> {
|
||||
let Mounting(entity) = mountings?;
|
||||
let entity = uid_allocator.retrieve_entity_internal((*entity).into())?;
|
||||
let body = *bodies.get(entity)?;
|
||||
let meta = self.states.get_mut(&body, &entity)?;
|
||||
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))
|
||||
})();
|
||||
|
||||
@ -869,7 +886,7 @@ impl FigureMgr {
|
||||
physics.on_ground.is_some(),
|
||||
rel_vel.magnitude_squared() > MOVING_THRESHOLD_SQR, // Moving
|
||||
physics.in_liquid().is_some(), // In water
|
||||
mountings.is_some(),
|
||||
is_rider.is_some(),
|
||||
) {
|
||||
// Standing
|
||||
(true, false, false, false) => {
|
||||
@ -1140,6 +1157,8 @@ impl FigureMgr {
|
||||
(
|
||||
Some(s.static_data.ability_info),
|
||||
hands,
|
||||
ori * anim::vek::Vec3::<f32>::unit_y(),
|
||||
look_dir,
|
||||
rel_vel,
|
||||
time,
|
||||
Some(s.stage_section),
|
||||
@ -1779,9 +1798,17 @@ impl FigureMgr {
|
||||
skeleton_attr,
|
||||
),
|
||||
// In air
|
||||
(false, _, false) => anim::quadruped_small::JumpAnimation::update_skeleton(
|
||||
(false, _, false) => anim::quadruped_small::RunAnimation::update_skeleton(
|
||||
&QuadrupedSmallSkeleton::default(),
|
||||
(rel_vel.magnitude(), time),
|
||||
(
|
||||
rel_vel.magnitude(),
|
||||
// TODO: Update to use the quaternion.
|
||||
ori * anim::vek::Vec3::<f32>::unit_y(),
|
||||
state.last_ori * anim::vek::Vec3::<f32>::unit_y(),
|
||||
time,
|
||||
rel_avg_vel,
|
||||
state.acc_vel,
|
||||
),
|
||||
state.state_time,
|
||||
&mut state_animation_rate,
|
||||
skeleton_attr,
|
||||
@ -1972,7 +1999,7 @@ impl FigureMgr {
|
||||
(false, _, false) => {
|
||||
anim::quadruped_medium::JumpAnimation::update_skeleton(
|
||||
&QuadrupedMediumSkeleton::default(),
|
||||
time,
|
||||
(time, rel_vel, rel_avg_vel),
|
||||
state.state_time,
|
||||
&mut state_animation_rate,
|
||||
skeleton_attr,
|
||||
@ -2282,9 +2309,17 @@ impl FigureMgr {
|
||||
skeleton_attr,
|
||||
),
|
||||
// In air
|
||||
(false, _, false) => anim::quadruped_low::JumpAnimation::update_skeleton(
|
||||
(false, _, false) => anim::quadruped_low::RunAnimation::update_skeleton(
|
||||
&QuadrupedLowSkeleton::default(),
|
||||
(rel_vel.magnitude(), time),
|
||||
(
|
||||
rel_vel.magnitude(),
|
||||
// TODO: Update to use the quaternion.
|
||||
ori * anim::vek::Vec3::<f32>::unit_y(),
|
||||
state.last_ori * anim::vek::Vec3::<f32>::unit_y(),
|
||||
time,
|
||||
rel_avg_vel,
|
||||
state.acc_vel,
|
||||
),
|
||||
state.state_time,
|
||||
&mut state_animation_rate,
|
||||
skeleton_attr,
|
||||
@ -5519,9 +5554,9 @@ impl FigureColLights {
|
||||
|
||||
pub struct FigureStateMeta {
|
||||
lantern_offset: Option<anim::vek::Vec3<f32>>,
|
||||
// Animation to be applied to mounter of this entity
|
||||
// Animation to be applied to rider of this entity
|
||||
mount_transform: anim::vek::Transform<f32, f32, f32>,
|
||||
// Contains the position of this figure or if it is a mounter it will contain the mountee's
|
||||
// Contains the position of this figure or if it is a rider it will contain the mount's
|
||||
// mount_world_pos
|
||||
// Unlike the interpolated position stored in the ecs this will be propagated along
|
||||
// mount chains
|
||||
@ -5635,7 +5670,7 @@ impl<S: Skeleton> FigureState<S> {
|
||||
model: Option<&FigureModelEntry<N>>,
|
||||
// TODO: there is the potential to drop the optional body from the common params and just
|
||||
// use this one but we need to add a function to the skelton trait or something in order to
|
||||
// get the mounter offset
|
||||
// get the rider offset
|
||||
skel_body: S::Body,
|
||||
) {
|
||||
// NOTE: As long as update() always gets called after get_or_create_model(), and
|
||||
@ -5672,26 +5707,26 @@ impl<S: Skeleton> FigureState<S> {
|
||||
let scale_mat = anim::vek::Mat4::scaling_3d(anim::vek::Vec3::from(*scale));
|
||||
if let Some((transform, _)) = *mount_transform_pos {
|
||||
// Note: if we had a way to compute a "default" transform of the bones then in
|
||||
// the animations we could make use of the mountee_offset from common by
|
||||
// computing what the offset of the mounter is from the mounted
|
||||
// bone in its default position when the mounter has the mount
|
||||
// the animations we could make use of the mount_offset from common by
|
||||
// computing what the offset of the rider is from the mounted
|
||||
// bone in its default position when the rider has the mount
|
||||
// offset in common applied to it. Since we don't have this
|
||||
// right now we instead need to recreate the same effect in the
|
||||
// animations and keep it in sync.
|
||||
//
|
||||
// Component of mounting offset specific to the mounter.
|
||||
let mounter_offset = anim::vek::Mat4::<f32>::translation_3d(
|
||||
body.map_or_else(Vec3::zero, |b| b.mounter_offset()),
|
||||
// Component of mounting offset specific to the rider.
|
||||
let rider_offset = anim::vek::Mat4::<f32>::translation_3d(
|
||||
body.map_or_else(Vec3::zero, |b| b.rider_offset()),
|
||||
);
|
||||
|
||||
// NOTE: It is kind of a hack to use this entity's ori here if it is
|
||||
// mounted on another but this happens to match the ori of the
|
||||
// mountee so it works, change this if it causes jankiness in the future.
|
||||
// mount so it works, change this if it causes jankiness in the future.
|
||||
let transform = anim::vek::Transform {
|
||||
orientation: *ori * transform.orientation,
|
||||
..transform
|
||||
};
|
||||
anim::vek::Mat4::from(transform) * mounter_offset * scale_mat
|
||||
anim::vek::Mat4::from(transform) * rider_offset * scale_mat
|
||||
} else {
|
||||
let ori_mat = anim::vek::Mat4::from(*ori);
|
||||
ori_mat * scale_mat
|
||||
|
@ -9,6 +9,7 @@ use vek::*;
|
||||
pub enum Interaction {
|
||||
Collect,
|
||||
Craft(CraftingTab),
|
||||
Mine,
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
|
@ -22,7 +22,7 @@ use crate::scene::{terrain::Interaction, Scene};
|
||||
// enum since they don't use the interaction key
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub enum Interactable {
|
||||
Block(Block, Vec3<i32>, Option<Interaction>),
|
||||
Block(Block, Vec3<i32>, Interaction),
|
||||
Entity(specs::Entity),
|
||||
}
|
||||
|
||||
@ -81,9 +81,8 @@ pub(super) fn select_interactable(
|
||||
.or_else(|| {
|
||||
collect_target.and_then(|t| {
|
||||
if Some(t.distance) == nearest_dist {
|
||||
get_block(client, t).map(|b| {
|
||||
Interactable::Block(b, t.position_int(), Some(Interaction::Collect))
|
||||
})
|
||||
get_block(client, t)
|
||||
.map(|b| Interactable::Block(b, t.position_int(), Interaction::Collect))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
@ -99,7 +98,7 @@ pub(super) fn select_interactable(
|
||||
// elements (e.g. minerals). The mineable weakrock are used
|
||||
// in the terrain selected_pos, but is not an interactable.
|
||||
if b.mine_tool().is_some() && b.is_air() {
|
||||
Some(Interactable::Block(b, t.position_int(), None))
|
||||
Some(Interactable::Block(b, t.position_int(), Interaction::Mine))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
@ -200,7 +199,7 @@ pub(super) fn select_interactable(
|
||||
.get(block_pos)
|
||||
.ok()
|
||||
.copied()
|
||||
.map(|b| Interactable::Block(b, block_pos, Some(*interaction)))
|
||||
.map(|b| Interactable::Block(b, block_pos, *interaction))
|
||||
})
|
||||
.or_else(|| closest_interactable_entity.map(|(e, _)| Interactable::Entity(e)))
|
||||
}
|
||||
|
@ -22,6 +22,8 @@ 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,
|
||||
@ -670,7 +672,7 @@ impl PlayState for SessionState {
|
||||
},
|
||||
GameInput::Mount if state => {
|
||||
let mut client = self.client.borrow_mut();
|
||||
if client.is_mounted() {
|
||||
if client.is_riding() {
|
||||
client.unmount();
|
||||
} else {
|
||||
let player_pos = client
|
||||
@ -683,17 +685,14 @@ impl PlayState for SessionState {
|
||||
let closest_mountable_entity = (
|
||||
&client.state().ecs().entities(),
|
||||
&client.state().ecs().read_storage::<comp::Pos>(),
|
||||
&client
|
||||
.state()
|
||||
.ecs()
|
||||
.read_storage::<comp::MountState>(),
|
||||
// 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, _, mount_state)| {
|
||||
*entity != client.entity()
|
||||
&& **mount_state == comp::MountState::Unmounted
|
||||
})
|
||||
.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)| {
|
||||
@ -714,18 +713,18 @@ impl PlayState for SessionState {
|
||||
match interactable {
|
||||
Interactable::Block(block, pos, interaction) => {
|
||||
match interaction {
|
||||
Some(Interaction::Collect) => {
|
||||
Interaction::Collect => {
|
||||
if block.is_collectible() {
|
||||
client.collect_block(pos);
|
||||
}
|
||||
},
|
||||
Some(Interaction::Craft(tab)) => {
|
||||
Interaction::Craft(tab) => {
|
||||
self.hud.show.open_crafting_tab(
|
||||
tab,
|
||||
block.get_sprite().map(|s| (pos, s)),
|
||||
)
|
||||
},
|
||||
_ => {},
|
||||
Interaction::Mine => {},
|
||||
}
|
||||
},
|
||||
Interactable::Entity(entity) => {
|
||||
|
Loading…
Reference in New Issue
Block a user