General maintenance for chem master (#82002)

## About The Pull Request

**1. Qol**
- Adds screen tips & examines for screwdriver, wrench, crowbar & beaker
insertion, removal & replacing actions
- Analyzing reagents is now a client side feature & not a back end mode,
meaning one person can see details of a reagent while the other can
print stuff and do other operations so it's a non blocking operation.
This also means 2 players can see information of 2 different reagents in
their own screens, With that the overlay for analysis mode has been
removed
- You cannot do any tool acts on machines while printing. Balloon alerts
will be displayed warning you of that.
- The preferred container for the master reagent in the beaker is now
showed in both condiment & chem master. It can be enabled/disabled via a
CheckBox

**2. Code Improvements**
- Removed defines like `TARGET_BEAKER` , `TARGET_BEAKER` etc. ther
functionality is implemented as params in the `transfer_reagent()` proc
directly
- Removed all variables relating to analyzing reagents like
`reagent_analysis_mode`, `has_container_suggestion` etc. all memory
savings
- `printable_containers` now stores static values that can be shared
across many chem masters
- Updates only overlays and not the whole icon during operations for
efficiency

**3. Fixes**
- You can hit the chem master with the screwdriver, wrench, crowbar &
beaker when in combat mode
 - You cannot insert hologram items into the chem master
- Deconstructing a condiment master will give you the circuit board
already pre-programmed with that option selected so you don't need to
use a screwdriver to re program it
- `printing_amount` is now the maximum number of containers that can be
printed at a time. Presently this number with upgraded parts would print
out empty containers especially for patches. This is because
`volume_per_item` does not take into consideration this var. Also this
var would not give control to the player on exactly how many containers
to print as whatever amount the player entered would be multiplied with
this value producing a lot of waste & worse empty containers. Now this
var determines exactly how many containers you can print and is imposed
on the client side UI as well

**4. Refactors (UI performance)**
- Beaker data is compressed into a single entity & sent to the UI. This
is set to null if no beaker is loaded thus saving data sent
- Reuses Beaker props from chem synthesizer to reduce code
- reagent REF replaced with direct type converted to text and later
converted with `text2path()` cause its much faster

## Changelog
🆑
qol: Adds screen tips & examines for screwdriver, wrench, crowbar &
beaker insertion, removal & replacing actions
qol: Analyzing reagents no longer blocks other players from doing other
operations. Multiple players can analyze different reagents on the same
machine
qol: You cannot do any tool acts on the machine while printing to
prevent any side effects.
qol: The preferred container for the master reagent in the beaker is now
showed in both condiment & chem master. The feature can be
enabled/disabled via a check box
code: removed defines for reagent transfer, vars for reagent analyzis to
save memory. Autodoc for other vars & procs
fix: You can hit the chem master with tools like screwdriver, crowbar,
wrench & beaker in combat mode
fix: You cannot insert hologram items into the chem master
fix: Deconstructing a condiment master will give you the circuit board
already pre-programmed with that option
fix: You now print the exact amount of containers requested even with
upgraded parts without creating empty containers. Max printable
containers is 13 with tier 4 parts able to print 50 containers.
refactor: Optimized client side UI code & chem master as a whole.
/🆑

---------

Co-authored-by: MrMelbert <51863163+MrMelbert@users.noreply.github.com>
This commit is contained in:
SyncIt21
2024-04-10 12:03:13 -07:00
committed by GitHub
co-authored by MrMelbert
parent 5fd86273ba
commit c045d79fca
4 changed files with 571 additions and 475 deletions
@@ -1,84 +1,94 @@
#define TRANSFER_MODE_DESTROY 0
#define TRANSFER_MODE_MOVE 1
#define TARGET_BEAKER "beaker"
#define TARGET_BUFFER "buffer"
/obj/machinery/chem_master
name = "ChemMaster 3000"
desc = "Used to separate chemicals and distribute them in a variety of forms."
density = TRUE
layer = BELOW_OBJ_LAYER
icon = 'icons/obj/medical/chemical.dmi'
icon_state = "chemmaster"
base_icon_state = "chemmaster"
density = TRUE
idle_power_usage = BASE_MACHINE_IDLE_CONSUMPTION * 0.2
active_power_usage = BASE_MACHINE_ACTIVE_CONSUMPTION * 0.2
resistance_flags = FIRE_PROOF | ACID_PROOF
circuit = /obj/item/circuitboard/machine/chem_master
/// Icons for different percentages of buffer reagents
var/fill_icon = 'icons/obj/medical/reagent_fillings.dmi'
var/fill_icon_state = "chemmaster"
var/static/list/fill_icon_thresholds = list(10, 20, 30, 40, 50, 60, 70, 80, 90, 100)
/// Inserted reagent container
var/obj/item/reagent_containers/beaker
/// Whether separated reagents should be moved back to container or destroyed.
var/transfer_mode = TRANSFER_MODE_MOVE
/// Whether reagent analysis screen is active
var/reagent_analysis_mode = FALSE
/// Reagent being analyzed
var/datum/reagent/analyzed_reagent
var/is_transfering = TRUE
/// List of printable container types
var/list/printable_containers = list()
/// Container used by default to reset to (REF)
var/default_container
/// Selected printable container type (REF)
var/selected_container
/// Whether the machine has an option to suggest container
var/has_container_suggestion = FALSE
/// Whether to suggest container or not
var/do_suggest_container = FALSE
/// The container suggested by main reagent in the buffer
var/suggested_container
var/list/printable_containers
/// Container used by default to reset to
var/obj/item/reagent_containers/default_container
/// Selected printable container type
var/obj/item/reagent_containers/selected_container
/// Whether the machine is busy with printing containers
var/is_printing = FALSE
/// Number of printed containers in the current printing cycle for UI progress bar
/// Number of containers printed so far
var/printing_progress
/// Number of containers to be printed
var/printing_total
/// Default duration of printing cycle
var/printing_speed = 0.75 SECONDS // Duration of animation
/// The amount of containers printed in one cycle
/// The amount of containers that can be printed in 1 cycle
var/printing_amount = 1
/obj/machinery/chem_master/Initialize(mapload)
create_reagents(100)
load_printable_containers()
default_container = REF(printable_containers[printable_containers[1]][1])
printable_containers = load_printable_containers()
default_container = printable_containers[printable_containers[1]][1]
selected_container = default_container
return ..()
register_context()
. = ..()
var/obj/item/circuitboard/machine/chem_master/board = circuit
board.build_path = type
board.name = name
/obj/machinery/chem_master/Destroy()
QDEL_NULL(beaker)
return ..()
/obj/machinery/chem_master/on_deconstruction(disassembled)
replace_beaker()
return ..()
/obj/machinery/chem_master/add_context(atom/source, list/context, obj/item/held_item, mob/user)
. = NONE
if(isnull(held_item) || (held_item.item_flags & ABSTRACT) || (held_item.flags_1 & HOLOGRAM_1))
if(isnull(held_item))
context[SCREENTIP_CONTEXT_RMB] = "Remove beaker"
. = CONTEXTUAL_SCREENTIP_SET
return .
/obj/machinery/chem_master/Exited(atom/movable/gone, direction)
if(is_reagent_container(held_item) && held_item.is_open_container())
if(!QDELETED(beaker))
context[SCREENTIP_CONTEXT_LMB] = "Replace beaker"
else
context[SCREENTIP_CONTEXT_LMB] = "Insert beaker"
return CONTEXTUAL_SCREENTIP_SET
if(held_item.tool_behaviour == TOOL_SCREWDRIVER)
context[SCREENTIP_CONTEXT_LMB] = "[panel_open ? "Close" : "Open"] panel"
return CONTEXTUAL_SCREENTIP_SET
else if(held_item.tool_behaviour == TOOL_WRENCH)
context[SCREENTIP_CONTEXT_LMB] = "[anchored ? "Un" : ""] anchor"
return CONTEXTUAL_SCREENTIP_SET
else if(panel_open && held_item.tool_behaviour == TOOL_CROWBAR)
context[SCREENTIP_CONTEXT_LMB] = "Deconstruct"
return CONTEXTUAL_SCREENTIP_SET
/obj/machinery/chem_master/examine(mob/user)
. = ..()
if(gone == beaker)
beaker = null
update_appearance(UPDATE_ICON)
if(in_range(user, src) || isobserver(user))
. += span_notice("The status display reads:<br>Reagent buffer capacity: <b>[reagents.maximum_volume]</b> units.<br>Number of containers printed per cycle <b>[printing_amount]</b>.")
if(!QDELETED(beaker))
. += span_notice("[beaker] of <b>[beaker.reagents.maximum_volume]u</b> capacity inserted")
. += span_notice("Right click with empty hand to remove beaker")
else
. += span_warning("Missing input beaker")
/obj/machinery/chem_master/RefreshParts()
. = ..()
reagents.maximum_volume = 0
for(var/obj/item/reagent_containers/cup/beaker/beaker in component_parts)
reagents.maximum_volume += beaker.reagents.maximum_volume
printing_amount = 0
for(var/datum/stock_part/servo/servo in component_parts)
printing_amount += servo.tier
. += span_notice("It can be [EXAMINE_HINT("wrenched")] [anchored ? "loose" : "in place"]")
. += span_notice("Its maintainence panel can be [EXAMINE_HINT("screwed")] [panel_open ? "close" : "open"]")
if(panel_open)
. += span_notice("The machine can be [EXAMINE_HINT("pried")] apart.")
/obj/machinery/chem_master/update_appearance(updates=ALL)
/obj/machinery/chem_master/update_appearance(updates)
. = ..()
if(panel_open || (machine_stat & (NOPOWER|BROKEN)))
set_light(0)
@@ -102,9 +112,7 @@
// Screen overlay
if(!panel_open && !(machine_stat & (NOPOWER | BROKEN)))
var/screen_overlay = base_icon_state + "_overlay_screen"
if(reagent_analysis_mode)
screen_overlay += "_analysis"
else if(is_printing)
if(is_printing)
screen_overlay += "_active"
else if(reagents.total_volume > 0)
screen_overlay += "_main"
@@ -114,45 +122,124 @@
// Buffer reagents overlay
if(reagents.total_volume)
var/threshold = null
var/static/list/fill_icon_thresholds = list(10, 20, 30, 40, 50, 60, 70, 80, 90, 100)
for(var/i in 1 to fill_icon_thresholds.len)
if(ROUND_UP(100 * reagents.total_volume / reagents.maximum_volume) >= fill_icon_thresholds[i])
if(ROUND_UP(100 * (reagents.total_volume / reagents.maximum_volume)) >= fill_icon_thresholds[i])
threshold = i
if(threshold)
var/fill_name = "[fill_icon_state][fill_icon_thresholds[threshold]]"
var/mutable_appearance/filling = mutable_appearance(fill_icon, fill_name)
var/fill_name = "chemmaster[fill_icon_thresholds[threshold]]"
var/mutable_appearance/filling = mutable_appearance('icons/obj/medical/reagent_fillings.dmi', fill_name)
filling.color = mix_color_from_reagents(reagents.reagent_list)
. += filling
/obj/machinery/chem_master/wrench_act(mob/living/user, obj/item/tool)
if(default_unfasten_wrench(user, tool) == SUCCESSFUL_UNFASTEN)
return ITEM_INTERACT_SUCCESS
return ITEM_INTERACT_BLOCKING
/obj/machinery/chem_master/Exited(atom/movable/gone, direction)
. = ..()
if(gone == beaker)
beaker = null
update_appearance(UPDATE_OVERLAYS)
/obj/machinery/chem_master/screwdriver_act(mob/living/user, obj/item/tool)
if(default_deconstruction_screwdriver(user, icon_state, icon_state, tool))
update_appearance(UPDATE_ICON)
return ITEM_INTERACT_SUCCESS
return ITEM_INTERACT_BLOCKING
/obj/machinery/chem_master/on_set_is_operational(old_value)
if(!is_operational)
is_printing = FALSE
update_appearance(UPDATE_OVERLAYS)
/obj/machinery/chem_master/crowbar_act(mob/living/user, obj/item/tool)
if(default_deconstruction_crowbar(tool))
return ITEM_INTERACT_SUCCESS
return ITEM_INTERACT_BLOCKING
/obj/machinery/chem_master/RefreshParts()
. = ..()
reagents.maximum_volume = 0
for(var/obj/item/reagent_containers/cup/beaker/beaker in component_parts)
reagents.maximum_volume += beaker.reagents.maximum_volume
printing_amount = 0
for(var/datum/stock_part/servo/servo in component_parts)
printing_amount += servo.tier * 12.5
printing_amount = min(50, ROUND_UP(printing_amount))
///Return a map of category->list of containers this machine can print
/obj/machinery/chem_master/proc/load_printable_containers()
PROTECTED_PROC(TRUE)
SHOULD_BE_PURE(TRUE)
var/static/list/containers
if(!length(containers))
containers = list(
CAT_TUBES = GLOB.reagent_containers[CAT_TUBES],
CAT_PILLS = GLOB.reagent_containers[CAT_PILLS],
CAT_PATCHES = GLOB.reagent_containers[CAT_PATCHES],
)
return containers
/obj/machinery/chem_master/item_interaction(mob/living/user, obj/item/tool, list/modifiers, is_right_clicking)
if(is_reagent_container(tool) && !(tool.item_flags & ABSTRACT) && tool.is_open_container())
if(user.combat_mode || (tool.item_flags & ABSTRACT) || (tool.flags_1 & HOLOGRAM_1) || !can_interact(user) || !user.can_perform_action(src, ALLOW_SILICON_REACH | FORBID_TELEKINESIS_REACH))
return ..()
if(is_reagent_container(tool) && tool.is_open_container())
replace_beaker(user, tool)
if(!panel_open)
ui_interact(user)
return ITEM_INTERACT_SUCCESS
return ITEM_INTERACT_SUCCESS
else
return ITEM_INTERACT_BLOCKING
return ..()
/obj/machinery/chem_master/wrench_act(mob/living/user, obj/item/tool)
if(user.combat_mode)
return NONE
. = ITEM_INTERACT_BLOCKING
if(is_printing)
balloon_alert(user, "still printing!")
return .
if(default_unfasten_wrench(user, tool) == SUCCESSFUL_UNFASTEN)
return ITEM_INTERACT_SUCCESS
/obj/machinery/chem_master/screwdriver_act(mob/living/user, obj/item/tool)
if(user.combat_mode)
return NONE
. = ITEM_INTERACT_BLOCKING
if(is_printing)
balloon_alert(user, "still printing!")
return .
if(default_deconstruction_screwdriver(user, icon_state, icon_state, tool))
update_appearance(UPDATE_OVERLAYS)
return ITEM_INTERACT_SUCCESS
/obj/machinery/chem_master/crowbar_act(mob/living/user, obj/item/tool)
if(user.combat_mode)
return NONE
. = ITEM_INTERACT_BLOCKING
if(is_printing)
balloon_alert(user, "still printing!")
return .
if(default_deconstruction_crowbar(tool))
return ITEM_INTERACT_SUCCESS
/**
* Insert, remove, replace the existig beaker
* Arguments
*
* * mob/living/user - the player trying to replace the beaker
* * obj/item/reagent_containers/new_beaker - the beaker we are trying to insert, swap with existing or remove if null
*/
/obj/machinery/chem_master/proc/replace_beaker(mob/living/user, obj/item/reagent_containers/new_beaker)
PRIVATE_PROC(TRUE)
if(!QDELETED(beaker))
try_put_in_hand(beaker, user)
if(!QDELETED(new_beaker) && user.transferItemToLoc(new_beaker, src))
beaker = new_beaker
update_appearance(UPDATE_OVERLAYS)
/obj/machinery/chem_master/attack_hand_secondary(mob/user, list/modifiers)
. = ..()
if(. == SECONDARY_ATTACK_CANCEL_ATTACK_CHAIN)
return .
if(!can_interact(user) || !user.can_perform_action(src, ALLOW_SILICON_REACH|FORBID_TELEKINESIS_REACH))
if(!can_interact(user) || !user.can_perform_action(src, ALLOW_SILICON_REACH | FORBID_TELEKINESIS_REACH))
return .
replace_beaker(user)
return SECONDARY_ATTACK_CANCEL_ATTACK_CHAIN
@@ -163,25 +250,6 @@
/obj/machinery/chem_master/attack_ai_secondary(mob/user, list/modifiers)
return attack_hand_secondary(user, modifiers)
/// Insert new beaker and/or eject the inserted one
/obj/machinery/chem_master/proc/replace_beaker(mob/living/user, obj/item/reagent_containers/new_beaker)
if(new_beaker && user && !user.transferItemToLoc(new_beaker, src))
return FALSE
if(beaker)
try_put_in_hand(beaker, user)
beaker = null
if(new_beaker)
beaker = new_beaker
update_appearance(UPDATE_ICON)
return TRUE
/obj/machinery/chem_master/proc/load_printable_containers()
printable_containers = list(
CAT_TUBES = GLOB.reagent_containers[CAT_TUBES],
CAT_PILLS = GLOB.reagent_containers[CAT_PILLS],
CAT_PATCHES = GLOB.reagent_containers[CAT_PATCHES],
)
/obj/machinery/chem_master/ui_assets(mob/user)
return list(
get_asset_datum(/datum/asset/spritesheet/chemmaster)
@@ -195,269 +263,282 @@
/obj/machinery/chem_master/ui_static_data(mob/user)
var/list/data = list()
data["categories"] = list()
for(var/category in printable_containers)
var/container_data = list()
//make the category
var/list/category_list = list(
"name" = category,
"containers" = list(),
)
//add containers to this category
for(var/obj/item/reagent_containers/container as anything in printable_containers[category])
container_data += list(list(
category_list["containers"] += list(list(
"icon" = sanitize_css_class_name("[container]"),
"ref" = REF(container),
"name" = initial(container.name),
"volume" = initial(container.volume),
))
data["categories"]+= list(list(
"name" = category,
"containers" = container_data,
))
//add the category
data["categories"] += list(category_list)
return data
/obj/machinery/chem_master/ui_data(mob/user)
var/list/data = list()
. = list()
data["reagentAnalysisMode"] = reagent_analysis_mode
if(reagent_analysis_mode && analyzed_reagent)
var/state
switch(analyzed_reagent.reagent_state)
if(SOLID)
state = "Solid"
if(LIQUID)
state = "Liquid"
if(GAS)
state = "Gas"
else
state = "Unknown"
data["analysisData"] = list(
"name" = analyzed_reagent.name,
"state" = state,
"pH" = analyzed_reagent.ph,
"color" = analyzed_reagent.color,
"description" = analyzed_reagent.description,
"purity" = analyzed_reagent.purity,
"metaRate" = analyzed_reagent.metabolization_rate,
"overdose" = analyzed_reagent.overdose_threshold,
"addictionTypes" = reagents.parse_addictions(analyzed_reagent),
)
else
data["isPrinting"] = is_printing
data["printingProgress"] = printing_progress
data["printingTotal"] = printing_total
data["hasBeaker"] = beaker ? TRUE : FALSE
data["beakerCurrentVolume"] = beaker ? round(beaker.reagents.total_volume, 0.01) : null
data["beakerMaxVolume"] = beaker ? beaker.volume : null
var/list/beaker_contents = list()
if(beaker)
for(var/datum/reagent/reagent in beaker.reagents.reagent_list)
beaker_contents.Add(list(list("name" = reagent.name, "ref" = REF(reagent), "volume" = round(reagent.volume, 0.01))))
data["beakerContents"] = beaker_contents
//printing statictics
.["isPrinting"] = is_printing
.["printingProgress"] = printing_progress
.["printingTotal"] = printing_total
.["maxPrintable"] = printing_amount
var/list/buffer_contents = list()
if(reagents.total_volume)
for(var/datum/reagent/reagent in reagents.reagent_list)
buffer_contents.Add(list(list("name" = reagent.name, "ref" = REF(reagent), "volume" = round(reagent.volume, 0.01))))
data["bufferContents"] = buffer_contents
data["bufferCurrentVolume"] = round(reagents.total_volume, 0.01)
data["bufferMaxVolume"] = reagents.maximum_volume
//contents of source beaker
var/list/beaker_data = null
if(!QDELETED(beaker))
beaker_data = list()
beaker_data["maxVolume"] = beaker.volume
beaker_data["currentVolume"] = round(beaker.reagents.total_volume, CHEMICAL_VOLUME_ROUNDING)
var/list/beakerContents = list()
if(length(beaker.reagents.reagent_list))
for(var/datum/reagent/reagent as anything in beaker.reagents.reagent_list)
beakerContents += list(list(
"ref" = "[reagent.type]",
"name" = reagent.name,
"volume" = round(reagent.volume, CHEMICAL_VOLUME_ROUNDING),
"pH" = reagent.ph,
"color" = reagent.color,
"description" = reagent.description,
"purity" = reagent.purity,
"metaRate" = reagent.metabolization_rate,
"overdose" = reagent.overdose_threshold,
"addictionTypes" = reagents.parse_addictions(reagent),
))
beaker_data["contents"] = beakerContents
.["beaker"] = beaker_data
data["transferMode"] = transfer_mode
//contents of buffer
beaker_data = list()
beaker_data["maxVolume"] = reagents.maximum_volume
beaker_data["currentVolume"] = round(reagents.total_volume, CHEMICAL_VOLUME_ROUNDING)
var/list/beakerContents = list()
if(length(reagents.reagent_list))
for(var/datum/reagent/reagent as anything in reagents.reagent_list)
beakerContents += list(list(
"ref" = "[reagent.type]",
"name" = reagent.name,
"volume" = round(reagent.volume, CHEMICAL_VOLUME_ROUNDING),
"pH" = reagent.ph,
"color" = reagent.color,
"description" = reagent.description,
"purity" = reagent.purity,
"metaRate" = reagent.metabolization_rate,
"overdose" = reagent.overdose_threshold,
"addictionTypes" = reagents.parse_addictions(reagent),
))
beaker_data["contents"] = beakerContents
.["buffer"] = beaker_data
data["hasContainerSuggestion"] = !!has_container_suggestion
if(has_container_suggestion)
data["doSuggestContainer"] = !!do_suggest_container
if(do_suggest_container)
if(reagents.total_volume > 0)
var/master_reagent = reagents.get_master_reagent()
suggested_container = get_suggested_container(master_reagent)
else
suggested_container = default_container
data["suggestedContainer"] = suggested_container
selected_container = suggested_container
else if (isnull(selected_container))
selected_container = default_container
//is transfering or destroying reagents. applied only for buffer
.["isTransfering"] = is_transfering
data["selectedContainerRef"] = selected_container
var/obj/item/reagent_containers/container = locate(selected_container)
data["selectedContainerVolume"] = initial(container.volume)
//container along with the suggested type
var/obj/item/reagent_containers/suggested_container = default_container
if(reagents.total_volume > 0)
var/datum/reagent/master_reagent = reagents.get_master_reagent()
var/container_found = FALSE
suggested_container = master_reagent.default_container
for(var/category in printable_containers)
for(var/obj/item/reagent_containers/container as anything in printable_containers[category])
if(container == suggested_container)
suggested_container = REF(container)
container_found = TRUE
break
if(!container_found)
suggested_container = REF(default_container)
.["suggestedContainerRef"] = suggested_container
return data
//selected container
.["selectedContainerRef"] = REF(selected_container)
.["selectedContainerVolume"] = initial(selected_container.volume)
/obj/machinery/chem_master/ui_act(action, params)
/**
* Transfers a single reagent between buffer & beaker
* Arguments
*
* * mob/user - the player who is attempting the transfer
* * datum/reagents/source - the holder we are transferring from
* * datum/reagents/target - the holder we are transferring to
* * datum/reagent/path - the reagent typepath we are transfering
* * amount - volume to transfer -1 means custom amount
* * do_transfer - transfer the reagents else destroy them
*/
/obj/machinery/chem_master/proc/transfer_reagent(mob/user, datum/reagents/source, datum/reagents/target, datum/reagent/path, amount, do_transfer)
PRIVATE_PROC(TRUE)
//sanity checks for transfer amount
if(isnull(amount))
return FALSE
amount = text2num(amount)
if(isnull(amount))
return FALSE
if(amount == -1)
var/target_amount = tgui_input_number(user, "Enter amount to transfer", "Transfer amount")
if(!target_amount)
return FALSE
amount = text2num(target_amount)
if(isnull(amount))
return FALSE
if(amount <= 0)
return FALSE
//sanity checks for reagent path
var/datum/reagent/reagent = text2path(path)
if (!reagent)
return FALSE
//use energy
if(!use_energy(active_power_usage))
return FALSE
//do the operation
. = FALSE
if(do_transfer)
if(target.is_reacting)
return FALSE
if(source.trans_to(target, amount, target_id = reagent))
. = TRUE
else if(source.remove_reagent(reagent, amount))
. = TRUE
if(. && !QDELETED(src)) //transferring volatile reagents can cause a explosion & destory us
update_appearance(UPDATE_OVERLAYS)
return .
/obj/machinery/chem_master/ui_act(action, params, datum/tgui/ui, datum/ui_state/state)
. = ..()
if(.)
return
if(action == "eject")
replace_beaker(usr)
return TRUE
if(action == "transfer")
var/reagent_ref = params["reagentRef"]
var/amount = text2num(params["amount"])
var/target = params["target"]
return transfer_reagent(reagent_ref, amount, target)
if(action == "toggleTransferMode")
transfer_mode = !transfer_mode
return TRUE
if(action == "analyze")
analyzed_reagent = locate(params["reagentRef"])
if(analyzed_reagent)
reagent_analysis_mode = TRUE
update_appearance(UPDATE_ICON)
switch(action)
if("eject")
replace_beaker(ui.user)
return TRUE
if(action == "stopAnalysis")
reagent_analysis_mode = FALSE
analyzed_reagent = null
update_appearance(UPDATE_ICON)
return TRUE
if("transfer")
if(is_printing)
say("buffer locked while printing!")
return
if(action == "stopPrinting")
var/reagent_ref = params["reagentRef"]
var/amount = params["amount"]
var/target = params["target"]
if(target == "buffer")
return transfer_reagent(ui.user, beaker.reagents, reagents, reagent_ref, amount, TRUE)
else if(target == "beaker")
return transfer_reagent(ui.user, reagents, beaker.reagents, reagent_ref, amount, is_transfering)
return FALSE
if("toggleTransferMode")
is_transfering = !is_transfering
return TRUE
if("stopPrinting")
is_printing = FALSE
update_appearance(UPDATE_OVERLAYS)
return TRUE
if("selectContainer")
var/obj/item/reagent_containers/target = locate(params["ref"])
if(!ispath(target))
return FALSE
selected_container = target
return TRUE
if("create")
if(!reagents.total_volume || is_printing)
return FALSE
//validate print count
var/item_count = params["itemCount"]
if(isnull(item_count))
return FALSE
item_count = text2num(item_count)
if(isnull(item_count) || item_count <= 0)
return FALSE
item_count = min(item_count, printing_amount)
var/volume_in_each = round(reagents.total_volume / item_count, CHEMICAL_VOLUME_ROUNDING)
// Generate item name
var/item_name_default = initial(selected_container.name)
var/datum/reagent/master_reagent = reagents.get_master_reagent()
if(selected_container == default_container) // Tubes and bottles gain reagent name
item_name_default = "[master_reagent.name] [item_name_default]"
if(!(initial(selected_container.reagent_flags) & OPENCONTAINER)) // Closed containers get both reagent name and units in the name
item_name_default = "[master_reagent.name] [item_name_default] ([volume_in_each]u)"
var/item_name = tgui_input_text(usr,
"Container name",
"Name",
item_name_default,
MAX_NAME_LEN)
if(!item_name)
return FALSE
//start printing
is_printing = TRUE
printing_progress = 0
printing_total = item_count
update_appearance(UPDATE_OVERLAYS)
create_containers(ui.user, item_count, item_name, volume_in_each)
return TRUE
/**
* Create N selected containers with reagents from buffer split between them
* Arguments
*
* * mob/user - the player printing these containers
* * item_count - number of containers to print
* * item_name - the name for each container printed
* * volume_in_each - volume in each container created
*/
/obj/machinery/chem_master/proc/create_containers(mob/user, item_count, item_name, volume_in_each)
PRIVATE_PROC(TRUE)
//lost power or manually stopped
if(!is_printing)
return
//use power
if(!use_energy(active_power_usage))
return
//print the stuff
var/obj/item/reagent_containers/item = new selected_container(drop_location())
adjust_item_drop_location(item)
item.name = item_name
item.reagents.clear_reagents()
reagents.trans_to(item, volume_in_each, transferred_by = user)
printing_progress++
update_appearance(UPDATE_OVERLAYS)
//print more items
item_count --
if(item_count > 0)
addtimer(CALLBACK(src, PROC_REF(create_containers), user, item_count, item_name, volume_in_each), 0.75 SECONDS)
else
is_printing = FALSE
return TRUE
if(action == "toggleContainerSuggestion")
do_suggest_container = !do_suggest_container
return TRUE
if(action == "selectContainer")
selected_container = params["ref"]
return TRUE
if(action == "create")
if(reagents.total_volume == 0)
return FALSE
var/item_count = text2num(params["itemCount"])
if(item_count <= 0)
return FALSE
create_containers(item_count)
return TRUE
/// Create N selected containers with reagents from buffer split between them
/obj/machinery/chem_master/proc/create_containers(item_count = 1)
var/obj/item/reagent_containers/container_style = locate(selected_container)
var/is_pill_subtype = ispath(container_style, /obj/item/reagent_containers/pill)
var/volume_in_each = reagents.total_volume / item_count
var/printing_amount_current = is_pill_subtype ? printing_amount * 2 : printing_amount
// Generate item name
var/item_name_default = initial(container_style.name)
var/datum/reagent/master_reagent = reagents.get_master_reagent()
if(selected_container == default_container) // Tubes and bottles gain reagent name
item_name_default = "[master_reagent.name] [item_name_default]"
if(!(initial(container_style.reagent_flags) & OPENCONTAINER)) // Closed containers get both reagent name and units in the name
item_name_default = "[master_reagent.name] [item_name_default] ([volume_in_each]u)"
var/item_name = tgui_input_text(usr,
"Container name",
"Name",
item_name_default,
MAX_NAME_LEN)
if(!item_name || !reagents.total_volume || QDELETED(src) || !usr.can_perform_action(src, ALLOW_SILICON_REACH))
return FALSE
// Print and fill containers
is_printing = TRUE
update_appearance(UPDATE_ICON)
printing_progress = 0
printing_total = item_count
while(item_count > 0)
if(!is_printing)
break
use_energy(active_power_usage)
stoplag(printing_speed)
for(var/i in 1 to printing_amount_current)
if(!item_count)
continue
var/obj/item/reagent_containers/item = new container_style(drop_location())
adjust_item_drop_location(item)
item.name = item_name
item.reagents.clear_reagents()
reagents.trans_to(item, volume_in_each, transferred_by = src)
printing_progress++
item_count--
update_appearance(UPDATE_ICON)
is_printing = FALSE
update_appearance(UPDATE_ICON)
return TRUE
/// Transfer reagents to specified target from the opposite source
/obj/machinery/chem_master/proc/transfer_reagent(reagent_ref, amount, target)
if (amount == -1)
amount = text2num(input("Enter the amount you want to transfer:", name, ""))
if (amount == null || amount <= 0)
return FALSE
if (!beaker && target == TARGET_BEAKER && transfer_mode == TRANSFER_MODE_MOVE)
return FALSE
var/datum/reagent/reagent = locate(reagent_ref)
if (!reagent)
return FALSE
use_energy(active_power_usage)
if (target == TARGET_BUFFER)
if(!check_reactions(reagent, beaker.reagents))
return FALSE
beaker.reagents.trans_to(src, amount, target_id = reagent.type)
update_appearance(UPDATE_ICON)
return TRUE
if (target == TARGET_BEAKER && transfer_mode == TRANSFER_MODE_DESTROY)
reagents.remove_reagent(reagent.type, amount)
update_appearance(UPDATE_ICON)
return TRUE
if (target == TARGET_BEAKER && transfer_mode == TRANSFER_MODE_MOVE)
if(!check_reactions(reagent, reagents))
return FALSE
reagents.trans_to(beaker, amount, target_id = reagent.type)
update_appearance(UPDATE_ICON)
return TRUE
return FALSE
/// Checks to see if the target reagent is being created (reacting) and if so prevents transfer
/// Only prevents reactant from being moved so that people can still manlipulate input reagents
/obj/machinery/chem_master/proc/check_reactions(datum/reagent/reagent, datum/reagents/holder)
if(!reagent)
return FALSE
var/canMove = TRUE
for(var/datum/equilibrium/equilibrium as anything in holder.reaction_list)
if(equilibrium.reaction.reaction_flags & REACTION_COMPETITIVE)
continue
for(var/datum/reagent/result as anything in equilibrium.reaction.required_reagents)
if(result == reagent.type)
canMove = FALSE
if(!canMove)
say("Cannot move reagent during reaction!")
return canMove
/// Retrieve REF to the best container for provided reagent
/obj/machinery/chem_master/proc/get_suggested_container(datum/reagent/reagent)
var/preferred_container = reagent.default_container
for(var/category in printable_containers)
for(var/container in printable_containers[category])
if(container == preferred_container)
return REF(container)
return default_container
/obj/machinery/chem_master/examine(mob/user)
. = ..()
if(in_range(user, src) || isobserver(user))
. += span_notice("The status display reads:<br>Reagent buffer capacity: <b>[reagents.maximum_volume]</b> units.<br>Number of containers printed at once increased by <b>[100 * (printing_amount / initial(printing_amount)) - 100]%</b>.")
update_appearance(UPDATE_OVERLAYS)
/obj/machinery/chem_master/condimaster
name = "CondiMaster 3000"
desc = "Used to create condiments and other cooking supplies."
icon_state = "condimaster"
has_container_suggestion = TRUE
/obj/machinery/chem_master/condimaster/load_printable_containers()
printable_containers = list(
CAT_CONDIMENTS = GLOB.reagent_containers[CAT_CONDIMENTS],
)
#undef TRANSFER_MODE_DESTROY
#undef TRANSFER_MODE_MOVE
#undef TARGET_BEAKER
#undef TARGET_BUFFER
var/static/list/containers
if(!length(containers))
containers = list(CAT_CONDIMENTS = GLOB.reagent_containers[CAT_CONDIMENTS])
return containers
Binary file not shown.

Before

Width:  |  Height:  |  Size: 69 KiB

After

Width:  |  Height:  |  Size: 69 KiB

+166 -151
View File
@@ -18,32 +18,22 @@ import {
Tooltip,
} from '../components';
import { Window } from '../layouts';
import { Beaker, BeakerReagent } from './common/BeakerDisplay';
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 Container = {
icon: string;
ref: string;
name: string;
volume: number;
};
type Analysis = {
type Category = {
name: string;
state: string;
containers: Container[];
};
type AnalyzableReagent = BeakerReagent & {
ref: string;
pH: number;
color: string;
description: string;
@@ -53,144 +43,162 @@ type Analysis = {
addictionTypes: string[];
};
type Category = {
name: string;
containers: Container[];
};
type AnalyzableBeaker = {
contents: AnalyzableReagent[];
} & Beaker;
type Reagent = {
ref: string;
name: string;
volume: number;
};
type Container = {
icon: string;
ref: string;
name: string;
volume: number;
type Data = {
categories: Category[];
isPrinting: BooleanLike;
printingProgress: number;
printingTotal: number;
maxPrintable: number;
beaker: AnalyzableBeaker;
buffer: AnalyzableBeaker;
isTransfering: BooleanLike;
suggestedContainerRef: string;
selectedContainerRef: string;
selectedContainerVolume: number;
};
export const ChemMaster = (props) => {
const { data } = useBackend<Data>();
const { reagentAnalysisMode } = data;
const [analyzedReagent, setAnalyzedReagent] = useState<AnalyzableReagent>();
return (
<Window width={400} height={620}>
<Window width={450} height={620}>
<Window.Content scrollable>
{reagentAnalysisMode ? <AnalysisResults /> : <ChemMasterContent />}
{analyzedReagent ? (
<AnalysisResults
analysisData={analyzedReagent}
onExit={() => setAnalyzedReagent(undefined)}
/>
) : (
<ChemMasterContent
analyze={(chemical: AnalyzableReagent) =>
setAnalyzedReagent(chemical)
}
/>
)}
</Window.Content>
</Window>
);
};
const ChemMasterContent = (props) => {
const ChemMasterContent = (props: {
analyze: (chemical: AnalyzableReagent) => void;
}) => {
const { act, data } = useBackend<Data>();
const {
isPrinting,
printingProgress,
printingTotal,
transferMode,
hasBeaker,
beakerCurrentVolume,
beakerMaxVolume,
beakerContents,
bufferContents,
bufferCurrentVolume,
bufferMaxVolume,
maxPrintable,
isTransfering,
beaker,
buffer,
categories,
selectedContainerVolume,
hasContainerSuggestion,
doSuggestContainer,
suggestedContainer,
} = data;
const [itemCount, setItemCount] = useState(1);
const [itemCount, setItemCount] = useState<number>(1);
const [showPreferredContainer, setShowPreferredContainer] =
useState<BooleanLike>(false);
const buffer_contents = buffer.contents;
return (
<Box>
<Section
title="Beaker"
buttons={
!!hasBeaker && (
beaker && (
<Box>
<Box inline color="label" mr={2}>
<AnimatedNumber value={beakerCurrentVolume} initial={0} />
{` / ${beakerMaxVolume} units`}
<AnimatedNumber value={beaker.currentVolume} initial={0} />
{` / ${beaker.maxVolume} units`}
</Box>
<Button
icon="eject"
content="Eject"
onClick={() => act('eject')}
/>
<Button icon="eject" onClick={() => act('eject')}>
Eject
</Button>
</Box>
)
}
>
{!hasBeaker && (
{!beaker ? (
<Box color="label" my={'4px'}>
No beaker loaded.
</Box>
)}
{!!hasBeaker && beakerCurrentVolume === 0 && (
) : beaker.currentVolume === 0 ? (
<Box color="label" my={'4px'}>
Beaker is empty.
</Box>
) : (
<Table>
{beaker.contents.map((chemical) => (
<ReagentEntry
key={chemical.ref}
chemical={chemical}
transferTo="buffer"
analyze={props.analyze}
/>
))}
</Table>
)}
<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`}
<AnimatedNumber value={buffer.currentVolume} initial={0} />
{` / ${buffer.maxVolume} units`}
</Box>
<Button
color={transferMode ? 'good' : 'bad'}
icon={transferMode ? 'exchange-alt' : 'trash'}
content={transferMode ? 'Moving reagents' : 'Destroying reagents'}
color={isTransfering ? 'good' : 'bad'}
icon={isTransfering ? 'exchange-alt' : 'trash'}
onClick={() => act('toggleTransferMode')}
/>
>
{isTransfering ? 'Moving reagents' : 'Destroying reagents'}
</Button>
</>
}
>
{bufferContents.length === 0 && (
{buffer_contents.length === 0 ? (
<Box color="label" my={'4px'}>
Buffer is empty.
</Box>
) : (
<Table>
{buffer_contents.map((chemical) => (
<ReagentEntry
key={chemical.ref}
chemical={chemical}
transferTo="beaker"
analyze={props.analyze}
/>
))}
</Table>
)}
<Table>
{bufferContents.map((chemical) => (
<ReagentEntry
key={chemical.ref}
chemical={chemical}
transferTo="beaker"
/>
))}
</Table>
</Section>
{!isPrinting && (
<Section
title="Packaging"
buttons={
bufferContents.length !== 0 &&
(!isPrinting ? (
buffer_contents.length !== 0 && (
<Box>
<Button.Checkbox
checked={showPreferredContainer}
onClick={() =>
setShowPreferredContainer((currentValue) => !currentValue)
}
>
Suggest
</Button.Checkbox>
<NumberInput
unit={'items'}
step={1}
value={itemCount}
minValue={1}
maxValue={50}
maxValue={maxPrintable}
onChange={(value) => {
setItemCount(value);
}}
@@ -200,51 +208,36 @@ const ChemMasterContent = (props) => {
Math.round(
Math.min(
selectedContainerVolume,
bufferCurrentVolume / itemCount,
buffer.currentVolume / itemCount,
) * 100,
) / 100
} u. each`}
</Box>
<Button
content="Print"
icon="flask"
onClick={() =>
act('create', {
itemCount: itemCount,
})
}
/>
>
Print
</Button>
</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}
/>
),
)}
{category.containers.map((container) => (
<ContainerButton
key={container.ref}
category={category}
container={container}
showPreferredContainer={showPreferredContainer}
/>
))}
</Box>
))}
</Section>
@@ -256,9 +249,10 @@ const ChemMasterContent = (props) => {
<Button
color="bad"
icon="times"
content="Stop"
onClick={() => act('stopPrinting')}
/>
>
Stop
</Button>
}
>
<ProgressBar
@@ -282,9 +276,15 @@ const ChemMasterContent = (props) => {
);
};
const ReagentEntry = (props) => {
type ReagentProps = {
chemical: AnalyzableReagent;
transferTo: string;
analyze: (chemical: AnalyzableReagent) => void;
};
const ReagentEntry = (props: ReagentProps) => {
const { data, act } = useBackend<Data>();
const { chemical, transferTo } = props;
const { chemical, transferTo, analyze } = props;
const { isPrinting } = data;
return (
<Table.Row key={chemical.ref}>
@@ -295,7 +295,6 @@ const ReagentEntry = (props) => {
</Table.Cell>
<Table.Cell collapsing>
<Button
content="1"
disabled={isPrinting}
onClick={() => {
act('transfer', {
@@ -304,9 +303,10 @@ const ReagentEntry = (props) => {
target: transferTo,
});
}}
/>
>
1
</Button>
<Button
content="5"
disabled={isPrinting}
onClick={() =>
act('transfer', {
@@ -315,9 +315,10 @@ const ReagentEntry = (props) => {
target: transferTo,
})
}
/>
>
5
</Button>
<Button
content="10"
disabled={isPrinting}
onClick={() =>
act('transfer', {
@@ -326,9 +327,10 @@ const ReagentEntry = (props) => {
target: transferTo,
})
}
/>
>
10
</Button>
<Button
content="All"
disabled={isPrinting}
onClick={() =>
act('transfer', {
@@ -337,7 +339,9 @@ const ReagentEntry = (props) => {
target: transferTo,
})
}
/>
>
All
</Button>
<Button
icon="ellipsis-h"
tooltip="Custom amount"
@@ -353,21 +357,25 @@ const ReagentEntry = (props) => {
<Button
icon="question"
tooltip="Analyze"
onClick={() =>
act('analyze', {
reagentRef: chemical.ref,
})
}
onClick={() => analyze(chemical)}
/>
</Table.Cell>
</Table.Row>
);
};
const ContainerButton = ({ container, category }) => {
type CategoryButtonProps = {
category: Category;
container: Container;
showPreferredContainer: BooleanLike;
};
const ContainerButton = (props: CategoryButtonProps) => {
const { act, data } = useBackend<Data>();
const { isPrinting, selectedContainerRef } = data;
const { isPrinting, selectedContainerRef, suggestedContainerRef } = data;
const { category, container, showPreferredContainer } = props;
const isPillPatch = ['pills', 'patches'].includes(category.name);
return (
<Tooltip
key={container.ref}
@@ -375,7 +383,14 @@ const ContainerButton = ({ container, category }) => {
>
<Button
overflow="hidden"
color="transparent"
color={'transparent'}
backgroundColor={
showPreferredContainer &&
selectedContainerRef !== suggestedContainerRef && // if we selected the same container as the suggested then don't override color
container.ref === suggestedContainerRef
? 'blue'
: 'transparent'
}
width={isPillPatch ? '32px' : '48px'}
height={isPillPatch ? '32px' : '48px'}
selected={container.ref === selectedContainerRef}
@@ -399,11 +414,12 @@ const ContainerButton = ({ container, category }) => {
) as any;
};
const AnalysisResults = (props) => {
const { act, data } = useBackend<Data>();
const AnalysisResults = (props: {
analysisData: AnalyzableReagent;
onExit: () => void;
}) => {
const {
name,
state,
pH,
color,
description,
@@ -411,18 +427,18 @@ const AnalysisResults = (props) => {
metaRate,
overdose,
addictionTypes,
} = data.analysisData;
} = props.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')}
/>
<Button icon="arrow-left" onClick={() => props.onExit()}>
Back
</Button>
}
>
<LabeledList>
@@ -438,7 +454,6 @@ const AnalysisResults = (props) => {
</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}
@@ -8,7 +8,7 @@ import {
Section,
} from '../../components';
type BeakerReagent = {
export type BeakerReagent = {
name: string;
volume: number;
};