diff --git a/code/modules/client/preference_setup/general/02_language.dm b/code/modules/client/preference_setup/general/02_language.dm index 4b15fdc7f0..25d8c2c10e 100644 --- a/code/modules/client/preference_setup/general/02_language.dm +++ b/code/modules/client/preference_setup/general/02_language.dm @@ -14,12 +14,14 @@ //CHOMPEdit Begin S["species"] >> pref.species //CHOMPEdit End + S["language_custom_keys"] >> pref.language_custom_keys /datum/category_item/player_setup_item/general/language/save_character(var/savefile/S) S["language"] << pref.alternate_languages S["extra_languages"] << pref.extra_languages testing("LANGSANI: Saved to [pref.client]'s character [pref.real_name || "-name not yet loaded-"] savefile: [english_list(pref.alternate_languages || list())]") S["language_prefixes"] << pref.language_prefixes + S["language_custom_keys"] << pref.language_custom_keys /datum/category_item/player_setup_item/general/language/sanitize_character() if(!islist(pref.alternate_languages)) pref.alternate_languages = list() @@ -43,6 +45,14 @@ for(var/prefix in pref.language_prefixes) if(prefix in forbidden_prefixes) pref.language_prefixes -= prefix + if(isnull(pref.language_custom_keys)) + pref.language_custom_keys = list() + var/datum/species/S = GLOB.all_species[pref.species] + for(var/key in pref.language_custom_keys) + if(!pref.language_custom_keys[key]) + pref.language_custom_keys.Remove(key) + if(!((pref.language_custom_keys[key] == S.language) || (pref.language_custom_keys[key] == S.default_language && S.default_language != S.language) || (pref.language_custom_keys[key] in pref.alternate_languages))) + pref.language_custom_keys.Remove(key) /datum/category_item/player_setup_item/general/language/content() . += "Languages
" @@ -51,14 +61,15 @@ testing("LANGSANI: Truncated [pref.client]'s character [pref.real_name || "-name not yet loaded-"] language list because it was too long (len: [pref.alternate_languages.len], allowed: [S.num_alternate_languages])") pref.alternate_languages.len = (S.num_alternate_languages + pref.extra_languages) // Truncate to allowed length if(S.language) - . += "- [S.language]
" + . += "- [S.language] - Set Custom Key
" if(S.default_language && S.default_language != S.language) - . += "- [S.default_language]
" + . += "- [S.default_language] - Set Custom Key
" if(S.num_alternate_languages + pref.extra_languages) if(pref.alternate_languages.len) for(var/i = 1 to pref.alternate_languages.len) var/lang = pref.alternate_languages[i] - . += "- [lang] - remove
" + . += "- [lang] - remove - Set Custom Key
" + if(pref.alternate_languages.len < (S.num_alternate_languages + pref.extra_languages)) . += "- add ([(S.num_alternate_languages + pref.extra_languages) - pref.alternate_languages.len] remaining)
" else @@ -125,4 +136,32 @@ pref.language_prefixes = config.language_prefixes.Copy() return TOPIC_REFRESH + else if(href_list["set_custom_key"]) + var/lang = href_list["set_custom_key"] + if(!(lang in GLOB.all_languages)) + return TOPIC_REFRESH + + var/oldkey = "" + for(var/key in pref.language_custom_keys) + if(pref.language_custom_keys[key] == lang) + oldkey = key + break + + var/char = tgui_input_text(user, "Input a language key for [lang]. Input a single space to reset.", "Language Custom Key", oldkey) + if(length(char) != 1) + return TOPIC_REFRESH + else if(char == " ") + for(var/key in pref.language_custom_keys) + if(pref.language_custom_keys[key] == lang) + pref.language_custom_keys -= key + break + else if(contains_az09(char)) + if(!(char in pref.language_custom_keys)) + pref.language_custom_keys += char + pref.language_custom_keys[char] = lang + else + tgui_alert_async(user, "Improper language key. Rejected.", "Error") + + return TOPIC_REFRESH + return ..() diff --git a/code/modules/client/preferences.dm b/code/modules/client/preferences.dm index b4a63bea83..d742bd9b60 100644 --- a/code/modules/client/preferences.dm +++ b/code/modules/client/preferences.dm @@ -63,7 +63,8 @@ var/list/preferences_datums = list() var/species = SPECIES_HUMAN //Species datum to use. var/species_preview //Used for the species selection window. var/list/alternate_languages = list() //Secondary language(s) - var/list/language_prefixes = list() //Kanguage prefix keys + var/list/language_prefixes = list() //Language prefix keys + var/list/language_custom_keys = list() //Language custom call keys var/list/gear //Left in for Legacy reasons, will no longer save. var/list/gear_list = list() //Custom/fluff item loadouts. var/gear_slot = 1 //The current gear save slot @@ -429,12 +430,12 @@ var/list/preferences_datums = list() selecting_slots = FALSE if(!choice) return - + var/slotnum = charlist[choice] if(!slotnum) error("Player picked [choice] slot to load, but that wasn't one we sent.") return - + load_character(slotnum) attempt_vr(user.client?.prefs_vr,"load_vore","") //VOREStation Edit sanitize_preferences() @@ -469,12 +470,12 @@ var/list/preferences_datums = list() selecting_slots = FALSE if(!choice) return - + var/slotnum = charlist[choice] if(!slotnum) error("Player picked [choice] slot to copy to, but that wasn't one we sent.") return - + overwrite_character(slotnum) sanitize_preferences() ShowChoices(user) diff --git a/code/modules/mob/language/language.dm b/code/modules/mob/language/language.dm index 4cb01b981f..4fa749f41e 100644 --- a/code/modules/mob/language/language.dm +++ b/code/modules/mob/language/language.dm @@ -192,6 +192,9 @@ /mob/proc/remove_language(var/rem_language) var/datum/language/L = GLOB.all_languages[rem_language] . = (L in languages) + var/prefix = get_custom_prefix_by_lang(src, L) + if(prefix) + language_keys.Remove(prefix) languages.Remove(L) /mob/living/remove_language(rem_language) @@ -240,7 +243,8 @@ for(var/datum/language/L in languages) if(!(L.flags & NONGLOBAL)) - . += "[L.name] ([get_language_prefix()][L.key])
[L.desc]

