mirror of
https://github.com/Bubberstation/Bubberstation.git
synced 2026-08-27 07:08:00 +01:00
ChemMaster and CondiMaster refactored, prints items over time (#75849)
 ## About The Pull Request Chem master has been using some hard coded styles for containers, pills and patches. Some people didn't bother to update styles, and they stayed outdated. Now it uses just a list of reagent containers instead, which makes it easy to add new containers - just one line of code. Also changed its sprite for better animations, emissive overlay, and to display buffer reagents on the sprite itself. Now it plays printing animation when you actually print containers and servos have a purpose now - they increase the amount of containers printed at once by up to 100% * part_tier. Pills and patches are always printed at double amount. And fixes #59734 ## Why It's Good For The Game Makes chem master easier to manage in code and easier to use in-game. Printing delay is added for consistency with other fabrication machines (like lathes), to give a purpose for the servo motor used in construction, for visuals (to show the printing animation only when something is being printed, and not just when you move stuff in and out of buffer as before) and to prevent people from spamming the world with unneeded objects with 0 second delay. ## Changelog 🆑 qol: Chemmaster UI tweaked fix: Fixed chem master showing wrong data during reagent analysis fix: Fixed chem master not working with fermented drinks image: Chemmaster resprited, now has an indicator for buffer reagents refactor: Refactored chemmaster code, it now uses reagent containers instead of styles balance: Chemmaster now uses servos and has printing animation of 0.75 second duration. Outputs 1 container or up to 2 pills/patches per cycle by default. Can be upgraded to output up to 4 and 8 with t4 servos. /🆑
This commit is contained in:
@@ -0,0 +1,15 @@
|
||||
///Icons for containers printed in ChemMaster
|
||||
/datum/asset/spritesheet/chemmaster
|
||||
name = "chemmaster"
|
||||
|
||||
/datum/asset/spritesheet/chemmaster/create_spritesheets()
|
||||
var/list/ids = list()
|
||||
for(var/category in GLOB.chem_master_containers)
|
||||
for(var/obj/item/reagent_containers/container as anything in GLOB.chem_master_containers[category])
|
||||
var/icon_file = initial(container.icon)
|
||||
var/icon_state = initial(container.icon_state)
|
||||
var/id = sanitize_css_class_name("[container]")
|
||||
if(id in ids) // exclude duplicate containers
|
||||
continue
|
||||
ids += id
|
||||
Insert(id, icon_file, icon_state)
|
||||
File diff suppressed because it is too large
Load Diff
@@ -340,6 +340,19 @@
|
||||
icon_state = "condi_chocolate"
|
||||
list_reagents = list(/datum/reagent/consumable/choccyshake = 10)
|
||||
|
||||
|
||||
/obj/item/reagent_containers/condiment/hotsauce
|
||||
name = "hotsauce bottle"
|
||||
desc= "You can almost TASTE the stomach ulcers!"
|
||||
icon_state = "hotsauce"
|
||||
list_reagents = list(/datum/reagent/consumable/capsaicin = 50)
|
||||
|
||||
/obj/item/reagent_containers/condiment/coldsauce
|
||||
name = "coldsauce bottle"
|
||||
desc= "Leaves the tongue numb from its passage."
|
||||
icon_state = "coldsauce"
|
||||
list_reagents = list(/datum/reagent/consumable/frostoil = 50)
|
||||
|
||||
//Food packs. To easily apply deadly toxi... delicious sauces to your food!
|
||||
|
||||
/obj/item/reagent_containers/condiment/pack
|
||||
|
||||
@@ -44,3 +44,46 @@
|
||||
desc = "Helps with brute and burn injuries. Slightly toxic."
|
||||
list_reagents = list(/datum/reagent/medicine/c2/synthflesh = 20)
|
||||
icon_state = "bandaid_both"
|
||||
|
||||
// Patch styles for chem master
|
||||
|
||||
/obj/item/reagent_containers/pill/patch/style
|
||||
icon_state = "bandaid_blank"
|
||||
/obj/item/reagent_containers/pill/patch/style/brute
|
||||
icon_state = "bandaid_brute_2"
|
||||
/obj/item/reagent_containers/pill/patch/style/burn
|
||||
icon_state = "bandaid_burn_2"
|
||||
/obj/item/reagent_containers/pill/patch/style/bruteburn
|
||||
icon_state = "bandaid_both"
|
||||
/obj/item/reagent_containers/pill/patch/style/toxin
|
||||
icon_state = "bandaid_toxin_2"
|
||||
/obj/item/reagent_containers/pill/patch/style/oxygen
|
||||
icon_state = "bandaid_suffocation_2"
|
||||
/obj/item/reagent_containers/pill/patch/style/omni
|
||||
icon_state = "bandaid_mix"
|
||||
/obj/item/reagent_containers/pill/patch/style/bruteplus
|
||||
icon_state = "bandaid_brute"
|
||||
/obj/item/reagent_containers/pill/patch/style/burnplus
|
||||
icon_state = "bandaid_burn"
|
||||
/obj/item/reagent_containers/pill/patch/style/toxinplus
|
||||
icon_state = "bandaid_toxin"
|
||||
/obj/item/reagent_containers/pill/patch/style/oxygenplus
|
||||
icon_state = "bandaid_suffocation"
|
||||
/obj/item/reagent_containers/pill/patch/style/monkey
|
||||
icon_state = "bandaid_monke"
|
||||
/obj/item/reagent_containers/pill/patch/style/clown
|
||||
icon_state = "bandaid_clown"
|
||||
/obj/item/reagent_containers/pill/patch/style/one
|
||||
icon_state = "bandaid_1"
|
||||
/obj/item/reagent_containers/pill/patch/style/two
|
||||
icon_state = "bandaid_2"
|
||||
/obj/item/reagent_containers/pill/patch/style/three
|
||||
icon_state = "bandaid_3"
|
||||
/obj/item/reagent_containers/pill/patch/style/four
|
||||
icon_state = "bandaid_4"
|
||||
/obj/item/reagent_containers/pill/patch/style/exclamation
|
||||
icon_state = "bandaid_exclaimationpoint"
|
||||
/obj/item/reagent_containers/pill/patch/style/question
|
||||
icon_state = "bandaid_questionmark"
|
||||
/obj/item/reagent_containers/pill/patch/style/colonthree
|
||||
icon_state = "bandaid_colonthree"
|
||||
|
||||
@@ -302,3 +302,54 @@
|
||||
icon_state = "pill8"
|
||||
list_reagents = list(/datum/reagent/iron = 30)
|
||||
rename_with_volume = TRUE
|
||||
|
||||
// Pill styles for chem master
|
||||
|
||||
/obj/item/reagent_containers/pill/style
|
||||
icon_state = "pill0"
|
||||
/obj/item/reagent_containers/pill/style/purplered
|
||||
icon_state = "pill1"
|
||||
/obj/item/reagent_containers/pill/style/greenwhite
|
||||
icon_state = "pill2"
|
||||
/obj/item/reagent_containers/pill/style/teal
|
||||
icon_state = "pill3"
|
||||
/obj/item/reagent_containers/pill/style/red
|
||||
icon_state = "pill4"
|
||||
/obj/item/reagent_containers/pill/style/redwhite
|
||||
icon_state = "pill5"
|
||||
/obj/item/reagent_containers/pill/style/tealbrown
|
||||
icon_state = "pill6"
|
||||
/obj/item/reagent_containers/pill/style/yellowflat
|
||||
icon_state = "pill7"
|
||||
/obj/item/reagent_containers/pill/style/tealflat
|
||||
icon_state = "pill8"
|
||||
/obj/item/reagent_containers/pill/style/whiteflat
|
||||
icon_state = "pill9"
|
||||
/obj/item/reagent_containers/pill/style/purpleflat
|
||||
icon_state = "pill10"
|
||||
/obj/item/reagent_containers/pill/style/limelat
|
||||
icon_state = "pill11"
|
||||
/obj/item/reagent_containers/pill/style/redflat
|
||||
icon_state = "pill12"
|
||||
/obj/item/reagent_containers/pill/style/greenpurpleflat
|
||||
icon_state = "pill13"
|
||||
/obj/item/reagent_containers/pill/style/yellowpurpleflat
|
||||
icon_state = "pill14"
|
||||
/obj/item/reagent_containers/pill/style/redyellowflat
|
||||
icon_state = "pill15"
|
||||
/obj/item/reagent_containers/pill/style/bluetealflat
|
||||
icon_state = "pill16"
|
||||
/obj/item/reagent_containers/pill/style/greenlimeflat
|
||||
icon_state = "pill17"
|
||||
/obj/item/reagent_containers/pill/style/white
|
||||
icon_state = "pill18"
|
||||
/obj/item/reagent_containers/pill/style/whitered
|
||||
icon_state = "pill19"
|
||||
/obj/item/reagent_containers/pill/style/purpleyellow
|
||||
icon_state = "pill20"
|
||||
/obj/item/reagent_containers/pill/style/blackwhite
|
||||
icon_state = "pill21"
|
||||
/obj/item/reagent_containers/pill/style/limewhite
|
||||
icon_state = "pill22"
|
||||
/obj/item/reagent_containers/pill/style/happy
|
||||
icon_state = "pill_happy"
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 67 KiB After Width: | Height: | Size: 63 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 7.6 KiB After Width: | Height: | Size: 8.1 KiB |
@@ -2778,6 +2778,7 @@
|
||||
#include "code\modules\asset_cache\assets\bibles.dm"
|
||||
#include "code\modules\asset_cache\assets\body_zones.dm"
|
||||
#include "code\modules\asset_cache\assets\chat.dm"
|
||||
#include "code\modules\asset_cache\assets\chemmaster.dm"
|
||||
#include "code\modules\asset_cache\assets\circuits.dm"
|
||||
#include "code\modules\asset_cache\assets\common.dm"
|
||||
#include "code\modules\asset_cache\assets\condiments.dm"
|
||||
|
||||
@@ -1,438 +0,0 @@
|
||||
import { useBackend, useSharedState } from '../backend';
|
||||
import { AnimatedNumber, Box, Button, ColorBox, LabeledList, NumberInput, Section, Table } from '../components';
|
||||
import { Window } from '../layouts';
|
||||
|
||||
export const ChemMaster = (props, context) => {
|
||||
const { data } = useBackend(context);
|
||||
const { screen } = data;
|
||||
return (
|
||||
<Window width={465} height={550}>
|
||||
<Window.Content scrollable>
|
||||
{(screen === 'analyze' && <AnalysisResults />) || <ChemMasterContent />}
|
||||
</Window.Content>
|
||||
</Window>
|
||||
);
|
||||
};
|
||||
|
||||
const ChemMasterContent = (props, context) => {
|
||||
const { act, data } = useBackend(context);
|
||||
const {
|
||||
screen,
|
||||
beakerContents = [],
|
||||
bufferContents = [],
|
||||
beakerCurrentVolume,
|
||||
beakerMaxVolume,
|
||||
isBeakerLoaded,
|
||||
isPillBottleLoaded,
|
||||
pillBottleCurrentAmount,
|
||||
pillBottleMaxAmount,
|
||||
} = data;
|
||||
if (screen === 'analyze') {
|
||||
return <AnalysisResults />;
|
||||
}
|
||||
return (
|
||||
<>
|
||||
<Section
|
||||
title="Beaker"
|
||||
buttons={
|
||||
!!data.isBeakerLoaded && (
|
||||
<>
|
||||
<Box inline color="label" mr={2}>
|
||||
<AnimatedNumber value={beakerCurrentVolume} initial={0} />
|
||||
{` / ${beakerMaxVolume} units`}
|
||||
</Box>
|
||||
<Button
|
||||
icon="eject"
|
||||
content="Eject"
|
||||
onClick={() => act('eject')}
|
||||
/>
|
||||
</>
|
||||
)
|
||||
}>
|
||||
{!isBeakerLoaded && (
|
||||
<Box color="label" mt="3px" mb="5px">
|
||||
No beaker loaded.
|
||||
</Box>
|
||||
)}
|
||||
{!!isBeakerLoaded && beakerContents.length === 0 && (
|
||||
<Box color="label" mt="3px" mb="5px">
|
||||
Beaker is empty.
|
||||
</Box>
|
||||
)}
|
||||
<ChemicalBuffer>
|
||||
{beakerContents.map((chemical) => (
|
||||
<ChemicalBufferEntry
|
||||
key={chemical.id}
|
||||
chemical={chemical}
|
||||
transferTo="buffer"
|
||||
/>
|
||||
))}
|
||||
</ChemicalBuffer>
|
||||
</Section>
|
||||
<Section
|
||||
title="Buffer"
|
||||
buttons={
|
||||
<>
|
||||
<Box inline color="label" mr={1}>
|
||||
Mode:
|
||||
</Box>
|
||||
<Button
|
||||
color={data.mode ? 'good' : 'bad'}
|
||||
icon={data.mode ? 'exchange-alt' : 'times'}
|
||||
content={data.mode ? 'Transfer' : 'Destroy'}
|
||||
onClick={() => act('toggleMode')}
|
||||
/>
|
||||
</>
|
||||
}>
|
||||
{bufferContents.length === 0 && (
|
||||
<Box color="label" mt="3px" mb="5px">
|
||||
Buffer is empty.
|
||||
</Box>
|
||||
)}
|
||||
<ChemicalBuffer>
|
||||
{bufferContents.map((chemical) => (
|
||||
<ChemicalBufferEntry
|
||||
key={chemical.id}
|
||||
chemical={chemical}
|
||||
transferTo="beaker"
|
||||
/>
|
||||
))}
|
||||
</ChemicalBuffer>
|
||||
</Section>
|
||||
<Section title="Packaging">
|
||||
<PackagingControls />
|
||||
</Section>
|
||||
{!!isPillBottleLoaded && (
|
||||
<Section
|
||||
title="Pill Bottle"
|
||||
buttons={
|
||||
<>
|
||||
<Box inline color="label" mr={2}>
|
||||
{pillBottleCurrentAmount} / {pillBottleMaxAmount} pills
|
||||
</Box>
|
||||
<Button
|
||||
icon="eject"
|
||||
content="Eject"
|
||||
onClick={() => act('ejectPillBottle')}
|
||||
/>
|
||||
</>
|
||||
}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
const ChemicalBuffer = Table;
|
||||
|
||||
const ChemicalBufferEntry = (props, context) => {
|
||||
const { act } = useBackend(context);
|
||||
const { chemical, transferTo } = props;
|
||||
return (
|
||||
<Table.Row key={chemical.id}>
|
||||
<Table.Cell color="label">
|
||||
<AnimatedNumber value={chemical.volume} initial={0} />
|
||||
{` units of ${chemical.name}`}
|
||||
</Table.Cell>
|
||||
<Table.Cell collapsing>
|
||||
<Button
|
||||
content="1"
|
||||
onClick={() =>
|
||||
act('transfer', {
|
||||
id: chemical.id,
|
||||
amount: 1,
|
||||
to: transferTo,
|
||||
})
|
||||
}
|
||||
/>
|
||||
<Button
|
||||
content="5"
|
||||
onClick={() =>
|
||||
act('transfer', {
|
||||
id: chemical.id,
|
||||
amount: 5,
|
||||
to: transferTo,
|
||||
})
|
||||
}
|
||||
/>
|
||||
<Button
|
||||
content="10"
|
||||
onClick={() =>
|
||||
act('transfer', {
|
||||
id: chemical.id,
|
||||
amount: 10,
|
||||
to: transferTo,
|
||||
})
|
||||
}
|
||||
/>
|
||||
<Button
|
||||
content="All"
|
||||
onClick={() =>
|
||||
act('transfer', {
|
||||
id: chemical.id,
|
||||
amount: 1000,
|
||||
to: transferTo,
|
||||
})
|
||||
}
|
||||
/>
|
||||
<Button
|
||||
icon="ellipsis-h"
|
||||
title="Custom amount"
|
||||
onClick={() =>
|
||||
act('transfer', {
|
||||
id: chemical.id,
|
||||
amount: -1,
|
||||
to: transferTo,
|
||||
})
|
||||
}
|
||||
/>
|
||||
<Button
|
||||
icon="question"
|
||||
title="Analyze"
|
||||
onClick={() =>
|
||||
act('analyze', {
|
||||
id: chemical.id,
|
||||
})
|
||||
}
|
||||
/>
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
);
|
||||
};
|
||||
|
||||
const PackagingControlsItem = (props) => {
|
||||
const { label, amountUnit, amount, onChangeAmount, onCreate, sideNote } =
|
||||
props;
|
||||
return (
|
||||
<LabeledList.Item label={label}>
|
||||
<NumberInput
|
||||
width="84px"
|
||||
unit={amountUnit}
|
||||
step={1}
|
||||
stepPixelSize={15}
|
||||
value={amount}
|
||||
minValue={1}
|
||||
maxValue={10}
|
||||
onChange={onChangeAmount}
|
||||
/>
|
||||
<Button ml={1} content="Create" onClick={onCreate} />
|
||||
<Box inline ml={1} color="label">
|
||||
{sideNote}
|
||||
</Box>
|
||||
</LabeledList.Item>
|
||||
);
|
||||
};
|
||||
|
||||
const PackagingControls = (props, context) => {
|
||||
const { act, data } = useBackend(context);
|
||||
const [pillAmount, setPillAmount] = useSharedState(context, 'pillAmount', 1);
|
||||
const [patchAmount, setPatchAmount] = useSharedState(
|
||||
context,
|
||||
'patchAmount',
|
||||
1
|
||||
);
|
||||
const [bottleAmount, setBottleAmount] = useSharedState(
|
||||
context,
|
||||
'bottleAmount',
|
||||
1
|
||||
);
|
||||
const [packAmount, setPackAmount] = useSharedState(context, 'packAmount', 1);
|
||||
const {
|
||||
condi,
|
||||
chosenPillStyle,
|
||||
chosenCondiStyle,
|
||||
autoCondiStyle,
|
||||
pillStyles = [],
|
||||
condiStyles = [],
|
||||
patch_style,
|
||||
patch_styles = [],
|
||||
} = data;
|
||||
const autoCondiStyleChosen = autoCondiStyle === chosenCondiStyle;
|
||||
return (
|
||||
<LabeledList>
|
||||
{!condi && (
|
||||
<LabeledList.Item label="Pill type">
|
||||
{pillStyles.map((pill) => (
|
||||
<Button
|
||||
key={pill.id}
|
||||
width="30px"
|
||||
selected={pill.id === chosenPillStyle}
|
||||
textAlign="center"
|
||||
color="transparent"
|
||||
onClick={() => act('pillStyle', { id: pill.id })}>
|
||||
<Box mx={-1} className={pill.className} />
|
||||
</Button>
|
||||
))}
|
||||
</LabeledList.Item>
|
||||
)}
|
||||
{!condi && (
|
||||
<PackagingControlsItem
|
||||
label="Pills"
|
||||
amount={pillAmount}
|
||||
amountUnit="pills"
|
||||
sideNote="max 50u"
|
||||
onChangeAmount={(e, value) => setPillAmount(value)}
|
||||
onCreate={() =>
|
||||
act('create', {
|
||||
type: 'pill',
|
||||
amount: pillAmount,
|
||||
volume: 'auto',
|
||||
})
|
||||
}
|
||||
/>
|
||||
)}
|
||||
{!condi && (
|
||||
<LabeledList.Item label="Patch type">
|
||||
{patch_styles.map((patch) => (
|
||||
<Button
|
||||
key={patch.style}
|
||||
selected={patch.style === patch_style}
|
||||
textAlign="center"
|
||||
color="transparent"
|
||||
onClick={() =>
|
||||
act('change_patch_style', { patch_style: patch.style })
|
||||
}>
|
||||
<Box mb={0} mt={1} className={patch.class_name} />
|
||||
</Button>
|
||||
))}
|
||||
</LabeledList.Item>
|
||||
)}
|
||||
{!condi && (
|
||||
<PackagingControlsItem
|
||||
label="Patches"
|
||||
amount={patchAmount}
|
||||
amountUnit="patches"
|
||||
sideNote="max 40u"
|
||||
onChangeAmount={(e, value) => setPatchAmount(value)}
|
||||
onCreate={() =>
|
||||
act('create', {
|
||||
type: 'patch',
|
||||
amount: patchAmount,
|
||||
volume: 'auto',
|
||||
})
|
||||
}
|
||||
/>
|
||||
)}
|
||||
{!condi && (
|
||||
<PackagingControlsItem
|
||||
label="Tubes"
|
||||
amount={bottleAmount}
|
||||
amountUnit="tubes"
|
||||
sideNote="max 30u"
|
||||
onChangeAmount={(e, value) => setBottleAmount(value)}
|
||||
onCreate={() =>
|
||||
act('create', {
|
||||
type: 'bottle',
|
||||
amount: bottleAmount,
|
||||
volume: 'auto',
|
||||
})
|
||||
}
|
||||
/>
|
||||
)}
|
||||
{!!condi && (
|
||||
<LabeledList.Item label="Bottle type">
|
||||
<Button.Checkbox
|
||||
onClick={() =>
|
||||
act('condiStyle', {
|
||||
id: autoCondiStyleChosen ? condiStyles[0].id : autoCondiStyle,
|
||||
})
|
||||
}
|
||||
checked={autoCondiStyleChosen}
|
||||
disabled={!condiStyles.length}>
|
||||
Guess from contents
|
||||
</Button.Checkbox>
|
||||
</LabeledList.Item>
|
||||
)}
|
||||
{!!condi && !autoCondiStyleChosen && (
|
||||
<LabeledList.Item label="">
|
||||
{condiStyles.map((style) => (
|
||||
<Button
|
||||
key={style.id}
|
||||
width="30px"
|
||||
selected={style.id === chosenCondiStyle}
|
||||
textAlign="center"
|
||||
color="transparent"
|
||||
title={style.title}
|
||||
onClick={() => act('condiStyle', { id: style.id })}>
|
||||
<Box mx={-1} className={style.className} />
|
||||
</Button>
|
||||
))}
|
||||
</LabeledList.Item>
|
||||
)}
|
||||
{!!condi && (
|
||||
<PackagingControlsItem
|
||||
label="Bottles"
|
||||
amount={bottleAmount}
|
||||
amountUnit="bottles"
|
||||
sideNote="max 50u"
|
||||
onChangeAmount={(e, value) => setBottleAmount(value)}
|
||||
onCreate={() =>
|
||||
act('create', {
|
||||
type: 'condimentBottle',
|
||||
amount: bottleAmount,
|
||||
volume: 'auto',
|
||||
})
|
||||
}
|
||||
/>
|
||||
)}
|
||||
{!!condi && (
|
||||
<PackagingControlsItem
|
||||
label="Packs"
|
||||
amount={packAmount}
|
||||
amountUnit="packs"
|
||||
sideNote="max 10u"
|
||||
onChangeAmount={(e, value) => setPackAmount(value)}
|
||||
onCreate={() =>
|
||||
act('create', {
|
||||
type: 'condimentPack',
|
||||
amount: packAmount,
|
||||
volume: 'auto',
|
||||
})
|
||||
}
|
||||
/>
|
||||
)}
|
||||
</LabeledList>
|
||||
);
|
||||
};
|
||||
|
||||
const AnalysisResults = (props, context) => {
|
||||
const { act, data } = useBackend(context);
|
||||
const { analyzeVars } = data;
|
||||
return (
|
||||
<Section
|
||||
title="Analysis Results"
|
||||
buttons={
|
||||
<Button
|
||||
icon="arrow-left"
|
||||
content="Back"
|
||||
onClick={() =>
|
||||
act('goScreen', {
|
||||
screen: 'home',
|
||||
})
|
||||
}
|
||||
/>
|
||||
}>
|
||||
<LabeledList>
|
||||
<LabeledList.Item label="Name">{analyzeVars.name}</LabeledList.Item>
|
||||
<LabeledList.Item label="State">{analyzeVars.state}</LabeledList.Item>
|
||||
<LabeledList.Item label="pH">{analyzeVars.ph}</LabeledList.Item>
|
||||
<LabeledList.Item label="Color">
|
||||
<ColorBox color={analyzeVars.color} mr={1} />
|
||||
{analyzeVars.color}
|
||||
</LabeledList.Item>
|
||||
<LabeledList.Item label="Description">
|
||||
{analyzeVars.description}
|
||||
</LabeledList.Item>
|
||||
<LabeledList.Item label="Metabolization Rate">
|
||||
{analyzeVars.metaRate} u/minute
|
||||
</LabeledList.Item>
|
||||
<LabeledList.Item label="Overdose Threshold">
|
||||
{analyzeVars.overD}
|
||||
</LabeledList.Item>
|
||||
<LabeledList.Item label="Addiction Threshold">
|
||||
{analyzeVars.addicD}
|
||||
</LabeledList.Item>
|
||||
</LabeledList>
|
||||
</Section>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,453 @@
|
||||
import { BooleanLike, classes } from 'common/react';
|
||||
import { capitalize } from 'common/string';
|
||||
import { useBackend, useLocalState } from '../backend';
|
||||
import { AnimatedNumber, Box, Button, Section, Table, NumberInput, Tooltip, LabeledList, ColorBox, ProgressBar, Stack, Divider } from '../components';
|
||||
import { Window } from '../layouts';
|
||||
|
||||
type Data = {
|
||||
reagentAnalysisMode: BooleanLike;
|
||||
analysisData: Analysis;
|
||||
isPrinting: BooleanLike;
|
||||
printingProgress: number;
|
||||
printingTotal: number;
|
||||
transferMode: BooleanLike;
|
||||
hasBeaker: BooleanLike;
|
||||
beakerCurrentVolume: number;
|
||||
beakerMaxVolume: number;
|
||||
beakerContents: Reagent[];
|
||||
bufferContents: Reagent[];
|
||||
bufferCurrentVolume: number;
|
||||
bufferMaxVolume: number;
|
||||
categories: Category[];
|
||||
selectedContainerRef: string;
|
||||
selectedContainerVolume: number;
|
||||
hasContainerSuggestion: BooleanLike;
|
||||
doSuggestContainer: BooleanLike;
|
||||
suggestedContainer: string;
|
||||
};
|
||||
|
||||
type Analysis = {
|
||||
name: string;
|
||||
state: string;
|
||||
pH: number;
|
||||
color: string;
|
||||
description: string;
|
||||
purity: number;
|
||||
metaRate: number;
|
||||
overdose: number;
|
||||
addictionTypes: string[];
|
||||
};
|
||||
|
||||
type Category = {
|
||||
name: string;
|
||||
containers: Container[];
|
||||
};
|
||||
|
||||
type Reagent = {
|
||||
ref: string;
|
||||
name: string;
|
||||
volume: number;
|
||||
};
|
||||
|
||||
type Container = {
|
||||
icon: string;
|
||||
ref: string;
|
||||
name: string;
|
||||
volume: number;
|
||||
};
|
||||
|
||||
export const ChemMaster = (props, context) => {
|
||||
const { data } = useBackend<Data>(context);
|
||||
const { reagentAnalysisMode } = data;
|
||||
return (
|
||||
<Window width={400} height={620}>
|
||||
<Window.Content scrollable>
|
||||
{reagentAnalysisMode ? <AnalysisResults /> : <ChemMasterContent />}
|
||||
</Window.Content>
|
||||
</Window>
|
||||
);
|
||||
};
|
||||
|
||||
const ChemMasterContent = (props, context) => {
|
||||
const { act, data } = useBackend<Data>(context);
|
||||
const {
|
||||
isPrinting,
|
||||
printingProgress,
|
||||
printingTotal,
|
||||
transferMode,
|
||||
hasBeaker,
|
||||
beakerCurrentVolume,
|
||||
beakerMaxVolume,
|
||||
beakerContents,
|
||||
bufferContents,
|
||||
bufferCurrentVolume,
|
||||
bufferMaxVolume,
|
||||
categories,
|
||||
selectedContainerVolume,
|
||||
hasContainerSuggestion,
|
||||
doSuggestContainer,
|
||||
suggestedContainer,
|
||||
} = data;
|
||||
|
||||
const [itemCount, setItemCount] = useLocalState(context, 'itemCount', 1);
|
||||
|
||||
return (
|
||||
<Box>
|
||||
<Section
|
||||
title="Beaker"
|
||||
buttons={
|
||||
!!hasBeaker && (
|
||||
<Box>
|
||||
<Box inline color="label" mr={2}>
|
||||
<AnimatedNumber value={beakerCurrentVolume} initial={0} />
|
||||
{` / ${beakerMaxVolume} units`}
|
||||
</Box>
|
||||
<Button
|
||||
icon="eject"
|
||||
content="Eject"
|
||||
onClick={() => act('eject')}
|
||||
/>
|
||||
</Box>
|
||||
)
|
||||
}>
|
||||
{!hasBeaker && (
|
||||
<Box color="label" my={'4px'}>
|
||||
No beaker loaded.
|
||||
</Box>
|
||||
)}
|
||||
{!!hasBeaker && beakerCurrentVolume === 0 && (
|
||||
<Box color="label" my={'4px'}>
|
||||
Beaker is empty.
|
||||
</Box>
|
||||
)}
|
||||
<Table>
|
||||
{beakerContents.map((chemical) => (
|
||||
<ReagentEntry
|
||||
key={chemical.ref}
|
||||
chemical={chemical}
|
||||
transferTo="buffer"
|
||||
/>
|
||||
))}
|
||||
</Table>
|
||||
</Section>
|
||||
<Section
|
||||
title="Buffer"
|
||||
buttons={
|
||||
<>
|
||||
<Box inline color="label" mr={1}>
|
||||
<AnimatedNumber value={bufferCurrentVolume} initial={0} />
|
||||
{` / ${bufferMaxVolume} units`}
|
||||
</Box>
|
||||
<Button
|
||||
color={transferMode ? 'good' : 'bad'}
|
||||
icon={transferMode ? 'exchange-alt' : 'trash'}
|
||||
content={transferMode ? 'Moving reagents' : 'Destroying reagents'}
|
||||
onClick={() => act('toggleTransferMode')}
|
||||
/>
|
||||
</>
|
||||
}>
|
||||
{bufferContents.length === 0 && (
|
||||
<Box color="label" my={'4px'}>
|
||||
Buffer is empty.
|
||||
</Box>
|
||||
)}
|
||||
<Table>
|
||||
{bufferContents.map((chemical) => (
|
||||
<ReagentEntry
|
||||
key={chemical.ref}
|
||||
chemical={chemical}
|
||||
transferTo="beaker"
|
||||
/>
|
||||
))}
|
||||
</Table>
|
||||
</Section>
|
||||
{!isPrinting && (
|
||||
<Section
|
||||
title="Packaging"
|
||||
buttons={
|
||||
bufferContents.length !== 0 &&
|
||||
(!isPrinting ? (
|
||||
<Box>
|
||||
<NumberInput
|
||||
unit={'items'}
|
||||
step={1}
|
||||
value={itemCount}
|
||||
minValue={1}
|
||||
maxValue={50}
|
||||
onChange={(e, value) => {
|
||||
setItemCount(value);
|
||||
}}
|
||||
/>
|
||||
<Box inline mx={1}>
|
||||
{`${
|
||||
Math.round(
|
||||
Math.min(
|
||||
selectedContainerVolume,
|
||||
bufferCurrentVolume / itemCount
|
||||
) * 100
|
||||
) / 100
|
||||
} u. each`}
|
||||
</Box>
|
||||
<Button
|
||||
content="Print"
|
||||
icon="flask"
|
||||
onClick={() =>
|
||||
act('create', {
|
||||
itemCount: itemCount,
|
||||
})
|
||||
}
|
||||
/>
|
||||
</Box>
|
||||
) : (
|
||||
<Button content="Printing..." icon="gear" iconSpin disabled />
|
||||
))
|
||||
}>
|
||||
{!!hasContainerSuggestion && (
|
||||
<Button.Checkbox
|
||||
onClick={() => act('toggleContainerSuggestion')}
|
||||
checked={doSuggestContainer}
|
||||
mb={1}>
|
||||
Guess container by main reagent in the buffer
|
||||
</Button.Checkbox>
|
||||
)}
|
||||
{categories.map((category) => (
|
||||
<Box key={category.name}>
|
||||
<GroupTitle title={category.name} />
|
||||
{category.containers.map(
|
||||
(container) =>
|
||||
(!hasContainerSuggestion || // Doesn't have suggestion
|
||||
(!!hasContainerSuggestion && !doSuggestContainer) || // Has sugestion and it's disabled
|
||||
(!!doSuggestContainer &&
|
||||
container.ref === suggestedContainer)) && ( // Suggestion enabled and container matches
|
||||
<ContainerButton
|
||||
key={container.ref}
|
||||
category={category}
|
||||
container={container}
|
||||
/>
|
||||
)
|
||||
)}
|
||||
</Box>
|
||||
))}
|
||||
</Section>
|
||||
)}
|
||||
{!!isPrinting && (
|
||||
<Section
|
||||
title="Printing"
|
||||
buttons={
|
||||
<Button
|
||||
color="bad"
|
||||
icon="times"
|
||||
content="Stop"
|
||||
onClick={() => act('stopPrinting')}
|
||||
/>
|
||||
}>
|
||||
<ProgressBar
|
||||
value={printingProgress}
|
||||
minValue={0}
|
||||
maxValue={printingTotal}
|
||||
color="good">
|
||||
<Box
|
||||
lineHeight={1.9}
|
||||
style={{
|
||||
'text-shadow': '1px 1px 0 black',
|
||||
}}>
|
||||
{`Printing ${printingProgress} out of ${printingTotal}`}
|
||||
</Box>
|
||||
</ProgressBar>
|
||||
</Section>
|
||||
)}
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
const ReagentEntry = (props, context) => {
|
||||
const { data, act } = useBackend<Data>(context);
|
||||
const { chemical, transferTo } = props;
|
||||
const { isPrinting } = data;
|
||||
return (
|
||||
<Table.Row key={chemical.ref}>
|
||||
<Table.Cell color="label">
|
||||
{`${chemical.name} `}
|
||||
<AnimatedNumber value={chemical.volume} initial={0} />
|
||||
{`u`}
|
||||
</Table.Cell>
|
||||
<Table.Cell collapsing>
|
||||
<Button
|
||||
content="1"
|
||||
disabled={isPrinting}
|
||||
onClick={() => {
|
||||
act('transfer', {
|
||||
reagentRef: chemical.ref,
|
||||
amount: 1,
|
||||
target: transferTo,
|
||||
});
|
||||
}}
|
||||
/>
|
||||
<Button
|
||||
content="5"
|
||||
disabled={isPrinting}
|
||||
onClick={() =>
|
||||
act('transfer', {
|
||||
reagentRef: chemical.ref,
|
||||
amount: 5,
|
||||
target: transferTo,
|
||||
})
|
||||
}
|
||||
/>
|
||||
<Button
|
||||
content="10"
|
||||
disabled={isPrinting}
|
||||
onClick={() =>
|
||||
act('transfer', {
|
||||
reagentRef: chemical.ref,
|
||||
amount: 10,
|
||||
target: transferTo,
|
||||
})
|
||||
}
|
||||
/>
|
||||
<Button
|
||||
content="All"
|
||||
disabled={isPrinting}
|
||||
onClick={() =>
|
||||
act('transfer', {
|
||||
reagentRef: chemical.ref,
|
||||
amount: 1000,
|
||||
target: transferTo,
|
||||
})
|
||||
}
|
||||
/>
|
||||
<Button
|
||||
icon="ellipsis-h"
|
||||
title="Custom amount"
|
||||
disabled={isPrinting}
|
||||
onClick={() =>
|
||||
act('transfer', {
|
||||
reagentRef: chemical.ref,
|
||||
amount: -1,
|
||||
target: transferTo,
|
||||
})
|
||||
}
|
||||
/>
|
||||
<Button
|
||||
icon="question"
|
||||
title="Analyze"
|
||||
onClick={() =>
|
||||
act('analyze', {
|
||||
reagentRef: chemical.ref,
|
||||
})
|
||||
}
|
||||
/>
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
);
|
||||
};
|
||||
|
||||
const ContainerButton = ({ container, category }, context) => {
|
||||
const { act, data } = useBackend<Data>(context);
|
||||
const { isPrinting, selectedContainerRef } = data;
|
||||
const isPillPatch = ['pills', 'patches'].includes(category.name);
|
||||
return (
|
||||
<Tooltip
|
||||
key={container.ref}
|
||||
content={`${capitalize(container.name)}\xa0(${container.volume}u)`}>
|
||||
<Button
|
||||
overflow="hidden"
|
||||
color="transparent"
|
||||
width={isPillPatch ? '32px' : '48px'}
|
||||
height={isPillPatch ? '32px' : '48px'}
|
||||
selected={container.ref === selectedContainerRef}
|
||||
disabled={isPrinting}
|
||||
p={0}
|
||||
onClick={() => {
|
||||
act('selectContainer', {
|
||||
ref: container.ref,
|
||||
});
|
||||
}}>
|
||||
<Box
|
||||
m={isPillPatch ? '0' : '8px'}
|
||||
style={{
|
||||
'transform': 'scale(2)',
|
||||
}}
|
||||
className={classes(['chemmaster32x32', container.icon])}
|
||||
/>
|
||||
</Button>
|
||||
</Tooltip>
|
||||
) as any;
|
||||
};
|
||||
|
||||
const AnalysisResults = (props, context) => {
|
||||
const { act, data } = useBackend<Data>(context);
|
||||
const {
|
||||
name,
|
||||
state,
|
||||
pH,
|
||||
color,
|
||||
description,
|
||||
purity,
|
||||
metaRate,
|
||||
overdose,
|
||||
addictionTypes,
|
||||
} = data.analysisData;
|
||||
const purityLevel =
|
||||
purity <= 0.5 ? 'bad' : purity <= 0.75 ? 'average' : 'good'; // Color names
|
||||
return (
|
||||
<Section
|
||||
title="Analysis Results"
|
||||
buttons={
|
||||
<Button
|
||||
icon="arrow-left"
|
||||
content="Back"
|
||||
onClick={() => act('stopAnalysis')}
|
||||
/>
|
||||
}>
|
||||
<LabeledList>
|
||||
<LabeledList.Item label="Name">{name}</LabeledList.Item>
|
||||
<LabeledList.Item label="Purity">
|
||||
<Box
|
||||
style={{
|
||||
'text-transform': 'capitalize',
|
||||
}}
|
||||
color={purityLevel}>
|
||||
{purityLevel}
|
||||
</Box>
|
||||
</LabeledList.Item>
|
||||
<LabeledList.Item label="pH">{pH}</LabeledList.Item>
|
||||
<LabeledList.Item label="State">{state}</LabeledList.Item>
|
||||
<LabeledList.Item label="Color">
|
||||
<ColorBox color={color} mr={1} />
|
||||
{color}
|
||||
</LabeledList.Item>
|
||||
<LabeledList.Item label="Description">{description}</LabeledList.Item>
|
||||
<LabeledList.Item label="Metabolization Rate">
|
||||
{metaRate} units/second
|
||||
</LabeledList.Item>
|
||||
<LabeledList.Item label="Overdose Threshold">
|
||||
{overdose > 0 ? `${overdose} units` : 'N/A'}
|
||||
</LabeledList.Item>
|
||||
<LabeledList.Item label="Addiction Types">
|
||||
{addictionTypes.length ? addictionTypes.toString() : 'N/A'}
|
||||
</LabeledList.Item>
|
||||
</LabeledList>
|
||||
</Section>
|
||||
);
|
||||
};
|
||||
|
||||
const GroupTitle = ({ title }) => {
|
||||
return (
|
||||
<Stack my={1}>
|
||||
<Stack.Item grow>
|
||||
<Divider />
|
||||
</Stack.Item>
|
||||
<Stack.Item
|
||||
style={{
|
||||
'text-transform': 'capitalize',
|
||||
}}
|
||||
color={'gray'}>
|
||||
{title}
|
||||
</Stack.Item>
|
||||
<Stack.Item grow>
|
||||
<Divider />
|
||||
</Stack.Item>
|
||||
</Stack>
|
||||
) as any;
|
||||
};
|
||||
Reference in New Issue
Block a user