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