diff --git a/_maps/RandomRuins/SpaceRuins/oldstation.dmm b/_maps/RandomRuins/SpaceRuins/oldstation.dmm index e7ed7c54d12..d631831d1b6 100644 --- a/_maps/RandomRuins/SpaceRuins/oldstation.dmm +++ b/_maps/RandomRuins/SpaceRuins/oldstation.dmm @@ -157,6 +157,7 @@ /obj/effect/decal/cleanable/dirt, /obj/structure/table, /obj/effect/decal/cleanable/dirt, +/obj/item/clothing/neck/link_scryer/loaded/charlie, /turf/open/floor/iron, /area/ruin/space/ancientstation/charlie/bridge) "aS" = ( @@ -2468,6 +2469,7 @@ /obj/machinery/light/small/directional/west, /obj/effect/decal/cleanable/dirt, /obj/effect/spawner/random/decoration/glowstick, +/obj/item/clothing/neck/link_scryer/loaded/charlie, /turf/open/floor/iron, /area/ruin/space/ancientstation/charlie/dorms) "kO" = ( @@ -6712,6 +6714,7 @@ /obj/effect/decal/cleanable/dirt, /obj/effect/spawner/random/decoration/glowstick, /obj/structure/mirror/directional/west, +/obj/item/clothing/neck/link_scryer/loaded/charlie, /turf/open/floor/iron, /area/ruin/space/ancientstation/charlie/dorms) "Mu" = ( diff --git a/_maps/map_files/Birdshot/birdshot.dmm b/_maps/map_files/Birdshot/birdshot.dmm index 0fcb1211132..13981c84fe4 100644 --- a/_maps/map_files/Birdshot/birdshot.dmm +++ b/_maps/map_files/Birdshot/birdshot.dmm @@ -25025,19 +25025,8 @@ /turf/open/floor/iron/textured_half, /area/station/hallway/primary/aft) "jif" = ( -/obj/item/mod/core/standard{ - pixel_x = -4 - }, -/obj/item/mod/core/standard{ - pixel_x = 4 - }, -/obj/item/mod/core/standard{ - pixel_y = 4 - }, -/obj/structure/closet/crate/science{ - name = "MOD core crate" - }, /obj/effect/turf_decal/tile/neutral/fourcorners, +/obj/structure/closet/crate/mod, /turf/open/floor/iron/white, /area/station/science/robotics/augments) "jig" = ( diff --git a/_maps/map_files/Deltastation/DeltaStation2.dmm b/_maps/map_files/Deltastation/DeltaStation2.dmm index c4016d54ffa..aea6d233f1b 100644 --- a/_maps/map_files/Deltastation/DeltaStation2.dmm +++ b/_maps/map_files/Deltastation/DeltaStation2.dmm @@ -87774,18 +87774,6 @@ /turf/open/floor/iron/dark, /area/station/ai_monitored/turret_protected/ai_upload) "vWV" = ( -/obj/structure/closet/crate/science{ - anchored = 1 - }, -/obj/item/mod/core/standard{ - pixel_x = -4 - }, -/obj/item/mod/core/standard{ - pixel_x = 4 - }, -/obj/item/mod/core/standard{ - pixel_y = 4 - }, /obj/effect/turf_decal/bot, /obj/machinery/requests_console/directional/north{ department = "Robotics"; @@ -87794,6 +87782,7 @@ /obj/effect/mapping_helpers/requests_console/ore_update, /obj/effect/mapping_helpers/requests_console/supplies, /obj/machinery/light/cold/directional/north, +/obj/structure/closet/crate/mod, /turf/open/floor/iron, /area/station/science/robotics/lab) "vXh" = ( diff --git a/_maps/map_files/IceBoxStation/IceBoxStation.dmm b/_maps/map_files/IceBoxStation/IceBoxStation.dmm index cb10424c6fa..6c026799b9c 100644 --- a/_maps/map_files/IceBoxStation/IceBoxStation.dmm +++ b/_maps/map_files/IceBoxStation/IceBoxStation.dmm @@ -33855,19 +33855,10 @@ /turf/open/floor/iron, /area/station/hallway/primary/aft) "kKI" = ( -/obj/item/mod/core/standard{ - pixel_x = -4 - }, -/obj/item/mod/core/standard{ - pixel_x = 4 - }, -/obj/item/mod/core/standard{ - pixel_y = 4 - }, -/obj/structure/table, /obj/effect/turf_decal/tile/red/half/contrasted{ dir = 1 }, +/obj/structure/closet/crate/mod, /turf/open/floor/iron/white, /area/station/science/robotics/lab) "kKL" = ( diff --git a/_maps/map_files/MetaStation/MetaStation.dmm b/_maps/map_files/MetaStation/MetaStation.dmm index b93718b1dcf..7736bc37867 100644 --- a/_maps/map_files/MetaStation/MetaStation.dmm +++ b/_maps/map_files/MetaStation/MetaStation.dmm @@ -8368,18 +8368,7 @@ name = "Robotics Privacy Control"; req_access = list("robotics") }, -/obj/item/mod/core/standard{ - pixel_x = -4 - }, -/obj/item/mod/core/standard{ - pixel_x = 4 - }, -/obj/item/mod/core/standard{ - pixel_y = 4 - }, -/obj/structure/closet/crate/science{ - name = "MOD core crate" - }, +/obj/structure/closet/crate/mod, /obj/structure/disposalpipe/segment{ dir = 4 }, diff --git a/_maps/map_files/NorthStar/north_star.dmm b/_maps/map_files/NorthStar/north_star.dmm index b819e0e364a..e03f5cb7fbd 100644 --- a/_maps/map_files/NorthStar/north_star.dmm +++ b/_maps/map_files/NorthStar/north_star.dmm @@ -66820,16 +66820,7 @@ /turf/open/floor/pod/light, /area/station/maintenance/floor2/port/fore) "rwV" = ( -/obj/structure/rack, -/obj/item/mod/core/standard{ - pixel_x = 4; - pixel_y = -4 - }, -/obj/item/mod/core/standard, -/obj/item/mod/core/standard{ - pixel_x = -4; - pixel_y = 4 - }, +/obj/structure/closet/crate/mod, /turf/open/floor/iron/dark/smooth_large, /area/station/science/robotics/lab) "rwY" = ( diff --git a/_maps/map_files/tramstation/tramstation.dmm b/_maps/map_files/tramstation/tramstation.dmm index ba6cf9ab66c..f0694f70afa 100644 --- a/_maps/map_files/tramstation/tramstation.dmm +++ b/_maps/map_files/tramstation/tramstation.dmm @@ -26202,24 +26202,13 @@ /turf/open/floor/catwalk_floor, /area/station/maintenance/port/central) "ixu" = ( -/obj/structure/closet/crate/science{ - name = "MOD core crate" - }, -/obj/item/mod/core/standard{ - pixel_x = -4 - }, -/obj/item/mod/core/standard{ - pixel_x = -4 - }, -/obj/item/mod/core/standard{ - pixel_x = -4 - }, /obj/structure/noticeboard/directional/north, /obj/machinery/camera/directional/north{ c_tag = "Science - Robotics"; network = list("ss13","rd") }, /obj/effect/turf_decal/tile/neutral/fourcorners, +/obj/structure/closet/crate/mod, /turf/open/floor/iron/dark, /area/station/science/robotics/lab) "ixC" = ( diff --git a/code/__DEFINES/dcs/signals/signals_mob/signals_mob_carbon.dm b/code/__DEFINES/dcs/signals/signals_mob/signals_mob_carbon.dm index 2e305427f88..dd5b38113d3 100644 --- a/code/__DEFINES/dcs/signals/signals_mob/signals_mob_carbon.dm +++ b/code/__DEFINES/dcs/signals/signals_mob/signals_mob_carbon.dm @@ -108,6 +108,10 @@ #define COMSIG_CARBON_BLOCK_EAT (1 << 0) ///Called when a carbon vomits : (distance, force) #define COMSIG_CARBON_VOMITED "carbon_vomited" +///Called from apply_overlay(cache_index, overlay) +#define COMSIG_CARBON_APPLY_OVERLAY "carbon_apply_overlay" +///Called from remove_overlay(cache_index, overlay) +#define COMSIG_CARBON_REMOVE_OVERLAY "carbon_remove_overlay" // /mob/living/carbon/human signals diff --git a/code/__DEFINES/mod.dm b/code/__DEFINES/mod.dm index e98230a8f3b..e0b7d6ff4a3 100644 --- a/code/__DEFINES/mod.dm +++ b/code/__DEFINES/mod.dm @@ -43,5 +43,13 @@ #define MOD_ICON_OVERRIDE "mod_icon_override" #define MOD_WORN_ICON_OVERRIDE "mod_worn_icon_override" +//Defines for MODlink frequencies +#define MODLINK_FREQ_NANOTRASEN "NT" +#define MODLINK_FREQ_SYNDICATE "SYND" +#define MODLINK_FREQ_CHARLIE "CHRL" +#define MODLINK_FREQ_CENTCOM "CC" + /// Global list of all /datum/mod_theme GLOBAL_LIST_INIT(mod_themes, setup_mod_themes()) +/// Global list of all ids associated to a /datum/mod_link instance +GLOBAL_LIST_EMPTY(mod_link_ids) diff --git a/code/__DEFINES/traits.dm b/code/__DEFINES/traits.dm index 5c2e7ece5c5..5dcb3252f63 100644 --- a/code/__DEFINES/traits.dm +++ b/code/__DEFINES/traits.dm @@ -573,6 +573,9 @@ Remember to update _globalvars/traits.dm if you're adding/removing/renaming trai /// Mobs with this trait do care about a few grisly things, such as digging up graves. They also really do not like bringing people back to life or tending wounds, but love autopsies and amputations. #define TRAIT_MORBID "morbid" +/// Whether or not the user is in a MODlink call, prevents making more calls +#define TRAIT_IN_CALL "in_call" + // METABOLISMS // Various jobs on the station have historically had better reactions // to various drinks and foodstuffs. Security liking donuts is a classic @@ -760,7 +763,7 @@ Remember to update _globalvars/traits.dm if you're adding/removing/renaming trai #define TRAIT_CUSTOM_TAP_SOUND "no_tap_sound" /// Makes the feedback message when someone else is putting this item on you more noticeable #define TRAIT_DANGEROUS_OBJECT "dangerous_object" -// determines whether or not objects are haunted and teleport/attack randomly +/// determines whether or not objects are haunted and teleport/attack randomly #define TRAIT_HAUNTED "haunted" //quirk traits diff --git a/code/__DEFINES/vv.dm b/code/__DEFINES/vv.dm index 32e18e5d146..07239ca07c5 100644 --- a/code/__DEFINES/vv.dm +++ b/code/__DEFINES/vv.dm @@ -98,6 +98,7 @@ #define VV_HK_EDIT_FILTERS "edit_filters" #define VV_HK_EDIT_PARTICLES "edit_particles" #define VV_HK_EDIT_COLOR_MATRIX "edit_color_matrix" +#define VV_HK_TEST_MATRIXES "test_matrixes" #define VV_HK_ADD_AI "add_ai" #define VV_HK_ARMOR_MOD "mod_obj_armor" diff --git a/code/datums/actions/items/toggles.dm b/code/datums/actions/items/toggles.dm index d40b187b3bb..bbdd2e9f7b9 100644 --- a/code/datums/actions/items/toggles.dm +++ b/code/datums/actions/items/toggles.dm @@ -101,3 +101,7 @@ /datum/action/item_action/flip name = "Flip" + +/datum/action/item_action/call_link + name = "Call MODlink" + diff --git a/code/datums/chatmessage.dm b/code/datums/chatmessage.dm index edeb52bda04..6ee4eaddd3f 100644 --- a/code/datums/chatmessage.dm +++ b/code/datums/chatmessage.dm @@ -193,7 +193,7 @@ /datum/chatmessage/proc/finish_image_generation(mheight, atom/target, mob/owner, complete_text, lifespan) var/rough_time = REALTIMEOFDAY approx_lines = max(1, mheight / CHAT_MESSAGE_APPROX_LHEIGHT) - + var/starting_height = target.maptext_height // Translate any existing messages upwards, apply exponential decay factors to timers message_loc = isturf(target) ? target : get_atom_on_turf(target) if (owned_by.seen_messages) @@ -208,7 +208,12 @@ // When choosing to update the remaining time we have to be careful not to update the // scheduled time once the EOL has been executed. if (time_spent >= time_before_fade) - animate(m.message, pixel_y = m.message.pixel_y + mheight, time = CHAT_MESSAGE_SPAWN_TIME, flags = ANIMATION_PARALLEL) + if(m.message.pixel_y < starting_height) + var/max_height = m.message.pixel_y + m.approx_lines * CHAT_MESSAGE_APPROX_LHEIGHT - starting_height + if(max_height > 0) + animate(m.message, pixel_y = m.message.pixel_y + max_height, time = CHAT_MESSAGE_SPAWN_TIME, flags = ANIMATION_PARALLEL) + else if(mheight + starting_height >= m.message.pixel_y) + animate(m.message, pixel_y = m.message.pixel_y + mheight, time = CHAT_MESSAGE_SPAWN_TIME, flags = ANIMATION_PARALLEL) continue var/remaining_time = time_before_fade * (CHAT_MESSAGE_EXP_DECAY ** idx++) * (CHAT_MESSAGE_HEIGHT_DECAY ** combined_height) @@ -225,7 +230,12 @@ animate(alpha = 0, time = CHAT_MESSAGE_EOL_FADE) // We run this after the alpha animate, because we don't want to interrup it, but also don't want to block it by running first // Sooo instead we do this. bit messy but it fuckin works - animate(m.message, pixel_y = m.message.pixel_y + mheight, time = CHAT_MESSAGE_SPAWN_TIME, flags = ANIMATION_PARALLEL) + if(m.message.pixel_y < starting_height) + var/max_height = m.message.pixel_y + m.approx_lines * CHAT_MESSAGE_APPROX_LHEIGHT - starting_height + if(max_height > 0) + animate(m.message, pixel_y = m.message.pixel_y + max_height, time = CHAT_MESSAGE_SPAWN_TIME, flags = ANIMATION_PARALLEL) + else if(mheight + starting_height >= m.message.pixel_y) + animate(m.message, pixel_y = m.message.pixel_y + mheight, time = CHAT_MESSAGE_SPAWN_TIME, flags = ANIMATION_PARALLEL) // Reset z index if relevant if (current_z_idx >= CHAT_LAYER_MAX_Z) @@ -236,7 +246,7 @@ SET_PLANE_EXPLICIT(message, RUNECHAT_PLANE, message_loc) message.appearance_flags = APPEARANCE_UI_IGNORE_ALPHA | KEEP_APART message.alpha = 0 - message.pixel_y = target.maptext_height + message.pixel_y = starting_height message.pixel_x = -target.base_pixel_x message.maptext_width = CHAT_MESSAGE_WIDTH message.maptext_height = mheight * 1.25 // We add extra because some characters are superscript, like actions diff --git a/code/game/atoms.dm b/code/game/atoms.dm index b60e922643f..76c2e77a0d5 100644 --- a/code/game/atoms.dm +++ b/code/game/atoms.dm @@ -1271,6 +1271,7 @@ VV_DROPDOWN_OPTION(VV_HK_TRIGGER_EXPLOSION, "Explosion") VV_DROPDOWN_OPTION(VV_HK_EDIT_FILTERS, "Edit Filters") VV_DROPDOWN_OPTION(VV_HK_EDIT_COLOR_MATRIX, "Edit Color as Matrix") + VV_DROPDOWN_OPTION(VV_HK_TEST_MATRIXES, "Test Matrices") VV_DROPDOWN_OPTION(VV_HK_ADD_AI, "Add AI controller") VV_DROPDOWN_OPTION(VV_HK_ARMOR_MOD, "Modify Armor") if(greyscale_colors) @@ -1354,7 +1355,7 @@ ai_controller = new result(src) if(href_list[VV_HK_MODIFY_TRANSFORM] && check_rights(R_VAREDIT)) - var/result = input(usr, "Choose the transformation to apply","Transform Mod") as null|anything in list("Scale","Translate","Rotate") + var/result = input(usr, "Choose the transformation to apply","Transform Mod") as null|anything in list("Scale","Translate","Rotate","Shear") var/matrix/M = transform if(!result) return @@ -1371,6 +1372,12 @@ if(isnull(x) || isnull(y)) return transform = M.Translate(x,y) + if("Shear") + var/x = input(usr, "Choose x mod","Transform Mod") as null|num + var/y = input(usr, "Choose y mod","Transform Mod") as null|num + if(isnull(x) || isnull(y)) + return + transform = M.Shear(x,y) if("Rotate") var/angle = input(usr, "Choose angle to rotate","Transform Mod") as null|num if(isnull(angle)) @@ -1413,12 +1420,13 @@ vv_auto_rename(newname) if(href_list[VV_HK_EDIT_FILTERS] && check_rights(R_VAREDIT)) - var/client/C = usr.client - C?.open_filter_editor(src) + usr.client?.open_filter_editor(src) if(href_list[VV_HK_EDIT_COLOR_MATRIX] && check_rights(R_VAREDIT)) - var/client/C = usr.client - C?.open_color_matrix_editor(src) + usr.client?.open_color_matrix_editor(src) + + if(href_list[VV_HK_TEST_MATRIXES] && check_rights(R_VAREDIT)) + usr.client?.open_matrix_tester(src) /atom/vv_get_header() . = ..() @@ -1605,7 +1613,7 @@ /atom/proc/multitool_act_secondary(mob/living/user, obj/item/tool) return -///Check if the multitool has an item in it's data buffer +///Check if an item supports a data buffer (is a multitool) /atom/proc/multitool_check_buffer(user, obj/item/multitool, silent = FALSE) if(!istype(multitool, /obj/item/multitool)) if(user && !silent) @@ -2098,16 +2106,18 @@ // Now we're gonna do a scanline effect // Gonna take this atom and give it a render target, then use it as a source for a filter // (We use an atom because it seems as if setting render_target on an MA is just invalid. I hate this engine) - var/static/atom/movable/scanline - if(!scanline) - scanline = new(null) - scanline.icon = 'icons/effects/effects.dmi' - scanline.icon_state = "scanline" - // * so it doesn't render - scanline.render_target = "*HoloScanline" + var/atom/movable/scanline = new(null) + scanline.icon = 'icons/effects/effects.dmi' + scanline.icon_state = "scanline" + scanline.appearance_flags |= RESET_TRANSFORM + // * so it doesn't render + var/static/uid_scan = 0 + scanline.render_target = "*HoloScanline [uid_scan]" + uid_scan++ // Now we add it as a filter, and overlay the appearance so the render source is always around add_filter("HOLO: Scanline", 2, alpha_mask_filter(render_source = scanline.render_target)) add_overlay(scanline) + qdel(scanline) // Annd let's make the sucker emissive, so it glows in the dark if(!render_target) var/static/uid = 0 diff --git a/code/game/machinery/botlaunchpad.dm b/code/game/machinery/botlaunchpad.dm index f6fd5843244..f1c85a293c7 100644 --- a/code/game/machinery/botlaunchpad.dm +++ b/code/game/machinery/botlaunchpad.dm @@ -26,7 +26,7 @@ if(!multitool_check_buffer(user, tool)) return var/obj/item/multitool/multitool = tool - multitool.buffer = src + multitool.set_buffer(src) to_chat(user, span_notice("You save the data in the [multitool.name]'s buffer.")) return TOOL_ACT_TOOLTYPE_SUCCESS diff --git a/code/game/machinery/computer/atmos_computers/_air_sensor.dm b/code/game/machinery/computer/atmos_computers/_air_sensor.dm index a34ffedac0b..faef0370ad0 100644 --- a/code/game/machinery/computer/atmos_computers/_air_sensor.dm +++ b/code/game/machinery/computer/atmos_computers/_air_sensor.dm @@ -91,7 +91,7 @@ if(istype(multi_tool.buffer, /obj/machinery/atmospherics/components/unary/outlet_injector)) var/obj/machinery/atmospherics/components/unary/outlet_injector/input = multi_tool.buffer inlet_id = input.id_tag - multi_tool.buffer = null + multi_tool.set_buffer(null) balloon_alert(user, "connected to input") else if(istype(multi_tool.buffer, /obj/machinery/atmospherics/components/unary/vent_pump)) @@ -105,11 +105,11 @@ output.external_pressure_bound = 0 //finally assign it to this sensor outlet_id = output.id_tag - multi_tool.buffer = null + multi_tool.set_buffer(null) balloon_alert(user, "connected to output") else - multi_tool.buffer = src + multi_tool.set_buffer(src) balloon_alert(user, "added to multitool buffer") return TRUE diff --git a/code/game/machinery/computer/launchpad_control.dm b/code/game/machinery/computer/launchpad_control.dm index 4023192c90c..5590297c54a 100644 --- a/code/game/machinery/computer/launchpad_control.dm +++ b/code/game/machinery/computer/launchpad_control.dm @@ -66,7 +66,7 @@ if(M.buffer && istype(M.buffer, /obj/machinery/launchpad)) if(LAZYLEN(launchpads) < maximum_pads) launchpads |= M.buffer - M.buffer = null + M.set_buffer(null) to_chat(user, span_notice("You upload the data from the [W.name]'s buffer.")) else to_chat(user, span_warning("[src] cannot handle any more connections!")) diff --git a/code/game/machinery/computer/mechlaunchpad.dm b/code/game/machinery/computer/mechlaunchpad.dm index c89bbab7fa0..106f87f1788 100644 --- a/code/game/machinery/computer/mechlaunchpad.dm +++ b/code/game/machinery/computer/mechlaunchpad.dm @@ -100,11 +100,11 @@ if(buffered_pad in mechpads) remove_pad(buffered_pad) connect_launchpad(buffered_pad) - multitool.buffer = null + multitool.set_buffer(null) to_chat(user, span_notice("You connect the console to the pad with data from the [multitool.name]'s buffer.")) else add_pad(buffered_pad) - multitool.buffer = null + multitool.set_buffer(null) to_chat(user, span_notice("You upload the data from the [multitool.name]'s buffer.")) return TRUE diff --git a/code/game/machinery/launch_pad.dm b/code/game/machinery/launch_pad.dm index 10a46502537..f3f463bcae7 100644 --- a/code/game/machinery/launch_pad.dm +++ b/code/game/machinery/launch_pad.dm @@ -77,7 +77,7 @@ if(!multitool_check_buffer(user, I)) return var/obj/item/multitool/M = I - M.buffer = src + M.set_buffer(src) to_chat(user, span_notice("You save the data in the [I.name]'s buffer.")) return 1 diff --git a/code/game/machinery/mechlaunchpad.dm b/code/game/machinery/mechlaunchpad.dm index 10f5ab66903..54cda76f989 100644 --- a/code/game/machinery/mechlaunchpad.dm +++ b/code/game/machinery/mechlaunchpad.dm @@ -36,7 +36,7 @@ if(!multitool_check_buffer(user, tool)) return var/obj/item/multitool/multitool = tool - multitool.buffer = src + multitool.set_buffer(src) to_chat(user, span_notice("You save the data in the [multitool.name]'s buffer.")) return TRUE diff --git a/code/game/machinery/porta_turret/portable_turret.dm b/code/game/machinery/porta_turret/portable_turret.dm index 4e65f957d79..b19a8032e7d 100644 --- a/code/game/machinery/porta_turret/portable_turret.dm +++ b/code/game/machinery/porta_turret/portable_turret.dm @@ -350,7 +350,7 @@ DEFINE_BITFIELD(turret_flags, list( if(!multitool_check_buffer(user, I)) return var/obj/item/multitool/M = I - M.buffer = src + M.set_buffer(src) to_chat(user, span_notice("You add [src] to multitool buffer.")) else return ..() diff --git a/code/game/machinery/porta_turret/portable_turret_cover.dm b/code/game/machinery/porta_turret/portable_turret_cover.dm index 2ff41a17283..89f41386691 100644 --- a/code/game/machinery/porta_turret/portable_turret_cover.dm +++ b/code/game/machinery/porta_turret/portable_turret_cover.dm @@ -65,7 +65,7 @@ if(!multitool_check_buffer(user, I)) return var/obj/item/multitool/M = I - M.buffer = parent_turret + M.set_buffer(parent_turret) to_chat(user, span_notice("You add [parent_turret] to multitool buffer.")) return return ..() diff --git a/code/game/machinery/quantum_pad.dm b/code/game/machinery/quantum_pad.dm index cc14288cb73..799a37a9395 100644 --- a/code/game/machinery/quantum_pad.dm +++ b/code/game/machinery/quantum_pad.dm @@ -62,7 +62,7 @@ if(!multitool_check_buffer(user, I)) return var/obj/item/multitool/M = I - M.buffer = src + M.set_buffer(src) to_chat(user, span_notice("You save the data in [I]'s buffer. It can now be saved to pads with closed panels.")) return TRUE else if(I.tool_behaviour == TOOL_MULTITOOL) diff --git a/code/game/machinery/telecomms/machine_interactions.dm b/code/game/machinery/telecomms/machine_interactions.dm index 8b19a4f70df..f3d5f8d8352 100644 --- a/code/game/machinery/telecomms/machine_interactions.dm +++ b/code/game/machinery/telecomms/machine_interactions.dm @@ -153,10 +153,10 @@ var/obj/machinery/telecomms/T = heldmultitool.buffer . = add_new_link(T, operator) if("buffer") - heldmultitool.buffer = src + heldmultitool.set_buffer(src) . = TRUE if("flush") - heldmultitool.buffer = null + heldmultitool.set_buffer(null) . = TRUE add_act(action, params) diff --git a/code/game/machinery/teleporter.dm b/code/game/machinery/teleporter.dm index fa58a203e95..7423102e0c5 100644 --- a/code/game/machinery/teleporter.dm +++ b/code/game/machinery/teleporter.dm @@ -165,13 +165,13 @@ return var/obj/item/multitool/M = W if(panel_open) - M.buffer = src + M.set_buffer(src) to_chat(user, span_notice("You download the data to the [W.name]'s buffer.")) else if(M.buffer && istype(M.buffer, /obj/machinery/teleport/station) && M.buffer != src) if(linked_stations.len < efficiency) linked_stations.Add(M.buffer) - M.buffer = null + M.set_buffer(null) to_chat(user, span_notice("You upload the data from the [W.name]'s buffer.")) else to_chat(user, span_alert("This station can't hold more information, try to use better parts.")) diff --git a/code/game/objects/items/botpad_remote.dm b/code/game/objects/items/botpad_remote.dm index 70a74ad3be6..49c49e8cb73 100644 --- a/code/game/objects/items/botpad_remote.dm +++ b/code/game/objects/items/botpad_remote.dm @@ -40,7 +40,7 @@ connected_botpad = buffered_remote connected_botpad.connected_remote = src connected_botpad.id = id - multitool.buffer = null + multitool.set_buffer(null) to_chat(user, span_notice("You connect the controller to the pad with data from the [multitool.name]'s buffer.")) else to_chat(user, span_warning("Unable to upload!")) diff --git a/code/game/objects/items/devices/multitool.dm b/code/game/objects/items/devices/multitool.dm index 5f54b498998..0a0216e4c90 100644 --- a/code/game/objects/items/devices/multitool.dm +++ b/code/game/objects/items/devices/multitool.dm @@ -30,7 +30,7 @@ custom_premium_price = PAYCHECK_COMMAND * 3 toolspeed = 1 usesound = 'sound/weapons/empty.ogg' - var/obj/machinery/buffer // simple machine buffer for device linkage + var/datum/buffer // simple machine buffer for device linkage var/mode = 0 /obj/item/multitool/examine(mob/user) @@ -41,6 +41,17 @@ user.visible_message(span_suicide("[user] puts the [src] to [user.p_their()] chest. It looks like [user.p_theyre()] trying to pulse [user.p_their()] heart off!")) return OXYLOSS//theres a reason it wasn't recommended by doctors +/obj/item/multitool/proc/set_buffer(datum/buffer) + if(buffer) + UnregisterSignal(buffer, COMSIG_QDELETING) + if(QDELETED(buffer)) + return + src.buffer = buffer + RegisterSignal(buffer, COMSIG_QDELETING, PROC_REF(on_buffer_del)) + +/obj/item/multitool/proc/on_buffer_del(datum/source) + SIGNAL_HANDLER + buffer = null // Syndicate device disguised as a multitool; it will turn red when an AI camera is nearby. diff --git a/code/game/objects/structures/crates_lockers/crates.dm b/code/game/objects/structures/crates_lockers/crates.dm index ebf31e45b53..fc60543799c 100644 --- a/code/game/objects/structures/crates_lockers/crates.dm +++ b/code/game/objects/structures/crates_lockers/crates.dm @@ -298,6 +298,18 @@ icon_state = "scicrate" base_icon_state = "scicrate" +/obj/structure/closet/crate/mod + name = "MOD crate" + icon_state = "scicrate" + base_icon_state = "scicrate" + +/obj/structure/closet/crate/mod/PopulateContents() + ..() + for(var/i in 1 to 3) + new /obj/item/mod/core/standard(src) + for(var/i in 1 to 2) + new /obj/item/clothing/neck/link_scryer(src) + /obj/structure/closet/crate/solarpanel_small name = "budget solar panel crate" icon_state = "engi_e_crate" diff --git a/code/modules/admin/view_variables/nobody_wants_to_learn_matrix_math.dm b/code/modules/admin/view_variables/nobody_wants_to_learn_matrix_math.dm new file mode 100644 index 00000000000..872bd27d627 --- /dev/null +++ b/code/modules/admin/view_variables/nobody_wants_to_learn_matrix_math.dm @@ -0,0 +1,80 @@ + +/** + * ## nobody wants to learn matrix math! + * + * More than just a completely true statement, this datum is created as a tgui interface + * allowing you to modify each vector until you know what you're doing. + * Much like filteriffic, 'nobody wants to learn matrix math' is meant for developers like you and I + * to implement interesting matrix transformations without the hassle if needing to know... algebra? Damn, i'm stupid. + */ +/datum/nobody_wants_to_learn_matrix_math + var/atom/target + var/matrix/testing_matrix + +/datum/nobody_wants_to_learn_matrix_math/New(atom/target) + src.target = target + testing_matrix = matrix(target.transform) + +/datum/nobody_wants_to_learn_matrix_math/Destroy(force, ...) + QDEL_NULL(testing_matrix) + return ..() + +/datum/nobody_wants_to_learn_matrix_math/ui_state(mob/user) + return GLOB.admin_state + +/datum/nobody_wants_to_learn_matrix_math/ui_close(mob/user) + qdel(src) + +/datum/nobody_wants_to_learn_matrix_math/ui_interact(mob/user, datum/tgui/ui) + ui = SStgui.try_update_ui(user, src, ui) + if(!ui) + ui = new(user, src, "MatrixMathTester") + ui.open() + +/datum/nobody_wants_to_learn_matrix_math/ui_data() + var/list/data = list() + data["matrix_a"] = testing_matrix.a + data["matrix_b"] = testing_matrix.b + data["matrix_c"] = testing_matrix.c + data["matrix_d"] = testing_matrix.d + data["matrix_e"] = testing_matrix.e + data["matrix_f"] = testing_matrix.f + data["pixelated"] = target.appearance_flags & PIXEL_SCALE + return data + +/datum/nobody_wants_to_learn_matrix_math/ui_act(action, list/params) + . = ..() + if(.) + return + + switch(action) + if("change_var") + var/matrix_var_name = params["var_name"] + var/matrix_var_value = params["var_value"] + if(testing_matrix.vv_edit_var(matrix_var_name, matrix_var_value) == FALSE) + to_chat(src, "Your edit was rejected by the object. This is a bug with the matrix tester, not your fault, so report it on github.", confidential = TRUE) + return + set_transform() + if("scale") + testing_matrix.Scale(params["x"], params["y"]) + set_transform() + if("translate") + testing_matrix.Translate(params["x"], params["y"]) + set_transform() + if("shear") + testing_matrix.Shear(params["x"], params["y"]) + set_transform() + if("turn") + testing_matrix.Turn(params["angle"]) + set_transform() + if("toggle_pixel") + target.appearance_flags ^= PIXEL_SCALE + +/datum/nobody_wants_to_learn_matrix_math/proc/set_transform() + animate(target, transform = testing_matrix, time = 0.5 SECONDS) + testing_matrix = matrix(target.transform) + +/client/proc/open_matrix_tester(atom/in_atom) + if(holder) + var/datum/nobody_wants_to_learn_matrix_math/matrix_tester = new(in_atom) + matrix_tester.ui_interact(mob) diff --git a/code/modules/antagonists/pirate/pirate_shuttle_equipment.dm b/code/modules/antagonists/pirate/pirate_shuttle_equipment.dm index a99735a2d5c..0966984c671 100644 --- a/code/modules/antagonists/pirate/pirate_shuttle_equipment.dm +++ b/code/modules/antagonists/pirate/pirate_shuttle_equipment.dm @@ -183,7 +183,7 @@ . = ..() if (istype(I)) to_chat(user, span_notice("You register [src] in [I]s buffer.")) - I.buffer = src + I.set_buffer(src) return TRUE /obj/machinery/piratepad/screwdriver_act_secondary(mob/living/user, obj/item/screwdriver/screw) diff --git a/code/modules/atmospherics/machinery/components/unary_devices/bluespace_sender.dm b/code/modules/atmospherics/machinery/components/unary_devices/bluespace_sender.dm index 1febca39269..d4977eb6454 100644 --- a/code/modules/atmospherics/machinery/components/unary_devices/bluespace_sender.dm +++ b/code/modules/atmospherics/machinery/components/unary_devices/bluespace_sender.dm @@ -122,7 +122,7 @@ GLOBAL_LIST_EMPTY_TYPED(bluespace_senders, /obj/machinery/atmospherics/component /obj/machinery/atmospherics/components/unary/bluespace_sender/multitool_act(mob/living/user, obj/item/item) var/obj/item/multitool/multitool = item - multitool.buffer = src + multitool.set_buffer(src) to_chat(user, span_notice("You store linkage information in [item]'s buffer.")) return TRUE diff --git a/code/modules/atmospherics/machinery/components/unary_devices/outlet_injector.dm b/code/modules/atmospherics/machinery/components/unary_devices/outlet_injector.dm index 33de80ffbaf..269091d4bdf 100644 --- a/code/modules/atmospherics/machinery/components/unary_devices/outlet_injector.dm +++ b/code/modules/atmospherics/machinery/components/unary_devices/outlet_injector.dm @@ -48,12 +48,12 @@ if(istype(multi_tool.buffer, /obj/machinery/air_sensor)) var/obj/machinery/air_sensor/sensor = multi_tool.buffer sensor.inlet_id = id_tag - multi_tool.buffer = null + multi_tool.set_buffer(null) balloon_alert(user, "input linked to sensor") return TOOL_ACT_TOOLTYPE_SUCCESS balloon_alert(user, "saved in buffer") - multi_tool.buffer = src + multi_tool.set_buffer(src) return TOOL_ACT_TOOLTYPE_SUCCESS /obj/machinery/atmospherics/components/unary/outlet_injector/CtrlClick(mob/user) diff --git a/code/modules/atmospherics/machinery/components/unary_devices/vent_pump.dm b/code/modules/atmospherics/machinery/components/unary_devices/vent_pump.dm index 9b05e3d868e..7ca3b1fc580 100644 --- a/code/modules/atmospherics/machinery/components/unary_devices/vent_pump.dm +++ b/code/modules/atmospherics/machinery/components/unary_devices/vent_pump.dm @@ -58,12 +58,12 @@ if(istype(multi_tool.buffer, /obj/machinery/air_sensor)) var/obj/machinery/air_sensor/sensor = multi_tool.buffer sensor.outlet_id = id_tag - multi_tool.buffer = null + multi_tool.set_buffer(null) balloon_alert(user, "output linked to sensor") return TOOL_ACT_TOOLTYPE_SUCCESS balloon_alert(user, "saved in buffer") - multi_tool.buffer = src + multi_tool.set_buffer(src) return TOOL_ACT_TOOLTYPE_SUCCESS /obj/machinery/atmospherics/components/unary/vent_pump/Destroy() diff --git a/code/modules/clothing/spacesuits/specialops.dm b/code/modules/clothing/spacesuits/specialops.dm index 77c49c96d3a..3634066e72f 100644 --- a/code/modules/clothing/spacesuits/specialops.dm +++ b/code/modules/clothing/spacesuits/specialops.dm @@ -6,7 +6,7 @@ icon_state = "beret_badge" inhand_icon_state = null greyscale_colors = "#397F3F#FFCE5B" - + clothing_flags = STOPSPRESSUREDAMAGE | THICKMATERIAL | SNUG_FIT flags_inv = 0 armor_type = /datum/armor/space_beret strip_delay = 130 diff --git a/code/modules/food_and_drinks/machinery/monkeyrecycler.dm b/code/modules/food_and_drinks/machinery/monkeyrecycler.dm index 4bb70ae490b..a94f0b1f70e 100644 --- a/code/modules/food_and_drinks/machinery/monkeyrecycler.dm +++ b/code/modules/food_and_drinks/machinery/monkeyrecycler.dm @@ -99,5 +99,5 @@ GLOBAL_LIST_EMPTY(monkey_recyclers) . = ..() if(istype(I)) to_chat(user, span_notice("You log [src] in the multitool's buffer.")) - I.buffer = src + I.set_buffer(src) return TRUE diff --git a/code/modules/jobs/job_types/research_director.dm b/code/modules/jobs/job_types/research_director.dm index 82b8cdf84fe..bec6024621f 100644 --- a/code/modules/jobs/job_types/research_director.dm +++ b/code/modules/jobs/job_types/research_director.dm @@ -82,5 +82,6 @@ suit_store = /obj/item/tank/internals/oxygen back = /obj/item/mod/control/pre_equipped/research suit = null + head = null mask = /obj/item/clothing/mask/breath internals_slot = ITEM_SLOT_SUITSTORE diff --git a/code/modules/mining/machine_silo.dm b/code/modules/mining/machine_silo.dm index b06188fb2c6..07b4c714915 100644 --- a/code/modules/mining/machine_silo.dm +++ b/code/modules/mining/machine_silo.dm @@ -183,7 +183,7 @@ GLOBAL_LIST_EMPTY(silo_access_logs) . = ..() if (istype(I)) to_chat(user, span_notice("You log [src] in the multitool's buffer.")) - I.buffer = src + I.set_buffer(src) return TRUE /** diff --git a/code/modules/mining/machine_stacking.dm b/code/modules/mining/machine_stacking.dm index 1635c5c2fa3..537f6cab99d 100644 --- a/code/modules/mining/machine_stacking.dm +++ b/code/modules/mining/machine_stacking.dm @@ -26,7 +26,7 @@ if(!multitool_check_buffer(user, I)) return var/obj/item/multitool/M = I - M.buffer = src + M.set_buffer(src) to_chat(user, span_notice("You store linkage information in [I]'s buffer.")) return TRUE diff --git a/code/modules/mob/living/carbon/carbon_update_icons.dm b/code/modules/mob/living/carbon/carbon_update_icons.dm index 875bdf6d757..297f605913c 100644 --- a/code/modules/mob/living/carbon/carbon_update_icons.dm +++ b/code/modules/mob/living/carbon/carbon_update_icons.dm @@ -75,12 +75,14 @@ /mob/living/carbon/proc/apply_overlay(cache_index) if((. = overlays_standing[cache_index])) add_overlay(.) + SEND_SIGNAL(src, COMSIG_CARBON_APPLY_OVERLAY, cache_index, .) /mob/living/carbon/proc/remove_overlay(cache_index) var/I = overlays_standing[cache_index] if(I) cut_overlay(I) overlays_standing[cache_index] = null + SEND_SIGNAL(src, COMSIG_CARBON_REMOVE_OVERLAY, cache_index, I) //used when putting/removing clothes that hide certain mutant body parts to just update those and not update the whole body. /mob/living/carbon/human/proc/update_mutant_bodyparts() diff --git a/code/modules/mod/mod_activation.dm b/code/modules/mod/mod_activation.dm index 9cf40babd9e..c45752769a5 100644 --- a/code/modules/mod/mod_activation.dm +++ b/code/modules/mod/mod_activation.dm @@ -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) diff --git a/code/modules/mod/mod_control.dm b/code/modules/mod/mod_control.dm index a9c36001923..ce82bf30555 100644 --- a/code/modules/mod/mod_control.dm +++ b/code/modules/mod/mod_control.dm @@ -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 storage card.") else if(isAI(ai_assistant)) . += span_notice("You could remove [ai_assistant] with an intellicard.") + . += span_notice("You could copy/set link frequency with a multitool.") . += span_notice("You could examine it more thoroughly...") /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!") diff --git a/code/modules/mod/mod_link.dm b/code/modules/mod/mod_link.dm new file mode 100644 index 00000000000..6c462718b3e --- /dev/null +++ b/code/modules/mod/mod_link.dm @@ -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()]%. Right-click 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]. Right-click 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 ..() diff --git a/code/modules/mod/mod_theme.dm b/code/modules/mod/mod_theme.dm index 889abe67dd4..aa22f8d9950 100644 --- a/code/modules/mod/mod_theme.dm +++ b/code/modules/mod/mod_theme.dm @@ -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, \ diff --git a/code/modules/mod/mod_types.dm b/code/modules/mod/mod_types.dm index e820525ff9a..75ab14a77f1 100644 --- a/code/modules/mod/mod_types.dm +++ b/code/modules/mod/mod_types.dm @@ -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 diff --git a/code/modules/mod/mod_ui.dm b/code/modules/mod/mod_ui.dm index cbf30fe1611..06f89ddb03c 100644 --- a/code/modules/mod/mod_ui.dm +++ b/code/modules/mod/mod_ui.dm @@ -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") diff --git a/code/modules/paperwork/ticketmachine.dm b/code/modules/paperwork/ticketmachine.dm index befa61d24ec..8d62bf784cb 100644 --- a/code/modules/paperwork/ticketmachine.dm +++ b/code/modules/paperwork/ticketmachine.dm @@ -54,7 +54,7 @@ MAPPING_DIRECTIONAL_HELPERS(/obj/machinery/ticket_machine, 32) if(!multitool_check_buffer(user, I)) //make sure it has a data buffer return var/obj/item/multitool/M = I - M.buffer = src + M.set_buffer(src) to_chat(user, span_notice("You store linkage information in [I]'s buffer.")) return TRUE diff --git a/code/modules/plumbing/plumbers/teleporter.dm b/code/modules/plumbing/plumbers/teleporter.dm index a003a1528b8..7b3a62c9939 100644 --- a/code/modules/plumbing/plumbers/teleporter.dm +++ b/code/modules/plumbing/plumbers/teleporter.dm @@ -72,7 +72,7 @@ return var/obj/item/multitool/M = I - M.buffer = src + M.set_buffer(src) to_chat(user, span_notice("You store linkage information in [I]'s buffer.")) return TRUE diff --git a/code/modules/power/turbine/turbine.dm b/code/modules/power/turbine/turbine.dm index 2888bedbd0e..fedfd42014e 100644 --- a/code/modules/power/turbine/turbine.dm +++ b/code/modules/power/turbine/turbine.dm @@ -421,7 +421,7 @@ //log rotor to link later to computer balloon_alert(user, "all parts linked") var/obj/item/multitool/multitool = tool - multitool.buffer = src + multitool.set_buffer(src) to_chat(user, span_notice("You store linkage information in [tool]'s buffer.")) //success diff --git a/code/modules/research/designs/mechfabricator_designs.dm b/code/modules/research/designs/mechfabricator_designs.dm index cde05bd852c..1b9921d7178 100644 --- a/code/modules/research/designs/mechfabricator_designs.dm +++ b/code/modules/research/designs/mechfabricator_designs.dm @@ -1513,6 +1513,18 @@ RND_CATEGORY_MODSUITS + RND_SUBCATEGORY_MODSUITS_MISC ) +/datum/design/modlink_scryer + name = "MODlink Scryer" + desc = "A neck-worn piece of gear that can call with another MODlink-compatible device." + id = "modlink_scryer" + build_type = MECHFAB + materials = list(/datum/material/iron = HALF_SHEET_MATERIAL_AMOUNT, /datum/material/gold = SMALL_MATERIAL_AMOUNT * 3, /datum/material/glass = SMALL_MATERIAL_AMOUNT * 3) + construction_time = 5 SECONDS + build_path = /obj/item/clothing/neck/link_scryer + category = list( + RND_CATEGORY_MODSUITS + RND_SUBCATEGORY_MODSUITS_MISC + ) + //MODsuit modules /datum/design/module diff --git a/code/modules/research/server.dm b/code/modules/research/server.dm index cb54716675e..7665082f698 100644 --- a/code/modules/research/server.dm +++ b/code/modules/research/server.dm @@ -102,7 +102,7 @@ /obj/machinery/rnd/server/multitool_act(mob/living/user, obj/item/multitool/tool) if(!stored_research) return - tool.buffer = stored_research + tool.set_buffer(stored_research) to_chat(user, span_notice("Stored [src]'s techweb information in [tool].")) return TRUE diff --git a/code/modules/research/techweb/all_nodes.dm b/code/modules/research/techweb/all_nodes.dm index c665529637f..1bf64185152 100644 --- a/code/modules/research/techweb/all_nodes.dm +++ b/code/modules/research/techweb/all_nodes.dm @@ -1650,6 +1650,7 @@ "mod_clamp", "mod_drill", "mod_orebag", + "modlink_scryer", ) research_costs = list(TECHWEB_POINT_TYPE_GENERIC = 2500) diff --git a/code/modules/station_goals/bsa.dm b/code/modules/station_goals/bsa.dm index 7ca8f270005..fe469c6739c 100644 --- a/code/modules/station_goals/bsa.dm +++ b/code/modules/station_goals/bsa.dm @@ -54,7 +54,7 @@ GLOBAL_VAR_INIT(bsa_unlock, FALSE) if(!multitool_check_buffer(user, I)) //make sure it has a data buffer return var/obj/item/multitool/M = I - M.buffer = src + M.set_buffer(src) to_chat(user, span_notice("You store linkage information in [I]'s buffer.")) return TRUE @@ -71,7 +71,7 @@ GLOBAL_VAR_INIT(bsa_unlock, FALSE) if(!multitool_check_buffer(user, I)) //make sure it has a data buffer return var/obj/item/multitool/M = I - M.buffer = src + M.set_buffer(src) to_chat(user, span_notice("You store linkage information in [I]'s buffer.")) return TRUE @@ -94,11 +94,11 @@ GLOBAL_VAR_INIT(bsa_unlock, FALSE) if(istype(M.buffer, /obj/machinery/bsa/back)) back_ref = WEAKREF(M.buffer) to_chat(user, span_notice("You link [src] with [M.buffer].")) - M.buffer = null + M.set_buffer(null) else if(istype(M.buffer, /obj/machinery/bsa/front)) front_ref = WEAKREF(M.buffer) to_chat(user, span_notice("You link [src] with [M.buffer].")) - M.buffer = null + M.set_buffer(null) else to_chat(user, span_warning("[I]'s data buffer is empty!")) return TRUE diff --git a/icons/effects/effects.dmi b/icons/effects/effects.dmi index 4feb3a8eb54..17292014aeb 100644 Binary files a/icons/effects/effects.dmi and b/icons/effects/effects.dmi differ diff --git a/icons/hud/screen_alert.dmi b/icons/hud/screen_alert.dmi index 7cc03330969..f4911e45bc4 100644 Binary files a/icons/hud/screen_alert.dmi and b/icons/hud/screen_alert.dmi differ diff --git a/icons/mob/clothing/neck.dmi b/icons/mob/clothing/neck.dmi index e0528491403..0750f61bd82 100644 Binary files a/icons/mob/clothing/neck.dmi and b/icons/mob/clothing/neck.dmi differ diff --git a/icons/obj/clothing/neck.dmi b/icons/obj/clothing/neck.dmi index acd00a44889..ff7e012324a 100644 Binary files a/icons/obj/clothing/neck.dmi and b/icons/obj/clothing/neck.dmi differ diff --git a/tgstation.dme b/tgstation.dme index 4a9ac2e3820..ac122b6ba20 100644 --- a/tgstation.dme +++ b/tgstation.dme @@ -2591,6 +2591,7 @@ #include "code\modules\admin\view_variables\mark_datum.dm" #include "code\modules\admin\view_variables\mass_edit_variables.dm" #include "code\modules\admin\view_variables\modify_variables.dm" +#include "code\modules\admin\view_variables\nobody_wants_to_learn_matrix_math.dm" #include "code\modules\admin\view_variables\particle_editor.dm" #include "code\modules\admin\view_variables\reference_tracking.dm" #include "code\modules\admin\view_variables\tag_datum.dm" @@ -4537,6 +4538,7 @@ #include "code\modules\mod\mod_construction.dm" #include "code\modules\mod\mod_control.dm" #include "code\modules\mod\mod_core.dm" +#include "code\modules\mod\mod_link.dm" #include "code\modules\mod\mod_paint.dm" #include "code\modules\mod\mod_theme.dm" #include "code\modules\mod\mod_types.dm" diff --git a/tgui/packages/tgui/interfaces/MODsuit.tsx b/tgui/packages/tgui/interfaces/MODsuit.tsx index 400ba7ecf65..3eac0bb4911 100644 --- a/tgui/packages/tgui/interfaces/MODsuit.tsx +++ b/tgui/packages/tgui/interfaces/MODsuit.tsx @@ -35,6 +35,8 @@ type SuitStatus = { ai_name: string; has_pai: boolean; is_ai: boolean; + link_id: string; + link_call: string; }; type UserStatus = { @@ -317,6 +319,8 @@ const SuitStatusSection = (props, context) => { ai_name, has_pai, is_ai, + link_id, + link_call, } = data.suit_status; const { display_time, shift_time, shift_id } = data.module_custom_status; const status = malfunctioning @@ -377,6 +381,19 @@ const SuitStatusSection = (props, context) => { onClick={() => act('lock')} /> + +