MODLink System (+ NWTLMM) (#77639)
## About The Pull Request A pact made with `@Kapu1178` Small changes you should not care about: RD MODsuit outfit (admin only) no longer has a beret that blocks the activation of the suit The beret used by death squad officers no longer is blocked from being put on a hat stabilizer module Admins can now Shear matrices of objects in Modify Transform Multitool buffers have been a little refactored to use a setter proc that saves them from causing hard dels Cooler stuff: A revival and remake of [Nobody Wants To Learn Matrix Math](https://github.com/tgstation/tgstation/pull/59103), this time with additional tooling for quick matrix calculations.  The MODLink system, available through every MODsuit and MODLink scryers (a neck item obtainable from advanced modsuit research or charliestation) Let's you make a holographic call with any other MODLink user, where you can chat in realtime and see what's up with em   ## Why It's Good For The Game Adds a fun way for the crew to communicate with each other that can be done in real-time with relative privacy compared to radio. ## Changelog 🆑 Fikou, Armhulen, Sheets (+rep for Mothblocks and Potato) fix: RD MODsuit outfit (admin only) no longer has a beret that blocks the activation of the suit fix: The beret used by death squad officers no longer is blocked from being put on a hat stabilizer module admin: Admins can now Shear matrices of objects in Modify Transform admin: Admins now have access to Test Matrices in the VV dropdown, an all-in-one tool for editing transforms. add: MODLink system, available through scryers (from RnD and Charlie Station) and through MODsuits. Lets you call people with holographs! /🆑
@@ -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" = (
|
||||
|
||||
@@ -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" = (
|
||||
|
||||
@@ -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" = (
|
||||
|
||||
@@ -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" = (
|
||||
|
||||
@@ -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
|
||||
},
|
||||
|
||||
@@ -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" = (
|
||||
|
||||
@@ -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" = (
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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"
|
||||
|
||||
|
||||
@@ -101,3 +101,7 @@
|
||||
|
||||
/datum/action/item_action/flip
|
||||
name = "Flip"
|
||||
|
||||
/datum/action/item_action/call_link
|
||||
name = "Call MODlink"
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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!"))
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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 ..()
|
||||
|
||||
@@ -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 ..()
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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."))
|
||||
|
||||
@@ -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!"))
|
||||
|
||||
@@ -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.
|
||||
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
/**
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -158,6 +158,7 @@
|
||||
if(!module.active || (module.allow_flags & MODULE_ALLOW_INACTIVE))
|
||||
continue
|
||||
module.on_deactivation(display_message = FALSE)
|
||||
mod_link.end_call()
|
||||
activating = TRUE
|
||||
to_chat(wearer, span_notice("MODsuit [active ? "shutting down" : "starting up"]."))
|
||||
if (ai_assistant)
|
||||
@@ -227,17 +228,15 @@
|
||||
if (!seal && wearer?.invalid_internals())
|
||||
wearer.cutoff_internals()
|
||||
|
||||
/// Finishes the suit's activation, starts processing
|
||||
/// Finishes the suit's activation
|
||||
/obj/item/mod/control/proc/finish_activation(on)
|
||||
active = on
|
||||
if(active)
|
||||
for(var/obj/item/mod/module/module as anything in modules)
|
||||
module.on_suit_activation()
|
||||
START_PROCESSING(SSobj, src)
|
||||
else
|
||||
for(var/obj/item/mod/module/module as anything in modules)
|
||||
module.on_suit_deactivation()
|
||||
STOP_PROCESSING(SSobj, src)
|
||||
update_speed()
|
||||
update_icon_state()
|
||||
wearer.update_clothing(slot_flags)
|
||||
|
||||
@@ -84,6 +84,10 @@
|
||||
var/obj/item/mod/module/selected_module
|
||||
/// AI or pAI mob inhabiting the MOD.
|
||||
var/mob/living/silicon/ai_assistant
|
||||
/// The MODlink datum, letting us call people from the suit.
|
||||
var/datum/mod_link/mod_link
|
||||
/// The starting MODlink frequency, overridden on subtypes that want it to be something.
|
||||
var/starting_frequency = null
|
||||
/// Delay between moves as AI.
|
||||
var/static/movedelay = 0
|
||||
/// Cooldown for AI moves.
|
||||
@@ -141,10 +145,10 @@
|
||||
for(var/obj/item/mod/module/module as anything in theme.inbuilt_modules)
|
||||
module = new module(src)
|
||||
install(module)
|
||||
START_PROCESSING(SSobj, src)
|
||||
|
||||
/obj/item/mod/control/Destroy()
|
||||
if(active)
|
||||
STOP_PROCESSING(SSobj, src)
|
||||
STOP_PROCESSING(SSobj, src)
|
||||
for(var/obj/item/mod/module/module as anything in modules)
|
||||
uninstall(module, deleting = TRUE)
|
||||
for(var/obj/item/part as anything in mod_parts)
|
||||
@@ -173,6 +177,7 @@
|
||||
if(core)
|
||||
QDEL_NULL(core)
|
||||
QDEL_NULL(wires)
|
||||
QDEL_NULL(mod_link)
|
||||
return ..()
|
||||
|
||||
/obj/item/mod/control/atom_destruction(damage_flag)
|
||||
@@ -217,6 +222,7 @@
|
||||
. += span_notice("You could install an AI or pAI using their <b>storage card</b>.")
|
||||
else if(isAI(ai_assistant))
|
||||
. += span_notice("You could remove [ai_assistant] with an <b>intellicard</b>.")
|
||||
. += span_notice("You could copy/set link frequency with a <b>multitool</b>.")
|
||||
. += span_notice("<i>You could examine it more thoroughly...</i>")
|
||||
|
||||
/obj/item/mod/control/examine_more(mob/user)
|
||||
@@ -226,13 +232,17 @@
|
||||
/obj/item/mod/control/process(seconds_per_tick)
|
||||
if(seconds_electrified > MACHINE_NOT_ELECTRIFIED)
|
||||
seconds_electrified--
|
||||
if(mod_link.link_call)
|
||||
subtract_charge((DEFAULT_CHARGE_DRAIN * 0.25) * seconds_per_tick)
|
||||
if(!active)
|
||||
return
|
||||
if(!get_charge() && active && !activating)
|
||||
power_off()
|
||||
return PROCESS_KILL
|
||||
return
|
||||
var/malfunctioning_charge_drain = 0
|
||||
if(malfunctioning)
|
||||
malfunctioning_charge_drain = rand(1,20)
|
||||
subtract_charge((charge_drain + malfunctioning_charge_drain)*seconds_per_tick)
|
||||
subtract_charge((charge_drain + malfunctioning_charge_drain) * seconds_per_tick)
|
||||
update_charge_alert()
|
||||
for(var/obj/item/mod/module/module as anything in modules)
|
||||
if(malfunctioning && module.active && SPT_PROB(5, seconds_per_tick))
|
||||
@@ -493,6 +503,7 @@
|
||||
retract(null, part)
|
||||
if(active)
|
||||
finish_activation(on = FALSE)
|
||||
mod_link?.end_call()
|
||||
var/mob/old_wearer = wearer
|
||||
unset_wearer()
|
||||
old_wearer.temporarilyRemoveItemFromInventory(src)
|
||||
@@ -587,6 +598,10 @@
|
||||
QDEL_LIST_ASSOC_VAL(old_module.pinned_to)
|
||||
old_module.mod = null
|
||||
|
||||
/// Intended for callbacks, don't use normally, just get wearer by itself.
|
||||
/obj/item/mod/control/proc/get_wearer()
|
||||
return wearer
|
||||
|
||||
/obj/item/mod/control/proc/update_access(mob/user, obj/item/card/id/card)
|
||||
if(!allowed(user))
|
||||
balloon_alert(user, "insufficient access!")
|
||||
|
||||
@@ -0,0 +1,520 @@
|
||||
/proc/make_link_visual_generic(datum/mod_link/mod_link, proc_path)
|
||||
var/mob/living/user = mod_link.get_user_callback.Invoke()
|
||||
var/obj/effect/overlay/link_visual = new()
|
||||
link_visual.name = "holocall ([mod_link.id])"
|
||||
link_visual.mouse_opacity = MOUSE_OPACITY_TRANSPARENT
|
||||
LAZYADD(mod_link.holder.update_on_z, link_visual)
|
||||
link_visual.appearance_flags |= KEEP_TOGETHER
|
||||
link_visual.makeHologram(0.75)
|
||||
mod_link.visual_overlays = user.overlays - user.active_thinking_indicator
|
||||
link_visual.add_overlay(mod_link.visual_overlays)
|
||||
mod_link.visual = link_visual
|
||||
mod_link.holder.become_hearing_sensitive(REF(mod_link))
|
||||
mod_link.holder.RegisterSignals(user, list(COMSIG_CARBON_APPLY_OVERLAY, COMSIG_CARBON_REMOVE_OVERLAY), proc_path)
|
||||
return link_visual
|
||||
|
||||
/proc/get_link_visual_generic(datum/mod_link/mod_link, atom/movable/visuals, proc_path)
|
||||
var/mob/living/user = mod_link.get_user_callback.Invoke()
|
||||
playsound(mod_link.holder, 'sound/machines/terminal_processing.ogg', 50, vary = TRUE)
|
||||
visuals.add_overlay(mutable_appearance('icons/effects/effects.dmi', "static_base", TURF_LAYER))
|
||||
visuals.add_overlay(mutable_appearance('icons/effects/effects.dmi', "modlink", ABOVE_ALL_MOB_LAYER))
|
||||
visuals.add_filter("crop_square", 1, alpha_mask_filter(icon = icon('icons/effects/effects.dmi', "modlink_filter")))
|
||||
visuals.maptext_height = 6
|
||||
visuals.alpha = 0
|
||||
user.vis_contents += visuals
|
||||
visuals.forceMove(user)
|
||||
animate(visuals, 0.5 SECONDS, alpha = 255)
|
||||
var/datum/callback/setdir_callback = CALLBACK(mod_link.holder, proc_path)
|
||||
setdir_callback.Invoke(user, user.dir, user.dir)
|
||||
mod_link.holder.RegisterSignal(mod_link.holder.loc, COMSIG_ATOM_DIR_CHANGE, proc_path)
|
||||
|
||||
/proc/delete_link_visual_generic(datum/mod_link/mod_link)
|
||||
var/mob/living/user = mod_link.get_user_callback.Invoke()
|
||||
playsound(mod_link.get_other().holder, 'sound/machines/terminal_processing.ogg', 50, vary = TRUE, frequency = -1)
|
||||
LAZYREMOVE(mod_link.holder.update_on_z, mod_link.visual)
|
||||
mod_link.holder.lose_hearing_sensitivity(REF(mod_link))
|
||||
mod_link.holder.UnregisterSignal(user, list(COMSIG_CARBON_APPLY_OVERLAY, COMSIG_CARBON_REMOVE_OVERLAY, COMSIG_ATOM_DIR_CHANGE))
|
||||
QDEL_NULL(mod_link.visual)
|
||||
|
||||
/proc/on_user_set_dir_generic(datum/mod_link/mod_link, newdir)
|
||||
var/atom/other_visual = mod_link.get_other().visual
|
||||
if(!newdir) //can sometimes be null or 0
|
||||
return
|
||||
other_visual.setDir(SOUTH)
|
||||
other_visual.pixel_x = 0
|
||||
other_visual.pixel_y = 0
|
||||
var/matrix/new_transform = matrix()
|
||||
if(newdir & NORTH)
|
||||
other_visual.pixel_y = 13
|
||||
other_visual.layer = BELOW_MOB_LAYER
|
||||
SET_PLANE_IMPLICIT(other_visual, GAME_PLANE_FOV_HIDDEN)
|
||||
if(newdir & SOUTH)
|
||||
other_visual.pixel_y = -24
|
||||
other_visual.layer = ABOVE_ALL_MOB_LAYER
|
||||
SET_PLANE_IMPLICIT(other_visual, GAME_PLANE_UPPER_FOV_HIDDEN)
|
||||
new_transform.Scale(-1, 1)
|
||||
new_transform.Translate(-1, 0)
|
||||
if(newdir & EAST)
|
||||
other_visual.pixel_x = 14
|
||||
other_visual.layer = BELOW_MOB_LAYER
|
||||
SET_PLANE_IMPLICIT(other_visual, GAME_PLANE_FOV_HIDDEN)
|
||||
new_transform.Shear(0.5, 0)
|
||||
new_transform.Scale(0.65, 1)
|
||||
if(newdir & WEST)
|
||||
other_visual.pixel_x = -14
|
||||
other_visual.layer = BELOW_MOB_LAYER
|
||||
SET_PLANE_IMPLICIT(other_visual, GAME_PLANE_FOV_HIDDEN)
|
||||
new_transform.Shear(-0.5, 0)
|
||||
new_transform.Scale(0.65, 1)
|
||||
other_visual.transform = new_transform
|
||||
|
||||
/obj/item/mod/control/Initialize(mapload, datum/mod_theme/new_theme, new_skin, obj/item/mod/core/new_core)
|
||||
. = ..()
|
||||
mod_link = new(
|
||||
src,
|
||||
starting_frequency,
|
||||
CALLBACK(src, PROC_REF(get_wearer)),
|
||||
CALLBACK(src, PROC_REF(can_call)),
|
||||
CALLBACK(src, PROC_REF(make_link_visual)),
|
||||
CALLBACK(src, PROC_REF(get_link_visual)),
|
||||
CALLBACK(src, PROC_REF(delete_link_visual))
|
||||
)
|
||||
|
||||
/obj/item/mod/control/multitool_act_secondary(mob/living/user, obj/item/multitool/tool)
|
||||
if(!multitool_check_buffer(user, tool))
|
||||
return
|
||||
var/tool_frequency = null
|
||||
if(istype(tool.buffer, /datum/mod_link))
|
||||
var/datum/mod_link/buffer_link = tool.buffer
|
||||
tool_frequency = buffer_link.frequency
|
||||
if(!tool_frequency && mod_link.frequency)
|
||||
tool.set_buffer(mod_link)
|
||||
else if(tool_frequency && !mod_link.frequency)
|
||||
mod_link.frequency = tool_frequency
|
||||
else if(tool_frequency && mod_link.frequency)
|
||||
var/response = tgui_alert(user, "Would you like to copy or imprint the frequency?", "MODlink Frequency", list("Copy", "Imprint"))
|
||||
if(!user.is_holding(tool))
|
||||
return
|
||||
switch(response)
|
||||
if("Copy")
|
||||
tool.set_buffer(mod_link)
|
||||
if("Imprint")
|
||||
mod_link.frequency = tool_frequency
|
||||
|
||||
/obj/item/mod/control/proc/can_call()
|
||||
return wearer && wearer.stat < DEAD
|
||||
|
||||
/obj/item/mod/control/proc/make_link_visual()
|
||||
return make_link_visual_generic(mod_link, PROC_REF(on_overlay_change))
|
||||
|
||||
/obj/item/mod/control/proc/get_link_visual(atom/movable/visuals)
|
||||
return get_link_visual_generic(mod_link, visuals, PROC_REF(on_wearer_set_dir))
|
||||
|
||||
/obj/item/mod/control/proc/delete_link_visual()
|
||||
return delete_link_visual_generic(mod_link)
|
||||
|
||||
/obj/item/mod/control/Hear(message, atom/movable/speaker, message_language, raw_message, radio_freq, list/spans, list/message_mods, message_range)
|
||||
. = ..()
|
||||
if(speaker != wearer && speaker != ai_assistant)
|
||||
return
|
||||
mod_link.visual.say(raw_message, sanitize = FALSE, message_range = 2)
|
||||
|
||||
/obj/item/mod/control/proc/on_overlay_change(atom/source, cache_index, overlay)
|
||||
SIGNAL_HANDLER
|
||||
addtimer(CALLBACK(src, PROC_REF(update_link_visual)), 1 TICKS, TIMER_UNIQUE)
|
||||
|
||||
/obj/item/mod/control/proc/update_link_visual()
|
||||
if(QDELETED(mod_link.link_call))
|
||||
return
|
||||
mod_link.visual.cut_overlay(mod_link.visual_overlays)
|
||||
mod_link.visual_overlays = wearer.overlays - wearer.active_thinking_indicator
|
||||
mod_link.visual.add_overlay(mod_link.visual_overlays)
|
||||
|
||||
/obj/item/mod/control/proc/on_wearer_set_dir(atom/source, dir, newdir)
|
||||
SIGNAL_HANDLER
|
||||
on_user_set_dir_generic(mod_link, newdir)
|
||||
|
||||
/obj/item/clothing/neck/link_scryer
|
||||
name = "\improper MODlink scryer"
|
||||
desc = "An intricate piece of machinery that creates a holographic video call with another MODlink-compatible device. Essentially a video necklace."
|
||||
icon_state = "modlink"
|
||||
actions_types = list(/datum/action/item_action/call_link)
|
||||
/// The installed power cell.
|
||||
var/obj/item/stock_parts/cell/cell
|
||||
/// The MODlink datum we operate.
|
||||
var/datum/mod_link/mod_link
|
||||
/// Initial frequency of the MODlink.
|
||||
var/starting_frequency = "NT"
|
||||
/// An additional name tag for the scryer, seen as "MODlink scryer - [label]"
|
||||
var/label
|
||||
|
||||
/obj/item/clothing/neck/link_scryer/Initialize(mapload)
|
||||
. = ..()
|
||||
mod_link = new(
|
||||
src,
|
||||
starting_frequency,
|
||||
CALLBACK(src, PROC_REF(get_user)),
|
||||
CALLBACK(src, PROC_REF(can_call)),
|
||||
CALLBACK(src, PROC_REF(make_link_visual)),
|
||||
CALLBACK(src, PROC_REF(get_link_visual)),
|
||||
CALLBACK(src, PROC_REF(delete_link_visual))
|
||||
)
|
||||
START_PROCESSING(SSobj, src)
|
||||
|
||||
/obj/item/clothing/neck/link_scryer/Destroy()
|
||||
QDEL_NULL(cell)
|
||||
QDEL_NULL(mod_link)
|
||||
STOP_PROCESSING(SSobj, src)
|
||||
return ..()
|
||||
|
||||
/obj/item/clothing/neck/link_scryer/examine(mob/user)
|
||||
. = ..()
|
||||
if(cell)
|
||||
. += span_notice("The battery charge reads [cell.percent()]%. <b>Right-click</b> with an empty hand to remove it.")
|
||||
else
|
||||
. += span_notice("It is missing a battery, one can be installed by clicking with a power cell on it.")
|
||||
. += span_notice("The MODlink ID is [mod_link.id], frequency is [mod_link.frequency]. <b>Right-click</b> with multitool to copy/imprint frequency.")
|
||||
. += span_notice("Use in hand to set name.")
|
||||
|
||||
/obj/item/clothing/neck/link_scryer/equipped(mob/living/user, slot)
|
||||
. = ..()
|
||||
if(slot != ITEM_SLOT_NECK)
|
||||
mod_link?.end_call()
|
||||
|
||||
/obj/item/clothing/neck/link_scryer/dropped(mob/living/user)
|
||||
. = ..()
|
||||
mod_link?.end_call()
|
||||
|
||||
/obj/item/clothing/neck/link_scryer/attack_self(mob/user, modifiers)
|
||||
var/new_label = reject_bad_text(tgui_input_text(user, "Change the visible name", "Set Name", label, MAX_NAME_LEN))
|
||||
if(!new_label)
|
||||
balloon_alert(user, "invalid name!")
|
||||
return
|
||||
label = new_label
|
||||
balloon_alert(user, "name set")
|
||||
update_name()
|
||||
|
||||
/obj/item/clothing/neck/link_scryer/process(seconds_per_tick)
|
||||
if(!mod_link.link_call)
|
||||
return
|
||||
cell.use(min(20 * seconds_per_tick, cell.charge))
|
||||
|
||||
/obj/item/clothing/neck/link_scryer/attackby(obj/item/attacked_by, mob/user, params)
|
||||
. = ..()
|
||||
if(cell || !istype(attacked_by, /obj/item/stock_parts/cell))
|
||||
return
|
||||
if(!user.transferItemToLoc(attacked_by, src))
|
||||
return
|
||||
cell = attacked_by
|
||||
balloon_alert(user, "installed [cell.name]")
|
||||
|
||||
/obj/item/clothing/neck/link_scryer/update_name(updates)
|
||||
. = ..()
|
||||
name = "[initial(name)][label ? " - [label]" : ""]"
|
||||
|
||||
/obj/item/clothing/neck/link_scryer/Exited(atom/movable/gone, direction)
|
||||
. = ..()
|
||||
if(gone == cell)
|
||||
cell = null
|
||||
|
||||
/obj/item/clothing/neck/link_scryer/attack_hand_secondary(mob/user, list/modifiers)
|
||||
if(!cell)
|
||||
return SECONDARY_ATTACK_CONTINUE_CHAIN
|
||||
balloon_alert(user, "removed [cell.name]")
|
||||
user.put_in_hands(cell)
|
||||
return SECONDARY_ATTACK_CANCEL_ATTACK_CHAIN
|
||||
|
||||
/obj/item/clothing/neck/link_scryer/multitool_act_secondary(mob/living/user, obj/item/multitool/tool)
|
||||
if(!multitool_check_buffer(user, tool))
|
||||
return
|
||||
var/tool_frequency = null
|
||||
if(istype(tool.buffer, /datum/mod_link))
|
||||
var/datum/mod_link/buffer_link = tool.buffer
|
||||
tool_frequency = buffer_link.frequency
|
||||
if(!tool_frequency && mod_link.frequency)
|
||||
tool.set_buffer(mod_link)
|
||||
else if(tool_frequency && !mod_link.frequency)
|
||||
mod_link.frequency = tool_frequency
|
||||
else if(tool_frequency && mod_link.frequency)
|
||||
var/response = tgui_alert(user, "Would you like to copy or imprint the frequency?", "MODlink Frequency", list("Copy", "Imprint"))
|
||||
switch(response)
|
||||
if("Copy")
|
||||
tool.set_buffer(mod_link)
|
||||
if("Imprint")
|
||||
mod_link.frequency = tool_frequency
|
||||
|
||||
/obj/item/clothing/neck/link_scryer/worn_overlays(mutable_appearance/standing, isinhands)
|
||||
. = ..()
|
||||
if(!QDELETED(mod_link.link_call))
|
||||
. += mutable_appearance('icons/mob/clothing/neck.dmi', "modlink_active")
|
||||
|
||||
/obj/item/clothing/neck/link_scryer/ui_action_click(mob/user)
|
||||
if(mod_link.link_call)
|
||||
mod_link.end_call()
|
||||
else
|
||||
call_link(user, mod_link)
|
||||
|
||||
/obj/item/clothing/neck/link_scryer/proc/get_user()
|
||||
var/mob/living/carbon/user = loc
|
||||
return istype(user) && user.wear_neck == src ? user : null
|
||||
|
||||
/obj/item/clothing/neck/link_scryer/proc/can_call()
|
||||
var/mob/living/user = loc
|
||||
return istype(user) && cell.charge && user.stat < DEAD
|
||||
|
||||
/obj/item/clothing/neck/link_scryer/proc/make_link_visual()
|
||||
var/mob/living/user = mod_link.get_user_callback.Invoke()
|
||||
user.update_worn_neck()
|
||||
return make_link_visual_generic(mod_link, PROC_REF(on_overlay_change))
|
||||
|
||||
/obj/item/clothing/neck/link_scryer/proc/get_link_visual(atom/movable/visuals)
|
||||
return get_link_visual_generic(mod_link, visuals, PROC_REF(on_user_set_dir))
|
||||
|
||||
/obj/item/clothing/neck/link_scryer/proc/delete_link_visual()
|
||||
var/mob/living/user = mod_link.get_user_callback.Invoke()
|
||||
if(!QDELETED(user))
|
||||
user.update_worn_neck()
|
||||
return delete_link_visual_generic(mod_link)
|
||||
|
||||
/obj/item/clothing/neck/link_scryer/Hear(message, atom/movable/speaker, message_language, raw_message, radio_freq, list/spans, list/message_mods, message_range)
|
||||
. = ..()
|
||||
if(speaker != loc)
|
||||
return
|
||||
mod_link.visual.say(raw_message, sanitize = FALSE, message_range = 3)
|
||||
|
||||
/obj/item/clothing/neck/link_scryer/proc/on_overlay_change(atom/source, cache_index, overlay)
|
||||
SIGNAL_HANDLER
|
||||
addtimer(CALLBACK(src, PROC_REF(update_link_visual)), 1 TICKS, TIMER_UNIQUE)
|
||||
|
||||
/obj/item/clothing/neck/link_scryer/proc/update_link_visual()
|
||||
if(QDELETED(mod_link.link_call))
|
||||
return
|
||||
var/mob/living/user = loc
|
||||
mod_link.visual.cut_overlay(mod_link.visual_overlays)
|
||||
mod_link.visual_overlays = user.overlays - user.active_thinking_indicator
|
||||
mod_link.visual.add_overlay(mod_link.visual_overlays)
|
||||
|
||||
/obj/item/clothing/neck/link_scryer/proc/on_user_set_dir(atom/source, dir, newdir)
|
||||
SIGNAL_HANDLER
|
||||
on_user_set_dir_generic(mod_link, newdir)
|
||||
|
||||
/obj/item/clothing/neck/link_scryer/loaded/Initialize(mapload)
|
||||
. = ..()
|
||||
cell = new /obj/item/stock_parts/cell/high(src)
|
||||
|
||||
/obj/item/clothing/neck/link_scryer/loaded/charlie
|
||||
starting_frequency = MODLINK_FREQ_CHARLIE
|
||||
|
||||
/// A MODlink datum, used to handle unique functions that will be used in the MODlink call.
|
||||
/datum/mod_link
|
||||
/// The frequency of the MODlink. You can only call other MODlinks on the same frequency.
|
||||
var/frequency
|
||||
/// The unique ID of the MODlink.
|
||||
var/id = ""
|
||||
/// The atom that holds the MODlink.
|
||||
var/atom/movable/holder
|
||||
/// A reference to the visuals generated by the MODlink.
|
||||
var/atom/movable/visual
|
||||
/// A list of all overlays of the user, copied everytime they have an overlay change.
|
||||
var/list/visual_overlays = list()
|
||||
/// A reference to the call between two MODlinks.
|
||||
var/datum/mod_link_call/link_call
|
||||
/// A callback that returns the user of the MODlink.
|
||||
var/datum/callback/get_user_callback
|
||||
/// A callback that returns whether the MODlink can currently call.
|
||||
var/datum/callback/can_call_callback
|
||||
/// A callback that returns the visuals of the MODlink.
|
||||
var/datum/callback/make_visual_callback
|
||||
/// A callback that receives the visuals of the other MODlink.
|
||||
var/datum/callback/get_visual_callback
|
||||
/// A callback that deletes the visuals of the MODlink.
|
||||
var/datum/callback/delete_visual_callback
|
||||
|
||||
/datum/mod_link/New(
|
||||
atom/holder,
|
||||
frequency,
|
||||
datum/callback/get_user_callback,
|
||||
datum/callback/can_call_callback,
|
||||
datum/callback/make_visual_callback,
|
||||
datum/callback/get_visual_callback,
|
||||
datum/callback/delete_visual_callback
|
||||
)
|
||||
var/attempts = 0
|
||||
var/digits_to_make = 3
|
||||
do
|
||||
if(attempts == 10)
|
||||
attempts = 0
|
||||
digits_to_make++
|
||||
id = ""
|
||||
for(var/i in 1 to digits_to_make)
|
||||
id += num2text(rand(0,9))
|
||||
attempts++
|
||||
while(GLOB.mod_link_ids[id])
|
||||
GLOB.mod_link_ids[id] = src
|
||||
src.frequency = frequency
|
||||
src.holder = holder
|
||||
src.get_user_callback = get_user_callback
|
||||
src.can_call_callback = can_call_callback
|
||||
src.make_visual_callback = make_visual_callback
|
||||
src.get_visual_callback = get_visual_callback
|
||||
src.delete_visual_callback = delete_visual_callback
|
||||
RegisterSignal(holder, COMSIG_QDELETING, PROC_REF(on_holder_delete))
|
||||
|
||||
/datum/mod_link/Destroy()
|
||||
GLOB.mod_link_ids -= id
|
||||
if(link_call)
|
||||
end_call()
|
||||
return ..()
|
||||
|
||||
/datum/mod_link/proc/get_other()
|
||||
RETURN_TYPE(/datum/mod_link)
|
||||
if(!link_call)
|
||||
return
|
||||
return link_call.caller == src ? link_call.receiver : link_call.caller
|
||||
|
||||
/datum/mod_link/proc/call_link(datum/mod_link/called, mob/user)
|
||||
if(!frequency)
|
||||
return
|
||||
if(!istype(called))
|
||||
holder.balloon_alert(user, "invalid target!")
|
||||
return
|
||||
var/mob/living/link_user = get_user_callback.Invoke()
|
||||
if(!link_user)
|
||||
return
|
||||
if(HAS_TRAIT(link_user, TRAIT_IN_CALL))
|
||||
holder.balloon_alert(user, "user already in call!")
|
||||
return
|
||||
var/mob/living/link_target = called.get_user_callback.Invoke()
|
||||
if(!link_target)
|
||||
holder.balloon_alert(user, "invalid target!")
|
||||
return
|
||||
if(HAS_TRAIT(link_target, TRAIT_IN_CALL))
|
||||
holder.balloon_alert(user, "target already in call!")
|
||||
return
|
||||
if(!can_call_callback.Invoke() || !called.can_call_callback.Invoke())
|
||||
holder.balloon_alert(user, "can't call!")
|
||||
return
|
||||
link_target.playsound_local(get_turf(called.holder), 'sound/weapons/ring.ogg', 15, vary = TRUE)
|
||||
var/atom/movable/screen/alert/modlink_call/alert = link_target.throw_alert("[REF(src)]_modlink", /atom/movable/screen/alert/modlink_call)
|
||||
alert.desc = "[holder] ([id]) is calling you! Left-click this to accept the call. Right-click to deny it."
|
||||
alert.caller_ref = WEAKREF(src)
|
||||
alert.receiver_ref = WEAKREF(called)
|
||||
alert.user_ref = WEAKREF(user)
|
||||
|
||||
/datum/mod_link/proc/end_call()
|
||||
QDEL_NULL(link_call)
|
||||
|
||||
/datum/mod_link/proc/on_holder_delete(atom/source)
|
||||
SIGNAL_HANDLER
|
||||
qdel(src)
|
||||
|
||||
/// A MODlink call datum, used to handle the call between two MODlinks.
|
||||
/datum/mod_link_call
|
||||
/// The MODlink that is calling.
|
||||
var/datum/mod_link/caller
|
||||
/// The MODlink that is being called.
|
||||
var/datum/mod_link/receiver
|
||||
|
||||
/datum/mod_link_call/New(datum/mod_link/caller, datum/mod_link/receiver)
|
||||
caller.link_call = src
|
||||
receiver.link_call = src
|
||||
src.caller = caller
|
||||
src.receiver = receiver
|
||||
var/mob/living/caller_mob = caller.get_user_callback.Invoke()
|
||||
ADD_TRAIT(caller_mob, TRAIT_IN_CALL, REF(src))
|
||||
var/mob/living/receiver_mob = receiver.get_user_callback.Invoke()
|
||||
ADD_TRAIT(receiver_mob, TRAIT_IN_CALL, REF(src))
|
||||
make_visuals()
|
||||
START_PROCESSING(SSprocessing, src)
|
||||
|
||||
/datum/mod_link_call/Destroy()
|
||||
var/mob/living/caller_mob = caller.get_user_callback.Invoke()
|
||||
if(!QDELETED(caller_mob))
|
||||
REMOVE_TRAIT(caller_mob, TRAIT_IN_CALL, REF(src))
|
||||
var/mob/living/receiver_mob = receiver.get_user_callback.Invoke()
|
||||
if(!QDELETED(receiver_mob))
|
||||
REMOVE_TRAIT(receiver_mob, TRAIT_IN_CALL, REF(src))
|
||||
STOP_PROCESSING(SSprocessing, src)
|
||||
clear_visuals()
|
||||
caller.link_call = null
|
||||
receiver.link_call = null
|
||||
return ..()
|
||||
|
||||
/datum/mod_link_call/process(seconds_per_tick)
|
||||
if(can_continue_call())
|
||||
return
|
||||
qdel(src)
|
||||
|
||||
/datum/mod_link_call/proc/can_continue_call()
|
||||
return caller.frequency == receiver.frequency && caller.can_call_callback.Invoke() && receiver.can_call_callback.Invoke()
|
||||
|
||||
/datum/mod_link_call/proc/make_visuals()
|
||||
var/caller_visual = caller.make_visual_callback.Invoke()
|
||||
var/receiver_visual = receiver.make_visual_callback.Invoke()
|
||||
caller.get_visual_callback.Invoke(receiver_visual)
|
||||
receiver.get_visual_callback.Invoke(caller_visual)
|
||||
|
||||
/datum/mod_link_call/proc/clear_visuals()
|
||||
caller.delete_visual_callback.Invoke()
|
||||
receiver.delete_visual_callback.Invoke()
|
||||
|
||||
/proc/call_link(mob/user, datum/mod_link/calling_link)
|
||||
if(!calling_link.frequency)
|
||||
return
|
||||
var/list/callers = list()
|
||||
for(var/id in GLOB.mod_link_ids)
|
||||
var/datum/mod_link/link = GLOB.mod_link_ids[id]
|
||||
if(link.frequency != calling_link.frequency)
|
||||
continue
|
||||
if(link == calling_link)
|
||||
continue
|
||||
if(!link.can_call_callback.Invoke())
|
||||
continue
|
||||
callers["[link.holder] ([id])"] = id
|
||||
if(!length(callers))
|
||||
calling_link.holder.balloon_alert(user, "no targets on freq [calling_link.frequency]!")
|
||||
return
|
||||
var/chosen_link = tgui_input_list(user, "Choose ID to call from [calling_link.frequency] frequency", "MODlink", callers)
|
||||
if(!chosen_link)
|
||||
return
|
||||
calling_link.call_link(GLOB.mod_link_ids[callers[chosen_link]], user)
|
||||
|
||||
/atom/movable/screen/alert/modlink_call
|
||||
name = "MODlink Call Incoming"
|
||||
desc = "Someone is calling you! Left-click this to accept the call. Right-click to deny it."
|
||||
icon_state = "called"
|
||||
timeout = 10 SECONDS
|
||||
var/end_message = "call timed out!"
|
||||
/// A weak reference to the MODlink that is calling.
|
||||
var/datum/weakref/caller_ref
|
||||
/// A weak reference to the MODlink that is being called.
|
||||
var/datum/weakref/receiver_ref
|
||||
/// A weak reference to the mob that is calling.
|
||||
var/datum/weakref/user_ref
|
||||
|
||||
/atom/movable/screen/alert/modlink_call/Click(location, control, params)
|
||||
. = ..()
|
||||
if(usr != owner)
|
||||
return
|
||||
var/datum/mod_link/caller = caller_ref.resolve()
|
||||
var/datum/mod_link/receiver = receiver_ref.resolve()
|
||||
if(!caller || !receiver)
|
||||
return
|
||||
if(caller.link_call || receiver.link_call)
|
||||
return
|
||||
var/list/modifiers = params2list(params)
|
||||
if(LAZYACCESS(modifiers, RIGHT_CLICK))
|
||||
end_message = "call denied!"
|
||||
owner.clear_alert("[REF(caller)]_modlink")
|
||||
return
|
||||
end_message = "call accepted"
|
||||
new /datum/mod_link_call(caller, receiver)
|
||||
owner.clear_alert("[REF(caller)]_modlink")
|
||||
|
||||
/atom/movable/screen/alert/modlink_call/Destroy()
|
||||
var/mob/living/user = user_ref?.resolve()
|
||||
var/datum/mod_link/caller = caller_ref?.resolve()
|
||||
if(!user || !caller)
|
||||
return ..()
|
||||
caller.holder.balloon_alert(user, end_message)
|
||||
return ..()
|
||||
@@ -306,7 +306,7 @@
|
||||
the bare essentials, geared far more for environmental hazards than combat against fauna; however, \
|
||||
this gives way to incredible protection against corrosives and thermal protection good enough for \
|
||||
both casual backstroking through molten magma and romantic walks through arctic terrain. \
|
||||
Instead, the suit is capable of using its' anomalous properties to attract and \
|
||||
Instead, the suit is capable of using its anomalous properties to attract and \
|
||||
carefully distribute layers of ash or ice across the surface; these layers are ablative, but incredibly strong. \
|
||||
Lastly, the suit is capable of compressing and shrinking the mass of the wearer, as well as \
|
||||
rearranging its own constitution, to allow them to fit upright in a sphere form that can \
|
||||
@@ -1437,7 +1437,7 @@
|
||||
|
||||
/datum/mod_theme/responsory
|
||||
name = "responsory"
|
||||
desc = "A high-speed rescue suit by Nanotrasen, intended for its' emergency response teams."
|
||||
desc = "A high-speed rescue suit by Nanotrasen, intended for its emergency response teams."
|
||||
extended_desc = "A streamlined suit of Nanotrasen design, these sleek black suits are only worn by \
|
||||
elite emergency response personnel to help save the day. While the slim and nimble design of the suit \
|
||||
cuts the ceramics and ablatives in it down, dropping the protection, \
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
/obj/item/mod/control/pre_equipped
|
||||
starting_frequency = MODLINK_FREQ_NANOTRASEN
|
||||
/// The skin we apply to the suit, defaults to the default_skin of the theme.
|
||||
var/applied_skin
|
||||
/// The MOD core we apply to the suit.
|
||||
@@ -196,6 +197,7 @@
|
||||
|
||||
/obj/item/mod/control/pre_equipped/traitor
|
||||
theme = /datum/mod_theme/syndicate
|
||||
starting_frequency = MODLINK_FREQ_SYNDICATE
|
||||
applied_cell = /obj/item/stock_parts/cell/super
|
||||
applied_modules = list(
|
||||
/obj/item/mod/module/storage/syndicate,
|
||||
@@ -213,6 +215,7 @@
|
||||
|
||||
/obj/item/mod/control/pre_equipped/traitor_elite
|
||||
theme = /datum/mod_theme/elite
|
||||
starting_frequency = MODLINK_FREQ_SYNDICATE
|
||||
applied_cell = /obj/item/stock_parts/cell/bluespace
|
||||
applied_modules = list(
|
||||
/obj/item/mod/module/storage/syndicate,
|
||||
@@ -229,6 +232,7 @@
|
||||
|
||||
/obj/item/mod/control/pre_equipped/nuclear
|
||||
theme = /datum/mod_theme/syndicate
|
||||
starting_frequency = MODLINK_FREQ_SYNDICATE
|
||||
applied_cell = /obj/item/stock_parts/cell/hyper
|
||||
req_access = list(ACCESS_SYNDICATE)
|
||||
applied_modules = list(
|
||||
@@ -248,6 +252,7 @@
|
||||
|
||||
/obj/item/mod/control/pre_equipped/elite
|
||||
theme = /datum/mod_theme/elite
|
||||
starting_frequency = MODLINK_FREQ_SYNDICATE
|
||||
applied_cell = /obj/item/stock_parts/cell/bluespace
|
||||
req_access = list(ACCESS_SYNDICATE)
|
||||
applied_modules = list(
|
||||
@@ -280,6 +285,7 @@
|
||||
|
||||
/obj/item/mod/control/pre_equipped/infiltrator
|
||||
theme = /datum/mod_theme/infiltrator
|
||||
starting_frequency = MODLINK_FREQ_SYNDICATE
|
||||
applied_cell = /obj/item/stock_parts/cell/super
|
||||
applied_modules = list(
|
||||
/obj/item/mod/module/emp_shield,
|
||||
@@ -291,6 +297,7 @@
|
||||
|
||||
/obj/item/mod/control/pre_equipped/interdyne
|
||||
theme = /datum/mod_theme/interdyne
|
||||
starting_frequency = MODLINK_FREQ_SYNDICATE
|
||||
applied_cell = /obj/item/stock_parts/cell/super
|
||||
applied_modules = list(
|
||||
/obj/item/mod/module/defibrillator/combat,
|
||||
@@ -304,6 +311,7 @@
|
||||
|
||||
/obj/item/mod/control/pre_equipped/enchanted
|
||||
theme = /datum/mod_theme/enchanted
|
||||
starting_frequency = null
|
||||
applied_core = /obj/item/mod/core/infinite
|
||||
applied_modules = list(
|
||||
/obj/item/mod/module/storage/large_capacity,
|
||||
@@ -313,6 +321,7 @@
|
||||
|
||||
/obj/item/mod/control/pre_equipped/ninja
|
||||
theme = /datum/mod_theme/ninja
|
||||
starting_frequency = null
|
||||
applied_cell = /obj/item/stock_parts/cell/ninja
|
||||
applied_modules = list(
|
||||
/obj/item/mod/module/storage,
|
||||
@@ -334,6 +343,7 @@
|
||||
|
||||
/obj/item/mod/control/pre_equipped/prototype
|
||||
theme = /datum/mod_theme/prototype
|
||||
starting_frequency = MODLINK_FREQ_CHARLIE
|
||||
req_access = list(ACCESS_AWAY_GENERAL)
|
||||
applied_modules = list(
|
||||
/obj/item/mod/module/storage,
|
||||
@@ -349,6 +359,7 @@
|
||||
|
||||
/obj/item/mod/control/pre_equipped/responsory
|
||||
theme = /datum/mod_theme/responsory
|
||||
starting_frequency = MODLINK_FREQ_CENTCOM
|
||||
applied_cell = /obj/item/stock_parts/cell/hyper
|
||||
req_access = list(ACCESS_CENT_GENERAL)
|
||||
applied_modules = list(
|
||||
@@ -427,6 +438,7 @@
|
||||
|
||||
/obj/item/mod/control/pre_equipped/apocryphal
|
||||
theme = /datum/mod_theme/apocryphal
|
||||
starting_frequency = MODLINK_FREQ_CENTCOM
|
||||
applied_cell = /obj/item/stock_parts/cell/bluespace
|
||||
req_access = list(ACCESS_CENT_SPECOPS)
|
||||
applied_modules = list(
|
||||
@@ -452,6 +464,7 @@
|
||||
|
||||
/obj/item/mod/control/pre_equipped/corporate
|
||||
theme = /datum/mod_theme/corporate
|
||||
starting_frequency = MODLINK_FREQ_CENTCOM
|
||||
applied_core = /obj/item/mod/core/infinite
|
||||
req_access = list(ACCESS_CENT_SPECOPS)
|
||||
applied_modules = list(
|
||||
@@ -463,6 +476,7 @@
|
||||
|
||||
/obj/item/mod/control/pre_equipped/chrono
|
||||
theme = /datum/mod_theme/chrono
|
||||
starting_frequency = null
|
||||
applied_core = /obj/item/mod/core/infinite
|
||||
applied_modules = list(
|
||||
/obj/item/mod/module/eradication_lock,
|
||||
@@ -483,6 +497,7 @@
|
||||
|
||||
/obj/item/mod/control/pre_equipped/debug
|
||||
theme = /datum/mod_theme/debug
|
||||
starting_frequency = null
|
||||
applied_core = /obj/item/mod/core/infinite
|
||||
applied_modules = list( //one of every type of module, for testing if they all work correctly
|
||||
/obj/item/mod/module/storage/bluespace,
|
||||
@@ -496,6 +511,7 @@
|
||||
|
||||
/obj/item/mod/control/pre_equipped/administrative
|
||||
theme = /datum/mod_theme/administrative
|
||||
starting_frequency = MODLINK_FREQ_CENTCOM
|
||||
applied_core = /obj/item/mod/core/infinite
|
||||
applied_modules = list(
|
||||
/obj/item/mod/module/storage/bluespace,
|
||||
@@ -516,6 +532,7 @@
|
||||
|
||||
//these exist for the prefs menu
|
||||
/obj/item/mod/control/pre_equipped/empty
|
||||
starting_frequency = null
|
||||
|
||||
/obj/item/mod/control/pre_equipped/empty/syndicate
|
||||
theme = /datum/mod_theme/syndicate
|
||||
|
||||
@@ -15,6 +15,8 @@
|
||||
"ai_name" = ai_assistant?.name,
|
||||
"has_pai" = ispAI(ai_assistant),
|
||||
"is_ai" = ai_assistant && ai_assistant == user,
|
||||
"link_id" = mod_link.id,
|
||||
"link_call" = mod_link.get_other()?.id,
|
||||
// Wires
|
||||
"open" = open,
|
||||
"seconds_electrified" = seconds_electrified,
|
||||
@@ -76,10 +78,6 @@
|
||||
. = ..()
|
||||
if(.)
|
||||
return
|
||||
if(locked && (!allowed(usr) || !ispAI(usr))) // pAIs automatically fail out of allowed()
|
||||
balloon_alert(usr, "insufficient access!")
|
||||
playsound(src, 'sound/machines/scanbuzz.ogg', 25, TRUE, SILENCED_SOUND_EXTRARANGE)
|
||||
return
|
||||
if(malfunctioning && prob(75))
|
||||
balloon_alert(usr, "button malfunctions!")
|
||||
return
|
||||
@@ -87,6 +85,11 @@
|
||||
if("lock")
|
||||
locked = !locked
|
||||
balloon_alert(usr, "[locked ? "locked" : "unlocked"]!")
|
||||
if("call")
|
||||
if(!mod_link.link_call)
|
||||
call_link(usr, mod_link)
|
||||
else
|
||||
mod_link.end_call()
|
||||
if("activate")
|
||||
toggle_activate(usr)
|
||||
if("select")
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -1650,6 +1650,7 @@
|
||||
"mod_clamp",
|
||||
"mod_drill",
|
||||
"mod_orebag",
|
||||
"modlink_scryer",
|
||||
)
|
||||
research_costs = list(TECHWEB_POINT_TYPE_GENERIC = 2500)
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
Before Width: | Height: | Size: 922 KiB After Width: | Height: | Size: 916 KiB |
|
Before Width: | Height: | Size: 132 KiB After Width: | Height: | Size: 133 KiB |
|
Before Width: | Height: | Size: 109 KiB After Width: | Height: | Size: 104 KiB |
|
Before Width: | Height: | Size: 3.6 KiB After Width: | Height: | Size: 3.7 KiB |
@@ -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"
|
||||
|
||||
@@ -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')}
|
||||
/>
|
||||
</LabeledList.Item>
|
||||
<LabeledList.Item label="MODLink">
|
||||
<Button
|
||||
icon={'wifi'}
|
||||
color={link_call ? 'good' : 'default'}
|
||||
disabled={!link_id}
|
||||
content={
|
||||
link_call
|
||||
? 'Calling (' + link_call + ')'
|
||||
: 'Call (' + link_id + ')'
|
||||
}
|
||||
onClick={() => act('call')}
|
||||
/>
|
||||
</LabeledList.Item>
|
||||
{!!open && (
|
||||
<LabeledList.Item label="Cover">
|
||||
<Box color="red">Open</Box>
|
||||
|
||||
@@ -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 (
|
||||
<NumberInput
|
||||
value={props.value}
|
||||
step={0.005}
|
||||
format={(value) => 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<MatrixData>(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 (
|
||||
<Window title="Nobody Wants to Learn Matrix Math" width={290} height={270}>
|
||||
<Window.Content>
|
||||
<Section fill>
|
||||
<Table>
|
||||
<Table.Row header>
|
||||
<Table.Cell width={'30%'}>X</Table.Cell>
|
||||
<Table.Cell width={'30%'}>Y</Table.Cell>
|
||||
<Table.Cell width={'40%'}>Z</Table.Cell>
|
||||
</Table.Row>
|
||||
<Table.Row>
|
||||
<Table.Cell>
|
||||
<MatrixMathTesterInput value={matrix_a} varName="a" />
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
<MatrixMathTesterInput value={matrix_d} varName="d" />
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
<Input disabled placeholder="0 (fixed value)" width={'100%'} />
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
<Table.Row>
|
||||
<Table.Cell>
|
||||
<MatrixMathTesterInput value={matrix_b} varName="b" />
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
<MatrixMathTesterInput value={matrix_e} varName="e" />
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
<Input disabled placeholder="0 (fixed value)" width={'100%'} />
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
<Table.Row>
|
||||
<Table.Cell>
|
||||
<MatrixMathTesterInput value={matrix_c} varName="c" />
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
<MatrixMathTesterInput value={matrix_f} varName="f" />
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
<Input disabled placeholder="1 (fixed value)" width={'100%'} />
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
</Table>
|
||||
<Table mt={3}>
|
||||
<Table.Row header>
|
||||
<Table.Cell>Action</Table.Cell>
|
||||
<Table.Cell>X</Table.Cell>
|
||||
<Table.Cell>Y</Table.Cell>
|
||||
</Table.Row>
|
||||
<Table.Row>
|
||||
<Table.Cell>
|
||||
<Button
|
||||
icon={'up-right-and-down-left-from-center'}
|
||||
content={'Scale'}
|
||||
width={'100%'}
|
||||
onClick={() => act('scale', { x: scaleX, y: scaleY })}
|
||||
/>
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
<NumberInput
|
||||
value={scaleX}
|
||||
step={0.05}
|
||||
format={(value) => toFixed(value, 2)}
|
||||
width={'100%'}
|
||||
onChange={(e, value) => setScaleX(value)}
|
||||
/>
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
<NumberInput
|
||||
value={scaleY}
|
||||
step={0.05}
|
||||
format={(value) => toFixed(value, 2)}
|
||||
width={'100%'}
|
||||
onChange={(e, value) => setScaleY(value)}
|
||||
/>
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
<Table.Row>
|
||||
<Table.Cell>
|
||||
<Button
|
||||
icon={'arrow-right'}
|
||||
content={'Translate'}
|
||||
width={'100%'}
|
||||
onClick={() =>
|
||||
act('translate', { x: translateX, y: translateY })
|
||||
}
|
||||
/>
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
<NumberInput
|
||||
value={translateX}
|
||||
step={1}
|
||||
format={(value) => toFixed(value, 0)}
|
||||
width={'100%'}
|
||||
onChange={(e, value) => setTranslateX(value)}
|
||||
/>
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
<NumberInput
|
||||
value={translateY}
|
||||
step={1}
|
||||
format={(value) => toFixed(value, 0)}
|
||||
width={'100%'}
|
||||
onChange={(e, value) => setTranslateY(value)}
|
||||
/>
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
<Table.Row>
|
||||
<Table.Cell>
|
||||
<Button
|
||||
icon={'maximize'}
|
||||
content={'Shear'}
|
||||
width={'100%'}
|
||||
onClick={() => act('shear', { x: shearX, y: shearY })}
|
||||
/>
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
<NumberInput
|
||||
value={shearX}
|
||||
step={0.005}
|
||||
format={(value) => toFixed(value, 3)}
|
||||
width={'100%'}
|
||||
onChange={(e, value) => setShearX(value)}
|
||||
/>
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
<NumberInput
|
||||
value={shearY}
|
||||
step={0.005}
|
||||
format={(value) => toFixed(value, 3)}
|
||||
width={'100%'}
|
||||
onChange={(e, value) => setShearY(value)}
|
||||
/>
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
<Table.Row>
|
||||
<Table.Cell>
|
||||
<Button
|
||||
icon={'rotate-right'}
|
||||
content={'Rotate'}
|
||||
width={'100%'}
|
||||
onClick={() => act('turn', { angle: angle })}
|
||||
/>
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
<NumberInput
|
||||
value={angle}
|
||||
step={0.5}
|
||||
maxValue={360}
|
||||
minValue={-360}
|
||||
format={(value) => toFixed(value, 1)}
|
||||
width={'100%'}
|
||||
onChange={(e, value) => setAngle(value)}
|
||||
/>
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
<Button
|
||||
icon={'dog'}
|
||||
color={'bad'}
|
||||
selected={pixelated}
|
||||
content={'PET'}
|
||||
tooltip={'Pixel Enhanced Transforming'}
|
||||
tooltipPosition={'bottom'}
|
||||
width={'100%'}
|
||||
onClick={() => act('toggle_pixel')}
|
||||
/>
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
</Table>
|
||||
</Section>
|
||||
</Window.Content>
|
||||
</Window>
|
||||
);
|
||||
};
|
||||