Merge remote-tracking branch 'upstream/master'

This commit is contained in:
Mosley
2025-04-24 17:03:15 -05:00
402 changed files with 2262 additions and 1524 deletions
+2 -2
View File
@@ -49,7 +49,7 @@
return
to_chat(user, "<span class='boldannounce'>You start skimming through [src], and suddenly your mind is filled with codewords and responses.</span>")
user.grant_language(/datum/language/codespeak, TRUE, TRUE, LANGUAGE_MIND)
user.grant_language(/datum/language/codespeak, source = LANGUAGE_MIND)
use_charge(user)
@@ -68,7 +68,7 @@
M.visible_message("<span class='danger'>[user] beats [M] over the head with [src]!</span>", "<span class='userdanger'>[user] beats you over the head with [src]!</span>", "<span class='italics'>You hear smacking.</span>")
else
M.visible_message("<span class='notice'>[user] teaches [M] by beating [M.p_them()] over the head with [src]!</span>", "<span class='boldnotice'>As [user] hits you with [src], codewords and responses flow through your mind.</span>", "<span class='italics'>You hear smacking.</span>")
M.grant_language(/datum/language/codespeak, TRUE, TRUE, LANGUAGE_MIND)
M.grant_language(/datum/language/codespeak, source = LANGUAGE_MIND)
use_charge(user)
/obj/item/codespeak_manual/proc/use_charge(mob/user)
+133 -97
View File
@@ -1,23 +1,28 @@
/*!Language holders will either exist in an atom/movable or a mind. Creation of language holders happens
/*!Language holders will either exist in an atom/movable. Creation of language holders happens
automatically when they are needed, for example when something tries to speak.
Where a mind is available, the mind language holder will be the one "in charge". The mind holder
will update its languages based on the atom holder, and will get updated as part of
transformations and other events that cause new languages to become available.
Every language holder has three lists of languages (and sources for each of them):
- understood_languages
- spoken_languages
- blocked_languages
Understood languages let you understand them, spoken languages lets you speak them
(if your tongue is compatible), and blocked languages will let you do neither no matter
what the source of the language is.
Language holders are designed to mostly only ever require the use the helpers in atom/movable
to achieve your goals, but it is also possible to work on them directly if needed. Any adding
and removing of languages and sources should only happen through the procs, as directly changing
these will mess something up somewhere down the line.
All atom movables have the initial_language_holder var which allows you to set the default language
holder to create. For example, /datum/language_holder/alien will give you xenocommon and a block for
galactic common. Human species also have a default language holder var that will be updated on
species change, initial_species_holder.
Key procs
* [grant_language](atom/movable.html#proc/grant_language)
* [remove_language](atom/movable.html#proc/remove_language)
@@ -28,83 +33,86 @@ Key procs
* [has_language](atom/movable.html#proc/has_language)
* [can_speak_language](atom/movable.html#proc/can_speak_language)
* [get_selected_language](atom/movable.html#proc/get_selected_language)
* [update_atom_languages](atom/movable.html#proc/update_atom_languages)
*/
/datum/language_holder
/// Understood languages.
var/list/understood_languages = list(/datum/language/common = list(LANGUAGE_MIND))
/// A list of languages that can be spoken. Tongue organ may also set limits beyond this list.
/// Lazyassoclist of all understood languages
var/list/understood_languages = list(/datum/language/common = list(LANGUAGE_ATOM))
/// Lazyassoclist of languages that can be spoken.
/// Tongue organ may also set limits beyond this list.
var/list/spoken_languages = list(/datum/language/common = list(LANGUAGE_ATOM))
/// A list of blocked languages. Used to prevent understanding and speaking certain languages, ie for certain mobs, mutations etc.
var/list/blocked_languages = list()
/// If true, overrides tongue limitations.
/// Lazyassoclist of blocked languages.
/// Used to prevent understanding and speaking certain languages, ie for certain mobs, mutations etc.
var/list/blocked_languages
/// If true, overrides tongue aforementioned limitations.
var/omnitongue = FALSE
/// Handles displaying the language menu UI.
var/datum/language_menu/language_menu
/// Currently spoken language
var/selected_language
/// Tracks the entity that owns the holder.
var/owner
var/atom/movable/owner
/// Initializes, and copies in the languages from the current atom if available.
/datum/language_holder/New(_owner)
owner = _owner
if(istype(owner, /datum/mind))
var/datum/mind/M = owner
if(M.current)
update_atom_languages(M.current)
get_selected_language()
/datum/language_holder/New(atom/new_owner)
if(new_owner)
if(QDELETED(new_owner))
CRASH("Langauge holder added to a qdeleting thing, what the fuck [text_ref(new_owner)]")
if(!ismovable(new_owner))
CRASH("Language holder being added to a non-movable thing, this is invalid (was: [new_owner] / [new_owner.type])")
owner = new_owner
// If we have an owner, we'll set a default selected language
if(owner)
get_selected_language()
/datum/language_holder/Destroy()
QDEL_NULL(language_menu)
owner = null
return ..()
/// Grants the supplied language.
/datum/language_holder/proc/grant_language(language, understood = TRUE, spoken = TRUE, source = LANGUAGE_MIND)
if(understood)
if(!understood_languages[language])
understood_languages[language] = list()
understood_languages[language] |= source
/datum/language_holder/proc/grant_language(language, language_flags = ALL, source = LANGUAGE_MIND)
if(language_flags & UNDERSTOOD_LANGUAGE)
LAZYORASSOCLIST(understood_languages, language, source)
. = TRUE
if(spoken)
if(!spoken_languages[language])
spoken_languages[language] = list()
spoken_languages[language] |= source
if(language_flags & SPOKEN_LANGUAGE)
LAZYORASSOCLIST(spoken_languages, language, source)
. = TRUE
return .
/// Grants every language to understood and spoken, and gives omnitongue.
/datum/language_holder/proc/grant_all_languages(understood = TRUE, spoken = TRUE, grant_omnitongue = TRUE, source = LANGUAGE_MIND)
/datum/language_holder/proc/grant_all_languages(language_flags = ALL, grant_omnitongue = TRUE, source = LANGUAGE_MIND)
for(var/language in GLOB.all_languages)
grant_language(language, understood, spoken, source)
if(grant_omnitongue) // Overrides tongue limitations.
grant_language(language, language_flags, source)
if(grant_omnitongue) // Overrides tongue limitations.
omnitongue = TRUE
return TRUE
/// Removes a single language or source, removing all sources returns the pre-removal state of the language.
/datum/language_holder/proc/remove_language(language, understood = TRUE, spoken = TRUE, source = LANGUAGE_ALL)
if(understood && understood_languages[language])
/datum/language_holder/proc/remove_language(language, language_flags = ALL, source = LANGUAGE_ALL)
if(language_flags & UNDERSTOOD_LANGUAGE)
if(source == LANGUAGE_ALL)
understood_languages -= language
LAZYREMOVE(understood_languages, language)
else
understood_languages[language] -= source
if(!length(understood_languages[language]))
understood_languages -= language
LAZYREMOVEASSOC(understood_languages, language, source)
. = TRUE
if(spoken && spoken_languages[language])
if(language_flags & SPOKEN_LANGUAGE)
if(source == LANGUAGE_ALL)
spoken_languages -= language
LAZYREMOVE(spoken_languages, language)
else
spoken_languages[language] -= source
if(!length(spoken_languages[language]))
spoken_languages -= language
LAZYREMOVEASSOC(spoken_languages, language, source)
. = TRUE
return .
/// Removes every language and optionally sets omnitongue false. If a non default source is supplied, only removes that source.
/datum/language_holder/proc/remove_all_languages(source = LANGUAGE_ALL, remove_omnitongue = FALSE)
for(var/language in GLOB.all_languages)
remove_language(language, TRUE, TRUE, source)
remove_language(language, ALL, source)
if(remove_omnitongue)
omnitongue = FALSE
return TRUE
@@ -113,41 +121,41 @@ Key procs
/datum/language_holder/proc/add_blocked_language(languages, source = LANGUAGE_MIND)
if(!islist(languages))
languages = list(languages)
for(var/language in languages)
if(!blocked_languages[language])
blocked_languages[language] = list()
blocked_languages[language] |= source
LAZYORASSOCLIST(blocked_languages, language, source)
return TRUE
/// Removes a single language or list of languages from the blocked language list.
/datum/language_holder/proc/remove_blocked_language(languages, source = LANGUAGE_MIND)
if(!islist(languages))
languages = list(languages)
for(var/language in languages)
if(blocked_languages[language])
if(source == LANGUAGE_ALL)
blocked_languages -= language
else
blocked_languages[language] -= source
if(!length(blocked_languages[language]))
blocked_languages -= language
if(source == LANGUAGE_ALL)
LAZYREMOVE(blocked_languages, language)
else
LAZYREMOVEASSOC(blocked_languages, language, source)
return TRUE
/// Checks if you have the language. If spoken is true, only checks if you can speak the language.
/datum/language_holder/proc/has_language(language, spoken = FALSE)
/// Checks if you have the language passed.
/datum/language_holder/proc/has_language(language, flag_to_check = UNDERSTOOD_LANGUAGE)
if(language in blocked_languages)
return FALSE
if(spoken)
return language in spoken_languages
return language in understood_languages
var/list/langs_to_check = list()
if(flag_to_check & SPOKEN_LANGUAGE)
langs_to_check |= spoken_languages
if(flag_to_check & UNDERSTOOD_LANGUAGE)
langs_to_check |= understood_languages
return language in langs_to_check
/// Checks if you can speak the language. Tongue limitations should be supplied as an argument.
/datum/language_holder/proc/can_speak_language(language)
var/atom/movable/ouratom = get_atom()
var/tongue = ouratom.could_speak_language(language)
if((omnitongue || tongue) && has_language(language, TRUE))
return TRUE
return FALSE
var/can_speak_language_path = omnitongue || owner.could_speak_language(language)
return (can_speak_language_path && has_language(language, SPOKEN_LANGUAGE))
/// Returns selected language if it can be spoken, or decides, sets and returns a new selected language if possible.
/datum/language_holder/proc/get_selected_language()
@@ -172,59 +180,79 @@ Key procs
/datum/language_holder/proc/get_random_spoken_language()
return pick(spoken_languages)
/// Gets a random spoken language, trying to get a non-common language.
/datum/language_holder/proc/get_random_spoken_uncommon_language()
var/list/languages_minus_common = assoc_to_keys(spoken_languages) - /datum/language/common
// They have a language other than common
if(length(languages_minus_common))
return pick(languages_minus_common)
// They can only speak common, oh well.
else
return /datum/language/common
/// Opens a language menu reading from the language holder.
/datum/language_holder/proc/open_language_menu(mob/user)
if(!language_menu)
language_menu = new (src)
language_menu.ui_interact(user)
/// Gets the atom, since we some times need to check if the tongue has limitations.
/datum/language_holder/proc/get_atom()
if(owner)
if(istype(owner, /datum/mind))
var/datum/mind/M = owner
return M.current
return owner
return FALSE
/// Empties out the atom specific languages and updates them according to the supplied atoms language holder.
/datum/language_holder/proc/update_atom_languages(atom/movable/thing)
var/datum/language_holder/from_atom = thing.get_language_holder(FALSE) //Gets the atoms language holder
if(from_atom == src) //This could happen if called on an atom without a mind.
return FALSE
for(var/language in understood_languages)
remove_language(language, TRUE, FALSE, LANGUAGE_ATOM)
for(var/language in spoken_languages)
remove_language(language, FALSE, TRUE, LANGUAGE_ATOM)
for(var/language in blocked_languages)
remove_blocked_language(language, LANGUAGE_ATOM)
copy_languages(from_atom)
get_selected_language()
return TRUE
/// Copies all languages from the supplied atom/language holder. Source should be overridden when you
/// do not want the language overwritten by later atom updates or want to avoid blocked languages.
/datum/language_holder/proc/copy_languages(var/datum/language_holder/from_holder, source_override)
if(source_override) //No blocked languages here, for now only used by ling absorb.
/datum/language_holder/proc/copy_languages(datum/language_holder/from_holder, source_override)
if(source_override) //No blocked languages here, for now only used by ling absorb.
for(var/language in from_holder.understood_languages)
grant_language(language, TRUE, FALSE, source_override)
grant_language(language, UNDERSTOOD_LANGUAGE, source_override)
for(var/language in from_holder.spoken_languages)
grant_language(language, FALSE, TRUE, source_override)
grant_language(language, SPOKEN_LANGUAGE, source_override)
else
for(var/language in from_holder.understood_languages)
grant_language(language, TRUE, FALSE, from_holder.understood_languages[language])
grant_language(language, UNDERSTOOD_LANGUAGE, from_holder.understood_languages[language])
for(var/language in from_holder.spoken_languages)
grant_language(language, FALSE, TRUE, from_holder.spoken_languages[language])
grant_language(language, SPOKEN_LANGUAGE, from_holder.spoken_languages[language])
for(var/language in from_holder.blocked_languages)
add_blocked_language(language, from_holder.blocked_languages[language])
return TRUE
/// Transfers all mind languages to the supplied language holder.
/datum/language_holder/proc/transfer_mind_languages(datum/language_holder/to_holder)
for(var/language in understood_languages)
if(LANGUAGE_MIND in understood_languages[language])
remove_language(language, UNDERSTOOD_LANGUAGE, LANGUAGE_MIND)
to_holder.grant_language(language, UNDERSTOOD_LANGUAGE, LANGUAGE_MIND)
for(var/language in spoken_languages)
if(LANGUAGE_MIND in spoken_languages[language])
remove_language(language, SPOKEN_LANGUAGE, LANGUAGE_MIND)
to_holder.grant_language(language, SPOKEN_LANGUAGE, LANGUAGE_MIND)
for(var/language in blocked_languages)
if(LANGUAGE_MIND in blocked_languages[language])
remove_blocked_language(language, LANGUAGE_MIND)
to_holder.add_blocked_language(language, LANGUAGE_MIND)
//************************************************
//* Specific language holders *
//* Use atom language sources only. *
//************************************************/
if(owner)
get_selected_language()
if(to_holder.owner)
to_holder.get_selected_language()
/// A global assoc list containing prototypes of all language holders
/// [Language holder typepath] to [language holder instance]
/// Used for easy reference of what can speak what without needing to constantly recreate language holders.
GLOBAL_LIST_INIT(prototype_language_holders, init_language_holder_prototypes())
/// Inits the global list of language holder prototypes.
/proc/init_language_holder_prototypes()
var/list/prototypes = list()
for(var/holdertype in typesof(/datum/language_holder))
prototypes[holdertype] = new holdertype()
return prototypes
/*
* Specific language holders presets
*
* Prefer to use [LANGUGAE_ATOM]. Atom languages will stick through species changes but not mindswaps.
*/
/datum/language_holder/alien
@@ -252,6 +280,10 @@ Key procs
/datum/language_holder/drone/syndicate
blocked_languages = null
/datum/language_holder/human_basic
understood_languages = list(/datum/language/common = list(LANGUAGE_ATOM))
spoken_languages = list(/datum/language/common = list(LANGUAGE_ATOM))
/datum/language_holder/dwarf
understood_languages = list(/datum/language/common = list(LANGUAGE_ATOM),
/datum/language/dwarf = list(LANGUAGE_ATOM))
@@ -353,8 +385,12 @@ Key procs
/datum/language/arachnid = list(LANGUAGE_ATOM))
/datum/language_holder/empty
understood_languages = list()
spoken_languages = list()
understood_languages = null
spoken_languages = null
/datum/language_holder/universal
understood_languages = null
spoken_languages = null
/datum/language_holder/universal/New()
..()
+59 -60
View File
@@ -20,109 +20,108 @@
/datum/language_menu/ui_data(mob/user)
var/list/data = list()
var/atom/movable/AM = language_holder.get_atom()
if(isliving(AM))
data["is_living"] = TRUE
else
data["is_living"] = FALSE
var/atom/movable/speaker = language_holder.owner
data["is_living"] = isliving(speaker)
data["languages"] = list()
for(var/lang in GLOB.all_languages)
var/result = language_holder.has_language(lang) || language_holder.has_language(lang, TRUE)
for(var/datum/language/language as anything in GLOB.all_languages)
var/result = language_holder.has_language(language) || language_holder.has_language(language, SPOKEN_LANGUAGE)
if(!result)
continue
var/datum/language/language = lang
var/list/L = list()
var/list/lang_data = list()
L["name"] = initial(language.name)
L["desc"] = initial(language.desc)
L["key"] = initial(language.key)
L["is_default"] = (language == language_holder.selected_language)
if(AM)
L["can_speak"] = AM.can_speak_language(language)
L["can_understand"] = AM.has_language(language)
lang_data["name"] = initial(language.name)
lang_data["desc"] = initial(language.desc)
lang_data["key"] = initial(language.key)
lang_data["is_default"] = (language == language_holder.selected_language)
if(speaker)
lang_data["can_speak"] = speaker.can_speak_language(language)
lang_data["can_understand"] = speaker.has_language(language)
data["languages"] += list(L)
UNTYPED_LIST_ADD(data["languages"], lang_data)
if(check_rights_for(user.client, R_ADMIN) || isobserver(AM))
if(check_rights_for(user.client, R_ADMIN) || isobserver(speaker))
data["admin_mode"] = TRUE
data["omnitongue"] = language_holder.omnitongue
data["unknown_languages"] = list()
for(var/lang in GLOB.all_languages)
if(language_holder.has_language(lang) || language_holder.has_language(lang, TRUE))
for(var/datum/language/language as anything in GLOB.all_languages)
if(language_holder.has_language(language) || language_holder.has_language(language, SPOKEN_LANGUAGE))
continue
var/datum/language/language = lang
var/list/L = list()
var/list/lang_data = list()
L["name"] = initial(language.name)
L["desc"] = initial(language.desc)
L["key"] = initial(language.key)
lang_data["name"] = initial(language.name)
lang_data["desc"] = initial(language.desc)
lang_data["key"] = initial(language.key)
UNTYPED_LIST_ADD(data["unknown_languages"], lang_data)
data["unknown_languages"] += list(L)
return data
/datum/language_menu/ui_act(action, params)
if(..())
. = ..()
if(.)
return
var/mob/user = usr
var/atom/movable/AM = language_holder.get_atom()
var/atom/movable/speaker = language_holder.owner
var/is_admin = check_rights_for(user.client, R_ADMIN)
var/language_name = params["language_name"]
var/datum/language/language_datum
for(var/lang in GLOB.all_languages)
var/datum/language/language = lang
for(var/datum/language/language as anything in GLOB.all_languages)
if(language_name == initial(language.name))
language_datum = language
var/is_admin = check_rights_for(user.client, R_ADMIN)
switch(action)
if("select_default")
if(language_datum && AM.can_speak_language(language_datum))
if(language_datum && speaker.can_speak_language(language_datum))
language_holder.selected_language = language_datum
. = TRUE
if("grant_language")
if((is_admin || isobserver(AM)) && language_datum)
if((is_admin || isobserver(speaker)) && language_datum)
var/list/choices = list("Only Spoken", "Only Understood", "Both")
var/choice = input(user,"How do you want to add this language?","[language_datum]",null) as null|anything in choices
var/spoken = FALSE
var/understood = FALSE
var/choice = tgui_input_list(user, "How do you want to add this language?", "[language_datum]", choices)
if(isnull(choice))
return
var/adding_flags = NONE
switch(choice)
if("Only Spoken")
spoken = TRUE
adding_flags |= SPOKEN_LANGUAGE
if("Only Understood")
understood = TRUE
adding_flags |= UNDERSTOOD_LANGUAGE
if("Both")
spoken = TRUE
understood = TRUE
language_holder.grant_language(language_datum, understood, spoken)
adding_flags |= ALL
if(LAZYACCESS(language_holder.blocked_languages, language_datum))
choice = tgui_alert(user, "Do you want to lift the blockage that's also preventing the language to be spoken or understood?", "[language_datum]", list("Yes", "No"))
if(choice == "Yes")
language_holder.remove_blocked_language(language_datum, LANGUAGE_ALL)
language_holder.grant_language(language_datum, adding_flags)
if(is_admin)
message_admins("[key_name_admin(user)] granted the [language_name] language to [key_name_admin(AM)].")
log_admin("[key_name(user)] granted the language [language_name] to [key_name(AM)].")
message_admins("[key_name_admin(user)] granted the [language_name] language to [key_name_admin(speaker)].")
log_admin("[key_name(user)] granted the language [language_name] to [key_name(speaker)].")
. = TRUE
if("remove_language")
if((is_admin || isobserver(AM)) && language_datum)
if((is_admin || isobserver(speaker)) && language_datum)
var/list/choices = list("Only Spoken", "Only Understood", "Both")
var/choice = input(user,"Which part do you wish to remove?","[language_datum]",null) as null|anything in choices
var/spoken = FALSE
var/understood = FALSE
var/choice = tgui_input_list(user, "Which part do you wish to remove?", "[language_datum]", choices)
if(isnull(choice))
return
var/removing_flags = NONE
switch(choice)
if("Only Spoken")
spoken = TRUE
removing_flags |= SPOKEN_LANGUAGE
if("Only Understood")
understood = TRUE
removing_flags |= UNDERSTOOD_LANGUAGE
if("Both")
spoken = TRUE
understood = TRUE
language_holder.remove_language(language_datum, understood, spoken)
removing_flags |= ALL
language_holder.remove_language(language_datum, removing_flags)
if(is_admin)
message_admins("[key_name_admin(user)] removed the [language_name] language to [key_name_admin(AM)].")
log_admin("[key_name(user)] removed the language [language_name] to [key_name(AM)].")
message_admins("[key_name_admin(user)] removed the [language_name] language to [key_name_admin(speaker)].")
log_admin("[key_name(user)] removed the language [language_name] to [key_name(speaker)].")
. = TRUE
if("toggle_omnitongue")
if(is_admin || isobserver(AM))
if(is_admin || isobserver(speaker))
language_holder.omnitongue = !language_holder.omnitongue
if(is_admin)
message_admins("[key_name_admin(user)] [language_holder.omnitongue ? "enabled" : "disabled"] the ability to speak all languages (that they know) of [key_name_admin(AM)].")
log_admin("[key_name(user)] [language_holder.omnitongue ? "enabled" : "disabled"] the ability to speak all languages (that_they know) of [key_name(AM)].")
message_admins("[key_name_admin(user)] [language_holder.omnitongue ? "enabled" : "disabled"] the ability to speak all languages (that they know) of [key_name_admin(speaker)].")
log_admin("[key_name(user)] [language_holder.omnitongue ? "enabled" : "disabled"] the ability to speak all languages (that_they know) of [key_name(speaker)].")
. = TRUE