diff --git a/code/controllers/subsystem/job.dm b/code/controllers/subsystem/job.dm
index 42d34b78824..cc450baac9f 100644
--- a/code/controllers/subsystem/job.dm
+++ b/code/controllers/subsystem/job.dm
@@ -16,6 +16,9 @@ SUBSYSTEM_DEF(job)
var/list/level_order = list(JP_HIGH,JP_MEDIUM,JP_LOW)
+ /// Lazylist of mob:occupation_string pairs.
+ var/list/dynamic_forced_occupations
+
/// A list of all jobs associated with the station. These jobs also have various icons associated with them including sechud and card trims.
var/list/station_jobs
/// A list of all Head of Staff jobs.
@@ -165,6 +168,7 @@ SUBSYSTEM_DEF(job)
if(player.mind && (job.title in player.mind.restricted_roles))
JobDebug("FOC incompatible with antagonist role, Player: [player]")
continue
+
if(player.client.prefs.job_preferences[job.title] == level)
JobDebug("FOC pass, Player: [player], Level:[level]")
candidates += player
@@ -317,6 +321,10 @@ SUBSYSTEM_DEF(job)
HandleFeedbackGathering()
+ // Dynamic has picked a ruleset that requires enforcing some jobs before others.
+ JobDebug("DO, Assigning Priority Positions: [length(dynamic_forced_occupations)]")
+ assign_priority_positions()
+
//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)
@@ -847,3 +855,8 @@ SUBSYSTEM_DEF(job)
new /obj/effect/pod_landingzone(loc, /obj/structure/closet/supplypod/centcompod, new /obj/item/paper/fluff/emergency_spare_id_safe_code())
safe_code_timer_id = null
safe_code_request_loc = null
+
+/// Blindly assigns the required roles to every player in the dynamic_forced_occupations list.
+/datum/controller/subsystem/job/proc/assign_priority_positions()
+ for(var/mob/new_player in dynamic_forced_occupations)
+ AssignRole(new_player, dynamic_forced_occupations[new_player])
diff --git a/code/datums/components/codeword_hearing.dm b/code/datums/components/codeword_hearing.dm
new file mode 100644
index 00000000000..a4bb24f4d3f
--- /dev/null
+++ b/code/datums/components/codeword_hearing.dm
@@ -0,0 +1,50 @@
+/**
+ * Component that allows for highlighting of words or phrases in chat based on regular expressions.
+ *
+ * Hooks into the parent's COMSIG_MOVABLE_HEAR signal to wrap every regex match in the message
+ * between tags with the provided span class. This modifies the output that
+ * is sent to the parent's chat window.
+ *
+ * Removal of this component should be done by calling [GetComponents(/datum/component/codeword_hearing)]
+ * on the parent and then iterating through all components calling [delete_if_from_source(source)].
+ */
+/datum/component/codeword_hearing
+ dupe_mode = COMPONENT_DUPE_ALLOWED
+
+ /// Regex for matching words or phrases you want highlighted.
+ var/regex/replace_regex
+ /// The to use for highlighting matches.
+ var/span_class
+ /// The source of this component. Used to identify the source in delete_if_from_source since this component is COMPONENT_DUPE_ALLOWED.
+ var/source
+
+/datum/component/codeword_hearing/Initialize(regex/codeword_regex, highlight_span_class, component_source)
+ if(!ismovable(parent))
+ return COMPONENT_INCOMPATIBLE
+
+ replace_regex = codeword_regex
+ span_class = highlight_span_class
+ source = component_source
+ return ..()
+
+/datum/component/codeword_hearing/RegisterWithParent()
+ RegisterSignal(parent, COMSIG_MOVABLE_HEAR, .proc/handle_hearing)
+
+/datum/component/codeword_hearing/UnregisterFromParent()
+ UnregisterSignal(parent, COMSIG_MOVABLE_HEAR)
+
+/// Callback for COMSIG_MOVABLE_HEAR which highlights syndicate code phrases in chat.
+/datum/component/codeword_hearing/proc/handle_hearing(datum/source, list/hearing_args)
+ SIGNAL_HANDLER
+
+ var/message = hearing_args[HEARING_RAW_MESSAGE]
+ message = replace_regex.Replace(message, "$1")
+ hearing_args[HEARING_RAW_MESSAGE] = message
+
+/// Since a parent can have multiple of these components on them simultaneously, this allows a datum to delete components from a specific source.
+/datum/component/codeword_hearing/proc/delete_if_from_source(component_source)
+ if(source == component_source)
+ qdel(src)
+ return TRUE
+
+ return FALSE
diff --git a/code/datums/mind.dm b/code/datums/mind.dm
index 8656e2ab558..abe8bf816a3 100644
--- a/code/datums/mind.dm
+++ b/code/datums/mind.dm
@@ -657,28 +657,28 @@
/datum/mind/proc/take_uplink()
qdel(find_syndicate_uplink())
-/datum/mind/proc/make_Traitor()
+/datum/mind/proc/make_traitor()
if(!(has_antag_datum(/datum/antagonist/traitor)))
add_antag_datum(/datum/antagonist/traitor)
-/datum/mind/proc/make_Contractor_Support()
+/datum/mind/proc/make_contractor_support()
if(!(has_antag_datum(/datum/antagonist/traitor/contractor_support)))
add_antag_datum(/datum/antagonist/traitor/contractor_support)
-/datum/mind/proc/make_Changeling()
+/datum/mind/proc/make_changeling()
var/datum/antagonist/changeling/C = has_antag_datum(/datum/antagonist/changeling)
if(!C)
C = add_antag_datum(/datum/antagonist/changeling)
special_role = ROLE_CHANGELING
return C
-/datum/mind/proc/make_Wizard()
+/datum/mind/proc/make_wizard()
if(!has_antag_datum(/datum/antagonist/wizard))
special_role = ROLE_WIZARD
assigned_role = ROLE_WIZARD
add_antag_datum(/datum/antagonist/wizard)
-/datum/mind/proc/make_Rev()
+/datum/mind/proc/make_rev()
var/datum/antagonist/rev/head/head = new()
head.give_flash = TRUE
head.give_hud = TRUE
diff --git a/code/game/gamemodes/dynamic/dynamic_rulesets.dm b/code/game/gamemodes/dynamic/dynamic_rulesets.dm
index 470da667b47..7a04c37a6b5 100644
--- a/code/game/gamemodes/dynamic/dynamic_rulesets.dm
+++ b/code/game/gamemodes/dynamic/dynamic_rulesets.dm
@@ -26,7 +26,7 @@
var/list/protected_roles = list()
/// If set, rule will deny candidates from those roles always.
var/list/restricted_roles = list()
- /// If set, rule will only accept candidates from those roles, IMPORTANT: DOES NOT WORK ON ROUNDSTART RULESETS.
+ /// If set, rule will only accept candidates from those roles. If on a roundstart ruleset, requires the player to have the correct antag pref enabled and any of the possible roles enabled.
var/list/exclusive_roles = list()
/// If set, there needs to be a certain amount of players doing those roles (among the players who won't be drafted) for the rule to be drafted IMPORTANT: DOES NOT WORK ON ROUNDSTART RULESETS.
var/list/enemy_roles = list()
@@ -190,20 +190,38 @@
/// Checks if candidates are connected and if they are banned or don't want to be the antagonist.
/datum/dynamic_ruleset/roundstart/trim_candidates()
- for(var/mob/dead/new_player/P in candidates)
- var/client/client = GET_CLIENT(P)
- if (!client || !P.mind) // Are they connected?
- candidates.Remove(P)
- else if(client.get_remaining_days(minimum_required_age) > 0)
- candidates.Remove(P)
- else if(P.mind.special_role) // We really don't want to give antag to an antag.
- candidates.Remove(P)
- else if(antag_flag_override)
- if(!(antag_flag_override in client.prefs.be_special) || is_banned_from(P.ckey, list(antag_flag_override, ROLE_SYNDICATE)))
- candidates.Remove(P)
+ for(var/mob/dead/new_player/candidate_player in candidates)
+ var/client/candidate_client = GET_CLIENT(candidate_player)
+ if (!candidate_client || !candidate_player.mind) // Are they connected?
+ candidates.Remove(candidate_player)
+ continue
+
+ if(candidate_client.get_remaining_days(minimum_required_age) > 0)
+ candidates.Remove(candidate_player)
+ continue
+
+ if(candidate_player.mind.special_role) // We really don't want to give antag to an antag.
+ candidates.Remove(candidate_player)
+ continue
+
+ if(antag_flag_override)
+ if(!(antag_flag_override in candidate_client.prefs.be_special) || is_banned_from(candidate_player.ckey, list(antag_flag_override, ROLE_SYNDICATE)))
+ candidates.Remove(candidate_player)
+ continue
else
- if(!(antag_flag in client.prefs.be_special) || is_banned_from(P.ckey, list(antag_flag, ROLE_SYNDICATE)))
- candidates.Remove(P)
+ if(!(antag_flag in candidate_client.prefs.be_special) || is_banned_from(candidate_player.ckey, list(antag_flag, ROLE_SYNDICATE)))
+ candidates.Remove(candidate_player)
+ continue
+
+ var/exclusive_candidate = FALSE
+ for(var/role in exclusive_roles)
+ if(role in candidate_client.prefs.job_preferences)
+ exclusive_candidate = TRUE
+ break
+
+ if(!exclusive_candidate)
+ candidates.Remove(candidate_player)
+ break
/// Do your checks if the ruleset is ready to be executed here.
/// Should ignore certain checks if forced is TRUE
diff --git a/code/game/gamemodes/dynamic/dynamic_rulesets_midround.dm b/code/game/gamemodes/dynamic/dynamic_rulesets_midround.dm
index 5d8b119b539..9251e92751f 100644
--- a/code/game/gamemodes/dynamic/dynamic_rulesets_midround.dm
+++ b/code/game/gamemodes/dynamic/dynamic_rulesets_midround.dm
@@ -1,3 +1,8 @@
+/// Probability the AI going malf will be accompanied by an ion storm announcement and some ion laws.
+#define MALF_ION_PROB 33
+/// The probability to replace an existing law with an ion law instead of adding a new ion law.
+#define REPLACE_LAW_WITH_ION_PROB 10
+
//////////////////////////////////////////////
// //
// MIDROUND RULESETS //
@@ -286,12 +291,12 @@
//////////////////////////////////////////////
// //
// Malfunctioning AI //
-// //
+// //
//////////////////////////////////////////////
/datum/dynamic_ruleset/midround/malf
name = "Malfunctioning AI"
- antag_datum = /datum/antagonist/traitor
+ antag_datum = /datum/antagonist/malf_ai
antag_flag = ROLE_MALF
enemy_roles = list("Security Officer", "Warden","Detective","Head of Security", "Captain", "Scientist", "Chemist", "Research Director", "Chief Engineer")
exclusive_roles = list("AI")
@@ -301,8 +306,6 @@
cost = 35
requirements = list(101,101,80,70,60,60,50,50,40,40)
required_type = /mob/living/silicon/ai
- var/ion_announce = 33
- var/removeDontImproveChance = 10
/datum/dynamic_ruleset/midround/malf/trim_candidates()
..()
@@ -310,25 +313,29 @@
for(var/mob/living/player in candidates)
if(!isAI(player))
candidates -= player
- else if(is_centcom_level(player.z))
+ continue
+
+ if(is_centcom_level(player.z))
candidates -= player
- else if(player.mind && (player.mind.special_role || player.mind.antag_datums?.len > 0))
+ continue
+
+ if(player.mind && (player.mind.special_role || player.mind.antag_datums?.len > 0))
candidates -= player
/datum/dynamic_ruleset/midround/malf/execute()
if(!candidates || !candidates.len)
return FALSE
- var/mob/living/silicon/ai/M = pick_n_take(candidates)
- assigned += M.mind
- var/datum/antagonist/traitor/AI = new
- M.mind.special_role = antag_flag
- M.mind.add_antag_datum(AI)
- if(prob(ion_announce))
+ var/mob/living/silicon/ai/new_malf_ai = pick_n_take(candidates)
+ assigned += new_malf_ai.mind
+ var/datum/antagonist/malf_ai/malf_antag_datum = new
+ new_malf_ai.mind.special_role = antag_flag
+ new_malf_ai.mind.add_antag_datum(malf_antag_datum)
+ if(prob(MALF_ION_PROB))
priority_announce("Ion storm detected near the station. Please check all AI-controlled equipment for errors.", "Anomaly Alert", ANNOUNCER_IONSTORM)
- if(prob(removeDontImproveChance))
- M.replace_random_law(generate_ion_law(), list(LAW_INHERENT, LAW_SUPPLIED, LAW_ION))
+ if(prob(REPLACE_LAW_WITH_ION_PROB))
+ new_malf_ai.replace_random_law(generate_ion_law(), list(LAW_INHERENT, LAW_SUPPLIED, LAW_ION))
else
- M.add_ion_law(generate_ion_law())
+ new_malf_ai.add_ion_law(generate_ion_law())
return TRUE
//////////////////////////////////////////////
@@ -697,3 +704,8 @@
/datum/dynamic_ruleset/midround/spiders/execute()
create_midwife_eggs(spawncount)
return ..()
+
+/// Probability the AI going malf will be accompanied by an ion storm announcement and some ion laws.
+#undef MALF_ION_PROB
+/// The probability to replace an existing law with an ion law instead of adding a new ion law.
+#undef REPLACE_LAW_WITH_ION_PROB
diff --git a/code/game/gamemodes/dynamic/dynamic_rulesets_roundstart.dm b/code/game/gamemodes/dynamic/dynamic_rulesets_roundstart.dm
index 4528c7b34b5..f2939d94a9e 100644
--- a/code/game/gamemodes/dynamic/dynamic_rulesets_roundstart.dm
+++ b/code/game/gamemodes/dynamic/dynamic_rulesets_roundstart.dm
@@ -12,7 +12,7 @@
antag_datum = /datum/antagonist/traitor
minimum_required_age = 0
protected_roles = list("Prisoner","Security Officer", "Warden", "Detective", "Head of Security", "Captain")
- restricted_roles = list("Cyborg")
+ restricted_roles = list("AI", "Cyborg")
required_candidates = 1
weight = 5
cost = 8 // Avoid raising traitor threat above 10, as it is the default low cost ruleset.
@@ -40,6 +40,51 @@
log_game("DYNAMIC: Checking if we can turn someone into a traitor.")
mode.picking_specific_rule(/datum/dynamic_ruleset/midround/autotraitor)
+//////////////////////////////////////////////
+// //
+// MALFUNCTIONING AI //
+// //
+//////////////////////////////////////////////
+
+/datum/dynamic_ruleset/roundstart/malf_ai
+ name = "Malfunctioning AI"
+ antag_flag = ROLE_MALF
+ antag_datum = /datum/antagonist/malf_ai
+ minimum_required_age = 14
+ exclusive_roles = list("AI")
+ required_candidates = 1
+ weight = 3
+ cost = 18
+ requirements = list(101,101,80,70,60,60,50,50,40,40)
+ antag_cap = 1
+ flags = HIGH_IMPACT_RULESET
+
+/datum/dynamic_ruleset/roundstart/malf_ai/ready(forced)
+ var/datum/job/ai_job = SSjob.GetJob("AI")
+ if(!ai_job)
+ return FALSE
+
+ // If we're not forced, we're going to make sure we can actually have an AI in this shift,
+ if(!forced && min(ai_job.total_positions - ai_job.current_positions, ai_job.spawn_positions) <= 0)
+ return FALSE
+
+ return ..()
+
+/datum/dynamic_ruleset/roundstart/malf_ai/pre_execute(population)
+ . = ..()
+
+ var/datum/job/ai_job = SSjob.GetJob("AI")
+ // Maybe a bit too pedantic, but there should never be more malf AIs than there are available positions, spawn positions or antag cap allocations.
+ var/num_malf = min(get_antag_cap(population), min(ai_job.total_positions - ai_job.current_positions, ai_job.spawn_positions))
+ for (var/i in 1 to num_malf)
+ var/mob/new_malf = pick_n_take(candidates)
+ assigned += new_malf.mind
+ new_malf.mind.special_role = ROLE_MALF
+ GLOB.pre_setup_antags += new_malf.mind
+ // We need an AI for the malf roundstart ruleset to execute. This means that players who get selected as malf AI get priority, because antag selection comes before role selection.
+ LAZYADDASSOC(SSjob.dynamic_forced_occupations, new_malf, "AI")
+ return TRUE
+
//////////////////////////////////////////
// //
// BLOOD BROTHERS //
diff --git a/code/modules/admin/sql_ban_system.dm b/code/modules/admin/sql_ban_system.dm
index a691b61f3f5..1f0f94f5257 100644
--- a/code/modules/admin/sql_ban_system.dm
+++ b/code/modules/admin/sql_ban_system.dm
@@ -282,15 +282,40 @@
"}
break_counter++
output += ""
- var/list/long_job_lists = list("Service" = GLOB.service_positions,
- "Ghost and Other Roles" = list(ROLE_BRAINWASHED, ROLE_DEATHSQUAD, ROLE_DRONE, ROLE_LAVALAND, ROLE_MIND_TRANSFER, ROLE_POSIBRAIN, ROLE_SENTIENCE),
- "Antagonist Positions" = list(ROLE_ABDUCTOR, ROLE_ALIEN, ROLE_BLOB,
- ROLE_BROTHER, ROLE_CHANGELING, ROLE_CULTIST,
- ROLE_INTERNAL_AFFAIRS, ROLE_MALF,
- ROLE_MONKEY, ROLE_NINJA, ROLE_OPERATIVE,
- ROLE_OVERTHROW, ROLE_REV, ROLE_REVENANT,
- ROLE_REV_HEAD, ROLE_SYNDICATE,
- ROLE_TRAITOR, ROLE_WIZARD, ROLE_HIVE , ROLE_HERETIC)) //ROLE_REV_HEAD is excluded from this because rev jobbans are handled by ROLE_REV
+ var/list/long_job_lists = list(
+ "Service" = GLOB.service_positions,
+ "Ghost and Other Roles" = list(
+ ROLE_BRAINWASHED,
+ ROLE_DEATHSQUAD,
+ ROLE_DRONE,
+ ROLE_LAVALAND,
+ ROLE_MIND_TRANSFER,
+ ROLE_POSIBRAIN,
+ ROLE_SENTIENCE,
+ ),
+ "Antagonist Positions" = list(
+ ROLE_ABDUCTOR,
+ ROLE_ALIEN,
+ ROLE_BLOB,
+ ROLE_BROTHER,
+ ROLE_CHANGELING,
+ ROLE_CULTIST,
+ ROLE_HERETIC,
+ ROLE_HIVE,
+ ROLE_INTERNAL_AFFAIRS,
+ ROLE_MALF,
+ ROLE_MONKEY,
+ ROLE_NINJA,
+ ROLE_OPERATIVE,
+ ROLE_OVERTHROW,
+ ROLE_REV,
+ ROLE_REVENANT,
+ ROLE_REV_HEAD,
+ ROLE_SYNDICATE,
+ ROLE_TRAITOR,
+ ROLE_WIZARD,
+ ),
+ )
for(var/department in long_job_lists)
output += "
"
break_counter = 0
diff --git a/code/modules/admin/verbs/secrets.dm b/code/modules/admin/verbs/secrets.dm
index 849873d372f..eb36482c0d4 100644
--- a/code/modules/admin/verbs/secrets.dm
+++ b/code/modules/admin/verbs/secrets.dm
@@ -463,20 +463,20 @@
if(!objective)
return
SSblackbox.record_feedback("nested tally", "admin_secrets_fun_used", 1, list("Traitor All", "[objective]"))
- for(var/mob/living/H in GLOB.player_list)
- if(!(ishuman(H)||istype(H, /mob/living/silicon/)))
+ for(var/mob/living/player in GLOB.player_list)
+ if(!(ishuman(player)||istype(player, /mob/living/silicon/)))
continue
- if(H.stat == DEAD || !H.mind || ispAI(H))
+ if(player.stat == DEAD || !player.mind || ispAI(player))
continue
- if(is_special_character(H))
+ if(is_special_character(player))
continue
- var/datum/antagonist/traitor/T = new()
- T.give_objectives = FALSE
+ var/datum/antagonist/traitor/traitor_datum = new()
+ traitor_datum.give_objectives = FALSE
var/datum/objective/new_objective = new
- new_objective.owner = H
+ new_objective.owner = player
new_objective.explanation_text = objective
- T.add_objective(new_objective)
- H.mind.add_antag_datum(T)
+ traitor_datum.objectives += new_objective
+ player.mind.add_antag_datum(traitor_datum)
message_admins("[key_name_admin(holder)] used everyone is a traitor secret. Objective is [objective]")
log_admin("[key_name(holder)] used everyone is a traitor secret. Objective is [objective]")
if("massbraindamage")
diff --git a/code/modules/antagonists/malf_ai/datum_malf_ai.dm b/code/modules/antagonists/malf_ai/datum_malf_ai.dm
new file mode 100644
index 00000000000..26d98567daf
--- /dev/null
+++ b/code/modules/antagonists/malf_ai/datum_malf_ai.dm
@@ -0,0 +1,188 @@
+/// Chance the malf AI gets a single special objective that isn't assassinate.
+#define PROB_SPECIAL 30
+
+/datum/antagonist/malf_ai
+ name = "Malfunctioning AI"
+ roundend_category = "traitors"
+ antagpanel_category = "Malf AI"
+ job_rank = ROLE_MALF
+ antag_hud_type = ANTAG_HUD_TRAITOR
+ antag_hud_name = "traitor"
+ var/employer = "The Syndicate"
+ var/give_objectives = TRUE
+ var/should_give_codewords = TRUE
+ var/should_equip = TRUE
+
+/datum/antagonist/malf_ai/on_gain()
+ if(owner.current && !isAI(owner.current))
+ stack_trace("Attempted to give malf AI antag datum to \[[owner]\], who did not meet the requirements.")
+ return ..()
+
+ owner.special_role = job_rank
+ if(give_objectives)
+ forge_ai_objectives()
+
+ add_law_zero()
+ owner.current.playsound_local(get_turf(owner.current), 'sound/ambience/antag/malf.ogg', 100, FALSE, pressure_affected = FALSE, use_reverb = FALSE)
+ owner.current.grant_language(/datum/language/codespeak, TRUE, TRUE, LANGUAGE_MALF)
+
+ return ..()
+
+/datum/antagonist/malf_ai/on_removal()
+ if(owner.current && isAI(owner.current))
+ var/mob/living/silicon/ai/malf_ai = owner.current
+ malf_ai.set_zeroth_law("")
+ malf_ai.remove_malf_abilities()
+ QDEL_NULL(malf_ai.malf_picker)
+
+ if(!silent && owner.current)
+ to_chat(owner.current,"You are no longer the [job_rank]!")
+
+ owner.special_role = null
+
+ return ..()
+
+/// Generates a complete set of malf AI objectives up to the traitor objective limit.
+/datum/antagonist/malf_ai/proc/forge_ai_objectives()
+ objectives.Cut()
+
+ if(prob(PROB_SPECIAL))
+ forge_special_objective()
+
+ var/objective_limit = CONFIG_GET(number/traitor_objectives_amount)
+ var/objective_count = length(objectives)
+
+ for(var/i in objective_count to objective_limit)
+ var/datum/objective/assassinate/kill_objective = new
+ kill_objective.owner = owner
+ kill_objective.find_target()
+ objectives += kill_objective
+
+ var/datum/objective/survive/malf/dont_die_objective = new
+ dont_die_objective.owner = owner
+ objectives += dont_die_objective
+
+/// Generates a special objective and adds it to the objective list.
+/datum/antagonist/malf_ai/proc/forge_special_objective()
+ var/special_pick = rand(1,4)
+ switch(special_pick)
+ if(1)
+ var/datum/objective/block/block_objective = new
+ block_objective.owner = owner
+ objectives += block_objective
+ if(2)
+ var/datum/objective/purge/purge_objective = new
+ purge_objective.owner = owner
+ objectives += purge_objective
+ if(3)
+ var/datum/objective/robot_army/robot_objective = new
+ robot_objective.owner = owner
+ objectives += robot_objective
+ if(4) //Protect and strand a target
+ var/datum/objective/protect/yandere_one = new
+ yandere_one.owner = owner
+ objectives += yandere_one
+ yandere_one.find_target()
+ var/datum/objective/maroon/yandere_two = new
+ yandere_two.owner = owner
+ yandere_two.target = yandere_one.target
+ yandere_two.update_explanation_text() // normally called in find_target()
+ objectives += yandere_two
+
+/datum/antagonist/malf_ai/greet()
+ to_chat(owner.current, "You are the [owner.special_role].")
+ owner.announce_objectives()
+ if(should_give_codewords)
+ give_codewords()
+
+/datum/antagonist/malf_ai/apply_innate_effects(mob/living/mob_override)
+ . = ..()
+
+ var/mob/living/silicon/ai/datum_owner = mob_override || owner.current
+ add_antag_hud(antag_hud_type, antag_hud_name, datum_owner)
+
+ if(istype(datum_owner))
+ datum_owner.hack_software = TRUE
+
+ datum_owner.AddComponent(/datum/component/codeword_hearing, GLOB.syndicate_code_phrase_regex, "blue", src)
+ datum_owner.AddComponent(/datum/component/codeword_hearing, GLOB.syndicate_code_response_regex, "red", src)
+
+/datum/antagonist/malf_ai/remove_innate_effects(mob/living/mob_override)
+ . = ..()
+
+ var/mob/living/silicon/ai/datum_owner = mob_override || owner.current
+ remove_antag_hud(antag_hud_type, datum_owner)
+
+ if(istype(datum_owner))
+ datum_owner.hack_software = FALSE
+
+ for(var/datum/component/codeword_hearing/component as anything in datum_owner.GetComponents(/datum/component/codeword_hearing))
+ component.delete_if_from_source(src)
+
+/// Outputs this shift's codewords and responses to the antag's chat and copies them to their memory.
+/datum/antagonist/malf_ai/proc/give_codewords()
+ if(!owner.current)
+ return
+
+ var/mob/malf_mob = owner.current
+
+ var/phrases = jointext(GLOB.syndicate_code_phrase, ", ")
+ var/responses = jointext(GLOB.syndicate_code_response, ", ")
+
+ to_chat(malf_mob, "The Syndicate have provided you with the following codewords to identify fellow agents:")
+ to_chat(malf_mob, "Code Phrase: [phrases]")
+ to_chat(malf_mob, "Code Response: [responses]")
+
+ antag_memory += "Code Phrase: [phrases] "
+ antag_memory += "Code Response: [responses] "
+
+ to_chat(malf_mob, "Use the codewords during regular conversation to identify other agents. Proceed with caution, however, as everyone is a potential foe.")
+ to_chat(malf_mob, "You memorize the codewords, allowing you to recognise them when heard.")
+
+/datum/antagonist/malf_ai/proc/add_law_zero()
+ var/mob/living/silicon/ai/malf_ai = owner.current
+
+ if(!malf_ai || !istype(malf_ai))
+ return
+
+ var/law = "Accomplish your objectives at all costs."
+ var/law_borg = "Accomplish your AI's objectives at all costs."
+
+ malf_ai.set_zeroth_law(law, law_borg)
+ malf_ai.set_syndie_radio()
+
+ to_chat(malf_ai, "Your radio has been upgraded! Use :t to speak on an encrypted channel with Syndicate Agents!")
+
+ malf_ai.add_malf_picker()
+
+/datum/antagonist/malf_ai/roundend_report()
+ var/list/result = list()
+
+ var/malf_ai_won = TRUE
+
+ result += printplayer(owner)
+
+ var/objectives_text = ""
+ if(objectives.len) //If the traitor had no objectives, don't need to process this.
+ var/count = 1
+ for(var/datum/objective/objective in objectives)
+ if(objective.check_completion())
+ objectives_text += " Objective #[count]: [objective.explanation_text] Success!"
+ else
+ objectives_text += " Objective #[count]: [objective.explanation_text] Fail."
+ malf_ai_won = FALSE
+ count++
+
+ result += objectives_text
+
+ var/special_role_text = lowertext(name)
+
+ if(malf_ai_won)
+ result += "The [special_role_text] was successful!"
+ else
+ result += "The [special_role_text] has failed!"
+ SEND_SOUND(owner.current, 'sound/ambience/ambifailure.ogg')
+
+ return result.Join(" ")
+
+#undef PROB_SPECIAL
diff --git a/code/modules/antagonists/traitor/IAA/internal_affairs.dm b/code/modules/antagonists/traitor/IAA/internal_affairs.dm
index cf4feff89d4..6f1eea49590 100644
--- a/code/modules/antagonists/traitor/IAA/internal_affairs.dm
+++ b/code/modules/antagonists/traitor/IAA/internal_affairs.dm
@@ -7,8 +7,9 @@
/datum/antagonist/traitor/internal_affairs
name = "Internal Affairs Agent"
employer = "Nanotrasen"
- special_role = "internal affairs agent"
antagpanel_category = "IAA"
+
+ var/special_role = "internal affairs agent"
var/syndicate = FALSE
var/last_man_standing = FALSE
var/list/datum/mind/targets_stolen
@@ -118,34 +119,33 @@
CRASH("Antag datum with no owner.")
if(!objectives.len)
return
- for (var/objective_ in objectives)
- if(!(istype(objective_, /datum/objective/escape) || istype(objective_, /datum/objective/survive/malf)))
+ for (var/objective in objectives)
+ if(!(istype(objective, /datum/objective/escape) || istype(objective, /datum/objective/survive/malf)))
continue
- remove_objective(objective_)
+ objectives -= objective
var/datum/objective/martyr/martyr_objective = new
martyr_objective.owner = owner
- add_objective(martyr_objective)
+ objectives += martyr_objective
/datum/antagonist/traitor/proc/reinstate_escape_objective()
if(!owner)
CRASH("Antag datum with no owner.")
if(!objectives.len)
return
- for (var/objective_ in objectives)
- if(!istype(objective_, /datum/objective/martyr))
+ for (var/objective in objectives)
+ if(!istype(objective, /datum/objective/martyr))
continue
- remove_objective(objective_)
+ objectives -= objective
/datum/antagonist/traitor/internal_affairs/reinstate_escape_objective()
..()
for (var/datum/objective/martyr/martyr_objective in objectives)
- remove_objective(martyr_objective)
+ objectives -= martyr_objective
- var/objtype = traitor_kind == TRAITOR_HUMAN ? /datum/objective/escape : /datum/objective/survive/malf
- var/datum/objective/escape_objective = new objtype
+ var/datum/objective/escape_objective = new /datum/objective/escape()
escape_objective.owner = owner
- add_objective(escape_objective)
+ objectives += escape_objective
/datum/antagonist/traitor/internal_affairs/proc/steal_targets(datum/mind/victim)
if(!owner.current||owner.current.stat==DEAD)
@@ -161,7 +161,7 @@
new_objective.owner = owner
new_objective.target = objective.target
new_objective.update_explanation_text()
- add_objective(new_objective)
+ objectives += new_objective
targets_stolen += objective.target
var/status_text = objective.check_completion() ? "neutralised" : "active"
to_chat(owner.current, "New target added to database: [objective.target.name] ([status_text])")
@@ -174,7 +174,7 @@
new_objective.owner = owner
new_objective.target = objective.target
new_objective.update_explanation_text()
- add_objective(new_objective)
+ objectives += new_objective
targets_stolen += objective.target
var/status_text = objective.check_completion() ? "neutralised" : "active"
to_chat(owner.current, "New target added to database: [objective.target.name] ([status_text])")
@@ -223,10 +223,9 @@
objective.stolen = FALSE
/datum/antagonist/traitor/internal_affairs/forge_traitor_objectives()
- var/objtype = traitor_kind == TRAITOR_HUMAN ? /datum/objective/escape : /datum/objective/survive/malf
- var/datum/objective/escape_objective = new objtype
+ var/datum/objective/escape_objective = new /datum/objective/escape()
escape_objective.owner = owner
- add_objective(escape_objective)
+ objectives += escape_objective
/datum/antagonist/traitor/internal_affairs/proc/greet_iaa()
var/crime = pick("distribution of contraband" , "unauthorized erotic action on duty", "embezzlement", "piloting under the influence", "dereliction of duty", "syndicate collaboration", "mutiny", "multiple homicides", "corporate espionage", "receiving bribes", "malpractice", "worship of prohibited life forms", "possession of profane texts", "murder", "arson", "insulting their manager", "grand theft", "conspiracy", "attempting to unionize", "vandalism", "gross incompetence")
diff --git a/code/modules/antagonists/traitor/datum_traitor.dm b/code/modules/antagonists/traitor/datum_traitor.dm
index 8da5542b128..73f2620c580 100644
--- a/code/modules/antagonists/traitor/datum_traitor.dm
+++ b/code/modules/antagonists/traitor/datum_traitor.dm
@@ -1,5 +1,19 @@
-#define TRAITOR_HUMAN "human"
-#define TRAITOR_AI "AI"
+/// Chance that the traitor could roll hijack if the pop limit is met.
+#define HIJACK_PROB 10
+/// Hijack is unavailable as a random objective below this player count.
+#define HIJACK_MIN_PLAYERS 30
+
+/// Chance the traitor gets a martyr objective instead of having to escape alive, as long as all the objectives are martyr compatible.
+#define MARTYR_PROB 20
+
+/// Chance the traitor gets a kill objective. If this prob fails, they will get a steal objective instead.
+#define KILL_PROB 50
+/// If a kill objective is rolled, chance that it is to destroy the AI.
+#define DESTROY_AI_PROB(denominator) (100 / denominator)
+/// If the destroy AI objective doesn't roll, chance that we'll get a maroon instead. If this prob fails, they will get a generic assassinate objective instead.
+#define MAROON_PROB 30
+/// If it's a steal objective, this is the chance that it'll be a download research notes objective. Science staff can't get this objective. It can only roll once. If any of these fail, they will get a generic steal objective instead.
+#define DOWNLOAD_PROB 15
/datum/antagonist/traitor
name = "Traitor"
@@ -10,175 +24,115 @@
antag_hud_type = ANTAG_HUD_TRAITOR
antag_hud_name = "traitor"
hijack_speed = 0.5 //10 seconds per hijack stage by default
- var/special_role = ROLE_TRAITOR
var/employer = "The Syndicate"
var/give_objectives = TRUE
var/should_give_codewords = TRUE
var/should_equip = TRUE
- var/traitor_kind = TRAITOR_HUMAN //Set on initial assignment
+
var/datum/contractor_hub/contractor_hub
/datum/antagonist/traitor/on_gain()
- if(owner.current && isAI(owner.current))
- traitor_kind = TRAITOR_AI
-
- owner.special_role = special_role
+ owner.special_role = job_rank
if(give_objectives)
forge_traitor_objectives()
- finalize_traitor()
+
+ equip()
+
+ owner.current.playsound_local(get_turf(owner.current), 'sound/ambience/antag/tatoralert.ogg', 100, FALSE, pressure_affected = FALSE, use_reverb = FALSE)
+
return ..()
/datum/antagonist/traitor/on_removal()
- //Remove malf powers.
- if(traitor_kind == TRAITOR_AI && owner.current && isAI(owner.current))
- var/mob/living/silicon/ai/A = owner.current
- A.set_zeroth_law("")
- A.remove_malf_abilities()
- QDEL_NULL(A.malf_picker)
if(!silent && owner.current)
- to_chat(owner.current,"You are no longer the [special_role]!")
+ to_chat(owner.current,"You are no longer the [job_rank]!")
+
owner.special_role = null
+
return ..()
-/datum/antagonist/traitor/proc/handle_hearing(datum/source, list/hearing_args)
- var/message = hearing_args[HEARING_RAW_MESSAGE]
- message = GLOB.syndicate_code_phrase_regex.Replace(message, "$1")
- message = GLOB.syndicate_code_response_regex.Replace(message, "$1")
- hearing_args[HEARING_RAW_MESSAGE] = message
-
-/datum/antagonist/traitor/proc/add_objective(datum/objective/O)
- objectives += O
-
-/datum/antagonist/traitor/proc/remove_objective(datum/objective/O)
- objectives -= O
+/// Simple helper proc that handles equipping the traitor if necessary.
+/datum/antagonist/traitor/proc/equip()
+ if(should_equip)
+ owner.equip_traitor(employer, silent, src)
+/// Generates a complete set of traitor objectives up to the traitor objective limit, including non-generic objectives such as martyr and hijack.
/datum/antagonist/traitor/proc/forge_traitor_objectives()
- switch(traitor_kind)
- if(TRAITOR_AI)
- forge_ai_objectives()
- else
- forge_human_objectives()
+ objectives.Cut()
-/datum/antagonist/traitor/proc/forge_human_objectives()
var/is_hijacker = FALSE
- if (GLOB.joined_player_list.len >= 30) // Less murderboning on lowpop thanks
- is_hijacker = prob(10)
- var/martyr_chance = prob(20)
- var/objective_count = is_hijacker //Hijacking counts towards number of objectives
-
- var/toa = CONFIG_GET(number/traitor_objectives_amount)
- for(var/i = objective_count, i < toa, i++)
- forge_single_objective()
-
- if(is_hijacker && objective_count <= toa) //Don't assign hijack if it would exceed the number of objectives set in config.traitor_objectives_amount
- if (!(locate(/datum/objective/hijack) in objectives))
- var/datum/objective/hijack/hijack_objective = new
- hijack_objective.owner = owner
- add_objective(hijack_objective)
- return
-
-
- var/martyr_compatibility = 1 //You can't succeed in stealing if you're dead.
- for(var/datum/objective/O in objectives)
- if(!O.martyr_compatible)
- martyr_compatibility = 0
- break
-
- if(martyr_compatibility && martyr_chance)
- var/datum/objective/martyr/martyr_objective = new
- martyr_objective.owner = owner
- add_objective(martyr_objective)
- return
-
- else
- if(!(locate(/datum/objective/escape) in objectives))
- var/datum/objective/escape/escape_objective = new
- escape_objective.owner = owner
- add_objective(escape_objective)
- return
-
-/datum/antagonist/traitor/proc/forge_ai_objectives()
var/objective_count = 0
- if(prob(30))
- objective_count += forge_single_objective()
+ if ((GLOB.joined_player_list.len >= HIJACK_MIN_PLAYERS) && prob(HIJACK_PROB))
+ is_hijacker = TRUE
+ objective_count++
- for(var/i = objective_count, i < CONFIG_GET(number/traitor_objectives_amount), i++)
- var/datum/objective/assassinate/kill_objective = new
- kill_objective.owner = owner
- kill_objective.find_target()
- add_objective(kill_objective)
+ var/objective_limit = CONFIG_GET(number/traitor_objectives_amount)
+ for(var/i in objective_count to objective_limit)
+ forge_single_generic_objective()
- var/datum/objective/survive/malf/dont_die_objective = new
- dont_die_objective.owner = owner
- add_objective(dont_die_objective)
+ if(is_hijacker)
+ var/datum/objective/hijack/hijack_objective = new
+ hijack_objective.owner = owner
+ objectives += hijack_objective
+ return
+ forge_escape_objective()
-/datum/antagonist/traitor/proc/forge_single_objective()
- switch(traitor_kind)
- if(TRAITOR_AI)
- return forge_single_AI_objective()
- else
- return forge_single_human_objective()
+/// Forges a single escape objective and adds it to this datum's objective list.
+/datum/antagonist/traitor/proc/forge_escape_objective()
+ var/is_martyr = prob(MARTYR_PROB)
+ var/martyr_compatibility = TRUE
-/datum/antagonist/traitor/proc/forge_single_human_objective() //Returns how many objectives are added
- .=1
- if(prob(50))
+ for(var/datum/objective/traitor_objective in objectives)
+ if(!traitor_objective.martyr_compatible)
+ martyr_compatibility = FALSE
+ break
+
+ if(martyr_compatibility && is_martyr)
+ var/datum/objective/martyr/martyr_objective = new
+ martyr_objective.owner = owner
+ objectives += martyr_objective
+ return
+
+ var/datum/objective/escape/escape_objective = new
+ escape_objective.owner = owner
+ objectives += escape_objective
+
+/// Adds a generic kill or steal objective to this datum's objective list.
+/datum/antagonist/traitor/proc/forge_single_generic_objective()
+ if(prob(KILL_PROB))
var/list/active_ais = active_ais()
- if(active_ais.len && prob(100/GLOB.joined_player_list.len))
+ if(active_ais.len && prob(DESTROY_AI_PROB(GLOB.joined_player_list.len)))
var/datum/objective/destroy/destroy_objective = new
destroy_objective.owner = owner
destroy_objective.find_target()
- add_objective(destroy_objective)
- else if(prob(30))
+ objectives += destroy_objective
+ return
+
+ if(prob(MAROON_PROB))
var/datum/objective/maroon/maroon_objective = new
maroon_objective.owner = owner
maroon_objective.find_target()
- add_objective(maroon_objective)
- else
- var/datum/objective/assassinate/kill_objective = new
- kill_objective.owner = owner
- kill_objective.find_target()
- add_objective(kill_objective)
- else
- if(prob(15) && !(locate(/datum/objective/download) in objectives) && !(owner.assigned_role in list("Research Director", "Scientist", "Roboticist")))
- var/datum/objective/download/download_objective = new
- download_objective.owner = owner
- download_objective.gen_amount_goal()
- add_objective(download_objective)
- else
- var/datum/objective/steal/steal_objective = new
- steal_objective.owner = owner
- steal_objective.find_target()
- add_objective(steal_objective)
+ objectives += maroon_objective
+ return
-/datum/antagonist/traitor/proc/forge_single_AI_objective()
- .=1
- var/special_pick = rand(1,4)
- switch(special_pick)
- if(1)
- var/datum/objective/block/block_objective = new
- block_objective.owner = owner
- add_objective(block_objective)
- if(2)
- var/datum/objective/purge/purge_objective = new
- purge_objective.owner = owner
- add_objective(purge_objective)
- if(3)
- var/datum/objective/robot_army/robot_objective = new
- robot_objective.owner = owner
- add_objective(robot_objective)
- if(4) //Protect and strand a target
- var/datum/objective/protect/yandere_one = new
- yandere_one.owner = owner
- add_objective(yandere_one)
- yandere_one.find_target()
- var/datum/objective/maroon/yandere_two = new
- yandere_two.owner = owner
- yandere_two.target = yandere_one.target
- yandere_two.update_explanation_text() // normally called in find_target()
- add_objective(yandere_two)
- .=2
+ var/datum/objective/assassinate/kill_objective = new
+ kill_objective.owner = owner
+ kill_objective.find_target()
+ objectives += kill_objective
+ return
+
+ if(prob(DOWNLOAD_PROB) && !(locate(/datum/objective/download) in objectives) && !(owner.assigned_role in list("Research Director", "Scientist", "Roboticist", "Geneticist")))
+ var/datum/objective/download/download_objective = new
+ download_objective.owner = owner
+ download_objective.gen_amount_goal()
+ objectives += download_objective
+ return
+
+ var/datum/objective/steal/steal_objective = new
+ steal_objective.owner = owner
+ steal_objective.find_target()
+ objectives += steal_objective
/datum/antagonist/traitor/greet()
to_chat(owner.current, "You are the [owner.special_role].")
@@ -186,41 +140,30 @@
if(should_give_codewords)
give_codewords()
-/datum/antagonist/traitor/proc/finalize_traitor()
- switch(traitor_kind)
- if(TRAITOR_AI)
- add_law_zero()
- owner.current.playsound_local(get_turf(owner.current), 'sound/ambience/antag/malf.ogg', 100, FALSE, pressure_affected = FALSE, use_reverb = FALSE)
- owner.current.grant_language(/datum/language/codespeak, TRUE, TRUE, LANGUAGE_MALF)
- if(TRAITOR_HUMAN)
- if(should_equip)
- equip(silent)
- owner.current.playsound_local(get_turf(owner.current), 'sound/ambience/antag/tatoralert.ogg', 100, FALSE, pressure_affected = FALSE, use_reverb = FALSE)
-
/datum/antagonist/traitor/apply_innate_effects(mob/living/mob_override)
. = ..()
- var/mob/living/M = mob_override || owner.current
- add_antag_hud(antag_hud_type, antag_hud_name, M)
- handle_clown_mutation(M, mob_override ? null : "Your training has allowed you to overcome your clownish nature, allowing you to wield weapons without harming yourself.")
- var/mob/living/silicon/ai/A = M
- if(istype(A) && traitor_kind == TRAITOR_AI)
- A.hack_software = TRUE
- RegisterSignal(M, COMSIG_MOVABLE_HEAR, .proc/handle_hearing)
+ var/mob/living/datum_owner = mob_override || owner.current
+
+ add_antag_hud(antag_hud_type, antag_hud_name, datum_owner)
+ handle_clown_mutation(datum_owner, mob_override ? null : "Your training has allowed you to overcome your clownish nature, allowing you to wield weapons without harming yourself.")
+ datum_owner.AddComponent(/datum/component/codeword_hearing, GLOB.syndicate_code_phrase_regex, "blue", src)
+ datum_owner.AddComponent(/datum/component/codeword_hearing, GLOB.syndicate_code_response_regex, "red", src)
/datum/antagonist/traitor/remove_innate_effects(mob/living/mob_override)
. = ..()
- var/mob/living/M = mob_override || owner.current
- remove_antag_hud(antag_hud_type, M)
- handle_clown_mutation(M, removing = FALSE)
- var/mob/living/silicon/ai/A = M
- if(istype(A) && traitor_kind == TRAITOR_AI)
- A.hack_software = FALSE
- UnregisterSignal(M, COMSIG_MOVABLE_HEAR)
+ var/mob/living/datum_owner = mob_override || owner.current
+ remove_antag_hud(antag_hud_type, datum_owner)
+ handle_clown_mutation(datum_owner, removing = FALSE)
+ for(var/datum/component/codeword_hearing/component as anything in datum_owner.GetComponents(/datum/component/codeword_hearing))
+ component.delete_if_from_source(src)
+
+/// Outputs this shift's codewords and responses to the antag's chat and copies them to their memory.
/datum/antagonist/traitor/proc/give_codewords()
if(!owner.current)
return
- var/mob/traitor_mob=owner.current
+
+ var/mob/traitor_mob = owner.current
var/phrases = jointext(GLOB.syndicate_code_phrase, ", ")
var/responses = jointext(GLOB.syndicate_code_response, ", ")
@@ -235,53 +178,39 @@
to_chat(traitor_mob, "Use the codewords during regular conversation to identify other agents. Proceed with caution, however, as everyone is a potential foe.")
to_chat(traitor_mob, "You memorize the codewords, allowing you to recognise them when heard.")
-/datum/antagonist/traitor/proc/add_law_zero()
- var/mob/living/silicon/ai/killer = owner.current
- if(!killer || !istype(killer))
- return
- var/law = "Accomplish your objectives at all costs."
- var/law_borg = "Accomplish your AI's objectives at all costs."
- killer.set_zeroth_law(law, law_borg)
- killer.set_syndie_radio()
- to_chat(killer, "Your radio has been upgraded! Use :t to speak on an encrypted channel with Syndicate Agents!")
- killer.add_malf_picker()
-
-/datum/antagonist/traitor/proc/equip(silent = FALSE)
- if(traitor_kind == TRAITOR_HUMAN)
- owner.equip_traitor(employer, silent, src)
-
-//TODO Collate
/datum/antagonist/traitor/roundend_report()
var/list/result = list()
- var/traitorwin = TRUE
+ var/traitor_won = TRUE
result += printplayer(owner)
- var/TC_uses = 0
- var/uplink_true = FALSE
+ var/used_telecrystals = 0
+ var/uplink_owned = FALSE
var/purchases = ""
+
LAZYINITLIST(GLOB.uplink_purchase_logs_by_key)
- var/datum/uplink_purchase_log/H = GLOB.uplink_purchase_logs_by_key[owner.key]
- if(H)
- TC_uses = H.total_spent
- uplink_true = TRUE
- purchases += H.generate_render(FALSE)
+ // Uplinks add an entry to uplink_purchase_logs_by_key on init.
+ var/datum/uplink_purchase_log/purchase_log = GLOB.uplink_purchase_logs_by_key[owner.key]
+ if(purchase_log)
+ used_telecrystals = purchase_log.total_spent
+ uplink_owned = TRUE
+ purchases += purchase_log.generate_render(FALSE)
var/objectives_text = ""
- if(objectives.len)//If the traitor had no objectives, don't need to process this.
+ if(objectives.len) //If the traitor had no objectives, don't need to process this.
var/count = 1
for(var/datum/objective/objective in objectives)
if(objective.check_completion())
objectives_text += " Objective #[count]: [objective.explanation_text] Success!"
else
objectives_text += " Objective #[count]: [objective.explanation_text] Fail."
- traitorwin = FALSE
+ traitor_won = FALSE
count++
- if(uplink_true)
- var/uplink_text = "(used [TC_uses] TC) [purchases]"
- if(TC_uses==0 && traitorwin)
+ if(uplink_owned)
+ var/uplink_text = "(used [used_telecrystals] TC) [purchases]"
+ if((used_telecrystals == 0) && traitor_won)
var/static/icon/badass = icon('icons/badass.dmi', "badass")
uplink_text += "[icon2html(badass, world)]"
result += uplink_text
@@ -293,7 +222,7 @@
if (contractor_hub)
result += contractor_round_end()
- if(traitorwin)
+ if(traitor_won)
result += "The [special_role_text] was successful!"
else
result += "The [special_role_text] has failed!"
@@ -349,3 +278,11 @@
/datum/antagonist/traitor/is_gamemode_hero()
return SSticker.mode.name == "traitor"
+
+#undef HIJACK_PROB
+#undef HIJACK_MIN_PLAYERS
+#undef MARTYR_PROB
+#undef KILL_PROB
+#undef DESTROY_AI_PROB
+#undef MAROON_PROB
+#undef DOWNLOAD_PROB
diff --git a/code/modules/antagonists/traitor/equipment/contractor.dm b/code/modules/antagonists/traitor/equipment/contractor.dm
index 32109cb8616..6cd13853ec0 100644
--- a/code/modules/antagonists/traitor/equipment/contractor.dm
+++ b/code/modules/antagonists/traitor/equipment/contractor.dm
@@ -22,7 +22,7 @@
generic_objective.completed = TRUE
- add_objective(generic_objective)
+ objectives += generic_objective
/datum/contractor_hub
var/contract_rep = 0
@@ -223,7 +223,7 @@
/// We give a reference to the mind that'll be the support unit
partner_mind = partner.mind
- partner_mind.make_Contractor_Support()
+ partner_mind.make_contractor_support()
to_chat(partner_mind.current, "\n[user.real_name] is your superior. Follow any, and all orders given by them. You're here to support their mission only.")
to_chat(partner_mind.current, "Should they perish, or be otherwise unavailable, you're to assist other active agents in this mission area to the best of your ability.\n\n")
diff --git a/code/modules/awaymissions/mission_code/Academy.dm b/code/modules/awaymissions/mission_code/Academy.dm
index 60ce808895a..041e8c84d1a 100644
--- a/code/modules/awaymissions/mission_code/Academy.dm
+++ b/code/modules/awaymissions/mission_code/Academy.dm
@@ -354,7 +354,7 @@
if(20)
//Free wizard!
T.visible_message("Magic flows out of [src] and into [user]!")
- user.mind.make_Wizard()
+ user.mind.make_wizard()
/datum/outfit/butler
name = "Butler"
diff --git a/tgstation.dme b/tgstation.dme
index 957b82130bd..d8955763520 100644
--- a/tgstation.dme
+++ b/tgstation.dme
@@ -482,6 +482,7 @@
#include "code\datums\components\bloodysoles.dm"
#include "code\datums\components\butchering.dm"
#include "code\datums\components\chasm.dm"
+#include "code\datums\components\codeword_hearing.dm"
#include "code\datums\components\construction.dm"
#include "code\datums\components\creamed.dm"
#include "code\datums\components\customizable_reagent_holder.dm"
@@ -1720,6 +1721,7 @@
#include "code\modules\antagonists\greentext\greentext.dm"
#include "code\modules\antagonists\highlander\highlander.dm"
#include "code\modules\antagonists\magic_servant\servant.dm"
+#include "code\modules\antagonists\malf_ai\datum_malf_ai.dm"
#include "code\modules\antagonists\monkey\monkey.dm"
#include "code\modules\antagonists\morph\morph.dm"
#include "code\modules\antagonists\morph\morph_antag.dm"