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

<img width="686" height="379" alt="image"
src="https://github.com/user-attachments/assets/fe83beb8-4c99-45be-a19e-77b6bdfaa856"
/>


![dreamseeker_mW39ThGpoy](https://github.com/user-attachments/assets/4af97916-517f-4a27-acac-e7f88f2f5e97)

## Changelog

🆑
add: MODsuit Remote Module that allows you to remotely control other
people's modsuits
/🆑
This commit is contained in:
The Sharkening
2025-08-05 10:57:52 -06:00
committed by GitHub
parent 3c0cb7bddb
commit 67d0110828
8 changed files with 190 additions and 4 deletions
@@ -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"
@@ -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)
@@ -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(
@@ -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)
@@ -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 ..()
@@ -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"
Binary file not shown.

Before

Width:  |  Height:  |  Size: 973 B

After

Width:  |  Height:  |  Size: 1.3 KiB

@@ -0,0 +1,84 @@
// BUBBER UI FILE
import { useState } from 'react';
import { Icon, Input, LabeledList, Modal, Section } from 'tgui-core/components';
import { useBackend } from '../backend';
import { Window } from '../layouts';
import { MODsuitContent } from './MODsuit';
type Data = {
ui_theme?: string;
linked_suit?: boolean;
wearer?: boolean;
erp_pref_check?: boolean;
};
export const MODsuitremote = (props) => {
const { data } = useBackend<Data>();
const { ui_theme } = data;
return (
<Window
theme={ui_theme}
width={600}
height={600}
title="MOD Remote Interface"
>
<Window.Content scrollable>
<RemoteMODsuitContent />
</Window.Content>
</Window>
);
};
const RemoteSection = (props) => {
const { act, data } = useBackend<Data>();
const { erp_pref_check } = data;
const [emote, setEmote] = useState('');
return (
<Section title="Remote Control">
<LabeledList>
<LabeledList.Item label="Force Emote">
<Input
fluid
selfClear
onChange={setEmote}
disabled={!erp_pref_check}
onEnter={() => act('emote', { emote: emote })}
/>
</LabeledList.Item>
</LabeledList>
</Section>
);
};
const RemoteMODsuitContent = (props) => {
const { data } = useBackend<Data>();
const { linked_suit, wearer } = data;
if (!linked_suit) {
return (
<Modal>
<center>
<Icon size={5} name="triangle-exclamation" />
</center>
<br />
Not installed
</Modal>
);
}
if (!wearer) {
return (
<Modal>
<center>
<Icon size={5} name="triangle-exclamation" />
</center>
<br />
MODsuit Not Worn
</Modal>
);
}
return (
<>
<RemoteSection />
<MODsuitContent />
</>
);
};