mirror of
https://github.com/Bubberstation/Bubberstation.git
synced 2026-08-22 04:30:44 +01: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! /🆑
This commit is contained in:
@@ -158,6 +158,7 @@
|
||||
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)
|
||||
@@ -227,17 +228,15 @@
|
||||
if (!seal && wearer?.invalid_internals())
|
||||
wearer.cutoff_internals()
|
||||
|
||||
/// Finishes the suit's activation, starts processing
|
||||
/// 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()
|
||||
START_PROCESSING(SSobj, src)
|
||||
else
|
||||
for(var/obj/item/mod/module/module as anything in modules)
|
||||
module.on_suit_deactivation()
|
||||
STOP_PROCESSING(SSobj, src)
|
||||
update_speed()
|
||||
update_icon_state()
|
||||
wearer.update_clothing(slot_flags)
|
||||
|
||||
@@ -84,6 +84,10 @@
|
||||
var/obj/item/mod/module/selected_module
|
||||
/// AI or pAI mob inhabiting the MOD.
|
||||
var/mob/living/silicon/ai_assistant
|
||||
/// The MODlink datum, letting us call people from the suit.
|
||||
var/datum/mod_link/mod_link
|
||||
/// The starting MODlink frequency, overridden on subtypes that want it to be something.
|
||||
var/starting_frequency = null
|
||||
/// Delay between moves as AI.
|
||||
var/static/movedelay = 0
|
||||
/// Cooldown for AI moves.
|
||||
@@ -141,10 +145,10 @@
|
||||
for(var/obj/item/mod/module/module as anything in theme.inbuilt_modules)
|
||||
module = new module(src)
|
||||
install(module)
|
||||
START_PROCESSING(SSobj, src)
|
||||
|
||||
/obj/item/mod/control/Destroy()
|
||||
if(active)
|
||||
STOP_PROCESSING(SSobj, src)
|
||||
STOP_PROCESSING(SSobj, src)
|
||||
for(var/obj/item/mod/module/module as anything in modules)
|
||||
uninstall(module, deleting = TRUE)
|
||||
for(var/obj/item/part as anything in mod_parts)
|
||||
@@ -173,6 +177,7 @@
|
||||
if(core)
|
||||
QDEL_NULL(core)
|
||||
QDEL_NULL(wires)
|
||||
QDEL_NULL(mod_link)
|
||||
return ..()
|
||||
|
||||
/obj/item/mod/control/atom_destruction(damage_flag)
|
||||
@@ -217,6 +222,7 @@
|
||||
. += span_notice("You could install an AI or pAI using their <b>storage card</b>.")
|
||||
else if(isAI(ai_assistant))
|
||||
. += span_notice("You could remove [ai_assistant] with an <b>intellicard</b>.")
|
||||
. += span_notice("You could copy/set link frequency with a <b>multitool</b>.")
|
||||
. += span_notice("<i>You could examine it more thoroughly...</i>")
|
||||
|
||||
/obj/item/mod/control/examine_more(mob/user)
|
||||
@@ -226,13 +232,17 @@
|
||||
/obj/item/mod/control/process(seconds_per_tick)
|
||||
if(seconds_electrified > MACHINE_NOT_ELECTRIFIED)
|
||||
seconds_electrified--
|
||||
if(mod_link.link_call)
|
||||
subtract_charge((DEFAULT_CHARGE_DRAIN * 0.25) * seconds_per_tick)
|
||||
if(!active)
|
||||
return
|
||||
if(!get_charge() && active && !activating)
|
||||
power_off()
|
||||
return PROCESS_KILL
|
||||
return
|
||||
var/malfunctioning_charge_drain = 0
|
||||
if(malfunctioning)
|
||||
malfunctioning_charge_drain = rand(1,20)
|
||||
subtract_charge((charge_drain + malfunctioning_charge_drain)*seconds_per_tick)
|
||||
subtract_charge((charge_drain + malfunctioning_charge_drain) * seconds_per_tick)
|
||||
update_charge_alert()
|
||||
for(var/obj/item/mod/module/module as anything in modules)
|
||||
if(malfunctioning && module.active && SPT_PROB(5, seconds_per_tick))
|
||||
@@ -493,6 +503,7 @@
|
||||
retract(null, part)
|
||||
if(active)
|
||||
finish_activation(on = FALSE)
|
||||
mod_link?.end_call()
|
||||
var/mob/old_wearer = wearer
|
||||
unset_wearer()
|
||||
old_wearer.temporarilyRemoveItemFromInventory(src)
|
||||
@@ -587,6 +598,10 @@
|
||||
QDEL_LIST_ASSOC_VAL(old_module.pinned_to)
|
||||
old_module.mod = null
|
||||
|
||||
/// Intended for callbacks, don't use normally, just get wearer by itself.
|
||||
/obj/item/mod/control/proc/get_wearer()
|
||||
return wearer
|
||||
|
||||
/obj/item/mod/control/proc/update_access(mob/user, obj/item/card/id/card)
|
||||
if(!allowed(user))
|
||||
balloon_alert(user, "insufficient access!")
|
||||
|
||||
@@ -0,0 +1,520 @@
|
||||
/proc/make_link_visual_generic(datum/mod_link/mod_link, proc_path)
|
||||
var/mob/living/user = mod_link.get_user_callback.Invoke()
|
||||
var/obj/effect/overlay/link_visual = new()
|
||||
link_visual.name = "holocall ([mod_link.id])"
|
||||
link_visual.mouse_opacity = MOUSE_OPACITY_TRANSPARENT
|
||||
LAZYADD(mod_link.holder.update_on_z, link_visual)
|
||||
link_visual.appearance_flags |= KEEP_TOGETHER
|
||||
link_visual.makeHologram(0.75)
|
||||
mod_link.visual_overlays = user.overlays - user.active_thinking_indicator
|
||||
link_visual.add_overlay(mod_link.visual_overlays)
|
||||
mod_link.visual = link_visual
|
||||
mod_link.holder.become_hearing_sensitive(REF(mod_link))
|
||||
mod_link.holder.RegisterSignals(user, list(COMSIG_CARBON_APPLY_OVERLAY, COMSIG_CARBON_REMOVE_OVERLAY), proc_path)
|
||||
return link_visual
|
||||
|
||||
/proc/get_link_visual_generic(datum/mod_link/mod_link, atom/movable/visuals, proc_path)
|
||||
var/mob/living/user = mod_link.get_user_callback.Invoke()
|
||||
playsound(mod_link.holder, 'sound/machines/terminal_processing.ogg', 50, vary = TRUE)
|
||||
visuals.add_overlay(mutable_appearance('icons/effects/effects.dmi', "static_base", TURF_LAYER))
|
||||
visuals.add_overlay(mutable_appearance('icons/effects/effects.dmi', "modlink", ABOVE_ALL_MOB_LAYER))
|
||||
visuals.add_filter("crop_square", 1, alpha_mask_filter(icon = icon('icons/effects/effects.dmi', "modlink_filter")))
|
||||
visuals.maptext_height = 6
|
||||
visuals.alpha = 0
|
||||
user.vis_contents += visuals
|
||||
visuals.forceMove(user)
|
||||
animate(visuals, 0.5 SECONDS, alpha = 255)
|
||||
var/datum/callback/setdir_callback = CALLBACK(mod_link.holder, proc_path)
|
||||
setdir_callback.Invoke(user, user.dir, user.dir)
|
||||
mod_link.holder.RegisterSignal(mod_link.holder.loc, COMSIG_ATOM_DIR_CHANGE, proc_path)
|
||||
|
||||
/proc/delete_link_visual_generic(datum/mod_link/mod_link)
|
||||
var/mob/living/user = mod_link.get_user_callback.Invoke()
|
||||
playsound(mod_link.get_other().holder, 'sound/machines/terminal_processing.ogg', 50, vary = TRUE, frequency = -1)
|
||||
LAZYREMOVE(mod_link.holder.update_on_z, mod_link.visual)
|
||||
mod_link.holder.lose_hearing_sensitivity(REF(mod_link))
|
||||
mod_link.holder.UnregisterSignal(user, list(COMSIG_CARBON_APPLY_OVERLAY, COMSIG_CARBON_REMOVE_OVERLAY, COMSIG_ATOM_DIR_CHANGE))
|
||||
QDEL_NULL(mod_link.visual)
|
||||
|
||||
/proc/on_user_set_dir_generic(datum/mod_link/mod_link, newdir)
|
||||
var/atom/other_visual = mod_link.get_other().visual
|
||||
if(!newdir) //can sometimes be null or 0
|
||||
return
|
||||
other_visual.setDir(SOUTH)
|
||||
other_visual.pixel_x = 0
|
||||
other_visual.pixel_y = 0
|
||||
var/matrix/new_transform = matrix()
|
||||
if(newdir & NORTH)
|
||||
other_visual.pixel_y = 13
|
||||
other_visual.layer = BELOW_MOB_LAYER
|
||||
SET_PLANE_IMPLICIT(other_visual, GAME_PLANE_FOV_HIDDEN)
|
||||
if(newdir & SOUTH)
|
||||
other_visual.pixel_y = -24
|
||||
other_visual.layer = ABOVE_ALL_MOB_LAYER
|
||||
SET_PLANE_IMPLICIT(other_visual, GAME_PLANE_UPPER_FOV_HIDDEN)
|
||||
new_transform.Scale(-1, 1)
|
||||
new_transform.Translate(-1, 0)
|
||||
if(newdir & EAST)
|
||||
other_visual.pixel_x = 14
|
||||
other_visual.layer = BELOW_MOB_LAYER
|
||||
SET_PLANE_IMPLICIT(other_visual, GAME_PLANE_FOV_HIDDEN)
|
||||
new_transform.Shear(0.5, 0)
|
||||
new_transform.Scale(0.65, 1)
|
||||
if(newdir & WEST)
|
||||
other_visual.pixel_x = -14
|
||||
other_visual.layer = BELOW_MOB_LAYER
|
||||
SET_PLANE_IMPLICIT(other_visual, GAME_PLANE_FOV_HIDDEN)
|
||||
new_transform.Shear(-0.5, 0)
|
||||
new_transform.Scale(0.65, 1)
|
||||
other_visual.transform = new_transform
|
||||
|
||||
/obj/item/mod/control/Initialize(mapload, datum/mod_theme/new_theme, new_skin, obj/item/mod/core/new_core)
|
||||
. = ..()
|
||||
mod_link = new(
|
||||
src,
|
||||
starting_frequency,
|
||||
CALLBACK(src, PROC_REF(get_wearer)),
|
||||
CALLBACK(src, PROC_REF(can_call)),
|
||||
CALLBACK(src, PROC_REF(make_link_visual)),
|
||||
CALLBACK(src, PROC_REF(get_link_visual)),
|
||||
CALLBACK(src, PROC_REF(delete_link_visual))
|
||||
)
|
||||
|
||||
/obj/item/mod/control/multitool_act_secondary(mob/living/user, obj/item/multitool/tool)
|
||||
if(!multitool_check_buffer(user, tool))
|
||||
return
|
||||
var/tool_frequency = null
|
||||
if(istype(tool.buffer, /datum/mod_link))
|
||||
var/datum/mod_link/buffer_link = tool.buffer
|
||||
tool_frequency = buffer_link.frequency
|
||||
if(!tool_frequency && mod_link.frequency)
|
||||
tool.set_buffer(mod_link)
|
||||
else if(tool_frequency && !mod_link.frequency)
|
||||
mod_link.frequency = tool_frequency
|
||||
else if(tool_frequency && mod_link.frequency)
|
||||
var/response = tgui_alert(user, "Would you like to copy or imprint the frequency?", "MODlink Frequency", list("Copy", "Imprint"))
|
||||
if(!user.is_holding(tool))
|
||||
return
|
||||
switch(response)
|
||||
if("Copy")
|
||||
tool.set_buffer(mod_link)
|
||||
if("Imprint")
|
||||
mod_link.frequency = tool_frequency
|
||||
|
||||
/obj/item/mod/control/proc/can_call()
|
||||
return wearer && wearer.stat < DEAD
|
||||
|
||||
/obj/item/mod/control/proc/make_link_visual()
|
||||
return make_link_visual_generic(mod_link, PROC_REF(on_overlay_change))
|
||||
|
||||
/obj/item/mod/control/proc/get_link_visual(atom/movable/visuals)
|
||||
return get_link_visual_generic(mod_link, visuals, PROC_REF(on_wearer_set_dir))
|
||||
|
||||
/obj/item/mod/control/proc/delete_link_visual()
|
||||
return delete_link_visual_generic(mod_link)
|
||||
|
||||
/obj/item/mod/control/Hear(message, atom/movable/speaker, message_language, raw_message, radio_freq, list/spans, list/message_mods, message_range)
|
||||
. = ..()
|
||||
if(speaker != wearer && speaker != ai_assistant)
|
||||
return
|
||||
mod_link.visual.say(raw_message, sanitize = FALSE, message_range = 2)
|
||||
|
||||
/obj/item/mod/control/proc/on_overlay_change(atom/source, cache_index, overlay)
|
||||
SIGNAL_HANDLER
|
||||
addtimer(CALLBACK(src, PROC_REF(update_link_visual)), 1 TICKS, TIMER_UNIQUE)
|
||||
|
||||
/obj/item/mod/control/proc/update_link_visual()
|
||||
if(QDELETED(mod_link.link_call))
|
||||
return
|
||||
mod_link.visual.cut_overlay(mod_link.visual_overlays)
|
||||
mod_link.visual_overlays = wearer.overlays - wearer.active_thinking_indicator
|
||||
mod_link.visual.add_overlay(mod_link.visual_overlays)
|
||||
|
||||
/obj/item/mod/control/proc/on_wearer_set_dir(atom/source, dir, newdir)
|
||||
SIGNAL_HANDLER
|
||||
on_user_set_dir_generic(mod_link, newdir)
|
||||
|
||||
/obj/item/clothing/neck/link_scryer
|
||||
name = "\improper MODlink scryer"
|
||||
desc = "An intricate piece of machinery that creates a holographic video call with another MODlink-compatible device. Essentially a video necklace."
|
||||
icon_state = "modlink"
|
||||
actions_types = list(/datum/action/item_action/call_link)
|
||||
/// The installed power cell.
|
||||
var/obj/item/stock_parts/cell/cell
|
||||
/// The MODlink datum we operate.
|
||||
var/datum/mod_link/mod_link
|
||||
/// Initial frequency of the MODlink.
|
||||
var/starting_frequency = "NT"
|
||||
/// An additional name tag for the scryer, seen as "MODlink scryer - [label]"
|
||||
var/label
|
||||
|
||||
/obj/item/clothing/neck/link_scryer/Initialize(mapload)
|
||||
. = ..()
|
||||
mod_link = new(
|
||||
src,
|
||||
starting_frequency,
|
||||
CALLBACK(src, PROC_REF(get_user)),
|
||||
CALLBACK(src, PROC_REF(can_call)),
|
||||
CALLBACK(src, PROC_REF(make_link_visual)),
|
||||
CALLBACK(src, PROC_REF(get_link_visual)),
|
||||
CALLBACK(src, PROC_REF(delete_link_visual))
|
||||
)
|
||||
START_PROCESSING(SSobj, src)
|
||||
|
||||
/obj/item/clothing/neck/link_scryer/Destroy()
|
||||
QDEL_NULL(cell)
|
||||
QDEL_NULL(mod_link)
|
||||
STOP_PROCESSING(SSobj, src)
|
||||
return ..()
|
||||
|
||||
/obj/item/clothing/neck/link_scryer/examine(mob/user)
|
||||
. = ..()
|
||||
if(cell)
|
||||
. += span_notice("The battery charge reads [cell.percent()]%. <b>Right-click</b> with an empty hand to remove it.")
|
||||
else
|
||||
. += span_notice("It is missing a battery, one can be installed by clicking with a power cell on it.")
|
||||
. += span_notice("The MODlink ID is [mod_link.id], frequency is [mod_link.frequency]. <b>Right-click</b> with multitool to copy/imprint frequency.")
|
||||
. += span_notice("Use in hand to set name.")
|
||||
|
||||
/obj/item/clothing/neck/link_scryer/equipped(mob/living/user, slot)
|
||||
. = ..()
|
||||
if(slot != ITEM_SLOT_NECK)
|
||||
mod_link?.end_call()
|
||||
|
||||
/obj/item/clothing/neck/link_scryer/dropped(mob/living/user)
|
||||
. = ..()
|
||||
mod_link?.end_call()
|
||||
|
||||
/obj/item/clothing/neck/link_scryer/attack_self(mob/user, modifiers)
|
||||
var/new_label = reject_bad_text(tgui_input_text(user, "Change the visible name", "Set Name", label, MAX_NAME_LEN))
|
||||
if(!new_label)
|
||||
balloon_alert(user, "invalid name!")
|
||||
return
|
||||
label = new_label
|
||||
balloon_alert(user, "name set")
|
||||
update_name()
|
||||
|
||||
/obj/item/clothing/neck/link_scryer/process(seconds_per_tick)
|
||||
if(!mod_link.link_call)
|
||||
return
|
||||
cell.use(min(20 * seconds_per_tick, cell.charge))
|
||||
|
||||
/obj/item/clothing/neck/link_scryer/attackby(obj/item/attacked_by, mob/user, params)
|
||||
. = ..()
|
||||
if(cell || !istype(attacked_by, /obj/item/stock_parts/cell))
|
||||
return
|
||||
if(!user.transferItemToLoc(attacked_by, src))
|
||||
return
|
||||
cell = attacked_by
|
||||
balloon_alert(user, "installed [cell.name]")
|
||||
|
||||
/obj/item/clothing/neck/link_scryer/update_name(updates)
|
||||
. = ..()
|
||||
name = "[initial(name)][label ? " - [label]" : ""]"
|
||||
|
||||
/obj/item/clothing/neck/link_scryer/Exited(atom/movable/gone, direction)
|
||||
. = ..()
|
||||
if(gone == cell)
|
||||
cell = null
|
||||
|
||||
/obj/item/clothing/neck/link_scryer/attack_hand_secondary(mob/user, list/modifiers)
|
||||
if(!cell)
|
||||
return SECONDARY_ATTACK_CONTINUE_CHAIN
|
||||
balloon_alert(user, "removed [cell.name]")
|
||||
user.put_in_hands(cell)
|
||||
return SECONDARY_ATTACK_CANCEL_ATTACK_CHAIN
|
||||
|
||||
/obj/item/clothing/neck/link_scryer/multitool_act_secondary(mob/living/user, obj/item/multitool/tool)
|
||||
if(!multitool_check_buffer(user, tool))
|
||||
return
|
||||
var/tool_frequency = null
|
||||
if(istype(tool.buffer, /datum/mod_link))
|
||||
var/datum/mod_link/buffer_link = tool.buffer
|
||||
tool_frequency = buffer_link.frequency
|
||||
if(!tool_frequency && mod_link.frequency)
|
||||
tool.set_buffer(mod_link)
|
||||
else if(tool_frequency && !mod_link.frequency)
|
||||
mod_link.frequency = tool_frequency
|
||||
else if(tool_frequency && mod_link.frequency)
|
||||
var/response = tgui_alert(user, "Would you like to copy or imprint the frequency?", "MODlink Frequency", list("Copy", "Imprint"))
|
||||
switch(response)
|
||||
if("Copy")
|
||||
tool.set_buffer(mod_link)
|
||||
if("Imprint")
|
||||
mod_link.frequency = tool_frequency
|
||||
|
||||
/obj/item/clothing/neck/link_scryer/worn_overlays(mutable_appearance/standing, isinhands)
|
||||
. = ..()
|
||||
if(!QDELETED(mod_link.link_call))
|
||||
. += mutable_appearance('icons/mob/clothing/neck.dmi', "modlink_active")
|
||||
|
||||
/obj/item/clothing/neck/link_scryer/ui_action_click(mob/user)
|
||||
if(mod_link.link_call)
|
||||
mod_link.end_call()
|
||||
else
|
||||
call_link(user, mod_link)
|
||||
|
||||
/obj/item/clothing/neck/link_scryer/proc/get_user()
|
||||
var/mob/living/carbon/user = loc
|
||||
return istype(user) && user.wear_neck == src ? user : null
|
||||
|
||||
/obj/item/clothing/neck/link_scryer/proc/can_call()
|
||||
var/mob/living/user = loc
|
||||
return istype(user) && cell.charge && user.stat < DEAD
|
||||
|
||||
/obj/item/clothing/neck/link_scryer/proc/make_link_visual()
|
||||
var/mob/living/user = mod_link.get_user_callback.Invoke()
|
||||
user.update_worn_neck()
|
||||
return make_link_visual_generic(mod_link, PROC_REF(on_overlay_change))
|
||||
|
||||
/obj/item/clothing/neck/link_scryer/proc/get_link_visual(atom/movable/visuals)
|
||||
return get_link_visual_generic(mod_link, visuals, PROC_REF(on_user_set_dir))
|
||||
|
||||
/obj/item/clothing/neck/link_scryer/proc/delete_link_visual()
|
||||
var/mob/living/user = mod_link.get_user_callback.Invoke()
|
||||
if(!QDELETED(user))
|
||||
user.update_worn_neck()
|
||||
return delete_link_visual_generic(mod_link)
|
||||
|
||||
/obj/item/clothing/neck/link_scryer/Hear(message, atom/movable/speaker, message_language, raw_message, radio_freq, list/spans, list/message_mods, message_range)
|
||||
. = ..()
|
||||
if(speaker != loc)
|
||||
return
|
||||
mod_link.visual.say(raw_message, sanitize = FALSE, message_range = 3)
|
||||
|
||||
/obj/item/clothing/neck/link_scryer/proc/on_overlay_change(atom/source, cache_index, overlay)
|
||||
SIGNAL_HANDLER
|
||||
addtimer(CALLBACK(src, PROC_REF(update_link_visual)), 1 TICKS, TIMER_UNIQUE)
|
||||
|
||||
/obj/item/clothing/neck/link_scryer/proc/update_link_visual()
|
||||
if(QDELETED(mod_link.link_call))
|
||||
return
|
||||
var/mob/living/user = loc
|
||||
mod_link.visual.cut_overlay(mod_link.visual_overlays)
|
||||
mod_link.visual_overlays = user.overlays - user.active_thinking_indicator
|
||||
mod_link.visual.add_overlay(mod_link.visual_overlays)
|
||||
|
||||
/obj/item/clothing/neck/link_scryer/proc/on_user_set_dir(atom/source, dir, newdir)
|
||||
SIGNAL_HANDLER
|
||||
on_user_set_dir_generic(mod_link, newdir)
|
||||
|
||||
/obj/item/clothing/neck/link_scryer/loaded/Initialize(mapload)
|
||||
. = ..()
|
||||
cell = new /obj/item/stock_parts/cell/high(src)
|
||||
|
||||
/obj/item/clothing/neck/link_scryer/loaded/charlie
|
||||
starting_frequency = MODLINK_FREQ_CHARLIE
|
||||
|
||||
/// A MODlink datum, used to handle unique functions that will be used in the MODlink call.
|
||||
/datum/mod_link
|
||||
/// The frequency of the MODlink. You can only call other MODlinks on the same frequency.
|
||||
var/frequency
|
||||
/// The unique ID of the MODlink.
|
||||
var/id = ""
|
||||
/// The atom that holds the MODlink.
|
||||
var/atom/movable/holder
|
||||
/// A reference to the visuals generated by the MODlink.
|
||||
var/atom/movable/visual
|
||||
/// A list of all overlays of the user, copied everytime they have an overlay change.
|
||||
var/list/visual_overlays = list()
|
||||
/// A reference to the call between two MODlinks.
|
||||
var/datum/mod_link_call/link_call
|
||||
/// A callback that returns the user of the MODlink.
|
||||
var/datum/callback/get_user_callback
|
||||
/// A callback that returns whether the MODlink can currently call.
|
||||
var/datum/callback/can_call_callback
|
||||
/// A callback that returns the visuals of the MODlink.
|
||||
var/datum/callback/make_visual_callback
|
||||
/// A callback that receives the visuals of the other MODlink.
|
||||
var/datum/callback/get_visual_callback
|
||||
/// A callback that deletes the visuals of the MODlink.
|
||||
var/datum/callback/delete_visual_callback
|
||||
|
||||
/datum/mod_link/New(
|
||||
atom/holder,
|
||||
frequency,
|
||||
datum/callback/get_user_callback,
|
||||
datum/callback/can_call_callback,
|
||||
datum/callback/make_visual_callback,
|
||||
datum/callback/get_visual_callback,
|
||||
datum/callback/delete_visual_callback
|
||||
)
|
||||
var/attempts = 0
|
||||
var/digits_to_make = 3
|
||||
do
|
||||
if(attempts == 10)
|
||||
attempts = 0
|
||||
digits_to_make++
|
||||
id = ""
|
||||
for(var/i in 1 to digits_to_make)
|
||||
id += num2text(rand(0,9))
|
||||
attempts++
|
||||
while(GLOB.mod_link_ids[id])
|
||||
GLOB.mod_link_ids[id] = src
|
||||
src.frequency = frequency
|
||||
src.holder = holder
|
||||
src.get_user_callback = get_user_callback
|
||||
src.can_call_callback = can_call_callback
|
||||
src.make_visual_callback = make_visual_callback
|
||||
src.get_visual_callback = get_visual_callback
|
||||
src.delete_visual_callback = delete_visual_callback
|
||||
RegisterSignal(holder, COMSIG_QDELETING, PROC_REF(on_holder_delete))
|
||||
|
||||
/datum/mod_link/Destroy()
|
||||
GLOB.mod_link_ids -= id
|
||||
if(link_call)
|
||||
end_call()
|
||||
return ..()
|
||||
|
||||
/datum/mod_link/proc/get_other()
|
||||
RETURN_TYPE(/datum/mod_link)
|
||||
if(!link_call)
|
||||
return
|
||||
return link_call.caller == src ? link_call.receiver : link_call.caller
|
||||
|
||||
/datum/mod_link/proc/call_link(datum/mod_link/called, mob/user)
|
||||
if(!frequency)
|
||||
return
|
||||
if(!istype(called))
|
||||
holder.balloon_alert(user, "invalid target!")
|
||||
return
|
||||
var/mob/living/link_user = get_user_callback.Invoke()
|
||||
if(!link_user)
|
||||
return
|
||||
if(HAS_TRAIT(link_user, TRAIT_IN_CALL))
|
||||
holder.balloon_alert(user, "user already in call!")
|
||||
return
|
||||
var/mob/living/link_target = called.get_user_callback.Invoke()
|
||||
if(!link_target)
|
||||
holder.balloon_alert(user, "invalid target!")
|
||||
return
|
||||
if(HAS_TRAIT(link_target, TRAIT_IN_CALL))
|
||||
holder.balloon_alert(user, "target already in call!")
|
||||
return
|
||||
if(!can_call_callback.Invoke() || !called.can_call_callback.Invoke())
|
||||
holder.balloon_alert(user, "can't call!")
|
||||
return
|
||||
link_target.playsound_local(get_turf(called.holder), 'sound/weapons/ring.ogg', 15, vary = TRUE)
|
||||
var/atom/movable/screen/alert/modlink_call/alert = link_target.throw_alert("[REF(src)]_modlink", /atom/movable/screen/alert/modlink_call)
|
||||
alert.desc = "[holder] ([id]) is calling you! Left-click this to accept the call. Right-click to deny it."
|
||||
alert.caller_ref = WEAKREF(src)
|
||||
alert.receiver_ref = WEAKREF(called)
|
||||
alert.user_ref = WEAKREF(user)
|
||||
|
||||
/datum/mod_link/proc/end_call()
|
||||
QDEL_NULL(link_call)
|
||||
|
||||
/datum/mod_link/proc/on_holder_delete(atom/source)
|
||||
SIGNAL_HANDLER
|
||||
qdel(src)
|
||||
|
||||
/// A MODlink call datum, used to handle the call between two MODlinks.
|
||||
/datum/mod_link_call
|
||||
/// The MODlink that is calling.
|
||||
var/datum/mod_link/caller
|
||||
/// The MODlink that is being called.
|
||||
var/datum/mod_link/receiver
|
||||
|
||||
/datum/mod_link_call/New(datum/mod_link/caller, datum/mod_link/receiver)
|
||||
caller.link_call = src
|
||||
receiver.link_call = src
|
||||
src.caller = caller
|
||||
src.receiver = receiver
|
||||
var/mob/living/caller_mob = caller.get_user_callback.Invoke()
|
||||
ADD_TRAIT(caller_mob, TRAIT_IN_CALL, REF(src))
|
||||
var/mob/living/receiver_mob = receiver.get_user_callback.Invoke()
|
||||
ADD_TRAIT(receiver_mob, TRAIT_IN_CALL, REF(src))
|
||||
make_visuals()
|
||||
START_PROCESSING(SSprocessing, src)
|
||||
|
||||
/datum/mod_link_call/Destroy()
|
||||
var/mob/living/caller_mob = caller.get_user_callback.Invoke()
|
||||
if(!QDELETED(caller_mob))
|
||||
REMOVE_TRAIT(caller_mob, TRAIT_IN_CALL, REF(src))
|
||||
var/mob/living/receiver_mob = receiver.get_user_callback.Invoke()
|
||||
if(!QDELETED(receiver_mob))
|
||||
REMOVE_TRAIT(receiver_mob, TRAIT_IN_CALL, REF(src))
|
||||
STOP_PROCESSING(SSprocessing, src)
|
||||
clear_visuals()
|
||||
caller.link_call = null
|
||||
receiver.link_call = null
|
||||
return ..()
|
||||
|
||||
/datum/mod_link_call/process(seconds_per_tick)
|
||||
if(can_continue_call())
|
||||
return
|
||||
qdel(src)
|
||||
|
||||
/datum/mod_link_call/proc/can_continue_call()
|
||||
return caller.frequency == receiver.frequency && caller.can_call_callback.Invoke() && receiver.can_call_callback.Invoke()
|
||||
|
||||
/datum/mod_link_call/proc/make_visuals()
|
||||
var/caller_visual = caller.make_visual_callback.Invoke()
|
||||
var/receiver_visual = receiver.make_visual_callback.Invoke()
|
||||
caller.get_visual_callback.Invoke(receiver_visual)
|
||||
receiver.get_visual_callback.Invoke(caller_visual)
|
||||
|
||||
/datum/mod_link_call/proc/clear_visuals()
|
||||
caller.delete_visual_callback.Invoke()
|
||||
receiver.delete_visual_callback.Invoke()
|
||||
|
||||
/proc/call_link(mob/user, datum/mod_link/calling_link)
|
||||
if(!calling_link.frequency)
|
||||
return
|
||||
var/list/callers = list()
|
||||
for(var/id in GLOB.mod_link_ids)
|
||||
var/datum/mod_link/link = GLOB.mod_link_ids[id]
|
||||
if(link.frequency != calling_link.frequency)
|
||||
continue
|
||||
if(link == calling_link)
|
||||
continue
|
||||
if(!link.can_call_callback.Invoke())
|
||||
continue
|
||||
callers["[link.holder] ([id])"] = id
|
||||
if(!length(callers))
|
||||
calling_link.holder.balloon_alert(user, "no targets on freq [calling_link.frequency]!")
|
||||
return
|
||||
var/chosen_link = tgui_input_list(user, "Choose ID to call from [calling_link.frequency] frequency", "MODlink", callers)
|
||||
if(!chosen_link)
|
||||
return
|
||||
calling_link.call_link(GLOB.mod_link_ids[callers[chosen_link]], user)
|
||||
|
||||
/atom/movable/screen/alert/modlink_call
|
||||
name = "MODlink Call Incoming"
|
||||
desc = "Someone is calling you! Left-click this to accept the call. Right-click to deny it."
|
||||
icon_state = "called"
|
||||
timeout = 10 SECONDS
|
||||
var/end_message = "call timed out!"
|
||||
/// A weak reference to the MODlink that is calling.
|
||||
var/datum/weakref/caller_ref
|
||||
/// A weak reference to the MODlink that is being called.
|
||||
var/datum/weakref/receiver_ref
|
||||
/// A weak reference to the mob that is calling.
|
||||
var/datum/weakref/user_ref
|
||||
|
||||
/atom/movable/screen/alert/modlink_call/Click(location, control, params)
|
||||
. = ..()
|
||||
if(usr != owner)
|
||||
return
|
||||
var/datum/mod_link/caller = caller_ref.resolve()
|
||||
var/datum/mod_link/receiver = receiver_ref.resolve()
|
||||
if(!caller || !receiver)
|
||||
return
|
||||
if(caller.link_call || receiver.link_call)
|
||||
return
|
||||
var/list/modifiers = params2list(params)
|
||||
if(LAZYACCESS(modifiers, RIGHT_CLICK))
|
||||
end_message = "call denied!"
|
||||
owner.clear_alert("[REF(caller)]_modlink")
|
||||
return
|
||||
end_message = "call accepted"
|
||||
new /datum/mod_link_call(caller, receiver)
|
||||
owner.clear_alert("[REF(caller)]_modlink")
|
||||
|
||||
/atom/movable/screen/alert/modlink_call/Destroy()
|
||||
var/mob/living/user = user_ref?.resolve()
|
||||
var/datum/mod_link/caller = caller_ref?.resolve()
|
||||
if(!user || !caller)
|
||||
return ..()
|
||||
caller.holder.balloon_alert(user, end_message)
|
||||
return ..()
|
||||
@@ -306,7 +306,7 @@
|
||||
the bare essentials, geared far more for environmental hazards than combat against fauna; however, \
|
||||
this gives way to incredible protection against corrosives and thermal protection good enough for \
|
||||
both casual backstroking through molten magma and romantic walks through arctic terrain. \
|
||||
Instead, the suit is capable of using its' anomalous properties to attract and \
|
||||
Instead, the suit is capable of using its anomalous properties to attract and \
|
||||
carefully distribute layers of ash or ice across the surface; these layers are ablative, but incredibly strong. \
|
||||
Lastly, the suit is capable of compressing and shrinking the mass of the wearer, as well as \
|
||||
rearranging its own constitution, to allow them to fit upright in a sphere form that can \
|
||||
@@ -1437,7 +1437,7 @@
|
||||
|
||||
/datum/mod_theme/responsory
|
||||
name = "responsory"
|
||||
desc = "A high-speed rescue suit by Nanotrasen, intended for its' emergency response teams."
|
||||
desc = "A high-speed rescue suit by Nanotrasen, intended for its emergency response teams."
|
||||
extended_desc = "A streamlined suit of Nanotrasen design, these sleek black suits are only worn by \
|
||||
elite emergency response personnel to help save the day. While the slim and nimble design of the suit \
|
||||
cuts the ceramics and ablatives in it down, dropping the protection, \
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
/obj/item/mod/control/pre_equipped
|
||||
starting_frequency = MODLINK_FREQ_NANOTRASEN
|
||||
/// The skin we apply to the suit, defaults to the default_skin of the theme.
|
||||
var/applied_skin
|
||||
/// The MOD core we apply to the suit.
|
||||
@@ -196,6 +197,7 @@
|
||||
|
||||
/obj/item/mod/control/pre_equipped/traitor
|
||||
theme = /datum/mod_theme/syndicate
|
||||
starting_frequency = MODLINK_FREQ_SYNDICATE
|
||||
applied_cell = /obj/item/stock_parts/cell/super
|
||||
applied_modules = list(
|
||||
/obj/item/mod/module/storage/syndicate,
|
||||
@@ -213,6 +215,7 @@
|
||||
|
||||
/obj/item/mod/control/pre_equipped/traitor_elite
|
||||
theme = /datum/mod_theme/elite
|
||||
starting_frequency = MODLINK_FREQ_SYNDICATE
|
||||
applied_cell = /obj/item/stock_parts/cell/bluespace
|
||||
applied_modules = list(
|
||||
/obj/item/mod/module/storage/syndicate,
|
||||
@@ -229,6 +232,7 @@
|
||||
|
||||
/obj/item/mod/control/pre_equipped/nuclear
|
||||
theme = /datum/mod_theme/syndicate
|
||||
starting_frequency = MODLINK_FREQ_SYNDICATE
|
||||
applied_cell = /obj/item/stock_parts/cell/hyper
|
||||
req_access = list(ACCESS_SYNDICATE)
|
||||
applied_modules = list(
|
||||
@@ -248,6 +252,7 @@
|
||||
|
||||
/obj/item/mod/control/pre_equipped/elite
|
||||
theme = /datum/mod_theme/elite
|
||||
starting_frequency = MODLINK_FREQ_SYNDICATE
|
||||
applied_cell = /obj/item/stock_parts/cell/bluespace
|
||||
req_access = list(ACCESS_SYNDICATE)
|
||||
applied_modules = list(
|
||||
@@ -280,6 +285,7 @@
|
||||
|
||||
/obj/item/mod/control/pre_equipped/infiltrator
|
||||
theme = /datum/mod_theme/infiltrator
|
||||
starting_frequency = MODLINK_FREQ_SYNDICATE
|
||||
applied_cell = /obj/item/stock_parts/cell/super
|
||||
applied_modules = list(
|
||||
/obj/item/mod/module/emp_shield,
|
||||
@@ -291,6 +297,7 @@
|
||||
|
||||
/obj/item/mod/control/pre_equipped/interdyne
|
||||
theme = /datum/mod_theme/interdyne
|
||||
starting_frequency = MODLINK_FREQ_SYNDICATE
|
||||
applied_cell = /obj/item/stock_parts/cell/super
|
||||
applied_modules = list(
|
||||
/obj/item/mod/module/defibrillator/combat,
|
||||
@@ -304,6 +311,7 @@
|
||||
|
||||
/obj/item/mod/control/pre_equipped/enchanted
|
||||
theme = /datum/mod_theme/enchanted
|
||||
starting_frequency = null
|
||||
applied_core = /obj/item/mod/core/infinite
|
||||
applied_modules = list(
|
||||
/obj/item/mod/module/storage/large_capacity,
|
||||
@@ -313,6 +321,7 @@
|
||||
|
||||
/obj/item/mod/control/pre_equipped/ninja
|
||||
theme = /datum/mod_theme/ninja
|
||||
starting_frequency = null
|
||||
applied_cell = /obj/item/stock_parts/cell/ninja
|
||||
applied_modules = list(
|
||||
/obj/item/mod/module/storage,
|
||||
@@ -334,6 +343,7 @@
|
||||
|
||||
/obj/item/mod/control/pre_equipped/prototype
|
||||
theme = /datum/mod_theme/prototype
|
||||
starting_frequency = MODLINK_FREQ_CHARLIE
|
||||
req_access = list(ACCESS_AWAY_GENERAL)
|
||||
applied_modules = list(
|
||||
/obj/item/mod/module/storage,
|
||||
@@ -349,6 +359,7 @@
|
||||
|
||||
/obj/item/mod/control/pre_equipped/responsory
|
||||
theme = /datum/mod_theme/responsory
|
||||
starting_frequency = MODLINK_FREQ_CENTCOM
|
||||
applied_cell = /obj/item/stock_parts/cell/hyper
|
||||
req_access = list(ACCESS_CENT_GENERAL)
|
||||
applied_modules = list(
|
||||
@@ -427,6 +438,7 @@
|
||||
|
||||
/obj/item/mod/control/pre_equipped/apocryphal
|
||||
theme = /datum/mod_theme/apocryphal
|
||||
starting_frequency = MODLINK_FREQ_CENTCOM
|
||||
applied_cell = /obj/item/stock_parts/cell/bluespace
|
||||
req_access = list(ACCESS_CENT_SPECOPS)
|
||||
applied_modules = list(
|
||||
@@ -452,6 +464,7 @@
|
||||
|
||||
/obj/item/mod/control/pre_equipped/corporate
|
||||
theme = /datum/mod_theme/corporate
|
||||
starting_frequency = MODLINK_FREQ_CENTCOM
|
||||
applied_core = /obj/item/mod/core/infinite
|
||||
req_access = list(ACCESS_CENT_SPECOPS)
|
||||
applied_modules = list(
|
||||
@@ -463,6 +476,7 @@
|
||||
|
||||
/obj/item/mod/control/pre_equipped/chrono
|
||||
theme = /datum/mod_theme/chrono
|
||||
starting_frequency = null
|
||||
applied_core = /obj/item/mod/core/infinite
|
||||
applied_modules = list(
|
||||
/obj/item/mod/module/eradication_lock,
|
||||
@@ -483,6 +497,7 @@
|
||||
|
||||
/obj/item/mod/control/pre_equipped/debug
|
||||
theme = /datum/mod_theme/debug
|
||||
starting_frequency = null
|
||||
applied_core = /obj/item/mod/core/infinite
|
||||
applied_modules = list( //one of every type of module, for testing if they all work correctly
|
||||
/obj/item/mod/module/storage/bluespace,
|
||||
@@ -496,6 +511,7 @@
|
||||
|
||||
/obj/item/mod/control/pre_equipped/administrative
|
||||
theme = /datum/mod_theme/administrative
|
||||
starting_frequency = MODLINK_FREQ_CENTCOM
|
||||
applied_core = /obj/item/mod/core/infinite
|
||||
applied_modules = list(
|
||||
/obj/item/mod/module/storage/bluespace,
|
||||
@@ -516,6 +532,7 @@
|
||||
|
||||
//these exist for the prefs menu
|
||||
/obj/item/mod/control/pre_equipped/empty
|
||||
starting_frequency = null
|
||||
|
||||
/obj/item/mod/control/pre_equipped/empty/syndicate
|
||||
theme = /datum/mod_theme/syndicate
|
||||
|
||||
@@ -15,6 +15,8 @@
|
||||
"ai_name" = ai_assistant?.name,
|
||||
"has_pai" = ispAI(ai_assistant),
|
||||
"is_ai" = ai_assistant && ai_assistant == user,
|
||||
"link_id" = mod_link.id,
|
||||
"link_call" = mod_link.get_other()?.id,
|
||||
// Wires
|
||||
"open" = open,
|
||||
"seconds_electrified" = seconds_electrified,
|
||||
@@ -76,10 +78,6 @@
|
||||
. = ..()
|
||||
if(.)
|
||||
return
|
||||
if(locked && (!allowed(usr) || !ispAI(usr))) // pAIs automatically fail out of allowed()
|
||||
balloon_alert(usr, "insufficient access!")
|
||||
playsound(src, 'sound/machines/scanbuzz.ogg', 25, TRUE, SILENCED_SOUND_EXTRARANGE)
|
||||
return
|
||||
if(malfunctioning && prob(75))
|
||||
balloon_alert(usr, "button malfunctions!")
|
||||
return
|
||||
@@ -87,6 +85,11 @@
|
||||
if("lock")
|
||||
locked = !locked
|
||||
balloon_alert(usr, "[locked ? "locked" : "unlocked"]!")
|
||||
if("call")
|
||||
if(!mod_link.link_call)
|
||||
call_link(usr, mod_link)
|
||||
else
|
||||
mod_link.end_call()
|
||||
if("activate")
|
||||
toggle_activate(usr)
|
||||
if("select")
|
||||
|
||||
Reference in New Issue
Block a user