[MIRROR] Add recipe macro recording to reagent dispenser (#8769)

Co-authored-by: ShadowLarkens <shadowlarkens@gmail.com>
Co-authored-by: Kashargul <144968721+Kashargul@users.noreply.github.com>
This commit is contained in:
CHOMPStation2
2024-08-13 17:07:34 -07:00
committed by GitHub
parent 1d101bcadb
commit 83818f4394
7 changed files with 275 additions and 25 deletions

View File

@@ -21,6 +21,11 @@
anchored = TRUE
unacidable = TRUE
/// Records the reagents dispensed by the user if this list is not null
var/list/recording_recipe
/// Saves all the recipes recorded by the machine
var/list/saved_recipes = list()
/obj/machinery/chemical_dispenser/Initialize()
. = ..()
if(spawn_cartridges)
@@ -154,25 +159,38 @@
var/obj/item/weapon/reagent_containers/chem_disp_cartridge/C = cartridges[label]
chemicals.Add(list(list("name" = label, "id" = label, "volume" = C.reagents.total_volume))) // list in a list because Byond merges the first list...
data["chemicals"] = chemicals
data["recipes"] = saved_recipes
data["recordingRecipe"] = recording_recipe
return data
/obj/machinery/chemical_dispenser/tgui_act(action, params)
if(..())
return TRUE
/obj/machinery/chemical_dispenser/tgui_act(action, list/params, datum/tgui/ui, datum/tgui_state/state)
. = ..()
if(.)
return
if(stat & BROKEN)
return FALSE
add_fingerprint(ui.user)
. = TRUE
switch(action)
if("amount")
amount = clamp(round(text2num(params["amount"]), 1), 0, 120) // round to nearest 1 and clamp 0 - 120
. = TRUE
if("dispense")
var/label = params["reagent"]
if(cartridges[label] && container && container.is_open_container())
if(recording_recipe)
recording_recipe += list(list("id" = label, "amount" = amount))
else if(cartridges[label] && container && container.is_open_container())
var/obj/item/weapon/reagent_containers/chem_disp_cartridge/C = cartridges[label]
playsound(src, 'sound/machines/reagent_dispense.ogg', 25, 1)
C.reagents.trans_to(container, amount)
. = TRUE
if("remove")
var/amount = text2num(params["amount"])
if(!container || !amount)
if(!container || !amount || recording_recipe)
return
var/datum/reagents/R = container.reagents
var/id = params["reagent"]
@@ -180,18 +198,82 @@
R.remove_reagent(id, amount)
else if(amount == -1) // Isolate
R.isolate_reagent(id)
. = TRUE
if("ejectBeaker")
if(container)
container.forceMove(get_turf(src))
if(Adjacent(usr)) // So the AI doesn't get a beaker somehow.
usr.put_in_hands(container)
if(Adjacent(ui.user)) // So the AI doesn't get a beaker somehow.
ui.user.put_in_hands(container)
container = null
else
return FALSE
. = TRUE
if("record_recipe")
recording_recipe = list()
. = TRUE
if("cancel_recording")
recording_recipe = null
. = TRUE
if("clear_recipes")
if(tgui_alert(ui.user, "Clear all recipes?", "Clear?", list("No", "Yes")) == "Yes")
saved_recipes = list()
. = TRUE
if("save_recording")
var/name = tgui_input_text(ui.user, "What do you want to name this recipe?", "Recipe Name?", "Recipe Name", MAX_NAME_LEN)
if(tgui_status(ui.user, state) != STATUS_INTERACTIVE)
return
if(saved_recipes[name] && tgui_alert(ui.user, "\"[name]\" already exists, do you want to overwrite it?",, list("No", "Yes")) != "Yes")
return
if(name && recording_recipe)
for(var/list/L in recording_recipe)
var/label = L["id"]
// Verify this dispenser can dispense every chemical
if(!cartridges[label])
visible_message(span_warning("[src] buzzes."), span_warning("You hear a faint buzz."))
to_chat(ui.user, span_warning("[src] cannot find <b>[label]</b>!"))
playsound(src, 'sound/machines/buzz-two.ogg', 50, TRUE)
return
saved_recipes[name] = recording_recipe
recording_recipe = null
. = TRUE
if("dispense_recipe")
var/list/chemicals_to_dispense = saved_recipes[params["recipe"]]
if(!LAZYLEN(chemicals_to_dispense))
return
if(!recording_recipe)
if(!container)
to_chat(ui.user, span_warning("There is no beaker in [src]."))
return
for(var/list/L in chemicals_to_dispense)
var/label = L["id"]
var/dispense_amount = L["amount"]
var/obj/item/weapon/reagent_containers/chem_disp_cartridge/C = cartridges[label]
if(!C)
visible_message(span_warning("[src] buzzes."), span_warning("You hear a faint buzz."))
to_chat(ui.user, span_warning("[src] cannot find <b>[label]</b>!"))
playsound(src, 'sound/machines/buzz-two.ogg', 50, TRUE)
break
// Allows copying recipes
playsound(src, 'sound/machines/reagent_dispense.ogg', 25, 1)
var/amount_actually_dispensed = C.reagents.trans_to(container, dispense_amount)
if(dispense_amount != amount_actually_dispensed)
visible_message(span_warning("[src] buzzes."), span_warning("You hear a faint buzz."))
to_chat(ui.user, span_warning("[src] was only able to dispense [amount_actually_dispensed]u out of [dispense_amount]u requested of <b>[label]</b>!"))
playsound(src, 'sound/machines/buzz-two.ogg', 50, TRUE)
break
else
recording_recipe += chemicals_to_dispense
. = TRUE
add_fingerprint(usr)
/obj/machinery/chemical_dispenser/attack_ghost(mob/user)
if(stat & BROKEN)

View File

@@ -11,12 +11,24 @@ export const ChemDispenserBeaker = (props) => {
beakerCurrentVolume,
beakerMaxVolume,
beakerContents = [],
recipes,
recordingRecipe,
} = data;
const recording = !!recordingRecipe;
const recordedContents =
recording &&
recordingRecipe.map((r) => ({
id: r.id,
name: r.id.replace(/_/, ' '),
volume: r.amount,
}));
return (
<Section
title="Beaker"
flex="content"
minHeight="25%"
title={recording ? 'Virtual Beaker' : 'Beaker'}
fill
scrollable
buttons={
<Box>
{!!isBeakerLoaded && (
@@ -35,12 +47,13 @@ export const ChemDispenserBeaker = (props) => {
}
>
<BeakerContents
beakerLoaded={isBeakerLoaded}
beakerContents={beakerContents}
beakerLoaded={recordedContents || isBeakerLoaded}
beakerContents={recordedContents || beakerContents}
buttons={(chemical) => (
<>
<Button
icon="compress-arrows-alt"
disabled={recording}
onClick={() =>
act('remove', {
reagent: chemical.id,
@@ -53,6 +66,7 @@ export const ChemDispenserBeaker = (props) => {
{removeAmounts.map((a, i) => (
<Button
key={i}
disabled={recording}
onClick={() =>
act('remove', {
reagent: chemical.id,
@@ -64,6 +78,7 @@ export const ChemDispenserBeaker = (props) => {
</Button>
))}
<Button
disabled={recording}
onClick={() =>
act('remove', {
reagent: chemical.id,

View File

@@ -1,5 +1,7 @@
import { useEffect, useState } from 'react';
import { useBackend } from '../../backend';
import { Button, Flex, Section } from '../../components';
import { Button, Flex, Icon, Section, Tooltip } from '../../components';
import { Data } from './types';
export const ChemDispenserChemicals = (props) => {
@@ -13,6 +15,7 @@ export const ChemDispenserChemicals = (props) => {
<Section
title={data.glass ? 'Drink Dispenser' : 'Chemical Dispenser'}
flexGrow
buttons={<RecordingBlinker />}
>
<Flex direction="row" wrap="wrap" height="100%" align="flex-start">
{chemicals.map((c, i) => (
@@ -39,3 +42,29 @@ export const ChemDispenserChemicals = (props) => {
</Section>
);
};
const RecordingBlinker = (props) => {
const { data } = useBackend<Data>();
const recording = !!data.recordingRecipe;
const [blink, setBlink] = useState(false);
useEffect(() => {
if (recording) {
const intervalId = setInterval(() => {
setBlink((v) => !v);
}, 1000);
return () => clearInterval(intervalId);
}
}, [recording]);
if (!recording) {
return null;
}
return (
<Tooltip content="Recording in progress">
<Icon mt={0.7} color="bad" name={blink ? 'circle-o' : 'circle'} />
</Tooltip>
);
};

View File

@@ -0,0 +1,92 @@
import { useBackend } from '../../backend';
import { Box, Button, Section } from '../../components';
import { Data } from './types';
export const ChemDispenserRecipes = (props) => {
const { act, data } = useBackend<Data>();
const { recipes, recordingRecipe } = data;
const recording: boolean = !!recordingRecipe;
const recipeData = Object.keys(recipes).sort();
return (
<Section
title="Recipes"
fill
scrollable
buttons={
<>
{!recording && (
<Button icon="circle" onClick={() => act('record_recipe')}>
Record
</Button>
)}
{recording && (
<Button
icon="ban"
color="bad"
onClick={() => act('cancel_recording')}
>
Discard
</Button>
)}
{recording && (
<Button
icon="save"
color="green"
onClick={() => act('save_recording')}
>
Save
</Button>
)}
{!recording && (
<Button.Confirm
icon="trash"
confirmIcon="trash"
color="bad"
onClick={() => act('clear_recipes')}
>
Clear All
</Button.Confirm>
)}
</>
}
>
{recording && (
<>
<Box color="green" fontSize={1.2} bold>
Recording In Progress...
</Box>
<Box color="label">
Press dispenser buttons in the order you wish for them to be
repeated, then click{' '}
<Box color="good" inline>
Save
</Box>
.
</Box>
<Box color="average" mb={1}>
Alternatively, if you mess up the recipe and want to discard this
recording, click{' '}
<Box color="bad" inline>
Discard
</Box>
.
</Box>
</>
)}
{recipeData.length
? recipeData.map((recipe) => (
<Button
key={recipe}
fluid
icon="flask"
onClick={() => act('dispense_recipe', { recipe })}
>
{recipe}
</Button>
))
: 'No Recipes.'}
</Section>
);
};

View File

@@ -7,7 +7,7 @@ export const ChemDispenserSettings = (props) => {
const { act, data } = useBackend<Data>();
const { amount } = data;
return (
<Section title="Settings" flex="content">
<Section title="Settings" fill>
<LabeledList>
<LabeledList.Item label="Dispense" verticalAlign="middle">
{dispenseAmounts.map((a, i) => (

View File

@@ -1,15 +1,40 @@
import { useBackend } from '../../backend';
import { Stack } from '../../components';
import { Window } from '../../layouts';
import { ChemDispenserBeaker } from './ChemDispenserBeaker';
import { ChemDispenserChemicals } from './ChemDispenserChemicals';
import { ChemDispenserRecipes } from './ChemDispenserRecipes';
import { ChemDispenserSettings } from './ChemDispenserSettings';
import { Data } from './types';
export const ChemDispenser = (props) => {
const { data } = useBackend<Data>();
return (
<Window width={390} height={655}>
<Window.Content className="Layout__content--flexColumn">
<ChemDispenserSettings />
<ChemDispenserChemicals />
<ChemDispenserBeaker />
<Window width={680} height={540}>
<Window.Content>
<Stack vertical fill>
<Stack.Item>
<Stack>
<Stack.Item grow>
<Stack vertical fill>
<Stack.Item>
<ChemDispenserSettings />
</Stack.Item>
<Stack.Item grow>
<ChemDispenserRecipes />
</Stack.Item>
</Stack>
</Stack.Item>
<Stack.Item grow>
<ChemDispenserChemicals />
</Stack.Item>
</Stack>
</Stack.Item>
<Stack.Item grow>
<ChemDispenserBeaker />
</Stack.Item>
</Stack>
</Window.Content>
</Window>
);

View File

@@ -1,5 +1,10 @@
import { BooleanLike } from 'common/react';
export type Recipe = {
id: string;
amount: number;
};
export type Data = {
amount: number;
isBeakerLoaded: BooleanLike;
@@ -8,6 +13,8 @@ export type Data = {
beakerCurrentVolume: number | null;
beakerMaxVolume: number | null;
chemicals: reagent[];
recipes: Record<string, Recipe[]>;
recordingRecipe: Recipe[];
};
type reagent = { name: string; id: string; volume: number };