mirror of
https://github.com/Bubberstation/Bubberstation.git
synced 2026-08-24 05:30:05 +01:00
[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:
@@ -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()
|
||||
|
||||
|
||||
@@ -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}>
|
||||
≥-{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>
|
||||
>{goal}
|
||||
</Stack.Item>
|
||||
<Stack.Divider />
|
||||
<Stack.Item grow style={{ 'background-color': 'black' }}>
|
||||
<span style={allystyle}>
|
||||
Morality Core Report:<br />
|
||||
</span>
|
||||
>{allies}
|
||||
</Stack.Item>
|
||||
<Stack.Divider />
|
||||
<Stack.Item style={{ 'background-color': 'black' }}>
|
||||
<span style={badstyle}>
|
||||
Overall Sentience Coherence Grade: FAILING.<br />
|
||||
</span>
|
||||
>Report to Nanotrasen?<br />
|
||||
>>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're one of their own.
|
||||
Proceed with caution, however, as everyone is a potential
|
||||
foe.
|
||||
<span style={badstyle}>
|
||||
 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>
|
||||
);
|
||||
};
|
||||
@@ -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,
|
||||
));
|
||||
|
||||
Reference in New Issue
Block a user