diff --git a/code/__DEFINES/preferences.dm b/code/__DEFINES/preferences.dm index 45b77f74457d..7dc6c0e15211 100644 --- a/code/__DEFINES/preferences.dm +++ b/code/__DEFINES/preferences.dm @@ -179,6 +179,10 @@ #define INFO_LAYER "layer" /// Is this item an heirloom item #define INFO_HEIRLOOM "heirloom" +// NON-MODULE CHANGE START +/// Used to set custom labels on MODlink devices. +#define INFO_MODLINK_LABEL "modlink_label" +// NON-MODULE CHANGE END // Lipstick styles #define UPPER_LIP "Upper" diff --git a/code/modules/mod/mod_link.dm b/code/modules/mod/mod_link.dm index 1aad85c04a50..1f2105e1a74f 100644 --- a/code/modules/mod/mod_link.dm +++ b/code/modules/mod/mod_link.dm @@ -415,6 +415,10 @@ if(HAS_TRAIT(link_user, TRAIT_IN_CALL)) holder.balloon_alert(user, "already calling!") return + // NON-MODULE CHANGE START : Custom Modlink Call Logic Overries + if(called.override_called_logic_callback && called.override_called_logic_callback.Invoke(src, user)) + return // Early return if our recipient's modlink wants us to. + // NON-MODULE CHANGE END var/mob/living/link_target = called.get_user_callback.Invoke() if(!link_target) holder.balloon_alert(user, "invalid target!") @@ -499,6 +503,11 @@ continue if(!link.can_call_callback.Invoke()) continue + // NON-MODULE CHANGE START : Allow MODlinks to override their displayname. + if(link.visual_name) + callers["[link.visual_name] ([id])"] = id + continue + // NON-MODULE CHANGE END callers["[link.holder] ([id])"] = id if(!length(callers)) calling_link.holder.balloon_alert(user, "no targets on freq [calling_link.frequency]!") diff --git a/maplestation.dme b/maplestation.dme index 16445d510ef8..418187f4ae1c 100644 --- a/maplestation.dme +++ b/maplestation.dme @@ -6385,6 +6385,7 @@ #include "maplestation_modules\code\game\objects\effects\temporary_visuals\projectiles\tracer.dm" #include "maplestation_modules\code\game\objects\items\ashtray.dm" #include "maplestation_modules\code\game\objects\items\bola.dm" +#include "maplestation_modules\code\game\objects\items\brick_phone_scryer.dm" #include "maplestation_modules\code\game\objects\items\captain_weapons.dm" #include "maplestation_modules\code\game\objects\items\cards_ids.dm" #include "maplestation_modules\code\game\objects\items\cybernetics_paintkit.dm" @@ -6727,6 +6728,7 @@ #include "maplestation_modules\code\modules\mob\living\silicon\robot\robot_defines.dm" #include "maplestation_modules\code\modules\mob\living\simple_animal\noble_cat.dm" #include "maplestation_modules\code\modules\mob\living\simple_animal\slime.dm" +#include "maplestation_modules\code\modules\mod\mod_link.dm" #include "maplestation_modules\code\modules\movespeed\modifiers\reagent.dm" #include "maplestation_modules\code\modules\paperwork\stamps.dm" #include "maplestation_modules\code\modules\pixel_shift\code\pixel_shift_component.dm" @@ -6753,12 +6755,14 @@ #include "maplestation_modules\code\modules\reagents\reagent_containers\cups\glassbottle.dm" #include "maplestation_modules\code\modules\recycling\holder.dm" #include "maplestation_modules\code\modules\research\designs\autolathe_designs.dm" +#include "maplestation_modules\code\modules\research\designs\comms_designs.dm" #include "maplestation_modules\code\modules\research\designs\mecha_designs.dm" #include "maplestation_modules\code\modules\research\designs\medical_designs.dm" #include "maplestation_modules\code\modules\research\designs\wiremod_designs.dm" #include "maplestation_modules\code\modules\research\machinery\experimentor.dm" #include "maplestation_modules\code\modules\research\techweb\_research.dm" #include "maplestation_modules\code\modules\research\techweb\all_nodes.dm" +#include "maplestation_modules\code\modules\research\techweb\engi_nodes.dm" #include "maplestation_modules\code\modules\research\xenobiology\cores.dm" #include "maplestation_modules\code\modules\research\xenobiology\potions.dm" #include "maplestation_modules\code\modules\robotic_limb_detach\robot_limb_detach_quirk.dm" diff --git a/maplestation_modules/code/game/objects/items/brick_phone_scryer.dm b/maplestation_modules/code/game/objects/items/brick_phone_scryer.dm new file mode 100644 index 000000000000..63d75661b7f1 --- /dev/null +++ b/maplestation_modules/code/game/objects/items/brick_phone_scryer.dm @@ -0,0 +1,492 @@ +// This was ported from Doppler Shift almost wholesale so please give them a major thanks! + +#define BRICK_SCRYERPHONE_RINGING_INTERVAL (3 SECONDS) +#define BRICK_SCRYERPHONE_RINGING_DURATION (15 SECONDS) +/// Rate at which we discharge when in use, per second. +#define BRICK_SCRYERPHONE_DISCHARGE_RATE (0.002 * STANDARD_CELL_RATE) +// The calling time left in our cell before we give our first warning, while in a call. +#define BRICK_SCRYERPHONE_TIME_LEFT_FIRST_INCALL_WARNING (10 MINUTES) +// The calling time left in our cell before we give our last warning, while in a call. +#define BRICK_SCRYERPHONE_TIME_LEFT_LAST_INCALL_WARNING (2 MINUTES) +/// The calling time left in our cell before we start warning whenever we start a call. +#define BRICK_SCRYERPHONE_TIME_LEFT_START_PRECALL_WARNINGS BRICK_SCRYERPHONE_TIME_LEFT_FIRST_INCALL_WARNING + +/obj/item/brick_phone_scryer + name = "brick scryerphone" + desc = "An ancient-looking brick phone, refurbished to turn it into a MODlink-compatible device. It can only do video calls now." + icon = 'icons/obj/antags/gang/cell_phone.dmi' + icon_state = "phone_off" + w_class = WEIGHT_CLASS_SMALL + + // Center sprite. + SET_BASE_PIXEL(3, 3) + + // Thing hits like an actual brick. + force = 10 + throwforce = 10 + throw_speed = 2 + throw_range = 2 + + /// 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 + /// A name tag for the scryer, seen in the list of MODlinks. + var/label + + /// Reference to the MODlink currently calling us. + var/datum/weakref/calling_mod_link_ref + /// ID for the timer used to end incoming calls. + var/calling_timer_id + /// ID for the timer used for our ringing loop. + var/calling_loop_timer_id + + /// Whether we have handled our first incall warning for low time left. + var/first_incall_warning_handled = FALSE + /// Whether we have handled our last incall warning for low time left. + var/last_incall_warning_handled = FALSE + + /// Whether this phone has had its ringer silenced. + var/ringing_silenced = FALSE + +/obj/item/brick_phone_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)) + ) + mod_link.override_called_logic_callback = CALLBACK(src, PROC_REF(override_called_logic)) + START_PROCESSING(SSobj, src) + set_label(label) + register_context() + +/obj/item/brick_phone_scryer/Destroy() + QDEL_NULL(cell) + QDEL_NULL(mod_link) + calling_mod_link_ref = null + STOP_PROCESSING(SSobj, src) + if(calling_timer_id) + deltimer(calling_timer_id) + calling_timer_id = null + if(calling_loop_timer_id) + deltimer(calling_loop_timer_id) + calling_loop_timer_id = null + return ..() + +/obj/item/brick_phone_scryer/add_context(atom/source, list/context, obj/item/held_item, mob/living/user) + context[SCREENTIP_CONTEXT_CTRL_LMB] = "[label ? "Reset" : "Set"] label" + if(held_item == src) + context[SCREENTIP_CONTEXT_LMB] = (calling_mod_link_ref?.resolve() ? "Answer call" : "Call") + context[SCREENTIP_CONTEXT_RMB] = "[mod_link.link_call ? "End" : "Deny"] call" + else if(isnull(held_item)) + context[SCREENTIP_CONTEXT_RMB] = "Remove cell" + else if(istype(held_item, /obj/item/stock_parts/power_store/cell)) + context[SCREENTIP_CONTEXT_LMB] = "[cell ? "Swap" : "Add"] cell" + else if(istype(held_item, /obj/item/multitool)) + context[SCREENTIP_CONTEXT_LMB] = "Set frequency" + context[SCREENTIP_CONTEXT_RMB] = "Copy frequency" + + return CONTEXTUAL_SCREENTIP_SET + +/obj/item/brick_phone_scryer/examine(mob/user) + . = ..() + . += span_notice("[EXAMINE_HINT("Left-Click")] inhand to answer calls, [EXAMINE_HINT("Right-Click")] to deny them.") + if(cell) + . += span_notice("The battery charge reads [cell.percent()]%. [EXAMINE_HINT("Right-Click")] with an empty hand to remove it.") + else + . += span_notice("It is missing a battery. One can be installed by clicking on it with a power cell .") + . += span_notice("The MODlink ID is [mod_link.id], frequency is [mod_link.frequency || "unset"].") + . += span_notice("The MODlink label is '[label || "unset"]'.") + . += span_notice("Using a multitool, [EXAMINE_HINT("Left-Click")] to imprint or [EXAMINE_HINT("Right-Click")] to copy frequency.") + . += span_notice("[EXAMINE_HINT("Ctrl-Click")] to [label ? "reset" : "set"] its label.") + +/obj/item/brick_phone_scryer/equipped(mob/living/user, slot) + . = ..() + if(slot != ITEM_SLOT_HANDS) + mod_link?.end_call() + +/obj/item/brick_phone_scryer/dropped(mob/living/user) + . = ..() + mod_link?.end_call() + +/obj/item/brick_phone_scryer/attack_self(mob/user, modifiers) + // We're a brick phone. Always go clicky. + playsound(src, 'sound/machines/click.ogg', 50, vary = TRUE) + + var/datum/mod_link/calling_mod_link = calling_mod_link_ref?.resolve() + if(calling_mod_link) + answer_call(user) + return + + if(mod_link.link_call) + mod_link.end_call() + return + + if(QDELETED(cell)) + balloon_alert(user, "no cell installed!") + return + if(!cell.charge) + balloon_alert(user, "no charge!") + return + if(isnull(mod_link.frequency)) + balloon_alert(user, "set frequency first!") + return + check_precall_warnings(user) + call_link(user, mod_link) + +/obj/item/brick_phone_scryer/attack_self_secondary(mob/user, modifiers) + // We're a brick phone. Always go clicky. + playsound(src, 'sound/machines/click.ogg', 50, vary = TRUE) + + if(mod_link.link_call) + mod_link.end_call() + return + + var/datum/mod_link/calling_mod_link = calling_mod_link_ref?.resolve() + if(isnull(calling_mod_link)) + balloon_alert(user, "nobody calling!") + return + + balloon_alert(user, "call denied") + deny_call() + +/obj/item/brick_phone_scryer/attack_hand_secondary(mob/user, list/modifiers) + if(isnull(cell)) + return SECONDARY_ATTACK_CONTINUE_CHAIN + user.put_in_hands(cell) + balloon_alert(user, "cell removed") + playsound(src, 'sound/machines/click.ogg', 50, vary = TRUE) + return SECONDARY_ATTACK_CANCEL_ATTACK_CHAIN + +/obj/item/brick_phone_scryer/item_ctrl_click(mob/user) + if(!user.is_holding(src)) + return NONE + + // We're a brick phone. Always go clicky. + playsound(src, 'sound/machines/click.ogg', 50, vary = TRUE) + + if(label) + balloon_alert(user, "reset label") + set_label(null) + return CLICK_ACTION_SUCCESS + + var/new_label = reject_bad_text(tgui_input_text(user, "Change the visible label", "Set Label", label, MAX_NAME_LEN)) + if(QDELETED(user) || !user.is_holding(src)) + return CLICK_ACTION_BLOCKING + if(!new_label) + balloon_alert(user, "invalid label!") + return CLICK_ACTION_BLOCKING + + set_label(new_label) + balloon_alert(user, "set label") + playsound(src, 'sound/machines/click.ogg', 50, vary = TRUE) + return CLICK_ACTION_SUCCESS + +/obj/item/brick_phone_scryer/multitool_act(mob/living/user, obj/item/multitool/tool) + if(!istype(tool)) + return NONE + if(isnull(tool.buffer)) + balloon_alert(user, "buffer empty!") + return ITEM_INTERACT_BLOCKING + if(!istype(tool.buffer, /datum/mod_link)) + balloon_alert(user, "wrong buffer!") + return ITEM_INTERACT_BLOCKING + var/datum/mod_link/buffer_link = tool.buffer + if(mod_link.frequency == buffer_link.frequency) + balloon_alert(user, "same frequency!") + return ITEM_INTERACT_BLOCKING + + balloon_alert(user, "set frequency") + mod_link.frequency = buffer_link.frequency + return ITEM_INTERACT_SUCCESS + +/obj/item/brick_phone_scryer/multitool_act_secondary(mob/living/user, obj/item/multitool/tool) + if(!istype(tool)) + return NONE + if(isnull(mod_link.frequency)) + balloon_alert(user, "no frequency!") + return ITEM_INTERACT_BLOCKING + + tool.set_buffer(mod_link) + balloon_alert(user, "copied frequency") + return ITEM_INTERACT_SUCCESS + +/obj/item/brick_phone_scryer/item_interaction(mob/living/user, obj/item/tool, list/modifiers) + if(!istype(tool, /obj/item/stock_parts/power_store/cell)) + return NONE + if(!user.transferItemToLoc(tool, src)) + return NONE + + if(cell) + user.put_in_hands(cell) + balloon_alert(user, "cell swapped") + else + balloon_alert(user, "cell installed") + cell = tool + reset_incall_warnings() + playsound(src, 'sound/machines/click.ogg', 50, vary = TRUE) + +/// Gets the call time left, in deciseconds. +/obj/item/brick_phone_scryer/proc/get_call_time_left() + if(isnull(cell)) + return 0 + return (cell.charge() / BRICK_SCRYERPHONE_DISCHARGE_RATE) SECONDS + +/// Takes in a time in deciseconds, and sends the given user an "X minutes left!" warning. +/obj/item/brick_phone_scryer/proc/warn_minutes_left(mob/living/user, time_in_deciseconds) + var/seconds = FLOOR(time_in_deciseconds * 0.1, 1) + var/minutes = FLOOR(seconds / 60, 1) + if(minutes > 0) + balloon_alert(user, "[minutes] minute[minutes > 1 ? "s" : ""] left!") + else + balloon_alert(user, "no time left!") + user.playsound_local(get_turf(src), 'sound/machines/twobeep.ogg', 15, vary = TRUE) + +/// Checks whether we should send a precall warning, and does so if needed. +/obj/item/brick_phone_scryer/proc/check_precall_warnings(mob/living/user) + var/time_left = get_call_time_left() + if(time_left > BRICK_SCRYERPHONE_TIME_LEFT_START_PRECALL_WARNINGS) + return + + warn_minutes_left(user, time_left) + +/// Resets our incall warning trackers, and makes sure we don't give unnecessary warnings. +/obj/item/brick_phone_scryer/proc/reset_incall_warnings() + first_incall_warning_handled = FALSE + last_incall_warning_handled = FALSE + + var/time_left = get_call_time_left() + if(time_left <= BRICK_SCRYERPHONE_TIME_LEFT_FIRST_INCALL_WARNING) + first_incall_warning_handled = TRUE + if(time_left <= BRICK_SCRYERPHONE_TIME_LEFT_LAST_INCALL_WARNING) + last_incall_warning_handled = TRUE + +/// Checks whether we should run our incall warnings, and does so if needed. +/obj/item/brick_phone_scryer/proc/check_incall_warnings() + var/mob/living/user = get_user() + if(isnull(user)) + return + + var/time_left = get_call_time_left() + if(!first_incall_warning_handled) + if(time_left > BRICK_SCRYERPHONE_TIME_LEFT_FIRST_INCALL_WARNING) + return + warn_minutes_left(user, BRICK_SCRYERPHONE_TIME_LEFT_FIRST_INCALL_WARNING) + first_incall_warning_handled = TRUE + return + if(!last_incall_warning_handled) + if(time_left > BRICK_SCRYERPHONE_TIME_LEFT_LAST_INCALL_WARNING) + return + warn_minutes_left(user, BRICK_SCRYERPHONE_TIME_LEFT_LAST_INCALL_WARNING) + last_incall_warning_handled = TRUE + return + +/obj/item/brick_phone_scryer/process(seconds_per_tick) + if(isnull(mod_link.link_call)) + return + if(isnull(cell)) + return + cell.use(BRICK_SCRYERPHONE_DISCHARGE_RATE * seconds_per_tick, force = TRUE) + check_incall_warnings() + +/obj/item/brick_phone_scryer/proc/incoming_call_loop() + if(isnull(cell)) + return + if(!cell.charge) + return + + var/datum/mod_link/calling_mod_link = calling_mod_link_ref?.resolve() + if(isnull(calling_mod_link) || calling_mod_link.link_call) + incoming_call_end() + return + + var/mob/living/calling_user = calling_mod_link.get_user() + if(isnull(calling_user)) + incoming_call_end() + return + + calling_user.playsound_local(get_turf(calling_mod_link.holder), 'sound/machines/twobeep.ogg', 15, vary = TRUE) + if(!ringing_silenced) + playsound(src, 'sound/weapons/ring.ogg', 15, vary = TRUE) + perform_shake() + calling_loop_timer_id = addtimer(CALLBACK(src, PROC_REF(incoming_call_loop)), BRICK_SCRYERPHONE_RINGING_INTERVAL, TIMER_STOPPABLE) + +/obj/item/brick_phone_scryer/proc/incoming_call_end(buzz_us = TRUE, buzz_caller = TRUE) + if(calling_timer_id) + deltimer(calling_timer_id) + calling_timer_id = null + if(calling_loop_timer_id) + deltimer(calling_loop_timer_id) + calling_loop_timer_id = null + + if(buzz_us) + playsound(src, 'sound/machines/buzz-sigh.ogg', 15, vary = TRUE) + balloon_alert_to_viewers("call ended!") + if(buzz_caller) + var/datum/mod_link/calling_mod_link = calling_mod_link_ref?.resolve() + var/mob/living/calling_user = calling_mod_link.get_user() + if(calling_user) + calling_user.playsound_local(get_turf(calling_mod_link.holder), 'sound/machines/buzz-sigh.ogg', 15, vary = TRUE) + + calling_mod_link_ref = null + +/obj/item/brick_phone_scryer/proc/perform_shake() + var/atom/topmost_atom = get_atom_on_turf(src) + topmost_atom.Shake(pixelshiftx = 1, pixelshifty = 1, duration = 0.75 SECONDS, shake_interval = 0.02 SECONDS) + +/obj/item/brick_phone_scryer/Exited(atom/movable/gone, direction) + . = ..() + if(gone == cell) + cell = null + +/obj/item/brick_phone_scryer/proc/set_label(new_label) + label = new_label + mod_link.visual_name = new_label ? "[new_label] (Phone)" : "Unlabeled Phone" + +/obj/item/brick_phone_scryer/proc/get_user() + if(!isliving(loc)) + return null + var/mob/living/user = loc + if(!user.is_holding(src)) + return null + return user + +/obj/item/brick_phone_scryer/proc/can_call() + return cell?.charge // You can call this whenever, whatever, forever... As long as it's charged, that is. + +/obj/item/brick_phone_scryer/proc/make_link_visual() + return make_link_visual_generic(mod_link, PROC_REF(on_overlay_change)) + +/obj/item/brick_phone_scryer/proc/get_link_visual(atom/movable/visuals) + return get_link_visual_generic(mod_link, visuals, PROC_REF(on_user_set_dir)) + +/obj/item/brick_phone_scryer/proc/delete_link_visual(old_user) + return delete_link_visual_generic(mod_link, old_user) + +/obj/item/brick_phone_scryer/proc/override_called_logic(datum/mod_link/new_calling, mob/calling_user) + if(!can_call()) + new_calling.holder.balloon_alert(calling_user, "can't call!") + return TRUE + if(mod_link.link_call) + new_calling.holder.balloon_alert(calling_user, "target already in call!") + return TRUE + var/datum/mod_link/calling_mod_link = calling_mod_link_ref?.resolve() + if(calling_mod_link) + new_calling.holder.balloon_alert(calling_user, "target busy!") + return TRUE + + balloon_alert_to_viewers("incoming call!") + calling_mod_link_ref = WEAKREF(new_calling) + incoming_call_loop() + calling_timer_id = addtimer(CALLBACK(src, PROC_REF(incoming_call_end)), BRICK_SCRYERPHONE_RINGING_DURATION, TIMER_STOPPABLE) + return TRUE + +/obj/item/brick_phone_scryer/proc/answer_call(mob/living/user) + if(!istype(user)) + balloon_alert(user, "invalid user!") + return + + var/datum/mod_link/calling_mod_link = calling_mod_link_ref?.resolve() + if(isnull(calling_mod_link)) + balloon_alert(user, "no calls!") + return + + incoming_call_end(buzz_us = FALSE, buzz_caller = FALSE) + var/mob/living/calling_user = calling_mod_link.get_user() + if(isnull(calling_user)) + balloon_alert(user, "no response!") + return + if(mod_link.link_call || calling_mod_link.link_call) + balloon_alert(user, "already in a call!") + return + + new /datum/mod_link_call(calling_mod_link, mod_link) + check_precall_warnings(user) + calling_mod_link.holder.balloon_alert(calling_user, "call accepted") + +/obj/item/brick_phone_scryer/proc/deny_call() + var/datum/mod_link/calling_mod_link = calling_mod_link_ref?.resolve() + if(isnull(calling_mod_link)) + return + + incoming_call_end(buzz_us = FALSE) + var/mob/living/calling_user = calling_mod_link.get_user() + if(isnull(calling_user)) + return + calling_mod_link.holder.balloon_alert(calling_user, "call denied!") + +/obj/item/brick_phone_scryer/Hear(message, 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/brick_phone_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/brick_phone_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/brick_phone_scryer/proc/on_user_set_dir(atom/source, dir, newdir) + SIGNAL_HANDLER + on_user_set_dir_generic(mod_link, newdir || SOUTH) + + +/** + * Pre-loaded brick scryerphones + */ + +/obj/item/brick_phone_scryer/loaded/Initialize(mapload) + . = ..() + cell = new /obj/item/stock_parts/power_store/cell/high(src) + +/obj/item/brick_phone_scryer/loaded/crew + starting_frequency = MODLINK_FREQ_NANOTRASEN + +/obj/item/brick_phone_scryer/loaded/antag + starting_frequency = MODLINK_FREQ_SYNDICATE + +// Special brick phone that can swap frequencies. +/obj/item/brick_phone_scryer/loaded/antag/burner + name = "brick burnerphone" + desc = "An ancient-looking brick phone, refurbished to turn it into a MODlink-compatible device. It can only do video calls now." + +/obj/item/brick_phone_scryer/loaded/antag/burner/examine(mob/user) + . = ..() + . += span_notice("Alt-click to toggle frequency.") + +/obj/item/brick_phone_scryer/loaded/antag/burner/add_context(atom/source, list/context, obj/item/held_item, mob/living/user) + . = ..() + context[SCREENTIP_CONTEXT_ALT_LMB] = "Toggle frequency range" + +/obj/item/brick_phone_scryer/loaded/antag/burner/click_alt(mob/user) + // We're a brick phone. Always go clicky. + playsound(src, 'sound/machines/click.ogg', 50, vary = TRUE) + + if(mod_link.frequency == MODLINK_FREQ_NANOTRASEN) + mod_link.frequency = MODLINK_FREQ_SYNDICATE + balloon_alert(user, "connected to cantina") + return + + mod_link.frequency = MODLINK_FREQ_NANOTRASEN + balloon_alert(user, "connected to 9lp") + +#undef BRICK_SCRYERPHONE_RINGING_INTERVAL +#undef BRICK_SCRYERPHONE_RINGING_DURATION +#undef BRICK_SCRYERPHONE_DISCHARGE_RATE +#undef BRICK_SCRYERPHONE_TIME_LEFT_FIRST_INCALL_WARNING +#undef BRICK_SCRYERPHONE_TIME_LEFT_LAST_INCALL_WARNING +#undef BRICK_SCRYERPHONE_TIME_LEFT_START_PRECALL_WARNINGS diff --git a/maplestation_modules/code/modules/cargo/goodies.dm b/maplestation_modules/code/modules/cargo/goodies.dm index d457245377c9..91a1dd7121b6 100644 --- a/maplestation_modules/code/modules/cargo/goodies.dm +++ b/maplestation_modules/code/modules/cargo/goodies.dm @@ -45,3 +45,9 @@ contains = list( /obj/item/mana_battery/mana_crystal/lignite, ) + +/datum/supply_pack/goody/brick_scryerphone + name = "Pre-loaded Brick Scryerphone" + desc = "A single brick scryerphone. Comes pre-charged and pre-tuned." + cost = PAYCHECK_CREW * 5 + contains = list(/obj/item/brick_phone_scryer/loaded/crew) diff --git a/maplestation_modules/code/modules/loadouts/loadout_items/_loadout_datum.dm b/maplestation_modules/code/modules/loadouts/loadout_items/_loadout_datum.dm index bcd404bd6177..d4deb6ee7916 100644 --- a/maplestation_modules/code/modules/loadouts/loadout_items/_loadout_datum.dm +++ b/maplestation_modules/code/modules/loadouts/loadout_items/_loadout_datum.dm @@ -59,6 +59,9 @@ GLOBAL_LIST_INIT_TYPED(all_loadout_categories, /datum/loadout_category, init_loa /// Jobs are prioritized over departments. /// Note: You don't need to set a color for every job or department! var/list/job_greyscale_palettes + /// Whether the item has a MODlink label we should be able to set in the loadout. + /// Requires setting such to be implemented on on_equip_item for each such item. + var/has_modlink_label = FALSE /datum/loadout_item/New(category) src.category = category @@ -146,6 +149,10 @@ GLOBAL_LIST_INIT_TYPED(all_loadout_categories, /datum/loadout_category, init_loa update_loadout(manager.preferences, loadout) return TRUE // Update UI + if("set_modlink_label") + if(has_modlink_label) + return set_modlink_label(manager, user) + return TRUE /// Opens up the GAGS editing menu. @@ -367,6 +374,9 @@ GLOBAL_LIST_INIT_TYPED(all_loadout_categories, /datum/loadout_category, init_loa if(required_holiday) displayed_text[check_holidays(required_holiday) ? FA_ICON_CALENDAR_CHECK : FA_ICON_CALENDAR_XMARK] = "Only available during [required_holiday]" + if(has_modlink_label) + displayed_text[FA_ICON_PENCIL] = "Relabelable" + return displayed_text /** @@ -429,6 +439,13 @@ GLOBAL_LIST_INIT_TYPED(all_loadout_categories, /datum/loadout_category, init_loa "tooltip" = "Toggle whether this item is your family heirloom.", "required_quirk" = sanitize_css_class_name(/datum/quirk/item_quirk/family_heirloom::name), )) + if(has_modlink_label) + UNTYPED_LIST_ADD(button_list, list( + "label" = "Change MODlink Label", + "act_key" = "set_modlink_label", + "button_icon" = FA_ICON_PEN, + "active_key" = INFO_MODLINK_LABEL, + )) return button_list @@ -479,3 +496,28 @@ GLOBAL_LIST_INIT_TYPED(all_loadout_categories, /datum/loadout_category, init_loa /datum/job_department/cargo = COLOR_JOB_CARGO_GENERIC, /datum/job = COLOR_JOB_DEFAULT, // default for any job not listed above ) + +/// Sets the MODlink label of the item. +/datum/loadout_item/proc/set_modlink_label(datum/preference_middleware/loadout/manager, mob/user) + var/list/loadout = manager.preferences.read_preference(/datum/preference/loadout) + var/input_label = tgui_input_text( + user = user, + message = "What MODlink label do you want to give the [name]? Leave blank to clear.", + title = "[name] MODlink label", + default = loadout?[item_path]?[INFO_MODLINK_LABEL], // plop in existing label (if any) + max_length = MAX_DESC_LEN, + ) + if(QDELETED(src) || QDELETED(user) || QDELETED(manager) || QDELETED(manager.preferences)) + return FALSE + + loadout = manager.preferences.read_preference(/datum/preference/loadout) // Make sure no shenanigans happened + if(!loadout?[item_path]) + return FALSE + + if(input_label) + loadout[item_path][INFO_MODLINK_LABEL] = input_label + else if(input_label == "") + loadout[item_path] -= INFO_MODLINK_LABEL + + manager.preferences.update_preference(GLOB.preference_entries[/datum/preference/loadout], loadout) + return TRUE diff --git a/maplestation_modules/code/modules/loadouts/loadout_items/loadout_datum_pocket.dm b/maplestation_modules/code/modules/loadouts/loadout_items/loadout_datum_pocket.dm index 4ddc75bae3a2..8c92f638505c 100644 --- a/maplestation_modules/code/modules/loadouts/loadout_items/loadout_datum_pocket.dm +++ b/maplestation_modules/code/modules/loadouts/loadout_items/loadout_datum_pocket.dm @@ -346,3 +346,19 @@ /datum/loadout_item/pocket_items/black_parasol name = "Umbrella (Black Parasol)" item_path = /obj/item/umbrella/parasol + +/datum/loadout_item/pocket_items/brick_scryerphone + name = "Brick Scryerphone" + item_path = /obj/item/brick_phone_scryer/loaded/crew + has_modlink_label = TRUE + +/datum/loadout_item/pocket_items/brick_scryerphone/on_equip_item(obj/item/equipped_item, list/item_details, mob/living/carbon/human/equipper, datum/outfit/outfit, visuals_only) + . = ..() + if(visuals_only) + return + if(isnull(equipped_item)) + return + + var/obj/item/brick_phone_scryer/our_phone = equipped_item + var/prefs_label = item_details?[INFO_MODLINK_LABEL] + our_phone.set_label(prefs_label ? prefs_label : equipper.real_name) diff --git a/maplestation_modules/code/modules/mod/mod_link.dm b/maplestation_modules/code/modules/mod/mod_link.dm new file mode 100644 index 000000000000..10b9eb1cbbf3 --- /dev/null +++ b/maplestation_modules/code/modules/mod/mod_link.dm @@ -0,0 +1,21 @@ +/** + * Overrides to make custom scryers work. + * Non-modular overrides hooking into this are: + * - code\modules\mod\mod_link.dm (/datum/mod_link/proc/call_link(...)) + */ + +/datum/mod_link + /// A callback that allows a MODlink to override the logic when getting called. + /// Takes the calling MODlink datum and the calling user as arguments. + /// Return TRUE if we need to override. + var/datum/callback/override_called_logic_callback + /// Optional visual name to display in the call list. + var/visual_name + +/datum/mod_link/Destroy() + override_called_logic_callback = null + return ..() + +// Get the current user. For use outside of the datum. +/datum/mod_link/proc/get_user() + return get_user_callback.Invoke() diff --git a/maplestation_modules/code/modules/research/designs/comms_designs.dm b/maplestation_modules/code/modules/research/designs/comms_designs.dm new file mode 100644 index 000000000000..8b3da6454b6e --- /dev/null +++ b/maplestation_modules/code/modules/research/designs/comms_designs.dm @@ -0,0 +1,15 @@ +/datum/design/brick_scryerphone + name = "Brick Scryerphone" + desc = "A large brick of a phone modified to hook into the MODlink network." + id = "modlink_scryer_brick" + build_type = PROTOLATHE | AWAY_LATHE + materials = list( + /datum/material/iron = HALF_SHEET_MATERIAL_AMOUNT, + /datum/material/gold = SMALL_MATERIAL_AMOUNT * 3, + /datum/material/glass = SMALL_MATERIAL_AMOUNT * 3, + ) + build_path = /obj/item/brick_phone_scryer + category = list( + RND_CATEGORY_TOOLS + RND_SUBCATEGORY_TOOLS_MISC + ) + departmental_flags = DEPARTMENT_BITFLAG_ENGINEERING | DEPARTMENT_BITFLAG_SCIENCE diff --git a/maplestation_modules/code/modules/research/techweb/engi_nodes.dm b/maplestation_modules/code/modules/research/techweb/engi_nodes.dm new file mode 100644 index 000000000000..5643ac72f0da --- /dev/null +++ b/maplestation_modules/code/modules/research/techweb/engi_nodes.dm @@ -0,0 +1,4 @@ +/datum/techweb_node/holographics + id_additions = list( + "modlink_scryer_brick", + )