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.

![image](https://github.com/tgstation/tgstation/assets/23585223/eb387738-0839-463a-aed8-4703d139b11a)
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

![image](https://github.com/tgstation/tgstation/assets/23585223/5a822f9f-e823-497e-b766-40055f2fc0d6)
![image](https://github.com/tgstation/tgstation/assets/23585223/062983ee-6058-4e78-a3aa-bccda1a3e224)


## 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!
/🆑
This commit is contained in:
Fikou
2023-08-19 06:24:57 +00:00
committed by GitHub
parent 551a41cab6
commit b77c1c85ea
59 changed files with 1034 additions and 135 deletions
@@ -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" = (
+1 -12
View File
@@ -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" = (
+1 -12
View File
@@ -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" = (
+1 -12
View File
@@ -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
},
+1 -10
View File
@@ -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" = (
+1 -12
View File
@@ -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
+8
View File
@@ -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)
+4 -1
View File
@@ -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
+1
View File
@@ -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"
+4
View File
@@ -101,3 +101,7 @@
/datum/action/item_action/flip
name = "Flip"
/datum/action/item_action/call_link
name = "Call MODlink"
+14 -4
View File
@@ -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
+23 -13
View File
@@ -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
+1 -1
View File
@@ -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
+1 -1
View File
@@ -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
+1 -1
View File
@@ -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 ..()
+1 -1
View File
@@ -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)
+2 -2
View File
@@ -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."))
+1 -1
View File
@@ -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!"))
+12 -1
View File
@@ -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
+1 -1
View File
@@ -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
/**
+1 -1
View File
@@ -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()
+2 -3
View File
@@ -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)
+19 -4
View File
@@ -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!")
+520
View File
@@ -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 ..()
+2 -2
View File
@@ -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, \
+17
View File
@@ -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
+7 -4
View File
@@ -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")
+1 -1
View File
@@ -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
+1 -1
View File
@@ -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
+1 -1
View File
@@ -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
+1 -1
View File
@@ -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)
+4 -4
View File
@@ -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
Binary file not shown.

Before

Width:  |  Height:  |  Size: 922 KiB

After

Width:  |  Height:  |  Size: 916 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 132 KiB

After

Width:  |  Height:  |  Size: 133 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 109 KiB

After

Width:  |  Height:  |  Size: 104 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.6 KiB

After

Width:  |  Height:  |  Size: 3.7 KiB

+2
View File
@@ -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"
+17
View File
@@ -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>
);
};