diff --git a/code/__defines/misc.dm b/code/__defines/misc.dm
index 87d600be4b..a74bdbfeb6 100644
--- a/code/__defines/misc.dm
+++ b/code/__defines/misc.dm
@@ -567,6 +567,12 @@ GLOBAL_LIST_INIT(all_volume_channels, list(
#define SHELTER_DEPLOY_ANCHORED_OBJECTS "anchored objects"
#define SHELTER_DEPLOY_SHIP_SPACE "ship not in space"
+// Borg hypo injection checks
+#define BORGHYPO_STATUS_CONTAINERFULL "container full"
+#define BORGHYPO_STATUS_NOCHARGE "not enough charge"
+#define BORGHYPO_STATUS_NORECIPE "recipe not found"
+#define BORGHYPO_STATUS_SUCCESS "success"
+
#define PTO_SECURITY "Security"
#define PTO_MEDICAL "Medical"
#define PTO_ENGINEERING "Engineering"
diff --git a/code/game/mecha/equipment/weapons/weapons.dm b/code/game/mecha/equipment/weapons/weapons.dm
index 3e215d516e..2cef0b4053 100644
--- a/code/game/mecha/equipment/weapons/weapons.dm
+++ b/code/game/mecha/equipment/weapons/weapons.dm
@@ -33,12 +33,7 @@
chassis.visible_message(span_warning("[chassis] fires [src]!"))
occupant_message(span_warning("You fire [src]!"))
src.mecha_log_message("Fired from [src], targeting [target].")
- var/target_for_log = "unknown"
- if(ismob(target))
- target_for_log = target
- else if(target)
- target_for_log = "[target.name]"
- add_attack_logs(chassis.occupant,target_for_log,"Fired exosuit weapon [src.name] (MANUAL)")
+ add_attack_logs(chassis.occupant,target,"Fired exosuit weapon [src.name] (MANUAL)")
for(var/i = 1 to min(projectiles, projectiles_per_shot))
var/turf/aimloc = targloc
diff --git a/code/game/objects/items/toys/toy_customizable.dm b/code/game/objects/items/toys/toy_customizable.dm
index 0a8105606e..94e6475775 100644
--- a/code/game/objects/items/toys/toy_customizable.dm
+++ b/code/game/objects/items/toys/toy_customizable.dm
@@ -150,16 +150,18 @@
if("import_config")
. = TRUE
var/our_data = params["config"]
- var/imported_color = sanitize_hexcolor(our_data["base_color"])
- if(imported_color)
- base_color = imported_color
- set_new_name(our_data["name"])
+ base_color = sanitize_hexcolor(our_data["base_color"])
+ var/new_name = sanitize_name(our_data["name"])
+ if(new_name)
+ set_new_name(new_name)
added_overlays.Cut()
if(!possible_overlays)
return
for(var/overlay in our_data["overlays"])
if(possible_overlays.Find(overlay["icon_state"]))
- added_overlays[overlay["icon_state"]] = list( color = overlay["color"], alpha = overlay["alpha"] )
+ var/new_color = sanitize_hexcolor(overlay["color"])
+ var/new_alpha = CLAMP(text2num(overlay["alpha"]), 0, 255)
+ added_overlays[overlay["icon_state"]] = list(color = new_color, alpha = new_alpha)
update_icon()
if("clear")
diff --git a/code/modules/mob/living/silicon/robot/dogborg/dog_defense_modules.dm b/code/modules/mob/living/silicon/robot/dogborg/dog_defense_modules.dm
index 89c38a6514..1291279f98 100644
--- a/code/modules/mob/living/silicon/robot/dogborg/dog_defense_modules.dm
+++ b/code/modules/mob/living/silicon/robot/dogborg/dog_defense_modules.dm
@@ -63,7 +63,7 @@
if(C.brute_damage == 0 && C.electronics_damage == 0)
to_chat(R, span_notice("Repair of [C] completed."))
return
- if(!R.use_direct_power(power_tick, 500)) //We don't want to drain ourselves too far down during exploration
+ if(!R.use_direct_power(power_tick, 500)) //We don't want to drain ourselves too far down during exploration
to_chat(R, span_warning("Not enough power to initialize the repair system."))
return
if(do_after(R, tick_delay, target = R))
diff --git a/code/modules/mob/living/silicon/robot/dogborg/dog_sleeper.dm b/code/modules/mob/living/silicon/robot/dogborg/dog_sleeper.dm
index 649ea0c683..ed542f47fb 100644
--- a/code/modules/mob/living/silicon/robot/dogborg/dog_sleeper.dm
+++ b/code/modules/mob/living/silicon/robot/dogborg/dog_sleeper.dm
@@ -249,9 +249,7 @@
UNTYPED_LIST_ADD(robot_chems, list("id" = possible_reagent.id, "name" = possible_reagent.name))
data["name"] = name
- var/robot_theme = robot_user.get_ui_theme()
- if(robot_theme)
- data["theme"] = robot_theme
+ data["theme"] = robot_user.get_ui_theme()
data["chems"] = robot_chems
return data
diff --git a/code/modules/mob/living/silicon/robot/robot.dm b/code/modules/mob/living/silicon/robot/robot.dm
index 97a42d869f..ae40e39830 100644
--- a/code/modules/mob/living/silicon/robot/robot.dm
+++ b/code/modules/mob/living/silicon/robot/robot.dm
@@ -1217,7 +1217,7 @@
if(cell.charge - (amount + lower_limit) <= 0)
return FALSE
- cell.charge -= amount
+ cell.use(amount)
return TRUE
/mob/living/silicon/robot/binarycheck()
diff --git a/code/modules/mob/living/silicon/robot/robot_ui.dm b/code/modules/mob/living/silicon/robot/robot_ui.dm
index aa673cd700..a07a0d857e 100644
--- a/code/modules/mob/living/silicon/robot/robot_ui.dm
+++ b/code/modules/mob/living/silicon/robot/robot_ui.dm
@@ -49,10 +49,8 @@
var/mob/living/silicon/robot/R = host
data["module_name"] = R.module ? "[R.module]" : null
- if(R.emagged)
- data["theme"] = "syndicate"
- else if (R.ui_theme)
- data["theme"] = R.ui_theme
+
+ data["theme"] = R.get_ui_theme()
if(!R.module)
return data
diff --git a/code/modules/mob/living/silicon/robot/robot_ui_decals.dm b/code/modules/mob/living/silicon/robot/robot_ui_decals.dm
index f10c52e69b..50024bace8 100644
--- a/code/modules/mob/living/silicon/robot/robot_ui_decals.dm
+++ b/code/modules/mob/living/silicon/robot/robot_ui_decals.dm
@@ -25,9 +25,7 @@
var/mob/living/silicon/robot/R = host
data["active_decals"] = R.robotdecal_on
- var/robot_theme = R.get_ui_theme()
- if(robot_theme)
- data["theme"] = robot_theme
+ data["theme"] = R.get_ui_theme()
return data
diff --git a/code/modules/mob/living/silicon/robot/robot_ui_module.dm b/code/modules/mob/living/silicon/robot/robot_ui_module.dm
index 96d84514fe..3fa820e4da 100644
--- a/code/modules/mob/living/silicon/robot/robot_ui_module.dm
+++ b/code/modules/mob/living/silicon/robot/robot_ui_module.dm
@@ -57,10 +57,7 @@
modules |= module_name
data["possible_modules"] = modules
data["mind_name"] = R.mind.name
- if(R.emagged)
- data["theme"] = "syndicate"
- else if (R.ui_theme)
- data["theme"] = R.ui_theme
+ data["theme"] = R.get_ui_theme()
return data
diff --git a/code/modules/reagents/machinery/dispenser/dispenser2.dm b/code/modules/reagents/machinery/dispenser/dispenser2.dm
index bbee022c51..d1d328c40d 100644
--- a/code/modules/reagents/machinery/dispenser/dispenser2.dm
+++ b/code/modules/reagents/machinery/dispenser/dispenser2.dm
@@ -202,6 +202,20 @@
container = null
. = TRUE
+ if("import_config")
+ var/list/our_data = params["config"]
+ if(!islist(our_data))
+ return FALSE
+ var/list/new_recipes = list()
+ for(var/key, value in our_data)
+ if(istext(key) && islist(value))
+ for(var/list/steps in value)
+ if(istext(steps["id"]) && isnum(steps["amount"]))
+ new_recipes[key] += list(list("id" = steps["id"], "amount" = steps["amount"]))
+ if(length(new_recipes))
+ saved_recipes = new_recipes
+ . = TRUE
+
if("record_recipe")
recording_recipe = list()
. = TRUE
@@ -260,7 +274,7 @@
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 [label]!"))
+ to_chat(ui.user, span_warning("[src] was only able to dispense [amount_actually_dispensed ? amount_actually_dispensed : 0]u out of [dispense_amount]u requested of [label]!"))
playsound(src, 'sound/machines/buzz-two.ogg', 50, TRUE)
break
else
diff --git a/code/modules/reagents/reagent_containers/borghypo.dm b/code/modules/reagents/reagent_containers/borghypo.dm
index 536bed9bc6..a71bad8cf3 100644
--- a/code/modules/reagents/reagent_containers/borghypo.dm
+++ b/code/modules/reagents/reagent_containers/borghypo.dm
@@ -5,18 +5,39 @@
item_state = "hypo"
icon_state = "borghypo"
amount_per_transfer_from_this = 5
+ min_transfer_amount = 1
volume = 30
- max_transfer_amount = null
+ max_transfer_amount = 10
+ /// The single chemical we have currently selected. Used to index `reagent_volumes`, `reagent_names`, and `reagent_ids`.
var/mode = 1
- var/charge_cost = 325
+ /// Amount of power this hypo will remove from the robot user's internal cell when a reagent's stores are replenished.
+ var/charge_cost = 325 // CHOMPEdit
var/charge_tick = 0
- var/recharge_time = 5 //Time it takes for shots to recharge (in seconds)
- var/bypass_protection = FALSE // If true, can inject through things like spacesuits and armor.
+ /// Time it takes for shots to recharge (in seconds)
+ var/recharge_time = 5
+ /// If true, can inject through things like spacesuits and armor.
+ var/bypass_protection = FALSE
+ /// Affects whether the TGUI will display itself as a chem or drink dispenser.
+ var/is_dispensing_drinks = FALSE
+ /// String contents in the TGUI search bar.
+ var/ui_chemical_search
+ /// Whether or not we're dispensing just a single reagent or are dispensing multiple reagents via a recipe
+ var/is_dispensing_recipe = FALSE
+ /// The recipe we will dispense if `is_dispensing_recipe` is `TRUE`
+ var/selected_recipe_id
+ var/hypo_sound = 'sound/effects/hypospray.ogg' // What sound do we play on use?
var/list/reagent_ids = list(REAGENT_ID_TRICORDRAZINE, REAGENT_ID_INAPROVALINE, REAGENT_ID_BICARIDINE, REAGENT_ID_ANTITOXIN, REAGENT_ID_KELOTANE, REAGENT_ID_TRAMADOL, REAGENT_ID_DEXALIN, REAGENT_ID_SPACEACILLIN) // CHOMPEdit
var/list/reagent_volumes = list()
+ /// Associated list of the names of each of our reagents. Indexed via `mode`.
var/list/reagent_names = list()
+ /// If we're currently recording a recipe, this will be set to a list containing the recipe's steps.
+ var/list/recording_recipe
+ /// Associated list of the recipes we have saved. Indexed via the string ID of the recipe.
+ var/list/saved_recipes = list()
+ /// In the hypo's TGUI, this determines the amount buttons that will be available to change this hypo's transfer amount.
+ var/list/transfer_amounts = list(5, 10)
/obj/item/reagent_containers/borghypo/surgeon
reagent_ids = list(REAGENT_ID_INAPROVALINE, REAGENT_ID_DEXALIN, REAGENT_ID_TRICORDRAZINE, REAGENT_ID_SPACEACILLIN, REAGENT_ID_OXYCODONE)
@@ -34,13 +55,54 @@
bypass_protection = TRUE // Because mercs tend to be in spacesuits.
reagent_ids = list(REAGENT_ID_HEALINGNANITES, REAGENT_ID_HYPERZINE, REAGENT_ID_TRAMADOL, REAGENT_ID_OXYCODONE, REAGENT_ID_SPACEACILLIN, REAGENT_ID_PERIDAXON, REAGENT_ID_OSTEODAXON, REAGENT_ID_MYELAMINE, REAGENT_ID_SYNTHBLOOD)
+/// Performs a single reagent addition. Returns its success (or error) status at doing so.
+/obj/item/reagent_containers/borghypo/proc/try_add_reagent(var/datum/reagents/target_reagents, var/mob/user, var/reagent_id, var/amount)
+ var/reagent_volume = reagent_volumes[reagent_id]
+ if(!reagent_volume || reagent_volume < amount)
+ return BORGHYPO_STATUS_NOCHARGE
+
+ if(!target_reagents.get_free_space())
+ return BORGHYPO_STATUS_CONTAINERFULL
+
+ if(hypo_sound)
+ playsound(src, hypo_sound, 25, TRUE)
+
+ var/amount_to_add = min(amount, reagent_volumes[reagent_id])
+ target_reagents.add_reagent(reagent_id, amount_to_add)
+ reagent_volumes[reagent_id] -= amount_to_add
+ return BORGHYPO_STATUS_SUCCESS
+
+/// Attempts to add one reagent or multiple reagents, depending on if this hypo is currently set to dispense a recipe, (see `is_dispensing_recipe`.) Returns its success (or error) status at doing so.
+/obj/item/reagent_containers/borghypo/proc/try_injection(var/datum/reagents/target_reagents, var/mob/user)
+ if(is_dispensing_recipe && selected_recipe_id)
+ // Add reagents with our selected ID
+ var/foundRecipe = saved_recipes[selected_recipe_id]
+ if(!foundRecipe)
+ to_chat(user, span_warning("Couldn't find recipe ") + span_boldwarning(selected_recipe_id) + span_warning("! Contact a coder."))
+ return BORGHYPO_STATUS_NORECIPE
+ for(var/recipe_step in foundRecipe)
+ var/step_reagent_id = recipe_step["id"]
+ var/step_dispense_amount = recipe_step["amount"]
+ var/result = try_add_reagent(target_reagents, user, step_reagent_id, step_dispense_amount)
+ switch(result)
+ if(BORGHYPO_STATUS_CONTAINERFULL)
+ return result
+ if(BORGHYPO_STATUS_NOCHARGE)
+ var/datum/reagent/empty_reagent = SSchemistry.chemical_reagents[step_reagent_id]
+ to_chat(user, span_warning("[src] doesn't have enough ") + span_boldwarning(empty_reagent.name) + span_warning(" to complete this recipe!"))
+ return result
+ return BORGHYPO_STATUS_SUCCESS
+ else
+ // Just add reagents
+ return try_add_reagent(target_reagents, user, reagent_ids[mode], amount_per_transfer_from_this)
+
/obj/item/reagent_containers/borghypo/Initialize(mapload)
. = ..()
for(var/T in reagent_ids)
reagent_volumes[T] = volume
- var/datum/reagent/R = SSchemistry.chemical_reagents[T]
- reagent_names += R.name
+ var/datum/reagent/hypo_reagent = SSchemistry.chemical_reagents[T]
+ reagent_names += hypo_reagent.name
START_PROCESSING(SSobj, src)
@@ -54,25 +116,19 @@
charge_tick = 0
if(isrobot(loc))
- var/mob/living/silicon/robot/R = loc
- if(R && R.cell)
+ var/mob/living/silicon/robot/robot_user = loc
+ if(robot_user && robot_user.cell)
for(var/T in reagent_ids)
if(reagent_volumes[T] < volume)
- if(R.cell.charge - charge_cost < 800) //This is so borgs don't kill themselves with it.
+ if(!robot_user.use_direct_power(charge_cost, 800))
return 0
- else
- R.cell.use(charge_cost)
- reagent_volumes[T] = min(reagent_volumes[T] + 5, volume)
+ reagent_volumes[T] = min(reagent_volumes[T] + 5, volume)
return 1
/obj/item/reagent_containers/borghypo/attack(var/mob/living/M, var/mob/user)
if(!istype(M))
return
- if(!reagent_volumes[reagent_ids[mode]])
- balloon_alert(user, "the injector is empty.")
- return
-
var/mob/living/carbon/human/H = M
if(istype(H))
var/obj/item/organ/external/affected = H.get_organ(user.zone_sel.selecting)
@@ -86,55 +142,210 @@
*/
if(M.can_inject(user, 1, ignore_thickness = bypass_protection))
- balloon_alert(user, "you inject [M] with the injector.")
- balloon_alert(M, "you feel a tiny prick!")
if(M.reagents)
- var/t = min(amount_per_transfer_from_this, reagent_volumes[reagent_ids[mode]])
- M.reagents.add_reagent(reagent_ids[mode], t)
- reagent_volumes[reagent_ids[mode]] -= t
- add_attack_logs(user, M, "Borg injected with [reagent_ids[mode]]")
- to_chat(user, span_notice("[t] units injected. [reagent_volumes[reagent_ids[mode]]] units remaining."))
+ var/reagent_id = reagent_ids[mode]
+ var/amount_to_add = min(amount_per_transfer_from_this, reagent_volumes[reagent_id])
+ var/result = try_injection(M.reagents, user)
+ if(is_dispensing_recipe)
+ // Log every reagent injected in the recipe
+ var/foundRecipe = saved_recipes[selected_recipe_id]
+ for(var/recipe_step in foundRecipe)
+ var/step_reagent_id = recipe_step["id"]
+ var/step_dispense_amount = recipe_step["amount"]
+ add_attack_logs(user, M, "Borg injected with [step_dispense_amount] units of '[step_reagent_id]'")
+ else
+ add_attack_logs(user, M, "Borg injected with [amount_to_add] units of '[reagent_id]'")
+ switch(result)
+ if(BORGHYPO_STATUS_CONTAINERFULL)
+ balloon_alert(user, "\the [M] has too many reagents in [M.p_their()] system!")
+ return
+ if(BORGHYPO_STATUS_NOCHARGE)
+ if(is_dispensing_recipe)
+ balloon_alert(user, "not enough reagents to inject full recipe!")
+ balloon_alert(M, "you feel multiple tiny pricks in quick succession!")
+ else
+ var/datum/reagent/empty_reagent = SSchemistry.chemical_reagents[reagent_id]
+ balloon_alert(user, "\the [src] doesn't have enough [empty_reagent.name]!")
+ return
+ if(BORGHYPO_STATUS_NORECIPE)
+ balloon_alert(user, "recipe '[selected_recipe_id]' not found!")
+ return
+ else
+ if(is_dispensing_recipe)
+ balloon_alert(user, "recipe '[selected_recipe_id]' injected into \the [M].")
+ balloon_alert(M, "you feel multiple tiny pricks in quick succession!")
+ else
+ balloon_alert(user, "[amount_to_add] units injected into \the [M].")
+ balloon_alert(M, "you feel a tiny prick!")
return
/obj/item/reagent_containers/borghypo/attack_self(mob/user as mob) //Change the mode
- var/t
- for(var/i = 1 to reagent_ids.len)
- if(t)
- t += ", "
- if(mode == i)
- t += span_bold("[reagent_names[i]]")
- else
- t += "[reagent_names[i]]"
- t = "Available reagents: [t]."
- to_chat(user,span_infoplain(t))
-
+ tgui_interact(user)
return
+/* No longer necessary because we use TGUI for this now!
/obj/item/reagent_containers/borghypo/Topic(var/href, var/list/href_list)
if(href_list["reagent"])
var/t = reagent_ids.Find(href_list["reagent"])
if(t)
playsound(src, 'sound/effects/pop.ogg', 50, 0)
mode = t
- var/datum/reagent/R = SSchemistry.chemical_reagents[reagent_ids[mode]]
- balloon_alert(usr, "synthesizer is now producing '[R.name]'")
+ var/datum/reagent/new_reagent = SSchemistry.chemical_reagents[reagent_ids[mode]]
+ balloon_alert(usr, "synthesizer is now producing '[new_reagent.name]'")
+*/
+
+/obj/item/reagent_containers/borghypo/tgui_interact(mob/user, datum/tgui/ui, datum/tgui/parent_ui, custom_state)
+ . = ..()
+ ui = SStgui.try_update_ui(user, src, ui)
+ if(!ui)
+ // Assuming the user is opening the UI, empty the chem search preemptively.
+ ui_chemical_search = null
+ ui = new(user, src, "BorgHypo", "Integrated [is_dispensing_drinks ? "Drink Dispenser" : "Chemical Hypo"]")
+ ui.open()
+
+/obj/item/reagent_containers/borghypo/tgui_static_data(mob/user)
+ var/list/static_data = list()
+ static_data["isDispensingDrinks"] = is_dispensing_drinks
+ static_data["minTransferAmount"] = min_transfer_amount
+ static_data["maxTransferAmount"] = max_transfer_amount
+ return static_data
+
+/obj/item/reagent_containers/borghypo/tgui_data(mob/user, datum/tgui/ui, datum/tgui_state/state)
+ var/list/data = list()
+ if(!isrobot(user))
+ return data
+ var/mob/living/silicon/robot/robot_user = user
+ data["theme"] = robot_user.get_ui_theme()
+ data["amount"] = amount_per_transfer_from_this
+ data["transferAmounts"] = transfer_amounts
+
+ var/list/chemicals = list()
+ for(var/key, value in reagent_volumes)
+ var/datum/reagent/available_reagent = SSchemistry.chemical_reagents[key]
+ // If the user is searching for a particular chemical by name, only add this one if its name matches their search!
+ if((ui_chemical_search && findtext(available_reagent.name, ui_chemical_search)) || !ui_chemical_search)
+ UNTYPED_LIST_ADD(chemicals, list("name" = available_reagent.name, "id" = key, "volume" = value))
+ data["chemicals"] = chemicals
+ data["uiChemicalSearch"] = ui_chemical_search
+ data["selectedReagentId"] = reagent_ids[mode]
+ data["recipes"] = saved_recipes
+ data["recordingRecipe"] = recording_recipe
+ data["isDispensingRecipe"] = is_dispensing_recipe
+ data["selectedRecipeId"] = selected_recipe_id
+
+ return data
+
+/obj/item/reagent_containers/borghypo/tgui_act(action, list/params, datum/tgui/ui, datum/tgui_state/state)
+ . = ..()
+ if(.)
+ return
+ switch(action)
+ if("select_reagent")
+ var/new_mode = reagent_ids.Find(params["selectedReagentId"])
+ if(new_mode)
+ var/datum/reagent/selected_reagent = SSchemistry.chemical_reagents[reagent_ids[new_mode]]
+ playsound(src, 'sound/effects/pop.ogg', 50, 0)
+ if(recording_recipe)
+ UNTYPED_LIST_ADD(recording_recipe, list("id" = selected_reagent.id, "amount" = amount_per_transfer_from_this))
+ else
+ mode = new_mode
+ balloon_alert(ui.user, "synthesizer is now producing '[selected_reagent.name]'")
+ is_dispensing_recipe = FALSE
+ . = TRUE
+
+ if("set_amount")
+ amount_per_transfer_from_this = clamp(round(text2num(params["amount"]), 1), min_transfer_amount, max_transfer_amount) // Round to nearest 1, clamp between min and max transfer amount
+ . = TRUE
+
+ if("import_config")
+ var/list/our_data = params["config"]
+ if(!islist(our_data))
+ return FALSE
+ var/list/new_recipes = list()
+ for(var/key, value in our_data)
+ if(istext(key) && islist(value))
+ for(var/list/steps in value)
+ if(istext(steps["id"]) && isnum(steps["amount"]))
+ new_recipes[key] += list(list("id" = steps["id"], "amount" = steps["amount"]))
+ if(length(new_recipes))
+ saved_recipes = new_recipes
+ . = TRUE
+
+ if("record_recipe")
+ recording_recipe = list()
+ . = TRUE
+
+ if("cancel_recording")
+ recording_recipe = null
+ . = TRUE
+
+ if("clear_recipes")
+ 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 hypo can dispense every chemical
+ if(!reagent_ids.Find(label))
+ to_chat(ui.user, span_warning("\The [src] cannot find ") + span_boldwarning(label) + span_warning("!"))
+ return
+ saved_recipes[name] = recording_recipe
+ recording_recipe = null
+ . = TRUE
+
+ if("remove_recipe")
+ var/recipe_name = params["recipe"]
+ // If we've selected the recipe we're deleting, un-select it!
+ if(selected_recipe_id == recipe_name)
+ selected_recipe_id = null
+ is_dispensing_recipe = FALSE
+ saved_recipes -= recipe_name
+ . = TRUE
+
+ if("select_recipe")
+ // Make sure we actually have a recipe saved with the given name before setting it!
+ var/recipe_name = params["recipe"]
+ var/selectedRecipe = saved_recipes[recipe_name]
+ if(!selectedRecipe)
+ to_chat(ui.user, span_warning("\The [src] cannot find the recipe ") + span_boldwarning(recipe_name) + span_warning("!"))
+ return
+ playsound(ui.user, 'sound/effects/pop.ogg', 50, 0)
+ balloon_alert(ui.user, "synthesizer is using macro: '[recipe_name]'")
+ is_dispensing_recipe = TRUE
+ selected_recipe_id = recipe_name
+ . = TRUE
+
+ if("set_chemical_search")
+ ui_chemical_search = params["uiChemicalSearch"]
+ . = TRUE
+
/obj/item/reagent_containers/borghypo/examine(mob/user)
. = ..()
if(get_dist(user, src) <= 2)
- var/datum/reagent/R = SSchemistry.chemical_reagents[reagent_ids[mode]]
- . += span_notice("It is currently producing [R.name] and has [reagent_volumes[reagent_ids[mode]]] out of [volume] units left.")
+ var/datum/reagent/current_reagent = SSchemistry.chemical_reagents[reagent_ids[mode]]
+ . += span_notice("It is currently producing [current_reagent.name] and has [reagent_volumes[reagent_ids[mode]]] out of [volume] units left.")
/obj/item/reagent_containers/borghypo/service
- name = "cyborg drink synthesizer"
- desc = "A portable drink dispencer."
+ name = "integrated drink synthesizer"
+ desc = "An inbuilt synthesizer capable of fabricating a broad variety of drinks."
icon = 'icons/obj/drinks.dmi'
icon_state = "shaker"
charge_cost = 20
recharge_time = 3
volume = 60
max_transfer_amount = 30
+ is_dispensing_drinks = TRUE
+ transfer_amounts = list(5, 10, 20, 30)
+ hypo_sound = 'sound/machines/reagent_dispense.ogg'
reagent_ids = list(REAGENT_ID_ALE,
REAGENT_ID_APPLEJUICE, //CHOMPADD it has literally every other type of juice..
REAGENT_ID_BEER,
@@ -192,16 +403,24 @@
if(!target.is_open_container() || !target.reagents)
return
- if(!reagent_volumes[reagent_ids[mode]])
- to_chat(user, span_notice("[src] is out of this reagent, give it some time to refill."))
- return
-
- if(!target.reagents.get_free_space())
- balloon_alert(user, "[target] is full!")
- return
-
- var/t = min(amount_per_transfer_from_this, reagent_volumes[reagent_ids[mode]])
- target.reagents.add_reagent(reagent_ids[mode], t)
- reagent_volumes[reagent_ids[mode]] -= t
- balloon_alert(user, "transfered [t] units to [target].")
+ var/result = try_injection(target.reagents, user)
+ switch(result)
+ if(BORGHYPO_STATUS_CONTAINERFULL)
+ if(is_dispensing_recipe)
+ balloon_alert(user, "\the [target] is too full to finish the recipe!")
+ else
+ balloon_alert(user, "\the [target] is full!")
+ if(BORGHYPO_STATUS_NOCHARGE)
+ if(is_dispensing_recipe)
+ balloon_alert(user, "not enough reagents to finish recipe '[selected_recipe_id]'!")
+ else
+ var/datum/reagent/empty_reagent = SSchemistry.chemical_reagents[reagent_ids[mode]]
+ balloon_alert(user, "not enough of reagent '[empty_reagent.name]'!")
+ if(BORGHYPO_STATUS_NORECIPE)
+ balloon_alert(user, "recipe '[selected_recipe_id]' not found!")
+ else
+ if(is_dispensing_recipe)
+ balloon_alert(user, "recipe '[selected_recipe_id]' dispensed to \the [target].")
+ else
+ balloon_alert(user, "[amount_per_transfer_from_this] units dispensed to \the [target].")
return
diff --git a/tgui/packages/tgui/interfaces/BorgHypo/BorgHypoChemicals.tsx b/tgui/packages/tgui/interfaces/BorgHypo/BorgHypoChemicals.tsx
new file mode 100644
index 0000000000..383742c824
--- /dev/null
+++ b/tgui/packages/tgui/interfaces/BorgHypo/BorgHypoChemicals.tsx
@@ -0,0 +1,33 @@
+import { useBackend } from 'tgui/backend';
+import { ChemDispenserChemicals } from '../ChemDispenser/ChemDispenserChemicals';
+import { BorgHypoSearch } from './BorgHypoSearch';
+import type { Data } from './types';
+
+export const BorgHypoChemicals = (props) => {
+ const { act, data } = useBackend();
+ const {
+ chemicals = [],
+ isDispensingRecipe,
+ selectedReagentId,
+ isDispensingDrinks,
+ recordingRecipe,
+ } = data;
+ const recording = !!recordingRecipe;
+ return (
+ {
+ if (recording || selectedReagentId !== reagentId) {
+ act('select_reagent', {
+ selectedReagentId: reagentId,
+ });
+ }
+ }}
+ buttons={}
+ chemicalButtonSelect={(reagentId) =>
+ !recording && selectedReagentId === reagentId && !isDispensingRecipe
+ }
+ />
+ );
+};
diff --git a/tgui/packages/tgui/interfaces/BorgHypo/BorgHypoRecipeDisplay.tsx b/tgui/packages/tgui/interfaces/BorgHypo/BorgHypoRecipeDisplay.tsx
new file mode 100644
index 0000000000..7145b6fc2a
--- /dev/null
+++ b/tgui/packages/tgui/interfaces/BorgHypo/BorgHypoRecipeDisplay.tsx
@@ -0,0 +1,46 @@
+import { useBackend } from 'tgui/backend';
+import { Box, Section, Stack } from 'tgui-core/components';
+import { formatUnits } from '../common/BeakerContents';
+import type { Data } from './types';
+
+export const BorgHypoRecipeDisplay = (props) => {
+ const { act, data } = useBackend();
+ const { recordingRecipe } = data;
+
+ const recording = !!recordingRecipe;
+
+ const recordedContents =
+ recording &&
+ recordingRecipe.map((r) => ({
+ id: r.id,
+ name: r.id.replace(/_/, ' '),
+ volume: r.amount,
+ }));
+
+ return (
+
+
+ Recording in progress...
+
+
+ )
+ }
+ >
+ {recording && (
+
+ {recordedContents.map((reagent, i) => (
+
+ {formatUnits(reagent.volume)} of {reagent.name}
+
+ ))}
+
+ )}
+
+ );
+};
diff --git a/tgui/packages/tgui/interfaces/BorgHypo/BorgHypoRecipes.tsx b/tgui/packages/tgui/interfaces/BorgHypo/BorgHypoRecipes.tsx
new file mode 100644
index 0000000000..c66d75971f
--- /dev/null
+++ b/tgui/packages/tgui/interfaces/BorgHypo/BorgHypoRecipes.tsx
@@ -0,0 +1,34 @@
+import { useBackend } from 'tgui/backend';
+import { Stack } from 'tgui-core/components';
+import { ChemDispenserRecipes } from '../ChemDispenser/ChemDispenserRecipes';
+import { BorgHypoRecipeDisplay } from './BorgHypoRecipeDisplay';
+import type { Data } from './types';
+
+export const BorgHypoRecipes = (props) => {
+ const { act, data } = useBackend();
+ const { recipes, isDispensingRecipe, selectedRecipeId, recordingRecipe } =
+ data;
+
+ return (
+
+
+ act('record_recipe')}
+ cancelAct={() => act('cancel_recording')}
+ saveAct={() => act('save_recording')}
+ clearAct={() => act('clear_recipes')}
+ dispenseAct={(recipe) => act('select_recipe', { recipe })}
+ removeAct={(recipe) => act('remove_recipe', { recipe })}
+ getDispenseButtonSelected={(recipe) => {
+ return isDispensingRecipe && selectedRecipeId === recipe;
+ }}
+ />
+
+
+
+
+
+ );
+};
diff --git a/tgui/packages/tgui/interfaces/BorgHypo/BorgHypoRecordingBlinker.tsx b/tgui/packages/tgui/interfaces/BorgHypo/BorgHypoRecordingBlinker.tsx
new file mode 100644
index 0000000000..343bd4a76c
--- /dev/null
+++ b/tgui/packages/tgui/interfaces/BorgHypo/BorgHypoRecordingBlinker.tsx
@@ -0,0 +1,37 @@
+import { useEffect, useState } from 'react';
+import { useBackend } from 'tgui/backend';
+import { Box, Icon, Stack } from 'tgui-core/components';
+import type { Data } from './types';
+
+export const BorgHypoRecordingBlinker = (props) => {
+ const { data } = useBackend();
+ const isRecording = !!data.recordingRecipe;
+
+ const [blink, setBlink] = useState(false);
+
+ useEffect(() => {
+ if (isRecording) {
+ const intervalId = setInterval(() => {
+ setBlink((v) => !v);
+ }, 1000);
+ return () => clearInterval(intervalId);
+ }
+ }, [isRecording]);
+
+ if (!isRecording) {
+ return null;
+ }
+
+ return (
+ <>
+
+
+
+
+
+ REC
+
+
+ >
+ );
+};
diff --git a/tgui/packages/tgui/interfaces/BorgHypo/BorgHypoSearch.tsx b/tgui/packages/tgui/interfaces/BorgHypo/BorgHypoSearch.tsx
new file mode 100644
index 0000000000..7cb6269643
--- /dev/null
+++ b/tgui/packages/tgui/interfaces/BorgHypo/BorgHypoSearch.tsx
@@ -0,0 +1,26 @@
+import { useBackend } from 'tgui/backend';
+import { Input, Stack } from 'tgui-core/components';
+import { BorgHypoRecordingBlinker } from './BorgHypoRecordingBlinker';
+import type { Data } from './types';
+
+export const BorgHypoSearch = (props) => {
+ const { act, data } = useBackend();
+ const { isDispensingDrinks } = data;
+ const uiChemicalsName = isDispensingDrinks ? 'drinks' : 'chemicals';
+ return (
+
+
+
+
+ act('set_chemical_search', {
+ uiChemicalSearch: input,
+ })
+ }
+ />
+
+
+ );
+};
diff --git a/tgui/packages/tgui/interfaces/BorgHypo/BorgHypoSettings.tsx b/tgui/packages/tgui/interfaces/BorgHypo/BorgHypoSettings.tsx
new file mode 100644
index 0000000000..16fbdcc68a
--- /dev/null
+++ b/tgui/packages/tgui/interfaces/BorgHypo/BorgHypoSettings.tsx
@@ -0,0 +1,18 @@
+import { useBackend } from 'tgui/backend';
+import { ChemDispenserSettings } from '../ChemDispenser/ChemDispenserSettings';
+import type { Data } from './types';
+
+export const BorgHypoSettings = (props) => {
+ const { act, data } = useBackend();
+ const { amount, minTransferAmount, maxTransferAmount, transferAmounts } =
+ data;
+ return (
+ act('set_amount', { amount: amt })}
+ />
+ );
+};
diff --git a/tgui/packages/tgui/interfaces/BorgHypo/index.tsx b/tgui/packages/tgui/interfaces/BorgHypo/index.tsx
new file mode 100644
index 0000000000..120f5024ad
--- /dev/null
+++ b/tgui/packages/tgui/interfaces/BorgHypo/index.tsx
@@ -0,0 +1,38 @@
+import { useBackend } from 'tgui/backend';
+import { Window } from 'tgui/layouts';
+import { Stack } from 'tgui-core/components';
+
+import { BorgHypoChemicals } from './BorgHypoChemicals';
+import { BorgHypoRecipes } from './BorgHypoRecipes';
+import { BorgHypoSettings } from './BorgHypoSettings';
+import type { Data } from './types';
+
+export const BorgHypo = (props) => {
+ const { data } = useBackend();
+ const { isDispensingDrinks, theme } = data;
+ return (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ );
+};
diff --git a/tgui/packages/tgui/interfaces/BorgHypo/types.ts b/tgui/packages/tgui/interfaces/BorgHypo/types.ts
new file mode 100644
index 0000000000..a5a011586a
--- /dev/null
+++ b/tgui/packages/tgui/interfaces/BorgHypo/types.ts
@@ -0,0 +1,18 @@
+import type { BooleanLike } from 'tgui-core/react';
+import type { Reagent, Recipe } from '../ChemDispenser/types';
+
+export type Data = {
+ amount: number;
+ transferAmounts: number[];
+ minTransferAmount: number;
+ maxTransferAmount: number;
+ chemicals: Reagent[];
+ selectedReagentId: string;
+ recipes: Record;
+ recordingRecipe: Recipe[];
+ isDispensingRecipe: BooleanLike;
+ selectedRecipeId: string;
+ uiChemicalSearch: string;
+ isDispensingDrinks: BooleanLike;
+ theme: string | null;
+};
diff --git a/tgui/packages/tgui/interfaces/ChemDispenser/ChemDispenserChemicals.tsx b/tgui/packages/tgui/interfaces/ChemDispenser/ChemDispenserChemicals.tsx
index 2e822529e4..1fe2af38b3 100644
--- a/tgui/packages/tgui/interfaces/ChemDispenser/ChemDispenserChemicals.tsx
+++ b/tgui/packages/tgui/interfaces/ChemDispenser/ChemDispenserChemicals.tsx
@@ -1,50 +1,61 @@
-import { useEffect, useState } from 'react';
+import { type ReactNode, useEffect, useState } from 'react';
import { useBackend } from 'tgui/backend';
import { Button, Icon, Section, Stack, Tooltip } from 'tgui-core/components';
+import type { BooleanLike } from 'tgui-core/react';
+import type { Data, Reagent } from './types';
-import type { Data } from './types';
-
-export const ChemDispenserChemicals = (props) => {
- const { act, data } = useBackend();
- const { chemicals = [] } = data;
+export const ChemDispenserChemicals = (props: {
+ sectionTitle: string;
+ chemicals: Reagent[];
+ /** Called when the user clicks on a reagent dispense button. Arg is the ID of the button's reagent. */
+ dispenseAct: (reagentId: string) => void;
+ /** Optional callback that returns whether or not a reagent dispense button will appear "activated". Arg is the ID of the button's reagent. */
+ chemicalButtonSelect?: (reagentId: string) => BooleanLike;
+ /** Extra UI elements that will appear within the header of the chemical UI. */
+ buttons: ReactNode;
+}) => {
+ const {
+ chemicals,
+ sectionTitle,
+ dispenseAct,
+ chemicalButtonSelect,
+ buttons,
+ } = props;
const flexFillers: boolean[] = [];
- for (let i = 0; i < (chemicals.length + 1) % 3; i++) {
- flexFillers.push(true);
- }
+ const sortedChemicals: Reagent[] = chemicals;
+ sortedChemicals.sort((a, b) => a.name.localeCompare(b.name));
return (
- }
- >
+
- {chemicals.map((c, i) => (
-
+ {sortedChemicals.map((c, i) => (
+
))}
- {flexFillers.map((_, i) => (
-
- ))}
);
};
-const RecordingBlinker = (props) => {
+export const RecordingBlinker = (props) => {
const { data } = useBackend();
const recording = !!data.recordingRecipe;
diff --git a/tgui/packages/tgui/interfaces/ChemDispenser/ChemDispenserRecipes.tsx b/tgui/packages/tgui/interfaces/ChemDispenser/ChemDispenserRecipes.tsx
index 44c63dd119..6c970b3615 100644
--- a/tgui/packages/tgui/interfaces/ChemDispenser/ChemDispenserRecipes.tsx
+++ b/tgui/packages/tgui/interfaces/ChemDispenser/ChemDispenserRecipes.tsx
@@ -1,13 +1,42 @@
-import { useBackend } from 'tgui/backend';
import { Box, Button, Section, Stack } from 'tgui-core/components';
+import type { BooleanLike } from 'tgui-core/react';
+import { handleImportData } from '../PlushieEditor/function';
+import { exportRecipes } from './functions';
+import type { Recipe } from './types';
-import type { Data } from './types';
+export const ChemDispenserRecipes = (props: {
+ /** Associated list of saved recipe macros. */
+ recipes: Record;
+ /** The current recipe macro that's being recorded, if any. We assume we aren't recording a recipe if this is undefined! */
+ recordingRecipe: Recipe[];
+ /** Called when the user attempts to start a recipe recording. */
+ recordAct: () => void;
+ /** Called when the user attempts to cancel a recipe recording. */
+ cancelAct: () => void;
+ /** Called when the user attempts to save a recipe recording. */
+ saveAct: () => void;
+ /** Called when the user attempts to clear all recipe recordings. */
+ clearAct: () => void;
+ /** Called when the user attempts to use a recipe macro. */
+ dispenseAct: (recipe: string) => void;
+ /** Called when a recipe dispense button is checking whether or not it will appear "selected". Arg is the ID of the button's reagent. Defaults to false if undefined. */
+ getDispenseButtonSelected?: (recipe: string) => BooleanLike;
+ /** Called when the user attempts to remove a recipe macro. */
+ removeAct: (recipe: string) => void;
+}) => {
+ const {
+ recipes,
+ recordingRecipe,
+ recordAct,
+ cancelAct,
+ saveAct,
+ clearAct,
+ dispenseAct,
+ getDispenseButtonSelected,
+ removeAct,
+ } = props;
-export const ChemDispenserRecipes = (props) => {
- const { act, data } = useBackend();
- const { recipes, recordingRecipe } = data;
-
- const recording: boolean = !!recordingRecipe;
+ const isRecording: boolean = !!recordingRecipe;
const recipeData = Object.keys(recipes).sort();
return (
@@ -17,51 +46,61 @@ export const ChemDispenserRecipes = (props) => {
scrollable
buttons={
- {!recording && (
+ {!isRecording && (
-
)}
- {recording && (
+ {isRecording && (
- act('cancel_recording')}
- >
+
Discard
)}
- {recording && (
+ {isRecording && (
- act('save_recording')}
- >
+
Save
)}
- {!recording && (
-
- act('clear_recipes')}
- >
- Clear All
-
-
+ {!isRecording && (
+ <>
+
+ handleImportData(files)}
+ />
+
+
+ exportRecipes(recipes)}
+ />
+
+
+
+ Clear All
+
+
+ >
)}
}
>
- {recording && (
+ {isRecording && (
<>
Recording In Progress...
@@ -91,7 +130,12 @@ export const ChemDispenserRecipes = (props) => {
act('dispense_recipe', { recipe })}
+ selected={
+ getDispenseButtonSelected
+ ? getDispenseButtonSelected(recipe)
+ : undefined
+ }
+ onClick={() => dispenseAct(recipe)}
>
{recipe}
@@ -102,7 +146,7 @@ export const ChemDispenserRecipes = (props) => {
confirmIcon="triangle-exclamation"
confirmContent={''}
color="bad"
- onClick={() => act('remove_recipe', { recipe })}
+ onClick={() => removeAct(recipe)}
/>
diff --git a/tgui/packages/tgui/interfaces/ChemDispenser/ChemDispenserSettings.tsx b/tgui/packages/tgui/interfaces/ChemDispenser/ChemDispenserSettings.tsx
index bdf025a5d9..d2fcabdb8f 100644
--- a/tgui/packages/tgui/interfaces/ChemDispenser/ChemDispenserSettings.tsx
+++ b/tgui/packages/tgui/interfaces/ChemDispenser/ChemDispenserSettings.tsx
@@ -1,4 +1,3 @@
-import { useBackend } from 'tgui/backend';
import {
Button,
LabeledList,
@@ -7,28 +6,35 @@ import {
Stack,
} from 'tgui-core/components';
-import { dispenseAmounts } from './constants';
-import type { Data } from './types';
-
-export const ChemDispenserSettings = (props) => {
- const { act, data } = useBackend();
- const { amount } = data;
+export const ChemDispenserSettings = (props: {
+ /** The dispense amount the user has currently selected. */
+ selectedAmount: number;
+ /** Available amounts for this dispenser to use. */
+ availableAmounts: number[];
+ /** The minimum allowed selectable amount. Used for the slider UI element. */
+ minAmount: number;
+ /** The maximum allowed selectable amount. Used for the slider UI element. */
+ maxAmount: number;
+ /** Called when the user tries to change the dispensed amount. Arg is the amount the user is trying to set it to. */
+ amountAct: (amount: number) => void;
+}) => {
+ const { selectedAmount, availableAmounts, minAmount, maxAmount, amountAct } =
+ props;
return (
-
+
- {dispenseAmounts.map((a, i) => (
+ {availableAmounts.map((a, i) => (
- act('amount', {
- amount: a,
- })
- }
+ onClick={() => amountAct(a)}
>
{`${a}u`}
@@ -40,14 +46,10 @@ export const ChemDispenserSettings = (props) => {
- act('amount', {
- amount: value,
- })
- }
+ value={selectedAmount}
+ minValue={minAmount}
+ maxValue={maxAmount}
+ onChange={(e, value) => amountAct(value)}
/>
diff --git a/tgui/packages/tgui/interfaces/ChemDispenser/functions.ts b/tgui/packages/tgui/interfaces/ChemDispenser/functions.ts
new file mode 100644
index 0000000000..ff34bbede3
--- /dev/null
+++ b/tgui/packages/tgui/interfaces/ChemDispenser/functions.ts
@@ -0,0 +1,12 @@
+import { getCurrentTimestamp } from '../VorePanelExport/VorePanelExportTimestamp';
+import type { Recipe } from './types';
+
+export function exportRecipes(recipes: Record) {
+ const blob = new Blob([JSON.stringify(recipes)], {
+ type: 'application/json',
+ });
+
+ const datesegment = getCurrentTimestamp();
+ const filename = `ChemRecipes${datesegment}.json`;
+ Byond.saveBlob(blob, filename, '.json');
+}
diff --git a/tgui/packages/tgui/interfaces/ChemDispenser/index.tsx b/tgui/packages/tgui/interfaces/ChemDispenser/index.tsx
index 036f06fa4b..7b9bbd5aa3 100644
--- a/tgui/packages/tgui/interfaces/ChemDispenser/index.tsx
+++ b/tgui/packages/tgui/interfaces/ChemDispenser/index.tsx
@@ -3,13 +3,18 @@ import { Window } from 'tgui/layouts';
import { Stack } from 'tgui-core/components';
import { ChemDispenserBeaker } from './ChemDispenserBeaker';
-import { ChemDispenserChemicals } from './ChemDispenserChemicals';
+import {
+ ChemDispenserChemicals,
+ RecordingBlinker,
+} from './ChemDispenserChemicals';
import { ChemDispenserRecipes } from './ChemDispenserRecipes';
import { ChemDispenserSettings } from './ChemDispenserSettings';
+import { dispenseAmounts } from './constants';
import type { Data } from './types';
export const ChemDispenser = (props) => {
- const { data } = useBackend();
+ const { data, act } = useBackend();
+ const { recipes, recordingRecipe, glass, chemicals, amount } = data;
return (
@@ -20,15 +25,45 @@ export const ChemDispenser = (props) => {
-
+
+ act('amount', {
+ amount: amt,
+ })
+ }
+ />
-
+ act('record_recipe')}
+ cancelAct={() => act('cancel_recording')}
+ saveAct={() => act('save_recording')}
+ clearAct={() => act('clear_recipes')}
+ dispenseAct={(recipe) =>
+ act('dispense_recipe', { recipe })
+ }
+ removeAct={(recipe) => act('remove_recipe', { recipe })}
+ />
-
+
+ act('dispense', { reagent: reagentId })
+ }
+ buttons={}
+ />
diff --git a/tgui/packages/tgui/interfaces/ChemDispenser/types.ts b/tgui/packages/tgui/interfaces/ChemDispenser/types.ts
index 09eb0c2daa..b88fcf0322 100644
--- a/tgui/packages/tgui/interfaces/ChemDispenser/types.ts
+++ b/tgui/packages/tgui/interfaces/ChemDispenser/types.ts
@@ -9,12 +9,12 @@ export type Data = {
amount: number;
isBeakerLoaded: BooleanLike;
glass: BooleanLike;
- beakerContents: reagent[];
+ beakerContents: Reagent[];
beakerCurrentVolume: number | null;
beakerMaxVolume: number | null;
- chemicals: reagent[];
+ chemicals: Reagent[];
recipes: Record;
recordingRecipe: Recipe[];
};
-type reagent = { name: string; id: string; volume: number };
+export type Reagent = { name: string; id: string; volume: number };
diff --git a/tgui/packages/tgui/interfaces/ModifyRobot/types.ts b/tgui/packages/tgui/interfaces/ModifyRobot/types.ts
index 7008dd7817..65fc3af5ef 100644
--- a/tgui/packages/tgui/interfaces/ModifyRobot/types.ts
+++ b/tgui/packages/tgui/interfaces/ModifyRobot/types.ts
@@ -38,7 +38,7 @@ export type Data = {
law_sets: law_pack[];
active_ais: DropdownEntry[];
selected_ai: string | null;
- theme?: string;
+ theme: string | null;
};
export type DropdownEntry = {
diff --git a/tgui/packages/tgui/interfaces/RobotChoose/types.ts b/tgui/packages/tgui/interfaces/RobotChoose/types.ts
index 6df4494734..4655e977fe 100644
--- a/tgui/packages/tgui/interfaces/RobotChoose/types.ts
+++ b/tgui/packages/tgui/interfaces/RobotChoose/types.ts
@@ -4,7 +4,7 @@ export type Data = {
possible_sprites?: spriteOption[];
currentName: string;
isDefaultName: boolean;
- theme?: string;
+ theme: string | null;
selected_module?: string;
sprite_datum?: string | null;
sprite_datum_class?: string | null;
diff --git a/tgui/packages/tgui/interfaces/RobotDecals.tsx b/tgui/packages/tgui/interfaces/RobotDecals.tsx
index 3202d34b20..8cddea6498 100644
--- a/tgui/packages/tgui/interfaces/RobotDecals.tsx
+++ b/tgui/packages/tgui/interfaces/RobotDecals.tsx
@@ -5,7 +5,7 @@ import { Box, Button, Input, Section, Stack } from 'tgui-core/components';
import { createSearch } from 'tgui-core/string';
type Data = {
- theme?: string;
+ theme: string | null;
all_decals?: string[] | null;
all_animations?: string[] | null;
active_decals: string[];
diff --git a/tgui/packages/tgui/interfaces/RobotSleeper/types.ts b/tgui/packages/tgui/interfaces/RobotSleeper/types.ts
index 91b206c097..001c674978 100644
--- a/tgui/packages/tgui/interfaces/RobotSleeper/types.ts
+++ b/tgui/packages/tgui/interfaces/RobotSleeper/types.ts
@@ -2,7 +2,7 @@ import type { BooleanLike } from 'tgui-core/react';
export type Data = {
name?: string;
- theme?: string;
+ theme: string | null;
chems?: RobotChem[];
our_patient: Patient | null;
diff --git a/tgui/packages/tgui/interfaces/Robotact/types.ts b/tgui/packages/tgui/interfaces/Robotact/types.ts
index 9e5a43f536..57ff18c583 100644
--- a/tgui/packages/tgui/interfaces/Robotact/types.ts
+++ b/tgui/packages/tgui/interfaces/Robotact/types.ts
@@ -31,7 +31,7 @@ export type Data = {
max_health: number;
light_color: string;
- theme?: string;
+ theme: string | null;
// Modules
modules_static: Module[];
diff --git a/tgui/packages/tgui/interfaces/common/BeakerContents.tsx b/tgui/packages/tgui/interfaces/common/BeakerContents.tsx
index 4095da20c6..943920f8fb 100644
--- a/tgui/packages/tgui/interfaces/common/BeakerContents.tsx
+++ b/tgui/packages/tgui/interfaces/common/BeakerContents.tsx
@@ -1,6 +1,6 @@
import { Box, Stack } from 'tgui-core/components';
-const formatUnits = (a) => `${a} unit${a === 1 ? '' : 's'}`;
+export const formatUnits = (a) => `${a} unit${a === 1 ? '' : 's'}`;
/**
* Displays a beaker's contents