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 (
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- );
-};