diff --git a/code/game/objects/effects/landmarks.dm b/code/game/objects/effects/landmarks.dm
index 68c23322f74..c4d2071c8ab 100644
--- a/code/game/objects/effects/landmarks.dm
+++ b/code/game/objects/effects/landmarks.dm
@@ -120,6 +120,16 @@
invisibility = 101
return 1
+/obj/effect/landmark/forbidden_level
+ delete_me = 1
+/obj/effect/landmark/forbidden_level/Initialize()
+ . = ..()
+ if(using_map)
+ using_map.secret_levels |= z
+ else
+ log_error("[type] mapped in but no using_map")
+
+
//Costume spawner landmarks
/obj/effect/landmark/costume/New() //costume spawner, selects a random subclass and disappears
diff --git a/code/modules/mob/dead/observer/observer.dm b/code/modules/mob/dead/observer/observer.dm
index c5fdbdf4241..be16a38b9a1 100644
--- a/code/modules/mob/dead/observer/observer.dm
+++ b/code/modules/mob/dead/observer/observer.dm
@@ -331,7 +331,28 @@ This is the proc mobs get to turn into a ghost. Forked from ghostize due to comp
plane_holder.set_vis(VIS_CH_SPECIAL, antagHUD)
to_chat(src, "AntagHUD [antagHUD ? "Enabled" : "Disabled"]")
-/mob/observer/dead/proc/dead_tele(var/area/A in return_sorted_areas())
+/mob/observer/dead/proc/jumpable_areas()
+ var/list/areas = return_sorted_areas()
+ if(client?.holder)
+ return areas
+
+ for(var/area/A as anything in areas)
+ if(A.z in using_map?.secret_levels)
+ areas -= A
+ return areas
+
+/mob/observer/dead/proc/jumpable_mobs()
+ var/list/mobs = getmobs()
+ if(client?.holder)
+ return mobs
+
+ for(var/key in mobs)
+ var/mobz = get_z(mobs[key])
+ if(mobz in using_map?.secret_levels)
+ mobs -= key
+ return mobs
+
+/mob/observer/dead/proc/dead_tele(var/area/A in jumpable_areas())
set category = "Ghost"
set name = "Teleport"
set desc = "Teleport to a location"
@@ -341,27 +362,51 @@ This is the proc mobs get to turn into a ghost. Forked from ghostize due to comp
return
if(!A)
- A = input(usr, "Select an area:", "Ghost Teleport") as null|anything in return_sorted_areas()
+ A = input(usr, "Select an area:", "Ghost Teleport") as null|anything in jumpable_areas()
if(!A)
return
usr.forceMove(pick(get_area_turfs(A)))
usr.on_mob_jump()
-/mob/observer/dead/verb/follow(input in getmobs())
+/mob/observer/dead/verb/follow(input in jumpable_mobs())
set category = "Ghost"
set name = "Follow" // "Haunt"
set desc = "Follow and haunt a mob."
if(!input)
- input = input(usr, "Select a mob:", "Ghost Follow") as null|anything in getmobs()
+ input = input(usr, "Select a mob:", "Ghost Follow") as null|anything in jumpable_mobs()
if(!input)
return
- var/target = getmobs()[input]
+ var/target = jumpable_mobs()[input]
if(!target) return
ManualFollow(target)
+/mob/observer/dead/forceMove(atom/destination)
+ if(client?.holder)
+ return ..()
+
+ if(get_z(destination) in using_map?.secret_levels)
+ to_chat(src,SPAN_WARNING("Sorry, that z-level does not allow ghosts."))
+ if(following)
+ stop_following()
+ return
+
+ return ..()
+
+/mob/observer/dead/Move(atom/newloc, direct = 0, movetime)
+ if(client?.holder)
+ return ..()
+
+ if(get_z(newloc) in using_map?.secret_levels)
+ to_chat(src,SPAN_WARNING("Sorry, that z-level does not allow ghosts."))
+ if(following)
+ stop_following()
+ return
+
+ return ..()
+
// This is the ghost's follow verb with an argument
/mob/observer/dead/proc/ManualFollow(var/atom/movable/target)
if(!target)
@@ -371,6 +416,9 @@ This is the proc mobs get to turn into a ghost. Forked from ghostize due to comp
if(check_holy(targetloc))
to_chat(usr, "You cannot follow a mob standing on holy grounds!")
return
+ if(get_z(target) in using_map?.secret_levels)
+ to_chat(src, SPAN_WARNING("Sorry, that target is in an area that ghosts aren't allowed to go."))
+ return
if(target != src)
if(following && following == target)
return
@@ -473,7 +521,7 @@ This is the proc mobs get to turn into a ghost. Forked from ghostize due to comp
return (T && T.holy) && (is_manifest || (mind in cult.current_antagonists))
-/mob/observer/dead/verb/jumptomob(input in getmobs()) //Moves the ghost instead of just changing the ghosts's eye -Nodrak
+/mob/observer/dead/verb/jumptomob(input in jumpable_mobs()) //Moves the ghost instead of just changing the ghosts's eye -Nodrak
set category = "Ghost"
set name = "Jump to Mob"
set desc = "Teleport to a mob"
@@ -483,11 +531,11 @@ This is the proc mobs get to turn into a ghost. Forked from ghostize due to comp
return
if(!input)
- input = input(usr, "Select a mob:", "Ghost Jump") as null|anything in getmobs()
+ input = input(usr, "Select a mob:", "Ghost Jump") as null|anything in jumpable_mobs()
if(!input)
return
- var/target = getmobs()[input]
+ var/target = jumpable_mobs()[input]
if (!target)//Make sure we actually have a target
return
else
diff --git a/maps/~map_system/maps.dm b/maps/~map_system/maps.dm
index 007e638593b..84e390f010f 100644
--- a/maps/~map_system/maps.dm
+++ b/maps/~map_system/maps.dm
@@ -36,6 +36,7 @@ var/list/all_maps = list()
var/static/list/persist_levels = list() // Z-levels where SSpersistence should persist between rounds. Defaults to station_levels if unset.
var/static/list/empty_levels = list() // Empty Z-levels that may be used for various things
var/static/list/mappable_levels = list()// List of levels where mapping or other similar devices might work fully
+ var/static/list/secret_levels = list() // Z-levels that (non-admin) ghosts can't get to
// End Static Lists
// Z-levels available to various consoles, such as the crew monitor. Defaults to station_levels if unset.