diff --git a/monkestation/code/modules/hydroponics/machines/composter.dm b/monkestation/code/modules/hydroponics/machines/composter.dm
index 2ee13764934c..ee6310ed26c4 100644
--- a/monkestation/code/modules/hydroponics/machines/composter.dm
+++ b/monkestation/code/modules/hydroponics/machines/composter.dm
@@ -10,21 +10,30 @@
var/biomatter = 0
/// The amount of biomatter needed to make 1 biocube.
var/biocube_cost = 40
+ /// Multiplier for how much biomass is created from input.
+ var/input_multiplier = 1
/obj/machinery/composters/Initialize(mapload)
. = ..()
if(mapload)
- biomatter = 80
+ biomatter = biocube_cost * 2
-/obj/machinery/composters/attacked_by(obj/item/attacking_item, mob/living/user)
+/obj/machinery/composters/RefreshParts()
. = ..()
- if(istype(attacking_item, /obj/item/seeds))
- compost(attacking_item)
+ input_multiplier = src::input_multiplier
+ for(var/datum/stock_part/matter_bin/bin in component_parts)
+ input_multiplier += max((bin.tier - 1) / 10, 0)
- if(istype(attacking_item, /obj/item/food))
- compost(attacking_item)
+ biocube_cost = src::biocube_cost
+ for(var/datum/stock_part/manipulator/manipulator in component_parts)
+ biocube_cost /= 1 + ((manipulator.tier - 1) / 8)
+ biocube_cost = FLOOR(biocube_cost, 5)
- if(istype(attacking_item, /obj/item/storage/bag)) // covers any kind of bag that has a compostible item
+/obj/machinery/composters/attacked_by(obj/item/attacking_item, mob/living/user)
+ . = ..()
+ if(istype(attacking_item, /obj/item/seeds) || istype(attacking_item, /obj/item/food))
+ compost(attacking_item)
+ else if(istype(attacking_item, /obj/item/storage/bag)) // covers any kind of bag that has a compostible item
var/obj/item/storage/bag/bag = attacking_item
var/list/to_compost
for(var/item in bag.contents)
@@ -37,7 +46,7 @@
compost(to_compost)
to_chat(user, span_info("You empty \the [bag] into \the [src]."))
-/obj/machinery/gibber/attack_paw(mob/user, list/modifiers)
+/obj/machinery/composters/attack_paw(mob/user, list/modifiers)
return attack_hand(user, modifiers)
/obj/machinery/composters/attack_hand(mob/living/user, list/modifiers)
@@ -68,23 +77,39 @@
if(C && user.pulling == C && !C.buckled && !C.has_buckled_mobs() && !occupant)
user.visible_message(span_danger("[user] stuffs [C] into [src]!"))
compost(C, allow_carbons = TRUE)
+ return
if(biomatter < biocube_cost)
- to_chat(user, span_notice("Not enough biomatter to produce Bio-Cube"))
+ to_chat(user, span_warning("You need at least [biocube_cost] units of biomatter to produce a biocube."))
+ return
+
+ var/biocubes_to_produce = tgui_input_number(
+ user,
+ message = "How many biocubes to produce?",
+ title = name,
+ default = 1,
+ max_value = min(floor(biomatter / biocube_cost), /obj/item/stack/biocube::max_amount),
+ min_value = 1,
+ )
+ if(!biocubes_to_produce || QDELETED(user) || QDELETED(src) || !user.can_perform_action(src, FORBID_TELEKINESIS_REACH | ALLOW_RESTING))
return
- new /obj/item/stack/biocube(drop_location(), 1)
- biomatter -= biocube_cost
- update_desc()
+ var/biomatter_needed = biocubes_to_produce * biocube_cost
+ if(biomatter < biomatter_needed)
+ to_chat(user, span_warning("You need at least [biomatter_needed] units of biomatter to produce [biocubes_to_produce] biocube\s."))
+ return
+ new /obj/item/stack/biocube(drop_location(), biocubes_to_produce)
+ biomatter -= biomatter_needed
update_appearance()
-/obj/machinery/composters/update_desc()
+/obj/machinery/composters/examine(mob/user)
. = ..()
- desc = "Just insert your bio degradable materials and it will produce compost."
- desc += "\nBiomatter: [biomatter]"
+ . += "It currently has [biomatter] units of biomatter."
+ . += "It costs [biocube_cost] units of biomatter to produce a biocube."
+ . += "It composts input into biomatter with [input_multiplier]x efficiency."
/obj/machinery/composters/update_overlays()
. = ..()
- if(biomatter < 40)
+ if(biomatter < biocube_cost)
. += mutable_appearance('monkestation/icons/obj/machines/composter.dmi', "light_off", layer = OBJ_LAYER + 0.01)
else
. += mutable_appearance('monkestation/icons/obj/machines/composter.dmi', "light_on", layer = OBJ_LAYER + 0.01)
@@ -120,11 +145,10 @@
CHECK_TICK
if(!biomatter_added)
return
- biomatter += biomatter_added
+ biomatter += round(biomatter_added * input_multiplier)
if(yucky)
playsound(loc, 'sound/machines/juicer.ogg', vol = 50, vary = TRUE)
audible_message(span_hear("You hear a loud squelchy grinding sound."))
- update_desc()
update_appearance()
flick("composter_animate", src)
diff --git a/tgui/packages/tgui/interfaces/Biogenerator.tsx b/tgui/packages/tgui/interfaces/Biogenerator.tsx
index 964f78396da9..d87ce7b3d11a 100644
--- a/tgui/packages/tgui/interfaces/Biogenerator.tsx
+++ b/tgui/packages/tgui/interfaces/Biogenerator.tsx
@@ -1,176 +1,91 @@
-import { BooleanLike } from 'common/react';
-import { classes } from 'common/react';
-import { useBackend, useLocalState } from '../backend';
-import { Window } from '../layouts';
import {
Box,
- Section,
- NumberInput,
- Table,
- Tabs,
+ Button,
+ Icon,
LabeledList,
NoticeBox,
- Button,
+ NumberInput,
ProgressBar,
+ Section,
Stack,
+ Table,
+ Tabs,
} from '../components';
+import { BooleanLike } from 'common/react';
+import { classes } from 'common/react';
+import { useBackend, useLocalState } from '../backend';
+import { Window } from '../layouts';
-type BiogeneratorData = {
- processing: BooleanLike;
+type Data = {
beaker: BooleanLike;
- reagent_color: string;
- biomass: number;
- max_visual_biomass: number;
- can_process: BooleanLike;
beakerCurrentVolume: number;
beakerMaxVolume: number;
- max_output: number;
- efficiency: number;
+ biomass: number;
+ can_process: BooleanLike;
categories: Category[];
+ efficiency: number;
+ max_output: number;
+ max_visual_biomass: number;
+ processing: BooleanLike;
+ reagent_color: string;
};
type Category = {
- name: string;
items: Design[];
+ name: string;
};
type Design = {
- id: number;
- name: string;
- is_reagent: BooleanLike;
- disable: BooleanLike;
- cost: number;
amount: number;
+ cost: number;
+ disable: BooleanLike;
+ id: string;
+ is_reagent: BooleanLike;
+ name: string;
};
-export const Biogenerator = (props) => {
- const { act, data } = useBackend();
- const {
- processing,
- beaker,
- reagent_color,
- biomass,
- max_visual_biomass,
- can_process,
- beakerCurrentVolume,
- beakerMaxVolume,
- max_output,
- efficiency,
- categories,
- } = data;
- const [selectedCategory, setSelectedCategory] = useLocalState(
+export const Biogenerator = () => {
+ const { data } = useBackend();
+ const { beaker, beakerCurrentVolume, beakerMaxVolume, categories } = data;
+
+ const [selectedCategory, setSelectedCategory] = useLocalState(
'category',
data.categories[0]?.name,
);
+
const items =
categories.find((category) => category.name === selectedCategory)?.items ||
[];
+
+ const space = beaker ? beakerMaxVolume - beakerCurrentVolume : 1;
+
return (
-
+
-
-
- act('activate')}
- />
- }
- >
-
-
- {`${parseFloat(biomass.toFixed(2))} units`}
-
-
-
- {!!beaker && (
- act('eject')}
- />
- }
- >
-
-
- {`${beakerCurrentVolume} of ${beakerMaxVolume} units`}
-
-
-
- )}
- {!beaker && (
-
-
- No liquid container
-
-
- )}
-
-
+
- {categories.map((category) => (
+ {categories.map(({ name }) => (
setSelectedCategory(category.name)}
+ key={name}
+ selected={name === selectedCategory}
+ onClick={() => setSelectedCategory(name)}
>
- {category.name}
+ {name}
))}
-
+
-
+ {items.map((item) => (
+
+ ))}
@@ -180,65 +95,164 @@ export const Biogenerator = (props) => {
);
};
-const ItemList = (props) => {
- const { act } = useBackend();
- const items = props.items.map((item) => {
- const [amount, setAmount] = useLocalState(
- 'amount' + item.name,
- item.is_reagent ? Math.min(Math.max(props.space, 1), 10) : 1,
- );
- const disabled =
- props.processing ||
- (item.is_reagent && !props.beaker) ||
- (item.is_reagent && props.space < amount) ||
- props.biomass < Math.ceil((item.cost * amount) / props.efficiency);
- const max_possible = Math.floor(
- (props.efficiency * props.biomass) / item.cost,
- );
- const max_capacity = item.is_reagent ? props.space : props.max_output;
- const max_amount = Math.max(1, Math.min(max_capacity, max_possible));
- return {
- ...item,
- disabled,
- max_amount,
- amount,
- setAmount,
- };
- });
- return items.map((item) => (
-
+const Controls = () => {
+ const { act, data } = useBackend();
+ const {
+ beaker,
+ beakerCurrentVolume,
+ beakerMaxVolume,
+ biomass,
+ can_process,
+ max_visual_biomass,
+ processing,
+ reagent_color,
+ } = data;
+
+ return (
+
+
+ act('activate')}
+ >
+ Generate
+
+ }
+ >
+
+
+ {`${parseFloat(biomass.toFixed(2))} units`}
+
+
+
+ {!!beaker && (
+ act('eject')}
+ >
+ Eject
+
+ }
+ >
+
+
+ {`${beakerCurrentVolume} of ${beakerMaxVolume} units`}
+
+
+
+ )}
+ {!beaker && (
+
+
+ No liquid container
+
+
+ )}
+
+
+ );
+};
+
+type Props = {
+ item: Design;
+ space: number;
+};
+
+const Item = (props: Props) => {
+ const { item, space } = props;
+ const { cost, id, is_reagent, name } = item;
+
+ const { act, data } = useBackend();
+ const { biomass, beaker, efficiency, max_output, processing } = data;
+
+ const minAmount = is_reagent ? Math.min(Math.max(space, 1), 10) : 1;
+
+ const [amount, setAmount] = useLocalState('amount-' + id, minAmount);
+
+ const disabled =
+ processing ||
+ (is_reagent && !beaker) ||
+ (is_reagent && space < amount) ||
+ biomass < Math.ceil(cost * amount);
+
+ const maxPossible = Math.floor(biomass / cost);
+
+ const maxCapacity = is_reagent ? space : max_output;
+ const maxAmount = Math.max(1, Math.min(maxCapacity, maxPossible));
+
+ return (
+
{' '}
- {item.name}
+ {name}
item.setAmount(value)}
+ maxValue={maxAmount}
+ onChange={(_, value) => setAmount(value)}
/>
- ));
+ );
};