mirror of
https://github.com/CHOMPStation2/CHOMPStation2.git
synced 2025-12-11 10:43:20 +00:00
[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:
@@ -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)
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
};
|
||||
@@ -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) => (
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
|
||||
@@ -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 };
|
||||
|
||||
Reference in New Issue
Block a user