mirror of
https://github.com/CHOMPStation2/CHOMPStation2.git
synced 2025-12-11 02:34:00 +00:00
[MIRROR] Pretty animations and UI overhaul for RIGSuits (#10743)
Co-authored-by: ShadowLarkens <shadowlarkens@gmail.com>
This commit is contained in:
committed by
GitHub
parent
9b233f14b1
commit
b10094c44e
@@ -803,6 +803,8 @@
|
||||
|
||||
/obj/item/rig/dropped(mob/user)
|
||||
. = ..(user)
|
||||
// So the next user will see the boot animation
|
||||
tgui_shared_states.Cut()
|
||||
for(var/piece in list("helmet","gauntlets","chest","boots"))
|
||||
toggle_piece(piece, user, ONLY_RETRACT)
|
||||
if(wearer && wearer.wearing_rig == src)
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
// Just used to force the icon into the rsc, Byond.iconRefMap does the rest
|
||||
GLOBAL_DATUM_INIT(rigsuit_ui_icon, /icon, 'icons/hud/rig_ui_slots.dmi')
|
||||
|
||||
/*
|
||||
* This defines the global UI for RIGSuits.
|
||||
* It has all of the relevant TGUI procs, but it's entry point is in rig_verbs.dm
|
||||
|
||||
BIN
icons/hud/rig_ui_slots.dmi
Normal file
BIN
icons/hud/rig_ui_slots.dmi
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 3.6 KiB |
@@ -1,5 +1,13 @@
|
||||
import { useBackend } from 'tgui/backend';
|
||||
import { Button, LabeledList, Section } from 'tgui-core/components';
|
||||
import {
|
||||
Box,
|
||||
DmIcon,
|
||||
Icon,
|
||||
Section,
|
||||
Stack,
|
||||
Tooltip,
|
||||
} from 'tgui-core/components';
|
||||
import { type BooleanLike } from 'tgui-core/react';
|
||||
import { capitalize } from 'tgui-core/string';
|
||||
|
||||
import type { Data } from './types';
|
||||
@@ -8,6 +16,9 @@ export const RIGSuitHardware = (props) => {
|
||||
const { act, data } = useBackend<Data>();
|
||||
|
||||
const {
|
||||
sealed,
|
||||
aioverride,
|
||||
cooling,
|
||||
// Disables buttons while the suit is busy
|
||||
sealing,
|
||||
// Each piece
|
||||
@@ -23,68 +34,212 @@ export const RIGSuitHardware = (props) => {
|
||||
|
||||
return (
|
||||
<Section title="Hardware">
|
||||
<LabeledList>
|
||||
<LabeledList.Item
|
||||
label="Helmet"
|
||||
buttons={
|
||||
<Button
|
||||
icon={helmetDeployed ? 'sign-out-alt' : 'sign-in-alt'}
|
||||
disabled={sealing}
|
||||
selected={helmetDeployed}
|
||||
onClick={() => act('toggle_piece', { piece: 'helmet' })}
|
||||
>
|
||||
{helmetDeployed ? 'Deployed' : 'Deploy'}
|
||||
</Button>
|
||||
}
|
||||
<Stack fill align="center" justify="space-around">
|
||||
<Stack.Item grow>
|
||||
<Stack align="center" justify="center">
|
||||
<Stack.Item>
|
||||
<Box
|
||||
className="RIGSuit__FadeIn"
|
||||
style={{
|
||||
display: 'grid',
|
||||
gridTemplateColumns: '1fr 1fr',
|
||||
gap: '10px',
|
||||
width: 'fit-content',
|
||||
}}
|
||||
textColor="#fff"
|
||||
>
|
||||
<HardwarePiece
|
||||
disabled={sealing}
|
||||
icon="head"
|
||||
name={helmet}
|
||||
selected={helmetDeployed}
|
||||
onClick={() => act('toggle_piece', { piece: 'helmet' })}
|
||||
/>
|
||||
<HardwarePiece
|
||||
disabled={sealing}
|
||||
icon="body"
|
||||
name={chest}
|
||||
selected={chestDeployed}
|
||||
onClick={() => act('toggle_piece', { piece: 'chest' })}
|
||||
/>
|
||||
<HardwarePiece
|
||||
disabled={sealing}
|
||||
icon="gloves"
|
||||
name={gauntlets}
|
||||
selected={gauntletsDeployed}
|
||||
onClick={() => act('toggle_piece', { piece: 'gauntlets' })}
|
||||
/>
|
||||
<HardwarePiece
|
||||
disabled={sealing}
|
||||
icon="boots"
|
||||
name={boots}
|
||||
selected={bootsDeployed}
|
||||
onClick={() => act('toggle_piece', { piece: 'boots' })}
|
||||
/>
|
||||
</Box>
|
||||
</Stack.Item>
|
||||
</Stack>
|
||||
</Stack.Item>
|
||||
<Stack.Item
|
||||
style={{
|
||||
borderLeft: '2px solid #246984',
|
||||
borderRight: '2px solid #246984',
|
||||
}}
|
||||
height={15}
|
||||
width={0}
|
||||
>
|
||||
{helmet ? capitalize(helmet) : 'ERROR'}
|
||||
</LabeledList.Item>
|
||||
<LabeledList.Item
|
||||
label="Gauntlets"
|
||||
buttons={
|
||||
<Button
|
||||
icon={gauntletsDeployed ? 'sign-out-alt' : 'sign-in-alt'}
|
||||
disabled={sealing}
|
||||
selected={gauntletsDeployed}
|
||||
onClick={() => act('toggle_piece', { piece: 'gauntlets' })}
|
||||
>
|
||||
{gauntletsDeployed ? 'Deployed' : 'Deploy'}
|
||||
</Button>
|
||||
}
|
||||
>
|
||||
{gauntlets ? capitalize(gauntlets) : 'ERROR'}
|
||||
</LabeledList.Item>
|
||||
<LabeledList.Item
|
||||
label="Boots"
|
||||
buttons={
|
||||
<Button
|
||||
icon={bootsDeployed ? 'sign-out-alt' : 'sign-in-alt'}
|
||||
disabled={sealing}
|
||||
selected={bootsDeployed}
|
||||
onClick={() => act('toggle_piece', { piece: 'boots' })}
|
||||
>
|
||||
{bootsDeployed ? 'Deployed' : 'Deploy'}
|
||||
</Button>
|
||||
}
|
||||
>
|
||||
{boots ? capitalize(boots) : 'ERROR'}
|
||||
</LabeledList.Item>
|
||||
<LabeledList.Item
|
||||
label="Chestpiece"
|
||||
buttons={
|
||||
<Button
|
||||
icon={chestDeployed ? 'sign-out-alt' : 'sign-in-alt'}
|
||||
disabled={sealing}
|
||||
selected={chestDeployed}
|
||||
onClick={() => act('toggle_piece', { piece: 'chest' })}
|
||||
>
|
||||
{chestDeployed ? 'Deployed' : 'Deploy'}
|
||||
</Button>
|
||||
}
|
||||
>
|
||||
{chest ? capitalize(chest) : 'ERROR'}
|
||||
</LabeledList.Item>
|
||||
</LabeledList>
|
||||
|
||||
</Stack.Item>
|
||||
<Stack.Item grow>
|
||||
<Stack fill align="center" justify="center">
|
||||
<Stack.Item>
|
||||
<Box
|
||||
className="RIGSuit__FadeIn"
|
||||
style={{
|
||||
display: 'grid',
|
||||
gridTemplateColumns: '1fr 1fr',
|
||||
gap: '10px',
|
||||
width: 'fit-content',
|
||||
}}
|
||||
>
|
||||
<Tooltip
|
||||
content={
|
||||
'Suit Seals: ' + sealing
|
||||
? 'Sealing'
|
||||
: sealed
|
||||
? 'Sealed'
|
||||
: 'UNSEALED'
|
||||
}
|
||||
>
|
||||
<Box
|
||||
position="relative"
|
||||
className="RIGSuit__Icon"
|
||||
style={{
|
||||
filter: sealing
|
||||
? 'grayscale(100%) brightness(0.5)'
|
||||
: sealed
|
||||
? undefined
|
||||
: 'grayscale(80%)',
|
||||
}}
|
||||
onClick={() => act('toggle_seals')}
|
||||
>
|
||||
<DmIcon
|
||||
height={6}
|
||||
width={6}
|
||||
icon="icons/hud/rig_ui_slots.dmi"
|
||||
icon_state="base"
|
||||
/>
|
||||
<Box position="absolute" top={0.5} left={0.5}>
|
||||
<Icon name="power-off" size={5} color="#5c9fd8" />
|
||||
</Box>
|
||||
</Box>
|
||||
</Tooltip>
|
||||
|
||||
<Tooltip
|
||||
content={
|
||||
aioverride
|
||||
? 'Toggle AI Control Off'
|
||||
: 'Toggle AI Control On'
|
||||
}
|
||||
>
|
||||
<Box
|
||||
position="relative"
|
||||
className="RIGSuit__Icon"
|
||||
style={{
|
||||
filter: aioverride ? undefined : 'grayscale(80%)',
|
||||
}}
|
||||
onClick={() => act('toggle_ai_control')}
|
||||
>
|
||||
<DmIcon
|
||||
height={6}
|
||||
width={6}
|
||||
icon="icons/hud/rig_ui_slots.dmi"
|
||||
icon_state="base"
|
||||
/>
|
||||
<Box position="absolute" top={0.7} left={0.5}>
|
||||
<Icon name="robot" size={4} color="#5c9fd8" />
|
||||
</Box>
|
||||
</Box>
|
||||
</Tooltip>
|
||||
|
||||
<Tooltip content={cooling ? 'Cooling: On' : 'Cooling: Off'}>
|
||||
<Box
|
||||
position="relative"
|
||||
className="RIGSuit__Icon"
|
||||
style={{
|
||||
filter: cooling ? undefined : 'grayscale(80%)',
|
||||
}}
|
||||
onClick={() => act('toggle_cooling')}
|
||||
>
|
||||
<DmIcon
|
||||
height={6}
|
||||
width={6}
|
||||
icon="icons/hud/rig_ui_slots.dmi"
|
||||
icon_state="base"
|
||||
/>
|
||||
<Box position="absolute" top={1} left={1}>
|
||||
<Icon name="wind" size={4} color="#5c9fd8" />
|
||||
</Box>
|
||||
</Box>
|
||||
</Tooltip>
|
||||
|
||||
<Tooltip content="Tank Settings">
|
||||
<Box
|
||||
position="relative"
|
||||
className="RIGSuit__Icon"
|
||||
onClick={() => act('tank_settings')}
|
||||
>
|
||||
<DmIcon
|
||||
height={6}
|
||||
width={6}
|
||||
icon="icons/hud/rig_ui_slots.dmi"
|
||||
icon_state="base"
|
||||
/>
|
||||
<Box position="absolute" top={1} left={0.5}>
|
||||
<Icon name="lungs" size={4} color="#5c9fd8" />
|
||||
</Box>
|
||||
</Box>
|
||||
</Tooltip>
|
||||
</Box>
|
||||
</Stack.Item>
|
||||
</Stack>
|
||||
</Stack.Item>
|
||||
</Stack>
|
||||
</Section>
|
||||
);
|
||||
};
|
||||
|
||||
const HardwarePiece = (props: {
|
||||
name: string;
|
||||
icon: string;
|
||||
selected: BooleanLike;
|
||||
disabled: BooleanLike;
|
||||
onClick: () => void;
|
||||
}) => {
|
||||
let filter;
|
||||
if (props.disabled) {
|
||||
filter = 'grayscale(100%) brightness(0.5)';
|
||||
} else if (props.selected) {
|
||||
filter = undefined;
|
||||
} else {
|
||||
filter = 'grayscale(80%)';
|
||||
}
|
||||
|
||||
return (
|
||||
<Tooltip content={capitalize(props.name) || 'ERROR'}>
|
||||
<Box className="RIGSuit__Icon">
|
||||
<DmIcon
|
||||
height={6}
|
||||
width={6}
|
||||
icon="icons/hud/rig_ui_slots.dmi"
|
||||
icon_state={props.icon}
|
||||
style={{
|
||||
cursor: 'pointer',
|
||||
filter,
|
||||
}}
|
||||
onClick={props.onClick}
|
||||
/>
|
||||
</Box>
|
||||
</Tooltip>
|
||||
);
|
||||
};
|
||||
|
||||
118
tgui/packages/tgui/interfaces/RIGSuit/RIGSuitLoader.tsx
Normal file
118
tgui/packages/tgui/interfaces/RIGSuit/RIGSuitLoader.tsx
Normal file
@@ -0,0 +1,118 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
import { Window } from 'tgui/layouts';
|
||||
import { Box, Stack } from 'tgui-core/components';
|
||||
|
||||
// This work is licensed under a Creative Commons Attribution-ShareAlike 4.0 International License.
|
||||
// http://creativecommons.org/licenses/by-sa/4.0/
|
||||
const NTLogoReact = (props) => {
|
||||
return (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
version="1.0"
|
||||
viewBox="0 0 425 200"
|
||||
fill="#00bbbb"
|
||||
>
|
||||
<path
|
||||
className="RIGSuit__FadeIn"
|
||||
d="m 178.00399,0.03869 -71.20393,0 a 6.7613422,6.0255495 0 0 0 -6.76134,6.02555 l 0,187.87147 a 6.7613422,6.0255495 0 0 0 6.76134,6.02554 l 53.1072,0 a 6.7613422,6.0255495 0 0 0 6.76135,-6.02554 l 0,-101.544018 72.21628,104.699398 a 6.7613422,6.0255495 0 0 0 5.76015,2.87016 l 73.55487,0 a 6.7613422,6.0255495 0 0 0 6.76135,-6.02554 l 0,-187.87147 a 6.7613422,6.0255495 0 0 0 -6.76135,-6.02555 l -54.71644,0 a 6.7613422,6.0255495 0 0 0 -6.76133,6.02555 l 0,102.61935 L 183.76413,2.90886 a 6.7613422,6.0255495 0 0 0 -5.76014,-2.87017 z"
|
||||
/>
|
||||
<path
|
||||
className="RIGSuit__Animation__WipeLeft"
|
||||
style={{ animationDelay: '1s' }}
|
||||
d="M 4.8446333,22.10875 A 13.412039,12.501842 0 0 1 13.477588,0.03924 l 66.118315,0 a 5.3648158,5.000737 0 0 1 5.364823,5.00073 l 0,79.87931 z"
|
||||
/>
|
||||
<path
|
||||
className="RIGSuit__Animation__WipeLeft"
|
||||
style={{ animationDelay: '2s' }}
|
||||
d="m 420.15535,177.89119 a 13.412038,12.501842 0 0 1 -8.63295,22.06951 l -66.11832,0 a 5.3648152,5.000737 0 0 1 -5.36482,-5.00074 l 0,-79.87931 z"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
};
|
||||
|
||||
const LoadingText = (props: { onFinish?: () => void }) => {
|
||||
const onFinish = props.onFinish ? props.onFinish : () => {};
|
||||
useEffect(() => {
|
||||
const timer = setTimeout(() => {
|
||||
onFinish();
|
||||
}, 5000);
|
||||
|
||||
return () => {
|
||||
clearTimeout(timer);
|
||||
};
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<Box>
|
||||
<Box className="RIGSuit__Animation__WipeLeft">RigOS Loading...</Box>
|
||||
<Box
|
||||
className="RIGSuit__Animation__WipeLeft"
|
||||
style={{ animationDelay: '1s' }}
|
||||
>
|
||||
Starting Power-On Self Test...
|
||||
</Box>
|
||||
<Box
|
||||
className="RIGSuit__Animation__WipeLeft"
|
||||
style={{ animationDelay: '2s' }}
|
||||
>
|
||||
POST:{' '}
|
||||
<Box inline color="good">
|
||||
GOOD.
|
||||
</Box>
|
||||
</Box>
|
||||
<Box
|
||||
className="RIGSuit__Animation__WipeLeft"
|
||||
style={{ animationDelay: '3s' }}
|
||||
>
|
||||
Loading UI...
|
||||
</Box>
|
||||
<Box
|
||||
className="RIGSuit__Animation__WipeLeft"
|
||||
style={{ animationDelay: '4s' }}
|
||||
>
|
||||
All Systems Ready!
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
export const LoaderNT = (props: { onFinish?: () => void }) => {
|
||||
const [showLogo, setShowLogo] = useState(true);
|
||||
|
||||
useEffect(() => {
|
||||
setTimeout(() => {
|
||||
setShowLogo(false);
|
||||
}, 3500);
|
||||
}, []);
|
||||
|
||||
if (showLogo) {
|
||||
return (
|
||||
<Stack vertical fill justify="center">
|
||||
<Stack.Item>
|
||||
<NTLogoReact />
|
||||
</Stack.Item>
|
||||
</Stack>
|
||||
);
|
||||
}
|
||||
|
||||
return <LoadingText onFinish={props.onFinish} />;
|
||||
};
|
||||
|
||||
export const RIGSuitLoader = (props: { onFinish?: () => void }) => {
|
||||
// You can skip to the end by clicking
|
||||
const onFinish = props.onFinish ? props.onFinish : () => {};
|
||||
|
||||
return (
|
||||
<Window height={400} width={600}>
|
||||
<Window.Content
|
||||
fitted
|
||||
onClick={() => onFinish()}
|
||||
style={{ cursor: 'pointer' }}
|
||||
>
|
||||
<Box backgroundColor="black" height="100%">
|
||||
<LoaderNT onFinish={props.onFinish} />
|
||||
</Box>
|
||||
</Window.Content>
|
||||
</Window>
|
||||
);
|
||||
};
|
||||
@@ -5,7 +5,6 @@ import {
|
||||
LabeledList,
|
||||
ProgressBar,
|
||||
Section,
|
||||
Stack,
|
||||
} from 'tgui-core/components';
|
||||
|
||||
import type { Data } from './types';
|
||||
@@ -31,57 +30,7 @@ export const RIGSuitStatus = (props) => {
|
||||
} = data;
|
||||
|
||||
return (
|
||||
<Section
|
||||
title="Status"
|
||||
buttons={
|
||||
<Stack>
|
||||
<Stack.Item>
|
||||
<Button
|
||||
icon={sealing ? 'redo' : sealed ? 'power-off' : 'lock-open'}
|
||||
iconSpin={sealing}
|
||||
disabled={sealing}
|
||||
selected={sealed}
|
||||
onClick={() => act('toggle_seals')}
|
||||
>
|
||||
{'Suit ' +
|
||||
(sealing
|
||||
? 'seals working...'
|
||||
: sealed
|
||||
? 'is Active'
|
||||
: 'is Inactive')}
|
||||
</Button>
|
||||
</Stack.Item>
|
||||
<Stack.Item>
|
||||
<Button
|
||||
icon="robot"
|
||||
selected={aioverride}
|
||||
onClick={() => act('toggle_ai_control')}
|
||||
tooltip={'AI Control ' + (aioverride ? 'Enabled' : 'Disabled')}
|
||||
tooltipPosition="bottom-end"
|
||||
/>
|
||||
</Stack.Item>
|
||||
<Stack.Item>
|
||||
<Button
|
||||
icon="wind"
|
||||
selected={cooling}
|
||||
onClick={() => act('toggle_cooling')}
|
||||
tooltip={
|
||||
'Suit Cooling ' + (cooling ? 'is Active' : 'is Inactive')
|
||||
}
|
||||
tooltipPosition="bottom-end"
|
||||
/>
|
||||
</Stack.Item>
|
||||
<Stack.Item>
|
||||
<Button
|
||||
icon="lungs"
|
||||
onClick={() => act('tank_settings')}
|
||||
tooltip="Tank Settings"
|
||||
tooltipPosition="bottom-end"
|
||||
/>
|
||||
</Stack.Item>
|
||||
</Stack>
|
||||
}
|
||||
>
|
||||
<Section title="Status">
|
||||
<LabeledList>
|
||||
<LabeledList.Item label="Power Supply">
|
||||
<ProgressBar
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
import { useBackend } from 'tgui/backend';
|
||||
import { useBackend, useSharedState } from 'tgui/backend';
|
||||
import { Window } from 'tgui/layouts';
|
||||
import { Box } from 'tgui-core/components';
|
||||
|
||||
import { RIGSuitHardware } from './RIGSuitHardware';
|
||||
import { RIGSuitLoader } from './RIGSuitLoader';
|
||||
import { RIGSuitModules } from './RIGSuitModules';
|
||||
import { RIGSuitStatus } from './RIGSuitStatus';
|
||||
import type { Data } from './types';
|
||||
@@ -12,6 +13,12 @@ export const RIGSuit = (props) => {
|
||||
|
||||
const { interfacelock, malf, aicontrol, ai } = data;
|
||||
|
||||
const [showLoading, setShowLoading] = useSharedState('rigsuit-loading', true);
|
||||
|
||||
if (showLoading) {
|
||||
return <RIGSuitLoader onFinish={() => setShowLoading(false)} />;
|
||||
}
|
||||
|
||||
let override: React.JSX.Element | null = null;
|
||||
|
||||
if (interfacelock || malf) {
|
||||
|
||||
51
tgui/packages/tgui/styles/interfaces/RIGSuit.scss
Normal file
51
tgui/packages/tgui/styles/interfaces/RIGSuit.scss
Normal file
@@ -0,0 +1,51 @@
|
||||
@keyframes fade-in {
|
||||
from {
|
||||
opacity: 0.1;
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.RIGSuit__FadeIn {
|
||||
animation: 2s linear fade-in forwards;
|
||||
opacity: 0.1;
|
||||
}
|
||||
|
||||
.RIGSuit__Name {
|
||||
animation: 1.5s linear wipe-in-left both;
|
||||
animation-delay: 1.8s;
|
||||
}
|
||||
|
||||
@keyframes wipe-in-left {
|
||||
from {
|
||||
clip-path: inset(0 100% 0 0);
|
||||
}
|
||||
to {
|
||||
clip-path: inset(0 0 0 0);
|
||||
}
|
||||
}
|
||||
|
||||
.RIGSuit__Line {
|
||||
animation: 1s linear wipe-in-left both;
|
||||
animation-delay: 1s;
|
||||
}
|
||||
|
||||
.RIGSuit__Animation__WipeLeft {
|
||||
animation: 1s linear wipe-in-left both;
|
||||
}
|
||||
|
||||
.RIGSuit__Icon {
|
||||
cursor: pointer;
|
||||
transition:
|
||||
0.2s filter linear,
|
||||
0.2s -webkit-filter linear;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.RIGSuit__Icon > img {
|
||||
transition:
|
||||
0.2s filter linear,
|
||||
0.2s -webkit-filter linear;
|
||||
-webkit-user-drag: none;
|
||||
}
|
||||
@@ -41,6 +41,7 @@
|
||||
@include meta.load-css('./interfaces/Pda.scss');
|
||||
@include meta.load-css('./interfaces/ResearchConsole.scss');
|
||||
@include meta.load-css('./interfaces/ResleevingConsole.scss');
|
||||
@include meta.load-css('./interfaces/RIGSuit.scss');
|
||||
@include meta.load-css('./interfaces/Roulette.scss');
|
||||
@include meta.load-css('./interfaces/Safe.scss');
|
||||
@include meta.load-css('./interfaces/TachyonArray.scss');
|
||||
|
||||
Reference in New Issue
Block a user