" + var/lang_key = get_custom_prefix_by_lang(src, L) + . += "[L.name] ([get_language_prefix()][L.key][lang_key ? " [get_language_prefix()][lang_key]" : ""])
[L.desc]

" /mob/living/check_lang_data() . = "" @@ -250,12 +254,13 @@ for(var/datum/language/L in languages) if(!(L.flags & NONGLOBAL)) + var/lang_key = get_custom_prefix_by_lang(src, L) if(L == default_language) - . += "[L.name] ([get_language_prefix()][L.key]) - default - reset
[L.desc]

" + . += "[L.name] ([get_language_prefix()][L.key][lang_key ? " [get_language_prefix()][lang_key]" : ""]) Edit Custom Key - default - reset
[L.desc]

" else if (can_speak(L)) - . += "[L.name] ([get_language_prefix()][L.key]) - set default
[L.desc]

" + . += "[L.name] ([get_language_prefix()][L.key][lang_key ? " [get_language_prefix()][lang_key]" : ""]) Edit Custom Key - set default
[L.desc]

" else - . += "[L.name] ([get_language_prefix()][L.key]) - cannot speak!
[L.desc]

" + . += "[L.name] ([get_language_prefix()][L.key][lang_key ? " [get_language_prefix()][lang_key]" : ""]) Edit Custom Key - cannot speak!
[L.desc]

