mirror of
https://github.com/Bubberstation/Bubberstation.git
synced 2026-08-29 16:18:01 +01:00
Split malf AI antag datum away from traitor, enable malf AI roundstart ruleset. (#59064)
Co-authored-by: Mothblocks <35135081+Mothblocks@users.noreply.github.com>
This commit is contained in:
@@ -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])
|
||||
|
||||
@@ -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 <span class=''></span> 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 <span class=''> 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, "<span class='[span_class]'>$1</span>")
|
||||
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
|
||||
+5
-5
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 //
|
||||
|
||||
@@ -282,15 +282,40 @@
|
||||
"}
|
||||
break_counter++
|
||||
output += "</div></div>"
|
||||
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 += "<div class='column'><label class='rolegroup long [ckey(department)]'><input type='checkbox' name='[department]' class='hidden' [usr.client.prefs.tgui_fancy ? " onClick='toggle_checkboxes(this, \"_com\")'" : ""]>[department]</label><div class='content'>"
|
||||
break_counter = 0
|
||||
|
||||
@@ -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("<span class='adminnotice'>[key_name_admin(holder)] used everyone is a traitor secret. Objective is [objective]</span>")
|
||||
log_admin("[key_name(holder)] used everyone is a traitor secret. Objective is [objective]")
|
||||
if("massbraindamage")
|
||||
|
||||
@@ -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,"<span class='userdanger'>You are no longer the [job_rank]!</span>")
|
||||
|
||||
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, "<span class='alertsyndie'>You are the [owner.special_role].</span>")
|
||||
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, "<U><B>The Syndicate have provided you with the following codewords to identify fellow agents:</B></U>")
|
||||
to_chat(malf_mob, "<B>Code Phrase</B>: <span class='blue'>[phrases]</span>")
|
||||
to_chat(malf_mob, "<B>Code Response</B>: <span class='red'>[responses]</span>")
|
||||
|
||||
antag_memory += "<b>Code Phrase</b>: <span class='blue'>[phrases]</span><br>"
|
||||
antag_memory += "<b>Code Response</b>: <span class='red'>[responses]</span><br>"
|
||||
|
||||
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, "<span class='alertwarning'>You memorize the codewords, allowing you to recognise them when heard.</span>")
|
||||
|
||||
/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 += "<br><B>Objective #[count]</B>: [objective.explanation_text] <span class='greentext'>Success!</span>"
|
||||
else
|
||||
objectives_text += "<br><B>Objective #[count]</B>: [objective.explanation_text] <span class='redtext'>Fail.</span>"
|
||||
malf_ai_won = FALSE
|
||||
count++
|
||||
|
||||
result += objectives_text
|
||||
|
||||
var/special_role_text = lowertext(name)
|
||||
|
||||
if(malf_ai_won)
|
||||
result += "<span class='greentext'>The [special_role_text] was successful!</span>"
|
||||
else
|
||||
result += "<span class='redtext'>The [special_role_text] has failed!</span>"
|
||||
SEND_SOUND(owner.current, 'sound/ambience/ambifailure.ogg')
|
||||
|
||||
return result.Join("<br>")
|
||||
|
||||
#undef PROB_SPECIAL
|
||||
@@ -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, "<span class='userdanger'>New target added to database: [objective.target.name] ([status_text])</span>")
|
||||
@@ -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, "<span class='userdanger'>New target added to database: [objective.target.name] ([status_text])</span>")
|
||||
@@ -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")
|
||||
|
||||
@@ -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,"<span class='userdanger'>You are no longer the [special_role]!</span>")
|
||||
to_chat(owner.current,"<span class='userdanger'>You are no longer the [job_rank]!</span>")
|
||||
|
||||
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, "<span class='blue'>$1</span>")
|
||||
message = GLOB.syndicate_code_response_regex.Replace(message, "<span class='red'>$1</span>")
|
||||
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, "<span class='alertsyndie'>You are the [owner.special_role].</span>")
|
||||
@@ -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, "<span class='alertwarning'>You memorize the codewords, allowing you to recognise them when heard.</span>")
|
||||
|
||||
/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 += "<br><B>Objective #[count]</B>: [objective.explanation_text] <span class='greentext'>Success!</span>"
|
||||
else
|
||||
objectives_text += "<br><B>Objective #[count]</B>: [objective.explanation_text] <span class='redtext'>Fail.</span>"
|
||||
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 += "<BIG>[icon2html(badass, world)]</BIG>"
|
||||
result += uplink_text
|
||||
@@ -293,7 +222,7 @@
|
||||
if (contractor_hub)
|
||||
result += contractor_round_end()
|
||||
|
||||
if(traitorwin)
|
||||
if(traitor_won)
|
||||
result += "<span class='greentext'>The [special_role_text] was successful!</span>"
|
||||
else
|
||||
result += "<span class='redtext'>The [special_role_text] has failed!</span>"
|
||||
@@ -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
|
||||
|
||||
@@ -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<span class='alertwarning'>[user.real_name] is your superior. Follow any, and all orders given by them. You're here to support their mission only.</span>")
|
||||
to_chat(partner_mind.current, "<span class='alertwarning'>Should they perish, or be otherwise unavailable, you're to assist other active agents in this mission area to the best of your ability.</span>\n\n")
|
||||
|
||||
@@ -354,7 +354,7 @@
|
||||
if(20)
|
||||
//Free wizard!
|
||||
T.visible_message("<span class='userdanger'>Magic flows out of [src] and into [user]!</span>")
|
||||
user.mind.make_Wizard()
|
||||
user.mind.make_wizard()
|
||||
|
||||
/datum/outfit/butler
|
||||
name = "Butler"
|
||||
|
||||
@@ -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"
|
||||
|
||||
Reference in New Issue
Block a user