mirror of
https://gitlab.com/veloren/veloren.git
synced 2024-08-30 18:12:32 +00:00
Added basic tethering
This commit is contained in:
parent
c422616f70
commit
9aa757cd09
@ -689,6 +689,11 @@ impl ServerChatCommand {
|
|||||||
Optional,
|
Optional,
|
||||||
),
|
),
|
||||||
Float("destination_degrees_ccw_of_east", 90.0, Optional),
|
Float("destination_degrees_ccw_of_east", 90.0, Optional),
|
||||||
|
Boolean(
|
||||||
|
"Whether the ship should be tethered to the target (or its mount)",
|
||||||
|
"false".to_string(),
|
||||||
|
Optional,
|
||||||
|
),
|
||||||
],
|
],
|
||||||
"Spawns a ship",
|
"Spawns a ship",
|
||||||
Some(Admin),
|
Some(Admin),
|
||||||
@ -723,6 +728,7 @@ impl ServerChatCommand {
|
|||||||
Integer("amount", 1, Optional),
|
Integer("amount", 1, Optional),
|
||||||
Boolean("ai", "true".to_string(), Optional),
|
Boolean("ai", "true".to_string(), Optional),
|
||||||
Float("scale", 1.0, Optional),
|
Float("scale", 1.0, Optional),
|
||||||
|
Boolean("tethered", "false".to_string(), Optional),
|
||||||
],
|
],
|
||||||
"Spawn a test entity",
|
"Spawn a test entity",
|
||||||
Some(Admin),
|
Some(Admin),
|
||||||
|
@ -1214,6 +1214,8 @@ impl Body {
|
|||||||
.into()
|
.into()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn tether_offset(&self) -> Vec3<f32> { Vec3::new(0.0, self.dimensions().y * 0.5, 0.0) }
|
||||||
|
|
||||||
pub fn localize(&self) -> Content {
|
pub fn localize(&self) -> Content {
|
||||||
match self {
|
match self {
|
||||||
Self::BipedLarge(biped_large) => biped_large.localize(),
|
Self::BipedLarge(biped_large) => biped_large.localize(),
|
||||||
|
@ -57,6 +57,7 @@ pub mod spiral;
|
|||||||
pub mod states;
|
pub mod states;
|
||||||
pub mod store;
|
pub mod store;
|
||||||
pub mod terrain;
|
pub mod terrain;
|
||||||
|
pub mod tether;
|
||||||
pub mod time;
|
pub mod time;
|
||||||
pub mod trade;
|
pub mod trade;
|
||||||
pub mod util;
|
pub mod util;
|
||||||
|
@ -2,7 +2,8 @@ use crate::{
|
|||||||
comp::{self, pet::is_mountable, ship::figuredata::VOXEL_COLLIDER_MANIFEST},
|
comp::{self, pet::is_mountable, ship::figuredata::VOXEL_COLLIDER_MANIFEST},
|
||||||
link::{Is, Link, LinkHandle, Role},
|
link::{Is, Link, LinkHandle, Role},
|
||||||
terrain::{Block, TerrainGrid},
|
terrain::{Block, TerrainGrid},
|
||||||
uid::{IdMaps, Uid},
|
tether,
|
||||||
|
uid::{Uid, UidAllocator},
|
||||||
vol::ReadVol,
|
vol::ReadVol,
|
||||||
};
|
};
|
||||||
use hashbrown::HashSet;
|
use hashbrown::HashSet;
|
||||||
@ -45,6 +46,7 @@ impl Link for Mounting {
|
|||||||
WriteStorage<'a, Is<Mount>>,
|
WriteStorage<'a, Is<Mount>>,
|
||||||
WriteStorage<'a, Is<Rider>>,
|
WriteStorage<'a, Is<Rider>>,
|
||||||
ReadStorage<'a, Is<VolumeRider>>,
|
ReadStorage<'a, Is<VolumeRider>>,
|
||||||
|
ReadStorage<'a, Is<tether::Follower>>,
|
||||||
);
|
);
|
||||||
type DeleteData<'a> = (
|
type DeleteData<'a> = (
|
||||||
Read<'a, IdMaps>,
|
Read<'a, IdMaps>,
|
||||||
@ -67,7 +69,7 @@ impl Link for Mounting {
|
|||||||
|
|
||||||
fn create(
|
fn create(
|
||||||
this: &LinkHandle<Self>,
|
this: &LinkHandle<Self>,
|
||||||
(id_maps, is_mounts, is_riders, is_volume_rider): &mut Self::CreateData<'_>,
|
(id_maps, is_mounts, is_riders, is_volume_rider, is_followers): &mut Self::CreateData<'_>,
|
||||||
) -> Result<(), Self::Error> {
|
) -> Result<(), Self::Error> {
|
||||||
let entity = |uid: Uid| id_maps.uid_entity(uid);
|
let entity = |uid: Uid| id_maps.uid_entity(uid);
|
||||||
|
|
||||||
@ -79,7 +81,7 @@ impl Link for Mounting {
|
|||||||
// relationship
|
// relationship
|
||||||
if !is_mounts.contains(mount)
|
if !is_mounts.contains(mount)
|
||||||
&& !is_riders.contains(rider)
|
&& !is_riders.contains(rider)
|
||||||
&& !is_riders.contains(rider)
|
&& !is_followers.contains(rider)
|
||||||
// TODO: Does this definitely prevent mount cycles?
|
// TODO: Does this definitely prevent mount cycles?
|
||||||
&& (!is_mounts.contains(rider) || !is_riders.contains(mount))
|
&& (!is_mounts.contains(rider) || !is_riders.contains(mount))
|
||||||
&& !is_volume_rider.contains(rider)
|
&& !is_volume_rider.contains(rider)
|
||||||
|
140
common/src/tether.rs
Normal file
140
common/src/tether.rs
Normal file
@ -0,0 +1,140 @@
|
|||||||
|
use crate::{
|
||||||
|
comp,
|
||||||
|
link::{Is, Link, LinkHandle, Role},
|
||||||
|
mounting::{Mount, Rider, VolumeRider},
|
||||||
|
uid::{Uid, UidAllocator},
|
||||||
|
};
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
use specs::{
|
||||||
|
saveload::MarkerAllocator, storage::GenericWriteStorage, Component, DenseVecStorage, Entities,
|
||||||
|
Entity, Read, ReadExpect, ReadStorage, Write, WriteStorage,
|
||||||
|
};
|
||||||
|
use vek::*;
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, Debug)]
|
||||||
|
pub struct Leader;
|
||||||
|
|
||||||
|
impl Role for Leader {
|
||||||
|
type Link = Tethered;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, Debug)]
|
||||||
|
pub struct Follower;
|
||||||
|
|
||||||
|
impl Role for Follower {
|
||||||
|
type Link = Tethered;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, Debug)]
|
||||||
|
pub struct Tethered {
|
||||||
|
pub leader: Uid,
|
||||||
|
pub follower: Uid,
|
||||||
|
pub tether_length: f32,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum TetherError {
|
||||||
|
NoSuchEntity,
|
||||||
|
NotTetherable,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Link for Tethered {
|
||||||
|
type CreateData<'a> = (
|
||||||
|
Read<'a, UidAllocator>,
|
||||||
|
WriteStorage<'a, Is<Leader>>,
|
||||||
|
WriteStorage<'a, Is<Follower>>,
|
||||||
|
ReadStorage<'a, Is<Rider>>,
|
||||||
|
ReadStorage<'a, Is<Mount>>,
|
||||||
|
ReadStorage<'a, Is<VolumeRider>>,
|
||||||
|
);
|
||||||
|
type DeleteData<'a> = (
|
||||||
|
Read<'a, UidAllocator>,
|
||||||
|
WriteStorage<'a, Is<Leader>>,
|
||||||
|
WriteStorage<'a, Is<Follower>>,
|
||||||
|
WriteStorage<'a, comp::Pos>,
|
||||||
|
WriteStorage<'a, comp::ForceUpdate>,
|
||||||
|
);
|
||||||
|
type Error = TetherError;
|
||||||
|
type PersistData<'a> = (
|
||||||
|
Read<'a, UidAllocator>,
|
||||||
|
Entities<'a>,
|
||||||
|
ReadStorage<'a, comp::Health>,
|
||||||
|
ReadStorage<'a, comp::Body>,
|
||||||
|
ReadStorage<'a, Is<Leader>>,
|
||||||
|
ReadStorage<'a, Is<Follower>>,
|
||||||
|
ReadStorage<'a, comp::CharacterState>,
|
||||||
|
);
|
||||||
|
|
||||||
|
fn create(
|
||||||
|
this: &LinkHandle<Self>,
|
||||||
|
(
|
||||||
|
uid_allocator,
|
||||||
|
is_leaders,
|
||||||
|
is_followers,
|
||||||
|
is_riders,
|
||||||
|
is_mounts,
|
||||||
|
is_volume_rider,
|
||||||
|
): &mut Self::CreateData<'_>,
|
||||||
|
) -> Result<(), Self::Error> {
|
||||||
|
let entity = |uid: Uid| uid_allocator.retrieve_entity_internal(uid.into());
|
||||||
|
|
||||||
|
if this.leader == this.follower {
|
||||||
|
// Forbid self-tethering
|
||||||
|
Err(TetherError::NotTetherable)
|
||||||
|
} else if let Some((leader, follower)) = entity(this.leader).zip(entity(this.follower)) {
|
||||||
|
// Ensure that neither leader or follower are already part of a conflicting
|
||||||
|
// relationship
|
||||||
|
if !is_riders.contains(follower)
|
||||||
|
&& !is_volume_rider.contains(follower)
|
||||||
|
&& !is_followers.contains(follower)
|
||||||
|
// TODO: Does this definitely prevent tether cycles?
|
||||||
|
&& (!is_leaders.contains(follower) || !is_followers.contains(leader))
|
||||||
|
{
|
||||||
|
let _ = is_leaders.insert(leader, this.make_role());
|
||||||
|
let _ = is_followers.insert(follower, this.make_role());
|
||||||
|
Ok(())
|
||||||
|
} else {
|
||||||
|
Err(TetherError::NotTetherable)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Err(TetherError::NoSuchEntity)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn persist(
|
||||||
|
this: &LinkHandle<Self>,
|
||||||
|
(uid_allocator, entities, healths, bodies, is_leaders, is_followers, character_states): &mut Self::PersistData<'_>,
|
||||||
|
) -> bool {
|
||||||
|
let entity = |uid: Uid| uid_allocator.retrieve_entity_internal(uid.into());
|
||||||
|
|
||||||
|
if let Some((leader, follower)) = entity(this.leader).zip(entity(this.follower)) {
|
||||||
|
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(leader)
|
||||||
|
&& is_alive(follower)
|
||||||
|
&& is_leaders.get(leader).is_some()
|
||||||
|
&& is_followers.get(follower).is_some()
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn delete(
|
||||||
|
this: &LinkHandle<Self>,
|
||||||
|
(uid_allocator, is_leaders, is_followers, positions, force_update): &mut Self::DeleteData<
|
||||||
|
'_,
|
||||||
|
>,
|
||||||
|
) {
|
||||||
|
let entity = |uid: Uid| uid_allocator.retrieve_entity_internal(uid.into());
|
||||||
|
|
||||||
|
let leader = entity(this.leader);
|
||||||
|
let follower = entity(this.follower);
|
||||||
|
|
||||||
|
// Delete link components
|
||||||
|
leader.map(|leader| is_leaders.remove(leader));
|
||||||
|
follower.map(|follower| is_followers.remove(follower));
|
||||||
|
}
|
||||||
|
}
|
@ -19,6 +19,7 @@ use common::{
|
|||||||
shared_server_config::ServerConstants,
|
shared_server_config::ServerConstants,
|
||||||
slowjob::SlowJobPool,
|
slowjob::SlowJobPool,
|
||||||
terrain::{Block, MapSizeLg, TerrainChunk, TerrainGrid},
|
terrain::{Block, MapSizeLg, TerrainChunk, TerrainGrid},
|
||||||
|
tether,
|
||||||
time::DayPeriod,
|
time::DayPeriod,
|
||||||
trade::Trades,
|
trade::Trades,
|
||||||
vol::{ReadVol, WriteVol},
|
vol::{ReadVol, WriteVol},
|
||||||
@ -202,6 +203,8 @@ impl State {
|
|||||||
ecs.register::<Is<Mount>>();
|
ecs.register::<Is<Mount>>();
|
||||||
ecs.register::<Is<Rider>>();
|
ecs.register::<Is<Rider>>();
|
||||||
ecs.register::<Is<VolumeRider>>();
|
ecs.register::<Is<VolumeRider>>();
|
||||||
|
ecs.register::<Is<tether::Leader>>();
|
||||||
|
ecs.register::<Is<tether::Follower>>();
|
||||||
ecs.register::<comp::Mass>();
|
ecs.register::<comp::Mass>();
|
||||||
ecs.register::<comp::Density>();
|
ecs.register::<comp::Density>();
|
||||||
ecs.register::<comp::Collider>();
|
ecs.register::<comp::Collider>();
|
||||||
|
@ -13,6 +13,7 @@ pub mod phys;
|
|||||||
pub mod projectile;
|
pub mod projectile;
|
||||||
mod shockwave;
|
mod shockwave;
|
||||||
mod stats;
|
mod stats;
|
||||||
|
mod tether;
|
||||||
|
|
||||||
// External
|
// External
|
||||||
use common_ecs::{dispatch, System};
|
use common_ecs::{dispatch, System};
|
||||||
@ -21,6 +22,7 @@ use specs::DispatcherBuilder;
|
|||||||
pub fn add_local_systems(dispatch_builder: &mut DispatcherBuilder) {
|
pub fn add_local_systems(dispatch_builder: &mut DispatcherBuilder) {
|
||||||
//TODO: don't run interpolation on server
|
//TODO: don't run interpolation on server
|
||||||
dispatch::<interpolation::Sys>(dispatch_builder, &[]);
|
dispatch::<interpolation::Sys>(dispatch_builder, &[]);
|
||||||
|
dispatch::<tether::Sys>(dispatch_builder, &[]);
|
||||||
dispatch::<mount::Sys>(dispatch_builder, &[]);
|
dispatch::<mount::Sys>(dispatch_builder, &[]);
|
||||||
dispatch::<controller::Sys>(dispatch_builder, &[&mount::Sys::sys_name()]);
|
dispatch::<controller::Sys>(dispatch_builder, &[&mount::Sys::sys_name()]);
|
||||||
dispatch::<character_behavior::Sys>(dispatch_builder, &[&controller::Sys::sys_name()]);
|
dispatch::<character_behavior::Sys>(dispatch_builder, &[&controller::Sys::sys_name()]);
|
||||||
|
106
common/systems/src/tether.rs
Normal file
106
common/systems/src/tether.rs
Normal file
@ -0,0 +1,106 @@
|
|||||||
|
use common::{
|
||||||
|
comp::{Body, Collider, InputKind, Mass, Ori, Pos, Scale, Vel},
|
||||||
|
link::Is,
|
||||||
|
resources::DeltaTime,
|
||||||
|
tether::{Follower, Leader},
|
||||||
|
uid::UidAllocator,
|
||||||
|
util::Dir,
|
||||||
|
};
|
||||||
|
use common_ecs::{Job, Origin, Phase, System};
|
||||||
|
use specs::{
|
||||||
|
saveload::{Marker, MarkerAllocator},
|
||||||
|
Entities, Join, Read, ReadExpect, ReadStorage, WriteStorage,
|
||||||
|
};
|
||||||
|
use tracing::error;
|
||||||
|
use vek::*;
|
||||||
|
|
||||||
|
/// This system is responsible for controlling mounts
|
||||||
|
#[derive(Default)]
|
||||||
|
pub struct Sys;
|
||||||
|
impl<'a> System<'a> for Sys {
|
||||||
|
type SystemData = (
|
||||||
|
Read<'a, UidAllocator>,
|
||||||
|
Entities<'a>,
|
||||||
|
Read<'a, DeltaTime>,
|
||||||
|
ReadStorage<'a, Is<Leader>>,
|
||||||
|
ReadStorage<'a, Is<Follower>>,
|
||||||
|
WriteStorage<'a, Pos>,
|
||||||
|
WriteStorage<'a, Vel>,
|
||||||
|
WriteStorage<'a, Ori>,
|
||||||
|
ReadStorage<'a, Body>,
|
||||||
|
ReadStorage<'a, Scale>,
|
||||||
|
ReadStorage<'a, Collider>,
|
||||||
|
ReadStorage<'a, Mass>,
|
||||||
|
);
|
||||||
|
|
||||||
|
const NAME: &'static str = "tether";
|
||||||
|
const ORIGIN: Origin = Origin::Common;
|
||||||
|
const PHASE: Phase = Phase::Create;
|
||||||
|
|
||||||
|
fn run(
|
||||||
|
_job: &mut Job<Self>,
|
||||||
|
(
|
||||||
|
uid_allocator,
|
||||||
|
entities,
|
||||||
|
dt,
|
||||||
|
is_leaders,
|
||||||
|
is_followers,
|
||||||
|
mut positions,
|
||||||
|
mut velocities,
|
||||||
|
mut orientations,
|
||||||
|
bodies,
|
||||||
|
scales,
|
||||||
|
colliders,
|
||||||
|
masses,
|
||||||
|
): Self::SystemData,
|
||||||
|
) {
|
||||||
|
for (follower, is_follower, follower_body) in
|
||||||
|
(&entities, &is_followers, bodies.maybe()).join()
|
||||||
|
{
|
||||||
|
let Some(leader) = uid_allocator
|
||||||
|
.retrieve_entity_internal(is_follower.leader.id())
|
||||||
|
else { continue };
|
||||||
|
|
||||||
|
let (Some(leader_pos), Some(follower_pos)) = (
|
||||||
|
positions.get(leader).copied(),
|
||||||
|
positions.get(follower).copied(),
|
||||||
|
) else { continue };
|
||||||
|
|
||||||
|
let (Some(leader_mass), Some(follower_mass)) = (
|
||||||
|
masses.get(leader).copied(),
|
||||||
|
masses.get(follower).copied(),
|
||||||
|
) else { continue };
|
||||||
|
|
||||||
|
if velocities.contains(follower) && velocities.contains(leader) {
|
||||||
|
let tether_offset = orientations
|
||||||
|
.get(follower)
|
||||||
|
.map(|ori| {
|
||||||
|
ori.to_quat() * follower_body.map(|b| b.tether_offset()).unwrap_or_default()
|
||||||
|
})
|
||||||
|
.unwrap_or_default();
|
||||||
|
let tether_pos = follower_pos.0 + tether_offset;
|
||||||
|
let pull_factor =
|
||||||
|
(leader_pos.0.distance(tether_pos) - is_follower.tether_length).clamp(0.0, 1.0);
|
||||||
|
let strength = pull_factor * 30000.0;
|
||||||
|
let pull_dir = (leader_pos.0 - follower_pos.0)
|
||||||
|
.try_normalized()
|
||||||
|
.unwrap_or_default();
|
||||||
|
let impulse = pull_dir * strength * dt.0;
|
||||||
|
|
||||||
|
// Can't fail
|
||||||
|
velocities.get_mut(follower).unwrap().0 += impulse / follower_mass.0;
|
||||||
|
velocities.get_mut(leader).unwrap().0 -= impulse / leader_mass.0;
|
||||||
|
|
||||||
|
if let Some(follower_ori) = orientations.get_mut(follower) {
|
||||||
|
let turn_strength = pull_factor
|
||||||
|
* follower_body
|
||||||
|
.map(|b| b.tether_offset().magnitude())
|
||||||
|
.unwrap_or(0.0)
|
||||||
|
* 4.0;
|
||||||
|
let target_ori = follower_ori.yawed_towards(Dir::new(pull_dir));
|
||||||
|
*follower_ori = follower_ori.slerped_towards(target_ori, turn_strength * dt.0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -46,7 +46,8 @@ use common::{
|
|||||||
resources::{BattleMode, PlayerPhysicsSettings, Secs, Time, TimeOfDay, TimeScale},
|
resources::{BattleMode, PlayerPhysicsSettings, Secs, Time, TimeOfDay, TimeScale},
|
||||||
rtsim::{Actor, Role},
|
rtsim::{Actor, Role},
|
||||||
terrain::{Block, BlockKind, CoordinateConversions, SpriteKind, TerrainChunkSize},
|
terrain::{Block, BlockKind, CoordinateConversions, SpriteKind, TerrainChunkSize},
|
||||||
uid::Uid,
|
tether::Tethered,
|
||||||
|
uid::{IdMaps, Uid},
|
||||||
vol::ReadVol,
|
vol::ReadVol,
|
||||||
weather, Damage, DamageKind, DamageSource, Explosion, LoadoutBuilder, RadiusEffect,
|
weather, Damage, DamageKind, DamageSource, Explosion, LoadoutBuilder, RadiusEffect,
|
||||||
};
|
};
|
||||||
@ -1558,8 +1559,15 @@ fn handle_spawn(
|
|||||||
args: Vec<String>,
|
args: Vec<String>,
|
||||||
action: &ServerChatCommand,
|
action: &ServerChatCommand,
|
||||||
) -> CmdResult<()> {
|
) -> CmdResult<()> {
|
||||||
match parse_cmd_args!(args, String, npc::NpcBody, u32, bool, f32) {
|
match parse_cmd_args!(args, String, npc::NpcBody, u32, bool, f32, bool) {
|
||||||
(Some(opt_align), Some(npc::NpcBody(id, mut body)), opt_amount, opt_ai, opt_scale) => {
|
(
|
||||||
|
Some(opt_align),
|
||||||
|
Some(npc::NpcBody(id, mut body)),
|
||||||
|
opt_amount,
|
||||||
|
opt_ai,
|
||||||
|
opt_scale,
|
||||||
|
opt_tethered,
|
||||||
|
) => {
|
||||||
let uid = uid(server, target, "target")?;
|
let uid = uid(server, target, "target")?;
|
||||||
let alignment = parse_alignment(uid, &opt_align)?;
|
let alignment = parse_alignment(uid, &opt_align)?;
|
||||||
let amount = opt_amount.filter(|x| *x > 0).unwrap_or(1).min(50);
|
let amount = opt_amount.filter(|x| *x > 0).unwrap_or(1).min(50);
|
||||||
@ -1608,6 +1616,28 @@ fn handle_spawn(
|
|||||||
|
|
||||||
let new_entity = entity_base.build();
|
let new_entity = entity_base.build();
|
||||||
|
|
||||||
|
if opt_tethered == Some(true) {
|
||||||
|
let tether_leader = server
|
||||||
|
.state
|
||||||
|
.read_component_cloned::<Is<Rider>>(target)
|
||||||
|
.map(|is_rider| is_rider.mount)
|
||||||
|
.or_else(|| server.state.ecs().uid_from_entity(target));
|
||||||
|
let tether_follower = server.state.ecs().uid_from_entity(new_entity);
|
||||||
|
|
||||||
|
if let (Some(leader), Some(follower)) = (tether_leader, tether_follower) {
|
||||||
|
server
|
||||||
|
.state
|
||||||
|
.link(Tethered {
|
||||||
|
leader,
|
||||||
|
follower,
|
||||||
|
tether_length: 4.0,
|
||||||
|
})
|
||||||
|
.map_err(|_| "Failed to tether entities")?;
|
||||||
|
} else {
|
||||||
|
return Err("Tether members don't have Uids.".into());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Add to group system if a pet
|
// Add to group system if a pet
|
||||||
if matches!(alignment, comp::Alignment::Owned { .. }) {
|
if matches!(alignment, comp::Alignment::Owned { .. }) {
|
||||||
let server_eventbus =
|
let server_eventbus =
|
||||||
@ -1748,7 +1778,7 @@ fn handle_spawn_ship(
|
|||||||
args: Vec<String>,
|
args: Vec<String>,
|
||||||
_action: &ServerChatCommand,
|
_action: &ServerChatCommand,
|
||||||
) -> CmdResult<()> {
|
) -> CmdResult<()> {
|
||||||
let (body_name, angle) = parse_cmd_args!(args, String, f32);
|
let (body_name, angle, tethered) = parse_cmd_args!(args, String, f32, bool);
|
||||||
let mut pos = position(server, target, "target")?;
|
let mut pos = position(server, target, "target")?;
|
||||||
pos.0.z += 2.0;
|
pos.0.z += 2.0;
|
||||||
const DESTINATION_RADIUS: f32 = 2000.0;
|
const DESTINATION_RADIUS: f32 = 2000.0;
|
||||||
@ -1767,16 +1797,40 @@ fn handle_spawn_ship(
|
|||||||
let mut builder = server
|
let mut builder = server
|
||||||
.state
|
.state
|
||||||
.create_ship(pos, ori, ship, |ship| ship.make_collider());
|
.create_ship(pos, ori, ship, |ship| ship.make_collider());
|
||||||
if let Some(pos) = destination {
|
|
||||||
let (kp, ki, kd) =
|
// if let Some(pos) = destination {
|
||||||
comp::agent::pid_coefficients(&comp::Body::Ship(ship)).unwrap_or((1.0, 0.0, 0.0));
|
// let (kp, ki, kd) =
|
||||||
fn pure_z(sp: Vec3<f32>, pv: Vec3<f32>) -> f32 { (sp - pv).z }
|
// comp::agent::pid_coefficients(&comp::Body::Ship(ship)).unwrap_or((1.
|
||||||
let agent = comp::Agent::from_body(&comp::Body::Ship(ship))
|
// 0, 0.0, 0.0)); fn pure_z(sp: Vec3<f32>, pv: Vec3<f32>) -> f32 { (sp -
|
||||||
.with_destination(pos)
|
// pv).z } let agent = comp::Agent::from_body(&comp::Body::Ship(ship))
|
||||||
.with_position_pid_controller(comp::PidController::new(kp, ki, kd, pos, 0.0, pure_z));
|
// .with_destination(pos)
|
||||||
builder = builder.with(agent);
|
// .with_position_pid_controller(comp::PidController::new(kp, ki, kd,
|
||||||
|
// pos, 0.0, pure_z)); builder = builder.with(agent);
|
||||||
|
// }
|
||||||
|
|
||||||
|
let new_entity = builder.build();
|
||||||
|
|
||||||
|
if tethered == Some(true) {
|
||||||
|
let tether_leader = server
|
||||||
|
.state
|
||||||
|
.read_component_cloned::<Is<Rider>>(target)
|
||||||
|
.map(|is_rider| is_rider.mount)
|
||||||
|
.or_else(|| server.state.ecs().uid_from_entity(target));
|
||||||
|
let tether_follower = server.state.ecs().uid_from_entity(new_entity);
|
||||||
|
|
||||||
|
if let (Some(leader), Some(follower)) = (tether_leader, tether_follower) {
|
||||||
|
server
|
||||||
|
.state
|
||||||
|
.link(Tethered {
|
||||||
|
leader,
|
||||||
|
follower,
|
||||||
|
tether_length: 6.0,
|
||||||
|
})
|
||||||
|
.map_err(|_| "Failed to tether entities")?;
|
||||||
|
} else {
|
||||||
|
return Err("Tether members don't have Uids.".into());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
builder.build();
|
|
||||||
|
|
||||||
server.notify_client(
|
server.notify_client(
|
||||||
client,
|
client,
|
||||||
|
Loading…
Reference in New Issue
Block a user