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.), }), )); }