diff --git a/code/__DEFINES/language.dm b/code/__DEFINES/language.dm
index 2ee6ba37703..6c0affaffe2 100644
--- a/code/__DEFINES/language.dm
+++ b/code/__DEFINES/language.dm
@@ -2,8 +2,12 @@
#define TONGUELESS_SPEECH (1<<1)
#define LANGUAGE_HIDE_ICON_IF_UNDERSTOOD (1<<2)
#define LANGUAGE_HIDE_ICON_IF_NOT_UNDERSTOOD (1<<3)
+/// This language can be selected in preferences.
+#define ROUNDSTART_LANGUAGE (1<<4)
/// This language is signed, not spoken.
-#define SIGNED_LANGUAGE (1<<4)
+#define SIGNED_LANGUAGE (1<<5)
+/// Sarathi do not "sss" when speaking this language
+#define NO_HISS (1<<6)
// LANGUAGE SOURCE DEFINES
/// For use in full removal only.
diff --git a/code/__DEFINES/preferences.dm b/code/__DEFINES/preferences.dm
index 131af782bf1..c5b37e2694d 100644
--- a/code/__DEFINES/preferences.dm
+++ b/code/__DEFINES/preferences.dm
@@ -128,5 +128,16 @@
#define PROSTHETIC_AMPUTATED "amputated"
#define PROSTHETIC_ROBOTIC "prosthetic"
+/// You cannot speak or understand this language whatsoever.
+#define LANGUAGE_UNKNOWN "Unknown (0)"
+/// You cannot speak this language, but can recognize some of the words.
+#define LANGUAGE_RECOGNIZED "Recognized (1)"
+/// You are familiar with this language enough to sort of speak it, but cannot understand it very well.
+#define LANGUAGE_FAMILIAR "Familiar (2)"
+/// You are fluent in this language, and can both understand and speak it perfectly.
+#define LANGUAGE_FLUENT "Fluent (3)"
+/// Maximum number of additional languages that can be selected.
+#define MAX_LANGUAGE_POINTS 4
+
#define NOT_SYNTHETIC FALSE
#define IS_SYNTHETIC TRUE
diff --git a/code/controllers/subsystem/overmap.dm b/code/controllers/subsystem/overmap.dm
index 6c474ef77b2..6a8986a46bd 100644
--- a/code/controllers/subsystem/overmap.dm
+++ b/code/controllers/subsystem/overmap.dm
@@ -23,6 +23,9 @@ SUBSYSTEM_DEF(overmap)
/// The mandatory and default star system
var/datum/overmap_star_system/default_system
+ /// The secondary star system that allows planet spawns
+ var/datum/overmap_star_system/wild_system
+
///Should events be processed
var/events_enabled = TRUE
@@ -41,8 +44,6 @@ SUBSYSTEM_DEF(overmap)
//if(length(tracked_star_systems) >= 1)
// CRASH("Attempted to create more than 1 star system. Having mutiple star systems is not supported.")
- if(length(tracked_star_systems) >= 1)
- WARNING("Attempted to create more than 1 star system. Bugs may occur as this isn't very well supported, you have been warned")
tracked_star_systems += new_starsystem
return new_starsystem
@@ -56,7 +57,9 @@ SUBSYSTEM_DEF(overmap)
dynamic_encounters = list()
events = list()
- default_system = create_new_star_system(new /datum/overmap_star_system/shiptest)
+ var/list/sector_types = pick(subtypesof(/datum/overmap_star_system/safezone))
+ default_system = create_new_star_system(new sector_types)
+ wild_system = create_new_star_system (new /datum/overmap_star_system/shiptest)
return ..()
/datum/controller/subsystem/overmap/proc/spawn_new_star_system(datum/overmap_star_system/system_to_spawn=/datum/overmap_star_system)
@@ -64,7 +67,6 @@ SUBSYSTEM_DEF(overmap)
return create_new_star_system(system_to_spawn)
return create_new_star_system(new system_to_spawn)
-
/datum/controller/subsystem/overmap/fire()
for(var/datum/overmap_star_system/current_system as anything in tracked_star_systems)
if(!current_system.encounters_refresh)
@@ -345,6 +347,9 @@ SUBSYSTEM_DEF(overmap)
//can our pallete be selected randomly roundstart? set to no for subtypes or if you dont change the pallete
var/can_be_selected_randomly = TRUE
+ /// Datum type for the main outpost spawned here
+ var/default_outpost_type
+
COOLDOWN_DECLARE(dynamic_despawn_cooldown)
/datum/overmap_star_system/New(generate_now=TRUE)
@@ -371,7 +376,7 @@ SUBSYSTEM_DEF(overmap)
if(!size)
size = CONFIG_GET(number/overmap_size)
if(!max_overmap_dynamic_events)
- max_overmap_dynamic_events = CONFIG_GET(number/max_overmap_dynamic_events)
+ max_overmap_dynamic_events = isnull(max_overmap_dynamic_events)
overmap_container = new/list(size, size, 0)
@@ -516,24 +521,23 @@ SUBSYSTEM_DEF(overmap)
/datum/overmap_star_system/proc/spawn_outpost()
var/list/location = get_unused_overmap_square_in_radius(rand(4, round(size/5)))
- var/datum/overmap/outpost/found_type
if(fexists(OUTPOST_OVERRIDE_FILEPATH))
var/file_text = trim_right(file2text(OUTPOST_OVERRIDE_FILEPATH)) // trim_right because there's often a trailing newline
var/datum/overmap/outpost/potential_type = text2path(file_text)
if(!potential_type || !ispath(potential_type, /datum/overmap/outpost))
stack_trace("SSovermap found an outpost override file at [OUTPOST_OVERRIDE_FILEPATH], but was unable to find the outpost type [potential_type]!")
else
- found_type = potential_type
+ default_outpost_type = potential_type
fdel(OUTPOST_OVERRIDE_FILEPATH) // don't want it to affect 2 rounds in a row.
- if(!found_type)
+ if(!default_outpost_type)
var/list/possible_types = subtypesof(/datum/overmap/outpost)
for(var/datum/overmap/outpost/outpost_type as anything in possible_types)
if(!initial(outpost_type.main_template))
possible_types -= outpost_type
- found_type = pick(possible_types)
+ default_outpost_type = pick(possible_types)
- var/datum/overmap/outpost/our_outpost = new found_type(location, src)
+ var/datum/overmap/outpost/our_outpost = new default_outpost_type(location, src)
//gets rid of nearby events that casue radio interference
for(var/direction as anything in GLOB.cardinals)
@@ -970,11 +974,7 @@ SUBSYSTEM_DEF(overmap)
override_object_colors = TRUE
overmap_icon_state = "overmap"
- dynamic_probabilities = list(\
- DYNAMIC_WORLD_BEACHPLANET = 10,
- DYNAMIC_WORLD_SPACERUIN = 5,
- DYNAMIC_WORLD_MOON = 20,
- )
+ max_overmap_dynamic_events = 0
/datum/overmap_star_system/zx_spectrum_pallete
//main colors, used for dockable terrestrials, and background
@@ -1067,9 +1067,11 @@ SUBSYSTEM_DEF(overmap)
else
datum_to_edit.token.add_filter("gloweffect", 5, list("type"="drop_shadow", "color"= "#808080", "size"=2, "offset"=1))
-/datum/overmap_star_system/ngr
- name = "Gorlex Controlled - Ecbatana"
+/datum/overmap_star_system/safezone/ngr
+ name = "Gorlex Controlled - Value of Public Works"
starname = "Ecbatana"
+ startype = /datum/overmap/star/dwarf
+ default_outpost_type = /datum/overmap/outpost/ngr_rock
//main colors, used for dockable terrestrials, and background
primary_color = "#d9ad82"
@@ -1086,6 +1088,69 @@ SUBSYSTEM_DEF(overmap)
override_object_colors = TRUE
overmap_icon_state = "overmap_dark"
+/datum/overmap_star_system/safezone/clip
+ name = "CLIP Controlled - High-Pier"
+ starname = "Chana"
+ startype = /datum/overmap/star/dwarf/orange
+ default_outpost_type = /datum/overmap/outpost/clip_ocean
+
+ //main colors, used for dockable terrestrials, and background
+ primary_color = "#6fa8de"
+ secondary_color = "#96b6d4"
+
+ //hazard colors, used for the overmap hazards and sun
+ hazard_primary_color = "#d5e3f0"
+ hazard_secondary_color = "#96a6b5"
+
+ //structure colors, used for ships and outposts/colonies
+ primary_structure_color = "#97dfe8"
+ secondary_structure_color = "#6fa8de"
+
+ override_object_colors = TRUE
+ overmap_icon_state = "overmap_dark"
+
+/datum/overmap_star_system/safezone/trifuge
+ name = "Independent - Minya"
+ starname = "Aubaine"
+ startype = /datum/overmap/star/medium
+ default_outpost_type = /datum/overmap/outpost/indie_space
+
+ //main colors, used for dockable terrestrials, and background
+ primary_color = "#5e5e5e"
+ secondary_color = "#242424"
+
+ //hazard colors, used for the overmap hazards and sun
+ hazard_primary_color = "#b56060"
+ hazard_secondary_color = "#824242"
+
+ //structure colors, used for ships and outposts/colonies
+ primary_structure_color = "#ffffff"
+ secondary_structure_color = "#ffffff"
+
+ override_object_colors = TRUE
+ overmap_icon_state = "overmap"
+
+/datum/overmap_star_system/safezone/nt
+ name = "Nanotrasen Controlled - Persei-277"
+ starname = "Persei-277"
+ startype = /datum/overmap/star/medium
+ default_outpost_type = /datum/overmap/outpost/nanotrasen_ice
+
+ //main colors, used for dockable terrestrials, and background
+ primary_color = "#7e8cd9"
+ secondary_color = "#33324a"
+
+ //hazard colors, used for the overmap hazards and sun
+ hazard_primary_color = "#ededed"
+ hazard_secondary_color = "#7f7db0"
+
+ //structure colors, used for ships and outposts/colonies
+ primary_structure_color = "#4272db"
+ secondary_structure_color = "#38a0eb"
+
+ override_object_colors = TRUE
+ overmap_icon_state = "overmap_dark"
+
/datum/overmap_star_system/c64
//main colors, used for dockable terrestrials, and background
@@ -1105,13 +1170,13 @@ SUBSYSTEM_DEF(overmap)
//default shiptest overmap
/datum/overmap_star_system/shiptest
- has_outpost = TRUE
+ has_outpost = FALSE
can_be_selected_randomly = FALSE
encounters_refresh = TRUE
+ max_overmap_dynamic_events = 15
/datum/overmap_star_system/shiptest/create_map()
. = ..()
- set_station_name(starname)
/datum/overmap_star_system/admin_sandbox
name = "Admin Sandbox"
diff --git a/code/controllers/subsystem/processing/quirks.dm b/code/controllers/subsystem/processing/quirks.dm
index 0813bc6640b..04d796c09ba 100644
--- a/code/controllers/subsystem/processing/quirks.dm
+++ b/code/controllers/subsystem/processing/quirks.dm
@@ -24,16 +24,14 @@ PROCESSING_SUBSYSTEM_DEF(quirks)
SetupQuirks()
quirk_blacklist = list(
- list("Blind","Nearsighted"), \
- list("Ageusia","Vegetarian","Deviant Tastes"), \
+ list("Ananas Affinity","Ananas Aversion"), //PENTEST ADDED
+ list("Blind","Nearsighted"),
+ list("Ageusia","Vegetarian","Deviant Tastes"),
list("Alcohol Tolerance","Light Drinker"),
- list("Bad Touch", "Friendly"), \
- list("Self-Aware", "Congenital Analgesia"), \
- //list("(Language) Moth Pidgin", "(Language) Solarian International", "(Language) Teceti Unified Standard", "(Language) Kalixcian Common"), \
- //PENTEST EDITS BELOW
- list("Ananas Affinity","Ananas Aversion"), \
- list("(Language) Moth Pidgin", "(Language) Terran International", "(Language) Teceti Unified Standard", "(Language) Draconic Common"), \
- )
+ list("Bad Touch", "Friendly"),
+ list("Self-Aware", "Congenital Analgesia"),
+ list("Trilingual", "Monolingual"),
+ )
species_blacklist = list("Blood Deficiency" = list(SPECIES_IPC, SPECIES_JELLYPERSON, SPECIES_PLASMAMAN, SPECIES_VAMPIRE))
diff --git a/code/datums/traits/negative/monolingual.dm b/code/datums/traits/negative/monolingual.dm
new file mode 100644
index 00000000000..e30b8454052
--- /dev/null
+++ b/code/datums/traits/negative/monolingual.dm
@@ -0,0 +1,4 @@
+/datum/quirk/monolingual
+ name = "Monolingual"
+ desc = "You're only fluent in your native language. (-2 language points)"
+ value = -1
diff --git a/code/datums/traits/neutral/languages.dm b/code/datums/traits/neutral/languages.dm
deleted file mode 100644
index 816a518d927..00000000000
--- a/code/datums/traits/neutral/languages.dm
+++ /dev/null
@@ -1,97 +0,0 @@
-//Languages - not worth their own file
-
-// Kalixcian Common
-/datum/quirk/lang_kalixcian
- name = "(Language) Kalixcian Common"
- desc = "You're fluent in Kalixcian Common."
- value = 0
- gain_text = span_notice("You know Kalixcian Common.")
- lose_text = span_danger("You forget Kalixcian Common.")
- detectable = FALSE
-
-/datum/quirk/lang_kalixcian/add()
- var/mob/living/carbon/human/knower = quirk_holder
- knower.grant_language(/datum/language/kalixcian_common, source = LANGUAGE_MIND)
-
-/datum/quirk/lang_kalixcian/remove()
- if(quirk_holder)
- var/mob/living/carbon/human/knower = quirk_holder
- knower.remove_language(/datum/language/kalixcian_common, source = LANGUAGE_MIND)
-
-// Teceti Unified Standard
-/datum/quirk/lang_tuc
- name = "(Language) Teceti Unified Standard"
- desc = "You're fluent in Teceti Unified Standard."
- value = 0
- gain_text = span_notice("You know Teceti Unified.")
- lose_text = span_danger("You forget Teceti Unified.")
- detectable = FALSE
-
-/datum/quirk/lang_tuc/add()
- var/mob/living/carbon/human/knower = quirk_holder
- knower.grant_language(/datum/language/teceti_unified, source = LANGUAGE_MIND)
-
-/datum/quirk/lang_tuc/remove()
- if(quirk_holder)
- var/mob/living/carbon/human/knower = quirk_holder
- knower.remove_language(/datum/language/teceti_unified, source = LANGUAGE_MIND)
-
-// Solarian International
-/datum/quirk/lang_solarian_international
- name = "(Language) Solarian International"
- desc = "You're fluent in Solarian International."
- value = 0
- gain_text = span_notice("You know Solarian International.")
- lose_text = span_danger("You forget Solarian International.")
- detectable = FALSE
-
-/datum/quirk/lang_solarian_international/add()
- var/mob/living/carbon/human/knower = quirk_holder
- knower.grant_language(/datum/language/solarian_international, source = LANGUAGE_MIND)
-
-/datum/quirk/lang_solarian_international/remove()
- if(quirk_holder)
- var/mob/living/carbon/human/knower = quirk_holder
- knower.remove_language(/datum/language/solarian_international, source = LANGUAGE_MIND)
-
-// Moth Pidgin
-/datum/quirk/lang_moth
- name = "(Language) Moth Pidgin"
- desc = "You're fluent in Moth Pidgin."
- gain_text = span_notice("You know Moth Pidgin.")
- lose_text = span_danger("You forget Moth Pidgin.")
- detectable = FALSE
-
-/datum/quirk/lang_moth/add()
- var/mob/living/carbon/human/knower = quirk_holder
- knower.grant_language(/datum/language/moffic, source = LANGUAGE_MIND)
-
-/datum/quirk/lang_moth/remove()
- if(quirk_holder)
- var/mob/living/carbon/human/knower = quirk_holder
- knower.remove_language(/datum/language/moffic, source = LANGUAGE_MIND)
-
-// Sign Language
-/datum/quirk/signer
- name = "Signer"
- desc = "You're fluent in Universal Sign Language and have translation gloves to communicate over radio."
- value = 0
- gain_text = span_notice("You know Universal Sign Language.")
- lose_text = span_danger("You forget Universal Sign Language.")
- detectable = FALSE
-
-/datum/quirk/signer/add()
- var/mob/living/carbon/human/knower = quirk_holder
- knower.grant_language(/datum/language/sign_language, source = LANGUAGE_MIND)
- // Give translation gloves so they can use sign language over radio
- var/obj/item/clothing/gloves/radio/translator_gloves = new(get_turf(knower))
- if(!knower.equip_to_slot_if_possible(translator_gloves, ITEM_SLOT_GLOVES, disable_warning = TRUE))
- // If they can't equip them (already wearing gloves), put them in hands or drop nearby
- if(!knower.put_in_hands(translator_gloves))
- translator_gloves.forceMove(get_turf(knower))
- to_chat(knower, span_notice("A pair of translation gloves appears at your feet."))
-
-/datum/quirk/signer/remove()
- if(quirk_holder)
- var/mob/living/carbon/human/knower = quirk_holder
- knower.remove_language(/datum/language/sign_language, source = LANGUAGE_MIND)
diff --git a/code/datums/traits/positive/trilingual.dm b/code/datums/traits/positive/trilingual.dm
new file mode 100644
index 00000000000..a7492c430c0
--- /dev/null
+++ b/code/datums/traits/positive/trilingual.dm
@@ -0,0 +1,4 @@
+/datum/quirk/trilingual
+ name = "Trilingual"
+ desc = "You're fluent in three languages. (+2 language points)"
+ value = 1
diff --git a/code/game/atoms_movable.dm b/code/game/atoms_movable.dm
index 29d69256e75..e34377749e1 100644
--- a/code/game/atoms_movable.dm
+++ b/code/game/atoms_movable.dm
@@ -1205,7 +1205,10 @@
/// Gets a lazylist of all mutually understood languages.
/atom/movable/proc/get_partially_understood_languages()
- return get_language_holder().best_mutual_languages
+ var/datum/language_holder/our_holder = get_language_holder()
+ if(!our_holder.best_mutual_languages)
+ our_holder.calculate_best_mutual_language()
+ return our_holder.best_mutual_languages
/// Gets a random spoken language, useful for forced speech and such.
/atom/movable/proc/get_random_spoken_language()
diff --git a/code/game/objects/effects/contraband.dm b/code/game/objects/effects/contraband.dm
index 35f620fbf20..a70ec9ca0b0 100644
--- a/code/game/objects/effects/contraband.dm
+++ b/code/game/objects/effects/contraband.dm
@@ -788,7 +788,7 @@
/obj/structure/sign/poster/retro/radio
name = "Radio"
- desc = "A poster advertising one of Nanotrasen's earliest products, a radio. One of its main selling points was a integrated OS and two way automatic translation for Solarian Common, and Kalixcian Common, which made it a smash hit. This thing is ancient."
+ desc = "A poster advertising one of Nanotrasen's earliest products, a radio. One of its main selling points was a integrated OS and two way automatic translation for Solarian Common and Gezenan, which made it a smash hit. This thing is ancient."
icon_state = "poster-radio70_retro"
//Safety moth posters, credit to AspEv for the art which the below posters are based on and to Ayy-Robotics for the sprites.
diff --git a/code/modules/admin/verbs/randomverbs.dm b/code/modules/admin/verbs/randomverbs.dm
index 820c5ed62a7..a564e868ad9 100644
--- a/code/modules/admin/verbs/randomverbs.dm
+++ b/code/modules/admin/verbs/randomverbs.dm
@@ -961,7 +961,7 @@
nova.size = inputed
inputed = input(usr, "Choose Maximum amount of Dynamic Events", "Spawn Overmap", nova.max_overmap_dynamic_events) as num
- if(!inputed)
+ if(isnull(inputed))
QDEL_NULL(nova)
return
nova.max_overmap_dynamic_events = inputed
diff --git a/code/modules/client/preferences.dm b/code/modules/client/preferences.dm
index bd89cf70c77..9bf04fad3bf 100644
--- a/code/modules/client/preferences.dm
+++ b/code/modules/client/preferences.dm
@@ -163,6 +163,15 @@ GLOBAL_LIST_EMPTY(preferences_datums)
//Job preferences 2.0 - indexed by job title , no key or value implies never
var/list/job_preferences = list()
+ /// Languages this character knows besides their native language.
+ var/list/learned_languages = list()
+
+ /// This character's native language.
+ var/datum/language/native_language = /datum/language/galactic_common
+
+ /// Associated list with language levels of understanding and their point costs.
+ var/static/list/language_level_costs = list(LANGUAGE_UNKNOWN = 0, LANGUAGE_RECOGNIZED = 1, LANGUAGE_FAMILIAR = 2, LANGUAGE_FLUENT = 3)
+
// 0 = character settings, 1 = game preferences
var/current_tab = 0
@@ -359,6 +368,18 @@ GLOBAL_LIST_EMPTY(preferences_datums)
dat += "Custom Job Preferences:
"
dat += "Preferred AI Core Display: [preferred_ai_core_display]
"
+ dat += "
Languages | "
+
dat += ""
dat += "Clothing
"
@@ -1502,6 +1523,24 @@ GLOBAL_LIST_EMPTY(preferences_datums)
if(SSquirks.quirk_points[q] > 0)
.++
+/datum/preferences/proc/init_learned_languages()
+ learned_languages = list()
+ for(var/datum/language/lang_type as anything in subtypesof(/datum/language))
+ if(initial(lang_type.flags) & ROUNDSTART_LANGUAGE)
+ learned_languages[lang_type] = LANGUAGE_UNKNOWN
+
+/datum/preferences/proc/get_language_point_balance()
+ var/points_balance = MAX_LANGUAGE_POINTS
+ for(var/datum/language/lang_type as anything in learned_languages)
+ if(lang_type == native_language)
+ continue // this should happen but just in case
+ points_balance -= language_level_costs[learned_languages[lang_type]]
+ if("Trilingual" in all_quirks)
+ points_balance += 2
+ if("Monolingual" in all_quirks)
+ points_balance -= 2
+ return points_balance
+
/datum/preferences/Topic(href, href_list, hsrc) //yeah, gotta do this I guess..
. = ..()
if(href_list["close"])
@@ -1575,6 +1614,18 @@ GLOBAL_LIST_EMPTY(preferences_datums)
if(balance + value < 0)
to_chat(user, span_warning("Refunding this would cause you to go below your balance!"))
return
+ // PENTEST ADDITION - START - Check if removing Trilingual or adding Monolingual would cause negative language points
+ if(quirk == "Trilingual" || quirk == "Monolingual")
+ var/temp_quirks = all_quirks.Copy()
+ temp_quirks -= quirk
+ var/old_all_quirks = all_quirks
+ all_quirks = temp_quirks
+ var/lang_balance = get_language_point_balance()
+ all_quirks = old_all_quirks
+ if(lang_balance < 0)
+ to_chat(user, span_warning("You cannot remove this quirk as it would leave you with insufficient language points! Reset your languages first."))
+ return
+ // PENTEST ADDITION - END
all_quirks -= quirk
else
var/is_positive_quirk = SSquirks.quirk_points[quirk] > 0
@@ -1584,6 +1635,18 @@ GLOBAL_LIST_EMPTY(preferences_datums)
if(balance - value < 0)
to_chat(user, span_warning("You don't have enough balance to gain this quirk!"))
return
+ // PENTEST ADDITION - START - Check if adding Monolingual or removing Trilingual would cause negative language points
+ if(quirk == "Trilingual" || quirk == "Monolingual")
+ var/temp_quirks = all_quirks.Copy()
+ temp_quirks += quirk
+ var/old_all_quirks = all_quirks
+ all_quirks = temp_quirks
+ var/lang_balance = get_language_point_balance()
+ all_quirks = old_all_quirks
+ if(lang_balance < 0)
+ to_chat(user, span_warning("You cannot select this quirk as it would leave you with insufficient language points! Reset your languages first."))
+ return
+ // PENTEST ADDITION - END
all_quirks += quirk
SetQuirks(user)
if("reset")
@@ -2098,6 +2161,34 @@ GLOBAL_LIST_EMPTY(preferences_datums)
if(ai_core_icon)
preferred_ai_core_display = ai_core_icon
+ if("native_language")
+ var/list/language_list = list()
+ for(var/datum/language/lang_type as anything in learned_languages)
+ language_list[initial(lang_type.name)] = lang_type
+ var/datum/language/new_lang = language_list[tgui_input_list(user, "Select a native language:", "Native Language", language_list)]
+ if(ispath(new_lang, /datum/language) && (initial(new_lang.flags) & ROUNDSTART_LANGUAGE)) // double-check to prevent exploits to gain codespeak or something as a native language
+ native_language = new_lang
+ learned_languages[new_lang] = LANGUAGE_UNKNOWN
+
+ if("learned_language")
+ var/datum/language/selected_lang = locate(href_list["language"])
+ if(selected_lang.type == native_language) // wuh oh
+ CRASH("[usr] attempted to change level of understanding for [selected_lang] despite it being their native language!")
+ if(selected_lang && (selected_lang.flags & ROUNDSTART_LANGUAGE)) // no using html exploits to learn codespeak
+ var/understanding = tgui_input_list(user, "Select level of understanding:", "Learn Language", language_level_costs)
+ if(!understanding)
+ return
+ if(!(understanding in language_level_costs))
+ CRASH("[usr] attempted to set level of understanding for [selected_lang.type] to \"[understanding]\"")
+ var/old_value = learned_languages[selected_lang.type]
+ learned_languages[selected_lang.type] = understanding
+ if(get_language_point_balance() < 0 && understanding != LANGUAGE_UNKNOWN) // in case something breaks REAL bad, you can still disable languages to fix it
+ learned_languages[selected_lang.type] = old_value
+ to_chat(usr, span_warning("You don't have enough language points!"))
+
+ if("reset_languages")
+ init_learned_languages()
+
if ("clientfps")
var/desiredfps = input(user, "Choose your desired fps. (0 = default, 60 FPS))", "Character Preference", clientfps) as null|num //WS Edit - Client FPS Tweak -
if (!isnull(desiredfps))
@@ -2566,6 +2657,36 @@ GLOBAL_LIST_EMPTY(preferences_datums)
character.update_body_parts(TRUE)
character.dna.update_body_size()
+ if(!character_setup && get_language_point_balance() < 0)
+ // PENTEST ADDITON - START - This is a check to prevent exploits where players can gain more languages than they should be able to. If this happens, it will reset their languages and log it to admins.
+ log_admin("WARNING: [parent?.ckey] spawned with negative language points. Resetting languages.")
+ message_admins("WARNING: [key_name_admin(parent)] spawned with negative language points. Languages have been reset.")
+ // PENTEST ADDITION - END
+ init_learned_languages() // no exploits allowed
+ else if(get_language_point_balance() < 0)
+ // Pentest ADDITION - START - Additional check during character setup
+ log_admin("WARNING: [parent?.ckey] in character setup with negative language points. Resetting languages.")
+ message_admins("WARNING: [key_name_admin(parent)] in character setup with negative language points. Languages have been reset.")
+ init_learned_languages()
+ // PENTEST ADDITION - END
+ character.grant_language(native_language)
+ character.get_language_holder().selected_language = native_language
+ for(var/datum/language/lang_type as anything in learned_languages)
+ if(lang_type == native_language)
+ continue
+ switch(learned_languages[lang_type])
+ if(LANGUAGE_FLUENT)
+ character.grant_language(lang_type)
+ if(LANGUAGE_FAMILIAR)
+ character.grant_language(lang_type, SPOKEN_LANGUAGE)
+ character.remove_language(lang_type, UNDERSTOOD_LANGUAGE)
+ character.grant_partial_language(lang_type, 80)
+ if(LANGUAGE_RECOGNIZED)
+ character.remove_language(lang_type)
+ character.grant_partial_language(lang_type, 40)
+ if(LANGUAGE_UNKNOWN)
+ character.remove_language(lang_type)
+
/datum/preferences/proc/get_default_name(name_id)
switch(name_id)
if("ai")
diff --git a/code/modules/client/preferences_savefile.dm b/code/modules/client/preferences_savefile.dm
index d29c6113145..17bd8fd7046 100644
--- a/code/modules/client/preferences_savefile.dm
+++ b/code/modules/client/preferences_savefile.dm
@@ -438,6 +438,10 @@ SAVEFILE UPDATING/VERSIONING - 'Simplified', or rather, more coder-friendly ~Car
READ_FILE(S["body_size"], features["body_size"])
READ_FILE(S["prosthetic_limbs"], prosthetic_limbs)
prosthetic_limbs ||= list(BODY_ZONE_L_ARM = PROSTHETIC_NORMAL, BODY_ZONE_R_ARM = PROSTHETIC_NORMAL, BODY_ZONE_L_LEG = PROSTHETIC_NORMAL, BODY_ZONE_R_LEG = PROSTHETIC_NORMAL)
+ READ_FILE(S["learned_languages"], learned_languages)
+ if(!learned_languages?.len) init_learned_languages()
+ READ_FILE(S["native_language"], native_language)
+ native_language ||= /datum/language/galactic_common
READ_FILE(S["feature_mcolor"], features["mcolor"])
READ_FILE(S["feature_mcolor2"], features["mcolor2"])
READ_FILE(S["feature_ethcolor"], features["ethcolor"])
@@ -502,6 +506,13 @@ SAVEFILE UPDATING/VERSIONING - 'Simplified', or rather, more coder-friendly ~Car
//Quirks
READ_FILE(S["all_quirks"], all_quirks)
+ var/list/removed_quirks = list()
+ for(var/quirk_name in all_quirks.Copy())
+ if(!(quirk_name in SSquirks.quirks))
+ all_quirks.Remove(quirk_name)
+ removed_quirks.Add(quirk_name)
+ if(removed_quirks.len)
+ to_chat(parent, "Some of your previously selected quirks have been removed: [english_list(removed_quirks)].")
//Flavor Text
S["feature_flavor_text"] >> features["flavor_text"]
@@ -599,6 +610,15 @@ SAVEFILE UPDATING/VERSIONING - 'Simplified', or rather, more coder-friendly ~Car
/datum/preferences/proc/save_character()
if(!path)
return FALSE
+
+ // PENTEST ADDITION - START - Validate language points before saving
+ var/lang_balance = get_language_point_balance()
+ if(lang_balance < 0)
+ log_admin("WARNING: [parent?.ckey] attempted to save character with negative language points ([lang_balance]). Resetting languages.")
+ message_admins("WARNING: [key_name_admin(parent)] attempted to save character with negative language points ([lang_balance]). Languages have been reset.")
+ init_learned_languages()
+ // PENTEST ADDITION - END
+
var/savefile/S = new /savefile(path)
if(!S)
return FALSE
@@ -631,6 +651,8 @@ SAVEFILE UPDATING/VERSIONING - 'Simplified', or rather, more coder-friendly ~Car
WRITE_FILE(S["generic_adjective"] , generic_adjective)
WRITE_FILE(S["body_size"] , features["body_size"])
WRITE_FILE(S["prosthetic_limbs"] , prosthetic_limbs)
+ WRITE_FILE(S["learned_languages"] , learned_languages)
+ WRITE_FILE(S["native_language"] , native_language)
WRITE_FILE(S["feature_mcolor"] , features["mcolor"])
WRITE_FILE(S["feature_mcolor2"] , features["mcolor2"])
WRITE_FILE(S["feature_ethcolor"] , features["ethcolor"])
diff --git a/code/modules/clothing/outfits/factions/gezena.dm b/code/modules/clothing/outfits/factions/gezena.dm
index ce24f187e64..9b74156b9ab 100644
--- a/code/modules/clothing/outfits/factions/gezena.dm
+++ b/code/modules/clothing/outfits/factions/gezena.dm
@@ -3,12 +3,6 @@
faction = FACTION_PLAYER_GEZENA
// faction_icon = "bg_pgf"
-/datum/outfit/job/gezena/post_equip(mob/living/carbon/human/H, visualsOnly)
- . = ..()
- if(visualsOnly)
- return
- H.grant_language(/datum/language/kalixcian_common)
-
//Playable Roles (put in ships):
/datum/outfit/job/gezena/assistant
name = "PGF - Crewman"
diff --git a/code/modules/clothing/outfits/factions/solgov.dm b/code/modules/clothing/outfits/factions/solgov.dm
index 5663a9655dd..d78880cd965 100644
--- a/code/modules/clothing/outfits/factions/solgov.dm
+++ b/code/modules/clothing/outfits/factions/solgov.dm
@@ -3,12 +3,6 @@
faction = FACTION_PLAYER_SOLCON
faction_icon = "bg_solgov"
-/datum/outfit/job/solgov/post_equip(mob/living/carbon/human/H, visualsOnly)
- . = ..()
- if(visualsOnly)
- return
- H.grant_language(/datum/language/solarian_international)
-
/datum/outfit/job/solgov/assistant
name = "SolGov - Scribe"
id_assignment = "Scribe"
diff --git a/code/modules/faction/faction_datum.dm b/code/modules/faction/faction_datum.dm
index 7d8d58c6290..30b9ad6bcf7 100644
--- a/code/modules/faction/faction_datum.dm
+++ b/code/modules/faction/faction_datum.dm
@@ -8,6 +8,8 @@
var/list/prefixes
/// List/Typecache of factions that this faction is allowed to interact with. Non-recursive.
var/list/allowed_factions = list()
+ /// The official language of this faction. Galactic Common by default.
+ var/official_language = /datum/language/galactic_common
/// Theme color for this faction, currently only used for the wiki
var/color = "#ffffff"
/// Contrast color for this faction, used for links on the wiki
@@ -68,6 +70,7 @@
/datum/faction/syndicate/suns
name = FACTION_SUNS
short_name = "SUNS"
+ official_language = /datum/language/solarian_international
prefixes = PREFIX_SUNS
color = "#CD94D3"
@@ -79,6 +82,7 @@
/datum/faction/solgov
name = FACTION_SOLCON
parent_faction = /datum/faction/solgov
+ official_language = /datum/language/solarian_international
prefixes = PREFIX_SOLCON
color = "#444e5f"
@@ -132,6 +136,7 @@
name = FACTION_PGF
short_name = "PGF"
parent_faction = /datum/faction/pgf
+ official_language = /datum/language/gezena_kalixcian
prefixes = PREFIX_PGF
color = "#359829"
diff --git a/code/modules/language/buzzwords.dm b/code/modules/language/buzzwords.dm
deleted file mode 100644
index ad41497b80d..00000000000
--- a/code/modules/language/buzzwords.dm
+++ /dev/null
@@ -1,15 +0,0 @@
-/datum/language/buzzwords
- name = "Buzzwords"
- desc = "A common language innate to all bugs, made by the rhythmic beating of wings."
- speech_verb = "buzzes"
- ask_verb = "buzzes"
- exclaim_verb = "loudly buzzes"
- sing_verb = "hums"
- flags = LANGUAGE_HIDE_ICON_IF_NOT_UNDERSTOOD
- key = "z"
- space_chance = 0
- syllables = list(
- "bzz","zzz","z","bz","bzzz","zzzz", "bzzzz", "b", "zz", "zzzzz"
- )
- icon_state = "buzz"
- default_priority = 90
diff --git a/code/modules/language/common.dm b/code/modules/language/common.dm
index 9bcc2e07b08..ce4f0ce4a18 100644
--- a/code/modules/language/common.dm
+++ b/code/modules/language/common.dm
@@ -3,14 +3,15 @@
name = "Galactic Common"
desc = "The common galactic tongue."
key = "0"
- flags = TONGUELESS_SPEECH | LANGUAGE_HIDE_ICON_IF_UNDERSTOOD
+ flags = TONGUELESS_SPEECH | LANGUAGE_HIDE_ICON_IF_UNDERSTOOD | ROUNDSTART_LANGUAGE
default_priority = 100
icon_state = "galcom"
mutual_understanding = list(
/datum/language/solarian_international = 20,
- /datum/language/kalixcian_common = 20,
+ /datum/language/gezena_kalixcian = 20,
+ /datum/language/zohil_kalixcian = 15, // similar to gezenan
)
diff --git a/code/modules/language/draconic.dm b/code/modules/language/gezenan.dm
similarity index 69%
rename from code/modules/language/draconic.dm
rename to code/modules/language/gezenan.dm
index 710beb6600a..d1e1699960e 100644
--- a/code/modules/language/draconic.dm
+++ b/code/modules/language/gezenan.dm
@@ -1,12 +1,12 @@
-/datum/language/kalixcian_common
- name = "Kalixcian Common"
- desc = "The most prevalent language to come out of Kalixcis, and generally understood by all those native to it."
+/datum/language/gezena_kalixcian
+ name = "Gezenan"
+ desc = "The most widely spoken Kalixcian language, and the official language of the Pan-Gezena Federation."
speech_verb = "hisses"
ask_verb = "hisses"
exclaim_verb = "roars"
sing_verb = "sings"
key = "o"
- flags = TONGUELESS_SPEECH | LANGUAGE_HIDE_ICON_IF_NOT_UNDERSTOOD
+ flags = TONGUELESS_SPEECH | LANGUAGE_HIDE_ICON_IF_NOT_UNDERSTOOD | ROUNDSTART_LANGUAGE | NO_HISS
space_chance = 12
sentence_chance = 0
between_word_sentence_chance = 10
@@ -20,7 +20,11 @@
"ka", "ak", "ke", "ek", "ki", "ik", "ko", "ok", "ku", "uk", "ks", "sk",
"sa", "as", "se", "es", "si", "is", "so", "os", "su", "us", "ss", "ss",
"ra", "ar", "re", "er", "ri", "ir", "ro", "or", "ru", "ur", "rs", "sr",
+ "na", "an", "ne", "en", "ni", "in", "no", "on", "nu", "un", "ng", "ts",
"a", "a", "e", "e", "i", "i", "o", "o", "u", "u", "s", "s"
)
icon_state = "lizard"
default_priority = 90
+ mutual_understanding = list(
+ /datum/language/zohil_kalixcian = 90, // enough to sort of understand each other, but not perfectly
+ )
diff --git a/code/modules/language/language.dm b/code/modules/language/language.dm
index b7b66dd4b23..76c9ef38388 100644
--- a/code/modules/language/language.dm
+++ b/code/modules/language/language.dm
@@ -2,6 +2,12 @@
#define SCRAMBLE_CACHE_LEN 50
/// Last 20 spoken sentences will be cached before we start cycling them out (re-randomizing them)
#define SENTENCE_CACHE_LEN 20
+/// Number of hex characters the MD5 will use to scramble text (16**SCRAMBLE_HASH_SIZE must not exceed 2**32)
+#define SCRAMBLE_HASH_SIZE 4
+/// Probability check using MD5 hash
+#define HASH_PROB(probability, raw_hash, hash_offset) (probability >= 100 ? TRUE : (probability / 100 >= hex2num(copytext(raw_hash, hash_offset, hash_offset + SCRAMBLE_HASH_SIZE)) / ((16**SCRAMBLE_HASH_SIZE) - 1)))
+/// Pick from a list using MD5 hash (will cause problems if SCRAMBLE_HASH_SIZE is too low)
+#define HASH_PICK(list, raw_hash, hash_offset) (list?.len ? list[1 + hex2num(copytext(raw_hash, hash_offset, hash_offset + SCRAMBLE_HASH_SIZE)) % list.len] : null)
/// Datum based languages. Easily editable and modular.
/datum/language
@@ -18,6 +24,8 @@
var/list/syllables
/// List of characters that will randomly be inserted between syllables.
var/list/special_characters
+ /// Likelihood of inserting special characters between syllables.
+ var/special_character_chance = 0
// These modify how syllables are combined.
/// Likelihood of making a new sentence after each syllable.
@@ -126,7 +134,8 @@
/// Checks whether we should display the language icon to the passed hearer.
/datum/language/proc/display_icon(atom/movable/hearer)
- var/understands = hearer.has_language(src.type)
+ var/list/partial_understanding = hearer.get_partially_understood_languages()
+ var/understands = hearer.has_language(type) || (partial_understanding?[type] >= 50)
if((flags & LANGUAGE_HIDE_ICON_IF_UNDERSTOOD) && understands)
return FALSE
if((flags & LANGUAGE_HIDE_ICON_IF_NOT_UNDERSTOOD) && !understands)
@@ -274,16 +283,16 @@
for(var/word in splittext(input, " "))
var/translate_prob = mutual_languages?[type] || 0
var/base_word = strip_outer_punctuation(word)
+ var/raw_hash = md5("[lowertext(base_word)]/[GLOB.round_id]")
if(translate_prob > 0)
// the probability of managing to understand a word is based on how common it is (+10%, -15%)
// 1000 words in the list, so words outside the list are just treated as "the 1250th most common word"
var/commonness = GLOB.most_common_words[lowertext(base_word)] || 1250
translate_prob += (10 * (1 - (min(commonness, 1250) / 500)))
- if(prob(translate_prob))
+ if(HASH_PROB(translate_prob, raw_hash, 1))
scrambled_words += word
translated_index += FALSE
continue
-
var/scrambled_word = scramble_word(base_word)
scrambled_words += scrambled_word
translated_index += (scrambled_word != base_word)
@@ -296,11 +305,12 @@
if(!translated_index[i])
sentence += " [word]"
continue
+ var/raw_hash = md5("[lowertext(word)]/[lowertext(scrambled_words[i - 1])]/[GLOB.round_id]")
// if the last word was scrambled, always include a space
- if(translated_index[i - 1] || prob(between_word_space_chance))
+ if(translated_index[i - 1] || HASH_PROB(between_word_space_chance, raw_hash, 1))
sentence += " "
// lastly try inserting a new sentence
- else if(prob(between_word_sentence_chance))
+ else if(HASH_PROB(between_word_sentence_chance, raw_hash, 1 + SCRAMBLE_HASH_SIZE))
sentence += ". "
word = capitalize(word)
@@ -333,30 +343,29 @@
var/add_period = FALSE
word = ""
while(length_char(word) < input_size)
+ // uses the MD5 hash to make sure each word is always scrambled to the same thing within a given round
+ var/raw_hash = md5("[lowertext(input)]/[lowertext(word)]/[GLOB.round_id]")
// add in the last syllable's period or space first
if(add_period)
word += ". "
else if(add_space)
word += " "
// insert special chars if we're not at the start of the word
- else if(word && prob(1) && length(special_characters))
- word += pick(special_characters)
+ else if(word && HASH_PROB(special_character_chance, raw_hash, 1) && length(special_characters))
+ word += HASH_PICK(special_characters, raw_hash, 1 + SCRAMBLE_HASH_SIZE)
// generate the next syllable (capitalize if we just added a period)
- var/next = pick_weight_recursive(syllables)
+ var/next = HASH_PICK(syllables, raw_hash, 1 + SCRAMBLE_HASH_SIZE * 2)
word += add_period ? capitalize(next) : next
// determine if the next syllable gets a period or space
- add_period = prob(sentence_chance)
- add_space = prob(space_chance)
+ add_period = HASH_PROB(sentence_chance, raw_hash, 1 + SCRAMBLE_HASH_SIZE * 3)
+ add_space = HASH_PROB(space_chance, raw_hash, 1 + SCRAMBLE_HASH_SIZE * 4)
write_word_cache(input, word)
// If they're shouting, we're shouting
return (is_uppercase(input) && length_char(input) >= 2) ? uppertext(word) : word
-/**
- * Called from mob/living/say()
- */
-/datum/language/proc/on_say(atom/movable/speaker, message, bubble_type, list/spans = list(), datum/language/language = null)
- return
-
+#undef HASH_PICK
+#undef HASH_PROB
+#undef SCRAMBLE_HASH_SIZE
#undef SCRAMBLE_CACHE_LEN
diff --git a/code/modules/language/language_holder.dm b/code/modules/language/language_holder.dm
index abaa89e71c7..d78a3b9a7d9 100644
--- a/code/modules/language/language_holder.dm
+++ b/code/modules/language/language_holder.dm
@@ -362,7 +362,7 @@ GLOBAL_LIST_INIT(prototype_language_holders, init_language_holder_prototypes())
spoken_languages = list(/datum/language/galactic_common = list(LANGUAGE_ATOM))
/datum/language_holder/lizard/ash
- selected_language = /datum/language/kalixcian_common
+ selected_language = /datum/language/gezena_kalixcian
/datum/language_holder/monkey
understood_languages = list(/datum/language/galactic_common = list(LANGUAGE_ATOM),
@@ -381,7 +381,7 @@ GLOBAL_LIST_INIT(prototype_language_holders, init_language_holder_prototypes())
/datum/language_holder/synthetic
understood_languages = list(/datum/language/galactic_common = list(LANGUAGE_ATOM),
/datum/language/machine = list(LANGUAGE_ATOM),
- /datum/language/kalixcian_common = list(LANGUAGE_ATOM),
+ /datum/language/gezena_kalixcian = list(LANGUAGE_ATOM),
/datum/language/moffic = list(LANGUAGE_ATOM),
/datum/language/rachnidian = list(LANGUAGE_ATOM),
/datum/language/teceti_unified = list(LANGUAGE_ATOM),
@@ -389,7 +389,7 @@ GLOBAL_LIST_INIT(prototype_language_holders, init_language_holder_prototypes())
/datum/language/sign_language = list(LANGUAGE_ATOM))
spoken_languages = list(/datum/language/galactic_common = list(LANGUAGE_ATOM),
/datum/language/machine = list(LANGUAGE_ATOM),
- /datum/language/kalixcian_common = list(LANGUAGE_ATOM),
+ /datum/language/gezena_kalixcian = list(LANGUAGE_ATOM),
/datum/language/moffic = list(LANGUAGE_ATOM),
/datum/language/teceti_unified = list(LANGUAGE_ATOM),
/datum/language/solarian_international = list(LANGUAGE_ATOM),
diff --git a/code/modules/language/language_menu.dm b/code/modules/language/language_menu.dm
index 33b96171c9f..11d76c9e75e 100644
--- a/code/modules/language/language_menu.dm
+++ b/code/modules/language/language_menu.dm
@@ -23,6 +23,7 @@
var/list/data = list()
var/atom/movable/speaker = language_holder.owner
+ var/list/partial_languages = speaker?.get_partially_understood_languages()
data["languages"] = list()
for(var/datum/language/language as anything in GLOB.all_languages)
var/list/lang_data = list()
@@ -37,6 +38,7 @@
lang_data["can_speak"] = !!speaker.has_language(language, SPOKEN_LANGUAGE)
lang_data["could_speak"] = !!(language_holder.omnitongue || speaker.could_speak_language(language))
lang_data["can_understand"] = !!speaker.has_language(language, UNDERSTOOD_LANGUAGE)
+ lang_data["partial_understanding"] = partial_languages?[language] || 0
UNTYPED_LIST_ADD(data["languages"], lang_data)
@@ -80,6 +82,14 @@
if("Both")
adding_flags |= ALL
+ if(adding_flags & UNDERSTOOD_LANGUAGE)
+ var/partial_understanding = tgui_input_number(user, "Set level of understanding:", "[language_datum]", 100, 1, 100)
+ if(isnull(partial_understanding))
+ return
+ if(partial_understanding < 100)
+ adding_flags &= ~UNDERSTOOD_LANGUAGE
+ language_holder.grant_partial_language(language_datum, partial_understanding)
+
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")
@@ -105,6 +115,8 @@
removing_flags |= ALL
language_holder.remove_language(language_datum, removing_flags)
+ if(removing_flags & UNDERSTOOD_LANGUAGE)
+ language_holder.remove_partial_language(language_datum)
if(is_admin)
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)].")
diff --git a/code/modules/language/moffic.dm b/code/modules/language/moffic.dm
index 9c43cda6642..a74eea63210 100644
--- a/code/modules/language/moffic.dm
+++ b/code/modules/language/moffic.dm
@@ -4,7 +4,7 @@
speech_verb = "flutters"
ask_verb = "fluffs"
exclaim_verb = "floofs"
- flags = LANGUAGE_HIDE_ICON_IF_NOT_UNDERSTOOD
+ flags = LANGUAGE_HIDE_ICON_IF_NOT_UNDERSTOOD | ROUNDSTART_LANGUAGE
key = "m"
space_chance = 55
syllables = list( //hollow knight
diff --git a/code/modules/language/sign.dm b/code/modules/language/sign.dm
index 1c2e6af2df3..37669e528d5 100644
--- a/code/modules/language/sign.dm
+++ b/code/modules/language/sign.dm
@@ -8,7 +8,7 @@
whisper_verb = "subtly signs"
sing_verb = "rythmically signs"
key = "u"
- flags = TONGUELESS_SPEECH | LANGUAGE_HIDE_ICON_IF_UNDERSTOOD | SIGNED_LANGUAGE
+ flags = TONGUELESS_SPEECH | LANGUAGE_HIDE_ICON_IF_UNDERSTOOD | ROUNDSTART_LANGUAGE | SIGNED_LANGUAGE | NO_HISS
default_priority = 99
use_tone_indicators = TRUE
bubble_override = "signlang"
diff --git a/code/modules/language/solarian.dm b/code/modules/language/solarian.dm
index 425156824e0..bbd0a931649 100644
--- a/code/modules/language/solarian.dm
+++ b/code/modules/language/solarian.dm
@@ -2,7 +2,7 @@
name = "Solarian International Standard"
desc = "The natural fusion of the Solarian languages that survived the Night Of Fire, which gradually coalesced into a single language."
key = "c"
- flags = TONGUELESS_SPEECH | LANGUAGE_HIDE_ICON_IF_NOT_UNDERSTOOD
+ flags = TONGUELESS_SPEECH | LANGUAGE_HIDE_ICON_IF_NOT_UNDERSTOOD | ROUNDSTART_LANGUAGE
default_priority = 90
space_chance = 20
sentence_chance = 0
diff --git a/code/modules/language/teceti_unified.dm b/code/modules/language/teceti_unified.dm
index 5d2f8339961..f3a9ffb86be 100644
--- a/code/modules/language/teceti_unified.dm
+++ b/code/modules/language/teceti_unified.dm
@@ -4,7 +4,7 @@
speech_verb = "chirps"
ask_verb = "chirps"
key = "f"
- flags = LANGUAGE_HIDE_ICON_IF_NOT_UNDERSTOOD
+ flags = LANGUAGE_HIDE_ICON_IF_NOT_UNDERSTOOD | ROUNDSTART_LANGUAGE
space_chance = 40
sentence_chance = 0
between_word_sentence_chance = 20
diff --git a/code/modules/language/zohilan.dm b/code/modules/language/zohilan.dm
new file mode 100644
index 00000000000..a64d15ef880
--- /dev/null
+++ b/code/modules/language/zohilan.dm
@@ -0,0 +1,33 @@
+/datum/language/zohil_kalixcian
+ name = "Zohilan"
+ desc = "A Kalixcian language commonly spoken in the Zohil Explorat and its former colonies in the Maxin system. Speakers of other Kalixcian languages often find it difficult to pronounce."
+ speech_verb = "hisses"
+ ask_verb = "hisses"
+ exclaim_verb = "roars"
+ sing_verb = "sings"
+ key = "z"
+ flags = TONGUELESS_SPEECH | LANGUAGE_HIDE_ICON_IF_NOT_UNDERSTOOD | ROUNDSTART_LANGUAGE | NO_HISS
+ space_chance = 12
+ sentence_chance = 0
+ between_word_sentence_chance = 10
+ between_word_space_chance = 75
+ additional_syllable_low = 0
+ additional_syllable_high = 3
+ syllables = list(
+ "za", "az", "ze", "ez", "zi", "iz", "zo", "oz", "zu", "uz", "zs", "sz",
+ "ha", "ah", "he", "eh", "hi", "ih", "ho", "oh", "hu", "uh", "hs", "sh",
+ "la", "al", "le", "el", "li", "il", "lo", "ol", "lu", "ul", "ls", "sl",
+ "ka", "ak", "ke", "ek", "ki", "ik", "ko", "ok", "ku", "uk", "ks", "sk",
+ "sa", "as", "se", "es", "si", "is", "so", "os", "su", "us", "ss", "ss",
+ "ra", "ar", "re", "er", "ri", "ir", "ro", "or", "ru", "ur", "rs", "sr",
+ "ta", "at", "te", "et", "ti", "it", "to", "ot", "tu", "ut", "th", "zh",
+ "qa", "aq", "qe", "eq", "qi", "iq", "qo", "oq", "qu", "uq", "bh", "zl",
+ "a", "a", "e", "e", "i", "i", "o", "o", "u", "u", "s", "s"
+ )
+ special_characters = list("'")
+ special_character_chance = 20
+ icon_state = "lizard-blue"
+ default_priority = 90
+ mutual_understanding = list(
+ /datum/language/gezena_kalixcian = 90, // enough to sort of understand each other, but not perfectly
+ )
diff --git a/code/modules/mob/dead/new_player/ship_select.dm b/code/modules/mob/dead/new_player/ship_select.dm
index 276e88ce611..c2fe8b46799 100644
--- a/code/modules/mob/dead/new_player/ship_select.dm
+++ b/code/modules/mob/dead/new_player/ship_select.dm
@@ -56,6 +56,12 @@
to_chat(spawnee, span_warning("You cannot join this ship anymore, as its join mode has changed!"))
return
+ var/datum/faction/registered_faction = target.shuttle_port.registered_faction
+ var/datum/language/official_lang = initial(registered_faction.official_language)
+ if(official_lang != spawnee.client.prefs.native_language && spawnee.client.prefs.learned_languages[official_lang] != LANGUAGE_FLUENT && \
+ tgui_alert(spawnee, "Your character does not fully understand this faction's official language ([initial(official_lang.name)]), are you sure?", "Official language", list("Yes", "No")) != "Yes")
+ return // pop-up warning for new players that forgot to set their
+
ui.close()
var/datum/job/selected_job = locate(params["job"]) in target.job_slots
//boots you out if you're banned from officer roles
diff --git a/code/modules/overmap/helm.dm b/code/modules/overmap/helm.dm
index d645c2da5c2..0a3078e4a30 100644
--- a/code/modules/overmap/helm.dm
+++ b/code/modules/overmap/helm.dm
@@ -50,12 +50,8 @@
icon_state = "computer-solgov"
deconpath = /obj/structure/frame/computer/solgov
-/datum/config_entry/number/bluespace_jump_wait
- default = 5 MINUTES
-
/obj/machinery/computer/helm/Initialize(mapload, obj/item/circuitboard/C)
. = ..()
- jump_allowed = world.time + CONFIG_GET(number/bluespace_jump_wait)
ntnet_relay = new(src)
/obj/machinery/computer/helm/examine(mob/user)
@@ -72,10 +68,6 @@
if(current_ship.docked_to || current_ship.docking)
say("Bluespace Jump Calibration detected interference in the local area.")
return
- if(world.time < jump_allowed)
- var/jump_wait = DisplayTimeText(jump_allowed - world.time)
- say("Bluespace Jump Calibration is currently recharging. ETA: [jump_wait].")
- return
message_admins("[ADMIN_LOOKUPFLW(usr)] has initiated a bluespace jump in [ADMIN_VERBOSEJMP(src)]")
jump_timer = addtimer(CALLBACK(src, PROC_REF(jump_sequence), TRUE), JUMP_CHARGEUP_TIME, TIMER_STOPPABLE)
if(new_system)
@@ -148,6 +140,11 @@
current_ship.helms -= src
current_ship = port.current_ship
current_ship.helms |= src
+ if(port.registered_faction)
+ var/datum/language/official_language = port.registered_faction.official_language
+ var/datum/language_holder/lang_holder = get_language_holder()
+ grant_language(official_language)
+ lang_holder.selected_language = official_language
/**
* This proc manually rechecks that the helm computer is connected to a proper ship
diff --git a/code/modules/shuttle/shuttle.dm b/code/modules/shuttle/shuttle.dm
index 9d8885f6f24..674ed536f6a 100644
--- a/code/modules/shuttle/shuttle.dm
+++ b/code/modules/shuttle/shuttle.dm
@@ -472,6 +472,9 @@
///List of all stationary docking ports that spawned on the ship roundstart, used for docking to other ships.
var/list/obj/docking_port/stationary/docking_points
+ ///The faction this shuttle is registered under.
+ var/datum/faction/registered_faction
+
/// Does this shuttle play sounds upon landing and takeoff?
var/shuttle_sounds = TRUE
/// The take off sound to be played
@@ -536,6 +539,7 @@
/obj/docking_port/mobile/proc/load(datum/map_template/shuttle/source_template)
+ registered_faction = source_template.faction
shuttle_areas = list()
var/list/all_turfs = return_ordered_turfs(x, y, z, dir)
for(var/turf/curT as anything in all_turfs)
diff --git a/code/modules/surgery/organs/tongue.dm b/code/modules/surgery/organs/tongue.dm
index dac77bb7e9a..302d38e7f5e 100644
--- a/code/modules/surgery/organs/tongue.dm
+++ b/code/modules/surgery/organs/tongue.dm
@@ -16,7 +16,8 @@
var/modifies_speech = FALSE
var/static/list/languages_possible_base = typecacheof(list(
/datum/language/galactic_common,
- /datum/language/kalixcian_common,
+ /datum/language/gezena_kalixcian,
+ /datum/language/zohil_kalixcian,
/datum/language/teceti_unified,
//datum/language/solarian_international,
/datum/language/moffic,
@@ -59,8 +60,8 @@
/obj/item/organ/tongue/lizard/handle_speech(datum/source, list/speech_args)
// Sarathi tongues don't hiss when speaking Kalixcian. Or when signing.
// we should make non-sarathi hiss in Kalixcian
- var/datum/language/language_used = speech_args[SPEECH_LANGUAGE]
- if((language_used == /datum/language/kalixcian_common) || (initial(language_used?.flags) & SIGNED_LANGUAGE))
+ var/datum/language/lang_type = speech_args[SPEECH_LANGUAGE]
+ if(initial(lang_type.flags) & NO_HISS)
return
var/static/regex/lizard_hiss = new("s+", "g")
@@ -95,7 +96,8 @@
var/list/phomeme_types = list("sans", "papyrus")
var/static/list/languages_possible_skeleton = typecacheof(list(
/datum/language/galactic_common,
- /datum/language/kalixcian_common,
+ /datum/language/gezena_kalixcian,
+ /datum/language/zohil_kalixcian,
/datum/language/codespeak,
/datum/language/monkey,
/datum/language/aphasia,
@@ -161,7 +163,8 @@
taste_sensitivity = 101 // Not a tongue, they can't taste shit
var/static/list/languages_possible_ethereal = typecacheof(list(
/datum/language/galactic_common,
- /datum/language/kalixcian_common,
+ /datum/language/gezena_kalixcian,
+ /datum/language/zohil_kalixcian,
/datum/language/teceti_unified,
/datum/language/solarian_international,
/datum/language/moffic,
@@ -183,7 +186,8 @@
say_mod = "flutters"
var/static/list/languages_possible_moth = typecacheof(list(
/datum/language/galactic_common,
- /datum/language/kalixcian_common,
+ /datum/language/gezena_kalixcian,
+ /datum/language/zohil_kalixcian,
/datum/language/teceti_unified,
/datum/language/solarian_international,
/datum/language/moffic,
@@ -202,7 +206,8 @@
say_mod = "chirps"
var/static/list/languages_possible_kepi = typecacheof(list(
/datum/language/galactic_common,
- /datum/language/kalixcian_common,
+ /datum/language/gezena_kalixcian,
+ /datum/language/zohil_kalixcian,
/datum/language/teceti_unified,
/datum/language/solarian_international,
/datum/language/moffic,
@@ -223,7 +228,8 @@
say_mod = "shrieks"
var/static/list/languages_possible_vox = typecacheof(list(
/datum/language/galactic_common,
- /datum/language/kalixcian_common,
+ /datum/language/gezena_kalixcian,
+ /datum/language/zohil_kalixcian,
/datum/language/teceti_unified,
/datum/language/solarian_international,
/datum/language/moffic,
@@ -245,13 +251,13 @@
say_mod = "chitters"
var/static/list/languages_possible_arachnid = typecacheof(list(
/datum/language/galactic_common,
- /datum/language/kalixcian_common,
+ /datum/language/gezena_kalixcian,
+ /datum/language/zohil_kalixcian,
/datum/language/codespeak,
/datum/language/monkey,
/datum/language/aphasia,
/datum/language/moffic,
- /datum/language/rachnidian,
- /datum/language/buzzwords
+ /datum/language/rachnidian
))
/obj/item/organ/tongue/spider/Initialize(mapload)
@@ -286,7 +292,8 @@
say_mod = "blorbles"
var/static/list/languages_possible_slime = typecacheof(list(
/datum/language/galactic_common,
- /datum/language/kalixcian_common,
+ /datum/language/gezena_kalixcian,
+ /datum/language/zohil_kalixcian,
/datum/language/teceti_unified,
/datum/language/solarian_international,
/datum/language/moffic,
@@ -420,7 +427,8 @@
modifies_speech = TRUE
var/static/list/languages_possible_fly = typecacheof(list(
/datum/language/galactic_common,
- /datum/language/kalixcian_common,
+ /datum/language/gezena_kalixcian,
+ /datum/language/zohil_kalixcian,
/datum/language/teceti_unified,
/datum/language/solarian_international,
/datum/language/moffic,
diff --git a/icons/misc/language.dmi b/icons/misc/language.dmi
index 9adf235b3a8..c6ced0ec683 100644
Binary files a/icons/misc/language.dmi and b/icons/misc/language.dmi differ
diff --git a/modular_pentest/master_files/code/modules/language/language_holder.dm b/modular_pentest/master_files/code/modules/language/language_holder.dm
index b8d38baf1f8..cdfaef69360 100644
--- a/modular_pentest/master_files/code/modules/language/language_holder.dm
+++ b/modular_pentest/master_files/code/modules/language/language_holder.dm
@@ -6,35 +6,3 @@
understood_languages = list(/datum/language/swarmer = list(LANGUAGE_ATOM))
spoken_languages = list(/datum/language/swarmer = list(LANGUAGE_ATOM))
blocked_languages = list(/datum/language/galactic_common = list(LANGUAGE_ATOM))
-
-/*
-/datum/language_holder/human
- understood_languages = list(/datum/language/galactic_common = list(LANGUAGE_ATOM),
- /datum/language/solarian_international = list(LANGUAGE_ATOM))
- spoken_languages = list(/datum/language/galactic_common = list(LANGUAGE_ATOM),
- /datum/language/solarian_international = list(LANGUAGE_ATOM))
-*/
-
-/datum/language_holder/lizard
- understood_languages = list(/datum/language/galactic_common = list(LANGUAGE_ATOM),
- /datum/language/kalixcian_common = list(LANGUAGE_ATOM))
- spoken_languages = list(/datum/language/galactic_common = list(LANGUAGE_ATOM),
- /datum/language/kalixcian_common = list(LANGUAGE_ATOM))
-
-/datum/language_holder/moth
- understood_languages = list(/datum/language/galactic_common = list(LANGUAGE_ATOM),
- /datum/language/moffic = list(LANGUAGE_ATOM))
- spoken_languages = list(/datum/language/galactic_common = list(LANGUAGE_ATOM),
- /datum/language/moffic = list(LANGUAGE_ATOM))
-
-/datum/language_holder/ethereal
- understood_languages = list(/datum/language/galactic_common = list(LANGUAGE_ATOM),
- /datum/language/kalixcian_common = list(LANGUAGE_ATOM))
- spoken_languages = list(/datum/language/galactic_common = list(LANGUAGE_ATOM),
- /datum/language/kalixcian_common = list(LANGUAGE_ATOM))
-
-/datum/language_holder/kepori
- understood_languages = list(/datum/language/galactic_common = list(LANGUAGE_ATOM),
- /datum/language/teceti_unified = list(LANGUAGE_ATOM))
- spoken_languages = list(/datum/language/galactic_common = list(LANGUAGE_ATOM),
- /datum/language/teceti_unified = list(LANGUAGE_ATOM))
diff --git a/shiptest.dme b/shiptest.dme
index 03f4b126a71..f35c34b5e57 100644
--- a/shiptest.dme
+++ b/shiptest.dme
@@ -918,6 +918,7 @@
#include "code\datums\traits\negative\frail.dm"
#include "code\datums\traits\negative\heavy_sleeper.dm"
#include "code\datums\traits\negative\light_drinker.dm"
+#include "code\datums\traits\negative\monolingual.dm"
#include "code\datums\traits\negative\mute.dm"
#include "code\datums\traits\negative\nearsighted.dm"
#include "code\datums\traits\negative\paraplegic.dm"
@@ -928,7 +929,6 @@
#include "code\datums\traits\neutral\bald.dm"
#include "code\datums\traits\neutral\deviant_tastes.dm"
#include "code\datums\traits\neutral\gunslinger.dm"
-#include "code\datums\traits\neutral\languages.dm"
#include "code\datums\traits\neutral\monochromatic.dm"
#include "code\datums\traits\neutral\musician.dm"
#include "code\datums\traits\neutral\photographer.dm"
@@ -941,6 +941,7 @@
#include "code\datums\traits\positive\friendly.dm"
#include "code\datums\traits\positive\light_step.dm"
#include "code\datums\traits\positive\self_aware.dm"
+#include "code\datums\traits\positive\trilingual.dm"
#include "code\datums\votes\_vote_datum.dm"
#include "code\datums\votes\custom_vote.dm"
#include "code\datums\votes\restart_vote.dm"
@@ -2542,11 +2543,10 @@
#include "code\modules\keybindings\focus.dm"
#include "code\modules\keybindings\setup.dm"
#include "code\modules\language\aphasia.dm"
-#include "code\modules\language\buzzwords.dm"
#include "code\modules\language\codespeak.dm"
#include "code\modules\language\common.dm"
-#include "code\modules\language\draconic.dm"
#include "code\modules\language\drone.dm"
+#include "code\modules\language\gezenan.dm"
#include "code\modules\language\language.dm"
#include "code\modules\language\language_holder.dm"
#include "code\modules\language\language_menu.dm"
@@ -2563,6 +2563,7 @@
#include "code\modules\language\teceti_unified.dm"
#include "code\modules\language\vox_pidgin.dm"
#include "code\modules\language\xenocommon.dm"
+#include "code\modules\language\zohilan.dm"
#include "code\modules\library\lib_items.dm"
#include "code\modules\library\lib_machines.dm"
#include "code\modules\library\random_books.dm"
diff --git a/tgui/packages/tgui/interfaces/LanguageMenu.tsx b/tgui/packages/tgui/interfaces/LanguageMenu.tsx
index ad58fd2163c..03577e0402a 100644
--- a/tgui/packages/tgui/interfaces/LanguageMenu.tsx
+++ b/tgui/packages/tgui/interfaces/LanguageMenu.tsx
@@ -70,6 +70,7 @@ const LangUnderstandIcon = (props: LanguageProps) => {
borderBottom: '2px dotted rgba(255, 255, 255, 0.8)',
}}
>
+
{language.partial_understanding}%