mirror of
https://github.com/Bubberstation/Bubberstation.git
synced 2025-12-17 13:12:37 +00:00
* MODLink System (+ NWTLMM) (#77639) ## About The Pull Request A pact made with `@ Kapu1178` Small changes you should not care about: RD MODsuit outfit (admin only) no longer has a beret that blocks the activation of the suit The beret used by death squad officers no longer is blocked from being put on a hat stabilizer module Admins can now Shear matrices of objects in Modify Transform Multitool buffers have been a little refactored to use a setter proc that saves them from causing hard dels Cooler stuff: A revival and remake of [Nobody Wants To Learn Matrix Math](https://github.com/tgstation/tgstation/pull/59103), this time with additional tooling for quick matrix calculations.  The MODLink system, available through every MODsuit and MODLink scryers (a neck item obtainable from advanced modsuit research or charliestation) Let's you make a holographic call with any other MODLink user, where you can chat in realtime and see what's up with em   ## Why It's Good For The Game Adds a fun way for the crew to communicate with each other that can be done in real-time with relative privacy compared to radio. ## Changelog 🆑 Fikou, Armhulen, Sheets (+rep for Mothblocks and Potato) fix: RD MODsuit outfit (admin only) no longer has a beret that blocks the activation of the suit fix: The beret used by death squad officers no longer is blocked from being put on a hat stabilizer module admin: Admins can now Shear matrices of objects in Modify Transform admin: Admins now have access to Test Matrices in the VV dropdown, an all-in-one tool for editing transforms. add: MODLink system, available through scryers (from RnD and Charlie Station) and through MODsuits. Lets you call people with holographs! /🆑 * MODLink System (+ NWTLMM) --------- Co-authored-by: Fikou <23585223+Fikou@users.noreply.github.com>
272 lines
12 KiB
Plaintext
272 lines
12 KiB
Plaintext
#define MOD_ACTIVATION_STEP_FLAGS IGNORE_USER_LOC_CHANGE|IGNORE_TARGET_LOC_CHANGE|IGNORE_HELD_ITEM|IGNORE_INCAPACITATED|IGNORE_SLOWDOWNS
|
|
|
|
/// Creates a radial menu from which the user chooses parts of the suit to deploy/retract. Repeats until all parts are extended or retracted.
|
|
/obj/item/mod/control/proc/choose_deploy(mob/user)
|
|
if(!length(mod_parts))
|
|
return
|
|
var/list/display_names = list()
|
|
var/list/items = list()
|
|
for(var/obj/item/part as anything in mod_parts)
|
|
display_names[part.name] = REF(part)
|
|
var/image/part_image = image(icon = part.icon, icon_state = part.icon_state)
|
|
if(part.loc != src)
|
|
part_image.underlays += image(icon = 'icons/hud/radial.dmi', icon_state = "module_active")
|
|
items += list(part.name = part_image)
|
|
var/pick = show_radial_menu(user, src, items, custom_check = FALSE, require_near = TRUE, tooltips = TRUE)
|
|
if(!pick)
|
|
return
|
|
var/part_reference = display_names[pick]
|
|
var/obj/item/part = locate(part_reference) in mod_parts
|
|
if(!istype(part) || user.incapacitated())
|
|
return
|
|
if(activating) // SKYRAT EDIT - RETRACTABLE EVERYTHING
|
|
balloon_alert(user, "deactivate the suit first!")
|
|
playsound(src, 'sound/machines/scanbuzz.ogg', 25, TRUE, SILENCED_SOUND_EXTRARANGE)
|
|
return
|
|
var/parts_to_check = mod_parts - part
|
|
if(part.loc == src)
|
|
deploy(user, part)
|
|
on_mod_deployed(user)
|
|
for(var/obj/item/checking_part as anything in parts_to_check)
|
|
if(checking_part.loc != src)
|
|
continue
|
|
choose_deploy(user)
|
|
break
|
|
else
|
|
retract(user, part)
|
|
on_mod_retracted(user)
|
|
for(var/obj/item/checking_part as anything in parts_to_check)
|
|
if(checking_part.loc == src)
|
|
continue
|
|
choose_deploy(user)
|
|
break
|
|
|
|
/// Quickly deploys all parts (or retracts if all are on the wearer)
|
|
/obj/item/mod/control/proc/quick_deploy(mob/user)
|
|
if(activating) // SKYRAT EDIT - RETRACTABLE EVERYTHING
|
|
balloon_alert(user, "deactivate the suit first!")
|
|
playsound(src, 'sound/machines/scanbuzz.ogg', 25, TRUE, SILENCED_SOUND_EXTRARANGE)
|
|
return FALSE
|
|
var/deploy = TRUE
|
|
for(var/obj/item/part as anything in mod_parts)
|
|
if(part.loc == src)
|
|
continue
|
|
deploy = FALSE
|
|
break
|
|
for(var/obj/item/part as anything in mod_parts)
|
|
if(deploy && part.loc == src)
|
|
deploy(null, part)
|
|
else if(!deploy && part.loc != src)
|
|
retract(null, part)
|
|
wearer.visible_message(span_notice("[wearer]'s [src] [deploy ? "deploys" : "retracts"] its' parts with a mechanical hiss."),
|
|
span_notice("[src] [deploy ? "deploys" : "retracts"] its' parts with a mechanical hiss."),
|
|
span_hear("You hear a mechanical hiss."))
|
|
playsound(src, 'sound/mecha/mechmove03.ogg', 25, TRUE, SHORT_RANGE_SOUND_EXTRARANGE)
|
|
if(deploy)
|
|
on_mod_deployed(user)
|
|
else
|
|
on_mod_retracted(user)
|
|
return TRUE
|
|
|
|
/// Deploys a part of the suit onto the user.
|
|
/obj/item/mod/control/proc/deploy(mob/user, obj/item/part)
|
|
if(!wearer)
|
|
playsound(src, 'sound/machines/scanbuzz.ogg', 25, TRUE, SILENCED_SOUND_EXTRARANGE)
|
|
return FALSE // pAI is trying to deploy it from your hands
|
|
if(part.loc != src)
|
|
if(!user)
|
|
return FALSE
|
|
balloon_alert(user, "[part.name] already deployed!")
|
|
playsound(src, 'sound/machines/scanbuzz.ogg', 25, TRUE, SILENCED_SOUND_EXTRARANGE)
|
|
if(part in overslotting_parts)
|
|
var/obj/item/overslot = wearer.get_item_by_slot(part.slot_flags)
|
|
if(overslot)
|
|
overslotting_parts[part] = overslot
|
|
wearer.transferItemToLoc(overslot, part, force = TRUE)
|
|
RegisterSignal(part, COMSIG_ATOM_EXITED, PROC_REF(on_overslot_exit))
|
|
if(wearer.equip_to_slot_if_possible(part, part.slot_flags, qdel_on_fail = FALSE, disable_warning = TRUE))
|
|
ADD_TRAIT(part, TRAIT_NODROP, MOD_TRAIT)
|
|
if(!user)
|
|
return TRUE
|
|
wearer.visible_message(span_notice("[wearer]'s [part.name] deploy[part.p_s()] with a mechanical hiss."),
|
|
span_notice("[part] deploy[part.p_s()] with a mechanical hiss."),
|
|
span_hear("You hear a mechanical hiss."))
|
|
playsound(src, 'sound/mecha/mechmove03.ogg', 25, TRUE, SHORT_RANGE_SOUND_EXTRARANGE)
|
|
return TRUE
|
|
else
|
|
if(!user)
|
|
return FALSE
|
|
balloon_alert(user, "bodypart clothed!")
|
|
playsound(src, 'sound/machines/scanbuzz.ogg', 25, TRUE, SILENCED_SOUND_EXTRARANGE)
|
|
return FALSE
|
|
|
|
/// Retract a part of the suit from the user.
|
|
/obj/item/mod/control/proc/retract(mob/user, obj/item/part)
|
|
if(part.loc == src)
|
|
if(!user)
|
|
return FALSE
|
|
balloon_alert(user, "[part.name] already retracted!")
|
|
playsound(src, 'sound/machines/scanbuzz.ogg', 25, TRUE, SILENCED_SOUND_EXTRARANGE)
|
|
REMOVE_TRAIT(part, TRAIT_NODROP, MOD_TRAIT)
|
|
wearer.transferItemToLoc(part, src, force = TRUE)
|
|
if(overslotting_parts[part])
|
|
UnregisterSignal(part, COMSIG_ATOM_EXITED)
|
|
var/obj/item/overslot = overslotting_parts[part]
|
|
if(!wearer.equip_to_slot_if_possible(overslot, overslot.slot_flags, qdel_on_fail = FALSE, disable_warning = TRUE))
|
|
wearer.dropItemToGround(overslot, force = TRUE, silent = TRUE)
|
|
overslotting_parts[part] = null
|
|
// SKYRAT EDIT START - Avoiding exploits with the modules staying active when any of the parts are retracted.
|
|
for(var/obj/item/mod/module/module as anything in modules)
|
|
if(module.active)
|
|
module.on_deactivation(display_message = !!user)
|
|
// SKYRAT EDIT END
|
|
if(!user)
|
|
return
|
|
wearer.visible_message(span_notice("[wearer]'s [part.name] retract[part.p_s()] back into [src] with a mechanical hiss."),
|
|
span_notice("[part] retract[part.p_s()] back into [src] with a mechanical hiss."),
|
|
span_hear("You hear a mechanical hiss."))
|
|
playsound(src, 'sound/mecha/mechmove03.ogg', 25, TRUE, SHORT_RANGE_SOUND_EXTRARANGE)
|
|
|
|
/// Starts the activation sequence, where parts of the suit activate one by one until the whole suit is on
|
|
/obj/item/mod/control/proc/toggle_activate(mob/user, force_deactivate = FALSE)
|
|
if(!wearer)
|
|
if(!force_deactivate)
|
|
balloon_alert(user, "equip suit first!")
|
|
playsound(src, 'sound/machines/scanbuzz.ogg', 25, TRUE, SILENCED_SOUND_EXTRARANGE)
|
|
return FALSE
|
|
if(!force_deactivate && (SEND_SIGNAL(src, COMSIG_MOD_ACTIVATE, user) & MOD_CANCEL_ACTIVATE))
|
|
playsound(src, 'sound/machines/scanbuzz.ogg', 25, TRUE, SILENCED_SOUND_EXTRARANGE)
|
|
return FALSE
|
|
for(var/obj/item/part as anything in mod_parts)
|
|
if(!force_deactivate && part.loc == src)
|
|
balloon_alert(user, "deploy all parts first!")
|
|
playsound(src, 'sound/machines/scanbuzz.ogg', 25, TRUE, SILENCED_SOUND_EXTRARANGE)
|
|
return FALSE
|
|
if(locked && !active && !allowed(user) && !force_deactivate)
|
|
balloon_alert(user, "access insufficient!")
|
|
playsound(src, 'sound/machines/scanbuzz.ogg', 25, TRUE, SILENCED_SOUND_EXTRARANGE)
|
|
return FALSE
|
|
if(!get_charge() && !force_deactivate)
|
|
balloon_alert(user, "suit not powered!")
|
|
playsound(src, 'sound/machines/scanbuzz.ogg', 25, TRUE, SILENCED_SOUND_EXTRARANGE)
|
|
return FALSE
|
|
if(open && !force_deactivate)
|
|
balloon_alert(user, "close the suit panel!")
|
|
playsound(src, 'sound/machines/scanbuzz.ogg', 25, TRUE, SILENCED_SOUND_EXTRARANGE)
|
|
return FALSE
|
|
if(activating)
|
|
if(!force_deactivate)
|
|
balloon_alert(user, "suit already [active ? "shutting down" : "starting up"]!")
|
|
playsound(src, 'sound/machines/scanbuzz.ogg', 25, TRUE, SILENCED_SOUND_EXTRARANGE)
|
|
return FALSE
|
|
for(var/obj/item/mod/module/module as anything in modules)
|
|
if(!module.active || (module.allow_flags & MODULE_ALLOW_INACTIVE))
|
|
continue
|
|
module.on_deactivation(display_message = FALSE)
|
|
mod_link.end_call()
|
|
activating = TRUE
|
|
to_chat(wearer, span_notice("MODsuit [active ? "shutting down" : "starting up"]."))
|
|
|
|
if (ai_assistant)
|
|
to_chat(ai_assistant, span_notice("MODsuit [active ? "shutting down" : "starting up"]."))
|
|
if(do_after(wearer, activation_step_time, wearer, MOD_ACTIVATION_STEP_FLAGS, extra_checks = CALLBACK(src, PROC_REF(has_wearer))))
|
|
to_chat(wearer, span_notice("[boots] [active ? "relax their grip on your legs" : "seal around your feet"]."))
|
|
playsound(src, 'sound/mecha/mechmove03.ogg', 25, TRUE, SHORT_RANGE_SOUND_EXTRARANGE)
|
|
seal_part(boots, seal = !active)
|
|
if(do_after(wearer, activation_step_time, wearer, MOD_ACTIVATION_STEP_FLAGS, extra_checks = CALLBACK(src, PROC_REF(has_wearer))))
|
|
to_chat(wearer, span_notice("[gauntlets] [active ? "become loose around your fingers" : "tighten around your fingers and wrists"]."))
|
|
playsound(src, 'sound/mecha/mechmove03.ogg', 25, TRUE, SHORT_RANGE_SOUND_EXTRARANGE)
|
|
seal_part(gauntlets, seal = !active)
|
|
if(do_after(wearer, activation_step_time, wearer, MOD_ACTIVATION_STEP_FLAGS, extra_checks = CALLBACK(src, PROC_REF(has_wearer))))
|
|
to_chat(wearer, span_notice("[chestplate] [active ? "releases your chest" : "cinches tightly against your chest"]."))
|
|
playsound(src, 'sound/mecha/mechmove03.ogg', 25, TRUE, SHORT_RANGE_SOUND_EXTRARANGE)
|
|
seal_part(chestplate, seal = !active)
|
|
if(do_after(wearer, activation_step_time, wearer, MOD_ACTIVATION_STEP_FLAGS, extra_checks = CALLBACK(src, PROC_REF(has_wearer))))
|
|
to_chat(wearer, span_notice("[helmet] hisses [active ? "open" : "closed"]."))
|
|
playsound(src, 'sound/mecha/mechmove03.ogg', 25, TRUE, SHORT_RANGE_SOUND_EXTRARANGE)
|
|
seal_part(helmet, seal = !active)
|
|
if(do_after(wearer, activation_step_time, wearer, MOD_ACTIVATION_STEP_FLAGS, extra_checks = CALLBACK(src, PROC_REF(has_wearer))))
|
|
to_chat(wearer, span_notice("Systems [active ? "shut down. Parts unsealed. Goodbye" : "started up. Parts sealed. Welcome"], [wearer]."))
|
|
if(ai_assistant)
|
|
to_chat(ai_assistant, span_notice("<b>SYSTEMS [active ? "DEACTIVATED. GOODBYE" : "ACTIVATED. WELCOME"]: \"[ai_assistant]\"</b>"))
|
|
finish_activation(on = !active)
|
|
if(active)
|
|
playsound(src, 'sound/machines/synth_yes.ogg', 50, TRUE, SHORT_RANGE_SOUND_EXTRARANGE, frequency = 6000)
|
|
if(!malfunctioning)
|
|
wearer.playsound_local(get_turf(src), 'sound/mecha/nominal.ogg', 50)
|
|
else
|
|
playsound(src, 'sound/machines/synth_no.ogg', 50, TRUE, SHORT_RANGE_SOUND_EXTRARANGE, frequency = 6000)
|
|
activating = FALSE
|
|
SEND_SIGNAL(src, COMSIG_MOD_TOGGLED, user)
|
|
return TRUE
|
|
|
|
///Seals or unseals the given part
|
|
/obj/item/mod/control/proc/seal_part(obj/item/clothing/part, seal)
|
|
if(seal)
|
|
part.icon_state = "[skin]-[part.base_icon_state]-sealed"
|
|
part.clothing_flags |= part.visor_flags
|
|
part.flags_inv |= part.visor_flags_inv
|
|
part.flags_cover |= part.visor_flags_cover
|
|
part.heat_protection = initial(part.heat_protection)
|
|
part.cold_protection = initial(part.cold_protection)
|
|
part.alternate_worn_layer = null
|
|
else
|
|
part.icon_state = "[skin]-[part.base_icon_state]"
|
|
part.flags_cover &= ~part.visor_flags_cover
|
|
part.flags_inv &= ~part.visor_flags_inv
|
|
part.clothing_flags &= ~part.visor_flags
|
|
part.heat_protection = NONE
|
|
part.cold_protection = NONE
|
|
part.alternate_worn_layer = mod_parts[part]
|
|
if(part == boots)
|
|
wearer.update_worn_shoes()
|
|
if(part == gauntlets)
|
|
wearer.update_worn_gloves()
|
|
if(part == chestplate)
|
|
wearer.update_worn_oversuit()
|
|
wearer.update_worn_undersuit()
|
|
if(part == helmet)
|
|
wearer.update_worn_head()
|
|
wearer.update_worn_mask()
|
|
wearer.update_worn_glasses()
|
|
wearer.update_body_parts()
|
|
// Close internal air tank if MOD helmet is unsealed and was the only breathing apparatus.
|
|
if (!seal && wearer?.invalid_internals())
|
|
wearer.cutoff_internals()
|
|
|
|
/// Finishes the suit's activation
|
|
/obj/item/mod/control/proc/finish_activation(on)
|
|
active = on
|
|
if(active)
|
|
for(var/obj/item/mod/module/module as anything in modules)
|
|
module.on_suit_activation()
|
|
else
|
|
for(var/obj/item/mod/module/module as anything in modules)
|
|
module.on_suit_deactivation()
|
|
update_speed()
|
|
update_icon_state()
|
|
wearer.update_clothing(slot_flags)
|
|
|
|
/// Quickly deploys all the suit parts and if successful, seals them and turns on the suit. Intended mostly for outfits.
|
|
/obj/item/mod/control/proc/quick_activation()
|
|
var/seal = TRUE
|
|
for(var/obj/item/part as anything in mod_parts)
|
|
if(!deploy(null, part))
|
|
seal = FALSE
|
|
if(!seal)
|
|
return
|
|
for(var/obj/item/part as anything in mod_parts)
|
|
seal_part(part, seal = TRUE)
|
|
finish_activation(on = TRUE)
|
|
|
|
/obj/item/mod/control/proc/has_wearer()
|
|
return wearer
|
|
|
|
/obj/item/mod/control/proc/on_mod_deployed(mob/user)
|
|
SEND_SIGNAL(src, COMSIG_MOD_DEPLOYED, user)
|
|
|
|
/obj/item/mod/control/proc/on_mod_retracted(mob/user)
|
|
SEND_SIGNAL(src, COMSIG_MOD_RETRACTED, user)
|
|
|
|
#undef MOD_ACTIVATION_STEP_FLAGS
|