Antag datum clean up/refactor (#15084)

This commit is contained in:
SteelSlayer
2021-11-25 01:29:46 -06:00
committed by GitHub
parent 57dcffa33e
commit 93908eefc8
13 changed files with 633 additions and 592 deletions
+6 -6
View File
@@ -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)
+42 -49
View File
@@ -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)
. += "<br><B>Objective #[obj_count++]</B>: [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())
. += "<b>Objective #[obj_count++]</b>: [objective.explanation_text]"
if(admin)
. += " <a href='?src=[UID()];obj_edit=\ref[objective]'>Edit</a> " // Edit
@@ -178,6 +172,22 @@
. += "</a>"
. += "<br>"
/**
* 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))
. += "<b><font color='red'>TRAITOR</font></b>|<a href='?src=[UID()];traitor=clear'>no</a>"
if(objectives.len==0)
var/datum/antagonist/traitor/T = has_antag_datum(/datum/antagonist/traitor)
if(!length(T.objectives))
. += "<br>Objectives are empty! <a href='?src=[UID()];traitor=autoobjectives'>Randomize!</a>"
else
. += "<a href='?src=[UID()];traitor=traitor'>traitor</a>|<b>NO</b>"
@@ -489,7 +500,7 @@
out += memory
out += "<br><a href='?src=[UID()];memory_edit=1'>Edit memory</a><br>"
out += "Objectives:<br>"
if(objectives.len == 0)
if(!length(get_all_objectives()))
out += "EMPTY<br>"
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, "<span class='warning'>Reequipping revolutionary goes wrong!</span>")
@@ -1091,7 +1107,6 @@
if(has_antag_datum(/datum/antagonist/traitor))
to_chat(current, "<span class='warning'><FONT size = 3><B>You have been brainwashed! You are no longer a traitor!</B></FONT></span>")
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, "<span class='notice'>The objectives for traitor [key] have been generated. You can edit them and announce manually.</span>")
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, "<span class='warning'>Equipping a syndicate failed!</span>")
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, "<span class='warning'><B>You're now a loyal zealot of [missionary.name]!</B> You now must lay down your life to protect [missionary.p_them()] and assist in [missionary.p_their()] goals at any cost.</span>")
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]!</B> 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
+2 -2
View File
@@ -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, "<span class='warning'>The Syndicate will only issue this extremely dangerous item to agents assigned the Hijack objective.</span>")
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
+4 -2
View File
@@ -201,9 +201,11 @@ GLOBAL_LIST_INIT(possible_changeling_IDs, list("Alpha","Beta","Gamma","Delta","E
text += "<br><b>Changeling ID:</b> [changeling.changeling.changelingID]."
text += "<br><b>Genomes Extracted:</b> [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 += "<br><B>Objective #[count]</B>: [objective.explanation_text] <font color='green'><B>Success!</B></font>"
SSblackbox.record_feedback("nested tally", "changeling_objective", 1, list("[objective.type]", "SUCCESS"))
+31 -44
View File
@@ -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, "<B>The current game mode is - Traitor!</B>")
@@ -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 = "<FONT size = 2><B>The traitors were:</B></FONT><br>"
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 += "<br><B>Objective #[count]</B>: [objective.explanation_text] <font color='green'><B>Success!</B></font>"
SSblackbox.record_feedback("nested tally", "traitor_objective", 1, list("[objective.type]", "SUCCESS"))
else
text += "<br><B>Objective #[count]</B>: [objective.explanation_text] <font color='red'>Fail.</font>"
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: <b>[master]</b>)<br>"
text += " (slaved by: <b>[master_mind.current]</b>)<br>"
var/phrases = jointext(GLOB.syndicate_code_phrase, ", ")
var/responses = jointext(GLOB.syndicate_code_response, ", ")
@@ -165,4 +152,4 @@
<b>The code responses were:</b> <span class='danger'>[responses]</span><br><br>"
to_chat(world, text)
return 1
return TRUE
+4 -2
View File
@@ -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 += "<br><B>Objective #[count]</B>: [objective.explanation_text] <font color='green'><B>Success!</B></font>"
SSblackbox.record_feedback("nested tally", "traitor_objective", 1, list("[objective.type]", "SUCCESS"))
+3 -4
View File
@@ -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.
@@ -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 = {"<b>Implant Specifications:</b><BR>
@@ -16,71 +16,40 @@
<b>Integrity:</b> 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, "<span class='warning'><i>This person doesn't have a mind for you to slave!</i></span>")
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("<span class='warning'>[mindslave_target] seems to resist the implant!</span>", "<span class='warning'>You feel a strange sensation in your head that quickly dissipates.</span>")
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, "<span class='notice'>Making yourself loyal to yourself was a great idea! Perhaps even the best idea ever! Actually, you just feel like an idiot.</span>")
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("<span class='warning'>[mindslave_target] seems to resist the implant!</span>", "<span class='warning'>You feel a strange sensation in your head that quickly dissipates.</span>")
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(
"<span class='warning'>[mindslave_target] seems to resist the implant!</span>", \
"<span class='warning'>You feel a strange sensation in your head that quickly dissipates.</span>")
removed(mindslave_target)
qdel(src)
return FALSE
to_chat(mindslave_target, "<span class='danger'>You feel completely loyal to [user.name].</span>")
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, "<span class='notice'>Making yourself loyal to yourself was a great idea! Perhaps even the best idea ever! Actually, you just feel like an idiot.</span>")
user.adjustBrainLoss(20)
removed(mindslave_target)
qdel(src)
return FALSE
to_chat(mindslave_target, "<span class='danger'><B>You're now completely loyal to [user.name]!</B> You now must lay down your life to protect [user.p_them()] and assist in [user.p_their()] goals at any cost.</span>")
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, "<span class='warning'>You are no longer a mindslave: you have complete and free control of your own faculties, once more!</span>")
return 1
return 0
return TRUE
return FALSE
+1 -1
View File
@@ -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")
+299 -54
View File
@@ -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, "<span class='boldnotice'>[message]</span>")
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, "<span class='notice'>Your current objectives:</span>")
var/objective_num = 1
for(var/objective in objectives)
var/datum/objective/O = objective
to_chat(owner.current, "<span><B>Objective #[objective_num++]</B>: [O.explanation_text]</span><br>")
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, "<span class='motd'>For more information, check the wiki page: ([GLOB.configuration.url.wiki_url]/index.php/[wiki_page_name])</span>")
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, "<span class='userdanger'>You are a [special_role]!</span>")
/**
* 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,"<span class='userdanger'>You are no longer a [special_role]! </span>")
/**
* 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()
@@ -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, "<b>New Objective:</b> [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, "<span class='biggerdanger'>[greet_text]</span>")
else // Default greeting text if nothing is given.
to_chat(mindslave, "<span class='biggerdanger'><B>You are now completely loyal to [master.current.name]!</B> \
You must lay down your life to protect [master.current.p_them()] and assist in [master.current.p_their()] goals at any cost.</span>")
/datum/antagonist/mindslave/farewell()
to_chat(owner.current, "<span class='biggerdanger'>You are no longer a mindslave of [master.current]!</span>")
/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)
+141 -325
View File
@@ -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,"<span class='userdanger'> You are no longer a [special_role]! </span>")
..()
/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, "<span class='warning'>Your training has allowed you to overcome your clownish nature, allowing you to wield weapons without harming yourself.</span>")
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, "<span class='warning'>You lose your syndicate training and return to your own clumsy, clownish self.</span>")
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, "<B><font size=3 color=red>You are a [owner.special_role]!</font></B>")
if(!LAZYLEN(owner.objectives)) // Remove "owner" when objectives are handled in the datum
to_chat(owner.current, "<span>You don't have any objectives right now.</span>")
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, "<span class='motd'>For more information, check the wiki page: ([GLOB.configuration.url.wiki_url]/index.php/Traitor)</span>")
/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, "<span class='warning'>Unfortunately, the Syndicate wasn't able to give you an uplink.</span>")
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("<B>Radio Freq:</B> [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, "<span class='notice'>The Syndicate have cunningly disguised a Syndicate Uplink as your [R.name]. Simply dial the frequency [format_frequency(freq)] to unlock its hidden features.</span>")
antag_memory += "<B>Radio Freq:</B> [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 += ("<B>Uplink Passcode:</B> [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, "<BR><BR><span class='info'>[where] is a folder containing <b>secret documents</b> 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.</span><BR>")
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, "<span class='notice'>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.</span>")
antag_memory += "<B>Uplink Passcode:</B> [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, ", ")
-6
View File
@@ -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