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) /datum/controller/subsystem/job/Initialize(timeofday) SSmapping.HACK_LoadMapConfig() 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, "Error setting up jobs, no job datums found") return 0 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(SSmapping.config)) //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 job.process_map_overrides(SSmapping.config) occupations += job name_occupations[job.title] = job type_occupations[J] = job return 1 /datum/controller/subsystem/job/proc/GetJob(rank) RETURN_TYPE(/datum/job) 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 && player.mind && rank) var/datum/job/job = GetJob(rank) if(!job) return FALSE if(jobban_isbanned(player, 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/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(jobban_isbanned(player, 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(!player.client.prefs.pref_species.qualifies_for_rank(job.title, player.client.prefs.features)) JobDebug("FOC non-human failed, 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(jobban_isbanned(player, 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(!player.client.prefs.pref_species.qualifies_for_rank(job.title, player.client.prefs.features)) JobDebug("GRJ non-human failed, 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/mob/dead/new_player/player in GLOB.player_list) if((player) && (player.mind)) player.mind.assigned_role = null player.mind.special_role = null SSpersistence.antag_rep_change[player.ckey] = 0 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 1 return 0 //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) /datum/controller/subsystem/job/proc/FillAIPosition() var/ai_selected = 0 var/datum/job/job = GetJob("AI") if(!job) return 0 for(var/i = job.total_positions, i > 0, i--) for(var/level in level_order) var/list/candidates = list() candidates = FindOccupationCandidates(job, level) if(candidates.len) var/mob/dead/new_player/candidate = pick(candidates) if(AssignRole(candidate, "AI")) ai_selected++ break if(ai_selected) return 1 return 0 /** 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(list/required_jobs) //Setup new player list and get the jobs list JobDebug("Running DO") //Holder for Triumvirate is stored in the SSticker, this just processes it if(SSticker.triai) for(var/datum/job/ai/A in occupations) A.spawn_positions = 3 for(var/obj/effect/landmark/start/ai/secondary/S in GLOB.start_landmarks_list) S.latejoin_active = TRUE //Get the players who are ready for(var/mob/dead/new_player/player in GLOB.player_list) 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]") if(unassigned.len == 0) return validate_required_jobs(required_jobs) //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() //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") //Check for an AI JobDebug("DO, Running AI Check") FillAIPosition() 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(jobban_isbanned(player, 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.client.prefs.pref_species.qualifies_for_rank(job.title, player.client.prefs.features)) JobDebug("DO non-human failed, 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 validate_required_jobs(required_jobs) /datum/controller/subsystem/job/proc/validate_required_jobs(list/required_jobs) if(!required_jobs.len) return TRUE for(var/required_group in required_jobs) var/group_ok = TRUE for(var/rank in required_group) var/datum/job/J = GetJob(rank) if(!J) SSticker.mode.setup_error = "Invalid job [rank] in gamemode required jobs." return FALSE if(J.current_positions < required_group[rank]) group_ok = FALSE break if(group_ok) return TRUE SSticker.mode.setup_error = "Required jobs not present." return FALSE //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 = !jobban_isbanned(player, 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) var/mob/dead/new_player/N var/mob/living/H if(!joined_late) N = M H = N.new_character else H = M var/datum/job/job = GetJob(rank) H.job = rank //If we joined at roundstart we should be positioned at our workstation if(!joined_late) var/obj/S = null for(var/obj/effect/landmark/start/sloc in GLOB.start_landmarks_list) if(!sloc.job_spawnpoint) continue if(sloc.name != rank) S = sloc //so we can revert to spawning them on top of eachother if something goes wrong continue if(locate(/mob/living) in sloc.loc) continue S = sloc sloc.used = TRUE break if(length(GLOB.jobspawn_overrides[rank])) S = pick(GLOB.jobspawn_overrides[rank]) if(S) S.JoinPlayerHere(H, FALSE) if(!S) //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]") SendToLateJoin(H) if(H.mind) H.mind.assigned_role = rank if(job) if(!job.dresscodecompliant)// CIT CHANGE - dress code compliance equip_loadout(N, H) // CIT CHANGE - allows players to spawn with loadout items var/new_mob = job.equip(H, null, null, joined_late , null, M.client) if(ismob(new_mob)) H = new_mob if(!joined_late) N.new_character = H else M = H SSpersistence.antag_rep_change[M.client.ckey] += job.GetAntagRep() /* 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, "You are the [rank].") if(job) 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 immediately, please notify the admins via adminhelp. Otherwise put your locker gear back into the locker and cryo out.") if(job.custom_spawn_text) to_chat(M, "[job.custom_spawn_text]") if(CONFIG_GET(number/minimal_access_threshold)) to_chat(M, "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.") if(ishuman(H)) var/mob/living/carbon/human/wageslave = H to_chat(M, "Your account ID is [wageslave.account_id].") H.add_memory("Your account ID is [wageslave.account_id].") if(job && H) if(job.dresscodecompliant)// CIT CHANGE - dress code compliance equip_loadout(N, H) // CIT CHANGE - allows players to spawn with loadout items job.after_spawn(H, M, joined_late) // note: this happens before the mob has a key! M will always have a client, H might not. equip_loadout(N, H, TRUE)//CIT CHANGE - makes players spawn with in-backpack loadout items properly. A little hacky but it works var/list/tcg_cards if(ishuman(H)) if(length(H.client?.prefs?.tcg_cards)) tcg_cards = H.client.prefs.tcg_cards else if(length(N?.client?.prefs?.tcg_cards)) tcg_cards = N.client.prefs.tcg_cards if(tcg_cards) var/obj/item/tcgcard_binder/binder = new(get_turf(H)) H.equip_to_slot_if_possible(binder, ITEM_SLOT_BACKPACK, disable_warning = TRUE, bypass_equip_delay_self = TRUE) for(var/card_type in N.client.prefs.tcg_cards) if(card_type) if(islist(H.client.prefs.tcg_cards[card_type])) for(var/duplicate in N.client.prefs.tcg_cards[card_type]) var/obj/item/tcg_card/card = new(get_turf(H), card_type, duplicate) card.forceMove(binder) binder.cards.Add(card) else var/obj/item/tcg_card/card = new(get_turf(H), card_type, N.client.prefs.tcg_cards[card_type]) card.forceMove(binder) binder.cards.Add(card) binder.check_for_exodia() if(length(N.client.prefs.tcg_decks)) binder.decks = N.client.prefs.tcg_decks return H /* /datum/controller/subsystem/job/proc/handle_auto_deadmin_roles(client/C, rank) if(!C?.holder) return TRUE var/datum/job/job = GetJob(rank) if(!job) return if((job.auto_deadmin_role_flags & DEADMIN_POSITION_HEAD) && (CONFIG_GET(flag/auto_deadmin_heads) || (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) || (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) || (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/mob/dead/new_player/player in GLOB.player_list) if(!(player.ready == PLAYER_READY_TO_PLAY && player.mind && !player.mind.assigned_role)) continue //This player is not ready if(jobban_isbanned(player, 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 if(latejoin_trackers.len) destination = pick(latejoin_trackers) destination.JoinPlayerHere(M, buckle) return //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 //last hurrah var/list/avail = list() for(var/turf/T in A) if(!is_blocked_turf(T, TRUE)) avail += T if(avail.len) destination = pick(avail) destination.JoinPlayerHere(M, FALSE) return //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(!is_blocked_turf(T, TRUE)) T.JoinPlayerHere(M, FALSE) return //last chance, pick ANY spot on arrivals and dump em destination = arrivals_turfs[1] destination.JoinPlayerHere(M, FALSE) else var/msg = "Unable to send mob [M] to late join!" message_admins(msg) CRASH(msg) /datum/controller/subsystem/job/proc/equip_loadout(mob/dead/new_player/N, mob/living/M, equipbackpackstuff, bypass_prereqs = FALSE, can_drop = TRUE) var/mob/the_mob = N if(!the_mob) the_mob = M // cause this doesn't get assigned if player is a latejoiner var/list/chosen_gear = the_mob.client.prefs.loadout_data["SAVE_[the_mob.client.prefs.loadout_slot]"] if(the_mob.client && the_mob.client.prefs && (chosen_gear && chosen_gear.len)) if(!ishuman(M))//no silicons allowed return for(var/i in chosen_gear) var/datum/gear/G = istext(i[LOADOUT_ITEM]) ? text2path(i[LOADOUT_ITEM]) : i[LOADOUT_ITEM] G = GLOB.loadout_items[initial(G.category)][initial(G.subcategory)][initial(G.name)] if(!G) continue var/permitted = TRUE if(!bypass_prereqs && G.restricted_roles && G.restricted_roles.len && !(M.mind.assigned_role in G.restricted_roles)) permitted = FALSE if(G.donoritem && !G.donator_ckey_check(the_mob.client.ckey)) permitted = FALSE if(!equipbackpackstuff && G.slot == ITEM_SLOT_BACKPACK)//snowflake check since plopping stuff in the backpack doesnt work for pre-job equip loadout stuffs permitted = FALSE if(equipbackpackstuff && G.slot != ITEM_SLOT_BACKPACK)//ditto permitted = FALSE if(!permitted) continue var/obj/item/I = new G.path if(I) if(length(i[LOADOUT_COLOR])) //handle loadout colors //handle polychromic items if((G.loadout_flags & LOADOUT_CAN_COLOR_POLYCHROMIC) && length(G.loadout_initial_colors)) var/datum/element/polychromic/polychromic = LAZYACCESS(I.comp_lookup, "item_worn_overlays") //stupid way to do it but GetElement does not work for this if(polychromic && istype(polychromic)) var/list/polychromic_entry = polychromic.colors_by_atom[I] if(polychromic_entry) if(polychromic.suits_with_helmet_typecache[I.type]) //is this one of those toggleable hood/helmet things? polychromic.connect_helmet(I,i[LOADOUT_COLOR]) polychromic.colors_by_atom[I] = i[LOADOUT_COLOR] I.update_icon() else //handle non-polychromic items (they only have one color) I.add_atom_colour(i[LOADOUT_COLOR][1], FIXED_COLOUR_PRIORITY) I.update_icon() //when inputting the data it's already sanitized if(i[LOADOUT_CUSTOM_NAME]) var/custom_name = i[LOADOUT_CUSTOM_NAME] I.name = custom_name if(i[LOADOUT_CUSTOM_DESCRIPTION]) var/custom_description = i[LOADOUT_CUSTOM_DESCRIPTION] I.desc = custom_description if(!M.equip_to_slot_if_possible(I, G.slot, disable_warning = TRUE, bypass_equip_delay_self = TRUE)) // If the job's dresscode compliant, try to put it in its slot, first if(iscarbon(M)) var/mob/living/carbon/C = M var/obj/item/storage/backpack/B = C.back if(!B || !SEND_SIGNAL(B, COMSIG_TRY_STORAGE_INSERT, I, null, TRUE, TRUE)) // Otherwise, try to put it in the backpack, for carbons. if(can_drop) I.forceMove(get_turf(C)) else qdel(I) else if(!M.equip_to_slot_if_possible(I, ITEM_SLOT_BACKPACK, disable_warning = TRUE, bypass_equip_delay_self = TRUE)) // Otherwise, try to put it in the backpack if(can_drop) I.forceMove(get_turf(M)) // If everything fails, just put it on the floor under the mob. else qdel(I) /datum/controller/subsystem/job/proc/FreeRole(rank) if(!rank) return var/datum/job/job = GetJob(rank) if(!job) return FALSE job.current_positions = max(0, job.current_positions - 1) /////////////////////////////////// //Keeps track of all living heads// /////////////////////////////////// /datum/controller/subsystem/job/proc/get_living_heads() . = list() for(var/mob/living/carbon/human/player in GLOB.alive_mob_list) 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/mob/living/carbon/human/player in GLOB.carbon_list) 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/mob/living/carbon/human/player in GLOB.carbon_list) if(player.mind && (player.mind.assigned_role in GLOB.security_positions)) . |= player.mind /datum/controller/subsystem/job/proc/JobDebug(message) log_job_debug(message)