mirror of
https://github.com/yogstation13/Yogstation.git
synced 2025-02-26 09:04:50 +00:00
Spiritual successor and extension to #17798, an almost entire rebuild of the SQL ban system backend and interface. Bantypes are removed per #8584 and #6174. All bans are now 'role bans', server bans are when a ban's role is server. Admin bans are a column, meaning it's possible to ban admins from jobs. Bans now have only an expiry datetime, duration is calculated from this when queried. unbanned column is removed as it's superfluous, checking unban status is now done through checking unban_datetime. unban_round_id column added. Each ip and computerid columns rearranged so ip is always first, like in other tables. Bans now permit a null ckey, ip and computerid. Ban checking is split into two procs now is_banned_from() does a check if a ckey is banned from one or more roles and returns true or false. This effectively replaces jobban_isbanned() used in simple if() statements. If connected a client's ban cache is checked rather than querying the DB. This makes it possible for a client connected to two or more servers to ignore any bans made on one server until their ban cache is rebuilt on the others. Could be avoided with cross-server calls to update ban caches or just the removal of the ban cache but as is I've done neither since I think it's enough of an edge case to not be worth it. The second proc is is_banned_from_with_details(), this queries the DB for a role ban on a player's ckey, ip or CID and returns the details. This replaces direct queries in IsBanned.dm and the preferences menu. The legacy ban system is removed. The interfaces for banning, unbanning and editing bans have been remade to require less clicking and easier simultaneous operations. The banning and jobban panel are combined. They also store player connection details when opened so a client disconnecting no longer stops a ban being placed. New banning panel: Key, IP and CID can all be toggled to allow excluding them from a ban. Checking Use IP and CID from last connection lets you enter only a ckey and have the DB fill these fields in for you, if possible. Temporary bans have a drop-menu which lets you select between seconds, minutes, hours, days, weeks, months and years so you don't need to calculate how many minutes a long ban would be. The ban is still converted into minutes on the DB however. Checking any of the head roles will check both of the boxes for you. The red role box indicates there is already a ban on that role for this ckey. You can apply additional role bans to stack them. New unbanning panel: Unbanning panel is now separate from the banning panel but otherwise functionally the same. Ban editing panel: Actually just a modified banning panel, all the features from it work the same here. You can now edit almost all parameters of a ban instead of just the reason. You can't edit severity as it's not really part of the ban. The panels have been tested but I've not been able to get my local server to be accessible so ban functionality isn't properly confirmed. Plenty of testing will be required as I'd rather not break bans. cl admin: Ban interface rework. The banning and unbanning panels have received a new design which is easier to use and allows multiple role bans to be made at once. prefix: Ban search and unbanning moved to unbanning panel, which is now a separate panel to the old banning panel. /cl
645 lines
22 KiB
Plaintext
645 lines
22 KiB
Plaintext
SUBSYSTEM_DEF(job)
|
|
name = "Jobs"
|
|
init_order = INIT_ORDER_JOBS
|
|
flags = SS_NO_FIRE
|
|
|
|
var/list/occupations = list() //List of all jobs
|
|
var/list/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"
|
|
|
|
/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.spawn_positions = cap
|
|
new_overflow.total_positions = cap
|
|
|
|
if(new_overflow_role != overflow_role)
|
|
var/datum/job/old_overflow = GetJob(overflow_role)
|
|
old_overflow.spawn_positions = initial(old_overflow.spawn_positions)
|
|
old_overflow.total_positions = initial(old_overflow.total_positions)
|
|
overflow_role = new_overflow_role
|
|
JobDebug("Overflow role set to : [new_overflow_role]")
|
|
|
|
/datum/controller/subsystem/job/proc/SetupOccupations(faction = "Station")
|
|
occupations = list()
|
|
var/list/all_jobs = subtypesof(/datum/job)
|
|
if(!all_jobs.len)
|
|
to_chat(world, "<span class='boldannounce'>Error setting up jobs, no job datums found</span>")
|
|
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()) //Even though we initialize before mapping, this is fine because the config is loaded at new
|
|
testing("Removed [job.type] due to map config");
|
|
continue
|
|
occupations += job
|
|
name_occupations[job.title] = job
|
|
type_occupations[J] = job
|
|
|
|
return 1
|
|
|
|
|
|
/datum/controller/subsystem/job/proc/GetJob(rank)
|
|
if(!occupations.len)
|
|
SetupOccupations()
|
|
return name_occupations[rank]
|
|
|
|
/datum/controller/subsystem/job/proc/GetJobType(jobtype)
|
|
if(!occupations.len)
|
|
SetupOccupations()
|
|
return type_occupations[jobtype]
|
|
|
|
/datum/controller/subsystem/job/proc/AssignRole(mob/dead/new_player/player, rank, latejoin = FALSE)
|
|
JobDebug("Running AR, Player: [player], Rank: [rank], LJ: [latejoin]")
|
|
if(player && player.mind && rank)
|
|
var/datum/job/job = GetJob(rank)
|
|
if(!job)
|
|
return FALSE
|
|
if(is_banned_from(player.ckey, rank) || QDELETED(player))
|
|
return FALSE
|
|
if(!job.player_old_enough(player.client))
|
|
return FALSE
|
|
if(job.required_playtime_remaining(player.client))
|
|
return FALSE
|
|
var/position_limit = job.total_positions
|
|
if(!latejoin)
|
|
position_limit = job.spawn_positions
|
|
JobDebug("Player: [player] is now Rank: [rank], JCP:[job.current_positions], JPL:[position_limit]")
|
|
player.mind.assigned_role = rank
|
|
unassigned -= player
|
|
job.current_positions++
|
|
return TRUE
|
|
JobDebug("AR has failed, Player: [player], Rank: [rank]")
|
|
return FALSE
|
|
|
|
|
|
/datum/controller/subsystem/job/proc/FindOccupationCandidates(datum/job/job, level, flag)
|
|
JobDebug("Running FOC, Job: [job], Level: [level], Flag: [flag]")
|
|
var/list/candidates = list()
|
|
for(var/mob/dead/new_player/player in unassigned)
|
|
if(is_banned_from(player.ckey, job.title) || QDELETED(player))
|
|
JobDebug("FOC isbanned failed, Player: [player]")
|
|
continue
|
|
if(!job.player_old_enough(player.client))
|
|
JobDebug("FOC player not old enough, Player: [player]")
|
|
continue
|
|
if(job.required_playtime_remaining(player.client))
|
|
JobDebug("FOC player not enough xp, Player: [player]")
|
|
continue
|
|
if(flag && (!(flag in player.client.prefs.be_special)))
|
|
JobDebug("FOC flag failed, Player: [player], Flag: [flag], ")
|
|
continue
|
|
if(player.mind && job.title in player.mind.restricted_roles)
|
|
JobDebug("FOC incompatible with antagonist role, Player: [player]")
|
|
continue
|
|
// yogs start - Donor features, quiet round
|
|
if(((job.title in GLOB.command_positions) || (job.title in GLOB.nonhuman_positions)) && (player.client.prefs.toggles & QUIET_ROUND))
|
|
JobDebug("FOC quiet check failed, Player: [player]")
|
|
continue
|
|
// yogs end
|
|
if(player.client.prefs.GetJobDepartment(job, level) & job.flag)
|
|
JobDebug("FOC pass, Player: [player], Level:[level]")
|
|
candidates += player
|
|
return candidates
|
|
|
|
/datum/controller/subsystem/job/proc/GiveRandomJob(mob/dead/new_player/player)
|
|
JobDebug("GRJ Giving random job, Player: [player]")
|
|
. = FALSE
|
|
for(var/datum/job/job in shuffle(occupations))
|
|
if(!job)
|
|
continue
|
|
|
|
if(istype(job, GetJob(SSjob.overflow_role))) // We don't want to give him assistant, that's boring!
|
|
continue
|
|
|
|
if(job.title in GLOB.command_positions) //If you want a command position, select it!
|
|
continue
|
|
|
|
if(is_banned_from(player.ckey, job.title) || QDELETED(player))
|
|
if(QDELETED(player))
|
|
JobDebug("GRJ isbanned failed, Player deleted")
|
|
break
|
|
JobDebug("GRJ isbanned failed, Player: [player], Job: [job.title]")
|
|
continue
|
|
|
|
if(!job.player_old_enough(player.client))
|
|
JobDebug("GRJ player not old enough, Player: [player]")
|
|
continue
|
|
|
|
if(job.required_playtime_remaining(player.client))
|
|
JobDebug("GRJ player not enough xp, Player: [player]")
|
|
continue
|
|
|
|
if(player.mind && job.title in player.mind.restricted_roles)
|
|
JobDebug("GRJ incompatible with antagonist role, Player: [player], Job: [job.title]")
|
|
continue
|
|
|
|
if((job.current_positions < job.spawn_positions) || job.spawn_positions == -1)
|
|
JobDebug("GRJ Random job given, Player: [player], Job: [job]")
|
|
if(AssignRole(player, job.title))
|
|
return TRUE
|
|
|
|
/datum/controller/subsystem/job/proc/ResetOccupations()
|
|
JobDebug("Occupations reset.")
|
|
for(var/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 = 1 to 3)
|
|
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 = 1 to 3)
|
|
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()
|
|
//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.mind && !player.mind.assigned_role)
|
|
unassigned += player
|
|
|
|
initial_players_to_assign = unassigned.len
|
|
|
|
JobDebug("DO, Len: [unassigned.len]")
|
|
if(unassigned.len == 0)
|
|
return 0
|
|
|
|
//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, 3)
|
|
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 = 1 to 3)
|
|
//Check the head jobs first each level
|
|
CheckHeadPositions(level)
|
|
|
|
// Loop through all unassigned players
|
|
for(var/mob/dead/new_player/player in unassigned)
|
|
if(PopcapReached())
|
|
RejectPlayer(player)
|
|
|
|
// Loop through all jobs
|
|
for(var/datum/job/job in shuffledoccupations) // SHUFFLE ME BABY
|
|
if(!job)
|
|
continue
|
|
|
|
if(is_banned_from(player.ckey, job.title))
|
|
JobDebug("DO isbanned failed, Player: [player], Job:[job.title]")
|
|
continue
|
|
|
|
if(QDELETED(player))
|
|
JobDebug("DO player deleted during job ban check")
|
|
break
|
|
|
|
if(!job.player_old_enough(player.client))
|
|
JobDebug("DO player not old enough, Player: [player], Job:[job.title]")
|
|
continue
|
|
|
|
if(job.required_playtime_remaining(player.client))
|
|
JobDebug("DO player not enough xp, Player: [player], Job:[job.title]")
|
|
continue
|
|
|
|
if(player.mind && job.title in player.mind.restricted_roles)
|
|
JobDebug("DO incompatible with antagonist role, Player: [player], Job:[job.title]")
|
|
continue
|
|
|
|
// If the player wants that job on this level, then try give it to him.
|
|
if(player.client.prefs.GetJobDepartment(job, level) & job.flag)
|
|
// 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))
|
|
AssignRole(player, SSjob.overflow_role) //If everything is already filled, make them an assistant
|
|
|
|
return 1
|
|
|
|
//We couldn't find a job from prefs for this guy.
|
|
/datum/controller/subsystem/job/proc/HandleUnassigned(mob/dead/new_player/player)
|
|
if(PopcapReached())
|
|
RejectPlayer(player)
|
|
else if(player.client.prefs.joblessrole == BEOVERFLOW)
|
|
var/allowed_to_be_a_loser = !is_banned_from(player.ckey, SSjob.overflow_role)
|
|
if(QDELETED(player) || !allowed_to_be_a_loser)
|
|
RejectPlayer(player)
|
|
else
|
|
if(!AssignRole(player, SSjob.overflow_role))
|
|
RejectPlayer(player)
|
|
else if(player.client.prefs.joblessrole == BERANDOMJOB)
|
|
if(!GiveRandomJob(player))
|
|
RejectPlayer(player)
|
|
else if(player.client.prefs.joblessrole == RETURNTOLOBBY)
|
|
RejectPlayer(player)
|
|
else //Something gone wrong if we got here.
|
|
var/message = "DO: [player] fell through handling unassigned"
|
|
JobDebug(message)
|
|
log_game(message)
|
|
message_admins(message)
|
|
RejectPlayer(player)
|
|
//Gives the player the stuff he should have with his rank
|
|
/datum/controller/subsystem/job/proc/EquipRank(mob/M, rank, joined_late = FALSE)
|
|
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.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)
|
|
SendToAtom(H, S, buckle = 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)
|
|
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()
|
|
|
|
to_chat(M, "<b>You are the [rank].</b>")
|
|
if(job)
|
|
to_chat(M, "<b>As the [rank] you answer directly to [job.supervisors]. Special circumstances may change this.</b>")
|
|
job.radio_help_message(M)
|
|
if(job.req_admin_notify)
|
|
to_chat(M, "<b>You are playing a job that is important for Game Progression. If you have to disconnect, please notify the admins via adminhelp.</b>")
|
|
if(CONFIG_GET(number/minimal_access_threshold))
|
|
to_chat(M, "<FONT color='blue'><B>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.</B></font>")
|
|
if(ishuman(H))
|
|
var/mob/living/carbon/human/wageslave = H
|
|
to_chat(M, "<b>Your account ID is [wageslave.account_id].</b>")
|
|
H.add_memory("Your account ID is [wageslave.account_id].")
|
|
if(job && H)
|
|
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.
|
|
|
|
log_game("[H.real_name]/[M.client.ckey] joined the round as [H.job].") //yogs - Job logging
|
|
job.give_donor_stuff(H, M) // yogs - Donor Features
|
|
|
|
return H
|
|
|
|
|
|
/datum/controller/subsystem/job/proc/setup_officer_positions()
|
|
var/datum/job/J = SSjob.GetJob("Security Officer")
|
|
if(!J)
|
|
throw EXCEPTION("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(is_banned_from(player.ckey, job.title) || QDELETED(player))
|
|
banned++
|
|
continue
|
|
if(!job.player_old_enough(player.client))
|
|
young++
|
|
continue
|
|
if(job.required_playtime_remaining(player.client))
|
|
young++
|
|
continue
|
|
if(player.client.prefs.GetJobDepartment(job, 1) & job.flag)
|
|
high++
|
|
else if(player.client.prefs.GetJobDepartment(job, 2) & job.flag)
|
|
medium++
|
|
else if(player.client.prefs.GetJobDepartment(job, 3) & job.flag)
|
|
low++
|
|
else never++ //not selected
|
|
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, "<b>You have failed to qualify for any job you desired.</b>")
|
|
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
|
|
|
|
/datum/controller/subsystem/job/proc/SendToAtom(mob/M, atom/A, buckle)
|
|
if(buckle && isliving(M) && istype(A, /obj/structure/chair))
|
|
var/obj/structure/chair/C = A
|
|
if(C.buckle_mob(M, FALSE, FALSE))
|
|
return
|
|
M.forceMove(get_turf(A))
|
|
|
|
/datum/controller/subsystem/job/proc/SendToLateJoin(mob/M, buckle = TRUE)
|
|
if(M.mind && M.mind.assigned_role && length(GLOB.jobspawn_overrides[M.mind.assigned_role])) //We're doing something special today.
|
|
SendToAtom(M,pick(GLOB.jobspawn_overrides[M.mind.assigned_role]),FALSE)
|
|
return
|
|
|
|
if(latejoin_trackers.len)
|
|
SendToAtom(M, pick(latejoin_trackers), buckle)
|
|
else
|
|
//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)
|
|
SendToAtom(M, C, buckle)
|
|
return
|
|
else //last hurrah
|
|
var/list/avail = list()
|
|
for(var/turf/T in A)
|
|
if(!is_blocked_turf(T, TRUE))
|
|
avail += T
|
|
if(avail.len)
|
|
SendToAtom(M, pick(avail), 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))
|
|
SendToAtom(M, T, FALSE)
|
|
return
|
|
//last chance, pick ANY spot on arrivals and dump em
|
|
SendToAtom(M, arrivals_turfs[1], FALSE)
|
|
else
|
|
var/msg = "Unable to send mob [M] to late join!"
|
|
message_admins(msg)
|
|
CRASH(msg)
|
|
|
|
|
|
///////////////////////////////////
|
|
//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)
|