diff --git a/_maps/map_levels/140x140/tradeport.dmm b/_maps/map_levels/140x140/tradeport.dmm index c81a0ba456b..bf81ac22046 100644 --- a/_maps/map_levels/140x140/tradeport.dmm +++ b/_maps/map_levels/140x140/tradeport.dmm @@ -10698,7 +10698,7 @@ /obj/item/storage/backpack/rig{ pixel_x = -7 }, -/obj/item/universal_translator/ear/all{ +/obj/item/universal_translator/ear/adaptive{ pixel_x = 11; pixel_y = -3 }, diff --git a/_maps/map_levels/192x192/tradeport.dmm b/_maps/map_levels/192x192/tradeport.dmm index 21adf748248..28bae4c1729 100644 --- a/_maps/map_levels/192x192/tradeport.dmm +++ b/_maps/map_levels/192x192/tradeport.dmm @@ -8862,7 +8862,7 @@ /obj/item/storage/backpack/rig{ pixel_x = -7 }, -/obj/item/universal_translator/ear/all{ +/obj/item/universal_translator/ear/adaptive{ pixel_x = 11; pixel_y = -3 }, diff --git a/citadel.dme b/citadel.dme index 19499b43a5a..f5df3f60f28 100644 --- a/citadel.dme +++ b/citadel.dme @@ -189,6 +189,7 @@ #include "code\__DEFINES\languages\flags.dm" #include "code\__DEFINES\languages\ids.dm" #include "code\__DEFINES\languages\legacy.dm" +#include "code\__DEFINES\languages\translation.dm" #include "code\__DEFINES\mapping\multiz.dm" #include "code\__DEFINES\misc\message_ranges.dm" #include "code\__DEFINES\mobs\grab.dm" @@ -317,6 +318,7 @@ #include "code\__HELPERS\unsorted\contents.dm" #include "code\__HELPERS\vfx\appearance_cloning.dm" #include "code\__HELPERS\vfx\color_animations.dm" +#include "code\__HELPERS\vfx\flick.dm" #include "code\__HELPERS\vfx\emissives.dm" #include "code\__HELPERS\vfx\see_anywhere.dm" #include "code\__HELPERS\vfx\shake_camera.dm" @@ -481,6 +483,7 @@ #include "code\controllers\subsystem\dbcore\query.dm" #include "code\controllers\subsystem\job\_job.dm" #include "code\controllers\subsystem\job\_legacy_job_stuff.dm" +#include "code\controllers\subsystem\job\jexp.dm" #include "code\controllers\subsystem\job\job_manager.dm" #include "code\controllers\subsystem\job\joining.dm" #include "code\controllers\subsystem\job\roundstart.dm" @@ -1363,7 +1366,6 @@ #include "code\game\objects\items\devices\spy_bug.dm" #include "code\game\objects\items\devices\suit_cooling.dm" #include "code\game\objects\items\devices\t_scanner.dm" -#include "code\game\objects\items\devices\taperecorder.dm" #include "code\game\objects\items\devices\text_to_speech.dm" #include "code\game\objects\items\devices\traitordevices.dm" #include "code\game\objects\items\devices\transfer_valve.dm" @@ -1389,6 +1391,9 @@ #include "code\game\objects\items\devices\radio\intercom.dm" #include "code\game\objects\items\devices\radio\jammer.dm" #include "code\game\objects\items\devices\radio\radio.dm" +#include "code\game\objects\items\devices\tape_recorder\cassette_tape.dm" +#include "code\game\objects\items\devices\tape_recorder\tape_iterator.dm" +#include "code\game\objects\items\devices\tape_recorder\tape_recorder.dm" #include "code\game\objects\items\id_cards\cards.dm" #include "code\game\objects\items\id_cards\sprite_stacks_cit.dm" #include "code\game\objects\items\id_cards\station_ids.dm" @@ -2552,42 +2557,44 @@ #include "code\modules\keybindings\keybind\mob.dm" #include "code\modules\keybindings\keybind\movement.dm" #include "code\modules\keybindings\keybind\robot.dm" -#include "code\modules\language\abstract.dm" -#include "code\modules\language\animal.dm" -#include "code\modules\language\core.dm" -#include "code\modules\language\event.dm" -#include "code\modules\language\galactic.dm" +#include "code\modules\language\hivemind.dm" #include "code\modules\language\holder.dm" #include "code\modules\language\language.dm" -#include "code\modules\language\monkey.dm" -#include "code\modules\language\silicons.dm" -#include "code\modules\language\antagonist\borer.dm" -#include "code\modules\language\antagonist\changeling.dm" -#include "code\modules\language\antagonist\cult.dm" -#include "code\modules\language\antagonist\swarmbot.dm" -#include "code\modules\language\antagonist\xenomorph.dm" -#include "code\modules\language\species\_species.dm" -#include "code\modules\language\species\adherent.dm" -#include "code\modules\language\species\akula.dm" -#include "code\modules\language\species\alraune.dm" -#include "code\modules\language\species\altevian.dm" -#include "code\modules\language\species\birdsong.dm" -#include "code\modules\language\species\celestial.dm" -#include "code\modules\language\species\diona.dm" -#include "code\modules\language\species\human.dm" -#include "code\modules\language\species\moth.dm" -#include "code\modules\language\species\naramadi.dm" -#include "code\modules\language\species\phoronoid.dm" -#include "code\modules\language\species\shadekin.dm" -#include "code\modules\language\species\skrell.dm" -#include "code\modules\language\species\tajaran.dm" -#include "code\modules\language\species\teshari.dm" -#include "code\modules\language\species\unathi.dm" -#include "code\modules\language\species\vasilissan.dm" -#include "code\modules\language\species\vox.dm" -#include "code\modules\language\species\vulpkanin.dm" -#include "code\modules\language\species\zaddat.dm" -#include "code\modules\language\species\zorren.dm" +#include "code\modules\language\translation.dm" +#include "code\modules\language\languages\abstract.dm" +#include "code\modules\language\languages\animal.dm" +#include "code\modules\language\languages\core.dm" +#include "code\modules\language\languages\event.dm" +#include "code\modules\language\languages\galactic.dm" +#include "code\modules\language\languages\monkey.dm" +#include "code\modules\language\languages\silicons.dm" +#include "code\modules\language\languages\antagonist\borer.dm" +#include "code\modules\language\languages\antagonist\changeling.dm" +#include "code\modules\language\languages\antagonist\cult.dm" +#include "code\modules\language\languages\antagonist\swarmbot.dm" +#include "code\modules\language\languages\antagonist\xenomorph.dm" +#include "code\modules\language\languages\species\_species.dm" +#include "code\modules\language\languages\species\adherent.dm" +#include "code\modules\language\languages\species\akula.dm" +#include "code\modules\language\languages\species\alraune.dm" +#include "code\modules\language\languages\species\altevian.dm" +#include "code\modules\language\languages\species\birdsong.dm" +#include "code\modules\language\languages\species\celestial.dm" +#include "code\modules\language\languages\species\diona.dm" +#include "code\modules\language\languages\species\human.dm" +#include "code\modules\language\languages\species\moth.dm" +#include "code\modules\language\languages\species\naramadi.dm" +#include "code\modules\language\languages\species\phoronoid.dm" +#include "code\modules\language\languages\species\shadekin.dm" +#include "code\modules\language\languages\species\skrell.dm" +#include "code\modules\language\languages\species\tajaran.dm" +#include "code\modules\language\languages\species\teshari.dm" +#include "code\modules\language\languages\species\unathi.dm" +#include "code\modules\language\languages\species\vasilissan.dm" +#include "code\modules\language\languages\species\vox.dm" +#include "code\modules\language\languages\species\vulpkanin.dm" +#include "code\modules\language\languages\species\zaddat.dm" +#include "code\modules\language\languages\species\zorren.dm" #include "code\modules\library\lib_items.dm" #include "code\modules\library\lib_machines.dm" #include "code\modules\library\lib_readme.dm" @@ -2970,8 +2977,8 @@ #include "code\modules\mob\living\silicon\login.dm" #include "code\modules\mob\living\silicon\say.dm" #include "code\modules\mob\living\silicon\silicon.dm" -#include "code\modules\mob\living\silicon\silicon_vr.dm" #include "code\modules\mob\living\silicon\subystems.dm" +#include "code\modules\mob\living\silicon\translation.dm" #include "code\modules\mob\living\silicon\ai\ai.dm" #include "code\modules\mob\living\silicon\ai\ai_movement.dm" #include "code\modules\mob\living\silicon\ai\ai_remote_control.dm" @@ -2986,6 +2993,7 @@ #include "code\modules\mob\living\silicon\ai\logout.dm" #include "code\modules\mob\living\silicon\ai\malf.dm" #include "code\modules\mob\living\silicon\ai\perspective.dm" +#include "code\modules\mob\living\silicon\ai\translation.dm" #include "code\modules\mob\living\silicon\decoy\death.dm" #include "code\modules\mob\living\silicon\decoy\decoy.dm" #include "code\modules\mob\living\silicon\decoy\life.dm" @@ -3019,6 +3027,7 @@ #include "code\modules\mob\living\silicon\robot\robot_movement.dm" #include "code\modules\mob\living\silicon\robot\robot_remote_control.dm" #include "code\modules\mob\living\silicon\robot\syndicate.dm" +#include "code\modules\mob\living\silicon\robot\translation.dm" #include "code\modules\mob\living\silicon\robot\dogborg\dog_modules_vr.dm" #include "code\modules\mob\living\silicon\robot\dogborg\dog_sleeper_vr.dm" #include "code\modules\mob\living\silicon\robot\drone\_defines.dm" diff --git a/code/__DEFINES/_bitfields.dm b/code/__DEFINES/_bitfields.dm index 2b2b7df589f..c12f2a04557 100644 --- a/code/__DEFINES/_bitfields.dm +++ b/code/__DEFINES/_bitfields.dm @@ -1,6 +1,11 @@ -#define DEFINE_BITFIELD(v, f) /datum/bitfield/##v { \ +#define DEFINE_BITFIELD(v, f) /datum/bitfield/single/##v { \ flags = ##f; \ variable = #v; \ } +#define DEFINE_SHARED_BITFIELD(n, v, f) /datum/bitfield/multi/##n { \ + flags = ##f; \ + variables = ##v; \ +} + #define BITFIELD(thing) #thing = thing diff --git a/code/__DEFINES/_cooldowns.dm b/code/__DEFINES/_cooldowns.dm index 8dfa20ac996..1f844d0fbcf 100644 --- a/code/__DEFINES/_cooldowns.dm +++ b/code/__DEFINES/_cooldowns.dm @@ -23,8 +23,10 @@ // INDEXES FOR VAR COOLDOWNS - DO NOT USE UPPERCASE, DO NOT USE cooldown_, APPENDS ADDED AUTOMATICALLY // INDEXES FOR TIMER COOLDOWNS - Must be unique! -#define COOLDOWN_SONAR_PULSE "sonar_pulse" -#define COOLDOWN_POWER_DRAIN_WARNING "power_drain_warning" +#define CD_INDEX_SONAR_PULSE "sonar_pulse" +#define CD_INDEX_POWER_DRAIN_WARNING "power_drain_warning" +#define CD_INDEX_TAPE_TRANSLATION "tape_translation" +#define CD_INDEX_TAPE_PRINT "tape_print" //TIMER COOLDOWN MACROS diff --git a/code/__DEFINES/languages/flags.dm b/code/__DEFINES/languages/flags.dm index 479abaad645..8dd270e78dc 100644 --- a/code/__DEFINES/languages/flags.dm +++ b/code/__DEFINES/languages/flags.dm @@ -1,34 +1,90 @@ -//! ## LANGUAGE_FLAGS -/// Language is available if the speaker is whitelisted. -#define WHITELISTED (1<<0) +//! /datum/language/var/language_flags +/// Language is available if the speaker is whitelisted, or if granted by a species. +#define LANGUAGE_WHITELISTED (1<<0) /// Language can only be acquired by spawning or an admin. -#define RESTRICTED (1<<1) +#define LANGUAGE_RESTRICTED (1<<1) /// Language has a significant non-verbal component. Speech is garbled without line-of-sight. -#define NONVERBAL (1<<2) +#define LANGUAGE_NONVERBAL (1<<2) /// Language is completely non-verbal. Speech is displayed through emotes for those who can understand. -#define SIGNLANG (1<<3) +#define LANGUAGE_SIGNLANG (1<<3) /// Broadcast to all mobs with this language. -#define HIVEMIND (1<<4) +#define LANGUAGE_HIVEMIND (1<<4) /// Do not add to general languages list. -#define NONGLOBAL (1<<5) +#define LANGUAGE_NONGLOBAL (1<<5) /// All mobs can be assumed to speak and understand this language. (audible emotes) -#define INNATE (1<<6) +#define LANGUAGE_INNATE (1<<6) /// Do not show the "\The [speaker] talks into \the [radio]" message -#define NO_TALK_MSG (1<<7) +#define LANGUAGE_NO_TALK_MSG (1<<7) /// No stuttering, slurring, or other speech problems. -#define NO_STUTTER (1<<8) +#define LANGUAGE_NO_STUTTER (1<<8) /// Language is not based on vision or sound. //Todo: Add this into the say code and use it for the rootspeak languages. -#define ALT_TRANSMIT (1<<9) +#define LANGUAGE_ALT_TRANSMIT (1<<9) DEFINE_BITFIELD(language_flags, list( - BITFIELD(WHITELISTED), - BITFIELD(RESTRICTED), - BITFIELD(NONVERBAL), - BITFIELD(SIGNLANG), - BITFIELD(HIVEMIND), - BITFIELD(NONGLOBAL), - BITFIELD(INNATE), - BITFIELD(NO_TALK_MSG), - BITFIELD(NO_STUTTER), - BITFIELD(ALT_TRANSMIT), + BITFIELD(LANGUAGE_WHITELISTED), + BITFIELD(LANGUAGE_RESTRICTED), + BITFIELD(LANGUAGE_NONVERBAL), + BITFIELD(LANGUAGE_SIGNLANG), + BITFIELD(LANGUAGE_HIVEMIND), + BITFIELD(LANGUAGE_NONGLOBAL), + BITFIELD(LANGUAGE_INNATE), + BITFIELD(LANGUAGE_NO_TALK_MSG), + BITFIELD(LANGUAGE_NO_STUTTER), + BITFIELD(LANGUAGE_ALT_TRANSMIT), +)) + +//! translation_class on /datum/language and /datum/translation_context as well as all translation operations +/// common galactic languages; most translators can instantly translate this +#define TRANSLATION_CLASS_LEVEL_1 (1<<0) +/// high end translators can translate these; you're looking at science/cargo; low level learning translators learn this +#define TRANSLATION_CLASS_LEVEL_2 (1<<1) +/// extremely high end translators can translate these; high end learning translators learn this; anything above is probably adminbus +#define TRANSLATION_CLASS_LEVEL_3 (1<<2) +/// xenomorphs, etc +#define TRANSLATION_CLASS_MONSTER (1<<18) +/// simpler animal languages +#define TRANSLATION_CLASS_ANIMAL (1<<19) +/// magical languages +#define TRANSLATION_CLASS_ARCANE (1<<20) +/// encrypted languages ; usually conjunction with machine class +#define TRANSLATION_CLASS_ENCRYPTED (1<<21) +/// tribal/native languages +#define TRANSLATION_CLASS_TRIBAL (1<<22) +/// machine-like languages +#define TRANSLATION_CLASS_MACHINE (1<<23) + +/// core galactic species +#define TRANSLATION_CLASS_DEFAULT_CORE_RACE (TRANSLATION_CLASS_LEVEL_1) +/// standard galactic species +#define TRANSLATION_CLASS_DEFAULT_STANDARD_RACE (TRANSLATION_CLASS_LEVEL_1) +/// people's snowflakes that wanna be super speshul and be mYsTerIoUS +#define TRANSLATION_CLASS_DEFAULT_RARE_RACE (TRANSLATION_CLASS_LEVEL_2) +/// native species +#define TRANSLATION_CLASS_DEFAULT_NATIVE_RACE (TRANSLATION_CLASS_LEVEL_3 | TRANSLATION_CLASS_TRIBAL) +/// core alt langauges for galaxy +#define TRANSLATION_CLASS_DEFAULT_CORE_LANGUAGE (TRANSLATION_CLASS_LEVEL_1) + +/// what cyborgs get by default +#define TRANSLATION_CLASSES_CYBORG_SPEAKS (TRANSLATION_CLASS_LEVEL_1) +/// what ais get by default +#define TRANSLATION_CLASSES_AI_SPEAKS (TRANSLATION_CLASS_LEVEL_1) +/// what all universal translators get intrinsically +#define TRANSLATION_CLASSES_STANDARD_TRANSLATE (TRANSLATION_CLASS_LEVEL_1) +/// what adaptive universal translators can learn +#define TRANSLATION_CLASSES_STANDARD_ADAPTIVE (TRANSLATION_CLASS_LEVEL_2) + +DEFINE_SHARED_BITFIELD(translation_classes, list( + "translation_class", + "translation_class_forbid", + "translation_class_learn", + "translation_class_learn_forbid", +), list( + BITFIELD(TRANSLATION_CLASS_LEVEL_1), + BITFIELD(TRANSLATION_CLASS_LEVEL_2), + BITFIELD(TRANSLATION_CLASS_LEVEL_3), + BITFIELD(TRANSLATION_CLASS_MONSTER), + BITFIELD(TRANSLATION_CLASS_ANIMAL), + BITFIELD(TRANSLATION_CLASS_ENCRYPTED), + BITFIELD(TRANSLATION_CLASS_TRIBAL), + BITFIELD(TRANSLATION_CLASS_MACHINE), )) diff --git a/code/__DEFINES/languages/translation.dm b/code/__DEFINES/languages/translation.dm new file mode 100644 index 00000000000..0755ca4e4a2 --- /dev/null +++ b/code/__DEFINES/languages/translation.dm @@ -0,0 +1,5 @@ +//! adaptive translation stuff +//! don't mess with this unless you know math + +/// above this point we're considered perfect, stop doing expensive computations +#define TRANSLATION_CONTEXT_PERFECT_THRESHOLD 0.98 diff --git a/code/__DEFINES/misc.dm b/code/__DEFINES/misc.dm index 4e9855b8309..3f0e09718eb 100644 --- a/code/__DEFINES/misc.dm +++ b/code/__DEFINES/misc.dm @@ -568,3 +568,16 @@ GLOBAL_LIST_EMPTY(##LIST_NAME);\ #define WINDOW_STATE_CROWBRARED_IN 2 /// window is secured to frame #define WINDOW_STATE_SECURED_TO_FRAME 3 + +//! cassette tapes +//* data list +#define CASSETTE_TAPE_DATA_MESSAGE 1 +#define CASSETTE_TAPE_DATA_NAME 2 +#define CASSETTE_TAPE_DATA_LANGUAGE 3 +#define CASSETTE_TAPE_DATA_DELAY 4 +#define CASSETTE_TAPE_DATA_OPCODE 5 +/// list length for data reads +#define CASSETTE_TAPE_DATA_LEN 5 +//* metadata +#define CASSETTE_METADATA_LANGUAGE "%" +#define CASSETTE_METADATA_NAME "^" diff --git a/code/__HELPERS/animations.dm b/code/__HELPERS/animations.dm index 6a8cc78538d..ea9fedb2c31 100644 --- a/code/__HELPERS/animations.dm +++ b/code/__HELPERS/animations.dm @@ -1,7 +1,3 @@ -/proc/remove_images_from_clients(image/I, list/show_to) - for(var/client/C in show_to) - C.images -= I - qdel(I) /proc/fade_out(image/I, list/show_to) animate(I, alpha = 0, time = 0.5 SECONDS, easing = EASE_IN) diff --git a/code/__HELPERS/game.dm b/code/__HELPERS/game.dm index 3b2c346d512..4cc7b616b97 100644 --- a/code/__HELPERS/game.dm +++ b/code/__HELPERS/game.dm @@ -369,26 +369,6 @@ return FALSE return TRUE -/proc/flick_overlay(image/I, list/show_to, duration, gc_after) - for(var/client/C in show_to) - C.images += I - spawn(duration) - if(gc_after) - qdel(I) - for(var/client/C in show_to) - C.images -= I - -/** - * Wrapper for flick_overlay, flicks to everyone who can see the target atom. - */ -/proc/flick_overlay_view(image/I, atom/target, duration, gc_after) - var/list/viewing = list() - for(var/m in viewers(target)) - var/mob/M = m - if(M.client) - viewing += M.client - flick_overlay(I, viewing, duration, gc_after) - /proc/isInSight(atom/A, atom/B) var/turf/Aturf = get_turf(A) var/turf/Bturf = get_turf(B) diff --git a/code/__HELPERS/lists/associations.dm b/code/__HELPERS/lists/associations.dm index 62e565f26a2..cf4c390fcab 100644 --- a/code/__HELPERS/lists/associations.dm +++ b/code/__HELPERS/lists/associations.dm @@ -24,6 +24,13 @@ flat_list[flat_list[i]] = TRUE return flat_list +/proc/make_associative_inplace_keep_values(list/flat_list) + for(var/i in 1 to length(flat_list)) + if(!isnull(flat_list[flat_list[i]])) + continue + flat_list[flat_list[i]] = TRUE + return flat_list + /proc/assoc_list_strip_value_inplace(list/input) for(var/i in 1 to length(input)) input[input[i]] = null diff --git a/code/__HELPERS/vfx/flick.dm b/code/__HELPERS/vfx/flick.dm new file mode 100644 index 00000000000..47df576b353 --- /dev/null +++ b/code/__HELPERS/vfx/flick.dm @@ -0,0 +1,20 @@ +/// Removes an image from a client's `.images`. Useful as a callback. +/proc/remove_image_from_client(image/image, client/remove_from) + remove_from?.images -= image + +/proc/remove_images_from_clients(image/I, list/show_to) + for(var/client/C in show_to) + C.images -= I + +/proc/flick_overlay(image/I, list/show_to, duration) + for(var/client/C in show_to) + C.images += I + addtimer(CALLBACK(GLOBAL_PROC, /proc/remove_images_from_clients, I, show_to), duration, TIMER_CLIENT_TIME) + +/proc/flick_overlay_view(image/I, atom/target, duration) //wrapper for the above, flicks to everyone who can see the target atom + var/list/viewing = list() + for(var/m in viewers(target)) + var/mob/M = m + if(M.client) + viewing += M.client + flick_overlay(I, viewing, duration) diff --git a/code/_globals/bitfields.dm b/code/_globals/bitfields.dm index 409b9fa8ec2..4ff36ae26f7 100644 --- a/code/_globals/bitfields.dm +++ b/code/_globals/bitfields.dm @@ -1,18 +1,27 @@ GLOBAL_LIST_INIT(bitfields, generate_bitfields()) /datum/bitfield + /// An associative list of the readable flag and its true value + var/list/flags + +/datum/bitfield/single /// The variable name that contains the bitfield var/variable - /// An associative list of the readable flag and its true value - var/list/flags +/datum/bitfield/multi + /// our variable names + var/list/variables /// Turns /datum/bitfield subtypes into a list for use in debugging /proc/generate_bitfields() var/list/bitfields = list() - for (var/_bitfield in subtypesof(/datum/bitfield)) - var/datum/bitfield/bitfield = new _bitfield + for (var/_bitfield in subtypesof(/datum/bitfield/single)) + var/datum/bitfield/single/bitfield = new _bitfield bitfields[bitfield.variable] = bitfield.flags + for (var/_bitfield in subtypesof(/datum/bitfield/multi)) + var/datum/bitfield/multi/bitfield = new _bitfield + for(var/n in bitfield.variables) + bitfields[n] = bitfield.flags // sue me i haven't slept in 18 hours // tl;dr convert these into DEFINE_BITFIELD's later #define FLAG(flag) "[#flag]" = flag diff --git a/code/_globals/regexes.dm b/code/_globals/regexes.dm index 7e571576213..5b3938deab8 100644 --- a/code/_globals/regexes.dm +++ b/code/_globals/regexes.dm @@ -17,3 +17,5 @@ GLOBAL_DATUM_INIT(angular_brackets, /regex, regex(@"[<>]", "g")) GLOBAL_DATUM_INIT(filename_forbidden_chars, /regex, regex(@{""|[\\\n\t/?%*:|<>]|\.\."}, "g")) GLOBAL_PROTECT(filename_forbidden_chars) // had to use the OR operator for quotes instead of putting them in the character class because it breaks the syntax highlighting otherwise. + +GLOBAL_DATUM_INIT(multi_space_splitter, /regex, regex("\[ \]+")) diff --git a/code/controllers/subsystem/characters/languages.dm b/code/controllers/subsystem/characters/languages.dm index 93cdf4ca7d2..7921019e36d 100644 --- a/code/controllers/subsystem/characters/languages.dm +++ b/code/controllers/subsystem/characters/languages.dm @@ -23,7 +23,7 @@ language_lookup[L.id] = L language_names[L.name] = L language_paths[path] = L - if(!(L.language_flags & NONGLOBAL) && L.key) + if(!(L.language_flags & LANGUAGE_NONGLOBAL) && L.key) language_keys[L.key] = L tim_sort(language_names, /proc/cmp_auto_compare, TRUE) diff --git a/code/controllers/subsystem/job/jexp.dm b/code/controllers/subsystem/job/jexp.dm new file mode 100644 index 00000000000..141086fa28f --- /dev/null +++ b/code/controllers/subsystem/job/jexp.dm @@ -0,0 +1,19 @@ +/** + * job experience system + */ + +//! wip + +/** + * immediate check for if someone has jexp bypass + * + * may return incorrect answer if datbase hasn't loaded; use has_jexp_bypass_blocking() to ensure it's loaded. + */ +/client/proc/has_jexp_bypass() + SHOULD_NOT_SLEEP(TRUE) + return !!(admin_datums[ckey] || (database.player_flags & PLAYER_FLAG_JEXP_EXEMPT)) + +/client/proc/has_jexp_bypass_blocking() + database.block_on_available() + return !!(admin_datums[ckey] || (database.player_flags & PLAYER_FLAG_JEXP_EXEMPT)) + diff --git a/code/controllers/subsystem/sound/soundbyte_manager.dm b/code/controllers/subsystem/sound/soundbyte_manager.dm index 4dfd35886ed..4953195cca4 100644 --- a/code/controllers/subsystem/sound/soundbyte_manager.dm +++ b/code/controllers/subsystem/sound/soundbyte_manager.dm @@ -68,6 +68,8 @@ * defaults to alias itself if not found, so passing in a valid sound datum is fine */ /datum/controller/subsystem/sounds/proc/fetch_asset(alias) + if(!istext(alias)) + return alias return fetch_soundbyte(alias)?.get_asset() || alias /** diff --git a/code/datums/soundbytes/_soundbyte.dm b/code/datums/soundbytes/_soundbyte.dm index 5fe21374d7a..718b468671f 100644 --- a/code/datums/soundbytes/_soundbyte.dm +++ b/code/datums/soundbytes/_soundbyte.dm @@ -9,6 +9,13 @@ * todo: in hindsight i'm shit at writing sound backend, this works but it could be a lot better. * todo: "is_sfx" only works to put typepaths & alias in for compiled in sounds. maybe we can have runtime soundbytes later? if needed. * + * this stuff is obviously expensive and shouldn't be used at a whim + * + * todo: refactor soundbytes; they should be resolved via get_sfx global proc + * todo: and automatically register their sounds in global list by id + * todo: have var/direct_access for this; global list lookup for sound asset is WAY cheaper than + * todo: subsystem resolve, use that in cases of short sounds like terminal clicks. + * * currently holds: * - path/file - required for preload * - name - defaults to filename diff --git a/code/game/atoms/atoms.dm b/code/game/atoms/atoms.dm index ec91495183d..2e7c090478f 100644 --- a/code/game/atoms/atoms.dm +++ b/code/game/atoms/atoms.dm @@ -814,7 +814,7 @@ /// message is the message output to anyone who can hear. /// deaf_message (optional) is what deaf people will see. /// hearing_distance (optional) is the range, how many tiles away the message can be heard. -/atom/proc/audible_message(var/message, var/deaf_message, var/hearing_distance) +/atom/proc/audible_message(var/message, var/deaf_message, var/hearing_distance, datum/language/lang) var/range = hearing_distance || world.view var/list/hear = get_mobs_and_objs_in_view_fast(get_turf(src),range,remote_ghosts = FALSE) @@ -826,13 +826,16 @@ var/obj/O = obj O.show_message(message, 2, deaf_message, 1) + var/no_runechat = FALSE for(var/mob in hearing_mobs) var/mob/M = mob var/msg = message + if(lang && !(lang.name in M.languages)) + msg = lang.scramble(msg) M.show_message(msg, 2, deaf_message, 1) heard_to_floating_message += M - INVOKE_ASYNC(src, /atom/movable/proc/animate_chat, (message ? message : deaf_message), null, FALSE, heard_to_floating_message, 30) - + if(!no_runechat) + INVOKE_ASYNC(src, /atom/movable/proc/animate_chat, (message ? message : deaf_message), null, FALSE, heard_to_floating_message, 30) /atom/movable/proc/dropInto(var/atom/destination) while(istype(destination)) @@ -870,16 +873,22 @@ /atom/proc/GenerateTag() return -/atom/proc/atom_say(message) +// todo: refactor this shit to be unified saycode, christ +/atom/proc/atom_say(message, datum/language/L) if(!message) return var/list/speech_bubble_hearers = list() + var/no_runechat = FALSE for(var/mob/M in get_hearers_in_view(MESSAGE_RANGE_COMBAT_LOUD, src)) - M.show_message("[src] [atom_say_verb], \"[message]\"", 2, null, 1) + var/processed = message + if(L && !(L.name in M.languages)) + processed = L.scramble(message) + no_runechat = TRUE + M.show_message("[src] [L?.speech_verb || atom_say_verb], \"[processed]\"", 2, null, 1) if(M.client) speech_bubble_hearers += M.client - if(length(speech_bubble_hearers)) + if(length(speech_bubble_hearers) && !no_runechat) var/image/I = generate_speech_bubble(src, "[bubble_icon][say_test(message)]", FLY_LAYER) I.appearance_flags = APPEARANCE_UI_IGNORE_ALPHA INVOKE_ASYNC(GLOBAL_PROC, /.proc/flick_overlay, I, speech_bubble_hearers, 30) diff --git a/code/game/objects/items/devices/tape_recorder/cassette_tape.dm b/code/game/objects/items/devices/tape_recorder/cassette_tape.dm new file mode 100644 index 00000000000..a14009fef18 --- /dev/null +++ b/code/game/objects/items/devices/tape_recorder/cassette_tape.dm @@ -0,0 +1,295 @@ +/** + * this is a horror + * we use the typepaths as enums... + */ +/datum/cassette_opcode +/datum/cassette_opcode/next_is_audible_emote +/datum/cassette_opcode/next_is_direct_broadcast + +/** + * cassette tapes; capable of storing spoken audio and their languages + * + * all access must be through getters/setters due to how ridiculously synchronized + * and packed this has to be to store player speech. + */ +/obj/item/cassette_tape + name = "tape" + desc = "A magnetic tape that can hold up to ten minutes of content." + icon = 'icons/obj/device.dmi' + icon_state = "tape_white" + item_state = "analyzer" + w_class = ITEMSIZE_TINY + matter = list(MAT_STEEL=20, "glass"=5) + force = 1 + throw_force = 0 + + /** + * this is a horror + * - text = message + * - positive number = delay of this many deciseconds after + * - negative number = list index of metadata + * - typepath = /datum/cassette_opcode opcode + * + * anything that modifies a message (e.g. delay) comes BEFORE a message. + * + * i'm sorry. + */ + var/list/reel + /** + * names, language ids go in here + * prefix ^ = name + * prefix % = language + */ + var/list/metadata + /// lookup for language id to metadata index + var/list/language_lookup + /// lookup for verbatim names to metadata indeix + var/list/name_lookup + /// active iterators + VAR_PRIVATE/list/datum/cassette_tape_iterator/iterators + /// active recording iterator + var/datum/cassette_tape_iterator/write/recording_lock + /// active translation iterator, or otherwise an iterator being used to rewrite our contents entirely + var/datum/cassette_tape_iterator/translator/translation_lock + /// max capacity in messages + var/capacity_messages = 1800 // it would take you 30 minutes of nonstop chatter to deplete this + /// used capacity in messages + var/used_messages = 0 + /// max capacity in deciseconds to avoid people spamming these things for no reason + var/capacity_time = 30 MINUTES + /// used time + var/used_time = 0 + /// last world.time of "tick" + var/last_recording_tick + /// last world.time of tangible messages + var/last_message_time + /// last language id + var/last_language_id + /// last speaker name + var/last_speaker_name + /// data list length - [text, speaker name, language id, opcode] + var/const/data_list_length = 4 + + /// did someone ruin this tape by unraveling the tape? + var/ruined = FALSE + +/obj/item/cassette_tape/Destroy() + kill_iterators() + return ..() + +/obj/item/cassette_tape/proc/wipe() + kill_iterators() + reel = null + metadata = null + used_messages = 0 + used_time = 0 + last_recording_tick = null + last_language_id = null + last_speaker_name = null + language_lookup = null + name_lookup = null + +/obj/item/cassette_tape/update_overlays() + . = ..() + if(ruined) + . += "ribbonoverlay" + +/obj/item/cassette_tape/fire_act() + ruin() + +/obj/item/cassette_tape/attack_self(mob/user) + . = ..() + if(user.a_intent != INTENT_HARM) + return + if(!ruined) + to_chat(user, "You pull out all the tape!") + ruin() + +/obj/item/cassette_tape/proc/ruin() + ruined = TRUE + update_icon() + +/obj/item/cassette_tape/proc/fix() + ruined = FALSE + update_icon() + +/obj/item/cassette_tape/attackby(obj/item/I, mob/user, params) + if(ruined && I.is_screwdriver()) + to_chat(user, "You start winding the tape back in...") + playsound(src, I.tool_sound, 50, 1) + if(do_after(user, 120 * I.tool_speed, target = src)) + to_chat(user, "You wound the tape back in.") + fix() + return + else if(istype(I, /obj/item/pen)) + if(loc == user && !user.incapacitated()) + var/new_name = input(user, "What would you like to label the tape?", "Tape labeling") as null|text + if(isnull(new_name)) + return + new_name = sanitizeSafe(new_name) + if(new_name) + name = "tape - '[new_name]'" + to_chat(user, "You label the tape '[new_name]'.") + else + var/old_name = name + name = "tape" + to_chat(user, "You scratch off '[old_name]' from the label.") + return + ..() + +/** + * returns an iterator for reading + * returns null on fail, otherwise a read iterator + */ +/obj/item/cassette_tape/proc/iterator() + if(translation_lock) + return + var/datum/cassette_tape_iterator/I = new + I.tape = src + LAZYADD(iterators, I) + I._init() // to first pos + return I + +/obj/item/cassette_tape/proc/kill_iterators() + QDEL_LIST_NULL(iterators) + QDEL_NULL(recording_lock) + +/obj/item/cassette_tape/proc/iterator_killed(datum/cassette_tape_iterator/I) + if(I == recording_lock) + recording_lock = null + if(iterators) + iterators -= I + +/** + * try to lock this tape for recording + * returns null on fail, otherwise a write-capable iterator + */ +/obj/item/cassette_tape/proc/obtain_recording_lock() + if(translation_lock) + return + if(recording_lock) + return recording_lock + if(full()) + return + LAZYINITLIST(reel) + LAZYINITLIST(metadata) + LAZYINITLIST(language_lookup) + LAZYINITLIST(name_lookup) + recording_lock = new /datum/cassette_tape_iterator/write + recording_lock.tape = src + recording_lock.current_language_id = last_language_id + recording_lock.current_speaker_name = last_speaker_name + return recording_lock + +/** + * locks tape for operation via translator + * kicks off all read/write iterators + * return null on fail, otherwise a translation iterator + */ +/obj/item/cassette_tape/proc/lock_for_translation() + kill_iterators() + if(translation_lock) + return + translation_lock = new /datum/cassette_tape_iterator/translator + translation_lock.tape = src + return translation_lock + +/obj/item/cassette_tape/proc/full() + return used_messages >= capacity_messages || used_time >= capacity_time + +/obj/item/cassette_tape/proc/increment_messages() + ++used_messages + if(full()) + kill_iterators() + + +/** + * returns index in metadata + */ +/obj/item/cassette_tape/proc/inject_latest_name(name) + var/index = name_lookup[name] + if(!index) + metadata += "^[name]" + name_lookup[name] = index = metadata.len + last_speaker_name = name + reel += -index + +/** + * returns index in metadata + */ +/obj/item/cassette_tape/proc/inject_latest_language(id) + var/index = language_lookup[id] + if(!index) + metadata += "%[id]" + language_lookup[id] = index = metadata.len + last_language_id = id + reel += -index + +//Random colour tapes +/obj/item/cassette_tape/random/Initialize(mapload) + . = ..() + icon_state = "tape_[pick("white", "blue", "red", "yellow", "purple")]" + +/obj/item/cassette_tape/premade + /** + * JSON: [[message, delay, speaker name, language id, opcode], ...] + * message mandatory, rest can be skipped + * delay defaults to 2 seconds, rest defaults to last value + * if no last value, defaults to "Unknown" name and common language + */ + var/list/preformatted_data + +/obj/item/cassette_tape/premade/Initialize(mapload) + . = ..() + inject(preformatted_data) + preformatted_data = null + +/obj/item/cassette_tape/premade/proc/inject(str) + wipe() + var/list/decoded + if(!str) + return + if(!islist(str)) + decoded = safe_json_decode(str) + else + decoded = str + if(!length(str)) + return + last_recording_tick = 0 + last_language_id = LANGUAGE_ID_COMMON + last_speaker_name = "Unknown" + var/msg + var/delay + var/name + var/lang + var/opcode + language_lookup = list() + name_lookup = list() + for(var/list/L in decoded) + var/dec_len = length(L) + msg = L[1] + if(dec_len >= 2) + delay = L[2] + else + delay = 1 SECONDS + if(dec_len >= 3) + name = L[3] + else + name = null + if(dec_len >= 4) + lang = L[4] + else + lang = null + if(dec_len >= 5) + opcode = text2path(L[5]) + ASSERT(ispath(opcode, /datum/cassette_opcode)) + else + opcode = null + if(opcode) + reel += opcode + if(name && name != last_speaker_name) + inject_latest_name(name) + if(lang && lang != last_language_id) + inject_latest_language(lang) + reel += delay // delay after + reel += msg diff --git a/code/game/objects/items/devices/tape_recorder/tape_iterator.dm b/code/game/objects/items/devices/tape_recorder/tape_iterator.dm new file mode 100644 index 00000000000..d831f604d6c --- /dev/null +++ b/code/game/objects/items/devices/tape_recorder/tape_iterator.dm @@ -0,0 +1,305 @@ +/** + * allows iteration of a cassette tape + * + * it is YOUR job to monitor for qdels! + */ +/datum/cassette_tape_iterator + /// tape + var/obj/item/cassette_tape/tape + /// index in reel + var/reel_index + /// current speaker name + var/current_speaker_name + /// current language id + var/current_language_id + /// current language datum to save on lookups + var/datum/language/current_language + //? we stage things because we have to be one *ahead* of the playback so delays are done properly. + /// staged message + var/staged_msg + /// staged opcode + var/staged_opcode + /// staged delay + var/staged_delay + /// default delay + var/static/default_delay = 1 SECONDS + +/datum/cassette_tape_iterator/Destroy() + tape?.iterator_killed(src) + tape = null // lol if you try to access after you get a runtime + return ..() + +//? a note on iterators +//? key here of knowing how this works is +//? staged vars are flushed every step +//? when advancing, we read what's before a message, then stage it +//? peek, and next, always emit staged vars +//? when initializing, we read up to the first delay WITHOUT setting staged +//? that's how we emit an "empty" delay without a message. +//? if we get two opcodes/language changes/etc, we don't care, +//? because the only sync information we need are messages, +//? and the first and second delays. + +/** + * returns delay + */ +/datum/cassette_tape_iterator/proc/_advance() + PRIVATE_PROC(TRUE) + // prep + if(!reel_index) + return + var/read + var/index = reel_index + var/list/reel = tape.reel + staged_msg = null + staged_opcode = null + staged_delay = default_delay + // scan + while(index <= reel.len) + read = reel[index] + if(istext(read)) + staged_msg = read + // flush + reel_index = index + 1 + return + else if(isnum(read)) + if(read >= 0) + staged_delay = read + else + var/metadata = tape.metadata[-read] + var/val = copytext(metadata, 2) + switch(metadata[1]) + if(CASSETTE_METADATA_NAME) + current_speaker_name = val + if(CASSETTE_METADATA_LANGUAGE) + current_language_id = val + current_language = SScharacters.resolve_language_id(val) + else if(ispath(read, /datum/cassette_opcode)) + staged_opcode = read + index++ + // we moved past + reel_index = null + +/datum/cassette_tape_iterator/proc/_init() + // reset + if(!tape.reel.len) + reel_index = null + return + reel_index = 1 + current_speaker_name = "Unknown" + current_language_id = LANGUAGE_ID_COMMON + current_language = SScharacters.resolve_language_id(LANGUAGE_ID_COMMON) + staged_msg = null + staged_opcode = null + staged_delay = 1 SECONDS + // read up to the first delay; if there isn't any, we just read up to the first message + var/read + var/index = reel_index + var/list/reel = tape.reel + while(index <= reel.len) + read = reel[index] + if(istext(read)) + staged_msg = read + reel_index = index + 1 + return + else if(isnum(read)) + if(read >= 0) + staged_delay = read + reel_index = index + 1 + return + else + var/metadata = tape.metadata[-read] + var/val = copytext(metadata, 2) + switch(metadata[1]) + if(CASSETTE_METADATA_NAME) + current_speaker_name = val + if(CASSETTE_METADATA_LANGUAGE) + current_language_id = val + current_language = SScharacters.resolve_language_id(val) + else if(ispath(read, /datum/cassette_opcode)) + staged_opcode = read + index++ + // we moved past + reel_index = null + +/** + * dumps next data to a returned list and advances; returns null if end + * [message, speaker name, language datum, delay, opcode] + * delay is to be used to delay after read. + * null name means it's a noise in the room + * null message means it's just a delay (or maybe read the opcode) + */ +/datum/cassette_tape_iterator/proc/next_slow() + if(!reel_index) + return + . = list( + staged_msg, + current_speaker_name, + current_language, + staged_delay, + staged_opcode + ) + _advance() + +/** + * dumps next data to a provided list and advances + * [message, speaker name, language datum, delay, opcode] + * delay is to be used to delay after read. + * null name means it's a noise in the room + * null message means it's just a delay (or maybe read the opcode) + * returns false if nothing's there + */ +/datum/cassette_tape_iterator/proc/next_fast(list/L) + if(!reel_index) + return FALSE + L[CASSETTE_TAPE_DATA_DELAY] = staged_delay + L[CASSETTE_TAPE_DATA_LANGUAGE] = current_language + L[CASSETTE_TAPE_DATA_NAME] = current_speaker_name + L[CASSETTE_TAPE_DATA_OPCODE] = staged_opcode + L[CASSETTE_TAPE_DATA_MESSAGE] = staged_msg + _advance() + return TRUE + +/** + * dumps next data to a returned list without advancing; returns null if end + * [message, speaker name, language datum, delay, opcode] + * delay is to be used to delay after read. + * null name means it's a noise in the room + * null message means it's just a delay (or maybe read the opcode) + */ +/datum/cassette_tape_iterator/proc/peek_slow() + if(!reel_index) + return + . = list( + staged_msg, + current_speaker_name, + current_language, + staged_delay, + staged_opcode + ) + +/** + * dumps next data to a provided list without advancing + * [message, speaker name, language datum, delay, opcode] + * delay is to be used to delay after read. + * null name means it's a noise in the room + * null message means it's just a delay (or maybe read the opcode) + * returns false if nothing's there + */ +/datum/cassette_tape_iterator/proc/peek_fast(list/L) + if(!reel_index) + return FALSE + L[CASSETTE_TAPE_DATA_DELAY] = staged_delay + L[CASSETTE_TAPE_DATA_LANGUAGE] = current_language + L[CASSETTE_TAPE_DATA_NAME] = current_speaker_name + L[CASSETTE_TAPE_DATA_OPCODE] = staged_opcode + L[CASSETTE_TAPE_DATA_MESSAGE] = staged_msg + return TRUE + +/** + * used to **write** to a tape. + */ +/datum/cassette_tape_iterator/write + +/** + * expects **raw** message + */ +/datum/cassette_tape_iterator/write/proc/write_speech(message, speaker_name, language_id, override_pause) + if(language_id != tape.last_language_id) + tape.inject_latest_language(language_id) + if(speaker_name != tape.last_speaker_name) + tape.inject_latest_name(speaker_name) + tape.reel += world.time - tape.last_message_time + tape.last_message_time = world.time + tape.reel += message + tape.increment_messages() + +/** + * expects **raw** message + * + * speaker_name to null for actions that are heard by the recorder not associated to anyone + */ +/datum/cassette_tape_iterator/write/proc/write_emote(message, speaker_name, override_pause) + if(speaker_name != tape.last_speaker_name) + tape.inject_latest_name(speaker_name) + tape.reel += world.time - tape.last_message_time + tape.last_message_time = world.time + tape.reel += /datum/cassette_opcode/next_is_audible_emote + tape.reel += message + tape.increment_messages() + +/datum/cassette_tape_iterator/write/proc/tick(dt) + tape.used_time += world.time - tape.last_recording_tick + tape.last_recording_tick = world.time + +/datum/cassette_tape_iterator/write/proc/mark_start() + tape.last_message_time = world.time + tape.last_recording_tick = world.time + +/datum/cassette_tape_iterator/write/proc/mark_stop() + if(!tape.reel.len) + // bro we haven't written anything + return + if(tape.reel[tape.reel.len] == "Jumping to next recording segment...") + return + if(tape.last_language_id != LANGUAGE_ID_COMMON) + tape.inject_latest_language(LANGUAGE_ID_COMMON) + if(tape.last_speaker_name != "\[Recorder\]") + tape.inject_latest_name("\[Recorder\]") + tape.reel += world.time - tape.last_message_time + tape.last_message_time = world.time + tape.reel += /datum/cassette_opcode/next_is_direct_broadcast + tape.reel += "Jumping to next recording segment..." + tape.used_time += world.time - tape.last_recording_tick + tape.last_recording_tick = world.time + +/** + * used to translate a tape + */ +/datum/cassette_tape_iterator/translator + +/** + * returns lines translated + */ +/datum/cassette_tape_iterator/translator/proc/translate(datum/translation_context/context) + // ez + . = 0 + var/list/reel = tape.reel + var/list/metadata = tape.metadata + var/static/common_str = "[CASSETTE_METADATA_LANGUAGE][LANGUAGE_ID_COMMON]" + var/common_index = metadata.Find(common_str) + var/translating = FALSE + var/ignore_next = FALSE + if(!common_index) + metadata += common_str + common_index = metadata.len + tape.language_lookup["[LANGUAGE_ID_COMMON]"] = common_index + for(var/i in 1 to reel.len) + var/read = reel[i] + if(isnum(read) && read > 0) + var/mdstr = metadata[read] + if(mdstr == common_str) + translating = FALSE + continue + if(mdstr[1] == CASSETTE_METADATA_LANGUAGE) + var/id = copytext(mdstr, 2) + var/datum/language/L = SScharacters.resolve_language_id(id) + if(context.can_translate(L, require_perfect = TRUE)) + reel[i] = common_index + translating = TRUE + else + translating = FALSE + else if(istext(read)) + if(ignore_next) + ignore_next = FALSE + else if(translating) + ++. + else if(ispath(read, /datum/cassette_opcode)) + switch(read) + if(/datum/cassette_opcode/next_is_audible_emote) + ignore_next = TRUE + if(/datum/cassette_opcode/next_is_direct_broadcast) + ignore_next = TRUE + +// todo: compactor +// todo: transformer/mapper diff --git a/code/game/objects/items/devices/tape_recorder/tape_recorder.dm b/code/game/objects/items/devices/tape_recorder/tape_recorder.dm new file mode 100644 index 00000000000..d49a4efb150 --- /dev/null +++ b/code/game/objects/items/devices/tape_recorder/tape_recorder.dm @@ -0,0 +1,461 @@ +/obj/item/tape_recorder + name = "universal recorder" + desc = "A device that can record to cassette tapes, and play them. It automatically translates the content in playback." + icon_state = "taperecorder_empty" + item_state = "analyzer" + icon = 'icons/obj/device.dmi' + w_class = ITEMSIZE_SMALL + slot_flags = SLOT_BELT + throw_force = 2 + throw_speed = 4 + throw_range = 20 + matter = list(MAT_STEEL = 60, MAT_GLASS = 30) + + /// inserted tape + var/obj/item/cassette_tape/tape = /obj/item/cassette_tape/random + /// obtained lock on the tape, whether read or write + var/datum/cassette_tape_iterator/tape_iterator + /// are we in writing mode? null if we shouldn't be doing anything + var/write_lock + + /// are we playing? + var/playing = FALSE + /// timerid for play tick + var/play_timerid + /// how long a delay has to be before we skip it + var/play_skip_threshold = 10 SECONDS + + /// are we recording? + var/recording = FALSE + + +/obj/item/tape_recorder/Initialize(mapload) + . = ..() + if(ispath(tape)) + tape = new tape(src) + update_icon() + listening_objects += src + +/obj/item/tape_recorder/Destroy() + stop_everything() + listening_objects -= src + if(tape) + QDEL_NULL(tape) + STOP_PROCESSING(SSobj, src) + return ..() + +/obj/item/tape_recorder/attackby(obj/item/I, mob/user, params) + if(istype(I, /obj/item/cassette_tape)) + if(tape) + to_chat(user, "There's already a tape inside.") + return + if(!user.attempt_insert_item_for_installation(I, src)) + return + tape = I + to_chat(user, "You insert [I] into [src].") + update_icon() + return + ..() + +/obj/item/tape_recorder/fire_act() + if(tape) + tape.ruin() //Fires destroy the tape + return ..() + +/obj/item/tape_recorder/attack_hand(mob/user) + if(user.get_inactive_held_item() == src) + if(tape) + eject() + return + ..() + +/obj/item/tape_recorder/proc/stop_recording(mob/user, silent) + if(!recording) + if(user && !silent) + to_chat(user, SPAN_WARNING("[src] isn't recording.")) + return + var/datum/cassette_tape_iterator/write/writer = tape_iterator + if(!writer) + recording = FALSE + CRASH("no writer?") + writer.mark_stop() + _release_lock() + recording = FALSE + if(user && !silent) + to_chat(user, SPAN_NOTICE("Recording stopped.")) + update_icon() + +/obj/item/tape_recorder/proc/start_recording(mob/user, silent) + if(!tape) + if(user && !silent) + to_chat(user, SPAN_WARNING("There's no tape in [src]!")) + return + if(tape.ruined) + if(user && !silent) + to_chat(user, SPAN_WARNING("[src] makes a scratchy noise.")) + return + if(recording) + if(user && !silent) + to_chat(user, SPAN_WARNING("[src] is already recording.")) + return + if(playing) + if(user && !silent) + to_chat(user, SPAN_WARNING("[src] can't both record and play at the same time.")) + return + if(is_busy()) + if(user && !silent) + to_chat(user, SPAN_WARNING("[src] is busy!")) + return + if(obj_flags & EMAGGED) + if(user && !silent) + to_chat(user, SPAN_WARNING("[src] makes a scratchy noise.")) + return + if(tape.full()) + if(user && !silent) + to_chat(user, SPAN_WARNING("The tape is full!")) + return + if(!_write_lock()) + if(user && !silent) + to_chat(user, SPAN_WARNING("The tape is busy.")) + return + var/datum/cassette_tape_iterator/write/writer = tape_iterator + writer.mark_start() + if(user && !silent) + to_chat(user, SPAN_NOTICE("Recording started.")) + recording = TRUE + update_icon() + +/obj/item/tape_recorder/proc/stop_playing(mob/user, silent, ending) + if(!silent) + if(ending) + audible_message("[SPAN_BOLD("[src]")]: End of recording.") + else + audible_message("[SPAN_BOLD("[src]")]: Playback stopped.") + + if(play_timerid) + deltimer(play_timerid) + _release_lock() + playing = FALSE + update_icon() + + if(obj_flags & EMAGGED) + tape.ruin() + audible_message("Tape Recorder: This tape recorder will self-destruct in... Five.") + addtimer(CALLBACK(src, /atom/proc/audible_message, "Tape Recorder: Four."), 1 SECONDS) + addtimer(CALLBACK(src, /atom/proc/audible_message, "Tape Recorder: Three."), 2 SECONDS) + addtimer(CALLBACK(src, /atom/proc/audible_message, "Tape Recorder: Two."), 3 SECONDS) + addtimer(CALLBACK(src, /atom/proc/audible_message, "Tape Recorder: One."), 4 SECONDS) + addtimer(CALLBACK(src, /obj/item/tape_recorder/proc/explode), 5 SECONDS) + +/obj/item/tape_recorder/proc/start_playing(mob/user, silent) + if(!tape) + if(user && !silent) + to_chat(user, SPAN_WARNING("There's no tape in [src].")) + return + if(tape.ruined) + if(user && !silent) + to_chat(user, SPAN_WARNING("[src] makes a scratchy noise.")) + return + if(is_recording()) + if(user && !silent) + to_chat(user, SPAN_WARNING("[src] can't playback while recording.")) + return + if(is_playing()) + if(user && !silent) + to_chat(user, SPAN_WARNING("[src] is already playing!")) + return + if(!_read_lock()) + if(user && !silent) + to_chat(user, SPAN_WARNING("[src] failed to lock the tape for reading.")) + return + if(!silent) + audible_message("[SPAN_BOLD("[src]")]: Playback started.") + playing = TRUE + update_icon() + _play_next() + +/obj/item/tape_recorder/proc/_play_next() + PRIVATE_PROC(TRUE) + var/datum/cassette_tape_iterator/reader = tape_iterator + var/list/got = reader.next_slow() + if(!got) + stop_playing(ending = TRUE) + return + var/msg = got[CASSETTE_TAPE_DATA_MESSAGE] + if(msg) + var/datum/language/L = got[CASSETTE_TAPE_DATA_LANGUAGE] + var/speaker = got[CASSETTE_TAPE_DATA_NAME] + var/opcode = got[CASSETTE_TAPE_DATA_OPCODE] + switch(opcode) + if(/datum/cassette_opcode/next_is_audible_emote) + audible_message("[SPAN_BOLD("[src]")]: *[SPAN_BOLD("-someone-")] [msg]*") + if(/datum/cassette_opcode/next_is_direct_broadcast) + audible_message("[SPAN_BOLD("[src]")]: [msg]") + else + audible_message("[SPAN_BOLD("[src]")]: [speaker] [L.speech_verb], [msg]", L) + // delays + var/delay = got[CASSETTE_TAPE_DATA_DELAY] + if(delay > play_skip_threshold) + audible_message("[SPAN_BOLD("[src]")]: Skipping [round(delay * 0.1)] seconds of silence.") + delay = 3 SECONDS + play_timerid = addtimer(CALLBACK(src, .proc/_play_next), delay, TIMER_STOPPABLE) + +/obj/item/tape_recorder/verb/playback_memory() + set name = "Playback Tape" + set category = "Object" + + if(usr.incapacitated()) + return + start_playing(usr) + +/obj/item/tape_recorder/proc/is_playing() + return playing + +/obj/item/tape_recorder/proc/is_recording() + return recording + +/obj/item/tape_recorder/proc/is_busy() + return playing || recording + +/obj/item/tape_recorder/proc/_release_lock() + PRIVATE_PROC(TRUE) + if(tape_iterator) + QDEL_NULL(tape_iterator) + return TRUE + return FALSE + +/obj/item/tape_recorder/proc/_lock_deleted(datum/source) + PRIVATE_PROC(TRUE) + if(source == tape_iterator) + tape_iterator = null + else + stack_trace("lock deleted called with something that isn't ours?") + +/obj/item/tape_recorder/proc/_read_lock() + PRIVATE_PROC(TRUE) + ASSERT(!tape_iterator) + tape_iterator = tape.iterator() + return !!tape_iterator + +/obj/item/tape_recorder/proc/_write_lock() + PRIVATE_PROC(TRUE) + ASSERT(!tape_iterator) + tape_iterator = tape.obtain_recording_lock() + return !!tape_iterator + +/obj/item/tape_recorder/proc/has_read_lock() + return write_lock == FALSE + +/obj/item/tape_recorder/proc/has_write_lock() + return write_lock == TRUE + +/obj/item/tape_recorder/proc/has_lock() + return !isnull(write_lock) + +/obj/item/tape_recorder/verb/eject() + set name = "Eject Tape" + set category = "Object" + + if(usr.incapacitated()) + return + if(!tape) + to_chat(usr, "There's no tape in \the [src].") + return + if(obj_flags & EMAGGED) + to_chat(usr, "The tape seems to be stuck inside.") + return + to_chat(usr, "You remove [tape] from [src].") + stop_everything() + usr.grab_item_from_interacted_with(tape, src) + tape = null + update_icon() + +/obj/item/tape_recorder/proc/stop_everything(mob/user, silent) + stop_playing(user, silent) + stop_recording(user, silent) + +/obj/item/tape_recorder/process(delta_time) + if(!is_recording()) + return PROCESS_KILL + var/datum/cassette_tape_iterator/write/writer = tape_iterator + writer.tick(delta_time) + +//! todo : saycode refactor +/obj/item/tape_recorder/hear_talk(mob/living/M as mob, msg, var/verb="says", datum/language/speaking=null) + if(!recording) + return + var/datum/cassette_tape_iterator/write/writer = tape_iterator + if(speaking.id == LANGUAGE_ID_NOISE) + writer.write_emote(msg, M.name) + else + writer.write_speech(msg, M.name, speaking.id) + +/obj/item/tape_recorder/see_emote(mob/M as mob, text, var/emote_type) + if(emote_type != 2) //only hearable emotes + return + if(!recording) + return + var/datum/cassette_tape_iterator/write/writer = tape_iterator + writer.write_emote(text, M.name) + +/obj/item/tape_recorder/show_message(msg, type, alt, alt_type) + var/recordedtext + if (msg && type == 2) //must be hearable + recordedtext = msg + else if (alt && alt_type == 2) + recordedtext = alt + else + return + if(!recording) + return + var/datum/cassette_tape_iterator/write/writer = tape_iterator + writer.write_emote(recordedtext) +//! end + +/obj/item/tape_recorder/emag_act(var/remaining_charges, var/mob/user) + if(obj_flags & EMAGGED) + obj_flags |= EMAGGED + recording = 0 + to_chat(user, "PZZTTPFFFT") + update_icon() + return TRUE + else + to_chat(user, "It is already emagged!") + +/obj/item/tape_recorder/proc/explode() + var/turf/T = get_turf(loc) + if(ismob(loc)) + var/mob/M = loc + to_chat(M, "\The [src] explodes!") + if(T) + T.hotspot_expose(700,125) + explosion(T, light_impact_range = 3) + qdel(src) + +/obj/item/tape_recorder/verb/record() + set name = "Start Recording" + set category = "Object" + + if(usr.incapacitated()) + return + start_recording(usr) + +/obj/item/tape_recorder/verb/stop() + set name = "Stop" + set category = "Object" + + if(usr.incapacitated()) + return + if(!is_busy()) + to_chat(usr, SPAN_WARNING("[src] isn't doing anything right now.")) + stop_everything(usr) + +/obj/item/tape_recorder/verb/wipe_tape() + set name = "Wipe Tape" + set category = "Object" + + if(usr.incapacitated()) + return + if(obj_flags & EMAGGED) + to_chat(usr, "The tape recorder makes a scratchy noise.") + return + if(!tape) + to_chat(usr, SPAN_WARNING("The tape recorder has no tape inside.")) + return + if(tape.ruined) + to_chat(usr, "The tape recorder makes a scratchy noise.") + return + if(is_busy()) + to_chat(usr, SPAN_WARNING("The tape recorder is busy!")) + return + tape.wipe() + to_chat(usr, SPAN_WARNING("You wipe the tape.")) + +/obj/item/tape_recorder/verb/print_transcript() + set name = "Print Transcript" + set category = "Object" + + if(usr.incapacitated()) + return + if(!tape) + to_chat(usr, "There's no tape!") + return + if(tape.ruined) + to_chat(usr, "The tape recorder makes a scratchy noise.") + return + if(obj_flags & EMAGGED) + to_chat(usr, "The tape recorder makes a scratchy noise.") + return + if(!print_ready()) + to_chat(usr, "The recorder can't print that fast!") + return + if(is_busy()) + to_chat(usr, "You can't print the transcript while playing or recording!") + return + if(!_read_lock()) + to_chat(usr, SPAN_WARNING("The tape is busy!")) + return + + to_chat(usr, "Transcript printed.") + var/obj/item/paper/P = new /obj/item/paper(get_turf(src)) + var/list/constructed = list() + constructed += "
Transcript:

" + var/list/head = list() + head.len = CASSETTE_TAPE_DATA_LEN + var/datum/cassette_tape_iterator/reader = tape_iterator + var/msg + var/name + var/datum/language/lang + var/delay + var/current_time = 0 + var/opcode + while(reader.next_fast(head)) + msg = head[CASSETTE_TAPE_DATA_MESSAGE] + name = head[CASSETTE_TAPE_DATA_NAME] + delay = head[CASSETTE_TAPE_DATA_DELAY] + opcode = head[CASSETTE_TAPE_DATA_OPCODE] + current_time += delay + switch(opcode) + if(/datum/cassette_opcode/next_is_audible_emote) + // constructed += "*[name]* --- [msg] ---" + constructed += "[time2text(current_time, "mm:ss")] (unrecognizable / not speech)" + if(/datum/cassette_opcode/next_is_direct_broadcast) + constructed += "
[time2text(current_time, "mm:ss")]
[msg]
" + else + lang = head[3] + if(lang.id == LANGUAGE_ID_COMMON) + constructed += "[time2text(current_time, "mm:ss")] [name]: [msg]" + else + constructed += "[time2text(current_time, "mm:ss")] [name]: (unknown language)" + P.info = constructed.Join("
") + P.name = "Transcript" + print_cooldown() + QDEL_NULL(tape_iterator) + +/obj/item/tape_recorder/proc/print_ready() + return !TIMER_COOLDOWN_CHECK(src, CD_INDEX_TAPE_PRINT) + +/obj/item/tape_recorder/proc/print_cooldown() + TIMER_COOLDOWN_START(src, CD_INDEX_TAPE_PRINT, 30 SECONDS) + +/obj/item/tape_recorder/attack_self(mob/user) + if(is_playing()) + stop_playing(user) + else if(is_recording()) + stop_recording(user) + else + start_recording(user) + +/obj/item/tape_recorder/update_icon_state() + . = ..() + if(!tape) + icon_state = "taperecorder_empty" + else if(recording) + icon_state = "taperecorder_recording" + else if(playing) + icon_state = "taperecorder_playing" + else + icon_state = "taperecorder_idle" + +/obj/item/tape_recorder/empty + tape = null diff --git a/code/game/objects/items/devices/translator.dm b/code/game/objects/items/devices/translator.dm index fa487bef4b2..3e51c3194f8 100644 --- a/code/game/objects/items/devices/translator.dm +++ b/code/game/objects/items/devices/translator.dm @@ -6,27 +6,107 @@ icon_state = "translator" w_class = ITEMSIZE_NORMAL origin_tech = list(TECH_DATA = 3, TECH_ENGINEERING = 3) + /// our translation context; set to path to start as path + var/datum/translation_context/context = /datum/translation_context/simple/universal_translator + var/mult_icons = 1 //Changes sprite when it translates var/visual = 1 //If you need to see to get the message var/audio = 0 //If you need to hear to get the message - var/omni = 0 //cit change - if the language translated doesn't need machine_understands var/listening = 0 var/datum/language/langset + /// allow us to translate tapes + var/cassette_translation = TRUE + /// allow knowledge transfering + var/allow_knowledge_transfer = TRUE + +/obj/item/universal_translator/Initialize(mapload) + . = ..() + if(ispath(context)) + set_context(context) + +/obj/item/universal_translator/Destroy() + if(context) + QDEL_NULL(context) + return ..() + +/obj/item/universal_translator/proc/set_context(datum/translation_context/context_or_path) + if(istype(context, /datum/translation_context/variable/learning)) + var/datum/translation_context/variable/learning/CTX = context + CTX.on_train = null + if(ispath(context_or_path)) + context_or_path = new context_or_path + context = context_or_path + if(istype(context, /datum/translation_context/variable/learning)) + var/datum/translation_context/variable/learning/CTX = context + CTX.on_train = CALLBACK(src, .proc/on_learn) + +/obj/item/universal_translator/proc/on_learn(datum/translation_context/context, datum/language/L, old_efficiency) + if(old_efficiency) + return + if(!ismob(loc)) + return + to_chat(loc, SPAN_NOTICE("New language detected. Beginning translation network training.")) + +/obj/item/universal_translator/examine(mob/user) + . = ..() + if(cassette_translation) + . += SPAN_NOTICE("Use a cassette tape on this to translate the tape's contents where possible.") + +/obj/item/universal_translator/attackby(obj/item/I, mob/user) + if(istype(I, /obj/item/cassette_tape)) + if(TIMER_COOLDOWN_CHECK(src, CD_INDEX_TAPE_TRANSLATION)) + user.action_feedback(SPAN_WARNING("[src] cannot translate tapes that fast."), src) + return CLICKCHAIN_DO_NOT_PROPAGATE + var/obj/item/cassette_tape/T = I + var/datum/cassette_tape_iterator/translator/iter = T.lock_for_translation() + if(!iter) + user.action_feedback(SPAN_WARNING("Could not lock [T] for translation."), src) + return CLICKCHAIN_DO_NOT_PROPAGATE + var/amt = iter.translate(context) + user.action_feedback(SPAN_NOTICE("Translated [amt] lines on [T]. Processor cooling down for 10 seconds."), src) + TIMER_COOLDOWN_START(src, CD_INDEX_TAPE_TRANSLATION, 10 SECONDS) + return CLICKCHAIN_DO_NOT_PROPAGATE + return ..() + +/obj/item/universal_translator/attack(mob/living/M, mob/living/user, target_zone, attack_modifier) + if(user.a_intent == INTENT_HARM) + return ..() + if(!isrobot(M)) + return ..() + if(!istype(context, /datum/translation_context/variable)) + user.action_feedback(SPAN_WARNING("[src] does not have a variable translation matrix."), src) + return + if(!allow_knowledge_transfer) + user.action_feedback(SPAN_WARNING("[src] doesn't have a data port."), src) + return + var/mob/living/silicon/robot/R = M + var/datum/translation_context/variable/theirs = R.translation_context + if(!istype(theirs)) + user.action_feedback(SPAN_WARNING("[R] does not have a variable translation matrix."), src) + return + var/datum/translation_context/variable/ours = context + ours.copy_knowledge(theirs) /obj/item/universal_translator/attack_self(mob/user) if(!listening) //Turning ON - langset = input(user,"Translate to which of your languages?","Language Selection") as null|anything in user.languages - if(langset) - if(langset && ((langset.language_flags & NONVERBAL) || (langset.language_flags & HIVEMIND) || (!langset.machine_understands))) - //Nonverbal means no spoken words to translate, so I didn't see the need to remove it. - to_chat(user, "\The [src] cannot output that language.") - return - else - listening = 1 - listening_objects |= src - if(mult_icons) - icon_state = "[initial(icon_state)]1" - to_chat(user, "You enable \the [src], translating into [langset.name].") + var/list/allowed = list() + for(var/datum/language/L in user.languages) + if(L.language_flags & (LANGUAGE_NONVERBAL | LANGUAGE_HIVEMIND)) + continue + if(!context.can_translate(L)) + continue + allowed += L + if(!length(allowed)) + to_chat(user, SPAN_WARNING("There's no language you know that this can translate to.")) + return + langset = input(user,"Translate to which of your languages?","Language Selection") as null|anything in allowed + if(!langset) + return + listening = 1 + listening_objects |= src + if(mult_icons) + icon_state = "[initial(icon_state)]1" + to_chat(user, "You enable \the [src], translating into [langset.name].") else //Turning OFF listening = 0 listening_objects -= src @@ -51,12 +131,9 @@ if(!language) return //Borgs were causing runtimes when passing language=null - if (language && (language.language_flags & NONVERBAL)) + if (language && (language.language_flags & LANGUAGE_NONVERBAL)) return //Not gonna translate sign language - if (!language.machine_understands && !omni) // cit change - omni check - return //Any other languages that it can't translate. - if (visual && ((L.sdisabilities & SDISABILITY_NERVOUS) || L.eye_blind)) return //Can't see the screen, don't get the message @@ -67,15 +144,10 @@ //I'll just assume they don't look at the screen in that case //They don't understand the spoken language we're translating FROM - if(!L.say_understands(speaker,language)) + if(!L.say_understands(speaker, language)) //They understand the output language if(L.say_understands(null,langset)) - to_chat(L, "[src] translates, \"[message]\"") - - //They don't understand the output language - /*else - to_chat(L, "[src] translates, \"[langset.scramble(message)]\"") - */ //not much of a 'universal translator' with this else clause + to_chat(L, "[src] translates, \"[context.attempt_translation(language, speaker, message)]\"") //Let's try an ear-worn version /obj/item/universal_translator/ear @@ -86,13 +158,23 @@ slot_flags = SLOT_EARS visual = 0 audio = 1 + context = /datum/translation_context/simple/universal_translator -/obj/item/universal_translator/all - name = "handheld omni-translator" - desc = "This handy device appears to translate the languages it hears into onscreen text for a user, but upgraded with more languages. Somehow." - omni = 1 +/obj/item/universal_translator/adaptive + name = "handheld adaptive translator" + desc = "This handheld translator has been upgraded with a positronic accelerator, allowing it to \ + slowly learn certain languages that wasn't originally included." + context = /datum/translation_context/variable/learning/universal_translator -/obj/item/universal_translator/ear/all - name = "omni-translator earpiece" - desc = "This handy device appears to translate the languages it hears into another language for a user, but upgraded with more languages. Somehow." - omni = 1 +/obj/item/universal_translator/ear/adaptive + name = "omni-translator earpiece" + desc = "This earpiece translator has been upgraded with a positronic accelerator, allowing it to \ + slowly learn certain languages that wasn't originally included." + context = /datum/translation_context/variable/learning/universal_translator + +/datum/translation_context/simple/universal_translator + translation_class = TRANSLATION_CLASSES_STANDARD_TRANSLATE + +/datum/translation_context/variable/learning/universal_translator + translation_class = TRANSLATION_CLASSES_STANDARD_TRANSLATE + translation_class_learn = TRANSLATION_CLASSES_STANDARD_ADAPTIVE diff --git a/code/game/objects/items/robot/robot_upgrades.dm b/code/game/objects/items/robot/robot_upgrades.dm index 50f92a77958..c9fd42993a5 100644 --- a/code/game/objects/items/robot/robot_upgrades.dm +++ b/code/game/objects/items/robot/robot_upgrades.dm @@ -181,7 +181,7 @@ to_chat(usr, "There's no mounting point for the module!") return FALSE -/obj/item/borg/upgrade/syndicate/ +/obj/item/borg/upgrade/syndicate name = "scrambled equipment module" desc = "Unlocks new and often deadly module specific items of a robot" icon_state = "cyborg_upgrade3" @@ -199,8 +199,8 @@ return TRUE /obj/item/borg/upgrade/language - name = "language module" - desc = "Used to let cyborgs other than clerical or service speak a variety of languages." + name = "adaptive translation module" + desc = "Upgrades a cyborg's language processing unit with an adaptive translation module." icon_state = "cyborg_upgrade3" item_state = "cyborg_upgrade" @@ -208,31 +208,14 @@ if(..()) return FALSE - R.add_language(LANGUAGE_SOL_COMMON, 1) - R.add_language(LANGUAGE_TRADEBAND, 1) - R.add_language(LANGUAGE_UNATHI, 1) - R.add_language(LANGUAGE_SIIK, 1) - R.add_language(LANGUAGE_AKHANI, 1) - R.add_language(LANGUAGE_SKRELLIAN, 1) - R.add_language(LANGUAGE_SKRELLIANFAR, 0) - R.add_language(LANGUAGE_GUTTER, 1) - R.add_language(LANGUAGE_SCHECHI, 1) - R.add_language(LANGUAGE_ROOTLOCAL, 1) - R.add_language(LANGUAGE_TERMINUS, 1) - R.add_language(LANGUAGE_ZADDAT, 1) - R.add_language(LANGUAGE_AKULA, 1) - R.add_language(LANGUAGE_ADHERENT, 1) //Cephalopod Activity language, Omni Translators have it so borgs should too - R.add_language(LANGUAGE_BIRDSONG, 1) //Languages moved from robot/upgrades_vr - Papalus - R.add_language(LANGUAGE_SAGARU, 1) - R.add_language(LANGUAGE_CANILUNZT, 1) - R.add_language(LANGUAGE_DAEMON, 1) - R.add_language(LANGUAGE_ENOCHIAN, 1) - R.add_language(LANGUAGE_SLAVIC, 1) - R.add_language(LANGUAGE_SQUEAKISH, 1) + if(istype(R.translation_context, /datum/translation_context/simple/silicons)) + qdel(R.translation_context) + R.translation_context = new /datum/translation_context/variable/learning/silicons + R.sync_translation_context() return TRUE -//Robot resizing module, moved from robot/upgrades_vr - Papalus +//Robot resizing module, moved from robot/upgrades_vr - Papalus /obj/item/borg/upgrade/sizeshift name = "robot size alteration module" desc = "Using technology similar to one used in sizeguns, allows cyborgs to adjust their own size as neccesary." diff --git a/code/modules/admin/admin.dm b/code/modules/admin/admin.dm index c49e94a39f8..7a9df088475 100644 --- a/code/modules/admin/admin.dm +++ b/code/modules/admin/admin.dm @@ -192,7 +192,7 @@ var/global/floorIsLava = 0 body += "

Languages:
" var/f = 1 for(var/datum/language/L as anything in SScharacters.all_languages()) - if(!(L.language_flags & INNATE)) + if(!(L.language_flags & LANGUAGE_INNATE)) if(!f) body += " | " else f = 0 if(L in M.languages) diff --git a/code/modules/admin/verbs/debug/reestablish_db_connection.dm b/code/modules/admin/verbs/debug/reestablish_db_connection.dm index df89e7d1ceb..f40e05f1cfc 100644 --- a/code/modules/admin/verbs/debug/reestablish_db_connection.dm +++ b/code/modules/admin/verbs/debug/reestablish_db_connection.dm @@ -30,4 +30,4 @@ message_admins("Database connection re-established") message_admins("Reloading client database data...") for(var/client/C in GLOB.clients) - C.database?.Load() + C.database?.load() diff --git a/code/modules/client/client_procs.dm b/code/modules/client/client_procs.dm index 5e83b651306..83532c51954 100644 --- a/code/modules/client/client_procs.dm +++ b/code/modules/client/client_procs.dm @@ -293,7 +293,7 @@ GLOBAL_LIST_INIT(blacklisted_builds, list( // resolve database data // this is down here because player_lookup won't have an entry for us until log_client_to_db() runs!! database = new(ckey) - database.LogConnect() + database.log_connect() if (byond_version >= 512) if (!byond_build || byond_build < 1386) diff --git a/code/modules/client/database.dm b/code/modules/client/database.dm index 82b8297641e..f1dcd867fd1 100644 --- a/code/modules/client/database.dm +++ b/code/modules/client/database.dm @@ -27,28 +27,28 @@ src.ckey = ckey if(!src.ckey) return - Load() + load() /** * async */ -/datum/client_dbdata/proc/Load() +/datum/client_dbdata/proc/load() if(!SSdbcore.Connect()) if(isnull(available)) available = FALSE return FALSE - INVOKE_ASYNC(src, .proc/Load_Async) + INVOKE_ASYNC(src, .proc/load_blocking) return TRUE -/datum/client_dbdata/proc/Load_Async() +/datum/client_dbdata/proc/load_blocking() // allow admin proccalls - there's no args here. var/was_proccall = !!IsAdminAdvancedProcCall() var/old_usr = usr usr = null - _Load_Lock(was_proccall) + _load_lock(was_proccall) usr = old_usr -/datum/client_dbdata/proc/_Load_Lock(was_proccall) +/datum/client_dbdata/proc/_load_lock(was_proccall) if(IsAdminAdvancedProcCall()) return if(loading) @@ -56,10 +56,10 @@ CRASH("loading is still locked; why are we loading this fast?") return loading = TRUE - _Load() + _load() loading = FALSE -/datum/client_dbdata/proc/_Load() +/datum/client_dbdata/proc/_load() if(IsAdminAdvancedProcCall()) return var/datum/db_query/lookup @@ -103,9 +103,9 @@ CRASH("failed to lookup player row on id [lookup_pid] even though id was present in player_lookup.") else qdel(lookup) - RegisterNewPlayer(lookup_firstseen, lookup_id) + register_new_player(lookup_firstseen, lookup_id) -/datum/client_dbdata/proc/RegisterNewPlayer(migrate_firstseen, lookup_id) +/datum/client_dbdata/proc/register_new_player(migrate_firstseen, lookup_id) if(IsAdminAdvancedProcCall()) return // new person! @@ -149,7 +149,7 @@ /** * async */ -/datum/client_dbdata/proc/Save() +/datum/client_dbdata/proc/save() set waitfor = FALSE // why are we in here if we're write locked? if(saving) @@ -164,11 +164,11 @@ usr = null // don't lock; if something is spamming our flags they probably shouldn't be saving = TRUE - _Save() + _save() saving = FALSE usr = old_usr -/datum/client_dbdata/proc/_Save() +/datum/client_dbdata/proc/_save() qdel(SSdbcore.ExecuteQuery( "UPDATE [format_table_name("player")] SET flags = :flags WHERE id = :id", list( @@ -180,11 +180,11 @@ /** * async */ -/datum/client_dbdata/proc/LogConnect() +/datum/client_dbdata/proc/log_connect() set waitfor = FALSE - UpdateLastSeen() + update_last_seen() -/datum/client_dbdata/proc/UpdateLastSeen() +/datum/client_dbdata/proc/update_last_seen() // don't interrupt if(!block_on_available()) return FALSE diff --git a/code/modules/integrated_electronics/subtypes/input.dm b/code/modules/integrated_electronics/subtypes/input.dm index 5c0daa7d660..b06588cdfc2 100644 --- a/code/modules/integrated_electronics/subtypes/input.dm +++ b/code/modules/integrated_electronics/subtypes/input.dm @@ -942,6 +942,11 @@ push_data() activate_pin(2) +GLOBAL_DATUM_INIT(circuit_translation_context, /datum/translation_context/simple/integrated_circuit, new) + +/datum/translation_context/simple/integrated_circuit + translation_class = TRANSLATION_CLASS_LEVEL_1 + /obj/item/integrated_circuit/input/microphone name = "microphone" desc = "Useful for spying on people, or for voice-activated machines." @@ -959,8 +964,11 @@ activators = list("on message received" = IC_PINTYPE_PULSE_OUT, "on translation" = IC_PINTYPE_PULSE_OUT) spawn_flags = IC_SPAWN_DEFAULT|IC_SPAWN_RESEARCH power_draw_per_use = 5 + /// translation context + var/datum/translation_context/translation_context /obj/item/integrated_circuit/input/microphone/Initialize(mapload) + translation_context = GLOB.circuit_translation_context . = ..() listening_objects |= src @@ -972,8 +980,10 @@ var/translated = FALSE if(M && msg) if(speaking) - if(!speaking.machine_understands) + if(!translation_context.can_translate(speaking, M)) msg = speaking.scramble(msg) + else + msg = translation_context.attempt_translation(speaking, M, msg) if(!istype(speaking, /datum/language/common) && !istype(speaking, /datum/language/noise)) translated = TRUE set_pin_data(IC_OUTPUT, 1, M.GetVoice()) @@ -1021,7 +1031,7 @@ var/signlang = FALSE if(M && msg) if(speaking) - if(!((speaking.language_flags & NONVERBAL) || (speaking.language_flags & SIGNLANG))) + if(!((speaking.language_flags & LANGUAGE_NONVERBAL) || (speaking.language_flags & LANGUAGE_SIGNLANG))) signlang = FALSE msg = speaking.scramble(msg, my_langs) else diff --git a/code/modules/jobs/job.dm b/code/modules/jobs/job.dm index 371f4b982cc..b68cfb51a7b 100644 --- a/code/modules/jobs/job.dm +++ b/code/modules/jobs/job.dm @@ -279,6 +279,8 @@ return (available_in_days(C) == 0) // Available in 0 days = available right now = player is old enough to play. /datum/job/proc/available_in_days(client/C) + if(C.has_jexp_bypass()) + return 0 if(C && config_legacy.use_age_restriction_for_jobs && isnum(C.player_age) && isnum(minimal_player_age)) return max(0, minimal_player_age - C.player_age) return 0 diff --git a/code/modules/language/hivemind.dm b/code/modules/language/hivemind.dm new file mode 100644 index 00000000000..75400816136 --- /dev/null +++ b/code/modules/language/hivemind.dm @@ -0,0 +1,17 @@ +/** + * todo: finish; these are just stubs + * + * Hivemind contexts control how languages with special sending methods process speech. + * They don't necessarily have to be for actual hiveminds; + * "broadcast to someone you're grabbing/have a brain parasite in" is just as valid. + * + * todo: probably need raw say args (??) and retval for if we should pass through to normal say + * todo: this probably needs a saycode rewrite to be sane + */ +/datum/hivemind_context + +/datum/hivemind_context/proc/broadcast(atom/movable/speaker, message, ...) + +/datum/hivemind_context/proc/get_listeners() + +/datum/hivemind_context/proc/send(atom/movable/listener, message, ...) diff --git a/code/modules/language/language.dm b/code/modules/language/language.dm index 0e345923c3f..39168abfa43 100644 --- a/code/modules/language/language.dm +++ b/code/modules/language/language.dm @@ -22,6 +22,8 @@ // todo: sort languages into categories /// category in UI var/category = "General" + /// translation classes + var/translation_class = NONE /// 'says', 'hisses', 'farts'. var/speech_verb = "says" /// Used when sentence ends in a ? @@ -30,7 +32,7 @@ var/exclaim_verb = "exclaims" /// Optional. When not specified speech_verb + quietly/softly is used instead. var/whisper_verb - /// list of emotes that might be displayed if this language has NONVERBAL or SIGNLANG language_flags + /// list of emotes that might be displayed if this language has LANGUAGE_NONVERBAL or LANGUAGE_SIGNLANG language_flags var/signlang_verb = list("signs", "gestures") /// CSS style to use for strings in this language. var/colour = "body" @@ -44,12 +46,11 @@ var/list/syllables /// Likelihood of getting a space in the random scramble string var/list/space_chance = 55 - /// Whether machines can parse and understand this language - var/machine_understands = 1 /// List of languages that can /somehwat/ understand it, format is: name = chance of understanding a word var/list/partial_understanding var/list/scramble_cache = list() + // todo: icon state support, shorthand is kind of jarring to see in chat /// The shorthand name of this language, used for the indicators in chat. Should be only a handful of all-caps letters. var/shorthand = "???" @@ -115,7 +116,7 @@ return stars(input) // If the input is cached already, move it to the end of the cache and return it - if(input in scramble_cache) + if(scramble_cache[input]) var/n = scramble_cache[input] scramble_cache -= input scramble_cache[input] = n @@ -197,7 +198,7 @@ if(name != "Noise") // Audible Emotes if(ishuman(speaker)) var/mob/living/carbon/human/H = speaker - if(H.species.has_organ[O_VOICE] && !(language_flags & SIGNLANG) && !(language_flags & NONVERBAL)) // Does the species need a voicebox? Is the language even spoken? + if(H.species.has_organ[O_VOICE] && !(language_flags & LANGUAGE_SIGNLANG) && !(language_flags & LANGUAGE_NONVERBAL)) // Does the species need a voicebox? Is the language even spoken? var/obj/item/organ/internal/voicebox/vocal = H.internal_organs_by_name[O_VOICE] if(!vocal || vocal.is_broken() || vocal.mute) return FALSE @@ -247,7 +248,7 @@ if(speaking.can_speak_special(src)) if(universal_speak) return 1 - if(speaking && (speaking.language_flags & INNATE)) + if(speaking && (speaking.language_flags & LANGUAGE_INNATE)) return 1 if(speaking in src.languages) return 1 @@ -268,7 +269,7 @@ var/dat = "Known Languages

" for(var/datum/language/L in languages) - if(!(L.language_flags & NONGLOBAL)) + if(!(L.language_flags & LANGUAGE_NONGLOBAL)) dat += "[L.name] ([get_language_prefix()][L.key])
[L.desc]

" src << browse(dat, "window=checklanguage") @@ -281,7 +282,7 @@ dat += "Current default language: [default_language] - reset

" for(var/datum/language/L in languages) - if(!(L.language_flags & NONGLOBAL)) + if(!(L.language_flags & LANGUAGE_NONGLOBAL)) if(L == default_language) dat += "[L.name] ([get_language_prefix()][L.key]) - default - reset
[L.desc]

" else if (can_speak(L)) diff --git a/code/modules/language/abstract.dm b/code/modules/language/languages/abstract.dm similarity index 97% rename from code/modules/language/abstract.dm rename to code/modules/language/languages/abstract.dm index 728983dfcab..64e81672f18 100644 --- a/code/modules/language/abstract.dm +++ b/code/modules/language/languages/abstract.dm @@ -4,7 +4,7 @@ name = "Noise" desc = "Noises" key = "" - language_flags = RESTRICTED|NONGLOBAL|INNATE|NO_TALK_MSG|NO_STUTTER + language_flags = LANGUAGE_RESTRICTED|LANGUAGE_NONGLOBAL|LANGUAGE_INNATE|LANGUAGE_NO_TALK_MSG|LANGUAGE_NO_STUTTER /datum/language/noise/format_message(message, verb) return "[message]" @@ -33,8 +33,7 @@ exclaim_verb = "shrieks" colour = "attack" key = "r" // Radda radda radda - language_flags = RESTRICTED|NONGLOBAL - machine_understands = 0 + language_flags = LANGUAGE_RESTRICTED|LANGUAGE_NONGLOBAL syllables = list ( "radda", "fea","vea","vei","veh","vee","feh","fa","soa","su","sua","sou","se","seh","twa","twe","twi", "ahm","lea","lee","nae","nah","pa","pau","fae","fai","soh","mou","ahe","ll","ea","ai","thi", diff --git a/code/modules/language/animal.dm b/code/modules/language/languages/animal.dm similarity index 75% rename from code/modules/language/animal.dm rename to code/modules/language/languages/animal.dm index 006ee661d43..1730ff3c264 100644 --- a/code/modules/language/animal.dm +++ b/code/modules/language/languages/animal.dm @@ -1,13 +1,13 @@ /datum/language/corgi id = LANGUAGE_ID_DOG name = "Dog" + translation_class = TRANSLATION_CLASS_ANIMAL desc = "Woof woof woof." speech_verb = "barks" ask_verb = "woofs" exclaim_verb = "howls" + language_flags = LANGUAGE_RESTRICTED key = null // demoted - language_flags = RESTRICTED - machine_understands = 0 space_chance = 100 syllables = list("bark", "woof", "bowwow", "yap", "arf") shorthand = "DOG" @@ -15,13 +15,13 @@ /datum/language/cat id = LANGUAGE_ID_CAT name = "Cat" + translation_class = TRANSLATION_CLASS_ANIMAL desc = "Meow meow meow." speech_verb = "meows" ask_verb = "mrowls" exclaim_verb = "yowls" - key = null // demoted - language_flags = RESTRICTED - machine_understands = 0 + language_flags = LANGUAGE_RESTRICTED + key = null // demoted space_chance = 100 syllables = list("meow", "mrowl", "purr", "meow", "meow", "meow") shorthand = "CAT" @@ -29,13 +29,13 @@ /datum/language/mouse id = LANGUAGE_ID_MOUSE name = "Mouse" + translation_class = TRANSLATION_CLASS_ANIMAL desc = "Squeak squeak. *Nibbles on cheese*" speech_verb = "squeaks" ask_verb = "squeaks" exclaim_verb = "squeaks" + language_flags = LANGUAGE_RESTRICTED key = null // demoted - language_flags = RESTRICTED - machine_understands = 0 space_chance = 100 syllables = list("squeak") // , "gripes", "oi", "meow") shorthand = "MSE" @@ -43,13 +43,13 @@ /datum/language/bird id = LANGUAGE_ID_BIRD name = "Bird" + translation_class = TRANSLATION_CLASS_ANIMAL desc = "Chirp chirp, give me food" speech_verb = "chirps" ask_verb = "tweets" exclaim_verb = "squawks" + language_flags = LANGUAGE_RESTRICTED key = null // demoted - language_flags = RESTRICTED - machine_understands = 0 space_chance = 100 syllables = list("chirp", "squawk", "tweet") shorthand = "BIRD" diff --git a/code/modules/language/antagonist/borer.dm b/code/modules/language/languages/antagonist/borer.dm similarity index 92% rename from code/modules/language/antagonist/borer.dm rename to code/modules/language/languages/antagonist/borer.dm index 35a1377f59f..066db06e8ad 100644 --- a/code/modules/language/antagonist/borer.dm +++ b/code/modules/language/languages/antagonist/borer.dm @@ -7,8 +7,7 @@ exclaim_verb = "sings" colour = "alien" key = "" //!! THIS DOES NOT HAVE A LANGUAGE KEY IF YOU SOMEHOW MIRACULOUSLY GET ANTAGS WORKING ON RP BEFORE WE TRANSITION TO SS14 IN LIKE 2025 PUT ONE IN. - machine_understands = 0 - language_flags = RESTRICTED | HIVEMIND + language_flags = LANGUAGE_RESTRICTED | LANGUAGE_HIVEMIND /datum/language/corticalborer/broadcast(var/mob/living/speaker,var/message,var/speaker_mask) diff --git a/code/modules/language/antagonist/changeling.dm b/code/modules/language/languages/antagonist/changeling.dm similarity index 87% rename from code/modules/language/antagonist/changeling.dm rename to code/modules/language/languages/antagonist/changeling.dm index b204eff4da2..77991471c67 100644 --- a/code/modules/language/antagonist/changeling.dm +++ b/code/modules/language/languages/antagonist/changeling.dm @@ -5,8 +5,7 @@ speech_verb = "says" colour = "changeling" key = "g" - machine_understands = 0 - language_flags = RESTRICTED | HIVEMIND + language_flags = LANGUAGE_RESTRICTED | LANGUAGE_HIVEMIND /datum/language/ling/broadcast(var/mob/living/speaker,var/message,var/speaker_mask) diff --git a/code/modules/language/antagonist/cult.dm b/code/modules/language/languages/antagonist/cult.dm similarity index 90% rename from code/modules/language/antagonist/cult.dm rename to code/modules/language/languages/antagonist/cult.dm index 38fa1b31fd2..46a0356bbdc 100644 --- a/code/modules/language/antagonist/cult.dm +++ b/code/modules/language/languages/antagonist/cult.dm @@ -1,15 +1,15 @@ /datum/language/cultcommon id = LANGUAGE_ID_CULT name = LANGUAGE_CULT + translation_class = TRANSLATION_CLASS_ARCANE desc = "The chants of the occult, the incomprehensible." speech_verb = "intones" ask_verb = "intones" exclaim_verb = "chants" colour = "cult" key = "f" - language_flags = RESTRICTED + language_flags = LANGUAGE_RESTRICTED space_chance = 100 - machine_understands = 0 syllables = list("ire","ego","nahlizet","certum","veri","jatkaa","mgar","balaq", "karazet", "geeri", \ "orkan", "allaq", "sas'so", "c'arta", "forbici", "tarem", "n'ath", "reth", "sh'yro", "eth", "d'raggathnor", \ "mah'weyh", "pleggh", "at", "e'ntrath", "tok-lyr", "rqa'nap", "g'lt-ulotf", "ta'gh", "fara'qha", "fel", "d'amar det", \ @@ -27,6 +27,5 @@ exclaim_verb = "chants" colour = "cult" key = "y" - machine_understands = 0 - language_flags = RESTRICTED | HIVEMIND + language_flags = LANGUAGE_RESTRICTED | LANGUAGE_HIVEMIND diff --git a/code/modules/language/antagonist/swarmbot.dm b/code/modules/language/languages/antagonist/swarmbot.dm similarity index 70% rename from code/modules/language/antagonist/swarmbot.dm rename to code/modules/language/languages/antagonist/swarmbot.dm index 769c9b30399..170118079c9 100644 --- a/code/modules/language/antagonist/swarmbot.dm +++ b/code/modules/language/languages/antagonist/swarmbot.dm @@ -1,13 +1,13 @@ /datum/language/swarmbot id = LANGUAGE_ID_SWARMBOT name = LANGUAGE_SWARMBOT + translation_class = TRANSLATION_CLASS_MACHINE | TRANSLATION_CLASS_ENCRYPTED desc = "A confusing mechanical language spoken by some form of ancient machine." speech_verb = "clatters" ask_verb = "chatters" exclaim_verb = "shrieks" colour = "changeling" key = "_" - machine_understands = 0 - language_flags = NO_STUTTER | RESTRICTED + language_flags = LANGUAGE_NO_STUTTER | LANGUAGE_RESTRICTED syllables = list("^", "v", "-", ".", "~") space_chance = 60 diff --git a/code/modules/language/antagonist/xenomorph.dm b/code/modules/language/languages/antagonist/xenomorph.dm similarity index 81% rename from code/modules/language/antagonist/xenomorph.dm rename to code/modules/language/languages/antagonist/xenomorph.dm index 75f34bb9b1d..8d885ea2cea 100644 --- a/code/modules/language/antagonist/xenomorph.dm +++ b/code/modules/language/languages/antagonist/xenomorph.dm @@ -1,14 +1,14 @@ /datum/language/xenocommon id = LANGUAGE_ID_XENOMORPH name = SPECIES_XENO + translation_class = TRANSLATION_CLASS_MONSTER | TRANSLATION_CLASS_LEVEL_3 colour = "alien" desc = "The common tongue of the xenomorphs." speech_verb = "hisses" ask_verb = "hisses" exclaim_verb = "hisses" key = "u" - machine_understands = 0 - language_flags = RESTRICTED + language_flags = LANGUAGE_RESTRICTED syllables = list("sss","sSs","SSS") shorthand = "XENO" @@ -20,9 +20,8 @@ ask_verb = "hisses" exclaim_verb = "hisses" colour = "alien" - machine_understands = 0 key = "x" - language_flags = RESTRICTED | HIVEMIND + language_flags = LANGUAGE_RESTRICTED | LANGUAGE_HIVEMIND /datum/language/xenos/check_special_condition(var/mob/other) diff --git a/code/modules/language/core.dm b/code/modules/language/languages/core.dm similarity index 79% rename from code/modules/language/core.dm rename to code/modules/language/languages/core.dm index 86dff292627..202cc531b4a 100644 --- a/code/modules/language/core.dm +++ b/code/modules/language/languages/core.dm @@ -2,28 +2,30 @@ /datum/language/common id = LANGUAGE_ID_COMMON name = LANGUAGE_GALCOM + translation_class = TRANSLATION_CLASS_DEFAULT_CORE_LANGUAGE desc = "The common galactic tongue, engineered for cross-species communication." speech_verb = "says" exclaim_verb = list("exclaims","shouts","yells") whisper_verb = "whispers" key = "0" - language_flags = RESTRICTED + language_flags = LANGUAGE_RESTRICTED syllables = list( "vol", "zum", "coo","zoo","bi","do","ooz","ite","og","re","si","ite","ish", "ar","at","on","ee","east","ma","da", "rim") partial_understanding = list(LANGUAGE_SKRELLIAN = 30, LANGUAGE_SOL_COMMON = 30) - shorthand = "GC" + shorthand = null // shorthand disabled - unnecessarily spammy. /datum/language/machine id = LANGUAGE_ID_EAL name = LANGUAGE_EAL + translation_class = (TRANSLATION_CLASS_DEFAULT_CORE_LANGUAGE | TRANSLATION_CLASS_MACHINE) desc = "An efficient language of encoded tones developed by positronics." speech_verb = "whistles" ask_verb = "chirps" exclaim_verb = "whistles loudly" colour = "changeling" key = "6" - language_flags = NO_STUTTER + language_flags = LANGUAGE_NO_STUTTER syllables = list("beep","beep","beep","beep","beep","boop","boop","boop","bop","bop","dee","dee","doo","doo","hiss","hss","buzz","buzz","bzz","ksssh","keey","wurr","wahh","tzzz","shh","shk") space_chance = 10 shorthand = "EAL" @@ -37,11 +39,12 @@ /datum/language/sign id = LANGUAGE_ID_SIGN name = LANGUAGE_SIGN + translation_class = TRANSLATION_CLASS_DEFAULT_CORE_LANGUAGE desc = "A sign language commonly used for those who are deaf or mute." signlang_verb = list("gestures") colour = "say_quote" key = "s" - language_flags = SIGNLANG|NO_STUTTER|NONVERBAL + language_flags = LANGUAGE_SIGNLANG|LANGUAGE_NO_STUTTER|LANGUAGE_NONVERBAL shorthand = "SIGN" /datum/language/sign/can_speak_special(var/mob/speaker) // TODO: If ever we make external organs assist languages, convert this over to the new format diff --git a/code/modules/language/event.dm b/code/modules/language/languages/event.dm similarity index 92% rename from code/modules/language/event.dm rename to code/modules/language/languages/event.dm index 488790f613e..6ed218ebe2f 100644 --- a/code/modules/language/event.dm +++ b/code/modules/language/languages/event.dm @@ -9,7 +9,7 @@ exclaim_verb = "shouts" colour = "warning" key = "]" - language_flags = RESTRICTED + language_flags = LANGUAGE_RESTRICTED syllables = list("chan","ange","thi","se") //for your antag purposes. @@ -21,9 +21,8 @@ ask_verb = "asks" exclaim_verb = "shouts" colour = "deadsay" + language_flags = LANGUAGE_RESTRICTED key = "\[" - machine_understands = 0 - language_flags = RESTRICTED syllables = list("rus","zem","ave","groz","ski","ska","ven","konst","pol","lin","svy", "danya","da","mied","zan","das","krem","myka","cyka","blyat","to","st","no","na","ni", "ko","ne","en","po","ra","li","on","byl","cto","eni","ost","ol","ego","ver","stv","pro") diff --git a/code/modules/language/galactic.dm b/code/modules/language/languages/galactic.dm similarity index 97% rename from code/modules/language/galactic.dm rename to code/modules/language/languages/galactic.dm index 87e3442a6bb..8db85d44d59 100644 --- a/code/modules/language/galactic.dm +++ b/code/modules/language/languages/galactic.dm @@ -2,6 +2,7 @@ /datum/language/trader id = LANGUAGE_ID_TRADEBAND name = LANGUAGE_TRADEBAND + translation_class = TRANSLATION_CLASS_DEFAULT_CORE_LANGUAGE desc = "Maintained by the various trading cartels in major systems, this elegant, structured language is used for bartering and bargaining." speech_verb = "enunciates" colour = "say_quote" @@ -18,12 +19,12 @@ // Criminal language. /datum/language/gutter name = LANGUAGE_GUTTER + translation_class = TRANSLATION_CLASS_DEFAULT_CORE_LANGUAGE // fuck you you lost your machine_understands immunity desc = "Gutter originated as a Thieves' Cant of sorts during the early colonization era. The language eventually spread from the cartels and triads to the disenfranchised people of the Bowl." speech_verb = "growls" colour = "rough" key = "3" space_chance = 45 - machine_understands = FALSE partial_understanding = list(LANGUAGE_GALCOM = 10, LANGUAGE_TRADEBAND = 20, LANGUAGE_SOL_COMMON = 20) shorthand = "GUT" syllables = list ( diff --git a/code/modules/language/monkey.dm b/code/modules/language/languages/monkey.dm similarity index 69% rename from code/modules/language/monkey.dm rename to code/modules/language/languages/monkey.dm index acbe281c350..cf0414b180a 100644 --- a/code/modules/language/monkey.dm +++ b/code/modules/language/languages/monkey.dm @@ -2,42 +2,42 @@ id = LANGUAGE_ID_CHIMPANZEE key = null name = "Chimpanzee" + translation_class = TRANSLATION_CLASS_LEVEL_3 desc = "Ook ook ook." speech_verb = "chimpers" ask_verb = "chimpers" exclaim_verb = "screeches" syllables = list("ook","eek") - language_flags = RESTRICTED - machine_understands = 0 + language_flags = LANGUAGE_RESTRICTED shorthand = "MONK" /datum/language/skrell/monkey id = LANGUAGE_ID_NEAERA key = null name = SPECIES_MONKEY_SKRELL + translation_class = TRANSLATION_CLASS_LEVEL_3 desc = "Squik squik squik." syllables = list("hiss","gronk") - language_flags = RESTRICTED - machine_understands = 0 + language_flags = LANGUAGE_RESTRICTED shorthand = "NEA" /datum/language/unathi/monkey id = LANGUAGE_ID_STOK key = null name = SPECIES_MONKEY_UNATHI + translation_class = TRANSLATION_CLASS_LEVEL_3 desc = "Hiss hiss hiss." syllables = list("squick","croak") - language_flags = RESTRICTED - machine_understands = 0 + language_flags = LANGUAGE_RESTRICTED shorthand = "STOK" /datum/language/tajaran/monkey id = LANGUAGE_ID_FARWA key = null name = SPECIES_MONKEY_TAJ + translation_class = TRANSLATION_CLASS_LEVEL_3 desc = "Meow meow meow." syllables = list("meow","mew") - language_flags = RESTRICTED - machine_understands = 0 + language_flags = LANGUAGE_RESTRICTED shorthand = "FAR" diff --git a/code/modules/language/silicons.dm b/code/modules/language/languages/silicons.dm similarity index 95% rename from code/modules/language/silicons.dm rename to code/modules/language/languages/silicons.dm index 4fec93b4049..b5d12308eea 100644 --- a/code/modules/language/silicons.dm +++ b/code/modules/language/languages/silicons.dm @@ -7,8 +7,7 @@ ask_verb = "queries" exclaim_verb = "declares" key = "b" - machine_understands = 0 - language_flags = RESTRICTED | HIVEMIND + language_flags = LANGUAGE_RESTRICTED | LANGUAGE_HIVEMIND var/drone_only /datum/language/binary/broadcast(var/mob/living/speaker,var/message,var/speaker_mask) @@ -67,6 +66,5 @@ exclaim_verb = "transmits" colour = "say_quote" key = "d" - machine_understands = 0 - language_flags = RESTRICTED | HIVEMIND + language_flags = LANGUAGE_RESTRICTED | LANGUAGE_HIVEMIND drone_only = 1 diff --git a/code/modules/language/species/_species.dm b/code/modules/language/languages/species/_species.dm similarity index 100% rename from code/modules/language/species/_species.dm rename to code/modules/language/languages/species/_species.dm diff --git a/code/modules/language/species/adherent.dm b/code/modules/language/languages/species/adherent.dm similarity index 83% rename from code/modules/language/species/adherent.dm rename to code/modules/language/languages/species/adherent.dm index 8447860f5c7..db5bb5a131e 100644 --- a/code/modules/language/species/adherent.dm +++ b/code/modules/language/languages/species/adherent.dm @@ -1,13 +1,14 @@ /datum/language/adherent id = LANGUAGE_ID_ADHERENT name = LANGUAGE_ADHERENT + translation_class = TRANSLATION_CLASS_MACHINE | TRANSLATION_CLASS_ENCRYPTED desc = "The mellifluous wind chime tones of the Vigil's formal shared language." speech_verb = "chimes" ask_verb = "rings" exclaim_verb = "peals" colour = "adherent" key = "P" - language_flags = WHITELISTED + language_flags = LANGUAGE_WHITELISTED syllables = list("\[Ab\]", "\[Bb\]", "\[Cb\]", "\[Db\]", "\[Eb\]", "\[Fb\]", "\[Gb\]", "\[A#\]", "\[B#\]", "\[C#\]", "\[D#\]", "\[E#\]", "\[F#\]", "\[G#\]", "\[A\]", "\[B\]", "\[C\]", "\[D\]", "\[E\]", "\[F\]", "\[G\]", diff --git a/code/modules/language/species/akula.dm b/code/modules/language/languages/species/akula.dm similarity index 93% rename from code/modules/language/species/akula.dm rename to code/modules/language/languages/species/akula.dm index 4151d01f4bc..98b637b3c13 100644 --- a/code/modules/language/species/akula.dm +++ b/code/modules/language/languages/species/akula.dm @@ -1,6 +1,7 @@ /datum/language/akula id = LANGUAGE_ID_AKULA name = LANGUAGE_AKULA + translation_class = TRANSLATION_CLASS_DEFAULT_STANDARD_RACE desc = "The language of the Akula consists of a wildly melodic combination of hard and soft consonants working in flowing conjuncture with one another." speech_verb = "cants" ask_verb = "inflects" diff --git a/code/modules/language/species/alraune.dm b/code/modules/language/languages/species/alraune.dm similarity index 90% rename from code/modules/language/species/alraune.dm rename to code/modules/language/languages/species/alraune.dm index ea7a7fb1a0a..21433202df5 100644 --- a/code/modules/language/species/alraune.dm +++ b/code/modules/language/languages/species/alraune.dm @@ -2,6 +2,7 @@ /datum/language/vernal id = LANGUAGE_ID_VERNAL name = LANGUAGE_VERNAL + translation_class = TRANSLATION_CLASS_DEFAULT_STANDARD_RACE desc = "An imperceptibly complex and nuanced language, often confused for the natural sounds of the forest." signlang_verb = list("sways") speech_verb = "rustles" diff --git a/code/modules/language/species/altevian.dm b/code/modules/language/languages/species/altevian.dm similarity index 88% rename from code/modules/language/species/altevian.dm rename to code/modules/language/languages/species/altevian.dm index f5cad6600b3..81e1522d3ed 100644 --- a/code/modules/language/species/altevian.dm +++ b/code/modules/language/languages/species/altevian.dm @@ -1,6 +1,7 @@ /datum/language/squeakish id = LANGUAGE_ID_ALTEVIAN name = LANGUAGE_SQUEAKISH + translation_class = TRANSLATION_CLASS_DEFAULT_STANDARD_RACE desc = "A language native to the Altevians, it has been adopted by other rodent faring species over time." key = "E" colour = "squeakish" @@ -9,5 +10,4 @@ exclaim_verb = "squeaks loudly" syllables = list ("sque", "uik", "squeak", "squee", "eak", "eek", "uek", "squik", "squeek", "sq", "eek", "squeee", "ee", "ek", "ak", "ueak", "squea") - machine_understands = 1 shorthand = "SQ" diff --git a/code/modules/language/species/birdsong.dm b/code/modules/language/languages/species/birdsong.dm similarity index 81% rename from code/modules/language/species/birdsong.dm rename to code/modules/language/languages/species/birdsong.dm index 1aff162859c..64ebd2ebdc3 100644 --- a/code/modules/language/species/birdsong.dm +++ b/code/modules/language/languages/species/birdsong.dm @@ -1,6 +1,7 @@ /datum/language/birdsong id = LANGUAGE_ID_BIRDSONG name = LANGUAGE_BIRDSONG + translation_class = TRANSLATION_CLASS_DEFAULT_STANDARD_RACE desc = "A language primarily spoken by Narvians" speech_verb = "chirps" colour = "birdsongc" diff --git a/code/modules/language/species/celestial.dm b/code/modules/language/languages/species/celestial.dm similarity index 81% rename from code/modules/language/species/celestial.dm rename to code/modules/language/languages/species/celestial.dm index 7da73f10873..12ece6b9cdf 100644 --- a/code/modules/language/species/celestial.dm +++ b/code/modules/language/languages/species/celestial.dm @@ -1,6 +1,8 @@ /datum/language/demon id = LANGUAGE_ID_DAEDAL_DREMACHIR name = LANGUAGE_DAEMON + //! set to common due to overwhelming playerbase saturation. + translation_class = TRANSLATION_CLASS_DEFAULT_STANDARD_RACE desc = "The language spoken by the demons of Infernum, it's composed of deep chanting. It's rarely spoken off of Infernum due to the volume one has to exert." speech_verb = "chants" ask_verb = "croons" @@ -9,12 +11,13 @@ key = "n" syllables = list("viepn","e","bag","docu","kar","xlaqf","raa","qwos","nen","ty","von","kytaf","xin","ty","ka","baak","hlafaifpyk","znu","agrith","na'ar","uah","plhu","six","fhler","bjel","scee","lleri", "dttm","aggr","uujl","hjjifr","wwuthaav",) - machine_understands = FALSE shorthand = "DEM" /datum/language/angel id = LANGUAGE_ID_DAEDAL_AURIL name = LANGUAGE_ENOCHIAN + //! trialling putting it on rare; put it to common if everyone uses it + translation_class = TRANSLATION_CLASS_DEFAULT_RARE_RACE desc = "The graceful language spoken by angels, composed of quiet hymns. Formally, Angels sing it." speech_verb = "sings" ask_verb = "hums" @@ -23,5 +26,4 @@ key = "i" syllables = list("salve","sum","loqui","operatur","iusta","et","permittit","facere","effercio","pluribus","enim","hoc", "mihi","wan","six","tartu") - machine_understands = FALSE shorthand = "ANG" diff --git a/code/modules/language/species/diona.dm b/code/modules/language/languages/species/diona.dm similarity index 87% rename from code/modules/language/species/diona.dm rename to code/modules/language/languages/species/diona.dm index 422e025b70b..0bc6e810003 100644 --- a/code/modules/language/species/diona.dm +++ b/code/modules/language/languages/species/diona.dm @@ -1,14 +1,14 @@ /datum/language/diona_local id = LANGUAGE_ID_DIONA name = LANGUAGE_ROOTLOCAL + translation_class = TRANSLATION_CLASS_DEFAULT_RARE_RACE desc = "A complex language known instinctively by Dionaea, 'spoken' by emitting modulated radio waves. This version uses high frequency waves for quick communication at short ranges." speech_verb = "creaks and rustles" ask_verb = "creaks" exclaim_verb = "rustles" colour = "soghun" key = "q" - machine_understands = 0 - language_flags = RESTRICTED + language_flags = LANGUAGE_RESTRICTED syllables = list("hs","zt","kr","st","sh") shorthand = "DIO" @@ -22,5 +22,4 @@ name = LANGUAGE_ROOTGLOBAL desc = "A complex language known instinctively by Dionaea, 'spoken' by emitting modulated radio waves. This version uses low frequency waves for slow communication at long ranges." key = "w" - machine_understands = 0 - language_flags = RESTRICTED | HIVEMIND + language_flags = LANGUAGE_RESTRICTED | LANGUAGE_HIVEMIND diff --git a/code/modules/language/species/human.dm b/code/modules/language/languages/species/human.dm similarity index 98% rename from code/modules/language/species/human.dm rename to code/modules/language/languages/species/human.dm index a7fe942ca41..0cfc81493ca 100644 --- a/code/modules/language/species/human.dm +++ b/code/modules/language/languages/species/human.dm @@ -1,6 +1,7 @@ /datum/language/human id = LANGUAGE_ID_HUMAN name = LANGUAGE_SOL_COMMON + translation_class = TRANSLATION_CLASS_DEFAULT_CORE_RACE desc = "A bastardized hybrid of many languages, including Chinese, English, French, and more; it is the common language of the Sol system." speech_verb = "says" whisper_verb = "whispers" @@ -22,6 +23,7 @@ /datum/language/slavic id = LANGUAGE_ID_HUMAN_SLAVIC name = LANGUAGE_SLAVIC + translation_class = TRANSLATION_CLASS_DEFAULT_CORE_RACE desc = "Was established in 2122 by the short-lived United Slavic Confederation on Earth." colour = "solcom" key = "G" diff --git a/code/modules/language/species/moth.dm b/code/modules/language/languages/species/moth.dm similarity index 95% rename from code/modules/language/species/moth.dm rename to code/modules/language/languages/species/moth.dm index 14902ab0191..e1b2173aae8 100644 --- a/code/modules/language/species/moth.dm +++ b/code/modules/language/languages/species/moth.dm @@ -1,6 +1,7 @@ /datum/language/species/moth id = LANGUAGE_ID_LUINIMMA name = LANGUAGE_LUINIMMA + translation_class = TRANSLATION_CLASS_DEFAULT_RARE_RACE desc = "The language of the Dnin-Nepids, Luinimma sounds like a quick, tonal series of chitters, chirps, and clicks to \ non-speakers." speech_verb = "chitters" @@ -35,7 +36,6 @@ "z" ) space_chance = 35 - machine_understands = FALSE partial_understanding = list() shorthand = "LUI" diff --git a/code/modules/language/species/naramadi.dm b/code/modules/language/languages/species/naramadi.dm similarity index 87% rename from code/modules/language/species/naramadi.dm rename to code/modules/language/languages/species/naramadi.dm index f299d04730a..803eb002262 100644 --- a/code/modules/language/species/naramadi.dm +++ b/code/modules/language/languages/species/naramadi.dm @@ -2,6 +2,7 @@ id = LANGUAGE_ID_NARAMADI // todo: papalus go replace the sergal text name = LANGUAGE_SAGARU + translation_class = TRANSLATION_CLASS_DEFAULT_RARE_RACE desc = "The dominant language of the Sergal homeworld, Vilous. It consists of aggressive low-pitched hissing and throaty growling." speech_verb = "snarls" colour = "sergal" diff --git a/code/modules/language/species/phoronoid.dm b/code/modules/language/languages/species/phoronoid.dm similarity index 87% rename from code/modules/language/species/phoronoid.dm rename to code/modules/language/languages/species/phoronoid.dm index 87db4dcfacc..e7e8bbc7810 100644 --- a/code/modules/language/species/phoronoid.dm +++ b/code/modules/language/languages/species/phoronoid.dm @@ -1,6 +1,7 @@ /datum/language/bones id = LANGUAGE_ID_PHORONOID name = LANGUAGE_BONES + translation_class = TRANSLATION_CLASS_DEFAULT_STANDARD_RACE desc = "The language of skeletons, characterised by clunks and clatters. Native language of Phoronoids." speech_verb = "rattles" ask_verb = "clinks" diff --git a/code/modules/language/species/shadekin.dm b/code/modules/language/languages/species/shadekin.dm similarity index 79% rename from code/modules/language/species/shadekin.dm rename to code/modules/language/languages/species/shadekin.dm index 869f91924ee..a19d49c909e 100644 --- a/code/modules/language/species/shadekin.dm +++ b/code/modules/language/languages/species/shadekin.dm @@ -7,5 +7,4 @@ exclaim_verb = "mars" colour = "changeling" key = "m" - machine_understands = FALSE - language_flags = WHITELISTED | HIVEMIND + language_flags = LANGUAGE_WHITELISTED | LANGUAGE_HIVEMIND diff --git a/code/modules/language/species/skrell.dm b/code/modules/language/languages/species/skrell.dm similarity index 89% rename from code/modules/language/species/skrell.dm rename to code/modules/language/languages/species/skrell.dm index ef0d5e756fa..5c8b65ae122 100644 --- a/code/modules/language/species/skrell.dm +++ b/code/modules/language/languages/species/skrell.dm @@ -1,6 +1,7 @@ /datum/language/skrell id = LANGUAGE_ID_SKRELL name = LANGUAGE_SKRELLIAN + translation_class = TRANSLATION_CLASS_DEFAULT_CORE_RACE desc = "A set of warbles and hums, the language itself a complex mesh of both melodic and rhythmic components, exceptionally capable of conveying intent and emotion of the speaker." speech_verb = "warbles" ask_verb = "warbles" @@ -16,6 +17,7 @@ /datum/language/skrellfar id = LANGUAGE_ID_SKRELL_ALT name = LANGUAGE_SKRELLIANFAR + translation_class = TRANSLATION_CLASS_DEFAULT_CORE_RACE desc = "The most common language among the Skrellian Far Kingdoms. Has an even higher than usual concentration of inaudible phonemes." speech_verb = "warbles" ask_verb = "warbles" @@ -24,7 +26,7 @@ colour = "skrellfar" key = "p" space_chance = 30 - language_flags = WHITELISTED + language_flags = LANGUAGE_WHITELISTED syllables = list("qr","qrr","xuq","qil","quum","xuqm","vol","xrim","zaoo","qu-uu","qix","qoo","zix", "...", "oo", "q", "nq", "x", "xq", "ll", "...", "...", "...") //should sound like there's holes in it shorthand = "SKRLFR" diff --git a/code/modules/language/species/tajaran.dm b/code/modules/language/languages/species/tajaran.dm similarity index 90% rename from code/modules/language/species/tajaran.dm rename to code/modules/language/languages/species/tajaran.dm index a5487245548..64b70f7496d 100644 --- a/code/modules/language/species/tajaran.dm +++ b/code/modules/language/languages/species/tajaran.dm @@ -1,6 +1,7 @@ /datum/language/tajaran id = LANGUAGE_ID_TAJARAN name = LANGUAGE_SIIK + translation_class = TRANSLATION_CLASS_DEFAULT_CORE_RACE desc = "The most prevalant language of Meralar, composed of expressive yowls and chirps. Native to the Tajaran." speech_verb = "mrowls" ask_verb = "mrowls" @@ -24,13 +25,14 @@ /datum/language/tajaranakhani id = LANGUAGE_ID_TAJARAN_ALT name = LANGUAGE_AKHANI + translation_class = TRANSLATION_CLASS_DEFAULT_CORE_RACE desc = "The language of the sea-faring Njarir'Akhan Tajaran. Borrowing some elements from Siik, the language is distinctly more structured." speech_verb = "chatters" ask_verb = "mrowls" exclaim_verb = "wails" colour = "akhani" key = "h" - language_flags = WHITELISTED + language_flags = LANGUAGE_WHITELISTED syllables = list("mrr","rr","marr","tar","ahk","ket","hal","kah","dra","nal","kra","vah","dar","hrar", "eh", "ara","ka","zar","mah","ner","zir","mur","hai","raz","ni","ri","nar","njar","jir","ri","ahn","kha","sir", "kar","yar","kzar","rha","hrar","err","fer","rir","rar","yarr","arr","ii'r","jar","kur","ran","rii","ii", @@ -40,12 +42,13 @@ /datum/language/tajsign id = LANGUAGE_ID_TAJARAN_SIGN name = LANGUAGE_ALAI + translation_class = TRANSLATION_CLASS_DEFAULT_CORE_RACE desc = "A standardized Tajaran sign language that was developed in Zarraya and gradually adopted by other nations, incorporating \ hand gestures and movements of the ears and tail." signlang_verb = list("gestures with their hands", "gestures with their ears and tail", "gestures with their ears, tail and hands") colour = "tajaran" key = "l" - language_flags = WHITELISTED | SIGNLANG | NO_STUTTER | NONVERBAL + language_flags = LANGUAGE_WHITELISTED | LANGUAGE_SIGNLANG | LANGUAGE_NO_STUTTER | LANGUAGE_NONVERBAL shorthand = "TAJSIGN" /datum/language/tajsign/can_speak_special(var/mob/speaker) // TODO: If ever we make external organs assist languages, convert this over to the new format diff --git a/code/modules/language/species/teshari.dm b/code/modules/language/languages/species/teshari.dm similarity index 90% rename from code/modules/language/species/teshari.dm rename to code/modules/language/languages/species/teshari.dm index e0895bd3f74..ca7e4d81bcc 100644 --- a/code/modules/language/species/teshari.dm +++ b/code/modules/language/languages/species/teshari.dm @@ -1,6 +1,7 @@ /datum/language/teshari id = LANGUAGE_ID_TESHARI name = LANGUAGE_SCHECHI + translation_class = TRANSLATION_CLASS_DEFAULT_STANDARD_RACE desc = "A trilling language spoken by the diminutive Teshari." speech_verb = "chirps" ask_verb = "chirrups" diff --git a/code/modules/language/species/unathi.dm b/code/modules/language/languages/species/unathi.dm similarity index 94% rename from code/modules/language/species/unathi.dm rename to code/modules/language/languages/species/unathi.dm index 43dee7bdd8d..b40bc5d6ed7 100644 --- a/code/modules/language/species/unathi.dm +++ b/code/modules/language/languages/species/unathi.dm @@ -1,6 +1,7 @@ /datum/language/unathi id = LANGUAGE_ID_UNATHI name = LANGUAGE_UNATHI + translation_class = TRANSLATION_CLASS_DEFAULT_CORE_RACE desc = "The common language of the Moghes Hegemony, composed of sibilant hisses and rattles. Spoken natively by Unathi." speech_verb = "hisses" ask_verb = "hisses" diff --git a/code/modules/language/species/vasilissan.dm b/code/modules/language/languages/species/vasilissan.dm similarity index 93% rename from code/modules/language/species/vasilissan.dm rename to code/modules/language/languages/species/vasilissan.dm index 58049ab06d1..750d58d0fbd 100644 --- a/code/modules/language/species/vasilissan.dm +++ b/code/modules/language/languages/species/vasilissan.dm @@ -1,6 +1,7 @@ /datum/language/bug id = LANGUAGE_ID_VASILISSAN name = LANGUAGE_VESPINAE + translation_class = TRANSLATION_CLASS_DEFAULT_STANDARD_RACE desc = "A jarring and clicky language developed and used by Vasilissans, it is designed for use with mouthparts and as a result has become a common language for various arthropod species." speech_verb = "clicks" ask_verb = "chitters" diff --git a/code/modules/language/species/vox.dm b/code/modules/language/languages/species/vox.dm similarity index 89% rename from code/modules/language/species/vox.dm rename to code/modules/language/languages/species/vox.dm index bb65d4503a6..38f59714ef9 100644 --- a/code/modules/language/species/vox.dm +++ b/code/modules/language/languages/species/vox.dm @@ -1,16 +1,16 @@ /datum/language/vox id = LANGUAGE_ID_VOX name = LANGUAGE_VOX + translation_class = NONE desc = "The common tongue of the various Vox ships making up the Shoal. It sounds like chaotic shrieking to everyone else." speech_verb = "shrieks" ask_verb = "creels" exclaim_verb = "SHRIEKS" colour = "vox" key = "5" - language_flags = WHITELISTED + language_flags = LANGUAGE_WHITELISTED syllables = list("ti","ti","ti","hi","hi","ki","ki","ki","ki","ya","ta","ha","ka","ya","chi","cha","kah", \ "SKRE","AHK","EHK","RAWK","KRA","AAA","EEE","KI","II","KRI","KA") - machine_understands = 0 shorthand = "VOX" /datum/language/vox/get_random_name() diff --git a/code/modules/language/species/vulpkanin.dm b/code/modules/language/languages/species/vulpkanin.dm similarity index 93% rename from code/modules/language/species/vulpkanin.dm rename to code/modules/language/languages/species/vulpkanin.dm index c6afdde3308..63f7880900e 100644 --- a/code/modules/language/species/vulpkanin.dm +++ b/code/modules/language/languages/species/vulpkanin.dm @@ -1,6 +1,7 @@ /datum/language/vulpkanin id = LANGUAGE_ID_VULPKANIN name = LANGUAGE_CANILUNZT + translation_class = TRANSLATION_CLASS_DEFAULT_STANDARD_RACE desc = "The guttural language spoken and utilized by the inhabitants of Vazzend system, composed of growls, barks, yaps, and heavy utilization of ears and tail movements. Vulpkanin speak this language with ease." speech_verb = "rrrfts" ask_verb = "rurs" diff --git a/code/modules/language/species/zaddat.dm b/code/modules/language/languages/species/zaddat.dm similarity index 87% rename from code/modules/language/species/zaddat.dm rename to code/modules/language/languages/species/zaddat.dm index c23ec6d44a2..162c346e74c 100644 --- a/code/modules/language/species/zaddat.dm +++ b/code/modules/language/languages/species/zaddat.dm @@ -1,6 +1,7 @@ /datum/language/zaddat id = LANGUAGE_ID_ZADDAT name = LANGUAGE_ZADDAT + translation_class = TRANSLATION_CLASS_DEFAULT_STANDARD_RACE desc = "A harsh buzzing language created by the Zaddat following their exodus from their homeworld." speech_verb = "buzzes" ask_verb = "buzzes" diff --git a/code/modules/language/species/zorren.dm b/code/modules/language/languages/species/zorren.dm similarity index 92% rename from code/modules/language/species/zorren.dm rename to code/modules/language/languages/species/zorren.dm index e70a17379c9..6115b31d279 100644 --- a/code/modules/language/species/zorren.dm +++ b/code/modules/language/languages/species/zorren.dm @@ -1,6 +1,7 @@ /datum/language/terminus id = LANGUAGE_ID_TERMINUS name = LANGUAGE_TERMINUS + translation_class = TRANSLATION_CLASS_DEFAULT_STANDARD_RACE desc = "A group of languages spoken by the Zorren with a certain degree of mutual intelligibility under each other. Evidence shows it was a single lingua franca which has diverged into many sub languages due to isolation." speech_verb = "mentions" exclaim_verb = "insinuates" diff --git a/code/modules/language/translation.dm b/code/modules/language/translation.dm new file mode 100644 index 00000000000..7ab756e454d --- /dev/null +++ b/code/modules/language/translation.dm @@ -0,0 +1,287 @@ +/** + * handles language translation + * + * individual context defines are scattered throughout the code + * use find all implementations to find them! + */ +/datum/translation_context + +/** + * returns if we can translate something + * partial/wrong translations are still translations + * + * @params + * - L - the language + * - speaker - guy talking, optional + * - require_perfect - must be perfect translation; used by tape recorders + */ +/datum/translation_context/proc/can_translate(datum/language/L, atom/movable/speaker, require_perfect) + CRASH("not implemented") + +/** + * translates a message + * **warning**: translation doesn't always handle mid-message spans/formatting + * we split words using the " " space character. + * + * should have **zero** side effects + * + * @return the translated message + */ +/datum/translation_context/proc/translate(datum/language/L, atom/movable/speaker, msg) + PROTECTED_PROC(TRUE) // please use attempt_translation. + CRASH("not implemented") + +/** + * attempts to translate a message + * has possible side effects like learning the language gradually + * + * @params + * - L - the language + * - speaker - guy talking, optional + * - msg - the message to translate + * - require_perfect - must be perfect translate; used by tape recorders + */ +/datum/translation_context/proc/attempt_translation(datum/language/L, atom/movable/speaker, msg, require_perfect) + CRASH("not implemented") + +/** + * todo: for future implementation + * directly modifies + */ + +/** + * directly translates languages; it either succeeds or fails + */ +/datum/translation_context/simple + /// classes we can translate directly + var/translation_class = NONE + /// classes we cannot translate; overrides translation_class + var/translation_class_forbid = NONE + /// lazy assoclist of ids we can translate (secondary lookup); overrides all + var/list/translated_ids + +/datum/translation_context/simple/New() + ..() + if(has_typelist(NAMEOF(src, translated_ids))) + translated_ids = get_typelist(NAMEOF(src, translated_ids)) + else + translated_ids = typelist(src, make_associative_inplace_keep_values(translated_ids)) + +/datum/translation_context/simple/can_translate(datum/language/L, atom/movable/speaker, require_perfect) + if(translated_ids?[L.id]) + return TRUE + return (translation_class & L.translation_class) && !(translation_class_forbid & L.translation_class) + +/datum/translation_context/simple/attempt_translation(datum/language/L, atom/movable/speaker, msg, require_perfect) + if(!can_translate(L, speaker)) + return null + return translate(L, speaker, msg) + +/datum/translation_context/simple/translate(datum/language/L, atom/movable/speaker, msg) + return msg + +/** + * variable language translator + * + * todo: efficiency "sharing" for similar languages + * + * it can either fully translate, + * fail to translate, + * or partially translate + */ +/datum/translation_context/variable + /// classes we can translate directly + var/translation_class = NONE + /// classes we cannot translate; overrides translation_class + var/translation_class_forbid = NONE + /// lazy assoclist of ids we can translate (secondary lookup); overrides all; also serves as efficiency; + /// set to 1 or don't set anything for normal perfect behaviour + var/list/translated_ids + /// default efficiency + var/translate_factor = 0 + /// are translated ids cloned? if not, we shouldn't modify directly due to typelists + var/translated_list_detached = FALSE + +/datum/translation_context/variable/New() + ..() + if(has_typelist(NAMEOF(src, translated_ids))) + translated_ids = get_typelist(NAMEOF(src, translated_ids)) + else + for(var/id in translated_ids) + if(!isnull(translated_ids[id])) + continue + translated_ids[id] = translate_factor + translated_ids = typelist(src, make_associative_inplace_keep_values(translated_ids)) + +/datum/translation_context/variable/Destroy() + translated_ids = null + return ..() + +/datum/translation_context/variable/proc/detach_translated_ids() + if(translated_list_detached) + return + translated_ids = translated_ids.Copy() + translated_list_detached = TRUE + +/datum/translation_context/variable/attempt_translation(datum/language/L, atom/movable/speaker, msg, require_perfect) + if((translation_class & L.translation_class) && !(translation_class_forbid & L.translation_class)) + return translate(L, speaker, msg) + var/effective = translated_ids?[L.id] + if(!effective) + return L.scramble(msg) + if(effective >= TRANSLATION_CONTEXT_PERFECT_THRESHOLD) + return translate(L, speaker, msg) + if(require_perfect) + return null + return imperfect_translation(L, speaker, msg, effective) + +/datum/translation_context/variable/translate(datum/language/L, atom/movable/speaker, msg) + return msg // perfect translations + +/datum/translation_context/variable/can_translate(datum/language/L, atom/movable/speaker, require_perfect) + if((translation_class & L.translation_class) && !(translation_class_forbid & L.translation_class)) + return TRUE + return require_perfect? (translated_ids?[L.id] >= TRANSLATION_CONTEXT_PERFECT_THRESHOLD) : (!!translated_ids?[L.id]) + +/datum/translation_context/variable/proc/imperfect_translation(datum/language/L, atom/movable/speaker, msg, efficiency) + //! all aboard the marakov train + var/effective_efficiency = clamp(efficiency, 0, 1) + var/current_efficiency = effective_efficiency * 100 + var/translating = prob(current_efficiency) + var/list/words = splittext(msg, " ") + for(var/i in 1 to length(words)) + var/str = words[i] + if(!str) + continue // genuinely don't care + if(translating) + // yay! + // don't touch the string + // see if we should stop translating + if(prob(current_efficiency)) + // success; drop efficiency + // current_efficiency *= effective_efficiency + //! or don't because this is brutal enough just see how rimworld works lmao + else + // oops! failed to keep going; penalize the next one too + current_efficiency = (effective_efficiency ** 2) * 100 + translating = FALSE + else + // nay! + // scrmable string + words[i] = L.scramble_word(str) + // see if we should translate again + if(prob(current_efficiency)) + // yes! + current_efficiency = effective_efficiency * 100 + translating = TRUE + return jointext(words, " ") + +/** + * copy all our knowledge to another if it can receive it + */ +/datum/translation_context/variable/proc/copy_knowledge(datum/translation_context/variable/other) + other.receive_knowledge(src) + +/** + * copy all our knowledge to another if it can receive it + */ +/datum/translation_context/variable/proc/receive_knowledge(datum/translation_context/variable/giver) + // typelists: if the lists are the same, don't bother + if(giver.translated_ids == translated_ids) + return + // split list + detach_translated_ids() + // copy + for(var/id in giver.translated_ids) + var/efficiency = giver.translated_ids[id] + if(efficiency >= translated_ids[id]) + continue + translated_ids[id] = efficiency + +/** + * variable language translator + * + * it can either fully translate, + * fail to translate, + * or partially translate. + * + * it will learn over time supported languages. + */ +/datum/translation_context/variable/learning + /// classes we automatically try to learn + var/translation_class_learn = NONE + /// classes we cannot automatically try to learn + var/translation_class_learn_forbid = NONE + /// lazy assoclist of ids we can learn (secondary lookup); also serves as efficiency override + var/list/learnable_ids + /// learning factor; 1, faster = up, slower = down; 0 to inf. + var/learn_factor = 1 + /// callback to call with (src, datum/language/L, old_efficiency) on successful training + var/datum/callback/on_train + +/datum/translation_context/variable/learning/New() + ..() + if(has_typelist(NAMEOF(src, learnable_ids))) + learnable_ids = get_typelist(NAMEOF(src, learnable_ids)) + else + for(var/id in learnable_ids) + if(!isnull(learnable_ids[id])) + continue + learnable_ids[id] = learn_factor + learnable_ids = typelist(src, make_associative_inplace_keep_values(learnable_ids)) + +/datum/translation_context/variable/learning/Destroy() + learnable_ids = null + on_train = null + return ..() + +/datum/translation_context/variable/learning/can_translate(datum/language/L, atom/movable/speaker, require_perfect) + if(require_perfect) + return ..() + if((translation_class_learn & L.translation_class) && !(translation_class_learn_forbid & L.translation_class)) + return TRUE + if(learnable_ids?[L.id]) + return TRUE + return ..() + +/datum/translation_context/variable/learning/attempt_translation(datum/language/L, atom/movable/speaker, msg) + . = ..() + var/efficiency = learnable_ids?[L.id] + if(efficiency) + train(L, speaker, msg, efficiency) + else if((translation_class_learn & L.translation_class) && !(translation_class_learn_forbid & L.translation_class)) + train(L, speaker, msg, learn_factor) + +/datum/translation_context/variable/learning/proc/train(datum/language/L, atom/movable/speaker, msg) + // split list + detach_translated_ids() + var/old_efficiency = ((L.translation_class & translation_class) && !(L.translation_class & translation_class_forbid) && 1) || translated_ids[L.id] || 0 + if(old_efficiency >= TRANSLATION_CONTEXT_PERFECT_THRESHOLD) + // good enough don't bother + return + var/list/words = splittext(msg, GLOB.multi_space_splitter) + // short sentences suck, but long sentences only get you so far + // if anyone codedives and abuses this, well, frankly: + // don't make me bring out the actual math and lag the server to make + // literally everyone miserable. just don't. roleplay it out. + var/amt = length(words) + var/gain = log((amt * 0.1) + 1) * 0.01 + var/new_efficiency = clamp(old_efficiency + gain, 0, 1) + if(old_efficiency >= new_efficiency) + return // how + translated_ids[L.id] = new_efficiency + on_train?.Invoke(src, L, old_efficiency) + +/** + * translates everything + */ +/datum/translation_context/omni + +/datum/translation_context/omni/can_translate(datum/language/L, atom/movable/speaker, require_perfect) + return TRUE + +/datum/translation_context/omni/translate(datum/language/L, atom/movable/speaker, msg) + return msg + +/datum/translation_context/omni/attempt_translation(datum/language/L, atom/movable/speaker, msg) + return translate(L, speaker, msg) diff --git a/code/modules/mob/action_feedback.dm b/code/modules/mob/action_feedback.dm index 180f232f69b..e96bcb76462 100644 --- a/code/modules/mob/action_feedback.dm +++ b/code/modules/mob/action_feedback.dm @@ -21,7 +21,7 @@ */ /mob/proc/bubble_action_feedback(msg, atom/target) // for now, just wrapper for chat action feedback - action_feedback(SPAN_NOTICE(msg)) + action_feedback("[target]: [SPAN_NOTICE(msg)]") /** * gives feedback to us and someone else diff --git a/code/modules/mob/animations.dm b/code/modules/mob/animations.dm index be847de994c..f6615ae7c27 100644 --- a/code/modules/mob/animations.dm +++ b/code/modules/mob/animations.dm @@ -272,7 +272,7 @@ note dizziness decrements automatically in the mob's Life() proc. I.dir = dir // Show the overlay to the clients - flick_overlay(I, viewing, 5, TRUE) // 5 ticks/half a second + flick_overlay(I, viewing, 5) // 5 ticks/half a second // Set the direction of the icon animation. var/direction = get_dir(src, A) diff --git a/code/modules/mob/hear_say.dm b/code/modules/mob/hear_say.dm index fcfcff6c2ad..335e87d2150 100644 --- a/code/modules/mob/hear_say.dm +++ b/code/modules/mob/hear_say.dm @@ -25,14 +25,15 @@ return //non-verbal languages are garbled if you can't see the speaker. Yes, this includes if they are inside a closet. - if (language && (language.language_flags & NONVERBAL)) + if (language && (language.language_flags & LANGUAGE_NONVERBAL)) if (!speaker || (src.sdisabilities & SDISABILITY_NERVOUS || src.blinded) || !(speaker in view(src))) message = stars(message) - if(!(language && (language.language_flags & INNATE))) // skip understanding checks for INNATE languages + + if(!(language && (language.language_flags & LANGUAGE_INNATE))) // skip understanding checks for LANGUAGE_INNATE languages if(!say_understands(speaker,language)) if(language) - message = language.scramble(message, languages) + message = language_scramble(language, message) else message = stars(message) @@ -57,7 +58,7 @@ message = "[message]" if(is_deaf()) - if(!language || !(language.language_flags & INNATE)) // INNATE is the flag for audible-emote-language, so we don't want to show an "x talks but you cannot hear them" message if it's set + if(!language || !(language.language_flags & LANGUAGE_INNATE)) // LANGUAGE_INNATE is the flag for audible-emote-language, so we don't want to show an "x talks but you cannot hear them" message if it's set if(speaker == src) to_chat(src, "You cannot hear yourself speak!") else @@ -66,7 +67,7 @@ var/message_to_send = null if(language) //Hivemind languages already say their names. Also, no indicator if you don't know the language. - if(client && !(language.language_flags & HIVEMIND) && say_understands(speaker, language) && client.is_preference_enabled(/datum/client_preference/language_indicator)) + if(client && !(language.language_flags & LANGUAGE_HIVEMIND) && say_understands(speaker, language) && language.shorthand && client.is_preference_enabled(/datum/client_preference/language_indicator)) verb += " ([language.shorthand])" message_to_send = "[speaker_name][alt_name] [track][language.format_message(message, verb)]" else @@ -87,6 +88,17 @@ if(has_AI()) // Won't happen if no ai_holder exists or there's a player inside w/o autopilot active. ai_holder.on_hear_say(speaker, message) +/mob/proc/language_scramble(datum/language/L, str) + return L.scramble(str, languages) + +//! "silicons, why are you putting this here?" +//? Because saycode needs a full rewrite so I'm shoving stuff in here until we do +//? as if I don't, it's just harder to find later. +/mob/living/silicon/language_scramble(datum/language/L, str) + // todo: this still does the "unknown language" highlighting potentially, oops + var/translated = translation_context.attempt_translation(L, msg = str) + return isnull(translated)? L.scramble(str, languages) : translated + /mob/proc/on_hear_say(var/message) to_chat(src, message) if(teleop) @@ -156,14 +168,14 @@ var/track = null //non-verbal languages are garbled if you can't see the speaker. Yes, this includes if they are inside a closet. - if (language && (language.language_flags & NONVERBAL)) + if (language && (language.language_flags & LANGUAGE_NONVERBAL)) if (!speaker || (src.sdisabilities & SDISABILITY_NERVOUS || src.blinded) || !(speaker in view(src))) message = stars(message) - if(!(language && (language.language_flags & INNATE))) // skip understanding checks for INNATE languages + if(!(language && (language.language_flags & LANGUAGE_INNATE))) // skip understanding checks for LANGUAGE_INNATE languages if(!say_understands(speaker,language)) if(language) - message = language.scramble(message, languages) + message = language_scramble(language, message) else message = stars(message) @@ -242,7 +254,7 @@ var/formatted if(language) - if(client && !(language.language_flags & HIVEMIND) && say_understands(speaker, language) && client.is_preference_enabled(/datum/client_preference/language_indicator)) + if(client && !(language.language_flags & LANGUAGE_HIVEMIND) && say_understands(speaker, language) && language.shorthand && client.is_preference_enabled(/datum/client_preference/language_indicator)) verb += " ([language.shorthand])" formatted = "[language.format_message_radio(message, verb)][part_c]" else diff --git a/code/modules/mob/living/autohiss.dm b/code/modules/mob/living/autohiss.dm index 2bbfdd0ad0a..c018ebd7715 100644 --- a/code/modules/mob/living/autohiss.dm +++ b/code/modules/mob/living/autohiss.dm @@ -9,7 +9,7 @@ if(!maps.basic) return message - if(L.language_flags & NO_STUTTER) // Currently prevents EAL, Sign language, and emotes from autohissing + if(L.language_flags & LANGUAGE_NO_STUTTER) // Currently prevents EAL, Sign language, and emotes from autohissing return message if(maps.exempt && (L.name in maps.exempt)) return message diff --git a/code/modules/mob/living/carbon/brain/say.dm b/code/modules/mob/living/carbon/brain/say.dm index b6ad7e1dd29..95185c01bb8 100644 --- a/code/modules/mob/living/carbon/brain/say.dm +++ b/code/modules/mob/living/carbon/brain/say.dm @@ -29,7 +29,7 @@ else message = Gibberish(message, (emp_damage*6))//scrambles the message, gets worse when emp_damage is higher - if(speaking && speaking.language_flags & HIVEMIND) + if(speaking && speaking.language_flags & LANGUAGE_HIVEMIND) speaking.broadcast(src,trim(message)) return diff --git a/code/modules/mob/living/carbon/carbon.dm b/code/modules/mob/living/carbon/carbon.dm index 6691432c035..4984e72bdc4 100644 --- a/code/modules/mob/living/carbon/carbon.dm +++ b/code/modules/mob/living/carbon/carbon.dm @@ -143,8 +143,9 @@ var/show_ssd var/mob/living/carbon/human/H = src var/datum/gender/T = GLOB.gender_datums[H.get_visible_gender()] // make sure to cast to human before using get_gender() or get_visible_gender()! - if(istype(H)) show_ssd = H.species.show_ssd - if(show_ssd && !client && !teleop) + if(istype(H)) + show_ssd = H.species.show_ssd + if(show_ssd && !client && !teleop && (!istype(H) || !H.override_ssd)) M.visible_message(SPAN_NOTICE("[M] shakes [src] trying to wake [T.him] up!"), SPAN_NOTICE("You shake [src], but [T.he] [T.does] not respond... Maybe [T.he] [T.has] S.S.D?")) else if(lying || src.sleeping) diff --git a/code/modules/mob/living/carbon/human/human_defines.dm b/code/modules/mob/living/carbon/human/human_defines.dm index 8eeae66c016..9dd65ca1e0c 100644 --- a/code/modules/mob/living/carbon/human/human_defines.dm +++ b/code/modules/mob/living/carbon/human/human_defines.dm @@ -259,3 +259,6 @@ * Only used for camera monitors currently. */ var/obj/machinery/machine_visual + + // ignore ssd status + var/override_ssd = FALSE diff --git a/code/modules/mob/living/carbon/human/human_powers.dm b/code/modules/mob/living/carbon/human/human_powers.dm index 9637379e974..22071eeb26e 100644 --- a/code/modules/mob/living/carbon/human/human_powers.dm +++ b/code/modules/mob/living/carbon/human/human_powers.dm @@ -189,13 +189,13 @@ for(var/mob/living/carbon/alien/diona/D in src) nymphs++ D.forceMove(T) - transfer_languages(src, D, WHITELISTED|RESTRICTED) + transfer_languages(src, D, LANGUAGE_WHITELISTED|LANGUAGE_RESTRICTED) D.setDir(pick(NORTH, SOUTH, EAST, WEST)) if(nymphs < number_of_resulting_nymphs) for(var/i in nymphs to (number_of_resulting_nymphs - 1)) var/mob/M = new /mob/living/carbon/alien/diona(T) - transfer_languages(src, M, WHITELISTED|RESTRICTED) + transfer_languages(src, M, LANGUAGE_WHITELISTED|LANGUAGE_RESTRICTED) M.setDir(pick(NORTH, SOUTH, EAST, WEST)) @@ -308,10 +308,10 @@ if(!get_turf(src)) to_chat(src, SPAN_WARNING("Not from here you can't.")) return - if(TIMER_COOLDOWN_CHECK(src, COOLDOWN_SONAR_PULSE)) + if(TIMER_COOLDOWN_CHECK(src, CD_INDEX_SONAR_PULSE)) to_chat(src, SPAN_WARNING("You need to wait some more to do that!")) return - TIMER_COOLDOWN_START(src, COOLDOWN_SONAR_PULSE, 2 SECONDS) + TIMER_COOLDOWN_START(src, CD_INDEX_SONAR_PULSE, 2 SECONDS) visible_message( SPAN_WARNING("[src] emits a quiet click."), diff --git a/code/modules/mob/living/carbon/human/life.dm b/code/modules/mob/living/carbon/human/life.dm index 33b70f6c914..85f7b486424 100644 --- a/code/modules/mob/living/carbon/human/life.dm +++ b/code/modules/mob/living/carbon/human/life.dm @@ -1040,7 +1040,7 @@ if(status_flags & GODMODE) return 0 //SSD check, if a logged player is awake put them back to sleep! - if(species.get_ssd(src) && !client && !teleop && !temporary_form) + if(species.get_ssd(src) && !client && !teleop && !override_ssd && !temporary_form) Sleeping(2) if(stat == DEAD) //DEAD. BROWN BREAD. SWIMMING WITH THE SPESS CARP blinded = 1 diff --git a/code/modules/mob/living/say.dm b/code/modules/mob/living/say.dm index ce5d7e3c199..082c3324786 100644 --- a/code/modules/mob/living/say.dm +++ b/code/modules/mob/living/say.dm @@ -209,8 +209,8 @@ var/list/channel_to_radio_key = new if(speaking && speaking == SScharacters.resolve_language_name("Noise")) message = copytext_char(message,2) - //HIVEMIND languages always send to all people with that language - if(speaking && (speaking.language_flags & HIVEMIND)) + //LANGUAGE_HIVEMIND languages always send to all people with that language + if(speaking && (speaking.language_flags & LANGUAGE_HIVEMIND)) speaking.broadcast(src,trim(message)) return 1 @@ -219,7 +219,7 @@ var/list/channel_to_radio_key = new return //Self explanatory. - if(is_muzzled() && !(speaking && (speaking.language_flags & SIGNLANG))) + if(is_muzzled() && !(speaking && (speaking.language_flags & LANGUAGE_SIGNLANG))) to_chat(src, "You're muzzled and cannot speak!") return @@ -250,7 +250,7 @@ var/list/channel_to_radio_key = new w_not_heard = "[speaking.speech_verb] something [w_adverb]" //For speech disorders (hulk, slurring, stuttering) - if(!(speaking && (speaking.language_flags & NO_STUTTER || speaking.language_flags & SIGNLANG))) + if(!(speaking && (speaking.language_flags & LANGUAGE_NO_STUTTER || speaking.language_flags & LANGUAGE_SIGNLANG))) var/list/message_data = list(message, verb, whispering) if(handle_speech_problems(message_data)) message = message_data[1] @@ -289,7 +289,7 @@ var/list/channel_to_radio_key = new if(speaking) message_range = speaking.get_talkinto_msg_range(message) var/msg - if(!speaking || !(speaking.language_flags & NO_TALK_MSG)) + if(!speaking || !(speaking.language_flags & LANGUAGE_NO_TALK_MSG)) msg = "\The [src] talks into \the [used_radios[1]]" for(var/mob/living/M in hearers(7, src)) if((M != src) && msg) @@ -318,11 +318,11 @@ var/list/channel_to_radio_key = new //Handle nonverbal and sign languages here if (speaking) - if (speaking.language_flags & SIGNLANG) + if (speaking.language_flags & LANGUAGE_SIGNLANG) log_say("(SIGN) [message]", src) return say_signlang(message, pick(speaking.signlang_verb), speaking) - if (speaking.language_flags & NONVERBAL) + if (speaking.language_flags & LANGUAGE_NONVERBAL) if (prob(30)) src.custom_emote(1, "[pick(speaking.signlang_verb)].") diff --git a/code/modules/mob/living/silicon/ai/ai.dm b/code/modules/mob/living/silicon/ai/ai.dm index a13f2e753cf..715c6fe7e0e 100644 --- a/code/modules/mob/living/silicon/ai/ai.dm +++ b/code/modules/mob/living/silicon/ai/ai.dm @@ -59,6 +59,7 @@ var/list/ai_verbs_default = list( can_be_antagged = TRUE status_flags = CANSTUN|CANPARALYSE|CANPUSH catalogue_data = list(/datum/category_item/catalogue/fauna/silicon/ai) + translation_context = /datum/translation_context/variable/learning/silicons // ai gets the gamer context by default /// The network we have access to. var/list/network = list(NETWORK_DEFAULT) @@ -67,7 +68,7 @@ var/list/ai_verbs_default = list( var/viewalerts = FALSE /// Default is assigned when AI is created. var/icon/holo_icon - var/list/connected_robots = list() + var/list/mob/living/silicon/robot/connected_robots = list() var/obj/item/pda/ai/aiPDA = null var/obj/item/communicator/aiCommunicator = null var/obj/item/multitool/aiMulti = null @@ -180,28 +181,6 @@ var/list/ai_verbs_default = list( //Languages add_language("Robot Talk", 1) - add_language(LANGUAGE_GALCOM, 1) - add_language(LANGUAGE_SOL_COMMON, 1) - add_language(LANGUAGE_UNATHI, 1) - add_language(LANGUAGE_SIIK, 1) - add_language(LANGUAGE_AKHANI, 1) - add_language(LANGUAGE_SKRELLIAN, 1) - add_language(LANGUAGE_SKRELLIANFAR, 0) - add_language(LANGUAGE_TRADEBAND, 1) - add_language(LANGUAGE_GUTTER, 1) - add_language(LANGUAGE_EAL, 1) - add_language(LANGUAGE_SCHECHI, 1) - add_language(LANGUAGE_SIGN, 1) - add_language(LANGUAGE_ROOTLOCAL, 1) - add_language(LANGUAGE_TERMINUS, 1) - add_language(LANGUAGE_ZADDAT, 1) - add_language(LANGUAGE_BIRDSONG, 1) - add_language(LANGUAGE_SAGARU, 1) - add_language(LANGUAGE_CANILUNZT, 1) - add_language(LANGUAGE_DAEMON, 1) - add_language(LANGUAGE_ENOCHIAN, 1) - add_language(LANGUAGE_SQUEAKISH, 1) - add_language(LANGUAGE_AKULA, 1) if(!safety)//Only used by AIize() to successfully spawn an AI. if (!B)//If there is no player/brain inside. diff --git a/code/modules/mob/living/silicon/ai/translation.dm b/code/modules/mob/living/silicon/ai/translation.dm new file mode 100644 index 00000000000..7d9db17fe59 --- /dev/null +++ b/code/modules/mob/living/silicon/ai/translation.dm @@ -0,0 +1,25 @@ +/mob/living/silicon/ai/sync_translation_context(datum/language/L) + if(!length(connected_robots)) + return + if(!istype(translation_context, /datum/translation_context/variable/learning/silicons)) + return + var/datum/translation_context/variable/learning/silicons/ours = translation_context + if(!L) + // full pull + push + for(var/mob/living/silicon/robot/R as anything in connected_robots) + if(!istype(R.translation_context, /datum/translation_context/variable/learning/silicons)) + return + var/datum/translation_context/variable/learning/silicons/theirs = R.translation_context + theirs.copy_knowledge(ours) + ours.copy_knowledge(theirs) + return + // partial sync - give, not receive + if(!ours.translated_list_detached) + return // full sync didn't happen yet + for(var/mob/living/silicon/robot/R as anything in connected_robots) + if(!istype(R.translation_context, /datum/translation_context/variable/learning/silicons)) + return + var/datum/translation_context/variable/learning/silicons/theirs = R.translation_context + if(!theirs.translated_list_detached) + return // full sync didn't happen yet + theirs.translated_ids[L.id] = max(ours.translated_ids[L.id], theirs.translated_ids[L.id]) diff --git a/code/modules/mob/living/silicon/pai/pai.dm b/code/modules/mob/living/silicon/pai/pai.dm index b911dc9a4c2..111e8027616 100644 --- a/code/modules/mob/living/silicon/pai/pai.dm +++ b/code/modules/mob/living/silicon/pai/pai.dm @@ -111,14 +111,6 @@ card.radio = new /obj/item/radio(src.card) radio = card.radio - //Default languages without universal translator software - add_language(LANGUAGE_SOL_COMMON, 1) - add_language(LANGUAGE_TRADEBAND, 1) - add_language(LANGUAGE_GUTTER, 1) - add_language(LANGUAGE_EAL, 1) - add_language(LANGUAGE_TERMINUS, 1) - add_language(LANGUAGE_SIGN, 0) - verbs += /mob/living/silicon/pai/proc/choose_chassis verbs += /mob/living/silicon/pai/proc/choose_verbs diff --git a/code/modules/mob/living/silicon/pai/software_modules.dm b/code/modules/mob/living/silicon/pai/software_modules.dm index 1ebe82d13ea..4d4eecff74e 100644 --- a/code/modules/mob/living/silicon/pai/software_modules.dm +++ b/code/modules/mob/living/silicon/pai/software_modules.dm @@ -471,31 +471,34 @@ /datum/pai_software/med_hud/is_active(mob/living/silicon/pai/user) return user.medHUD -/datum/pai_software/translator - name = "Universal Translator" - ram_cost = 35 - id = "translator" +// todo: translation context -/datum/pai_software/translator/toggle(mob/living/silicon/pai/user) - // Sol Common, Tradeband, Terminus and Gutter are added with New() and are therefore the current default, always active languages - user.translator_on = !user.translator_on - if(user.translator_on) - user.add_language(LANGUAGE_UNATHI) - user.add_language(LANGUAGE_SIIK) - user.add_language(LANGUAGE_AKHANI) - user.add_language(LANGUAGE_SKRELLIAN) - user.add_language(LANGUAGE_ZADDAT) - user.add_language(LANGUAGE_SCHECHI) - else - user.remove_language(LANGUAGE_UNATHI) - user.remove_language(LANGUAGE_SIIK) - user.remove_language(LANGUAGE_AKHANI) - user.remove_language(LANGUAGE_SKRELLIAN) - user.remove_language(LANGUAGE_ZADDAT) - user.remove_language(LANGUAGE_SCHECHI) +// /datum/pai_software/translator +// name = "Universal Translator" +// ram_cost = 35 +// id = "translator" -/datum/pai_software/translator/is_active(mob/living/silicon/pai/user) - return user.translator_on +// /datum/pai_software/translator/toggle(mob/living/silicon/pai/user) +// // Sol Common, Tradeband, Terminus and Gutter are added with New() and are therefore the current default, always active languages +// // todo: translation contexts for pais +// user.translator_on = !user.translator_on +// if(user.translator_on) +// user.add_language(LANGUAGE_UNATHI) +// user.add_language(LANGUAGE_SIIK) +// user.add_language(LANGUAGE_AKHANI) +// user.add_language(LANGUAGE_SKRELLIAN) +// user.add_language(LANGUAGE_ZADDAT) +// user.add_language(LANGUAGE_SCHECHI) +// else +// user.remove_language(LANGUAGE_UNATHI) +// user.remove_language(LANGUAGE_SIIK) +// user.remove_language(LANGUAGE_AKHANI) +// user.remove_language(LANGUAGE_SKRELLIAN) +// user.remove_language(LANGUAGE_ZADDAT) +// user.remove_language(LANGUAGE_SCHECHI) + +// /datum/pai_software/translator/is_active(mob/living/silicon/pai/user) +// return user.translator_on /datum/pai_software/signaller name = "Remote Signaller" diff --git a/code/modules/mob/living/silicon/robot/robot.dm b/code/modules/mob/living/silicon/robot/robot.dm index edae7160744..dd064996d6a 100644 --- a/code/modules/mob/living/silicon/robot/robot.dm +++ b/code/modules/mob/living/silicon/robot/robot.dm @@ -170,8 +170,12 @@ spark_system.attach(src) add_language("Robot Talk", 1) - add_language(LANGUAGE_GALCOM, 1) - add_language(LANGUAGE_EAL, 1) + // todo: translation contexts on language holder? + // this is messy + for(var/datum/language/L as anything in SScharacters.all_languages()) + if(!(L.translation_class & TRANSLATION_CLASSES_CYBORG_SPEAKS)) + continue + add_language(L, TRUE) wires = new(src) @@ -246,8 +250,8 @@ if(!cell) return 0 - if(!TIMER_COOLDOWN_CHECK(src, COOLDOWN_POWER_DRAIN_WARNING)) - TIMER_COOLDOWN_START(src, COOLDOWN_POWER_DRAIN_WARNING, 2 SECONDS) + if(!TIMER_COOLDOWN_CHECK(src, CD_INDEX_POWER_DRAIN_WARNING)) + TIMER_COOLDOWN_START(src, CD_INDEX_POWER_DRAIN_WARNING, 2 SECONDS) to_chat(src, SPAN_DANGER("Warning: Abnormal usage on power channel [rand(11, 29)] detected!")) return cell.drain_energy(actor, amount, flags) diff --git a/code/modules/mob/living/silicon/robot/translation.dm b/code/modules/mob/living/silicon/robot/translation.dm new file mode 100644 index 00000000000..090ed37c12b --- /dev/null +++ b/code/modules/mob/living/silicon/robot/translation.dm @@ -0,0 +1,21 @@ +/mob/living/silicon/robot/sync_translation_context(datum/language/L) + if(!connected_ai) + return + if(!istype(connected_ai.translation_context, /datum/translation_context/variable/learning/silicons)) + return + if(!istype(translation_context, /datum/translation_context/variable/learning/silicons)) + return + var/datum/translation_context/variable/learning/silicons/ours = translation_context + var/datum/translation_context/variable/learning/silicons/theirs = connected_ai.translation_context + if(!L) + // expensive full sync + theirs.copy_knowledge(ours) + ours.copy_knowledge(theirs) + to_chat(src, SPAN_NOTICE("Adaptive translator synced to master AI.")) + return + // partial sync - give, not receive + if(!theirs.translated_list_detached || !ours.translated_list_detached) + return // full sync didn't happen yet + if(!theirs.translated_ids[L.id]) + to_chat(connected_ai, SPAN_NOTICE("New language received from connected units: [L.name]. Adaptive translation started.")) + theirs.translated_ids[L.id] = max(ours.translated_ids[L.id], theirs.translated_ids[L.id]) diff --git a/code/modules/mob/living/silicon/say.dm b/code/modules/mob/living/silicon/say.dm index 74354bd16a5..62c250132df 100644 --- a/code/modules/mob/living/silicon/say.dm +++ b/code/modules/mob/living/silicon/say.dm @@ -56,6 +56,8 @@ return 1 if (istype(other, /mob/living/carbon/brain)) return 1 + if(translation_context.can_translate(speaking, require_perfect = TRUE)) + return TRUE return ..() //For holopads only. Usable by AI. diff --git a/code/modules/mob/living/silicon/silicon.dm b/code/modules/mob/living/silicon/silicon.dm index 4453fba0b14..79633f587c3 100644 --- a/code/modules/mob/living/silicon/silicon.dm +++ b/code/modules/mob/living/silicon/silicon.dm @@ -48,11 +48,15 @@ var/hudmode = null + /// our translation context + var/datum/translation_context/translation_context = /datum/translation_context/simple/silicons + /mob/living/silicon/Initialize(mapload) silicon_mob_list |= src . = ..() add_language(LANGUAGE_GALCOM) set_default_language(SScharacters.resolve_language_name(LANGUAGE_GALCOM)) + create_translation_context() init_id() init_subsystems() @@ -223,7 +227,7 @@ dat += "Current default language: [default_language] - reset

" for(var/datum/language/L in languages) - if(!(L.language_flags & NONGLOBAL)) + if(!(L.language_flags & LANGUAGE_NONGLOBAL)) var/default_str if(L == default_language) default_str = " - default - reset" @@ -399,3 +403,12 @@ /mob/living/silicon/get_bullet_impact_effect_type(var/def_zone) return BULLET_IMPACT_METAL + +//! Topic +/mob/living/silicon/Topic(href, href_list) + . = ..() + if(.) + return + if(href_list["ooc_notes"]) + src.Examine_OOC() + return TRUE diff --git a/code/modules/mob/living/silicon/silicon_vr.dm b/code/modules/mob/living/silicon/silicon_vr.dm deleted file mode 100644 index 624406bd8bd..00000000000 --- a/code/modules/mob/living/silicon/silicon_vr.dm +++ /dev/null @@ -1,5 +0,0 @@ -/mob/living/silicon/Topic(href, href_list) //For Robots and pAI's. And possibly AI's too. - if(href_list["ooc_notes"]) - src.Examine_OOC() - return 1 - return ..() diff --git a/code/modules/mob/living/silicon/translation.dm b/code/modules/mob/living/silicon/translation.dm new file mode 100644 index 00000000000..f546d07c359 --- /dev/null +++ b/code/modules/mob/living/silicon/translation.dm @@ -0,0 +1,27 @@ +/datum/translation_context/simple/silicons + translation_class = TRANSLATION_CLASSES_STANDARD_TRANSLATE + +/datum/translation_context/variable/learning/silicons + translation_class = TRANSLATION_CLASSES_STANDARD_TRANSLATE + translation_class_learn = TRANSLATION_CLASSES_STANDARD_TRANSLATE + +/** + * @params + * - L - language to sync; if null, syncs all + */ +/mob/living/silicon/proc/sync_translation_context(datum/language/L) + return + +/mob/living/silicon/proc/translation_train_hook(datum/translation_context/context, datum/language/L, old_efficiency) + if(!old_efficiency) + to_chat(src, SPAN_NOTICE("Identifying new language . . .")) + to_chat(src, SPAN_NOTICE("New language identified: [L.name]. Registering and beginning adaptive translation.")) + sync_translation_context(L) + +/mob/living/silicon/proc/create_translation_context(path = translation_context) + if(istype(translation_context)) + qdel(translation_context) + translation_context = new path + if(istype(translation_context, /datum/translation_context/variable/learning/silicons)) + var/datum/translation_context/variable/learning/silicons/CTX = translation_context + CTX.on_train = CALLBACK(src, .proc/translation_train_hook) diff --git a/code/modules/mob/living/simple_mob/subtypes/animal/borer/borer.dm b/code/modules/mob/living/simple_mob/subtypes/animal/borer/borer.dm index 6acf1fc84a9..59b357283fb 100644 --- a/code/modules/mob/living/simple_mob/subtypes/animal/borer/borer.dm +++ b/code/modules/mob/living/simple_mob/subtypes/animal/borer/borer.dm @@ -238,7 +238,7 @@ return emote(copytext(message, 2)) var/datum/language/L = parse_language(message) - if(L && L.language_flags & HIVEMIND) + if(L && L.language_flags & LANGUAGE_HIVEMIND) L.broadcast(src,trim(copytext(message,3)), src.true_name) return diff --git a/code/modules/mob/say.dm b/code/modules/mob/say.dm index 092b98f44e3..1eb6e8b8dbe 100644 --- a/code/modules/mob/say.dm +++ b/code/modules/mob/say.dm @@ -77,7 +77,7 @@ return 1 return 0 - if(speaking.language_flags & INNATE) + if(speaking.language_flags & LANGUAGE_INNATE) return 1 //Language check. diff --git a/code/modules/mob/transform_procs.dm b/code/modules/mob/transform_procs.dm index 04ca0f3f031..462e10aefd9 100644 --- a/code/modules/mob/transform_procs.dm +++ b/code/modules/mob/transform_procs.dm @@ -73,19 +73,6 @@ //Languages add_language("Robot Talk", 1) - add_language(LANGUAGE_GALCOM, 1) - add_language(LANGUAGE_SOL_COMMON, 1) - add_language(LANGUAGE_UNATHI, 1) - add_language(LANGUAGE_SIIK, 1) - add_language(LANGUAGE_AKHANI, 1) - add_language(LANGUAGE_SKRELLIAN, 1) - add_language(LANGUAGE_TRADEBAND, 1) - add_language(LANGUAGE_GUTTER, 1) - add_language(LANGUAGE_EAL, 1) - add_language(LANGUAGE_SCHECHI, 1) - add_language(LANGUAGE_SIGN, 1) - add_language(LANGUAGE_TERMINUS, 1) - add_language(LANGUAGE_ZADDAT, 0) // Lorefolks say it may be so. if(LANGUAGE_ROOTGLOBAL in languages) diff --git a/code/modules/preferences/preference_setup/background/language.dm b/code/modules/preferences/preference_setup/background/language.dm index eefdb2c9627..6ec0c6c75ff 100644 --- a/code/modules/preferences/preference_setup/background/language.dm +++ b/code/modules/preferences/preference_setup/background/language.dm @@ -66,7 +66,7 @@ var/datum/character_species/CS = prefs.character_species_datum() for(var/id in extraneous) var/datum/language/L = SScharacters.resolve_language_id(id) - if((L.language_flags & WHITELISTED) && (!(L in CS.whitelist_languages)) && !config.check_alien_whitelist(ckey(L.name), prefs.client_ckey)) + if((L.language_flags & LANGUAGE_WHITELISTED) && (!(L in CS.whitelist_languages)) && !config.check_alien_whitelist(ckey(L.name), prefs.client_ckey)) errors?.Add(SPAN_WARNING("[L.id] is a whitelisted language.")) return FALSE return TRUE diff --git a/code/modules/preferences/preference_setup/helpers/language_pick.dm b/code/modules/preferences/preference_setup/helpers/language_pick.dm index 8044c26e8b3..c2b1c4432e9 100644 --- a/code/modules/preferences/preference_setup/helpers/language_pick.dm +++ b/code/modules/preferences/preference_setup/helpers/language_pick.dm @@ -17,7 +17,7 @@ to_chat(user, SPAN_WARNING("You cannot select another language!")) return TRUE var/datum/character_species/CS = character_species_datum() - if((L.language_flags & WHITELISTED) && !((L.id in CS.whitelist_languages) || config.check_alien_whitelist(ckey(L.name), client_ckey))) + if((L.language_flags & LANGUAGE_WHITELISTED) && !((L.id in CS.whitelist_languages) || config.check_alien_whitelist(ckey(L.name), client_ckey))) to_chat(user, SPAN_WARNING("[L.name] is a whitelisted language!")) return FALSE var/list/current = get_character_data(CHARACTER_DATA_LANGUAGES) @@ -67,7 +67,7 @@ GLOBAL_LIST_EMPTY(language_picker_active) var/list/built = list() var/list/categories = list("General") for(var/datum/language/L as anything in SScharacters.all_languages()) - if(L.language_flags & RESTRICTED) + if(L.language_flags & LANGUAGE_RESTRICTED) continue built[++built.len] = list( "id" = L.id, diff --git a/code/modules/research/designs/misc.dm b/code/modules/research/designs/misc.dm index cb5d6fdbc94..a559af8ae49 100644 --- a/code/modules/research/designs/misc.dm +++ b/code/modules/research/designs/misc.dm @@ -113,7 +113,7 @@ id = "translator_all" req_tech = list(TECH_DATA = 5, TECH_ENGINEERING = 5) materials = list(MAT_STEEL = 3000, MAT_GLASS = 3000, MAT_GOLD = 500, MAT_SILVER = 500) - build_path = /obj/item/universal_translator/all + build_path = /obj/item/universal_translator/adaptive sort_string = "TAACC" /datum/design/item/general/ear_translator_all @@ -121,7 +121,7 @@ id = "ear_translator_all" req_tech = list(TECH_DATA = 6, TECH_ENGINEERING = 6) //dude what hte fuck lmao materials = list(MAT_STEEL = 2000, MAT_GLASS = 2000, MAT_GOLD = 2000, MAT_SILVER = 2000) - build_path = /obj/item/universal_translator/ear/all + build_path = /obj/item/universal_translator/ear/adaptive sort_string = "TAACD" /datum/design/item/advmop diff --git a/code/modules/vore/eating/living_vr.dm b/code/modules/vore/eating/living_vr.dm index 4e14a3a3823..596f81033ff 100644 --- a/code/modules/vore/eating/living_vr.dm +++ b/code/modules/vore/eating/living_vr.dm @@ -759,6 +759,7 @@ /mob/living/Topic(href, href_list) //Can't find any instances of Topic() being overridden by /mob/living in polaris' base code, even though /mob/living/carbon/human's Topic() has a ..() call if(href_list["vore_prefs"]) display_voreprefs(usr) + return TRUE return ..() /mob/living/proc/display_voreprefs(mob/user) //Called by Topic() calls on instances of /mob/living (and subtypes) containing vore_prefs as an argument