mirror of
https://github.com/Aurorastation/Aurora.3.git
synced 2026-07-24 06:18:13 +01:00
11a9a2d644
Rewrites Saycode and Langchat to add support for multiple languages in one message, including audible emotes. <img width="1139" height="338" alt="image" src="https://github.com/user-attachments/assets/25e26932-7a6e-4c54-ab74-56fffb92ecad" /> Here's some samples to explain how to mix languages. <img width="422" height="26" alt="image" src="https://github.com/user-attachments/assets/e1b176cc-8625-4dc9-83c8-a053d3f310e6" /> `Languages ,2 can be mixed ,3 like this.` <img width="540" height="21" alt="image" src="https://github.com/user-attachments/assets/19156c67-4670-4d7a-84d7-26e527de2676" /> `!explains, ,2Emotes work too. ,0The text will get auto-quoted.` <img width="592" height="18" alt="image" src="https://github.com/user-attachments/assets/cfc31c5c-2383-41c8-82be-b36836339321" /> `,3Most languages ,0can be ,2mixed ,1arbitrarily, ,3any number of ,0times.` <img width="636" height="20" alt="image" src="https://github.com/user-attachments/assets/388b4f9d-192a-4374-ac31-bbd4e4e5dfe4" /> `,2Emotes. ,eAnd he nods. ,3They don't have to come first anymore.` There are some exceptions. The exceptions are currently anything with any of the flags `SIGNLANG`, `HIVEMIND`, `PRESSUREPROOF`, `KNOWONLYHEAR`. Exceptions work the same as current languages do: they must be the first language in the message. If so, they prevent switching into any other language mid-message; if they're not first, they just wont trigger. They're exceptions currently because there's not really a clean way that I or the people I asked for help on this one to make them look nice. `SIGNLANG` for example doesn't scramble text, it shows it's own ` gestures a lengthy message.` text for those that don't understand. Could we just replace every instance of sign language with that if somebody doesn't understand? Probably. It would look pretty awful though. e.g. `Alina Eskelinen says, "Hello." Alina Eskelinen gestures a short message.` This definitely needs testmerging because it more or less rewrites the entire pipeline surrounding `say`. The `say` code itself had to be rewritten to support the multiple languages, as well as all the existing plumbing for listeners receiving messages. In return though, it's significantly more straightforward and hopefully by extension easier for people to add to in the future. Primarily, instead of having four different `hear_say`, `hear_radio`, `hear_sleep`, etc., routes for messages to come through, every single audible message is received by `hear_message`, which is responsible for figuring out how clear the message is (is the radio damaged? is it a whisper we're eavesdropping on?), who needs to receive it in their chatbox, formatting it correctly for each listener, and finally if any npc or object within range needs to react to it in some way, like a parrot or a mech. changelog: - rscadd: "Adds code-switching: you can now speak in multiple languages in the same message." - rscadd: "Adds audible emotes to the language list. They can be triggered with ,e." - rscdel: "Removes SSrunechat." - refactor: "Rewrote langchat in order to support multiple languages and partial comprehension." - refactor: "Rewrote a vast majority of all saycode and the code responsible for displaying saytext to clients." - bugfix: "Sleeping mobs are no longer able to understand all languages." - bugfix: "Langchat now correctly shows the appropriate comprehension for all viewers rather than all viewers sharing the comprehension of the last viewer." - bugfix: "Languages which are supposed to be invisible when not understood no longer appear as scrambled overhead text." Forgive me whoever has to review this. Biggest areas that have room for error is stuff like a borer inside someone's head, and Dionae stuff. Old langchat had odd exceptions for those and I was forced to rewrite it entirely, but I think I got it all back to how it was working before.
448 lines
14 KiB
Plaintext
448 lines
14 KiB
Plaintext
/obj
|
|
layer = OBJ_LAYER
|
|
animate_movement = 2
|
|
|
|
/// Used to store information about the contents of the object.
|
|
var/list/matter
|
|
/// Whether the object can be recycled (eaten) by something like the Autolathe.
|
|
var/recyclable = FALSE
|
|
/// Size of the object.
|
|
var/w_class
|
|
/// Used by R&D to determine what research bonuses it grants.
|
|
var/list/origin_tech = null
|
|
/// Universal "unacidabliness" var, here so you can use it in any obj. As xeno acid is gone, this is now only used for chemistry acid.
|
|
var/unacidable = 0
|
|
|
|
/// Special flags such as whether or not this object can be rotated.
|
|
var/obj_flags
|
|
/// Integer. Used for varied damage and item volume calculations.
|
|
var/throwforce = 1
|
|
/// Used in attackby() to say how something was attacked "[x] has been [z.attack_verb] by [y] with [z]".
|
|
var/list/attack_verb
|
|
/// Whether this object creates CUT wounds.
|
|
var/sharp = 0
|
|
/// Whether this object is more likely to dismember.
|
|
var/edge = FALSE
|
|
/// If we have a user using us, this will be set on. We will check if the user has stopped using us, and thus stop updating and LAGGING EVERYTHING!
|
|
var/in_use = 0
|
|
/// The type of damage this object deals.
|
|
var/damtype = DAMAGE_BRUTE
|
|
/// The damage this object deals.
|
|
var/force = 0
|
|
/// The armour penetration this object has.
|
|
var/armor_penetration = 0
|
|
/// To make it not able to slice things. Used for curtains, flaps, pumpkins... why aren't you just using edge?
|
|
var/noslice = FALSE
|
|
/// The armor of this object, turned into an armor component.
|
|
var/list/armor
|
|
/// Set to TRUE when shocked by the tesla ball, to not repeatedly shock the object.
|
|
var/being_shocked = FALSE
|
|
|
|
/// The slot the object will equip to.
|
|
var/equip_slot = 0
|
|
/// If set, this holds the 3-letter shortname of a species, used for species-specific worn icons
|
|
var/icon_species_tag = ""
|
|
/// If 1, this item will automatically change its species tag to match the wearer's species. Requires that the wearer's species is listed in icon_supported_species_tags.
|
|
var/icon_auto_adapt = 0
|
|
|
|
/**
|
|
* A list of strings used with icon_auto_adapt, a list of species which have differing appearances for this item,
|
|
* based on the species short name
|
|
*/
|
|
var/list/icon_supported_species_tags
|
|
|
|
/// If `TRUE`, will use the `icon_species_tag` var for rendering this item in the left/right hand.
|
|
var/icon_species_in_hand = FALSE
|
|
|
|
///Played when the item is used, for example tools
|
|
var/usesound
|
|
/// The speed of the tool. This is generally a divisor.
|
|
var/toolspeed = 1
|
|
|
|
/// The sound this tool makes in surgery.
|
|
var/surgerysound
|
|
|
|
/* START BUCKLING VARS */
|
|
/// A list of things that can buckle to this atom.
|
|
var/list/can_buckle
|
|
/// If the buckled atom can move, and thus face directions.
|
|
var/buckle_movable = 0
|
|
/// The direction forced on a buckled atom.
|
|
var/buckle_dir = 0
|
|
/// Causes bed-like behavior, forces mob.lying = buckle_lying if != -1.
|
|
var/buckle_lying = -1
|
|
/// Require people to be handcuffed before being able to buckle. eg: pipes.
|
|
var/buckle_require_restraints = 0
|
|
/// The atom buckled to us.
|
|
var/atom/movable/buckled = null
|
|
/**
|
|
* Stores the original layer of a buckled atom.
|
|
*
|
|
* Set in `/obj/proc/buckle` when the atom's layer is adjusted.
|
|
*
|
|
* Used in `/unbuckle()` to restore the original layer.
|
|
*/
|
|
var/buckled_original_layer = null
|
|
/// How much extra time to buckle someone to this object.
|
|
var/buckle_delay = 0
|
|
/* END BUCKLING VARS */
|
|
|
|
/* START ACCESS VARS */
|
|
/// Required access.
|
|
var/list/req_access
|
|
/// Only require one of these accesses.
|
|
var/list/req_one_access
|
|
/* END ACCESS VARS */
|
|
|
|
/* START PERSISTENCE VARS */
|
|
// State check if the subsystem is tracking the object, used for easy state checking without iterating the register
|
|
var/persistent_objects_track_active = FALSE
|
|
// Tracking ID of the object used by the persistence subsystem
|
|
var/persistent_objects_track_id = 0
|
|
// Author ckey of the object used in persistence subsystem
|
|
// Note: Not every type can have an author, like generated dirt for example
|
|
// Additionally, the ckey is only an indicator, for example: A player could pin a paper without having written it
|
|
// This should be considered for any moderation purpose
|
|
var/persistent_objects_author_ckey = null
|
|
// Expiration time used when saving/updating a persistent type, this can be changed depending on the use case by assigning a new value
|
|
var/persistant_objects_expiration_time_days = PERSISTENT_DEFAULT_EXPIRATION_DAYS
|
|
/* END PERSISTENCE VARS */
|
|
|
|
/// for easy reference of talking atoms
|
|
var/datum/talking_atom/talking_atom
|
|
|
|
/// Whether this object has been contaminated by atmos gasses like chlorine or phoron.
|
|
var/contaminated = FALSE
|
|
|
|
/// Allow overriding rad resistance.
|
|
var/rad_resistance_modifier = 1
|
|
|
|
/obj/Initialize(mapload, ...)
|
|
. = ..()
|
|
if(maxhealth)
|
|
if(!health)
|
|
// Allows you to set dynamic health states on initialize.
|
|
health = maxhealth
|
|
if(islist(armor))
|
|
for(var/type in armor)
|
|
if(armor[type])
|
|
AddComponent(/datum/component/armor, armor)
|
|
break
|
|
else if(should_use_health)
|
|
AddComponent(/datum/component/armor, GLOB.default_object_armor, TRUE)
|
|
|
|
/obj/Destroy()
|
|
if(persistent_objects_track_active) // Prevent hard deletion of references in the persistence register by removing it preemptively
|
|
SSpersistence.objectsDeregisterTrack(src)
|
|
STOP_PROCESSING(SSprocessing, src)
|
|
unbuckle()
|
|
QDEL_NULL(talking_atom)
|
|
return ..()
|
|
|
|
/obj/Topic(href, href_list, var/datum/ui_state/state = GLOB.default_state)
|
|
if(..())
|
|
return 1
|
|
|
|
// In the far future no checks are made in an overriding Topic() beyond if(..()) return
|
|
// Instead any such checks are made in CanUseTopic()
|
|
if(CanUseTopic(usr, state, href_list) == STATUS_INTERACTIVE)
|
|
CouldUseTopic(usr)
|
|
return 0
|
|
|
|
CouldNotUseTopic(usr)
|
|
return 1
|
|
|
|
/obj/CanUseTopic(var/mob/user, var/datum/ui_state/state)
|
|
if(user.CanUseObjTopic(src))
|
|
return ..()
|
|
to_chat(user, SPAN_DANGER("[icon2html(src, user)]Access denied!"))
|
|
return STATUS_CLOSE
|
|
|
|
/mob/living/silicon/CanUseObjTopic(var/obj/O)
|
|
var/id = src.GetIdCard()
|
|
return O.check_access(id)
|
|
|
|
/mob/proc/CanUseObjTopic()
|
|
return 1
|
|
|
|
|
|
|
|
/obj/proc/CouldUseTopic(var/mob/user)
|
|
user.AddTopicPrint(src)
|
|
|
|
/mob/proc/AddTopicPrint(var/obj/target)
|
|
target.add_hiddenprint(src)
|
|
|
|
/mob/living/AddTopicPrint(var/obj/target)
|
|
if(Adjacent(target))
|
|
target.add_fingerprint(src)
|
|
else
|
|
target.add_hiddenprint(src)
|
|
|
|
/mob/living/silicon/ai/AddTopicPrint(var/obj/target)
|
|
target.add_hiddenprint(src)
|
|
|
|
/obj/proc/CouldNotUseTopic(var/mob/user)
|
|
// Nada
|
|
|
|
/obj/proc/ai_can_interact(var/mob/user)
|
|
if(!Adjacent(user) && within_jamming_range(src, FALSE)) // if not adjacent to it, it uses wireless signal
|
|
to_chat(user, SPAN_WARNING("Something in the area of \the [src] is blocking the remote signal!"))
|
|
return FALSE
|
|
return TRUE
|
|
|
|
/obj/item/proc/is_used_on(obj/O, mob/user)
|
|
|
|
/obj/assume_air(datum/gas_mixture/giver)
|
|
if(loc)
|
|
return loc.assume_air(giver)
|
|
else
|
|
return null
|
|
|
|
/obj/remove_air(amount)
|
|
if(loc)
|
|
return loc.remove_air(amount)
|
|
else
|
|
return null
|
|
|
|
/obj/return_air()
|
|
if(loc)
|
|
return loc.return_air()
|
|
|
|
/obj/condition_hints(mob/user, distance, is_adjacent)
|
|
. += ..()
|
|
if(health < maxhealth)
|
|
. += get_damage_condition_hints(user, distance, is_adjacent)
|
|
|
|
/obj/proc/updateUsrDialog()
|
|
if(in_use)
|
|
var/is_in_use = 0
|
|
var/list/nearby = viewers(1, src)
|
|
for(var/mob/M in nearby)
|
|
if ((M.client && M.machine == src))
|
|
is_in_use = 1
|
|
src.attack_hand(M)
|
|
if (istype(usr, /mob/living/silicon/ai) || istype(usr, /mob/living/silicon/robot))
|
|
if (!(usr in nearby))
|
|
if (usr.client && usr.machine==src) // && M.machine == src is omitted because if we triggered this by using the dialog, it doesn't matter if our machine changed in between triggering it and this - the dialog is probably still supposed to refresh.
|
|
is_in_use = 1
|
|
src.attack_ai(usr)
|
|
in_use = is_in_use
|
|
|
|
/obj/proc/updateDialog()
|
|
// Check that people are actually using the machine. If not, don't update anymore.
|
|
if(in_use)
|
|
var/list/nearby = viewers(1, src)
|
|
var/is_in_use = 0
|
|
for(var/mob/M in nearby)
|
|
if ((M.client && M.machine == src))
|
|
is_in_use = 1
|
|
src.interact(M)
|
|
var/ai_in_use = AutoUpdateAI(src)
|
|
|
|
if(!ai_in_use && !is_in_use)
|
|
in_use = 0
|
|
|
|
/obj/attack_ghost(mob/user)
|
|
ui_interact(user)
|
|
..()
|
|
|
|
/obj/proc/interact(mob/user)
|
|
return
|
|
|
|
/mob/proc/unset_machine()
|
|
src.machine = null
|
|
|
|
/mob/proc/set_machine(var/obj/O)
|
|
if(src.machine)
|
|
unset_machine()
|
|
src.machine = O
|
|
if(istype(O))
|
|
O.in_use = 1
|
|
|
|
/obj/item/proc/updateSelfDialog()
|
|
var/mob/M = src.loc
|
|
if(istype(M) && M.client && M.machine == src)
|
|
src.attack_self(M)
|
|
|
|
/obj/proc/hide(var/hide)
|
|
set_invisibility(hide ? INVISIBILITY_MAXIMUM : initial(invisibility))
|
|
level = hide ? 1 : initial(level)
|
|
|
|
/obj/proc/hides_under_flooring()
|
|
return level == 1
|
|
|
|
/obj/proc/hear_talk(mob/M as mob, text, verb, datum/language/speaking)
|
|
if(talking_atom)
|
|
talking_atom.catchMessage(text, M)
|
|
|
|
/obj/proc/see_emote(mob/M as mob, text, var/emote_type)
|
|
return
|
|
|
|
/obj/proc/tesla_act(var/power, var/melt = FALSE)
|
|
if(melt)
|
|
visible_message(SPAN_DANGER("\The [src] melts down until ashes are left!"))
|
|
new /obj/effect/decal/cleanable/ash(loc)
|
|
qdel(src)
|
|
return
|
|
being_shocked = 1
|
|
var/power_bounced = power / 2
|
|
tesla_zap(src, 3, power_bounced)
|
|
addtimer(CALLBACK(src, PROC_REF(reset_shocked)), 10)
|
|
|
|
/obj/proc/reset_shocked()
|
|
being_shocked = 0
|
|
|
|
|
|
/obj/show_message(msg, type, alt, alt_type)//Message, type of message (1 or 2), alternative message, alt message type (1 or 2)
|
|
return
|
|
|
|
//To be called from things that spill objects on the floor.
|
|
//Makes an object move around randomly for a couple of tiles
|
|
/obj/proc/tumble(var/dist)
|
|
if (dist >= 1)
|
|
spawn()
|
|
dist += rand(0,1)
|
|
for(var/i = 1, i <= dist, i++)
|
|
if(src)
|
|
step(src, pick(NORTH,SOUTH,EAST,WEST))
|
|
sleep(rand(2,4))
|
|
|
|
/**
|
|
* Sets the `icon_species_tag` on the `/obj` based on the wearer specie, which is
|
|
* then used by the icon generator to select the correct overlay of the object
|
|
*
|
|
* * wearer - A `/mob/living/carbon/human` to adapt the object to the specie of
|
|
*
|
|
* Returns `TRUE` on successful adaptation, `FALSE` otherwise
|
|
*/
|
|
/obj/proc/auto_adapt_species(mob/living/carbon/human/wearer)
|
|
SHOULD_NOT_SLEEP(TRUE)
|
|
if(icon_auto_adapt)
|
|
icon_species_tag = ""
|
|
if(wearer && length(icon_supported_species_tags))
|
|
if(wearer.species.short_name in icon_supported_species_tags)
|
|
icon_species_tag = wearer.species.short_name
|
|
return TRUE
|
|
return FALSE
|
|
|
|
|
|
//This function should be called on an item when it is:
|
|
//Built, autolathed, protolathed, crafted or constructed. At runtime, by players or machines
|
|
|
|
//It should NOT be called on things that:
|
|
//spawn at roundstart, are adminspawned, arrive on shuttles, spawned from vendors, removed from fridges and containers, etc
|
|
//This is useful for setting special behaviour for built items that shouldn't apply to those spawned at roundstart
|
|
/obj/proc/Created()
|
|
return
|
|
|
|
/obj/proc/rotate(var/mob/user, var/anchored_ignore = FALSE)
|
|
if(use_check_and_message(user))
|
|
return
|
|
|
|
if(anchored && !anchored_ignore)
|
|
to_chat(user, SPAN_WARNING("\The [src] is bolted down to the floor!"))
|
|
return
|
|
|
|
set_dir(turn(dir, 90))
|
|
update_icon()
|
|
return TRUE
|
|
|
|
/obj/AltClick(var/mob/user)
|
|
if(obj_flags & OBJ_FLAG_ROTATABLE)
|
|
rotate(user)
|
|
return
|
|
if(obj_flags & OBJ_FLAG_ROTATABLE_ANCHORED)
|
|
rotate(user, TRUE)
|
|
return
|
|
..()
|
|
|
|
/obj/get_examine_text(mob/user, distance, is_adjacent, infix, suffix, get_extended = FALSE)
|
|
. = ..()
|
|
if((obj_flags & OBJ_FLAG_ROTATABLE) || (obj_flags & OBJ_FLAG_ROTATABLE_ANCHORED))
|
|
. += SPAN_SUBTLE("Can be rotated with alt-click.")
|
|
if(contaminated)
|
|
. += SPAN_ALIEN("\The [src] has been contaminated!")
|
|
|
|
// whether mobs can unequip and drop items into us or not
|
|
/obj/proc/can_hold_dropped_items()
|
|
return TRUE
|
|
|
|
/obj/proc/damage_flags()
|
|
. = 0
|
|
if(has_edge(src))
|
|
. |= DAMAGE_FLAG_EDGE
|
|
if(is_sharp(src))
|
|
. |= DAMAGE_FLAG_SHARP
|
|
if(damtype == DAMAGE_BURN)
|
|
. |= DAMAGE_FLAG_LASER
|
|
|
|
/obj/proc/set_pixel_offsets()
|
|
return
|
|
|
|
//wash an object
|
|
/obj/proc/clean()
|
|
clean_blood()
|
|
color = initial(color)
|
|
|
|
/obj/proc/output_spoken_message(var/message, var/message_verb = "transmits", var/display_overhead = TRUE, var/overhead_time = 2 SECONDS)
|
|
audible_message("\The <b>[src.name]</b> [message_verb], \"[message]\"")
|
|
if(display_overhead)
|
|
var/list/hearers = get_hearers_in_view(7, src)
|
|
var/list/clients_in_hearers = list()
|
|
for(var/mob/mob in hearers)
|
|
if(mob.client)
|
|
clients_in_hearers += mob.client
|
|
if(length(clients_in_hearers))
|
|
langchat_speech(message, hearers)
|
|
|
|
/// Override this to customize the effects an activated signaler has.
|
|
/obj/proc/do_signaler()
|
|
return
|
|
|
|
|
|
/**
|
|
* Called when an access cable - from an IPC or from a roboticist tool - is inserted into an object.
|
|
*/
|
|
/obj/proc/insert_cable(obj/item/access_cable/cable, mob/user)
|
|
user.drop_from_inventory(cable)
|
|
cable.forceMove(src)
|
|
cable.target = src
|
|
|
|
/**
|
|
* Called when an access cable is removed from something, forcibly or not.
|
|
* Override as needed to clear variables up.
|
|
*/
|
|
/obj/proc/remove_cable(obj/item/access_cable/cable)
|
|
SHOULD_CALL_PARENT(TRUE)
|
|
cable.target = null
|
|
|
|
/**
|
|
* Called when an access cable - from an IPC or from a roboticist tool - is used on an object to interact with it.
|
|
* For example, brings up diagnostics for an access port if it's hooked into one.
|
|
*/
|
|
/obj/proc/cable_interact(obj/item/access_cable/cable, mob/user)
|
|
return
|
|
|
|
/*#############################################
|
|
PERSISTENT
|
|
#############################################*/
|
|
|
|
/**
|
|
* Called by the persistence subsystem to retrieve relevant persistent information to be stored in the database.
|
|
* Expected to be overriden by derived objects.
|
|
* RETURN: Associated list with custom information (e.g.: ["test" = "abc", "counter" = 123])
|
|
*/
|
|
/obj/proc/persistent_objects_get_content()
|
|
return list()
|
|
|
|
/**
|
|
* Called by the persistence subsystem to apply persistent data on the created object.
|
|
* Expected to be overriden by derived objects.
|
|
* PARAMS:
|
|
* content = Associated list with custom information (e.g.: ["test" = "abc", "counter" = 123]).
|
|
* x,y,z = x-y-z coordinates of object, can be null.
|
|
*/
|
|
/obj/proc/persistent_objects_apply_content(content, x, y, z)
|
|
return
|