From 4e041ce765ed432d0d84c4b64c49bca5deb99c95 Mon Sep 17 00:00:00 2001
From: tralezab <40974010+tralezab@users.noreply.github.com>
Date: Thu, 29 Sep 2022 18:00:23 -0700
Subject: [PATCH] The Link to the Changeling Hivemind has been Reestablished
(#70193)
Readds the changeling hivemind, but in a simplified form.
There is no linglinking. It was rarely used and imma be real with you, stupid.
Hivemind isn't an unlocked ability, it is just a default.
Bz still mutes lings
Fallen changelings now get an agonizing message about being locked out. We love fallen changeling flavor
Changelings have their Honorific names back! Hello, Mr. Omnicron!
This does NOT readd changeling team objectives, or really anything encouraging teaming beyond the hivemind itself.
I think antagonists have generally lost a lot of power versus the station, and this is one big way to pump up the heat for this antag in particular. Changelings, given an ability to possibly team up, are much MUCH scarier.
One reason it was removed years ago was because teaming was too often, but back then there were entire gamemodes about changelings having team objectives and many other things to encourage it. I'm bringing us back to a point BEFORE all those design ideas were explored, because I don't think they worked out.
Add information about the changeling hivemind, and the dangers of being turned into a fallen changeling if you get betrayed to the Changeling Antag Info UI
Changelog
add: Changelings once again have reestablished their changeling hivemind, and can secretly communicate between each other.
fix: Fixed up the Changeling UI a bit, like for example some dimmers would never render.
---
code/__DEFINES/say.dm | 4 +
code/__DEFINES/traits.dm | 2 +
code/_globalvars/lists/names.dm | 3 +-
code/datums/saymode.dm | 38 +++++
code/modules/antagonists/abductor/abductor.dm | 3 +-
.../antagonists/changeling/changeling.dm | 23 +++
code/modules/mob/living/living_say.dm | 1 +
code/modules/mob/say_readme.md | 3 -
.../chemistry/reagents/other_reagents.dm | 8 ++
.../tgui/interfaces/AntagInfoChangeling.tsx | 131 +++++++++++-------
10 files changed, 163 insertions(+), 53 deletions(-)
diff --git a/code/__DEFINES/say.dm b/code/__DEFINES/say.dm
index a93f6a9fc18..f091ed882f2 100644
--- a/code/__DEFINES/say.dm
+++ b/code/__DEFINES/say.dm
@@ -45,6 +45,10 @@
#define MODE_ALIEN "alientalk"
#define MODE_HOLOPAD "holopad"
+#define MODE_CHANGELING "changeling"
+#define MODE_KEY_CHANGELING "g"
+#define MODE_TOKEN_CHANGELING ":g"
+
#define MODE_VOCALCORDS "cords"
#define MODE_KEY_VOCALCORDS "x"
diff --git a/code/__DEFINES/traits.dm b/code/__DEFINES/traits.dm
index 6666e5e51a8..7fbfbc1377a 100644
--- a/code/__DEFINES/traits.dm
+++ b/code/__DEFINES/traits.dm
@@ -748,6 +748,8 @@ Remember to update _globalvars/traits.dm if you're adding/removing/renaming trai
// unique trait sources, still defines
#define STATUE_MUTE "statue"
#define CHANGELING_DRAIN "drain"
+/// changelings with this trait can no longer talk over the hivemind
+#define CHANGELING_HIVEMIND_MUTE "ling_mute"
#define ABYSSAL_GAZE_BLIND "abyssal_gaze"
#define HIGHLANDER "highlander"
#define TRAIT_HULK "hulk"
diff --git a/code/_globalvars/lists/names.dm b/code/_globalvars/lists/names.dm
index b36312439fd..018164e2c78 100644
--- a/code/_globalvars/lists/names.dm
+++ b/code/_globalvars/lists/names.dm
@@ -30,8 +30,9 @@ GLOBAL_LIST_INIT(martial_prefix, world.file2list("strings/names/martial_prefix.t
GLOBAL_LIST_INIT(adverbs, world.file2list("strings/names/adverbs.txt"))
GLOBAL_LIST_INIT(adjectives, world.file2list("strings/names/adjectives.txt"))
GLOBAL_LIST_INIT(gross_adjectives, world.file2list("strings/names/gross_adjectives.txt"))
-GLOBAL_LIST_INIT(hive_names, world.file2list("strings/names/hive_names.txt"))
GLOBAL_LIST_INIT(dream_strings, world.file2list("strings/dreamstrings.txt"))
GLOBAL_LIST_INIT(vampire_house_names, world.file2list("strings/names/vampire_house_names.txt"))
+GLOBAL_LIST_INIT(greek_alphabet, world.file2list("strings/greek_alphabet.txt"))
+GLOBAL_LIST_INIT(hive_names, world.file2list("strings/names/hive_names.txt"))
//loaded on startup because of "
//would include in rsc if ' was used
diff --git a/code/datums/saymode.dm b/code/datums/saymode.dm
index 154cf644016..b3855fe8070 100644
--- a/code/datums/saymode.dm
+++ b/code/datums/saymode.dm
@@ -9,6 +9,44 @@
/datum/saymode/proc/handle_message(mob/living/user, message, datum/language/language)
return TRUE
+/datum/saymode/changeling
+ key = MODE_KEY_CHANGELING
+ mode = MODE_CHANGELING
+
+/datum/saymode/changeling/handle_message(mob/living/user, message, datum/language/language)
+ //we can send the message
+ if(!user.mind)
+ return FALSE
+ if(user.mind.has_antag_datum(/datum/antagonist/fallen_changeling))
+ to_chat(user, span_changeling("We're cut off from the hivemind! We've lost everything! EVERYTHING!!"))
+ return FALSE
+ var/datum/antagonist/changeling/ling_sender = user.mind.has_antag_datum(/datum/antagonist/changeling)
+ if(!ling_sender)
+ return FALSE
+ if(HAS_TRAIT(user, CHANGELING_HIVEMIND_MUTE))
+ to_chat(user, span_warning("The poison in the air hinders our ability to interact with the hivemind."))
+ return FALSE
+
+ user.log_talk(message, LOG_SAY, tag="changeling [ling_sender.changelingID]")
+ var/msg = span_changeling("[ling_sender.changelingID]: [message]")
+
+ //the recipients can recieve the message
+ for(var/datum/antagonist/changeling/ling_reciever in GLOB.antagonists)
+ if(!ling_reciever.owner)
+ continue
+ var/mob/living/ling_mob = ling_reciever.owner.current
+ //removes types that override the presence of being changeling (for example, borged lings still can't hivemind chat)
+ if(!isliving(ling_mob) || issilicon(ling_mob) || isbrain(ling_mob))
+ continue
+ // can't recieve messages on the hivemind right now
+ if(HAS_TRAIT(ling_mob, CHANGELING_HIVEMIND_MUTE))
+ continue
+ to_chat(ling_mob, msg)
+
+ for(var/mob/dead/ghost as anything in GLOB.dead_mob_list)
+ to_chat(ghost, "[FOLLOW_LINK(ghost, user)] [msg]")
+ return FALSE
+
/datum/saymode/xeno
key = "a"
mode = MODE_ALIEN
diff --git a/code/modules/antagonists/abductor/abductor.dm b/code/modules/antagonists/abductor/abductor.dm
index 51eb878ec48..1640a817f0c 100644
--- a/code/modules/antagonists/abductor/abductor.dm
+++ b/code/modules/antagonists/abductor/abductor.dm
@@ -1,5 +1,4 @@
#define ABDUCTOR_MAX_TEAMS 4
-GLOBAL_LIST_INIT(possible_abductor_names, list("Alpha","Beta","Gamma","Delta","Epsilon","Zeta","Eta","Theta","Iota","Kappa","Lambda","Mu","Nu","Xi","Omicron","Pi","Rho","Sigma","Tau","Upsilon","Phi","Chi","Psi","Omega"))
/datum/antagonist/abductor
name = "\improper Abductor"
@@ -157,7 +156,7 @@ GLOBAL_LIST_INIT(possible_abductor_names, list("Alpha","Beta","Gamma","Delta","E
/datum/team/abductor_team/New()
..()
team_number = team_count++
- name = "Mothership [pick(GLOB.possible_abductor_names)]" //TODO Ensure unique and actual alieny names
+ name = "Mothership [pick(GLOB.greek_letters)]" //TODO Ensure unique and actual alieny names
add_objective(new /datum/objective/experiment)
/datum/team/abductor_team/roundend_report()
diff --git a/code/modules/antagonists/changeling/changeling.dm b/code/modules/antagonists/changeling/changeling.dm
index 896e6cad5c3..82cef6d5ad9 100644
--- a/code/modules/antagonists/changeling/changeling.dm
+++ b/code/modules/antagonists/changeling/changeling.dm
@@ -46,6 +46,8 @@
var/chem_recharge_slowdown = 0
/// The range this ling can sting things.
var/sting_range = 2
+ /// Changeling name, what other lings see over the hivemind when talking.
+ var/changelingID = "Changeling"
/// The number of genetics points (to buy powers) this ling currently has.
var/genetic_points = 10
/// The max number of genetics points (to buy powers) this ling can have..
@@ -78,6 +80,9 @@
/// Static typecache of all changeling powers that are usable.
var/static/list/all_powers = typecacheof(/datum/action/changeling, ignore_root_path = TRUE)
+ /// Static list of possible ids. Initialized into the greek alphabet the first time it is used
+ var/static/list/possible_changeling_IDs
+
/// Satic list of what each slot associated with (in regard to changeling flesh items).
var/static/list/slot2type = list(
"head" = /obj/item/clothing/head/changeling,
@@ -117,6 +122,7 @@
return ..()
/datum/antagonist/changeling/on_gain()
+ generate_name()
create_emporium()
create_innate_actions()
create_initial_profile()
@@ -158,6 +164,22 @@
our_ling_brain.organ_flags &= ~ORGAN_VITAL
our_ling_brain.decoy_override = TRUE
+/datum/antagonist/changeling/proc/generate_name()
+ var/honorific
+ if(owner.current.gender == FEMALE)
+ honorific = "Ms."
+ else if(owner.current.gender == MALE)
+ honorific = "Mr."
+ else
+ honorific = "Mx."
+
+ if(!possible_changeling_IDs)
+ possible_changeling_IDs = GLOB.greek_letters.Copy()
+ if(possible_changeling_IDs.len)
+ changelingID = "[honorific] [pick_n_take(possible_changeling_IDs)]"
+ else
+ changelingID = "[honorific] [rand(1,999)]"
+
/datum/antagonist/changeling/proc/on_hud_created(datum/source)
SIGNAL_HANDLER
@@ -941,6 +963,7 @@
memories += list(list("name" = memory_key, "story" = stolen_memories[memory_key]))
data["memories"] = memories
+ data["true_name"] = changelingID
data["hive_name"] = hive_name
data["stolen_antag_info"] = antag_memory
data["objectives"] = get_objectives()
diff --git a/code/modules/mob/living/living_say.dm b/code/modules/mob/living/living_say.dm
index a4c92e45803..2def8228c42 100644
--- a/code/modules/mob/living/living_say.dm
+++ b/code/modules/mob/living/living_say.dm
@@ -64,6 +64,7 @@ GLOBAL_LIST_INIT(department_radio_keys, list(
*/
GLOBAL_LIST_INIT(message_modes_stat_limits, list(
MODE_INTERCOM = HARD_CRIT,
+ MODE_CHANGELING = HARD_CRIT,
MODE_ALIEN = HARD_CRIT,
MODE_BINARY = HARD_CRIT, //extra stat check on human/binarycheck()
MODE_MONKEY = HARD_CRIT,
diff --git a/code/modules/mob/say_readme.md b/code/modules/mob/say_readme.md
index a98d3206fa5..479b5d74410 100644
--- a/code/modules/mob/say_readme.md
+++ b/code/modules/mob/say_readme.md
@@ -92,9 +92,6 @@ global procs
hivecheck()
Returns TRUE if the mob can hear and talk in the alien hivemind.
- lingcheck()
- Returns a bitflag representing who is trying to talk to the changeling hivemind.
-
/mob/living
say(message, bubble_type, var/list/spans, sanitize, datum/language/languag, ignore_spam, forced)
The say() of mob_living is significantly more complex than that of objects.
diff --git a/code/modules/reagents/chemistry/reagents/other_reagents.dm b/code/modules/reagents/chemistry/reagents/other_reagents.dm
index 3438e7b0454..e374a6a2601 100644
--- a/code/modules/reagents/chemistry/reagents/other_reagents.dm
+++ b/code/modules/reagents/chemistry/reagents/other_reagents.dm
@@ -2378,6 +2378,14 @@
metabolization_rate = 0.2 * REAGENTS_METABOLISM
chemical_flags = REAGENT_CAN_BE_SYNTHESIZED|REAGENT_NO_RANDOM_RECIPE
+/datum/reagent/bz_metabolites/on_mob_metabolize(mob/living/ling)
+ ..()
+ ADD_TRAIT(ling, CHANGELING_HIVEMIND_MUTE, type)
+
+/datum/reagent/bz_metabolites/on_mob_end_metabolize(mob/living/ling)
+ ..()
+ REMOVE_TRAIT(ling, CHANGELING_HIVEMIND_MUTE, type)
+
/datum/reagent/bz_metabolites/on_mob_life(mob/living/carbon/target, delta_time, times_fired)
if(target.mind)
var/datum/antagonist/changeling/changeling = target.mind.has_antag_datum(/datum/antagonist/changeling)
diff --git a/tgui/packages/tgui/interfaces/AntagInfoChangeling.tsx b/tgui/packages/tgui/interfaces/AntagInfoChangeling.tsx
index bfdf7383d92..497942fe142 100644
--- a/tgui/packages/tgui/interfaces/AntagInfoChangeling.tsx
+++ b/tgui/packages/tgui/interfaces/AntagInfoChangeling.tsx
@@ -1,6 +1,6 @@
import { multiline } from 'common/string';
import { useBackend, useSharedState } from '../backend';
-import { Box, Button, Dimmer, Dropdown, Section, Stack } from '../components';
+import { Button, Dimmer, Dropdown, Section, Stack, NoticeBox } from '../components';
import { Window } from '../layouts';
const hivestyle = {
@@ -28,6 +28,16 @@ const storestyle = {
fontWeight: 'bold',
};
+const hivemindstyle = {
+ color: 'violet',
+ fontWeight: 'bold',
+};
+
+const fallenstyle = {
+ color: 'black',
+ fontWeight: 'bold',
+};
+
type Objective = {
count: number;
name: string;
@@ -40,12 +50,46 @@ type Memory = {
};
type Info = {
+ true_name: string;
hive_name: string;
stolen_antag_info: string;
memories: Memory[];
objectives: Objective[];
};
+export const AntagInfoChangeling = (props, context) => {
+ return (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ );
+};
+
const ObjectivePrintout = (props, context) => {
const { data } = useBackend(context);
const { objectives } = data;
@@ -64,9 +108,37 @@ const ObjectivePrintout = (props, context) => {
);
};
+const HivemindSection = (props, context) => {
+ const { act, data } = useBackend(context);
+ const { true_name } = data;
+ return (
+
+
+
+ All Changelings, regardless of origin, are linked together by the{' '}
+ hivemind. You may communicate to
+ other Changelings under your mental alias,{' '}
+ {true_name}, by starting a message
+ with :g. Work together, and you
+ will bring the station to new heights of terror.
+
+
+
+ Other Changelings are strong allies, but some Changelings may betray
+ you. Changelings grow in power greatly by absorbing their kind, and
+ getting absorbed by another Changeling will leave you as a{' '}
+ Fallen Changeling. There is no
+ greater humiliation.
+
+
+
+
+ );
+};
+
const IntroductionSection = (props, context) => {
const { act, data } = useBackend(context);
- const { hive_name, objectives } = data;
+ const { true_name, hive_name, objectives } = data;
return (
{
scrollable={!!objectives && objectives.length > 4}>
- You are the Changeling from the
- {hive_name}.
+ You are {true_name} from the
+ {hive_name}.
@@ -136,8 +208,8 @@ const AbilitiesSection = (props, context) => {
};
const MemoriesSection = (props, context) => {
- const { act, data } = useBackend(context);
- const { memories, stolen_antag_info } = data;
+ const { data } = useBackend(context);
+ const { memories } = data;
const [selectedMemory, setSelectedMemory] = useSharedState(
context,
'memory',
@@ -151,7 +223,7 @@ const MemoriesSection = (props, context) => {
return (
{
`}
/>
}>
- {(!memories && (
-
- {!stolen_antag_info && (
-
- You need to absorb a victim first!
-
- )}
-
+ {(!!memories && !memories.length && (
+ Absorb a victim first!
)) || (
@@ -199,38 +265,9 @@ const VictimPatternsSection = (props, context) => {
fill
scrollable={!!stolen_antag_info}
title="Additional Stolen Information">
- {!!stolen_antag_info && stolen_antag_info}
+ {(!!stolen_antag_info && stolen_antag_info) || (
+ Absorb a victim first!
+ )}
);
};
-
-export const AntagInfoChangeling = (props, context) => {
- const { data } = useBackend(context);
- return (
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- );
-};