i think it works

MAJOR antag backend update
- you cannot bruteforce the pen anymore
- uplink now uses bitflag to lock purchases
- poplock is handled entirely by the buyable uplink items
- tgui antag intro (for selected ones)
This commit is contained in:
LetterN
2021-10-30 11:45:50 +08:00
parent f7b898a2a9
commit 99019166e4
117 changed files with 3049 additions and 1868 deletions
+240 -64
View File
@@ -1,33 +1,70 @@
GLOBAL_LIST_EMPTY(antagonists)
/datum/antagonist
///Public name for this antagonist. Appears for player prompts and round-end reports.
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
var/delete_on_mind_deletion = TRUE
///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
///If false, the roundtype will still convert with this antag active
var/prevent_roundtype_conversion = TRUE
///Mind that owns this datum
var/datum/mind/owner
///Silent will prevent the gain/lose texts to show
var/silent = FALSE
///Whether or not the person will be able to have more than one datum
var/can_coexist_with_others = TRUE
///List of datums this type can't coexist with
var/list/typecache_datum_blacklist = list()
///The define string we use to identify the role for bans/player polls to spawn a random new one in.
var/job_rank
var/replace_banned = TRUE //Should replace jobbaned player with ghosts if granted.
///Should replace jobbanned player with ghosts if granted.
var/replace_banned = TRUE
///List of the objective datums that this role currently has, completing all objectives at round-end will cause this antagonist to greentext.
var/list/objectives = list()
var/antag_memory = ""//These will be removed with antag datum
var/antag_moodlet //typepath of moodlet that the mob will gain with their status
///String dialogue that is added to the player's in-round notes and memories regarding specifics of that antagonist, eg. the nuke code for nuke ops, or your unlock code for traitors.
var/antag_memory = ""
///typepath of moodlet that the mob will gain when granted this antagonist type.
var/antag_moodlet
///What is the configuration of this antagonist's hud icon, such as it's screen position and style, so thatit doesn't break other in-game hud icons.
var/antag_hud_type
///Name of the antag hud we provide to this mob.
var/antag_hud_name
/// If above 0, this is the multiplier for the speed at which we hijack the shuttle. Do not directly read, use hijack_speed().
var/hijack_speed = 0
/// If set to true, the antag will not be added to the living antag list.
var/soft_antag = FALSE
/// The battlecry this antagonist shouts when suiciding with C4/X4.
var/suicide_cry = ""
//Antag panel properties
var/show_in_antagpanel = TRUE //This will hide adding this antag type in antag panel, use only for internal subtypes that shouldn't be added directly but still show if possessed by mind
var/antagpanel_category = "Uncategorized" //Antagpanel will display these together, REQUIRED
var/show_name_in_check_antagonists = FALSE //Will append antagonist name in admin listings - use for categories that share more than one antag type
var/list/blacklisted_quirks = list(/datum/quirk/nonviolent,/datum/quirk/mute) // Quirks that will be removed upon gaining this antag. Pacifist and mute are default.
var/threat = 0 // Amount of threat this antag poses, for dynamic mode
var/show_to_ghosts = FALSE // Should this antagonist be shown as antag to ghosts? Shouldn't be used for stealthy antagonists like traitors
///This will hide adding this antag type in antag panel, use only for internal subtypes that shouldn't be added directly but still show if possessed by mind
var/show_in_antagpanel = TRUE
///Antagpanel will display these together, REQUIRED
var/antagpanel_category = "Uncategorized"
///Will append antagonist name in admin listings - use for categories that share more than one antag type
var/show_name_in_check_antagonists = FALSE
/// Should this antagonist be shown as antag to ghosts? Shouldn't be used for stealthy antagonists like traitors
var/show_to_ghosts = FALSE
/* CIT SPECIFIC */
/// Quirks that will be removed upon gaining this antag. Pacifist and mute are default.
var/list/blacklisted_quirks = list(/datum/quirk/nonviolent,/datum/quirk/mute)
/// Amount of threat this antag poses, for dynamic mode
var/threat = 0
var/delete_on_mind_deletion = TRUE
var/list/skill_modifiers
/* CIT SPECIFIC end */
//ANTAG UI
///name of the UI that will try to open, right now having nothing means this won't exist but in the future all should.
var/ui_name
///button to access antag interface
var/datum/action/antag_info/info_button
/datum/antagonist/New()
GLOB.antagonists += src
@@ -35,7 +72,9 @@ GLOBAL_LIST_EMPTY(antagonists)
/datum/antagonist/Destroy()
GLOB.antagonists -= src
if(owner)
if(!owner)
stack_trace("Destroy()ing antagonist datum when it has no owner.")
else
LAZYREMOVE(owner.antag_datums, src)
owner = null
return ..()
@@ -55,14 +94,14 @@ GLOBAL_LIST_EMPTY(antagonists)
/datum/antagonist/proc/specialization(datum/mind/new_owner)
return src
///Called by the transfer_to() mind proc after the mind (mind.current and new_character.mind) has moved but before the player (key and client) is transfered.
///Called by the transfer_to() mind proc after the mind (mind.current and new_character.mind) has moved but before the player (key and client) is transfered.
/datum/antagonist/proc/on_body_transfer(mob/living/old_body, mob/living/new_body)
SHOULD_CALL_PARENT(TRUE)
remove_innate_effects(old_body)
if(old_body.stat != DEAD && !LAZYLEN(old_body.mind?.antag_datums))
if(!soft_antag && old_body.stat != DEAD && !LAZYLEN(old_body.mind?.antag_datums))
old_body.remove_from_current_living_antags()
apply_innate_effects(new_body)
if(new_body.stat != DEAD)
if(!soft_antag && new_body.stat != DEAD)
new_body.add_to_current_living_antags()
//This handles the application of antag huds/special abilities
@@ -73,46 +112,76 @@ GLOBAL_LIST_EMPTY(antagonists)
/datum/antagonist/proc/remove_innate_effects(mob/living/mob_override)
return
/// This is called when the antagonist is being mindshielded.
/datum/antagonist/proc/pre_mindshield(mob/implanter, mob/living/mob_override)
SIGNAL_HANDLER
// return COMPONENT_MINDSHIELD_PASSED
/// This is called when the antagonist is successfully mindshielded.
/datum/antagonist/proc/on_mindshield(mob/implanter, mob/living/mob_override)
SIGNAL_HANDLER
return
// Adds the specified antag hud to the player. Usually called in an antag datum file
/datum/antagonist/proc/add_antag_hud(antag_hud_type, antag_hud_name, mob/living/mob_override)
var/datum/atom_hud/antag/hud = GLOB.huds[antag_hud_type]
hud.join_hud(mob_override)
set_antag_hud(mob_override, antag_hud_name)
// Removes the specified antag hud from the player. Usually called in an antag datum file
/datum/antagonist/proc/remove_antag_hud(antag_hud_type, mob/living/mob_override)
var/datum/atom_hud/antag/hud = GLOB.huds[antag_hud_type]
hud.leave_hud(mob_override)
set_antag_hud(mob_override, null)
// Handles adding and removing the clumsy mutation from clown antags. Gets called in apply/remove_innate_effects
/// Handles adding and removing the clumsy mutation from clown antags. Gets called in apply/remove_innate_effects
/datum/antagonist/proc/handle_clown_mutation(mob/living/mob_override, message, removing = TRUE)
var/mob/living/carbon/human/H = mob_override
if(H && istype(H) && owner.assigned_role == "Clown")
if(removing) // They're a clown becoming an antag, remove clumsy
H.dna.remove_mutation(CLOWNMUT)
if(!silent && message)
to_chat(H, "<span class='boldnotice'>[message]</span>")
else
H.dna.add_mutation(CLOWNMUT) // We're removing their antag status, add back clumsy
if(!ishuman(mob_override) || owner.assigned_role != "Clown")
return
var/mob/living/carbon/human/human_override = mob_override
if(removing) // They're a clown becoming an antag, remove clumsy
human_override.dna.remove_mutation(CLOWNMUT)
if(!silent && message)
to_chat(human_override, span_boldnotice("[message]"))
else
human_override.dna.add_mutation(CLOWNMUT) // We're removing their antag status, add back clumsy
//Assign default team and creates one for one of a kind team antagonists
/datum/antagonist/proc/create_team(datum/team/team)
return
///Called by the add_antag_datum() mind proc after the instanced datum is added to the mind's antag_datums list.
///Called by the add_antag_datum() mind proc after the instanced datum is added to the mind's antag_datums list.
/datum/antagonist/proc/on_gain()
SHOULD_CALL_PARENT(TRUE)
set waitfor = FALSE
if(!(owner?.current))
return
if(!owner)
CRASH("[src] ran on_gain() without a mind")
if(!owner.current)
CRASH("[src] ran on_gain() on a mind without a mob")
if(ui_name)//in the future, this should entirely replace greet.
info_button = new(owner.current, src)
info_button.Grant(owner.current)
if(!silent)
greet()
if(ui_name)
to_chat(owner.current, span_big("You are \a [src]."))
to_chat(owner.current, span_boldnotice("For more info, read the panel. you can always come back to it using the button in the top left."))
info_button.Trigger()
apply_innate_effects()
give_antag_moodies()
remove_blacklisted_quirks()
// RegisterSignal(owner, COMSIG_PRE_MINDSHIELD_IMPLANT, .proc/pre_mindshield)
// RegisterSignal(owner, COMSIG_MINDSHIELD_IMPLANTED, .proc/on_mindshield)
if(is_banned(owner.current) && replace_banned)
replace_banned_player()
// else if(owner.current.client?.holder && (CONFIG_GET(flag/auto_deadmin_antagonists) || owner.current.client.prefs?.toggles & DEADMIN_ANTAGONIST))
// owner.current.client.holder.auto_deadmin()
if(!soft_antag && owner.current.stat != DEAD)
owner.current.add_to_current_living_antags()
// cit skill
if(skill_modifiers)
for(var/A in skill_modifiers)
ADD_SINGLETON_SKILL_MODIFIER(owner, A, type)
@@ -120,61 +189,95 @@ GLOBAL_LIST_EMPTY(antagonists)
if(istype(M))
M.name = "[name] Training"
owner.current.AddComponent(/datum/component/activity)
if(owner.current.stat != DEAD)
owner.current.add_to_current_living_antags()
SEND_SIGNAL(owner.current, COMSIG_MOB_ANTAG_ON_GAIN, src)
/**
* Proc that checks the sent mob aganst the banlistfor this antagonist.
* Returns FALSE if no mob is sent, or the mob is not found to be banned.
*
* * mob/M: The mob that you are looking for on the banlist.
*/
/datum/antagonist/proc/is_banned(mob/M)
if(!M)
return FALSE
. = (jobban_isbanned(M, ROLE_SYNDICATE) || QDELETED(M) || (job_rank && (jobban_isbanned(M,job_rank) || QDELETED(M))))
/**
* Proc that replaces a player who cannot play a specific antagonist due to being banned via a poll, and alerts the player of their being on the banlist.
*/
/datum/antagonist/proc/replace_banned_player()
set waitfor = FALSE
var/list/mob/candidates = pollCandidatesForMob("Do you want to play as a [name]?", "[name]", null, job_rank, 50, owner.current)
var/list/mob/dead/observer/candidates = pollCandidatesForMob("Do you want to play as a [name]?", "[name]", job_rank, 50, owner.current)
if(LAZYLEN(candidates))
var/mob/C = pick(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.")
message_admins("[key_name_admin(C)] has taken control of ([key_name_admin(owner)]) to replace a jobbanned player.")
owner.current.ghostize(0)
C.transfer_ckey(owner.current, FALSE)
///Called by the remove_antag_datum() and remove_all_antag_datums() mind procs for the antag datum to handle its own removal and deletion.
/**
* Called by the remove_antag_datum() and remove_all_antag_datums() mind procs for the antag datum to handle its own removal and deletion.
*/
/datum/antagonist/proc/on_removal()
SHOULD_CALL_PARENT(TRUE)
if(!owner)
CRASH("Antag datum with no owner.")
remove_innate_effects()
clear_antag_moodies()
if(owner)
LAZYREMOVE(owner.antag_datums, src)
for(var/A in skill_modifiers)
owner.remove_skill_modifier(GET_SKILL_MOD_ID(A, type))
if(!LAZYLEN(owner.antag_datums))
owner.current.remove_from_current_living_antags()
if(!silent && owner.current)
farewell()
LAZYREMOVE(owner.antag_datums, src)
// cit skill
for(var/A in skill_modifiers)
owner.remove_skill_modifier(GET_SKILL_MOD_ID(A, type))
// end
if(!LAZYLEN(owner.antag_datums) && !soft_antag)
owner.current.remove_from_current_living_antags()
if(info_button)
QDEL_NULL(info_button)
if(!silent && owner.current)
farewell()
// UnregisterSignal(owner, COMSIG_PRE_MINDSHIELD_IMPLANT)
// UnregisterSignal(owner, COMSIG_MINDSHIELD_IMPLANTED)
var/datum/team/team = get_team()
if(team)
team.remove_member(owner)
// we don't remove the activity component on purpose--no real point to it
qdel(src)
/**
* Proc that sends fluff or instructional messages to the player when they are given this antag datum.
* Use this proc for playing sounds, sending alerts, or helping to setup non-gameplay influencing aspects of the antagonist type.
*/
/datum/antagonist/proc/greet()
return
/**
* Proc that sends fluff or instructional messages to the player when they lose this antag datum.
* Use this proc for playing sounds, sending alerts, or otherwise informing the player that they're no longer a specific antagonist type.
*/
/datum/antagonist/proc/farewell()
return
/**
* Proc that assigns this antagonist's ascribed moodlet to the player.
*/
/datum/antagonist/proc/give_antag_moodies()
if(!antag_moodlet)
return
SEND_SIGNAL(owner.current, COMSIG_ADD_MOOD_EVENT, "antag_moodlet", antag_moodlet)
/**
* Proc that removes this antagonist's ascribed moodlet from the player.
*/
/datum/antagonist/proc/clear_antag_moodies()
if(!antag_moodlet)
return
SEND_SIGNAL(owner.current, COMSIG_CLEAR_MOOD_EVENT, "antag_moodlet")
/**
* Removes invalid quirks.
*/
/datum/antagonist/proc/remove_blacklisted_quirks()
var/mob/living/L = owner.current
if(istype(L))
@@ -185,16 +288,22 @@ GLOBAL_LIST_EMPTY(antagonists)
to_chat(L, "<span class='boldannounce'>[initial(Q.antag_removal_text)]</span>")
qdel(Q)
//Returns the team antagonist belongs to if any.
/**
* Proc that will return the team this antagonist belongs to, when called. Helpful with antagonists that may belong to multiple potential teams in a single round, like families.
*/
/datum/antagonist/proc/get_team()
return
//Individual roundend report
/**
* Proc that sends string information for the end-round report window to the server.
* This runs on every instance of every antagonist that exists at the end of the round.
* This is the body of the message, sandwiched between roundend_report_header and roundend_report_footer.
*/
/datum/antagonist/proc/roundend_report()
var/list/report = list()
if(!owner)
CRASH("antagonist datum without owner")
CRASH("Antagonist datum without owner")
report += printplayer(owner)
@@ -213,11 +322,19 @@ GLOBAL_LIST_EMPTY(antagonists)
return report.Join("<br>")
//Displayed at the start of roundend_category section, default to roundend_category header
/**
* Proc that sends string data for the round-end report.
* Displayed before roundend_report and roundend_report_footer.
* Appears at start of roundend_catagory section.
*/
/datum/antagonist/proc/roundend_report_header()
return "<span class='header'>The [roundend_category] were:</span><br>"
return "<span class='header'>The [roundend_category] were:</span><br>"
//Displayed at the end of roundend_category section
/**
* Proc that sends string data for the round-end report.
* Displayed after roundend_report and roundend_report_footer.
* Appears at the end of the roundend_catagory section.
*/
/datum/antagonist/proc/roundend_report_footer()
return
@@ -226,22 +343,24 @@ GLOBAL_LIST_EMPTY(antagonists)
//Called when using admin tools to give antag status
/datum/antagonist/proc/admin_add(datum/mind/new_owner,mob/admin)
message_admins("[key_name_admin(admin)] made [new_owner.current] into [name].")
log_admin("[key_name(admin)] made [new_owner.current] into [name].")
message_admins("[key_name_admin(admin)] made [key_name_admin(new_owner)] into [name].")
log_admin("[key_name(admin)] made [key_name(new_owner)] into [name].")
new_owner.add_antag_datum(src)
//Called when removing antagonist using admin tools
/datum/antagonist/proc/admin_remove(mob/user)
if(!user)
return
message_admins("[key_name_admin(user)] has removed [name] antagonist status from [owner.current].")
log_admin("[key_name(user)] has removed [name] antagonist status from [owner.current].")
message_admins("[key_name_admin(user)] has removed [name] antagonist status from [key_name_admin(owner)].")
log_admin("[key_name(user)] has removed [name] antagonist status from [key_name(owner)].")
on_removal()
//gamemode/proc/is_mode_antag(antagonist/A) => TRUE/FALSE
//Additional data to display in antagonist panel section
//nuke disk code, genome count, etc
/**
* Additional data to display in the antagonist panel section.
* For example, nuke disk code, genome count, etc
*/
/datum/antagonist/proc/antag_panel_data()
return ""
@@ -253,7 +372,7 @@ GLOBAL_LIST_EMPTY(antagonists)
return FALSE
return TRUE
// List if ["Command"] = CALLBACK(), user will be appeneded to callback arguments on execution
/// List of ["Command"] = CALLBACK(), user will be appeneded to callback arguments on execution
/datum/antagonist/proc/get_admin_commands()
. = list()
@@ -283,14 +402,19 @@ GLOBAL_LIST_EMPTY(antagonists)
return
antag_memory = new_memo
/// Gets how fast we can hijack the shuttle, return 0 for can not hijack. Defaults to hijack_speed var, override for custom stuff like buffing hijack speed for hijack objectives or something.
/**
* Gets how fast we can hijack the shuttle, return 0 for can not hijack.
* Defaults to hijack_speed var, override for custom stuff like buffing hijack speed for hijack objectives or something.
*/
/datum/antagonist/proc/hijack_speed()
var/datum/objective/hijack/H = locate() in objectives
if(!isnull(H?.hijack_speed_override))
return H.hijack_speed_override
return hijack_speed
/// Gets our threat level. Defaults to threat var, override for custom stuff like different traitor goals having different threats.
/**
* Gets our threat level. Override this proc for custom functionality/dynamic threat level.
*/
/datum/antagonist/proc/threat()
. = CONFIG_GET(keyed_list/antag_threat)[lowertext(name)]
if(. == null)
@@ -300,6 +424,13 @@ GLOBAL_LIST_EMPTY(antagonists)
/datum/antagonist/custom
antagpanel_category = "Custom"
show_name_in_check_antagonists = TRUE //They're all different
var/datum/team/custom_team
/datum/antagonist/custom/create_team(datum/team/team)
custom_team = team
/datum/antagonist/custom/get_team()
return custom_team
/datum/antagonist/custom/admin_add(datum/mind/new_owner,mob/admin)
var/custom_name = stripped_input(admin, "Custom antagonist name:", "Custom antag", "Antagonist")
@@ -308,3 +439,48 @@ GLOBAL_LIST_EMPTY(antagonists)
else
return
..()
///ANTAGONIST UI STUFF
/datum/antagonist/ui_interact(mob/user, datum/tgui/ui)
ui = SStgui.try_update_ui(user, src, ui)
if(!ui)
ui = new(user, src, ui_name, name)
ui.open()
/datum/antagonist/ui_state(mob/user)
return GLOB.always_state
///generic helper to send objectives as data through tgui. supports smart objectives too!
/datum/antagonist/proc/get_objectives()
var/objective_count = 1
var/list/objective_data = list()
//all obj
for(var/datum/objective/objective in objectives)
objective_data += list(list(
"count" = objective_count,
"name" = objective.name,
"explanation" = objective.explanation_text,
"complete" = objective.completed,
))
objective_count++
return objective_data
//button for antags to review their descriptions/info
/datum/action/antag_info
name = "Open Antag Information:"
button_icon_state = "round_end"
var/datum/antagonist/antag_datum
/datum/action/antag_info/New(Target, datum/antagonist/antag_datum)
. = ..()
src.antag_datum = antag_datum
name += " [antag_datum.name]"
/datum/action/antag_info/Trigger()
if(antag_datum)
antag_datum.ui_interact(owner)
/datum/action/antag_info/IsAvailable()
return TRUE
@@ -17,11 +17,13 @@
B.objectives += objective
M.add_antag_datum(B)
var/begin_message = "<span class='deadsay'><b>[L]</b> has been brainwashed with the following objectives: "
var/begin_message = " has been brainwashed with the following objectives: "
var/obj_message = english_list(directives)
var/end_message = "</b>.</span>"
var/end_message = "."
var/rendered = begin_message + obj_message + end_message
deadchat_broadcast(rendered, follow_target = L, turf_target = get_turf(L), message_type=DEADCHAT_REGULAR)
deadchat_broadcast(rendered, "<b>[L]</b>", follow_target = L, turf_target = get_turf(L), message_type=DEADCHAT_ANNOUNCEMENT)
if(prob(1) || SSevents.holidays && SSevents.holidays[APRIL_FOOLS])
L.say("You son of a bitch! I'm in.", forced = "That son of a bitch! They're in.")
/datum/antagonist/brainwashed
name = "Brainwashed Victim"
@@ -30,19 +32,21 @@
show_in_antagpanel = TRUE
antagpanel_category = "Other"
show_name_in_check_antagonists = TRUE
ui_name = "AntagInfoBrainwashed"
suicide_cry = "FOR... SOMEONE!!"
/datum/antagonist/brainwashed/greet()
to_chat(owner, "<span class='warning'>Your mind reels as it begins focusing on a single purpose...</span>")
to_chat(owner, "<big><span class='warning'><b>Follow the Directives, at any cost!</b></span></big>")
var/i = 1
for(var/X in objectives)
var/datum/objective/O = X
to_chat(owner, "<b>[i].</b> [O.explanation_text]")
i++
/datum/antagonist/brainwashed/ui_static_data(mob/user)
. = ..()
var/list/data = list()
data["objectives"] = get_objectives()
return data
/datum/antagonist/brainwashed/farewell()
to_chat(owner, "<span class='warning'>Your mind suddenly clears...</span>")
to_chat(owner, "<big><span class='warning'><b>You feel the weight of the Directives disappear! You no longer have to obey them.</b></span></big>")
to_chat(owner, span_warning("Your mind suddenly clears..."))
to_chat(owner, "<big>[span_warning("<b>You feel the weight of the Directives disappear! You no longer have to obey them.</b>")]</big>")
if(owner.current)
var/mob/living/owner_mob = owner.current
owner_mob.log_message("is no longer brainwashed with the objectives: [english_list(objectives)].", LOG_ATTACK)
owner.announce_objectives()
/datum/antagonist/brainwashed/admin_add(datum/mind/new_owner,mob/admin)
@@ -54,9 +58,9 @@
var/objective = stripped_input(admin, "Add an objective, or leave empty to finish.", "Brainwashing", null, MAX_MESSAGE_LEN)
if(objective)
objectives += objective
while(tgui_alert(admin, "Add another objective?","More Brainwashing",list("Yes","No")) == "Yes")
while(tgui_alert(admin,"Add another objective?","More Brainwashing",list("Yes","No")) == "Yes")
if(tgui_alert(admin, "Confirm Brainwashing?","Are you sure?",list("Yes","No")) == "No")
if(tgui_alert(admin,"Confirm Brainwashing?","Are you sure?",list("Yes","No")) == "No")
return
if(!LAZYLEN(objectives))
@@ -69,6 +73,7 @@
brainwash(C, objectives)
var/obj_list = english_list(objectives)
message_admins("[key_name_admin(admin)] has brainwashed [key_name_admin(C)] with the following objectives: [obj_list].")
C.log_message("has been force-brainwashed with the objective '[obj_list]' by admin [key_name(admin)]", LOG_ATTACK, log_globally = FALSE)
log_admin("[key_name(admin)] has brainwashed [key_name(C)] with the following objectives: [obj_list].")
/datum/objective/brainwashing
@@ -6,6 +6,7 @@
job_rank = ROLE_SERVANT_OF_RATVAR
antag_moodlet = /datum/mood_event/cult
skill_modifiers = list(/datum/skill_modifier/job/level/wiring, /datum/skill_modifier/job/level/dwarfy/blacksmithing)
ui_name = "AntagInfoClockwork"
var/datum/action/innate/hierophant/hierophant_network = new
threat = 3
var/datum/team/clockcult/clock_team
@@ -14,6 +15,12 @@
var/ignore_eligibility_check = FALSE
var/ignore_holy_water = FALSE
/datum/antagonist/brainwashed/ui_static_data(mob/user)
. = ..()
var/list/data = list()
data["objectives"] = get_objectives()
return data
/datum/antagonist/clockcult/silent
name = "Silent Clock Cultist"
silent = TRUE
@@ -22,6 +29,8 @@
/datum/antagonist/clockcult/neutered
name = "Neutered Clock Cultist"
neutered = TRUE
soft_antag = TRUE
ui_name = null // no.
/datum/antagonist/clockcult/neutered/traitor
name = "Traitor Clock Cultist"
@@ -3,5 +3,6 @@
show_name_in_check_antagonists = TRUE
show_in_antagpanel = FALSE
threat = 2
ui_name = "AntagInfoMorph"
//It does nothing! (Besides tracking)
@@ -4,3 +4,5 @@
show_name_in_check_antagonists = TRUE
threat = 5
show_to_ghosts = TRUE
ui_name = "AntagInfoNightmare"
suicide_cry = "FOR THE DARKNESS!!"
@@ -236,6 +236,8 @@
var/obj/machinery/nuclearbomb/tracked_nuke
var/core_objective = /datum/objective/nuclear
var/memorized_code
var/list/team_discounts
var/datum/weakref/war_button_ref
/datum/team/nuclear/New()
..()
@@ -121,7 +121,7 @@
return
var/mob/living/carbon/human/H = owner.current
// Give uplink
var/obj/item/uplink_holder = owner.equip_traitor(uplink_owner = src)
var/obj/item/uplink_holder = owner.equip_traitor()
var/datum/component/uplink/uplink = uplink_holder.GetComponent(/datum/component/uplink)
uplink.telecrystals = INITIAL_CRYSTALS
// Give AI hacking board
@@ -7,37 +7,51 @@
/datum/antagonist/traitor/internal_affairs
name = "Internal Affairs Agent"
employer = "Nanotrasen"
special_role = "internal affairs agent"
suicide_cry = "FOR THE COMPANY!!"
antagpanel_category = "IAA"
var/special_role = "internal affairs agent"
var/syndicate = FALSE
var/last_man_standing = FALSE
var/list/datum/mind/targets_stolen
/datum/antagonist/traitor/internal_affairs/New()
. = ..()
LAZYADD(targets_stolen, src)
/datum/antagonist/traitor/internal_affairs/proc/give_pinpointer()
if(owner && owner.current)
if(!owner)
CRASH("Antag datum with no owner.")
if(owner.current)
owner.current.apply_status_effect(/datum/status_effect/agent_pinpointer)
/datum/antagonist/traitor/internal_affairs/apply_innate_effects()
.=..() //in case the base is used in future
if(owner && owner.current)
. = ..()
if(!owner)
CRASH("Antag datum with no owner.")
if(owner.current)
give_pinpointer(owner.current)
/datum/antagonist/traitor/internal_affairs/remove_innate_effects()
.=..()
if(owner && owner.current)
. = ..()
if(!owner)
CRASH("Antag datum with no owner.")
if(owner.current)
owner.current.remove_status_effect(/datum/status_effect/agent_pinpointer)
/datum/antagonist/traitor/internal_affairs/on_gain()
START_PROCESSING(SSprocessing, src)
.=..()
. = ..()
/datum/antagonist/traitor/internal_affairs/on_removal()
STOP_PROCESSING(SSprocessing,src)
.=..()
. = ..()
/datum/antagonist/traitor/internal_affairs/process()
iaa_process()
/datum/status_effect/agent_pinpointer
id = "agent_pinpointer"
duration = -1
@@ -46,6 +60,8 @@
var/minimum_range = PINPOINTER_MINIMUM_RANGE
var/range_fuzz_factor = PINPOINTER_EXTRA_RANDOM_RANGE
var/mob/scan_target = null
var/range_mid = 8
var/range_far = 16
/atom/movable/screen/alert/status_effect/agent_pinpointer
name = "Internal Affairs Integrated Pinpointer"
@@ -66,13 +82,13 @@
linked_alert.icon_state = "pinondirect"
else
linked_alert.setDir(get_dir(here, there))
switch(get_dist(here, there))
if(1 to 8)
linked_alert.icon_state = "pinonclose"
if(9 to 16)
linked_alert.icon_state = "pinonmedium"
if(16 to INFINITY)
linked_alert.icon_state = "pinonfar"
var/dist = (get_dist(here, there))
if(dist >= 1 && dist <= range_mid)
linked_alert.icon_state = "pinonclose"
else if(dist > range_mid && dist <= range_far)
linked_alert.icon_state = "pinonmedium"
else if(dist > range_far)
linked_alert.icon_state = "pinonfar"
/datum/status_effect/agent_pinpointer/proc/scan_for_target()
scan_target = null
@@ -99,37 +115,42 @@
return (istype(O, /datum/objective/assassinate/internal)||istype(O, /datum/objective/destroy/internal))
/datum/antagonist/traitor/proc/replace_escape_objective()
if(!owner || !objectives.len)
if(!owner)
CRASH("Antag datum with no owner.")
if(!objectives.len)
return
for (var/objective_ in objectives)
if(!(istype(objective_, /datum/objective/escape)||istype(objective_, /datum/objective/survive)))
for (var/objective in objectives)
if(!(istype(objective, /datum/objective/escape) || istype(objective, /datum/objective/survive)))
continue
remove_objective(objective_)
objectives -= objective
var/datum/objective/martyr/martyr_objective = new
martyr_objective.owner = owner
add_objective(martyr_objective)
/datum/antagonist/traitor/proc/reinstate_escape_objective()
if(!owner||!objectives.len)
if(!owner)
CRASH("Antag datum with no owner.")
if(!objectives.len)
return
for (var/objective_ in objectives)
if(!istype(objective_, /datum/objective/martyr))
for (var/objective in objectives)
if(!istype(objective, /datum/objective/martyr))
continue
remove_objective(objective_)
remove_objective(objective)
/datum/antagonist/traitor/internal_affairs/reinstate_escape_objective()
..()
var/objtype = !istype(traitor_kind,TRAITOR_AI) ? /datum/objective/escape : /datum/objective/survive
var/datum/objective/escape_objective = new objtype
for (var/datum/objective/martyr/martyr_objective in objectives)
objectives -= martyr_objective
var/datum/objective/escape_objective = new /datum/objective/escape()
escape_objective.owner = owner
add_objective(escape_objective)
objectives += escape_objective
/datum/antagonist/traitor/internal_affairs/proc/steal_targets(datum/mind/victim)
if(!owner.current||owner.current.stat==DEAD)
return
to_chat(owner.current, "<span class='userdanger'> Target eliminated: [victim.name]</span>")
LAZYINITLIST(targets_stolen)
to_chat(owner.current, span_userdanger("Target eliminated: [victim.name]"))
for(var/objective_ in victim.get_all_objectives())
if(istype(objective_, /datum/objective/assassinate/internal))
var/datum/objective/assassinate/internal/objective = objective_
@@ -143,7 +164,7 @@
add_objective(new_objective)
targets_stolen += objective.target
var/status_text = objective.check_completion() ? "neutralised" : "active"
to_chat(owner.current, "<span class='userdanger'> New target added to database: [objective.target.name] ([status_text]) </span>")
to_chat(owner.current, span_userdanger("New target added to database: [objective.target.name] ([status_text])"))
else if(istype(objective_, /datum/objective/destroy/internal))
var/datum/objective/destroy/internal/objective = objective_
var/datum/objective/destroy/internal/new_objective = new
@@ -156,7 +177,7 @@
add_objective(new_objective)
targets_stolen += objective.target
var/status_text = objective.check_completion() ? "neutralised" : "active"
to_chat(owner.current, "<span class='userdanger'> New target added to database: [objective.target.name] ([status_text]) </span>")
to_chat(owner.current, span_userdanger("New target added to database: [objective.target.name] ([status_text])"))
last_man_standing = TRUE
for(var/objective_ in objectives)
if(!is_internal_objective(objective_))
@@ -167,13 +188,15 @@
return
if(last_man_standing)
if(syndicate)
to_chat(owner.current,"<span class='userdanger'>All the suspected agents are dead, and no more is required of you. Die a glorious death, agent.</span>")
replace_escape_objective(owner)
to_chat(owner.current,span_userdanger("All the suspected agents are dead, and no more is required of you. Die a glorious death, agent."))
else
to_chat(owner.current,"<span class='userdanger'>All the other agents are dead. You have done us all a great service and shall be honorably exiled upon returning to base.</span>")
to_chat(owner.current,span_userdanger("All the other agents are dead. You have done us all a great service and shall be honorably exiled upon returning to base."))
replace_escape_objective(owner)
/datum/antagonist/traitor/internal_affairs/proc/iaa_process()
if(owner&&owner.current&&owner.current.stat!=DEAD)
if(!owner)
CRASH("Antag datum with no owner.")
if(owner.current && owner.current.stat != DEAD)
for(var/objective_ in objectives)
if(!is_internal_objective(objective_))
continue
@@ -188,12 +211,12 @@
objective.stolen = TRUE
else
if(objective.stolen)
var/fail_msg = "<span class='userdanger'>Your sensors tell you that [objective.target.current.real_name], one of the targets you were meant to have killed, pulled one over on you, and is still alive - do the job properly this time! </span>"
var/fail_msg = span_userdanger("Your sensors tell you that [objective.target.current.real_name], one of the targets you were meant to have killed, pulled one over on you, and is still alive - do the job properly this time! ")
if(last_man_standing)
if(syndicate)
fail_msg += "<span class='userdanger'> You no longer have permission to die. </span>"
fail_msg += span_userdanger(" You no longer have permission to die. ")
else
fail_msg += "<span class='userdanger'> The truth could still slip out!</font><B><font size=5 color=red> Cease any terrorist actions as soon as possible, unneeded property damage or loss of employee life will lead to great shame.</span>"
fail_msg += span_userdanger(" The truth could still slip out!</font><B><font size=5 color=red> Cease any terrorist actions as soon as possible, unneeded property damage or loss of employee life will lead to your contract being terminated.")
reinstate_escape_objective(owner)
last_man_standing = FALSE
to_chat(owner.current, fail_msg)
@@ -220,29 +243,24 @@
/datum/antagonist/traitor/internal_affairs/forge_traitor_objectives()
forge_iaa_objectives()
var/objtype = !istype(traitor_kind,TRAITOR_AI) ? /datum/objective/escape : /datum/objective/survive
var/datum/objective/escape_objective = new objtype
var/datum/objective/escape_objective = new /datum/objective/escape()
escape_objective.owner = owner
add_objective(escape_objective)
/datum/antagonist/traitor/internal_affairs/proc/greet_iaa()
var/crime = pick("distribution of contraband" , "embezzlement", "piloting under the influence", "dereliction of duty", "syndicate collaboration", "mutiny", "multiple homicides", "corporate espionage", "receiving bribes", "malpractice", "worship of prohibited life forms", "possession of profane texts", "murder", "arson", "insulting their manager", "grand theft", "conspiracy", "attempting to unionize", "vandalism", "gross incompetence")
var/crime = pick("distribution of contraband", "embezzlement", "piloting under the influence", "dereliction of duty", "syndicate collaboration", "mutiny", "multiple homicides", "corporate espionage", "receiving bribes", "malpractice", "worship of prohibited life forms", "possession of profane texts", "murder", "arson", "insulting their manager", "grand theft", "conspiracy", "attempting to unionize", "vandalism", "gross incompetence")
to_chat(owner.current, "<span class='userdanger'>You are the [special_role].</span>")
to_chat(owner.current, span_userdanger("You are the [special_role]."))
if(syndicate)
to_chat(owner.current, "<span class='userdanger'>GREAT LEADER IS DEAD. NANOTRASEN MUST FALL.</span>")
to_chat(owner.current, "<span class='userdanger'>Your have infiltrated this vessel to cause chaos and assassinate targets known to have conspired against the Syndicate.</span>")
to_chat(owner.current, "<span class='userdanger'>Any damage you cause will be a further embarrassment to Nanotrasen, so you have no limits on collateral damage.</span>")
to_chat(owner.current, "<span class='userdanger'>You have been provided with a standard uplink to accomplish your task. </span>")
to_chat(owner.current, "<span class='userdanger'>By no means reveal that you are a Syndicate agent. By no means reveal that your targets are being hunted.</span>")
to_chat(owner.current, span_userdanger("Your target has been framed for [crime], and you have been tasked with eliminating them to prevent them defending themselves in court."))
to_chat(owner.current, "<span class='warningplain'><B><font size=5 color=red>Any damage you cause will be a further embarrassment to Nanotrasen, so you have no limits on collateral damage.</font></B></span>")
to_chat(owner.current, span_userdanger("You have been provided with a standard uplink to accomplish your task."))
else
to_chat(owner.current, "<span class='userdanger'>CAUTION: Your legal status as a citizen of NanoTrasen will be permanently revoked upon completion of your first contract.</span>")
to_chat(owner.current, "<span class='userdanger'>Your target has been suspected of [crime], and must be removed from this plane.</span>")
to_chat(owner.current, "<span class='userdanger'>While you have a license to kill, you are to eliminate your targets with no collateral or unrelated deaths.</span>")
to_chat(owner.current, "<span class='userdanger'>For the sake of plausable deniability, you have been equipped with captured Syndicate equipment via uplink.</span>")
to_chat(owner.current, "<span class='userdanger'>By no means reveal that you, or any other NT employees, are undercover agents.</span>")
to_chat(owner.current, span_userdanger("Your target is suspected of [crime], and you have been tasked with eliminating them by any means necessary to avoid a costly and embarrassing public trial."))
to_chat(owner.current, "<span class='warningplain'><B><font size=5 color=red>While you have a license to kill, unneeded property damage or loss of employee life will lead to your contract being terminated.</font></B></span>")
to_chat(owner.current, span_userdanger("For the sake of plausible deniability, you have been equipped with an array of captured Syndicate weaponry available via uplink."))
to_chat(owner.current, "<span class='userdanger'>Finally, watch your back. Your target has friends in high places, and intel suggests someone may have taken out a contract of their own to protect them.</span>")
to_chat(owner.current, span_userdanger("Finally, watch your back. Your target has friends in high places, and intel suggests someone may have taken out a contract of their own to protect them."))
owner.announce_objectives()
/datum/antagonist/traitor/internal_affairs/greet()
@@ -5,7 +5,7 @@
chaos = 5
threat = 5
min_players = 20
uplink_filters = list(/datum/uplink_item/stealthy_weapons/romerol_kit,/datum/uplink_item/bundles_TC/contract_kit)
uplink_filters = list(/datum/uplink_item/stealthy_weapons/romerol_kit,/datum/uplink_item/bundles_tc/contract_kit)
/datum/traitor_class/human/martyr/forge_objectives(datum/antagonist/traitor/T)
var/datum/objective/martyr/O = new
@@ -11,6 +11,8 @@ GLOBAL_LIST_EMPTY(traitor_classes)
/// Minimum players for this to randomly roll via get_random_traitor_kind().
var/min_players = 0
var/list/uplink_filters
/// Specific tgui theme for the player's antag info panel.
var/tgui_theme = "syndicate"
/datum/traitor_class/New()
..()
@@ -5,14 +5,22 @@
job_rank = ROLE_TRAITOR
antag_moodlet = /datum/mood_event/focused
skill_modifiers = list(/datum/skill_modifier/job/level/wiring/basic)
var/special_role = ROLE_TRAITOR
hijack_speed = 0.5 //10 seconds per hijack stage by default
ui_name = "AntagInfoTraitor"
suicide_cry = "FOR THE SYNDICATE!!"
var/employer = "The Syndicate"
var/give_objectives = TRUE
var/should_give_codewords = TRUE
var/should_equip = TRUE
///special datum about what kind of employer the trator has
var/datum/traitor_class/traitor_kind
///reference to the uplink this traitor was given, if they were.
var/datum/component/uplink/uplink
var/datum/contractor_hub/contractor_hub
hijack_speed = 0.5 //10 seconds per hijack stage by default
threat = 5
/datum/antagonist/traitor/New()
@@ -45,7 +53,7 @@
/datum/antagonist/traitor/process()
traitor_kind.on_process(src)
/proc/get_random_traitor_kind(var/list/blacklist = list())
/proc/get_random_traitor_kind(list/blacklist = list())
var/list/weights = list()
for(var/C in GLOB.traitor_classes)
if(!(C in blacklist))
@@ -62,23 +70,24 @@
return choice
/datum/antagonist/traitor/on_gain()
owner.special_role = job_rank
if(owner.current && isAI(owner.current))
set_traitor_kind(TRAITOR_AI)
else
set_traitor_kind(get_random_traitor_kind())
SSticker.mode.traitors += owner
owner.special_role = special_role
finalize_traitor()
..()
uplink = owner.find_syndicate_uplink()
return ..()
/datum/antagonist/traitor/on_removal()
if(!silent && owner.current)
to_chat(owner.current,span_userdanger("You are no longer the [job_rank]!"))
//Remove malf powers.
traitor_kind.on_removal(src)
SSticker.mode.traitors -= owner
if(!silent && owner.current)
to_chat(owner.current,"<span class='userdanger'> You are no longer the [special_role]! </span>")
owner.special_role = null
. = ..()
return ..()
/datum/antagonist/traitor/proc/handle_hearing(datum/source, list/hearing_args)
var/message = hearing_args[HEARING_RAW_MESSAGE]
@@ -93,6 +102,7 @@
/datum/antagonist/traitor/proc/remove_objective(datum/objective/O)
objectives -= O
/// Generates a complete set of traitor objectives up to the traitor objective limit, including non-generic objectives such as martyr and hijack.
/datum/antagonist/traitor/proc/forge_traitor_objectives()
traitor_kind.forge_objectives(src)
@@ -151,21 +161,42 @@
H.dna.add_mutation(CLOWNMUT)
UnregisterSignal(M, COMSIG_MOVABLE_HEAR)
/datum/antagonist/traitor/ui_static_data(mob/user)
var/list/data = list()
data["phrases"] = jointext(GLOB.syndicate_code_phrase, ", ")
data["responses"] = jointext(GLOB.syndicate_code_response, ", ")
data["theme"] = traitor_kind.tgui_theme //traitor_flavor["ui_theme"]
data["code"] = uplink.unlock_code
data["intro"] = "You are from [traitor_kind.employer]." //traitor_flavor["introduction"]
data["allies"] = "Most other syndicate operatives are not to be trusted (but try not to rat them out), as they might have been assigned opposing objectives." //traitor_flavor["allies"]
data["goal"] = "We do not approve of mindless killing of innocent workers; \"get in, get done, get out\" is our motto." //traitor_flavor["goal"]
data["has_uplink"] = uplink ? TRUE : FALSE
if(uplink)
data["uplink_intro"] = "You have been provided with a standard uplink to accomplish your task." //traitor_flavor["uplink"]
data["uplink_unlock_info"] = uplink.unlock_text
data["objectives"] = get_objectives()
return data
/// Outputs this shift's codewords and responses to the antag's chat and copies them to their memory.
/datum/antagonist/traitor/proc/give_codewords()
if(!owner.current)
return
var/mob/traitor_mob=owner.current
var/mob/traitor_mob = owner.current
var/phrases = jointext(GLOB.syndicate_code_phrase, ", ")
var/responses = jointext(GLOB.syndicate_code_response, ", ")
var/dat = "<U><B>The Syndicate have provided you with the following codewords to identify fellow agents:</B></U>\n"
dat += "<B>Code Phrase</B>: <span class='blue'>[phrases]</span>\n"
dat += "<B>Code Response</B>: <span class='red'>[responses]</span>"
to_chat(traitor_mob, dat)
to_chat(traitor_mob, "<U><B>The Syndicate have provided you with the following codewords to identify fellow agents:</B></U>")
to_chat(traitor_mob, "<B>Code Phrase</B>: [span_blue("[phrases]")]")
to_chat(traitor_mob, "<B>Code Response</B>: [span_red("[responses]")]")
antag_memory += "<b>Code Phrase</b>: <span class='blue'>[phrases]</span><br>"
antag_memory += "<b>Code Response</b>: <span class='red'>[responses]</span><br>"
antag_memory += "<b>Code Phrase</b>: [span_blue("[phrases]")]<br>"
antag_memory += "<b>Code Response</b>: [span_red("[responses]")]<br>"
to_chat(traitor_mob, "Use the codewords during regular conversation to identify other agents. Proceed with caution, however, as everyone is a potential foe.")
to_chat(traitor_mob, span_alertwarning("You memorize the codewords, allowing you to recognise them when heard."))
/datum/antagonist/traitor/proc/add_law_zero()
var/mob/living/silicon/ai/killer = owner.current
@@ -220,44 +251,43 @@
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>")
//TODO Collate
/datum/antagonist/traitor/roundend_report()
var/list/result = list()
var/traitorwin = TRUE
var/traitor_won = TRUE
result += printplayer(owner)
var/TC_uses = 0
var/uplink_true = FALSE
var/used_telecrystals = 0
var/uplink_owned = FALSE
var/purchases = ""
LAZYINITLIST(GLOB.uplink_purchase_logs_by_key)
var/datum/uplink_purchase_log/H = GLOB.uplink_purchase_logs_by_key[owner.key]
if(H)
TC_uses = H.total_spent
uplink_true = TRUE
purchases += H.generate_render(FALSE)
// Uplinks add an entry to uplink_purchase_logs_by_key on init.
var/datum/uplink_purchase_log/purchase_log = GLOB.uplink_purchase_logs_by_key[owner.key]
if(purchase_log)
used_telecrystals = purchase_log.total_spent
uplink_owned = TRUE
purchases += purchase_log.generate_render(FALSE)
var/objectives_text = ""
if(objectives.len)//If the traitor had no objectives, don't need to process this.
if(objectives.len) //If the traitor had no objectives, don't need to process this.
var/count = 1
for(var/datum/objective/objective in objectives)
if(objective.completable)
var/completion = objective.check_completion()
if(completion >= 1)
objectives_text += "<br><B>Objective #[count]</B>: [objective.explanation_text] <span class='greentext'><B>Success!</B></span>"
else if(completion <= 0)
objectives_text += "<br><B>Objective #[count]</B>: [objective.explanation_text] <span class='redtext'>Fail.</span>"
traitorwin = FALSE
else
objectives_text += "<br><B>Objective #[count]</B>: [objective.explanation_text] <span class='yellowtext'>[completion*100]%</span>"
var/completion = objective.check_completion()
if(completion >= 1)
objectives_text += "<br><B>Objective #[count]</B>: [objective.explanation_text] [span_greentext("Success!")]"
else if(completion <= 0)
objectives_text += "<br><B>Objective #[count]</B>: [objective.explanation_text] [span_redtext("Fail.")]"
traitor_won = FALSE
else
objectives_text += "<br><B>Objective #[count]</B>: [objective.explanation_text]"
objectives_text += "<br><B>Objective #[count]</B>: [objective.explanation_text] <span class='yellowtext'>[completion*100]%</span>"
count++
if(uplink_true)
var/uplink_text = "(used [TC_uses] TC) [purchases]"
if(TC_uses==0 && traitorwin)
if(uplink_owned)
var/uplink_text = "(used [used_telecrystals] TC) [purchases]"
if((used_telecrystals == 0) && traitor_won)
var/static/icon/badass = icon('icons/badass.dmi', "badass")
uplink_text += "<BIG>[icon2html(badass, world)]</BIG>"
result += uplink_text
@@ -266,55 +296,59 @@
var/special_role_text = lowertext(name)
if(contractor_hub)
if (contractor_hub)
result += contractor_round_end()
if(traitorwin)
result += "<span class='greentext'>The [special_role_text] was successful!</span>"
if(traitor_won)
result += span_greentext("The [special_role_text] was successful!")
else
result += "<span class='redtext'>The [special_role_text] has failed!</span>"
result += span_redtext("The [special_role_text] has failed!")
SEND_SOUND(owner.current, 'sound/ambience/ambifailure.ogg')
return result.Join("<br>")
/// Proc detailing contract kit buys/completed contracts/additional info
/datum/antagonist/traitor/proc/contractor_round_end()
var result = ""
var total_spent_rep = 0
var/result = ""
var/total_spent_rep = 0
var/completed_contracts = 0
var/completed_contracts = contractor_hub.contracts_completed
var/tc_total = contractor_hub.contract_TC_payed_out + contractor_hub.contract_TC_to_redeem
for(var/datum/syndicate_contract/contract in contractor_hub.assigned_contracts)
if(contract.status == CONTRACT_STATUS_COMPLETE)
completed_contracts++
var/contractor_item_icons = "" // Icons of purchases
var/contractor_support_unit = "" // Set if they had a support unit - and shows appended to their contracts completed
for(var/datum/contractor_item/contractor_purchase in contractor_hub.purchased_items) // Get all the icons/total cost for all our items bought
/// Get all the icons/total cost for all our items bought
for (var/datum/contractor_item/contractor_purchase in contractor_hub.purchased_items)
contractor_item_icons += "<span class='tooltip_container'>\[ <i class=\"fas [contractor_purchase.item_icon]\"></i><span class='tooltip_hover'><b>[contractor_purchase.name] - [contractor_purchase.cost] Rep</b><br><br>[contractor_purchase.desc]</span> \]</span>"
total_spent_rep += contractor_purchase.cost
if(istype(contractor_purchase, /datum/contractor_item/contractor_partner)) // Special case for reinforcements, we want to show their ckey and name on round end.
/// Special case for reinforcements, we want to show their ckey and name on round end.
if (istype(contractor_purchase, /datum/contractor_item/contractor_partner))
var/datum/contractor_item/contractor_partner/partner = contractor_purchase
contractor_support_unit += "<br><b>[partner.partner_mind.key]</b> played <b>[partner.partner_mind.current.name]</b>, their contractor support unit."
if (contractor_hub.purchased_items.len)
result += "<br>(used [total_spent_rep] Rep)"
result += "<br>(used [total_spent_rep] Rep) "
result += contractor_item_icons
result += "<br>"
if(completed_contracts > 0)
if (completed_contracts > 0)
var/pluralCheck = "contract"
if(completed_contracts > 1)
if (completed_contracts > 1)
pluralCheck = "contracts"
result += "Completed <span class='greentext'>[completed_contracts]</span> [pluralCheck] for a total of \
<span class='greentext'>[tc_total] TC</span>!<br>"
result += "Completed [span_greentext("[completed_contracts]")] [pluralCheck] for a total of \
[span_greentext("[tc_total] TC")]![contractor_support_unit]<br>"
return result
/datum/antagonist/traitor/roundend_report_footer()
var/phrases = jointext(GLOB.syndicate_code_phrase, ", ")
var/responses = jointext(GLOB.syndicate_code_response, ", ")
var message = "<br><b>The code phrases were:</b> <span class='bluetext'>[phrases]</span><br>\
<b>The code responses were:</b> <span class='redtext'>[responses]</span><br>"
var/message = "<br><b>The code phrases were:</b> <span class='bluetext'>[phrases]</span><br>\
<b>The code responses were:</b> [span_redtext("[responses]")]<br>"
return message
+34 -42
View File
@@ -3,12 +3,15 @@
roundend_category = "wizards/witches"
antagpanel_category = "Wizard"
job_rank = ROLE_WIZARD
antag_hud_type = ANTAG_HUD_WIZ
antag_hud_name = "wizard"
antag_moodlet = /datum/mood_event/focused
threat = 30
hijack_speed = 0.5
ui_name = "AntagInfoWizard"
suicide_cry = "FOR THE FEDERATION!!"
var/give_objectives = TRUE
var/strip = TRUE //strip before equipping
var/allow_rename = TRUE
var/hud_version = "wizard"
var/datum/team/wizard/wiz_team //Only created if wizard summons apprentices
var/move_to_lair = TRUE
var/outfit_type = /datum/outfit/wizard
@@ -50,16 +53,17 @@
wiz_team = new(owner)
wiz_team.name = "[owner.current.real_name] team"
wiz_team.master_wizard = src
update_wiz_icons_added(owner.current)
add_antag_hud(antag_hud_type, antag_hud_name, owner.current)
/datum/antagonist/wizard/proc/send_to_lair()
if(!owner || !owner.current)
if(!owner)
CRASH("Antag datum with no owner.")
if(!owner.current)
return
if(!GLOB.wizardstart.len)
SSjob.SendToLateJoin(owner.current)
to_chat(owner, "HOT INSERTION, GO GO GO")
else
owner.current.forceMove(pick(GLOB.wizardstart))
owner.current.forceMove(pick(GLOB.wizardstart))
/datum/antagonist/wizard/proc/create_objectives()
var/datum/objective/flavor/wizard/new_objective = new
@@ -79,7 +83,7 @@
/datum/antagonist/wizard/proc/equip_wizard()
if(!owner)
return
CRASH("Antag datum with no owner.")
var/mob/living/carbon/human/H = owner.current
if(!istype(H))
return
@@ -91,18 +95,14 @@
H.age = wiz_age
H.equipOutfit(outfit_type)
/datum/antagonist/wizard/greet()
to_chat(owner, "<span class='boldannounce'>You are the Space Wizard!</span>")
to_chat(owner, "<B>The Space Wizards Federation has given you the following tasks:</B>")
owner.announce_objectives()
to_chat(owner, "<B>These are merely guidelines! The federation are your masters, but you forge your own path!</B>")
to_chat(owner, "You will find a list of available spells in your spell book. Choose your magic arsenal carefully.")
to_chat(owner, "The spellbook is bound to you, and others cannot use it.")
to_chat(owner, "In your pockets you will find a teleport scroll. Use it as needed.")
to_chat(owner,"<B>Remember:</B> do not forget to prepare your spells.")
/datum/antagonist/wizard/ui_static_data(mob/user)
. = ..()
var/list/data = list()
data["objectives"] = get_objectives()
return data
/datum/antagonist/wizard/farewell()
to_chat(owner, "<span class='userdanger'>You have been brainwashed! You are no longer a wizard!</span>")
to_chat(owner, span_userdanger("You have been brainwashed! You are no longer a wizard!"))
/datum/antagonist/wizard/proc/rename_wizard()
set waitfor = FALSE
@@ -111,7 +111,7 @@
var/wizard_name_second = pick(GLOB.wizard_second)
var/randomname = "[wizard_name_first] [wizard_name_second]"
var/mob/living/wiz_mob = owner.current
var/newname = reject_bad_name(stripped_input(wiz_mob, "You are the [name]. Would you like to change your name to something else?", "Name change", randomname, MAX_NAME_LEN))
var/newname = sanitize_name(reject_bad_text(stripped_input(wiz_mob, "You are the [name]. Would you like to change your name to something else?", "Name change", randomname, MAX_NAME_LEN)))
if (!newname)
newname = randomname
@@ -120,12 +120,12 @@
/datum/antagonist/wizard/apply_innate_effects(mob/living/mob_override)
var/mob/living/M = mob_override || owner.current
update_wiz_icons_added(M, wiz_team ? TRUE : FALSE) //Don't bother showing the icon if you're solo wizard
add_antag_hud(antag_hud_type, antag_hud_name, M)
M.faction |= ROLE_WIZARD
/datum/antagonist/wizard/remove_innate_effects(mob/living/mob_override)
var/mob/living/M = mob_override || owner.current
update_wiz_icons_removed(M)
remove_antag_hud(antag_hud_type, M)
M.faction -= ROLE_WIZARD
@@ -138,7 +138,7 @@
/datum/antagonist/wizard/apprentice
name = "Wizard Apprentice"
hud_version = "apprentice"
antag_hud_name = "apprentice"
var/datum/mind/master
var/school = APPRENTICE_DESTRUCTION
outfit_type = /datum/outfit/wizard/apprentice
@@ -157,7 +157,7 @@
/datum/antagonist/wizard/apprentice/equip_wizard()
. = ..()
if(!owner)
return
CRASH("Antag datum with no owner.")
var/mob/living/carbon/human/H = owner.current
if(!istype(H))
return
@@ -169,12 +169,12 @@
if(APPRENTICE_BLUESPACE)
owner.AddSpell(new /obj/effect/proc_holder/spell/targeted/turf_teleport/blink(null))
owner.AddSpell(new /obj/effect/proc_holder/spell/targeted/ethereal_jaunt(null))
to_chat(owner, "<B>Your service has not gone unrewarded, however. Studying under [master.current.real_name], you have learned reality bending mobility spells. You are able to cast teleport and ethereal jaunt.")
to_chat(owner, "<B>Your service has not gone unrewarded, however. Studying under [master.current.real_name], you have learned reality-bending mobility spells. You are able to cast teleport and ethereal jaunt.")
if(APPRENTICE_HEALING)
owner.AddSpell(new /obj/effect/proc_holder/spell/targeted/charge(null))
owner.AddSpell(new /obj/effect/proc_holder/spell/targeted/forcewall(null))
H.put_in_hands(new /obj/item/gun/magic/staff/healing(H))
to_chat(owner, "<B>Your service has not gone unrewarded, however. Studying under [master.current.real_name], you have learned livesaving survival spells. You are able to cast charge and forcewall.")
to_chat(owner, "<B>Your service has not gone unrewarded, however. Studying under [master.current.real_name], you have learned life-saving survival spells. You are able to cast charge and forcewall.")
if(APPRENTICE_ROBELESS)
owner.AddSpell(new /obj/effect/proc_holder/spell/aoe_turf/knock(null))
owner.AddSpell(new /obj/effect/proc_holder/spell/pointed/mind_transfer(null))
@@ -194,6 +194,7 @@
//Random event wizard
/datum/antagonist/wizard/apprentice/imposter
name = "Wizard Imposter"
show_in_antagpanel = FALSE
allow_rename = FALSE
move_to_lair = FALSE
@@ -224,20 +225,11 @@
owner.AddSpell(new /obj/effect/proc_holder/spell/targeted/turf_teleport/blink(null))
owner.AddSpell(new /obj/effect/proc_holder/spell/targeted/ethereal_jaunt(null))
/datum/antagonist/wizard/proc/update_wiz_icons_added(mob/living/wiz,join = TRUE)
var/datum/atom_hud/antag/wizhud = GLOB.huds[ANTAG_HUD_WIZ]
wizhud.join_hud(wiz)
set_antag_hud(wiz, hud_version)
/datum/antagonist/wizard/proc/update_wiz_icons_removed(mob/living/wiz)
var/datum/atom_hud/antag/wizhud = GLOB.huds[ANTAG_HUD_WIZ]
wizhud.leave_hud(wiz)
set_antag_hud(wiz, null)
/datum/antagonist/wizard/academy
name = "Academy Teacher"
show_in_antagpanel = FALSE
outfit_type = /datum/outfit/wizard/academy
move_to_lair = FALSE
/datum/antagonist/wizard/academy/equip_wizard()
. = ..()
@@ -250,7 +242,7 @@
if(!istype(M))
return
var/obj/item/implant/exile/Implant = new
var/obj/item/implant/exile/Implant = new/obj/item/implant/exile(M)
Implant.implant(M)
/datum/antagonist/wizard/academy/create_objectives()
@@ -265,25 +257,25 @@
parts += printplayer(owner)
var/count = 1
var/wizardwin = 1
var/wizardwin = TRUE
for(var/datum/objective/objective in objectives)
if(objective.completable)
var/completion = objective.check_completion()
if(completion >= 1)
parts += "<B>Objective #[count]</B>: [objective.explanation_text] <span class='greentext'><B>Success!</B></span>"
parts += "<br><B>Objective #[count]</B>: [objective.explanation_text] [span_greentext("Success!")]"
else if(completion <= 0)
parts += "<B>Objective #[count]</B>: [objective.explanation_text] <span class='redtext'>Fail.</span>"
parts += "<br><B>Objective #[count]</B>: [objective.explanation_text] [span_redtext("Fail.")]"
wizardwin = FALSE
else
parts += "<B>Objective #[count]</B>: [objective.explanation_text] <span class='yellowtext'>[completion*100]%</span>"
parts += "<br><B>Objective #[count]</B>: [objective.explanation_text] <span class='yellowtext'>[completion*100]%</span>"
else
parts += "<B>Objective #[count]</B>: [objective.explanation_text]"
count++
if(wizardwin)
parts += "<span class='greentext'>The wizard was successful!</span>"
parts += span_greentext("The wizard was successful!")
else
parts += "<span class='redtext'>The wizard has failed!</span>"
parts += span_redtext("The wizard has failed!")
if(owner.spell_list.len>0)
parts += "<B>[owner.name] used the following spells: </B>"