diff --git a/code/__defines/spawn.dm b/code/__defines/spawn.dm
new file mode 100644
index 00000000000..c576e4f0ac4
--- /dev/null
+++ b/code/__defines/spawn.dm
@@ -0,0 +1,8 @@
+/// Allows the spawn point to be used for observers spawning.
+#define SPAWN_FLAG_GHOSTS_CAN_SPAWN BITFLAG(0)
+/// Allows admin prison releases to use this spawn point.
+#define SPAWN_FLAG_PRISONERS_CAN_SPAWN BITFLAG(1)
+/// Allows general job latejoining to use this spawn point.
+#define SPAWN_FLAG_JOBS_CAN_SPAWN BITFLAG(2)
+/// Allows persistence decls (currently just bookcases) to use this spawn point.
+#define SPAWN_FLAG_PERSISTENCE_CAN_SPAWN BITFLAG(3)
\ No newline at end of file
diff --git a/code/controllers/subsystems/jobs.dm b/code/controllers/subsystems/jobs.dm
index 335e6df2521..3c3415ffe04 100644
--- a/code/controllers/subsystems/jobs.dm
+++ b/code/controllers/subsystems/jobs.dm
@@ -503,7 +503,7 @@ SUBSYSTEM_DEF(jobs)
H.forceMove(S.loc)
else
var/decl/spawnpoint/spawnpoint = job.get_spawnpoint(H.client)
- H.forceMove(pick(spawnpoint.turfs))
+ H.forceMove(DEFAULTPICK(spawnpoint.get_spawn_turfs(H), get_random_spawn_turf(SPAWN_FLAG_JOBS_CAN_SPAWN)))
spawnpoint.after_join(H)
// Moving wheelchair if they have one
diff --git a/code/datums/repositories/decls.dm b/code/datums/repositories/decls.dm
index 4c97482a93e..0bc268c1a66 100644
--- a/code/datums/repositories/decls.dm
+++ b/code/datums/repositories/decls.dm
@@ -40,6 +40,22 @@ var/global/repository/decls/decls_repository = new
RETURN_TYPE(/decl)
. = get_decl(fetched_decl_ids[decl_id], validate_decl_type)
+// This proc and get_decl_by_id_or_var() are being added solely to grandfather in decls saved to player saves under name
+// rather than UID. They should be considered deprecated for this purpose - uid and get_decl_by_id() should be used instead.
+/repository/decls/proc/get_decl_by_var(var/decl_value, var/decl_prototype, var/check_var = "name")
+ var/list/all_decls = get_decls_of_type(decl_prototype)
+ var/decl/prototype = all_decls[all_decls[1]] // Can't just grab the prototype as it may be abstract
+ if(!(check_var in prototype.vars))
+ CRASH("Attempted to retrieve a decl by a var that does not exist on the decl type ('[check_var]')")
+ for(var/decl_type in all_decls)
+ var/decl/decl = all_decls[decl_type]
+ if(decl.vars[check_var] == decl_value)
+ return decl
+
+/repository/decls/proc/get_decl_by_id_or_var(var/decl_id, var/decl_prototype, var/check_var = "name")
+ RETURN_TYPE(/decl)
+ return get_decl_by_id(decl_id, validate_decl_type = FALSE) || get_decl_by_var(decl_id, decl_prototype, check_var)
+
/repository/decls/proc/get_decl_path_by_id(decl_id)
. = fetched_decl_ids[decl_id]
diff --git a/code/game/jobs/job/_job.dm b/code/game/jobs/job/_job.dm
index 9ba3f18c8a2..896730ebdc8 100644
--- a/code/game/jobs/job/_job.dm
+++ b/code/game/jobs/job/_job.dm
@@ -439,7 +439,7 @@
spawnpos = null
if(!spawnpos)
// Step through all spawnpoints and pick first appropriate for job
- for(var/decl/spawnpoint/candidate as anything in global.using_map.allowed_spawns)
+ for(var/decl/spawnpoint/candidate as anything in global.using_map.allowed_latejoin_spawns)
if(candidate?.check_job_spawning(src))
spawnpos = candidate
break
diff --git a/code/game/objects/effects/landmarks_latejoin.dm b/code/game/objects/effects/landmarks_latejoin.dm
index 6d7f768167c..c7ca55458b0 100644
--- a/code/game/objects/effects/landmarks_latejoin.dm
+++ b/code/game/objects/effects/landmarks_latejoin.dm
@@ -1,23 +1,9 @@
-var/global/list/latejoin_locations = list()
-var/global/list/latejoin_cryo_locations = list()
-var/global/list/latejoin_cyborg_locations = list()
-var/global/list/latejoin_gateway_locations = list()
-
/obj/abstract/landmark/latejoin
delete_me = TRUE
+ var/spawn_decl = /decl/spawnpoint/arrivals
/obj/abstract/landmark/latejoin/Initialize()
- add_loc()
+ if(spawn_decl)
+ var/decl/spawnpoint/spawn_instance = GET_DECL(spawn_decl)
+ spawn_instance.add_spawn_turf(get_turf(src))
. = ..()
-
-/obj/abstract/landmark/latejoin/proc/add_loc()
- global.latejoin_locations |= get_turf(src)
-
-/obj/abstract/landmark/latejoin/gateway/add_loc()
- global.latejoin_gateway_locations |= get_turf(src)
-
-/obj/abstract/landmark/latejoin/cryo/add_loc()
- global.latejoin_cryo_locations |= get_turf(src)
-
-/obj/abstract/landmark/latejoin/cyborg/add_loc()
- global.latejoin_cyborg_locations |= get_turf(src)
diff --git a/code/modules/admin/admin.dm b/code/modules/admin/admin.dm
index 86ffd24d930..5f9d41c586e 100644
--- a/code/modules/admin/admin.dm
+++ b/code/modules/admin/admin.dm
@@ -988,7 +988,7 @@ var/global/floorIsLava = 0
set name = "Unprison"
if (isAdminLevel(M.z))
if (config.allow_admin_jump)
- M.forceMove(pick(global.latejoin_locations))
+ M.forceMove(get_random_spawn_turf(SPAWN_FLAG_PRISONERS_CAN_SPAWN))
message_admins("[key_name_admin(usr)] has unprisoned [key_name_admin(M)]", 1)
log_admin("[key_name(usr)] has unprisoned [key_name(M)]")
else
diff --git a/code/modules/admin/verbs/randomverbs.dm b/code/modules/admin/verbs/randomverbs.dm
index e6a81b177a0..30027e94cbd 100644
--- a/code/modules/admin/verbs/randomverbs.dm
+++ b/code/modules/admin/verbs/randomverbs.dm
@@ -440,8 +440,7 @@ Traitors and the like can also be revived with the previous role mostly intact.
to_chat(usr, SPAN_WARNING("There is no active key like that in the game or the person is not currently a ghost."))
return
- var/mob/living/carbon/human/new_character = new(pick(global.latejoin_locations))//The mob being spawned.
-
+ var/mob/living/carbon/human/new_character = new(get_random_spawn_turf(SPAWN_FLAG_JOBS_CAN_SPAWN)) //The mob being spawned.
var/datum/computer_file/report/crew_record/record_found //Referenced to later to either randomize or not randomize the character.
if(G_found.mind && !G_found.mind.active)
record_found = get_crewmember_record(G_found.real_name)
diff --git a/code/modules/client/preference_setup/background/02_culture.dm b/code/modules/client/preference_setup/background/02_culture.dm
index 852a634db24..43b7cb5180c 100644
--- a/code/modules/client/preference_setup/background/02_culture.dm
+++ b/code/modules/client/preference_setup/background/02_culture.dm
@@ -118,7 +118,7 @@
if(href_list["toggle_verbose_[token]"])
hidden[token] = !hidden[token]
return TOPIC_REFRESH
-
+
var/decl/cultural_info/new_token = href_list["set_token_entry_[token]"]
if(!isnull(new_token))
new_token = locate(new_token)
diff --git a/code/modules/client/preference_setup/general/01_basic.dm b/code/modules/client/preference_setup/general/01_basic.dm
index 2d799b9206d..5bde5e6d9b4 100644
--- a/code/modules/client/preference_setup/general/01_basic.dm
+++ b/code/modules/client/preference_setup/general/01_basic.dm
@@ -24,13 +24,10 @@
pref.bodytype = R.read("bodytype")
pref.real_name = R.read("real_name")
pref.be_random_name = R.read("name_is_always_random")
-
- pref.spawnpoint = R.read("spawnpoint")
- for(var/decl/spawnpoint/spawnpoint as anything in global.using_map.allowed_spawns)
- if(pref.spawnpoint == spawnpoint.name)
- pref.spawnpoint = spawnpoint.type
- break
- if(!ispath(pref.spawnpoint, /decl/spawnpoint))
+ var/decl/spawnpoint/loaded_spawnpoint = decls_repository.get_decl_by_id_or_var(R.read("spawnpoint"), /decl/spawnpoint)
+ if(istype(loaded_spawnpoint) && (loaded_spawnpoint in global.using_map.allowed_latejoin_spawns))
+ pref.spawnpoint = loaded_spawnpoint.type
+ else
pref.spawnpoint = global.using_map.default_spawn
/datum/category_item/player_setup_item/physical/basic/save_character(datum/pref_record_writer/W)
@@ -38,14 +35,13 @@
W.write("bodytype", pref.bodytype)
W.write("real_name", pref.real_name)
W.write("name_is_always_random", pref.be_random_name)
-
var/decl/spawnpoint/spawnpoint = GET_DECL(pref.spawnpoint)
- W.write("spawnpoint", spawnpoint.name)
+ W.write("spawnpoint", spawnpoint.uid)
/datum/category_item/player_setup_item/physical/basic/sanitize_character()
var/valid_spawn = FALSE
- for(var/decl/spawnpoint/spawnpoint as anything in global.using_map.allowed_spawns)
+ for(var/decl/spawnpoint/spawnpoint as anything in global.using_map.allowed_latejoin_spawns)
if(pref.spawnpoint == spawnpoint.type)
valid_spawn = TRUE
break
@@ -93,8 +89,13 @@
else
. += "[G.pronoun_string]"
+ . += "
Spawnpoint:"
var/decl/spawnpoint/spawnpoint = GET_DECL(pref.spawnpoint)
- . += "
Spawn point: [spawnpoint.name]"
+ for(var/decl/spawnpoint/allowed_spawnpoint in global.using_map.allowed_latejoin_spawns)
+ if(spawnpoint == allowed_spawnpoint)
+ . += "[allowed_spawnpoint.name]"
+ else
+ . += "[allowed_spawnpoint.name]"
. = jointext(.,null)
/datum/category_item/player_setup_item/physical/basic/OnTopic(var/href,var/list/href_list, var/mob/user)
@@ -146,8 +147,8 @@
return TOPIC_REFRESH_UPDATE_PREVIEW
else if(href_list["spawnpoint"])
- var/decl/spawnpoint/choice = input(user, "Where would you like to spawn when late-joining?") as null|anything in global.using_map.allowed_spawns
- if(!istype(choice) || !CanUseTopic(user))
+ var/decl/spawnpoint/choice = locate(href_list["spawnpoint"])
+ if(!istype(choice) || !CanUseTopic(user) || !(choice in global.using_map.allowed_latejoin_spawns))
return TOPIC_NOACTION
pref.spawnpoint = choice.type
return TOPIC_REFRESH
diff --git a/code/modules/client/preference_setup/general/02_body.dm b/code/modules/client/preference_setup/general/02_body.dm
index c8e310bbd4b..a45885a7efd 100644
--- a/code/modules/client/preference_setup/general/02_body.dm
+++ b/code/modules/client/preference_setup/general/02_body.dm
@@ -33,47 +33,18 @@
pref.bgstate = R.read("bgstate")
// Get h_style type.
- var/list/all_sprite_accessories
- var/load_h_style = R.read("hair_style_name")
- var/decl/h_style_decl = decls_repository.get_decl_by_id(load_h_style, validate_decl_type = FALSE)
- // Grandfather in name-based sprite accessories.
- if(!istype(h_style_decl) && load_h_style)
- all_sprite_accessories = decls_repository.get_decls_of_subtype(/decl/sprite_accessory/hair)
- for(var/accessory in all_sprite_accessories)
- var/decl/sprite_accessory/sprite = all_sprite_accessories[accessory]
- if(sprite.name == load_h_style)
- h_style_decl = sprite
- break
+ var/decl/h_style_decl = decls_repository.get_decl_by_id_or_var(R.read("hair_style_name"), /decl/sprite_accessory/hair)
pref.h_style = istype(h_style_decl) ? h_style_decl.type : /decl/sprite_accessory/hair/bald
-
// Get f_style type.
- var/load_f_style = R.read("facial_style_name")
- var/decl/f_style_decl = decls_repository.get_decl_by_id(load_f_style, validate_decl_type = FALSE)
- // Grandfather in name-based accessories.
- if(!istype(f_style_decl) && load_f_style)
- all_sprite_accessories = decls_repository.get_decls_of_subtype(/decl/sprite_accessory/facial_hair)
- for(var/accessory in all_sprite_accessories)
- var/decl/sprite_accessory/sprite = all_sprite_accessories[accessory]
- if(sprite.name == load_f_style)
- f_style_decl = sprite
- break
+ var/decl/f_style_decl = decls_repository.get_decl_by_id_or_var(R.read("facial_style_name"), /decl/sprite_accessory/facial_hair)
pref.f_style = istype(f_style_decl) ? f_style_decl.type : /decl/sprite_accessory/facial_hair/shaved
-
// Get markings type.
var/list/load_markings = R.read("body_markings")
pref.body_markings = list()
- all_sprite_accessories = decls_repository.get_decls_of_subtype(/decl/sprite_accessory/marking)
if(length(load_markings))
for(var/marking in load_markings)
- var/decl/sprite_accessory/marking/loaded_marking = decls_repository.get_decl_by_id(marking, validate_decl_type = FALSE)
- // Grandfather in name-based accessories.
- if(isnull(loaded_marking))
- for(var/accessory in all_sprite_accessories)
- var/decl/sprite_accessory/sprite = all_sprite_accessories[accessory]
- if(sprite.name == marking)
- loaded_marking = pref.body_markings[marking]
- break
- if(loaded_marking)
+ var/decl/sprite_accessory/marking/loaded_marking = decls_repository.get_decl_by_id_or_var(marking, /decl/sprite_accessory/marking)
+ if(istype(loaded_marking))
pref.body_markings[loaded_marking.type] = load_markings[marking]
/datum/category_item/player_setup_item/physical/body/save_character(datum/pref_record_writer/W)
diff --git a/code/modules/client/preferences_spawnpoints.dm b/code/modules/client/preferences_spawnpoints.dm
index 815aba407a7..76692d83e6a 100644
--- a/code/modules/client/preferences_spawnpoints.dm
+++ b/code/modules/client/preferences_spawnpoints.dm
@@ -1,14 +1,50 @@
-/decl/spawnpoint
- var/name // Name used in preference setup.
- var/msg // Message to display on the arrivals computer.
- var/list/turfs // List of turfs to spawn on.
+// This proc will return a random valid respawn location, defaulting to
+// observer spawn points if nothing else is available. Flags can be used to
+// filter the spawnpoints considered valid, see code/__defines/spawn.dm.
+/proc/get_random_spawn_turf(var/mob/spawning, var/check_flags)
+ var/list/spawn_locs = list()
+ var/list/all_spawns = decls_repository.get_decls_of_subtype(/decl/spawnpoint)
+ for(var/spawn_type in all_spawns)
+ var/decl/spawnpoint/spawn_data = all_spawns[spawn_type]
+ if((!check_flags || (spawn_data.spawn_flags & check_flags)))
+ var/add_spawn_turfs = spawn_data.get_spawn_turfs(spawning)
+ if(length(add_spawn_turfs))
+ spawn_locs |= add_spawn_turfs
+ . = SAFEPICK(spawn_locs)
+ if(!.)
+ // Observer spawn is guaranteed by CI to be populated.
+ var/decl/spawnpoint/observer_spawn = GET_DECL(/decl/spawnpoint/observer)
+ return pick(observer_spawn.get_spawn_turfs())
+/decl/spawnpoint
+ abstract_type = /decl/spawnpoint
+ decl_flags = DECL_FLAG_MANDATORY_UID
+ /// Name displayed in preference setup.
+ var/name
+ /// Message to display on the arrivals computer. If null, no message will be sent.
+ var/spawn_announcement
+ /// Determines validity for get_random_spawn_turf()
+ var/spawn_flags = (SPAWN_FLAG_GHOSTS_CAN_SPAWN | SPAWN_FLAG_JOBS_CAN_SPAWN | SPAWN_FLAG_PRISONERS_CAN_SPAWN | SPAWN_FLAG_PERSISTENCE_CAN_SPAWN)
+ /// List of turfs to spawn on. Retrieved via get_spawn_turfs().
+ VAR_PRIVATE/list/_spawn_turfs
+ /// A list of job types that are allowed to use this spawnpoint.
var/list/restrict_job
+ /// A list of event categories that are allowed to use this spawnpoint (ex. ASSIGNMENT_JANITOR)
var/list/restrict_job_event_categories
-
+ /// A list of job types that are not allowed to use this spawnpoint.
var/list/disallow_job
+ /// A list of event categories that are not allowed to use this spawnpoint (ex. ASSIGNMENT_JANITOR)
var/list/disallow_job_event_categories
+// Returns the spawn list. Mob is supplied in case overrides want to check prefs.
+/decl/spawnpoint/proc/get_spawn_turfs(var/mob/spawning)
+ return _spawn_turfs
+
+// Adds to the spawn list. Uses a proc for subtype overrides.
+/decl/spawnpoint/proc/add_spawn_turf(var/turf/adding)
+ LAZYDISTINCTADD(_spawn_turfs, adding)
+
+// Validates that a job is allowed to use this spawn point.
/decl/spawnpoint/proc/check_job_spawning(var/datum/job/job)
if(restrict_job && !(job.type in restrict_job) && !(job.title in restrict_job))
@@ -33,30 +69,31 @@
/decl/spawnpoint/proc/after_join(mob/victim)
return
-/decl/spawnpoint/arrivals
- name = "Arrivals"
- msg = "has arrived on the station"
-
-/decl/spawnpoint/arrivals/Initialize()
- . = ..()
- turfs = global.latejoin_locations
+// Dummy spawnpoint for ghosts.
+/decl/spawnpoint/observer
+ name = "Observer"
+ uid = "spawn_observer"
+ spawn_flags = SPAWN_FLAG_GHOSTS_CAN_SPAWN
-/decl/spawnpoint/gateway
- name = "Gateway"
- msg = "has completed translation from offsite gateway"
+/obj/abstract/landmark/latejoin/observer
+ spawn_decl = /decl/spawnpoint/observer
-/decl/spawnpoint/gateway/Initialize()
- . = ..()
- turfs = global.latejoin_gateway_locations
+// The 'default' latejoin spawn location.
+/decl/spawnpoint/arrivals
+ name = "Arrivals"
+ spawn_announcement = "has arrived on the station"
+ uid = "spawn_arrivals"
+// Spawn the mob inside a cryopod at the spawn loc.
/decl/spawnpoint/cryo
name = "Cryogenic Storage"
- msg = "has completed cryogenic revival"
+ spawn_announcement = "has completed cryogenic revival"
disallow_job_event_categories = list(ASSIGNMENT_ROBOT)
+ uid = "spawn_cryo"
+ spawn_flags = (SPAWN_FLAG_GHOSTS_CAN_SPAWN | SPAWN_FLAG_JOBS_CAN_SPAWN)
-/decl/spawnpoint/cryo/Initialize()
- . = ..()
- turfs = global.latejoin_cryo_locations
+/obj/abstract/landmark/latejoin/cryo
+ spawn_decl = /decl/spawnpoint/cryo
/decl/spawnpoint/cryo/after_join(mob/living/carbon/human/victim)
if(!istype(victim) || victim.buckled) // They may have spawned with a wheelchair; don't move them into a pod in that case.
@@ -79,11 +116,13 @@
to_chat(victim,SPAN_NOTICE("You are slowly waking up from the cryostasis aboard [global.using_map.full_name]. It might take a few seconds."))
return
+// Spawnpoint used specifically for robots.
/decl/spawnpoint/cyborg
name = "Robot Storage"
- msg = "has been activated from storage"
+ spawn_announcement = "has been activated from storage"
restrict_job_event_categories = list(ASSIGNMENT_ROBOT)
+ spawn_flags = SPAWN_FLAG_JOBS_CAN_SPAWN
+ uid = "spawn_cyborg"
-/decl/spawnpoint/cyborg/Initialize()
- . = ..()
- turfs = global.latejoin_cyborg_locations
+/obj/abstract/landmark/latejoin/cyborg
+ spawn_decl = /decl/spawnpoint/cyborg
diff --git a/code/modules/goals/definitions/department_clerical.dm b/code/modules/goals/definitions/department_clerical.dm
index b323429fe85..1a4ba5a136b 100644
--- a/code/modules/goals/definitions/department_clerical.dm
+++ b/code/modules/goals/definitions/department_clerical.dm
@@ -25,21 +25,21 @@
..()
-/datum/goal/department/paperwork/proc/get_spawn_turfs()
+/datum/goal/department/paperwork/proc/get_paper_spawn_turfs()
return
-/datum/goal/department/paperwork/proc/get_end_areas()
+/datum/goal/department/paperwork/proc/get_paper_end_areas()
return
/datum/goal/department/paperwork/try_initialize()
- var/list/start_candidates = get_spawn_turfs()
+ var/list/start_candidates = get_paper_spawn_turfs()
if(!length(start_candidates))
PRINT_STACK_TRACE("Paperwork goal [type] initialized with no spawn landmarks mapped!")
SSgoals.pending_goals -= src
return FALSE
- var/list/end_candidates = get_end_areas()
+ var/list/end_candidates = get_paper_end_areas()
if(!length(end_candidates))
PRINT_STACK_TRACE("Paperwork goal [type] initialized with no end landmarks mapped!")
SSgoals.pending_goals -= src
diff --git a/code/modules/mob/new_player/new_player.dm b/code/modules/mob/new_player/new_player.dm
index 9da8084630d..e3de5663743 100644
--- a/code/modules/mob/new_player/new_player.dm
+++ b/code/modules/mob/new_player/new_player.dm
@@ -115,13 +115,13 @@ INITIALIZE_IMMEDIATE(/mob/new_player)
spawning = 1
sound_to(src, sound(null, repeat = 0, wait = 0, volume = 85, channel = sound_channels.lobby_channel))// MAD JAMS cant last forever yo
-
observer.started_as_observer = 1
close_spawn_windows()
- var/obj/O = locate("landmark*Observer-Start")
- if(istype(O))
+ var/decl/spawnpoint/spawnpoint = GET_DECL(/decl/spawnpoint/observer)
+ var/turf/T = SAFEPICK(spawnpoint.get_spawn_turfs(src))
+ if(istype(T))
to_chat(src, SPAN_NOTICE("Now teleporting."))
- observer.forceMove(O.loc)
+ observer.forceMove(T)
else
to_chat(src, SPAN_DANGER("Could not locate an observer spawn point. Use the Teleport verb to jump to the map."))
observer.timeofdeath = world.time // Set the time of death so that the respawn timer works correctly.
@@ -198,12 +198,14 @@ INITIALIZE_IMMEDIATE(/mob/new_player)
to_chat(src, alert("That spawnpoint is unavailable. Please try another."))
return 0
- var/turf/spawn_turf = pick(spawnpoint.turfs)
+ var/turf/spawn_turf
if(job.latejoin_at_spawnpoints)
var/obj/S = job.get_roundstart_spawnpoint()
spawn_turf = get_turf(S)
+ else
+ spawn_turf = SAFEPICK(spawnpoint.get_spawn_turfs(src))
- if(!job.no_warn_unsafe && !SSjobs.check_unsafe_spawn(src, spawn_turf))
+ if(!spawn_turf || !job.no_warn_unsafe && !SSjobs.check_unsafe_spawn(src, spawn_turf))
return
// Just in case someone stole our position while we were waiting for input from alert() proc
@@ -231,9 +233,11 @@ INITIALIZE_IMMEDIATE(/mob/new_player)
if(!(ASSIGNMENT_ROBOT in job.event_categories))
CreateModularRecord(character)
SSticker.minds += character.mind//Cyborgs and AIs handle this in the transform proc. //TODO!!!!! ~Carn
- AnnounceArrival(character, job, spawnpoint.msg)
- else
- AnnounceCyborg(character, job, spawnpoint.msg)
+ if(spawnpoint.spawn_announcement)
+ AnnounceArrival(character, job, spawnpoint.spawn_announcement)
+ else if(spawnpoint.spawn_announcement)
+ AnnounceCyborg(character, job, spawnpoint.spawn_announcement)
+
callHook("player_latejoin", list(job, character))
log_and_message_admins("has joined the round as [character.mind.assigned_role].", character)
@@ -356,7 +360,7 @@ INITIALIZE_IMMEDIATE(/mob/new_player)
if(!job)
job = SSjobs.get_by_title(global.using_map.default_job_title)
var/decl/spawnpoint/spawnpoint = job.get_spawnpoint(client, client.prefs.ranks[job.title])
- spawn_turf = pick(spawnpoint.turfs)
+ spawn_turf = DEFAULTPICK(spawnpoint.get_spawn_turfs(src), get_random_spawn_turf(SPAWN_FLAG_JOBS_CAN_SPAWN))
if(chosen_species)
if(!check_species_allowed(chosen_species))
diff --git a/code/modules/mob/observer/ghost/ghost.dm b/code/modules/mob/observer/ghost/ghost.dm
index 4631897824e..a8a3766c85a 100644
--- a/code/modules/mob/observer/ghost/ghost.dm
+++ b/code/modules/mob/observer/ghost/ghost.dm
@@ -61,11 +61,8 @@ var/global/list/image/ghost_sightless_images = list() //this is a list of images
mind = new /datum/mind(key)
mind.current = src
if(!T)
- var/list/spawn_locs = global.latejoin_locations | global.latejoin_cryo_locations | global.latejoin_gateway_locations
- if(length(spawn_locs))
- T = pick(spawn_locs)
- else
- T = locate(1, 1, 1)
+ T = get_random_spawn_turf(SPAWN_FLAG_GHOSTS_CAN_SPAWN)
+
forceMove(T)
if(!name) //To prevent nameless ghosts
diff --git a/code/modules/persistence/persistence_datum_book.dm b/code/modules/persistence/persistence_datum_book.dm
index c890055a7e1..e55a5cc9427 100644
--- a/code/modules/persistence/persistence_datum_book.dm
+++ b/code/modules/persistence/persistence_datum_book.dm
@@ -60,7 +60,7 @@
if(length(global.station_bookcases))
T = get_turf(pick(global.station_bookcases))
else
- T = pick(global.latejoin_locations)
+ T = get_random_spawn_turf(SPAWN_FLAG_PERSISTENCE_CAN_SPAWN)
. = ..(T, tokens)
diff --git a/code/unit_tests/map_tests.dm b/code/unit_tests/map_tests.dm
index ed95db958b6..54e56f315d0 100644
--- a/code/unit_tests/map_tests.dm
+++ b/code/unit_tests/map_tests.dm
@@ -299,19 +299,70 @@
//=======================================================================================
/datum/unit_test/correct_allowed_spawn_test
- name = "MAP: All allowed_spawns entries should have spawnpoints on map."
+ name = "MAP: All allowed_latejoin_spawns entries should have spawnpoints on map."
/datum/unit_test/correct_allowed_spawn_test/start_test()
+
var/list/failed = list()
- for(var/decl/spawnpoint/spawnpoint as anything in global.using_map.allowed_spawns)
- if(!length(spawnpoint.turfs))
+ var/list/check_spawn_flags = list(
+ "SPAWN_FLAG_PRISONERS_CAN_SPAWN" = SPAWN_FLAG_PRISONERS_CAN_SPAWN,
+ "SPAWN_FLAG_JOBS_CAN_SPAWN" = SPAWN_FLAG_JOBS_CAN_SPAWN,
+ "SPAWN_FLAG_PERSISTENCE_CAN_SPAWN" = SPAWN_FLAG_PERSISTENCE_CAN_SPAWN
+ )
+
+ // Check that all flags are represented in compiled spawnpoints.
+ // The actual validation will happen at the end of the proc.
+ var/list/all_spawnpoints = decls_repository.get_decls_of_subtype(/decl/spawnpoint)
+ for(var/spawn_type in all_spawnpoints)
+ var/decl/spawnpoint/spawnpoint = all_spawnpoints[spawn_type]
+ // No turfs probably means it isn't mapped; if it's in the allowed list this will be picked up below.
+ if(!length(spawnpoint.get_spawn_turfs()))
+ continue
+ if(spawnpoint.spawn_flags)
+ for(var/spawn_flag in check_spawn_flags)
+ if(spawnpoint.spawn_flags & check_spawn_flags[spawn_flag])
+ check_spawn_flags -= spawn_flag
+ if(!length(check_spawn_flags))
+ break
+
+ // Check if spawn points have any turfs at all associated.
+ for(var/decl/spawnpoint/spawnpoint as anything in global.using_map.allowed_latejoin_spawns)
+ if(!length(spawnpoint.get_spawn_turfs()))
log_unit_test("Map allows spawning in [spawnpoint.name], but [spawnpoint.name] has no associated spawn turfs.")
failed += spawnpoint.type
- if(length(failed))
- fail("Some allowed spawnpoints have no spawnpoint turfs:\n[jointext(failed, "\n")]")
- else
+ // Validate our forced job spawnpoints since they may not be included in allowed_latejoin_spawns.
+ for(var/job_title in SSjobs.titles_to_datums)
+ var/datum/job/job = SSjobs.titles_to_datums[job_title]
+ if(!job.forced_spawnpoint)
+ continue
+ var/decl/spawnpoint/spawnpoint = GET_DECL(job.forced_spawnpoint)
+ if(!spawnpoint.check_job_spawning(job))
+ log_unit_test("Forced spawnpoint for [job_title], [spawnpoint.name], does not permit the job to spawn there.")
+ failed += spawnpoint.type
+ if(!length(spawnpoint.get_spawn_turfs()))
+ log_unit_test("Job [job_title] forces spawning in [spawnpoint.name], but [spawnpoint.name] has no associated spawn turfs.")
+ failed += spawnpoint.type
+
+ // Observer spawn is special and isn't in the using_map list.
+ var/decl/spawnpoint/observer_spawn = GET_DECL(/decl/spawnpoint/observer)
+ if(!length(observer_spawn.get_spawn_turfs()))
+ log_unit_test("Map has no [observer_spawn.name] spawn turfs.")
+ failed += observer_spawn.type
+ if(!(observer_spawn.spawn_flags & SPAWN_FLAG_GHOSTS_CAN_SPAWN))
+ log_unit_test("[observer_spawn.name] is missing SPAWN_FLAG_GHOSTS_CAN_SPAWN.")
+ failed |= observer_spawn.type
+
+ // Report test outcome.
+ if(!length(failed) && !length(check_spawn_flags))
pass("All allowed spawnpoints have spawnpoint turfs.")
+ else
+ var/list/failstring = list()
+ if(length(failed))
+ failstring += "Some allowed spawnpoints have no spawnpoint turfs:\n[jointext(failed, "\n")]"
+ if(length(check_spawn_flags))
+ failstring += "Some required spawn flags are not set on available spawnpoints:\n[jointext(check_spawn_flags, "\n")]"
+ fail(jointext(failstring, "\n"))
return 1
//=======================================================================================
diff --git a/maps/away_sites_testing/away_sites_testing_define.dm b/maps/away_sites_testing/away_sites_testing_define.dm
index a75e88dd2e7..6f3a96274ee 100644
--- a/maps/away_sites_testing/away_sites_testing_define.dm
+++ b/maps/away_sites_testing/away_sites_testing_define.dm
@@ -5,9 +5,13 @@
path = "away_sites_testing"
overmap_ids = list(OVERMAP_ID_SPACE)
- allowed_spawns = list()
+ allowed_latejoin_spawns = list()
default_spawn = null
+// Set the observer spawn to include every flag so that CI flag checks pass.
+/decl/spawnpoint/observer
+ spawn_flags = (SPAWN_FLAG_GHOSTS_CAN_SPAWN | SPAWN_FLAG_JOBS_CAN_SPAWN | SPAWN_FLAG_PRISONERS_CAN_SPAWN | SPAWN_FLAG_PERSISTENCE_CAN_SPAWN)
+
/datum/map/away_sites_testing/build_away_sites()
var/list/unsorted_sites = list_values(SSmapping.get_templates_by_category(MAP_TEMPLATE_CATEGORY_AWAYSITE))
var/list/sorted_sites = sortTim(unsorted_sites, /proc/cmp_sort_templates_tallest_to_shortest)
diff --git a/maps/away_sites_testing/blank.dmm b/maps/away_sites_testing/blank.dmm
index 6d50a5c258f..ad94ea9ecd0 100644
--- a/maps/away_sites_testing/blank.dmm
+++ b/maps/away_sites_testing/blank.dmm
@@ -8,6 +8,10 @@
"c" = (
/turf/simulated/floor/tiled,
/area/space)
+"d" = (
+/obj/abstract/landmark/latejoin/observer,
+/turf/space,
+/area/space)
"e" = (
/obj/abstract/landmark/latejoin,
/turf/simulated/floor/tiled,
@@ -60,7 +64,7 @@ a
a
a
a
-a
+d
a
a
a
diff --git a/maps/example/example-1.dmm b/maps/example/example-1.dmm
index cbf45a8633f..338043f0280 100644
--- a/maps/example/example-1.dmm
+++ b/maps/example/example-1.dmm
@@ -91,6 +91,10 @@
/obj/item/stack/material/sheet/mapped/steel/fifty,
/turf/simulated/floor/tiled/steel_grid,
/area/example/first)
+"dK" = (
+/obj/abstract/landmark/latejoin/observer,
+/turf/simulated/wall/r_wall/prepainted,
+/area/example/first)
"eh" = (
/obj/machinery/atmospherics/unary/vent_scrubber/on{
dir = 1
@@ -501,8 +505,8 @@
/area/example/first)
"xq" = (
/obj/abstract/level_data_spawner/main_level{
- name = "Example First Deck";
-},
+ name = "Example First Deck"
+ },
/turf/space,
/area/space)
"xR" = (
@@ -2453,7 +2457,7 @@ XZ
Wr
Wr
Wr
-XZ
+dK
XZ
XZ
XZ
diff --git a/maps/example/example_define.dm b/maps/example/example_define.dm
index 389d11249d0..9c48d50899f 100644
--- a/maps/example/example_define.dm
+++ b/maps/example/example_define.dm
@@ -11,7 +11,7 @@
/decl/music_track/absconditus
)
- allowed_spawns = list(
+ allowed_latejoin_spawns = list(
/decl/spawnpoint/arrivals
)
diff --git a/maps/exodus/exodus-2.dmm b/maps/exodus/exodus-2.dmm
index 899d1850def..2e444764008 100644
--- a/maps/exodus/exodus-2.dmm
+++ b/maps/exodus/exodus-2.dmm
@@ -63833,9 +63833,7 @@
/area/ship/exodus_pod_mining)
"igB" = (
/obj/machinery/hologram/holopad,
-/obj/abstract/landmark{
- name = "Observer-Start"
- },
+/obj/abstract/landmark/latejoin/observer,
/turf/simulated/floor/tiled/dark/monotile,
/area/shuttle/arrival/station)
"ihN" = (
diff --git a/maps/exodus/exodus_jobs.dm b/maps/exodus/exodus_jobs.dm
index 7c460652cea..df6c7b54782 100644
--- a/maps/exodus/exodus_jobs.dm
+++ b/maps/exodus/exodus_jobs.dm
@@ -1,3 +1,11 @@
+/decl/spawnpoint/gateway
+ name = "Gateway"
+ spawn_announcement = "has completed translation from offsite gateway"
+ uid = "spawn_exodus_gateway"
+
+/obj/abstract/landmark/latejoin/gateway
+ spawn_decl = /decl/spawnpoint/gateway
+
/datum/map/exodus
default_job_type = /datum/job/assistant
default_department_type = /decl/department/civilian
diff --git a/maps/exodus/jobs/_goals.dm b/maps/exodus/jobs/_goals.dm
index 180d00bfd37..72c324b5c1d 100644
--- a/maps/exodus/jobs/_goals.dm
+++ b/maps/exodus/jobs/_goals.dm
@@ -37,10 +37,10 @@ var/global/list/exodus_paperwork_end_areas = list()
/datum/job/hos
)
-/datum/goal/department/paperwork/exodus/get_spawn_turfs()
+/datum/goal/department/paperwork/exodus/get_paper_spawn_turfs()
return global.exodus_paperwork_spawn_turfs
-/datum/goal/department/paperwork/exodus/get_end_areas()
+/datum/goal/department/paperwork/exodus/get_paper_end_areas()
return global.exodus_paperwork_end_areas
/obj/item/paperwork/exodus
diff --git a/maps/ministation/ministation.dmm b/maps/ministation/ministation.dmm
index 94ccf42d6df..e95dcf69a13 100644
--- a/maps/ministation/ministation.dmm
+++ b/maps/ministation/ministation.dmm
@@ -4319,9 +4319,7 @@
/area/ministation/science)
"kX" = (
/obj/machinery/atmospherics/pipe/simple/hidden/supply,
-/obj/abstract/landmark{
- name = "Observer-Start"
- },
+/obj/abstract/landmark/latejoin/observer,
/turf/simulated/floor/tiled,
/area/ministation/hall/w)
"kY" = (
diff --git a/maps/ministation/ministation_define.dm b/maps/ministation/ministation_define.dm
index 85ea9221e5b..343d30304a3 100644
--- a/maps/ministation/ministation_define.dm
+++ b/maps/ministation/ministation_define.dm
@@ -39,7 +39,7 @@
radiation_detected_message = "High levels of radiation have been detected in proximity of the %STATION_NAME%. Station wide maintenance access has been granted. Please take shelter within the nearest maintenance tunnel."
- allowed_spawns = list(
+ allowed_latejoin_spawns = list(
/decl/spawnpoint/arrivals,
/decl/spawnpoint/cryo
)
diff --git a/maps/modpack_testing/blank.dmm b/maps/modpack_testing/blank.dmm
index 6d50a5c258f..53af9def94a 100644
--- a/maps/modpack_testing/blank.dmm
+++ b/maps/modpack_testing/blank.dmm
@@ -16,6 +16,10 @@
/obj/abstract/level_data_spawner/player,
/turf/space,
/area/space)
+"X" = (
+/obj/abstract/landmark/latejoin/observer,
+/turf/space,
+/area/space)
(1,1,1) = {"
a
@@ -60,7 +64,7 @@ a
a
a
a
-a
+X
a
a
a
diff --git a/maps/modpack_testing/modpack_testing_define.dm b/maps/modpack_testing/modpack_testing_define.dm
index edaf134b62d..f5b09decd1f 100644
--- a/maps/modpack_testing/modpack_testing_define.dm
+++ b/maps/modpack_testing/modpack_testing_define.dm
@@ -2,5 +2,5 @@
name = "Modpack Testing"
full_name = "Modpack Testing District"
path = "modpack_testing"
- allowed_spawns = list()
+ allowed_latejoin_spawns = list()
default_spawn = null
diff --git a/maps/planets_testing/planets_testing_define.dm b/maps/planets_testing/planets_testing_define.dm
index ed547504cb4..c82359943fd 100644
--- a/maps/planets_testing/planets_testing_define.dm
+++ b/maps/planets_testing/planets_testing_define.dm
@@ -3,14 +3,19 @@
full_name = "Planets Testing"
path = "planets_testing"
overmap_ids = list(OVERMAP_ID_SPACE)
- allowed_spawns = list()
+ allowed_latejoin_spawns = list()
default_spawn = null
+// Set the observer spawn to include every flag so that CI flag checks pass.
+/decl/spawnpoint/observer
+ spawn_flags = (SPAWN_FLAG_GHOSTS_CAN_SPAWN | SPAWN_FLAG_JOBS_CAN_SPAWN | SPAWN_FLAG_PRISONERS_CAN_SPAWN | SPAWN_FLAG_PERSISTENCE_CAN_SPAWN)
+
/datum/map/planet_testing/build_planets()
report_progress("Instantiating planets...")
//Spawn all templates once
spawn_planet_templates(list_values(get_all_planet_templates()))
-
+ // Place an observer landmark just to appease CI and on the off-chance that anyone ever wants to observer.
+ new /obj/abstract/landmark/latejoin/observer(locate(round(world.maxx/2), round(world.maxy/2), world.maxz))
report_progress("Finished instantiating planets.")
diff --git a/maps/tradeship/jobs/_goals.dm b/maps/tradeship/jobs/_goals.dm
index bc7e9be65bc..c0c13ad937d 100644
--- a/maps/tradeship/jobs/_goals.dm
+++ b/maps/tradeship/jobs/_goals.dm
@@ -27,10 +27,10 @@ var/global/list/tradeship_paperwork_end_areas = list()
paperwork_types = list(/obj/item/paperwork/tradeship)
signatory_job_list = list(/datum/job/tradeship_captain, /datum/job/tradeship_first_mate)
-/datum/goal/department/paperwork/tradeship/get_spawn_turfs()
+/datum/goal/department/paperwork/tradeship/get_paper_spawn_turfs()
return global.tradeship_paperwork_spawn_turfs
-/datum/goal/department/paperwork/tradeship/get_end_areas()
+/datum/goal/department/paperwork/tradeship/get_paper_end_areas()
return global.tradeship_paperwork_end_areas
/obj/item/paperwork/tradeship
diff --git a/maps/tradeship/tradeship-2.dmm b/maps/tradeship/tradeship-2.dmm
index 9d5f5a73c7e..364752ec9ac 100644
--- a/maps/tradeship/tradeship-2.dmm
+++ b/maps/tradeship/tradeship-2.dmm
@@ -5468,9 +5468,7 @@
/obj/structure/table,
/obj/random/smokes,
/obj/item/ashtray/glass,
-/obj/abstract/landmark{
- name = "Observer-Start"
- },
+/obj/abstract/landmark/latejoin/observer,
/obj/structure/cable{
icon_state = "1-2"
},
diff --git a/maps/tradeship/tradeship_spawnpoints.dm b/maps/tradeship/tradeship_spawnpoints.dm
index 9a93e28df93..054216b46fe 100644
--- a/maps/tradeship/tradeship_spawnpoints.dm
+++ b/maps/tradeship/tradeship_spawnpoints.dm
@@ -1,38 +1,29 @@
-var/global/list/latejoin_cryo_two = list()
-var/global/list/latejoin_cryo_captain = list()
-/obj/abstract/landmark/latejoin/cryo_two/add_loc()
- global.latejoin_cryo_two |= get_turf(src)
-
-/obj/abstract/landmark/latejoin/cryo_captain/add_loc()
- global.latejoin_cryo_captain |= get_turf(src)
-
/datum/map/tradeship
- allowed_spawns = list(
+ allowed_latejoin_spawns = list(
/decl/spawnpoint/cryo,
/decl/spawnpoint/cryo/two,
/decl/spawnpoint/cyborg,
- /decl/spawnpoint/cryo/captain
)
default_spawn = /decl/spawnpoint/cryo
/decl/spawnpoint/cryo
name = "Port Cryogenic Storage"
- msg = "has completed revival in the port cryogenics bay"
+ spawn_announcement = "has completed revival in the port cryogenics bay"
disallow_job = list(/datum/job/tradeship_robot)
/decl/spawnpoint/cryo/two
name = "Starboard Cryogenic Storage"
- msg = "has completed revival in the starboard cryogenics bay"
+ spawn_announcement = "has completed revival in the starboard cryogenics bay"
+ uid = "spawn_cryo_two"
-/decl/spawnpoint/cryo/two/Initialize()
- . = ..()
- turfs = global.latejoin_cryo_two
+/obj/abstract/landmark/latejoin/cryo_two
+ spawn_decl = /decl/spawnpoint/cryo/two
/decl/spawnpoint/cryo/captain
name = "Captain Compartment"
- msg = "has completed revival in the captain compartment"
+ spawn_announcement = "has completed revival in the captain compartment"
restrict_job = list(/datum/job/tradeship_captain)
+ uid = "spawn_cryo_captain"
-/decl/spawnpoint/cryo/captain/Initialize()
- . = ..()
- turfs = global.latejoin_cryo_captain
+/obj/abstract/landmark/latejoin/cryo_captain
+ spawn_decl = /decl/spawnpoint/cryo/captain
diff --git a/maps/~mapsystem/maps.dm b/maps/~mapsystem/maps.dm
index 1133f421c16..acb8e573409 100644
--- a/maps/~mapsystem/maps.dm
+++ b/maps/~mapsystem/maps.dm
@@ -69,11 +69,8 @@ var/global/const/MAP_HAS_RANK = 2 //Rank system, also togglable
// as defined in holodeck_programs
var/list/holodeck_restricted_programs = list() // as above... but EVIL!
- var/allowed_spawns = list(
- /decl/spawnpoint/arrivals,
- /decl/spawnpoint/gateway,
- /decl/spawnpoint/cryo,
- /decl/spawnpoint/cyborg
+ var/allowed_latejoin_spawns = list(
+ /decl/spawnpoint/arrivals
)
var/default_spawn = /decl/spawnpoint/arrivals
@@ -185,12 +182,12 @@ var/global/const/MAP_HAS_RANK = 2 //Rank system, also togglable
var/datum/job/J = default_job_type
default_job_title = initial(J.title)
- if(default_spawn && !(default_spawn in allowed_spawns))
+ if(default_spawn && !(default_spawn in allowed_latejoin_spawns))
PRINT_STACK_TRACE("Map datum [type] has default spawn point [default_spawn] not in the allowed spawn list.")
- for(var/spawn_type in allowed_spawns)
- allowed_spawns -= spawn_type
- allowed_spawns += GET_DECL(spawn_type)
+ for(var/spawn_type in allowed_latejoin_spawns)
+ allowed_latejoin_spawns -= spawn_type
+ allowed_latejoin_spawns += GET_DECL(spawn_type)
if(!SSmapping.map_levels)
SSmapping.map_levels = SSmapping.station_levels.Copy()
diff --git a/mods/mobs/borers/datum/symbiote.dm b/mods/mobs/borers/datum/symbiote.dm
index 2b31eedb572..94b885087d1 100644
--- a/mods/mobs/borers/datum/symbiote.dm
+++ b/mods/mobs/borers/datum/symbiote.dm
@@ -87,7 +87,7 @@ var/global/list/symbiote_starting_points = list()
if(length(global.symbiote_starting_points))
symbiote.forceMove(pick(global.symbiote_starting_points))
else
- symbiote.forceMove(pick(global.latejoin_locations))
+ symbiote.forceMove(get_random_spawn_turf(SPAWN_FLAG_JOBS_CAN_SPAWN))
if(H.mind)
H.mind.transfer_to(symbiote)
diff --git a/nebula.dme b/nebula.dme
index 7505c78c895..827cde722d0 100644
--- a/nebula.dme
+++ b/nebula.dme
@@ -83,6 +83,7 @@
#include "code\__defines\skills.dm"
#include "code\__defines\sound.dm"
#include "code\__defines\spaceman_dmm.dm"
+#include "code\__defines\spawn.dm"
#include "code\__defines\species.dm"
#include "code\__defines\status.dm"
#include "code\__defines\stress.dm"