Files
Bubberstation/code/datums/components/supermatter_crystal.dm
Jacquerel f8c48e75cb Feeding the supermatter a nuke will make it explode (#90875)
## About The Pull Request

Feeding the nuclear bomb (or its radioactive core) to the Supermatter
will cause an oversized radiation pulse, generate a large amount of
energy, and immediately damage the supermatter sufficiently to start
delaminating.
If the core isn't in the bomb it won't do this (meaning that the beer
nuke doesn't do it sorry)

I wish there was a nicer interface for these special supermatter
interactions but I also can't be bothered to figure one out right now

ALSO for some reason `consume_returns` was being called twice if a
living mob hit the crystal which seems like an unintended bug that has
been in the codebase for three years?
I fixed this but also doubled the values being passed into it because at
this point I guess those are the values we expect to get for mobs
running into the supermatter

## Why It's Good For The Game

Someone posted a clip today of a group of people pushing a nuke into the
supermatter and then it ate it and nothing happened.
That is sad, if you feed the supermatter a big bomb full of energy then
it should explode.

Damaging it immediately enough for it to explode might be over the top
in which case let me know, but having a movable bomb around is
reasonably uncommon if nukies aren't on the station as the station one
is not usually movable. Traitors extracting the nuke core also cannot be
done without a specific traitor objective giving you special tools.
Doing like 90 damage would, with a default supermatter setup, generally
just make it yell loudly but then slowly heal itself without any
required input which made me sad.

## Changelog

🆑
balance: The supermatter will react negatively to being fed nuclear
fissile materials
/🆑
2025-05-08 19:21:10 -04:00

373 lines
18 KiB
Plaintext

/datum/component/supermatter_crystal
///Callback for the wrench act call
var/datum/callback/tool_act_callback
///Callback used by the SM to get the damage and matter power increase/decrease
var/datum/callback/consume_callback
// A whitelist of items that can interact with the SM without dusting the user
var/static/list/sm_item_whitelist = typecacheof(list(
/obj/item/melee/roastingstick,
/obj/item/toy/crayon/spraycan
))
/datum/component/supermatter_crystal/Initialize(datum/callback/tool_act_callback, datum/callback/consume_callback)
RegisterSignal(parent, COMSIG_ATOM_BLOB_ACT, PROC_REF(blob_hit))
RegisterSignal(parent, COMSIG_ATOM_ATTACK_PAW, PROC_REF(paw_hit))
RegisterSignal(parent, COMSIG_ATOM_ATTACK_ANIMAL, PROC_REF(animal_hit))
RegisterSignal(parent, COMSIG_ATOM_HULK_ATTACK, PROC_REF(hulk_hit))
RegisterSignal(parent, COMSIG_LIVING_UNARMED_ATTACK, PROC_REF(unarmed_hit))
RegisterSignal(parent, COMSIG_ATOM_ATTACK_HAND, PROC_REF(hand_hit))
RegisterSignal(parent, COMSIG_ATOM_ATTACKBY, PROC_REF(attackby_hit))
RegisterSignal(parent, COMSIG_ATOM_TOOL_ACT(TOOL_WRENCH), PROC_REF(tool_hit))
RegisterSignal(parent, COMSIG_ATOM_SECONDARY_TOOL_ACT(TOOL_WRENCH), PROC_REF(tool_hit))
RegisterSignal(parent, COMSIG_ATOM_BUMPED, PROC_REF(bumped_hit))
RegisterSignal(parent, COMSIG_ATOM_INTERCEPT_Z_FALL, PROC_REF(intercept_z_fall))
RegisterSignal(parent, COMSIG_ATOM_ON_Z_IMPACT, PROC_REF(on_z_impact))
src.tool_act_callback = tool_act_callback
src.consume_callback = consume_callback
/datum/component/supermatter_crystal/Destroy(force)
tool_act_callback = null
consume_callback = null
return ..()
/datum/component/supermatter_crystal/UnregisterFromParent(force, silent)
var/list/signals_to_remove = list(
COMSIG_ATOM_BLOB_ACT,
COMSIG_ATOM_ATTACK_PAW,
COMSIG_ATOM_ATTACK_ANIMAL,
COMSIG_ATOM_HULK_ATTACK,
COMSIG_LIVING_UNARMED_ATTACK,
COMSIG_ATOM_ATTACK_HAND,
COMSIG_ATOM_ATTACKBY,
COMSIG_ATOM_TOOL_ACT(TOOL_WRENCH),
COMSIG_ATOM_SECONDARY_TOOL_ACT(TOOL_WRENCH),
COMSIG_ATOM_BUMPED,
COMSIG_ATOM_INTERCEPT_Z_FALL,
COMSIG_ATOM_ON_Z_IMPACT,
)
UnregisterSignal(parent, signals_to_remove)
/datum/component/supermatter_crystal/proc/blob_hit(datum/source, obj/structure/blob/blob)
SIGNAL_HANDLER
var/atom/atom_source = source
if(!blob || isspaceturf(atom_source)) //does nothing in space
return
playsound(get_turf(atom_source), 'sound/effects/supermatter.ogg', 50, TRUE)
consume_returns(damage_increase = blob.get_integrity() * 0.05)
if(blob.get_integrity() > 100)
blob.visible_message(span_danger("\The [blob] strikes at \the [atom_source] and flinches away!"),
span_hear("You hear a loud crack as you are washed with a wave of heat."))
blob.take_damage(100, BURN)
else
blob.visible_message(span_danger("\The [blob] strikes at \the [atom_source] and rapidly flashes to ash."),
span_hear("You hear a loud crack as you are washed with a wave of heat."))
consume(atom_source, blob)
/datum/component/supermatter_crystal/proc/paw_hit(datum/source, mob/user, list/modifiers)
SIGNAL_HANDLER
if(isliving(user))
var/mob/living/living_mob = user
if(living_mob.incorporeal_move || HAS_TRAIT(living_mob, TRAIT_GODMODE))
return
if(isalien(user))
dust_mob(source, user, cause = "alien attack")
return
dust_mob(source, user, cause = "monkey attack")
/datum/component/supermatter_crystal/proc/animal_hit(datum/source, mob/living/simple_animal/user, list/modifiers)
SIGNAL_HANDLER
if(user.incorporeal_move || HAS_TRAIT(user, TRAIT_GODMODE))
return
var/atom/atom_source = source
var/murder
if(!user.melee_damage_upper && !user.melee_damage_lower)
murder = user.friendly_verb_continuous
else
murder = user.attack_verb_continuous
dust_mob(source, user, \
span_danger("[user] unwisely [murder] [atom_source], and [user.p_their()] body burns brilliantly before flashing into ash!"), \
span_userdanger("You unwisely touch [atom_source], and your vision glows brightly as your body crumbles to dust. Oops."), \
"simple animal attack")
/datum/component/supermatter_crystal/proc/hulk_hit(datum/source, mob/user)
SIGNAL_HANDLER
dust_mob(source, user, cause = "hulk attack")
/datum/component/supermatter_crystal/proc/unarmed_hit(datum/source, mob/user, list/modifiers)
SIGNAL_HANDLER
if(isliving(user))
var/mob/living/living_mob = user
if(living_mob.incorporeal_move || HAS_TRAIT(living_mob, TRAIT_GODMODE))
return
var/atom/atom_source = source
if(iscyborg(user) && atom_source.Adjacent(user))
dust_mob(source, user, cause = "cyborg attack")
return
if(iscameramob(user))
return
if(islarva(user))
dust_mob(source, user, cause = "larva attack")
return
/datum/component/supermatter_crystal/proc/hand_hit(datum/source, mob/living/user, list/modifiers)
SIGNAL_HANDLER
if(user.incorporeal_move || HAS_TRAIT(user, TRAIT_GODMODE))
return
if(user.zone_selected != BODY_ZONE_PRECISE_MOUTH)
dust_mob(source, user, cause = "hand")
return
var/atom/atom_source = source
if(!user.is_mouth_covered())
if(user.combat_mode)
dust_mob(source, user,
span_danger("As [user] tries to take a bite out of [atom_source] everything goes silent before [user.p_their()] body starts to glow and burst into flames before flashing to ash."),
span_userdanger("You try to take a bite out of [atom_source], but find [p_them()] far too hard to get anywhere before everything starts burning and your ears fill with ringing!"),
"attempted bite"
)
return
var/obj/item/organ/tongue/licking_tongue = user.get_organ_slot(ORGAN_SLOT_TONGUE)
if(licking_tongue)
dust_mob(source, user,
span_danger("As [user] hesitantly leans in and licks [atom_source] everything goes silent before [user.p_their()] body starts to glow and burst into flames before flashing to ash!"),
span_userdanger("You tentatively lick [atom_source], but you can't figure out what it tastes like before everything starts burning and your ears fill with ringing!"),
"attempted lick"
)
return
var/obj/item/bodypart/head/forehead = user.get_bodypart(BODY_ZONE_HEAD)
if(forehead)
dust_mob(source, user,
span_danger("As [user]'s forehead bumps into [atom_source], inducing a resonance... Everything goes silent before [user.p_their()] [forehead] flashes to ash!"),
span_userdanger("You feel your forehead bump into [atom_source] and everything suddenly goes silent. As your head fills with ringing you come to realize that that was not a wise decision."),
"failed lick"
)
return
dust_mob(source, user,
span_danger("[user] leans in and tries to lick [atom_source], inducing a resonance... [user.p_their()] body starts to glow and burst into flames before flashing into dust!"),
span_userdanger("You lean in and try to lick [atom_source]. Everything starts burning and all you can hear is ringing. Your last thought is \"That was not a wise decision.\""),
"failed lick"
)
/datum/component/supermatter_crystal/proc/attackby_hit(datum/source, obj/item/item, mob/living/user, params)
SIGNAL_HANDLER
var/atom/atom_source = source
if(!istype(item) || (item.item_flags & ABSTRACT) || !istype(user))
return
if(is_type_in_typecache(item, sm_item_whitelist))
return FALSE
if(istype(item, /obj/item/cigarette))
var/obj/item/cigarette/cig = item
var/clumsy = HAS_TRAIT(user, TRAIT_CLUMSY)
if(clumsy)
var/obj/item/bodypart/dust_arm = user.get_active_hand()
dust_arm.dismember()
user.visible_message(span_danger("The [item] flashes out of existence on contact with \the [atom_source], resonating with a horrible sound..."),\
span_danger("Oops! The [item] flashes out of existence on contact with \the [atom_source], taking your arm with it! That was clumsy of you!"))
playsound(atom_source, 'sound/effects/supermatter.ogg', 150, TRUE)
consume(atom_source, dust_arm)
qdel(item)
return
if(cig.lit || user.combat_mode)
user.visible_message(span_danger("A hideous sound echoes as [item] is ashed out on contact with \the [atom_source]. That didn't seem like a good idea..."))
playsound(atom_source, 'sound/effects/supermatter.ogg', 150, TRUE)
consume(atom_source, item)
radiation_pulse(atom_source, max_range = 3, threshold = 0.1, chance = 50)
return
else
cig.light()
user.visible_message(span_danger("As [user] lights \their [item] on \the [atom_source], silence fills the room..."),\
span_danger("Time seems to slow to a crawl as you touch \the [atom_source] with \the [item].</span>\n<span class='notice'>\The [item] flashes alight with an eerie energy as you nonchalantly lift your hand away from \the [atom_source]. Damn."))
playsound(atom_source, 'sound/effects/supermatter.ogg', 50, TRUE)
radiation_pulse(atom_source, max_range = 1, threshold = 0, chance = 100)
return
if(user.dropItemToGround(item))
user.visible_message(span_danger("As [user] touches \the [atom_source] with \a [item], silence fills the room..."),\
span_userdanger("You touch \the [atom_source] with \the [item], and everything suddenly goes silent.</span>\n<span class='notice'>\The [item] flashes into dust as you flinch away from \the [atom_source]."),\
span_hear("Everything suddenly goes silent."))
user.investigate_log("has been attacked ([item]) by [key_name(user)]", INVESTIGATE_ENGINE)
consume(atom_source, item)
playsound(get_turf(atom_source), 'sound/effects/supermatter.ogg', 50, TRUE)
radiation_pulse(atom_source, max_range = 3, threshold = 0.1, chance = 50)
return
if(atom_source.Adjacent(user)) //if the item is stuck to the person, kill the person too instead of eating just the item.
if(user.incorporeal_move || HAS_TRAIT(user, TRAIT_GODMODE))
return
var/vis_msg = span_danger("[user] reaches out and touches [atom_source] with [item], inducing a resonance... [item] starts to glow briefly before the light continues up to [user]'s body. [user.p_They()] burst[user.p_s()] into flames before flashing into dust!")
var/mob_msg = span_userdanger("You reach out and touch [atom_source] with [item]. Everything starts burning and all you can hear is ringing. Your last thought is \"That was not a wise decision.\"")
dust_mob(source, user, vis_msg, mob_msg)
/datum/component/supermatter_crystal/proc/tool_hit(datum/source, mob/user, obj/item/tool)
SIGNAL_HANDLER
if(tool_act_callback)
tool_act_callback.Invoke(user, tool)
return ITEM_INTERACT_BLOCKING
attackby_hit(source, tool, user)
/datum/component/supermatter_crystal/proc/bumped_hit(datum/source, atom/movable/hit_object)
SIGNAL_HANDLER
if(isliving(hit_object))
var/mob/living/hit_mob = hit_object
if(hit_mob.incorporeal_move || HAS_TRAIT(hit_mob, TRAIT_GODMODE))
return
var/atom/atom_source = source
var/obj/machinery/power/supermatter_crystal/our_supermatter = parent // Why is this a component?
if(istype(our_supermatter))
our_supermatter.log_activation(who = hit_object)
if(isliving(hit_object))
hit_object.visible_message(span_danger("\The [hit_object] slams into \the [atom_source] inducing a resonance... [hit_object.p_their()] body starts to glow and burst into flames before flashing into dust!"),
span_userdanger("You slam into \the [atom_source] as your ears are filled with unearthly ringing. Your last thought is \"Oh, fuck.\""),
span_hear("You hear an unearthly noise as a wave of heat washes over you."))
else if(isobj(hit_object) && !iseffect(hit_object))
hit_object.visible_message(span_danger("\The [hit_object] smacks into \the [atom_source] and rapidly flashes to ash."), null,
span_hear("You hear a loud crack as you are washed with a wave of heat."))
else
return
playsound(get_turf(atom_source), 'sound/effects/supermatter.ogg', 50, TRUE)
consume(atom_source, hit_object)
/datum/component/supermatter_crystal/proc/intercept_z_fall(datum/source, list/falling_movables, levels)
SIGNAL_HANDLER
for(var/atom/movable/hit_object as anything in falling_movables)
if(parent == hit_object)
return
bumped_hit(parent, hit_object)
return FALL_INTERCEPTED | FALL_NO_MESSAGE
/datum/component/supermatter_crystal/proc/on_z_impact(datum/source, turf/impacted_turf, levels)
SIGNAL_HANDLER
var/atom/atom_source = source
for(var/mob/living/poor_target in impacted_turf)
consume(atom_source, poor_target)
playsound(get_turf(atom_source), 'sound/effects/supermatter.ogg', 50, TRUE)
poor_target.visible_message(span_danger("\The [atom_source] slams into \the [poor_target] out of nowhere inducing a resonance... [poor_target.p_their()] body starts to glow and burst into flames before flashing into dust!"),
span_userdanger("\The [atom_source] slams into you out of nowhere as your ears are filled with unearthly ringing. Your last thought is \"The fuck.\""),
span_hear("You hear an unearthly noise as a wave of heat washes over you."))
for(var/atom/movable/hit_object as anything in impacted_turf)
if(parent == hit_object)
return
if(iseffect(hit_object))
continue
consume(atom_source, hit_object)
playsound(get_turf(atom_source), 'sound/effects/supermatter.ogg', 50, TRUE)
atom_source.visible_message(span_danger("\The [atom_source], smacks into the plating out of nowhere, reducing everything below to ash."), null,
span_hear("You hear a loud crack as you are washed with a wave of heat."))
/datum/component/supermatter_crystal/proc/dust_mob(datum/source, mob/living/nom, vis_msg, mob_msg, cause)
if(nom.incorporeal_move || HAS_TRAIT(nom, TRAIT_GODMODE)) //try to keep supermatter sliver's + hemostat's dust conditions in sync with this too
return
var/atom/atom_source = source
if(!vis_msg)
vis_msg = span_danger("[nom] reaches out and touches [atom_source], inducing a resonance... [nom.p_their()] body starts to glow and burst into flames before flashing into dust!")
if(!mob_msg)
mob_msg = span_userdanger("You reach out and touch [atom_source]. Everything starts burning and all you can hear is ringing. Your last thought is \"That was not a wise decision.\"")
if(!cause)
cause = "contact"
nom.visible_message(vis_msg, mob_msg, span_hear("You hear an unearthly noise as a wave of heat washes over you."))
atom_source.investigate_log("has been attacked ([cause]) by [key_name(nom)]", INVESTIGATE_ENGINE)
add_memory_in_range(atom_source, 7, /datum/memory/witness_supermatter_dusting, protagonist = nom, antagonist = atom_source)
playsound(get_turf(atom_source), 'sound/effects/supermatter.ogg', 50, TRUE)
consume(atom_source, nom)
/datum/component/supermatter_crystal/proc/consume(atom/source, atom/movable/consumed_object)
if(consumed_object.flags_1 & SUPERMATTER_IGNORES_1)
return
if(HAS_TRAIT(consumed_object, TRAIT_GODMODE))
return
var/atom/atom_source = source
SEND_SIGNAL(consumed_object, COMSIG_SUPERMATTER_CONSUMED, atom_source)
var/object_size = 0
var/matter_increase = 0
var/damage_increase = 0
var/radiation_range = 6
var/effects_calculated = FALSE
if(isliving(consumed_object))
var/mob/living/consumed_mob = consumed_object
object_size = consumed_mob.mob_size + 2
message_admins("[atom_source] has consumed [key_name_admin(consumed_mob)] [ADMIN_JMP(atom_source)].")
atom_source.investigate_log("has consumed [key_name(consumed_mob)].", INVESTIGATE_ENGINE)
consumed_mob.investigate_log("has been dusted by [atom_source].", INVESTIGATE_DEATHS)
if(istype(consumed_mob, /mob/living/basic/parrot/poly)) // Dusting Poly creates a power surge
force_event(/datum/round_event_control/supermatter_surge/poly, "Poly's revenge")
notify_ghosts(
"[consumed_mob.real_name] has been dusted by [atom_source]!",
source = atom_source,
header = "Polytechnical Difficulties",
)
consumed_mob.dust(force = TRUE)
matter_increase += 100 * object_size * 2
if(is_clown_job(consumed_mob.mind?.assigned_role))
damage_increase += rand(-30, 30) * 2 // HONK
effects_calculated = TRUE
else if(isobj(consumed_object))
if(!iseffect(consumed_object) && !(consumed_object.flags_1 & SUPERMATTER_NOLOGS_1)) //BUBBER EDIT | NOLOGS flag Addition to ignore items engineers like to feed the SM :3
var/suspicion = ""
if(consumed_object.fingerprintslast)
suspicion = "last touched by [consumed_object.fingerprintslast]"
message_admins("[atom_source] has consumed [consumed_object], [suspicion] [ADMIN_JMP(atom_source)].")
atom_source.investigate_log("has consumed [consumed_object] - [suspicion].", INVESTIGATE_ENGINE)
var/is_nuke = FALSE
if (consumed_object.type == /obj/item/nuke_core) // No subtypes, the supermatter sliver shouldn't trigger this
is_nuke = TRUE
else if (istype(consumed_object, /obj/machinery/nuclearbomb))
var/obj/machinery/nuclearbomb/bomb = consumed_object
is_nuke = !!bomb.core
if (is_nuke)
object_size = 10
radiation_range *= 2
matter_increase += 10000
damage_increase += 110
effects_calculated = TRUE
qdel(consumed_object)
if(!iseffect(consumed_object) && !effects_calculated)
if(isitem(consumed_object))
var/obj/item/consumed_item = consumed_object
object_size = consumed_item.w_class
matter_increase += 70 * object_size
else
matter_increase += min(0.5 * consumed_object.max_integrity, 1000)
//Some poor sod got eaten, go ahead and irradiate people nearby.
radiation_pulse(atom_source, max_range = radiation_range, threshold = 1.2 / max(object_size, 1), chance = 10 * object_size)
for(var/mob/living/near_mob in range(10))
atom_source.investigate_log("has irradiated [key_name(near_mob)] after consuming [consumed_object].", INVESTIGATE_ENGINE)
if (HAS_TRAIT(near_mob, TRAIT_RADIMMUNE) || issilicon(near_mob))
continue
if(ishuman(near_mob) && SSradiation.wearing_rad_protected_clothing(near_mob))
continue
if(near_mob in view())
near_mob.show_message(span_danger("As \the [atom_source] slowly stops resonating, you find your skin covered in new radiation burns."), MSG_VISUAL,
span_danger("The unearthly ringing subsides and you find your skin covered in new radiation burns."), MSG_AUDIBLE)
else
near_mob.show_message(span_hear("An unearthly ringing fills your ears, and you find your skin covered in new radiation burns."), MSG_AUDIBLE)
consume_returns(matter_increase, damage_increase)
var/obj/machinery/power/supermatter_crystal/our_crystal = parent
if(istype(our_crystal))
our_crystal.log_activation(who = consumed_object)
/datum/component/supermatter_crystal/proc/consume_returns(matter_increase = 0, damage_increase = 0)
if(consume_callback)
consume_callback.Invoke(matter_increase, damage_increase)