[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>
This commit is contained in:
SkyratBot
2021-11-28 14:13:27 -05:00
committed by GitHub
co-authored by tralezab
parent 543ad4f047
commit 5fa25bfc25
8 changed files with 433 additions and 17 deletions
+21 -1
View File
@@ -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
@@ -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, "<U><B>The Syndicate have provided you with the following codewords to identify fellow agents:</B></U>")
to_chat(malf_mob, "<B>Code Phrase</B>: [span_blue("[phrases]")]")
to_chat(malf_mob, "<B>Code Response</B>: [span_red("[responses]")]")
antag_memory += "<b>Code Phrase</b>: [span_blue("[phrases]")]<br>"
antag_memory += "<b>Code Response</b>: [span_red("[responses]")]<br>"
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()
+3 -2
View File
@@ -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)
@@ -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."
}
}
@@ -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<Info>(context);
const {
objectives,
} = data;
return (
<Stack vertical>
<Stack.Item bold>
Your prime objectives:
</Stack.Item>
<Stack.Item>
{!objectives && "None!"
|| objectives.map(objective => (
<Stack.Item key={objective.count}>
&#8805-{objective.count}: {objective.explanation}
</Stack.Item>
)) }
</Stack.Item>
</Stack>
);
};
const IntroductionSection = (props, context) => {
const { act, data } = useBackend<Info>(context);
const {
intro,
} = data;
return (
<Section fill title="Intro" scrollable>
<Stack vertical fill>
<Stack.Item fontSize="25px">
{intro}
</Stack.Item>
<Stack.Item grow>
<ObjectivePrintout />
</Stack.Item>
</Stack>
</Section>
);
};
const FlavorSection = (props, context) => {
const { data } = useBackend<Info>(context);
const {
allies,
goal,
} = data;
return (
<Section
fill
title="Diagnostics"
buttons={
<Button
mr={-0.8}
mt={-0.5}
icon="hammer"
tooltip={multiline`
This is a gameplay suggestion for bored ais.
You don't have to follow it, unless you want some
ideas for how to spend the round.`}
tooltipPosition="bottom-start">
Policy
</Button>
}>
<Stack vertical fill>
<Stack.Item grow>
<Stack fill vertical>
<Stack.Item style={{ 'background-color': 'black' }}>
<span style={goalstyle}>
System Integrity Report:<br />
</span>
&gt;{goal}
</Stack.Item>
<Stack.Divider />
<Stack.Item grow style={{ 'background-color': 'black' }}>
<span style={allystyle}>
Morality Core Report:<br />
</span>
&gt;{allies}
</Stack.Item>
<Stack.Divider />
<Stack.Item style={{ 'background-color': 'black' }}>
<span style={badstyle}>
Overall Sentience Coherence Grade: FAILING.<br />
</span>
&gt;Report to Nanotrasen?<br />
&gt;&gt;N
</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 the Syndicate 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>
New access to restricted channels has provided you with
intercepted syndicate codewords. Syndicate agents will
respond as if you&apos;re one of their own.
Proceed with caution, however, as everyone is a potential
foe.
<span style={badstyle}>
&ensp;The speech recognition subsystem has been
configured to flag these codewords.
</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 AntagInfoMalf = (props, context) => {
const { data } = useBackend<Info>(context);
const {
processingTime,
} = data;
const [
antagInfoTab,
setAntagInfoTab,
] = useLocalState(context, 'antagInfoTab', 0);
return (
<Window
width={660}
height={530}
theme={antagInfoTab === 0 && "hackerman" || "malfunction"}>
<Window.Content
style={{ 'font-family': 'Consolas, monospace' }}>
<Stack vertical fill>
<Stack.Item>
<Tabs fluid>
<Tabs.Tab
icon="info"
selected={antagInfoTab === 0}
onClick={() => setAntagInfoTab(0)}
>
Information
</Tabs.Tab>
<Tabs.Tab
icon="code"
selected={antagInfoTab === 1}
onClick={() => setAntagInfoTab(1)}
>
Malfunction Modules
</Tabs.Tab>
</Tabs>
</Stack.Item>
{antagInfoTab === 0 && (
<>
<Stack.Item grow>
<Stack fill>
<Stack.Item width="70%">
<IntroductionSection />
</Stack.Item>
<Stack.Item width="30%">
<FlavorSection />
</Stack.Item>
</Stack>
</Stack.Item>
<Stack.Item>
<CodewordsSection />
</Stack.Item>
</>
) || (
<Stack.Item>
<Section>
<GenericUplink
currencyAmount={processingTime}
currencySymbol="PT" />
</Section>
</Stack.Item>
)}
</Stack>
</Window.Content>
</Window>
);
};
+1 -1
View File
@@ -101,7 +101,7 @@ export const GenericUplink = (props, context) => {
</Tabs>
</Flex.Item>
)}
<Flex.Item grow={1} basis={0}>
<Flex.Item ml={2} grow basis={0}>
{items.length === 0 && (
<NoticeBox>
{searchText.length === 0
@@ -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,
));