From 67d0110828a97e2ebd534912e2ceb8d9cab62788 Mon Sep 17 00:00:00 2001
From: The Sharkening <95130227+StrangeWeirdKitten@users.noreply.github.com>
Date: Tue, 5 Aug 2025 10:57:52 -0600
Subject: [PATCH] MOD Remote Control module (#4328)
## About The Pull Request
Adds a MOD remote module that can control a modsuit remotely.
## Why It's Good For The Game
Eventually I want to transition Proteans to this system where you access
all your abilities via the UI of your modsuit. But I think it'll be fun
to allow these interactions with all mods and a framework to expand on
it.
## Proof Of Testing

## Changelog
:cl:
add: MODsuit Remote Module that allows you to remotely control other
people's modsuits
/:cl:
---
.../designs/mechfabricator_designs.dm | 10 ++
.../code/lewd_clothing/kink_collars.dm | 4 +-
.../code/lewd_machinery/lustwish.dm | 1 +
.../species/proteans/protean_modsuit.dm | 2 +-
.../code/modules/mod/modules_lustwish.dm | 88 +++++++++++++++++-
.../modules/research/techweb/all_nodes.dm | 5 +
.../mob/clothing/modsuit/mod_modules.dmi | Bin 973 -> 1342 bytes
.../tgui/interfaces/MODsuitremote.tsx | 84 +++++++++++++++++
8 files changed, 190 insertions(+), 4 deletions(-)
create mode 100644 tgui/packages/tgui/interfaces/MODsuitremote.tsx
diff --git a/modular_skyrat/master_files/code/modules/research/designs/mechfabricator_designs.dm b/modular_skyrat/master_files/code/modules/research/designs/mechfabricator_designs.dm
index 148526d93b8..84c4aa090bb 100644
--- a/modular_skyrat/master_files/code/modules/research/designs/mechfabricator_designs.dm
+++ b/modular_skyrat/master_files/code/modules/research/designs/mechfabricator_designs.dm
@@ -1,3 +1,13 @@
/datum/design/modlink_scryer
// use the loaded modlink scryer object to have the frequency set to NT upon creation
build_path = /obj/item/clothing/neck/link_scryer/loaded
+
+/datum/design/module/mod_remote
+ name = "MODsuit Remote Module"
+ id = "mod_remote_module"
+ build_path = /obj/item/mod/module/remote_control
+ materials = list(
+ /datum/material/iron = SHEET_MATERIAL_AMOUNT *3 ,
+ /datum/material/glass = SHEET_MATERIAL_AMOUNT * 1.5,
+ )
+ research_icon_state = "module_remote"
diff --git a/modular_skyrat/modules/modular_items/lewd_items/code/lewd_clothing/kink_collars.dm b/modular_skyrat/modules/modular_items/lewd_items/code/lewd_clothing/kink_collars.dm
index 8314a3942a8..e9a8ad5d171 100644
--- a/modular_skyrat/modules/modular_items/lewd_items/code/lewd_clothing/kink_collars.dm
+++ b/modular_skyrat/modules/modular_items/lewd_items/code/lewd_clothing/kink_collars.dm
@@ -256,7 +256,7 @@
/obj/item/mind_controller/Destroy(force)
collar?.remote = null
collar = null
- . = ..()
+ return ..()
/obj/item/mind_controller/attack_self(mob/user)
if(!collar)
@@ -274,7 +274,7 @@
inhand_icon_state = null
kink_collar = TRUE
/// Reference to the mind control remote
- var/obj/item/mind_controller/remote = null
+ var/obj/item/mind_controller/remote
var/emoting = "Shivers."
/obj/item/clothing/neck/mind_collar/Initialize(mapload)
diff --git a/modular_skyrat/modules/modular_items/lewd_items/code/lewd_machinery/lustwish.dm b/modular_skyrat/modules/modular_items/lewd_items/code/lewd_machinery/lustwish.dm
index a639fbfaa8c..763b4eedf74 100644
--- a/modular_skyrat/modules/modular_items/lewd_items/code/lewd_machinery/lustwish.dm
+++ b/modular_skyrat/modules/modular_items/lewd_items/code/lewd_machinery/lustwish.dm
@@ -146,6 +146,7 @@
"products" = list(
/obj/item/mod/construction/plating/lustwish = 5,
/obj/item/mod/module/hypno_visor = 5,
+ /obj/item/mod/module/remote_control = 5,
)
),
list(
diff --git a/modular_zubbers/code/modules/customization/species/proteans/protean_modsuit.dm b/modular_zubbers/code/modules/customization/species/proteans/protean_modsuit.dm
index 1624daab779..c2427daf239 100644
--- a/modular_zubbers/code/modules/customization/species/proteans/protean_modsuit.dm
+++ b/modular_zubbers/code/modules/customization/species/proteans/protean_modsuit.dm
@@ -175,7 +175,7 @@
var/datum/mod_theme/the_theme = GLOB.mod_themes[plates.theme]
name = initial(name)
- name = initial(desc)
+ desc = initial(desc)
for(var/obj/item/part as anything in get_parts())
part.name = initial(name)
diff --git a/modular_zubbers/code/modules/mod/modules_lustwish.dm b/modular_zubbers/code/modules/mod/modules_lustwish.dm
index 6bf1d1e46e5..e1f1f03a61b 100644
--- a/modular_zubbers/code/modules/mod/modules_lustwish.dm
+++ b/modular_zubbers/code/modules/mod/modules_lustwish.dm
@@ -4,7 +4,7 @@
theme = /datum/mod_theme/lustwish
/obj/item/mod/module/hypno_visor
- name = "Hypnosis Module"
+ name = "hypnosis module"
desc = "A module inserted into the visor of a suit in which commands can be processed. Use on self to set directives."
icon = 'modular_zubbers/icons/mob/clothing/modsuit/mod_modules.dmi'
icon_state = "module_hypno"
@@ -47,3 +47,89 @@
. = ..()
if(isnull(overlay_state_inactive))
overlay_state_inactive = initial(overlay_state_inactive)
+
+/datum/storage/pockets/small/remote_module
+ max_slots = 1
+
+/datum/storage/pockets/small/remote_module/New(atom/parent, max_slots, max_specific_storage, max_total_storage, rustle_sound, remove_rustle_sound)
+ . = ..()
+ set_holdable(can_hold_list = list(/obj/item/remote_controller))
+
+/obj/item/mod/module/remote_control
+ name = "modsuit remote module"
+ desc = "A module, once inserted, will allow anyone with its linked remote to control all functionality of the suit."
+ icon = 'modular_zubbers/icons/mob/clothing/modsuit/mod_modules.dmi'
+ icon_state = "module_remote"
+ module_type = MODULE_PASSIVE
+ complexity = 0
+ idle_power_cost = DEFAULT_CHARGE_DRAIN * 0
+ incompatible_modules = list(/obj/item/mod/module/remote_control)
+ required_slots = list(ITEM_SLOT_OCLOTHING)
+ overlay_state_inactive = "module_remote_overlay"
+ overlay_icon_file = 'modular_zubbers/icons/mob/clothing/modsuit/mod_modules.dmi'
+
+/obj/item/mod/module/remote_control/Initialize(mapload)
+ . = ..()
+ create_storage(storage_type = /datum/storage/pockets/small/remote_module)
+ var/obj/item/remote_controller/remote = new /obj/item/remote_controller()
+ remote.module = src
+ remote.forceMove(src)
+
+/obj/item/mod/module/remote_control/can_install(obj/item/mod/control/mod)
+ if(locate(/obj/item/remote_controller) in contents)
+ balloon_alert(usr, "remove remote from storage")
+ return FALSE
+ return TRUE
+
+/obj/item/mod/module/remote_control/attack_self(mob/user)
+ . = ..()
+ atom_storage?.open_storage(user)
+
+/obj/item/remote_controller
+ name = "modsuit remote"
+ desc = "A remote that allows control of a modsuit once its paired module is inserted."
+ icon = 'modular_zubbers/icons/mob/clothing/modsuit/mod_modules.dmi'
+ icon_state = "remote_item"
+ w_class = WEIGHT_CLASS_SMALL
+ var/obj/item/mod/module/remote_control/module
+
+/obj/item/remote_controller/ui_interact(mob/user, datum/tgui/ui)
+ ui = SStgui.try_update_ui(user, src, ui)
+ if(!ui)
+ ui = new(user, src, "MODsuitremote")
+ ui.open()
+
+/obj/item/remote_controller/ui_data(mob/user)
+ var/list/data = list()
+ data["linked_suit"] = !!module?.mod
+ data["wearer"] = !!module.mod?.wearer
+ data["erp_pref_check"] = module.mod?.wearer?.client?.prefs.read_preference(/datum/preference/toggle/erp/sex_toy)
+ if(module.mod)
+ data += module.mod.ui_data()
+ return data
+
+/obj/item/remote_controller/ui_act(action, list/params, datum/tgui/ui, datum/ui_state/state)
+ . = ..()
+ if(.)
+ return
+ switch(action)
+ if("emote")
+ module.mod.wearer.emote("me", 1, params["emote"], TRUE)
+
+/obj/item/remote_controller/proc/forced_emote(emoter, user)
+
+/obj/item/remote_controller/Initialize(mapload, module_init)
+ . = ..()
+ src.module = module_init
+
+/obj/item/remote_controller/ui_static_data(mob/user)
+ return module?.mod?.ui_static_data()
+
+/obj/item/remote_controller/ui_act(action, list/params, datum/tgui/ui, datum/ui_state/state)
+ . = ..()
+ return module?.mod?.ui_act(action, params, ui, state)
+
+/obj/item/remote_controller/Destroy(force)
+ module = null
+ return ..()
+
diff --git a/modular_zubbers/code/modules/research/techweb/all_nodes.dm b/modular_zubbers/code/modules/research/techweb/all_nodes.dm
index 03ab6f7b56b..dd2c2140275 100644
--- a/modular_zubbers/code/modules/research/techweb/all_nodes.dm
+++ b/modular_zubbers/code/modules/research/techweb/all_nodes.dm
@@ -146,6 +146,11 @@
design_ids += "mod_mind_transfer"
. = ..()
+// Modsuit tech
+/datum/techweb_node/mod_equip/New()
+ . = ..()
+ design_ids += list("mod_remote_module")
+
/datum/techweb_node/nerd
id = TECHWEB_NODE_NERD
display_name = "Theoretical Physics"
diff --git a/modular_zubbers/icons/mob/clothing/modsuit/mod_modules.dmi b/modular_zubbers/icons/mob/clothing/modsuit/mod_modules.dmi
index e246c5915e813c583cc52f7aeae1ec0306c23a7f..083ee9914e0743362715df4d012c207fdfc3550f 100644
GIT binary patch
delta 1287
zcmV+i1^D{S2fhj=iBL{Q4GJ0x0000DNk~Le0001>0001>2m=5B0K5^G9{>OVPLU-^
ze@VV|sOyhBcXx(jR#pH200DGTPE!Ct=GbNc006mqR9JLGWpiV4X>fFDZ*Bkpc$|%p
z%L;=q5JlI?R|MU*rE0g`6s5SRzaRmpCXk0DW5vI3tb$$G2eY~JICF=I*`>Ia70YiW
zeSq`Q8g^!^mM`_xXPu}=s+jM*Qv)RHr&=%pR(>zv71pV(-3-a|B
z35{7%<*$!F0hBRuNT$u=!TiDpQ~d-gta3p@KuQzhUJNE
zz=?}PO~R|ePb-ZZO0-_0=+HX53ja91^c~hiqiY)-N!A0ni{1e;J%%7=~dOhG7_nVSX;;oBzf-6>TUD1W%U(P<1Rn
zBf>l7<1N&H${#5dm|exH#`DV*kakT#q97Cw5$U2*3&dv>Q7|8`c|m;P{(f)^=>`fB
z=3pnW3lZQ*RA>MmX*~$a;QWta7=~dOhG7_nVV(nZeIR*pQ*W?zZO+#of9Z2;0A*LP
z`h3H55hi?b7(N3Y0T?nqBz-I(UWEz?66qP$V;#T-mvle}UL_hXAjSQ{`ayv>zZH0q
zWtv{}G=hd8^kzS37R-K-;mNPpM4$cgYhMleERgBIYKR{p;2#t$;rFs$zhuheq~jz8
z1ji|T#6e$*+vX?Nudi!Re=v0j5R8+cCt0r#9>LCi@h576j(tNwC8ll;H&8KSg>o
z<}Ldad>4oMyo)=0RH4-Nk7Fyun>BxP&$u{B^Z?akhqLuoLAl#Hc200_vx4I^&F8^M
zOpeNWGrQD6sDqaR!!Xe^z$eo%PxW!{6v~o;OwI6-I-!*Cb)xmy*sNk
zr^eNN_3n%}H|pvP!!QieLGG2*e}kUVDIb6g%}DqO3l$a+^nM{=$r^gW(_(iBei<{jI8|Q_@jvk6
zKjPLxhtToDD%iV=dfV3X{22BX~9h9JEY=a7x`Gn9}pK
zk0C*`H;|w-ZU-A{+)g(4<$LDp?=|`?m~Vlfh2aON8fj(tf4Orv6N0Nj&j$&R&)I$Y
zf~wYP_hq4HG~(m2beaM3IoFx^mG|>UurYo|@4FcKd@i1-p?of?0fNcSCdUqfA)gB!
z1oAm^A$a@V-vqTt!?^|p&*zLY41(@=W)J$~cfNV=Q9^;_bE7>und=u0C#~GgMJdbY
zIt^YzQ9k!XS-+xuuFDy&l#+aoqdSIS7>4hG7_nVHoD;@gG`?A$Edy4SxUt002ovPDHLkV1f|pSMLA-
delta 915
zcmV;E18n@h3e5*4iBL{Q4GJ0x0000DNk~Le0001h0001h2m=5B0QBII`2YX_MUf>*
ze^A`ET>t<80d!JMQvg8b*k%9#0HAtQSad{Xb7OL8aCB*JZU6vyoKseCa&`CgQ*iP1
zZ44Am1_Y&OF+`3gWX+ub00N3hL_t(|ob8)!Z`&{o
zge5Aj?WV~(FU$Y`sgw2M1S?N;D|h>H4}zlb#p01t0#H_*BhPpd@g2Go@1;Xuf5$f*
z_-6d5uOq*$RElHMA?B;SsbY^&LU556P5%SnWw4zXUhvP%`{iJu(!pbZj@d9%9G
zH36Febr2~0{jR?FjU*1pZnuTcT_=PPLI@#*kSDC>#cXam^|gC%cc*rD+v>lq=0Ekd
zUH!Sh-Z-nla^Q^(ggq8k1E@JQf3r&!=!w530k$C|8?f#+Hko!9Kqc5yfP?l2KoVjs
z@Z;eA03n1BLI@$G1FJQY03J+lZ!pdm7!Pk{?dqBltn&k{?!5Nrjl0VMhMxTa)Ej5j
zbRgXim^~gs%<~yXJ-)~L1Lo*y4fGkvr$~?iV+q-dY>xvp&FO$h@8~-%f9Ti$QFuZK
zA%qY@2qB~kYY!tNuOUK@(~(zP$ud=YaW~=sqtUN@kNoRIpj?qU|^cd5gzbUmZ*nPBfZF5
z{^RTG2o01Bfq^_p`W}&Ge_)F*=W#qM2FmZevH>v1n*m*a7*KRy)~C3)%jHcq(4K;%
z*D28ThXF+wx&DY;oqKz~{M7D$T7_Xidae-C>mt;2@yj)`L<7IxRRhyJ*YAPZ&5+V)cfDAZS9E3Rz;9MMHW%#t7DuDFmy2O_|
zUw#7v!~MP{aKGmphhaKazb&`;iUz=^z(wP8{reqy {
+ const { data } = useBackend();
+ const { ui_theme } = data;
+ return (
+
+
+
+
+
+ );
+};
+
+const RemoteSection = (props) => {
+ const { act, data } = useBackend();
+ const { erp_pref_check } = data;
+ const [emote, setEmote] = useState('');
+ return (
+
+ );
+};
+const RemoteMODsuitContent = (props) => {
+ const { data } = useBackend();
+ const { linked_suit, wearer } = data;
+ if (!linked_suit) {
+ return (
+
+
+
+
+
+ Not installed
+
+ );
+ }
+ if (!wearer) {
+ return (
+
+
+
+
+
+ MODsuit Not Worn
+
+ );
+ }
+ return (
+ <>
+
+
+ >
+ );
+};