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::