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