diff --git a/code/__DEFINES/language.dm b/code/__DEFINES/language.dm index 8f9c76299bc..7b105e09338 100644 --- a/code/__DEFINES/language.dm +++ b/code/__DEFINES/language.dm @@ -4,10 +4,22 @@ #define LANGUAGE_HIDE_ICON_IF_NOT_UNDERSTOOD 8 // LANGUAGE SOURCE DEFINES -#define LANGUAGE_ALL "all" // For use in full removal only. -#define LANGUAGE_ATOM "atom" -#define LANGUAGE_MIND "mind" +/// For use in full removal only. +#define LANGUAGE_ALL "all" +// Generic language sources. +/// Language is linked to the movable directly. +#define LANGUAGE_ATOM "atom" +/// Language is linked to the mob's mind. +/// If a mind transfer happens, language follows. +#define LANGUAGE_MIND "mind" +/// Language is linked to the mob's species. +/// If a species change happens, language goes away. +/// If applied to a non-human (no species) atom, this is effectively the same as [LANGUAGE_ATOM]. +#define LANGUAGE_SPECIES "species" + +// More specific language sources. +// Only ever goes away when dismissed directly. #define LANGUAGE_ABSORB "absorb" #define LANGUAGE_APHASIA "aphasia" #define LANGUAGE_CTF "ctf" @@ -24,3 +36,9 @@ #define LANGUAGE_VOICECHANGE "voicechange" #define LANGUAGE_RADIOKEY "radiokey" #define LANGUAGE_BABEL "babel" + +// Language flags. Used in granting and removing languages. +/// This language can be spoken. +#define SPOKEN_LANGUAGE (1<<0) +/// This language can be understood. +#define UNDERSTOOD_LANGUAGE (1<<1) diff --git a/code/_onclick/hud/screen_objects.dm b/code/_onclick/hud/screen_objects.dm index f3de202f01c..a53c8e446c7 100644 --- a/code/_onclick/hud/screen_objects.dm +++ b/code/_onclick/hud/screen_objects.dm @@ -116,9 +116,7 @@ screen_loc = ui_language_menu /atom/movable/screen/language_menu/Click() - var/mob/M = usr - var/datum/language_holder/H = M.get_language_holder() - H.open_language_menu(usr) + usr.get_language_holder().open_language_menu(usr) /atom/movable/screen/inventory /// The identifier for the slot. It has nothing to do with ID cards. diff --git a/code/controllers/subsystem/tts.dm b/code/controllers/subsystem/tts.dm index 2db93f3749f..3bb20ad006f 100644 --- a/code/controllers/subsystem/tts.dm +++ b/code/controllers/subsystem/tts.dm @@ -116,7 +116,7 @@ SUBSYSTEM_DEF(tts) sound_volume = sound_volume * (volume_to_play_at / 100) var/datum/language_holder/holder = listening_mob.get_language_holder() var/audio_to_use = use_blips ? audio_blips : audio - if(!holder.has_language(language, spoken = FALSE)) + if(!holder.has_language(language)) continue if(get_dist(listening_mob, turf_source) <= range) listening_mob.playsound_local( diff --git a/code/datums/actions/mobs/language_menu.dm b/code/datums/actions/mobs/language_menu.dm deleted file mode 100644 index bcfcb5437a2..00000000000 --- a/code/datums/actions/mobs/language_menu.dm +++ /dev/null @@ -1,13 +0,0 @@ -/datum/action/language_menu - name = "Language Menu" - desc = "Open the language menu to review your languages, their keys, and select your default language." - button_icon_state = "language_menu" - check_flags = NONE - -/datum/action/language_menu/Trigger(trigger_flags) - . = ..() - if(!.) - return - - var/datum/language_holder/owner_holder = owner.get_language_holder() - owner_holder.open_language_menu(usr) diff --git a/code/datums/brain_damage/imaginary_friend.dm b/code/datums/brain_damage/imaginary_friend.dm index 7b8b1d37590..d507b4f9139 100644 --- a/code/datums/brain_damage/imaginary_friend.dm +++ b/code/datums/brain_damage/imaginary_friend.dm @@ -237,7 +237,7 @@ var/rendered = "[span_name("[name]")] [quoted_message]" var/dead_rendered = "[span_name("[name] (Imaginary friend of [owner])")] [quoted_message]" - var/language = message_language || owner.language_holder.get_selected_language() + var/language = message_language || owner.get_selected_language() Hear(rendered, src, language, message, null, spans, message_mods) // We always hear what we say var/group = owner.imaginary_group - src // The people in our group don't, so we have to exclude ourselves not to hear twice for(var/mob/person in group) diff --git a/code/datums/brain_damage/severe.dm b/code/datums/brain_damage/severe.dm index 0174e517756..331a76aa130 100644 --- a/code/datums/brain_damage/severe.dm +++ b/code/datums/brain_damage/severe.dm @@ -28,14 +28,14 @@ lose_text = span_notice("You suddenly remember how languages work.") /datum/brain_trauma/severe/aphasia/on_gain() - owner.add_blocked_language(subtypesof(/datum/language/) - /datum/language/aphasia, LANGUAGE_APHASIA) - owner.grant_language(/datum/language/aphasia, TRUE, TRUE, LANGUAGE_APHASIA) + owner.add_blocked_language(subtypesof(/datum/language) - /datum/language/aphasia, LANGUAGE_APHASIA) + owner.grant_language(/datum/language/aphasia, source = LANGUAGE_APHASIA) ..() /datum/brain_trauma/severe/aphasia/on_lose() if(!QDELING(owner)) - owner.remove_blocked_language(subtypesof(/datum/language/), LANGUAGE_APHASIA) - owner.remove_language(/datum/language/aphasia, TRUE, TRUE, LANGUAGE_APHASIA) + owner.remove_blocked_language(subtypesof(/datum/language), LANGUAGE_APHASIA) + owner.remove_language(/datum/language/aphasia, LANGUAGE_APHASIA) ..() diff --git a/code/datums/components/spirit_holding.dm b/code/datums/components/spirit_holding.dm index b5e99ad4822..c5d14d95384 100644 --- a/code/datums/components/spirit_holding.dm +++ b/code/datums/components/spirit_holding.dm @@ -72,8 +72,7 @@ bound_spirit.fully_replace_character_name(null, "The spirit of [parent]") bound_spirit.status_flags |= GODMODE bound_spirit.copy_languages(awakener, LANGUAGE_MASTER) //Make sure the sword can understand and communicate with the awakener. - bound_spirit.update_atom_languages() - bound_spirit.grant_all_languages(FALSE, FALSE, TRUE) //Grants omnitongue + bound_spirit.get_language_holder().omnitongue = TRUE //Grants omnitongue //Add new signals for parent and stop attempting to awaken RegisterSignal(parent, COMSIG_ATOM_RELAYMOVE, PROC_REF(block_buckle_message)) diff --git a/code/datums/diseases/advance/symptoms/voice_change.dm b/code/datums/diseases/advance/symptoms/voice_change.dm index 2f1a65b1a71..7e287bbeb9f 100644 --- a/code/datums/diseases/advance/symptoms/voice_change.dm +++ b/code/datums/diseases/advance/symptoms/voice_change.dm @@ -57,7 +57,7 @@ if(scramble_language && !current_language) // Last part prevents rerolling language with small amounts of cure. current_language = pick(subtypesof(/datum/language) - /datum/language/common) H.add_blocked_language(subtypesof(/datum/language) - current_language, LANGUAGE_VOICECHANGE) - H.grant_language(current_language, TRUE, TRUE, LANGUAGE_VOICECHANGE) + H.grant_language(current_language, source = LANGUAGE_VOICECHANGE) /datum/symptom/voice_change/End(datum/disease/advance/A) ..() diff --git a/code/datums/dna.dm b/code/datums/dna.dm index 1c8766d327b..f7528d97e7a 100644 --- a/code/datums/dna.dm +++ b/code/datums/dna.dm @@ -488,11 +488,6 @@ GLOBAL_LIST_INIT(total_uf_len_by_block, populate_total_uf_len_by_block()) old_species.on_species_loss(src, new_race, pref_load) dna.species.on_species_gain(src, old_species, pref_load) - if(ishuman(src)) - qdel(language_holder) - var/species_holder = initial(mrace.species_language_holder) - language_holder = new species_holder(src) - update_atom_languages() log_mob_tag("TAG: [tag] SPECIES: [key_name(src)] \[[mrace]\]") /mob/living/carbon/human/set_species(datum/species/mrace, icon_update = TRUE, pref_load = FALSE) diff --git a/code/datums/mind/_mind.dm b/code/datums/mind/_mind.dm index 287503058af..b17dda03769 100644 --- a/code/datums/mind/_mind.dm +++ b/code/datums/mind/_mind.dm @@ -65,7 +65,6 @@ ///If this mind's master is another mob (i.e. adamantine golems). Weakref of a /living. var/datum/weakref/enslaved_to - var/datum/language_holder/language_holder var/unconvertable = FALSE var/late_joiner = FALSE /// has this mind ever been an AI @@ -118,7 +117,6 @@ QDEL_LIST(memories) QDEL_NULL(memory_panel) QDEL_LIST(antag_datums) - QDEL_NULL(language_holder) set_current(null) return ..() @@ -166,18 +164,6 @@ SIGNAL_HANDLER set_current(null) -/datum/mind/proc/get_language_holder() - if(isnull(language_holder)) - if(iscarbon(current)) - var/mob/living/carbon/talker = current - // AHH WHY DO MINDS ALSO HAVE LANGUAGE HOLDERS WHAT'S THE POINT OF THE "MIND" LANGUAGE SOURCE IF WE STORE IT TWICE ANYWAYS - var/type_to_use = talker.dna?.species?.species_language_holder || talker.language_holder?.type || /datum/language_holder - language_holder = new type_to_use(src) - else - language_holder = new(src) - - return language_holder - /datum/mind/proc/transfer_to(mob/new_character, force_key_move = 0) set_original_character(null) if(current) // remove ourself from our old body's mind variable @@ -196,7 +182,20 @@ var/mob/living/old_current = current if(current) - current.transfer_observers_to(new_character) //transfer anyone observing the old character to the new one + //transfer anyone observing the old character to the new one + current.transfer_observers_to(new_character) + + // Offload all mind languages from the old holder to a temp one + var/datum/language_holder/empty/temp_holder = new() + var/datum/language_holder/old_holder = old_current.get_language_holder() + var/datum/language_holder/new_holder = new_character.get_language_holder() + // Off load mind languages to the temp holder momentarily + new_holder.transfer_mind_languages(temp_holder) + // Transfer the old holder's mind languages to the new holder + old_holder.transfer_mind_languages(new_holder) + // And finally transfer the temp holder's mind languages back to the old holder + temp_holder.transfer_mind_languages(old_holder) + set_current(new_character) //associate ourself with our new body QDEL_NULL(antag_hud) new_character.mind = src //and associate our new body with ourself @@ -214,7 +213,7 @@ if(new_character.client) LAZYCLEARLIST(new_character.client.recent_examines) new_character.client.init_verbs() // re-initialize character specific verbs - current.update_atom_languages() + SEND_SIGNAL(src, COMSIG_MIND_TRANSFERRED, old_current) SEND_SIGNAL(current, COMSIG_MOB_MIND_TRANSFERRED_INTO) diff --git a/code/datums/mutations/speech.dm b/code/datums/mutations/speech.dm index 8828cd4a1ea..4c78c196104 100644 --- a/code/datums/mutations/speech.dm +++ b/code/datums/mutations/speech.dm @@ -192,12 +192,12 @@ /datum/mutation/human/stoner/on_acquiring(mob/living/carbon/human/owner) ..() - owner.grant_language(/datum/language/beachbum, TRUE, TRUE, LANGUAGE_STONER) + owner.grant_language(/datum/language/beachbum, source = LANGUAGE_STONER) owner.add_blocked_language(subtypesof(/datum/language) - /datum/language/beachbum, LANGUAGE_STONER) /datum/mutation/human/stoner/on_losing(mob/living/carbon/human/owner) ..() - owner.remove_language(/datum/language/beachbum, TRUE, TRUE, LANGUAGE_STONER) + owner.remove_language(/datum/language/beachbum, source = LANGUAGE_STONER) owner.remove_blocked_language(subtypesof(/datum/language) - /datum/language/beachbum, LANGUAGE_STONER) /datum/mutation/human/medieval diff --git a/code/datums/quirks/neutral_quirks.dm b/code/datums/quirks/neutral_quirks.dm index a9c70184020..b835f44985c 100644 --- a/code/datums/quirks/neutral_quirks.dm +++ b/code/datums/quirks/neutral_quirks.dm @@ -48,7 +48,7 @@ var/mob/living/carbon/human/human_holder = quirk_holder human_holder.add_blocked_language(/datum/language/common) if(ishumanbasic(human_holder)) - human_holder.grant_language(/datum/language/uncommon, understood = TRUE, spoken = TRUE, source = LANGUAGE_QUIRK) + human_holder.grant_language(/datum/language/uncommon, source = LANGUAGE_QUIRK) /datum/quirk/foreigner/remove() var/mob/living/carbon/human/human_holder = quirk_holder diff --git a/code/datums/status_effects/debuffs/drugginess.dm b/code/datums/status_effects/debuffs/drugginess.dm index c9a4b034634..8730e0ccd6e 100644 --- a/code/datums/status_effects/debuffs/drugginess.dm +++ b/code/datums/status_effects/debuffs/drugginess.dm @@ -14,7 +14,7 @@ owner.add_mood_event(id, /datum/mood_event/high) owner.overlay_fullscreen(id, /atom/movable/screen/fullscreen/high) owner.sound_environment_override = SOUND_ENVIRONMENT_DRUGGED - owner.grant_language(/datum/language/beachbum, TRUE, TRUE, id) + owner.grant_language(/datum/language/beachbum, source = id) return TRUE /datum/status_effect/drugginess/on_remove() @@ -24,7 +24,7 @@ owner.clear_fullscreen(id) if(owner.sound_environment_override == SOUND_ENVIRONMENT_DRUGGED) owner.sound_environment_override = SOUND_ENVIRONMENT_NONE - owner.remove_language(/datum/language/beachbum, TRUE, TRUE, id) + owner.remove_language(/datum/language/beachbum, source = id) /// Removes all of our drugginess (self delete) on signal /datum/status_effect/drugginess/proc/remove_drugginess(datum/source, admin_revive) diff --git a/code/datums/status_effects/debuffs/tower_of_babel.dm b/code/datums/status_effects/debuffs/tower_of_babel.dm index 1ba46d0b87b..b3c1ae0c477 100644 --- a/code/datums/status_effects/debuffs/tower_of_babel.dm +++ b/code/datums/status_effects/debuffs/tower_of_babel.dm @@ -25,7 +25,6 @@ // if user is affected by tower of babel, we remove the blocked languages owner.remove_blocked_language(GLOB.all_languages, source = LANGUAGE_BABEL) owner.remove_all_languages(source = LANGUAGE_BABEL) - owner.update_atom_languages() if(owner.mind) REMOVE_TRAIT(owner.mind, TRAIT_TOWER_OF_BABEL, trait_source) return ..() diff --git a/code/game/atoms_movable.dm b/code/game/atoms_movable.dm index f12b1230da8..1ffca8e1cd7 100644 --- a/code/game/atoms_movable.dm +++ b/code/game/atoms_movable.dm @@ -14,8 +14,11 @@ ///Max range this atom can be thrown via telekinesis var/tk_throw_range = 10 var/mob/pulledby = null + /// What language holder type to init as var/initial_language_holder = /datum/language_holder - var/datum/language_holder/language_holder // Mindless mobs and objects need language too, some times. Mind holder takes prescedence. + /// Holds all languages this mob can speak and understand + VAR_PRIVATE/datum/language_holder/language_holder + var/verb_say = "says" var/verb_ask = "asks" var/verb_exclaim = "exclaims" @@ -1446,7 +1449,8 @@ */ /// Gets or creates the relevant language holder. For mindless atoms, gets the local one. For atom with mind, gets the mind one. -/atom/movable/proc/get_language_holder(get_minds = TRUE) +/atom/movable/proc/get_language_holder() + RETURN_TYPE(/datum/language_holder) if(QDELING(src)) CRASH("get_language_holder() called on a QDELing atom, \ this will try to re-instantiate the language holder that's about to be deleted, which is bad.") @@ -1456,44 +1460,36 @@ return language_holder /// Grants the supplied language and sets omnitongue true. -/atom/movable/proc/grant_language(language, understood = TRUE, spoken = TRUE, source = LANGUAGE_ATOM) - var/datum/language_holder/language_holder = get_language_holder() - return language_holder.grant_language(language, understood, spoken, source) +/atom/movable/proc/grant_language(language, language_flags = ALL, source = LANGUAGE_ATOM) + return get_language_holder().grant_language(language, language_flags, source) /// Grants every language. -/atom/movable/proc/grant_all_languages(understood = TRUE, spoken = TRUE, grant_omnitongue = TRUE, source = LANGUAGE_MIND) - var/datum/language_holder/language_holder = get_language_holder() - return language_holder.grant_all_languages(understood, spoken, grant_omnitongue, source) +/atom/movable/proc/grant_all_languages(language_flags = ALL, grant_omnitongue = TRUE, source = LANGUAGE_MIND) + return get_language_holder().grant_all_languages(language_flags, grant_omnitongue, source) /// Removes a single language. -/atom/movable/proc/remove_language(language, understood = TRUE, spoken = TRUE, source = LANGUAGE_ALL) - var/datum/language_holder/language_holder = get_language_holder() - return language_holder.remove_language(language, understood, spoken, source) +/atom/movable/proc/remove_language(language, language_flags = ALL, source = LANGUAGE_ALL) + return get_language_holder().remove_language(language, language_flags, source) /// Removes every language and sets omnitongue false. /atom/movable/proc/remove_all_languages(source = LANGUAGE_ALL, remove_omnitongue = FALSE) - var/datum/language_holder/language_holder = get_language_holder() - return language_holder.remove_all_languages(source, remove_omnitongue) + return get_language_holder().remove_all_languages(source, remove_omnitongue) /// Adds a language to the blocked language list. Use this over remove_language in cases where you will give languages back later. /atom/movable/proc/add_blocked_language(language, source = LANGUAGE_ATOM) - var/datum/language_holder/language_holder = get_language_holder() - return language_holder.add_blocked_language(language, source) + return get_language_holder().add_blocked_language(language, source) /// Removes a language from the blocked language list. /atom/movable/proc/remove_blocked_language(language, source = LANGUAGE_ATOM) - var/datum/language_holder/language_holder = get_language_holder() - return language_holder.remove_blocked_language(language, source) + return get_language_holder().remove_blocked_language(language, source) /// Checks if atom has the language. If spoken is true, only checks if atom can speak the language. -/atom/movable/proc/has_language(language, spoken = FALSE) - var/datum/language_holder/language_holder = get_language_holder() - return language_holder.has_language(language, spoken) +/atom/movable/proc/has_language(language, flags_to_check) + return get_language_holder().has_language(language, flags_to_check) /// Checks if atom can speak the language. /atom/movable/proc/can_speak_language(language) - var/datum/language_holder/language_holder = get_language_holder() - return language_holder.can_speak_language(language) + return get_language_holder().can_speak_language(language) /// Returns the result of tongue specific limitations on spoken languages. /atom/movable/proc/could_speak_language(datum/language/language_path) @@ -1501,33 +1497,32 @@ /// Returns selected language, if it can be spoken, or finds, sets and returns a new selected language if possible. /atom/movable/proc/get_selected_language() - var/datum/language_holder/language_holder = get_language_holder() - return language_holder.get_selected_language() + return get_language_holder().get_selected_language() /// Gets a random understood language, useful for hallucinations and such. /atom/movable/proc/get_random_understood_language() - var/datum/language_holder/language_holder = get_language_holder() - return language_holder.get_random_understood_language() + return get_language_holder().get_random_understood_language() /// Gets a random spoken language, useful for forced speech and such. /atom/movable/proc/get_random_spoken_language() - var/datum/language_holder/language_holder = get_language_holder() - return language_holder.get_random_spoken_language() + return get_language_holder().get_random_spoken_language() /// Copies all languages into 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. -/atom/movable/proc/copy_languages(from_holder, source_override) - if(isatom(from_holder)) +/atom/movable/proc/copy_languages(datum/language_holder/from_holder, source_override) + if(ismovable(from_holder)) var/atom/movable/thing = from_holder from_holder = thing.get_language_holder() - var/datum/language_holder/language_holder = get_language_holder() - return language_holder.copy_languages(from_holder, source_override) -/// Empties out the atom specific languages and updates them according to the current atoms language holder. -/// As a side effect, it also creates missing language holders in the process. -/atom/movable/proc/update_atom_languages() - var/datum/language_holder/language_holder = get_language_holder() - return language_holder.update_atom_languages(src) + return get_language_holder().copy_languages(from_holder, source_override) + +/// Sets the passed path as the active language +/// Returns the currently selected language if successful, if the language was not valid, returns null +/atom/movable/proc/set_active_language(language_path) + var/datum/language_holder/our_holder = get_language_holder() + our_holder.selected_language = language_path + + return our_holder.get_selected_language() // verifies its validity, returns it if successful. /** * Randomizes our atom's language to an uncommon language if: diff --git a/code/game/machinery/_machinery.dm b/code/game/machinery/_machinery.dm index 6d2832f17dd..3f4ba46e4c8 100644 --- a/code/game/machinery/_machinery.dm +++ b/code/game/machinery/_machinery.dm @@ -316,8 +316,7 @@ new /obj/effect/temp_visual/emp(loc) if(prob(70/severity)) - var/datum/language_holder/machine_languages = get_language_holder() - machine_languages.selected_language = machine_languages.get_random_spoken_language() + set_active_language(get_random_spoken_language()) /** * Opens the machine. diff --git a/code/game/objects/items/devices/radio/headset.dm b/code/game/objects/items/devices/radio/headset.dm index 0d681b96832..2b1337f44ee 100644 --- a/code/game/objects/items/devices/radio/headset.dm +++ b/code/game/objects/items/devices/radio/headset.dm @@ -99,7 +99,7 @@ GLOBAL_LIST_INIT(channel_tokens, list( /// Grants all the languages this headset allows the mob to understand via installed chips. /obj/item/radio/headset/proc/grant_headset_languages(mob/grant_to) for(var/language in language_list) - grant_to.grant_language(language, understood = TRUE, spoken = FALSE, source = LANGUAGE_RADIOKEY) + grant_to.grant_language(language, language_flags = UNDERSTOOD_LANGUAGE, source = LANGUAGE_RADIOKEY) /obj/item/radio/headset/equipped(mob/user, slot, initial) . = ..() @@ -111,7 +111,7 @@ GLOBAL_LIST_INIT(channel_tokens, list( /obj/item/radio/headset/dropped(mob/user, silent) . = ..() for(var/language in language_list) - user.remove_language(language, understood = TRUE, spoken = FALSE, source = LANGUAGE_RADIOKEY) + user.remove_language(language, language_flags = UNDERSTOOD_LANGUAGE, source = LANGUAGE_RADIOKEY) /obj/item/radio/headset/syndicate //disguised to look like a normal headset for stealth ops @@ -431,7 +431,7 @@ GLOBAL_LIST_INIT(channel_tokens, list( if(istype(mob_loc) && mob_loc.get_item_by_slot(slot_flags) == src) // Remove all the languages we may not be able to know anymore for(var/language in old_language_list) - mob_loc.remove_language(language, understood = TRUE, spoken = FALSE, source = LANGUAGE_RADIOKEY) + mob_loc.remove_language(language, language_flags = UNDERSTOOD_LANGUAGE, source = LANGUAGE_RADIOKEY) // And grant all the languages we definitely should know now grant_headset_languages(mob_loc) diff --git a/code/modules/admin/topic.dm b/code/modules/admin/topic.dm index 14434fc183a..ca811b0f81d 100644 --- a/code/modules/admin/topic.dm +++ b/code/modules/admin/topic.dm @@ -1060,8 +1060,7 @@ if(!ismob(M)) to_chat(usr, "This can only be used on instances of type /mob.", confidential = TRUE) return - var/datum/language_holder/H = M.get_language_holder() - H.open_language_menu(usr) + M.get_language_holder().open_language_menu(usr) else if(href_list["traitor"]) if(!check_rights(R_ADMIN)) diff --git a/code/modules/antagonists/abductor/equipment/glands/slime.dm b/code/modules/antagonists/abductor/equipment/glands/slime.dm index 50e83ee3735..e3c966e3b6c 100644 --- a/code/modules/antagonists/abductor/equipment/glands/slime.dm +++ b/code/modules/antagonists/abductor/equipment/glands/slime.dm @@ -10,12 +10,12 @@ /obj/item/organ/internal/heart/gland/slime/on_insert(mob/living/carbon/gland_owner) . = ..() gland_owner.faction |= FACTION_SLIME - gland_owner.grant_language(/datum/language/slime, TRUE, TRUE, LANGUAGE_GLAND) + gland_owner.grant_language(/datum/language/slime, source = LANGUAGE_GLAND) /obj/item/organ/internal/heart/gland/slime/on_remove(mob/living/carbon/gland_owner) . = ..() gland_owner.faction -= FACTION_SLIME - gland_owner.remove_language(/datum/language/slime, TRUE, TRUE, LANGUAGE_GLAND) + gland_owner.remove_language(/datum/language/slime, source = LANGUAGE_GLAND) /obj/item/organ/internal/heart/gland/slime/activate() to_chat(owner, span_warning("You feel nauseated!")) diff --git a/code/modules/antagonists/changeling/changeling.dm b/code/modules/antagonists/changeling/changeling.dm index 8fc6f3d6c16..8376be4c031 100644 --- a/code/modules/antagonists/changeling/changeling.dm +++ b/code/modules/antagonists/changeling/changeling.dm @@ -123,7 +123,7 @@ create_initial_profile() if(give_objectives) forge_objectives() - owner.current.grant_all_languages(FALSE, FALSE, TRUE) //Grants omnitongue. We are able to transform our body after all. + owner.current.get_language_holder().omnitongue = TRUE owner.current.playsound_local(get_turf(owner.current), 'sound/ambience/antag/ling_alert.ogg', 100, FALSE, pressure_affected = FALSE, use_reverb = FALSE) return ..() diff --git a/code/modules/antagonists/cult/cult.dm b/code/modules/antagonists/cult/cult.dm index 7f2e72c4a56..27211aac607 100644 --- a/code/modules/antagonists/cult/cult.dm +++ b/code/modules/antagonists/cult/cult.dm @@ -55,7 +55,7 @@ var/mob/living/current = owner.current || mob_override handle_clown_mutation(current, mob_override ? null : "Your training has allowed you to overcome your clownish nature, allowing you to wield weapons without harming yourself.") current.faction |= FACTION_CULT - current.grant_language(/datum/language/narsie, TRUE, TRUE, LANGUAGE_CULTIST) + current.grant_language(/datum/language/narsie, source = LANGUAGE_CULTIST) var/datum/action/innate/cult/comm/communion = new communion.Grant(current) @@ -78,7 +78,7 @@ var/mob/living/current = owner.current || mob_override handle_clown_mutation(current, removing = FALSE) current.faction -= FACTION_CULT - current.remove_language(/datum/language/narsie, TRUE, TRUE, LANGUAGE_CULTIST) + current.remove_language(/datum/language/narsie, source = LANGUAGE_CULTIST) QDEL_NULL(vote_ability) for(var/datum/action/innate/cult/cult_buttons in owner.current.actions) diff --git a/code/modules/antagonists/malf_ai/malf_ai.dm b/code/modules/antagonists/malf_ai/malf_ai.dm index 50ddaf6c9ef..4575382c25c 100644 --- a/code/modules/antagonists/malf_ai/malf_ai.dm +++ b/code/modules/antagonists/malf_ai/malf_ai.dm @@ -38,7 +38,7 @@ add_law_zero() owner.current.playsound_local(get_turf(owner.current), 'sound/ambience/antag/malf.ogg', 100, FALSE, pressure_affected = FALSE, use_reverb = FALSE) - owner.current.grant_language(/datum/language/codespeak, TRUE, TRUE, LANGUAGE_MALF) + owner.current.grant_language(/datum/language/codespeak, source = LANGUAGE_MALF) return ..() diff --git a/code/modules/antagonists/pirate/pirate.dm b/code/modules/antagonists/pirate/pirate.dm index 31639b3688e..c88c49873af 100644 --- a/code/modules/antagonists/pirate/pirate.dm +++ b/code/modules/antagonists/pirate/pirate.dm @@ -43,12 +43,12 @@ . = ..() var/mob/living/owner_mob = mob_override || owner.current var/datum/language_holder/holder = owner_mob.get_language_holder() - holder.grant_language(/datum/language/piratespeak, TRUE, TRUE, LANGUAGE_PIRATE) + holder.grant_language(/datum/language/piratespeak, source = LANGUAGE_PIRATE) holder.selected_language = /datum/language/piratespeak /datum/antagonist/pirate/remove_innate_effects(mob/living/mob_override) var/mob/living/owner_mob = mob_override || owner.current - owner_mob.remove_language(/datum/language/piratespeak, TRUE, TRUE, LANGUAGE_PIRATE) + owner_mob.remove_language(/datum/language/piratespeak, source = LANGUAGE_PIRATE) return ..() /datum/team/pirate diff --git a/code/modules/antagonists/sentient_creature/sentient_creature.dm b/code/modules/antagonists/sentient_creature/sentient_creature.dm index 4b68a288be1..d1197265ced 100644 --- a/code/modules/antagonists/sentient_creature/sentient_creature.dm +++ b/code/modules/antagonists/sentient_creature/sentient_creature.dm @@ -23,8 +23,7 @@ var/mob/living/master = owner.enslaved_to?.resolve() if(master) owner.current.copy_languages(master, LANGUAGE_MASTER) - owner.current.update_atom_languages() - . = ..() + return ..() /datum/antagonist/sentient_creature/ui_static_data(mob/user) var/list/data = list() diff --git a/code/modules/antagonists/wizard/equipment/soulstone.dm b/code/modules/antagonists/wizard/equipment/soulstone.dm index 038785817d2..f72fa42dce2 100644 --- a/code/modules/antagonists/wizard/equipment/soulstone.dm +++ b/code/modules/antagonists/wizard/equipment/soulstone.dm @@ -384,8 +384,7 @@ soulstone_spirit.copy_languages(victim, LANGUAGE_MIND)//Copies the old mobs languages into the new mob holder. if(user) soulstone_spirit.copy_languages(user, LANGUAGE_MASTER) - soulstone_spirit.update_atom_languages() - soulstone_spirit.grant_all_languages(FALSE, FALSE, TRUE) //Grants omnitongue + soulstone_spirit.get_language_holder().omnitongue = TRUE //Grants omnitongue if(user) soulstone_spirit.faction |= "[REF(user)]" //Add the master as a faction, allowing inter-mob cooperation if(IS_CULTIST(user)) diff --git a/code/modules/clothing/head/pirate.dm b/code/modules/clothing/head/pirate.dm index 8084ef47594..fd45404efc3 100644 --- a/code/modules/clothing/head/pirate.dm +++ b/code/modules/clothing/head/pirate.dm @@ -13,7 +13,7 @@ if(!ishuman(user)) return if(slot & ITEM_SLOT_HEAD) - user.grant_language(/datum/language/piratespeak/, TRUE, TRUE, LANGUAGE_HAT) + user.grant_language(/datum/language/piratespeak/, source = LANGUAGE_HAT) to_chat(user, span_boldnotice("You suddenly know how to speak like a pirate!")) /obj/item/clothing/head/costume/pirate/dropped(mob/user) @@ -22,7 +22,7 @@ return var/mob/living/carbon/human/H = user if(H.get_item_by_slot(ITEM_SLOT_HEAD) == src && !QDELETED(src)) //This can be called as a part of destroy - user.remove_language(/datum/language/piratespeak/, TRUE, TRUE, LANGUAGE_HAT) + user.remove_language(/datum/language/piratespeak/, source = LANGUAGE_HAT) to_chat(user, span_boldnotice("You can no longer speak like a pirate.")) /obj/item/clothing/head/costume/pirate/armored diff --git a/code/modules/events/ghost_role/sentience.dm b/code/modules/events/ghost_role/sentience.dm index 2471849be63..21d939cdaca 100644 --- a/code/modules/events/ghost_role/sentience.dm +++ b/code/modules/events/ghost_role/sentience.dm @@ -87,7 +87,7 @@ GLOBAL_LIST_INIT(high_priority_sentience, typecacheof(list( selected.key = picked_candidate.key - selected.grant_all_languages(TRUE, FALSE, FALSE) + selected.grant_all_languages(UNDERSTOOD_LANGUAGE, grant_omnitongue = FALSE, source = LANGUAGE_ATOM) if (isanimal(selected)) var/mob/living/simple_animal/animal_selected = selected diff --git a/code/modules/jobs/job_types/curator.dm b/code/modules/jobs/job_types/curator.dm index 54dd4cbb61d..1d10634fa6a 100644 --- a/code/modules/jobs/job_types/curator.dm +++ b/code/modules/jobs/job_types/curator.dm @@ -61,5 +61,5 @@ if(visualsOnly) return - translator.grant_all_languages(source=LANGUAGE_CURATOR) + translator.grant_all_languages(source = LANGUAGE_CURATOR) translator.remove_blocked_language(GLOB.all_languages, source=LANGUAGE_ALL) diff --git a/code/modules/language/language_holder.dm b/code/modules/language/language_holder.dm index 378c5f60808..6a6ff2e8265 100644 --- a/code/modules/language/language_holder.dm +++ b/code/modules/language/language_holder.dm @@ -1,4 +1,4 @@ -/*!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 @@ -33,34 +33,35 @@ 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/atom/owner + var/atom/movable/owner /// Initializes, and copies in the languages from the current atom if available. -/datum/language_holder/New(atom/_owner) - if(_owner && QDELETED(_owner)) - CRASH("Langauge holder added to a qdeleting thing, what the fuck [text_ref(_owner)]") - owner = _owner - if(istype(owner, /datum/mind)) - var/datum/mind/M = owner - if(M.current) - update_atom_languages(M.current) +/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) @@ -72,50 +73,46 @@ Key procs 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) + 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 @@ -124,39 +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/can_speak_language_path = omnitongue || ouratom.could_speak_language(language) - return (can_speak_language_path && has_language(language, TRUE)) + 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() @@ -199,54 +198,61 @@ Key procs 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(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 understood_languages = list(/datum/language/xenocommon = list(LANGUAGE_ATOM)) @@ -254,10 +260,14 @@ Key procs blocked_languages = list(/datum/language/common = list(LANGUAGE_ATOM)) /datum/language_holder/construct - understood_languages = list(/datum/language/common = list(LANGUAGE_ATOM), - /datum/language/narsie = list(LANGUAGE_ATOM)) - spoken_languages = list(/datum/language/common = list(LANGUAGE_ATOM), - /datum/language/narsie = list(LANGUAGE_ATOM)) + understood_languages = list( + /datum/language/common = list(LANGUAGE_ATOM), + /datum/language/narsie = list(LANGUAGE_ATOM), + ) + spoken_languages = list( + /datum/language/common = list(LANGUAGE_ATOM), + /datum/language/narsie = list(LANGUAGE_ATOM), + ) /datum/language_holder/drone understood_languages = list(/datum/language/drone = list(LANGUAGE_ATOM)) @@ -265,13 +275,21 @@ Key procs blocked_languages = list(/datum/language/common = list(LANGUAGE_ATOM)) /datum/language_holder/drone/syndicate - blocked_languages = list() + 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/jelly - understood_languages = list(/datum/language/common = list(LANGUAGE_ATOM), - /datum/language/slime = list(LANGUAGE_ATOM)) - spoken_languages = list(/datum/language/common = list(LANGUAGE_ATOM), - /datum/language/slime = list(LANGUAGE_ATOM)) + understood_languages = list( + /datum/language/common = list(LANGUAGE_ATOM), + /datum/language/slime = list(LANGUAGE_ATOM), + ) + spoken_languages = list( + /datum/language/common = list(LANGUAGE_ATOM), + /datum/language/slime = list(LANGUAGE_ATOM), + ) /datum/language_holder/lightbringer understood_languages = list(/datum/language/slime = list(LANGUAGE_ATOM)) @@ -279,10 +297,14 @@ Key procs blocked_languages = list(/datum/language/common = list(LANGUAGE_ATOM)) /datum/language_holder/lizard - understood_languages = list(/datum/language/common = list(LANGUAGE_ATOM), - /datum/language/draconic = list(LANGUAGE_ATOM)) - spoken_languages = list(/datum/language/common = list(LANGUAGE_ATOM), - /datum/language/draconic = list(LANGUAGE_ATOM)) + understood_languages = list( + /datum/language/common = list(LANGUAGE_ATOM), + /datum/language/draconic = list(LANGUAGE_ATOM), + ) + spoken_languages = list( + /datum/language/common = list(LANGUAGE_ATOM), + /datum/language/draconic = list(LANGUAGE_ATOM), + ) /datum/language_holder/lizard/ash selected_language = /datum/language/draconic @@ -300,19 +322,29 @@ Key procs selected_language = /datum/language/uncommon /datum/language_holder/monkey - understood_languages = list(/datum/language/common = list(LANGUAGE_ATOM), - /datum/language/monkey = list(LANGUAGE_ATOM)) - spoken_languages = list(/datum/language/monkey = list(LANGUAGE_ATOM)) + understood_languages = list( + /datum/language/common = list(LANGUAGE_ATOM), + /datum/language/monkey = list(LANGUAGE_ATOM), + ) + spoken_languages = list( + /datum/language/monkey = list(LANGUAGE_ATOM), + ) /datum/language_holder/mushroom - understood_languages = list(/datum/language/common = list(LANGUAGE_ATOM), - /datum/language/mushroom = list(LANGUAGE_ATOM)) - spoken_languages = list(/datum/language/common = list(LANGUAGE_ATOM), - /datum/language/mushroom = list(LANGUAGE_ATOM)) + understood_languages = list( + /datum/language/common = list(LANGUAGE_ATOM), + /datum/language/mushroom = list(LANGUAGE_ATOM), + ) + spoken_languages = list( + /datum/language/common = list(LANGUAGE_ATOM), + /datum/language/mushroom = list(LANGUAGE_ATOM), + ) /datum/language_holder/slime - understood_languages = list(/datum/language/common = list(LANGUAGE_ATOM), - /datum/language/slime = list(LANGUAGE_ATOM)) + understood_languages = list( + /datum/language/common = list(LANGUAGE_ATOM), + /datum/language/slime = list(LANGUAGE_ATOM), + ) spoken_languages = list(/datum/language/slime = list(LANGUAGE_ATOM)) /datum/language_holder/venus @@ -338,92 +370,124 @@ Key procs ) /datum/language_holder/moth - understood_languages = list(/datum/language/common = list(LANGUAGE_ATOM), - /datum/language/moffic = list(LANGUAGE_ATOM)) - spoken_languages = list(/datum/language/common = list(LANGUAGE_ATOM), - /datum/language/moffic = list(LANGUAGE_ATOM)) + understood_languages = list( + /datum/language/common = list(LANGUAGE_ATOM), + /datum/language/moffic = list(LANGUAGE_ATOM), + ) + spoken_languages = list( + /datum/language/common = list(LANGUAGE_ATOM), + /datum/language/moffic = list(LANGUAGE_ATOM), + ) /datum/language_holder/skeleton - understood_languages = list(/datum/language/common = list(LANGUAGE_ATOM), - /datum/language/calcic = list(LANGUAGE_ATOM)) - spoken_languages = list(/datum/language/common = list(LANGUAGE_ATOM), - /datum/language/calcic = list(LANGUAGE_ATOM)) + understood_languages = list( + /datum/language/common = list(LANGUAGE_ATOM), + /datum/language/calcic = list(LANGUAGE_ATOM), + ) + spoken_languages = list( + /datum/language/common = list(LANGUAGE_ATOM), + /datum/language/calcic = list(LANGUAGE_ATOM), + ) /datum/language_holder/ethereal - understood_languages = list(/datum/language/common = list(LANGUAGE_ATOM), - /datum/language/voltaic = list(LANGUAGE_ATOM)) - spoken_languages = list(/datum/language/common = list(LANGUAGE_ATOM), - /datum/language/voltaic = list(LANGUAGE_ATOM)) + understood_languages = list( + /datum/language/common = list(LANGUAGE_ATOM), + /datum/language/voltaic = list(LANGUAGE_ATOM), + ) + spoken_languages = list( + /datum/language/common = list(LANGUAGE_ATOM), + /datum/language/voltaic = list(LANGUAGE_ATOM), + ) /datum/language_holder/golem - understood_languages = list(/datum/language/common = list(LANGUAGE_ATOM), - /datum/language/terrum = list(LANGUAGE_ATOM)) - spoken_languages = list(/datum/language/common = list(LANGUAGE_ATOM), - /datum/language/terrum = list(LANGUAGE_ATOM)) - -/datum/language_holder/golem/bone - understood_languages = list(/datum/language/common = list(LANGUAGE_ATOM), - /datum/language/terrum = list(LANGUAGE_ATOM), - /datum/language/calcic = list(LANGUAGE_ATOM)) - spoken_languages = list(/datum/language/common = list(LANGUAGE_ATOM), - /datum/language/terrum = list(LANGUAGE_ATOM), - /datum/language/calcic = list(LANGUAGE_ATOM)) - -/datum/language_holder/golem/runic - understood_languages = list(/datum/language/common = list(LANGUAGE_ATOM), - /datum/language/terrum = list(LANGUAGE_ATOM), - /datum/language/narsie = list(LANGUAGE_ATOM)) - spoken_languages = list(/datum/language/common = list(LANGUAGE_ATOM), - /datum/language/terrum = list(LANGUAGE_ATOM), - /datum/language/narsie = list(LANGUAGE_ATOM)) + understood_languages = list( + /datum/language/common = list(LANGUAGE_ATOM), + /datum/language/terrum = list(LANGUAGE_ATOM), + ) + spoken_languages = list( + /datum/language/common = list(LANGUAGE_ATOM), + /datum/language/terrum = list(LANGUAGE_ATOM), + ) /datum/language_holder/fly - understood_languages = list(/datum/language/common = list(LANGUAGE_ATOM), - /datum/language/buzzwords = list(LANGUAGE_ATOM)) - spoken_languages = list(/datum/language/common = list(LANGUAGE_ATOM), - /datum/language/buzzwords = list(LANGUAGE_ATOM)) + understood_languages = list( + /datum/language/common = list(LANGUAGE_ATOM), + /datum/language/buzzwords = list(LANGUAGE_ATOM), + ) + spoken_languages = list( + /datum/language/common = list(LANGUAGE_ATOM), + /datum/language/buzzwords = list(LANGUAGE_ATOM), + ) /datum/language_holder/plant - understood_languages = list(/datum/language/common = list(LANGUAGE_ATOM), - /datum/language/sylvan = list(LANGUAGE_ATOM)) - spoken_languages = list(/datum/language/common = list(LANGUAGE_ATOM), - /datum/language/sylvan = list(LANGUAGE_ATOM)) + understood_languages = list( + /datum/language/common = list(LANGUAGE_ATOM), + /datum/language/sylvan = list(LANGUAGE_ATOM), + ) + spoken_languages = list( + /datum/language/common = list(LANGUAGE_ATOM), + /datum/language/sylvan = list(LANGUAGE_ATOM), + ) /datum/language_holder/felinid - understood_languages = list(/datum/language/common = list(LANGUAGE_ATOM), - /datum/language/nekomimetic = list(LANGUAGE_ATOM)) - spoken_languages = list(/datum/language/common = list(LANGUAGE_ATOM), - /datum/language/nekomimetic = list(LANGUAGE_ATOM)) + understood_languages = list( + /datum/language/common = list(LANGUAGE_ATOM), + /datum/language/nekomimetic = list(LANGUAGE_ATOM), + ) + spoken_languages = list( + /datum/language/common = list(LANGUAGE_ATOM), + /datum/language/nekomimetic = list(LANGUAGE_ATOM), + ) /datum/language_holder/shadowpeople - understood_languages = list(/datum/language/common = list(LANGUAGE_ATOM), - /datum/language/shadowtongue = list(LANGUAGE_ATOM)) - spoken_languages = list(/datum/language/common = list(LANGUAGE_ATOM), - /datum/language/shadowtongue = list(LANGUAGE_ATOM)) + understood_languages = list( + /datum/language/common = list(LANGUAGE_ATOM), + /datum/language/shadowtongue = list(LANGUAGE_ATOM), + ) + spoken_languages = list( + /datum/language/common = list(LANGUAGE_ATOM), + /datum/language/shadowtongue = list(LANGUAGE_ATOM), + ) /datum/language_holder/clown - understood_languages = list(/datum/language/common = list(LANGUAGE_ATOM), - /datum/language/monkey = list(LANGUAGE_ATOM)) - spoken_languages = list(/datum/language/common = list(LANGUAGE_ATOM), - /datum/language/monkey = list(LANGUAGE_ATOM)) + understood_languages = list( + /datum/language/common = list(LANGUAGE_ATOM), + /datum/language/monkey = list(LANGUAGE_ATOM), + ) + spoken_languages = list( + /datum/language/common = list(LANGUAGE_ATOM), + /datum/language/monkey = list(LANGUAGE_ATOM), + ) /datum/language_holder/syndicate - understood_languages = list(/datum/language/common = list(LANGUAGE_ATOM), - /datum/language/codespeak = list(LANGUAGE_ATOM)) - spoken_languages = list(/datum/language/common = list(LANGUAGE_ATOM), - /datum/language/codespeak = list(LANGUAGE_ATOM)) + understood_languages = list( + /datum/language/common = list(LANGUAGE_ATOM), + /datum/language/codespeak = list(LANGUAGE_ATOM), + ) + spoken_languages = list( + /datum/language/common = list(LANGUAGE_ATOM), + /datum/language/codespeak = list(LANGUAGE_ATOM), + ) /datum/language_holder/beachbum - understood_languages = list(/datum/language/common = list(LANGUAGE_ATOM), - /datum/language/beachbum = list(LANGUAGE_ATOM)) - spoken_languages = list(/datum/language/common = list(LANGUAGE_ATOM), - /datum/language/beachbum = list(LANGUAGE_ATOM)) + understood_languages = list( + /datum/language/common = list(LANGUAGE_ATOM), + /datum/language/beachbum = list(LANGUAGE_ATOM), + ) + spoken_languages = list( + /datum/language/common = list(LANGUAGE_ATOM), + /datum/language/beachbum = list(LANGUAGE_ATOM), + ) selected_language = /datum/language/beachbum /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() - ..() + . = ..() grant_all_languages() diff --git a/code/modules/language/language_manuals.dm b/code/modules/language/language_manuals.dm index a0158606393..6220ea211cc 100644 --- a/code/modules/language/language_manuals.dm +++ b/code/modules/language/language_manuals.dm @@ -39,7 +39,7 @@ M.visible_message(span_danger("[user] beats [M] over the head with [src]!"), span_userdanger("[user] beats you over the head with [src]!"), span_hear("You hear smacking.")) else M.visible_message(span_notice("[user] teaches [M] by beating [M.p_them()] over the head with [src]!"), span_boldnotice("As [user] hits you with [src], [flavour_text]."), span_hear("You hear smacking.")) - M.grant_language(language, TRUE, TRUE, LANGUAGE_MIND) + M.grant_language(language, source = LANGUAGE_MIND) use_charge(user) /obj/item/language_manual/proc/use_charge(mob/user) diff --git a/code/modules/language/language_menu.dm b/code/modules/language/language_menu.dm index f2cebf052d0..0bfb7a79977 100644 --- a/code/modules/language/language_menu.dm +++ b/code/modules/language/language_menu.dm @@ -20,46 +20,40 @@ /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) @@ -67,71 +61,67 @@ 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 = tgui_input_list(user, "How do you want to add this language?", "[language_datum]", choices) if(isnull(choice)) return - var/spoken = FALSE - var/understood = FALSE + 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 - if(language_holder.blocked_languages && language_holder.blocked_languages[language_datum]) + 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, understood, spoken) + 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 = tgui_input_list(user, "Which part do you wish to remove?", "[language_datum]", choices) if(isnull(choice)) return - var/spoken = FALSE - var/understood = FALSE + 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 diff --git a/code/modules/mining/lavaland/megafauna_loot.dm b/code/modules/mining/lavaland/megafauna_loot.dm index 05e7d8ff90e..f42d48b5b1d 100644 --- a/code/modules/mining/lavaland/megafauna_loot.dm +++ b/code/modules/mining/lavaland/megafauna_loot.dm @@ -428,7 +428,6 @@ var/mob/dead/observer/picked_ghost = pick(candidates) soul.ckey = picked_ghost.ckey soul.copy_languages(user, LANGUAGE_MASTER) //Make sure the sword can understand and communicate with the user. - soul.update_atom_languages() soul.faction = list("[REF(user)]") balloon_alert(user, "the scythe glows up") add_overlay("soulscythe_gem") diff --git a/code/modules/mining/lavaland/tendril_loot.dm b/code/modules/mining/lavaland/tendril_loot.dm index 17c6dcbbe19..bd7e26091b3 100644 --- a/code/modules/mining/lavaland/tendril_loot.dm +++ b/code/modules/mining/lavaland/tendril_loot.dm @@ -503,9 +503,10 @@ return FALSE to_chat(user, span_notice("You flip through the pages of the book, quickly and conveniently learning every language in existence. Somewhat less conveniently, the aging book crumbles to dust in the process. Whoops.")) cure_curse_of_babel(user) // removes tower of babel if we have it - user.grant_all_languages(source=LANGUAGE_BABEL) + user.grant_all_languages(source = LANGUAGE_BABEL) user.remove_blocked_language(GLOB.all_languages, source = LANGUAGE_ALL) - ADD_TRAIT(user.mind, TRAIT_TOWER_OF_BABEL, MAGIC_TRAIT) // this makes you immune to babel effects + if(user.mind) + ADD_TRAIT(user.mind, TRAIT_TOWER_OF_BABEL, MAGIC_TRAIT) // this makes you immune to babel effects new /obj/effect/decal/cleanable/ash(get_turf(user)) qdel(src) diff --git a/code/modules/mob/living/carbon/human/_species.dm b/code/modules/mob/living/carbon/human/_species.dm index c9e1a55615c..dc1d33250cd 100644 --- a/code/modules/mob/living/carbon/human/_species.dm +++ b/code/modules/mob/living/carbon/human/_species.dm @@ -53,8 +53,10 @@ GLOBAL_LIST_EMPTY(features_by_species) var/skinned_type ///flags for inventory slots the race can't equip stuff to. Golems cannot wear jumpsuits, for example. var/no_equip_flags - ///What languages this species can understand and say. Use a [language holder datum][/datum/language_holder] in this var. - var/datum/language_holder/species_language_holder = /datum/language_holder + /// What languages this species can understand and say. + /// Use a [language holder datum][/datum/language_holder] typepath in this var. + /// Should never be null. + var/datum/language_holder/species_language_holder = /datum/language_holder/human_basic /** * Visible CURRENT bodyparts that are unique to a species. * DO NOT USE THIS AS A LIST OF ALL POSSIBLE BODYPARTS AS IT WILL FUCK @@ -491,6 +493,16 @@ GLOBAL_LIST_EMPTY(features_by_species) for(var/i in inherent_factions) C.faction += i //Using +=/-= for this in case you also gain the faction from a different source. + // All languages associated with this language holder are added with source [LANGUAGE_SPECIES] + // rather than source [LANGUAGE_ATOM], so we can track what to remove if our species changes again + var/datum/language_holder/gaining_holder = GLOB.prototype_language_holders[species_language_holder] + for(var/language in gaining_holder.understood_languages) + C.grant_language(language, UNDERSTOOD_LANGUAGE, LANGUAGE_SPECIES) + for(var/language in gaining_holder.spoken_languages) + C.grant_language(language, SPOKEN_LANGUAGE, LANGUAGE_SPECIES) + for(var/language in gaining_holder.blocked_languages) + C.add_blocked_language(language, LANGUAGE_SPECIES) + SEND_SIGNAL(C, COMSIG_SPECIES_GAIN, src, old_species) properly_gained = TRUE @@ -531,6 +543,15 @@ GLOBAL_LIST_EMPTY(features_by_species) clear_tail_moodlets(C) + // Removes all languages previously associated with [LANGUAGE_SPECIES], gaining our new species will add new ones back + var/datum/language_holder/losing_holder = GLOB.prototype_language_holders[species_language_holder] + for(var/language in losing_holder.understood_languages) + C.remove_language(language, UNDERSTOOD_LANGUAGE, LANGUAGE_SPECIES) + for(var/language in losing_holder.spoken_languages) + C.remove_language(language, SPOKEN_LANGUAGE, LANGUAGE_SPECIES) + for(var/language in losing_holder.blocked_languages) + C.remove_blocked_language(language, LANGUAGE_SPECIES) + SEND_SIGNAL(C, COMSIG_SPECIES_LOSS, src) /** @@ -2231,21 +2252,23 @@ GLOBAL_LIST_EMPTY(features_by_species) * Returns a list containing perks, or an empty list. */ /datum/species/proc/create_pref_language_perk() - var/list/to_add = list() // Grab galactic common as a path, for comparisons var/datum/language/common_language = /datum/language/common // Now let's find all the languages they can speak that aren't common var/list/bonus_languages = list() - var/datum/language_holder/temp_holder = new species_language_holder() - for(var/datum/language/language_type as anything in temp_holder.spoken_languages) + var/datum/language_holder/basic_holder = GLOB.prototype_language_holders[species_language_holder] + for(var/datum/language/language_type as anything in basic_holder.spoken_languages) if(ispath(language_type, common_language)) continue bonus_languages += initial(language_type.name) - // If we have any languages we can speak: create a perk for them all - if(length(bonus_languages)) + if(!length(bonus_languages)) + return // You're boring + + var/list/to_add = list() + if(common_language in basic_holder.spoken_languages) to_add += list(list( SPECIES_PERK_TYPE = SPECIES_POSITIVE_PERK, SPECIES_PERK_ICON = "comment", @@ -2253,7 +2276,13 @@ GLOBAL_LIST_EMPTY(features_by_species) SPECIES_PERK_DESC = "Alongside [initial(common_language.name)], [plural_form] gain the ability to speak [english_list(bonus_languages)].", )) - qdel(temp_holder) + else + to_add += list(list( + SPECIES_PERK_TYPE = SPECIES_NEUTRAL_PERK, + SPECIES_PERK_ICON = "comment", + SPECIES_PERK_NAME = "Foreign Speaker", + SPECIES_PERK_DESC = "[plural_form] may not speak [initial(common_language.name)], but they can speak [english_list(bonus_languages)].", + )) return to_add diff --git a/code/modules/mob/living/carbon/human/human_defines.dm b/code/modules/mob/living/carbon/human/human_defines.dm index 36dcc80fa12..65e67d8ce74 100644 --- a/code/modules/mob/living/carbon/human/human_defines.dm +++ b/code/modules/mob/living/carbon/human/human_defines.dm @@ -12,6 +12,7 @@ buckle_lying = 0 mob_biotypes = MOB_ORGANIC|MOB_HUMANOID can_be_shoved_into = TRUE + initial_language_holder = /datum/language_holder/empty // We get stuff from our species //Hair colour and style var/hair_color = "#000000" diff --git a/code/modules/mob/living/living_say.dm b/code/modules/mob/living/living_say.dm index 5c06bc69e39..585df5303bf 100644 --- a/code/modules/mob/living/living_say.dm +++ b/code/modules/mob/living/living_say.dm @@ -154,10 +154,7 @@ GLOBAL_LIST_INIT(message_modes_stat_limits, list( if(!try_speak(original_message, ignore_spam, forced, filterproof)) return - language = message_mods[LANGUAGE_EXTENSION] - - if(!language) - language = get_selected_language() + language = message_mods[LANGUAGE_EXTENSION] || get_selected_language() var/succumbed = FALSE @@ -554,8 +551,3 @@ GLOBAL_LIST_INIT(message_modes_stat_limits, list( if(!message) return say("#[message]", bubble_type, spans, sanitize, language, ignore_spam, forced, filterproof) - -/mob/living/get_language_holder(get_minds = TRUE) - if(get_minds && mind) - return mind.get_language_holder() - . = ..() diff --git a/code/modules/mob/living/simple_animal/bot/bot.dm b/code/modules/mob/living/simple_animal/bot/bot.dm index d77f7fa0ea1..3275cbcf87c 100644 --- a/code/modules/mob/living/simple_animal/bot/bot.dm +++ b/code/modules/mob/living/simple_animal/bot/bot.dm @@ -428,8 +428,7 @@ ejectpai(0) if(prob(70/severity)) - var/datum/language_holder/bot_languages = get_language_holder() - bot_languages.selected_language = bot_languages.get_random_spoken_language() + set_active_language(get_random_spoken_language()) if(bot_mode_flags & BOT_MODE_ON) turn_off() diff --git a/code/modules/mob/living/simple_animal/guardian/guardian.dm b/code/modules/mob/living/simple_animal/guardian/guardian.dm index 866d725202f..c26ba8fc27d 100644 --- a/code/modules/mob/living/simple_animal/guardian/guardian.dm +++ b/code/modules/mob/living/simple_animal/guardian/guardian.dm @@ -123,7 +123,6 @@ GLOBAL_LIST_EMPTY(parasites) //all currently existing/living guardians summoner.faction += "[REF(src)]" remove_all_languages(LANGUAGE_MASTER) copy_languages(to_who, LANGUAGE_MASTER) // make sure holoparasites speak same language as master - update_atom_languages() RegisterSignal(to_who, COMSIG_MOVABLE_MOVED, PROC_REF(check_distance)) RegisterSignal(to_who, COMSIG_QDELETING, PROC_REF(on_summoner_deletion)) RegisterSignal(to_who, COMSIG_LIVING_DEATH, PROC_REF(on_summoner_death)) diff --git a/code/modules/mob/mob.dm b/code/modules/mob/mob.dm index 5e2ef600a67..fdc9daf4987 100644 --- a/code/modules/mob/mob.dm +++ b/code/modules/mob/mob.dm @@ -1390,12 +1390,11 @@ fully_replace_character_name(real_name, new_name) ///Show the language menu for this mob -/mob/verb/open_language_menu() +/mob/verb/open_language_menu_verb() set name = "Open Language Menu" set category = "IC" - var/datum/language_holder/H = get_language_holder() - H.open_language_menu(usr) + get_language_holder().open_language_menu(usr) ///Adjust the nutrition of a mob /mob/proc/adjust_nutrition(change) //Honestly FUCK the oldcoders for putting nutrition on /mob someone else can move it up because holy hell I'd have to fix SO many typechecks diff --git a/code/modules/mob/transform_procs.dm b/code/modules/mob/transform_procs.dm index a2b03049bf5..9502042ce20 100644 --- a/code/modules/mob/transform_procs.dm +++ b/code/modules/mob/transform_procs.dm @@ -215,7 +215,6 @@ new_xeno.set_combat_mode(TRUE) new_xeno.key = key - update_atom_languages() to_chat(new_xeno, "You are now an alien.") . = new_xeno diff --git a/code/modules/mob_spawn/ghost_roles/mining_roles.dm b/code/modules/mob_spawn/ghost_roles/mining_roles.dm index f7ae8f35c4f..ff51f98c9ca 100644 --- a/code/modules/mob_spawn/ghost_roles/mining_roles.dm +++ b/code/modules/mob_spawn/ghost_roles/mining_roles.dm @@ -278,7 +278,7 @@ /obj/effect/mob_spawn/ghost_role/human/lavaland_syndicate/special(mob/living/new_spawn) . = ..() - new_spawn.grant_language(/datum/language/codespeak, TRUE, TRUE, LANGUAGE_MIND) + new_spawn.grant_language(/datum/language/codespeak, source = LANGUAGE_MIND) /obj/effect/mob_spawn/ghost_role/human/lavaland_syndicate/comms name = "Syndicate Comms Agent" diff --git a/code/modules/mob_spawn/ghost_roles/unused_roles.dm b/code/modules/mob_spawn/ghost_roles/unused_roles.dm index 11d17f53112..0a434f00dce 100644 --- a/code/modules/mob_spawn/ghost_roles/unused_roles.dm +++ b/code/modules/mob_spawn/ghost_roles/unused_roles.dm @@ -271,7 +271,7 @@ /obj/effect/mob_spawn/ghost_role/human/syndicatespace/special(mob/living/new_spawn) . = ..() - new_spawn.grant_language(/datum/language/codespeak, TRUE, TRUE, LANGUAGE_MIND) + new_spawn.grant_language(/datum/language/codespeak, source = LANGUAGE_MIND) var/datum/job/spawn_job = SSjob.GetJobType(spawner_job_path) var/policy = get_policy(spawn_job.policy_index) if(policy) diff --git a/code/modules/pai/defense.dm b/code/modules/pai/defense.dm index 8cd2bd9632c..3a888fa9c83 100644 --- a/code/modules/pai/defense.dm +++ b/code/modules/pai/defense.dm @@ -23,8 +23,7 @@ set_derpspeech(INFINITY) to_chat(src, span_danger("Warning: Vocabulary databank corrupted.")) if(prob(40)) - mind.language_holder.selected_language = get_random_spoken_language() - + set_active_language(get_random_spoken_language()) /mob/living/silicon/pai/ex_act(severity, target) take_holo_damage(50 * severity) diff --git a/code/modules/pai/software.dm b/code/modules/pai/software.dm index 9bbe7b95333..85f952c43eb 100644 --- a/code/modules/pai/software.dm +++ b/code/modules/pai/software.dm @@ -180,7 +180,7 @@ /mob/living/silicon/pai/proc/grant_languages() if(languages_granted) return FALSE - grant_all_languages(TRUE, TRUE, TRUE, LANGUAGE_SOFTWARE) + grant_all_languages(source = LANGUAGE_SOFTWARE) languages_granted = TRUE return TRUE diff --git a/code/modules/research/xenobiology/crossbreeding/_status_effects.dm b/code/modules/research/xenobiology/crossbreeding/_status_effects.dm index bde40d944ec..f8043f9d6f3 100644 --- a/code/modules/research/xenobiology/crossbreeding/_status_effects.dm +++ b/code/modules/research/xenobiology/crossbreeding/_status_effects.dm @@ -1043,7 +1043,6 @@ familiar.copy_languages(owner, LANGUAGE_MASTER) if(linked.saved_mind) linked.saved_mind.transfer_to(familiar) - familiar.update_atom_languages() familiar.ckey = linked.saved_mind.key else if(familiar.mind) diff --git a/code/modules/research/xenobiology/xenobiology.dm b/code/modules/research/xenobiology/xenobiology.dm index ddbbe3eca43..8ea95301780 100644 --- a/code/modules/research/xenobiology/xenobiology.dm +++ b/code/modules/research/xenobiology/xenobiology.dm @@ -777,7 +777,6 @@ SEND_SIGNAL(switchy_mob, COMSIG_SIMPLEMOB_TRANSFERPOTION, user) switchy_mob.faction = user.faction.Copy() switchy_mob.copy_languages(user, LANGUAGE_MIND) - switchy_mob.update_atom_languages() user.death() to_chat(switchy_mob, span_notice("In a quick flash, you feel your consciousness flow into [switchy_mob]!")) to_chat(switchy_mob, span_warning("You are now [switchy_mob]. Your allegiances, alliances, and role is still the same as it was prior to consciousness transfer!")) diff --git a/code/modules/spells/spell_types/tower_of_babel.dm b/code/modules/spells/spell_types/tower_of_babel.dm index 2ebc050c94e..3c652579b7f 100644 --- a/code/modules/spells/spell_types/tower_of_babel.dm +++ b/code/modules/spells/spell_types/tower_of_babel.dm @@ -16,7 +16,7 @@ GLOBAL_DATUM(tower_of_babel, /datum/tower_of_babel) continue if(IS_WIZARD(target) && !badmin) // wizards are not only immune but can speak all languages to taunt their victims over the radio - target.grant_all_languages(source=LANGUAGE_BABEL) + target.grant_all_languages(source = LANGUAGE_BABEL) ADD_TRAIT(target.mind, TRAIT_TOWER_OF_BABEL, MAGIC_TRAIT) to_chat(target, span_reallybig(span_hypnophrase("You feel a magical force improving your speech patterns!"))) continue diff --git a/code/modules/tgui/states/language_menu.dm b/code/modules/tgui/states/language_menu.dm index eaaa125786d..1786175f051 100644 --- a/code/modules/tgui/states/language_menu.dm +++ b/code/modules/tgui/states/language_menu.dm @@ -14,6 +14,6 @@ GLOBAL_DATUM_INIT(language_menu_state, /datum/ui_state/language_menu, new) if(check_rights_for(user.client, R_ADMIN)) . = UI_INTERACTIVE else if(istype(src_object, /datum/language_menu)) - var/datum/language_menu/LM = src_object - if(LM.language_holder.get_atom() == user) + var/datum/language_menu/my_languages = src_object + if(my_languages.language_holder.owner == user) . = UI_INTERACTIVE diff --git a/code/modules/unit_tests/_unit_tests.dm b/code/modules/unit_tests/_unit_tests.dm index a0d308d69ba..ee386513eb2 100644 --- a/code/modules/unit_tests/_unit_tests.dm +++ b/code/modules/unit_tests/_unit_tests.dm @@ -145,6 +145,7 @@ #include "json_savefile_importing.dm" #include "keybinding_init.dm" #include "knockoff_component.dm" +#include "language_transfer.dm" #include "lesserform.dm" #include "limbsanity.dm" #include "liver.dm" diff --git a/code/modules/unit_tests/language_transfer.dm b/code/modules/unit_tests/language_transfer.dm new file mode 100644 index 00000000000..7e031f3a8b4 --- /dev/null +++ b/code/modules/unit_tests/language_transfer.dm @@ -0,0 +1,191 @@ +// Dedicated to testing language holders + +/// Simply tests that swapping to a new species gives you the languages of that species and removes the languages of the old species +/datum/unit_test/language_species_swap_simple + +/datum/unit_test/language_species_swap_simple/Run() + var/mob/living/carbon/human/dummy = allocate(/mob/living/carbon/human/consistent) + + var/datum/language_holder/holder = dummy.get_language_holder() + + var/list/initial_spoken = holder.spoken_languages.Copy() + var/list/initial_understood = holder.understood_languages.Copy() + + TEST_ASSERT(length(initial_spoken) == 1, \ + "Dummy should only speak one language! Instead, it knew the following: [print_language_list(initial_spoken)]") + TEST_ASSERT(length(initial_understood) == 1, \ + "Dummy should only understand one language! Instead, it knew the following: [print_language_list(initial_understood)]") + + dummy.set_species(/datum/species/lizard) + + TEST_ASSERT(length(holder.spoken_languages) == 2, \ + "Dummy should speak two languages - Common and Draconic! Instead, it knew the following: [print_language_list(holder.spoken_languages)]") + + TEST_ASSERT(length(holder.understood_languages) == 2, \ + "Dummy should understand two languages - Common and Draconic! Instead, it knew the following: [print_language_list(holder.understood_languages)]") + + dummy.set_species(/datum/species/human) + + TEST_ASSERT(length(initial_spoken & holder.spoken_languages) == 1, \ + "Dummy did not speak Common after returning to human! Instead, it knew the following: [print_language_list(holder.spoken_languages)]") + + TEST_ASSERT(length(initial_understood & holder.understood_languages) == 1, \ + "Dummy did not understand Common after returning to human! Instead, it knew the following: [print_language_list(holder.understood_languages)]") + +/// Tests species changes which are more complex are functional (e.g. from a species which speaks common to one which does not) +/datum/unit_test/language_species_swap_complex + +/datum/unit_test/language_species_swap_complex/Run() + var/mob/living/carbon/human/dummy = allocate(/mob/living/carbon/human/consistent) + + var/datum/language_holder/holder = dummy.get_language_holder() + + dummy.set_species(/datum/species/lizard/silverscale) + + TEST_ASSERT(!dummy.has_language(/datum/language/common, SPOKEN_LANGUAGE), \ + "Changing a mob's species from one which speaks common to one which does not should remove the language!") + + TEST_ASSERT(dummy.has_language(/datum/language/common, UNDERSTOOD_LANGUAGE), \ + "Changing a mob's species from one which understands common another which does should not remove the language!") + + TEST_ASSERT(length(holder.spoken_languages) == 2, \ + "Dummy should speak two languages - Uncommon and Draconic! Instead, it knew the following: [print_language_list(holder.spoken_languages)]") + + TEST_ASSERT(length(holder.understood_languages) == 3, \ + "Dummy should understand two languages - Common, Uncommon and Draconic! Instead, it knew the following: [print_language_list(holder.understood_languages)]") + +/// Test that other random languages known are not lost on species change +/datum/unit_test/language_species_change_other_known + +/datum/unit_test/language_species_change_other_known/Run() + var/mob/living/carbon/human/dummy = allocate(/mob/living/carbon/human/consistent) + dummy.grant_language(/datum/language/piratespeak, source = LANGUAGE_MIND) + dummy.grant_language(/datum/language/draconic, source = LANGUAGE_ATOM) + dummy.set_species(/datum/species/lizard) + + TEST_ASSERT(dummy.has_language(/datum/language/piratespeak, SPOKEN_LANGUAGE), \ + "Dummy should still speak Pirate after changing species, as it's a mind language!") + + TEST_ASSERT(dummy.has_language(/datum/language/piratespeak, UNDERSTOOD_LANGUAGE), \ + "Dummy should still understand Pirate after changing species, as it's a mind language!") + + dummy.set_species(/datum/species/human) + + TEST_ASSERT(dummy.has_language(/datum/language/draconic, SPOKEN_LANGUAGE), \ + "Dummy should still speak Draconic after changing species, as it's an atom language!") + + TEST_ASSERT(dummy.has_language(/datum/language/draconic, UNDERSTOOD_LANGUAGE), \ + "Dummy should still understand Draconic after changing species, as it's an atom language!") + +/// Tests that mind bound languages are not lost swapping into a new mob, but other languages are +/datum/unit_test/language_mind_transfer + +/datum/unit_test/language_mind_transfer/Run() + var/mob/living/carbon/human/dummy = allocate(/mob/living/carbon/human/consistent) + var/mob/living/basic/pet/dog/corgi/transfer_target = allocate(/mob/living/basic/pet/dog/corgi) + dummy.mind_initialize() + dummy.grant_language(/datum/language/piratespeak, source = LANGUAGE_MIND) + dummy.grant_language(/datum/language/draconic, source = LANGUAGE_ATOM) + dummy.set_species(/datum/species/lizard/silverscale) + + dummy.mind.transfer_to(transfer_target) + + // transfer_target should speak and understand pirate + TEST_ASSERT(!dummy.has_language(/datum/language/piratespeak, SPOKEN_LANGUAGE), \ + "Dummy should no longer be speaking Pirate after losing their mind!") + TEST_ASSERT(transfer_target.has_language(/datum/language/piratespeak, SPOKEN_LANGUAGE), \ + "Dummy's new mob should be capable of speaking Pirate!") + + TEST_ASSERT(!dummy.has_language(/datum/language/piratespeak, UNDERSTOOD_LANGUAGE), \ + "Dummy should no longer be understanding Pirate after losing their mind!") + TEST_ASSERT(transfer_target.has_language(/datum/language/piratespeak, UNDERSTOOD_LANGUAGE), \ + "Dummy's new mob should be capable of understanding Pirate!") + + // transfer_target should NOT speak and understand draconic + TEST_ASSERT(dummy.has_language(/datum/language/draconic, SPOKEN_LANGUAGE), \ + "Dummy should still understand Draconic after losing their mind - it's an atom language!") + TEST_ASSERT(!transfer_target.has_language(/datum/language/draconic, SPOKEN_LANGUAGE), \ + "Dummy's new mob should not understand Draconic - it's an atom language!") + + TEST_ASSERT(dummy.has_language(/datum/language/draconic, UNDERSTOOD_LANGUAGE), \ + "Dummy should still understand Draconic after losing their mind - it's an atom language!") + TEST_ASSERT(!transfer_target.has_language(/datum/language/draconic, UNDERSTOOD_LANGUAGE), \ + "Dummy's new mob should not understand Draconic - it's an atom language!") + + // transfer_target should NOT speak and understand uncommon + TEST_ASSERT(dummy.has_language(/datum/language/uncommon, SPOKEN_LANGUAGE), \ + "Dummy should still understand Uncommon after losing their mind - it's a species language!") + TEST_ASSERT(!transfer_target.has_language(/datum/language/uncommon, SPOKEN_LANGUAGE), \ + "Dummy's new mob should not understand Uncommon - it's a species language!") + + TEST_ASSERT(dummy.has_language(/datum/language/uncommon, UNDERSTOOD_LANGUAGE), \ + "Dummy should still understand Uncommon after losing their mind - it's a species language!") + TEST_ASSERT(!transfer_target.has_language(/datum/language/uncommon, UNDERSTOOD_LANGUAGE), \ + "Dummy's new mob should not understand Uncommon - it's a species language!") + +/// Tests that mind bound languages are not lost when swapping with another person (wiz mindswap) +/datum/unit_test/language_mind_swap + +/datum/unit_test/language_mind_swap/Run() + var/mob/living/carbon/human/dummy_A = allocate(/mob/living/carbon/human/consistent) + var/mob/living/carbon/human/dummy_B = allocate(/mob/living/carbon/human/consistent) + + dummy_A.mind_initialize() + dummy_B.mind_initialize() + + var/datum/mind/dummy_A_mind = dummy_A.mind + var/datum/mind/dummy_B_mind = dummy_B.mind + + dummy_A.set_species(/datum/species/lizard) + dummy_B.grant_language(/datum/language/piratespeak, source = LANGUAGE_MIND) + + dummy_A_mind.transfer_to(dummy_B) + dummy_B_mind.transfer_to(dummy_A) + + var/datum/language_holder/holder_A = dummy_A.get_language_holder() + var/datum/language_holder/holder_B = dummy_B.get_language_holder() + + // Holder A is a lizard: starts with 2 languages (common, draconic) + // Holder B is a human with a mind language: starts with 2 language (common, pirate) + // Swap occurs + // Holder A is a lizard with 2 languages, but should now also have pirate: 3 languages (common, draconic, pirate) + // Holder B is a human with just 1 language left over (common) + + TEST_ASSERT_EQUAL(length(holder_A.spoken_languages), 3, \ + "Holder A / Dummy A / Dummy B mind should speak Common, Draconic, and Pirate! \ + Instead, it knew the following: [print_language_list(holder_A.spoken_languages)]") + + TEST_ASSERT_EQUAL(length(holder_A.understood_languages), 3, \ + "Holder A / Dummy A / Dummy B mind should only understand Common, Draconic, and Pirate! \ + Instead, it knew the following: [print_language_list(holder_A.understood_languages)]") + + TEST_ASSERT_EQUAL(length(holder_B.spoken_languages), 1, \ + "Holder B / Dummy B / Dummy A mind should only speak 1 language - Common! \ + Instead, it knew the following: [print_language_list(holder_B.spoken_languages)]") + + TEST_ASSERT_EQUAL(length(holder_B.understood_languages), 1, \ + "Holder B / Dummy B / Dummy A mind only understand 1 language - Common! \ + Instead, it knew the following: [print_language_list(holder_B.understood_languages)]") + +/// Tests that the book of babel, and by extension grant_all_languages, works as intended +/datum/unit_test/book_of_babel + +/datum/unit_test/book_of_babel/Run() + var/mob/living/carbon/human/dummy = allocate(/mob/living/carbon/human/consistent) + var/obj/item/book_of_babel/book = allocate(/obj/item/book_of_babel) + var/datum/language_holder/holder = dummy.get_language_holder() + var/expected_amount = length(GLOB.all_languages) + + book.attack_self(dummy) + TEST_ASSERT_EQUAL(length(holder.spoken_languages), expected_amount, "Book of Babel failed to give all languages out!") + dummy.set_species(/datum/species/lizard) + TEST_ASSERT_EQUAL(length(holder.spoken_languages), expected_amount, "Changing species after using the Book of Bable should not remove languages!") + +/// Helper proc to print a list of languages in a human readable format +/proc/print_language_list(list/languages_to_print) + var/list/printed_languages = list() + + for(var/datum/language/language as anything in languages_to_print) + printed_languages += initial(language.name) + + return english_list(printed_languages) diff --git a/code/modules/unit_tests/say.dm b/code/modules/unit_tests/say.dm index 16de2ac424c..1beebc8211b 100644 --- a/code/modules/unit_tests/say.dm +++ b/code/modules/unit_tests/say.dm @@ -49,11 +49,11 @@ host_mob = allocate(/mob/living/carbon/human/consistent) var/surfer_quote = "surfing in the USA" - host_mob.grant_language(/datum/language/beachbum, spoken=TRUE, understood=FALSE) // can speak but can't understand + host_mob.grant_language(/datum/language/beachbum, SPOKEN_LANGUAGE) // can speak but can't understand host_mob.add_blocked_language(subtypesof(/datum/language) - /datum/language/beachbum, LANGUAGE_STONER) TEST_ASSERT_NOTEQUAL(surfer_quote, host_mob.translate_language(host_mob, /datum/language/beachbum, surfer_quote), "Language test failed. Mob was supposed to understand: [surfer_quote]") - host_mob.grant_language(/datum/language/beachbum, spoken=TRUE, understood=TRUE) // can now understand + host_mob.grant_language(/datum/language/beachbum, ALL) // can now understand TEST_ASSERT_EQUAL(surfer_quote, host_mob.translate_language(host_mob, /datum/language/beachbum, surfer_quote), "Language test failed. Mob was supposed NOT to understand: [surfer_quote]") /// This runs some simple speech tests on a speaker and listener and determines if a person can hear whispering or speaking as they are moved a distance away @@ -116,7 +116,7 @@ // Language test speaker.grant_language(/datum/language/beachbum) - speaker.language_holder.selected_language = /datum/language/beachbum + speaker.set_active_language(/datum/language/beachbum) listener.add_blocked_language(/datum/language/beachbum) // speaking and whispering should be hearable conversation(distance = 1) diff --git a/code/modules/vending/cola.dm b/code/modules/vending/cola.dm index 947db7f2274..e446c1d2938 100644 --- a/code/modules/vending/cola.dm +++ b/code/modules/vending/cola.dm @@ -109,10 +109,8 @@ /obj/machinery/vending/cola/shamblers/Initialize(mapload) . = ..() - var/datum/language_holder/ling_languages = get_language_holder() - ling_languages.selected_language = ling_languages.get_random_spoken_language() + set_active_language(get_random_spoken_language()) /obj/machinery/vending/cola/shamblers/speak(message) . = ..() - var/datum/language_holder/ling_languages = get_language_holder() - ling_languages.selected_language = ling_languages.get_random_spoken_language() + set_active_language(get_random_spoken_language()) diff --git a/tgstation.dme b/tgstation.dme index 5f6fa19788b..7a0aaf1e3d5 100644 --- a/tgstation.dme +++ b/tgstation.dme @@ -740,7 +740,6 @@ #include "code\datums\actions\mobs\charge_apc.dm" #include "code\datums\actions\mobs\dash.dm" #include "code\datums\actions\mobs\fire_breath.dm" -#include "code\datums\actions\mobs\language_menu.dm" #include "code\datums\actions\mobs\lava_swoop.dm" #include "code\datums\actions\mobs\meteors.dm" #include "code\datums\actions\mobs\mobcooldown.dm"