Skip to content
This repository was archived by the owner on May 22, 2025. It is now read-only.

[PORT] Ports traitor backstories from BEE #21836

Merged
merged 4 commits into from
Apr 20, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions code/__DEFINES/chat.dm
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,9 @@
#define MESSAGE_TYPE_MENTORPM "mentorpm"
#define MESSAGE_TYPE_DONATOR "donator"

/// Adds a generic box around whatever message you're sending in chat. Really makes things stand out.
#define EXAMINE_BLOCK(str) ("<div class='examine_block'>" + str + "</div>")

/// Max length of chat message in characters
#define CHAT_MESSAGE_MAX_LENGTH 110

Expand Down
34 changes: 34 additions & 0 deletions code/__DEFINES/html_assistant.dm
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
#define TOOLTIP_CSS_SETUP \
"<style>\
.tooltip {\
position: relative;\
display: inline-block;\
border-bottom: 2px dotted;\
}\
\
.tooltip .tooltiptext {\
visibility: hidden;\
background-color: black;\
color: #fff;\
text-align: center;\
border-radius: 3px;\
border: 1px solid grey;\
padding: 10px 17px;\
\
position: absolute;\
z-index: 1;\
}\
\
.tooltip:hover .tooltiptext {\
visibility: visible;\
}\
</style>"
// IE11 does not support the max-content attribute, so 'width: max-content;' doesn't work.

#define TOOLTIP_WRAPPER(hover_me, width_px, tooltip_text) \
"<div class='tooltip'>[hover_me]<span class='tooltiptext' style='width: [width_px]px'>[tooltip_text]</span></div>"

#define TOOLTIP_CONFIG_CALLER(hover_me, width_px, config_key) \
"[(GLOB.tooltips[config_key] ? "<div class='tooltip'>[hover_me]<span class='tooltiptext' style='width: [width_px]px'>[GLOB.tooltips[config_key]]</span></div>" : "[hover_me]")]"

#define OPEN_WIKI(wiki_url, text) (CONFIG_GET(string/wikiurl) ? "<a href='[CONFIG_GET(string/wikiurl)+"/"+wiki_url]' target='_blank'>[text]</a>" : "[text]")
22 changes: 22 additions & 0 deletions code/__DEFINES/traitor.dm
Original file line number Diff line number Diff line change
@@ -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"
35 changes: 35 additions & 0 deletions code/_globalvars/lists/traitor.dm
Original file line number Diff line number Diff line change
@@ -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,
))

2 changes: 2 additions & 0 deletions code/datums/actions/action.dm
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand Down
3 changes: 0 additions & 3 deletions code/game/gamemodes/objective.dm
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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(..())
Expand Down Expand Up @@ -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)
Expand Down
2 changes: 1 addition & 1 deletion code/modules/admin/antag_panel.dm
Original file line number Diff line number Diff line change
Expand Up @@ -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 = "<B>[name]</B>[(current && (current.real_name!=name))?" (as [current.real_name])":""]<br>"
var/out = "[TOOLTIP_CSS_SETUP]<B>[name]</B>[(current && (current.real_name!=name))?" (as [current.real_name])":""]<br>"
// yogs start - Donor features, quiet round
if(quiet_round)
out += "<font color=red><b>QUIET ROUND ACTIVE</b></font> (<a href='?src=\ref[src];quiet_override=1'>Override</a>)<br>"
Expand Down
10 changes: 10 additions & 0 deletions code/modules/antagonists/_common/antag_datum.dm
Original file line number Diff line number Diff line change
Expand Up @@ -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

42 changes: 38 additions & 4 deletions code/modules/antagonists/traitor/datum_traitor.dm
Original file line number Diff line number Diff line change
Expand Up @@ -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))
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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 += "<span class='alertsyndie'>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!</span>"
to_chat(owner.current, EXAMINE_BLOCK(msg.Join("\n")))
owner.announce_objectives()
if(should_give_codewords)
give_codewords()
Expand All @@ -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, ", ")
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -390,6 +401,15 @@

result += objectives_text

