diff --git a/code/__DEFINES/subsystems.dm b/code/__DEFINES/subsystems.dm
index 287ef02a02a..b6253ef6c4c 100644
--- a/code/__DEFINES/subsystems.dm
+++ b/code/__DEFINES/subsystems.dm
@@ -57,27 +57,28 @@ var/global/list/runlevel_flags = list(RUNLEVEL_LOBBY, RUNLEVEL_SETUP, RUNLEVEL_G
// Subsystem init_order, from highest priority to lowest priority
// Subsystems shutdown in the reverse of the order they initialize in
// The numbers just define the ordering, they are meaningless otherwise.
-#define INIT_ORDER_FAIL2TOPIC 101
-#define INIT_ORDER_SOUNDS 95
-#define INIT_ORDER_GARBAGE 70
-#define INIT_ORDER_TIMER 60
-#define INIT_ORDER_INSTRUMENTS 50
-#define INIT_ORDER_MAPPING 20 // VOREStation Edit
+#define INIT_ORDER_FAIL2TOPIC 101
+#define INIT_ORDER_SOUNDS 95
+#define INIT_ORDER_JOBS 85
+#define INIT_ORDER_GARBAGE 70
+#define INIT_ORDER_TIMER 60
+#define INIT_ORDER_INSTRUMENTS 50
+#define INIT_ORDER_MAPPING 20 // VOREStation Edit
#define INIT_ORDER_SERVER_MAINT 17
-#define INIT_ORDER_DECALS 16
-#define INIT_ORDER_ATOMS 15
-#define INIT_ORDER_MACHINES 10
-#define INIT_ORDER_SHUTTLES 3
-#define INIT_ORDER_DEFAULT 0
-#define INIT_ORDER_LIGHTING 0
-#define INIT_ORDER_AIR -1
-#define INIT_ORDER_PLANETS -4
-#define INIT_ORDER_HOLOMAPS -5
-#define INIT_ORDER_OVERLAY -6
-#define INIT_ORDER_XENOARCH -20
-#define INIT_ORDER_CIRCUIT -21
-#define INIT_ORDER_CHEMISTRY 18
-#define INIT_ORDER_AI -22
+#define INIT_ORDER_DECALS 16
+#define INIT_ORDER_ATOMS 15
+#define INIT_ORDER_MACHINES 10
+#define INIT_ORDER_SHUTTLES 3
+#define INIT_ORDER_DEFAULT 0
+#define INIT_ORDER_LIGHTING 0
+#define INIT_ORDER_AIR -1
+#define INIT_ORDER_PLANETS -4
+#define INIT_ORDER_HOLOMAPS -5
+#define INIT_ORDER_OVERLAY -6
+#define INIT_ORDER_XENOARCH -20
+#define INIT_ORDER_CIRCUIT -21
+#define INIT_ORDER_CHEMISTRY 18
+#define INIT_ORDER_AI -22
// Subsystem fire priority, from lowest to highest priority
diff --git a/code/controllers/subsystems/emergency_shuttle.dm b/code/controllers/subsystems/emergency_shuttle.dm
index aaeb8343c60..a4d8e20667e 100644
--- a/code/controllers/subsystems/emergency_shuttle.dm
+++ b/code/controllers/subsystems/emergency_shuttle.dm
@@ -1,3 +1,5 @@
+
+
/datum/controller/process/emergencyShuttle/setup()
name = "emergency shuttle"
schedule_interval = 20 // every 2 seconds
diff --git a/code/controllers/subsystems/game_master.dm b/code/controllers/subsystems/game_master.dm
index 7f89f3ab135..7ae19937922 100644
--- a/code/controllers/subsystems/game_master.dm
+++ b/code/controllers/subsystems/game_master.dm
@@ -1,6 +1,157 @@
-/datum/controller/process/game_master/setup()
- name = "\improper GM controller"
- schedule_interval = 600 // every 60 seconds
+// This is a sort of successor to the various event systems created over the years. It is designed to be just a tad smarter than the
+// previous ones, checking various things like player count, department size and composition, individual player activity,
+// individual player (IC) skill, and such, in order to try to choose the best actions to take in order to add spice or variety to
+// the round.
+SUBSYSTEM_DEF(gamemaster)
+ name = "Game Master"
+ wait = 600
+ var/suspended = TRUE // If true, it will not do anything.
+ var/ignore_time_restrictions = FALSE// Useful for debugging without needing to wait 20 minutes each time.
+ var/list/available_actions = list() // A list of 'actions' that the GM has access to, to spice up a round, such as events.
+ var/danger = 0 // The GM's best guess at how chaotic the round is. High danger makes it hold back.
+ var/staleness = -20 // Determines liklihood of the GM doing something, increases over time.
+ var/danger_modifier = 1 // Multiplier for how much 'danger' is accumulated.
+ var/staleness_modifier = 1 // Ditto. Higher numbers generally result in more events occuring in a round.
+ var/ticks_completed = 0 // Counts amount of ticks completed. Note that this ticks once a minute.
+ var/next_action = 0 // Minimum amount of time of nothingness until the GM can pick something again.
+ var/last_department_used = null // If an event was done for a specific department, it is written here, so it doesn't do it again.
+
+/datum/controller/subsystem/gamemaster/Initialize()
+ available_actions = init_subtypes(/datum/gm_action)
+ for(var/datum/gm_action/action in available_actions)
+ action.gm = src
+
+ var/config_setup_delay = TRUE
+ spawn(0)
+ while(config_setup_delay)
+ if(config)
+ config_setup_delay = FALSE
+ if(config_legacy.enable_game_master)
+ suspended = FALSE
+ else
+ sleep(30 SECONDS)
+ return ..()
+
+/datum/controller/subsystem/gamemaster/fire(resumed)
+ if(ticker && ticker.current_state == GAME_STATE_PLAYING && !suspended)
+ adjust_staleness(1)
+ adjust_danger(-1)
+ ticks_completed++
+
+ var/global_afk = metric.assess_all_living_mobs()
+ global_afk -= 100
+ global_afk = abs(global_afk)
+ global_afk = round(global_afk / 100, 0.1)
+ adjust_staleness(global_afk) // Staleness increases faster if more people are less active.
+
+ if(world.time < next_action && prob(staleness * 2) )
+ log_debug("Game Master going to start something.")
+ start_action()
+
+// This is run before committing to an action/event.
+/datum/controller/subsystem/gamemaster/proc/pre_action_checks()
+ if(!ticker || ticker.current_state != GAME_STATE_PLAYING)
+ log_debug("Game Master unable to start event: Ticker is nonexistant, or the game is not ongoing.")
+ return FALSE
+ if(suspended)
+ return FALSE
+ if(ignore_time_restrictions)
+ return TRUE
+ // Last minute antagging is bad for humans to do, so the GM will respect the start and end of the round.
+ var/mills = round_duration_in_ticks
+ var/mins = round((mills % 36000) / 600)
+ var/hours = round(mills / 36000)
+
+ if(hours < 1 && mins <= 20) // Don't do anything for the first twenty minutes of the round.
+ log_debug("Game Master unable to start event: It is too early.")
+ return FALSE
+ if(hours >= 2 && mins >= 40) // Don't do anything in the last twenty minutes of the round, as well.
+ log_debug("Game Master unable to start event: It is too late.")
+ return FALSE
+ return TRUE
+
+/datum/controller/subsystem/gamemaster/proc/start_action()
+ if(!pre_action_checks()) // Make sure we're not doing last minute events, or early events.
+ return
+ log_debug("Game Master now starting action decision.")
+ var/list/most_active_departments = metric.assess_all_departments(3, list(last_department_used))
+ var/list/best_actions = decide_best_action(most_active_departments)
+
+ if(best_actions && best_actions.len)
+ var/list/weighted_actions = list()
+ for(var/datum/gm_action/action in best_actions)
+ if(action.chaotic > danger)
+ continue // We skip dangerous events when bad stuff is already occuring.
+ weighted_actions[action] = action.get_weight()
+
+ var/datum/gm_action/choice = pickweight(weighted_actions)
+ if(choice)
+ log_debug("[choice.name] was chosen by the Game Master, and is now being ran.")
+ run_action(choice)
+
+/datum/controller/subsystem/gamemaster/proc/run_action(var/datum/gm_action/action)
+ action.set_up()
+ action.start()
+ action.announce()
+ if(action.chaotic)
+ danger += action.chaotic
+ if(action.length)
+ spawn(action.length)
+ action.end()
+ next_action = world.time + rand(15 MINUTES, 30 MINUTES)
+ last_department_used = action.departments[1]
+
+
+/datum/controller/subsystem/gamemaster/proc/decide_best_action(var/list/most_active_departments)
+ if(!most_active_departments.len) // Server's empty?
+ log_debug("Game Master failed to find any active departments.")
+ return list()
+
+ var/list/best_actions = list() // List of actions which involve the most active departments.
+ if(most_active_departments.len >= 2)
+ for(var/datum/gm_action/action in available_actions)
+ if(!action.enabled)
+ continue
+ // Try to incorporate an action with the top two departments first.
+ if(most_active_departments[1] in action.departments && most_active_departments[2] in action.departments)
+ best_actions.Add(action)
+ log_debug("[action.name] is being considered because both most active departments are involved.")
+
+ if(best_actions.len) // We found something for those two, let's do it.
+ return best_actions
+
+ // Otherwise we probably couldn't find something for the second highest group, so let's ignore them.
+ for(var/datum/gm_action/action in available_actions)
+ if(!action.enabled)
+ continue
+ if(most_active_departments[1] in action.departments)
+ best_actions.Add(action)
+ log_debug("[action.name] is being considered because the most active department is involved.")
+
+ if(best_actions.len) // Found something for the one guy.
+ return best_actions
+
+ // At this point we should expand our horizons.
+ for(var/datum/gm_action/action in available_actions)
+ if(!action.enabled)
+ continue
+ if(ROLE_EVERYONE in action.departments)
+ best_actions.Add(action)
+ log_debug("[action.name] is being considered because it involves everyone.")
+
+ if(best_actions.len) // Finally, perhaps?
+ return best_actions
+
+ // Just give a random event if for some reason it still can't make up its mind.
+ for(var/datum/gm_action/action in available_actions)
+ if(!action.enabled)
+ continue
+ best_actions.Add(action)
+ log_debug("[action.name] is being considered because everything else failed.")
+
+ if(best_actions.len) // Finally, perhaps?
+ return best_actions
+ else
+ log_debug("Game Master failed to find a suitable event, something very wrong is going on.")
+
-/datum/controller/process/game_master/doWork()
- game_master.process()
\ No newline at end of file
diff --git a/code/controllers/subsystems/jobs.dm b/code/controllers/subsystems/jobs.dm
index 271bf63c383..ee46e2945d9 100644
--- a/code/controllers/subsystems/jobs.dm
+++ b/code/controllers/subsystems/jobs.dm
@@ -1,673 +1,678 @@
-var/global/datum/controller/occupations/job_master
-
-#define GET_RANDOM_JOB 0
-#define BE_ASSISTANT 1
-#define RETURN_TO_LOBBY 2
-
-/datum/controller/occupations
- //List of all jobs
+SUBSYSTEM_DEF(jobs)
+ name = "Jobs"
+ flags = SS_NO_FIRE
+ init_order = INIT_ORDER_JOBS
var/list/occupations = list()
//Players who need jobs
var/list/unassigned = list()
//Debug info
var/list/job_debug = list()
+ //List of all jobs
- proc/SetupOccupations(var/faction = "Station")
- occupations = list()
- //var/list/all_jobs = typesof(/datum/job)
- var/list/all_jobs = list(/datum/job/assistant) | GLOB.using_map.allowed_jobs
- if(!all_jobs.len)
- 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
- occupations += job
- sortTim(occupations, /proc/cmp_job_datums)
+#define GET_RANDOM_JOB 0
+#define BE_ASSISTANT 1
+#define RETURN_TO_LOBBY 2
- return 1
+
+/datum/controller/subsystem/jobs/proc/SetupOccupations(var/faction = "Station")
+ occupations = list()
+ //var/list/all_jobs = typesof(/datum/job)
+ var/list/all_jobs = list(/datum/job/assistant) | GLOB.using_map.allowed_jobs
+ if(!all_jobs.len)
+ 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
+ occupations += job
+ sortTim(occupations, /proc/cmp_job_datums)
+ return 1
- proc/Debug(var/text)
- if(!Debug2) return 0
- job_debug.Add(text)
- return 1
+/datum/controller/subsystem/jobs/proc/Debug(var/text)
+ if(!Debug2)
+ return 0
+ job_debug.Add(text)
+ return 1
- proc/GetJob(var/rank)
- if(!rank) return null
- for(var/datum/job/J in occupations)
- if(!J) continue
- if(J.title == rank) return J
+/datum/controller/subsystem/jobs/proc/GetJob(var/rank)
+ if(!rank)
return null
+ for(var/datum/job/J in occupations)
+ if(!J)
+ continue
+ if(J.title == rank)
+ return J
+ return null
- proc/GetPlayerAltTitle(mob/new_player/player, rank)
- return player.client.prefs.GetPlayerAltTitle(GetJob(rank))
+/datum/controller/subsystem/jobs/proc/GetPlayerAltTitle(mob/new_player/player, rank)
+ return player.client.prefs.GetPlayerAltTitle(GetJob(rank))
- proc/AssignRole(var/mob/new_player/player, var/rank, var/latejoin = 0)
- 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(job.minimum_character_age && (player.client.prefs.age < job.minimum_character_age))
- return 0
- if(jobban_isbanned(player, rank))
- return 0
- if(!job.player_old_enough(player.client))
- return 0
- if(!is_job_whitelisted(player, rank)) //VOREStation Code
- return 0
-
- var/position_limit = job.total_positions
- if(!latejoin)
- position_limit = job.spawn_positions
- if((job.current_positions < position_limit) || position_limit == -1)
- Debug("Player: [player] is now Rank: [rank], JCP:[job.current_positions], JPL:[position_limit]")
- player.mind.assigned_role = rank
- player.mind.role_alt_title = GetPlayerAltTitle(player, rank)
- unassigned -= player
- job.current_positions++
- return 1
- Debug("AR has failed, Player: [player], Rank: [rank]")
- return 0
-
- proc/FreeRole(var/rank) //making additional slot on the fly
+/datum/controller/subsystem/jobs/proc/AssignRole(var/mob/new_player/player, var/rank, var/latejoin = 0)
+ Debug("Running AR, Player: [player], Rank: [rank], LJ: [latejoin]")
+ if(player && player.mind && rank)
var/datum/job/job = GetJob(rank)
- if(job && job.total_positions != -1)
- job.total_positions++
+ if(!job)
+ return 0
+ if(job.minimum_character_age && (player.client.prefs.age < job.minimum_character_age))
+ return 0
+ if(jobban_isbanned(player, rank))
+ return 0
+ if(!job.player_old_enough(player.client))
+ return 0
+ if(!is_job_whitelisted(player, rank)) //VOREStation Code
+ return 0
+
+ var/position_limit = job.total_positions
+ if(!latejoin)
+ position_limit = job.spawn_positions
+ if((job.current_positions < position_limit) || position_limit == -1)
+ Debug("Player: [player] is now Rank: [rank], JCP:[job.current_positions], JPL:[position_limit]")
+ player.mind.assigned_role = rank
+ player.mind.role_alt_title = GetPlayerAltTitle(player, rank)
+ unassigned -= player
+ job.current_positions++
return 1
- return 0
+ Debug("AR has failed, Player: [player], Rank: [rank]")
+ return 0
- 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(job.minimum_character_age && (player.client.prefs.age < job.minimum_character_age))
- Debug("FOC character not old enough, Player: [player]")
- continue
- //VOREStation Code Start
- if(!is_job_whitelisted(player, job.title))
- Debug("FOC is_job_whitelisted failed, Player: [player]")
- continue
- //VOREStation Code End
- if(flag && (!player.client.prefs.be_special & flag))
- Debug("FOC flag failed, Player: [player], Flag: [flag], ")
- continue
- if(player.client.prefs.GetJobDepartment(job, level) & job.flag)
- Debug("FOC pass, Player: [player], Level:[level]")
- candidates += player
- return candidates
+/datum/controller/subsystem/jobs/proc/FreeRole(var/rank) //making additional slot on the fly
+ var/datum/job/job = GetJob(rank)
+ if(job && job.total_positions != -1)
+ job.total_positions++
+ return 1
+ return 0
- 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
+/datum/controller/subsystem/jobs/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(job.minimum_character_age && (player.client.prefs.age < job.minimum_character_age))
+ Debug("FOC character not old enough, Player: [player]")
+ continue
+ //VOREStation Code Start
+ if(!is_job_whitelisted(player, job.title))
+ Debug("FOC is_job_whitelisted failed, Player: [player]")
+ continue
+ //VOREStation Code End
+ if(flag && (!player.client.prefs.be_special & flag))
+ Debug("FOC flag failed, Player: [player], Flag: [flag], ")
+ continue
+ if(player.client.prefs.GetJobDepartment(job, level) & job.flag)
+ Debug("FOC pass, Player: [player], Level:[level]")
+ candidates += player
+ return candidates
- if(job.minimum_character_age && (player.client.prefs.age < job.minimum_character_age))
- continue
+/datum/controller/subsystem/jobs/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(istype(job, GetJob(USELESS_JOB))) // We don't want to give him assistant, that's boring! //VOREStation Edit - Visitor not Assistant
- continue
+ if(job.minimum_character_age && (player.client.prefs.age < job.minimum_character_age))
+ continue
- if(job.title in command_positions) //If you want a command position, select it!
- continue
+ if(istype(job, GetJob(USELESS_JOB))) // We don't want to give him assistant, that's boring! //VOREStation Edit - Visitor not Assistant
+ continue
- if(jobban_isbanned(player, job.title))
- Debug("GRJ isbanned failed, Player: [player], Job: [job.title]")
- continue
+ if(job.title in command_positions) //If you want a command position, select it!
+ continue
- if(!job.player_old_enough(player.client))
- Debug("GRJ player not old enough, Player: [player]")
- continue
+ if(jobban_isbanned(player, job.title))
+ Debug("GRJ isbanned failed, Player: [player], Job: [job.title]")
+ continue
- //VOREStation Code Start
- if(!is_job_whitelisted(player, job.title))
- Debug("GRJ player not whitelisted for this job, Player: [player], Job: [job.title]")
- continue
- //VOREStation Code End
+ 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
+ //VOREStation Code Start
+ if(!is_job_whitelisted(player, job.title))
+ Debug("GRJ player not whitelisted for this job, Player: [player], Job: [job.title]")
+ continue
+ //VOREStation Code End
- 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
+ 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/subsystem/jobs/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 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
- proc/FillHeadPosition()
- for(var/level = 1 to 3)
- 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
-
- // Build a weighted list, weight by age.
- var/list/weightedCandidates = list()
- for(var/mob/V in candidates)
- // Log-out during round-start? What a bad boy, no head position for you!
- if(!V.client) continue
- var/age = V.client.prefs.age
-
- if(age < job.minimum_character_age) // Nope.
- continue
-
- switch(age)
- if(job.minimum_character_age to (job.minimum_character_age+10))
- weightedCandidates[V] = 3 // Still a bit young.
- if((job.minimum_character_age+10) to (job.ideal_character_age-10))
- weightedCandidates[V] = 6 // Better.
- if((job.ideal_character_age-10) to (job.ideal_character_age+10))
- weightedCandidates[V] = 10 // Great.
- if((job.ideal_character_age+10) to (job.ideal_character_age+20))
- weightedCandidates[V] = 6 // Still good.
- if((job.ideal_character_age+20) to INFINITY)
- weightedCandidates[V] = 3 // Geezer.
- else
- // If there's ABSOLUTELY NOBODY ELSE
- if(candidates.len == 1) weightedCandidates[V] = 1
-
-
- var/mob/new_player/candidate = pickweight(weightedCandidates)
- 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
- proc/CheckHeadPositions(var/level)
+///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
+/datum/controller/subsystem/jobs/proc/FillHeadPosition()
+ for(var/level = 1 to 3)
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
+
+ // Build a weighted list, weight by age.
+ var/list/weightedCandidates = list()
+ for(var/mob/V in candidates)
+ // Log-out during round-start? What a bad boy, no head position for you!
+ if(!V.client) continue
+ var/age = V.client.prefs.age
+
+ if(age < job.minimum_character_age) // Nope.
+ continue
+
+ switch(age)
+ if(job.minimum_character_age to (job.minimum_character_age+10))
+ weightedCandidates[V] = 3 // Still a bit young.
+ if((job.minimum_character_age+10) to (job.ideal_character_age-10))
+ weightedCandidates[V] = 6 // Better.
+ if((job.ideal_character_age-10) to (job.ideal_character_age+10))
+ weightedCandidates[V] = 10 // Great.
+ if((job.ideal_character_age+10) to (job.ideal_character_age+20))
+ weightedCandidates[V] = 6 // Still good.
+ if((job.ideal_character_age+20) to INFINITY)
+ weightedCandidates[V] = 3 // Geezer.
+ else
+ // If there's ABSOLUTELY NOBODY ELSE
+ if(candidates.len == 1) weightedCandidates[V] = 1
+
+
+ var/mob/new_player/candidate = pickweight(weightedCandidates)
+ 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
+/datum/controller/subsystem/jobs/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 DivideOccupations
* fills var "assigned_role" for all ready players.
* This proc must not have any side effect besides of modifying "assigned_role".
**/
- proc/DivideOccupations()
- //Setup new player list and get the jobs list
- Debug("Running DO")
- SetupOccupations()
+/datum/controller/subsystem/jobs/proc/DivideOccupations()
+ //Setup new player list and get the jobs list
+ Debug("Running DO")
+ SetupOccupations()
- //Holder for Triumvirate is stored in the ticker, this just processes it
- if(ticker && ticker.triai)
- for(var/datum/job/A in occupations)
- if(A.title == "AI")
- A.spawn_positions = 3
- break
-
- //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
-
- Debug("DO, Len: [unassigned.len]")
- if(unassigned.len == 0) return 0
-
- //Shuffle players and jobs
- unassigned = shuffle(unassigned)
-
- HandleFeedbackGathering()
-
- //People who wants to be assistants, sure, go on.
- Debug("DO, Running Assistant Check 1")
- var/datum/job/assist = new DEFAULT_JOB_TYPE ()
- var/list/assistant_candidates = FindOccupationCandidates(assist, 3)
- Debug("AC1, Candidates: [assistant_candidates.len]")
- for(var/mob/new_player/player in assistant_candidates)
- Debug("AC1 pass, Player: [player]")
- AssignRole(player, USELESS_JOB) //VOREStation Edit - Visitor not Assistant
- assistant_candidates -= player
- Debug("DO, AC1 end")
-
- //Select one head
- Debug("DO, Running Head Check")
- FillHeadPosition()
- Debug("DO, Head Check end")
-
- //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)
- // var/list/disabled_jobs = ticker.mode.disabled_jobs // So we can use .Find down below without a colon.
- for(var/level = 1 to 3)
- //Check the head jobs first each level
- CheckHeadPositions(level)
-
- // Loop through all unassigned players
- for(var/mob/new_player/player in unassigned)
-
- // Loop through all jobs
- for(var/datum/job/job in shuffledoccupations) // SHUFFLE ME BABY
- if(!job || ticker.mode.disabled_jobs.Find(job.title) )
- continue
-
- if(jobban_isbanned(player, job.title))
- Debug("DO isbanned failed, Player: [player], Job:[job.title]")
- continue
-
- if(!job.player_old_enough(player.client))
- Debug("DO player not old enough, 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)
- Debug("DO pass, Player: [player], Level:[level], Job:[job.title]")
- AssignRole(player, job.title)
- 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.alternate_option == GET_RANDOM_JOB)
- GiveRandomJob(player)
- /*
- Old job system
- for(var/level = 1 to 3)
- for(var/datum/job/job in occupations)
- Debug("Checking job: [job]")
- if(!job)
- continue
- if(!unassigned.len)
- break
- if((job.current_positions >= job.spawn_positions) && job.spawn_positions != -1)
- continue
- var/list/candidates = FindOccupationCandidates(job, level)
- while(candidates.len && ((job.current_positions < job.spawn_positions) || job.spawn_positions == -1))
- var/mob/new_player/candidate = pick(candidates)
- Debug("Selcted: [candidate], for: [job.title]")
- AssignRole(candidate, job.title)
- candidates -= candidate*/
-
- Debug("DO, Standard Check end")
-
- Debug("DO, Running AC2")
-
- // 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.alternate_option == BE_ASSISTANT)
- Debug("AC2 Assistant located, Player: [player]")
- AssignRole(player, USELESS_JOB) //VOREStation Edit - Visitor not Assistant
-
- //For ones returning to lobby
- for(var/mob/new_player/player in unassigned)
- if(player.client.prefs.alternate_option == RETURN_TO_LOBBY)
- player.ready = 0
- player.new_player_panel_proc()
- unassigned -= player
- return 1
-
-
- proc/EquipRank(var/mob/living/carbon/human/H, var/rank, var/joined_late = 0)
- if(!H)
- return
-
- var/datum/job/job = GetJob(rank)
- var/list/spawn_in_storage = list()
-
- if(!joined_late)
- var/obj/S = null
- var/list/possible_spawns = list()
- for(var/obj/effect/landmark/start/sloc in landmarks_list)
- if(sloc.name != rank) continue
- if(locate(/mob/living) in sloc.loc) continue
- possible_spawns.Add(sloc)
- if(possible_spawns.len)
- S = pick(possible_spawns)
- if(!S)
- S = locate("start*[rank]") // use old stype
- if(istype(S, /obj/effect/landmark/start) && istype(S.loc, /turf))
- H.forceMove(S.loc)
- else
- var/list/spawn_props = LateSpawn(H.client, rank)
- var/turf/T = spawn_props["turf"]
- H.forceMove(T)
-
- // Moving wheelchair if they have one
- if(H.buckled && istype(H.buckled, /obj/structure/bed/chair/wheelchair))
- H.buckled.forceMove(H.loc)
- H.buckled.setDir(H.dir)
-
- if(job)
-
- //Equip custom gear loadout.
- var/list/custom_equip_slots = list() //If more than one item takes the same slot, all after the first one spawn in storage.
- var/list/custom_equip_leftovers = list()
- if(H.client.prefs.gear && H.client.prefs.gear.len && job.title != "Cyborg" && job.title != "AI")
- for(var/thing in H.client.prefs.gear)
- var/datum/gear/G = gear_datums[thing]
- if(G)
- var/permitted
- if(G.allowed_roles)
- for(var/job_name in G.allowed_roles)
- if(job.title == job_name)
- permitted = 1
- else
- permitted = 1
-
- if(G.whitelisted && !is_alien_whitelisted(H, all_species[G.whitelisted]))
-
- //if(G.whitelisted && (G.whitelisted != H.species.name || !is_alien_whitelisted(H, G.whitelisted)))
- permitted = 0
-
- if(!permitted)
- H << "Your current species, job or whitelist status does not permit you to spawn with [thing]!"
- continue
-
- if(G.slot == "implant")
- var/obj/item/implant/I = G.spawn_item(H, H.client.prefs.gear[G.display_name])
- I.invisibility = 100
- I.implant_loadout(H)
- continue
-
- if(G.slot && !(G.slot in custom_equip_slots))
- // This is a miserable way to fix the loadout overwrite bug, but the alternative requires
- // adding an arg to a bunch of different procs. Will look into it after this merge. ~ Z
- var/metadata = H.client.prefs.gear[G.display_name]
- if(G.slot == slot_wear_mask || G.slot == slot_wear_suit || G.slot == slot_head)
- custom_equip_leftovers += thing
- else if(H.equip_to_slot_or_del(G.spawn_item(H, metadata), G.slot))
- H << "Equipping you with \the [thing]!"
- custom_equip_slots.Add(G.slot)
- else
- custom_equip_leftovers.Add(thing)
- else
- spawn_in_storage += thing
- //Equip job items.
- job.setup_account(H)
- job.equip(H, H.mind ? H.mind.role_alt_title : "")
- job.equip_backpack(H)
-// job.equip_survival(H)
- job.apply_fingerprints(H)
- if(job.title != "Cyborg" && job.title != "AI")
- H.equip_post_job()
-
- //If some custom items could not be equipped before, try again now.
- for(var/thing in custom_equip_leftovers)
- var/datum/gear/G = gear_datums[thing]
- if(G.slot in custom_equip_slots)
- spawn_in_storage += thing
- else
- var/metadata = H.client.prefs.gear[G.display_name]
- if(H.equip_to_slot_or_del(G.spawn_item(H, metadata), G.slot))
- H << "Equipping you with \the [thing]!"
- custom_equip_slots.Add(G.slot)
- else
- spawn_in_storage += thing
- else
- H << "Your job is [rank] and the game just can't handle it! Please report this bug to an administrator."
-
- H.job = rank
- log_game("JOINED [key_name(H)] as \"[rank]\"")
- log_game("SPECIES [key_name(H)] is a: \"[H.species.name]\"") //VOREStation Add
-
- // 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)
-
- var/alt_title = null
- if(H.mind)
- H.mind.assigned_role = rank
- alt_title = H.mind.role_alt_title
-
- switch(rank)
- if("Cyborg")
- return H.Robotize()
- if("AI")
- return H
- if("Colony Director")
- var/sound/announce_sound = (ticker.current_state <= GAME_STATE_SETTING_UP)? null : sound('sound/misc/boatswain.ogg', volume=20)
- captain_announcement.Announce("All hands, [alt_title ? alt_title : "Colony Director"] [H.real_name] on deck!", new_sound=announce_sound)
-
- //Deferred item spawning.
- if(spawn_in_storage && spawn_in_storage.len)
- var/obj/item/storage/B
- for(var/obj/item/storage/S in H.contents)
- B = S
- break
-
- if(!isnull(B))
- for(var/thing in spawn_in_storage)
- var/datum/gear/G = gear_datums[thing]
- var/obj/item/I = G.spawn_item(H, H.client.prefs.gear[G.display_name]) //Create the item...
- if(B.can_be_inserted(I, 1)) //Try putting it in their backpack.
- H << "Placing \the [I] in your [B.name]!"
- I.forceMove(B)
- continue
- if(H.equip_to_appropriate_slot(I)) //Other slots?
- H << "Equipping you with \the [I]!"
- continue
- if(H.put_in_hands(I)) //Well, hands?
- H << "Placing \the [I] in your hand!"
- continue
- //Throw a tantrum, having exhausted all other options.
- H << "Inventory space exhausted. Putting \the [I] on the ground!"
- I.forceMove(get_turf(H))
-
- else
- H << "Failed to locate storage on your mob. Please report this at our Github. Dumping your loadout at your feet..."
- for(var/thing in spawn_in_storage)
- var/datum/gear/G = gear_datums[thing]
- var/obj/item/I = G.spawn_item(H, H.client.prefs.gear[G.display_name])
- I.forceMove(get_turf(H))
- H << "Putting \the [I] on the ground!"
-
-
- if(istype(H)) //give humans wheelchairs, if they need them.
- var/obj/item/organ/external/l_foot = H.get_organ("l_foot")
- var/obj/item/organ/external/r_foot = H.get_organ("r_foot")
- var/obj/item/storage/S = locate() in H.contents
- var/obj/item/wheelchair/R = null
- if(S)
- R = locate() in S.contents
- if(!l_foot || !r_foot || R)
- var/obj/structure/bed/chair/wheelchair/W = new /obj/structure/bed/chair/wheelchair(H.loc)
- W.buckle_mob(H)
- H.update_canmove()
- W.setDir(H.dir)
- W.add_fingerprint(H)
- if(R)
- W.color = R.color
- qdel(R)
-
- H << "You are [job.total_positions == 1 ? "the" : "a"] [alt_title ? alt_title : rank]."
-
- if(job.supervisors)
- H << "As the [alt_title ? alt_title : rank] you answer directly to [job.supervisors]. Special circumstances may change this."
-
- if(job.idtype)
- spawnId(H, rank, alt_title)
- H.equip_to_slot_or_del(new /obj/item/radio/headset(H), slot_l_ear)
- H << "To speak on your department's radio channel use :h. For the use of other channels, examine your headset."
-
- if(job.req_admin_notify)
- H << "You are playing a job that is important for Game Progression. If you have to disconnect, please notify the admins via adminhelp."
-
- // EMAIL GENERATION
- // Email addresses will be created under this domain name. Mostly for the looks.
- var/domain = "freemail.nt"
- var/sanitized_name = sanitize(replacetext(replacetext(lowertext(H.real_name), " ", "."), "'", ""))
- var/complete_login = "[sanitized_name]@[domain]"
-
- // It is VERY unlikely that we'll have two players, in the same round, with the same name and branch, but still, this is here.
- // If such conflict is encountered, a random number will be appended to the email address. If this fails too, no email account will be created.
- if(ntnet_global.does_email_exist(complete_login))
- complete_login = "[sanitized_name][random_id(/datum/computer_file/data/email_account/, 100, 999)]@[domain]"
-
- // If even fallback login generation failed, just don't give them an email. The chance of this happening is astronomically low.
- if(ntnet_global.does_email_exist(complete_login))
- to_chat(H, "You were not assigned an email address.")
- H.mind.store_memory("You were not assigned an email address.")
- else
- var/datum/computer_file/data/email_account/EA = new/datum/computer_file/data/email_account()
- EA.password = GenerateKey()
- EA.login = complete_login
- to_chat(H, "Your email account address is [EA.login] and the password is [EA.password]. This information has also been placed into your notes.")
- H.mind.store_memory("Your email account address is [EA.login] and the password is [EA.password].")
- // END EMAIL GENERATION
-
- //Gives glasses to the vision impaired
- if(H.disabilities & NEARSIGHTED)
- var/equipped = H.equip_to_slot_or_del(new /obj/item/clothing/glasses/regular(H), slot_glasses)
- if(equipped != 1)
- var/obj/item/clothing/glasses/G = H.glasses
- G.prescription = 1
-
- ENABLE_BITFIELD(H.hud_updateflag, ID_HUD)
- ENABLE_BITFIELD(H.hud_updateflag, IMPLOYAL_HUD)
- ENABLE_BITFIELD(H.hud_updateflag, SPECIALROLE_HUD)
- return H
-
-
- proc/spawnId(var/mob/living/carbon/human/H, rank, title)
- if(!H) return 0
- var/obj/item/card/id/C = H.get_equipped_item(slot_wear_id)
- if(istype(C)) return 0
-
- var/datum/job/job = null
- for(var/datum/job/J in occupations)
- if(J.title == rank)
- job = J
+ //Holder for Triumvirate is stored in the ticker, this just processes it
+ if(ticker && ticker.triai)
+ for(var/datum/job/A in occupations)
+ if(A.title == "AI")
+ A.spawn_positions = 3
break
- if(job)
- if(job.title == "Cyborg")
- return
- else
- C = new job.idtype(H)
- C.access = job.get_access()
- else
- C = new /obj/item/card/id(H)
- if(C)
- C.rank = rank
- C.assignment = title ? title : rank
- H.set_id_info(C)
+ //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
- //put the player's account number onto the ID
- if(H.mind && H.mind.initial_account)
- C.associated_account_number = H.mind.initial_account.account_number
+ Debug("DO, Len: [unassigned.len]")
+ if(unassigned.len == 0) return 0
- H.equip_to_slot_or_del(C, slot_wear_id)
+ //Shuffle players and jobs
+ unassigned = shuffle(unassigned)
-// H.equip_to_slot_or_del(new /obj/item/pda(H), slot_belt)
- if(locate(/obj/item/pda,H))
- var/obj/item/pda/pda = locate(/obj/item/pda,H)
- pda.owner = H.real_name
- pda.ownjob = C.assignment
- pda.ownrank = C.rank
- pda.name = "PDA-[H.real_name] ([pda.ownjob])"
+ HandleFeedbackGathering()
- return 1
+ //People who wants to be assistants, sure, go on.
+ Debug("DO, Running Assistant Check 1")
+ var/datum/job/assist = new DEFAULT_JOB_TYPE ()
+ var/list/assistant_candidates = FindOccupationCandidates(assist, 3)
+ Debug("AC1, Candidates: [assistant_candidates.len]")
+ for(var/mob/new_player/player in assistant_candidates)
+ Debug("AC1 pass, Player: [player]")
+ AssignRole(player, USELESS_JOB) //VOREStation Edit - Visitor not Assistant
+ assistant_candidates -= player
+ Debug("DO, AC1 end")
+
+ //Select one head
+ Debug("DO, Running Head Check")
+ FillHeadPosition()
+ Debug("DO, Head Check end")
+
+ //Other jobs are now checked
+ Debug("DO, Running Standard Check")
- proc/LoadJobs(jobsfile) //ran during round setup, reads info from jobs.txt -- Urist
- if(!config_legacy.load_jobs_from_txt)
- return 0
+ // 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.
- var/list/jobEntries = file2list(jobsfile)
+ // Loop through all levels from high to low
+ var/list/shuffledoccupations = shuffle(occupations)
+ // var/list/disabled_jobs = ticker.mode.disabled_jobs // So we can use .Find down below without a colon.
+ for(var/level = 1 to 3)
+ //Check the head jobs first each level
+ CheckHeadPositions(level)
- for(var/job in jobEntries)
+ // Loop through all unassigned players
+ for(var/mob/new_player/player in unassigned)
+
+ // Loop through all jobs
+ for(var/datum/job/job in shuffledoccupations) // SHUFFLE ME BABY
+ if(!job || ticker.mode.disabled_jobs.Find(job.title) )
+ continue
+
+ if(jobban_isbanned(player, job.title))
+ Debug("DO isbanned failed, Player: [player], Job:[job.title]")
+ continue
+
+ if(!job.player_old_enough(player.client))
+ Debug("DO player not old enough, 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)
+ Debug("DO pass, Player: [player], Level:[level], Job:[job.title]")
+ AssignRole(player, job.title)
+ 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.alternate_option == GET_RANDOM_JOB)
+ GiveRandomJob(player)
+ /*
+ Old job system
+ for(var/level = 1 to 3)
+ for(var/datum/job/job in occupations)
+ Debug("Checking job: [job]")
if(!job)
continue
-
- job = trim(job)
- if (!length(job))
+ if(!unassigned.len)
+ break
+ if((job.current_positions >= job.spawn_positions) && job.spawn_positions != -1)
continue
+ var/list/candidates = FindOccupationCandidates(job, level)
+ while(candidates.len && ((job.current_positions < job.spawn_positions) || job.spawn_positions == -1))
+ var/mob/new_player/candidate = pick(candidates)
+ Debug("Selcted: [candidate], for: [job.title]")
+ AssignRole(candidate, job.title)
+ candidates -= candidate*/
- var/pos = findtext(job, "=")
- var/name = null
- var/value = null
+ Debug("DO, Standard Check end")
- if(pos)
- name = copytext(job, 1, pos)
- value = copytext(job, pos + 1)
+ Debug("DO, Running AC2")
+
+ // 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.alternate_option == BE_ASSISTANT)
+ Debug("AC2 Assistant located, Player: [player]")
+ AssignRole(player, USELESS_JOB) //VOREStation Edit - Visitor not Assistant
+
+ //For ones returning to lobby
+ for(var/mob/new_player/player in unassigned)
+ if(player.client.prefs.alternate_option == RETURN_TO_LOBBY)
+ player.ready = 0
+ player.new_player_panel_proc()
+ unassigned -= player
+ return 1
+
+
+/datum/controller/subsystem/jobs/proc/EquipRank(var/mob/living/carbon/human/H, var/rank, var/joined_late = 0)
+ if(!H)
+ return
+
+ var/datum/job/job = GetJob(rank)
+ var/list/spawn_in_storage = list()
+
+ if(!joined_late)
+ var/obj/S = null
+ var/list/possible_spawns = list()
+ for(var/obj/effect/landmark/start/sloc in landmarks_list)
+ if(sloc.name != rank) continue
+ if(locate(/mob/living) in sloc.loc) continue
+ possible_spawns.Add(sloc)
+ if(possible_spawns.len)
+ S = pick(possible_spawns)
+ if(!S)
+ S = locate("start*[rank]") // use old stype
+ if(istype(S, /obj/effect/landmark/start) && istype(S.loc, /turf))
+ H.forceMove(S.loc)
+ else
+ var/list/spawn_props = LateSpawn(H.client, rank)
+ var/turf/T = spawn_props["turf"]
+ H.forceMove(T)
+
+ // Moving wheelchair if they have one
+ if(H.buckled && istype(H.buckled, /obj/structure/bed/chair/wheelchair))
+ H.buckled.forceMove(H.loc)
+ H.buckled.setDir(H.dir)
+
+ if(job)
+
+ //Equip custom gear loadout.
+ var/list/custom_equip_slots = list() //If more than one item takes the same slot, all after the first one spawn in storage.
+ var/list/custom_equip_leftovers = list()
+ if(H.client.prefs.gear && H.client.prefs.gear.len && job.title != "Cyborg" && job.title != "AI")
+ for(var/thing in H.client.prefs.gear)
+ var/datum/gear/G = gear_datums[thing]
+ if(G)
+ var/permitted
+ if(G.allowed_roles)
+ for(var/job_name in G.allowed_roles)
+ if(job.title == job_name)
+ permitted = 1
+ else
+ permitted = 1
+
+ if(G.whitelisted && !is_alien_whitelisted(H, all_species[G.whitelisted]))
+
+ //if(G.whitelisted && (G.whitelisted != H.species.name || !is_alien_whitelisted(H, G.whitelisted)))
+ permitted = 0
+
+ if(!permitted)
+ H << "Your current species, job or whitelist status does not permit you to spawn with [thing]!"
+ continue
+
+ if(G.slot == "implant")
+ var/obj/item/implant/I = G.spawn_item(H, H.client.prefs.gear[G.display_name])
+ I.invisibility = 100
+ I.implant_loadout(H)
+ continue
+
+ if(G.slot && !(G.slot in custom_equip_slots))
+ // This is a miserable way to fix the loadout overwrite bug, but the alternative requires
+ // adding an arg to a bunch of different procs. Will look into it after this merge. ~ Z
+ var/metadata = H.client.prefs.gear[G.display_name]
+ if(G.slot == slot_wear_mask || G.slot == slot_wear_suit || G.slot == slot_head)
+ custom_equip_leftovers += thing
+ else if(H.equip_to_slot_or_del(G.spawn_item(H, metadata), G.slot))
+ H << "Equipping you with \the [thing]!"
+ custom_equip_slots.Add(G.slot)
+ else
+ custom_equip_leftovers.Add(thing)
+ else
+ spawn_in_storage += thing
+ //Equip job items.
+ job.setup_account(H)
+ job.equip(H, H.mind ? H.mind.role_alt_title : "")
+ job.equip_backpack(H)
+// job.equip_survival(H)
+ job.apply_fingerprints(H)
+ if(job.title != "Cyborg" && job.title != "AI")
+ H.equip_post_job()
+
+ //If some custom items could not be equipped before, try again now.
+ for(var/thing in custom_equip_leftovers)
+ var/datum/gear/G = gear_datums[thing]
+ if(G.slot in custom_equip_slots)
+ spawn_in_storage += thing
else
+ var/metadata = H.client.prefs.gear[G.display_name]
+ if(H.equip_to_slot_or_del(G.spawn_item(H, metadata), G.slot))
+ H << "Equipping you with \the [thing]!"
+ custom_equip_slots.Add(G.slot)
+ else
+ spawn_in_storage += thing
+ else
+ H << "Your job is [rank] and the game just can't handle it! Please report this bug to an administrator."
+
+ H.job = rank
+ log_game("JOINED [key_name(H)] as \"[rank]\"")
+ log_game("SPECIES [key_name(H)] is a: \"[H.species.name]\"") //VOREStation Add
+
+ // 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)
+
+ var/alt_title = null
+ if(H.mind)
+ H.mind.assigned_role = rank
+ alt_title = H.mind.role_alt_title
+
+ switch(rank)
+ if("Cyborg")
+ return H.Robotize()
+ if("AI")
+ return H
+ if("Colony Director")
+ var/sound/announce_sound = (ticker.current_state <= GAME_STATE_SETTING_UP)? null : sound('sound/misc/boatswain.ogg', volume=20)
+ captain_announcement.Announce("All hands, [alt_title ? alt_title : "Colony Director"] [H.real_name] on deck!", new_sound=announce_sound)
+
+ //Deferred item spawning.
+ if(spawn_in_storage && spawn_in_storage.len)
+ var/obj/item/storage/B
+ for(var/obj/item/storage/S in H.contents)
+ B = S
+ break
+
+ if(!isnull(B))
+ for(var/thing in spawn_in_storage)
+ var/datum/gear/G = gear_datums[thing]
+ var/obj/item/I = G.spawn_item(H, H.client.prefs.gear[G.display_name]) //Create the item...
+ if(B.can_be_inserted(I, 1)) //Try putting it in their backpack.
+ H << "Placing \the [I] in your [B.name]!"
+ I.forceMove(B)
+ continue
+ if(H.equip_to_appropriate_slot(I)) //Other slots?
+ H << "Equipping you with \the [I]!"
+ continue
+ if(H.put_in_hands(I)) //Well, hands?
+ H << "Placing \the [I] in your hand!"
+ continue
+ //Throw a tantrum, having exhausted all other options.
+ H << "Inventory space exhausted. Putting \the [I] on the ground!"
+ I.forceMove(get_turf(H))
+
+ else
+ H << "Failed to locate storage on your mob. Please report this at our Github. Dumping your loadout at your feet..."
+ for(var/thing in spawn_in_storage)
+ var/datum/gear/G = gear_datums[thing]
+ var/obj/item/I = G.spawn_item(H, H.client.prefs.gear[G.display_name])
+ I.forceMove(get_turf(H))
+ H << "Putting \the [I] on the ground!"
+
+
+ if(istype(H)) //give humans wheelchairs, if they need them.
+ var/obj/item/organ/external/l_foot = H.get_organ("l_foot")
+ var/obj/item/organ/external/r_foot = H.get_organ("r_foot")
+ var/obj/item/storage/S = locate() in H.contents
+ var/obj/item/wheelchair/R = null
+ if(S)
+ R = locate() in S.contents
+ if(!l_foot || !r_foot || R)
+ var/obj/structure/bed/chair/wheelchair/W = new /obj/structure/bed/chair/wheelchair(H.loc)
+ W.buckle_mob(H)
+ H.update_canmove()
+ W.setDir(H.dir)
+ W.add_fingerprint(H)
+ if(R)
+ W.color = R.color
+ qdel(R)
+
+ H << "You are [job.total_positions == 1 ? "the" : "a"] [alt_title ? alt_title : rank]."
+
+ if(job.supervisors)
+ H << "As the [alt_title ? alt_title : rank] you answer directly to [job.supervisors]. Special circumstances may change this."
+
+ if(job.idtype)
+ spawnId(H, rank, alt_title)
+ H.equip_to_slot_or_del(new /obj/item/radio/headset(H), slot_l_ear)
+ H << "To speak on your department's radio channel use :h. For the use of other channels, examine your headset."
+
+ if(job.req_admin_notify)
+ H << "You are playing a job that is important for Game Progression. If you have to disconnect, please notify the admins via adminhelp."
+
+ // EMAIL GENERATION
+ // Email addresses will be created under this domain name. Mostly for the looks.
+ var/domain = "freemail.nt"
+ var/sanitized_name = sanitize(replacetext(replacetext(lowertext(H.real_name), " ", "."), "'", ""))
+ var/complete_login = "[sanitized_name]@[domain]"
+
+ // It is VERY unlikely that we'll have two players, in the same round, with the same name and branch, but still, this is here.
+ // If such conflict is encountered, a random number will be appended to the email address. If this fails too, no email account will be created.
+ if(ntnet_global.does_email_exist(complete_login))
+ complete_login = "[sanitized_name][random_id(/datum/computer_file/data/email_account/, 100, 999)]@[domain]"
+
+ // If even fallback login generation failed, just don't give them an email. The chance of this happening is astronomically low.
+ if(ntnet_global.does_email_exist(complete_login))
+ to_chat(H, "You were not assigned an email address.")
+ H.mind.store_memory("You were not assigned an email address.")
+ else
+ var/datum/computer_file/data/email_account/EA = new/datum/computer_file/data/email_account()
+ EA.password = GenerateKey()
+ EA.login = complete_login
+ to_chat(H, "Your email account address is [EA.login] and the password is [EA.password]. This information has also been placed into your notes.")
+ H.mind.store_memory("Your email account address is [EA.login] and the password is [EA.password].")
+ // END EMAIL GENERATION
+
+ //Gives glasses to the vision impaired
+ if(H.disabilities & NEARSIGHTED)
+ var/equipped = H.equip_to_slot_or_del(new /obj/item/clothing/glasses/regular(H), slot_glasses)
+ if(equipped != 1)
+ var/obj/item/clothing/glasses/G = H.glasses
+ G.prescription = 1
+
+ ENABLE_BITFIELD(H.hud_updateflag, ID_HUD)
+ ENABLE_BITFIELD(H.hud_updateflag, IMPLOYAL_HUD)
+ ENABLE_BITFIELD(H.hud_updateflag, SPECIALROLE_HUD)
+ return H
+
+
+/datum/controller/subsystem/jobs/proc/spawnId(var/mob/living/carbon/human/H, rank, title)
+ if(!H) return 0
+ var/obj/item/card/id/C = H.get_equipped_item(slot_wear_id)
+ if(istype(C)) return 0
+
+ var/datum/job/job = null
+ for(var/datum/job/J in occupations)
+ if(J.title == rank)
+ job = J
+ break
+
+ if(job)
+ if(job.title == "Cyborg")
+ return
+ else
+ C = new job.idtype(H)
+ C.access = job.get_access()
+ else
+ C = new /obj/item/card/id(H)
+ if(C)
+ C.rank = rank
+ C.assignment = title ? title : rank
+ H.set_id_info(C)
+
+ //put the player's account number onto the ID
+ if(H.mind && H.mind.initial_account)
+ C.associated_account_number = H.mind.initial_account.account_number
+
+ H.equip_to_slot_or_del(C, slot_wear_id)
+
+// H.equip_to_slot_or_del(new /obj/item/pda(H), slot_belt)
+ if(locate(/obj/item/pda,H))
+ var/obj/item/pda/pda = locate(/obj/item/pda,H)
+ pda.owner = H.real_name
+ pda.ownjob = C.assignment
+ pda.ownrank = C.rank
+ pda.name = "PDA-[H.real_name] ([pda.ownjob])"
+
+ return 1
+
+
+/datum/controller/subsystem/jobs/proc/LoadJobs(jobsfile) //ran during round setup, reads info from jobs.txt -- Urist
+ if(!config_legacy.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.total_positions = text2num(value)
+ J.spawn_positions = text2num(value)
+ if(name == "AI" || name == "Cyborg")//I dont like this here but it will do for now
+ J.total_positions = 0
+
+ return 1
+
+
+/datum/controller/subsystem/jobs/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
+ if(player.client.prefs.GetJobDepartment(job, 1) & job.flag)
+ level1++
+ else if(player.client.prefs.GetJobDepartment(job, 2) & job.flag)
+ level2++
+ else if(player.client.prefs.GetJobDepartment(job, 3) & job.flag)
+ level3++
+ else level4++ //not selected
- if(name && value)
- var/datum/job/J = GetJob(name)
- if(!J) continue
- J.total_positions = text2num(value)
- J.spawn_positions = text2num(value)
- if(name == "AI" || name == "Cyborg")//I dont like this here but it will do for now
- J.total_positions = 0
-
- return 1
-
-
- 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
- if(player.client.prefs.GetJobDepartment(job, 1) & job.flag)
- level1++
- else if(player.client.prefs.GetJobDepartment(job, 2) & job.flag)
- level2++
- else if(player.client.prefs.GetJobDepartment(job, 3) & job.flag)
- 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)
+ 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/LateSpawn(var/client/C, var/rank)
diff --git a/code/controllers/subsystems/master_controller.dm b/code/controllers/subsystems/master_controller.dm
index 8b3a39e536b..0bc1c199f75 100644
--- a/code/controllers/subsystems/master_controller.dm
+++ b/code/controllers/subsystems/master_controller.dm
@@ -24,10 +24,10 @@ datum/controller/game_controller/New()
qdel(master_controller)
master_controller = src
- if(!job_master)
- job_master = new /datum/controller/occupations()
- job_master.SetupOccupations()
- job_master.LoadJobs("config/jobs.txt")
+ if(!SSjobs)
+ SSjobs = new /datum/controller/occupations()
+ SSjobs.SetupOccupations()
+ SSjobs.LoadJobs("config/jobs.txt")
admin_notice("Job setup complete", R_DEBUG)
if(!syndicate_code_phrase) syndicate_code_phrase = generate_code_phrase()
diff --git a/code/controllers/subsystems/persist_vr.dm b/code/controllers/subsystems/persist_vr.dm
index f30cc86b216..f65c612d051 100644
--- a/code/controllers/subsystems/persist_vr.dm
+++ b/code/controllers/subsystems/persist_vr.dm
@@ -72,10 +72,10 @@ SUBSYSTEM_DEF(persist)
if(R) // We found someone with a record.
var/recorded_rank = R.fields["real_rank"]
if(recorded_rank)
- . = job_master.GetJob(recorded_rank)
+ . = SSjobs.GetJob(recorded_rank)
if(.) return
// They have a custom title, aren't crew, or someone deleted their record, so we need a fallback method.
// Let's check the mind.
if(M.mind && M.mind.assigned_role)
- . = job_master.GetJob(M.mind.assigned_role)
+ . = SSjobs.GetJob(M.mind.assigned_role)
diff --git a/code/game/gamemodes/gameticker.dm b/code/game/gamemodes/gameticker.dm
index 3e13fac8804..9d21d8ca7cb 100644
--- a/code/game/gamemodes/gameticker.dm
+++ b/code/game/gamemodes/gameticker.dm
@@ -95,10 +95,10 @@ var/global/datum/controller/gameticker/ticker
to_chat(world, "Serious error in mode setup! Reverting to pregame lobby.") //Uses setup instead of set up due to computational context.
return 0
- job_master.ResetOccupations()
+ SSjobs.ResetOccupations()
src.mode.create_antagonists()
src.mode.pre_setup()
- job_master.DivideOccupations() // Apparently important for new antagonist system to register specific job antags properly.
+ SSjobs.DivideOccupations() // Apparently important for new antagonist system to register specific job antags properly.
if(!src.mode.can_start())
world << "Unable to start [mode.name]. Not enough players readied, [config_legacy.player_requirements[mode.config_tag]] players needed. Reverting to pregame lobby."
@@ -106,7 +106,7 @@ var/global/datum/controller/gameticker/ticker
Master.SetRunLevel(RUNLEVEL_LOBBY)
mode.fail_setup()
mode = null
- job_master.ResetOccupations()
+ SSjobs.ResetOccupations()
return 0
if(hide_mode)
@@ -302,7 +302,7 @@ var/global/datum/controller/gameticker/ticker
if(player.mind.assigned_role == "Colony Director")
captainless=0
if(!player_is_antag(player.mind, only_offstation_roles = 1))
- job_master.EquipRank(player, player.mind.assigned_role, 0)
+ SSjobs.EquipRank(player, player.mind.assigned_role, 0)
UpdateFactionList(player)
//equip_custom_items(player) //VOREStation Removal
//player.apply_traits() //VOREStation Removal
diff --git a/code/game/jobs/whitelist_vr.dm b/code/game/jobs/whitelist_vr.dm
index 058a9c0dc17..d4809aa9f03 100644
--- a/code/game/jobs/whitelist_vr.dm
+++ b/code/game/jobs/whitelist_vr.dm
@@ -12,7 +12,7 @@ var/list/job_whitelist = list()
job_whitelist = splittext(text, "\n")
/proc/is_job_whitelisted(mob/M, var/rank)
- var/datum/job/job = job_master.GetJob(rank)
+ var/datum/job/job = SSjobs.GetJob(rank)
if(!job.whitelist_only)
return 1
if(rank == USELESS_JOB) //VOREStation Edit - Visitor not Assistant
diff --git a/code/game/machinery/computer/timeclock_vr.dm b/code/game/machinery/computer/timeclock_vr.dm
index 32aa2da2698..9a1ba16fe75 100644
--- a/code/game/machinery/computer/timeclock_vr.dm
+++ b/code/game/machinery/computer/timeclock_vr.dm
@@ -81,7 +81,7 @@
if(card)
data["card"] = "[card]"
data["assignment"] = card.assignment
- var/datum/job/job = job_master.GetJob(card.rank)
+ var/datum/job/job = SSjobs.GetJob(card.rank)
if (job)
data["job_datum"] = list(
"title" = job.title,
@@ -140,7 +140,7 @@
/obj/machinery/computer/timeclock/proc/getOpenOnDutyJobs(var/mob/user, var/department)
var/list/available_jobs = list()
- for(var/datum/job/job in job_master.occupations)
+ for(var/datum/job/job in SSjobs.occupations)
if(job && job.is_position_available() && !job.whitelist_only && !jobban_isbanned(user,job.title) && job.player_old_enough(user.client))
if(job.department == department && !job.disallow_jobhop && job.timeoff_factor > 0)
available_jobs += job.title
@@ -151,14 +151,14 @@
/obj/machinery/computer/timeclock/proc/makeOnDuty(var/newjob)
var/datum/job/foundjob = null
- for(var/datum/job/job in job_master.occupations)
+ for(var/datum/job/job in SSjobs.occupations)
if(newjob == job.title)
foundjob = job
break
if(newjob in job.alt_titles)
foundjob = job
break
- if(!newjob in getOpenOnDutyJobs(usr, job_master.GetJob(card.rank).department))
+ if(!newjob in getOpenOnDutyJobs(usr, SSjobs.GetJob(card.rank).department))
return
if(foundjob && card)
card.access = foundjob.get_access()
@@ -177,7 +177,7 @@
/obj/machinery/computer/timeclock/proc/makeOffDuty()
var/datum/job/foundjob = null
- for(var/datum/job/job in job_master.occupations)
+ for(var/datum/job/job in SSjobs.occupations)
if(card.rank == job.title)
foundjob = job
break
@@ -187,7 +187,7 @@
if(real_dept && real_dept == "Command")
real_dept = "Civilian"
var/datum/job/ptojob = null
- for(var/datum/job/job in job_master.occupations)
+ for(var/datum/job/job in SSjobs.occupations)
if(job.department == real_dept && job.timeoff_factor < 0)
ptojob = job
break
diff --git a/code/game/machinery/cryopod.dm b/code/game/machinery/cryopod.dm
index ea55eec3d39..6f07f441160 100644
--- a/code/game/machinery/cryopod.dm
+++ b/code/game/machinery/cryopod.dm
@@ -474,7 +474,7 @@
//Handle job slot/tater cleanup.
var/job = to_despawn.mind.assigned_role
- job_master.FreeRole(job)
+ SSjobs.FreeRole(job)
if(to_despawn.mind.objectives.len)
qdel(to_despawn.mind.objectives)
diff --git a/code/game/objects/items/weapons/id cards/station_ids.dm b/code/game/objects/items/weapons/id cards/station_ids.dm
index 0268187c68d..3a59a2eccf1 100755
--- a/code/game/objects/items/weapons/id cards/station_ids.dm
+++ b/code/game/objects/items/weapons/id cards/station_ids.dm
@@ -124,7 +124,7 @@
/obj/item/card/id/Initialize()
. = ..()
- var/datum/job/J = job_master.GetJob(rank)
+ var/datum/job/J = SSjobs.GetJob(rank)
if(J)
access = J.get_access()
diff --git a/code/modules/admin/admin_verbs.dm b/code/modules/admin/admin_verbs.dm
index eec358e20a1..71c568269ad 100644
--- a/code/modules/admin/admin_verbs.dm
+++ b/code/modules/admin/admin_verbs.dm
@@ -1010,7 +1010,7 @@ var/list/admin_verbs_event_manager = list(
set category = "Admin"
if(holder)
var/list/jobs = list()
- for (var/datum/job/J in job_master.occupations)
+ for (var/datum/job/J in SSjobs.occupations)
if (J.current_positions >= J.total_positions && J.total_positions != -1)
jobs += J.title
if (!jobs.len)
@@ -1018,7 +1018,7 @@ var/list/admin_verbs_event_manager = list(
return
var/job = input("Please select job slot to free", "Free job slot") as null|anything in jobs
if (job)
- job_master.FreeRole(job)
+ SSjobs.FreeRole(job)
message_admins("A job slot for [job] has been opened by [key_name_admin(usr)]")
return
diff --git a/code/modules/admin/topic.dm b/code/modules/admin/topic.dm
index 359fd38d3f0..e1eb09c007b 100644
--- a/code/modules/admin/topic.dm
+++ b/code/modules/admin/topic.dm
@@ -376,7 +376,7 @@
if(!M.ckey) //sanity
usr << "This mob has no ckey"
return
- if(!job_master)
+ if(!SSjobs)
usr << "Job Master has not been setup!"
return
@@ -397,7 +397,7 @@
jobs += "