diff --git a/code/__DEFINES/maps.dm b/code/__DEFINES/maps.dm
index 1d7008b7b92..1870d4dc058 100644
--- a/code/__DEFINES/maps.dm
+++ b/code/__DEFINES/maps.dm
@@ -44,6 +44,12 @@ require only minor tweaks.
#define ZTRAIT_ASHSTORM "Weather_Ashstorm"
#define ZTRAIT_VOIDSTORM "Weather_Voidstorm"
+/// boolean - does this z prevent ghosts from observing it
+#define ZTRAIT_SECRET "Secret"
+
+/// boolean - does this z prevent phasing
+#define ZTRAIT_NOPHASE "No Phase"
+
// number - bombcap is multiplied by this before being applied to bombs
#define ZTRAIT_BOMBCAP_MULTIPLIER "Bombcap Multiplier"
@@ -67,15 +73,23 @@ require only minor tweaks.
#define ZTRAIT_BASETURF "Baseturf"
// default trait definitions, used by SSmapping
-#define ZTRAITS_CENTCOM list(ZTRAIT_CENTCOM = TRUE)
+///Z level traits for CentCom
+#define ZTRAITS_CENTCOM list(ZTRAIT_CENTCOM = TRUE, ZTRAIT_NOPHASE = TRUE)
+///Z level traits for Space Station 13
#define ZTRAITS_STATION list(ZTRAIT_LINKAGE = CROSSLINKED, ZTRAIT_STATION = TRUE)
+///Z level traits for Deep Space
#define ZTRAITS_SPACE list(ZTRAIT_LINKAGE = CROSSLINKED, ZTRAIT_SPACE_RUINS = TRUE)
+///Z level traits for Lavaland
#define ZTRAITS_LAVALAND list(\
ZTRAIT_MINING = TRUE, \
ZTRAIT_ASHSTORM = TRUE, \
ZTRAIT_LAVA_RUINS = TRUE, \
ZTRAIT_BOMBCAP_MULTIPLIER = 2, \
ZTRAIT_BASETURF = /turf/open/lava/smooth/lava_land_surface)
+///Z level traits for Away Missions
+#define ZTRAITS_AWAY list(ZTRAIT_AWAY = TRUE)
+///Z level traits for Secret Away Missions
+#define ZTRAITS_AWAY_SECRET list(ZTRAIT_AWAY = TRUE, ZTRAIT_SECRET = TRUE, ZTRAIT_NOPHASE = TRUE)
#define DL_NAME "name"
#define DL_TRAITS "traits"
diff --git a/code/controllers/configuration/entries/game_options.dm b/code/controllers/configuration/entries/game_options.dm
index 499abd1c1ba..6109f1bc781 100644
--- a/code/controllers/configuration/entries/game_options.dm
+++ b/code/controllers/configuration/entries/game_options.dm
@@ -245,6 +245,11 @@
integer = FALSE
min_val = 0
+/datum/config_entry/number/config_gateway_chance
+ integer = FALSE
+ min_val = 0
+ max_val = 100
+
/datum/config_entry/flag/ghost_interaction
/datum/config_entry/flag/near_death_experience //If carbons can hear ghosts when unconscious and very close to death
diff --git a/code/controllers/subsystem/mapping.dm b/code/controllers/subsystem/mapping.dm
index 4a03cbc722a..8bd982c30b1 100644
--- a/code/controllers/subsystem/mapping.dm
+++ b/code/controllers/subsystem/mapping.dm
@@ -83,7 +83,7 @@ SUBSYSTEM_DEF(mapping)
// Pick a random away mission.
if(CONFIG_GET(flag/roundstart_away))
- createRandomZlevel()
+ createRandomZlevel(prob(CONFIG_GET(number/config_gateway_chance)))
// Load the virtual reality hub
if(CONFIG_GET(flag/virtual_reality))
@@ -480,8 +480,10 @@ GLOBAL_LIST_EMPTY(the_station_areas)
var/list/possible_options = GLOB.potentialRandomZlevels + "Custom"
var/away_name
var/datum/space_level/away_level
-
- var/answer = input("What kind ? ","Away") as null|anything in possible_options
+ var/secret = FALSE
+ if(tgui_alert(usr, "Do you want your mission secret? (This will prevent ghosts from looking at your map in any way other than through a living player's eyes.)", "Are you $$$ekret?", list("Yes", "No")) == "Yes")
+ secret = TRUE
+ var/answer = input("What kind?","Away") as null|anything in possible_options
switch(answer)
if("Custom")
var/mapfile = input("Pick file:", "File") as null|file
@@ -490,13 +492,13 @@ GLOBAL_LIST_EMPTY(the_station_areas)
away_name = "[mapfile] custom"
to_chat(usr,span_notice("Loading [away_name]..."))
var/datum/map_template/template = new(mapfile, "Away Mission")
- away_level = template.load_new_z()
+ away_level = template.load_new_z(secret)
else
if(answer in GLOB.potentialRandomZlevels)
away_name = answer
to_chat(usr,span_notice("Loading [away_name]..."))
var/datum/map_template/template = new(away_name, "Away Mission")
- away_level = template.load_new_z()
+ away_level = template.load_new_z(secret)
else
return
diff --git a/code/game/area/areas/away_content.dm b/code/game/area/areas/away_content.dm
index d290bb7c314..27903e1f1bb 100644
--- a/code/game/area/areas/away_content.dm
+++ b/code/game/area/areas/away_content.dm
@@ -10,6 +10,7 @@ Unused icons for new areas are "awaycontent1" ~ "awaycontent30"
has_gravity = STANDARD_GRAVITY
ambience_index = AMBIENCE_AWAY
sound_environment = SOUND_ENVIRONMENT_ROOM
+ area_flags = UNIQUE_AREA|NO_ALERTS
/area/awaymission/beach
name = "Beach"
@@ -26,3 +27,5 @@ Unused icons for new areas are "awaycontent1" ~ "awaycontent30"
has_gravity = STANDARD_GRAVITY
+/area/awaymission/secret
+ area_flags = UNIQUE_AREA|NOTELEPORT|HIDDEN_AREA|NO_ALERTS
diff --git a/code/game/objects/effects/phased_mob.dm b/code/game/objects/effects/phased_mob.dm
index 8cdfe932c28..5ddd317b092 100644
--- a/code/game/objects/effects/phased_mob.dm
+++ b/code/game/objects/effects/phased_mob.dm
@@ -57,7 +57,7 @@
if(newloc.flags_1 & NOJAUNT)
to_chat(user, span_warning("Some strange aura is blocking the way."))
return
- if(destination_area.area_flags & NOTELEPORT)
+ if(destination_area.area_flags & NOTELEPORT || SSmapping.level_trait(newloc.z, ZTRAIT_NOPHASE))
to_chat(user, span_danger("Some dull, universal force is blocking the way. It's overwhelmingly oppressive force feels dangerous."))
return
return newloc
diff --git a/code/modules/admin/verbs/mapping.dm b/code/modules/admin/verbs/mapping.dm
index 6df17c6da91..19dc1688f27 100644
--- a/code/modules/admin/verbs/mapping.dm
+++ b/code/modules/admin/verbs/mapping.dm
@@ -42,6 +42,7 @@ GLOBAL_LIST_INIT(admin_verbs_debug_mapping, list(
/client/proc/cmd_admin_rejuvenate,
/datum/admins/proc/show_traitor_panel,
/client/proc/disable_communication,
+ /client/proc/show_map_reports,
/client/proc/cmd_show_at_list,
/client/proc/cmd_show_at_markers,
/client/proc/manipulate_organs,
@@ -148,6 +149,18 @@ GLOBAL_LIST_EMPTY(dirty_vars)
new /obj/effect/abstract/marker/intercom(turf)
SSblackbox.record_feedback("tally", "admin_verb", 1, "Show Intercom Range") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
+/client/proc/show_map_reports()
+ set category = "Mapping"
+ set name = "Show map report list"
+ set desc = "Displays a list of map reports"
+
+ var/dat = {"List of all map reports:
"}
+
+ for(var/datum/map_report/report as anything in GLOB.map_reports)
+ dat += "[report.tag] ([report.original_path]) - View
"
+
+ usr << browse(dat, "window=map_reports")
+
/client/proc/cmd_show_at_list()
set category = "Mapping"
set name = "Show roundstart AT list"
diff --git a/code/modules/awaymissions/zlevel.dm b/code/modules/awaymissions/zlevel.dm
index 90d98649b79..71cd13f8891 100644
--- a/code/modules/awaymissions/zlevel.dm
+++ b/code/modules/awaymissions/zlevel.dm
@@ -1,12 +1,20 @@
// How much "space" we give the edge of the map
GLOBAL_LIST_INIT(potentialRandomZlevels, generateMapList(filename = "[global.config.directory]/awaymissionconfig.txt"))
+GLOBAL_LIST_INIT(potentialConfigRandomZlevels, generateConfigMapList(directory = "[global.config.directory]/away_missions/"))
-/proc/createRandomZlevel()
- if(GLOB.potentialRandomZlevels && GLOB.potentialRandomZlevels.len)
- to_chat(world, span_boldannounce("Loading away mission..."))
- var/map = pick(GLOB.potentialRandomZlevels)
- load_new_z_level(map, "Away Mission")
- to_chat(world, span_boldannounce("Away mission loaded."))
+/proc/createRandomZlevel(config_gateway = FALSE)
+ var/map
+ if(config_gateway && GLOB.potentialConfigRandomZlevels?.len)
+ map = pick_n_take(GLOB.potentialConfigRandomZlevels)
+ else if(GLOB.potentialRandomZlevels?.len)
+ map = pick_n_take(GLOB.potentialRandomZlevels)
+ else
+ return to_chat(world, span_boldannounce("No valid away mission files, loading aborted."))
+ to_chat(world, span_boldannounce("Loading away mission..."))
+ var/loaded = load_new_z_level(map, "Away Mission", config_gateway)
+ to_chat(world, span_boldannounce("Away mission [loaded ? "loaded" : "aborted due to errors"]."))
+ if(!loaded)
+ createRandomZlevel(config_gateway)
/obj/effect/landmark/awaystart
name = "away mission spawn"
@@ -60,3 +68,12 @@ GLOBAL_LIST_INIT(potentialRandomZlevels, generateMapList(filename = "[global.con
continue
. += t
+
+/proc/generateConfigMapList(directory)
+ var/list/config_maps = list()
+ var/list/maps = flist(directory)
+ for(var/map_file in maps)
+ if(!findtext(map_file, ".dmm"))
+ continue
+ config_maps += (directory + map_file)
+ return config_maps
diff --git a/code/modules/mapping/map_template.dm b/code/modules/mapping/map_template.dm
index 1ac1b553cd7..6645582c7b6 100644
--- a/code/modules/mapping/map_template.dm
+++ b/code/modules/mapping/map_template.dm
@@ -104,11 +104,11 @@
affected_turf.air_update_turf(TRUE, TRUE)
affected_turf.levelupdate()
-/datum/map_template/proc/load_new_z()
+/datum/map_template/proc/load_new_z(secret = FALSE)
var/x = round((world.maxx - width) * 0.5) + 1
var/y = round((world.maxy - height) * 0.5) + 1
- var/datum/space_level/level = SSmapping.add_new_zlevel(name, list(ZTRAIT_AWAY = TRUE))
+ var/datum/space_level/level = SSmapping.add_new_zlevel(name, secret ? ZTRAITS_AWAY_SECRET : ZTRAITS_AWAY)
var/datum/parsed_map/parsed = load_map(file(mappath), x, y, level.z_value, no_changeturf=(SSatoms.initialized == INITIALIZATION_INSSATOMS), placeOnTop=should_place_on_top)
var/list/bounds = parsed.bounds
if(!bounds)
@@ -181,6 +181,9 @@
//for your ever biggening badminnery kevinz000
//❤ - Cyberboss
-/proc/load_new_z_level(file, name)
- var/datum/map_template/template = new(file, name)
- template.load_new_z()
+/proc/load_new_z_level(file, name, secret)
+ var/datum/map_template/template = new(file, name, TRUE)
+ if(!template.cached_map || template.cached_map.check_for_errors())
+ return FALSE
+ template.load_new_z(secret)
+ return TRUE
diff --git a/code/modules/mapping/verify.dm b/code/modules/mapping/verify.dm
index 7fe07d3b9ec..60067bc5db7 100644
--- a/code/modules/mapping/verify.dm
+++ b/code/modules/mapping/verify.dm
@@ -1,3 +1,6 @@
+/// Global list of map report datums
+GLOBAL_LIST_EMPTY(map_reports)
+
/// An error report generated by [/datum/parsed_map/proc/check_for_errors].
/datum/map_report
var/original_path
@@ -11,6 +14,12 @@
/datum/map_report/New(datum/parsed_map/map)
original_path = map.original_path || "Untitled"
+ GLOB.map_reports += src
+
+/datum/map_report/Destroy(force, ...)
+ GLOB.map_reports -= src
+ return ..()
+
/// Show a rendered version of this report to a client.
/datum/map_report/proc/show_to(client/C)
diff --git a/code/modules/mob/dead/observer/logout.dm b/code/modules/mob/dead/observer/logout.dm
index 1bc86a7ce2d..89acca32f3a 100644
--- a/code/modules/mob/dead/observer/logout.dm
+++ b/code/modules/mob/dead/observer/logout.dm
@@ -3,13 +3,8 @@
if (client)
client.images -= (GLOB.ghost_images_default+GLOB.ghost_images_simple)
- if(observetarget)
- if(ismob(observetarget))
- var/mob/target = observetarget
- if(target.observers)
- target.observers -= src
- UNSETEMPTY(target.observers)
- observetarget = null
+ if(observetarget && ismob(observetarget))
+ cleanup_observe()
..()
spawn(0)
if(src && !key) //we've transferred to another mob. This ghost should be deleted.
diff --git a/code/modules/mob/dead/observer/observer.dm b/code/modules/mob/dead/observer/observer.dm
index ef6fb6394b1..253bde503c4 100644
--- a/code/modules/mob/dead/observer/observer.dm
+++ b/code/modules/mob/dead/observer/observer.dm
@@ -114,7 +114,7 @@ GLOBAL_VAR_INIT(observer_default_invisibility, INVISIBILITY_OBSERVER)
update_appearance()
- if(!T)
+ if(!T || SSmapping.level_trait(T.z, ZTRAIT_SECRET))
var/list/turfs = get_area_turfs(/area/shuttle/arrival)
if(turfs.len)
T = pick(turfs)
@@ -459,7 +459,7 @@ This is the proc mobs get to turn into a ghost. Forked from ghostize due to comp
// This is the ghost's follow verb with an argument
/mob/dead/observer/proc/ManualFollow(atom/movable/target)
- if (!istype(target))
+ if (!istype(target) || (SSmapping.level_trait(target.z, ZTRAIT_SECRET) && !client?.holder))
return
var/icon/I = icon(target.icon,target.icon_state,target.dir)
@@ -846,16 +846,23 @@ This is the proc mobs get to turn into a ghost. Forked from ghostize due to comp
/mob/dead/observer/reset_perspective(atom/A)
if(client)
if(ismob(client.eye) && (client.eye != src))
- var/mob/target = client.eye
- observetarget = null
- if(target.observers)
- target.observers -= src
- UNSETEMPTY(target.observers)
+ cleanup_observe()
if(..())
if(hud_used)
client.screen = list()
hud_used.show_hud(hud_used.hud_version)
+
+/mob/dead/observer/proc/cleanup_observe()
+ var/mob/target = observetarget
+ observetarget = null
+ client?.perspective = initial(client.perspective)
+ sight = initial(sight)
+ UnregisterSignal(target, COMSIG_MOVABLE_Z_CHANGED)
+ if(target.observers)
+ target.observers -= src
+ UNSETEMPTY(target.observers)
+
/mob/dead/observer/verb/observe()
set name = "Observe"
set category = "Ghost"
@@ -890,6 +897,10 @@ This is the proc mobs get to turn into a ghost. Forked from ghostize due to comp
//Istype so we filter out points of interest that are not mobs
if(client && mob_eye && istype(mob_eye))
client.eye = mob_eye
+ client.perspective = EYE_PERSPECTIVE
+ if(SSmapping.level_trait(mob_eye.z, ZTRAIT_SECRET) && !client?.holder)
+ sight = null //we dont want ghosts to see through walls in secret areas
+ RegisterSignal(mob_eye, COMSIG_MOVABLE_Z_CHANGED, .proc/on_observing_z_changed)
if(mob_eye.hud_used)
client.screen = list()
LAZYINITLIST(mob_eye.observers)
@@ -897,6 +908,14 @@ This is the proc mobs get to turn into a ghost. Forked from ghostize due to comp
mob_eye.hud_used.show_hud(mob_eye.hud_used.hud_version, src)
observetarget = mob_eye
+/mob/dead/observer/proc/on_observing_z_changed(datum/source, turf/old_turf, turf/new_turf)
+ SIGNAL_HANDLER
+
+ if(SSmapping.level_trait(new_turf.z, ZTRAIT_SECRET) && !client?.holder)
+ sight = null //we dont want ghosts to see through walls in secret areas
+ else
+ sight = initial(sight)
+
/mob/dead/observer/verb/register_pai_candidate()
set category = "Ghost"
set name = "pAI Setup"
diff --git a/code/modules/mob/living/carbon/carbon.dm b/code/modules/mob/living/carbon/carbon.dm
index 5e7eb7caeaa..164008b101f 100644
--- a/code/modules/mob/living/carbon/carbon.dm
+++ b/code/modules/mob/living/carbon/carbon.dm
@@ -534,7 +534,10 @@
if(!client)
return
if(stat == DEAD)
- sight = (SEE_TURFS|SEE_MOBS|SEE_OBJS)
+ if(!SSmapping.level_trait(z, ZTRAIT_SECRET))
+ sight = (SEE_TURFS|SEE_MOBS|SEE_OBJS)
+ else
+ sight = initial(sight)
see_in_dark = 8
see_invisible = SEE_INVISIBLE_OBSERVER
return
diff --git a/code/modules/mob/living/silicon/robot/robot.dm b/code/modules/mob/living/silicon/robot/robot.dm
index 15b43f44f85..7baa5456861 100644
--- a/code/modules/mob/living/silicon/robot/robot.dm
+++ b/code/modules/mob/living/silicon/robot/robot.dm
@@ -547,7 +547,10 @@
if(!client)
return
if(stat == DEAD)
- sight = (SEE_TURFS|SEE_MOBS|SEE_OBJS)
+ if(!SSmapping.level_trait(z, ZTRAIT_SECRET))
+ sight = (SEE_TURFS|SEE_MOBS|SEE_OBJS)
+ else
+ sight = initial(sight)
see_in_dark = 8
see_invisible = SEE_INVISIBLE_OBSERVER
return
diff --git a/code/modules/mob/living/simple_animal/simple_animal.dm b/code/modules/mob/living/simple_animal/simple_animal.dm
index 867f3c739f2..e50f5595c46 100644
--- a/code/modules/mob/living/simple_animal/simple_animal.dm
+++ b/code/modules/mob/living/simple_animal/simple_animal.dm
@@ -562,7 +562,10 @@
if(!client)
return
if(stat == DEAD)
- sight = (SEE_TURFS|SEE_MOBS|SEE_OBJS)
+ if(!SSmapping.level_trait(z, ZTRAIT_SECRET))
+ sight = (SEE_TURFS|SEE_MOBS|SEE_OBJS)
+ else
+ sight = initial(sight)
see_in_dark = 8
see_invisible = SEE_INVISIBLE_OBSERVER
return
diff --git a/code/modules/mob/mob_movement.dm b/code/modules/mob/mob_movement.dm
index f1929e95cf5..65b9c06fb1d 100644
--- a/code/modules/mob/mob_movement.dm
+++ b/code/modules/mob/mob_movement.dm
@@ -549,3 +549,9 @@
/// Can this mob move between z levels
/mob/proc/canZMove(direction, turf/target)
return FALSE
+
+/mob/abstract_move(atom/destination)
+ var/turf/new_turf = get_turf(destination)
+ if(SSmapping.level_trait(new_turf.z, ZTRAIT_SECRET) && !client?.holder)
+ return
+ return ..()
diff --git a/code/modules/vehicles/mecha/_mecha.dm b/code/modules/vehicles/mecha/_mecha.dm
index 8c27e4803fc..69247a04bc9 100644
--- a/code/modules/vehicles/mecha/_mecha.dm
+++ b/code/modules/vehicles/mecha/_mecha.dm
@@ -274,8 +274,9 @@
return ..()
if(phase_state)
flick(phase_state, src)
- var/area/destination_area = get_step(loc, movement_dir).loc
- if(destination_area.area_flags & NOTELEPORT)
+ var/turf/destination_turf = get_step(loc, movement_dir)
+ var/area/destination_area = destination_turf.loc
+ if(destination_area.area_flags & NOTELEPORT || SSmapping.level_trait(destination_turf.z, ZTRAIT_NOPHASE))
return FALSE
return TRUE
diff --git a/config/away_missions/README.md b/config/away_missions/README.md
new file mode 100644
index 00000000000..9b1dfa66f1d
--- /dev/null
+++ b/config/away_missions/README.md
@@ -0,0 +1,3 @@
+Add away mission dmms here.
+
+**These are fully cached so keep this directory empty by default.**
\ No newline at end of file
diff --git a/config/game_options.txt b/config/game_options.txt
index a011b7f80ed..027ba857da7 100644
--- a/config/game_options.txt
+++ b/config/game_options.txt
@@ -161,13 +161,15 @@ ALLOW_AI_MULTICAM
## Uncomment to load the virtual reality hub map
#VIRTUAL_REALITY
-## Uncomment to load one of the missions from awaymissionconfig.txt at roundstart.
+## Uncomment to load one of the missions from awaymissionconfig.txt or away_missions/ at roundstart.
#ROUNDSTART_AWAY
## How long the delay is before the Away Mission gate opens. Default is half an hour.
## 600 is one minute.
GATEWAY_DELAY 18000
+## The probability of the gateway mission being a config one
+CONFIG_GATEWAY_CHANCE 0
## ACCESS ###