Revert "Assorted changes to job assignment code and logging." (#85929)

Reverts tgstation/tgstation#85308

![image](https://github.com/user-attachments/assets/eb74d378-29da-44f0-bd14-89c95654cb4e)
This commit is contained in:
Kyle Spier-Swenson
2024-08-17 15:26:08 -07:00
committed by GitHub
parent 4ce1f048fe
commit 4d1639b04c
69 changed files with 295 additions and 323 deletions
@@ -526,7 +526,7 @@ SUBSYSTEM_DEF(dynamic)
//To new_player and such, and we want the datums to just free when the roundstart work is done
var/list/roundstart_rules = init_rulesets(/datum/dynamic_ruleset/roundstart)
SSjob.divide_occupations(pure = TRUE, allow_all = TRUE)
SSjob.DivideOccupations(pure = TRUE, allow_all = TRUE)
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.check_preferences())
@@ -541,7 +541,7 @@ SUBSYSTEM_DEF(dynamic)
else
roundstart_pop_ready++
candidates.Add(player)
SSjob.reset_occupations()
SSjob.ResetOccupations()
log_dynamic("Listing [roundstart_rules.len] round start rulesets, and [candidates.len] players ready.")
if (candidates.len <= 0)
log_dynamic("[candidates.len] candidates.")
@@ -1018,7 +1018,7 @@ SUBSYSTEM_DEF(dynamic)
var/list/reopened_jobs = list()
for(var/mob/living/quitter in GLOB.suicided_mob_list)
var/datum/job/job = SSjob.get_job(quitter.job)
var/datum/job/job = SSjob.GetJob(quitter.job)
if(!job || !(job.job_flags & JOB_REOPEN_ON_ROUNDSTART_LOSS))
continue
if(!include_command && job.departments_bitflags & DEPARTMENT_BITFLAG_COMMAND)
@@ -283,7 +283,7 @@
if(length(exclusive_roles))
var/exclusive_candidate = FALSE
for(var/role in exclusive_roles)
var/datum/job/job = SSjob.get_job(role)
var/datum/job/job = SSjob.GetJob(role)
if((role in candidate_client.prefs.job_preferences) && SSjob.check_job_eligibility(candidate_player, job, "Dynamic Roundstart TC", add_job_to_log = TRUE) == JOB_AVAILABLE)
exclusive_candidate = TRUE
@@ -413,7 +413,7 @@
return ..()
/datum/dynamic_ruleset/midround/from_ghosts/nuclear/finish_setup(mob/new_character, index)
new_character.mind.set_assigned_role(SSjob.get_job_type(/datum/job/nuclear_operative))
new_character.mind.set_assigned_role(SSjob.GetJobType(/datum/job/nuclear_operative))
new_character.mind.special_role = ROLE_NUCLEAR_OPERATIVE
if(index == 1)
var/datum/antagonist/nukeop/leader/leader_antag_datum = new()
@@ -572,7 +572,7 @@
var/mob/living/carbon/human/new_nightmare = new (find_maintenance_spawn(atmos_sensitive = TRUE, require_darkness = TRUE))
player_mind.transfer_to(new_nightmare)
player_mind.set_assigned_role(SSjob.get_job_type(/datum/job/nightmare))
player_mind.set_assigned_role(SSjob.GetJobType(/datum/job/nightmare))
player_mind.special_role = ROLE_NIGHTMARE
player_mind.add_antag_datum(/datum/antagonist/nightmare)
new_nightmare.set_species(/datum/species/shadow/nightmare)
@@ -991,7 +991,7 @@
var/mob/living/carbon/human/voidwalker = new (space_turf)
player_mind.transfer_to(voidwalker)
player_mind.set_assigned_role(SSjob.get_job_type(/datum/job/voidwalker))
player_mind.set_assigned_role(SSjob.GetJobType(/datum/job/voidwalker))
player_mind.special_role = antag_flag
player_mind.add_antag_datum(antag_datum)
@@ -63,7 +63,7 @@ GLOBAL_VAR_INIT(revolutionary_win, FALSE)
flags = HIGH_IMPACT_RULESET
/datum/dynamic_ruleset/roundstart/malf_ai/ready(forced)
var/datum/job/ai_job = SSjob.get_job_type(/datum/job/ai)
var/datum/job/ai_job = SSjob.GetJobType(/datum/job/ai)
// If we're not forced, we're going to make sure we can actually have an AI in this shift,
if(!forced && min(ai_job.total_positions - ai_job.current_positions, ai_job.spawn_positions) <= 0)
@@ -75,7 +75,7 @@ GLOBAL_VAR_INIT(revolutionary_win, FALSE)
/datum/dynamic_ruleset/roundstart/malf_ai/pre_execute(population)
. = ..()
var/datum/job/ai_job = SSjob.get_job_type(/datum/job/ai)
var/datum/job/ai_job = SSjob.GetJobType(/datum/job/ai)
// Maybe a bit too pedantic, but there should never be more malf AIs than there are available positions, spawn positions or antag cap allocations.
var/num_malf = min(get_antag_cap(population), min(ai_job.total_positions - ai_job.current_positions, ai_job.spawn_positions))
for (var/i in 1 to num_malf)
@@ -296,7 +296,7 @@ GLOBAL_VAR_INIT(revolutionary_win, FALSE)
var/mob/M = pick_n_take(candidates)
if (M)
assigned += M.mind
M.mind.set_assigned_role(SSjob.get_job_type(/datum/job/space_wizard))
M.mind.set_assigned_role(SSjob.GetJobType(/datum/job/space_wizard))
M.mind.special_role = ROLE_WIZARD
return TRUE
@@ -429,7 +429,7 @@ GLOBAL_VAR_INIT(revolutionary_win, FALSE)
break
var/mob/M = pick_n_take(candidates)
assigned += M.mind
M.mind.set_assigned_role(SSjob.get_job_type(job_type))
M.mind.set_assigned_role(SSjob.GetJobType(job_type))
M.mind.special_role = required_role
return TRUE
+194 -221
View File
@@ -22,7 +22,7 @@ SUBSYSTEM_DEF(job)
var/list/unassigned = list() //Players who need jobs
var/initial_players_to_assign = 0 //used for checking against population caps
// Whether to run divide_occupations pure so that there are no side-effects from calling it other than
// Whether to run DivideOccupations pure so that there are no side-effects from calling it other than
// a player's assigned_role being set to some value.
var/run_divide_occupation_pure = FALSE
@@ -31,7 +31,7 @@ SUBSYSTEM_DEF(job)
var/overflow_role = /datum/job/assistant
var/list/level_order = list(JP_HIGH, JP_MEDIUM, JP_LOW)
var/list/level_order = list(JP_HIGH,JP_MEDIUM,JP_LOW)
/// Lazylist of mob:occupation_string pairs.
var/list/dynamic_forced_occupations
@@ -88,7 +88,7 @@ SUBSYSTEM_DEF(job)
setup_job_lists()
job_config_datum_singletons = generate_config_singletons() // we set this up here regardless in case someone wants to use the verb to generate the config file.
if(!length(all_occupations))
setup_occupations()
SetupOccupations()
if(CONFIG_GET(flag/load_jobs_from_txt))
load_jobs_from_config()
set_overflow_role(CONFIG_GET(string/overflow_job)) // this must always go after load_jobs_from_config() due to how the legacy systems operate, this always takes precedent.
@@ -108,9 +108,9 @@ SUBSYSTEM_DEF(job)
return overflow_jobs
/datum/controller/subsystem/job/proc/set_overflow_role(new_overflow_role)
var/datum/job/new_overflow = ispath(new_overflow_role) ? get_job_type(new_overflow_role) : get_job(new_overflow_role)
var/datum/job/new_overflow = ispath(new_overflow_role) ? GetJobType(new_overflow_role) : GetJob(new_overflow_role)
if(!new_overflow)
job_debug("SET_OVRFLW: Failed to set new overflow role: [new_overflow_role]")
JobDebug("Failed to set new overflow role: [new_overflow_role]")
CRASH("set_overflow_role failed | new_overflow_role: [isnull(new_overflow_role) ? "null" : new_overflow_role]")
var/cap = CONFIG_GET(number/overflow_cap)
@@ -121,16 +121,17 @@ SUBSYSTEM_DEF(job)
if(new_overflow.type == overflow_role)
return
var/datum/job/old_overflow = get_job_type(overflow_role)
var/datum/job/old_overflow = GetJobType(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)
if(!(initial(old_overflow.job_flags) & JOB_CANNOT_OPEN_SLOTS))
old_overflow.job_flags &= ~JOB_CANNOT_OPEN_SLOTS
overflow_role = new_overflow.type
job_debug("SET_OVRFLW: Overflow role set to: [new_overflow.type]")
JobDebug("Overflow role set to : [new_overflow.type]")
/datum/controller/subsystem/job/proc/setup_occupations()
/datum/controller/subsystem/job/proc/SetupOccupations()
name_occupations = list()
type_occupations = list()
@@ -204,20 +205,20 @@ SUBSYSTEM_DEF(job)
return TRUE
/datum/controller/subsystem/job/proc/get_job(rank)
/datum/controller/subsystem/job/proc/GetJob(rank)
if(!length(all_occupations))
setup_occupations()
SetupOccupations()
return name_occupations[rank]
/datum/controller/subsystem/job/proc/get_job_type(jobtype)
/datum/controller/subsystem/job/proc/GetJobType(jobtype)
RETURN_TYPE(/datum/job)
if(!length(all_occupations))
setup_occupations()
SetupOccupations()
return type_occupations[jobtype]
/datum/controller/subsystem/job/proc/get_department_type(department_type)
if(!length(all_occupations))
setup_occupations()
SetupOccupations()
return joinable_departments_by_type[department_type]
/**
@@ -229,92 +230,89 @@ SUBSYSTEM_DEF(job)
* * latejoin - Set to TRUE if this is a latejoin role assignment.
* * do_eligibility_checks - Set to TRUE to conduct all job eligibility tests and reject on failure. Set to FALSE if job eligibility has been tested elsewhere and they can be safely skipped.
*/
/datum/controller/subsystem/job/proc/assign_role(mob/dead/new_player/player, datum/job/job, latejoin = FALSE, do_eligibility_checks = TRUE)
job_debug("AR: Running, Player: [player], Job: [isnull(job) ? "null" : job], LateJoin: [latejoin]")
/datum/controller/subsystem/job/proc/AssignRole(mob/dead/new_player/player, datum/job/job, latejoin = FALSE, do_eligibility_checks = TRUE)
JobDebug("Running AR, Player: [player], Job: [isnull(job) ? "null" : job], LateJoin: [latejoin]")
if(!player?.mind || !job)
job_debug("AR: Failed, player has no mind or job is null. Player: [player], Rank: [isnull(job) ? "null" : job.type]")
JobDebug("AR has failed, player has no mind or job is null, Player: [player], Rank: [isnull(job) ? "null" : job.type]")
return FALSE
if(do_eligibility_checks && (check_job_eligibility(player, job, "AR", add_job_to_log = TRUE) != JOB_AVAILABLE))
return FALSE
job_debug("AR: Role now set and assigned - [player] is [job.title], JCP:[job.current_positions], JPL:[latejoin ? job.total_positions : job.spawn_positions]")
JobDebug("Player: [player] is now Rank: [job.title], JCP:[job.current_positions], JPL:[latejoin ? job.total_positions : job.spawn_positions]")
player.mind.set_assigned_role(job)
unassigned -= player
job.current_positions++
return TRUE
/datum/controller/subsystem/job/proc/find_occupation_candidates(datum/job/job, level = 0)
job_debug("FOC: Now running, Job: [job], Level: [job_priority_level_to_string(level)]")
/datum/controller/subsystem/job/proc/FindOccupationCandidates(datum/job/job, level)
JobDebug("Running FOC, Job: [job], Level: [job_priority_level_to_string(level)]")
var/list/candidates = list()
for(var/mob/dead/new_player/player in unassigned)
if(!player)
job_debug("FOC: Player no longer exists.")
JobDebug("FOC player no longer exists.")
continue
if(!player.client)
job_debug("FOC: Player client no longer exists, Player: [player]")
JobDebug("FOC player client no longer exists, Player: [player]")
continue
// Initial screening check. Does the player even have the job enabled, if they do - Is it at the correct priority level?
var/player_job_level = player.client?.prefs.job_preferences[job.title]
if(isnull(player_job_level))
job_debug("FOC: Player job not enabled, Player: [player]")
JobDebug("FOC player job not enabled, Player: [player]")
continue
else if(player_job_level != level)
JobDebug("FOC player job enabled at wrong level, Player: [player], TheirLevel: [job_priority_level_to_string(player_job_level)], ReqLevel: [job_priority_level_to_string(level)]")
continue
if(level && (player_job_level != level))
job_debug("FOC: Player job enabled at wrong level, Player: [player], TheirLevel: [job_priority_level_to_string(player_job_level)], ReqLevel: [job_priority_level_to_string(level)]")
continue
// This check handles its own output to job_debug.
// This check handles its own output to JobDebug.
if(check_job_eligibility(player, job, "FOC", add_job_to_log = FALSE) != JOB_AVAILABLE)
continue
// They have the job enabled, at this priority level, with no restrictions applying to them.
job_debug("FOC: Player eligible, Player: [player], Level: [job_priority_level_to_string(level)]")
JobDebug("FOC pass, Player: [player], Level: [job_priority_level_to_string(level)]")
candidates += player
return candidates
/datum/controller/subsystem/job/proc/give_random_job(mob/dead/new_player/player)
job_debug("GRJ: Giving random job, Player: [player]")
/datum/controller/subsystem/job/proc/GiveRandomJob(mob/dead/new_player/player)
JobDebug("GRJ Giving random job, Player: [player]")
. = FALSE
for(var/datum/job/job as anything in shuffle(joinable_occupations))
if(QDELETED(player))
job_debug("GRJ: Player is deleted, aborting")
JobDebug("GRJ player is deleted, aborting")
break
if((job.current_positions >= job.spawn_positions) && job.spawn_positions != -1)
job_debug("GRJ: Job lacks spawn positions to be eligible, Player: [player], Job: [job]")
JobDebug("GRJ job lacks spawn positions to be eligible, Player: [player], Job: [job]")
continue
if(istype(job, get_job_type(overflow_role))) // We don't want to give him assistant, that's boring!
job_debug("GRJ: Skipping overflow role, Player: [player], Job: [job]")
if(istype(job, GetJobType(overflow_role))) // We don't want to give him assistant, that's boring!
JobDebug("GRJ skipping overflow role, Player: [player], Job: [job]")
continue
if(job.departments_bitflags & DEPARTMENT_BITFLAG_COMMAND) //If you want a command position, select it!
job_debug("GRJ: Skipping command role, Player: [player], Job: [job]")
JobDebug("GRJ skipping command role, Player: [player], Job: [job]")
continue
// This check handles its own output to job_debug.
// This check handles its own output to JobDebug.
if(check_job_eligibility(player, job, "GRJ", add_job_to_log = TRUE) != JOB_AVAILABLE)
continue
if(assign_role(player, job, do_eligibility_checks = FALSE))
job_debug("GRJ: Random job given, Player: [player], Job: [job]")
if(AssignRole(player, job, do_eligibility_checks = FALSE))
JobDebug("GRJ Random job given, Player: [player], Job: [job]")
return TRUE
job_debug("GRJ: Player eligible but assign_role failed, Player: [player], Job: [job]")
JobDebug("GRJ Player eligible but AssignRole failed, Player: [player], Job: [job]")
/datum/controller/subsystem/job/proc/reset_occupations()
job_debug("RO: Occupations reset.")
/datum/controller/subsystem/job/proc/ResetOccupations()
JobDebug("Occupations reset.")
for(var/mob/dead/new_player/player as anything in GLOB.new_player_list)
if(!player?.mind)
continue
player.mind.set_assigned_role(get_job_type(/datum/job/unassigned))
player.mind.set_assigned_role(GetJobType(/datum/job/unassigned))
player.mind.special_role = null
setup_occupations()
SetupOccupations()
unassigned = list()
if(CONFIG_GET(flag/load_jobs_from_txt))
// Any errors with the configs has already been said, we don't need to repeat them here.
@@ -323,11 +321,12 @@ SUBSYSTEM_DEF(job)
return
/*
* Forces a random Head of Staff role to be assigned to a random eligible player.
* Returns TRUE if a player was selected and assigned the role. FALSE otherwise.
/**
* Will try to select a head, ignoring ALL non-head preferences for every level until.
*
* Basically tries to ensure there is at least one head in every shift if anyone has that job preference enabled at all.
*/
/datum/controller/subsystem/job/proc/force_one_head_assignment()
/datum/controller/subsystem/job/proc/FillHeadPosition()
var/datum/job_department/command_department = get_department_type(/datum/job_department/command)
if(!command_department)
return FALSE
@@ -335,65 +334,60 @@ SUBSYSTEM_DEF(job)
for(var/datum/job/job as anything in command_department.department_jobs)
if((job.current_positions >= job.total_positions) && job.total_positions != -1)
continue
var/list/candidates = find_occupation_candidates(job, level)
var/list/candidates = FindOccupationCandidates(job, level)
if(!candidates.len)
continue
var/mob/dead/new_player/candidate = pick(candidates)
// Eligibility checks done as part of find_occupation_candidates.
if(assign_role(candidate, job, do_eligibility_checks = FALSE))
// Eligibility checks done as part of FindOccupationCandidates.
if(AssignRole(candidate, job, do_eligibility_checks = FALSE))
return TRUE
return FALSE
/**
* Attempts to fill out all possible head positions for players with that job at a a given job priority level.
* Returns the number of Head positions assigned.
*
* Arguments:
* * level - One of the JP_LOW, JP_MEDIUM, JP_HIGH or JP_ANY defines. Attempts to find candidates with head jobs at that priority only.
* * level - One of the JP_LOW, JP_MEDIUM or JP_HIGH defines. Attempts to find candidates with head jobs at this priority only.
*/
/datum/controller/subsystem/job/proc/fill_all_head_positions_at_priority(level)
. = 0
/datum/controller/subsystem/job/proc/CheckHeadPositions(level)
var/datum/job_department/command_department = get_department_type(/datum/job_department/command)
if(!command_department)
return .
return
for(var/datum/job/job as anything in command_department.department_jobs)
if((job.current_positions >= job.total_positions) && job.total_positions != -1)
continue
var/list/candidates = find_occupation_candidates(job, level)
var/list/candidates = FindOccupationCandidates(job, level)
if(!candidates.len)
continue
var/mob/dead/new_player/candidate = pick(candidates)
// Eligibility checks done as part of find_occupation_candidates
if(assign_role(candidate, job, do_eligibility_checks = FALSE))
.++
// Eligibility checks done as part of FindOccupationCandidates
AssignRole(candidate, job, do_eligibility_checks = FALSE)
/// Attempts to fill out all available AI positions.
/datum/controller/subsystem/job/proc/fill_ai_positions()
var/datum/job/ai_job = get_job(JOB_AI)
var/datum/job/ai_job = GetJob(JOB_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 = find_occupation_candidates(ai_job, level)
candidates = FindOccupationCandidates(ai_job, level)
if(candidates.len)
var/mob/dead/new_player/candidate = pick(candidates)
// Eligibility checks done as part of find_occupation_candidates
if(assign_role(candidate, get_job_type(/datum/job/ai), do_eligibility_checks = FALSE))
// Eligibility checks done as part of FindOccupationCandidates
if(AssignRole(candidate, GetJobType(/datum/job/ai), do_eligibility_checks = FALSE))
break
/** Proc divide_occupations
/** 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/divide_occupations(pure = FALSE, allow_all = FALSE)
/datum/controller/subsystem/job/proc/DivideOccupations(pure = FALSE, allow_all = FALSE)
//Setup new player list and get the jobs list
job_debug("DO: Running, allow_all = [allow_all], pure = [pure]")
JobDebug("Running DO, allow_all = [allow_all], pure = [pure]")
run_divide_occupation_pure = pure
SEND_SIGNAL(src, COMSIG_OCCUPATIONS_DIVIDED, pure, allow_all)
@@ -403,161 +397,162 @@ SUBSYSTEM_DEF(job)
if(player.ready == PLAYER_READY_TO_PLAY && player.check_preferences() && player.mind && is_unassigned_job(player.mind.assigned_role))
unassigned += player
initial_players_to_assign = length(unassigned)
initial_players_to_assign = unassigned.len
job_debug("DO: Player count to assign roles to: [initial_players_to_assign]")
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/min_access_threshold = CONFIG_GET(number/minimal_access_threshold)
if(min_access_threshold)
if(min_access_threshold > initial_players_to_assign)
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 player list.
shuffle_inplace(unassigned)
//Shuffle players and jobs
unassigned = shuffle(unassigned)
handle_feedback_gathering()
HandleFeedbackGathering()
// Assign any priority positions before all other standard job selections.
job_debug("DO: Assigning priority positions")
// Dynamic has picked a ruleset that requires enforcing some jobs before others.
JobDebug("DO, Assigning Priority Positions: [length(dynamic_forced_occupations)]")
assign_priority_positions()
job_debug("DO: Priority assignment complete")
// The overflow role has limitless slots, plus having the Overflow box ticked in prefs should (with one exception) set the priority to JP_HIGH.
// So everyone with overflow enabled will get that job. Thus we can assign it immediately to all players that have it enabled.
job_debug("DO: Assigning early overflow roles")
assign_all_overflow_positions()
job_debug("DO: Early overflow roles assigned.")
//People who wants to be the overflow role, sure, go on.
JobDebug("DO, Running Overflow Check 1")
var/datum/job/overflow_datum = GetJobType(overflow_role)
var/list/overflow_candidates = FindOccupationCandidates(overflow_datum, JP_LOW)
JobDebug("AC1, Candidates: [overflow_candidates.len]")
for(var/mob/dead/new_player/player in overflow_candidates)
JobDebug("AC1 pass, Player: [player]")
// Eligibility checks done as part of FindOccupationCandidates
AssignRole(player, GetJobType(overflow_role), do_eligibility_checks = FALSE)
overflow_candidates -= player
JobDebug("DO, AC1 end")
// At this point we can assume the following:
// From assign_priority_positions()
// 1. If possible, any necessary job roles to allow Dynamic rulesets to execute (such as an AI for malf AI) are satisfied.
// 2. All Head of Staff roles with any player pref set to JP_HIGH are filled out.
// 3. If any player not selected by the above has any Head of Staff preference enabled at any JP_ level, there is at least one Head of Staff.
//
// From assign_all_overflow_positions()
// 4. Anyone with the overflow role enabled has been given the overflow role.
//Select one head
JobDebug("DO, Running Head Check")
FillHeadPosition()
JobDebug("DO, Head Check end")
// Shuffle the joinable occupation list and filter out ineligible occupations due to above job assignments.
var/list/available_occupations = joinable_occupations.Copy()
for(var/datum/job/job in available_occupations)
// Make sure the job isn't filled. If it is, remove it from the list so it doesn't get checked.
if((job.current_positions >= job.spawn_positions) && job.spawn_positions != -1)
job_debug("DO: Job is now filled, Job: [job], Current: [job.current_positions], Limit: [job.spawn_positions]")
available_occupations -= job
// Fill out any remaining AI positions.
JobDebug("DO, Running AI Check")
fill_ai_positions()
JobDebug("DO, AI Check end")
job_debug("DO: Running standard job assignment")
//Other jobs are now checked
JobDebug("DO, Running standard job assignment")
// 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(joinable_occupations)
for(var/level in level_order)
job_debug("JOBS: Filling in head roles, Level: [job_priority_level_to_string(level)]")
// Fill the head jobs first each level
fill_all_head_positions_at_priority(level)
//Check the head jobs first each level
CheckHeadPositions(level)
// Loop through all unassigned players
for(var/mob/dead/new_player/player in unassigned)
if(!allow_all)
if(popcap_reached())
job_debug("JOBS: Popcap reached, trying to reject player: [player]")
try_reject_player(player)
if(PopcapReached())
RejectPlayer(player)
job_debug("JOBS: Finding a job for player: [player], at job priority pref: [job_priority_level_to_string(level)]")
// Loop through all jobs
for(var/datum/job/job in shuffledoccupations) // SHUFFLE ME BABY
if(!job)
JobDebug("FOC invalid/null job in occupations, Player: [player], Job: [job]")
shuffledoccupations -= job
continue
// Make sure the job isn't filled. If it is, remove it from the list so it doesn't get checked again.
if((job.current_positions >= job.spawn_positions) && job.spawn_positions != -1)
JobDebug("FOC job filled and not overflow, Player: [player], Job: [job], Current: [job.current_positions], Limit: [job.spawn_positions]")
shuffledoccupations -= job
continue
// Loop through all jobs and build a list of jobs this player could be eligible for.
var/list/possible_jobs = list()
for(var/datum/job/job in available_occupations)
// Filter any job that doesn't fit the current level.
var/player_job_level = player.client?.prefs.job_preferences[job.title]
if(isnull(player_job_level))
job_debug("JOBS: Job not enabled, Job: [job]")
JobDebug("FOC player job not enabled, Player: [player]")
continue
if(player_job_level != level)
job_debug("JOBS: Job enabled at different priority pref, TheirLevel: [job_priority_level_to_string(player_job_level)], ReqLevel: [job_priority_level_to_string(level)]")
else if(player_job_level != level)
JobDebug("FOC player job enabled but at different level, Player: [player], TheirLevel: [job_priority_level_to_string(player_job_level)], ReqLevel: [job_priority_level_to_string(level)]")
continue
if(check_job_eligibility(player, job, "JOBS", add_job_to_log = TRUE) != JOB_AVAILABLE)
if(check_job_eligibility(player, job, "DO", add_job_to_log = TRUE) != JOB_AVAILABLE)
continue
possible_jobs += job
JobDebug("DO pass, Player: [player], Level:[level], Job:[job.title]")
AssignRole(player, job, do_eligibility_checks = FALSE)
unassigned -= player
break
// Pick one of those jobs at random.
var/datum/job/picked_job = pick(possible_jobs)
JobDebug("DO, Ending standard job assignment")
job_debug("JOBS: Now assigning role to player: [player], Job:[picked_job.title]")
assign_role(player, picked_job, do_eligibility_checks = FALSE)
if((picked_job.current_positions >= picked_job.spawn_positions) && picked_job.spawn_positions != -1)
job_debug("JOBS: Job is now full, Job: [picked_job], Positions: [picked_job.current_positions], Limit: [picked_job.spawn_positions]")
available_occupations -= picked_job
job_debug("DO: Ending standard job assignment")
job_debug("DO: Handle unassigned")
// For any players that didn't get a job, fall back on their pref setting for what to do.
JobDebug("DO, Handle 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)
handle_unassigned(player, allow_all)
job_debug("DO: Ending handle unassigned")
HandleUnassigned(player, allow_all)
JobDebug("DO, Ending handle unassigned.")
job_debug("DO: Handle unrejectable unassigned")
JobDebug("DO, Handle 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(!give_random_job(player))
if(!assign_role(player, get_job_type(overflow_role))) //If everything is already filled, make them an assistant
job_debug("DO: Forced antagonist could not be assigned any random job or the overflow role. divide_occupations failed.")
job_debug("---------------------------------------------------")
if(!GiveRandomJob(player))
if(!AssignRole(player, GetJobType(overflow_role))) //If everything is already filled, make them an assistant
JobDebug("DO, Forced antagonist could not be assigned any random job or the overflow role. DivideOccupations failed.")
JobDebug("---------------------------------------------------")
run_divide_occupation_pure = FALSE
return FALSE //Living on the edge, the forced antagonist couldn't be assigned to overflow role (bans, client age) - just reroll
job_debug("DO: Ending handle unrejectable unassigned")
JobDebug("DO, Ending handle unrejectable unassigned")
job_debug("All divide occupations tasks completed.")
job_debug("---------------------------------------------------")
JobDebug("All divide occupations tasks completed.")
JobDebug("---------------------------------------------------")
run_divide_occupation_pure = FALSE
return TRUE
//We couldn't find a job from prefs for this guy.
/datum/controller/subsystem/job/proc/handle_unassigned(mob/dead/new_player/player, allow_all = FALSE)
/datum/controller/subsystem/job/proc/HandleUnassigned(mob/dead/new_player/player, allow_all = FALSE)
var/jobless_role = player.client.prefs.read_preference(/datum/preference/choiced/jobless_role)
if(!allow_all)
if(popcap_reached())
job_debug("HU: Popcap reached, trying to reject player: [player]")
try_reject_player(player)
if(PopcapReached())
RejectPlayer(player)
return
switch (jobless_role)
if (BEOVERFLOW)
var/datum/job/overflow_role_datum = get_job_type(overflow_role)
var/datum/job/overflow_role_datum = GetJobType(overflow_role)
if(check_job_eligibility(player, overflow_role_datum, debug_prefix = "HU", add_job_to_log = TRUE) != JOB_AVAILABLE)
job_debug("HU: Player cannot be overflow, trying to reject: [player]")
try_reject_player(player)
RejectPlayer(player)
return
if(!assign_role(player, overflow_role_datum, do_eligibility_checks = FALSE))
job_debug("HU: Player could not be assigned overflow role, trying to reject: [player]")
try_reject_player(player)
if(!AssignRole(player, overflow_role_datum, do_eligibility_checks = FALSE))
RejectPlayer(player)
return
if (BERANDOMJOB)
if(!give_random_job(player))
job_debug("HU: Player cannot be given a random job, trying to reject: [player]")
try_reject_player(player)
if(!GiveRandomJob(player))
RejectPlayer(player)
return
if (RETURNTOLOBBY)
job_debug("HU: Player unable to be assigned job, return to lobby enabled: [player]")
try_reject_player(player)
RejectPlayer(player)
return
else //Something gone wrong if we got here.
job_debug("HU: [player] has an invalid jobless_role var: [jobless_role]")
log_game("[player] has an invalid jobless_role var: [jobless_role]")
message_admins("[player] has an invalid jobless_role, this shouldn't happen.")
try_reject_player(player)
var/message = "HU: [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/equip_rank(mob/living/equipping, datum/job/job, client/player_client)
/datum/controller/subsystem/job/proc/EquipRank(mob/living/equipping, datum/job/job, client/player_client)
equipping.job = job.title
SEND_SIGNAL(equipping, COMSIG_JOB_RECEIVED, job)
@@ -577,7 +572,7 @@ SUBSYSTEM_DEF(job)
/datum/controller/subsystem/job/proc/handle_auto_deadmin_roles(client/C, rank)
if(!C?.holder)
return TRUE
var/datum/job/job = get_job(rank)
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
@@ -595,7 +590,7 @@ SUBSYSTEM_DEF(job)
return C.holder.auto_deadmin()
/datum/controller/subsystem/job/proc/setup_officer_positions()
var/datum/job/J = SSjob.get_job(JOB_SECURITY_OFFICER)
var/datum/job/J = SSjob.GetJob(JOB_SECURITY_OFFICER)
if(!J)
CRASH("setup_officer_positions(): Security officer job is missing")
@@ -603,7 +598,7 @@ SUBSYSTEM_DEF(job)
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
job_debug("SOP: Setting open security officer positions to [officer_positions]")
JobDebug("Setting open security officer positions to [officer_positions]")
J.total_positions = officer_positions
J.spawn_positions = officer_positions
@@ -619,7 +614,7 @@ SUBSYSTEM_DEF(job)
else //We ran out of spare locker spawns!
break
/datum/controller/subsystem/job/proc/handle_feedback_gathering()
/datum/controller/subsystem/job/proc/HandleFeedbackGathering()
for(var/datum/job/job as anything in joinable_occupations)
var/high = 0 //high
var/medium = 0 //medium
@@ -658,7 +653,7 @@ SUBSYSTEM_DEF(job)
SSblackbox.record_feedback("nested tally", "job_preferences", young, list("[job.title]", "young"))
SSblackbox.record_feedback("nested tally", "job_preferences", newbie, list("[job.title]", "newbie"))
/datum/controller/subsystem/job/proc/popcap_reached()
/datum/controller/subsystem/job/proc/PopcapReached()
var/hpc = CONFIG_GET(number/hard_popcap)
var/epc = CONFIG_GET(number/extreme_popcap)
if(hpc || epc)
@@ -667,12 +662,12 @@ SUBSYSTEM_DEF(job)
return 1
return 0
/datum/controller/subsystem/job/proc/try_reject_player(mob/dead/new_player/player)
/datum/controller/subsystem/job/proc/RejectPlayer(mob/dead/new_player/player)
if(player.mind && player.mind.special_role)
job_debug("RJCT: Player unable to be rejected due to special_role, Player: [player], SpecialRole: [player.mind.special_role]")
return FALSE
job_debug("RJCT: Player rejected, Player: [player]")
return
if(PopcapReached())
JobDebug("Popcap overflow Check observer located, Player: [player]")
JobDebug("Player rejected :[player]")
unassigned -= player
if(!run_divide_occupation_pure)
to_chat(player, "<span class='infoplain'><b>You have failed to qualify for any job you desired.</b></span>")
@@ -684,10 +679,10 @@ SUBSYSTEM_DEF(job)
var/oldjobs = SSjob.all_occupations
sleep(2 SECONDS)
for (var/datum/job/job as anything in oldjobs)
INVOKE_ASYNC(src, PROC_REF(recover_job), job)
INVOKE_ASYNC(src, PROC_REF(RecoverJob), job)
/datum/controller/subsystem/job/proc/recover_job(datum/job/J)
var/datum/job/newjob = get_job(J.title)
/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
@@ -704,7 +699,7 @@ SUBSYSTEM_DEF(job)
if(buckle && isliving(joining_mob))
buckle_mob(joining_mob, FALSE, FALSE)
/datum/controller/subsystem/job/proc/send_to_late_join(mob/M, buckle = TRUE)
/datum/controller/subsystem/job/proc/SendToLateJoin(mob/M, buckle = TRUE)
var/atom/destination
if(M.mind && !is_unassigned_job(M.mind.assigned_role) && length(GLOB.jobspawn_overrides[M.mind.assigned_role.title])) //We're doing something special today.
destination = pick(GLOB.jobspawn_overrides[M.mind.assigned_role.title])
@@ -739,6 +734,19 @@ SUBSYSTEM_DEF(job)
stack_trace("Unable to find last resort spawn point.")
return GET_ERROR_ROOM
///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/station/hallway))
if(!spawn_turf)
SendToLateJoin(living_mob)
else
podspawn(list(
"target" = spawn_turf,
"path" = /obj/structure/closet/supplypod/centcompod,
"spawn" = living_mob
))
/// Returns a list of minds of all heads of staff who are alive
/datum/controller/subsystem/job/proc/get_living_heads()
. = list()
@@ -773,7 +781,7 @@ SUBSYSTEM_DEF(job)
if(sec.assigned_role.departments_bitflags & DEPARTMENT_BITFLAG_SECURITY)
. += sec
/datum/controller/subsystem/job/proc/job_debug(message)
/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.
@@ -838,47 +846,12 @@ SUBSYSTEM_DEF(job)
safe_code_timer_id = null
safe_code_request_loc = null
/// Assigns roles that are considered high priority, either due to dynamic needing to force a specific role for a specific ruleset
/// or making sure roles critical to round progression exist where possible every shift.
/// Blindly assigns the required roles to every player in the dynamic_forced_occupations list.
/datum/controller/subsystem/job/proc/assign_priority_positions()
job_debug("APP: Assigning Dynamic ruleset forced occupations: [length(dynamic_forced_occupations)]")
for(var/mob/new_player in dynamic_forced_occupations)
// Eligibility checks already carried out as part of the dynamic ruleset trim_candidates proc.
// However no guarantee of game state between then and now, so don't skip eligibility checks on assign_role.
assign_role(new_player, get_job(dynamic_forced_occupations[new_player]))
// Get JP_HIGH department Heads of Staff in place. Indirectly useful for the Revolution ruleset to have as many Heads as possible.
job_debug("APP: Assigning all JP_HIGH head of staff roles.")
var/head_count = fill_all_head_positions_at_priority(JP_HIGH)
// If nobody has JP_HIGH on a Head role, try to force at least one Head of Staff so every shift has the best chance
// of having at least one leadership role.
if(head_count == 0)
force_one_head_assignment()
// Fill out all AI positions.
job_debug("APP: Filling all AI positions")
fill_ai_positions()
/datum/controller/subsystem/job/proc/assign_all_overflow_positions()
job_debug("OVRFLW: Assigning all overflow roles.")
job_debug("OVRFLW: This shift's overflow role: [overflow_role]")
var/datum/job/overflow_datum = get_job_type(overflow_role)
// When the Overflow role changes for any reason, this allows players to set otherwise invalid job priority pref states.
// So if Assistant is the "usual" Overflow but it gets changed to Clown for a shift, players can set the Assistant role's priorities
// to JP_MEDIUM and JP_LOW. When the "usual" Overflow role comes back, it returns to an On option in the prefs menu but still
// keeps its old JP_MEDIUM or JP_LOW value in the background.
// Due to this prefs quirk, we actually don't want to find JP_HIGH candidates as it may exclude people with abnormal pref states that
// appear normal from the UI. By passing in JP_ANY, it will return all players that have the overflow job pref (which should be a toggle)
// set to any level.
var/list/overflow_candidates = find_occupation_candidates(overflow_datum, JP_ANY)
for(var/mob/dead/new_player/player in overflow_candidates)
// Eligibility checks done as part of find_occupation_candidates, so skip them.
assign_role(player, get_job_type(overflow_role), do_eligibility_checks = FALSE)
job_debug("OVRFLW: Assigned overflow to player: [player]")
job_debug("OVRFLW: All overflow roles assigned.")
// Eligibility checks already carried out as part of the dynamic ruleset trim_candidates proc.area
// However no guarantee of game state between then and now, so don't skip eligibility checks on AssignRole.
AssignRole(new_player, GetJob(dynamic_forced_occupations[new_player]))
/// Takes a job priority #define such as JP_LOW and gets its string representation for logging.
/datum/controller/subsystem/job/proc/job_priority_level_to_string(priority)
@@ -896,40 +869,40 @@ SUBSYSTEM_DEF(job)
* Arguments:
* * player - The player to check for job eligibility.
* * possible_job - The job to check for eligibility against.
* * debug_prefix - Logging prefix for the job_debug log entries. For example, GRJ during give_random_job or DO during divide_occupations.
* * debug_prefix - Logging prefix for the JobDebug log entries. For example, GRJ during GiveRandomJob or DO during DivideOccupations.
* * add_job_to_log - If TRUE, appends the job type to the log entry. If FALSE, does not. Set to FALSE when check is part of iterating over players for a specific job, set to TRUE when check is part of iterating over jobs for a specific player and you don't want extra log entry spam.
*/
/datum/controller/subsystem/job/proc/check_job_eligibility(mob/dead/new_player/player, datum/job/possible_job, debug_prefix = "", add_job_to_log = FALSE)
if(!player.mind)
job_debug("[debug_prefix]: Player has no mind, Player: [player][add_job_to_log ? ", Job: [possible_job]" : ""]")
JobDebug("[debug_prefix] player has no mind, Player: [player][add_job_to_log ? ", Job: [possible_job]" : ""]")
return JOB_UNAVAILABLE_GENERIC
if(possible_job.title in player.mind.restricted_roles)
job_debug("[debug_prefix] Error: [get_job_unavailable_error_message(JOB_UNAVAILABLE_ANTAG_INCOMPAT, possible_job.title)], Player: [player][add_job_to_log ? ", Job: [possible_job]" : ""]")
JobDebug("[debug_prefix] Error: [get_job_unavailable_error_message(JOB_UNAVAILABLE_ANTAG_INCOMPAT, possible_job.title)], Player: [player][add_job_to_log ? ", Job: [possible_job]" : ""]")
return JOB_UNAVAILABLE_ANTAG_INCOMPAT
if(!possible_job.player_old_enough(player.client))
job_debug("[debug_prefix] Error: [get_job_unavailable_error_message(JOB_UNAVAILABLE_ACCOUNTAGE, possible_job.title)], Player: [player][add_job_to_log ? ", Job: [possible_job]" : ""]")
JobDebug("[debug_prefix] Error: [get_job_unavailable_error_message(JOB_UNAVAILABLE_ACCOUNTAGE, possible_job.title)], Player: [player][add_job_to_log ? ", Job: [possible_job]" : ""]")
return JOB_UNAVAILABLE_ACCOUNTAGE
var/required_playtime_remaining = possible_job.required_playtime_remaining(player.client)
if(required_playtime_remaining)
job_debug("[debug_prefix] Error: [get_job_unavailable_error_message(JOB_UNAVAILABLE_PLAYTIME, possible_job.title)], Player: [player], MissingTime: [required_playtime_remaining][add_job_to_log ? ", Job: [possible_job]" : ""]")
JobDebug("[debug_prefix] Error: [get_job_unavailable_error_message(JOB_UNAVAILABLE_PLAYTIME, possible_job.title)], Player: [player], MissingTime: [required_playtime_remaining][add_job_to_log ? ", Job: [possible_job]" : ""]")
return JOB_UNAVAILABLE_PLAYTIME
// Run the banned check last since it should be the rarest check to fail and can access the database.
if(is_banned_from(player.ckey, possible_job.title))
job_debug("[debug_prefix] Error: [get_job_unavailable_error_message(JOB_UNAVAILABLE_BANNED, possible_job.title)], Player: [player][add_job_to_log ? ", Job: [possible_job]" : ""]")
JobDebug("[debug_prefix] Error: [get_job_unavailable_error_message(JOB_UNAVAILABLE_BANNED, possible_job.title)], Player: [player][add_job_to_log ? ", Job: [possible_job]" : ""]")
return JOB_UNAVAILABLE_BANNED
// Check for character age
if(possible_job.required_character_age > player.client.prefs.read_preference(/datum/preference/numeric/age) && possible_job.required_character_age != null)
job_debug("[debug_prefix] Error: [get_job_unavailable_error_message(JOB_UNAVAILABLE_AGE)], Player: [player][add_job_to_log ? ", Job: [possible_job]" : ""]")
JobDebug("[debug_prefix] Error: [get_job_unavailable_error_message(JOB_UNAVAILABLE_AGE)], Player: [player][add_job_to_log ? ", Job: [possible_job]" : ""]")
return JOB_UNAVAILABLE_AGE
// Need to recheck the player exists after is_banned_from since it can query the DB which may sleep.
if(QDELETED(player))
job_debug("[debug_prefix]: Player is qdeleted, Player: [player][add_job_to_log ? ", Job: [possible_job]" : ""]")
JobDebug("[debug_prefix] player is qdeleted, Player: [player][add_job_to_log ? ", Job: [possible_job]" : ""]")
return JOB_UNAVAILABLE_GENERIC
return JOB_AVAILABLE
+3 -3
View File
@@ -236,14 +236,14 @@ SUBSYSTEM_DEF(ticker)
can_continue = SSdynamic.pre_setup() //Choose antagonists
CHECK_TICK
SEND_GLOBAL_SIGNAL(COMSIG_GLOB_PRE_JOBS_ASSIGNED, src)
can_continue = can_continue && SSjob.divide_occupations() //Distribute jobs
can_continue = can_continue && SSjob.DivideOccupations() //Distribute jobs
CHECK_TICK
if(!GLOB.Debug2)
if(!can_continue)
log_game("Game failed pre_setup")
to_chat(world, "<B>Error setting up game.</B> Reverting to pre-game lobby.")
SSjob.reset_occupations()
SSjob.ResetOccupations()
return FALSE
else
message_admins(span_notice("DEBUG: Bypassing prestart checks..."))
@@ -416,7 +416,7 @@ SUBSYSTEM_DEF(ticker)
continue
var/datum/job/player_assigned_role = new_player_living.mind.assigned_role
if(player_assigned_role.job_flags & JOB_EQUIP_RANK)
SSjob.equip_rank(new_player_living, player_assigned_role, new_player_mob.client)
SSjob.EquipRank(new_player_living, player_assigned_role, new_player_mob.client)
player_assigned_role.after_roundstart_spawn(new_player_living, new_player_mob.client)
if(picked_spare_id_candidate == new_player_mob)
captainless = FALSE