diff --git a/code/__DEFINES/jobs.dm b/code/__DEFINES/jobs.dm index 2896b2fa5ae..ba8d876db39 100644 --- a/code/__DEFINES/jobs.dm +++ b/code/__DEFINES/jobs.dm @@ -4,6 +4,8 @@ #define JOB_UNAVAILABLE_PLAYTIME 3 #define JOB_UNAVAILABLE_ACCOUNTAGE 4 #define JOB_UNAVAILABLE_SLOTFULL 5 +/// Job unavailable due to incompatibility with an antag role. +#define JOB_UNAVAILABLE_ANTAG_INCOMPAT 6 #define DEFAULT_RELIGION "Christianity" #define DEFAULT_DEITY "Space Jesus" diff --git a/code/__DEFINES/~skyrat_defines/jobs.dm b/code/__DEFINES/~skyrat_defines/jobs.dm index 38060903428..8032b0b8fff 100644 --- a/code/__DEFINES/~skyrat_defines/jobs.dm +++ b/code/__DEFINES/~skyrat_defines/jobs.dm @@ -1,7 +1,8 @@ -#define JOB_UNAVAILABLE_QUIRK 6 -#define JOB_UNAVAILABLE_SPECIES 7 -#define JOB_UNAVAILABLE_LANGUAGE 7 -#define JOB_NOT_VETERAN 8 +// Just keeping this easy to maintain in the future. +#define JOB_NOT_VETERAN JOB_UNAVAILABLE_GENERIC + 1 +#define JOB_UNAVAILABLE_QUIRK JOB_NOT_VETERAN + 1 +#define JOB_UNAVAILABLE_SPECIES JOB_UNAVAILABLE_QUIRK + 1 +#define JOB_UNAVAILABLE_LANGUAGE JOB_UNAVAILABLE_SPECIES + 1 #define SEC_RESTRICTED_QUIRKS "Blind" = TRUE, "Brain Tumor" = TRUE, "Deaf" = TRUE, "Paraplegic" = TRUE, "Mute" = TRUE, "Foreigner" = TRUE, "Pacifist" = TRUE, "Chunky Fingers" = TRUE, "Nymphomania" = TRUE #define HEAD_RESTRICTED_QUIRKS "Blind" = TRUE, "Deaf" = TRUE, "Mute" = TRUE, "Foreigner" = TRUE, "Chunky Fingers" = TRUE, "Nymphomania" = TRUE diff --git a/code/controllers/subsystem/job.dm b/code/controllers/subsystem/job.dm index a1a39daf5e0..48cf12ebbc5 100644 --- a/code/controllers/subsystem/job.dm +++ b/code/controllers/subsystem/job.dm @@ -67,6 +67,8 @@ SUBSYSTEM_DEF(job) /// The loc to which the emergency safe code has been requested for delivery. var/turf/safe_code_request_loc + /// Dictionary that maps job priorities to low/medium/high. Keys have to be number-strings as assoc lists cannot be indexed by integers. Set in setup_job_lists. + var/list/job_priorities_to_strings /datum/controller/subsystem/job/Initialize(timeofday) setup_job_lists() @@ -184,38 +186,30 @@ SUBSYSTEM_DEF(job) SetupOccupations() return joinable_departments_by_type[department_type] - -/datum/controller/subsystem/job/proc/AssignRole(mob/dead/new_player/player, datum/job/job, latejoin = FALSE) - JobDebug("Running AR, Player: [player], Rank: [isnull(job) ? "null" : job.type], LJ: [latejoin]") +/** + * Assigns the given job role to the player. + * + * Arguments: + * * player - The player to assign the job to + * * job - The job to assign + * * 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/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) - JobDebug("AR has failed, 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(is_banned_from(player.ckey, job.title) || QDELETED(player)) + + if(do_eligibility_checks && (check_job_eligibility(player, job, "AR", add_job_to_log = TRUE) != JOB_AVAILABLE)) return FALSE - if(!job.player_old_enough(player.client)) - return FALSE - if(job.required_playtime_remaining(player.client)) - return FALSE - //SKYRAT EDIT ADDITION BEGIN - CUSTOMIZATION - if(job.has_banned_quirk(player.client.prefs)) - return FALSE - if(!job.has_required_languages(player.client.prefs)) - return FALSE - if(job.veteran_only && !is_veteran_player(player.client)) - return FALSE - if(job.has_banned_species(player.client.prefs)) - return FALSE - //SKYRAT EDIT END - var/position_limit = job.total_positions - if(!latejoin) - position_limit = job.spawn_positions - JobDebug("Player: [player] is now Rank: [job.title], JCP:[job.current_positions], JPL:[position_limit]") + + 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/FreeRole(rank) if(!rank) return @@ -225,42 +219,26 @@ SUBSYSTEM_DEF(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]") +/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(is_banned_from(player.ckey, job.title) || QDELETED(player)) - JobDebug("FOC isbanned failed, Player: [player]") + // 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)) + JobDebug("FOC player job not enabled, Player: [player]") continue - if(!job.player_old_enough(player.client)) - JobDebug("FOC player not old enough, Player: [player]") - continue - //SKYRAT EDIT ADDITION BEGIN - CUSTOMIZATION - if(job.has_banned_quirk(player.client.prefs)) - JobDebug("FOC job not compatible with quirks, Player: [player]") - continue - if(!job.has_required_languages(player.client.prefs)) - JobDebug("FOC job not compatible with languages, Player: [player]") - continue - if(job.veteran_only && !is_veteran_player(player.client)) - JobDebug("FOC player is not veteran, Player: [player]") - if(job.has_banned_species(player.client.prefs)) - JobDebug("FOC job not compatible with species, Player: [player]") - continue - //SKYRAT EDIT END - 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]") + 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(player.client.prefs.job_preferences[job.title] == level) - JobDebug("FOC pass, Player: [player], Level:[level]") - candidates += player + // 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. + JobDebug("FOC pass, Player: [player], Level: [job_priority_level_to_string(level)]") + candidates += player return candidates @@ -268,55 +246,36 @@ SUBSYSTEM_DEF(job) JobDebug("GRJ Giving random job, Player: [player]") . = FALSE for(var/datum/job/job as anything in shuffle(joinable_occupations)) + if(QDELETED(player)) + JobDebug("GRJ player is deleted, aborting") + break + + if((job.current_positions >= job.spawn_positions) && job.spawn_positions != -1) + JobDebug("GRJ job lacks spawn positions to be eligible, 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! + JobDebug("GRJ skipping command role, Player: [player], Job: [job]") continue //SKYRAT EDIT ADDITION if(job.departments_bitflags & DEPARTMENT_BITFLAG_CENTRAL_COMMAND) //If you want a CC position, select it! + JobDebug("GRJ skipping Central Command role, Player: [player], Job: [job]") continue //SKYRAT EDIT END - - 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]") + + // This check handles its own output to JobDebug. + if(check_job_eligibility(player, job, "GRJ", add_job_to_log = TRUE) != JOB_AVAILABLE) continue - if(!job.player_old_enough(player.client)) - JobDebug("GRJ player not old enough, Player: [player]") - continue - - //SKYRAT EDIT ADDITION BEGIN - CUSTOMIZATION - if(job.has_banned_quirk(player.client.prefs)) - JobDebug("GRJ player has incompatible quirk, Player: [player]") - continue - if(!job.has_required_languages(player.client.prefs)) - JobDebug("GRJ player has incompatible languages, Player: [player]") - continue - if(job.veteran_only && !is_veteran_player(player.client)) - JobDebug("GRJ player is not veteran, Player: [player]") - if(job.has_banned_species(player.client.prefs)) - JobDebug("GRJ player has incompatible species, Player: [player]") - continue - //SKYRAT EDIT END - - 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) + if(AssignRole(player, job, do_eligibility_checks = FALSE)) JobDebug("GRJ Random job given, Player: [player], Job: [job]") - if(AssignRole(player, job)) - return TRUE + return TRUE + + JobDebug("GRJ Player eligible but AssignRole failed, Player: [player], Job: [job]") /datum/controller/subsystem/job/proc/ResetOccupations() @@ -331,9 +290,11 @@ SUBSYSTEM_DEF(job) 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 +/** + * 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/FillHeadPosition() var/datum/job_department/command_department = get_department_type(/datum/job_department/command) if(!command_department) @@ -346,13 +307,18 @@ SUBSYSTEM_DEF(job) if(!candidates.len) continue var/mob/dead/new_player/candidate = pick(candidates) - if(AssignRole(candidate, job)) + // Eligibility checks done as part of FindOccupationCandidates. + if(AssignRole(candidate, job, do_eligibility_checks = FALSE)) 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 +/** + * Attempts to fill out all possible head positions for players with that job at a a given job priority level. + * + * Arguments: + * * 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/CheckHeadPositions(level) var/datum/job_department/command_department = get_department_type(/datum/job_department/command) if(!command_department) @@ -364,7 +330,8 @@ SUBSYSTEM_DEF(job) if(!candidates.len) continue var/mob/dead/new_player/candidate = pick(candidates) - AssignRole(candidate, job) + // 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() @@ -378,7 +345,8 @@ SUBSYSTEM_DEF(job) candidates = FindOccupationCandidates(ai_job, level) if(candidates.len) var/mob/dead/new_player/candidate = pick(candidates) - if(AssignRole(candidate, GetJobType(/datum/job/ai))) + // Eligibility checks done as part of FindOccupationCandidates + if(AssignRole(candidate, GetJobType(/datum/job/ai), do_eligibility_checks = FALSE)) break @@ -429,7 +397,8 @@ SUBSYSTEM_DEF(job) JobDebug("AC1, Candidates: [overflow_candidates.len]") for(var/mob/dead/new_player/player in overflow_candidates) JobDebug("AC1 pass, Player: [player]") - AssignRole(player, GetJobType(overflow_role)) + // Eligibility checks done as part of FindOccupationCandidates + AssignRole(player, GetJobType(overflow_role), do_eligibility_checks = FALSE) overflow_candidates -= player JobDebug("DO, AC1 end") @@ -444,9 +413,7 @@ SUBSYSTEM_DEF(job) JobDebug("DO, AI Check end") //Other jobs are now checked - JobDebug("DO, Running Standard Check") - - + 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. @@ -465,65 +432,54 @@ SUBSYSTEM_DEF(job) // 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 - if(is_banned_from(player.ckey, job.title)) - JobDebug("DO isbanned failed, Player: [player], Job:[job.title]") + // 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 - 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]") + // 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)) + JobDebug("FOC player job not enabled, Player: [player]") + continue + 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 - //SKYRAT EDIT ADDITION BEGIN - CUSTOMIZATION - if(job.has_banned_quirk(player.client.prefs)) - JobDebug("DO player has incompatible quirk, Player: [player], Job:[job.title]") - continue - if(!job.has_required_languages(player.client.prefs)) - JobDebug("DO player has incompatible languages, Player: [player], Job:[job.title]") - continue - if(job.veteran_only && !is_veteran_player(player.client)) - JobDebug("DO player is not veteran, Player: [player], Job:[job.title]") - continue - if(job.has_banned_species(player.client.prefs)) - JobDebug("DO player has incompatible species, Player: [player], Job:[job.title]") - continue - //SKYRAT EDIT END - - if(job.required_playtime_remaining(player.client)) - JobDebug("DO player not enough xp, Player: [player], Job:[job.title]") + if(check_job_eligibility(player, job, "DO", add_job_to_log = TRUE) != JOB_AVAILABLE) continue - if(player.mind && (job.title in player.mind.restricted_roles)) - JobDebug("DO incompatible with antagonist role, Player: [player], Job:[job.title]") - continue + JobDebug("DO pass, Player: [player], Level:[level], Job:[job.title]") + AssignRole(player, job, do_eligibility_checks = FALSE) + unassigned -= player + break - // 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) - unassigned -= player - break + JobDebug("DO, Ending standard job assignment") - - JobDebug("DO, Handling unassigned.") + 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) HandleUnassigned(player) + JobDebug("DO, Ending handle unassigned.") - JobDebug("DO, Handling 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(!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("---------------------------------------------------") return FALSE //Living on the edge, the forced antagonist couldn't be assigned to overflow role (bans, client age) - just reroll + JobDebug("DO, Ending handle unrejectable unassigned") + + JobDebug("All divide occupations tasks completed.") + JobDebug("---------------------------------------------------") return TRUE @@ -538,19 +494,23 @@ SUBSYSTEM_DEF(job) switch (jobless_role) if (BEOVERFLOW) var/datum/job/overflow_role_datum = GetJobType(overflow_role) - var/allowed_to_be_a_loser = !is_banned_from(player.ckey, overflow_role_datum.title) - if(QDELETED(player) || !allowed_to_be_a_loser) + + if(check_job_eligibility(player, overflow_role_datum, debug_prefix = "HU", add_job_to_log = TRUE) != JOB_AVAILABLE) RejectPlayer(player) - else - if(!AssignRole(player, overflow_role_datum)) - RejectPlayer(player) + return + + if(!AssignRole(player, overflow_role_datum, do_eligibility_checks = FALSE)) + RejectPlayer(player) + return if (BERANDOMJOB) if(!GiveRandomJob(player)) RejectPlayer(player) + return if (RETURNTOLOBBY) RejectPlayer(player) + return else //Something gone wrong if we got here. - var/message = "DO: [player] fell through handling unassigned" + var/message = "HU: [player] fell through handling unassigned" JobDebug(message) log_game(message) message_admins(message) @@ -854,6 +814,12 @@ SUBSYSTEM_DEF(job) "Special Ops Officer", "Admiral", "CentCom Commander", "CentCom Bartender", "Private Security Force", "Fleetmaster", "Bridge Officer", "Operations Inspector", \ "Deck Crewman") + job_priorities_to_strings = list( + "[JP_LOW]" = "Low Priority", + "[JP_MEDIUM]" = "Medium Priority", + "[JP_HIGH]" = "High Priority", + ) + /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." @@ -914,4 +880,73 @@ SUBSYSTEM_DEF(job) /// 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) + // 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) + return job_priorities_to_strings["[priority]"] || "Undefined Priority \[[priority]\]" + +/** + * Runs a standard suite of eligibility checks to make sure the player can take the reqeusted job. + * + * Checks: + * * Role bans + * * How many days old the player account is + * * Whether the player has the required hours in other jobs to take that role + * * If the job is in the mind's restricted roles, for example if they have an antag datum that's incompatible with certain roles. + * + * Arguments: + * * player - The player to check for job eligibility. + * * possible_job - The job to check for eligibility against. + * * 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) + 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) + JobDebug("[debug_prefix] Error: [get_job_unavailable_error_message(JOB_UNAVAILABLE_ANTAG_INCOMPAT)], Player: [player][add_job_to_log ? ", Job: [possible_job]" : ""]") + return JOB_UNAVAILABLE_ANTAG_INCOMPAT + + if(!possible_job.player_old_enough(player.client)) + JobDebug("[debug_prefix] Error: [get_job_unavailable_error_message(JOB_UNAVAILABLE_ACCOUNTAGE)], 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) + JobDebug("[debug_prefix] Error: [get_job_unavailable_error_message(JOB_UNAVAILABLE_PLAYTIME)], 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)) + JobDebug("[debug_prefix] Error: [get_job_unavailable_error_message(JOB_UNAVAILABLE_BANNED)], Player: [player][add_job_to_log ? ", Job: [possible_job]" : ""]") + return JOB_UNAVAILABLE_BANNED + + //SKYRAT EDIT ADDITION BEGIN - CUSTOMIZATION + if(possible_job.veteran_only && !is_veteran_player(player.client)) + JobDebug("[debug_prefix] Error: [get_job_unavailable_error_message(JOB_NOT_VETERAN)], Player: [player][add_job_to_log ? ", Job: [possible_job]" : ""]") + return JOB_NOT_VETERAN + + if(possible_job.has_banned_quirk(player.client.prefs)) + JobDebug("[debug_prefix] Error: [get_job_unavailable_error_message(JOB_UNAVAILABLE_QUIRK)], Player: [player][add_job_to_log ? ", Job: [possible_job]" : ""]") + return JOB_UNAVAILABLE_QUIRK + + if(!possible_job.has_required_languages(player.client.prefs)) + JobDebug("[debug_prefix] Error: [get_job_unavailable_error_message(JOB_UNAVAILABLE_LANGUAGE)], Player: [player][add_job_to_log ? ", Job: [possible_job]" : ""]") + return JOB_UNAVAILABLE_LANGUAGE + + if(possible_job.has_banned_species(player.client.prefs)) + JobDebug("[debug_prefix] Error: [get_job_unavailable_error_message(JOB_UNAVAILABLE_SPECIES)], Player: [player][add_job_to_log ? ", Job: [possible_job]" : ""]") + return JOB_UNAVAILABLE_SPECIES + //SKYRAT EDIT END + + // Run this check after is_banned_from since it can query the DB which may sleep. + if(QDELETED(player)) + JobDebug("[debug_prefix] player is qdeleted, Player: [player][add_job_to_log ? ", Job: [possible_job]" : ""]") + return JOB_UNAVAILABLE_GENERIC + + return JOB_AVAILABLE diff --git a/code/game/gamemodes/dynamic/dynamic_rulesets.dm b/code/game/gamemodes/dynamic/dynamic_rulesets.dm index fb23d839014..e5e2cbfe836 100644 --- a/code/game/gamemodes/dynamic/dynamic_rulesets.dm +++ b/code/game/gamemodes/dynamic/dynamic_rulesets.dm @@ -230,7 +230,8 @@ var/exclusive_candidate = FALSE for(var/role in exclusive_roles) var/datum/job/job = SSjob.GetJob(role) - if((role in candidate_client.prefs.job_preferences) && !is_banned_from(candidate_player.ckey, role) && !job.required_playtime_remaining(candidate_client)) + + if((role in candidate_client.prefs.job_preferences) && SSjob.check_job_eligibility(candidate_player, job, "Dynamic Roundstart TC", add_job_to_log = TRUE)) exclusive_candidate = TRUE break diff --git a/code/modules/mob/dead/new_player/new_player.dm b/code/modules/mob/dead/new_player/new_player.dm index 35b52237292..06ebbdeb515 100644 --- a/code/modules/mob/dead/new_player/new_player.dm +++ b/code/modules/mob/dead/new_player/new_player.dm @@ -150,15 +150,17 @@ if(JOB_UNAVAILABLE_SLOTFULL) return "[jobtitle] is already filled to capacity." //SKYRAT EDIT ADDITION + if(JOB_NOT_VETERAN) + return "You need to be veteran to join as [jobtitle]." if(JOB_UNAVAILABLE_QUIRK) return "[jobtitle] is restricted from your quirks." if(JOB_UNAVAILABLE_LANGUAGE) return "[jobtitle] is restricted from your languages." - if(JOB_NOT_VETERAN) - return "You need to be veteran to join as [jobtitle]." if(JOB_UNAVAILABLE_SPECIES) return "[jobtitle] is restricted from your species." //SKYRAT EDIT END + if(JOB_UNAVAILABLE_ANTAG_INCOMPAT) + return "[jobtitle] is not compatible with some antagonist role assigned to you." return "Error: Unknown job availability." /mob/dead/new_player/proc/IsJobUnavailable(rank, latejoin = FALSE) @@ -174,14 +176,11 @@ return JOB_UNAVAILABLE_SLOTFULL else return JOB_UNAVAILABLE_SLOTFULL - if(is_banned_from(ckey, rank)) - return JOB_UNAVAILABLE_BANNED - if(QDELETED(src)) - return JOB_UNAVAILABLE_GENERIC - if(!job.player_old_enough(client)) - return JOB_UNAVAILABLE_ACCOUNTAGE - if(job.required_playtime_remaining(client)) - return JOB_UNAVAILABLE_PLAYTIME + + var/eligibility_check = SSjob.check_job_eligibility(src, job, "Mob IsJobUnavailable") + if(eligibility_check != JOB_AVAILABLE) + return eligibility_check + if(latejoin && !job.special_check_latejoin(client)) return JOB_UNAVAILABLE_GENERIC //SKYRAT EDIT ADDITION @@ -217,7 +216,9 @@ var/datum/job/job = SSjob.GetJob(rank) - SSjob.AssignRole(src, job, TRUE) + if(!SSjob.AssignRole(src, job, TRUE)) + tgui_alert(usr, "There was an unexpected error putting you into your requested job. If you cannot join with any job, you should contact an admin.") + return FALSE mind.late_joiner = TRUE var/atom/destination = mind.assigned_role.get_latejoin_spawn_point()