var/backstory_text = "<br>"
if(istype(faction))
backstory_text += "<b>Faction:</b> <span class='tooltip_container' style=\"font-size: 12px\">\[ [faction.name]<span class='tooltip_hover' style=\"width: 320px; padding: 5px;\">[faction.description]</span> \]</span><br>"
if(istype(backstory))
backstory_text += "<b>Backstory:</b> <span class='tooltip_container' style=\"font-size: 12px\">\[ [backstory.name]<span class='tooltip_hover' style=\"width: 320px; padding: 5px;\">[backstory.description]</span> \]</span><br>"
else
backstory_text += "<span class='redtext'>No backstory was selected!</span><br>"
result += backstory_text

var/special_role_text = lowertext(name)

if (contractor_hub)
Expand Down Expand Up @@ -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 = "<b>Traitor Backstory:</b><br>"
if(istype(faction))
backstory_text += "<b>Faction:</b> <span class='tooltip' style=\"font-size: 12px\">\[ [faction.name]<span class='tooltiptext' style=\"width: 320px; padding: 5px;\">[faction.description]</span> \]</span><br>"
else
backstory_text += "<font color='red'>No faction selected!</font><br>"
if(istype(backstory))
backstory_text += "<b>Backstory:</b> <span class='tooltip' style=\"font-size: 12px\">\[ [backstory.name]<span class='tooltiptext' style=\"width: 320px; padding: 5px;\">[backstory.description]</span> \]</span><br>"
else
backstory_text += "<font color='red'>No backstory selected!</font><br>"
return backstory_text
132 changes: 132 additions & 0 deletions tgui/packages/tgui/interfaces/AntagInfoTraitor.tsx
Original file line number Diff line number Diff line change
@@ -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<Info>(context);
const { has_uplink, uplink_unlock_info, code, failsafe_code } = data;
return (
<Section title="Uplink" mb={!has_uplink && -1}>
<Stack vertical>
<Stack.Item>
<BlockQuote>
Keep this uplink safe, and don&apos;t feel like you need to buy everything immediately — you can save your
telecrystals to use whenever you&apos;re in a tough situation and need help.
</BlockQuote>
</Stack.Item>
<Stack.Divider />
<Stack.Item>
<Stack fill>
<Stack.Item bold>{code && <span style={goalstyle}>Code: {code}</span>}</Stack.Item>
<Stack.Divider />
{failsafe_code && (
<>
<Stack.Item bold>{code && <span style={goalstyle}>Code: {code}</span>}</Stack.Item>
<Stack.Divider />
</>
)}
<Stack.Item>
<BlockQuote>{uplink_unlock_info}</BlockQuote>
</Stack.Item>
</Stack>
</Stack.Item>
</Stack>
</Section>
);
};

const CodewordsSection = (_props, context) => {
const { data } = useBackend<Info>(context);
const { has_codewords, phrases, responses } = data;
return (
<Section title="Codewords" mb={!has_codewords && -1}>
<Stack fill>
{(!has_codewords && (
<BlockQuote>
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.
</BlockQuote>
)) || (
<>
<Stack.Item grow basis={0}>
<BlockQuote>
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.
<span style={badstyle}>&ensp;You have memorized the codewords, allowing you to recognise them when heard.</span>
</BlockQuote>
</Stack.Item>
<Stack.Divider mr={1} />
<Stack.Item grow basis={0}>
<Stack vertical>
<Stack.Item>Code Phrases:</Stack.Item>
<Stack.Item bold textColor="blue">
{phrases}
</Stack.Item>
<Stack.Item>Code Responses:</Stack.Item>
<Stack.Item bold textColor="red">
{responses}
</Stack.Item>
</Stack>
</Stack.Item>
</>
)}
</Stack>
</Section>
);
};

export const AntagInfoTraitorContent = (_props, context) => {
const { data } = useBackend<Info>(context);
const { antag_name, objectives } = data;
return (
<Stack vertical fill>
<Stack.Item>
<AntagInfoHeader name={antag_name || 'Traitor'} asset="traitor.png" />
</Stack.Item>
<Stack.Item grow>
<ObjectivesSection objectives={objectives} />
</Stack.Item>
<Stack.Item>
<UplinkSection />
</Stack.Item>
<Stack.Item>
<CodewordsSection />
</Stack.Item>
</Stack>
);
};

export const AntagInfoTraitor = (_props, context) => {
return (
<Window width={620} height={620} theme="syndicate">
<Window.Content>
<AntagInfoTraitorContent />
</Window.Content>
</Window>
);
};
Loading