diff --git a/code/__DEFINES/~skyrat_defines/colors.dm b/code/__DEFINES/~skyrat_defines/colors.dm index 4f00b0651b1..1703fe15413 100644 --- a/code/__DEFINES/~skyrat_defines/colors.dm +++ b/code/__DEFINES/~skyrat_defines/colors.dm @@ -3,3 +3,7 @@ #define LIGHT_COLOR_YELLOW "#E1E17D" #define COLOR_NRI_POLICE_BLUE "#1f3347" #define COLOR_NRI_POLICE_SILVER "#c0c0c0" + +GLOBAL_LIST_INIT(chat_colors_by_mob_name, list( + "Unknown" = list("#ffffff", "#d8d8d8"), +)) diff --git a/code/datums/chatmessage.dm b/code/datums/chatmessage.dm index 6ee4eaddd3f..8a4db318d85 100644 --- a/code/datums/chatmessage.dm +++ b/code/datums/chatmessage.dm @@ -127,8 +127,8 @@ // Calculate target color if not already present if (!target.chat_color || target.chat_color_name != target.name) - target.chat_color = colorize_string(target.name) - target.chat_color_darkened = colorize_string(target.name, 0.85, 0.85) + target.chat_color = get_chat_color_string(target.name) // NOVA EDIT CHANGE - ORIGINAL: target.chat_color = colorize_string(target.name) + target.chat_color_darkened = get_chat_color_string(target.name, darkened = TRUE) // NOVA EDIT CHANGE - ORIGINAL: target.chat_color_darkened = colorize_string(target.name, 0.85, 0.85) target.chat_color_name = target.name // Get rid of any URL schemes that might cause BYOND to automatically wrap something in an anchor tag diff --git a/code/modules/mob/mob.dm b/code/modules/mob/mob.dm index 0b111cf8af3..c7215340409 100644 --- a/code/modules/mob/mob.dm +++ b/code/modules/mob/mob.dm @@ -1285,7 +1285,11 @@ // Only update if this player is a target if(obj.target && obj.target.current && obj.target.current.real_name == name) obj.update_explanation_text() + if(client) // NOVA EDIT ADDITION - Update the mob chat color list, removing the old name + GLOB.chat_colors_by_mob_name -= oldname // NOVA EDIT ADDITION + if(client) // NOVA EDIT ADDITION - Update the mob chat color list, adding the new name + GLOB.chat_colors_by_mob_name[name] = list(chat_color, chat_color_darkened) // NOVA EDIT ADDITION log_mob_tag("TAG: [tag] RENAMED: [key_name(src)]") return TRUE diff --git a/modular_nova/master_files/code/modules/client/preferences.dm b/modular_nova/master_files/code/modules/client/preferences.dm index 52a70bd907c..ab6ea51e6d3 100644 --- a/modular_nova/master_files/code/modules/client/preferences.dm +++ b/modular_nova/master_files/code/modules/client/preferences.dm @@ -175,3 +175,7 @@ safe_transfer_prefs_to(character, icon_updates, is_antag) qdel(added_tracker) +// Updates the mob's chat color in the global cache +/datum/preferences/safe_transfer_prefs_to(mob/living/carbon/human/character, icon_updates = TRUE, is_antag = FALSE) + . = ..() + GLOB.chat_colors_by_mob_name[character.name] = list(character.chat_color, character.chat_color_darkened) // by now the mob has had its prefs applied to it diff --git a/modular_nova/modules/chat_colors/code/chat_color.dm b/modular_nova/modules/chat_colors/code/chat_color.dm new file mode 100644 index 00000000000..028c1df63ab --- /dev/null +++ b/modular_nova/modules/chat_colors/code/chat_color.dm @@ -0,0 +1,81 @@ +/datum/preference/color/chat_color + category = PREFERENCE_CATEGORY_NON_CONTEXTUAL + priority = PREFERENCE_PRIORITY_NAME_MODIFICATIONS + savefile_identifier = PREFERENCE_CHARACTER + savefile_key = "ic_chat_color" + +/datum/preference/color/chat_color/apply_to_human(mob/living/carbon/human/target, value) + target.apply_preference_chat_color(value) + return + +/datum/preference/color/chat_color/deserialize(input, datum/preferences/preferences) + return process_chat_color(sanitize_hexcolor(input)) + +/datum/preference/color/chat_color/create_default_value() + return process_chat_color(random_color()) + +/datum/preference/color/chat_color/serialize(input) + return process_chat_color(sanitize_hexcolor(input)) + +/mob/living/carbon/human/proc/apply_preference_chat_color(value) + if(isnull(value)) + return FALSE + + chat_color = process_chat_color(value, sat_shift = 1, lum_shift = 1) + chat_color_darkened = process_chat_color(value, sat_shift = 0.85, lum_shift = 0.85) + chat_color_name = name + return TRUE + +#define CHAT_COLOR_NORMAL 1 +#define CHAT_COLOR_DARKENED 2 + +/// Get the mob's chat color by looking up their name in the cached list, if no match is found default to colorize_string(). +/datum/chatmessage/proc/get_chat_color_string(name, darkened) + var/chat_color_strings = GLOB.chat_colors_by_mob_name[name] + if(chat_color_strings) + return darkened ? chat_color_strings[CHAT_COLOR_DARKENED] : chat_color_strings[CHAT_COLOR_NORMAL] + if(darkened) + return colorize_string(name, 0.85, 0.85) + + return colorize_string(name) + +#undef CHAT_COLOR_NORMAL +#undef CHAT_COLOR_DARKENED +#define CM_COLOR_SAT_MIN 0 +#define CM_COLOR_SAT_MAX 1 +#define CM_COLOR_LUM_MIN 0.35 +#define CM_COLOR_LUM_MAX 1 + +/** + * Converts a given color to comply within a smaller subset of colors to be used in runechat. + * If a color is outside the min/max saturation or value/lum, it will be set at the nearest + * value that passes validation. + * + * Arguments: + * * color - The color to process + * * sat_shift - A value between 0 and 1 that will be multiplied against the saturation + * * lum_shift - A value between 0 and 1 that will be multiplied against the luminescence + */ +/proc/process_chat_color(color, sat_shift = 1, lum_shift = 1) + if(isnull(color)) + return + + var/input_hsv = RGBtoHSV(color) + var/list/split_hsv = ReadHSV(input_hsv) + var/split_h = split_hsv[1] + var/split_s = split_hsv[2] + var/split_v = split_hsv[3] + var/processed_s = clamp(split_s, CM_COLOR_SAT_MIN * 255, CM_COLOR_SAT_MAX * 255) + var/processed_v = clamp(split_v, CM_COLOR_LUM_MIN * 255, CM_COLOR_LUM_MAX * 255) + // adjust for shifts + processed_s *= clamp(sat_shift, 0, 1) + processed_v *= clamp(lum_shift, 0, 1) + var/processed_hsv = hsv(split_h, processed_s, processed_v) + var/processed_rgb = HSVtoRGB(processed_hsv) + + return processed_rgb + +#undef CM_COLOR_LUM_MAX +#undef CM_COLOR_LUM_MIN +#undef CM_COLOR_SAT_MAX +#undef CM_COLOR_SAT_MIN diff --git a/modular_nova/modules/customization/game/objects/items/devices/ttsdevice.dm b/modular_nova/modules/customization/game/objects/items/devices/ttsdevice.dm index fa7bf87bf9e..9ff52756144 100644 --- a/modular_nova/modules/customization/game/objects/items/devices/ttsdevice.dm +++ b/modular_nova/modules/customization/game/objects/items/devices/ttsdevice.dm @@ -18,7 +18,13 @@ user.balloon_alert_to_viewers("stops typing", "stopped typing") playsound(src, 'modular_nova/master_files/sound/items/tts/stopped_type.ogg', 50, TRUE) return - src.say(str) + + chat_color_name = name + chat_color = user.client?.prefs?.read_preference(/datum/preference/color/chat_color) + if(chat_color) + chat_color_darkened = process_chat_color(chat_color, sat_shift = 0.85, lum_shift = 0.85) + + say(str) str = null /obj/item/ttsdevice/CtrlClick(mob/living/user) diff --git a/modular_nova/modules/ghostcafe/code/ghost_role_spawners.dm b/modular_nova/modules/ghostcafe/code/ghost_role_spawners.dm index 61152a8abb7..1c4f18e4312 100644 --- a/modular_nova/modules/ghostcafe/code/ghost_role_spawners.dm +++ b/modular_nova/modules/ghostcafe/code/ghost_role_spawners.dm @@ -24,6 +24,7 @@ if(new_spawn.client) new_spawn.custom_name = null new_spawn.updatename(new_spawn.client) + new_spawn.transfer_brain_pref(new_spawn.client) new_spawn.gender = NEUTER var/area/A = get_area(src) //new_spawn.AddElement(/datum/element/ghost_role_eligibility, free_ghosting = TRUE) SKYRAT PORT -- Needs to be completely rewritten diff --git a/modular_nova/modules/synths/code/bodyparts/silicon_alt_brains.dm b/modular_nova/modules/synths/code/bodyparts/silicon_alt_brains.dm index 76c4fbe3243..4e87f4f3d95 100644 --- a/modular_nova/modules/synths/code/bodyparts/silicon_alt_brains.dm +++ b/modular_nova/modules/synths/code/bodyparts/silicon_alt_brains.dm @@ -35,15 +35,37 @@ /mob/living/silicon/robot/Initialize(mapload) . = ..() // Intentionally set like this, because people have different lore for their cyborgs, and there's no real non-invasive way to print posibrains that match. - RegisterSignal(src, COMSIG_MOB_MIND_TRANSFERRED_INTO, PROC_REF(update_brain_type)) + RegisterSignal(src, COMSIG_MOB_MIND_TRANSFERRED_INTO, PROC_REF(on_mob_mind_transferred_into)) -/// Sets the MMI type for a cyborg, if applicable. -/mob/living/silicon/robot/proc/update_brain_type() - var/obj/item/mmi/new_mmi = prefs_get_brain_to_use(client?.prefs?.read_preference(/datum/preference/choiced/brain_type), TRUE) - if(!mmi || !new_mmi || new_mmi == mmi.type) +/mob/living/silicon/proc/on_mob_mind_transferred_into(mob/living/silicon/robot) + SIGNAL_HANDLER + + if(isnull(client)) return - new_mmi = new new_mmi() + transfer_silicon_prefs(client) + +/// Transfers the chat color pref to the silicon mob +/mob/living/silicon/proc/transfer_chat_color_pref(client/player_client) + // Read the chat color from prefs and apply it to the mob. Cache it as well in case of any voice changing shenanigans. + var/chat_color_pref = player_client?.prefs?.read_preference(/datum/preference/color/chat_color) + if(chat_color_pref && chat_color != chat_color_pref) + var/chat_color_pref_darkened = process_chat_color(chat_color_pref, sat_shift = 0.85, lum_shift = 0.85) + chat_color = chat_color_pref + chat_color_darkened = chat_color_pref_darkened + GLOB.chat_colors_by_mob_name[real_name] = list(chat_color, chat_color_darkened) + +/// Transfers the brain type pref to the silicon mob +/mob/living/silicon/proc/transfer_brain_pref(client/player_client) + return + +// This is only implemented for cyborgs at the moment. AI has their own weird way of doing things. +/mob/living/silicon/robot/transfer_brain_pref(client/player_client) + // Read the brain type from prefs and apply it to the mob. + var/obj/item/mmi/new_mmi = prefs_get_brain_to_use(player_client?.prefs?.read_preference(/datum/preference/choiced/brain_type), TRUE) + if(!mmi || !new_mmi || new_mmi == mmi.type) + return + new_mmi = new new_mmi(src) // Probably shitcode, but silicon code is spaghetti as fuck. new_mmi.brain = new /obj/item/organ/internal/brain(new_mmi) @@ -59,3 +81,21 @@ QDEL_NULL(mmi) mmi = new_mmi + +/// Sets the MMI type for a cyborg/AI, if applicable, as well as the chat color +/mob/living/silicon/proc/transfer_silicon_prefs(client/player_client) + transfer_chat_color_pref(player_client) + transfer_brain_pref(player_client) + +/mob/living/silicon/robot/apply_prefs_job(client/player_client, datum/job/job) + . = ..() + transfer_silicon_prefs(player_client) + +/mob/living/silicon/ai/apply_prefs_job(client/player_client, datum/job/job) + . = ..() + transfer_silicon_prefs(player_client) + +// hooks into this proc in order to make sure chat color prefs get applied +/mob/living/silicon/robot/updatename(client/player_client) + . = ..() + transfer_chat_color_pref(player_client) diff --git a/tgstation.dme b/tgstation.dme index 1b2cf239d6f..81e3c7f2ab8 100644 --- a/tgstation.dme +++ b/tgstation.dme @@ -6776,6 +6776,7 @@ #include "modular_nova\modules\central_command_module\code\obj\wall.dm" #include "modular_nova\modules\chadian\code\chadian.dm" #include "modular_nova\modules\chaplain\code\mortis.dm" +#include "modular_nova\modules\chat_colors\code\chat_color.dm" #include "modular_nova\modules\clock_cult\code\antagonist.dm" #include "modular_nova\modules\clock_cult\code\area.dm" #include "modular_nova\modules\clock_cult\code\globals.dm" diff --git a/tgui/packages/tgui/interfaces/PreferencesMenu/preferences/features/character_preferences/skyrat/chat_color.tsx b/tgui/packages/tgui/interfaces/PreferencesMenu/preferences/features/character_preferences/skyrat/chat_color.tsx new file mode 100644 index 00000000000..a5c0253a9e3 --- /dev/null +++ b/tgui/packages/tgui/interfaces/PreferencesMenu/preferences/features/character_preferences/skyrat/chat_color.tsx @@ -0,0 +1,6 @@ +import { Feature, FeatureColorInput } from '../../base'; + +export const ic_chat_color: Feature = { + name: 'Chat Message Color', + component: FeatureColorInput, +};