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"