From 0f2db6849810d11a6e3cd0f9835540fdb5a1eb40 Mon Sep 17 00:00:00 2001 From: Maxicarlos08 Date: Sat, 15 Jul 2023 22:40:22 +0200 Subject: [PATCH] added portals --- assets/voxygen/voxel/object/portal.vox | 3 + assets/voxygen/voxel/object_manifest.ron | 10 +++ common/net/src/synced_components.rs | 6 ++ common/src/cmd.rs | 12 +++ common/src/comp/body/object.rs | 6 +- common/src/comp/misc.rs | 11 +++ common/src/comp/mod.rs | 2 +- common/src/event.rs | 3 +- common/src/generation.rs | 18 ++-- common/state/src/state.rs | 1 + server/src/cmd.rs | 35 ++++++++ server/src/events/entity_creation.rs | 9 +- server/src/events/mod.rs | 8 +- server/src/rtsim/tick.rs | 2 + server/src/state_ext.rs | 19 ++++ server/src/sys/mod.rs | 2 + server/src/sys/teleporter.rs | 87 +++++++++++++++++++ server/src/sys/terrain.rs | 17 ++-- world/src/lib.rs | 5 +- world/src/site2/plot/cliff_tower.rs | 7 +- world/src/site2/plot/coastal_workshop.rs | 4 +- world/src/site2/plot/desert_city_multiplot.rs | 7 +- world/src/site2/plot/dungeon.rs | 32 ++++++- world/src/site2/plot/dwarven_mine.rs | 5 +- world/src/site2/plot/savannah_pit.rs | 6 +- world/src/site2/plot/savannah_workshop.rs | 8 +- world/src/site2/plot/workshop.rs | 4 +- 27 files changed, 296 insertions(+), 33 deletions(-) create mode 100644 assets/voxygen/voxel/object/portal.vox create mode 100644 server/src/sys/teleporter.rs 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), ); } }