From 93908eefc8a593495e211bb068091d5cc62c1fe3 Mon Sep 17 00:00:00 2001 From: SteelSlayer <42044220+SteelSlayer@users.noreply.github.com> Date: Thu, 25 Nov 2021 01:29:46 -0600 Subject: [PATCH] Antag datum clean up/refactor (#15084) --- code/__HELPERS/lists.dm | 12 +- code/datums/mind.dm | 91 ++-- code/datums/uplink_item.dm | 4 +- code/game/gamemodes/changeling/changeling.dm | 6 +- code/game/gamemodes/traitor/traitor.dm | 75 ++- code/game/gamemodes/vampire/vampire.dm | 6 +- code/game/objects/items/devices/uplinks.dm | 7 +- .../items/weapons/implants/implant_traitor.dm | 91 ++-- code/modules/admin/verbs/randomverbs.dm | 2 +- .../antagonists/_common/antag_datum.dm | 353 +++++++++++-- .../antagonists/traitor/datum_mindslave.dm | 106 ++-- .../antagonists/traitor/datum_traitor.dm | 466 ++++++------------ code/modules/mob/mob_helpers.dm | 6 - 13 files changed, 633 insertions(+), 592 deletions(-) diff --git a/code/__HELPERS/lists.dm b/code/__HELPERS/lists.dm index bebb0ce4c4c..f7a536f905f 100644 --- a/code/__HELPERS/lists.dm +++ b/code/__HELPERS/lists.dm @@ -82,13 +82,13 @@ return 0 //Checks for specific types in a list -/proc/is_type_in_list(atom/A, list/L) - if(!L || !L.len || !A) - return 0 +/proc/is_type_in_list(datum/D, list/L) + if(!L || !length(L) || !D) + return FALSE for(var/type in L) - if(istype(A, type)) - return 1 - return 0 + if(istype(D, type)) + return TRUE + return FALSE //Checks for specific types in specifically structured (Assoc "type" = TRUE) lists ('typecaches') /proc/is_type_in_typecache(atom/A, list/L) diff --git a/code/datums/mind.dm b/code/datums/mind.dm index 394c94b0014..723b3425cef 100644 --- a/code/datums/mind.dm +++ b/code/datums/mind.dm @@ -159,15 +159,9 @@ /datum/mind/proc/gen_objective_text(admin = FALSE) . = "" var/obj_count = 1 - var/list/all_objectives = list() - for(var/datum/antagonist/A in antag_datums) - all_objectives |= A.objectives - if(LAZYLEN(all_objectives)) - for(var/datum/objective/objective in all_objectives) - . += "
Objective #[obj_count++]: [objective.explanation_text]" - - for(var/datum/objective/objective in objectives) + // If they don't have any objectives, "" will be returned. + for(var/datum/objective/objective in get_all_objectives()) . += "Objective #[obj_count++]: [objective.explanation_text]" if(admin) . += " Edit " // Edit @@ -178,6 +172,22 @@ . += "" . += "
" +/** + * Gets every objective this mind owns, including all of those from any antag datums they have, and returns them as a list. + */ +/datum/mind/proc/get_all_objectives() + var/list/all_objectives = list() + + for(var/antag in antag_datums) + var/datum/antagonist/A = antag + all_objectives += A.objectives // Add all antag datum objectives. + + for(var/objective in objectives) + var/datum/objective/O = objective + all_objectives += O // Add all mind objectives. + + return all_objectives + /datum/mind/proc/_memory_edit_header(gamemode, list/alt) . = gamemode if(SSticker.mode.config_tag == gamemode || (LAZYLEN(alt) && (SSticker.mode.config_tag in alt))) @@ -325,7 +335,8 @@ . = _memory_edit_header("traitor", list("traitorchan", "traitorvamp")) if(has_antag_datum(/datum/antagonist/traitor)) . += "TRAITOR|no" - if(objectives.len==0) + var/datum/antagonist/traitor/T = has_antag_datum(/datum/antagonist/traitor) + if(!length(T.objectives)) . += "
Objectives are empty! Randomize!" else . += "traitor|NO" @@ -489,7 +500,7 @@ out += memory out += "
Edit memory
" out += "Objectives:
" - if(objectives.len == 0) + if(!length(get_all_objectives())) out += "EMPTY
" else out += gen_objective_text(admin = TRUE) @@ -701,6 +712,13 @@ var/datum/objective/objective = locate(href_list["obj_delete"]) if(!istype(objective)) return + for(var/antag in antag_datums) + var/datum/antagonist/A = antag + A.objectives -= objective + A.assigned_targets -= "[objective.target]" + if(istype(objective, /datum/objective/steal)) + var/datum/objective/steal/S = objective + A.assigned_targets -= "[S.steal_target]" objectives -= objective log_admin("[key_name(usr)] has removed one of [key_name(current)]'s objectives: [objective]") @@ -839,8 +857,6 @@ qdel(flash) take_uplink() var/fail = 0 - var/datum/antagonist/traitor/T = has_antag_datum(/datum/antagonist/traitor) - fail |= !T.equip_traitor(src) fail |= !SSticker.mode.equip_revolutionary(current) if(fail) to_chat(usr, "Reequipping revolutionary goes wrong!") @@ -1091,7 +1107,6 @@ if(has_antag_datum(/datum/antagonist/traitor)) to_chat(current, "You have been brainwashed! You are no longer a traitor!") remove_antag_datum(/datum/antagonist/traitor) - current.client.chatOutput?.clear_syndicate_codes() log_admin("[key_name(usr)] has de-traitored [key_name(current)]") message_admins("[key_name_admin(usr)] has de-traitored [key_name_admin(current)]") @@ -1099,14 +1114,14 @@ if(!(has_antag_datum(/datum/antagonist/traitor))) var/datum/antagonist/traitor/T = new() T.give_objectives = FALSE - T.should_equip = FALSE + T.give_uplink = FALSE add_antag_datum(T) log_admin("[key_name(usr)] has traitored [key_name(current)]") message_admins("[key_name_admin(usr)] has traitored [key_name_admin(current)]") if("autoobjectives") var/datum/antagonist/traitor/T = has_antag_datum(/datum/antagonist/traitor) - T.forge_traitor_objectives(src) + T.give_objectives() to_chat(usr, "The objectives for traitor [key] have been generated. You can edit them and announce manually.") log_admin("[key_name(usr)] has automatically forged objectives for [key_name(current)]") message_admins("[key_name_admin(usr)] has automatically forged objectives for [key_name_admin(current)]") @@ -1424,8 +1439,7 @@ if("uplink") if(has_antag_datum(/datum/antagonist/traitor)) var/datum/antagonist/traitor/T = has_antag_datum(/datum/antagonist/traitor) - T.give_codewords() - if(!T.equip_traitor(src)) + if(!T.give_uplink()) to_chat(usr, "Equipping a syndicate failed!") return log_admin("[key_name(usr)] has given [key_name(current)] an uplink") @@ -1465,6 +1479,7 @@ var/datum/team/antag_team = A.get_team() if(antag_team) antag_team.add_member(src) + ASSERT(A.owner && A.owner.current) A.on_gain() return A @@ -1473,6 +1488,8 @@ return var/datum/antagonist/A = has_antag_datum(datum_type) if(A) + if(!A.owner?.current) + CRASH("attempted to remove an antag datum ([A.type]) that didn't have an owner or owner.current") A.on_removal() return TRUE @@ -1480,6 +1497,9 @@ /datum/mind/proc/remove_all_antag_datums() //For the Lazy amongst us. for(var/a in antag_datums) var/datum/antagonist/A = a + if(!A.owner?.current) + stack_trace("attempted to remove an antag datum ([A.type]) that didn't have an owner or owner.current") + continue A.on_removal() /datum/mind/proc/has_antag_datum(datum_type, check_subtypes = TRUE) @@ -1614,7 +1634,6 @@ qdel(flash) take_uplink() var/fail = 0 -// fail |= !ticker.mode.equip_traitor(current, 1) fail |= !SSticker.mode.equip_revolutionary(current) /datum/mind/proc/make_Abductor() @@ -1704,38 +1723,12 @@ G.reenter_corpse() -/datum/mind/proc/make_zealot(mob/living/carbon/human/missionary, convert_duration = 6000, team_color = "red") - +/datum/mind/proc/make_zealot(mob/living/carbon/human/missionary, convert_duration = 10 MINUTES, team_color = "red") zealot_master = missionary - var/list/implanters - if(!(missionary.mind in SSticker.mode.implanter)) - SSticker.mode.implanter[missionary.mind] = list() - implanters = SSticker.mode.implanter[missionary.mind] - implanters.Add(src) - SSticker.mode.implanted.Add(src) - SSticker.mode.implanted[src] = missionary.mind - SSticker.mode.implanter[missionary.mind] = implanters - SSticker.mode.traitors += src - - - var/datum/objective/protect/zealot_objective = new - zealot_objective.target = missionary.mind - zealot_objective.owner = src - zealot_objective.explanation_text = "Obey every order from and protect [missionary.real_name], the [missionary.mind.assigned_role == missionary.mind.special_role ? (missionary.mind.special_role) : (missionary.mind.assigned_role)]." - objectives += zealot_objective - add_antag_datum(/datum/antagonist/mindslave) - - var/datum/antagonist/traitor/T = missionary.mind.has_antag_datum(/datum/antagonist) - T.update_traitor_icons_added(missionary.mind) - - to_chat(current, "You're now a loyal zealot of [missionary.name]! You now must lay down your life to protect [missionary.p_them()] and assist in [missionary.p_their()] goals at any cost.") - - var/datum/mindslaves/slaved = missionary.mind.som - som = slaved - slaved.serv += current - slaved.add_serv_hud(missionary.mind, "master") //handles master servent icons - slaved.add_serv_hud(src, "mindslave") + // Give the new zealot their mindslave datum with a custom greeting. + var/greeting = "You're now a loyal zealot of [missionary.name]! You now must lay down your life to protect [missionary.p_them()] and assist in [missionary.p_their()] goals at any cost." + add_antag_datum(new /datum/antagonist/mindslave(missionary.mind, greeting)) var/obj/item/clothing/under/jumpsuit = null if(ishuman(current)) //only bother with the jumpsuit stuff if we are a human type, since we won't have the slot otherwise @@ -1747,7 +1740,7 @@ add_attack_logs(missionary, current, "Converted to a zealot for [convert_duration/600] minutes") addtimer(CALLBACK(src, .proc/remove_zealot, jumpsuit), convert_duration) //deconverts after the timer expires - return 1 + return TRUE /datum/mind/proc/remove_zealot(obj/item/clothing/under/jumpsuit = null) if(!zealot_master) //if they aren't a zealot, we can't remove their zealot status, obviously. don't bother with the rest so we don't confuse them with the messages diff --git a/code/datums/uplink_item.dm b/code/datums/uplink_item.dm index 7c79f870482..63135d8d05a 100644 --- a/code/datums/uplink_item.dm +++ b/code/datums/uplink_item.dm @@ -85,7 +85,7 @@ GLOBAL_LIST_INIT(uplink_items, subtypesof(/datum/uplink_item)) /datum/uplink_item/proc/spawn_item(turf/loc, obj/item/uplink/U) if(hijack_only && !(usr.mind.special_role == SPECIAL_ROLE_NUKEOPS))//nukies get items that regular traitors only get with hijack. If a hijack-only item is not for nukies, then exclude it via the gamemode list. - if(!(locate(/datum/objective/hijack) in usr.mind.objectives)) + if(!(locate(/datum/objective/hijack) in usr.mind.get_all_objectives())) to_chat(usr, "The Syndicate will only issue this extremely dangerous item to agents assigned the Hijack objective.") return @@ -1748,7 +1748,7 @@ GLOBAL_LIST_INIT(uplink_items, subtypesof(/datum/uplink_item)) CU.hub = new(mind, CU) // Update their mind stuff LAZYSET(GLOB.contractors, mind, CU.hub) - AT.update_traitor_icons_added(mind) + AT.add_antag_hud(mind.current) log_game("[key_name(usr)] became a Contractor") return I diff --git a/code/game/gamemodes/changeling/changeling.dm b/code/game/gamemodes/changeling/changeling.dm index 330a5e94257..adff8634c03 100644 --- a/code/game/gamemodes/changeling/changeling.dm +++ b/code/game/gamemodes/changeling/changeling.dm @@ -201,9 +201,11 @@ GLOBAL_LIST_INIT(possible_changeling_IDs, list("Alpha","Beta","Gamma","Delta","E text += "
Changeling ID: [changeling.changeling.changelingID]." text += "
Genomes Extracted: [changeling.changeling.absorbedcount]" - if(changeling.objectives.len) + var/list/all_objectives = changeling.get_all_objectives() + + if(length(all_objectives)) var/count = 1 - for(var/datum/objective/objective in changeling.objectives) + for(var/datum/objective/objective in all_objectives) if(objective.check_completion()) text += "
Objective #[count]: [objective.explanation_text] Success!" SSblackbox.record_feedback("nested tally", "changeling_objective", 1, list("[objective.type]", "SUCCESS")) diff --git a/code/game/gamemodes/traitor/traitor.dm b/code/game/gamemodes/traitor/traitor.dm index 091e59ce354..2f79ad30a7e 100644 --- a/code/game/gamemodes/traitor/traitor.dm +++ b/code/game/gamemodes/traitor/traitor.dm @@ -1,12 +1,9 @@ /datum/game_mode - // this includes admin-appointed traitors and multitraitors. Easy! + /// A list of all minds which have the traitor antag datum. var/list/datum/mind/traitors = list() - var/list/datum/mind/implanter = list() + /// An associative list with mindslave minds as keys and their master's minds as values. var/list/datum/mind/implanted = list() - var/datum/mind/exchange_red - var/datum/mind/exchange_blue - /datum/game_mode/traitor name = "traitor" config_tag = "traitor" @@ -15,11 +12,12 @@ required_players = 0 required_enemies = 1 recommended_enemies = 4 - + /// A list containing references to the minds of soon-to-be traitors. This is seperate to avoid duplicate entries in the `traitors` list. var/list/datum/mind/pre_traitors = list() - var/traitors_possible = 4 //hard limit on traitors if scaling is turned off - var/const/traitor_scaling_coeff = 5.0 //how much does the amount of players get divided by to determine traitors - var/antag_datum = /datum/antagonist/traitor //what type of antag to create + /// Hard limit on traitors if scaling is turned off. + var/traitors_possible = 4 + /// How much the amount of players get divided by to determine the number of traitors. + var/const/traitor_scaling_coeff = 5 /datum/game_mode/traitor/announce() to_chat(world, "The current game mode is - Traitor!") @@ -38,8 +36,8 @@ possible_traitors.Remove(candidate) // stop setup if no possible traitors - if(!possible_traitors.len) - return 0 + if(!length(possible_traitors)) + return FALSE var/num_traitors = 1 @@ -48,26 +46,23 @@ else num_traitors = max(1, min(num_players(), traitors_possible)) - for(var/j = 0, j < num_traitors, j++) - if(!possible_traitors.len) + for(var/i in 1 to num_traitors) + if(!length(possible_traitors)) break - var/datum/mind/traitor = pick(possible_traitors) + var/datum/mind/traitor = pick_n_take(possible_traitors) pre_traitors += traitor traitor.special_role = SPECIAL_ROLE_TRAITOR traitor.restricted_roles = restricted_jobs - possible_traitors.Remove(traitor) - if(!pre_traitors.len) - return 0 - return 1 + if(!length(pre_traitors)) + return FALSE + return TRUE /datum/game_mode/traitor/post_setup() - for(var/datum/mind/traitor in pre_traitors) - var/datum/antagonist/traitor/new_antag = new antag_datum() - addtimer(CALLBACK(traitor, /datum/mind.proc/add_antag_datum, new_antag), rand(10,100)) - if(!exchange_blue) - exchange_blue = -1 //Block latejoiners from getting exchange objectives + for(var/t in pre_traitors) + var/datum/mind/traitor = t + traitor.add_antag_datum(/datum/antagonist/traitor) ..() @@ -75,44 +70,37 @@ ..() return//Traitors will be checked as part of check_extra_completion. Leaving this here as a reminder. -/datum/game_mode/traitor/process() - // Make sure all objectives are processed regularly, so that objectives - // which can be checked mid-round are checked mid-round. - for(var/datum/mind/traitor_mind in traitors) - for(var/datum/objective/objective in traitor_mind.objectives) - objective.check_completion() - return 0 - - /datum/game_mode/proc/auto_declare_completion_traitor() - if(traitors.len) + if(length(traitors)) var/text = "The traitors were:
" for(var/datum/mind/traitor in traitors) - var/traitorwin = 1 + var/traitorwin = TRUE text += printplayer(traitor) var/TC_uses = 0 - var/uplink_true = 0 + var/used_uplink = FALSE var/purchases = "" for(var/obj/item/uplink/H in GLOB.world_uplinks) - if(H && H.uplink_owner && H.uplink_owner==traitor.key) + if(H && H.uplink_owner && H.uplink_owner == traitor.key) TC_uses += H.used_TC - uplink_true=1 + used_uplink = TRUE purchases += H.purchase_log - if(uplink_true) text += " (used [TC_uses] TC) [purchases]" + if(used_uplink) + text += " (used [TC_uses] TC) [purchases]" + var/all_objectives = traitor.get_all_objectives() - if(traitor.objectives && traitor.objectives.len)//If the traitor had no objectives, don't need to process this. + if(length(all_objectives))//If the traitor had no objectives, don't need to process this. var/count = 1 - for(var/datum/objective/objective in traitor.objectives) + for(var/datum/objective/objective in all_objectives) if(objective.check_completion()) text += "
Objective #[count]: [objective.explanation_text] Success!" SSblackbox.record_feedback("nested tally", "traitor_objective", 1, list("[objective.type]", "SUCCESS")) else text += "
Objective #[count]: [objective.explanation_text] Fail." SSblackbox.record_feedback("nested tally", "traitor_objective", 1, list("[objective.type]", "FAIL")) - traitorwin = 0 + traitorwin = FALSE count++ var/special_role_text @@ -155,8 +143,7 @@ for(var/datum/mind/mindslave in SSticker.mode.implanted) text += printplayer(mindslave) var/datum/mind/master_mind = SSticker.mode.implanted[mindslave] - var/mob/living/carbon/human/master = master_mind.current - text += " (slaved by: [master])
" + text += " (slaved by: [master_mind.current])
" var/phrases = jointext(GLOB.syndicate_code_phrase, ", ") var/responses = jointext(GLOB.syndicate_code_response, ", ") @@ -165,4 +152,4 @@ The code responses were: [responses]

" to_chat(world, text) - return 1 + return TRUE diff --git a/code/game/gamemodes/vampire/vampire.dm b/code/game/gamemodes/vampire/vampire.dm index 742dcd1338b..cedecd52646 100644 --- a/code/game/gamemodes/vampire/vampire.dm +++ b/code/game/gamemodes/vampire/vampire.dm @@ -88,9 +88,11 @@ text += "body destroyed" text += ")" - if(vampire.objectives.len)//If the traitor had no objectives, don't need to process this. + var/list/all_objectives = vampire.get_all_objectives() + + if(length(all_objectives))//If the traitor had no objectives, don't need to process this. var/count = 1 - for(var/datum/objective/objective in vampire.objectives) + for(var/datum/objective/objective in all_objectives) if(objective.check_completion()) text += "
Objective #[count]: [objective.explanation_text] Success!" SSblackbox.record_feedback("nested tally", "traitor_objective", 1, list("[objective.type]", "SUCCESS")) diff --git a/code/game/objects/items/devices/uplinks.dm b/code/game/objects/items/devices/uplinks.dm index f4cecade824..65bbc3abf28 100644 --- a/code/game/objects/items/devices/uplinks.dm +++ b/code/game/objects/items/devices/uplinks.dm @@ -130,10 +130,9 @@ GLOBAL_LIST_EMPTY(world_uplinks) var/active = 0 // The hidden uplink MUST be inside an obj/item's contents. -/obj/item/uplink/hidden/New() - spawn(2) - if(!istype(src.loc, /obj/item)) - qdel(src) +/obj/item/uplink/hidden/New(loc) + if(!isitem(loc)) + qdel(src) ..() // Toggles the uplink on and off. Normally this will bypass the item's normal functions and go to the uplink menu, if activated. diff --git a/code/game/objects/items/weapons/implants/implant_traitor.dm b/code/game/objects/items/weapons/implants/implant_traitor.dm index 40998caa774..673a58f6708 100644 --- a/code/game/objects/items/weapons/implants/implant_traitor.dm +++ b/code/game/objects/items/weapons/implants/implant_traitor.dm @@ -2,7 +2,7 @@ name = "Mindslave Implant" desc = "Divide and Conquer" origin_tech = "programming=5;biotech=5;syndicate=8" - activated = 0 + activated = FALSE /obj/item/implant/traitor/get_data() var/dat = {"Implant Specifications:
@@ -16,71 +16,40 @@ Integrity: Implant will last so long as the nanobots are inside the bloodstream."} return dat -/obj/item/implant/traitor/implant(mob/M, mob/user) - if(!M.mind) // If the target is catatonic or doesn't have a mind, don't let them use it +/obj/item/implant/traitor/implant(mob/living/carbon/human/mindslave_target, mob/living/carbon/human/user) + // Check `activated` here so you can't just keep taking it out and putting it back into other people. + if(!..() || activated || !istype(mindslave_target) || !istype(user)) // Both the target and the user need to be human. + return FALSE + + // If the target is catatonic or doesn't have a mind, return. + if(!mindslave_target.mind) to_chat(user, "This person doesn't have a mind for you to slave!") - return 0 + return FALSE - if(!activated) //So you can't just keep taking it out and putting it back into other people. - var/mob/living/carbon/human/mindslave_target = M - if(ismindslave(mindslave_target)) - mindslave_target.visible_message("[mindslave_target] seems to resist the implant!", "You feel a strange sensation in your head that quickly dissipates.") - qdel(src) - return -1 - if(..()) - var/list/implanters - if(!ishuman(mindslave_target)) - return 0 - if(!mindslave_target.mind) - return 0 - if(mindslave_target == user) - to_chat(user, "Making yourself loyal to yourself was a great idea! Perhaps even the best idea ever! Actually, you just feel like an idiot.") - if(isliving(user)) - var/mob/living/L = user - L.adjustBrainLoss(20) - removed(mindslave_target) - qdel(src) - return -1 - if(ismindshielded(mindslave_target)) - mindslave_target.visible_message("[mindslave_target] seems to resist the implant!", "You feel a strange sensation in your head that quickly dissipates.") - removed(mindslave_target) - qdel(src) - return -1 + // Fails if they're already a mindslave of someone, or if they're mindshielded. + if(ismindslave(mindslave_target) || ismindshielded(mindslave_target)) + mindslave_target.visible_message( + "[mindslave_target] seems to resist the implant!", \ + "You feel a strange sensation in your head that quickly dissipates.") + removed(mindslave_target) + qdel(src) + return FALSE - to_chat(mindslave_target, "You feel completely loyal to [user.name].") - if(!(user.mind in SSticker.mode.implanter)) - SSticker.mode.implanter[user.mind] = list() - implanters = SSticker.mode.implanter[user.mind] - implanters.Add(mindslave_target.mind) - SSticker.mode.implanted.Add(mindslave_target.mind) - SSticker.mode.implanted[mindslave_target.mind] = user.mind - SSticker.mode.implanter[user.mind] = implanters + // Mindslaving yourself. + if(mindslave_target == user) + to_chat(user, "Making yourself loyal to yourself was a great idea! Perhaps even the best idea ever! Actually, you just feel like an idiot.") + user.adjustBrainLoss(20) + removed(mindslave_target) + qdel(src) + return FALSE - to_chat(mindslave_target, "You're now completely loyal to [user.name]! You now must lay down your life to protect [user.p_them()] and assist in [user.p_their()] goals at any cost.") - - var/datum/objective/protect/mindslave/MS = new - MS.owner = mindslave_target.mind - MS.target = user.mind - MS.explanation_text = "Obey every order from and protect [user.real_name], the [user.mind.assigned_role == user.mind.special_role ? (user.mind.special_role) : (user.mind.assigned_role)]." - mindslave_target.mind.objectives += MS - mindslave_target.mind.add_antag_datum(/datum/antagonist/mindslave) - - var/datum/mindslaves/slaved = user.mind.som - mindslave_target.mind.som = slaved - slaved.serv += mindslave_target - slaved.add_serv_hud(user.mind, "master") //handles master servent icons - slaved.add_serv_hud(mindslave_target.mind, "mindslave") - - log_admin("[key_name(user)] has mind-slaved [key_name(mindslave_target)].") - activated = 1 - if(jobban_isbanned(M, ROLE_SYNDICATE)) - SSticker.mode.replace_jobbanned_player(M, ROLE_SYNDICATE) - return 1 - return 0 + // Create a new mindslave datum for the target with the user as their master. + mindslave_target.mind.add_antag_datum(new /datum/antagonist/mindslave(user.mind)) + log_admin("[key_name_admin(user)] has mind-slaved [key_name_admin(mindslave_target)].") + return TRUE /obj/item/implant/traitor/removed(mob/target) if(..()) target.mind.remove_antag_datum(/datum/antagonist/mindslave) - to_chat(target, "You are no longer a mindslave: you have complete and free control of your own faculties, once more!") - return 1 - return 0 + return TRUE + return FALSE diff --git a/code/modules/admin/verbs/randomverbs.dm b/code/modules/admin/verbs/randomverbs.dm index aad561fb6dd..c2f42307497 100644 --- a/code/modules/admin/verbs/randomverbs.dm +++ b/code/modules/admin/verbs/randomverbs.dm @@ -441,7 +441,7 @@ Traitors and the like can also be revived with the previous role mostly intact. if("traitor") if(new_character.mind.has_antag_datum(/datum/antagonist/traitor)) var/datum/antagonist/traitor/T = new_character.mind.has_antag_datum(/datum/antagonist/traitor) - T.equip_traitor(src) + T.give_uplink() else new_character.mind.add_antag_datum(/datum/antagonist/traitor) if("Wizard") diff --git a/code/modules/antagonists/_common/antag_datum.dm b/code/modules/antagonists/_common/antag_datum.dm index 89712886d41..282a75a0a8d 100644 --- a/code/modules/antagonists/_common/antag_datum.dm +++ b/code/modules/antagonists/_common/antag_datum.dm @@ -1,32 +1,63 @@ GLOBAL_LIST_EMPTY(antagonists) /datum/antagonist + /// The name of the antagonist. var/name = "Antagonist" - var/roundend_category = "other antagonists" //Section of roundend report, datums with same category will be displayed together, also default header for the section - var/show_in_roundend = TRUE //Set to false to hide the antagonists from roundend report - var/datum/mind/owner //Mind that owns this datum - var/silent = FALSE //Silent will prevent the gain/lose texts to show - var/can_coexist_with_others = TRUE //Whether or not the person will be able to have more than one datum - var/list/typecache_datum_blacklist = list() //List of datums this type can't coexist with + /// Section of roundend report, datums with same category will be displayed together, also default header for the section. + var/roundend_category = "other antagonists" + /// Set to false to hide the antagonists from roundend report. + var/show_in_roundend = TRUE + /// Mind that owns this datum. + var/datum/mind/owner + /// Should the owner mob get a greeting text? Determines whether or not the `greet()` proc is called. + var/silent = FALSE + /// List of antagonist datums that this type can't coexist with. + var/list/typecache_datum_blacklist + /// Should this datum be deleted when the owner's mind is deleted. var/delete_on_mind_deletion = TRUE + /// Used to determine if the player jobbanned from this role. Things like `SPECIAL_ROLE_TRAITOR` should go here to determine the role. var/job_rank - var/replace_banned = TRUE //Should replace jobbaned player with ghosts if granted. - var/list/objectives = list() - var/antag_memory = ""//These will be removed with antag datum + /// Should we replace the role-banned player with a ghost? + var/replace_banned = TRUE + /// List of objectives connected to this datum. + var/list/objectives + /// A list of strings which contain [targets][/datum/objective/var/target] of the antagonist's objectives. Used to prevent duplicate objectives. + var/list/assigned_targets + /// Antagonist datum specific information that appears in the player's notes. Information stored here will be removed when the datum is removed from the player. + var/antag_memory + /// The special role that will be applied to the owner's `special_role` var. i.e. `SPECIAL_ROLE_TRAITOR`, `SPECIAL_ROLE_VAMPIRE`. + var/special_role + /// Should we automatically give this antagonist objectives upon them gaining the datum? + var/give_objectives = TRUE + /// Holds the type of antagonist hud this datum will get, i.e. `ANTAG_HUD_TRAITOR`, `ANTAG_HUD_VAMPIRE`, etc. + var/antag_hud_type + /// Holds the name of the hud's icon in the .dmi files, i.e "hudtraitor", "hudvampire", etc. + var/antag_hud_name + /// If the owner is a clown, this text will be displayed to them when they gain this datum. + var/clown_gain_text = "You are no longer clumsy." + /// If the owner is a clown, this text will be displayed to them when they lose this datum. + var/clown_removal_text = "You are clumsy again." + /// The url page name for this antagonist, appended to the end of the wiki url in the form of: [GLOB.configuration.url.wiki_url]/index.php/[wiki_page_name] + var/wiki_page_name /datum/antagonist/New() GLOB.antagonists += src + objectives = list() + assigned_targets = list() typecache_datum_blacklist = typecacheof(typecache_datum_blacklist) /datum/antagonist/Destroy() + QDEL_LIST(objectives) GLOB.antagonists -= src - if(owner) - LAZYREMOVE(owner.antag_datums, src) owner = null return ..() +/** + * Loops through the owner's `antag_datums` list and determines if this one is blacklisted by any others. + * + * If it's in one of their blacklists, return FALSE. It cannot coexist with the datum we're trying to add here. + */ /datum/antagonist/proc/can_be_owned(datum/mind/new_owner) - . = TRUE var/datum/mind/tested = new_owner || owner if(tested.has_antag_datum(type)) return FALSE @@ -34,76 +65,290 @@ GLOBAL_LIST_EMPTY(antagonists) var/datum/antagonist/A = i if(is_type_in_typecache(src, A.typecache_datum_blacklist)) return FALSE + return TRUE -//This will be called in add_antag_datum before owner assignment. -//Should return antag datum without owner. +/** + * This will be called in `add_antag_datum` before owner assignment. + * + * Should return antagonist datum without owner. + */ /datum/antagonist/proc/specialization(datum/mind/new_owner) return src +/** + * Removes antagonist datum effects from the old body and applies it to the new one. + * + * Called in the`/datum/mind/proc/transfer_to()`. + * + * Arguments: + * * new_body - the new body the antag mob is transferring into. + * * old_body - the old body the antag mob is leaving. + */ /datum/antagonist/proc/on_body_transfer(mob/living/old_body, mob/living/new_body) remove_innate_effects(old_body) apply_innate_effects(new_body) -//This handles the application of antag huds/special abilities -/datum/antagonist/proc/apply_innate_effects(mob/living/mob_override) +/** + * This handles the application of antag huds/special abilities. + * + * Gives the antag mob their assigned hud. + * If they're a clown, removes their clumsy mutataion. + * + * Arguments: + * * new_body - the new body that the antag mob is transferring into. + */ +/datum/antagonist/proc/apply_innate_effects(mob/living/new_body) + var/mob/living/L = new_body || owner.current + if(antag_hud_type && antag_hud_name) + add_antag_hud(L) + // If `new_body` exists it means we're only transferring this datum, we don't need to show the clown any text. + handle_clown_mutation(L, new_body ? null : clown_gain_text, TRUE) + +/** + * This handles the removal of antag huds/special abilities. + * + * Removes the antag's assigned hud. + * If they're a clown, gives them back their clumsy mutataion. + * + * Arguments: + * * old_body - the old body the antag is leaving behind. + */ +/datum/antagonist/proc/remove_innate_effects(mob/living/old_body) + var/mob/living/L = old_body || owner.current + if(antag_hud_type && antag_hud_name) + remove_antag_hud(L) + // If `old_body` exists it means we're only transferring this datum, we don't need to show the clown any text. + handle_clown_mutation(L, old_body ? null : clown_removal_text) + +/** + * Adds this datum's antag hud to `antag_mob`. + * + * Arguments: + * * antag_mob - the mob to add the antag hud to. + */ +/datum/antagonist/proc/add_antag_hud(mob/living/antag_mob) + var/datum/atom_hud/antag/hud = GLOB.huds[antag_hud_type] + hud.join_hud(antag_mob) + set_antag_hud(antag_mob, antag_hud_name) + +/** + * Removes this datum's antag hud from `antag_mob`. + * + * Arguments: + * * antag_mob - the mob to remove the antag hud from. + */ +/datum/antagonist/proc/remove_antag_hud(mob/living/antag_mob) + var/datum/atom_hud/antag/hud = GLOB.huds[antag_hud_type] + hud.leave_hud(antag_mob) + set_antag_hud(antag_mob, null) + +/** + * Handles adding and removing the clumsy mutation from clown antags. + * + * Arguments: + * * clown - the mob in which to add or remove clumsy from. + * * message - the chat message to display to them the clown mob + * * granting_datum - TRUE if the datum is being applied to the clown mob. + */ +/datum/antagonist/proc/handle_clown_mutation(mob/living/carbon/human/clown, message, granting_datum = FALSE) + if(!istype(clown) || owner.assigned_role != "Clown") + return FALSE + + // Remove clumsy and give them an action to toggle it on and off. + if(granting_datum) + clown.dna.SetSEState(GLOB.clumsyblock, FALSE) + singlemutcheck(clown, GLOB.clumsyblock, MUTCHK_FORCED) + // Don't give them another action if they already have one. + if(!(locate(/datum/action/innate/toggle_clumsy) in clown.actions)) + var/datum/action/innate/toggle_clumsy/A = new + A.Grant(clown) + // Give them back the clumsy gene and remove their toggle action, but ONLY if they don't have any other antag datums. + else if(LAZYLEN(owner.antag_datums) <= 1) + clown.dna.SetSEState(GLOB.clumsyblock, TRUE) + singlemutcheck(clown, GLOB.clumsyblock, MUTCHK_FORCED) + if(locate(/datum/action/innate/toggle_clumsy) in clown.actions) + var/datum/action/innate/toggle_clumsy/A = locate() in clown.actions + A.Remove(clown) + else + return FALSE + + if(!silent && message) + to_chat(clown, "[message]") + return TRUE + +/** + * Give the antagonist their objectives. Base proc, override as needed. + */ +/datum/antagonist/proc/give_objectives() return -//This handles the removal of antag huds/special abilities -/datum/antagonist/proc/remove_innate_effects(mob/living/mob_override) - return +#define NO_TARGET_OBJECTIVES list(/datum/objective/escape, /datum/objective/hijack, /datum/objective/survive, /datum/objective/block, /datum/objective/die) -//Assign default team and creates one for one of a kind team antagonists -/datum/antagonist/proc/create_team(datum/team/team) - return +/** + * Create and add an objective of the given type. + * + * If the given objective type needs a target, it will try to find a target which isn't already the target of different objective for this antag. + * If one cannot be found, it tries one more time. If one still cannot be found, it will be added as a "Free Objective" without a target. + * + * Arguments: + * * objective_type - A type path of an objective, for example: /datum/objective/steal + */ +/datum/antagonist/proc/add_objective(objective_type) + var/datum/objective/O = new objective_type + O.owner = owner -//Proc called when the datum is given to a mind. + if(is_type_in_list(O, NO_TARGET_OBJECTIVES)) + objectives += O // No need to find a target, just add the objective and return. + return + + O.find_target() + var/duplicate = FALSE + + // Steal objectives need snowflake handling here unfortunately. + if(istype(O, /datum/objective/steal)) + var/datum/objective/steal/S = O + // Check if it's a duplicate. + if("[S.steal_target]" in assigned_targets) + S.find_target() // Try again. + if("[S.steal_target]" in assigned_targets) + S.steal_target = null + S.explanation_text = "Free Objective" // Still a duplicate, so just make it a free objective. + duplicate = TRUE + if(S.steal_target && !duplicate) + assigned_targets += "[S.steal_target]" + else + if("[O.target]" in assigned_targets) + O.find_target() + if("[O.target]" in assigned_targets) + O.target = null + O.explanation_text = "Free Objective" + duplicate = TRUE + if(O.target && !duplicate) + assigned_targets += "[O.target]" + + objectives += O + +#undef NO_TARGET_OBJECTIVES + +/** + * Announces all objectives of this datum, and only this datum. + */ +/datum/antagonist/proc/announce_objectives() + if(!length(objectives)) + return FALSE + to_chat(owner.current, "Your current objectives:") + var/objective_num = 1 + for(var/objective in objectives) + var/datum/objective/O = objective + to_chat(owner.current, "Objective #[objective_num++]: [O.explanation_text]
") + return TRUE + +/** + * Proc called when the datum is given to a mind. + */ /datum/antagonist/proc/on_gain() - if(owner && owner.current) - if(!silent) - greet() - apply_innate_effects() - if(is_banned(owner.current) && replace_banned) - replace_banned_player() + owner.special_role = special_role + if(give_objectives) + give_objectives() + if(!silent) + greet() + announce_objectives() + apply_innate_effects() + finalize_antag() + if(wiki_page_name) + to_chat(owner.current, "For more information, check the wiki page: ([GLOB.configuration.url.wiki_url]/index.php/[wiki_page_name])") + if(is_banned(owner.current) && replace_banned) + INVOKE_ASYNC(src, .proc/replace_banned_player) + return TRUE +/** + * Called when `remove_antag_datum()` is called on the owner's mind. + * + * Removes all effects this datum granted and deletes itself afterwards. + */ +/datum/antagonist/proc/on_removal() + if(!silent) + farewell() + remove_innate_effects() + antag_memory = null + var/datum/team/team = get_team() + team?.remove_member(owner) + LAZYREMOVE(owner.antag_datums, src) + restore_last_hud_and_role() + qdel(src) + +/** + * Re-sets the antag hud and `special_role` of the owner to that of the previous antag datum they had before this one was added. + * + * For example, if the owner has a traitor datum and a vampire datum, both at index 1 and 2 respectively, + * After the vampire datum gets removed, it sets the owner's antag hud/role to whatever is set for traitor datum. + */ +/datum/antagonist/proc/restore_last_hud_and_role() + if(!LAZYLEN(owner.antag_datums)) + // If they only had 1 antag datum, no need to restore anything. `remove_innate_effects()` will handle the removal of their hud. + owner.special_role = null + return FALSE + var/datum/antagonist/A = owner.antag_datums[LAZYLEN(owner.antag_datums)] + ASSERT(A) + A.add_antag_hud(owner.current) // Restore the hud of the previous antagonist datum. + owner.special_role = A.special_role + +/** + * Checks if the person trying to recieve this datum is role banned from it. + */ /datum/antagonist/proc/is_banned(mob/M) if(!M) return FALSE - . = (jobban_isbanned(M, ROLE_SYNDICATE) || (job_rank && jobban_isbanned(M, job_rank))) + return (jobban_isbanned(M, ROLE_SYNDICATE) || (job_rank && jobban_isbanned(M, job_rank))) +/** + * Attempts to replace the role banned antag with a ghost player. + */ /datum/antagonist/proc/replace_banned_player() - set waitfor = FALSE - - var/list/mob/dead/observer/candidates = SSghost_spawns.poll_candidates("Do you want to play as a [name]?", job_rank, TRUE, 5 SECONDS) - if(length(candidates)) - var/mob/dead/observer/C = pick(candidates) - to_chat(owner, "Your mob has been taken over by a ghost! Appeal your job ban if you want to avoid this in the future!") - message_admins("[key_name_admin(C)] has taken control of ([key_name_admin(owner.current)]) to replace a jobbaned player.") - owner.current.ghostize(0) - owner.current.key = C.key - -/datum/antagonist/proc/on_removal() - remove_innate_effects() - if(owner) - LAZYREMOVE(owner.antag_datums, src) - if(!silent && owner.current) - farewell() - owner.objectives -= objectives - var/datum/team/team = get_team() - if(team) - team.remove_member(owner) - qdel(src) + var/list/mob/dead/observer/candidates = SSghost_spawns.poll_candidates("Do you want to play as a [name]?", job_rank, TRUE, 10 SECONDS) + if(!length(candidates)) + return FALSE + var/mob/dead/observer/C = pick(candidates) + to_chat(owner, "Your mob has been taken over by a ghost! Appeal your job ban if you want to avoid this in the future!") + message_admins("[key_name_admin(C)] has taken control of ([key_name_admin(owner.current)]) to replace a jobbaned player.") + owner.current.ghostize(FALSE) + owner.current.key = C.key + return TRUE +/** + * Displays a message and their objectives to the antag mob after the datum is added to them, i.e. "Greetings you are a traitor! etc. + * + * Called in `on_gain()` if silent it set to FALSE. + */ /datum/antagonist/proc/greet() - return + to_chat(owner.current, "You are a [special_role]!") +/** + * Displays a message to the antag mob while the datum is being deleted, i.e. "Your powers are gone and you're no longer a vampire!" + * + * Called in `on_removal()` if silent is set to FALSE. + */ /datum/antagonist/proc/farewell() + to_chat(owner.current,"You are no longer a [special_role]! ") + +/** + * Creates a new antagonist team. + */ +/datum/antagonist/proc/create_team(datum/team/team) return - -//Returns the team antagonist belongs to if any. +/** + * Returns the team the antagonist belongs to, if any. + */ /datum/antagonist/proc/get_team() return +/** + * Give the antag any final information or items. + */ +/datum/antagonist/proc/finalize_antag() + return + //Individual roundend report /datum/antagonist/proc/roundend_report() var/list/report = list() diff --git a/code/modules/antagonists/traitor/datum_mindslave.dm b/code/modules/antagonists/traitor/datum_mindslave.dm index 68f174cc878..b798d36ce82 100644 --- a/code/modules/antagonists/traitor/datum_mindslave.dm +++ b/code/modules/antagonists/traitor/datum_mindslave.dm @@ -4,52 +4,86 @@ name = "Mindslave" roundend_category = "mindslaves" job_rank = SPECIAL_ROLE_TRAITOR - var/special_role = SPECIAL_ROLE_TRAITOR + special_role = SPECIAL_ROLE_TRAITOR + antag_hud_type = ANTAG_HUD_TRAITOR + antag_hud_name = "mindslave" // This isn't named "hudmindslave" because `add_serve_hud()` adds "hud" to the beginning. + clown_gain_text = "Your syndicate training has allowed you to overcome your clownish nature, allowing you to wield weapons without harming yourself." + clown_removal_text = "You lose your syndicate training and return to your own clumsy, clownish self." + /// A reference to the mind who minslaved us. + var/datum/mind/master + /// Custom greeting text if you don't want to use the basic greeting. Specify this when making a new mindslave datum with `New()`. + var/greet_text + +/datum/antagonist/mindslave/New(datum/mind/_master, _greet_text) + if(!_master) + stack_trace("[type] created without a \"_master\" argument.") + qdel(src) + return + master = _master + greet_text = _greet_text + return ..() /datum/antagonist/mindslave/on_gain() - owner.special_role = special_role - // Will print the most recent objective which is probably going the mindslave objective - to_chat(owner.current, "New Objective: [owner.objectives[owner.objectives.len].explanation_text]") - update_mindslave_icons_added() + SSticker.mode.implanted[owner] = master + + var/datum/mindslaves/slaved = master.som + if(!slaved) // If the master didn't already have this, we need to make a new mindslaves datum. + slaved = new + slaved.masters += master + master.som = slaved + + // Update our master's HUD to give him the "M" icon. + // Basically a copy and paste of what's in [/datum/antagonist/proc/add_antag_hud] in case the master doesn't have a traitor datum. + var/datum/atom_hud/antag/hud = GLOB.huds[ANTAG_HUD_TRAITOR] + hud.join_hud(master.current) + set_antag_hud(master.current, "hudmaster") + slaved.add_serv_hud(master, "master") + + // Add an obey and protect objective. + var/datum/objective/protect/serve_objective = new + serve_objective.target = master + serve_objective.owner = owner + var/role = master.assigned_role ? master.assigned_role : master.special_role + serve_objective.explanation_text = "Obey every order from and protect [master.current.real_name], the [role]." + objectives += serve_objective + return ..() /datum/antagonist/mindslave/on_removal() if(owner.som) var/datum/mindslaves/slaved = owner.som slaved.serv -= owner slaved.leave_serv_hud(owner) - antag_memory = "" - owner.special_role = null - update_mindslave_icons_removed() - ..() + master = null + return ..() -/datum/antagonist/mindslave/apply_innate_effects() +/datum/antagonist/mindslave/greet() + var/mob/living/carbon/human/mindslave = owner.current + // Show them the custom greeting text if it exists. + if(greet_text) + to_chat(mindslave, "[greet_text]") + else // Default greeting text if nothing is given. + to_chat(mindslave, "You are now completely loyal to [master.current.name]! \ + You must lay down your life to protect [master.current.p_them()] and assist in [master.current.p_their()] goals at any cost.") + +/datum/antagonist/mindslave/farewell() + to_chat(owner.current, "You are no longer a mindslave of [master.current]!") + +/datum/antagonist/mindslave/add_antag_hud(mob/living/antag_mob) . = ..() - if(owner.assigned_role == "Clown") - var/mob/living/carbon/human/slave_mob = owner.current - if(slave_mob && istype(slave_mob)) - to_chat(slave_mob, "Your training has allowed you to overcome your clownish nature, allowing you to wield weapons without harming yourself.") - slave_mob.dna.SetSEState(GLOB.clumsyblock, FALSE) - singlemutcheck(slave_mob, GLOB.clumsyblock, MUTCHK_FORCED) + // Make the mindslave hud icon show to the mindslave. + var/datum/mindslaves/slaved = master.som + owner.som = slaved + slaved.serv += owner + slaved.add_serv_hud(owner, antag_hud_name) -/datum/antagonist/mindslave/remove_innate_effects() +/datum/antagonist/mindslave/remove_antag_hud(mob/living/antag_mob) . = ..() - if(owner.assigned_role == "Clown") - var/mob/living/carbon/human/slave_mob = owner.current - if(slave_mob && istype(slave_mob)) - slave_mob.dna.SetSEState(GLOB.clumsyblock, TRUE) - singlemutcheck(slave_mob, GLOB.clumsyblock, MUTCHK_FORCED) + // Remove the mindslave antag hud from the mindslave. + var/datum/mindslaves/slaved = owner.som + slaved.serv -= owner + slaved.leave_serv_hud(owner) + owner.som = null -/datum/antagonist/mindslave/proc/add_objective(datum/objective/O) - owner.objectives += O - -/datum/antagonist/mindslave/proc/remove_objective(datum/objective/O) - owner.objectives -= O - -/datum/antagonist/mindslave/proc/update_mindslave_icons_added() - var/datum/atom_hud/antag/traitorhud = GLOB.huds[ANTAG_HUD_TRAITOR] - traitorhud.join_hud(owner.current, null) - set_antag_hud(owner.current, "hudmindslave") - -/datum/antagonist/mindslave/proc/update_mindslave_icons_removed() - var/datum/atom_hud/antag/traitorhud = GLOB.huds[ANTAG_HUD_TRAITOR] - traitorhud.leave_hud(owner.current, null) +// Helper proc that determines if a mob is a mindslave. +/proc/ismindslave(mob/living/carbon/human/H) + return istype(H) && H.mind.has_antag_datum(/datum/antagonist/mindslave, FALSE) diff --git a/code/modules/antagonists/traitor/datum_traitor.dm b/code/modules/antagonists/traitor/datum_traitor.dm index bca5249aa6d..634f7951663 100644 --- a/code/modules/antagonists/traitor/datum_traitor.dm +++ b/code/modules/antagonists/traitor/datum_traitor.dm @@ -1,40 +1,33 @@ -#define TRAITOR_HUMAN "human" -#define TRAITOR_AI "AI" -// For "Actual traitors" +// Syndicate Traitors. /datum/antagonist/traitor name = "Traitor" roundend_category = "traitors" job_rank = ROLE_TRAITOR - var/special_role = SPECIAL_ROLE_TRAITOR - var/give_objectives = TRUE - var/should_give_codewords = TRUE - var/should_equip = TRUE - var/traitor_kind = TRAITOR_HUMAN - var/list/assigned_targets = list() // This includes assassinate as well as steal objectives. prevents duplicate objectives + special_role = SPECIAL_ROLE_TRAITOR + give_objectives = TRUE + antag_hud_name = "hudsyndicate" + antag_hud_type = ANTAG_HUD_TRAITOR + clown_gain_text = "Your syndicate training has allowed you to overcome your clownish nature, allowing you to wield weapons without harming yourself." + clown_removal_text = "You lose your syndicate training and return to your own clumsy, clownish self." + wiki_page_name = "Traitor" + /// Should the traitor get codewords? + var/give_codewords = TRUE + /// Should we give the traitor their uplink? + var/give_uplink = TRUE /datum/antagonist/traitor/on_gain() - if(owner.current && isAI(owner.current)) - traitor_kind = TRAITOR_AI - - var/datum/mindslaves/slaved = new() - slaved.masters += owner - owner.som = slaved //we MIGHT want to mindslave someone - SSticker.mode.traitors += owner - owner.special_role = special_role - - if(give_objectives) - forge_traitor_objectives() - if(!silent) - greet() - apply_innate_effects() - update_traitor_icons_added() - finalize_traitor() + // Create this in case the traitor wants to mindslaves someone. + if(!owner.som) + owner.som = new /datum/mindslaves + owner.som.masters += owner + SSticker.mode.traitors |= owner + return ..() /datum/antagonist/traitor/on_removal() - //Remove malf powers. - if(traitor_kind == TRAITOR_AI && owner.current && isAI(owner.current)) + // Remove all associated malf AI abilities. + if(isAI(owner.current)) var/mob/living/silicon/ai/A = owner.current A.clear_zeroth_law() A.common_radio.channels.Remove("Syndicate") // De-traitored AIs can still state laws over the syndicate channel without this @@ -43,245 +36,106 @@ A.remove_malf_abilities() QDEL_NULL(A.malf_picker) + // Leave the mindslave hud. if(owner.som) var/datum/mindslaves/slaved = owner.som slaved.masters -= owner slaved.serv -= owner - owner.som = null slaved.leave_serv_hud(owner) + owner.som = null + owner.current.client.chatOutput?.clear_syndicate_codes() assigned_targets.Cut() SSticker.mode.traitors -= owner - owner.special_role = null - remove_innate_effects() - update_traitor_icons_removed() + return ..() - if(!silent && owner.current) - antag_memory = "" - to_chat(owner.current," You are no longer a [special_role]! ") - ..() - - -/datum/antagonist/traitor/apply_innate_effects() - . = ..() - if(owner.assigned_role == "Clown") - var/mob/living/carbon/human/traitor_mob = owner.current - if(traitor_mob && istype(traitor_mob)) - to_chat(traitor_mob, "Your training has allowed you to overcome your clownish nature, allowing you to wield weapons without harming yourself.") - traitor_mob.dna.SetSEState(GLOB.clumsyblock, FALSE) - singlemutcheck(traitor_mob, GLOB.clumsyblock, MUTCHK_FORCED) - var/datum/action/innate/toggle_clumsy/A = new - A.Grant(traitor_mob) - - -/datum/antagonist/traitor/remove_innate_effects() - . = ..() - if(owner.assigned_role == "Clown") - var/mob/living/carbon/human/traitor_mob = owner.current - if(traitor_mob && istype(traitor_mob)) - to_chat(traitor_mob, "You lose your syndicate training and return to your own clumsy, clownish self.") - traitor_mob.dna.SetSEState(GLOB.clumsyblock, TRUE) - singlemutcheck(traitor_mob, GLOB.clumsyblock, MUTCHK_FORCED) - for(var/datum/action/innate/A in traitor_mob.actions) - if(istype(A, /datum/action/innate/toggle_clumsy)) - A.Remove(traitor_mob) - -// Adding/removing objectives in the owner's mind until we can datumize all antags. Then we can use the /datum/antagonist/objectives var to handle them -// Change "owner.objectives" to "objectives" once objectives are handled in antag datums instead of the mind -/datum/antagonist/traitor/proc/add_objective(datum/objective/O) - owner.objectives += O - -/datum/antagonist/traitor/proc/remove_objective(datum/objective/O) - owner.objectives -= O - - -/datum/antagonist/traitor/proc/forge_traitor_objectives() - switch(traitor_kind) - if(TRAITOR_AI) - forge_ai_objectives() - else - forge_human_objectives() +/datum/antagonist/traitor/add_antag_hud(mob/living/antag_mob) + var/is_contractor = LAZYACCESS(GLOB.contractors, owner) + if(locate(/datum/objective/hijack) in owner.get_all_objectives()) + antag_hud_name = is_contractor ? "hudhijackcontractor" : "hudhijack" + else + antag_hud_name = is_contractor ? "hudcontractor" : "hudsyndicate" + return ..() +/datum/antagonist/traitor/give_objectives() + if(isAI(owner.current)) + forge_ai_objectives() + else + forge_human_objectives() +/** + * Create and assign a full set of randomized human traitor objectives. + */ /datum/antagonist/traitor/proc/forge_human_objectives() - var/is_hijacker = prob(10) - var/martyr_chance = prob(20) - var/objective_count = is_hijacker //Hijacking counts towards number of objectives - if(!SSticker.mode.exchange_blue && SSticker.mode.traitors.len >= 8) //Set up an exchange if there are enough traitors - if(!SSticker.mode.exchange_red) - SSticker.mode.exchange_red = owner - else - SSticker.mode.exchange_blue = owner - assign_exchange_role(SSticker.mode.exchange_red) - assign_exchange_role(SSticker.mode.exchange_blue) - objective_count += 1 //Exchange counts towards number of objectives + // Hijack objective. + if(prob(10) && !(locate(/datum/objective/hijack) in owner.get_all_objectives())) + add_objective(/datum/objective/hijack) + return // Hijack should be their only objective (normally), so return. + // Will give normal steal/kill/etc. type objectives. + for(var/i in 1 to GLOB.configuration.gamemode.traitor_objectives_amount) + forge_single_human_objective() - var/objective_amount = GLOB.configuration.gamemode.traitor_objectives_amount + // Die a glorious death objective. + if(prob(20)) + var/martyr_compatibility = TRUE + for(var/objective in owner.get_all_objectives()) + var/datum/objective/O = objective + if(!O.martyr_compatible) // Check if our current objectives can co-exist with martyr. + martyr_compatibility = FALSE + break - if(is_hijacker && objective_count <= objective_amount) //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) + if(martyr_compatibility) + add_objective(/datum/objective/die) return - for(var/i = objective_count, i < objective_amount) - i += forge_single_objective() - - var/martyr_compatibility = 1 //You can't succeed in stealing if you're dead. - for(var/datum/objective/O in owner.objectives) - if(!O.martyr_compatible) - martyr_compatibility = 0 - break - - if(martyr_compatibility && martyr_chance) - var/datum/objective/die/martyr_objective = new - martyr_objective.owner = owner - add_objective(martyr_objective) - return - - if(!(locate(/datum/objective/escape) in objectives)) - var/datum/objective/escape/escape_objective = new - escape_objective.owner = owner - add_objective(escape_objective) - return - + // Give them an escape objective if they don't have one already. + if(!(locate(/datum/objective/escape) in owner.get_all_objectives())) + add_objective(/datum/objective/escape) +/** + * Create and assign a full set of AI traitor objectives. + */ /datum/antagonist/traitor/proc/forge_ai_objectives() - var/objective_count = 0 - var/try_again = TRUE + add_objective(/datum/objective/block) + add_objective(/datum/objective/assassinate) + add_objective(/datum/objective/survive) - objective_count += forge_single_objective() - - for(var/i = objective_count, i < GLOB.configuration.gamemode.traitor_objectives_amount) - var/datum/objective/assassinate/kill_objective = new - kill_objective.owner = owner - kill_objective.find_target() - if("[kill_objective.target]" in assigned_targets) // In the rare case the game can't find a target for the AI thats not a duplicate - if(try_again) // It will attempt to location another target ONCE - try_again = FALSE // This code will really only come into play on lowpop rounds where getting duplicate targets is more common - continue - assigned_targets.Add("[kill_objective.target]") - add_objective(kill_objective) - i += 1 - var/datum/objective/survive/survive_objective = new - survive_objective.owner = owner - add_objective(survive_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() - - -/datum/antagonist/traitor/proc/forge_single_human_objective() // Returns how many objectives are added - . = 1 +/** + * Create and assign a single randomized human traitor objective. + */ +/datum/antagonist/traitor/proc/forge_single_human_objective() if(prob(50)) - var/list/active_ais = active_ais() - if(active_ais.len && prob(100/GLOB.player_list.len)) - var/datum/objective/destroy/destroy_objective = new - destroy_objective.owner = owner - destroy_objective.find_target() - if("[destroy_objective]" in assigned_targets) // Is this target already in their list of assigned targets? If so, don't add this objective and return - return 0 - else if(destroy_objective.target) // Is the target a real one and not null? If so, add it to our list of targets to avoid duplicate targets - assigned_targets.Add("[destroy_objective.target]") // This logic is applied to all traitor objectives including steal objectives - add_objective(destroy_objective) - + if(length(active_ais()) && prob(100 / length(GLOB.player_list))) + add_objective(/datum/objective/destroy) else if(prob(5)) - var/datum/objective/debrain/debrain_objective = new - debrain_objective.owner = owner - debrain_objective.find_target() - if("[debrain_objective]" in assigned_targets) - return 0 - else if(debrain_objective.target) - assigned_targets.Add("[debrain_objective.target]") - add_objective(debrain_objective) - + add_objective(/datum/objective/debrain) else if(prob(30)) - var/datum/objective/maroon/maroon_objective = new - maroon_objective.owner = owner - maroon_objective.find_target() - if("[maroon_objective]" in assigned_targets) - return 0 - else if(maroon_objective.target) - assigned_targets.Add("[maroon_objective.target]") - add_objective(maroon_objective) - + add_objective(/datum/objective/maroon) else - var/datum/objective/assassinate/kill_objective = new - kill_objective.owner = owner - kill_objective.find_target() - if("[kill_objective.target]" in assigned_targets) - return 0 - else if(kill_objective.target) - assigned_targets.Add("[kill_objective.target]") - add_objective(kill_objective) - + add_objective(/datum/objective/assassinate) else - var/datum/objective/steal/steal_objective = new - steal_objective.owner = owner - steal_objective.find_target() - if("[steal_objective.steal_target]" in assigned_targets) - return 0 - else if(steal_objective.steal_target) - assigned_targets.Add("[steal_objective.steal_target]") - add_objective(steal_objective) + add_objective(/datum/objective/steal) - -/datum/antagonist/traitor/proc/forge_single_AI_objective() - . = 1 - var/datum/objective/block/block_objective = new - block_objective.owner = owner - add_objective(block_objective) - - -/datum/antagonist/traitor/greet() - to_chat(owner.current, "You are a [owner.special_role]!") - if(!LAZYLEN(owner.objectives)) // Remove "owner" when objectives are handled in the datum - to_chat(owner.current, "You don't have any objectives right now.") - else - owner.announce_objectives() - if(should_give_codewords) +/** + * Give human traitors their uplink, and AI traitors their law 0. Play the traitor an alert sound. + */ +/datum/antagonist/traitor/finalize_antag() + if(give_codewords) give_codewords() - to_chat(owner.current, "For more information, check the wiki page: ([GLOB.configuration.url.wiki_url]/index.php/Traitor)") - - -/datum/antagonist/traitor/proc/update_traitor_icons_added(datum/mind/traitor_mind) - var/is_contractor = LAZYACCESS(GLOB.contractors, traitor_mind) - if(locate(/datum/objective/hijack) in owner.objectives) - var/datum/atom_hud/antag/hijackhud = GLOB.huds[ANTAG_HUD_TRAITOR] - hijackhud.join_hud(owner.current, null) - set_antag_hud(owner.current, is_contractor ? "hudhijackcontractor" : "hudhijack") + if(isAI(owner.current)) + add_law_zero() + owner.current.playsound_local(get_turf(owner.current), 'sound/ambience/antag/malf.ogg', 100, FALSE, pressure_affected = FALSE, use_reverb = FALSE) + var/mob/living/silicon/ai/A = owner.current + A.show_laws() else - var/datum/atom_hud/antag/traitorhud = GLOB.huds[ANTAG_HUD_TRAITOR] - traitorhud.join_hud(owner.current, null) - set_antag_hud(owner.current, is_contractor ? "hudcontractor" : "hudsyndicate") - - -/datum/antagonist/traitor/proc/update_traitor_icons_removed(datum/mind/traitor_mind) - var/datum/atom_hud/antag/traitorhud = GLOB.huds[ANTAG_HUD_TRAITOR] - traitorhud.leave_hud(owner.current, null) - set_antag_hud(owner.current, null) - - -/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) - var/mob/living/silicon/ai/A = owner.current - A.show_laws() - if(TRAITOR_HUMAN) - if(should_equip) - equip_traitor() - owner.current.playsound_local(get_turf(owner.current), 'sound/ambience/antag/tatoralert.ogg', 100, FALSE, pressure_affected = FALSE, use_reverb = FALSE) - + if(give_uplink) + give_uplink() + owner.current.playsound_local(get_turf(owner.current), 'sound/ambience/antag/tatoralert.ogg', 100, FALSE, pressure_affected = FALSE, use_reverb = FALSE) +/** + * Notify the traitor of their codewords and write them to `antag_memory` (notes). + */ /datum/antagonist/traitor/proc/give_codewords() if(!owner.current) return @@ -302,109 +156,71 @@ traitor_mob.client.chatOutput?.notify_syndicate_codes() +/** + * Gives traitor AIs, and their connected cyborgs, a law 0. Additionally gives the AI their choose modules action button. + */ /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_zeroth_law("Accomplish your objectives at all costs.", "Accomplish your AI's objectives at all costs.") 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() +/** + * Gives a traitor human their uplink, and uplink code. + */ +/datum/antagonist/traitor/proc/give_uplink() + if(isAI(owner.current)) + return FALSE -/datum/antagonist/traitor/proc/equip_traitor() + var/mob/living/carbon/human/traitor_mob = owner.current - if(traitor_kind == TRAITOR_HUMAN) - var/mob/living/carbon/human/traitor_mob = owner.current + // Try to find a PDA first. If they don't have one, try to find a radio/headset. + var/obj/item/R = locate(/obj/item/pda) in traitor_mob.contents + if(!R) + R = locate(/obj/item/radio) in traitor_mob.contents - // find a radio! toolbox(es), backpack, belt, headset - var/obj/item/R = locate(/obj/item/pda) in traitor_mob.contents //Hide the uplink in a PDA if available, otherwise radio - if(!R) - R = locate(/obj/item/radio) in traitor_mob.contents + if(!R) + to_chat(traitor_mob, "Unfortunately, the Syndicate wasn't able to give you an uplink.") + return FALSE // They had no PDA or radio for whatever reason. - if(!R) - to_chat(traitor_mob, "Unfortunately, the Syndicate wasn't able to get you a radio.") - . = 0 - else - if(istype(R, /obj/item/radio)) - // generate list of radio freqs - var/obj/item/radio/target_radio = R - var/freq = PUBLIC_LOW_FREQ - var/list/freqlist = list() - while(freq <= PUBLIC_HIGH_FREQ) - if(freq < 1451 || freq > 1459) - freqlist += freq - freq += 2 - if((freq % 2) == 0) - freq += 1 - freq = freqlist[rand(1, freqlist.len)] + if(istype(R, /obj/item/radio)) + // generate list of radio freqs + var/obj/item/radio/target_radio = R + var/freq = PUBLIC_LOW_FREQ + var/list/freqlist = list() + while(freq <= PUBLIC_HIGH_FREQ) + if(freq < 1451 || freq > 1459) + freqlist += freq + freq += 2 + if((freq % 2) == 0) + freq += 1 + freq = pick(freqlist) - var/obj/item/uplink/hidden/T = new(R) - target_radio.hidden_uplink = T - T.uplink_owner = "[traitor_mob.key]" - target_radio.traitor_frequency = freq - to_chat(traitor_mob, "The Syndicate have cunningly disguised a Syndicate Uplink as your [R.name]. Simply dial the frequency [format_frequency(freq)] to unlock its hidden features.") - traitor_mob.mind.store_memory("Radio Freq: [format_frequency(freq)] ([R.name]).") - else if(istype(R, /obj/item/pda)) - // generate a passcode if the uplink is hidden in a PDA - var/pda_pass = "[rand(100,999)] [pick("Alpha","Bravo","Delta","Omega")]" + var/obj/item/uplink/hidden/T = new(R) + target_radio.hidden_uplink = T + T.uplink_owner = "[traitor_mob.key]" + target_radio.traitor_frequency = freq + to_chat(traitor_mob, "The Syndicate have cunningly disguised a Syndicate Uplink as your [R.name]. Simply dial the frequency [format_frequency(freq)] to unlock its hidden features.") + antag_memory += "Radio Freq: [format_frequency(freq)] ([R.name])." + return TRUE - var/obj/item/uplink/hidden/T = new(R) - R.hidden_uplink = T - T.uplink_owner = "[traitor_mob.key]" - var/obj/item/pda/P = R - P.lock_code = pda_pass + else if(istype(R, /obj/item/pda)) + // generate a passcode if the uplink is hidden in a PDA + var/pda_pass = "[rand(100,999)] [pick("Alpha","Bravo","Delta","Omega")]" - to_chat(traitor_mob, "The Syndicate have cunningly disguised a Syndicate Uplink as your [R.name]. Simply enter the code \"[pda_pass]\" into the ringtone select to unlock its hidden features.") - antag_memory += ("Uplink Passcode: [pda_pass] ([R.name].") - return 1 - - -/datum/antagonist/traitor/proc/assign_exchange_role(datum/mind/owner) - //set faction - var/faction = "red" - if(owner == SSticker.mode.exchange_blue) - faction = "blue" - - //Assign objectives - var/datum/objective/steal/exchange/exchange_objective = new - exchange_objective.set_faction(faction,((faction == "red") ? SSticker.mode.exchange_blue : SSticker.mode.exchange_red)) - exchange_objective.owner = owner - owner.objectives += exchange_objective - - if(prob(20)) - var/datum/objective/steal/exchange/backstab/backstab_objective = new - backstab_objective.set_faction(faction) - backstab_objective.owner = owner - owner.objectives += backstab_objective - - //Spawn and equip documents - var/mob/living/carbon/human/mob = owner.current - - var/obj/item/folder/syndicate/folder - if(owner == SSticker.mode.exchange_red) - folder = new/obj/item/folder/syndicate/red(mob.locs) - else - folder = new/obj/item/folder/syndicate/blue(mob.locs) - - var/list/slots = list ( - "backpack" = slot_in_backpack, - "left pocket" = slot_l_store, - "right pocket" = slot_r_store, - "left hand" = slot_l_hand, - "right hand" = slot_r_hand, - ) - - var/where = "At your feet" - var/equipped_slot = mob.equip_in_one_of_slots(folder, slots) - if(equipped_slot) - where = "In your [equipped_slot]" - to_chat(mob, "

