diff --git a/assets/voxygen/voxel/object/portal.vox b/assets/voxygen/voxel/object/portal.vox
index f30dc90ddf..e8358bf549 100644
--- a/assets/voxygen/voxel/object/portal.vox
+++ b/assets/voxygen/voxel/object/portal.vox
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:af311d5461a03bec05799d20b3298b5d98f07a108603300b0514376965aa02e2
-size 28971
+oid sha256:a7908a4ec9f15eebaa3360598de0f44fdbb317bdebdf94538015bef363aa5a17
+size 64414
diff --git a/assets/voxygen/voxel/object/portal_active.vox b/assets/voxygen/voxel/object/portal_active.vox
new file mode 100644
index 0000000000..c8a3df3720
--- /dev/null
+++ b/assets/voxygen/voxel/object/portal_active.vox
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:98cfc67080935c5a832939c857dbe76912a93e233b93387a338f413c151f3929
+size 64414
diff --git a/assets/voxygen/voxel/object_manifest.ron b/assets/voxygen/voxel/object_manifest.ron
index 9592647da3..c66b0ecaa9 100644
--- a/assets/voxygen/voxel/object_manifest.ron
+++ b/assets/voxygen/voxel/object_manifest.ron
@@ -981,12 +981,22 @@
),
Portal: (
bone0: (
- offset: (-11.0, -1.5, 0.0),
+ 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/common/src/cmd.rs b/common/src/cmd.rs
index 9c201563e7..1a20c5132d 100644
--- a/common/src/cmd.rs
+++ b/common/src/cmd.rs
@@ -625,6 +625,7 @@ impl ServerChatCommand {
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(Moderator),
diff --git a/common/src/comp/body/object.rs b/common/src/comp/body/object.rs
index 261c4b6cd5..8119470f95 100644
--- a/common/src/comp/body/object.rs
+++ b/common/src/comp/body/object.rs
@@ -111,6 +111,7 @@ make_case_elim!(
LightningBolt = 96,
SpearIcicle = 97,
Portal = 98,
+ PortalActive = 99,
}
);
@@ -121,7 +122,7 @@ impl Body {
}
}
-pub const ALL_OBJECTS: [Body; 99] = [
+pub const ALL_OBJECTS: [Body; 100] = [
Body::Arrow,
Body::Bomb,
Body::Scarecrow,
@@ -221,6 +222,7 @@ pub const ALL_OBJECTS: [Body; 99] = [
Body::LightningBolt,
Body::SpearIcicle,
Body::Portal,
+ Body::PortalActive,
];
impl From
for super::Body {
@@ -329,6 +331,7 @@ impl Body {
Body::LightningBolt => "lightning_bolt",
Body::SpearIcicle => "spear_icicle",
Body::Portal => "portal",
+ Self::PortalActive => "portal_active",
}
}
@@ -453,7 +456,7 @@ impl Body {
Body::AdletTrap => 10.0,
Body::Mine => 100.0,
Body::LightningBolt | Body::SpearIcicle => 20000.0,
- Body::Portal => 10., // I dont know really
+ Body::Portal | Body::PortalActive => 10., // I dont know really
};
Mass(m)
diff --git a/common/src/comp/misc.rs b/common/src/comp/misc.rs
index f6002140f1..c803ec64e9 100644
--- a/common/src/comp/misc.rs
+++ b/common/src/comp/misc.rs
@@ -3,7 +3,6 @@ use crate::{resources::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 {
@@ -23,13 +22,3 @@ pub enum Object {
impl Component for Object {
type Storage = DerefFlaggedStorage>;
}
-
-#[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)]
-pub struct Teleporter {
- pub target: Vec3,
- pub requires_no_aggro: bool,
-}
-
-impl Component for Teleporter {
- type Storage = DerefFlaggedStorage>;
-}
diff --git a/common/src/comp/mod.rs b/common/src/comp/mod.rs
index 5fb3d86e07..e64dcce8da 100644
--- a/common/src/comp/mod.rs
+++ b/common/src/comp/mod.rs
@@ -34,6 +34,7 @@ pub mod projectile;
pub mod shockwave;
pub mod skillset;
mod stats;
+mod teleport;
pub mod visual;
// Reexports
@@ -86,7 +87,7 @@ pub use self::{
location::{MapMarker, MapMarkerChange, MapMarkerUpdate, Waypoint, WaypointArea},
loot_owner::LootOwner,
melee::{Melee, MeleeConstructor, MeleeConstructorKind},
- misc::{Object, Teleporter},
+ misc::Object,
ori::Ori,
pet::Pet,
phys::{
@@ -103,6 +104,7 @@ pub use self::{
SkillGroup, SkillGroupKind, SkillSet,
},
stats::{Stats, StatsModifier},
+ teleport::{Teleporter, Teleporting},
visual::{LightAnimation, LightEmitter},
};
diff --git a/common/src/comp/teleport.rs b/common/src/comp/teleport.rs
new file mode 100644
index 0000000000..63fbd4c2aa
--- /dev/null
+++ b/common/src/comp/teleport.rs
@@ -0,0 +1,27 @@
+use serde::{Deserialize, Serialize};
+use specs::{Component, DerefFlaggedStorage, Entity};
+use vek::Vec3;
+
+use crate::resources::{Secs, Time};
+
+#[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)]
+pub struct Teleporter {
+ pub target: Vec3,
+ pub requires_no_aggro: bool,
+ pub buildup_time: Secs,
+}
+
+impl Component for Teleporter {
+ type Storage = DerefFlaggedStorage>;
+}
+
+#[derive(Copy, Clone, Debug, PartialEq)]
+pub struct Teleporting {
+ pub teleport_start: Time,
+ pub portal: Entity,
+ pub end_time: Time,
+}
+
+impl Component for Teleporting {
+ type Storage = DerefFlaggedStorage>;
+}
diff --git a/common/state/src/state.rs b/common/state/src/state.rs
index 84341cf56e..47cbd990c6 100644
--- a/common/state/src/state.rs
+++ b/common/state/src/state.rs
@@ -220,6 +220,7 @@ impl State {
ecs.register::();
ecs.register::();
ecs.register::();
+ ecs.register::();
// Register components send from clients -> server
ecs.register::();
diff --git a/server/src/cmd.rs b/server/src/cmd.rs
index fe2bf29404..97b8c9e742 100644
--- a/server/src/cmd.rs
+++ b/server/src/cmd.rs
@@ -2051,15 +2051,17 @@ fn handle_spawn_portal(
) -> CmdResult<()> {
let pos = position(server, target, "target")?;
- if let (Some(x), Some(y), Some(z), requires_no_aggro) =
- parse_cmd_args!(args, f32, f32, f32, bool)
+ 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(0.));
server
.state
.create_teleporter(pos, comp::Teleporter {
target: Vec3::new(x, y, z),
requires_no_aggro,
+ buildup_time,
})
.build();
diff --git a/server/src/sys/teleporter.rs b/server/src/sys/teleporter.rs
index 442dae21de..f4a1c3a809 100644
--- a/server/src/sys/teleporter.rs
+++ b/server/src/sys/teleporter.rs
@@ -1,16 +1,22 @@
use common::{
- comp::{Agent, ForceUpdate, Player, Pos, Teleporter},
+ comp::{object, Agent, Body, ForceUpdate, Player, Pos, Teleporter, Teleporting},
+ resources::Time,
CachedSpatialGrid,
};
use common_ecs::{Origin, Phase, System};
use specs::{Entities, Join, Read, ReadStorage, WriteStorage};
+use vek::Vec3;
-const TELEPORT_RADIUS: f32 = 1.;
+const TELEPORT_RADIUS: f32 = 3.;
const MAX_AGGRO_DIST: f32 = 200.; // If an entity further than this is aggroed at a player, the portal will still work
#[derive(Default)]
pub struct Sys;
+fn in_portal_range(player_pos: Vec3, portal_pos: Vec3) -> bool {
+ player_pos.distance_squared(portal_pos) <= (TELEPORT_RADIUS).powi(2)
+}
+
impl<'a> System<'a> for Sys {
type SystemData = (
Entities<'a>,
@@ -19,7 +25,10 @@ impl<'a> System<'a> for Sys {
ReadStorage<'a, Teleporter>,
ReadStorage<'a, Agent>,
WriteStorage<'a, ForceUpdate>,
+ WriteStorage<'a, Teleporting>,
+ WriteStorage<'a, Body>,
Read<'a, CachedSpatialGrid>,
+ Read<'a, Time>,
);
const NAME: &'static str = "teleporter";
@@ -35,47 +44,101 @@ impl<'a> System<'a> for Sys {
teleporters,
agent,
mut forced_update,
+ mut teleporting,
+ mut bodies,
spatial_grid,
+ time,
): Self::SystemData,
) {
let mut attempt_teleport = vec![];
- let mut player_data = (&entities, &positions, &players).join();
+ let mut cancel_teleport = vec![];
+ let mut start_teleporting = vec![];
- for (_entity, teleporter_pos, teleporter) in (&entities, &positions, &teleporters).join() {
+ let mut player_data = (&entities, &positions, &players, teleporting.maybe()).join();
+
+ let check_aggro = |entity, pos: Vec3| {
+ spatial_grid
+ .0
+ .in_circle_aabr(pos.xy(), MAX_AGGRO_DIST)
+ .any(|agent_entity| {
+ agent.get(agent_entity).map_or(false, |agent| {
+ agent.target.map_or(false, |agent_target| {
+ agent_target.target == entity && agent_target.aggro_on
+ })
+ })
+ })
+ };
+
+ for (portal_entity, teleporter_pos, mut body, teleporter) in
+ (&entities, &positions, &mut bodies, &teleporters).join()
+ {
let nearby_entities = spatial_grid
.0
.in_circle_aabr(teleporter_pos.0.xy(), TELEPORT_RADIUS);
- for (entity, position, _) in nearby_entities.filter_map(|entity| {
+ let mut is_active = false;
+
+ for (entity, pos, _, teleporting) in nearby_entities.filter_map(|entity| {
player_data
.get(entity, &entities)
- .filter(|(_, player_pos, _)| {
- player_pos.0.distance_squared(teleporter_pos.0) <= (TELEPORT_RADIUS).powi(2)
- })
+ .filter(|(_, player_pos, _, _)| in_portal_range(player_pos.0, teleporter_pos.0))
}) {
- // TODO: Check for aggro
- attempt_teleport.push((entity, position.0, teleporter))
+ if teleporter.requires_no_aggro && check_aggro(entity, pos.0) {
+ if teleporting.is_some() {
+ cancel_teleport.push(entity)
+ };
+
+ continue;
+ }
+
+ if teleporting.is_none() {
+ start_teleporting.push((entity, Teleporting {
+ teleport_start: *time,
+ portal: portal_entity,
+ end_time: Time(time.0 + teleporter.buildup_time.0),
+ }));
+ }
+
+ is_active = true;
+ }
+
+ if (*body == Body::Object(object::Body::PortalActive)) != is_active {
+ *body = Body::Object(if is_active {
+ object::Body::PortalActive
+ } else {
+ object::Body::Portal
+ });
}
}
- for (entity, origin_pos, teleporter) in attempt_teleport {
- if teleporter.requires_no_aggro {
- // FIXME: How does this go with performance?
- let is_aggroed = spatial_grid
- .0
- .in_circle_aabr(origin_pos.xy(), MAX_AGGRO_DIST)
- .any(|agent_entity| {
- agent.get(agent_entity).map_or(false, |agent| {
- agent.target.map_or(false, |agent_target| {
- agent_target.target == entity && agent_target.aggro_on
- })
- })
- });
+ for (entity, teleporting_data) in start_teleporting {
+ let _ = teleporting.insert(entity, teleporting_data);
+ }
- if is_aggroed {
- continue;
- }
+ for (entity, position, _, teleporting) in
+ (&entities, &positions, &players, &teleporting).join()
+ {
+ let portal_pos = positions.get(teleporting.portal);
+ let Some(teleporter) = teleporters.get(teleporting.portal) else {
+ cancel_teleport.push(entity);
+ continue
+ };
+
+ if portal_pos.map_or(true, |portal_pos| {
+ !in_portal_range(position.0, portal_pos.0)
+ }) {
+ cancel_teleport.push(entity);
+ } else if teleporting.end_time.0 <= time.0 {
+ attempt_teleport.push((entity, *teleporter));
+ cancel_teleport.push(entity);
}
+ }
+
+ for entity in cancel_teleport {
+ teleporting.remove(entity);
+ }
+
+ for (entity, teleporter) in attempt_teleport {
positions
.get_mut(entity)
.map(|position| position.0 = teleporter.target);
diff --git a/world/src/site2/plot/coastal_workshop.rs b/world/src/site2/plot/coastal_workshop.rs
index c81931a150..03f57c92b1 100644
--- a/world/src/site2/plot/coastal_workshop.rs
+++ b/world/src/site2/plot/coastal_workshop.rs
@@ -3,7 +3,10 @@ use crate::{
util::{RandomField, Sampler, CARDINALS},
Land,
};
-use common::{terrain::{BlockKind, SpriteKind}, generation::SpecialEntity};
+use common::{
+ generation::SpecialEntity,
+ terrain::{BlockKind, SpriteKind},
+};
use rand::prelude::*;
use std::sync::Arc;
use vek::*;
@@ -326,7 +329,8 @@ impl Structure for CoastalWorkshop {
}
painter.spawn(
- EntityInfo::at((center - 2).with_z(base - 2).map(|e| e as f32 + 0.5)).into_special(SpecialEntity::Waypoint),
+ EntityInfo::at((center - 2).with_z(base - 2).map(|e| e as f32 + 0.5))
+ .into_special(SpecialEntity::Waypoint),
);
}
}
diff --git a/world/src/site2/plot/dungeon.rs b/world/src/site2/plot/dungeon.rs
index cd0d9cbf98..70c1f7a743 100644
--- a/world/src/site2/plot/dungeon.rs
+++ b/world/src/site2/plot/dungeon.rs
@@ -11,6 +11,7 @@ use common::{
astar::Astar,
comp::Teleporter,
generation::{ChunkSupplement, EntityInfo, SpecialEntity},
+ resources::Secs,
store::{Id, Store},
terrain::{
BiomeKind, Block, BlockKind, SpriteKind, Structure, StructuresGroup, TerrainChunkSize,
@@ -668,12 +669,14 @@ impl Floor {
SpecialEntity::Teleporter(Teleporter {
target: bottom_pos+ Vec3::unit_x() * 5.,
requires_no_aggro: false,
+ buildup_time: Secs(1.),
}),
));
supplement.add_entity(EntityInfo::at(bottom_pos).into_special(
SpecialEntity::Teleporter(Teleporter {
target: top_pos+ Vec3::unit_x() * 5.,
requires_no_aggro: true,
+ buildup_time: Secs(3.),
}),
));
}