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')}
/>
+
+
{!!open && (
Open
diff --git a/tgui/packages/tgui/interfaces/MatrixMathTester.tsx b/tgui/packages/tgui/interfaces/MatrixMathTester.tsx
new file mode 100644
index 00000000000..aa0637b78d7
--- /dev/null
+++ b/tgui/packages/tgui/interfaces/MatrixMathTester.tsx
@@ -0,0 +1,226 @@
+import { useBackend, useLocalState } from '../backend';
+import { Input, NumberInput, Section, Button, Table } from '../components';
+import { toFixed } from 'common/math';
+import { Window } from '../layouts';
+
+const MatrixMathTesterInput = (
+ props: { value: number; varName: string },
+ context
+) => {
+ const { act } = useBackend(context);
+ return (
+ toFixed(value, 3)}
+ width={'100%'}
+ onChange={(e, value) =>
+ act('change_var', { var_name: props.varName, var_value: value })
+ }
+ />
+ );
+};
+
+type MatrixData = {
+ matrix_a: number;
+ matrix_b: number;
+ matrix_c: number;
+ matrix_d: number;
+ matrix_e: number;
+ matrix_f: number;
+ pixelated: boolean;
+};
+
+export const MatrixMathTester = (props, context) => {
+ const { act, data } = useBackend(context);
+ const {
+ matrix_a,
+ matrix_b,
+ matrix_c,
+ matrix_d,
+ matrix_e,
+ matrix_f,
+ pixelated,
+ } = data;
+ const [scaleX, setScaleX] = useLocalState(context, 'scale_x', 1);
+ const [scaleY, setScaleY] = useLocalState(context, 'scale_y', 1);
+ const [translateX, setTranslateX] = useLocalState(context, 'translate_x', 0);
+ const [translateY, setTranslateY] = useLocalState(context, 'translate_y', 0);
+ const [shearX, setShearX] = useLocalState(context, 'shear_x', 0);
+ const [shearY, setShearY] = useLocalState(context, 'shear_y', 0);
+ const [angle, setAngle] = useLocalState(context, 'angle', 0);
+ return (
+
+
+
+
+
+
+ Action
+ X
+ Y
+
+
+
+
+
+ toFixed(value, 2)}
+ width={'100%'}
+ onChange={(e, value) => setScaleX(value)}
+ />
+
+
+ toFixed(value, 2)}
+ width={'100%'}
+ onChange={(e, value) => setScaleY(value)}
+ />
+
+
+
+
+
+
+ toFixed(value, 0)}
+ width={'100%'}
+ onChange={(e, value) => setTranslateX(value)}
+ />
+
+
+ toFixed(value, 0)}
+ width={'100%'}
+ onChange={(e, value) => setTranslateY(value)}
+ />
+
+
+
+
+
+
+ toFixed(value, 3)}
+ width={'100%'}
+ onChange={(e, value) => setShearX(value)}
+ />
+
+
+ toFixed(value, 3)}
+ width={'100%'}
+ onChange={(e, value) => setShearY(value)}
+ />
+
+
+
+
+
+
+ toFixed(value, 1)}
+ width={'100%'}
+ onChange={(e, value) => setAngle(value)}
+ />
+
+
+
+
+
+
+
+
+ );
+};