" /mob/verb/check_languages() set name = "Check Known Languages" @@ -279,6 +284,22 @@ set_default_language(L) check_languages() return 1 + else if(href_list["set_lang_key"]) + var/datum/language/L = locate(href_list["set_lang_key"]) + if(L && (L in languages)) + var/old_key = get_custom_prefix_by_lang(src, L) + var/custom_key = tgui_input_text(src, "Input a new key for [L.name]", "Language Key", old_key) + if(custom_key && length(custom_key) == 1) + if(contains_az09(custom_key)) + language_keys[custom_key] = L + if(old_key && old_key != custom_key) + language_keys.Remove(old_key) + else if(custom_key == " ") + if(old_key && old_key != custom_key) + language_keys.Remove(old_key) + else + tgui_alert_async(src, "Improper language key. Rejected.", "Error") + check_languages() else return ..() @@ -287,5 +308,17 @@ if(L.flags & except_flags) continue target.add_language(L.name) + for(var/key in source.language_keys) + if(L == source.language_keys[key]) + if(!(key in target.language_keys)) + target.language_keys[key] = L + +/proc/get_custom_prefix_by_lang(var/mob/our_mob, var/language) + if(!our_mob || !our_mob.language_keys.len || !language) + return + + for(var/key in our_mob.language_keys) + if(our_mob.language_keys[key] == language) + return key #undef SCRAMBLE_CACHE_LEN diff --git a/code/modules/mob/mob_defines.dm b/code/modules/mob/mob_defines.dm index dc1f1fc219..19476f2460 100644 --- a/code/modules/mob/mob_defines.dm +++ b/code/modules/mob/mob_defines.dm @@ -94,6 +94,7 @@ var/list/pinned = list() // List of things pinning this creature to walls (see living_defense.dm) var/list/embedded = list() // Embedded items, since simple mobs don't have organs. var/list/languages = list() // For speaking/listening. + var/list/language_keys = list() // List of language keys indexing languages var/species_language = null // For species who want reset to use a specified default. var/only_species_language = 0 // For species who can only speak their default and no other languages. Does not affect understanding. var/list/speak_emote = list("says") // Verbs used when speaking. Defaults to 'say' if speak_emote is null. diff --git a/code/modules/mob/new_player/new_player.dm b/code/modules/mob/new_player/new_player.dm index c152c80b12..3f3b649ee6 100644 --- a/code/modules/mob/new_player/new_player.dm +++ b/code/modules/mob/new_player/new_player.dm @@ -623,6 +623,11 @@ if(chosen_language) if(is_lang_whitelisted(src,chosen_language) || (new_character.species && (chosen_language.name in new_character.species.secondary_langs))) new_character.add_language(lang) + for(var/key in client.prefs.language_custom_keys) + if(client.prefs.language_custom_keys[key]) + var/datum/language/keylang = GLOB.all_languages[client.prefs.language_custom_keys[key]] + if(keylang) + new_character.language_keys[key] = keylang // And uncomment this, too. //new_character.dna.UpdateSE() diff --git a/code/modules/mob/say.dm b/code/modules/mob/say.dm index ceda101ede..ab6be4b1d0 100644 --- a/code/modules/mob/say.dm +++ b/code/modules/mob/say.dm @@ -193,6 +193,8 @@ // Okay, we're definitely now trying to invoke a language (probably) // This "[]" is probably unnecessary but BYOND will runtime if a number is used var/datum/language/L = GLOB.language_keys["[language_key]"] + if((language_key in language_keys) && language_keys[language_key]) + L = language_keys[language_key] // MULTILINGUAL_SPACE enforces a space after the language key if(client && (client.prefs.multilingual_mode == MULTILINGUAL_SPACE) && (text2ascii(copytext(selection, 3, 4)) != 32)) // If we're looking for a space and we don't find one diff --git a/code/modules/resleeving/autoresleever.dm b/code/modules/resleeving/autoresleever.dm index 275c2a14e5..b902df6796 100644 --- a/code/modules/resleeving/autoresleever.dm +++ b/code/modules/resleeving/autoresleever.dm @@ -74,7 +74,7 @@ return var/client/ghost_client = ghost.client - + if(!is_alien_whitelisted(ghost, GLOB.all_species[ghost_client?.prefs?.species]) && !check_rights(R_ADMIN, 0)) // Prevents a ghost ghosting in on a slot and spawning via a resleever with race they're not whitelisted for, getting around normal join restrictions. to_chat(ghost, "You are not whitelisted to spawn as this species!") return @@ -132,7 +132,7 @@ else spawn_slots -- return - + if(tgui_alert(ghost, "Would you like to be resleeved?", "Resleeve", list("No","Yes")) == "No") return var/mob/living/carbon/human/new_character @@ -152,7 +152,7 @@ ghost.mind.transfer_to(new_character) new_character.key = player_key - + //Were they any particular special role? If so, copy. if(new_character.mind) new_character.mind.loaded_from_ckey = picked_ckey @@ -167,6 +167,11 @@ if(chosen_language) if(is_lang_whitelisted(src,chosen_language) || (new_character.species && (chosen_language.name in new_character.species.secondary_langs))) new_character.add_language(lang) + for(var/key in ghost_client.prefs.language_custom_keys) + if(ghost_client.prefs.language_custom_keys[key]) + var/datum/language/keylang = GLOB.all_languages[ghost_client.prefs.language_custom_keys[key]] + if(keylang) + new_character.language_keys[key] = keylang //If desired, apply equipment. if(equip_body) diff --git a/code/modules/vore/eating/inbelly_spawn.dm b/code/modules/vore/eating/inbelly_spawn.dm new file mode 100644 index 0000000000..207f3ca4e0 --- /dev/null +++ b/code/modules/vore/eating/inbelly_spawn.dm @@ -0,0 +1,182 @@ +/mob/observer/dead/verb/spawn_in_belly() + set category = "Ghost" + set name = "Spawn In Belly" + set desc = "Spawn in someone's belly." + + if(!client) + return + + // If any ghost-side restrictions are desired, they'll go here + + tgui_alert(src,{" +This verb allows you to spawn inside someone's belly when they are in round. +Make sure you to coordinate with your predator OOCly as well as roleplay approprietly. +You are considered to have been in the belly entire time the predator was around and are not added to crew lists. +This is not intended to be used for mechanical advantage or providing assistance, but for facilitating longterm scenes. +Please do not abuse this ability. +"},"OOC Warning") // Warning. + + var/list/eligible_targets = list() + + for(var/mob/living/pred in living_mob_list) + if(!istype(pred) || !pred.client) // Ignore preds that aren't living mobs or player controlled + continue + if(pred.no_vore) // No vore, no bellies, no inbelly spawning + continue + if(!(get_z(pred) in using_map.station_levels)) // No explo reinforcements + continue + if(ishuman(pred)) + var/mob/living/carbon/human/H = pred + if(!H.allow_inbelly_spawning) + continue + eligible_targets += H + continue + if(issilicon(pred)) + var/mob/living/silicon/S = pred + if(isAI(S)) + continue // Sorry, AI buddies. Your vore works too differently. + if(!S.allow_inbelly_spawning) + continue + eligible_targets += S + continue + if(istype(pred, /mob/living/simple_mob)) + var/mob/living/simple_mob/SM = pred + if(!SM.vore_active) // No vore, no bellies, no inbelly spawning + continue + if(!SM.allow_inbelly_spawning) + continue + eligible_targets += SM + continue + + // Only humans, simple_mobs and non-AI silicons are included. Obscure stuff like bots is skipped. + + if(!eligible_targets.len) + to_chat(src, "No eligible preds were found.") // :( + return + + var/mob/living/target = tgui_input_list(src, "Please specify which character you want to spawn inside of.", "Predator", eligible_targets) // Offer the list of things we gathered. + + if(!target || !client) // Did out target cease to exist? Or did we? + return + + // Notify them that its now pred's turn + to_chat(src, "Inbelly spawn request sent to predator.") + target.inbelly_spawn_prompt(client) // Hand reins over to them + +/mob/living/proc/inbelly_spawn_prompt(client/potential_prey) + if(!potential_prey || !istype(potential_prey)) // Did our prey cease to exist? + return + + // Are we cool with this prey spawning in at all? + var/answer = tgui_alert(src, "[potential_prey.ckey] (as [potential_prey.prefs.real_name]) wants to spawn in one of your bellies. Do you accept?", "Inbelly Spawning", list("Yes", "No")) + if(answer != "Yes") + to_chat(potential_prey, "Your request was turned down.") + return + + // Let them know so that they don't spam it. + to_chat(potential_prey, "Predator agreed to your request. Wait a bit while they choose a belly.") + + // Where we dropping? + var/obj/belly/belly_choice = tgui_input_list(src, "Choose Target Belly", "Belly Choice", src.vore_organs) + + // Wdym nowhere? + if(!belly_choice || !istype(belly_choice)) + to_chat(potential_prey, "Something went wrong with predator selecting a belly. Try again?") + to_chat(src, "No valid belly selected. Inbelly spawn cancelled.") + return + + // Extra caution never hurts + if(belly_choice.digest_mode == DM_DIGEST) + var/digest_answer = tgui_alert(src, "[belly_choice] is currently set to Digest. Are you sure you want to spawn prey there?", "Inbelly Spawning", list("Yes", "No")) + if(digest_answer != "Yes") + to_chat(potential_prey, "Something went wrong with predator selecting a belly. Try again?") + to_chat(src, "Inbelly spawn cancelled.") + + // Are they already fat (and/or appropriate equivalent)? + var/absorbed = FALSE + var/absorbed_answer = tgui_alert(src, "Do you want them to start absorbed?", "Inbelly Spawning", list("Yes", "No")) + + if(absorbed_answer == "Yes") + absorbed = TRUE + + // They disappeared? + if(!potential_prey) + to_chat(src, "No prey found. Something went wrong!") + return + + // Final confirmation for pred + var/confirmation_pred = tgui_alert(src, "Are you certain that you want [potential_prey.prefs.real_name] spawned in your [belly_choice][absorbed ? ", absorbed" : ""]?", "Inbelly Spawning", list("Yes", "No")) + + if(confirmation_pred != "Yes") + to_chat(potential_prey, "Your pred couldn't finish selection. Try again?") + to_chat(src, "Inbelly spawn cancelled.") + return + + to_chat(src, "Waiting for prey's confirmation...") + + // And final confirmation for prey + var/confirmation_prey = tgui_alert(potential_prey, "Are you certain that you to spawn in [src]'s [belly_choice][absorbed ? ", absorbed" : ""]?", "Inbelly Spawning", list("Yes", "No")) + + if(confirmation_prey == "Yes" && potential_prey && src && belly_choice) + //Now we finally spawn them in! + if(!is_alien_whitelisted(potential_prey, GLOB.all_species[potential_prey.prefs.species])) + to_chat(potential_prey, "You are not whitelisted to play as currently selected character.") + to_chat(src, "Prey accepted the confirmation, but something went wrong with spawning their character.") + return + inbelly_spawn(potential_prey, src, belly_choice, absorbed) + else + to_chat(potential_prey, "Inbelly spawn cancelled.") + to_chat(src, "Prey cancelled their inbelly spawn request.") + return + +/proc/inbelly_spawn(client/prey, mob/living/pred, obj/belly/target_belly, var/absorbed = FALSE) + // All this is basically admin late spawn-in, but skipping all parts related to records and equipment and with predteremined location + var/player_key = prey.key + var/picked_ckey = prey.ckey + var/picked_slot = prey.prefs.default_slot + var/mob/living/carbon/human/new_character + + new_character = new(null) // Spawn them in nullspace first. Can't have "Defaultname Defaultnameson slides into your Stomach". + + if(!new_character) + return + + prey.prefs.copy_to(new_character) + if(new_character.dna) + new_character.dna.ResetUIFrom(new_character) + new_character.sync_organ_dna() + new_character.key = player_key + if(new_character.mind) + var/datum/antagonist/antag_data = get_antag_data(new_character.mind.special_role) + if(antag_data) + antag_data.add_antagonist(new_character.mind) + antag_data.place_mob(new_character) + + if(new_character.mind) + new_character.mind.loaded_from_ckey = picked_ckey + new_character.mind.loaded_from_slot = picked_slot + + for(var/lang in prey.prefs.alternate_languages) + var/datum/language/chosen_language = GLOB.all_languages[lang] + if(chosen_language) + if(is_lang_whitelisted(prey,chosen_language) || (new_character.species && (chosen_language.name in new_character.species.secondary_langs))) + new_character.add_language(lang) + for(var/key in prey.prefs.language_custom_keys) + if(prey.prefs.language_custom_keys[key]) + var/datum/language/keylang = GLOB.all_languages[prey.prefs.language_custom_keys[key]] + if(keylang) + new_character.language_keys[key] = keylang + + new_character.regenerate_icons() + + new_character.update_transform() + + new_character.forceMove(target_belly) // Now that they're all setup and configured, send them to their destination. + + if(absorbed) + target_belly.absorb_living(new_character) // Glorp. + + log_admin("[prey] (as [new_character.real_name] has spawned inside one of [pred]'s bellies.") // Log it. Avoid abuse. + message_admins("[prey] (as [new_character.real_name] has spawned inside one of [pred]'s bellies.", 1) + + return new_character // incase its ever needed \ No newline at end of file