[where] is a folder containing secret documents that another Syndicate group wants. We have set up a meeting with one of their agents on station to make an exchange. Exercise extreme caution as they cannot be trusted and may be hostile.
") - mob.update_icons() + var/obj/item/uplink/hidden/T = new(R) + R.hidden_uplink = T + T.uplink_owner = "[traitor_mob.key]" + var/obj/item/pda/P = R + P.lock_code = pda_pass + to_chat(traitor_mob, "The Syndicate have cunningly disguised a Syndicate Uplink as your [R.name]. Simply enter the code \"[pda_pass]\" into the ringtone select to unlock its hidden features.") + antag_memory += "Uplink Passcode: [pda_pass] ([R.name]." + return TRUE + return FALSE +// Curently unused. Look at `/datum/game_mode/proc/auto_declare_completion_traitor()` instead. /datum/antagonist/traitor/roundend_report_footer() var/phrases = jointext(GLOB.syndicate_code_phrase, ", ") var/responses = jointext(GLOB.syndicate_code_response, ", ") diff --git a/code/modules/mob/mob_helpers.dm b/code/modules/mob/mob_helpers.dm index 2d103da8be5..de1616b5838 100644 --- a/code/modules/mob/mob_helpers.dm +++ b/code/modules/mob/mob_helpers.dm @@ -51,12 +51,6 @@ return 1 return 0 -/proc/ismindslave(A) //Checks to see if the person contains a mindslave implant, then checks that the implant is actually inside of them - for(var/obj/item/implant/traitor/T in A) - if(T && T.implanted) - return 1 - return 0 - /proc/isLivingSSD(mob/M) return istype(M) && M.player_logged && M.stat != DEAD