SUBSYSTEM_DEF(job) name = "Jobs" init_order = INIT_ORDER_JOBS flags = SS_NO_FIRE var/list/occupations = list() //List of all jobs var/list/datum/job/name_occupations = list() //Dict of all jobs, keys are titles var/list/type_occupations = list() //Dict of all jobs, keys are types var/list/unassigned = list() //Players who need jobs var/initial_players_to_assign = 0 //used for checking against population caps var/list/prioritized_jobs = list() var/list/latejoin_trackers = list() //Don't read this list, use GetLateJoinTurfs() instead var/overflow_role = "Assistant" var/list/level_order = list(JP_HIGH,JP_MEDIUM,JP_LOW) /// Lazylist of mob:occupation_string pairs. var/list/dynamic_forced_occupations /// A list of all jobs associated with the station. These jobs also have various icons associated with them including sechud and card trims. var/list/station_jobs /// A list of all Head of Staff jobs. var/list/head_of_staff_jobs /// A list of additional jobs that have various icons associated with them including sechud and card trims. var/list/additional_jobs_with_icons /// A list of jobs associed with Centcom and should use the standard NT Centcom icons. var/list/centcom_jobs /** * Keys should be assigned job roles. Values should be >= 1. * Represents the chain of command on the station. Lower numbers mean higher priority. * Used to give the Cap's Spare safe code to a an appropriate player. * Assumed Captain is always the highest in the chain of command. * See [/datum/controller/subsystem/ticker/proc/equip_characters] */ var/list/chain_of_command = list( "Captain" = 1, "Head of Personnel" = 2, "Research Director" = 3, "Chief Engineer" = 4, "Chief Medical Officer" = 5, "Head of Security" = 6, "Quartermaster" = 7) /// If TRUE, some player has been assigned Captaincy or Acting Captaincy at some point during the shift and has been given the spare ID safe code. var/assigned_captain = FALSE /// Whether the emergency safe code has been requested via a comms console on shifts with no Captain or Acting Captain. var/safe_code_requested = FALSE /// Timer ID for the emergency safe code request. var/safe_code_timer_id /// The loc to which the emergency safe code has been requested for delivery. var/turf/safe_code_request_loc /// If TRUE, the "Captain" job will always be given the code to the spare ID safe and always have a "Captain on deck!" announcement. var/always_promote_captain_job = TRUE /datum/controller/subsystem/job/Initialize(timeofday) setup_job_lists() if(!occupations.len) SetupOccupations() if(CONFIG_GET(flag/load_jobs_from_txt)) LoadJobs() generate_selectable_species() set_overflow_role(CONFIG_GET(string/overflow_job)) return ..() /datum/controller/subsystem/job/proc/set_overflow_role(new_overflow_role) var/datum/job/new_overflow = GetJob(new_overflow_role) var/cap = CONFIG_GET(number/overflow_cap) new_overflow.allow_bureaucratic_error = FALSE new_overflow.spawn_positions = cap new_overflow.total_positions = cap if(new_overflow_role != overflow_role) var/datum/job/old_overflow = GetJob(overflow_role) old_overflow.allow_bureaucratic_error = initial(old_overflow.allow_bureaucratic_error) old_overflow.spawn_positions = initial(old_overflow.spawn_positions) old_overflow.total_positions = initial(old_overflow.total_positions) overflow_role = new_overflow_role JobDebug("Overflow role set to : [new_overflow_role]") /datum/controller/subsystem/job/proc/SetupOccupations(faction = "Station") occupations = list() var/list/all_jobs = subtypesof(/datum/job) if(!all_jobs.len) to_chat(world, span_boldannounce("Error setting up jobs, no job datums found")) return FALSE for(var/J in all_jobs) var/datum/job/job = new J() if(!job) continue if(job.faction != faction) continue if(!job.config_check()) continue if(!job.map_check()) //Even though we initialize before mapping, this is fine because the config is loaded at new testing("Removed [job.type] due to map config") continue occupations += job name_occupations[job.title] = job type_occupations[J] = job return TRUE /datum/controller/subsystem/job/proc/GetJob(rank) if(!occupations.len) SetupOccupations() return name_occupations[rank] /datum/controller/subsystem/job/proc/GetJobType(jobtype) if(!occupations.len) SetupOccupations() return type_occupations[jobtype] /datum/controller/subsystem/job/proc/AssignRole(mob/dead/new_player/player, rank, latejoin = FALSE) JobDebug("Running AR, Player: [player], Rank: [rank], LJ: [latejoin]") if(player?.mind && rank) var/datum/job/job = GetJob(rank) if(!job) return FALSE if(is_banned_from(player.ckey, rank) || QDELETED(player)) return FALSE if(!job.player_old_enough(player.client)) return FALSE if(job.required_playtime_remaining(player.client)) return FALSE var/position_limit = job.total_positions if(!latejoin) position_limit = job.spawn_positions JobDebug("Player: [player] is now Rank: [rank], JCP:[job.current_positions], JPL:[position_limit]") player.mind.assigned_role = rank unassigned -= player job.current_positions++ return TRUE JobDebug("AR has failed, Player: [player], Rank: [rank]") return FALSE /datum/controller/subsystem/job/proc/FreeRole(rank) if(!rank) return JobDebug("Freeing role: [rank]") var/datum/job/job = GetJob(rank) if(!job) return FALSE job.current_positions = max(0, job.current_positions - 1) /datum/controller/subsystem/job/proc/FindOccupationCandidates(datum/job/job, level, flag) JobDebug("Running FOC, Job: [job], Level: [level], Flag: [flag]") var/list/candidates = list() for(var/mob/dead/new_player/player in unassigned) if(is_banned_from(player.ckey, job.title) || QDELETED(player)) JobDebug("FOC isbanned failed, Player: [player]") continue if(!job.player_old_enough(player.client)) JobDebug("FOC player not old enough, Player: [player]") continue if(job.required_playtime_remaining(player.client)) JobDebug("FOC player not enough xp, Player: [player]") continue if(flag && (!(flag in player.client.prefs.be_special))) JobDebug("FOC flag failed, Player: [player], Flag: [flag], ") continue if(player.mind && (job.title in player.mind.restricted_roles)) JobDebug("FOC incompatible with antagonist role, Player: [player]") continue if(player.client.prefs.job_preferences[job.title] == level) JobDebug("FOC pass, Player: [player], Level:[level]") candidates += player return candidates /datum/controller/subsystem/job/proc/GiveRandomJob(mob/dead/new_player/player) JobDebug("GRJ Giving random job, Player: [player]") . = FALSE for(var/datum/job/job in shuffle(occupations)) if(!job) continue if(istype(job, GetJob(SSjob.overflow_role))) // We don't want to give him assistant, that's boring! continue if(job.title in GLOB.command_positions) //If you want a command position, select it! continue if(is_banned_from(player.ckey, job.title) || QDELETED(player)) if(QDELETED(player)) JobDebug("GRJ isbanned failed, Player deleted") break JobDebug("GRJ isbanned failed, Player: [player], Job: [job.title]") continue if(!job.player_old_enough(player.client)) JobDebug("GRJ player not old enough, Player: [player]") continue if(job.required_playtime_remaining(player.client)) JobDebug("GRJ player not enough xp, Player: [player]") continue if(player.mind && (job.title in player.mind.restricted_roles)) JobDebug("GRJ incompatible with antagonist role, Player: [player], Job: [job.title]") continue if((job.current_positions < job.spawn_positions) || job.spawn_positions == -1) JobDebug("GRJ Random job given, Player: [player], Job: [job]") if(AssignRole(player, job.title)) return TRUE /datum/controller/subsystem/job/proc/ResetOccupations() JobDebug("Occupations reset.") for(var/i in GLOB.new_player_list) var/mob/dead/new_player/player = i if((player) && (player.mind)) player.mind.assigned_role = null player.mind.special_role = null SetupOccupations() unassigned = list() return //This proc is called before the level loop of DivideOccupations() and will try to select a head, ignoring ALL non-head preferences for every level until //it locates a head or runs out of levels to check //This is basically to ensure that there's atleast a few heads in the round /datum/controller/subsystem/job/proc/FillHeadPosition() for(var/level in level_order) for(var/command_position in GLOB.command_positions) var/datum/job/job = GetJob(command_position) if(!job) continue if((job.current_positions >= job.total_positions) && job.total_positions != -1) continue var/list/candidates = FindOccupationCandidates(job, level) if(!candidates.len) continue var/mob/dead/new_player/candidate = pick(candidates) if(AssignRole(candidate, command_position)) return TRUE return FALSE //This proc is called at the start of the level loop of DivideOccupations() and will cause head jobs to be checked before any other jobs of the same level //This is also to ensure we get as many heads as possible /datum/controller/subsystem/job/proc/CheckHeadPositions(level) for(var/command_position in GLOB.command_positions) var/datum/job/job = GetJob(command_position) if(!job) continue if((job.current_positions >= job.total_positions) && job.total_positions != -1) continue var/list/candidates = FindOccupationCandidates(job, level) if(!candidates.len) continue var/mob/dead/new_player/candidate = pick(candidates) AssignRole(candidate, command_position) /// Attempts to fill out all available AI positions. /datum/controller/subsystem/job/proc/fill_ai_positions() var/datum/job/ai_job = GetJob("AI") if(!ai_job) return // In byond for(in to) loops, the iteration is inclusive so we need to stop at ai_job.total_positions - 1 for(var/i in ai_job.current_positions to ai_job.total_positions - 1) for(var/level in level_order) var/list/candidates = list() candidates = FindOccupationCandidates(ai_job, level) if(candidates.len) var/mob/dead/new_player/candidate = pick(candidates) if(AssignRole(candidate, "AI")) break /** Proc DivideOccupations * fills var "assigned_role" for all ready players. * This proc must not have any side effect besides of modifying "assigned_role". **/ /datum/controller/subsystem/job/proc/DivideOccupations() //Setup new player list and get the jobs list JobDebug("Running DO") SEND_SIGNAL(src, COMSIG_OCCUPATIONS_DIVIDED) //Get the players who are ready for(var/i in GLOB.new_player_list) var/mob/dead/new_player/player = i if(player.ready == PLAYER_READY_TO_PLAY && player.check_preferences() && player.mind && !player.mind.assigned_role) unassigned += player initial_players_to_assign = unassigned.len JobDebug("DO, Len: [unassigned.len]") //Scale number of open security officer slots to population setup_officer_positions() //Jobs will have fewer access permissions if the number of players exceeds the threshold defined in game_options.txt var/mat = CONFIG_GET(number/minimal_access_threshold) if(mat) if(mat > unassigned.len) CONFIG_SET(flag/jobs_have_minimal_access, FALSE) else CONFIG_SET(flag/jobs_have_minimal_access, TRUE) //Shuffle players and jobs unassigned = shuffle(unassigned) HandleFeedbackGathering() // Dynamic has picked a ruleset that requires enforcing some jobs before others. JobDebug("DO, Assigning Priority Positions: [length(dynamic_forced_occupations)]") assign_priority_positions() //People who wants to be the overflow role, sure, go on. JobDebug("DO, Running Overflow Check 1") var/datum/job/overflow = GetJob(SSjob.overflow_role) var/list/overflow_candidates = FindOccupationCandidates(overflow, JP_LOW) JobDebug("AC1, Candidates: [overflow_candidates.len]") for(var/mob/dead/new_player/player in overflow_candidates) JobDebug("AC1 pass, Player: [player]") AssignRole(player, SSjob.overflow_role) overflow_candidates -= player JobDebug("DO, AC1 end") //Select one head JobDebug("DO, Running Head Check") FillHeadPosition() JobDebug("DO, Head Check end") // Fill out any remaining AI positions. JobDebug("DO, Running AI Check") fill_ai_positions() JobDebug("DO, AI Check end") //Other jobs are now checked JobDebug("DO, Running Standard Check") // New job giving system by Donkie // This will cause lots of more loops, but since it's only done once it shouldn't really matter much at all. // Hopefully this will add more randomness and fairness to job giving. // Loop through all levels from high to low var/list/shuffledoccupations = shuffle(occupations) for(var/level in level_order) //Check the head jobs first each level CheckHeadPositions(level) // Loop through all unassigned players for(var/mob/dead/new_player/player in unassigned) if(PopcapReached()) RejectPlayer(player) // Loop through all jobs for(var/datum/job/job in shuffledoccupations) // SHUFFLE ME BABY if(!job) continue if(is_banned_from(player.ckey, job.title)) JobDebug("DO isbanned failed, Player: [player], Job:[job.title]") continue if(QDELETED(player)) JobDebug("DO player deleted during job ban check") break if(!job.player_old_enough(player.client)) JobDebug("DO player not old enough, Player: [player], Job:[job.title]") continue if(job.required_playtime_remaining(player.client)) JobDebug("DO player not enough xp, Player: [player], Job:[job.title]") continue if(player.mind && (job.title in player.mind.restricted_roles)) JobDebug("DO incompatible with antagonist role, Player: [player], Job:[job.title]") continue // If the player wants that job on this level, then try give it to him. if(player.client.prefs.job_preferences[job.title] == level) // If the job isn't filled if((job.current_positions < job.spawn_positions) || job.spawn_positions == -1) JobDebug("DO pass, Player: [player], Level:[level], Job:[job.title]") AssignRole(player, job.title) unassigned -= player break JobDebug("DO, Handling unassigned.") // Hand out random jobs to the people who didn't get any in the last check // Also makes sure that they got their preference correct for(var/mob/dead/new_player/player in unassigned) HandleUnassigned(player) JobDebug("DO, Handling unrejectable unassigned") //Mop up people who can't leave. for(var/mob/dead/new_player/player in unassigned) //Players that wanted to back out but couldn't because they're antags (can you feel the edge case?) if(!GiveRandomJob(player)) if(!AssignRole(player, SSjob.overflow_role)) //If everything is already filled, make them an assistant return FALSE //Living on the edge, the forced antagonist couldn't be assigned to overflow role (bans, client age) - just reroll return TRUE //We couldn't find a job from prefs for this guy. /datum/controller/subsystem/job/proc/HandleUnassigned(mob/dead/new_player/player) if(PopcapReached()) RejectPlayer(player) else if(player.client.prefs.joblessrole == BEOVERFLOW) var/allowed_to_be_a_loser = !is_banned_from(player.ckey, SSjob.overflow_role) if(QDELETED(player) || !allowed_to_be_a_loser) RejectPlayer(player) else if(!AssignRole(player, SSjob.overflow_role)) RejectPlayer(player) else if(player.client.prefs.joblessrole == BERANDOMJOB) if(!GiveRandomJob(player)) RejectPlayer(player) else if(player.client.prefs.joblessrole == RETURNTOLOBBY) RejectPlayer(player) else //Something gone wrong if we got here. var/message = "DO: [player] fell through handling unassigned" JobDebug(message) log_game(message) message_admins(message) RejectPlayer(player) //Gives the player the stuff he should have with his rank /datum/controller/subsystem/job/proc/EquipRank(mob/M, rank, joined_late = FALSE, is_captain = FALSE) var/mob/dead/new_player/newplayer var/mob/living/living_mob if(!joined_late) newplayer = M living_mob = newplayer.new_character else living_mob = M var/datum/job/job = GetJob(rank) living_mob.job = rank SEND_SIGNAL(living_mob, COMSIG_JOB_RECEIVED, living_mob.job) //If we joined at roundstart we should be positioned at our workstation if(!joined_late) var/spawning_handled = FALSE var/obj/S = null if(HAS_TRAIT(SSstation, STATION_TRAIT_LATE_ARRIVALS) && job.random_spawns_possible) SendToLateJoin(living_mob) spawning_handled = TRUE else if(HAS_TRAIT(SSstation, STATION_TRAIT_RANDOM_ARRIVALS) && job.random_spawns_possible) DropLandAtRandomHallwayPoint(living_mob) spawning_handled = TRUE else if(HAS_TRAIT(SSstation, STATION_TRAIT_HANGOVER) && job.random_spawns_possible) for(var/obj/effect/landmark/start/hangover/hangover_spawn in GLOB.start_landmarks_list) S = hangover_spawn if(locate(/mob/living) in hangover_spawn.loc) //so we can revert to spawning them on top of eachother if something goes wrong continue hangover_spawn.used = TRUE break else if(length(GLOB.jobspawn_overrides[rank])) S = pick(GLOB.jobspawn_overrides[rank]) else for(var/_sloc in GLOB.start_landmarks_list) var/obj/effect/landmark/start/sloc = _sloc if(sloc.name != rank) continue S = sloc if(locate(/mob/living) in sloc.loc) //so we can revert to spawning them on top of eachother if something goes wrong continue sloc.used = TRUE break if(S) S.JoinPlayerHere(living_mob, FALSE) if(!S && !spawning_handled) //if there isn't a spawnpoint send them to latejoin, if there's no latejoin go yell at your mapper log_world("Couldn't find a round start spawn point for [rank]") if(!SendToLateJoin(living_mob)) living_mob.move_to_error_room() if(living_mob.mind) living_mob.mind.assigned_role = rank to_chat(M, "You are the [rank].") if(job) var/new_mob = job.equip(living_mob, null, null, joined_late, null, M.client, is_captain)//silicons override this proc to return a mob if(ismob(new_mob)) living_mob = new_mob if(!joined_late) newplayer.new_character = living_mob else M = living_mob if(M.client.holder) if(CONFIG_GET(flag/auto_deadmin_players) || (M.client.prefs?.toggles & DEADMIN_ALWAYS)) M.client.holder.auto_deadmin() else handle_auto_deadmin_roles(M.client, rank) to_chat(M, "As the [rank] you answer directly to [job.supervisors]. Special circumstances may change this.") job.radio_help_message(M) if(job.req_admin_notify) to_chat(M, "You are playing a job that is important for Game Progression. If you have to disconnect, please notify the admins via adminhelp.") if(CONFIG_GET(number/minimal_access_threshold)) to_chat(M, span_notice("As this station was initially staffed with a [CONFIG_GET(flag/jobs_have_minimal_access) ? "full crew, only your job's necessities" : "skeleton crew, additional access may"] have been added to your ID card.")) var/related_policy = get_policy(rank) if(related_policy) to_chat(M,related_policy) if(ishuman(living_mob)) var/mob/living/carbon/human/wageslave = living_mob living_mob.add_memory("Your account ID is [wageslave.account_id].") if(job && living_mob) job.after_spawn(living_mob, M, joined_late) // note: this happens before the mob has a key! M will always have a client, living_mob might not. return living_mob /datum/controller/subsystem/job/proc/handle_auto_deadmin_roles(client/C, rank) if(!C?.holder) return TRUE var/datum/job/job = GetJob(rank) var/timegate_expired = FALSE // allow only forcing deadminning in the first X seconds of the round if auto_deadmin_timegate is set in config var/timegate = CONFIG_GET(number/auto_deadmin_timegate) if(timegate && (world.time - SSticker.round_start_time > timegate)) timegate_expired = TRUE if(!job) return if((job.auto_deadmin_role_flags & DEADMIN_POSITION_HEAD) && ((CONFIG_GET(flag/auto_deadmin_heads) && !timegate_expired) || (C.prefs?.toggles & DEADMIN_POSITION_HEAD))) return C.holder.auto_deadmin() else if((job.auto_deadmin_role_flags & DEADMIN_POSITION_SECURITY) && ((CONFIG_GET(flag/auto_deadmin_security) && !timegate_expired) || (C.prefs?.toggles & DEADMIN_POSITION_SECURITY))) return C.holder.auto_deadmin() else if((job.auto_deadmin_role_flags & DEADMIN_POSITION_SILICON) && ((CONFIG_GET(flag/auto_deadmin_silicons) && !timegate_expired) || (C.prefs?.toggles & DEADMIN_POSITION_SILICON))) //in the event there's ever psuedo-silicon roles added, ie synths. return C.holder.auto_deadmin() /datum/controller/subsystem/job/proc/setup_officer_positions() var/datum/job/J = SSjob.GetJob("Security Officer") if(!J) CRASH("setup_officer_positions(): Security officer job is missing") var/ssc = CONFIG_GET(number/security_scaling_coeff) if(ssc > 0) if(J.spawn_positions > 0) var/officer_positions = min(12, max(J.spawn_positions, round(unassigned.len / ssc))) //Scale between configured minimum and 12 officers JobDebug("Setting open security officer positions to [officer_positions]") J.total_positions = officer_positions J.spawn_positions = officer_positions //Spawn some extra eqipment lockers if we have more than 5 officers var/equip_needed = J.total_positions if(equip_needed < 0) // -1: infinite available slots equip_needed = 12 for(var/i=equip_needed-5, i>0, i--) if(GLOB.secequipment.len) var/spawnloc = GLOB.secequipment[1] new /obj/structure/closet/secure_closet/security/sec(spawnloc) GLOB.secequipment -= spawnloc else //We ran out of spare locker spawns! break /datum/controller/subsystem/job/proc/LoadJobs() var/jobstext = file2text("[global.config.directory]/jobs.txt") for(var/datum/job/J in occupations) var/regex/jobs = new("[J.title]=(-1|\\d+),(-1|\\d+)") jobs.Find(jobstext) J.total_positions = text2num(jobs.group[1]) J.spawn_positions = text2num(jobs.group[2]) /datum/controller/subsystem/job/proc/HandleFeedbackGathering() for(var/datum/job/job in occupations) var/high = 0 //high var/medium = 0 //medium var/low = 0 //low var/never = 0 //never var/banned = 0 //banned var/young = 0 //account too young for(var/i in GLOB.new_player_list) var/mob/dead/new_player/player = i if(!(player.ready == PLAYER_READY_TO_PLAY && player.mind && !player.mind.assigned_role)) continue //This player is not ready if(is_banned_from(player.ckey, job.title) || QDELETED(player)) banned++ continue if(!job.player_old_enough(player.client)) young++ continue if(job.required_playtime_remaining(player.client)) young++ continue switch(player.client.prefs.job_preferences[job.title]) if(JP_HIGH) high++ if(JP_MEDIUM) medium++ if(JP_LOW) low++ else never++ SSblackbox.record_feedback("nested tally", "job_preferences", high, list("[job.title]", "high")) SSblackbox.record_feedback("nested tally", "job_preferences", medium, list("[job.title]", "medium")) SSblackbox.record_feedback("nested tally", "job_preferences", low, list("[job.title]", "low")) SSblackbox.record_feedback("nested tally", "job_preferences", never, list("[job.title]", "never")) SSblackbox.record_feedback("nested tally", "job_preferences", banned, list("[job.title]", "banned")) SSblackbox.record_feedback("nested tally", "job_preferences", young, list("[job.title]", "young")) /datum/controller/subsystem/job/proc/PopcapReached() var/hpc = CONFIG_GET(number/hard_popcap) var/epc = CONFIG_GET(number/extreme_popcap) if(hpc || epc) var/relevent_cap = max(hpc, epc) if((initial_players_to_assign - unassigned.len) >= relevent_cap) return 1 return 0 /datum/controller/subsystem/job/proc/RejectPlayer(mob/dead/new_player/player) if(player.mind && player.mind.special_role) return if(PopcapReached()) JobDebug("Popcap overflow Check observer located, Player: [player]") JobDebug("Player rejected :[player]") to_chat(player, "You have failed to qualify for any job you desired.") unassigned -= player player.ready = PLAYER_NOT_READY /datum/controller/subsystem/job/Recover() set waitfor = FALSE var/oldjobs = SSjob.occupations sleep(20) for (var/datum/job/J in oldjobs) INVOKE_ASYNC(src, .proc/RecoverJob, J) /datum/controller/subsystem/job/proc/RecoverJob(datum/job/J) var/datum/job/newjob = GetJob(J.title) if (!istype(newjob)) return newjob.total_positions = J.total_positions newjob.spawn_positions = J.spawn_positions newjob.current_positions = J.current_positions /atom/proc/JoinPlayerHere(mob/M, buckle) // By default, just place the mob on the same turf as the marker or whatever. M.forceMove(get_turf(src)) /obj/structure/chair/JoinPlayerHere(mob/M, buckle) // Placing a mob in a chair will attempt to buckle it, or else fall back to default. if (buckle && isliving(M) && buckle_mob(M, FALSE, FALSE)) return ..() /datum/controller/subsystem/job/proc/SendToLateJoin(mob/M, buckle = TRUE) var/atom/destination if(M.mind && M.mind.assigned_role && length(GLOB.jobspawn_overrides[M.mind.assigned_role])) //We're doing something special today. destination = pick(GLOB.jobspawn_overrides[M.mind.assigned_role]) destination.JoinPlayerHere(M, FALSE) return TRUE if(latejoin_trackers.len) destination = pick(latejoin_trackers) destination.JoinPlayerHere(M, buckle) return TRUE //bad mojo var/area/shuttle/arrival/A = GLOB.areas_by_type[/area/shuttle/arrival] if(A) //first check if we can find a chair var/obj/structure/chair/C = locate() in A if(C) C.JoinPlayerHere(M, buckle) return TRUE //last hurrah var/list/avail = list() for(var/turf/T in A) if(!T.is_blocked_turf(TRUE)) avail += T if(avail.len) destination = pick(avail) destination.JoinPlayerHere(M, FALSE) return TRUE //pick an open spot on arrivals and dump em var/list/arrivals_turfs = shuffle(get_area_turfs(/area/shuttle/arrival)) if(arrivals_turfs.len) for(var/turf/T in arrivals_turfs) if(!T.is_blocked_turf(TRUE)) T.JoinPlayerHere(M, FALSE) return TRUE //last chance, pick ANY spot on arrivals and dump em destination = arrivals_turfs[1] destination.JoinPlayerHere(M, FALSE) return TRUE else var/msg = "Unable to send mob [M] to late join!" message_admins(msg) CRASH(msg) ///Lands specified mob at a random spot in the hallways /datum/controller/subsystem/job/proc/DropLandAtRandomHallwayPoint(mob/living/living_mob) var/turf/spawn_turf = get_safe_random_station_turf(typesof(/area/hallway)) if(!spawn_turf) SendToLateJoin(living_mob) else var/obj/structure/closet/supplypod/centcompod/toLaunch = new() living_mob.forceMove(toLaunch) new /obj/effect/pod_landingzone(spawn_turf, toLaunch) /////////////////////////////////// //Keeps track of all living heads// /////////////////////////////////// /datum/controller/subsystem/job/proc/get_living_heads() . = list() for(var/i in GLOB.human_list) var/mob/living/carbon/human/player = i if(player.stat != DEAD && player.mind && (player.mind.assigned_role in GLOB.command_positions)) . |= player.mind //////////////////////////// //Keeps track of all heads// //////////////////////////// /datum/controller/subsystem/job/proc/get_all_heads() . = list() for(var/i in GLOB.mob_list) var/mob/player = i if(player.mind && (player.mind.assigned_role in GLOB.command_positions)) . |= player.mind ////////////////////////////////////////////// //Keeps track of all living security members// ////////////////////////////////////////////// /datum/controller/subsystem/job/proc/get_living_sec() . = list() for(var/i in GLOB.human_list) var/mob/living/carbon/human/player = i if(player.stat != DEAD && player.mind && (player.mind.assigned_role in GLOB.security_positions)) . |= player.mind //////////////////////////////////////// //Keeps track of all security members// //////////////////////////////////////// /datum/controller/subsystem/job/proc/get_all_sec() . = list() for(var/i in GLOB.human_list) var/mob/living/carbon/human/player = i if(player.mind && (player.mind.assigned_role in GLOB.security_positions)) . |= player.mind /datum/controller/subsystem/job/proc/JobDebug(message) log_job_debug(message) /// Builds various lists of jobs based on station, centcom and additional jobs with icons associated with them. /datum/controller/subsystem/job/proc/setup_job_lists() station_jobs = list("Assistant", "Captain", "Head of Personnel", "Bartender", "Cook", "Botanist", "Quartermaster", "Cargo Technician", \ "Shaft Miner", "Clown", "Mime", "Janitor", "Curator", "Lawyer", "Chaplain", "Chief Engineer", "Station Engineer", \ "Atmospheric Technician", "Chief Medical Officer", "Medical Doctor", "Paramedic", "Chemist", "Geneticist", "Virologist", "Psychologist", \ "Research Director", "Scientist", "Roboticist", "Head of Security", "Warden", "Detective", "Security Officer", "Prisoner") head_of_staff_jobs = list("Head of Personnel", "Chief Engineer", "Chief Medical Officer", "Research Director", "Head of Security", "Captain") additional_jobs_with_icons = list("Emergency Response Team Commander", "Security Response Officer", "Engineering Response Officer", "Medical Response Officer", \ "Entertainment Response Officer", "Religious Response Officer", "Janitorial Response Officer", "Death Commando", "Security Officer (Engineering)", \ "Security Officer (Cargo)", "Security Officer (Medical)", "Security Officer (Science)") centcom_jobs = list("Central Command","VIP Guest","Custodian","Thunderdome Overseer","CentCom Official","Medical Officer","Research Officer", \ "Special Ops Officer","Admiral","CentCom Commander","CentCom Bartender","Private Security Force") /obj/item/paper/fluff/spare_id_safe_code name = "Nanotrasen-Approved Spare ID Safe Code" desc = "Proof that you have been approved for Captaincy, with all its glory and all its horror." /obj/item/paper/fluff/spare_id_safe_code/Initialize() . = ..() var/safe_code = SSid_access.spare_id_safe_code info = "Captain's Spare ID safe code combination: [safe_code ? safe_code : "\[REDACTED\]"]

