Files
Bubberstation/code/game/objects/items/emags.dm
MrMelbert 7c0973ccf4 Refactors names and voices / potted plants no longer conceal voice (just appearance) / honorifics show on examine (#92781)
## About The Pull Request

Closes #92778
Closes #86829

<img width="347" height="39" alt="image"
src="https://github.com/user-attachments/assets/c50bd1ff-8c00-47a7-a31a-617fae2adc5b"
/>

1. Splits `TRAIT_UNKNOWN` into `TRAIT_UNKNOWN_APPEARANCE` and
`TRAIT_UNKNOWN_VOICE`

2. Renames some stuff like `getvoice` and `getspecialvoice`

3. Gets rid some crummy signals around `get_visible_name` and
`get_voice`

4. Heads now apply the disfigured trait when relevant (rather than
snowflake checking for damage amount)

5. Ling voice refactored into using special voice (it was only used by a
viro symptom anyways; I don't anticipate this overlap being problematic)

6. Mask voice changer refactored into a trait

## Why It's Good For The Game

Potted plants shouldn't have magical voice concealing powers -
especially not over radio, but not over in person either. It's a damn
plant

So I addressed this by refactoring our face and voice system. Overall
things should be a lot cleaner and easier to use.

## Changelog

🆑 Melbert
refactor: Refactored a lot of code relating to human face and voice, ie,
what shows up in examine and in say. Report anything odd when examining
people, with ID cards, when talking over radio, or when disguised
refcator: Refactored how you get disfigured when your head's super
damaged
refactor: Refactored ling mimic voice and traitor voice changer
del: Potted plants no longer hide voice. They still hide appearance,
though
qol: Honorifics now show in examine / in world, rather than only when
speaking.
/🆑
2025-09-12 17:11:06 +02:00

251 lines
10 KiB
Plaintext

/* Emags
* Contains:
* EMAGS AND DOORMAGS
*/
/*
* EMAG AND SUBTYPES
*/
/obj/item/card/emag
desc = "It's a card with a magnetic strip attached to some circuitry."
name = "cryptographic sequencer"
icon_state = "emag"
item_flags = NO_MAT_REDEMPTION | NOBLUDGEON
slot_flags = ITEM_SLOT_ID
worn_icon_state = "emag"
var/prox_check = TRUE //If the emag requires you to be in range
var/type_blacklist //List of types that require a specialized emag
/obj/item/card/emag/get_displayed_name(honorifics = FALSE)
return name // That's Grey Tider (as "cryptographic sequencer")
/obj/item/card/emag/attack_self(mob/user) //for traitors with balls of plastitanium
if(Adjacent(user))
user.visible_message(span_notice("[user] shows you: [icon2html(src, viewers(user))] [name]."), span_notice("You show [src]."))
add_fingerprint(user)
/obj/item/card/emag/emag_act(mob/user, obj/item/card/emag/emag_card)
if(isnull(user) || !istype(emag_card))
return FALSE
var/emag_count = 0
for(var/obj/item/card/emag/emag in get_all_contents() + emag_card.get_all_contents()) // This is including itself
emag_count++
if(emag_count > 6) // 1 uplink's worth is the limit
to_chat(user, span_warning("Nope, lesson learned. No more."))
return FALSE
if(emag_card.loc != loc) // Both have to be in your hand (or TK shenanigans)
return FALSE
if(!user.transferItemToLoc(emag_card, src, silent = FALSE))
return FALSE
user.visible_message(
span_notice("[user] holds [emag_card] to [src], getting the two cards stuck together!"),
span_notice("As you hold [emag_card] to [src], [emag_card.p_their()] magnets attract to one another, \
and [emag_card.p_they()] become stuck together!"),
visible_message_flags = ALWAYS_SHOW_SELF_MESSAGE,
)
playsound(src, 'sound/effects/bang.ogg', 33, TRUE, SHORT_RANGE_SOUND_EXTRARANGE)
addtimer(CALLBACK(src, PROC_REF(contemplation_period), user), 2 SECONDS, TIMER_DELETE_ME)
emag_card.vis_flags |= VIS_INHERIT_ID|VIS_INHERIT_PLANE
vis_contents += emag_card
name = initial(name)
desc = initial(desc)
var/list/all_emags = get_all_contents_type(/obj/item/card/emag) - src
for(var/i in 1 to length(all_emags))
var/obj/item/card/emag/other_emag = all_emags[i]
other_emag.pixel_x = pixel_x + (4 * i)
other_emag.pixel_y = pixel_y + (4 * i)
other_emag.layer = layer - (0.01 * i)
name += "-[initial(other_emag.name)]"
desc += " There seems to be another card stuck to it...pretty soundly."
return TRUE
/obj/item/card/emag/proc/contemplation_period(mob/user)
if(QDELETED(user))
return
if(QDELETED(src))
to_chat(user, span_notice("Oh, well."))
else
to_chat(user, span_warning("Well, shit. Those are never coming apart now."))
/obj/item/card/emag/Exited(atom/movable/gone, direction)
. = ..()
if(istype(gone, /obj/item/card/emag))
// This is here so if(when) admins fish it out of contents it doesn't become glitchy
gone.layer = initial(gone.layer)
gone.vis_flags = initial(gone.vis_flags)
vis_contents -= gone
name = initial(name)
desc = initial(desc)
gone.name = initial(name)
gone.desc = initial(desc)
/obj/item/card/emag/bluespace
name = "bluespace cryptographic sequencer"
desc = "It's a blue card with a magnetic strip attached to some circuitry. It appears to have some sort of transmitter attached to it."
color = rgb(40, 130, 255)
prox_check = FALSE
/obj/item/card/emag/halloween
name = "hack-o'-lantern"
desc = "It's a pumpkin with a cryptographic sequencer sticking out."
icon_state = "hack_o_lantern"
/obj/item/card/emagfake
name = /obj/item/card/emag::name
desc = /obj/item/card/emag::desc + " Closer inspection shows that this card is a poorly made replica, with a \"Donk Co.\" logo stamped on the back."
icon = /obj/item/card/emag::icon
icon_state = /obj/item/card/emag::icon_state
worn_icon_state = /obj/item/card/emag::worn_icon_state
slot_flags = ITEM_SLOT_ID
/// Whether we are exploding
var/exploding = FALSE
/obj/item/card/emagfake/get_displayed_name(honorifics = FALSE)
return name // That's Grey Tider (as "cryptographic sequencer")
/obj/item/card/emagfake/attack_self(mob/user) //for assistants with balls of plasteel
if(Adjacent(user))
user.visible_message(span_notice("[user] shows you: [icon2html(src, viewers(user))] [name]."), span_notice("You show [src]."))
add_fingerprint(user)
/obj/item/card/emagfake/interact_with_atom(atom/interacting_with, mob/living/user, list/modifiers)
if(exploding)
playsound(src, 'sound/items/bikehorn.ogg', 50, TRUE, frequency = 2)
else if(obj_flags & EMAGGED)
log_bomber(user, "triggered", src, "(rigged/emagged)")
visible_message(span_boldwarning("[src] begins to heat up!"))
playsound(src, 'sound/items/bikehorn.ogg', 100, TRUE, frequency = 0.25)
addtimer(CALLBACK(src, PROC_REF(blow_up)), 1 SECONDS, TIMER_DELETE_ME)
exploding = TRUE
else
playsound(src, 'sound/items/bikehorn.ogg', 50, TRUE)
return ITEM_INTERACT_SKIP_TO_ATTACK // So it does the attack animation.
/obj/item/card/emagfake/proc/blow_up()
visible_message(span_boldwarning("[src] explodes!"))
explosion(src, light_impact_range = 1, explosion_cause = src)
qdel(src)
/obj/item/card/emagfake/emag_act(mob/user, obj/item/card/emag/emag_card)
if(obj_flags & EMAGGED)
return FALSE
playsound(src, SFX_SPARKS, 50, TRUE, SILENCED_SOUND_EXTRARANGE)
desc = /obj/item/card/emag::desc
obj_flags |= EMAGGED
if(user)
balloon_alert(user, "rigged to blow")
log_bomber(user, "rigged to blow", src, "(emagging)")
return TRUE
/obj/item/card/emag/Initialize(mapload)
. = ..()
type_blacklist = list(typesof(/obj/machinery/door/airlock) + typesof(/obj/machinery/door/window/) + typesof(/obj/machinery/door/firedoor) - typesof(/obj/machinery/door/airlock/tram)) //list of all typepaths that require a specialized emag to hack.
/obj/item/card/emag/interact_with_atom(atom/interacting_with, mob/living/user, list/modifiers)
if(SHOULD_SKIP_INTERACTION(interacting_with, src, user))
return NONE // lets us put things in bags without trying to emag them
if(!can_emag(interacting_with, user))
return ITEM_INTERACT_BLOCKING
log_combat(user, interacting_with, "attempted to emag")
if(interacting_with.emag_act(user, src))
SSblackbox.record_feedback("tally", "atom_emagged", 1, interacting_with.type)
return ITEM_INTERACT_SUCCESS
return NONE // In a perfect world this would be blocking, but this is not a perfect world
/obj/item/card/emag/ranged_interact_with_atom(atom/interacting_with, mob/living/user, list/modifiers)
return prox_check ? NONE : interact_with_atom(interacting_with, user)
/obj/item/card/emag/proc/can_emag(atom/target, mob/user)
for (var/subtypelist in type_blacklist)
if (target.type in subtypelist)
to_chat(user, span_warning("The [target] cannot be affected by the [src]! A more specialized hacking device is required."))
return FALSE
return TRUE
/*
* DOORMAG
*/
/obj/item/card/emag/doorjack
desc = "Commonly known as a \"doorjack\", this device is a specialized cryptographic sequencer specifically designed to override station airlock access codes. Uses self-refilling charges to hack airlocks."
name = "airlock authentication override card"
icon_state = "doorjack"
worn_icon_state = "doorjack"
var/type_whitelist //List of types
var/charges = 3
var/max_charges = 3
var/list/charge_timers = list()
var/charge_time = 1800 //three minutes
/obj/item/card/emag/doorjack/Initialize(mapload)
. = ..()
type_whitelist = list(typesof(/obj/machinery/door/airlock), typesof(/obj/machinery/door/window/), typesof(/obj/machinery/door/firedoor)) //list of all acceptable typepaths that this device can affect
/obj/item/card/emag/doorjack/interact_with_atom(atom/interacting_with, mob/living/user, list/modifiers)
// only for doorjacks since regular emags still need to be able to break locks on storage items
if(interacting_with.atom_storage)
return NONE
. = ..()
/obj/item/card/emag/doorjack/proc/use_charge(mob/user)
charges --
to_chat(user, span_notice("You use [src]. It now has [charges] charge[charges == 1 ? "" : "s"] remaining."))
charge_timers.Add(addtimer(CALLBACK(src, PROC_REF(recharge)), charge_time, TIMER_STOPPABLE))
/obj/item/card/emag/doorjack/proc/recharge(mob/user)
charges = min(charges+1, max_charges)
playsound(src,'sound/machines/beep/twobeep.ogg',10,TRUE, extrarange = SILENCED_SOUND_EXTRARANGE, falloff_distance = 0)
charge_timers.Remove(charge_timers[1])
/obj/item/card/emag/doorjack/examine(mob/user)
. = ..()
. += span_notice("It has [charges] charges remaining.")
if (length(charge_timers))
. += "[span_notice("<b>A small display on the back reads:")]</b>"
for (var/i in 1 to length(charge_timers))
var/timeleft = timeleft(charge_timers[i])
var/loadingbar = num2loadingbar(timeleft/charge_time)
. += span_notice("<b>CHARGE #[i]: [loadingbar] ([DisplayTimeText(timeleft)])</b>")
/obj/item/card/emag/doorjack/can_emag(atom/target, mob/user)
if (charges <= 0)
to_chat(user, span_warning("[src] is recharging!"))
return FALSE
for (var/list/subtypelist in type_whitelist)
if (target.type in subtypelist)
return TRUE
to_chat(user, span_warning("[src] is unable to interface with this. It only seems to fit into airlock electronics."))
return FALSE
/*
* Battlecruiser Access
*/
/obj/item/card/emag/battlecruiser
name = "battlecruiser coordinates upload card"
desc = "An ominous card that contains the location of the station, and when applied to a communications console, \
the ability to long-distance contact the Syndicate fleet."
icon_state = "battlecruisercaller"
worn_icon_state = "emag"
///whether we have called the battlecruiser
var/used = FALSE
/// The battlecruiser team that the battlecruiser will get added to
var/datum/team/battlecruiser/team
/obj/item/card/emag/battlecruiser/proc/use_charge(mob/user)
used = TRUE
to_chat(user, span_boldwarning("You use [src], and it interfaces with the communication console. No going back..."))
/obj/item/card/emag/battlecruiser/examine(mob/user)
. = ..()
. += span_notice("It can only be used on the communications console.")
/obj/item/card/emag/battlecruiser/can_emag(atom/target, mob/user)
if(used)
to_chat(user, span_warning("[src] is used up."))
return FALSE
if(!istype(target, /obj/machinery/computer/communications))
to_chat(user, span_warning("[src] is unable to interface with this. It only seems to interface with the communication console."))
return FALSE
return TRUE