var/global/datum/controller/occupations/job_master var/global/alt_job_limit = 0 //list of alternate jobs available for new hires #define FREE_ASSISTANTS 2 // The logic requires a shift of 1. The technical reason is that the way it is written, it boils to if (0 > 0) {"reject the assistants"}. Unfortunately, 0 is not > 0. #define FREE_ASSISTANTS_BRUT (FREE_ASSISTANTS-1) /datum/controller/occupations //List of all jobs var/list/occupations = list() //Players who need jobs var/list/unassigned = list() //Debug info var/list/job_debug = list() var/list/crystal_ball = list() //This should be an assoc. list. Job = # of players ready. Configured by predict_manifest() in obj.dm var/priority_jobs_remaining = 3 //Limit on how many prioritized jobs can be had at once. var/list/labor_consoles = list() var/list/assistant_second_chance = list() var/alt_database_active var/list/alternates = list() /datum/controller/occupations/proc/SetupOccupations(var/faction = "Station") occupations = list() var/list/all_jobs = typesof(/datum/job) if(!all_jobs.len) to_chat(world, "Error setting up jobs, no job datums found") return 0 for(var/J in all_jobs) var/datum/job/job = new J() if(!job) continue if(job.faction != faction) continue if(istype(job,/datum/job/alternate)) alternates += job continue if(job.must_be_map_enabled) if(!map) continue if(!map.enabled_jobs.Find(job.type)) continue if(map.disabled_jobs.Find(job.type)) continue occupations += job return 1 /datum/controller/occupations/proc/Debug(var/text) if(!Debug2) return 0 job_debug.Add(text) return 1 /datum/controller/occupations/proc/GetJob(var/rank) RETURN_TYPE(/datum/job) var/list/combined_occupations = list() combined_occupations.Add(occupations) if(alt_database_active) combined_occupations.Add(alternates) if(!rank) return null for(var/datum/job/J in combined_occupations) if(!J) continue if(J.title == rank) return J return null /datum/controller/occupations/proc/GetPlayerAltTitle(mob/new_player/player, rank) return player.client.prefs.GetPlayerAltTitle(GetJob(rank)) /datum/controller/occupations/proc/AssignRole(var/mob/new_player/player, var/rank, var/latejoin = 0, var/pref_level = 5) // We assume we got the job we wanted (latejoin, etc). Debug("Running AR, Player: [player], Rank: [rank], LJ: [latejoin]") if(player && player.mind && rank) var/datum/job/job = GetJob(rank) if(!job) return 0 if(jobban_isbanned(player, rank)) return 0 if(!job.player_old_enough(player.client)) return 0 var/position_limit = job.get_total_positions() if(!latejoin) position_limit = job.spawn_positions if((job.current_positions < position_limit) || position_limit == -1) if(alt_database_active && (total_alt_positions < alt_job_limit) && latejoin) //Labor console database has been hacked; Centcomm is sending the wrong employees! var/altrank = pick(alternate_positions) if(altjobprompt(altrank,rank,player)) rank = altrank Debug("[player] is being assigned non-standard job as the alternate jobs database is installed.") Debug("Player: [player] is now Rank: [rank], JCP:[total_alt_positions], JPL:[alt_job_limit]") total_alt_positions++ else Debug("Player: [player] is now Rank: [rank], JCP:[job.current_positions], JPL:[position_limit]") player.mind.assigned_role = rank player.mind.job_priority = pref_level player.mind.role_alt_title = GetPlayerAltTitle(player, rank) unassigned -= player job.current_positions++ for(var/obj/machinery/computer/labor/L in labor_consoles) L.updateUsrDialog() return 1 Debug("AR has failed, Player: [player], Rank: [rank]") return 0 /datum/controller/occupations/proc/UnassignRole(var/mob/new_player/player) Debug("Unassigning role for [player]") var/datum/job/job = GetJob(player.mind.assigned_role) player.mind.assigned_role = "" player.mind.role_alt_title = "" job.current_positions-- for(var/obj/machinery/computer/labor/L in labor_consoles) L.updateUsrDialog() /datum/controller/occupations/proc/FreeRole(var/rank, mob/user) //making additional slot on the fly var/datum/job/job = GetJob(rank) if(job && job.current_positions >= job.get_total_positions()) job.bump_position_limit() if(user) log_admin("[key_name(user)] has freed up a slot for the [rank] job.") message_admins("[key_name_admin(user)] has freed up a slot for the [rank] job.") for(var/mob/new_player/player in player_list) to_chat(player, "The [rank] job is now available!") return 1 return 0 /datum/controller/occupations/proc/CloseRole(var/rank, mob/user) //eliminating xtra_positions var/datum/job/job = GetJob(rank) if(job && job.current_positions < job.get_total_positions() && job.xtra_positions > 0) job.remove_xtra_position() if(user) log_admin("[key_name(user)] has closed a slot for the [rank] job.") message_admins("[key_name_admin(user)] has closed a slot for the [rank] job.") for(var/mob/new_player/player in player_list) to_chat(player, "The [rank] job is now closed.") return 1 return 0 /datum/controller/occupations/proc/CheckPriorityFulfilled(var/rank) var/datum/job/job = GetJob(rank) if(job.current_positions >= job.get_total_positions() && job.priority) job_master.TogglePriority(rank) /datum/controller/occupations/proc/TogglePriority(var/rank, mob/user) var/datum/job/job = GetJob(rank) if(job) if(job.priority) job.priority = FALSE priority_jobs_remaining++ if(!job.head_position) DePrioritzeDeparmentHead(job) else if(priority_jobs_remaining < 1) return 0 job.priority = TRUE priority_jobs_remaining-- if(!job.head_position) PrioritzeDeparmentHead(job) if(user) log_admin("[key_name(user)] has set the priority of the [rank] job to [job.priority].") message_admins("[key_name_admin(user)] has set the priority of the [rank] job to [job.priority].") for(var/mob/new_player/player in player_list) to_chat(player, "The [rank] job is [job.priority ? "now highly requested!" : "no longer highly requested."]") return 1 return 0 /datum/controller/occupations/proc/PrioritzeDeparmentHead(var/datum/job/job) if(job.department == "Civilian") return var/datum/job/head = locate(job.department_head) in job_master.occupations head.department_prioritized = TRUE /datum/controller/occupations/proc/DePrioritzeDeparmentHead(var/datum/job/job) var/datum/job/head = locate(job.department_head) in job_master.occupations var/list/remaining_prioritized_jobs = job_master.GetPrioritizedJobs() for(var/datum/job/J in remaining_prioritized_jobs) // If there is still a job from that department prioritized if(J.department == job.department) return head.department_prioritized = FALSE /datum/controller/occupations/proc/IsJobPrioritized(var/rank) var/datum/job/job = GetJob(rank) if(job) return job.priority return 0 /datum/controller/occupations/proc/GetPrioritizedJobs() //Returns a list of job datums. . = list() for(var/datum/job/J in occupations) if(J.priority) . += J /datum/controller/occupations/proc/GetUnprioritizedJobs() //Returns a list of job datums. . = list() for(var/datum/job/J in occupations) if(!J.priority) . += J /datum/controller/occupations/proc/FindOccupationCandidates(datum/job/job, level, flag) Debug("Running FOC, Job: [job], Level: [level], Flag: [flag]") var/list/candidates = list() for(var/mob/new_player/player in unassigned) if(jobban_isbanned(player, job.title)) Debug("FOC isbanned failed, Player: [player]") continue if(!job.player_old_enough(player.client)) Debug("FOC player not old enough, Player: [player]") continue if(flag && !player.client.desires_role(job.title)) Debug("FOC flag failed, Player: [player], Flag: [flag], ") continue var/list/jobs = player.client.prefs.get_pref(/datum/preference_setting/assoc_list_setting/jobs) if(jobs[job.title] == level) Debug("FOC pass, Player: [player], Level:[level]") candidates += player return candidates /datum/controller/occupations/proc/GiveRandomJob(var/mob/new_player/player) Debug("GRJ Giving random job, Player: [player]") for(var/datum/job/job in shuffle(occupations)) if(!job) continue if(job.no_random_roll) continue if(job.title in command_positions) //If you want a command position, select it! continue if(jobban_isbanned(player, job.title)) Debug("GRJ isbanned failed, Player: [player], Job: [job.title]") continue if(!job.player_old_enough(player.client)) Debug("GRJ player not old enough, Player: [player]") continue if((job.current_positions < job.spawn_positions) || job.spawn_positions == -1) Debug("GRJ Random job given, Player: [player], Job: [job]") AssignRole(player, job.title) unassigned -= player break /datum/controller/occupations/proc/ResetOccupations() for(var/mob/new_player/player in player_list) if((player) && (player.mind)) player.mind.assigned_role = null player.mind.special_role = null SetupOccupations() unassigned = list() return ///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 /datum/controller/occupations/proc/CheckHeadPositions(var/level) for(var/command_position in command_positions) var/datum/job/job = GetJob(command_position) if(!job) continue var/list/candidates = FindOccupationCandidates(job, level) if(!candidates.len) continue var/mob/new_player/candidate = pick(candidates) AssignRole(candidate, command_position) return /** Proc GetSecurityCount * gets the current number of 'security' roles currently assigned to the station **/ /datum/controller/occupations/proc/GetSecurityCount() var/datum/job/officer = job_master.GetJob("Security Officer") var/datum/job/warden = job_master.GetJob("Warden") var/datum/job/hos = job_master.GetJob("Head of Security") var/datum/job/detective = job_master.GetJob("Detective") return (officer.current_positions + warden.current_positions + hos.current_positions + detective.current_positions) /** 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/occupations/proc/DivideOccupations() //Setup new player list and get the jobs list Debug("Running DO") //Holder for Triumvirate is stored in the ticker, this just processes it if(ticker) for(var/datum/job/ai/A in occupations) if(ticker.triai) A.spawn_positions = 3 for(var/datum/job/cyborg/C in occupations) if(ticker.triai) C.spawn_positions = 3 //Get the players who are ready for(var/mob/new_player/player in player_list) if(player.ready && player.mind && !player.mind.assigned_role) unassigned += player if(player.client.prefs.get_pref(/datum/preference_setting/toggle/randomslot)) player.client.prefs.random_character_sqlite(player, player.ckey) Debug("DO, Len: [unassigned.len]") if(unassigned.len == 0) return 0 //Shuffle players and jobs unassigned = shuffle(unassigned) HandleFeedbackGathering() //Other jobs are now checked Debug("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 = 3 to 1 step -1) //Check the head jobs first each level CheckHeadPositions(level) // Loop through all unassigned players for(var/mob/new_player/player in unassigned) if(player.client.prefs.get_pref(/datum/preference_setting/enum/alternate_option) == GET_EMPTY_JOB) continue //This player doesn't want to share a job title. We need to deal with them last. // Loop through all jobs for(var/datum/job/job in shuffledoccupations) if(TryAssignJob(player,level,job)) unassigned -= player break // 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/new_player/player in unassigned) if(player.client.prefs.get_pref(/datum/preference_setting/enum/alternate_option) == GET_RANDOM_JOB) GiveRandomJob(player) Debug("DO, Standard Check end") // Rejoice, for you have been given a second chance to be a greytider. Debug("DO, Running AC2") var/count = GetSecurityCount() var/datum/job/master_assistant = GetJob("Assistant") // For those who wanted to be assistant if their preferences were filled, here you go. for(var/mob/new_player/player in unassigned) if(player.client.prefs.get_pref(/datum/preference_setting/enum/alternate_option) == BE_ASSISTANT) if(config.assistantlimit) if(master_assistant.current_positions-FREE_ASSISTANTS_BRUT > (config.assistantratio * count)) // Not enough sec... if(count < 5) // if theres more than 5 security on the station just let assistants join regardless, they should be able to handle the tide ; this block then doesn't get checked. to_chat(player, "You have been returned to lobby because there's not enough security to make you an assistant.") player.ready = 0 unassigned -= player continue if(master_assistant.species_blacklist.len && master_assistant.species_blacklist.Find(player.client.prefs.get_pref(/datum/preference_setting/string/species))) to_chat(player, "You have been returned to lobby because your species is blacklisted from assistant.") player.ready = 0 unassigned -= player continue //no, you can't evade the blacklist just by not being picked for your available jobs Debug("AC2 Assistant located, Player: [player]") AssignRole(player, "Assistant") master_assistant = GetJob("Assistant") // Those that got assigned a role, but had assistant higher. var/security_jobs = list( /datum/job/hos, /datum/job/warden, /datum/job/detective, /datum/job/officer) for (var/mob/new_player/player in shuffle(player_list)) if (player.ckey in assistant_second_chance) var/secmod = 0 Debug("AC3: [player] running the second chance for assistant") //if they are already a security officer, add a modifier to the number of secoffs to see if they qualify for assistant var/datum/job/oldjob = GetJob(player.mind.assigned_role) for(var/secjob in security_jobs) if(istype(oldjob, secjob)) Debug("AC3: [player] is a security officer of some sort, noting in case of the assistant cap.") secmod = 1 //and if there's enough security officers (assuming you lose your current job) to let you be an assistant... if(!(master_assistant.current_positions-FREE_ASSISTANTS_BRUT > (config.assistantratio * (count-secmod))) || ((count-secmod) >= 5)) //No need to check assistant prefs, if you're here then they're on the second_chance list Debug("AC3: [player] got made an assistant as a second chance.") UnassignRole(player) //This may change the number of security players, so we have to update the list of secoffs if(secmod) count = GetSecurityCount() AssignRole(player, "Assistant") master_assistant = GetJob("Assistant") else Debug("AC3: [player] failed the second chance assistant lottery.") //Final pass - first deal with the empty job group, otherwise send any leftovers to the lobby final_pass: //this is a loop label for(var/mob/new_player/player in unassigned) if(player.client.prefs.get_pref(/datum/preference_setting/enum/alternate_option) == GET_EMPTY_JOB) for(var/level = 3 to 1 step -1) for(var/datum/job/job in shuffledoccupations) if(job.current_positions) //already someone in this job title continue if(TryAssignJob(player,level,job)) unassigned -= player continue final_pass //move on to the next player entirely to_chat(player, "You have been returned to lobby due to your job preferences being filled.") player.ready = 0 unassigned -= player return 1 /datum/controller/occupations/proc/TryAssignJob(var/mob/new_player/player, var/level, var/datum/job/job) if(!job || job.is_disabled()) return FALSE if(jobban_isbanned(player, job.title)) Debug("DO isbanned failed, Player: [player], Job:[job.title]") return FALSE if(!job.player_old_enough(player.client)) Debug("DO player not old enough, Player: [player], Job:[job.title]") return FALSE // If the player wants that job on this level, then try give it to him. var/list/jobs = player.client.prefs.get_pref(/datum/preference_setting/assoc_list_setting/jobs) if(jobs[job.title] == level) if (job.title == "Assistant" && !CheckAssistantCount(player, level)) return FALSE // If the job isn't filled if((job.current_positions < job.spawn_positions) || job.spawn_positions == -1) Debug("DO pass, Player: [player], Level:[level], Job:[job.title]") AssignRole(player, job.title, pref_level = level) return TRUE // -- Snowflaked proc which can be adjusted to more jobs than assistants if needed. /datum/controller/occupations/proc/CheckAssistantCount(var/mob/new_player/player, var/level) //People who wants to be assistants, sure, go on. var/count = GetSecurityCount() Debug("DO, Running Assistant Check 1 for [player]") var/datum/job/master_assistant = GetJob("Assistant") var/not_enough_sec = (master_assistant.current_positions - FREE_ASSISTANTS_BRUT) > (config.assistantratio * count) if(not_enough_sec && (count < 5)) Debug("AC1 failed, not enough sec.") // Does he want anything else...? var/list/jobs = player.client.prefs.get_pref(/datum/preference_setting/assoc_list_setting/jobs) for (var/datum/job/J in occupations) if (jobs[J.title] == level) Debug("AC1 failed, but other job slots for [player]. Adding them to the list of backup assistant slots.") assistant_second_chance[player.ckey] = level return FALSE // If this failed, then we don't want anything else, so we'll for the second assistant check. return FALSE Debug("DO, AC1 end") return TRUE /datum/controller/occupations/proc/PostJobSetup(var/mob/living/carbon/human/H) if(!(H && H.mind && H.mind.assigned_role)) return 0 var/joined_late = ticker.current_state == GAME_STATE_PLAYING ? TRUE : FALSE var/rank = H.mind.assigned_role var/datum/job/job = GetJob(rank) if(job && !job.no_starting_money) //give them an account in the station database // Total between $200 and $500 var/balance_bank = rand(100,250) var/balance_wallet = rand(100,250) var/bank_pref_number = H.client.prefs.get_pref(/datum/preference_setting/enum/bank_security) var/bank_pref = bank_security_num2text(bank_pref_number) var/pref_wage_ratio = H.client.prefs.get_pref(/datum/preference_setting/numerical/wage_ratio) if(centcomm_account_db) var/wage = job.get_wage() var/datum/money_account/M = create_account(H.real_name, balance_bank, null, wage_payout = wage, security_pref = bank_pref_number, ratio_pref = pref_wage_ratio) if (joined_late) latejoiner_allowance += wage + round(wage/10) else station_allowance += wage + round(wage/10)//overhead of 10% if(H.mind) var/remembered_info = "" remembered_info += "Your account number is: #[M.account_number]
" remembered_info += "Your account pin is: [M.remote_access_pin]
" remembered_info += "Your bank account funds are: $[balance_bank]
" remembered_info += "Your virtual wallet funds are: $[balance_wallet]
" if(M.transaction_log.len) var/datum/transaction/T = M.transaction_log[1] remembered_info += "Your account was created: [T.time], [T.date] at [T.source_terminal]
" H.mind.store_memory(remembered_info, category=MIND_MEMORY_GENERAL, forced=TRUE) H.mind.initial_account = M H.mind.initial_wallet_funds = balance_wallet // If they're head, give them the account info for their department if(H.mind && job.head_position) var/remembered_info = "" var/datum/money_account/department_account = department_accounts[job.department] if(department_account) remembered_info += "Your department's account number is: #[department_account.account_number]
" remembered_info += "Your department's account pin is: [department_account.remote_access_pin]
" remembered_info += "Your department's account funds are: $[department_account.money]
" H.mind.store_memory(remembered_info, category=MIND_MEMORY_GENERAL, forced=TRUE) spawn() to_chat(H, "Your bank account number is: [M.account_number], your bank account pin is: [M.remote_access_pin]") to_chat(H, "Your virtual wallet funds are: $[balance_wallet], your bank account funds are: $[balance_bank]") to_chat(H, "Your bank account security level is set to: [bank_pref]") var/alt_title = null H.job = rank if(H.mind) H.mind.assigned_role = rank alt_title = H.mind.role_alt_title if(job) job.introduce(H, (alt_title ? alt_title : rank)) else to_chat(H, "You are the [alt_title ? alt_title : rank]. Special circumstances may change this.") return 1 /datum/controller/occupations/proc/LoadJobs(jobsfile) //ran during round setup, reads info from jobs.txt -- Urist if(!config.load_jobs_from_txt) return 0 var/list/jobEntries = file2list(jobsfile) for(var/job in jobEntries) if(!job) continue job = trim(job) if (!length(job)) continue var/pos = findtext(job, "=") var/name = null var/value = null if(pos) name = copytext(job, 1, pos) value = copytext(job, pos + 1) else continue if(name && value) var/datum/job/J = GetJob(name) if(!J) continue J.set_total_positions(value) J.spawn_positions = text2num(value) if(name == "AI" || name == "Cyborg" || name == "Mobile MMI" || name == "Trader")//I dont like this here but it will do for now J.set_total_positions(0) return 1 /datum/controller/occupations/proc/HandleFeedbackGathering() for(var/datum/job/job in occupations) var/tmp_str = "|[job.title]|" var/level1 = 0 //high var/level2 = 0 //medium var/level3 = 0 //low var/level4 = 0 //never var/level5 = 0 //banned var/level6 = 0 //account too young for(var/mob/new_player/player in player_list) if(!(player.ready && player.mind && !player.mind.assigned_role)) continue //This player is not ready if(jobban_isbanned(player, job.title)) level5++ continue if(!job.player_old_enough(player.client)) level6++ continue var/list/jobs = player.client.prefs.get_pref(/datum/preference_setting/assoc_list_setting/jobs) switch(jobs[job.title]) if(JOB_PREF_LOW) level1++ if(JOB_PREF_MED) level2++ if(JOB_PREF_HIGH) level3++ else level4++ //not selected tmp_str += "HIGH=[level1]|MEDIUM=[level2]|LOW=[level3]|NEVER=[level4]|BANNED=[level5]|YOUNG=[level6]|-" feedback_add_details("job_preferences",tmp_str) /datum/controller/occupations/proc/altjobprompt(var/newrank,var/oldrank,var/mob/user) var/turf/oldloc = get_turf(user) user.forceMove(null) if(alert(user,"Central Command had a mix-up and is attempting to send you to the station as \an [newrank]! Would you like to correct them?",,"No - play as [newrank]","Yes - play as [oldrank]") == "No - play as [newrank]") return 1 user.forceMove(oldloc) message_admins("[user.key] has opted out of playing as \an [newrank].") return 0