mirror of
https://github.com/CHOMPStation2/CHOMPStation2.git
synced 2025-12-10 10:12:45 +00:00
486 lines
13 KiB
JavaScript
486 lines
13 KiB
JavaScript
import { round } from 'common/math';
|
|
import { Fragment } from 'inferno';
|
|
import { useBackend } from '../backend';
|
|
import { AnimatedNumber, Box, Button, Flex, Icon, LabeledList, ProgressBar, Section, Table, Tooltip } from '../components';
|
|
import { Window } from '../layouts';
|
|
|
|
const stats = [
|
|
['good', 'Alive'],
|
|
['average', 'Unconscious'],
|
|
['bad', 'DEAD'],
|
|
];
|
|
|
|
const abnormalities = [
|
|
[
|
|
'hasBorer',
|
|
'bad',
|
|
(occupant) =>
|
|
'Large growth detected in frontal lobe,' +
|
|
' possibly cancerous. Surgical removal is recommended.',
|
|
],
|
|
['hasVirus', 'bad', (occupant) => 'Viral pathogen detected in blood stream.'],
|
|
['blind', 'average', (occupant) => 'Cataracts detected.'],
|
|
[
|
|
'colourblind',
|
|
'average',
|
|
(occupant) => 'Photoreceptor abnormalities detected.',
|
|
],
|
|
['nearsighted', 'average', (occupant) => 'Retinal misalignment detected.'],
|
|
/* VOREStation Add */
|
|
[
|
|
'humanPrey',
|
|
'average',
|
|
(occupant) => {
|
|
return 'Foreign Humanoid(s) detected: ' + occupant.humanPrey;
|
|
},
|
|
],
|
|
[
|
|
'livingPrey',
|
|
'average',
|
|
(occupant) => {
|
|
return 'Foreign Creature(s) detected: ' + occupant.livingPrey;
|
|
},
|
|
],
|
|
[
|
|
'objectPrey',
|
|
'average',
|
|
(occupant) => {
|
|
return 'Foreign Object(s) detected: ' + occupant.objectPrey;
|
|
},
|
|
],
|
|
/* VOREStation Add End */
|
|
];
|
|
|
|
const damages = [
|
|
['Respiratory', 'oxyLoss'],
|
|
['Brain', 'brainLoss'],
|
|
['Toxin', 'toxLoss'],
|
|
['Radiation', 'radLoss'],
|
|
['Brute', 'bruteLoss'],
|
|
['Genetic', 'cloneLoss'],
|
|
['Burn', 'fireLoss'],
|
|
['Paralysis', 'paralysis'],
|
|
];
|
|
|
|
const damageRange = {
|
|
average: [0.25, 0.5],
|
|
bad: [0.5, Infinity],
|
|
};
|
|
|
|
const mapTwoByTwo = (a, c) => {
|
|
let result = [];
|
|
for (let i = 0; i < a.length; i += 2) {
|
|
result.push(c(a[i], a[i + 1], i));
|
|
}
|
|
return result;
|
|
};
|
|
|
|
const reduceOrganStatus = (A) => {
|
|
return A.length > 0
|
|
? A.reduce((a, s) =>
|
|
a === null ? (
|
|
s
|
|
) : (
|
|
<Fragment>
|
|
{a}
|
|
{!!s && <Box>{s}</Box>}
|
|
</Fragment>
|
|
)
|
|
)
|
|
: null;
|
|
};
|
|
|
|
const germStatus = (i) => {
|
|
if (i > 100) {
|
|
if (i < 300) {
|
|
return 'mild infection';
|
|
}
|
|
if (i < 400) {
|
|
return 'mild infection+';
|
|
}
|
|
if (i < 500) {
|
|
return 'mild infection++';
|
|
}
|
|
if (i < 700) {
|
|
return 'acute infection';
|
|
}
|
|
if (i < 800) {
|
|
return 'acute infection+';
|
|
}
|
|
if (i < 900) {
|
|
return 'acute infection++';
|
|
}
|
|
if (i >= 900) {
|
|
return 'septic';
|
|
}
|
|
}
|
|
|
|
return '';
|
|
};
|
|
|
|
export const BodyScanner = (props, context) => {
|
|
const { data } = useBackend(context);
|
|
const { occupied, occupant = {} } = data;
|
|
const body = occupied ? (
|
|
<BodyScannerMain occupant={occupant} />
|
|
) : (
|
|
<BodyScannerEmpty />
|
|
);
|
|
return (
|
|
<Window width={690} height={600} resizable>
|
|
<Window.Content scrollable className="Layout__content--flexColumn">
|
|
{body}
|
|
</Window.Content>
|
|
</Window>
|
|
);
|
|
};
|
|
|
|
const BodyScannerMain = (props) => {
|
|
const { occupant } = props;
|
|
return (
|
|
<Box>
|
|
<BodyScannerMainOccupant occupant={occupant} />
|
|
<BodyScannerMainReagents occupant={occupant} />
|
|
<BodyScannerMainAbnormalities occupant={occupant} />
|
|
<BodyScannerMainDamage occupant={occupant} />
|
|
<BodyScannerMainOrgansExternal organs={occupant.extOrgan} />
|
|
<BodyScannerMainOrgansInternal organs={occupant.intOrgan} />
|
|
</Box>
|
|
);
|
|
};
|
|
|
|
const BodyScannerMainOccupant = (props, context) => {
|
|
const { act, data } = useBackend(context);
|
|
const { occupant } = data;
|
|
return (
|
|
<Section
|
|
title="Occupant"
|
|
buttons={
|
|
<Fragment>
|
|
<Button icon="user-slash" onClick={() => act('ejectify')}>
|
|
Eject
|
|
</Button>
|
|
<Button icon="print" onClick={() => act('print_p')}>
|
|
Print Report
|
|
</Button>
|
|
</Fragment>
|
|
}>
|
|
<LabeledList>
|
|
<LabeledList.Item label="Name">{occupant.name}</LabeledList.Item>
|
|
<LabeledList.Item label="Health">
|
|
<ProgressBar
|
|
min="0"
|
|
max={occupant.maxHealth}
|
|
value={occupant.health / occupant.maxHealth}
|
|
ranges={{
|
|
good: [0.5, Infinity],
|
|
average: [0, 0.5],
|
|
bad: [-Infinity, 0],
|
|
}}
|
|
/>
|
|
</LabeledList.Item>
|
|
<LabeledList.Item label="Status" color={stats[occupant.stat][0]}>
|
|
{stats[occupant.stat][1]}
|
|
</LabeledList.Item>
|
|
<LabeledList.Item label="Temperature">
|
|
<AnimatedNumber value={round(occupant.bodyTempC, 0)} />
|
|
°C,
|
|
<AnimatedNumber value={round(occupant.bodyTempF, 0)} />
|
|
°F
|
|
</LabeledList.Item>
|
|
<LabeledList.Item label="Blood Volume">
|
|
<AnimatedNumber value={round(occupant.blood.volume, 0)} />{' '}
|
|
units (
|
|
<AnimatedNumber value={round(occupant.blood.percent, 0)} />
|
|
%)
|
|
</LabeledList.Item>
|
|
{/* VOREStation Add */}
|
|
<LabeledList.Item label="Weight">
|
|
{round(data.occupant.weight) +
|
|
'lbs, ' +
|
|
round(data.occupant.weight / 2.20463) +
|
|
'kgs'}
|
|
</LabeledList.Item>
|
|
{/* VOREStation Add End */}
|
|
</LabeledList>
|
|
</Section>
|
|
);
|
|
};
|
|
|
|
const BodyScannerMainReagents = (props) => {
|
|
const { occupant } = props;
|
|
|
|
return (
|
|
<Fragment>
|
|
<Section title="Blood Reagents">
|
|
{occupant.reagents ? (
|
|
<Table>
|
|
<Table.Row header>
|
|
<Table.Cell>Reagent</Table.Cell>
|
|
<Table.Cell textAlign="right">Amount</Table.Cell>
|
|
</Table.Row>
|
|
{occupant.reagents.map((reagent) => (
|
|
<Table.Row key={reagent.name}>
|
|
<Table.Cell>{reagent.name}</Table.Cell>
|
|
<Table.Cell textAlign="right">
|
|
{reagent.amount} Units{' '}
|
|
{reagent.overdose ? <Box color="bad">OVERDOSING</Box> : null}
|
|
</Table.Cell>
|
|
</Table.Row>
|
|
))}
|
|
</Table>
|
|
) : (
|
|
<Box color="good">No Blood Reagents Detected</Box>
|
|
)}
|
|
</Section>
|
|
<Section title="Stomach Reagents">
|
|
{occupant.ingested ? (
|
|
<Table>
|
|
<Table.Row header>
|
|
<Table.Cell>Reagent</Table.Cell>
|
|
<Table.Cell textAlign="right">Amount</Table.Cell>
|
|
</Table.Row>
|
|
{occupant.ingested.map((reagent) => (
|
|
<Table.Row key={reagent.name}>
|
|
<Table.Cell>{reagent.name}</Table.Cell>
|
|
<Table.Cell textAlign="right">
|
|
{reagent.amount} Units{' '}
|
|
{reagent.overdose ? <Box color="bad">OVERDOSING</Box> : null}
|
|
</Table.Cell>
|
|
</Table.Row>
|
|
))}
|
|
</Table>
|
|
) : (
|
|
<Box color="good">No Stomach Reagents Detected</Box>
|
|
)}
|
|
</Section>
|
|
</Fragment>
|
|
);
|
|
};
|
|
|
|
const BodyScannerMainAbnormalities = (props) => {
|
|
const { occupant } = props;
|
|
|
|
let hasAbnormalities =
|
|
occupant.hasBorer ||
|
|
occupant.blind ||
|
|
occupant.colourblind ||
|
|
occupant.nearsighted ||
|
|
occupant.hasVirus;
|
|
|
|
/* VOREStation Add */
|
|
hasAbnormalities =
|
|
hasAbnormalities ||
|
|
occupant.humanPrey ||
|
|
occupant.livingPrey ||
|
|
occupant.objectPrey;
|
|
/* VOREStation Add End */
|
|
|
|
if (!hasAbnormalities) {
|
|
return (
|
|
<Section title="Abnormalities">
|
|
<Box color="label">No abnormalities found.</Box>
|
|
</Section>
|
|
);
|
|
}
|
|
|
|
return (
|
|
<Section title="Abnormalities">
|
|
{abnormalities.map((a, i) => {
|
|
if (occupant[a[0]]) {
|
|
return (
|
|
<Box color={a[1]} bold={a[1] === 'bad'}>
|
|
{a[2](occupant)}
|
|
</Box>
|
|
);
|
|
}
|
|
})}
|
|
</Section>
|
|
);
|
|
};
|
|
|
|
const BodyScannerMainDamage = (props) => {
|
|
const { occupant } = props;
|
|
return (
|
|
<Section title="Damage">
|
|
<Table>
|
|
{mapTwoByTwo(damages, (d1, d2, i) => (
|
|
<Fragment>
|
|
<Table.Row color="label">
|
|
<Table.Cell>{d1[0]}:</Table.Cell>
|
|
<Table.Cell>{!!d2 && d2[0] + ':'}</Table.Cell>
|
|
</Table.Row>
|
|
<Table.Row>
|
|
<Table.Cell>
|
|
<BodyScannerMainDamageBar
|
|
value={occupant[d1[1]]}
|
|
marginBottom={i < damages.length - 2}
|
|
/>
|
|
</Table.Cell>
|
|
<Table.Cell>
|
|
{!!d2 && <BodyScannerMainDamageBar value={occupant[d2[1]]} />}
|
|
</Table.Cell>
|
|
</Table.Row>
|
|
</Fragment>
|
|
))}
|
|
</Table>
|
|
</Section>
|
|
);
|
|
};
|
|
|
|
const BodyScannerMainDamageBar = (props) => {
|
|
return (
|
|
<ProgressBar
|
|
min="0"
|
|
max="100"
|
|
value={props.value / 100}
|
|
mt="0.5rem"
|
|
mb={!!props.marginBottom && '0.5rem'}
|
|
ranges={damageRange}>
|
|
{round(props.value, 0)}
|
|
</ProgressBar>
|
|
);
|
|
};
|
|
|
|
const BodyScannerMainOrgansExternal = (props) => {
|
|
if (props.organs.length === 0) {
|
|
return (
|
|
<Section title="External Organs">
|
|
<Box color="label">N/A</Box>
|
|
</Section>
|
|
);
|
|
}
|
|
|
|
return (
|
|
<Section title="External Organs">
|
|
<Table>
|
|
<Table.Row header>
|
|
<Table.Cell>Name</Table.Cell>
|
|
<Table.Cell textAlign="center">Damage</Table.Cell>
|
|
<Table.Cell textAlign="right">Injuries</Table.Cell>
|
|
</Table.Row>
|
|
{props.organs.map((o, i) => (
|
|
<Table.Row key={i} textTransform="capitalize">
|
|
<Table.Cell width="33%">{o.name}</Table.Cell>
|
|
<Table.Cell textAlign="center" q>
|
|
<ProgressBar
|
|
min="0"
|
|
max={o.maxHealth}
|
|
mt={i > 0 && '0.5rem'}
|
|
value={o.totalLoss / 100}
|
|
ranges={damageRange}>
|
|
<Box float="left" inline>
|
|
{!!o.bruteLoss && (
|
|
<Box inline position="relative">
|
|
<Icon name="bone" />
|
|
{round(o.bruteLoss, 0)}
|
|
<Tooltip position="top" content="Brute damage" />
|
|
</Box>
|
|
)}
|
|
{!!o.fireLoss && (
|
|
<Box inline position="relative">
|
|
<Icon name="fire" />
|
|
{round(o.fireLoss, 0)}
|
|
<Tooltip position="top" content="Burn damage" />
|
|
</Box>
|
|
)}
|
|
</Box>
|
|
<Box inline>{round(o.totalLoss, 0)}</Box>
|
|
</ProgressBar>
|
|
</Table.Cell>
|
|
<Table.Cell textAlign="right" width="33%">
|
|
<Box color="average" inline>
|
|
{reduceOrganStatus([
|
|
o.internalBleeding && 'Internal bleeding',
|
|
!!o.status.bleeding && 'External bleeding',
|
|
o.lungRuptured && 'Ruptured lung',
|
|
o.destroyed && 'Destroyed',
|
|
!!o.status.broken && o.status.broken,
|
|
germStatus(o.germ_level),
|
|
!!o.open && 'Open incision',
|
|
])}
|
|
</Box>
|
|
<Box inline>
|
|
{reduceOrganStatus([
|
|
!!o.status.splinted && 'Splinted',
|
|
!!o.status.robotic && 'Robotic',
|
|
!!o.status.dead && <Box color="bad">DEAD</Box>,
|
|
])}
|
|
{reduceOrganStatus(
|
|
o.implants.map((s) => (s.known ? s.name : 'Unknown object'))
|
|
)}
|
|
</Box>
|
|
</Table.Cell>
|
|
</Table.Row>
|
|
))}
|
|
</Table>
|
|
</Section>
|
|
);
|
|
};
|
|
|
|
const BodyScannerMainOrgansInternal = (props) => {
|
|
if (props.organs.length === 0) {
|
|
return (
|
|
<Section title="Internal Organs">
|
|
<Box color="label">N/A</Box>
|
|
</Section>
|
|
);
|
|
}
|
|
|
|
return (
|
|
<Section title="Internal Organs">
|
|
<Table>
|
|
<Table.Row header>
|
|
<Table.Cell>Name</Table.Cell>
|
|
<Table.Cell textAlign="center">Damage</Table.Cell>
|
|
<Table.Cell textAlign="right">Injuries</Table.Cell>
|
|
</Table.Row>
|
|
{props.organs.map((o, i) => (
|
|
<Table.Row key={i} textTransform="capitalize">
|
|
<Table.Cell width="33%">{o.name}</Table.Cell>
|
|
<Table.Cell textAlign="center">
|
|
<ProgressBar
|
|
min="0"
|
|
max={o.maxHealth}
|
|
value={o.damage / 100}
|
|
mt={i > 0 && '0.5rem'}
|
|
ranges={damageRange}>
|
|
{round(o.damage, 0)}
|
|
</ProgressBar>
|
|
</Table.Cell>
|
|
<Table.Cell textAlign="right" width="33%">
|
|
<Box color="average" inline>
|
|
{reduceOrganStatus([
|
|
germStatus(o.germ_level),
|
|
!!o.inflamed && 'Appendicitis detected.',
|
|
])}
|
|
</Box>
|
|
<Box inline>
|
|
{reduceOrganStatus([
|
|
o.robotic === 1 && 'Robotic',
|
|
o.robotic === 2 && 'Assisted',
|
|
!!o.dead && <Box color="bad">DEAD</Box>,
|
|
])}
|
|
</Box>
|
|
</Table.Cell>
|
|
</Table.Row>
|
|
))}
|
|
</Table>
|
|
</Section>
|
|
);
|
|
};
|
|
|
|
const BodyScannerEmpty = () => {
|
|
return (
|
|
<Section textAlign="center" flexGrow="1">
|
|
<Flex height="100%">
|
|
<Flex.Item grow="1" align="center" color="label">
|
|
<Icon name="user-slash" mb="0.5rem" size="5" />
|
|
<br />
|
|
No occupant detected.
|
|
</Flex.Item>
|
|
</Flex>
|
|
</Section>
|
|
);
|
|
};
|