mirror of
https://github.com/CHOMPStation2/CHOMPStation2.git
synced 2025-12-10 02:09:41 +00:00
[MIRROR] Add Multishock integration (#11003)
Co-authored-by: ShadowLarkens <shadowlarkens@gmail.com>
This commit is contained in:
committed by
GitHub
parent
76c9d5e08a
commit
74d886613b
4
code/__defines/shock.dm
Normal file
4
code/__defines/shock.dm
Normal file
@@ -0,0 +1,4 @@
|
||||
// KEEP THIS UP TO DATE WITH tgui/packages/tgui/interfaces/ShockConfigurator.tsx
|
||||
#define SHOCKFLAG_BURNDAMAGE 0x1
|
||||
#define SHOCKFLAG_DIGESTION 0x2
|
||||
#define SHOCKFLAG_TEST "test"
|
||||
@@ -256,6 +256,7 @@
|
||||
|
||||
// Instantiate tgui panel
|
||||
tgui_say = new(src, "tgui_say")
|
||||
tgui_shocker = new(src, "tgui_shock")
|
||||
initialize_commandbar_spy()
|
||||
tgui_panel = new(src, "browseroutput")
|
||||
|
||||
@@ -304,6 +305,7 @@
|
||||
|
||||
// Initialize tgui panel
|
||||
tgui_say.initialize()
|
||||
tgui_shocker.initialize()
|
||||
|
||||
connection_time = world.time
|
||||
connection_realtime = world.realtime
|
||||
|
||||
149
code/modules/client/shock.dm
Normal file
149
code/modules/client/shock.dm
Normal file
@@ -0,0 +1,149 @@
|
||||
/client/var/datum/tgui_shock/tgui_shocker
|
||||
|
||||
/client/verb/configure_shocker()
|
||||
set name = "Configure MultiShock Integration"
|
||||
set category = "OOC.Game Settings"
|
||||
|
||||
if(tgui_shocker)
|
||||
tgui_shocker.tgui_interact(mob)
|
||||
|
||||
/mob/proc/attempt_multishock(flag)
|
||||
client?.attempt_multishock(flag)
|
||||
|
||||
/client/proc/attempt_multishock(flag)
|
||||
tgui_shocker?.shock(flag)
|
||||
|
||||
// NOTE: This datum controls TWO UIs, `window` is hidden and provides all of the WebSocket shit, `tgui_interact` is a
|
||||
// normal configuration UI!
|
||||
/datum/tgui_shock
|
||||
/// The user who opened the window
|
||||
var/client/client
|
||||
/// The modal window
|
||||
var/datum/tgui_window/window
|
||||
|
||||
var/port = 8765
|
||||
var/enabled_flags = 0
|
||||
var/intensity = 15
|
||||
var/duration = 1
|
||||
|
||||
var/connected = FALSE
|
||||
var/selected_device = -1
|
||||
var/list/available_devices
|
||||
|
||||
//////////////////////////////////////////
|
||||
// SHOCK.JS UI //
|
||||
//////////////////////////////////////////
|
||||
/datum/tgui_shock/New(client/client, id)
|
||||
src.client = client
|
||||
window = new(client, id)
|
||||
window.subscribe(src, PROC_REF(on_message))
|
||||
window.is_browser = TRUE
|
||||
|
||||
/datum/tgui_shock/proc/initialize()
|
||||
set waitfor = FALSE
|
||||
window.initialize(
|
||||
inline_js = file2text('html/shock.js')
|
||||
)
|
||||
|
||||
/datum/tgui_shock/proc/connect()
|
||||
window.send_message("connect", list(
|
||||
"port" = port,
|
||||
))
|
||||
|
||||
/datum/tgui_shock/proc/request_devices()
|
||||
if(!connected)
|
||||
return
|
||||
window.send_message("enumerateShockers")
|
||||
|
||||
/datum/tgui_shock/proc/shock(flag)
|
||||
if(!connected || !selected_device)
|
||||
return
|
||||
|
||||
if(flag != SHOCKFLAG_TEST)
|
||||
if(!(enabled_flags & flag))
|
||||
return
|
||||
|
||||
window.send_message("shock", list(
|
||||
"intensity" = intensity,
|
||||
"duration" = duration,
|
||||
"shocker_ids" = list(
|
||||
selected_device
|
||||
),
|
||||
"warning" = FALSE,
|
||||
))
|
||||
|
||||
/datum/tgui_shock/proc/estop()
|
||||
window.send_message("estop")
|
||||
|
||||
/datum/tgui_shock/proc/on_message(type, payload, href_list)
|
||||
if(type == "connected")
|
||||
connected = TRUE
|
||||
else if(type == "disconnected")
|
||||
connected = FALSE
|
||||
else if(type == "error")
|
||||
connected = FALSE
|
||||
log_debug("WebSocket Error [json_encode(payload)]")
|
||||
else if(type == "incomingMessage")
|
||||
if(payload["lastCall"] == "get_devices")
|
||||
available_devices = json_decode(payload["data"])
|
||||
|
||||
//////////////////////////////////////////
|
||||
// TGUI //
|
||||
//////////////////////////////////////////
|
||||
/datum/tgui_shock/tgui_state(mob/user)
|
||||
return GLOB.tgui_always_state
|
||||
|
||||
/datum/tgui_shock/tgui_interact(mob/user, datum/tgui/ui, datum/tgui/parent_ui, custom_state)
|
||||
ui = SStgui.try_update_ui(user, src, ui)
|
||||
if(!ui)
|
||||
ui = new(user, src, "ShockConfigurator", "Shock Configurator")
|
||||
ui.open()
|
||||
|
||||
/datum/tgui_shock/tgui_data(mob/user, datum/tgui/ui, datum/tgui_state/state)
|
||||
var/list/data = ..()
|
||||
|
||||
data["port"] = port
|
||||
data["connected"] = connected
|
||||
data["intensity"] = intensity
|
||||
data["duration"] = duration
|
||||
data["selectedDevice"] = selected_device
|
||||
data["availableDevices"] = available_devices
|
||||
data["enabledFlags"] = enabled_flags
|
||||
|
||||
return data
|
||||
|
||||
/datum/tgui_shock/tgui_act(action, list/params, datum/tgui/ui, datum/tgui_state/state)
|
||||
. = ..()
|
||||
|
||||
switch(action)
|
||||
if("connect")
|
||||
if(connected)
|
||||
estop()
|
||||
else
|
||||
// TODO: preferences
|
||||
connect()
|
||||
. = TRUE
|
||||
if("request_devices")
|
||||
request_devices()
|
||||
. = TRUE
|
||||
if("estop")
|
||||
estop()
|
||||
. = TRUE
|
||||
if("setSelectedDevice")
|
||||
selected_device = text2num(params["device"])
|
||||
. = TRUE
|
||||
if("test")
|
||||
shock(SHOCKFLAG_TEST)
|
||||
. = TRUE
|
||||
if("set_flag")
|
||||
enabled_flags ^= text2num(params["flag"])
|
||||
. = TRUE
|
||||
if("port")
|
||||
port = text2num(params["port"])
|
||||
. = TRUE
|
||||
if("intensity")
|
||||
intensity = text2num(params["intensity"])
|
||||
. = TRUE
|
||||
if("duration")
|
||||
duration = text2num(params["duration"])
|
||||
. = TRUE
|
||||
@@ -76,6 +76,7 @@
|
||||
if(COLD_RESISTANCE in mutations)
|
||||
damage = 0
|
||||
adjustFireLoss(damage * blocked)
|
||||
attempt_multishock(SHOCKFLAG_BURNDAMAGE)
|
||||
if(SEARING)
|
||||
apply_damage(round(damage / 3), BURN, def_zone, initial_blocked, soaked, sharp, edge, used_weapon)
|
||||
apply_damage(round(damage / 3 * 2), BRUTE, def_zone, initial_blocked, soaked, sharp, edge, used_weapon)
|
||||
|
||||
@@ -71,6 +71,7 @@ GLOBAL_LIST_INIT(digest_modes, list())
|
||||
L.adjustOxyLoss(B.digest_oxy)
|
||||
L.adjustToxLoss(B.digest_tox)
|
||||
L.adjustCloneLoss(B.digest_clone)
|
||||
L.attempt_multishock(SHOCKFLAG_DIGESTION)
|
||||
// Send a message when a prey-thing enters hard crit.
|
||||
if(iscarbon(L) && old_health > 0 && L.health <= 0)
|
||||
to_chat(B.owner, span_notice("You feel [L] go still within your [lowertext(B.name)]."))
|
||||
|
||||
79
html/shock.js
Normal file
79
html/shock.js
Normal file
@@ -0,0 +1,79 @@
|
||||
let webSocket;
|
||||
let authKey;
|
||||
let lastCall;
|
||||
|
||||
const reactRoot = document.getElementById("react-root");
|
||||
if (reactRoot) {
|
||||
reactRoot.innerHTML = "<h1>You shouldn't see this window, update your skin.</h1>";
|
||||
}
|
||||
|
||||
Byond.subscribeTo("estop", function () {
|
||||
if (webSocket) {
|
||||
webSocket.close();
|
||||
} else {
|
||||
Byond.sendMessage("disconnected");
|
||||
}
|
||||
})
|
||||
|
||||
Byond.subscribeTo("connect", function (data) {
|
||||
if (webSocket) {
|
||||
webSocket.close();
|
||||
}
|
||||
webSocket = new WebSocket(`ws://localhost:${data.port}`);
|
||||
webSocket.sendJson = (data) => {
|
||||
webSocket.send(JSON.stringify(data));
|
||||
};
|
||||
authKey = JSON.parse(window.hubStorage.getItem("virgo-shocker-authkey"));
|
||||
webSocket.onopen = (ev) => {
|
||||
Byond.sendMessage("connected");
|
||||
};
|
||||
webSocket.onerror = (ev) => {
|
||||
Byond.sendMessage("error", ev);
|
||||
};
|
||||
webSocket.onclose = (ev) => {
|
||||
Byond.sendMessage("disconnected");
|
||||
}
|
||||
webSocket.onmessage = (ev) => {
|
||||
Byond.sendMessage("incomingMessage", { data: ev.data, lastCall });
|
||||
};
|
||||
});
|
||||
|
||||
Byond.subscribeTo("enumerateShockers", function () {
|
||||
if (!webSocket) {
|
||||
return;
|
||||
}
|
||||
|
||||
lastCall = "get_devices";
|
||||
webSocket.sendJson({
|
||||
cmd: "get_devices",
|
||||
auth_key: authKey,
|
||||
});
|
||||
});
|
||||
|
||||
// data: {
|
||||
// "intensity": 10, // 1 - 100 - int
|
||||
// "duration": 1, // 0.1 - 15 - float
|
||||
// "shocker_ids": [], // [] - List of shocker ids
|
||||
// "warning": false, // true, false - will send a vibrate with the same intensity and duration
|
||||
// },
|
||||
Byond.subscribeTo("shock", function (data) {
|
||||
if (!webSocket) {
|
||||
return;
|
||||
}
|
||||
|
||||
lastCall = "operate";
|
||||
webSocket.sendJson({
|
||||
"cmd": "operate",
|
||||
"value": {
|
||||
"intensity": data.intensity, // 1 - 100 - int
|
||||
"duration": data.duration, // 0.1 - 15 - float
|
||||
"shocker_option": "all", // all, random
|
||||
"action": "shock", // shock, vibrate, beep, end
|
||||
"shocker_ids": data.shocker_ids, // [] - List of shocker ids
|
||||
"device_ids": [], // [] - list of pishock client ids, if one of these is provided it will activate all shockers associated with it
|
||||
"warning": data.warning, // true, false - will send a vibrate with the same intensity and duration
|
||||
"held": false, // true, false - for continuous commands
|
||||
},
|
||||
"auth_key": authKey,
|
||||
});
|
||||
});
|
||||
@@ -1192,6 +1192,14 @@ window "mainwindow"
|
||||
anchor2 = -1,-1
|
||||
is-visible = false
|
||||
saved-params = ""
|
||||
elem "tgui_shock"
|
||||
type = BROWSER
|
||||
pos = 0,0
|
||||
size = 200x200
|
||||
anchor1 = -1,-1
|
||||
anchor2 = -1,-1
|
||||
is-visible = false
|
||||
saved-params = ""
|
||||
elem "hotkey_toggle"
|
||||
type = BUTTON
|
||||
pos = 560,400
|
||||
|
||||
208
tgui/packages/tgui/interfaces/ShockConfigurator.tsx
Normal file
208
tgui/packages/tgui/interfaces/ShockConfigurator.tsx
Normal file
@@ -0,0 +1,208 @@
|
||||
import { storage } from 'common/storage';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useBackend } from 'tgui/backend';
|
||||
import { Window } from 'tgui/layouts';
|
||||
import {
|
||||
Box,
|
||||
Button,
|
||||
Input,
|
||||
LabeledList,
|
||||
NumberInput,
|
||||
Section,
|
||||
Stack,
|
||||
} from 'tgui-core/components';
|
||||
import { type BooleanLike } from 'tgui-core/react';
|
||||
|
||||
// These are straight from https://docs.pishock.com/multishock/multishock-websocket-api.html
|
||||
type MultishockAPIShocker = {
|
||||
name: string;
|
||||
identifier: number;
|
||||
};
|
||||
|
||||
type MultishockAPIDevice = {
|
||||
name: string;
|
||||
id: number;
|
||||
shockers: Array<MultishockAPIShocker>;
|
||||
};
|
||||
|
||||
type MultishockAPIAvailableDevices = Array<MultishockAPIDevice>;
|
||||
|
||||
// KEEP THIS UP TO DATE WITH code/__defines/shock.dm
|
||||
enum ShockFlags {
|
||||
BurnDamage = 1,
|
||||
Digestion = 2,
|
||||
}
|
||||
|
||||
type Data = {
|
||||
port: number;
|
||||
authKey: string;
|
||||
connected: BooleanLike;
|
||||
intensity: number;
|
||||
duration: number;
|
||||
selectedDevice: number;
|
||||
availableDevices: MultishockAPIAvailableDevices;
|
||||
enabledFlags: ShockFlags;
|
||||
};
|
||||
|
||||
export const ShockConfigurator = (props) => {
|
||||
const { act, data } = useBackend<Data>();
|
||||
const {
|
||||
port,
|
||||
connected,
|
||||
intensity,
|
||||
duration,
|
||||
selectedDevice,
|
||||
availableDevices,
|
||||
enabledFlags,
|
||||
} = data;
|
||||
|
||||
const [authKey, setAuthKey] = useState('');
|
||||
|
||||
useEffect(() => {
|
||||
const async_get = async () => {
|
||||
setAuthKey(await storage.get('shocker-authkey'));
|
||||
};
|
||||
async_get();
|
||||
});
|
||||
|
||||
return (
|
||||
<Window width={400} height={600}>
|
||||
<Window.Content>
|
||||
<Section title="Status">
|
||||
<Stack>
|
||||
<Stack.Item grow>
|
||||
{connected ? (
|
||||
<Box inline color="good">
|
||||
Connected
|
||||
</Box>
|
||||
) : (
|
||||
<Box inline color="bad">
|
||||
Disconnected
|
||||
</Box>
|
||||
)}
|
||||
</Stack.Item>
|
||||
<Stack.Item>
|
||||
<Button onClick={() => act('connect')}>
|
||||
{connected ? 'Disconnect' : 'Connect'}
|
||||
</Button>
|
||||
<Button ml={1} onClick={() => act('estop')}>
|
||||
E-STOP
|
||||
</Button>
|
||||
</Stack.Item>
|
||||
</Stack>
|
||||
<LabeledList>
|
||||
<LabeledList.Item label="MultiShock Port">
|
||||
<NumberInput
|
||||
value={port}
|
||||
minValue={0}
|
||||
maxValue={25565}
|
||||
step={1}
|
||||
onChange={(val) => act('port', { port: val })}
|
||||
/>
|
||||
</LabeledList.Item>
|
||||
<LabeledList.Item label="MultiShock Auth Key">
|
||||
<Input
|
||||
value={authKey}
|
||||
onChange={(val) => {
|
||||
storage.set('shocker-authkey', val);
|
||||
setAuthKey(val);
|
||||
}}
|
||||
/>
|
||||
</LabeledList.Item>
|
||||
<LabeledList.Item label="Shock Intensity">
|
||||
<NumberInput
|
||||
value={intensity}
|
||||
minValue={0}
|
||||
maxValue={100}
|
||||
step={1}
|
||||
onChange={(val) => act('intensity', { intensity: val })}
|
||||
/>
|
||||
</LabeledList.Item>
|
||||
<LabeledList.Item label="Shock Duration (Seconds)">
|
||||
<NumberInput
|
||||
value={duration}
|
||||
minValue={0}
|
||||
maxValue={15}
|
||||
step={0.1}
|
||||
onChange={(val) => act('duration', { duration: val })}
|
||||
/>
|
||||
</LabeledList.Item>
|
||||
</LabeledList>
|
||||
<Button onClick={() => act('test')}>Test Shocker</Button>
|
||||
<Box color="bad">
|
||||
ETIQUITTE NOTE: You must let your partners know you're using
|
||||
this!
|
||||
</Box>
|
||||
</Section>
|
||||
<Section title="Shock Sources">
|
||||
<Box>
|
||||
This section indicates when you would like to be shocked. Note: Burn
|
||||
+ Digestion is not recommended, as it will shock you twice per
|
||||
digest tick.
|
||||
</Box>
|
||||
<Button.Checkbox
|
||||
checked={enabledFlags & ShockFlags.BurnDamage}
|
||||
selected={enabledFlags & ShockFlags.BurnDamage}
|
||||
onClick={() => act('set_flag', { flag: ShockFlags.BurnDamage })}
|
||||
>
|
||||
Burn
|
||||
</Button.Checkbox>
|
||||
<Button.Checkbox
|
||||
checked={enabledFlags & ShockFlags.Digestion}
|
||||
selected={enabledFlags & ShockFlags.Digestion}
|
||||
onClick={() => act('set_flag', { flag: ShockFlags.Digestion })}
|
||||
>
|
||||
Digestion
|
||||
</Button.Checkbox>
|
||||
</Section>
|
||||
<Section
|
||||
title="Devices"
|
||||
buttons={
|
||||
<Button
|
||||
icon="refresh"
|
||||
tooltip="Refresh"
|
||||
onClick={() => act('request_devices')}
|
||||
/>
|
||||
}
|
||||
>
|
||||
{availableDevices && availableDevices.length ? (
|
||||
availableDevices.map((device) => (
|
||||
<Box key={device.id}>
|
||||
<Box bold>
|
||||
Device: {device.name} - {device.id}
|
||||
</Box>
|
||||
<Box>
|
||||
{device.shockers.map((shocker) => (
|
||||
<Box ml={4} key={shocker.identifier}>
|
||||
Shocker: {shocker.name} - {shocker.identifier}
|
||||
<Button.Checkbox
|
||||
ml={1}
|
||||
checked={shocker.identifier === selectedDevice}
|
||||
selected={shocker.identifier === selectedDevice}
|
||||
onClick={() => {
|
||||
if (shocker.identifier !== selectedDevice) {
|
||||
act('setSelectedDevice', {
|
||||
device: shocker.identifier,
|
||||
});
|
||||
} else {
|
||||
act('setSelectedDevice', {
|
||||
device: -1,
|
||||
});
|
||||
}
|
||||
}}
|
||||
>
|
||||
Use This Device
|
||||
</Button.Checkbox>
|
||||
</Box>
|
||||
))}
|
||||
</Box>
|
||||
</Box>
|
||||
))
|
||||
) : (
|
||||
<Box color="bad">No Devices Found</Box>
|
||||
)}
|
||||
</Section>
|
||||
</Window.Content>
|
||||
</Window>
|
||||
);
|
||||
};
|
||||
@@ -54,7 +54,7 @@ export const VorePanelEditToggle = (props: {
|
||||
</Stack.Item>
|
||||
<Stack.Item>
|
||||
<Button
|
||||
icon={persistEditMode ? 'lock' : 'lock-open'}
|
||||
icon={persistEditMode ? 'lock-open' : 'lock'}
|
||||
selected={persistEditMode}
|
||||
tooltip={
|
||||
(persistEditMode ? 'Dis' : 'En') +
|
||||
|
||||
@@ -144,6 +144,7 @@
|
||||
#include "code\__defines\SDQL_2.dm"
|
||||
#include "code\__defines\shadekin.dm"
|
||||
#include "code\__defines\shields.dm"
|
||||
#include "code\__defines\shock.dm"
|
||||
#include "code\__defines\shuttle.dm"
|
||||
#include "code\__defines\simple_mob.dm"
|
||||
#include "code\__defines\size.dm"
|
||||
@@ -2265,6 +2266,7 @@
|
||||
#include "code\modules\client\preferences_toggle_procs.dm"
|
||||
#include "code\modules\client\preferences_vr.dm"
|
||||
#include "code\modules\client\record_updater.dm"
|
||||
#include "code\modules\client\shock.dm"
|
||||
#include "code\modules\client\spam_prevention.dm"
|
||||
#include "code\modules\client\stored_item.dm"
|
||||
#include "code\modules\client\ui_style.dm"
|
||||
|
||||
Reference in New Issue
Block a user