tgui: Biogenerator (#51022)

* First version

* Pre-release version

* Cleanup

* Review fixes and finishing touches
This commit is contained in:
Arkatos1
2020-05-11 02:32:52 +03:00
committed by GitHub
parent 602270d178
commit d010f09e34
3 changed files with 295 additions and 109 deletions
+106 -108
View File
@@ -7,16 +7,18 @@
use_power = IDLE_POWER_USE
idle_power_usage = 40
circuit = /obj/item/circuitboard/machine/biogenerator
ui_x = 550
ui_y = 380
var/processing = FALSE
var/obj/item/reagent_containers/glass/beaker = null
var/points = 0
var/menustat = "menu"
var/efficiency = 0
var/productivity = 0
var/max_items = 40
var/datum/techweb/stored_research
var/list/show_categories = list("Food", "Botany Chemicals", "Organic Materials")
var/list/timesFiveCategories = list("Food", "Botany Chemicals")
/// Currently selected category in the UI
var/selected_cat
/obj/machinery/biogenerator/Initialize()
. = ..()
@@ -43,7 +45,6 @@
if(A == beaker)
beaker = null
update_icon()
updateUsrDialog()
/obj/machinery/biogenerator/RefreshParts()
var/E = 0
@@ -106,7 +107,6 @@
beaker = O
to_chat(user, "<span class='notice'>You add the container to the machine.</span>")
update_icon()
updateUsrDialog()
else
to_chat(user, "<span class='warning'>Close the maintenance panel first.</span>")
return
@@ -157,101 +157,51 @@
else
to_chat(user, "<span class='warning'>You cannot put this in [src.name]!</span>")
/obj/machinery/biogenerator/ui_interact(mob/user)
if(machine_stat & BROKEN || panel_open)
return
. = ..()
var/dat
if(processing)
dat += "<div class='statusDisplay'>Biogenerator is processing! Please wait...</div><BR>"
else
switch(menustat)
if("nopoints")
dat += "<div class='statusDisplay'>You do not have enough biomass to create products.<BR>Please, put growns into reactor and activate it.</div>"
menustat = "menu"
if("complete")
dat += "<div class='statusDisplay'>Operation complete.</div>"
menustat = "menu"
if("void")
dat += "<div class='statusDisplay'>Error: No growns inside.<BR>Please, put growns into reactor.</div>"
menustat = "menu"
if("nobeakerspace")
dat += "<div class='statusDisplay'>Not enough space left in container. Unable to create product.</div>"
menustat = "menu"
if(beaker)
var/categories = show_categories.Copy()
for(var/V in categories)
categories[V] = list()
for(var/V in stored_research.researched_designs)
var/datum/design/D = SSresearch.techweb_design_by_id(V)
for(var/C in categories)
if(C in D.category)
categories[C] += D
dat += "<div class='statusDisplay'>Biomass: [points] units.</div><BR>"
dat += "<A href='?src=[REF(src)];activate=1'>Activate</A><A href='?src=[REF(src)];detach=1'>Detach Container</A>"
for(var/cat in categories)
dat += "<h3>[cat]:</h3>"
dat += "<div class='statusDisplay'>"
for(var/V in categories[cat])
var/datum/design/D = V
dat += "[D.name]: <A href='?src=[REF(src)];create=[D.id];amount=1'>Make</A>"
if(cat in timesFiveCategories)
dat += "<A href='?src=[REF(src)];create=[D.id];amount=5'>x5</A>"
if(ispath(D.build_path, /obj/item/stack))
dat += "<A href='?src=[REF(src)];create=[D.id];amount=10'>x10</A>"
dat += "([D.materials[SSmaterials.GetMaterialRef(/datum/material/biomass)]/efficiency])<br>"
dat += "</div>"
else
dat += "<div class='statusDisplay'>No container inside, please insert container.</div>"
var/datum/browser/popup = new(user, "biogen", name, 350, 520)
popup.set_content(dat)
popup.open()
/obj/machinery/biogenerator/AltClick(mob/living/user)
. = ..()
if(user.canUseTopic(src, BE_CLOSE, FALSE, NO_TK) && can_interact(user))
detach(user)
/obj/machinery/biogenerator/proc/activate()
if (usr.stat != CONSCIOUS)
/**
* activate: Activates biomass processing and converts all inserted grown products into biomass
*
* Arguments:
* * user The mob starting the biomass processing
*/
/obj/machinery/biogenerator/proc/activate(mob/user)
if(user.stat != CONSCIOUS)
return
if (src.machine_stat != NONE) //NOPOWER etc
if(machine_stat != NONE)
return
if(processing)
to_chat(usr, "<span class='warning'>The biogenerator is in the process of working.</span>")
to_chat(user, "<span class='warning'>The biogenerator is in the process of working.</span>")
return
var/S = 0
for(var/obj/item/reagent_containers/food/snacks/grown/I in contents)
S += 5
if(I.reagents.get_reagent_amount(/datum/reagent/consumable/nutriment) < 0.1)
points += 1*productivity
else points += I.reagents.get_reagent_amount(/datum/reagent/consumable/nutriment)*10*productivity
points += 1 * productivity
else
points += I.reagents.get_reagent_amount(/datum/reagent/consumable/nutriment) * 10 * productivity
qdel(I)
if(S)
processing = TRUE
update_icon()
updateUsrDialog()
playsound(src.loc, 'sound/machines/blender.ogg', 50, TRUE)
use_power(S*30)
sleep(S+15/productivity)
playsound(loc, 'sound/machines/blender.ogg', 50, TRUE)
use_power(S * 30)
sleep(S + 15 / productivity)
processing = FALSE
update_icon()
else
menustat = "void"
/obj/machinery/biogenerator/proc/check_cost(list/materials, multiplier = 1, remove_points = TRUE)
if(materials.len != 1 || materials[1] != SSmaterials.GetMaterialRef(/datum/material/biomass))
return FALSE
if (materials[SSmaterials.GetMaterialRef(/datum/material/biomass)]*multiplier/efficiency > points)
menustat = "nopoints"
return FALSE
else
if(remove_points)
points -= materials[SSmaterials.GetMaterialRef(/datum/material/biomass)]*multiplier/efficiency
update_icon()
updateUsrDialog()
return TRUE
/obj/machinery/biogenerator/proc/check_container_volume(list/reagents, multiplier = 1)
@@ -261,7 +211,6 @@
sum_reagents *= multiplier
if(beaker.reagents.total_volume + sum_reagents > beaker.reagents.maximum_volume)
menustat = "nobeakerspace"
return FALSE
return TRUE
@@ -283,6 +232,7 @@
var/i = amount
while(i > 0)
if(!check_container_volume(D.make_reagents))
say("Warning: Attached container does not have enough free capacity!")
return .
if(!check_cost(D.materials))
return .
@@ -292,8 +242,6 @@
beaker.reagents.add_reagent(R, D.make_reagents[R])
. = 1
--i
menustat = "complete"
update_icon()
return .
@@ -306,40 +254,90 @@
beaker = null
update_icon()
/obj/machinery/biogenerator/Topic(href, href_list)
if(..() || panel_open)
/obj/machinery/biogenerator/ui_status(mob/user)
if(machine_stat & BROKEN || panel_open)
return UI_CLOSE
return ..()
/obj/machinery/biogenerator/ui_base_html(html)
var/datum/asset/spritesheet/simple/assets = get_asset_datum(/datum/asset/spritesheet/research_designs)
. = replacetext(html, "<!--customheadhtml-->", assets.css_tag())
/obj/machinery/biogenerator/ui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = FALSE, \
datum/tgui/master_ui = null, datum/ui_state/state = GLOB.default_state)
ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open)
if(!ui)
var/datum/asset/assets = get_asset_datum(/datum/asset/spritesheet/research_designs)
assets.send(user)
ui = new(user, src, ui_key, "Biogenerator", name, ui_x, ui_y, master_ui, state)
ui.open()
/obj/machinery/biogenerator/ui_data(mob/user)
var/list/data = list()
data["beaker"] = beaker ? TRUE : FALSE
data["biomass"] = points
data["processing"] = processing
if(locate(/obj/item/reagent_containers/food/snacks/grown) in contents)
data["can_process"] = TRUE
else
data["can_process"] = FALSE
return data
/obj/machinery/biogenerator/ui_static_data(mob/user)
var/list/data = list()
data["categories"] = list()
var/categories = show_categories.Copy()
for(var/V in categories)
categories[V] = list()
for(var/V in stored_research.researched_designs)
var/datum/design/D = SSresearch.techweb_design_by_id(V)
for(var/C in categories)
if(C in D.category)
categories[C] += D
for(var/category in categories)
var/list/cat = list(
"name" = category,
"items" = (category == selected_cat ? list() : null))
for(var/item in categories[category])
var/datum/design/D = item
cat["items"] += list(list(
"id" = D.id,
"name" = D.name,
"cost" = D.materials[SSmaterials.GetMaterialRef(/datum/material/biomass)]/efficiency,
))
data["categories"] += list(cat)
return data
/obj/machinery/biogenerator/ui_act(action, list/params)
if(..())
return
usr.set_machine(src)
if(href_list["activate"])
activate()
updateUsrDialog()
else if(href_list["detach"])
detach(usr)
updateUsrDialog()
else if(href_list["create"])
var/amount = (text2num(href_list["amount"]))
//Can't be outside these (if you change this keep a sane limit)
amount = clamp(amount, 1, 10)
var/id = href_list["create"]
if(!stored_research.researched_designs.Find(id))
//naughty naughty
stack_trace("ID did not map to a researched datum [id]")
return
//Get design by id (or may return error design)
var/datum/design/D = SSresearch.techweb_design_by_id(id)
//Valid design datum, amount and the datum is not the error design, lets proceed
if(D && amount && !istype(D, /datum/design/error_design))
create_product(D, amount)
//This shouldnt happen normally but href forgery is real
else
stack_trace("ID could not be turned into a valid techweb design datum [id]")
updateUsrDialog()
else if(href_list["menu"])
menustat = "menu"
updateUsrDialog()
switch(action)
if("activate")
activate(usr)
return TRUE
if("detach")
detach(usr)
return TRUE
if("create")
var/amount = text2num(params["amount"])
amount = clamp(amount, 1, 10)
if(!amount)
return
var/id = params["id"]
if(!stored_research.researched_designs.Find(id))
stack_trace("ID did not map to a researched datum [id]")
return
var/datum/design/D = SSresearch.techweb_design_by_id(id)
if(D && !istype(D, /datum/design/error_design))
create_product(D, amount)
else
stack_trace("ID could not be turned into a valid techweb design datum [id]")
return
return TRUE
if("select")
selected_cat = params["category"]
return TRUE
@@ -0,0 +1,188 @@
import { classes } from 'common/react';
import { createSearch } from 'common/string';
import { Fragment } from 'inferno';
import { useBackend, useLocalState } from '../backend';
import { Box, Button, Dimmer, Flex, Icon, Input, Section, Table, Tabs, NoticeBox, NumberInput } from '../components';
import { formatMoney } from '../format';
import { Window } from '../layouts';
const MAX_SEARCH_RESULTS = 25;
export const Biogenerator = (props, context) => {
const { data } = useBackend(context);
const {
beaker,
processing,
} = data;
return (
<Window resizable>
{!!processing && (
<Dimmer fontSize="32px">
<Icon name="cog" spin={1} />
{' Processing...'}
</Dimmer>
)}
<Window.Content scrollable>
{!beaker && (
<NoticeBox>No Container</NoticeBox>
)}
{!!beaker && (
<BiogeneratorContent />
)}
</Window.Content>
</Window>
);
};
export const BiogeneratorContent = (props, context) => {
const { act, data } = useBackend(context);
const {
biomass,
can_process,
categories = [],
} = data;
const [
searchText,
setSearchText,
] = useLocalState(context, 'searchText', '');
const [
selectedCategory,
setSelectedCategory,
] = useLocalState(context, 'category', categories[0]?.name);
const testSearch = createSearch(searchText, item => {
return item.name;
});
const items = searchText.length > 0
// Flatten all categories and apply search to it
&& categories
.flatMap(category => category.items || [])
.filter(testSearch)
.filter((item, i) => i < MAX_SEARCH_RESULTS)
// Select a category and show all items in it
|| categories
.find(category => category.name === selectedCategory)
?.items
// If none of that results in a list, return an empty list
|| [];
return (
<Section
title={(
<Box
inline
color={biomass > 0 ? 'good' : 'bad'}>
{formatMoney(biomass)} Biomass
</Box>
)}
buttons={(
<Fragment>
Search
<Input
value={searchText}
onInput={(e, value) => setSearchText(value)}
mx={1} />
<Button
icon="eject"
content="Eject"
onClick={() => act('detach')} />
<Button
icon="cog"
content="Activate"
disabled={!can_process}
onClick={() => act('activate')} />
</Fragment>
)}>
<Flex>
{searchText.length === 0 && (
<Flex.Item>
<Tabs vertical>
{categories.map(category => (
<Tabs.Tab
key={category.name}
selected={category.name === selectedCategory}
onClick={() => setSelectedCategory(category.name)}>
{category.name} ({category.items?.length || 0})
</Tabs.Tab>
))}
</Tabs>
</Flex.Item>
)}
<Flex.Item grow={1} basis={0}>
{items.length === 0 && (
<NoticeBox>
{searchText.length === 0
? 'No items in this category.'
: 'No results found.'}
</NoticeBox>
)}
<Table>
<ItemList
biomass={biomass}
items={items} />
</Table>
</Flex.Item>
</Flex>
</Section>
);
};
const ItemList = (props, context) => {
const { act } = useBackend(context);
const [
hoveredItem,
setHoveredItem,
] = useLocalState(context, 'hoveredItem', {});
const hoveredCost = hoveredItem && hoveredItem.cost || 0;
// Append extra hover data to items
const items = props.items.map(item => {
const [
amount,
setAmount,
] = useLocalState(context, "amount" + item.name, 1);
const notSameItem = hoveredItem && hoveredItem.name !== item.name;
const notEnoughHovered = props.biomass - hoveredCost
* hoveredItem.amount < item.cost * amount;
const disabledDueToHovered = notSameItem && notEnoughHovered;
const disabled = props.biomass < item.cost * amount || disabledDueToHovered;
return {
...item,
disabled,
amount,
setAmount,
};
});
return items.map(item => (
<Table.Row key={item.id}>
<Table.Cell>
<span
className={classes(['design32x32', item.id])}
style={{
'vertical-align': 'middle',
}} />
{' '}<b>{item.name}</b>
</Table.Cell>
<Table.Cell collapsing>
<NumberInput
value={Math.round(item.amount)}
width="35px"
minValue={1}
maxValue={10}
onChange={(e, value) => item.setAmount(value)} />
</Table.Cell>
<Table.Cell collapsing>
<Button
style={{
'text-align': 'right',
}}
fluid
content={item.cost * item.amount + ' ' + "BIO"}
disabled={item.disabled}
onmouseover={() => setHoveredItem(item)}
onmouseout={() => setHoveredItem({})}
onClick={() => act('create', {
id: item.id,
amount: item.amount,
})} />
</Table.Cell>
</Table.Row>
));
};
File diff suppressed because one or more lines are too long