Files
Bubberstation/code/modules/mod/mod_link.dm

555 lines
20 KiB
Plaintext

/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/terminal_processing.ogg', 50, vary = TRUE)
visuals.add_overlay(mutable_appearance('icons/effects/effects.dmi', "static_base", ABOVE_NORMAL_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, mob/living/old_user)
playsound(mod_link.get_other().holder, 'sound/machines/terminal/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(old_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
if(newdir & SOUTH)
other_visual.pixel_y = -24
other_visual.layer = ABOVE_ALL_MOB_LAYER
new_transform.Scale(-1, 1)
new_transform.Translate(-1, 0)
if(newdir & EAST)
other_visual.pixel_x = 14
other_visual.layer = BELOW_MOB_LAYER
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
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)
. = NONE
var/tool_frequency = null
if(istype(tool.buffer, /datum/mod_link))
var/datum/mod_link/buffer_link = tool.buffer
tool_frequency = buffer_link.frequency
balloon_alert(user, "frequency set")
. = ITEM_INTERACT_SUCCESS
if(!tool_frequency && mod_link.frequency)
tool.set_buffer(mod_link)
balloon_alert(user, "frequency copied")
. = ITEM_INTERACT_SUCCESS
else if(tool_frequency && !mod_link.frequency)
mod_link.frequency = tool_frequency
. = ITEM_INTERACT_SUCCESS
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 ITEM_INTERACT_BLOCKING
switch(response)
if("Copy")
tool.set_buffer(mod_link)
balloon_alert(user, "frequency copied")
. = ITEM_INTERACT_SUCCESS
if("Imprint")
mod_link.frequency = tool_frequency
balloon_alert(user, "frequency set")
. = ITEM_INTERACT_SUCCESS
/obj/item/mod/control/proc/can_call()
return get_charge() && 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(mob/living/old_user)
return delete_link_visual_generic(mod_link, old_user)
/obj/item/mod/control/Hear(atom/movable/speaker, message_language, raw_message, radio_freq, radio_freq_name, radio_freq_color, list/spans, list/message_mods, message_range)
. = ..()
if(speaker != wearer && speaker != ai_assistant)
return
mod_link.visual.say(raw_message, spans = spans, sanitize = FALSE, language = message_language, message_range = 2, message_mods = message_mods)
/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 || SOUTH)
/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/power_store/cell
/// The MODlink datum we operate.
var/datum/mod_link/mod_link
/// Initial frequency of the MODlink.
var/starting_frequency
/// 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)
. = ..()
// SKYRAT EDIT NIFSOFT SCRYERS - START
if(custom_examine_controls)
return
// SKYRAT EDIT NIFSOFT SCRYERS - END
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 || "unset"]. <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(!user.is_holding(src))
return
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(0.02 * STANDARD_CELL_RATE * seconds_per_tick, force = TRUE)
/obj/item/clothing/neck/link_scryer/attackby(obj/item/attacked_by, mob/user, list/modifiers, list/attack_modifiers)
. = ..()
if(cell || !istype(attacked_by, /obj/item/stock_parts/power_store/cell))
return
if(!user.transferItemToLoc(attacked_by, src))
return
cell = attacked_by
balloon_alert(user, "cell installed")
/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, "cell removed")
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)
. = NONE
var/tool_frequency = null
if(istype(tool.buffer, /datum/mod_link))
var/datum/mod_link/buffer_link = tool.buffer
tool_frequency = buffer_link.frequency
balloon_alert(user, "frequency set")
. = ITEM_INTERACT_SUCCESS
if(!tool_frequency && mod_link.frequency)
tool.set_buffer(mod_link)
balloon_alert(user, "frequency copied")
. = ITEM_INTERACT_SUCCESS
else if(tool_frequency && !mod_link.frequency)
mod_link.frequency = tool_frequency
. = ITEM_INTERACT_SUCCESS
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 ITEM_INTERACT_BLOCKING
switch(response)
if("Copy")
tool.set_buffer(mod_link)
balloon_alert(user, "frequency copied")
. = ITEM_INTERACT_SUCCESS
if("Imprint")
mod_link.frequency = tool_frequency
balloon_alert(user, "frequency set")
. = ITEM_INTERACT_SUCCESS
/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 if(QDELETED(cell))
user.balloon_alert(user, "no cell installed!")
else if(!cell.charge)
user.balloon_alert(user, "no charge!")
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(mob/living/old_user)
if(!QDELETED(old_user))
old_user.update_worn_neck()
return delete_link_visual_generic(mod_link, old_user)
/obj/item/clothing/neck/link_scryer/Hear(atom/movable/speaker, message_language, raw_message, radio_freq, radio_freq_name, radio_freq_color, list/spans, list/message_mods, message_range)
. = ..()
if(speaker != loc)
return
mod_link.visual.say(raw_message, spans = spans, sanitize = FALSE, language = message_language, message_range = 3, message_mods = message_mods)
/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 || SOUTH)
/obj/item/clothing/neck/link_scryer/loaded
starting_frequency = MODLINK_FREQ_NANOTRASEN
/obj/item/clothing/neck/link_scryer/loaded/Initialize(mapload)
. = ..()
cell = new /obj/item/stock_parts/power_store/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
/// Generic name for multitool buffers.
var/name = "MODlink"
/// 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
/// Weakref to the user that is involved in our current call, for cleaning up after ourselves.
var/datum/weakref/user_in_call_ref
/// 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()
get_user_callback = null
make_visual_callback = null
get_visual_callback = null
delete_visual_callback = null
return ..()
/datum/mod_link/proc/get_other()
RETURN_TYPE(/datum/mod_link)
if(!link_call)
return
return link_call.link_caller == src ? link_call.link_receiver : link_call.link_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, "already calling!")
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/items/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.link_caller_ref = WEAKREF(src)
alert.link_receiver_ref = WEAKREF(called)
alert.user_ref = WEAKREF(user)
/datum/mod_link/proc/end_call()
QDEL_NULL(link_call)
/datum/mod_link/proc/entered_call(datum/mod_link/other_link)
var/mob/living/user = get_user_callback.Invoke()
user_in_call_ref = WEAKREF(user)
ADD_TRAIT(user, TRAIT_IN_CALL, REF(src))
var/other_visual = other_link.make_visual_callback.Invoke()
get_visual_callback.Invoke(other_visual)
/datum/mod_link/proc/exiting_call()
var/mob/living/old_user = user_in_call_ref?.resolve()
user_in_call_ref = null
if(old_user)
REMOVE_TRAIT(old_user, TRAIT_IN_CALL, REF(src))
delete_visual_callback.Invoke(old_user)
/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/link_caller
/// The MODlink that is being called.
var/datum/mod_link/link_receiver
/datum/mod_link_call/New(datum/mod_link/link_caller, datum/mod_link/link_receiver)
src.link_caller = link_caller
src.link_receiver = link_receiver
link_caller.link_call = src
link_receiver.link_call = src
link_caller.entered_call(link_receiver)
link_receiver.entered_call(link_caller)
START_PROCESSING(SSprocessing, src)
/datum/mod_link_call/Destroy()
link_caller.exiting_call()
link_receiver.exiting_call()
link_caller.link_call = null
link_receiver.link_call = null
STOP_PROCESSING(SSprocessing, src)
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 link_caller.frequency == link_receiver.frequency && link_caller.can_call_callback.Invoke() && link_receiver.can_call_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
clickable_glow = TRUE
var/end_message = "call timed out!"
/// A weak reference to the MODlink that is calling.
var/datum/weakref/link_caller_ref
/// A weak reference to the MODlink that is being called.
var/datum/weakref/link_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/link_caller = link_caller_ref.resolve()
var/datum/mod_link/link_receiver = link_receiver_ref.resolve()
if(!link_caller || !link_receiver)
return
if(link_caller.link_call || link_receiver.link_call)
return
var/list/modifiers = params2list(params)
if(LAZYACCESS(modifiers, RIGHT_CLICK))
end_message = "call denied!"
owner.clear_alert("[REF(link_caller)]_modlink")
return
end_message = "call accepted"
new /datum/mod_link_call(link_caller, link_receiver)
owner.clear_alert("[REF(link_caller)]_modlink")
/atom/movable/screen/alert/modlink_call/Destroy()
var/mob/living/user = user_ref?.resolve()
var/datum/mod_link/link_caller = link_caller_ref?.resolve()
if(!user || !link_caller)
return ..()
link_caller.holder.balloon_alert(user, end_message)
return ..()