" : "[hover_me]")]"
+
+#define OPEN_WIKI(wiki_url, text) (CONFIG_GET(string/wikiurl) ? "[text]" : "[text]")
diff --git a/code/__DEFINES/traitor.dm b/code/__DEFINES/traitor.dm
new file mode 100644
index 000000000000..00388b3afbb9
--- /dev/null
+++ b/code/__DEFINES/traitor.dm
@@ -0,0 +1,22 @@
+#define TRAITOR_FACTION_BLACK_MARKET "black_market"
+#define TRAITOR_FACTION_SYNDICATE "syndicate"
+#define TRAITOR_FACTION_INDEPENDENT "independent"
+
+/// If this backstory involves being forced into the job
+#define TRAITOR_MOTIVATION_FORCED "Forced Into It"
+/// If this backstory does not involve being forced into the job
+#define TRAITOR_MOTIVATION_NOT_FORCED "Not Forced Into It"
+/// If this backstory is motivated by money or personal gain
+#define TRAITOR_MOTIVATION_MONEY "Money"
+/// If this backstory is politically motivated, wanting to "change the world".
+#define TRAITOR_MOTIVATION_POLITICAL "Political"
+/// If this backstory is motivated through the power of love (your family, friends, etc)
+#define TRAITOR_MOTIVATION_LOVE "Love"
+/// If this backstory is motivated by your reputation, or by knowledge (blackmail)
+#define TRAITOR_MOTIVATION_REPUTATION "Reputation"
+/// If this backstory is motivated by the threat of death or personal harm
+#define TRAITOR_MOTIVATION_DEATH_THREAT "Death Threat"
+/// If this backstory is motivated by their faction or presence in an organization
+#define TRAITOR_MOTIVATION_AUTHORITY "Authority"
+/// If this backstory is motivated by themselves or the activity
+#define TRAITOR_MOTIVATION_FUN "Fun"
diff --git a/code/_globalvars/lists/traitor.dm b/code/_globalvars/lists/traitor.dm
new file mode 100644
index 000000000000..f9cd921f13b3
--- /dev/null
+++ b/code/_globalvars/lists/traitor.dm
@@ -0,0 +1,35 @@
+/// Associative list of /datum/traitor_backstory path strings to datums
+GLOBAL_LIST_INIT(traitor_backstories, generate_traitor_backstories())
+/// Associative list of /datum/traitor_faction keys to datums
+GLOBAL_LIST_INIT(traitor_factions_to_datum, generate_traitor_factions())
+GLOBAL_LIST_INIT(traitor_factions, assoc_to_keys(GLOB.traitor_factions_to_datum))
+
+/proc/generate_traitor_backstories()
+ var/list/result = list()
+ for(var/datum/traitor_backstory/path as anything in subtypesof(/datum/traitor_backstory))
+ if(isnull(initial(path.name)))
+ continue
+ result["[path]"] = new path()
+ return result
+
+/proc/generate_traitor_factions()
+ var/list/result = list()
+ for(var/datum/traitor_faction/path as anything in subtypesof(/datum/traitor_faction))
+ var/key = initial(path.key)
+ if(!istext(key))
+ continue
+ result[key] = new path()
+ return result
+
+GLOBAL_LIST_INIT(traitor_motivations, list(
+ TRAITOR_MOTIVATION_FORCED,
+ TRAITOR_MOTIVATION_NOT_FORCED,
+ TRAITOR_MOTIVATION_MONEY,
+ TRAITOR_MOTIVATION_POLITICAL,
+ TRAITOR_MOTIVATION_LOVE,
+ TRAITOR_MOTIVATION_REPUTATION,
+ TRAITOR_MOTIVATION_DEATH_THREAT,
+ TRAITOR_MOTIVATION_AUTHORITY,
+ TRAITOR_MOTIVATION_FUN,
+))
+
diff --git a/code/datums/actions/action.dm b/code/datums/actions/action.dm
index e7edb81bf763..ae65faa00c93 100644
--- a/code/datums/actions/action.dm
+++ b/code/datums/actions/action.dm
@@ -46,6 +46,8 @@
/// This is the icon state for any FOREGROUND overlay icons on the button (such as borders)
var/overlay_icon_state
+ var/atom/movable/screen/movable/action_button/button = null
+
/datum/action/New(Target)
link_to(Target)
diff --git a/code/game/gamemodes/objective.dm b/code/game/gamemodes/objective.dm
index 549bc89f94a5..a29d31346f1d 100644
--- a/code/game/gamemodes/objective.dm
+++ b/code/game/gamemodes/objective.dm
@@ -209,7 +209,6 @@ GLOBAL_LIST_EMPTY(objectives)
/datum/objective/assassinate
name = "assassinate"
var/target_role_type=FALSE
- martyr_compatible = 1
/datum/objective/assassinate/find_target_by_role(role, role_type=FALSE,invert=FALSE)
if(!invert)
@@ -472,7 +471,6 @@ GLOBAL_LIST_EMPTY(objectives)
/datum/objective/purge
name = "no mutants on shuttle"
explanation_text = "Ensure no mutant humanoids or nonhuman species are present aboard the escape shuttle. Felinids/Catpeople do NOT count as nonhuman."
- martyr_compatible = 1
/datum/objective/purge/check_completion()
if(..())
@@ -986,7 +984,6 @@ GLOBAL_LIST_EMPTY(possible_items_special)
/datum/objective/destroy
name = "destroy AI"
- martyr_compatible = 1
/datum/objective/destroy/find_target(dupe_search_range, blacklist)
var/list/possible_targets = active_ais(1)
diff --git a/code/modules/admin/antag_panel.dm b/code/modules/admin/antag_panel.dm
index 8d7aaeba7c33..5f494c9ab29b 100644
--- a/code/modules/admin/antag_panel.dm
+++ b/code/modules/admin/antag_panel.dm
@@ -113,7 +113,7 @@ GLOBAL_VAR(antag_prototypes)
tgui_alert(usr, "This mind doesn't have a mob, or is deleted! For some reason!", "Edit Memory")
return
- var/out = "[name][(current && (current.real_name!=name))?" (as [current.real_name])":""] "
+ var/out = "[TOOLTIP_CSS_SETUP][name][(current && (current.real_name!=name))?" (as [current.real_name])":""] "
// yogs start - Donor features, quiet round
if(quiet_round)
out += "QUIET ROUND ACTIVE (Override) "
diff --git a/code/modules/antagonists/_common/antag_datum.dm b/code/modules/antagonists/_common/antag_datum.dm
index 604ef5a2b179..4e2808bb342e 100644
--- a/code/modules/antagonists/_common/antag_datum.dm
+++ b/code/modules/antagonists/_common/antag_datum.dm
@@ -453,3 +453,13 @@ GLOBAL_LIST_EMPTY(antagonists)
if(!(target in owner.mind.antag_datums))
return FALSE
return TRUE
+
+//in the future, this should entirely replace greet.
+/datum/antagonist/proc/make_info_button()
+ if(!ui_name)
+ return
+ var/datum/action/antag_info/info_button = new(src)
+ info_button.Grant(owner.current)
+ info_button_ref = WEAKREF(info_button)
+ return info_button
+
diff --git a/code/modules/antagonists/traitor/datum_traitor.dm b/code/modules/antagonists/traitor/datum_traitor.dm
index 43cf5f048c65..3cf6ce940ebd 100644
--- a/code/modules/antagonists/traitor/datum_traitor.dm
+++ b/code/modules/antagonists/traitor/datum_traitor.dm
@@ -19,6 +19,9 @@
var/datum/contractor_hub/contractor_hub
var/obj/item/uplink_holder
can_hijack = HIJACK_HIJACKER
+ /// If this specific traitor has been assigned codewords. This is not always true, because it varies by faction.
+ var/has_codewords = FALSE
+ var/datum/weakref/uplink_ref
/datum/antagonist/traitor/on_gain()
if(owner.current && iscyborg(owner.current))
@@ -168,6 +171,8 @@
add_objective(escape_objective)
else
forge_single_human_objective()
+ // Finally, set up our traitor's backstory!
+ setup_backstories(!is_hijacker && martyr_compatibility, is_hijacker)
/datum/antagonist/traitor/proc/forge_ai_objectives()
var/objective_count = 0
@@ -249,7 +254,10 @@
.=2
/datum/antagonist/traitor/greet()
+ var/list/msg = list()
to_chat(owner.current, span_alertsyndie("You are the [owner.special_role]."))
+ msg += "Use the 'Traitor Info and Backstory' action at the top left in order to select a backstory and review your objectives, uplink location, and codewords!"
+ to_chat(owner.current, EXAMINE_BLOCK(msg.Join("\n")))
owner.announce_objectives()
if(should_give_codewords)
give_codewords()
@@ -272,13 +280,13 @@
ability.Grant(owner.current)
if(TRAITOR_HUMAN)
- if(should_equip)
- equip(silent)
+ ui_interact(owner.current)
owner.current.playsound_local(get_turf(owner.current), 'sound/ambience/antag/tatoralert.ogg', 100, FALSE, pressure_affected = FALSE)
/datum/antagonist/traitor/proc/give_codewords()
if(!owner.current)
return
+ has_codewords = TRUE
var/mob/traitor_mob=owner.current
var/phrases = jointext(GLOB.syndicate_code_phrase, ", ")
@@ -306,9 +314,12 @@
if(malf)
killer.add_malf_picker()
-/datum/antagonist/traitor/proc/equip(silent = FALSE)
+/datum/antagonist/traitor/proc/equip(var/silent = FALSE)
if(traitor_kind == TRAITOR_HUMAN)
- uplink_holder = owner.equip_traitor(employer, silent, src) //yogs - uplink_holder =
+ var/obj/item/uplink_loc = owner.equip_traitor(employer, silent, src)
+ var/datum/component/uplink/uplink = uplink_loc?.GetComponent(/datum/component/uplink)
+ if(uplink)
+ uplink_ref = WEAKREF(uplink) //yogs - uplink_holder =
/datum/antagonist/traitor/proc/assign_exchange_role()
//set faction
@@ -390,6 +401,15 @@
result += objectives_text
+ var/backstory_text = " "
+ if(istype(faction))
+ backstory_text += "Faction:\[ [faction.name][faction.description] \] "
+ if(istype(backstory))
+ backstory_text += "Backstory:\[ [backstory.name][backstory.description] \] "
+ else
+ backstory_text += "No backstory was selected! "
+ result += backstory_text
+
var/special_role_text = lowertext(name)
if (contractor_hub)
@@ -464,3 +484,17 @@
/datum/outfit/traitor/post_equip(mob/living/carbon/human/H, visualsOnly)
var/obj/item/melee/transforming/energy/sword/sword = locate() in H.held_items
sword.transform_weapon(H)
+
+
+/datum/antagonist/traitor/antag_panel_data()
+ // Traitor Backstory
+ var/backstory_text = "Traitor Backstory: "
+ if(istype(faction))
+ backstory_text += "Faction:\[ [faction.name][faction.description] \] "
+ else
+ backstory_text += "No faction selected! "
+ if(istype(backstory))
+ backstory_text += "Backstory:\[ [backstory.name][backstory.description] \] "
+ else
+ backstory_text += "No backstory selected! "
+ return backstory_text
diff --git a/tgui/packages/tgui/interfaces/AntagInfoTraitor.tsx b/tgui/packages/tgui/interfaces/AntagInfoTraitor.tsx
new file mode 100644
index 000000000000..5b26dc7cf94c
--- /dev/null
+++ b/tgui/packages/tgui/interfaces/AntagInfoTraitor.tsx
@@ -0,0 +1,132 @@
+import { useBackend } from '../backend';
+import { BlockQuote, Section, Stack } from '../components';
+import { BooleanLike } from 'common/react';
+import { Window } from '../layouts';
+import { ObjectivesSection, Objective } from './common/ObjectiveSelection';
+import { AntagInfoHeader } from './common/AntagInfoHeader';
+
+const badstyle = {
+ color: 'red',
+ fontWeight: 'bold',
+};
+
+const goalstyle = {
+ color: 'lightblue',
+ fontWeight: 'bold',
+};
+
+type Info = {
+ antag_name: string;
+ has_codewords: BooleanLike;
+ phrases: string;
+ responses: string;
+ code: string;
+ failsafe_code: string;
+ has_uplink: BooleanLike;
+ uplink_unlock_info: string;
+ objectives: Objective[];
+};
+
+const UplinkSection = (_props, context) => {
+ const { data } = useBackend(context);
+ const { has_uplink, uplink_unlock_info, code, failsafe_code } = data;
+ return (
+
+
+
+
+ Keep this uplink safe, and don't feel like you need to buy everything immediately — you can save your
+ telecrystals to use whenever you're in a tough situation and need help.
+
+ You have not been supplied with codewords. You will have to use alternative methods to find potential allies.
+ Proceed with caution, however, as everyone is a potential foe.
+
+ )) || (
+ <>
+
+
+ Your employer provided you with the following codewords to identify fellow agents. Use the codewords during
+ regular conversation to identify other agents. Proceed with caution, however, as everyone is a potential foe.
+ You have memorized the codewords, allowing you to recognise them when heard.
+
+
+
+
+ );
+};
diff --git a/tgui/packages/tgui/interfaces/common/ObjectiveSelection.tsx b/tgui/packages/tgui/interfaces/common/ObjectiveSelection.tsx
new file mode 100644
index 000000000000..36c40ccc5d0a
--- /dev/null
+++ b/tgui/packages/tgui/interfaces/common/ObjectiveSelection.tsx
@@ -0,0 +1,39 @@
+import { BooleanLike } from 'common/react';
+import { Section, Stack } from '../../components';
+import { sanitizeText } from '../../sanitize';
+
+export type Objective = {
+ count: number;
+ name: string;
+ explanation: string;
+ optional: BooleanLike;
+};
+
+type Props = {
+ objectives: Objective[];
+};
+
+export const ObjectivesSection = (props: Props, _context) => {
+ const { objectives } = props;
+ return (
+
+
+ Your current objectives:
+
+ {(!objectives && 'None!') ||
+ objectives.map((objective) => (
+
+ #{objective.count}:{' '}
+
+
+ ))}
+
+
+
+ );
+};
diff --git a/yogstation.dme b/yogstation.dme
index d1ddf06ba047..f4cc489c9efe 100644
--- a/yogstation.dme
+++ b/yogstation.dme
@@ -76,6 +76,7 @@
#include "code\__DEFINES\footsteps.dm"
#include "code\__DEFINES\forensics.dm"
#include "code\__DEFINES\gradient.dm"
+#include "code\__DEFINES\html_assistant.dm"
#include "code\__DEFINES\hud.dm"
#include "code\__DEFINES\icon_smoothing.dm"
#include "code\__DEFINES\instruments.dm"
@@ -152,6 +153,7 @@
#include "code\__DEFINES\tgui.dm"
#include "code\__DEFINES\time.dm"
#include "code\__DEFINES\tools.dm"
+#include "code\__DEFINES\traitor.dm"
#include "code\__DEFINES\traits.dm"
#include "code\__DEFINES\turfs.dm"
#include "code\__DEFINES\typeids.dm"
@@ -327,6 +329,7 @@
#include "code\_globalvars\lists\names.dm"
#include "code\_globalvars\lists\objects.dm"
#include "code\_globalvars\lists\poll_ignore.dm"
+#include "code\_globalvars\lists\traitor.dm"
#include "code\_globalvars\lists\typecache.dm"
#include "code\_globalvars\traits\_traits.dm"
#include "code\_globalvars\traits\admin_tooling.dm"
@@ -4243,6 +4246,10 @@
#include "yogstation\code\modules\antagonists\nukeop\equipment\nuclearbomb.dm"
#include "yogstation\code\modules\antagonists\slaughter\slaughter.dm"
#include "yogstation\code\modules\antagonists\traitor\datum_mindslave.dm"
+#include "yogstation\code\modules\antagonists\traitor\backstory\traitor_backstories.dm"
+#include "yogstation\code\modules\antagonists\traitor\backstory\traitor_backstory_ui.dm"
+#include "yogstation\code\modules\antagonists\traitor\backstory\traitor_datum_backstory.dm"
+#include "yogstation\code\modules\antagonists\traitor\backstory\traitor_factions.dm"
#include "yogstation\code\modules\assembly\signaler.dm"
#include "yogstation\code\modules\atmospherics\airalarm.dm"
#include "yogstation\code\modules\atmospherics\machinery\pipes\bluespace.dm"
diff --git a/yogstation/code/modules/antagonists/traitor/backstory/traitor_backstories.dm b/yogstation/code/modules/antagonists/traitor/backstory/traitor_backstories.dm
new file mode 100644
index 000000000000..229a42f92a2c
--- /dev/null
+++ b/yogstation/code/modules/antagonists/traitor/backstory/traitor_backstories.dm
@@ -0,0 +1,123 @@
+/datum/traitor_backstory
+ /// The name of this traitor backstory type, displayed as a title
+ var/name
+ /// A description of the events leading up to this traitor's existence
+ var/description
+ /// Factions you can have as this backstory
+ var/allowed_factions = list(
+ TRAITOR_FACTION_BLACK_MARKET,
+ TRAITOR_FACTION_SYNDICATE
+ )
+ /// A list of motivation types for this backstory, used for filtering and searching
+ var/list/motivations = list()
+ /// If this backstory suggested for murderboning or hijacking
+ var/murderbone = FALSE
+
+/datum/traitor_backstory/proc/has_motivation(motivation)
+ return motivation in motivations
+
+// ------------------
+// ACTUAL BACKSTORIES
+// ------------------
+
+/datum/traitor_backstory/debtor
+ name = "The Debtor"
+ description = "I owe a lot of money... Falling on hard times... \
+ I couldn't pay to live - and now I have to earn it all back if I want to continue living."
+ motivations = list(TRAITOR_MOTIVATION_FORCED, TRAITOR_MOTIVATION_MONEY, TRAITOR_MOTIVATION_DEATH_THREAT)
+
+/datum/traitor_backstory/stolen
+ name = "The Stolen"
+ description = "They have... everything. They stole my entire fortune, and now I'm destitute. \
+ The only way I'm earning it back is if I do what they say."
+ motivations = list(TRAITOR_MOTIVATION_FORCED, TRAITOR_MOTIVATION_MONEY)
+
+/datum/traitor_backstory/gambler
+ name = "The Gambler"
+ description = "They warned me, told me to not enter that card game, but they didn't stop me. They knew I'd lose tens of thousands, \
+ I can't help it. Now, there's only one way to crawl out of this hole I dug myself into. Such bad luck... \
+ but if I can only repay them by fulfilling these tasks, maybe just maybe I can make it big."
+ allowed_factions = list(TRAITOR_FACTION_BLACK_MARKET, TRAITOR_FACTION_SYNDICATE)
+ motivations = list(TRAITOR_MOTIVATION_FORCED, TRAITOR_MOTIVATION_MONEY)
+
+/datum/traitor_backstory/blackmailed
+ name = "The Blackmailed"
+ description = "They know all about what I did... and they're not afraid to turn me in if I don't do what they say."
+ motivations = list(TRAITOR_MOTIVATION_FORCED, TRAITOR_MOTIVATION_REPUTATION)
+
+/datum/traitor_backstory/hostage
+ name = "The Hostage"
+ description = "They have someone I love hostage. Oh god... What would I do without them? \
+ I need to do this, or I'll never see them again. I have to do this. There's no other way out."
+ motivations = list(TRAITOR_MOTIVATION_FORCED, TRAITOR_MOTIVATION_LOVE, TRAITOR_MOTIVATION_DEATH_THREAT)
+
+/datum/traitor_backstory/legally_enslaved
+ name = "The Legal Slave"
+ description = "Shit... I signed a contract I shouldn't have. Now they own me. I have to do their bidding, and if I don't... \
+ They'll come for me."
+ motivations = list(TRAITOR_MOTIVATION_FORCED, TRAITOR_MOTIVATION_DEATH_THREAT)
+
+/datum/traitor_backstory/savior
+ name = "The Savior"
+ description = "Nanotrasen are corrupt, evil to the core. The crew here are sheep. Cogs in a machine. \
+ I must liberate them by showing the error in their ways, and expose Nanotrasen for what they truly are."
+ allowed_factions = list(
+ TRAITOR_FACTION_SYNDICATE,
+ TRAITOR_FACTION_INDEPENDENT
+ )
+ motivations = list(TRAITOR_MOTIVATION_NOT_FORCED, TRAITOR_MOTIVATION_POLITICAL, TRAITOR_MOTIVATION_AUTHORITY)
+
+/datum/traitor_backstory/hater
+ name = "The Hater"
+ description = "Nanotrasen ruined my life. They ruined everything. They took the things that I love away from me. Now I'm going to make them pay."
+ allowed_factions = list(
+ TRAITOR_FACTION_BLACK_MARKET,
+ TRAITOR_FACTION_SYNDICATE,
+ TRAITOR_FACTION_INDEPENDENT
+ )
+ motivations = list(TRAITOR_MOTIVATION_NOT_FORCED, TRAITOR_MOTIVATION_POLITICAL, TRAITOR_MOTIVATION_LOVE)
+ murderbone = TRUE
+
+/datum/traitor_backstory/greedy
+ name = "The Greedy"
+ description = "If I do this, I'll be set for life. I'll have everything I ever wanted, and more. \
+ The payment is astronomical, and I'm fit for the job. Let's do this."
+ allowed_factions = list(
+ TRAITOR_FACTION_BLACK_MARKET,
+ TRAITOR_FACTION_SYNDICATE,
+ TRAITOR_FACTION_INDEPENDENT
+ )
+ motivations = list(TRAITOR_MOTIVATION_NOT_FORCED, TRAITOR_MOTIVATION_MONEY)
+
+/datum/traitor_backstory/climber
+ name = "The Climber"
+ description = "Life is a ladder, and there is only climbing it. I shall have no friends, I shall hold no loyalties, \
+ for the only end goal in life is for my ego to be supreme. \
+ In my many years of observing the dynamics in this universe, it is clear to me that this is the surest way to achieve the domination of myself. \
+ Today marks the beginning of my ascent, nothing matters but my rise. I am supreme."
+ allowed_factions = list(
+ TRAITOR_FACTION_BLACK_MARKET,
+ TRAITOR_FACTION_SYNDICATE,
+ TRAITOR_FACTION_INDEPENDENT
+ )
+ motivations = list(TRAITOR_MOTIVATION_NOT_FORCED, TRAITOR_MOTIVATION_MONEY, TRAITOR_MOTIVATION_REPUTATION, TRAITOR_MOTIVATION_FUN)
+
+/datum/traitor_backstory/machine
+ name = "The Machine"
+ description = "I was born in the Syndicate. I was made in the Syndicate. I am the Syndicate. \
+ I am nothing without the Syndicate, and I will do everything I am asked."
+ allowed_factions = list(TRAITOR_FACTION_SYNDICATE)
+ motivations = list(TRAITOR_MOTIVATION_NOT_FORCED, TRAITOR_MOTIVATION_AUTHORITY)
+ murderbone = TRUE
+
+/datum/traitor_backstory/sadist
+ name = "The Sadist"
+ description = "I want power, not over people, but over life, to inflict pain and suffering is my road to power. \
+ They want a killer? I shall play their little game if it helps me fulfill my morbid desires. \
+ No, I do not want money or influence, power over the souls that inhabit this station is my payment."
+ allowed_factions = list(
+ TRAITOR_FACTION_SYNDICATE,
+ TRAITOR_FACTION_INDEPENDENT
+ )
+ motivations = list(TRAITOR_MOTIVATION_NOT_FORCED, TRAITOR_MOTIVATION_FUN)
+ murderbone = TRUE
diff --git a/yogstation/code/modules/antagonists/traitor/backstory/traitor_backstory_ui.dm b/yogstation/code/modules/antagonists/traitor/backstory/traitor_backstory_ui.dm
new file mode 100644
index 000000000000..c591b959fb63
--- /dev/null
+++ b/yogstation/code/modules/antagonists/traitor/backstory/traitor_backstory_ui.dm
@@ -0,0 +1,102 @@
+/datum/antagonist/traitor
+ ui_name = "TraitorBackstoryMenu"
+
+/// We will handle this ourselves, thank you.
+/datum/antagonist/traitor/make_info_button()
+ return null
+
+/datum/antagonist/traitor/proc/add_menu_action()
+ if(info_button_ref?.resolve() != null)
+ return
+ var/datum/action/antag_info/traitor_menu/menu = new(src)
+ menu.Grant(owner.current)
+ info_button_ref = WEAKREF(menu)
+
+/datum/action/antag_info/traitor_menu
+ name = "Traitor Info and Backstory"
+ desc = "View and customize your traitor faction, backstory, objectives, codewords, uplink location, \
+ and objective backstories."
+ button_icon_state = "traitor_objectives"
+ background_icon_state = "bg_agent"
+
+/datum/action/antag_info/traitor_menu/New(datum/H)
+ . = ..()
+ name = "Traitor Info and Backstory"
+ button.name = name
+
+/datum/antagonist/traitor/ui_data(mob/user)
+
+ var/list/data = list()
+ data["allowed_factions"] = allowed_factions
+ data["allowed_backstories"] = allowed_backstories
+ data["recommended_factions"] = recommended_factions
+ data["recommended_backstories"] = recommended_backstories
+ if(istype(backstory))
+ data["backstory"] = "[backstory.type]"
+ if(istype(faction))
+ data["faction"] = faction.key
+ data["employer"] = employer
+
+ var/datum/component/uplink/uplink = uplink_ref?.resolve()
+ data["antag_name"] = name
+ data["has_codewords"] = has_codewords
+ if(has_codewords)
+ data["phrases"] = jointext(GLOB.syndicate_code_phrase, ", ")
+ data["responses"] = jointext(GLOB.syndicate_code_response, ", ")
+ data["code"] = uplink?.unlock_code
+ data["failsafe_code"] = uplink?.failsafe_code
+ data["has_uplink"] = uplink ? TRUE : FALSE
+ if(uplink)
+ data["uplink_unlock_info"] = uplink.unlock_text
+ data["objectives"] = get_objectives()
+
+ return data
+
+/datum/antagonist/traitor/ui_static_data(mob/user)
+ var/list/data = list()
+ var/list/all_factions = list()
+ for(var/key in GLOB.traitor_factions_to_datum)
+ var/datum/traitor_faction/faction = GLOB.traitor_factions_to_datum[key]
+ all_factions[key] = list(
+ "name" = faction.name,
+ "description" = faction.description,
+ "key" = key,
+ )
+ data["all_factions"] = all_factions
+ var/list/all_backstories = list()
+ for(var/path in GLOB.traitor_backstories)
+ var/datum/traitor_backstory/backstory = GLOB.traitor_backstories[path]
+ all_backstories[path] = list(
+ "name" = backstory.name,
+ "description" = backstory.description,
+ "path" = path,
+ "allowed_factions" = backstory.allowed_factions,
+ "motivations" = backstory.motivations,
+ )
+ data["all_backstories"] = all_backstories
+ data["all_motivations"] = GLOB.traitor_motivations
+ return data
+
+/datum/antagonist/traitor/ui_act(action, params)
+ . = ..()
+ if(.)
+ return
+
+ switch(action)
+ if("select_backstory")
+ if(istype(backstory)) // bad!!
+ return TRUE
+ var/datum/traitor_backstory/selected_backstory = GLOB.traitor_backstories[params["backstory"]]
+ var/datum/traitor_faction/selected_faction = GLOB.traitor_factions_to_datum[params["faction"]]
+ if(!istype(selected_faction) || !istype(selected_backstory))
+ return TRUE
+ if(istype(faction) && faction.key != selected_faction.key) // bad!
+ return TRUE
+ if(!(selected_faction.key in selected_backstory.allowed_factions))
+ return TRUE
+ if(!("[selected_backstory.type]" in allowed_backstories))
+ return TRUE
+ if(!istype(faction))
+ set_faction(selected_faction)
+ set_backstory(selected_backstory)
+ return TRUE
diff --git a/yogstation/code/modules/antagonists/traitor/backstory/traitor_datum_backstory.dm b/yogstation/code/modules/antagonists/traitor/backstory/traitor_datum_backstory.dm
new file mode 100644
index 000000000000..fca3e6610267
--- /dev/null
+++ b/yogstation/code/modules/antagonists/traitor/backstory/traitor_datum_backstory.dm
@@ -0,0 +1,53 @@
+/datum/antagonist/traitor
+ /// A list of factions the traitor can pick from freely.
+ var/list/allowed_factions = list(TRAITOR_FACTION_SYNDICATE, TRAITOR_FACTION_BLACK_MARKET, TRAITOR_FACTION_INDEPENDENT)
+ /// A list of factions the traitor can pick from freely.
+ var/list/recommended_factions = list()
+ /// A list of backstories that are allowed for this traitor.
+ var/list/allowed_backstories
+ /// A list of recommended backstories for this traitor, based on their murderbone status.
+ var/list/recommended_backstories
+ /// The actual backstory for this traitor. Can be null.
+ var/datum/traitor_backstory/backstory
+ /// The actual faction for this traitor. Can be null.
+ var/datum/traitor_faction/faction
+
+/datum/antagonist/traitor/proc/setup_backstories(murderbone, hijack)
+ if(murderbone || hijack)
+ recommended_factions = list(TRAITOR_FACTION_SYNDICATE, TRAITOR_FACTION_INDEPENDENT)
+ allowed_backstories = list()
+ recommended_backstories = list()
+ for(var/datum/traitor_backstory/path as anything in subtypesof(/datum/traitor_backstory))
+ var/datum/traitor_backstory/backstory = GLOB.traitor_backstories["[path]"]
+ if(!istype(backstory))
+ continue
+ if(!murderbone)
+ allowed_backstories += "[path]"
+ if(hijack && backstory.murderbone)
+ recommended_backstories += "[path]"
+ continue
+ if(backstory.has_motivation(TRAITOR_MOTIVATION_FORCED))
+ continue
+ allowed_backstories += "[path]"
+ if(backstory.murderbone)
+ recommended_backstories += "[path]"
+
+ // add_menu_action() -- dont need doubles lol -- cowbot93
+
+/datum/antagonist/traitor/proc/set_faction(datum/traitor_faction/new_faction)
+ if(!istype(new_faction))
+ return
+ var/no_faction = isnull(faction)
+ faction = new_faction
+ employer = new_faction.employer_name
+ if(no_faction)
+ if(new_faction.give_codewords)
+ give_codewords()
+ equip(silent)
+ log_game("[key_name(owner)] selected traitor faction [new_faction.name]")
+ SSblackbox.record_feedback("tally", "traitor_faction_selected", 1, new_faction.name)
+
+/datum/antagonist/traitor/proc/set_backstory(datum/traitor_backstory/new_backstory)
+ backstory = new_backstory
+ log_game("[key_name(owner)] selected traitor backstory [new_backstory.name]")
+ SSblackbox.record_feedback("tally", "traitor_backstory_selected", 1, new_backstory.name)
diff --git a/yogstation/code/modules/antagonists/traitor/backstory/traitor_factions.dm b/yogstation/code/modules/antagonists/traitor/backstory/traitor_factions.dm
new file mode 100644
index 000000000000..1a0e92a1c284
--- /dev/null
+++ b/yogstation/code/modules/antagonists/traitor/backstory/traitor_factions.dm
@@ -0,0 +1,41 @@
+/datum/traitor_faction
+ /// The name of this faction
+ var/name
+ /// The name of this faction when shown to the player as their employer.
+ var/employer_name
+ /// The define tied to this faction
+ var/key
+ /// A short description of this faction, OOC
+ var/description
+ /// If this faction has access to codewords
+ var/give_codewords = FALSE
+
+/datum/traitor_faction/syndicate
+ name = "The Syndicate"
+ employer_name = "The Syndicate"
+ description = "A classic - either you were forced into it through blackmail, threat, or debts - or you were born for it, built for it, or \
+ maybe you joined to get revenge.\n\
+ Either way, you will have potential allies in other syndicate agents, codewords, and communication methods. You'll have all the resources at your disposal.\n\
+ Get the job done right, and you will be rewarded - or simply freed of your debts.\n"
+ key = TRAITOR_FACTION_SYNDICATE
+ give_codewords = TRUE
+
+/datum/traitor_faction/black_market
+ name = "The Black Market"
+ employer_name = "Your black market liason"
+ description = "You're in it for the money, or because you were forced into it.\n\
+ The monetary potential aboard a Nanotrasen station is huge, and there are actors willing to take advantage of your position.\n\
+ Your employer expects nothing but good results - and you'd better give it to them, lest you face the consequences.\n\
+ You won't have the same benefits as working with the Syndicate - no codewords or communication methods, and limited potential allies. \
+ Just hope that your goals align with the other traitors.\n\
+ Get the job done right, and you will be paid in full - or simply freed of your debts.\n"
+ key = TRAITOR_FACTION_BLACK_MARKET
+
+/datum/traitor_faction/independent
+ name = "Independent"
+ employer_name = "You"
+ description = "Not for the faint of heart, being an independent traitor requires superior roleplay abilities, and superior traitor skills. \n\
+ You are a person who holds grudges, and has been hurt greatly by Nanotrasen.\n\
+ You will have no allies, no codewords, and you can only get by on your stolen Syndicate uplink. You have one chance, don't blow it. \n\
+ It's personal."
+ key = TRAITOR_FACTION_INDEPENDENT