diff --git a/assets/voxygen/voxel/object/portal.vox b/assets/voxygen/voxel/object/portal.vox
new file mode 100644
index 0000000000..f30dc90ddf
--- /dev/null
+++ b/assets/voxygen/voxel/object/portal.vox
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:af311d5461a03bec05799d20b3298b5d98f07a108603300b0514376965aa02e2
+size 28971
diff --git a/assets/voxygen/voxel/object_manifest.ron b/assets/voxygen/voxel/object_manifest.ron
index 6b220d517d..9592647da3 100644
--- a/assets/voxygen/voxel/object_manifest.ron
+++ b/assets/voxygen/voxel/object_manifest.ron
@@ -979,4 +979,14 @@
central: ("armor.empty"),
)
),
+ Portal: (
+ bone0: (
+ offset: (-11.0, -1.5, 0.0),
+ central: ("object.portal"),
+ ),
+ bone1: (
+ offset: (0.0, 0.0, 0.0),
+ central: ("armor.empty"),
+ )
+ )
})
diff --git a/common/net/src/synced_components.rs b/common/net/src/synced_components.rs
index d8d5bf858b..bf8cc26a19 100755
--- a/common/net/src/synced_components.rs
+++ b/common/net/src/synced_components.rs
@@ -102,6 +102,8 @@ synced_components!(reexport_comps);
// === NetSync implementations ===
// ===============================
+use common::comp::Teleporter;
+
use crate::sync::{NetSync, SyncFrom};
impl NetSync for Body {
@@ -234,6 +236,10 @@ impl NetSync for Stance {
const SYNC_FROM: SyncFrom = SyncFrom::AnyEntity;
}
+impl NetSync for Teleporter {
+ const SYNC_FROM: SyncFrom = SyncFrom::AnyEntity;
+}
+
// These are synced only from the client's own entity.
impl NetSync for Admin {
diff --git a/common/src/cmd.rs b/common/src/cmd.rs
index a420e7224b..9c201563e7 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,16 @@ 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),
+ ],
+ "Spawns a portal",
+ Some(Moderator),
+ ),
ServerChatCommand::ReloadChunks => cmd(
vec![],
"Reloads all chunks loaded on the server",
@@ -890,6 +901,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/object.rs b/common/src/comp/body/object.rs
index 44735011e1..261c4b6cd5 100644
--- a/common/src/comp/body/object.rs
+++ b/common/src/comp/body/object.rs
@@ -110,6 +110,7 @@ make_case_elim!(
Mine = 95,
LightningBolt = 96,
SpearIcicle = 97,
+ Portal = 98,
}
);
@@ -120,7 +121,7 @@ impl Body {
}
}
-pub const ALL_OBJECTS: [Body; 98] = [
+pub const ALL_OBJECTS: [Body; 99] = [
Body::Arrow,
Body::Bomb,
Body::Scarecrow,
@@ -219,6 +220,7 @@ pub const ALL_OBJECTS: [Body; 98] = [
Body::Mine,
Body::LightningBolt,
Body::SpearIcicle,
+ Body::Portal,
];
impl From
for super::Body {
@@ -326,6 +328,7 @@ impl Body {
Body::Mine => "mine",
Body::LightningBolt => "lightning_bolt",
Body::SpearIcicle => "spear_icicle",
+ Body::Portal => "portal",
}
}
@@ -450,6 +453,7 @@ impl Body {
Body::AdletTrap => 10.0,
Body::Mine => 100.0,
Body::LightningBolt | Body::SpearIcicle => 20000.0,
+ Body::Portal => 10., // I dont know really
};
Mass(m)
diff --git a/common/src/comp/misc.rs b/common/src/comp/misc.rs
index c803ec64e9..f6002140f1 100644
--- a/common/src/comp/misc.rs
+++ b/common/src/comp/misc.rs
@@ -3,6 +3,7 @@ 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 {
@@ -22,3 +23,13 @@ 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 198bf4402d..5fb3d86e07 100644
--- a/common/src/comp/mod.rs
+++ b/common/src/comp/mod.rs
@@ -86,7 +86,7 @@ pub use self::{
location::{MapMarker, MapMarkerChange, MapMarkerUpdate, Waypoint, WaypointArea},
loot_owner::LootOwner,
melee::{Melee, MeleeConstructor, MeleeConstructorKind},
- misc::Object,
+ misc::{Object, Teleporter},
ori::Ori,
pet::Pet,
phys::{
diff --git a/common/src/event.rs b/common/src/event.rs
index 006607afd5..eef17c1035 100644
--- a/common/src/event.rs
+++ b/common/src/event.rs
@@ -6,7 +6,7 @@ use crate::{
agent::Sound,
dialogue::Subject,
invite::{InviteKind, InviteResponse},
- DisconnectReason, Ori, Pos,
+ DisconnectReason, Ori, Pos, Teleporter,
},
lottery::LootSpec,
mounting::VolumePos,
@@ -239,6 +239,7 @@ pub enum ServerEvent {
driver: Option,
},
CreateWaypoint(Vec3),
+ CreateTeleporter(Vec3, Teleporter),
ClientDisconnect(EcsEntity, DisconnectReason),
ClientDisconnectWithoutPersistence(EcsEntity),
Command(EcsEntity, String, Vec),
diff --git a/common/src/generation.rs b/common/src/generation.rs
index 404833a148..4818ff5554 100644
--- a/common/src/generation.rs
+++ b/common/src/generation.rs
@@ -3,7 +3,7 @@ use crate::{
comp::{
self, agent, humanoid,
inventory::loadout_builder::{LoadoutBuilder, LoadoutSpec},
- Alignment, Body, Item,
+ Alignment, Body, Item, Teleporter,
},
lottery::LootSpec,
npc::{self, NPC_NAMES},
@@ -163,10 +163,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(Teleporter),
+}
+
#[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 +199,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, // Campfire
}
impl EntityInfo {
pub fn at(pos: Vec3) -> Self {
Self {
pos,
- is_waypoint: false,
alignment: Alignment::Wild,
has_agency: true,
@@ -219,6 +226,7 @@ impl EntityInfo {
skillset_asset: None,
pet: None,
trading_information: None,
+ special_entity: None,
}
}
@@ -377,8 +385,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/state/src/state.rs b/common/state/src/state.rs
index 7771d54f00..84341cf56e 100644
--- a/common/state/src/state.rs
+++ b/common/state/src/state.rs
@@ -219,6 +219,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 5b1a4fbba7..fe2bf29404 100644
--- a/server/src/cmd.rs
+++ b/server/src/cmd.rs
@@ -167,6 +167,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,
@@ -694,6 +695,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,
@@ -2038,6 +2042,37 @@ 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) =
+ parse_cmd_args!(args, f32, f32, f32, bool)
+ {
+ let requires_no_aggro = requires_no_aggro.unwrap_or(false);
+ server
+ .state
+ .create_teleporter(pos, comp::Teleporter {
+ target: Vec3::new(x, y, z),
+ 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,
diff --git a/server/src/events/entity_creation.rs b/server/src/events/entity_creation.rs
index 1362722ccc..7d36516d02 100644
--- a/server/src/events/entity_creation.rs
+++ b/server/src/events/entity_creation.rs
@@ -11,7 +11,7 @@ use common::{
buff::{BuffCategory, BuffData, BuffKind, BuffSource},
ship::figuredata::VOXEL_COLLIDER_MANIFEST,
shockwave, Alignment, BehaviorCapability, Body, ItemDrops, LightEmitter, Object, Ori, Pos,
- Projectile, TradingBehavior, Vel, WaypointArea,
+ Projectile, Teleporter, TradingBehavior, Vel, WaypointArea,
},
event::{EventBus, NpcBuilder, UpdateCharacterMetadata},
mounting::{Mounting, Volume, VolumeMounting, VolumePos},
@@ -418,3 +418,10 @@ pub fn handle_create_waypoint(server: &mut Server, pos: Vec3) {
]))
.build();
}
+
+pub fn handle_create_teleporter(server: &mut Server, pos: Vec3, teleporter: Teleporter) {
+ server
+ .state
+ .create_teleporter(comp::Pos(pos), teleporter)
+ .build();
+}
diff --git a/server/src/events/mod.rs b/server/src/events/mod.rs
index 1173f9267b..24e519b8a2 100644
--- a/server/src/events/mod.rs
+++ b/server/src/events/mod.rs
@@ -1,5 +1,8 @@
use crate::{
- events::interaction::{handle_mount_volume, handle_tame_pet},
+ events::{
+ entity_creation::handle_create_teleporter,
+ interaction::{handle_mount_volume, handle_tame_pet},
+ },
persistence::PersistedComponents,
state_ext::StateExt,
Server,
@@ -209,6 +212,9 @@ impl Server {
driver,
} => handle_create_ship(self, pos, ori, ship, rtsim_entity, driver, Vec::new()),
ServerEvent::CreateWaypoint(pos) => handle_create_waypoint(self, pos),
+ ServerEvent::CreateTeleporter(pos, teleporter) => {
+ handle_create_teleporter(self, pos, teleporter)
+ },
ServerEvent::ClientDisconnect(entity, reason) => {
frontend_events.push(handle_client_disconnect(self, entity, reason, false))
},
diff --git a/server/src/rtsim/tick.rs b/server/src/rtsim/tick.rs
index e9edcb9000..af6938be4d 100644
--- a/server/src/rtsim/tick.rs
+++ b/server/src/rtsim/tick.rs
@@ -338,6 +338,7 @@ impl<'a> System<'a> for Sys {
// as of now, and if someone will try to spawn
// rtsim waypoint it is definitely error.
NpcData::Waypoint(_) => unimplemented!(),
+ NpcData::Teleporter(_, _) => unimplemented!(),
})
} else {
error!("Npc is loaded but vehicle is unloaded");
@@ -401,6 +402,7 @@ impl<'a> System<'a> for Sys {
// as of now, and if someone will try to spawn
// rtsim waypoint it is definitely error.
NpcData::Waypoint(_) => unimplemented!(),
+ NpcData::Teleporter(_, _) => unimplemented!(),
});
}
}
diff --git a/server/src/state_ext.rs b/server/src/state_ext.rs
index ae77bd5d53..3602e3f64f 100644
--- a/server/src/state_ext.rs
+++ b/server/src/state_ext.rs
@@ -18,6 +18,7 @@ use common::{
comp::{
self,
item::{ItemKind, MaterialStatManifest},
+ object,
skills::{GeneralSkill, Skill},
ChatType, Group, Inventory, Item, LootOwner, Object, Player, Poise, Presence, PresenceKind,
},
@@ -114,6 +115,14 @@ pub trait StateExt {
world: &std::sync::Arc,
index: &world::IndexOwned,
) -> EcsEntityBuilder;
+ /// Creates a teleporter entity, which allows players to teleport to the
+ /// `target` position. You might want to require the teleporting entity
+ /// to not have agro for teleporting.
+ fn create_teleporter(
+ &mut self,
+ pos: comp::Pos,
+ teleporter: comp::Teleporter,
+ ) -> EcsEntityBuilder;
/// Insert common/default components for a new character joining the server
fn initialize_character_data(
&mut self,
@@ -607,6 +616,16 @@ impl StateExt for State {
))
}
+ fn create_teleporter(
+ &mut self,
+ pos: comp::Pos,
+ teleporter: comp::Teleporter,
+ ) -> EcsEntityBuilder {
+ self.create_object(pos, object::Body::Portal)
+ .with(comp::Immovable)
+ .with(teleporter)
+ }
+
fn initialize_character_data(
&mut self,
entity: EcsEntity,
diff --git a/server/src/sys/mod.rs b/server/src/sys/mod.rs
index 59e2b446f6..9e71553634 100644
--- a/server/src/sys/mod.rs
+++ b/server/src/sys/mod.rs
@@ -11,6 +11,7 @@ pub mod persistence;
pub mod pets;
pub mod sentinel;
pub mod subscription;
+pub mod teleporter;
pub mod terrain;
pub mod terrain_sync;
pub mod waypoint;
@@ -32,6 +33,7 @@ pub fn add_server_systems(dispatch_builder: &mut DispatcherBuilder) {
dispatch::(dispatch_builder, &[]);
dispatch::(dispatch_builder, &[&msg::terrain::Sys::sys_name()]);
dispatch::(dispatch_builder, &[]);
+ dispatch::(dispatch_builder, &[]);
dispatch::(dispatch_builder, &[]);
dispatch::(dispatch_builder, &[]);
dispatch::(dispatch_builder, &[]);
diff --git a/server/src/sys/teleporter.rs b/server/src/sys/teleporter.rs
new file mode 100644
index 0000000000..442dae21de
--- /dev/null
+++ b/server/src/sys/teleporter.rs
@@ -0,0 +1,87 @@
+use common::{
+ comp::{Agent, ForceUpdate, Player, Pos, Teleporter},
+ CachedSpatialGrid,
+};
+use common_ecs::{Origin, Phase, System};
+use specs::{Entities, Join, Read, ReadStorage, WriteStorage};
+
+const TELEPORT_RADIUS: f32 = 1.;
+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;
+
+impl<'a> System<'a> for Sys {
+ type SystemData = (
+ Entities<'a>,
+ WriteStorage<'a, Pos>,
+ ReadStorage<'a, Player>,
+ ReadStorage<'a, Teleporter>,
+ ReadStorage<'a, Agent>,
+ WriteStorage<'a, ForceUpdate>,
+ Read<'a, CachedSpatialGrid>,
+ );
+
+ const NAME: &'static str = "teleporter";
+ const ORIGIN: Origin = Origin::Server;
+ const PHASE: Phase = Phase::Create;
+
+ fn run(
+ _job: &mut common_ecs::Job,
+ (
+ entities,
+ mut positions,
+ players,
+ teleporters,
+ agent,
+ mut forced_update,
+ spatial_grid,
+ ): Self::SystemData,
+ ) {
+ let mut attempt_teleport = vec![];
+ let mut player_data = (&entities, &positions, &players).join();
+
+ for (_entity, teleporter_pos, teleporter) in (&entities, &positions, &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| {
+ player_data
+ .get(entity, &entities)
+ .filter(|(_, player_pos, _)| {
+ player_pos.0.distance_squared(teleporter_pos.0) <= (TELEPORT_RADIUS).powi(2)
+ })
+ }) {
+ // TODO: Check for aggro
+ attempt_teleport.push((entity, position.0, teleporter))
+ }
+ }
+
+ 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
+ })
+ })
+ });
+
+ if is_aggroed {
+ continue;
+ }
+ }
+ positions
+ .get_mut(entity)
+ .map(|position| position.0 = teleporter.target);
+ forced_update
+ .get_mut(entity)
+ .map(|forced_update| forced_update.update());
+ }
+ }
+}
diff --git a/server/src/sys/terrain.rs b/server/src/sys/terrain.rs
index 185848174c..5ce339ed22 100644
--- a/server/src/sys/terrain.rs
+++ b/server/src/sys/terrain.rs
@@ -13,10 +13,10 @@ use common::{
calendar::Calendar,
comp::{
self, agent, biped_small, bird_medium, skillset::skills, BehaviorCapability, ForceUpdate,
- Pos, Presence, Waypoint,
+ Pos, Presence, Teleporter, Waypoint,
},
event::{EventBus, NpcBuilder, ServerEvent},
- generation::EntityInfo,
+ generation::{EntityInfo, SpecialEntity},
lottery::LootSpec,
resources::{Time, TimeOfDay},
slowjob::SlowJobPool,
@@ -222,6 +222,9 @@ impl<'a> System<'a> for Sys {
.with_loot(loot),
});
},
+ NpcData::Teleporter(pos, teleporter) => {
+ server_emitter.emit(ServerEvent::CreateTeleporter(pos, teleporter));
+ },
}
}
}
@@ -415,13 +418,14 @@ pub enum NpcData {
loot: LootSpec,
},
Waypoint(Vec3),
+ Teleporter(Vec3, Teleporter),
}
impl NpcData {
pub fn from_entity_info(entity: EntityInfo) -> Self {
let EntityInfo {
// flags
- is_waypoint,
+ special_entity,
has_agency,
agent_mark,
alignment,
@@ -444,8 +448,11 @@ impl NpcData {
pet: _, // TODO: I had no idea we have this.
} = entity;
- if is_waypoint {
- return Self::Waypoint(pos);
+ if let Some(special) = special_entity {
+ return match special {
+ SpecialEntity::Waypoint => Self::Waypoint(pos),
+ SpecialEntity::Teleporter(teleporter) => Self::Teleporter(pos, teleporter),
+ };
}
let name = name.unwrap_or_else(|| "Unnamed".to_string());
diff --git a/world/src/lib.rs b/world/src/lib.rs
index 4a2ebbfb28..4cc675b468 100644
--- a/world/src/lib.rs
+++ b/world/src/lib.rs
@@ -46,7 +46,7 @@ use crate::{
use common::{
assets,
calendar::Calendar,
- generation::{ChunkSupplement, EntityInfo},
+ generation::{ChunkSupplement, EntityInfo, SpecialEntity},
lod,
resources::TimeOfDay,
rtsim::ChunkResource,
@@ -487,7 +487,8 @@ impl World {
.fold(SpawnRules::default(), |a, b| a.combine(b))
.waypoints
{
- supplement.add_entity(EntityInfo::at(waypoint_pos).into_waypoint());
+ supplement
+ .add_entity(EntityInfo::at(waypoint_pos).into_special(SpecialEntity::Waypoint));
}
}
diff --git a/world/src/site2/plot/cliff_tower.rs b/world/src/site2/plot/cliff_tower.rs
index b0015977c0..0babab8929 100644
--- a/world/src/site2/plot/cliff_tower.rs
+++ b/world/src/site2/plot/cliff_tower.rs
@@ -4,7 +4,7 @@ use crate::{
Land,
};
use common::{
- generation::EntityInfo,
+ generation::{EntityInfo, SpecialEntity},
terrain::{BlockKind, SpriteKind},
};
use rand::prelude::*;
@@ -752,7 +752,10 @@ impl Structure for CliffTower {
// spawn campfire next to some clifftowers
if self.campfire {
let campfire_pos = (plot_center - 20).with_z(self.alt + 18);
- painter.spawn(EntityInfo::at(campfire_pos.map(|e| e as f32)).into_waypoint());
+ painter.spawn(
+ EntityInfo::at(campfire_pos.map(|e| e as f32))
+ .into_special(SpecialEntity::Waypoint),
+ );
}
}
}
diff --git a/world/src/site2/plot/coastal_workshop.rs b/world/src/site2/plot/coastal_workshop.rs
index 129119eb41..c81931a150 100644
--- a/world/src/site2/plot/coastal_workshop.rs
+++ b/world/src/site2/plot/coastal_workshop.rs
@@ -3,7 +3,7 @@ use crate::{
util::{RandomField, Sampler, CARDINALS},
Land,
};
-use common::terrain::{BlockKind, SpriteKind};
+use common::{terrain::{BlockKind, SpriteKind}, generation::SpecialEntity};
use rand::prelude::*;
use std::sync::Arc;
use vek::*;
@@ -326,7 +326,7 @@ impl Structure for CoastalWorkshop {
}
painter.spawn(
- EntityInfo::at((center - 2).with_z(base - 2).map(|e| e as f32 + 0.5)).into_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/desert_city_multiplot.rs b/world/src/site2/plot/desert_city_multiplot.rs
index b9a10af893..d7cadc4eff 100644
--- a/world/src/site2/plot/desert_city_multiplot.rs
+++ b/world/src/site2/plot/desert_city_multiplot.rs
@@ -6,7 +6,7 @@ use crate::{
Land,
};
use common::{
- generation::EntityInfo,
+ generation::{EntityInfo, SpecialEntity},
terrain::{Block, BlockKind, SpriteKind, Structure as PrefabStructure, StructuresGroup},
};
use lazy_static::lazy_static;
@@ -2432,7 +2432,10 @@ impl Structure for DesertCityMultiPlot {
// spawn campfire in some multiplots that are not markethall
let campfire_pos = (center).with_z(base + 1);
if self.campfire {
- painter.spawn(EntityInfo::at(campfire_pos.map(|e| e as f32)).into_waypoint())
+ painter.spawn(
+ EntityInfo::at(campfire_pos.map(|e| e as f32))
+ .into_special(SpecialEntity::Waypoint),
+ )
}
},
}
diff --git a/world/src/site2/plot/dungeon.rs b/world/src/site2/plot/dungeon.rs
index 8d24f169b1..cd0d9cbf98 100644
--- a/world/src/site2/plot/dungeon.rs
+++ b/world/src/site2/plot/dungeon.rs
@@ -9,7 +9,8 @@ use crate::{
use common::{
assets::{self, AssetExt, AssetHandle},
astar::Astar,
- generation::{ChunkSupplement, EntityInfo},
+ comp::Teleporter,
+ generation::{ChunkSupplement, EntityInfo, SpecialEntity},
store::{Id, Store},
terrain::{
BiomeKind, Block, BlockKind, SpriteKind, Structure, StructuresGroup, TerrainChunkSize,
@@ -146,7 +147,7 @@ impl Dungeon {
if area.contains_point(pos - self.origin) {
supplement.add_entity(
EntityInfo::at(Vec3::new(pos.x as f32, pos.y as f32, self.alt as f32) + 5.0)
- .into_waypoint(),
+ .into_special(SpecialEntity::Waypoint),
);
}
@@ -615,6 +616,14 @@ impl Floor {
for y in area.min.y..area.max.y {
let tile_pos = Vec2::new(x, y).map(|e| e.div_euclid(TILE_SIZE)) - self.tile_offset;
let wpos2d = origin.xy() + Vec2::new(x, y);
+ let is_boss_tile = self.tiles.get(tile_pos).filter(|_| self.final_level);
+
+ let tile_wcenter = origin
+ + Vec3::from(
+ Vec2::new(x, y)
+ .map(|e| e.div_euclid(TILE_SIZE) * TILE_SIZE + TILE_SIZE / 2),
+ );
+
if let Some(Tile::Room(room)) = self.tiles.get(tile_pos) {
let room = &self.rooms[*room];
@@ -648,6 +657,25 @@ impl Floor {
),
RoomKind::Peaceful | RoomKind::LavaPlatforming => {},
}
+ } else if let Some(Tile::UpStair(_, _)) = is_boss_tile && tile_wcenter.xy() == wpos2d {
+ // Create one teleporter at the top of the "stairs" and one at the botton
+ let bottom_pos = tile_wcenter.map(|v| v as f32) ;
+ let top_pos =
+ (tile_wcenter + Vec3::unit_z() * self.total_depth()).map(|v| v as f32);
+
+ // Move both a bit to the side to prevent teleportation loop, ideally we'd have the portals at another location
+ supplement.add_entity(EntityInfo::at(top_pos).into_special(
+ SpecialEntity::Teleporter(Teleporter {
+ target: bottom_pos+ Vec3::unit_x() * 5.,
+ requires_no_aggro: false,
+ }),
+ ));
+ supplement.add_entity(EntityInfo::at(bottom_pos).into_special(
+ SpecialEntity::Teleporter(Teleporter {
+ target: top_pos+ Vec3::unit_x() * 5.,
+ requires_no_aggro: true,
+ }),
+ ));
}
}
}
diff --git a/world/src/site2/plot/dwarven_mine.rs b/world/src/site2/plot/dwarven_mine.rs
index 4e5a64e80a..bf09aa574a 100644
--- a/world/src/site2/plot/dwarven_mine.rs
+++ b/world/src/site2/plot/dwarven_mine.rs
@@ -13,7 +13,7 @@ ChatGPT: Improving Code Quality, Speeding up finding Syntax Errors
use super::*;
use crate::{site2::gen::PrimitiveTransform, Land};
-use common::terrain::Structure as PrefabStructure;
+use common::{generation::SpecialEntity, terrain::Structure as PrefabStructure};
use rand::prelude::*;
use vek::*;
@@ -166,7 +166,8 @@ impl Structure for DwarvenMine {
let entrance_offset: Vec3 = (20.0, -20.0, 45.0).into();
// Spawn waypoint
let waypoint_pos = (entrance_pos + Vec3::new(1, -1, 4)).map(|x| x as f32) + entrance_offset;
- painter.spawn(EntityInfo::at(waypoint_pos.map(|e| e)).into_waypoint());
+ painter
+ .spawn(EntityInfo::at(waypoint_pos.map(|e| e)).into_special(SpecialEntity::Waypoint));
let miner_pos: Vec<(Vec3, &str)> = vec![
// Entrance
diff --git a/world/src/site2/plot/savannah_pit.rs b/world/src/site2/plot/savannah_pit.rs
index b626d2da97..a614f001d4 100644
--- a/world/src/site2/plot/savannah_pit.rs
+++ b/world/src/site2/plot/savannah_pit.rs
@@ -4,7 +4,7 @@ use crate::{
Land,
};
use common::{
- generation::EntityInfo,
+ generation::{EntityInfo, SpecialEntity},
terrain::{BlockKind, SpriteKind},
};
use rand::prelude::*;
@@ -942,7 +942,9 @@ impl Structure for SavannahPit {
// campfire
let campfire_pos = (center - 10).with_z(base);
- painter.spawn(EntityInfo::at(campfire_pos.map(|e| e as f32)).into_waypoint());
+ painter.spawn(
+ EntityInfo::at(campfire_pos.map(|e| e as f32)).into_special(SpecialEntity::Waypoint),
+ );
// underground market hall
let stairs_pos = Vec2::new(center.x, center.y - (2 * length) + 1);
diff --git a/world/src/site2/plot/savannah_workshop.rs b/world/src/site2/plot/savannah_workshop.rs
index 8cffcf37c8..7d2fcef6f7 100644
--- a/world/src/site2/plot/savannah_workshop.rs
+++ b/world/src/site2/plot/savannah_workshop.rs
@@ -3,7 +3,10 @@ use crate::{
util::{RandomField, Sampler, CARDINALS, DIAGONALS},
Land,
};
-use common::terrain::{BlockKind, SpriteKind};
+use common::{
+ generation::SpecialEntity,
+ terrain::{BlockKind, SpriteKind},
+};
use rand::prelude::*;
use std::{f32::consts::TAU, sync::Arc};
use vek::*;
@@ -302,7 +305,8 @@ impl Structure for SavannahWorkshop {
}
painter.spawn(
- EntityInfo::at((center).with_z(base - 2).map(|e| e as f32 + 0.5)).into_waypoint(),
+ EntityInfo::at((center).with_z(base - 2).map(|e| e as f32 + 0.5))
+ .into_special(SpecialEntity::Waypoint),
);
}
}
diff --git a/world/src/site2/plot/workshop.rs b/world/src/site2/plot/workshop.rs
index dc06b9601d..805feba992 100644
--- a/world/src/site2/plot/workshop.rs
+++ b/world/src/site2/plot/workshop.rs
@@ -4,7 +4,7 @@ use crate::{
Land,
};
use common::{
- generation::EntityInfo,
+ generation::{EntityInfo, SpecialEntity},
terrain::{Block, BlockKind, SpriteKind},
};
use rand::prelude::*;
@@ -145,7 +145,7 @@ impl Structure for Workshop {
painter.spawn(
EntityInfo::at(self.bounds.center().with_z(base).map(|e| e as f32 + 0.5))
- .into_waypoint(),
+ .into_special(SpecialEntity::Waypoint),
);
}
}