The spare ID can be found in its dedicated safe on the bridge.

If your job would not ordinarily have Head of Staff access, your ID card has been specially modified to possess it." update_appearance() /obj/item/paper/fluff/emergency_spare_id_safe_code name = "Emergency Spare ID Safe Code Requisition" desc = "Proof that nobody has been approved for Captaincy. A skeleton key for a skeleton shift." /obj/item/paper/fluff/emergency_spare_id_safe_code/Initialize() . = ..() var/safe_code = SSid_access.spare_id_safe_code info = "Captain's Spare ID safe code combination: [safe_code ? safe_code : "\[REDACTED\]"]

The spare ID can be found in its dedicated safe on the bridge." update_appearance() /datum/controller/subsystem/job/proc/promote_to_captain(mob/living/carbon/human/new_captain, acting_captain = FALSE) var/id_safe_code = SSid_access.spare_id_safe_code if(!id_safe_code) CRASH("Cannot promote [new_captain.real_name] to Captain, there is no id_safe_code.") var/paper = new /obj/item/paper/fluff/spare_id_safe_code() var/list/slots = list( LOCATION_LPOCKET = ITEM_SLOT_LPOCKET, LOCATION_RPOCKET = ITEM_SLOT_RPOCKET, LOCATION_BACKPACK = ITEM_SLOT_BACKPACK, LOCATION_HANDS = ITEM_SLOT_HANDS ) var/where = new_captain.equip_in_one_of_slots(paper, slots, FALSE) || "at your feet" if(acting_captain) to_chat(new_captain, span_notice("Due to your position in the chain of command, you have been promoted to Acting Captain. You can find in important note about this [where].")) else to_chat(new_captain, span_notice("You can find the code to obtain your spare ID from the secure safe on the Bridge [where].")) // Force-give their ID card bridge access. var/obj/item/id_slot = new_captain.get_item_by_slot(ITEM_SLOT_ID) if(id_slot) var/obj/item/card/id/id_card = id_slot.GetID() if(!(ACCESS_HEADS in id_card.access)) id_card.add_wildcards(list(ACCESS_HEADS), mode=FORCE_ADD_ALL) assigned_captain = TRUE /// Send a drop pod containing a piece of paper with the spare ID safe code to loc /datum/controller/subsystem/job/proc/send_spare_id_safe_code(loc) new /obj/effect/pod_landingzone(loc, /obj/structure/closet/supplypod/centcompod, new /obj/item/paper/fluff/emergency_spare_id_safe_code()) safe_code_timer_id = null safe_code_request_loc = null /// Blindly assigns the required roles to every player in the dynamic_forced_occupations list. /datum/controller/subsystem/job/proc/assign_priority_positions() for(var/mob/new_player in dynamic_forced_occupations) AssignRole(new_player, dynamic_forced_occupations[new_player])