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