[MIRROR] New MOD Suit UI [MDB IGNORE] (#22718)

* New MOD Suit UI

* Fix merge conflict+ bug that doesn't respect locked modsuits for non pais

---------

Co-authored-by: Andrew <mt.forspam@gmail.com>
Co-authored-by: Giz <13398309+vinylspiders@users.noreply.github.com>
This commit is contained in:
SkyratBot
2023-07-27 17:29:34 -04:00
committed by GitHub
co-authored by Andrew Giz
parent d3628a68f6
commit cbc88db9e4
9 changed files with 827 additions and 868 deletions
+36 -21
View File
@@ -6,24 +6,39 @@
/obj/item/mod/control/ui_data(mob/user)
var/data = list()
data["interface_break"] = interface_break
data["malfunctioning"] = malfunctioning
data["open"] = open
data["active"] = active
data["locked"] = locked
data["complexity"] = complexity
data["selected_module"] = selected_module?.name
data["wearer_name"] = wearer ? (wearer.get_authentification_name("Unknown") || "Unknown") : "No Occupant"
data["wearer_job"] = wearer ? wearer.get_assignment("Unknown", "Unknown", FALSE) : "No Job"
// SKYRAT EDIT START - pAIs in MODsuits
data["pAI"] = mod_pai?.name
data["ispAI"] = mod_pai ? mod_pai == user : FALSE
// SKYRAT EDIT END
data["core"] = core?.name
data["charge"] = get_charge_percent()
data["modules"] = list()
// Suit information
var/suit_status = list(
"core_name" = core?.name,
"cell_charge_current" = get_charge(),
"cell_charge_max" = get_max_charge(),
"active" = active,
//"ai_name" = ai?.name, // SKYRAT EDIT REMOVAL - pAIs in MODsuits
// Wires
"open" = open,
"seconds_electrified" = seconds_electrified,
"malfunctioning" = malfunctioning,
"locked" = locked,
"interface_break" = interface_break,
// Modules
"complexity" = complexity,
// SKYRAT EDIT START - pAIs in MODsuits
"pAI" = mod_pai?.name,
"ispAI" = mod_pai ? mod_pai == user : FALSE,
// SKYRAT EDIT END
)
data["suit_status"] = suit_status
// User information
var/user_status = list(
"user_name" = wearer ? (wearer.get_authentification_name("Unknown") || "Unknown") : "",
"user_assignment" = wearer ? wearer.get_assignment("Unknown", "Unknown", FALSE) : "",
)
data["user_status"] = user_status
// Module information
var/module_custom_status = list()
var/module_info = list()
for(var/obj/item/mod/module/module as anything in modules)
var/list/module_data = list(
module_custom_status += module.add_ui_data()
module_info += list(list(
"module_name" = module.name,
"description" = module.desc,
"module_type" = module.module_type,
@@ -38,9 +53,9 @@
"id" = module.tgui_id,
"ref" = REF(module),
"configuration_data" = module.get_configuration()
)
module_data += module.add_ui_data()
data["modules"] += list(module_data)
))
data["module_custom_status"] = module_custom_status
data["module_info"] = module_info
return data
/obj/item/mod/control/ui_static_data(mob/user)
@@ -59,7 +74,7 @@
if(.)
return
// allowed() doesn't allow for pAIs
if((!allowed(usr) || !ispAI(usr)) && locked) // SKYRAT EDIT - pAIs in MODsuits
if(((locked && !ispAI(usr)) && !allowed(usr))) // SKYRAT EDIT CHANGE - ORIGINAL: if(locked && !allowed(usr))
balloon_alert(usr, "insufficient access!")
playsound(src, 'sound/machines/scanbuzz.ogg', 25, TRUE, SILENCED_SOUND_EXTRARANGE)
return
@@ -165,10 +165,10 @@
/obj/item/mod/module/rad_protection/add_ui_data()
. = ..()
.["userradiated"] = mod.wearer ? HAS_TRAIT(mod.wearer, TRAIT_IRRADIATED) : FALSE
.["usertoxins"] = mod.wearer?.getToxLoss() || 0
.["usermaxtoxins"] = mod.wearer?.getMaxHealth() || 0
.["threatlevel"] = perceived_threat_level
.["is_user_irradiated"] = mod.wearer ? HAS_TRAIT(mod.wearer, TRAIT_IRRADIATED) : FALSE
.["background_radiation_level"] = perceived_threat_level
.["health_max"] = mod.wearer?.getMaxHealth() || 0
.["loss_tox"] = mod.wearer?.getToxLoss() || 0
/obj/item/mod/module/rad_protection/proc/on_pre_potential_irradiation(datum/source, datum/radiation_pulse_information/pulse_information, insulation_to_target)
SIGNAL_HANDLER
+35 -17
View File
@@ -101,7 +101,7 @@
overlay_state_active = "module_jetpack_on"
/// Do we give the wearer a speed buff.
var/full_speed = FALSE
var/stabilize = FALSE
var/stabilize = TRUE
var/thrust_callback
/obj/item/mod/module/jetpack/Initialize(mapload)
@@ -182,8 +182,12 @@
use_power_cost = DEFAULT_CHARGE_DRAIN * 0.1
incompatible_modules = list(/obj/item/mod/module/status_readout)
tgui_id = "status_readout"
/// Does this show damage types, body temp, satiety
var/display_detailed_vitals = TRUE
/// Does this show DNA data
var/display_dna = FALSE
/// Does this show the round ID and shift time?
var/show_time = FALSE
var/display_time = FALSE
/// Death sound. May or may not be funny. Vareditable at your own risk.
var/death_sound = 'sound/effects/flatline3.ogg'
/// Death sound volume. Please be responsible with this.
@@ -191,20 +195,22 @@
/obj/item/mod/module/status_readout/add_ui_data()
. = ..()
.["show_time"] = show_time
.["statustime"] = station_time_timestamp()
.["statusid"] = GLOB.round_id
.["statushealth"] = mod.wearer?.health || 0
.["statusmaxhealth"] = mod.wearer?.getMaxHealth() || 0
.["statusbrute"] = mod.wearer?.getBruteLoss() || 0
.["statusburn"] = mod.wearer?.getFireLoss() || 0
.["statustoxin"] = mod.wearer?.getToxLoss() || 0
.["statusoxy"] = mod.wearer?.getOxyLoss() || 0
.["statustemp"] = mod.wearer?.bodytemperature || 0
.["statusnutrition"] = mod.wearer?.nutrition || 0
.["statusfingerprints"] = mod.wearer ? md5(mod.wearer.dna.unique_identity) : null
.["statusdna"] = mod.wearer?.dna.unique_enzymes
.["statusviruses"] = null
.["display_time"] = display_time
.["shift_time"] = station_time_timestamp()
.["shift_id"] = GLOB.round_id
.["health"] = mod.wearer?.health || 0
.["health_max"] = mod.wearer?.getMaxHealth() || 0
if(display_detailed_vitals)
.["loss_brute"] = mod.wearer?.getBruteLoss() || 0
.["loss_fire"] = mod.wearer?.getFireLoss() || 0
.["loss_tox"] = mod.wearer?.getToxLoss() || 0
.["loss_oxy"] = mod.wearer?.getOxyLoss() || 0
.["body_temperature"] = mod.wearer?.bodytemperature || 0
.["nutrition"] = mod.wearer?.nutrition || 0
if(display_dna)
.["dna_unique_identity"] = mod.wearer ? md5(mod.wearer.dna.unique_identity) : null
.["dna_unique_enzymes"] = mod.wearer?.dna.unique_enzymes
.["viruses"] = null
if(!length(mod.wearer?.diseases))
return .
var/list/viruses = list()
@@ -216,10 +222,22 @@
virus_data["maxstage"] = virus.max_stages
virus_data["cure"] = virus.cure_text
viruses += list(virus_data)
.["statusviruses"] = viruses
.["viruses"] = viruses
return .
/obj/item/mod/module/status_readout/get_configuration()
. = ..()
.["display_detailed_vitals"] = add_ui_configuration("Detailed Vitals", "bool", display_detailed_vitals)
.["display_dna"] = add_ui_configuration("DNA Information", "bool", display_dna)
/obj/item/mod/module/status_readout/configure_edit(key, value)
switch(key)
if("display_detailed_vitals")
display_detailed_vitals = text2num(value)
if("display_dna")
display_dna = text2num(value)
/obj/item/mod/module/status_readout/on_suit_activation()
RegisterSignal(mod.wearer, COMSIG_LIVING_DEATH, PROC_REF(death_sound))
+9 -15
View File
@@ -20,22 +20,19 @@
tgui_id = "health_analyzer"
/// Scanning mode, changes how we scan something.
var/mode = HEALTH_SCAN
/// Do we relay the wearer's health data to the info tab? Mainly useful for turning off if you also have a status readout.
var/show_vitals = TRUE
/// List of all scanning modes.
var/static/list/modes = list(HEALTH_SCAN, WOUND_SCAN, CHEM_SCAN)
/obj/item/mod/module/health_analyzer/add_ui_data()
. = ..()
.["show_vitals"] = show_vitals
if(show_vitals)
.["userhealth"] = mod.wearer?.health || 0
.["usermaxhealth"] = mod.wearer?.getMaxHealth() || 0
.["userbrute"] = mod.wearer?.getBruteLoss() || 0
.["userburn"] = mod.wearer?.getFireLoss() || 0
.["usertoxin"] = mod.wearer?.getToxLoss() || 0
.["useroxy"] = mod.wearer?.getOxyLoss() || 0
.["health"] = mod.wearer?.health || 0
.["health_max"] = mod.wearer?.getMaxHealth() || 0
.["loss_brute"] = mod.wearer?.getBruteLoss() || 0
.["loss_fire"] = mod.wearer?.getFireLoss() || 0
.["loss_tox"] = mod.wearer?.getToxLoss() || 0
.["loss_oxy"] = mod.wearer?.getOxyLoss() || 0
return .
/obj/item/mod/module/health_analyzer/on_select_use(atom/target)
@@ -56,16 +53,13 @@
/obj/item/mod/module/health_analyzer/get_configuration()
. = ..()
.["mode"] = add_ui_configuration("Scan Mode", "list", mode, modes)
.["show_vitals"] = add_ui_configuration("Self Vitals Display", "bool", show_vitals)
return .
/obj/item/mod/module/health_analyzer/configure_edit(key, value)
switch(key)
if("mode")
mode = value
if("show_vitals")
show_vitals = value
#undef HEALTH_SCAN
#undef WOUND_SCAN
+1 -1
View File
@@ -298,7 +298,7 @@
all possible biometric data of the wearer; sleep, nutrition, fitness, fingerprints, \
and even useful information such as their overall health and wellness. This one comes with a clock that calibrates to the \
local system time, and an operational ID number display. The vital monitor's speaker has been removed."
show_time = TRUE
display_time = TRUE
death_sound = null
death_sound_volume = null
@@ -177,7 +177,7 @@
capable of capturing and displaying all possible biometric data of the wearer; sleep, nutrition, fitness, fingerprints, \
and even useful information such as their overall health and wellness. The vitals monitor also comes with a speaker, loud enough \
to alert anyone nearby that someone has, in fact, died. This specific unit has a clock and operational ID readout."
show_time = TRUE
display_time = TRUE
///Blatant copy of the adrenaline boost module.
/obj/item/mod/module/auto_doc
-802
View File
@@ -1,802 +0,0 @@
import { useBackend, useLocalState } from '../backend';
import { Button, ColorBox, LabeledList, ProgressBar, Section, Collapsible, Box, Icon, Stack, Table, Dimmer, NumberInput, Flex, AnimatedNumber, Dropdown } from '../components';
import { Window } from '../layouts';
const ConfigureNumberEntry = (props, context) => {
const { name, value, module_ref } = props;
const { act } = useBackend(context);
return (
<NumberInput
value={value}
minValue={-50}
maxValue={50}
stepPixelSize={5}
width="39px"
onChange={(e, value) =>
act('configure', {
'key': name,
'value': value,
'ref': module_ref,
})
}
/>
);
};
const ConfigureBoolEntry = (props, context) => {
const { name, value, module_ref } = props;
const { act } = useBackend(context);
return (
<Button.Checkbox
checked={value}
onClick={() =>
act('configure', {
'key': name,
'value': !value,
'ref': module_ref,
})
}
/>
);
};
const ConfigureColorEntry = (props, context) => {
const { name, value, module_ref } = props;
const { act } = useBackend(context);
return (
<>
<Button
icon="paint-brush"
onClick={() =>
act('configure', {
'key': name,
'ref': module_ref,
})
}
/>
<ColorBox color={value} mr={0.5} />
</>
);
};
const ConfigureListEntry = (props, context) => {
const { name, value, values, module_ref } = props;
const { act } = useBackend(context);
return (
<Dropdown
displayText={value}
options={values}
onSelected={(value) =>
act('configure', {
'key': name,
'value': value,
'ref': module_ref,
})
}
/>
);
};
const ConfigureDataEntry = (props, context) => {
const { name, display_name, type, value, values, module_ref } = props;
const configureEntryTypes = {
number: <ConfigureNumberEntry {...props} />,
bool: <ConfigureBoolEntry {...props} />,
color: <ConfigureColorEntry {...props} />,
list: <ConfigureListEntry {...props} />,
};
return (
<Box>
{display_name}: {configureEntryTypes[type]}
</Box>
);
};
const RadCounter = (props, context) => {
const { active, userradiated, usertoxins, usermaxtoxins, threatlevel } =
props;
return (
<Stack fill textAlign="center">
<Stack.Item grow>
<Section
title="Radiation Level"
color={active && userradiated ? 'bad' : 'good'}>
{active && userradiated ? 'IRRADIATED' : 'RADIATION-FREE'}
</Section>
</Stack.Item>
<Stack.Item grow>
<Section title="Toxins Level">
<ProgressBar
value={active ? usertoxins / usermaxtoxins : 0}
ranges={{
good: [-Infinity, 0.2],
average: [0.2, 0.5],
bad: [0.5, Infinity],
}}>
<AnimatedNumber value={usertoxins} />
</ProgressBar>
</Section>
</Stack.Item>
<Stack.Item grow>
<Section
title="Hazard Level"
color={active && threatlevel ? 'bad' : 'good'}
bold>
{active && threatlevel ? threatlevel : 0}
</Section>
</Stack.Item>
</Stack>
);
};
const HealthAnalyzer = (props, context) => {
const {
active,
show_vitals,
userhealth,
usermaxhealth,
userbrute,
userburn,
usertoxin,
useroxy,
} = props;
return (
<Section>
{show_vitals ? (
<>
<Section title="Health">
<ProgressBar
value={active ? userhealth / usermaxhealth : 0}
ranges={{
good: [0.5, Infinity],
average: [0.2, 0.5],
bad: [-Infinity, 0.2],
}}>
<AnimatedNumber value={active ? userhealth : 0} />
</ProgressBar>
</Section>
<Stack textAlign="center">
<Stack.Item grow>
<Section title="Brute">
<ProgressBar
value={active ? userbrute / usermaxhealth : 0}
ranges={{
good: [-Infinity, 0.2],
average: [0.2, 0.5],
bad: [0.5, Infinity],
}}>
<AnimatedNumber value={active ? userbrute : 0} />
</ProgressBar>
</Section>
</Stack.Item>
<Stack.Item grow>
<Section title="Burn">
<ProgressBar
value={active ? userburn / usermaxhealth : 0}
ranges={{
good: [-Infinity, 0.2],
average: [0.2, 0.5],
bad: [0.5, Infinity],
}}>
<AnimatedNumber value={active ? userburn : 0} />
</ProgressBar>
</Section>
</Stack.Item>
<Stack.Item grow>
<Section title="Toxin">
<ProgressBar
value={active ? usertoxin / usermaxhealth : 0}
ranges={{
good: [-Infinity, 0.2],
average: [0.2, 0.5],
bad: [0.5, Infinity],
}}>
<AnimatedNumber value={active ? usertoxin : 0} />
</ProgressBar>
</Section>
</Stack.Item>
<Stack.Item grow>
<Section title="Suffocation">
<ProgressBar
value={active ? useroxy / usermaxhealth : 0}
ranges={{
good: [-Infinity, 0.2],
average: [0.2, 0.5],
bad: [0.5, Infinity],
}}>
<AnimatedNumber value={active ? useroxy : 0} />
</ProgressBar>
</Section>
</Stack.Item>
</Stack>
</>
) : (
<Section>
{'Health Analyzer Vitals Readout Disabled In Settings'}
</Section>
)}
</Section>
);
};
const StatusReadout = (props, context) => {
const {
active,
show_time,
statustime,
statusid,
statushealth,
statusmaxhealth,
statusbrute,
statusburn,
statustoxin,
statusoxy,
statustemp,
statusnutrition,
statusfingerprints,
statusdna,
statusviruses,
} = props;
return (
<>
{!!show_time && (
<Stack textAlign="center">
<Stack.Item grow>
<Section title="Operation Time">
{active ? statustime : '00:00:00'}
</Section>
</Stack.Item>
<Stack.Item grow>
<Section title="Operation Number">
{active ? statusid : '???'}
</Section>
</Stack.Item>
</Stack>
)}
<Section title="Health">
<ProgressBar
value={active ? statushealth / statusmaxhealth : 0}
ranges={{
good: [0.5, Infinity],
average: [0.2, 0.5],
bad: [-Infinity, 0.2],
}}>
<AnimatedNumber value={active ? statushealth : 0} />
</ProgressBar>
</Section>
<Stack textAlign="center">
<Stack.Item grow>
<Section title="Brute">
<ProgressBar
value={active ? statusbrute / statusmaxhealth : 0}
ranges={{
good: [-Infinity, 0.2],
average: [0.2, 0.5],
bad: [0.5, Infinity],
}}>
<AnimatedNumber value={active ? statusbrute : 0} />
</ProgressBar>
</Section>
</Stack.Item>
<Stack.Item grow>
<Section title="Burn">
<ProgressBar
value={active ? statusburn / statusmaxhealth : 0}
ranges={{
good: [-Infinity, 0.2],
average: [0.2, 0.5],
bad: [0.5, Infinity],
}}>
<AnimatedNumber value={active ? statusburn : 0} />
</ProgressBar>
</Section>
</Stack.Item>
<Stack.Item grow>
<Section title="Toxin">
<ProgressBar
value={active ? statustoxin / statusmaxhealth : 0}
ranges={{
good: [-Infinity, 0.2],
average: [0.2, 0.5],
bad: [0.5, Infinity],
}}>
<AnimatedNumber value={statustoxin} />
</ProgressBar>
</Section>
</Stack.Item>
<Stack.Item grow>
<Section title="Suffocation">
<ProgressBar
value={active ? statusoxy / statusmaxhealth : 0}
ranges={{
good: [-Infinity, 0.2],
average: [0.2, 0.5],
bad: [0.5, Infinity],
}}>
<AnimatedNumber value={statusoxy} />
</ProgressBar>
</Section>
</Stack.Item>
</Stack>
<Stack textAlign="center">
<Stack.Item grow>
<Section title="Body Temperature">{active ? statustemp : 0}</Section>
</Stack.Item>
<Stack.Item grow>
<Section title="Nutrition Status">
{active ? statusnutrition : 0}
</Section>
</Stack.Item>
</Stack>
<Section title="DNA">
<LabeledList>
<LabeledList.Item label="Fingerprints">
{active ? statusfingerprints : '???'}
</LabeledList.Item>
<LabeledList.Item label="Unique Enzymes">
{active ? statusdna : '???'}
</LabeledList.Item>
</LabeledList>
</Section>
{!!active && !!statusviruses && (
<Section title="Diseases">
<Table>
<Table.Row header>
<Table.Cell textAlign="center">
<Button
color="transparent"
icon="signature"
tooltip="Name"
tooltipPosition="top"
/>
</Table.Cell>
<Table.Cell textAlign="center">
<Button
color="transparent"
icon="wind"
tooltip="Type"
tooltipPosition="top"
/>
</Table.Cell>
<Table.Cell textAlign="center">
<Button
color="transparent"
icon="bolt"
tooltip="Stage"
tooltipPosition="top"
/>
</Table.Cell>
<Table.Cell textAlign="center">
<Button
color="transparent"
icon="flask"
tooltip="Cure"
tooltipPosition="top"
/>
</Table.Cell>
</Table.Row>
{statusviruses.map((virus) => {
return (
<Table.Row key={virus.name}>
<Table.Cell textAlign="center">{virus.name}</Table.Cell>
<Table.Cell textAlign="center">{virus.type}</Table.Cell>
<Table.Cell textAlign="center">
{virus.stage}/{virus.maxstage}
</Table.Cell>
<Table.Cell textAlign="center">{virus.cure}</Table.Cell>
</Table.Row>
);
})}
</Table>
</Section>
)}
</>
);
};
const ID2MODULE = {
rad_counter: RadCounter,
health_analyzer: HealthAnalyzer,
status_readout: StatusReadout,
};
const LockedInterface = () => (
<Section align="center" fill>
<Icon color="red" name="exclamation-triangle" size={15} />
<Box fontSize="30px" color="red">
ERROR: INTERFACE UNRESPONSIVE
</Box>
</Section>
);
const LockedModule = (props, context) => {
const { act, data } = useBackend(context);
return (
<Dimmer>
<Stack>
<Stack.Item fontSize="16px" color="blue">
SUIT UNPOWERED
</Stack.Item>
</Stack>
</Dimmer>
);
};
const ConfigureScreen = (props, context) => {
const { configuration_data, module_ref } = props;
const configuration_keys = Object.keys(configuration_data);
return (
<Dimmer backgroundColor="rgba(0, 0, 0, 0.8)">
<Stack vertical>
{configuration_keys.map((key) => {
const data = configuration_data[key];
return (
<Stack.Item key={data.key}>
<ConfigureDataEntry
name={key}
display_name={data.display_name}
type={data.type}
value={data.value}
values={data.values}
module_ref={module_ref}
/>
</Stack.Item>
);
})}
<Stack.Item>
<Box>
<Button
fluid
onClick={props.onExit}
icon="times"
textAlign="center">
Exit
</Button>
</Box>
</Stack.Item>
</Stack>
</Dimmer>
);
};
const displayText = (param) => {
switch (param) {
case 1:
return 'Use';
case 2:
return 'Toggle';
case 3:
return 'Select';
}
};
const ParametersSection = (props, context) => {
const { act, data } = useBackend(context);
const {
active,
malfunctioning,
locked,
open,
selected_module,
complexity,
complexity_max,
wearer_name,
wearer_job,
pAI, // SKYRAT EDIT - pAIs in MODsuits
ispAI, // SKYRAT EDIT - pAIs in MODsuits
} = data;
const status = malfunctioning
? 'Malfunctioning'
: active
? 'Active'
: 'Inactive';
return (
<Section title="Parameters">
<LabeledList>
<LabeledList.Item
label="Status"
buttons={
<Button
icon="power-off"
content={active ? 'Deactivate' : 'Activate'}
onClick={() => act('activate')}
/>
}>
{status}
</LabeledList.Item>
<LabeledList.Item
label="ID Lock"
buttons={
<Button
icon={locked ? 'lock-open' : 'lock'}
content={locked ? 'Unlock' : 'Lock'}
onClick={() => act('lock')}
/>
}>
{locked ? 'Locked' : 'Unlocked'}
</LabeledList.Item>
<LabeledList.Item label="Cover">
{open ? 'Open' : 'Closed'}
</LabeledList.Item>
<LabeledList.Item label="Selected Module">
{selected_module || 'None'}
</LabeledList.Item>
<LabeledList.Item label="Complexity">
{complexity} ({complexity_max})
</LabeledList.Item>
<LabeledList.Item label="Occupant">
{wearer_name}, {wearer_job}
</LabeledList.Item>
<LabeledList.Item
label="Onboard pAI"
buttons={
// SKYRAT EDIT START - pAIs in MODsuits
pAI && !ispAI ? (
<Button
icon="eject"
content="Eject pAI"
onClick={() => act('remove_pai')}
/>
) : (
<> </>
)
}>
{pAI || 'None' /* SKYRAT EDIT END */}
</LabeledList.Item>
</LabeledList>
</Section>
);
};
const HardwareSection = (props, context) => {
const { act, data } = useBackend(context);
const {
active,
control,
helmet,
chestplate,
gauntlets,
boots,
core,
charge,
} = data;
return (
<Section title="Hardware">
<Collapsible title="Parts">
<LabeledList>
<LabeledList.Item label="Control Unit">{control}</LabeledList.Item>
<LabeledList.Item label="Helmet">{helmet || 'None'}</LabeledList.Item>
<LabeledList.Item label="Chestplate">
{chestplate || 'None'}
</LabeledList.Item>
<LabeledList.Item label="Gauntlets">
{gauntlets || 'None'}
</LabeledList.Item>
<LabeledList.Item label="Boots">{boots || 'None'}</LabeledList.Item>
</LabeledList>
</Collapsible>
<Collapsible title="Core">
{(core && (
<LabeledList>
<LabeledList.Item label="Core Type">{core}</LabeledList.Item>
<LabeledList.Item label="Core Charge">
<ProgressBar
value={charge / 100}
content={charge + '%'}
ranges={{
good: [0.6, Infinity],
average: [0.3, 0.6],
bad: [-Infinity, 0.3],
}}
/>
</LabeledList.Item>
</LabeledList>
)) || (
<Box color="bad" textAlign="center">
No Core Detected
</Box>
)}
</Collapsible>
</Section>
);
};
const InfoSection = (props, context) => {
const { act, data } = useBackend(context);
const { active, modules } = data;
const info_modules = modules.filter((module) => !!module.id);
return (
<Section title="Info">
<Stack vertical>
{(info_modules.length !== 0 &&
info_modules.map((module) => {
const Module = ID2MODULE[module.id];
return (
<Stack.Item key={module.ref}>
{!active && <LockedModule />}
<Module {...module} active={active} />
</Stack.Item>
);
})) || <Box textAlign="center">No Info Modules Detected</Box>}
</Stack>
</Section>
);
};
const ModuleSection = (props, context) => {
const { act, data } = useBackend(context);
const { complexity_max, modules } = data;
const [configureState, setConfigureState] = useLocalState(
context,
'module_configuration',
null
);
return (
<Section title="Modules" fill>
<Flex direction="column">
{(modules.length !== 0 &&
modules.map((module) => {
return (
<Flex.Item key={module.ref}>
<Collapsible title={module.module_name}>
<Section>
{configureState === module.ref && (
<ConfigureScreen
configuration_data={module.configuration_data}
module_ref={module.ref}
onExit={() => setConfigureState(null)}
/>
)}
<Table>
<Table.Row header>
<Table.Cell textAlign="center">
<Button
color="transparent"
icon="save"
tooltip="Complexity"
tooltipPosition="top"
/>
</Table.Cell>
<Table.Cell textAlign="center">
<Button
color="transparent"
icon="plug"
tooltip="Idle Power Cost"
tooltipPosition="top"
/>
</Table.Cell>
<Table.Cell textAlign="center">
<Button
color="transparent"
icon="lightbulb"
tooltip="Active Power Cost"
tooltipPosition="top"
/>
</Table.Cell>
<Table.Cell textAlign="center">
<Button
color="transparent"
icon="bolt"
tooltip="Use Power Cost"
tooltipPosition="top"
/>
</Table.Cell>
<Table.Cell textAlign="center">
<Button
color="transparent"
icon="hourglass-half"
tooltip="Cooldown"
tooltipPosition="top"
/>
</Table.Cell>
<Table.Cell textAlign="center">
<Button
color="transparent"
icon="tasks"
tooltip="Actions"
tooltipPosition="top"
/>
</Table.Cell>
</Table.Row>
<Table.Row>
<Table.Cell textAlign="center">
{module.module_complexity}/{complexity_max}
</Table.Cell>
<Table.Cell textAlign="center">
{module.idle_power}
</Table.Cell>
<Table.Cell textAlign="center">
{module.active_power}
</Table.Cell>
<Table.Cell textAlign="center">
{module.use_power}
</Table.Cell>
<Table.Cell textAlign="center">
{(module.cooldown > 0 && module.cooldown / 10) || '0'}
/{module.cooldown_time / 10}s
</Table.Cell>
<Table.Cell textAlign="center">
<Button
onClick={() => act('select', { 'ref': module.ref })}
icon="bullseye"
selected={module.module_active}
tooltip={displayText(module.module_type)}
tooltipPosition="left"
disabled={!module.module_type}
/>
<Button
onClick={() => setConfigureState(module.ref)}
icon="cog"
selected={configureState === module.ref}
tooltip="Configure"
tooltipPosition="left"
disabled={module.configuration_data.length === 0}
/>
<Button
onClick={() => act('pin', { 'ref': module.ref })}
icon="thumbtack"
selected={module.pinned}
tooltip="Pin"
tooltipPosition="left"
disabled={!module.module_type}
/>
</Table.Cell>
</Table.Row>
</Table>
<Box>{module.description}</Box>
</Section>
</Collapsible>
</Flex.Item>
);
})) || (
<Flex.Item>
<Box textAlign="center">No Modules Detected</Box>
</Flex.Item>
)}
</Flex>
</Section>
);
};
export const MODsuitContent = (props, context) => {
const { act, data } = useBackend(context);
const { ui_theme, interface_break } = data;
return (
<Section>
{(!!interface_break && <LockedInterface />) || (
<Stack vertical fill>
<Stack.Item>
<ParametersSection />
</Stack.Item>
<Stack.Item>
<HardwareSection />
</Stack.Item>
<Stack.Item>
<InfoSection />
</Stack.Item>
<Stack.Item grow>
<ModuleSection />
</Stack.Item>
</Stack>
)}
</Section>
);
};
export const MODsuit = (props, context) => {
const { act, data } = useBackend(context);
const { ui_theme, interface_break } = data;
return (
<Window
width={400}
height={525}
theme={ui_theme}
title="MOD Interface Panel"
resizable>
<Window.Content scrollable={!interface_break}>
<MODsuitContent />
</Window.Content>
</Window>
);
};
+736
View File
@@ -0,0 +1,736 @@
import { BooleanLike } from 'common/react';
import { formatSiUnit } from '../format';
import { useBackend, useLocalState } from '../backend';
import { Button, ColorBox, LabeledList, ProgressBar, Section, Collapsible, Box, Icon, Stack, Table, Dimmer, NumberInput, AnimatedNumber, Dropdown, NoticeBox } from '../components';
import { Window } from '../layouts';
type MODsuitData = {
// Static
ui_theme: string;
control: string;
complexity_max: number;
helmet: string;
chestplate: string;
gauntlets: string;
boots: string;
// Dynamic
suit_status: SuitStatus;
user_status: UserStatus;
module_custom_status: ModuleCustomStatus;
module_info: Module[];
};
type SuitStatus = {
core_name: string;
cell_charge_current: number;
cell_charge_max: number;
active: BooleanLike;
open: BooleanLike;
seconds_electrified: number;
malfunctioning: BooleanLike;
locked: BooleanLike;
interface_break: BooleanLike;
complexity: number;
selected_module: string;
ai_name: string;
pAI: string; // SKYRAT EDIT ADDITION - pAIs in MODsuits
ispAI: BooleanLike; // SKYRAT EDIT ADDITION - pAIs in MODsuits
};
type UserStatus = {
user_name: string;
user_assignment: string;
};
type ModuleCustomStatus = {
health: number;
health_max: number;
loss_brute: number;
loss_fire: number;
loss_tox: number;
loss_oxy: number;
is_user_irradiated: BooleanLike;
background_radiation_level: number;
display_time: BooleanLike;
shift_time: string;
shift_id: string;
body_temperature: number;
nutrition: number;
dna_unique_identity: string;
dna_unique_enzymes: string;
viruses: VirusData[];
};
type VirusData = {
name: string;
type: string;
stage: number;
maxstage: number;
cure: string;
};
type Module = {
module_name: string;
description: string;
module_type: number;
module_active: BooleanLike;
pinned: BooleanLike;
idle_power: number;
active_power: number;
use_power: number;
module_complexity: number;
cooldown_time: number;
cooldown: number;
id: string;
ref: string;
configuration_data: ModuleConfig[];
};
type ModuleConfig = {
display_name: string;
type: string;
value: number;
values: [];
};
export const MODsuit = (props, context) => {
const { act, data } = useBackend<MODsuitData>(context);
const { ui_theme } = data;
const { interface_break } = data.suit_status;
return (
<Window
width={600}
height={600}
theme={ui_theme}
title="MOD Interface Panel"
resizable>
<Window.Content scrollable={!interface_break}>
<MODsuitContent />
</Window.Content>
</Window>
);
};
export const MODsuitContent = (props, context) => {
const { act, data } = useBackend<MODsuitData>(context);
const { interface_break } = data.suit_status;
return (
<Box>
{interface_break ? (
<LockedInterface />
) : (
<Stack vertical>
<Stack.Item>
<Stack>
<Stack.Item grow>
<SuitStatusSection />
</Stack.Item>
<Stack.Item grow>
<UserStatusSection />
</Stack.Item>
</Stack>
</Stack.Item>
<Stack.Item>
<ModuleSection />
</Stack.Item>
<Stack.Item>
<HardwareSection />
</Stack.Item>
</Stack>
)}
</Box>
);
};
const ConfigureNumberEntry = (props, context) => {
const { name, value, module_ref } = props;
const { act } = useBackend(context);
return (
<NumberInput
value={value}
minValue={-50}
maxValue={50}
stepPixelSize={5}
width="39px"
onChange={(e, value) =>
act('configure', {
'key': name,
'value': value,
'ref': module_ref,
})
}
/>
);
};
const ConfigureBoolEntry = (props, context) => {
const { name, value, module_ref } = props;
const { act } = useBackend(context);
return (
<Button.Checkbox
checked={value}
onClick={() =>
act('configure', {
'key': name,
'value': !value,
'ref': module_ref,
})
}
/>
);
};
const ConfigureColorEntry = (props, context) => {
const { name, value, module_ref } = props;
const { act } = useBackend(context);
return (
<>
<Button
icon="paint-brush"
onClick={() =>
act('configure', {
'key': name,
'ref': module_ref,
})
}
/>
<ColorBox color={value} mr={0.5} />
</>
);
};
const ConfigureListEntry = (props, context) => {
const { name, value, values, module_ref } = props;
const { act } = useBackend(context);
return (
<Dropdown
displayText={value}
options={values}
onSelected={(value) =>
act('configure', {
'key': name,
'value': value,
'ref': module_ref,
})
}
/>
);
};
const ConfigureDataEntry = (props, context) => {
const { name, display_name, type, value, values, module_ref } = props;
const configureEntryTypes = {
number: <ConfigureNumberEntry {...props} />,
bool: <ConfigureBoolEntry {...props} />,
color: <ConfigureColorEntry {...props} />,
list: <ConfigureListEntry {...props} />,
};
return (
<LabeledList.Item label={display_name}>
{configureEntryTypes[type]}
</LabeledList.Item>
);
};
const LockedInterface = () => (
<Section align="center" fill>
<Icon color="red" name="exclamation-triangle" size={15} />
<Box fontSize="30px" color="red">
ERROR: INTERFACE UNRESPONSIVE
</Box>
</Section>
);
const LockedModule = (props, context) => {
const { act, data } = useBackend(context);
return (
<Dimmer>
<Stack>
<Stack.Item fontSize="16px" color="blue">
SUIT UNPOWERED
</Stack.Item>
</Stack>
</Dimmer>
);
};
const ConfigureScreen = (props, context) => {
const { configuration_data, module_ref, module_name } = props;
const configuration_keys = Object.keys(configuration_data);
return (
<Box pb={1}>
<LabeledList>
{configuration_keys.map((key) => {
const data = configuration_data[key];
return (
<ConfigureDataEntry
key={data.key}
name={key}
display_name={data.display_name}
type={data.type}
value={data.value}
values={data.values}
module_ref={module_ref}
/>
);
})}
</LabeledList>
</Box>
);
};
const moduleTypeAction = (param) => {
switch (param) {
case 1:
return 'Use';
case 2:
return 'Toggle';
case 3:
return 'Select';
}
};
const radiationLevels = (param) => {
switch (param) {
case 1:
return 'Low';
case 2:
return 'Medium';
case 3:
return 'High';
case 4:
return 'Extreme';
}
};
const SuitStatusSection = (props, context) => {
const { act, data } = useBackend<MODsuitData>(context);
const {
core_name,
cell_charge_current,
cell_charge_max,
active,
open,
seconds_electrified,
malfunctioning,
locked,
ai_name,
pAI,
ispAI,
} = data.suit_status;
const { display_time, shift_time, shift_id } = data.module_custom_status;
const status = malfunctioning
? 'Malfunctioning'
: active
? 'Active'
: 'Inactive';
const charge_percent = Math.round(
(100 * cell_charge_current) / cell_charge_max
);
return (
<Section
title="Suit Status"
fill
buttons={
<Button
icon="power-off"
color={active ? 'good' : 'default'}
content={status}
onClick={() => act('activate')}
/>
}>
<LabeledList>
<LabeledList.Item label="Charge">
<ProgressBar
value={cell_charge_current / cell_charge_max}
ranges={{
good: [0.6, Infinity],
average: [0.3, 0.6],
bad: [-Infinity, 0.3],
}}
style={{
'text-shadow': '1px 1px 0 black',
}}>
{!core_name
? 'No Core Detected'
: cell_charge_max === 1
? 'Power Cell Missing'
: cell_charge_current === 1e31
? 'Infinite'
: `${formatSiUnit(
cell_charge_current * 1000,
0,
'J'
)} of ${formatSiUnit(
cell_charge_max * 1000,
0,
'J'
)} (${charge_percent}%)`}
</ProgressBar>
</LabeledList.Item>
<LabeledList.Item label="ID Lock">
<Button
icon={locked ? 'lock' : 'lock-open'}
color={locked ? 'good' : 'default'}
content={locked ? 'Locked' : 'Unlocked'}
onClick={() => act('lock')}
/>
</LabeledList.Item>
{!!open && (
<LabeledList.Item label="Cover">
<Box color="red">Open</Box>
</LabeledList.Item>
)}
{!!seconds_electrified && (
<LabeledList.Item label="Circuits">
<Box color="red">Shorted</Box>
</LabeledList.Item>
)}
{!!ai_name && (
<LabeledList.Item label="AI Core">{ai_name}</LabeledList.Item>
)}
{/* SKYRAT EDIT START - pAIs in MODsuits*/}
<LabeledList.Item label="Onboard pAI">
{pAI || 'None'} &nbsp;
{!!pAI && !ispAI && (
<Button
icon="eject"
content="Eject pAI"
onClick={() => act('remove_pai')}
/>
)}
</LabeledList.Item>
{/* SKYRAT EDIT END */}
</LabeledList>
{!!display_time && (
<Section title="Operation" mt={2}>
<LabeledList.Item label="Time">
{active ? shift_time : '00:00:00'}
</LabeledList.Item>
<LabeledList.Item label="Number">
{active && shift_id ? shift_id : '???'}
</LabeledList.Item>
</Section>
)}
</Section>
);
};
const HardwareSection = (props, context) => {
const { act, data } = useBackend<MODsuitData>(context);
const { control, helmet, chestplate, gauntlets, boots } = data;
const { pAI, core_name } = data.suit_status; /* SKYRAT EDIT CHANGE - pAI */
return (
<Section title="Hardware" style={{ 'text-transform': 'capitalize' }}>
<LabeledList>
<LabeledList.Item label="pAI Card" /* SKYRAT EDIT CHANGE - pAI */>
{pAI || 'No pAI Card Detected' /* SKYRAT EDIT CHANGE - pAI */}
</LabeledList.Item>
<LabeledList.Item label="Core">
{core_name || 'No Core Detected'}
</LabeledList.Item>
<LabeledList.Item label="Control Unit">{control}</LabeledList.Item>
<LabeledList.Item label="Helmet">{helmet || 'None'}</LabeledList.Item>
<LabeledList.Item label="Chestplate">
{chestplate || 'None'}
</LabeledList.Item>
<LabeledList.Item label="Gauntlets">
{gauntlets || 'None'}
</LabeledList.Item>
<LabeledList.Item label="Boots">{boots || 'None'}</LabeledList.Item>
</LabeledList>
</Section>
);
};
const UserStatusSection = (props, context) => {
const { act, data } = useBackend<MODsuitData>(context);
const { active } = data.suit_status;
const { user_name, user_assignment } = data.user_status;
const {
health,
health_max,
loss_brute,
loss_fire,
loss_tox,
loss_oxy,
is_user_irradiated,
background_radiation_level,
body_temperature,
nutrition,
dna_unique_identity,
dna_unique_enzymes,
viruses,
} = data.module_custom_status;
return (
<Section title="User Status" fill>
{!active && <LockedModule />}
<LabeledList>
{health !== undefined && (
<LabeledList.Item label="Health">
<ProgressBar
value={active ? health / health_max : 0}
ranges={{
good: [0.5, Infinity],
average: [0.2, 0.5],
bad: [-Infinity, 0.2],
}}>
<AnimatedNumber value={active ? health : 0} />
</ProgressBar>
</LabeledList.Item>
)}
{loss_brute !== undefined && (
<LabeledList.Item label="Brute Damage">
<ProgressBar
value={active ? loss_brute / health_max : 0}
ranges={{
good: [-Infinity, 0.2],
average: [0.2, 0.5],
bad: [0.5, Infinity],
}}>
<AnimatedNumber value={active ? loss_brute : 0} />
</ProgressBar>
</LabeledList.Item>
)}
{loss_fire !== undefined && (
<LabeledList.Item label="Burn Damage">
<ProgressBar
value={active ? loss_fire / health_max : 0}
ranges={{
good: [-Infinity, 0.2],
average: [0.2, 0.5],
bad: [0.5, Infinity],
}}>
<AnimatedNumber value={active ? loss_fire : 0} />
</ProgressBar>
</LabeledList.Item>
)}
{loss_oxy !== undefined && (
<LabeledList.Item label="Oxy Damage">
<ProgressBar
value={active ? loss_oxy / health_max : 0}
ranges={{
good: [-Infinity, 0.2],
average: [0.2, 0.5],
bad: [0.5, Infinity],
}}>
<AnimatedNumber value={active ? loss_oxy : 0} />
</ProgressBar>
</LabeledList.Item>
)}
{loss_tox !== undefined && (
<LabeledList.Item label="Tox Damage">
<ProgressBar
value={active ? loss_tox / health_max : 0}
ranges={{
good: [-Infinity, 0.2],
average: [0.2, 0.5],
bad: [0.5, Infinity],
}}>
<AnimatedNumber value={active ? loss_tox : 0} />
</ProgressBar>
</LabeledList.Item>
)}
{background_radiation_level !== undefined && (
<LabeledList.Item label="Radiation">
{!active ? (
'Unknown'
) : is_user_irradiated ? (
<NoticeBox danger>User Irradiated</NoticeBox>
) : background_radiation_level ? (
<NoticeBox>
{`Background: ${radiationLevels(background_radiation_level)}`}
</NoticeBox>
) : (
<NoticeBox info>Not Detected</NoticeBox>
)}
</LabeledList.Item>
)}
{body_temperature !== undefined && (
<LabeledList.Item label="Body Temp">
{`${active ? Math.round(body_temperature) : 0} K`}
</LabeledList.Item>
)}
{nutrition !== undefined && (
<LabeledList.Item label="Satiety Level">
{`${active ? Math.round(nutrition) : 0}`}
</LabeledList.Item>
)}
<LabeledList.Item label="Name">{user_name}</LabeledList.Item>
<LabeledList.Item label="Assignment">
{user_assignment}
</LabeledList.Item>
{dna_unique_identity !== undefined && (
<LabeledList.Item label="Fingerprints">
<Box
style={{
'word-break': 'break-all',
'word-wrap': 'break-word',
}}>
{active ? dna_unique_identity : '???'}
</Box>
</LabeledList.Item>
)}
{dna_unique_enzymes !== undefined && (
<LabeledList.Item label="Enzymes">
<Box
style={{
'word-break': 'break-all',
'word-wrap': 'break-word',
}}>
{active ? dna_unique_enzymes : '???'}
</Box>
</LabeledList.Item>
)}
</LabeledList>
{!!viruses && (
<Section title="Diseases">
{viruses.map((virus) => {
return (
<Collapsible title={virus.name} key={virus.name}>
<LabeledList>
<LabeledList.Item label="Spread">
{virus.type}
</LabeledList.Item>
<LabeledList.Item label="Stage">
{virus.stage}/{virus.maxstage}
</LabeledList.Item>
<LabeledList.Item label="Cure">{virus.cure}</LabeledList.Item>
</LabeledList>
</Collapsible>
);
})}
</Section>
)}
</Section>
);
};
const ModuleSection = (props, context) => {
const { act, data } = useBackend<MODsuitData>(context);
const { complexity_max, module_info } = data;
const { complexity } = data.suit_status;
const [configureState, setConfigureState] = useLocalState(
context,
'module_configuration',
''
);
return (
<Section
title="Modules"
fill
buttons={`${complexity} of ${complexity_max} complexity used`}>
{!module_info.length ? (
<NoticeBox>No Modules Detected</NoticeBox>
) : (
<Table>
<Table.Row header>
<Table.Cell colspan={3}>Actions</Table.Cell>
<Table.Cell>Name</Table.Cell>
<Table.Cell width={1} textAlign="center">
<Button
color="transparent"
icon="plug"
tooltip="Idle Power Cost"
tooltipPosition="top"
/>
</Table.Cell>
<Table.Cell width={1} textAlign="center">
<Button
color="transparent"
icon="lightbulb"
tooltip="Active Power Cost"
tooltipPosition="top"
/>
</Table.Cell>
<Table.Cell width={1} textAlign="center">
<Button
color="transparent"
icon="bolt"
tooltip="Use Power Cost"
tooltipPosition="top"
/>
</Table.Cell>
<Table.Cell width={1} textAlign="center">
<Button
color="transparent"
icon="save"
tooltip="Complexity"
tooltipPosition="top"
/>
</Table.Cell>
</Table.Row>
{module_info.map((module) => {
return (
<Table.Row key={module.ref}>
<Table.Cell width={1}>
<Button
onClick={() => act('select', { 'ref': module.ref })}
icon={
module.module_type === 3
? module.module_active
? 'check-square-o'
: 'square-o'
: 'power-off'
}
selected={module.module_active}
tooltip={moduleTypeAction(module.module_type)}
tooltipPosition="left"
disabled={!module.module_type || module.cooldown > 0}
/>
</Table.Cell>
<Table.Cell width={1}>
<Button
onClick={() =>
setConfigureState(
configureState === module.ref ? '' : module.ref
)
}
icon="cog"
selected={configureState === module.ref}
tooltip="Configure"
tooltipPosition="left"
disabled={module.configuration_data.length === 0}
/>
</Table.Cell>
<Table.Cell width={1}>
<Button
onClick={() => act('pin', { 'ref': module.ref })}
icon="thumbtack"
selected={module.pinned}
tooltip="Pin"
tooltipPosition="left"
disabled={!module.module_type}
/>
</Table.Cell>
<Table.Cell>
<Collapsible
title={module.module_name}
color={module.module_active ? 'green' : 'default'}>
<Section mr={-19}>{module.description}</Section>
</Collapsible>
{configureState === module.ref && (
<ConfigureScreen
configuration_data={module.configuration_data}
module_ref={module.ref}
module_name={module.module_name}
/>
)}
</Table.Cell>
<Table.Cell textAlign="center">{module.idle_power}</Table.Cell>
<Table.Cell textAlign="center">
{module.active_power}
</Table.Cell>
<Table.Cell textAlign="center">{module.use_power}</Table.Cell>
<Table.Cell textAlign="center">
{module.module_complexity}
</Table.Cell>
</Table.Row>
);
})}
</Table>
)}
</Section>
);
};
@@ -1,4 +1,5 @@
import { BooleanLike } from 'common/react';
import { formatPower } from '../format';
import { useBackend } from '../backend';
import { Box, Button, LabeledList, NumberInput, ProgressBar, Chart, Section, Stack, Icon } from '../components';
import { Window } from '../layouts';
@@ -80,13 +81,10 @@ export const SolarControl = (props, context) => {
bad: [-Infinity, 0.33],
}}>
{capacity > 0
? Math.round(supply).toLocaleString() +
' of ' +
Math.round(capacity).toLocaleString() +
' W (' +
Math.round((100 * supply) / capacity) +
'%)'
: 0 + ' W'}
? `${formatPower(supply)} of ${formatPower(
capacity
)} (${Math.round((100 * supply) / capacity)}%)`
: formatPower(0)}
</ProgressBar>
</LabeledList.Item>
<LabeledList.Item