diff --git a/CHANGELOG.md b/CHANGELOG.md index ea621f30eb..b42e196162 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -22,6 +22,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Recipe for a new leather pack - Keybinds for zooming the camera (Defaults: ']' for zooming in and '[' for zooming out) - Added the ability to make pets sit, they wont follow nor defend you in this state +- Portals that spawn in place of the last staircase at old style dungeons to prevent stair cheesing ### Changed @@ -53,6 +54,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Fixed - Fixed wild roaming cyclop loot table to not drop the quarry key +- Dungeons now have an outer wall, preventing them from intersecting with caves or leaving holes in sides of mountains. ## [0.15.0] - 2023-07-01 diff --git a/assets/voxygen/audio/sfx.ron b/assets/voxygen/audio/sfx.ron index 93dca1865d..88600e438a 100644 --- a/assets/voxygen/audio/sfx.ron +++ b/assets/voxygen/audio/sfx.ron @@ -1498,6 +1498,20 @@ threshold: 0.8, subtitle: "subtitle-attack-shovel", ), + PortalActivated: ( + files: [ + "voxygen.audio.sfx.ambient.portal_2", + ], + threshold: 0.8, + subtitle: "subtitle-portal-activated", + ), + TeleportedByPortal: ( + files: [ + "voxygen.audio.sfx.ambient.portal_1", + ], + threshold: 0.8, + subtitle: "subtitle-portal-teleported", + ), // Utterances (NPCs) diff --git a/assets/voxygen/audio/sfx/ambient/portal_1.ogg b/assets/voxygen/audio/sfx/ambient/portal_1.ogg new file mode 100644 index 0000000000..b98d209e4b --- /dev/null +++ b/assets/voxygen/audio/sfx/ambient/portal_1.ogg @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:496209e12bbd83e1f4de9dc519e3f626ad3dd0cc462c94f73c6abb1ac5921b80 +size 40960 diff --git a/assets/voxygen/audio/sfx/ambient/portal_2.ogg b/assets/voxygen/audio/sfx/ambient/portal_2.ogg new file mode 100644 index 0000000000..fbc1674497 --- /dev/null +++ b/assets/voxygen/audio/sfx/ambient/portal_2.ogg @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:cef2ca0198aa33ba0d365b2f8440762e53406d0da43f4c9c4c77fc166f4d1e57 +size 196653 diff --git a/assets/voxygen/i18n/en/hud/misc.ftl b/assets/voxygen/i18n/en/hud/misc.ftl index 1115e37759..cc99b03fe2 100644 --- a/assets/voxygen/i18n/en/hud/misc.ftl +++ b/assets/voxygen/i18n/en/hud/misc.ftl @@ -39,6 +39,7 @@ hud-auto_walk_indicator = Auto walk/swim active hud-zoom_lock_indicator-remind = Zoom locked hud-zoom_lock_indicator-enable = Camera zoom locked hud-zoom_lock_indicator-disable = Camera zoom unlocked +hud-activate = Activate hud-collect = Collect hud-pick_up = Pick up hud-open = Open @@ -58,3 +59,4 @@ hud-follow = Follow hud-stay= Stay hud-sit = Sit hud-steer = Steer +hud-portal = Portal diff --git a/assets/voxygen/i18n/en/hud/subtitles.ftl b/assets/voxygen/i18n/en/hud/subtitles.ftl index c94df031e1..561890670f 100644 --- a/assets/voxygen/i18n/en/hud/subtitles.ftl +++ b/assets/voxygen/i18n/en/hud/subtitles.ftl @@ -4,6 +4,8 @@ subtitle-bees = Bees buzzing subtitle-owl = Owl hooting subtitle-running_water = Water bubbling subtitle-lightning = Thunder +subtitle-portal-activated = Portal Activated +subtitle-portal-teleported = Teleported via portal subtitle-footsteps_grass = Walking on grass subtitle-footsteps_earth = Walking on dirt diff --git a/assets/voxygen/shaders/particle-vert.glsl b/assets/voxygen/shaders/particle-vert.glsl index 6352fde4b9..af733b1492 100644 --- a/assets/voxygen/shaders/particle-vert.glsl +++ b/assets/voxygen/shaders/particle-vert.glsl @@ -83,6 +83,7 @@ const int GIGA_SNOW = 42; const int CYCLOPS_CHARGE = 43; const int PORTAL_FIZZ = 45; const int INK = 46; +const int UPWARD_PORTAL_FIZZ = 47; // meters per second squared (acceleration) const float earth_gravity = 9.807; @@ -703,6 +704,15 @@ void main() { spin_in_axis(vec3(rand6, rand7, rand8), percent() * 10 + 3 * rand9) ); break; + case UPWARD_PORTAL_FIZZ: + f_reflect = 0.0; + attr = Attr( + inst_dir * percent(), + vec3(max(1.0, 0.05 * length(start_pos + inst_dir * percent()))), + vec4(vec3(1.23, 1.41, 1.44), 1.0),// * (1.0 - length(inst_dir) * 0.1), + identity()//spin_in_axis(perp_axis, asin(inst_dir.z / length(inst_dir)) + PI / 2.0) + ); + break; default: attr = Attr( linear_motion( diff --git a/assets/voxygen/voxel/object/portal.vox b/assets/voxygen/voxel/object/portal.vox new file mode 100644 index 0000000000..1cf5786208 --- /dev/null +++ b/assets/voxygen/voxel/object/portal.vox @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:441d3720809b51ea6a9885346f470efaa134e2f507052d95e1986100f23a3ace +size 50670 diff --git a/assets/voxygen/voxel/object/portal_active.vox b/assets/voxygen/voxel/object/portal_active.vox new file mode 100644 index 0000000000..8136fbf713 --- /dev/null +++ b/assets/voxygen/voxel/object/portal_active.vox @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f4ad6e5f6bc14a9bdf497159d885a3bf38d3b474c8e305f6deac57ab631765fb +size 50670 diff --git a/assets/voxygen/voxel/object_manifest.ron b/assets/voxygen/voxel/object_manifest.ron index 6b220d517d..c66b0ecaa9 100644 --- a/assets/voxygen/voxel/object_manifest.ron +++ b/assets/voxygen/voxel/object_manifest.ron @@ -979,4 +979,24 @@ central: ("armor.empty"), ) ), + Portal: ( + bone0: ( + offset: (-33.0, -33.0, 0.0), + central: ("object.portal"), + ), + bone1: ( + offset: (0.0, 0.0, 0.0), + central: ("armor.empty"), + ) + ), + PortalActive: ( + bone0: ( + offset: (-33.0, -33.0, 0.0), + central: ("object.portal_active"), + ), + bone1: ( + offset: (0.0, 0.0, 0.0), + central: ("armor.empty"), + ) + ), }) diff --git a/assets/world/structure/dungeon/temperate_entrance/ruins_4.vox b/assets/world/structure/dungeon/temperate_entrance/ruins_4.vox index 9a78312a9a..bc5c9fd3ab 100644 --- a/assets/world/structure/dungeon/temperate_entrance/ruins_4.vox +++ b/assets/world/structure/dungeon/temperate_entrance/ruins_4.vox @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:3bf9a8a38d5e42b1cf731df40cda238104dd9f17b261d0ce48f89da8f683a376 -size 63972 +oid sha256:c4a1e96fd77bf5c630674666ab1db1dc92a039ed61529f21ff5c505a6fa127da +size 93851 diff --git a/client/src/lib.rs b/client/src/lib.rs index cd07e42870..122ec37837 100644 --- a/client/src/lib.rs +++ b/client/src/lib.rs @@ -1625,6 +1625,12 @@ impl Client { } } + pub fn activate_portal(&mut self, portal: Uid) { + self.send_msg(ClientGeneral::ControlEvent(ControlEvent::ActivatePortal( + portal, + ))); + } + fn control_action(&mut self, control_action: ControlAction) { if let Some(controller) = self .state diff --git a/common/src/cmd.rs b/common/src/cmd.rs index a420e7224b..78cc5955ee 100644 --- a/common/src/cmd.rs +++ b/common/src/cmd.rs @@ -310,6 +310,7 @@ pub enum ServerChatCommand { Object, PermitBuild, Players, + Portal, Region, ReloadChunks, RemoveLights, @@ -618,6 +619,17 @@ impl ServerChatCommand { Some(Admin), ), ServerChatCommand::Players => cmd(vec![], "Lists players currently online", None), + ServerChatCommand::Portal => cmd( + vec![ + Float("x", 0., Required), + Float("y", 0., Required), + Float("z", 0., Required), + Boolean("requires_no_aggro", "true".to_string(), Optional), + Float("buildup_time", 5., Optional), + ], + "Spawns a portal", + Some(Admin), + ), ServerChatCommand::ReloadChunks => cmd( vec![], "Reloads all chunks loaded on the server", @@ -890,6 +902,7 @@ impl ServerChatCommand { ServerChatCommand::Object => "object", ServerChatCommand::PermitBuild => "permit_build", ServerChatCommand::Players => "players", + ServerChatCommand::Portal => "portal", ServerChatCommand::Region => "region", ServerChatCommand::ReloadChunks => "reload_chunks", ServerChatCommand::RemoveLights => "remove_lights", diff --git a/common/src/comp/body.rs b/common/src/comp/body.rs index 6cf0118a78..a3fad51819 100644 --- a/common/src/comp/body.rs +++ b/common/src/comp/body.rs @@ -261,6 +261,13 @@ impl Body { pub fn is_campfire(&self) -> bool { matches!(self, Body::Object(object::Body::CampfireLit)) } + pub fn is_portal(&self) -> bool { + matches!( + self, + Body::Object(object::Body::Portal | object::Body::PortalActive) + ) + } + pub fn bleeds(&self) -> bool { !matches!( self, diff --git a/common/src/comp/body/object.rs b/common/src/comp/body/object.rs index 44735011e1..313e617f07 100644 --- a/common/src/comp/body/object.rs +++ b/common/src/comp/body/object.rs @@ -110,6 +110,8 @@ make_case_elim!( Mine = 95, LightningBolt = 96, SpearIcicle = 97, + Portal = 98, + PortalActive = 99, } ); @@ -120,7 +122,7 @@ impl Body { } } -pub const ALL_OBJECTS: [Body; 98] = [ +pub const ALL_OBJECTS: [Body; 100] = [ Body::Arrow, Body::Bomb, Body::Scarecrow, @@ -219,6 +221,8 @@ pub const ALL_OBJECTS: [Body; 98] = [ Body::Mine, Body::LightningBolt, Body::SpearIcicle, + Body::Portal, + Body::PortalActive, ]; impl From for super::Body { @@ -326,6 +330,8 @@ impl Body { Body::Mine => "mine", Body::LightningBolt => "lightning_bolt", Body::SpearIcicle => "spear_icicle", + Body::Portal => "portal", + Body::PortalActive => "portal_active", } } @@ -450,6 +456,7 @@ impl Body { Body::AdletTrap => 10.0, Body::Mine => 100.0, Body::LightningBolt | Body::SpearIcicle => 20000.0, + Body::Portal | Body::PortalActive => 10., // I dont know really }; Mass(m) diff --git a/common/src/comp/controller.rs b/common/src/comp/controller.rs index c0e1e82159..fe84c1f642 100644 --- a/common/src/comp/controller.rs +++ b/common/src/comp/controller.rs @@ -158,6 +158,7 @@ pub enum ControlEvent { auxiliary_key: ability::AuxiliaryKey, new_ability: ability::AuxiliaryAbility, }, + ActivatePortal(Uid), } #[derive(Clone, Copy, Debug, PartialEq, Serialize, Deserialize)] diff --git a/common/src/comp/misc.rs b/common/src/comp/misc.rs index c803ec64e9..74269e1a86 100644 --- a/common/src/comp/misc.rs +++ b/common/src/comp/misc.rs @@ -1,8 +1,12 @@ use super::item::Reagent; -use crate::{resources::Time, uid::Uid}; +use crate::{ + resources::{Secs, Time}, + uid::Uid, +}; use serde::{Deserialize, Serialize}; use specs::{Component, DerefFlaggedStorage}; use std::time::Duration; +use vek::Vec3; #[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)] pub enum Object { @@ -17,8 +21,36 @@ pub enum Object { spawned_at: Time, timeout: Duration, }, + Portal { + target: Vec3, + requires_no_aggro: bool, + buildup_time: Secs, + }, } impl Component for Object { type Storage = DerefFlaggedStorage>; } + +#[derive(Clone)] +pub struct PortalData { + pub target: Vec3, + pub requires_no_aggro: bool, + pub buildup_time: Secs, +} + +impl From for Object { + fn from( + PortalData { + target, + requires_no_aggro, + buildup_time, + }: PortalData, + ) -> Self { + Self::Portal { + target, + requires_no_aggro, + buildup_time, + } + } +} diff --git a/common/src/comp/mod.rs b/common/src/comp/mod.rs index 198bf4402d..bed901532f 100644 --- a/common/src/comp/mod.rs +++ b/common/src/comp/mod.rs @@ -23,7 +23,7 @@ mod last; mod location; pub mod loot_owner; pub mod melee; -mod misc; +pub mod misc; pub mod ori; pub mod pet; mod phys; @@ -34,6 +34,7 @@ pub mod projectile; pub mod shockwave; pub mod skillset; mod stats; +pub mod teleport; pub mod visual; // Reexports @@ -103,6 +104,7 @@ pub use self::{ SkillGroup, SkillGroupKind, SkillSet, }, stats::{Stats, StatsModifier}, + teleport::Teleporting, visual::{LightAnimation, LightEmitter}, }; diff --git a/common/src/comp/teleport.rs b/common/src/comp/teleport.rs new file mode 100644 index 0000000000..683ad323c4 --- /dev/null +++ b/common/src/comp/teleport.rs @@ -0,0 +1,13 @@ +use specs::{Component, DerefFlaggedStorage, Entity}; + +use crate::resources::Time; + +#[derive(Copy, Clone, Debug, PartialEq)] +pub struct Teleporting { + pub portal: Entity, + pub end_time: Time, +} + +impl Component for Teleporting { + type Storage = DerefFlaggedStorage>; +} diff --git a/common/src/consts.rs b/common/src/consts.rs index 033f743278..526deee752 100644 --- a/common/src/consts.rs +++ b/common/src/consts.rs @@ -32,3 +32,5 @@ pub const SOUND_TRAVEL_DIST_PER_VOLUME: f32 = 3.0; // Stat increase per level (multiplied by 10 compared to what you'll see in UI) pub const ENERGY_PER_LEVEL: u16 = 5; pub const HP_PER_LEVEL: u16 = 5; + +pub const TELEPORTER_RADIUS: f32 = 3.; diff --git a/common/src/event.rs b/common/src/event.rs index 006607afd5..07986b0589 100644 --- a/common/src/event.rs +++ b/common/src/event.rs @@ -6,6 +6,7 @@ use crate::{ agent::Sound, dialogue::Subject, invite::{InviteKind, InviteResponse}, + misc::PortalData, DisconnectReason, Ori, Pos, }, lottery::LootSpec, @@ -239,6 +240,7 @@ pub enum ServerEvent { driver: Option, }, CreateWaypoint(Vec3), + CreateTeleporter(Vec3, PortalData), ClientDisconnect(EcsEntity, DisconnectReason), ClientDisconnectWithoutPersistence(EcsEntity), Command(EcsEntity, String, Vec), @@ -331,6 +333,14 @@ pub enum ServerEvent { RemoveLightEmitter { entity: EcsEntity, }, + TeleportToPosition { + entity: EcsEntity, + position: Vec3, + }, + StartTeleporting { + entity: EcsEntity, + portal: EcsEntity, + }, } pub struct EventBus { diff --git a/common/src/generation.rs b/common/src/generation.rs index 404833a148..c33202ca0e 100644 --- a/common/src/generation.rs +++ b/common/src/generation.rs @@ -3,6 +3,7 @@ use crate::{ comp::{ self, agent, humanoid, inventory::loadout_builder::{LoadoutBuilder, LoadoutSpec}, + misc::PortalData, Alignment, Body, Item, }, lottery::LootSpec, @@ -163,10 +164,15 @@ pub fn try_all_entity_configs() -> Result, Error> { Ok(configs.ids().map(|id| id.to_string()).collect()) } +#[derive(Clone)] +pub enum SpecialEntity { + Waypoint, + Teleporter(PortalData), +} + #[derive(Clone)] pub struct EntityInfo { pub pos: Vec3, - pub is_waypoint: bool, // Edge case, overrides everything else pub alignment: Alignment, /// Parameterises agent behaviour pub has_agency: bool, @@ -194,13 +200,15 @@ pub struct EntityInfo { // we can't use DHashMap, do we want to move that into common? pub trading_information: Option, //Option>, /* price and available amount */ + + // Edge cases, override everything else + pub special_entity: Option, } impl EntityInfo { pub fn at(pos: Vec3) -> Self { Self { pos, - is_waypoint: false, alignment: Alignment::Wild, has_agency: true, @@ -219,6 +227,7 @@ impl EntityInfo { skillset_asset: None, pet: None, trading_information: None, + special_entity: None, } } @@ -377,8 +386,8 @@ impl EntityInfo { } #[must_use] - pub fn into_waypoint(mut self) -> Self { - self.is_waypoint = true; + pub fn into_special(mut self, special: SpecialEntity) -> Self { + self.special_entity = Some(special); self } diff --git a/common/src/outcome.rs b/common/src/outcome.rs index 4e0b0a9a46..1338d79b40 100644 --- a/common/src/outcome.rs +++ b/common/src/outcome.rs @@ -140,6 +140,12 @@ pub enum Outcome { GroundDig { pos: Vec3, }, + PortalActivated { + pos: Vec3, + }, + TeleportedByPortal { + pos: Vec3, + }, } impl Outcome { @@ -170,6 +176,8 @@ impl Outcome { | Outcome::FlamethrowerCharge { pos } | Outcome::LaserBeam { pos } | Outcome::GroundDig { pos } + | Outcome::PortalActivated { pos } + | Outcome::TeleportedByPortal { pos} | Outcome::Glider { pos, .. } => Some(*pos), Outcome::BreakBlock { pos, .. } | Outcome::SpriteUnlocked { pos } diff --git a/common/state/src/state.rs b/common/state/src/state.rs index 01a1e23b31..c492e35c3c 100644 --- a/common/state/src/state.rs +++ b/common/state/src/state.rs @@ -218,6 +218,7 @@ impl State { ecs.register::(); ecs.register::(); ecs.register::(); + ecs.register::(); // Register components send from clients -> server ecs.register::(); diff --git a/common/systems/src/controller.rs b/common/systems/src/controller.rs index 9da0652d13..4ad8a18177 100644 --- a/common/systems/src/controller.rs +++ b/common/systems/src/controller.rs @@ -138,6 +138,11 @@ impl<'a> System<'a> for Sys { stance: Stance::None, }); }, + ControlEvent::ActivatePortal(portal_uid) => { + if let Some(portal) = read_data.id_maps.uid_entity(portal_uid) { + server_emitter.emit(ServerEvent::StartTeleporting { entity, portal }); + } + }, } } } diff --git a/server/src/cmd.rs b/server/src/cmd.rs index 5b1a4fbba7..6072e3cd3a 100644 --- a/server/src/cmd.rs +++ b/server/src/cmd.rs @@ -33,21 +33,20 @@ use common::{ slot::Slot, }, invite::InviteKind, - AdminRole, ChatType, Inventory, Item, LightEmitter, Presence, PresenceKind, WaypointArea, + misc::PortalData, + AdminRole, ChatType, Inventory, Item, LightEmitter, WaypointArea, }, depot, effect::Effect, event::{EventBus, ServerEvent}, generation::{EntityConfig, EntityInfo}, - link::Is, - mounting::{Rider, VolumeRider}, npc::{self, get_npc_name}, outcome::Outcome, parse_cmd_args, resources::{BattleMode, PlayerPhysicsSettings, Secs, Time, TimeOfDay, TimeScale}, rtsim::{Actor, Role}, terrain::{Block, BlockKind, CoordinateConversions, SpriteKind, TerrainChunkSize}, - uid::{IdMaps, Uid}, + uid::Uid, vol::ReadVol, weather, Damage, DamageKind, DamageSource, Explosion, LoadoutBuilder, RadiusEffect, }; @@ -167,6 +166,7 @@ fn do_command( ServerChatCommand::Object => handle_object, ServerChatCommand::PermitBuild => handle_permit_build, ServerChatCommand::Players => handle_players, + ServerChatCommand::Portal => handle_spawn_portal, ServerChatCommand::Region => handle_region, ServerChatCommand::ReloadChunks => handle_reload_chunks, ServerChatCommand::RemoveLights => handle_remove_lights, @@ -222,89 +222,6 @@ fn position(server: &Server, entity: EcsEntity, descriptor: &str) -> CmdResult( - server: &mut Server, - entity: EcsEntity, - descriptor: &str, - dismount_volume: Option, - f: impl for<'a> FnOnce(&'a mut comp::Pos) -> T, -) -> CmdResult { - if dismount_volume.unwrap_or(true) { - server - .state - .ecs() - .write_storage::>() - .remove(entity); - } - - let entity = server - .state - .read_storage::>() - .get(entity) - .and_then(|is_rider| { - server - .state - .ecs() - .read_resource::() - .uid_entity(is_rider.mount) - }) - .map(Ok) - .or_else(|| { - server - .state - .read_storage::>() - .get(entity) - .and_then(|volume_rider| { - Some(match volume_rider.pos.kind { - common::mounting::Volume::Terrain => { - Err("Tried to move the world.".to_string()) - }, - common::mounting::Volume::Entity(uid) => Ok(server - .state - .ecs() - .read_resource::() - .uid_entity(uid)?), - }) - }) - }) - .unwrap_or(Ok(entity))?; - - let mut maybe_pos = None; - - let res = server - .state - .ecs() - .write_storage::() - .get_mut(entity) - .map(|pos| { - let res = f(pos); - maybe_pos = Some(pos.0); - res - }) - .ok_or_else(|| format!("Cannot get position for {:?}!", descriptor)); - - if let Some(pos) = maybe_pos { - if server - .state - .ecs() - .read_storage::() - .get(entity) - .map(|presence| presence.kind == PresenceKind::Spectator) - .unwrap_or(false) - { - server.notify_client(entity, ServerGeneral::SpectatePosition(pos)); - } else { - server - .state - .ecs() - .write_storage::() - .get_mut(entity) - .map(|force_update| force_update.update()); - } - } - res -} - fn insert_or_replace_component( server: &mut Server, entity: EcsEntity, @@ -694,6 +611,9 @@ fn handle_make_npc( NpcData::Waypoint(_) => { return Err("Waypoint spawning is not implemented".to_owned()); }, + NpcData::Teleporter(_, _) => { + return Err("Teleporter spawning is not implemented".to_owned()); + }, NpcData::Data { inventory, pos, @@ -855,9 +775,12 @@ fn handle_jump( ) -> CmdResult<()> { if let (Some(x), Some(y), Some(z), dismount_volume) = parse_cmd_args!(args, f32, f32, f32, bool) { - position_mut(server, target, "target", dismount_volume, |current_pos| { - current_pos.0 += Vec3::new(x, y, z) - }) + server + .state + .position_mut(target, dismount_volume.unwrap_or(true), |current_pos| { + current_pos.0 += Vec3::new(x, y, z) + }) + .map_err(ToString::to_string) } else { Err(action.help_string()) } @@ -872,9 +795,12 @@ fn handle_goto( ) -> CmdResult<()> { if let (Some(x), Some(y), Some(z), dismount_volume) = parse_cmd_args!(args, f32, f32, f32, bool) { - position_mut(server, target, "target", dismount_volume, |current_pos| { - current_pos.0 = Vec3::new(x, y, z) - }) + server + .state + .position_mut(target, dismount_volume.unwrap_or(true), |current_pos| { + current_pos.0 = Vec3::new(x, y, z) + }) + .map_err(ToString::to_string) } else { Err(action.help_string()) } @@ -907,9 +833,12 @@ fn handle_site( false, ); - position_mut(server, target, "target", dismount_volume, |current_pos| { - current_pos.0 = site_pos - }) + server + .state + .position_mut(target, dismount_volume.unwrap_or(true), |current_pos| { + current_pos.0 = site_pos + }) + .map_err(ToString::to_string) } else { Err(action.help_string()) } @@ -932,9 +861,12 @@ fn handle_respawn( .ok_or("No waypoint set")? .get_pos(); - position_mut(server, target, "target", Some(true), |current_pos| { - current_pos.0 = waypoint; - }) + server + .state + .position_mut(target, true, |current_pos| { + current_pos.0 = waypoint; + }) + .map_err(ToString::to_string) } fn handle_kill( @@ -1304,9 +1236,12 @@ fn handle_tp( return Err(action.help_string()); }; let player_pos = position(server, player, "player")?; - position_mut(server, target, "target", dismount_volume, |target_pos| { - *target_pos = player_pos - }) + server + .state + .position_mut(target, dismount_volume.unwrap_or(true), |target_pos| { + *target_pos = player_pos + }) + .map_err(ToString::to_string) } fn handle_rtsim_tp( @@ -1334,9 +1269,12 @@ fn handle_rtsim_tp( } else { return Err(action.help_string()); }; - position_mut(server, target, "target", dismount_volume, |target_pos| { - target_pos.0 = pos; - }) + server + .state + .position_mut(target, dismount_volume.unwrap_or(true), |target_pos| { + target_pos.0 = pos; + }) + .map_err(ToString::to_string) } fn handle_rtsim_info( @@ -2038,6 +1976,39 @@ fn handle_players( Ok(()) } +fn handle_spawn_portal( + server: &mut Server, + client: EcsEntity, + target: EcsEntity, + args: Vec, + _action: &ServerChatCommand, +) -> CmdResult<()> { + let pos = position(server, target, "target")?; + + if let (Some(x), Some(y), Some(z), requires_no_aggro, buildup_time) = + parse_cmd_args!(args, f32, f32, f32, bool, f64) + { + let requires_no_aggro = requires_no_aggro.unwrap_or(false); + let buildup_time = Secs(buildup_time.unwrap_or(7.)); + server + .state + .create_teleporter(pos, PortalData { + target: Vec3::new(x, y, z), + buildup_time, + requires_no_aggro, + }) + .build(); + + server.notify_client( + client, + ServerGeneral::server_msg(ChatType::CommandInfo, "Spawned portal"), + ); + Ok(()) + } else { + Err("Invalid arguments".to_string()) + } +} + fn handle_build( server: &mut Server, client: EcsEntity, @@ -4075,9 +4046,12 @@ fn handle_location( if let Some(name) = parse_cmd_args!(args, String) { let loc = server.state.ecs().read_resource::().get(&name); match loc { - Ok(loc) => position_mut(server, target, "target", Some(true), |target_pos| { - target_pos.0 = loc; - }), + Ok(loc) => server + .state + .position_mut(target, true, |target_pos| { + target_pos.0 = loc; + }) + .map_err(ToString::to_string), Err(e) => Err(e.to_string()), } } else { diff --git a/server/src/events/entity_creation.rs b/server/src/events/entity_creation.rs index 1362722ccc..61ce93980b 100644 --- a/server/src/events/entity_creation.rs +++ b/server/src/events/entity_creation.rs @@ -9,6 +9,7 @@ use common::{ aura::{Aura, AuraKind, AuraTarget}, beam, buff::{BuffCategory, BuffData, BuffKind, BuffSource}, + misc::PortalData, ship::figuredata::VOXEL_COLLIDER_MANIFEST, shockwave, Alignment, BehaviorCapability, Body, ItemDrops, LightEmitter, Object, Ori, Pos, Projectile, TradingBehavior, Vel, WaypointArea, @@ -418,3 +419,10 @@ pub fn handle_create_waypoint(server: &mut Server, pos: Vec3) { ])) .build(); } + +pub fn handle_create_teleporter(server: &mut Server, pos: Vec3, portal: PortalData) { + server + .state + .create_teleporter(comp::Pos(pos), portal) + .build(); +} diff --git a/server/src/events/entity_manipulation.rs b/server/src/events/entity_manipulation.rs index 2e2664ead7..6d6f7b8ee9 100644 --- a/server/src/events/entity_manipulation.rs +++ b/server/src/events/entity_manipulation.rs @@ -22,8 +22,9 @@ use common::{ item::flatten_counted_items, loot_owner::LootOwnerKind, Alignment, Auras, Body, CharacterState, Energy, Group, Health, HealthChange, Inventory, - Player, Poise, Pos, SkillSet, Stats, + Object, Player, Poise, Pos, SkillSet, Stats, }, + consts::TELEPORTER_RADIUS, event::{EventBus, ServerEvent}, lottery::distribute_many, outcome::{HealthChangeInfo, Outcome}, @@ -43,7 +44,7 @@ use hashbrown::HashSet; use rand::Rng; use specs::{join::Join, Builder, Entity as EcsEntity, Entity, WorldExt}; use std::{collections::HashMap, iter, sync::Arc, time::Duration}; -use tracing::{debug, error}; +use tracing::{debug, error, warn}; use vek::{Vec2, Vec3}; #[derive(Hash, Eq, PartialEq)] @@ -1656,3 +1657,47 @@ pub fn handle_remove_light_emitter(server: &mut Server, entity: EcsEntity) { .write_storage::() .remove(entity); } + +pub fn handle_teleport_to_position(server: &mut Server, entity: EcsEntity, position: Vec3) { + if let Err(error) = server + .state + .position_mut(entity, true, |pos| pos.0 = position) + { + warn!("Failed to teleport entity: {error}"); + } +} + +pub fn handle_start_teleporting(server: &mut Server, entity: EcsEntity, portal: EcsEntity) { + let ecs = server.state.ecs(); + let positions = ecs.read_storage::(); + let mut teleportings = ecs.write_storage::(); + let now = ecs.read_resource::