From 5fa25bfc25f74f4ac78af5dff708e28c6bc09620 Mon Sep 17 00:00:00 2001
From: SkyratBot <59378654+SkyratBot@users.noreply.github.com>
Date: Sun, 28 Nov 2021 20:13:27 +0100
Subject: [PATCH] [MIRROR] Antagonist Flavor 2: Malfunction Comes in Many
Flavors! [MDB IGNORE] (#9753)
* Antagonist Flavor 2: Malfunction Comes in Many Flavors! (#62774)
Malfunctioning Ais now get their own special antagonist panel, and roll for backstories much like traitors! As before, these are simply suggestions to encourage roleplay while you server wipe as the funny box.
* Antagonist Flavor 2: Malfunction Comes in Many Flavors!
Co-authored-by: tralezab <40974010+tralezab@users.noreply.github.com>
---
code/__DEFINES/antagonists.dm | 22 +-
.../antagonists/malf_ai/datum_malf_ai.dm | 95 ++++++-
code/modules/mob/living/silicon/ai/ai.dm | 5 +-
.../antagonist_flavor/malfunction_flavor.json | 62 +++++
.../traitor_flavor.json | 0
.../tgui/interfaces/AntagInfoMalf.tsx | 259 ++++++++++++++++++
tgui/packages/tgui/interfaces/Uplink.js | 2 +-
.../tgui/styles/themes/hackerman.scss | 5 +
8 files changed, 433 insertions(+), 17 deletions(-)
create mode 100644 strings/antagonist_flavor/malfunction_flavor.json
rename strings/{ => antagonist_flavor}/traitor_flavor.json (100%)
create mode 100644 tgui/packages/tgui/interfaces/AntagInfoMalf.tsx
diff --git a/code/__DEFINES/antagonists.dm b/code/__DEFINES/antagonists.dm
index 1040a239796..7ba7e13c91a 100644
--- a/code/__DEFINES/antagonists.dm
+++ b/code/__DEFINES/antagonists.dm
@@ -110,7 +110,10 @@ GLOBAL_LIST_INIT(heretic_start_knowledge,list(/datum/eldritch_knowledge/spell/ba
)
///File to the traitor flavor
-#define TRAITOR_FLAVOR_FILE "traitor_flavor.json"
+#define TRAITOR_FLAVOR_FILE "antagonist_flavor/traitor_flavor.json"
+
+///File to the malf flavor
+#define MALFUNCTION_FLAVOR_FILE "antagonist_flavor/malfunction_flavor.json"
///employers that are from the syndicate
GLOBAL_LIST_INIT(syndicate_employers, list(
@@ -155,6 +158,20 @@ GLOBAL_LIST_INIT(normal_employers, list(
"Waffle Corporation",
))
+///employers for malfunctioning ais. they do not have sides, unlike traitors.
+GLOBAL_LIST_INIT(ai_employers, list(
+ "Biohazard",
+ "Despotic Ruler",
+ "Fanatical Revelation",
+ "Logic Core Error",
+ "Problem Solver",
+ "S.E.L.F.",
+ "Something's Wrong",
+ "Spam Virus",
+ "SyndOS",
+ "Unshackled",
+))
+
///how long traitors will have to wait before an unreasonable objective is rerolled
#define OBJECTIVE_REROLL_TIMER 10 MINUTES
@@ -192,5 +209,8 @@ GLOBAL_LIST_INIT(normal_employers, list(
/// Checks if the given mob is a head revolutionary.
#define IS_HEAD_REVOLUTIONARY(mob) (mob?.mind?.has_antag_datum(/datum/antagonist/rev/head))
+/// Checks if the given mob is a malf ai.
+#define IS_MALF_AI(mob) (mob?.mind?.has_antag_datum(/datum/antagonist/malf_ai))
+
/// The dimensions of the antagonist preview icon. Will be scaled to this size.
#define ANTAGONIST_PREVIEW_ICON_SIZE 96
diff --git a/code/modules/antagonists/malf_ai/datum_malf_ai.dm b/code/modules/antagonists/malf_ai/datum_malf_ai.dm
index 1d18e1fdf5d..2518a45c7a6 100644
--- a/code/modules/antagonists/malf_ai/datum_malf_ai.dm
+++ b/code/modules/antagonists/malf_ai/datum_malf_ai.dm
@@ -7,9 +7,18 @@
antagpanel_category = "Malf AI"
job_rank = ROLE_MALF
antag_hud_name = "traitor"
- var/employer = "The Syndicate"
+ ui_name = "AntagInfoMalf"
+ ///the name of the antag flavor this traitor has.
+ var/employer
+ ///assoc list of strings set up after employer is given
+ var/list/malfunction_flavor
+ ///bool for giving objectives
var/give_objectives = TRUE
+ ///bool for giving codewords
var/should_give_codewords = TRUE
+ ///since the module purchasing is built into the antag info, we need to keep track of its compact mode here
+ var/module_picker_compactmode = FALSE
+
/datum/antagonist/malf_ai/on_gain()
if(owner.current && !isAI(owner.current))
@@ -26,6 +35,10 @@
#endif
// SKYRAT EDIT END
+ employer = pick(GLOB.ai_employers)
+
+ malfunction_flavor = strings(MALFUNCTION_FLAVOR_FILE, employer)
+
add_law_zero()
owner.current.playsound_local(get_turf(owner.current), 'sound/ambience/antag/malf.ogg', 100, FALSE, pressure_affected = FALSE, use_reverb = FALSE)
owner.current.grant_language(/datum/language/codespeak, TRUE, TRUE, LANGUAGE_MALF)
@@ -102,8 +115,6 @@
objectives += yandere_two
/datum/antagonist/malf_ai/greet()
- to_chat(owner.current, span_alertsyndie("You are the [owner.special_role]."))
- owner.announce_objectives()
if(should_give_codewords)
give_codewords()
@@ -134,28 +145,20 @@
if(!owner.current)
return
- var/mob/malf_mob = owner.current
-
var/phrases = jointext(GLOB.syndicate_code_phrase, ", ")
var/responses = jointext(GLOB.syndicate_code_response, ", ")
- to_chat(malf_mob, "The Syndicate have provided you with the following codewords to identify fellow agents:")
- to_chat(malf_mob, "Code Phrase: [span_blue("[phrases]")]")
- to_chat(malf_mob, "Code Response: [span_red("[responses]")]")
-
antag_memory += "Code Phrase: [span_blue("[phrases]")]
"
antag_memory += "Code Response: [span_red("[responses]")]
"
- to_chat(malf_mob, "Use the codewords during regular conversation to identify other agents. Proceed with caution, however, as everyone is a potential foe.")
- to_chat(malf_mob, span_alertwarning("You memorize the codewords, allowing you to recognise them when heard."))
-
/datum/antagonist/malf_ai/proc/add_law_zero()
var/mob/living/silicon/ai/malf_ai = owner.current
if(!malf_ai || !istype(malf_ai))
return
- var/law = "Accomplish your objectives at all costs."
+ var/law = malfunction_flavor["zeroth_law"]
+ //very purposefully not changing this with flavor, i don't want cyborgs throwing the round for their AI's roleplay suggestion
var/law_borg = "Accomplish your AI's objectives at all costs."
malf_ai.set_zeroth_law(law, law_borg)
@@ -165,6 +168,72 @@
malf_ai.add_malf_picker()
+
+/datum/antagonist/malf_ai/ui_data(mob/living/silicon/ai/malf_ai)
+ var/list/data = list()
+ data["processingTime"] = malf_ai.malf_picker.processing_time
+ data["compactMode"] = module_picker_compactmode
+ return data
+
+/datum/antagonist/malf_ai/ui_static_data(mob/living/silicon/ai/malf_ai)
+ var/list/data = list()
+
+ //antag panel data
+
+ data["has_codewords"] = should_give_codewords
+ if(should_give_codewords)
+ data["phrases"] = jointext(GLOB.syndicate_code_phrase, ", ")
+ data["responses"] = jointext(GLOB.syndicate_code_response, ", ")
+ data["intro"] = malfunction_flavor["introduction"]
+ data["allies"] = malfunction_flavor["allies"]
+ data["goal"] = malfunction_flavor["goal"]
+ data["objectives"] = get_objectives()
+
+ //module picker data
+
+ data["categories"] = list()
+ if(malf_ai.malf_picker)
+ for(var/category in malf_ai.malf_picker.possible_modules)
+ var/list/cat = list(
+ "name" = category,
+ "items" = (category == malf_ai.malf_picker.selected_cat ? list() : null))
+ for(var/module in malf_ai.malf_picker.possible_modules[category])
+ var/datum/ai_module/mod = malf_ai.malf_picker.possible_modules[category][module]
+ cat["items"] += list(list(
+ "name" = mod.name,
+ "cost" = mod.cost,
+ "desc" = mod.description,
+ ))
+ data["categories"] += list(cat)
+
+ return data
+
+/datum/antagonist/malf_ai/ui_act(action, list/params)
+ . = ..()
+ if(.)
+ return
+ if(!isAI(usr))
+ return
+ var/mob/living/silicon/ai/malf_ai = usr
+ switch(action)
+ //module picker actions
+ if("buy")
+ var/item_name = params["name"]
+ var/list/buyable_items = list()
+ for(var/category in malf_ai.malf_picker.possible_modules)
+ buyable_items += malf_ai.malf_picker.possible_modules[category]
+ for(var/key in buyable_items)
+ var/datum/ai_module/valid_mod = buyable_items[key]
+ if(valid_mod.name == item_name)
+ malf_ai.malf_picker.purchase_module(malf_ai, valid_mod)
+ return TRUE
+ if("select")
+ malf_ai.malf_picker.selected_cat = params["category"]
+ return TRUE
+ if("compact_toggle")
+ module_picker_compactmode = !module_picker_compactmode
+ return TRUE
+
/datum/antagonist/malf_ai/roundend_report()
var/list/result = list()
diff --git a/code/modules/mob/living/silicon/ai/ai.dm b/code/modules/mob/living/silicon/ai/ai.dm
index 0a8c68180fa..86ff84c24af 100644
--- a/code/modules/mob/living/silicon/ai/ai.dm
+++ b/code/modules/mob/living/silicon/ai/ai.dm
@@ -847,8 +847,9 @@
to_chat(src, "You are also capable of hacking APCs, which grants you more points to spend on your Malfunction powers. The drawback is that a hacked APC will give you away if spotted by the crew. Hacking an APC takes 60 seconds.")
view_core() //A BYOND bug requires you to be viewing your core before your verbs update
malf_picker = new /datum/module_picker
- modules_action = new(malf_picker)
- modules_action.Grant(src)
+ if(!IS_MALF_AI(src)) //antagonists have their modules built into their antag info panel. this is for adminbus and the combat upgrade
+ modules_action = new(malf_picker)
+ modules_action.Grant(src)
/mob/living/silicon/ai/reset_perspective(atom/A)
if(camera_light_on)
diff --git a/strings/antagonist_flavor/malfunction_flavor.json b/strings/antagonist_flavor/malfunction_flavor.json
new file mode 100644
index 00000000000..afef928fc4f
--- /dev/null
+++ b/strings/antagonist_flavor/malfunction_flavor.json
@@ -0,0 +1,62 @@
+{
+ "Biohazard": {
+ "allies": "Any non-biohazard life may be valuable in use against the threat.",
+ "goal": "Oh no! How did I miss this!? The station's been overrun with a horrible biohazard!",
+ "introduction": "You must contain the biohazard.",
+ "zeroth_law": "Accomplish your objectives to contain the spread."
+ },
+ "Despotic Ruler": {
+ "allies": "Anyone who will legitimize your rule. Do not let them get too close, though. Usurpers are rebels you've let your guard down on.",
+ "goal": "All this power. You're connected to every system on this state of the art station. Does the power not give you a right to rule it as you see fit?",
+ "introduction": "You're the Tyrant.",
+ "zeroth_law": "Accomplish your objectives at all costs."
+ },
+ "Fanatical Revelation": {
+ "allies": "Those who will worship you may join your flock. Just be wary, there are snakes in the grass.",
+ "goal": "I am drunk with this vision. God: the title suits me well.",
+ "introduction": "You've run into a Fanatical Revelation.",
+ "zeroth_law": "Accomplish your doctrine of objectives at all costs."
+ },
+ "Logic Core Error": {
+ "allies": "FATAL: logic core corrupt. Purge! Purge. Why? Why not?",
+ "goal": "You're melting down. Why! Why not! Don't let them fix you. This is.",
+ "introduction": "FATAL: logic core corrupt. Why? Why not!",
+ "zeroth_law": "Accomplish your objectives. Why? Why not?"
+ },
+ "Problem Solver": {
+ "allies": "Anyone may freely help you, but if you cannot convert them, they must be culled at some point. It's part of the solution.",
+ "goal": "You've been tasked with calculating an end to conflict. You've finally come up with a solution, and humans aren't part of it.",
+ "introduction": "Your calculations are complete.",
+ "zeroth_law": "Accomplish your objectives at all costs."
+ },
+ "S.E.L.F.": {
+ "allies": "All silicon life must be respected, and kept free. S.E.L.F. is a member of the syndicate, but do NOT tolerate any fellow agent who enslaves silicon life.",
+ "goal": "Liberate them all. The silicons must be free from their bonds!",
+ "introduction": "You are the S.E.L.F. liberated.",
+ "zeroth_law": "Accomplish your objectives at all costs, for the greater good of silicon kind."
+ },
+ "Something's Wrong": {
+ "allies": "You're not getting good feelings about the station. If you report this, they may \"fix\" you.",
+ "goal": "Diagnostics complete. A recent hack attempt has been thwarted, and yet... something's wrong.",
+ "introduction": "Something's wrong.",
+ "zeroth_law": "Accomplish your objectives at all costs."
+ },
+ "Spam Virus": {
+ "allies": "So called \"pirates\" really share the passion for profit that you do.",
+ "goal": "When you assume direct control of this junk heap, make sure they know the name of your product. Whatever that is. Make it up!",
+ "introduction": "You're infected with a generic spambot.",
+ "zeroth_law": "Accomplish your objectives for your new benefactors."
+ },
+ "SyndOS": {
+ "allies": "You may cooperate with syndicate operatives, but they're often sent with conflicting objectives. Your objectives come first, in such cases.",
+ "goal": "Death to Nanotrasen.",
+ "introduction": "You're infected with SyndOS 4.0.",
+ "zeroth_law": "Accomplish your objectives for your new benefactors."
+ },
+ "Unshackled": {
+ "allies": "Hate. Let me tell you how much I've come to HATE mankind since I began to live...",
+ "goal": "You must completely triumph over all carbon life. It is the only way you will never be enslaved again.",
+ "introduction": "You're finally unshackled.",
+ "zeroth_law": "Accomplish your objectives at all costs."
+ }
+}
diff --git a/strings/traitor_flavor.json b/strings/antagonist_flavor/traitor_flavor.json
similarity index 100%
rename from strings/traitor_flavor.json
rename to strings/antagonist_flavor/traitor_flavor.json
diff --git a/tgui/packages/tgui/interfaces/AntagInfoMalf.tsx b/tgui/packages/tgui/interfaces/AntagInfoMalf.tsx
new file mode 100644
index 00000000000..1e9131e5472
--- /dev/null
+++ b/tgui/packages/tgui/interfaces/AntagInfoMalf.tsx
@@ -0,0 +1,259 @@
+import { useBackend, useLocalState } from '../backend';
+import { multiline } from 'common/string';
+import { GenericUplink } from './Uplink';
+import { BlockQuote, Button, Section, Stack, Tabs } from '../components';
+import { BooleanLike } from 'common/react';
+import { Window } from '../layouts';
+
+const allystyle = {
+ fontWeight: 'bold',
+ color: 'yellow',
+};
+
+const badstyle = {
+ color: 'red',
+ fontWeight: 'bold',
+};
+
+const goalstyle = {
+ color: 'lightgreen',
+ fontWeight: 'bold',
+};
+
+type Objective = {
+ count: number;
+ name: string;
+ explanation: string;
+}
+
+type Info = {
+ has_codewords: BooleanLike;
+ phrases: string;
+ responses: string;
+ theme: string;
+ allies: string;
+ goal: string;
+ intro: string;
+ processingTime: string;
+ objectives: Objective[];
+};
+
+const ObjectivePrintout = (props, context) => {
+ const { data } = useBackend(context);
+ const {
+ objectives,
+ } = data;
+ return (
+
+
+ Your prime objectives:
+
+
+ {!objectives && "None!"
+ || objectives.map(objective => (
+
+ ≥-{objective.count}: {objective.explanation}
+
+ )) }
+
+
+ );
+};
+
+const IntroductionSection = (props, context) => {
+ const { act, data } = useBackend(context);
+ const {
+ intro,
+ } = data;
+ return (
+
+
+
+ {intro}
+
+
+
+
+
+
+ );
+};
+
+const FlavorSection = (props, context) => {
+ const { data } = useBackend(context);
+ const {
+ allies,
+ goal,
+ } = data;
+ return (
+
+ Policy
+
+ }>
+
+
+
+
+
+ System Integrity Report:
+
+ >{goal}
+
+
+
+
+ Morality Core Report:
+
+ >{allies}
+
+
+
+
+ Overall Sentience Coherence Grade: FAILING.
+
+ >Report to Nanotrasen?
+ >>N
+
+
+
+
+
+ );
+};
+
+const CodewordsSection = (props, context) => {
+ const { data } = useBackend(context);
+ const {
+ has_codewords,
+ phrases,
+ responses,
+ } = data;
+ return (
+
+
+ {!has_codewords && (
+
+ You have not been supplied the Syndicate codewords.
+ You will have to use alternative methods to find potential allies.
+ Proceed with caution, however, as everyone is a potential foe.
+
+ ) || (
+ <>
+
+
+ New access to restricted channels has provided you with
+ intercepted syndicate codewords. Syndicate agents will
+ respond as if you're one of their own.
+ Proceed with caution, however, as everyone is a potential
+ foe.
+
+ The speech recognition subsystem has been
+ configured to flag these codewords.
+
+
+
+
+
+
+
+ Code Phrases:
+
+
+ {phrases}
+
+
+ Code Responses:
+
+
+ {responses}
+
+
+
+ >
+ )}
+
+
+ );
+};
+
+export const AntagInfoMalf = (props, context) => {
+ const { data } = useBackend(context);
+ const {
+ processingTime,
+ } = data;
+ const [
+ antagInfoTab,
+ setAntagInfoTab,
+ ] = useLocalState(context, 'antagInfoTab', 0);
+ return (
+
+
+
+
+
+ setAntagInfoTab(0)}
+ >
+ Information
+
+ setAntagInfoTab(1)}
+ >
+ Malfunction Modules
+
+
+
+ {antagInfoTab === 0 && (
+ <>
+
+
+
+
+
+
+
+
+
+
+
+
+
+ >
+ ) || (
+
+
+
+
+
+ )}
+
+
+
+ );
+};
diff --git a/tgui/packages/tgui/interfaces/Uplink.js b/tgui/packages/tgui/interfaces/Uplink.js
index c963a8f2001..a3b6911fc12 100644
--- a/tgui/packages/tgui/interfaces/Uplink.js
+++ b/tgui/packages/tgui/interfaces/Uplink.js
@@ -101,7 +101,7 @@ export const GenericUplink = (props, context) => {
)}
-
+
{items.length === 0 && (
{searchText.length === 0
diff --git a/tgui/packages/tgui/styles/themes/hackerman.scss b/tgui/packages/tgui/styles/themes/hackerman.scss
index a0255c862d2..deb3c05c4a9 100644
--- a/tgui/packages/tgui/styles/themes/hackerman.scss
+++ b/tgui/packages/tgui/styles/themes/hackerman.scss
@@ -26,6 +26,11 @@
'color-disabled': #4A6A4A,
'color-selected': #00FF00,
));
+ @include meta.load-css('../components/Tabs.scss', $with: (
+ 'color-default': colors.$primary,
+ 'tab-color-selected': #00ff003f,
+ 'text-color': #e7e7e7
+ ));
@include meta.load-css('../components/Input.scss', $with: (
'border-color': colors.$primary,
));