mirror of
https://github.com/CHOMPStation2/CHOMPStation2.git
synced 2025-12-12 19:22:56 +00:00
[MIRROR] Overhauls borg hypos to work like chem dispensers (#11931)
Co-authored-by: Ryumi <ghosttehspychecka@gmail.com> Co-authored-by: Kashargul <144968721+Kashargul@users.noreply.github.com>
This commit is contained in:
committed by
GitHub
parent
e13cfbc71c
commit
38f998779a
@@ -567,6 +567,12 @@ GLOBAL_LIST_INIT(all_volume_channels, list(
|
|||||||
#define SHELTER_DEPLOY_ANCHORED_OBJECTS "anchored objects"
|
#define SHELTER_DEPLOY_ANCHORED_OBJECTS "anchored objects"
|
||||||
#define SHELTER_DEPLOY_SHIP_SPACE "ship not in space"
|
#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_SECURITY "Security"
|
||||||
#define PTO_MEDICAL "Medical"
|
#define PTO_MEDICAL "Medical"
|
||||||
#define PTO_ENGINEERING "Engineering"
|
#define PTO_ENGINEERING "Engineering"
|
||||||
|
|||||||
@@ -33,12 +33,7 @@
|
|||||||
chassis.visible_message(span_warning("[chassis] fires [src]!"))
|
chassis.visible_message(span_warning("[chassis] fires [src]!"))
|
||||||
occupant_message(span_warning("You fire [src]!"))
|
occupant_message(span_warning("You fire [src]!"))
|
||||||
src.mecha_log_message("Fired from [src], targeting [target].")
|
src.mecha_log_message("Fired from [src], targeting [target].")
|
||||||
var/target_for_log = "unknown"
|
add_attack_logs(chassis.occupant,target,"Fired exosuit weapon [src.name] (MANUAL)")
|
||||||
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)")
|
|
||||||
|
|
||||||
for(var/i = 1 to min(projectiles, projectiles_per_shot))
|
for(var/i = 1 to min(projectiles, projectiles_per_shot))
|
||||||
var/turf/aimloc = targloc
|
var/turf/aimloc = targloc
|
||||||
|
|||||||
@@ -150,16 +150,18 @@
|
|||||||
if("import_config")
|
if("import_config")
|
||||||
. = TRUE
|
. = TRUE
|
||||||
var/our_data = params["config"]
|
var/our_data = params["config"]
|
||||||
var/imported_color = sanitize_hexcolor(our_data["base_color"])
|
base_color = sanitize_hexcolor(our_data["base_color"])
|
||||||
if(imported_color)
|
var/new_name = sanitize_name(our_data["name"])
|
||||||
base_color = imported_color
|
if(new_name)
|
||||||
set_new_name(our_data["name"])
|
set_new_name(new_name)
|
||||||
added_overlays.Cut()
|
added_overlays.Cut()
|
||||||
if(!possible_overlays)
|
if(!possible_overlays)
|
||||||
return
|
return
|
||||||
for(var/overlay in our_data["overlays"])
|
for(var/overlay in our_data["overlays"])
|
||||||
if(possible_overlays.Find(overlay["icon_state"]))
|
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()
|
update_icon()
|
||||||
|
|
||||||
if("clear")
|
if("clear")
|
||||||
|
|||||||
@@ -249,9 +249,7 @@
|
|||||||
UNTYPED_LIST_ADD(robot_chems, list("id" = possible_reagent.id, "name" = possible_reagent.name))
|
UNTYPED_LIST_ADD(robot_chems, list("id" = possible_reagent.id, "name" = possible_reagent.name))
|
||||||
|
|
||||||
data["name"] = name
|
data["name"] = name
|
||||||
var/robot_theme = robot_user.get_ui_theme()
|
data["theme"] = robot_user.get_ui_theme()
|
||||||
if(robot_theme)
|
|
||||||
data["theme"] = robot_theme
|
|
||||||
data["chems"] = robot_chems
|
data["chems"] = robot_chems
|
||||||
return data
|
return data
|
||||||
|
|
||||||
|
|||||||
@@ -1217,7 +1217,7 @@
|
|||||||
if(cell.charge - (amount + lower_limit) <= 0)
|
if(cell.charge - (amount + lower_limit) <= 0)
|
||||||
return FALSE
|
return FALSE
|
||||||
|
|
||||||
cell.charge -= amount
|
cell.use(amount)
|
||||||
return TRUE
|
return TRUE
|
||||||
|
|
||||||
/mob/living/silicon/robot/binarycheck()
|
/mob/living/silicon/robot/binarycheck()
|
||||||
|
|||||||
@@ -49,10 +49,8 @@
|
|||||||
var/mob/living/silicon/robot/R = host
|
var/mob/living/silicon/robot/R = host
|
||||||
|
|
||||||
data["module_name"] = R.module ? "[R.module]" : null
|
data["module_name"] = R.module ? "[R.module]" : null
|
||||||
if(R.emagged)
|
|
||||||
data["theme"] = "syndicate"
|
data["theme"] = R.get_ui_theme()
|
||||||
else if (R.ui_theme)
|
|
||||||
data["theme"] = R.ui_theme
|
|
||||||
|
|
||||||
if(!R.module)
|
if(!R.module)
|
||||||
return data
|
return data
|
||||||
|
|||||||
@@ -25,9 +25,7 @@
|
|||||||
var/mob/living/silicon/robot/R = host
|
var/mob/living/silicon/robot/R = host
|
||||||
data["active_decals"] = R.robotdecal_on
|
data["active_decals"] = R.robotdecal_on
|
||||||
|
|
||||||
var/robot_theme = R.get_ui_theme()
|
data["theme"] = R.get_ui_theme()
|
||||||
if(robot_theme)
|
|
||||||
data["theme"] = robot_theme
|
|
||||||
|
|
||||||
return data
|
return data
|
||||||
|
|
||||||
|
|||||||
@@ -57,10 +57,7 @@
|
|||||||
modules |= module_name
|
modules |= module_name
|
||||||
data["possible_modules"] = modules
|
data["possible_modules"] = modules
|
||||||
data["mind_name"] = R.mind.name
|
data["mind_name"] = R.mind.name
|
||||||
if(R.emagged)
|
data["theme"] = R.get_ui_theme()
|
||||||
data["theme"] = "syndicate"
|
|
||||||
else if (R.ui_theme)
|
|
||||||
data["theme"] = R.ui_theme
|
|
||||||
|
|
||||||
return data
|
return data
|
||||||
|
|
||||||
|
|||||||
@@ -202,6 +202,20 @@
|
|||||||
container = null
|
container = null
|
||||||
. = TRUE
|
. = 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")
|
if("record_recipe")
|
||||||
recording_recipe = list()
|
recording_recipe = list()
|
||||||
. = TRUE
|
. = TRUE
|
||||||
@@ -260,7 +274,7 @@
|
|||||||
var/amount_actually_dispensed = C.reagents.trans_to(container, dispense_amount)
|
var/amount_actually_dispensed = C.reagents.trans_to(container, dispense_amount)
|
||||||
if(dispense_amount != amount_actually_dispensed)
|
if(dispense_amount != amount_actually_dispensed)
|
||||||
visible_message(span_warning("[src] buzzes."), span_warning("You hear a faint buzz."))
|
visible_message(span_warning("[src] buzzes."), span_warning("You hear a faint buzz."))
|
||||||
to_chat(ui.user, span_warning("[src] was only able to dispense [amount_actually_dispensed]u out of [dispense_amount]u requested of <b>[label]</b>!"))
|
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 <b>[label]</b>!"))
|
||||||
playsound(src, 'sound/machines/buzz-two.ogg', 50, TRUE)
|
playsound(src, 'sound/machines/buzz-two.ogg', 50, TRUE)
|
||||||
break
|
break
|
||||||
else
|
else
|
||||||
|
|||||||
@@ -5,18 +5,39 @@
|
|||||||
item_state = "hypo"
|
item_state = "hypo"
|
||||||
icon_state = "borghypo"
|
icon_state = "borghypo"
|
||||||
amount_per_transfer_from_this = 5
|
amount_per_transfer_from_this = 5
|
||||||
|
min_transfer_amount = 1
|
||||||
volume = 30
|
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/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/charge_tick = 0
|
||||||
var/recharge_time = 5 //Time it takes for shots to recharge (in seconds)
|
/// Time it takes for shots to recharge (in seconds)
|
||||||
var/bypass_protection = FALSE // If true, can inject through things like spacesuits and armor.
|
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_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()
|
var/list/reagent_volumes = list()
|
||||||
|
/// Associated list of the names of each of our reagents. Indexed via `mode`.
|
||||||
var/list/reagent_names = list()
|
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
|
/obj/item/reagent_containers/borghypo/surgeon
|
||||||
reagent_ids = list(REAGENT_ID_INAPROVALINE, REAGENT_ID_DEXALIN, REAGENT_ID_TRICORDRAZINE, REAGENT_ID_SPACEACILLIN, REAGENT_ID_OXYCODONE)
|
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.
|
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)
|
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)
|
/obj/item/reagent_containers/borghypo/Initialize(mapload)
|
||||||
. = ..()
|
. = ..()
|
||||||
|
|
||||||
for(var/T in reagent_ids)
|
for(var/T in reagent_ids)
|
||||||
reagent_volumes[T] = volume
|
reagent_volumes[T] = volume
|
||||||
var/datum/reagent/R = SSchemistry.chemical_reagents[T]
|
var/datum/reagent/hypo_reagent = SSchemistry.chemical_reagents[T]
|
||||||
reagent_names += R.name
|
reagent_names += hypo_reagent.name
|
||||||
|
|
||||||
START_PROCESSING(SSobj, src)
|
START_PROCESSING(SSobj, src)
|
||||||
|
|
||||||
@@ -54,14 +116,12 @@
|
|||||||
charge_tick = 0
|
charge_tick = 0
|
||||||
|
|
||||||
if(isrobot(loc))
|
if(isrobot(loc))
|
||||||
var/mob/living/silicon/robot/R = loc
|
var/mob/living/silicon/robot/robot_user = loc
|
||||||
if(R && R.cell)
|
if(robot_user && robot_user.cell)
|
||||||
for(var/T in reagent_ids)
|
for(var/T in reagent_ids)
|
||||||
if(reagent_volumes[T] < volume)
|
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
|
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
|
return 1
|
||||||
|
|
||||||
@@ -69,10 +129,6 @@
|
|||||||
if(!istype(M))
|
if(!istype(M))
|
||||||
return
|
return
|
||||||
|
|
||||||
if(!reagent_volumes[reagent_ids[mode]])
|
|
||||||
balloon_alert(user, "the injector is empty.")
|
|
||||||
return
|
|
||||||
|
|
||||||
var/mob/living/carbon/human/H = M
|
var/mob/living/carbon/human/H = M
|
||||||
if(istype(H))
|
if(istype(H))
|
||||||
var/obj/item/organ/external/affected = H.get_organ(user.zone_sel.selecting)
|
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))
|
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)
|
if(M.reagents)
|
||||||
var/t = min(amount_per_transfer_from_this, reagent_volumes[reagent_ids[mode]])
|
var/reagent_id = reagent_ids[mode]
|
||||||
M.reagents.add_reagent(reagent_ids[mode], t)
|
var/amount_to_add = min(amount_per_transfer_from_this, reagent_volumes[reagent_id])
|
||||||
reagent_volumes[reagent_ids[mode]] -= t
|
var/result = try_injection(M.reagents, user)
|
||||||
add_attack_logs(user, M, "Borg injected with [reagent_ids[mode]]")
|
if(is_dispensing_recipe)
|
||||||
to_chat(user, span_notice("[t] units injected. [reagent_volumes[reagent_ids[mode]]] units remaining."))
|
// 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
|
return
|
||||||
|
|
||||||
/obj/item/reagent_containers/borghypo/attack_self(mob/user as mob) //Change the mode
|
/obj/item/reagent_containers/borghypo/attack_self(mob/user as mob) //Change the mode
|
||||||
var/t
|
tgui_interact(user)
|
||||||
for(var/i = 1 to reagent_ids.len)
|
|
||||||
if(t)
|
|
||||||
t += ", "
|
|
||||||
if(mode == i)
|
|
||||||
t += span_bold("[reagent_names[i]]")
|
|
||||||
else
|
|
||||||
t += "<a href='byond://?src=\ref[src];reagent=[reagent_ids[i]]'>[reagent_names[i]]</a>"
|
|
||||||
t = "Available reagents: [t]."
|
|
||||||
to_chat(user,span_infoplain(t))
|
|
||||||
|
|
||||||
return
|
return
|
||||||
|
|
||||||
|
/* No longer necessary because we use TGUI for this now!
|
||||||
/obj/item/reagent_containers/borghypo/Topic(var/href, var/list/href_list)
|
/obj/item/reagent_containers/borghypo/Topic(var/href, var/list/href_list)
|
||||||
if(href_list["reagent"])
|
if(href_list["reagent"])
|
||||||
var/t = reagent_ids.Find(href_list["reagent"])
|
var/t = reagent_ids.Find(href_list["reagent"])
|
||||||
if(t)
|
if(t)
|
||||||
playsound(src, 'sound/effects/pop.ogg', 50, 0)
|
playsound(src, 'sound/effects/pop.ogg', 50, 0)
|
||||||
mode = t
|
mode = t
|
||||||
var/datum/reagent/R = SSchemistry.chemical_reagents[reagent_ids[mode]]
|
var/datum/reagent/new_reagent = SSchemistry.chemical_reagents[reagent_ids[mode]]
|
||||||
balloon_alert(usr, "synthesizer is now producing '[R.name]'")
|
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)
|
/obj/item/reagent_containers/borghypo/examine(mob/user)
|
||||||
. = ..()
|
. = ..()
|
||||||
if(get_dist(user, src) <= 2)
|
if(get_dist(user, src) <= 2)
|
||||||
var/datum/reagent/R = SSchemistry.chemical_reagents[reagent_ids[mode]]
|
var/datum/reagent/current_reagent = 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.")
|
. += 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
|
/obj/item/reagent_containers/borghypo/service
|
||||||
name = "cyborg drink synthesizer"
|
name = "integrated drink synthesizer"
|
||||||
desc = "A portable drink dispencer."
|
desc = "An inbuilt synthesizer capable of fabricating a broad variety of drinks."
|
||||||
icon = 'icons/obj/drinks.dmi'
|
icon = 'icons/obj/drinks.dmi'
|
||||||
icon_state = "shaker"
|
icon_state = "shaker"
|
||||||
charge_cost = 20
|
charge_cost = 20
|
||||||
recharge_time = 3
|
recharge_time = 3
|
||||||
volume = 60
|
volume = 60
|
||||||
max_transfer_amount = 30
|
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_ids = list(REAGENT_ID_ALE,
|
||||||
REAGENT_ID_APPLEJUICE, //CHOMPADD it has literally every other type of juice..
|
REAGENT_ID_APPLEJUICE, //CHOMPADD it has literally every other type of juice..
|
||||||
REAGENT_ID_BEER,
|
REAGENT_ID_BEER,
|
||||||
@@ -192,16 +403,24 @@
|
|||||||
if(!target.is_open_container() || !target.reagents)
|
if(!target.is_open_container() || !target.reagents)
|
||||||
return
|
return
|
||||||
|
|
||||||
if(!reagent_volumes[reagent_ids[mode]])
|
var/result = try_injection(target.reagents, user)
|
||||||
to_chat(user, span_notice("[src] is out of this reagent, give it some time to refill."))
|
switch(result)
|
||||||
return
|
if(BORGHYPO_STATUS_CONTAINERFULL)
|
||||||
|
if(is_dispensing_recipe)
|
||||||
if(!target.reagents.get_free_space())
|
balloon_alert(user, "\the [target] is too full to finish the recipe!")
|
||||||
balloon_alert(user, "[target] is full!")
|
else
|
||||||
return
|
balloon_alert(user, "\the [target] is full!")
|
||||||
|
if(BORGHYPO_STATUS_NOCHARGE)
|
||||||
var/t = min(amount_per_transfer_from_this, reagent_volumes[reagent_ids[mode]])
|
if(is_dispensing_recipe)
|
||||||
target.reagents.add_reagent(reagent_ids[mode], t)
|
balloon_alert(user, "not enough reagents to finish recipe '[selected_recipe_id]'!")
|
||||||
reagent_volumes[reagent_ids[mode]] -= t
|
else
|
||||||
balloon_alert(user, "transfered [t] units to [target].")
|
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
|
return
|
||||||
|
|||||||
33
tgui/packages/tgui/interfaces/BorgHypo/BorgHypoChemicals.tsx
Normal file
33
tgui/packages/tgui/interfaces/BorgHypo/BorgHypoChemicals.tsx
Normal file
@@ -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<Data>();
|
||||||
|
const {
|
||||||
|
chemicals = [],
|
||||||
|
isDispensingRecipe,
|
||||||
|
selectedReagentId,
|
||||||
|
isDispensingDrinks,
|
||||||
|
recordingRecipe,
|
||||||
|
} = data;
|
||||||
|
const recording = !!recordingRecipe;
|
||||||
|
return (
|
||||||
|
<ChemDispenserChemicals
|
||||||
|
chemicals={chemicals}
|
||||||
|
sectionTitle={isDispensingDrinks ? 'Drinks' : 'Chemicals'}
|
||||||
|
dispenseAct={(reagentId) => {
|
||||||
|
if (recording || selectedReagentId !== reagentId) {
|
||||||
|
act('select_reagent', {
|
||||||
|
selectedReagentId: reagentId,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
buttons={<BorgHypoSearch />}
|
||||||
|
chemicalButtonSelect={(reagentId) =>
|
||||||
|
!recording && selectedReagentId === reagentId && !isDispensingRecipe
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -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<Data>();
|
||||||
|
const { recordingRecipe } = data;
|
||||||
|
|
||||||
|
const recording = !!recordingRecipe;
|
||||||
|
|
||||||
|
const recordedContents =
|
||||||
|
recording &&
|
||||||
|
recordingRecipe.map((r) => ({
|
||||||
|
id: r.id,
|
||||||
|
name: r.id.replace(/_/, ' '),
|
||||||
|
volume: r.amount,
|
||||||
|
}));
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Section
|
||||||
|
title="Recipe Creation"
|
||||||
|
fill
|
||||||
|
scrollable
|
||||||
|
buttons={
|
||||||
|
!recording ? null : (
|
||||||
|
<Stack>
|
||||||
|
<Box color="Bad" inline bold>
|
||||||
|
Recording in progress...
|
||||||
|
</Box>
|
||||||
|
</Stack>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
>
|
||||||
|
{recording && (
|
||||||
|
<Stack align="start" justify="space-between" direction="column">
|
||||||
|
{recordedContents.map((reagent, i) => (
|
||||||
|
<Stack.Item key={i} color="label">
|
||||||
|
{formatUnits(reagent.volume)} of {reagent.name}
|
||||||
|
</Stack.Item>
|
||||||
|
))}
|
||||||
|
</Stack>
|
||||||
|
)}
|
||||||
|
</Section>
|
||||||
|
);
|
||||||
|
};
|
||||||
34
tgui/packages/tgui/interfaces/BorgHypo/BorgHypoRecipes.tsx
Normal file
34
tgui/packages/tgui/interfaces/BorgHypo/BorgHypoRecipes.tsx
Normal file
@@ -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<Data>();
|
||||||
|
const { recipes, isDispensingRecipe, selectedRecipeId, recordingRecipe } =
|
||||||
|
data;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Stack vertical fill>
|
||||||
|
<Stack.Item basis="70%">
|
||||||
|
<ChemDispenserRecipes
|
||||||
|
recipes={recipes}
|
||||||
|
recordingRecipe={recordingRecipe}
|
||||||
|
recordAct={() => 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;
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Stack.Item>
|
||||||
|
<Stack.Item basis="30%">
|
||||||
|
<BorgHypoRecipeDisplay />
|
||||||
|
</Stack.Item>
|
||||||
|
</Stack>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -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<Data>();
|
||||||
|
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 (
|
||||||
|
<>
|
||||||
|
<Stack.Item>
|
||||||
|
<Icon mt={0.7} color="bad" name={blink ? 'circle-o' : 'circle'} />
|
||||||
|
</Stack.Item>
|
||||||
|
<Stack.Item>
|
||||||
|
<Box color="bad" inline bold>
|
||||||
|
REC
|
||||||
|
</Box>
|
||||||
|
</Stack.Item>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
26
tgui/packages/tgui/interfaces/BorgHypo/BorgHypoSearch.tsx
Normal file
26
tgui/packages/tgui/interfaces/BorgHypo/BorgHypoSearch.tsx
Normal file
@@ -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<Data>();
|
||||||
|
const { isDispensingDrinks } = data;
|
||||||
|
const uiChemicalsName = isDispensingDrinks ? 'drinks' : 'chemicals';
|
||||||
|
return (
|
||||||
|
<Stack align="baseline">
|
||||||
|
<BorgHypoRecordingBlinker />
|
||||||
|
<Stack.Item>
|
||||||
|
<Input
|
||||||
|
width="150px"
|
||||||
|
placeholder={`Search ${uiChemicalsName}...`}
|
||||||
|
onChange={(input) =>
|
||||||
|
act('set_chemical_search', {
|
||||||
|
uiChemicalSearch: input,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</Stack.Item>
|
||||||
|
</Stack>
|
||||||
|
);
|
||||||
|
};
|
||||||
18
tgui/packages/tgui/interfaces/BorgHypo/BorgHypoSettings.tsx
Normal file
18
tgui/packages/tgui/interfaces/BorgHypo/BorgHypoSettings.tsx
Normal file
@@ -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<Data>();
|
||||||
|
const { amount, minTransferAmount, maxTransferAmount, transferAmounts } =
|
||||||
|
data;
|
||||||
|
return (
|
||||||
|
<ChemDispenserSettings
|
||||||
|
selectedAmount={amount}
|
||||||
|
availableAmounts={transferAmounts}
|
||||||
|
minAmount={minTransferAmount}
|
||||||
|
maxAmount={maxTransferAmount}
|
||||||
|
amountAct={(amt) => act('set_amount', { amount: amt })}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
38
tgui/packages/tgui/interfaces/BorgHypo/index.tsx
Normal file
38
tgui/packages/tgui/interfaces/BorgHypo/index.tsx
Normal file
@@ -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<Data>();
|
||||||
|
const { isDispensingDrinks, theme } = data;
|
||||||
|
return (
|
||||||
|
<Window
|
||||||
|
width={680}
|
||||||
|
height={isDispensingDrinks ? 610 : 540}
|
||||||
|
theme={theme || 'ntos'}
|
||||||
|
>
|
||||||
|
<Window.Content>
|
||||||
|
<Stack fill>
|
||||||
|
<Stack.Item grow>
|
||||||
|
<Stack vertical fill>
|
||||||
|
<Stack.Item>
|
||||||
|
<BorgHypoSettings />
|
||||||
|
</Stack.Item>
|
||||||
|
<Stack.Item grow>
|
||||||
|
<BorgHypoRecipes />
|
||||||
|
</Stack.Item>
|
||||||
|
</Stack>
|
||||||
|
</Stack.Item>
|
||||||
|
<Stack.Item grow>
|
||||||
|
<BorgHypoChemicals />
|
||||||
|
</Stack.Item>
|
||||||
|
</Stack>
|
||||||
|
</Window.Content>
|
||||||
|
</Window>
|
||||||
|
);
|
||||||
|
};
|
||||||
18
tgui/packages/tgui/interfaces/BorgHypo/types.ts
Normal file
18
tgui/packages/tgui/interfaces/BorgHypo/types.ts
Normal file
@@ -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<string, Recipe[]>;
|
||||||
|
recordingRecipe: Recipe[];
|
||||||
|
isDispensingRecipe: BooleanLike;
|
||||||
|
selectedRecipeId: string;
|
||||||
|
uiChemicalSearch: string;
|
||||||
|
isDispensingDrinks: BooleanLike;
|
||||||
|
theme: string | null;
|
||||||
|
};
|
||||||
@@ -1,50 +1,61 @@
|
|||||||
import { useEffect, useState } from 'react';
|
import { type ReactNode, useEffect, useState } from 'react';
|
||||||
import { useBackend } from 'tgui/backend';
|
import { useBackend } from 'tgui/backend';
|
||||||
import { Button, Icon, Section, Stack, Tooltip } from 'tgui-core/components';
|
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: {
|
||||||
|
sectionTitle: string;
|
||||||
export const ChemDispenserChemicals = (props) => {
|
chemicals: Reagent[];
|
||||||
const { act, data } = useBackend<Data>();
|
/** Called when the user clicks on a reagent dispense button. Arg is the ID of the button's reagent. */
|
||||||
const { chemicals = [] } = data;
|
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[] = [];
|
const flexFillers: boolean[] = [];
|
||||||
for (let i = 0; i < (chemicals.length + 1) % 3; i++) {
|
const sortedChemicals: Reagent[] = chemicals;
|
||||||
flexFillers.push(true);
|
sortedChemicals.sort((a, b) => a.name.localeCompare(b.name));
|
||||||
}
|
|
||||||
return (
|
return (
|
||||||
<Section
|
<Section title={sectionTitle} fill scrollable buttons={buttons}>
|
||||||
title={data.glass ? 'Drink Dispenser' : 'Chemical Dispenser'}
|
|
||||||
fill
|
|
||||||
scrollable
|
|
||||||
buttons={<RecordingBlinker />}
|
|
||||||
>
|
|
||||||
<Stack direction="row" wrap="wrap" align="flex-start" g={0.3}>
|
<Stack direction="row" wrap="wrap" align="flex-start" g={0.3}>
|
||||||
{chemicals.map((c, i) => (
|
{sortedChemicals.map((c, i) => (
|
||||||
<Stack.Item key={i} basis="40%" grow height="20px">
|
<Stack.Item key={i} basis="49%" grow maxWidth="50%">
|
||||||
<Button
|
<Button
|
||||||
icon="arrow-circle-down"
|
|
||||||
fluid
|
fluid
|
||||||
ellipsis
|
|
||||||
align="flex-start"
|
align="flex-start"
|
||||||
onClick={() =>
|
tooltip={c.name.length > 15 ? c.name : undefined}
|
||||||
act('dispense', {
|
selected={
|
||||||
reagent: c.id,
|
chemicalButtonSelect ? chemicalButtonSelect(c.id) : false
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
onClick={() => dispenseAct(c.id)}
|
||||||
>
|
>
|
||||||
{`${c.name} (${c.volume})`}
|
<Stack>
|
||||||
|
<Stack.Item>
|
||||||
|
<Icon name="arrow-circle-down" />
|
||||||
|
</Stack.Item>
|
||||||
|
<Stack.Item grow overflow="hidden">
|
||||||
|
{c.name}
|
||||||
|
</Stack.Item>
|
||||||
|
<Stack.Item>{`(${c.volume})`}</Stack.Item>
|
||||||
|
</Stack>
|
||||||
</Button>
|
</Button>
|
||||||
</Stack.Item>
|
</Stack.Item>
|
||||||
))}
|
))}
|
||||||
{flexFillers.map((_, i) => (
|
|
||||||
<Stack.Item key={i} grow basis="25%" height="20px" />
|
|
||||||
))}
|
|
||||||
</Stack>
|
</Stack>
|
||||||
</Section>
|
</Section>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const RecordingBlinker = (props) => {
|
export const RecordingBlinker = (props) => {
|
||||||
const { data } = useBackend<Data>();
|
const { data } = useBackend<Data>();
|
||||||
const recording = !!data.recordingRecipe;
|
const recording = !!data.recordingRecipe;
|
||||||
|
|
||||||
|
|||||||
@@ -1,13 +1,42 @@
|
|||||||
import { useBackend } from 'tgui/backend';
|
|
||||||
import { Box, Button, Section, Stack } from 'tgui-core/components';
|
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<string, Recipe[]>;
|
||||||
|
/** 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 isRecording: boolean = !!recordingRecipe;
|
||||||
const { act, data } = useBackend<Data>();
|
|
||||||
const { recipes, recordingRecipe } = data;
|
|
||||||
|
|
||||||
const recording: boolean = !!recordingRecipe;
|
|
||||||
const recipeData = Object.keys(recipes).sort();
|
const recipeData = Object.keys(recipes).sort();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -17,51 +46,61 @@ export const ChemDispenserRecipes = (props) => {
|
|||||||
scrollable
|
scrollable
|
||||||
buttons={
|
buttons={
|
||||||
<Stack>
|
<Stack>
|
||||||
{!recording && (
|
{!isRecording && (
|
||||||
<Stack.Item>
|
<Stack.Item>
|
||||||
<Button icon="circle" onClick={() => act('record_recipe')}>
|
<Button icon="circle" onClick={recordAct}>
|
||||||
Record
|
Record
|
||||||
</Button>
|
</Button>
|
||||||
</Stack.Item>
|
</Stack.Item>
|
||||||
)}
|
)}
|
||||||
{recording && (
|
{isRecording && (
|
||||||
<Stack.Item>
|
<Stack.Item>
|
||||||
<Button
|
<Button icon="ban" color="bad" onClick={cancelAct}>
|
||||||
icon="ban"
|
|
||||||
color="bad"
|
|
||||||
onClick={() => act('cancel_recording')}
|
|
||||||
>
|
|
||||||
Discard
|
Discard
|
||||||
</Button>
|
</Button>
|
||||||
</Stack.Item>
|
</Stack.Item>
|
||||||
)}
|
)}
|
||||||
{recording && (
|
{isRecording && (
|
||||||
<Stack.Item>
|
<Stack.Item>
|
||||||
<Button
|
<Button icon="save" color="green" onClick={saveAct}>
|
||||||
icon="save"
|
|
||||||
color="green"
|
|
||||||
onClick={() => act('save_recording')}
|
|
||||||
>
|
|
||||||
Save
|
Save
|
||||||
</Button>
|
</Button>
|
||||||
</Stack.Item>
|
</Stack.Item>
|
||||||
)}
|
)}
|
||||||
{!recording && (
|
{!isRecording && (
|
||||||
|
<>
|
||||||
|
<Stack.Item>
|
||||||
|
<Button.File
|
||||||
|
accept=".json"
|
||||||
|
tooltip="Import recipes"
|
||||||
|
icon="file-alt"
|
||||||
|
onSelectFiles={(files) => handleImportData(files)}
|
||||||
|
/>
|
||||||
|
</Stack.Item>
|
||||||
|
<Stack.Item>
|
||||||
|
<Button
|
||||||
|
icon="download"
|
||||||
|
tooltip="Export recipes"
|
||||||
|
disabled={!recipeData.length}
|
||||||
|
onClick={() => exportRecipes(recipes)}
|
||||||
|
/>
|
||||||
|
</Stack.Item>
|
||||||
<Stack.Item>
|
<Stack.Item>
|
||||||
<Button.Confirm
|
<Button.Confirm
|
||||||
icon="trash"
|
icon="trash"
|
||||||
confirmIcon="trash"
|
confirmIcon="trash"
|
||||||
color="bad"
|
color="bad"
|
||||||
onClick={() => act('clear_recipes')}
|
onClick={clearAct}
|
||||||
>
|
>
|
||||||
Clear All
|
Clear All
|
||||||
</Button.Confirm>
|
</Button.Confirm>
|
||||||
</Stack.Item>
|
</Stack.Item>
|
||||||
|
</>
|
||||||
)}
|
)}
|
||||||
</Stack>
|
</Stack>
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
{recording && (
|
{isRecording && (
|
||||||
<>
|
<>
|
||||||
<Box color="green" fontSize={1.2} bold>
|
<Box color="green" fontSize={1.2} bold>
|
||||||
Recording In Progress...
|
Recording In Progress...
|
||||||
@@ -91,7 +130,12 @@ export const ChemDispenserRecipes = (props) => {
|
|||||||
<Button
|
<Button
|
||||||
fluid
|
fluid
|
||||||
icon="flask"
|
icon="flask"
|
||||||
onClick={() => act('dispense_recipe', { recipe })}
|
selected={
|
||||||
|
getDispenseButtonSelected
|
||||||
|
? getDispenseButtonSelected(recipe)
|
||||||
|
: undefined
|
||||||
|
}
|
||||||
|
onClick={() => dispenseAct(recipe)}
|
||||||
>
|
>
|
||||||
{recipe}
|
{recipe}
|
||||||
</Button>
|
</Button>
|
||||||
@@ -102,7 +146,7 @@ export const ChemDispenserRecipes = (props) => {
|
|||||||
confirmIcon="triangle-exclamation"
|
confirmIcon="triangle-exclamation"
|
||||||
confirmContent={''}
|
confirmContent={''}
|
||||||
color="bad"
|
color="bad"
|
||||||
onClick={() => act('remove_recipe', { recipe })}
|
onClick={() => removeAct(recipe)}
|
||||||
/>
|
/>
|
||||||
</Stack.Item>
|
</Stack.Item>
|
||||||
</Stack>
|
</Stack>
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
import { useBackend } from 'tgui/backend';
|
|
||||||
import {
|
import {
|
||||||
Button,
|
Button,
|
||||||
LabeledList,
|
LabeledList,
|
||||||
@@ -7,28 +6,35 @@ import {
|
|||||||
Stack,
|
Stack,
|
||||||
} from 'tgui-core/components';
|
} from 'tgui-core/components';
|
||||||
|
|
||||||
import { dispenseAmounts } from './constants';
|
export const ChemDispenserSettings = (props: {
|
||||||
import type { Data } from './types';
|
/** The dispense amount the user has currently selected. */
|
||||||
|
selectedAmount: number;
|
||||||
export const ChemDispenserSettings = (props) => {
|
/** Available amounts for this dispenser to use. */
|
||||||
const { act, data } = useBackend<Data>();
|
availableAmounts: number[];
|
||||||
const { amount } = data;
|
/** 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 (
|
return (
|
||||||
<Section title="Settings" fill>
|
<Section
|
||||||
|
title="Settings"
|
||||||
|
fill
|
||||||
|
>
|
||||||
<LabeledList>
|
<LabeledList>
|
||||||
<LabeledList.Item label="Dispense" verticalAlign="middle">
|
<LabeledList.Item label="Dispense" verticalAlign="middle">
|
||||||
<Stack g={0.1}>
|
<Stack g={0.1}>
|
||||||
{dispenseAmounts.map((a, i) => (
|
{availableAmounts.map((a, i) => (
|
||||||
<Stack.Item key={i}>
|
<Stack.Item key={i}>
|
||||||
<Button
|
<Button
|
||||||
textAlign="center"
|
textAlign="center"
|
||||||
selected={amount === a}
|
selected={selectedAmount === a}
|
||||||
m="0"
|
m="0"
|
||||||
onClick={() =>
|
onClick={() => amountAct(a)}
|
||||||
act('amount', {
|
|
||||||
amount: a,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
>
|
>
|
||||||
{`${a}u`}
|
{`${a}u`}
|
||||||
</Button>
|
</Button>
|
||||||
@@ -40,14 +46,10 @@ export const ChemDispenserSettings = (props) => {
|
|||||||
<Slider
|
<Slider
|
||||||
step={1}
|
step={1}
|
||||||
stepPixelSize={5}
|
stepPixelSize={5}
|
||||||
value={amount}
|
value={selectedAmount}
|
||||||
minValue={1}
|
minValue={minAmount}
|
||||||
maxValue={120}
|
maxValue={maxAmount}
|
||||||
onChange={(e, value) =>
|
onChange={(e, value) => amountAct(value)}
|
||||||
act('amount', {
|
|
||||||
amount: value,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
/>
|
/>
|
||||||
</LabeledList.Item>
|
</LabeledList.Item>
|
||||||
</LabeledList>
|
</LabeledList>
|
||||||
|
|||||||
12
tgui/packages/tgui/interfaces/ChemDispenser/functions.ts
Normal file
12
tgui/packages/tgui/interfaces/ChemDispenser/functions.ts
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
import { getCurrentTimestamp } from '../VorePanelExport/VorePanelExportTimestamp';
|
||||||
|
import type { Recipe } from './types';
|
||||||
|
|
||||||
|
export function exportRecipes(recipes: Record<string, Recipe[]>) {
|
||||||
|
const blob = new Blob([JSON.stringify(recipes)], {
|
||||||
|
type: 'application/json',
|
||||||
|
});
|
||||||
|
|
||||||
|
const datesegment = getCurrentTimestamp();
|
||||||
|
const filename = `ChemRecipes${datesegment}.json`;
|
||||||
|
Byond.saveBlob(blob, filename, '.json');
|
||||||
|
}
|
||||||
@@ -3,13 +3,18 @@ import { Window } from 'tgui/layouts';
|
|||||||
import { Stack } from 'tgui-core/components';
|
import { Stack } from 'tgui-core/components';
|
||||||
|
|
||||||
import { ChemDispenserBeaker } from './ChemDispenserBeaker';
|
import { ChemDispenserBeaker } from './ChemDispenserBeaker';
|
||||||
import { ChemDispenserChemicals } from './ChemDispenserChemicals';
|
import {
|
||||||
|
ChemDispenserChemicals,
|
||||||
|
RecordingBlinker,
|
||||||
|
} from './ChemDispenserChemicals';
|
||||||
import { ChemDispenserRecipes } from './ChemDispenserRecipes';
|
import { ChemDispenserRecipes } from './ChemDispenserRecipes';
|
||||||
import { ChemDispenserSettings } from './ChemDispenserSettings';
|
import { ChemDispenserSettings } from './ChemDispenserSettings';
|
||||||
|
import { dispenseAmounts } from './constants';
|
||||||
import type { Data } from './types';
|
import type { Data } from './types';
|
||||||
|
|
||||||
export const ChemDispenser = (props) => {
|
export const ChemDispenser = (props) => {
|
||||||
const { data } = useBackend<Data>();
|
const { data, act } = useBackend<Data>();
|
||||||
|
const { recipes, recordingRecipe, glass, chemicals, amount } = data;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Window width={680} height={540}>
|
<Window width={680} height={540}>
|
||||||
@@ -20,15 +25,45 @@ export const ChemDispenser = (props) => {
|
|||||||
<Stack.Item grow>
|
<Stack.Item grow>
|
||||||
<Stack vertical fill>
|
<Stack vertical fill>
|
||||||
<Stack.Item>
|
<Stack.Item>
|
||||||
<ChemDispenserSettings />
|
<ChemDispenserSettings
|
||||||
|
selectedAmount={amount}
|
||||||
|
availableAmounts={dispenseAmounts}
|
||||||
|
minAmount={1}
|
||||||
|
maxAmount={120}
|
||||||
|
amountAct={(amt) =>
|
||||||
|
act('amount', {
|
||||||
|
amount: amt,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
/>
|
||||||
</Stack.Item>
|
</Stack.Item>
|
||||||
<Stack.Item grow>
|
<Stack.Item grow>
|
||||||
<ChemDispenserRecipes />
|
<ChemDispenserRecipes
|
||||||
|
recipes={recipes}
|
||||||
|
recordingRecipe={recordingRecipe}
|
||||||
|
recordAct={() => 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 })}
|
||||||
|
/>
|
||||||
</Stack.Item>
|
</Stack.Item>
|
||||||
</Stack>
|
</Stack>
|
||||||
</Stack.Item>
|
</Stack.Item>
|
||||||
<Stack.Item grow>
|
<Stack.Item grow>
|
||||||
<ChemDispenserChemicals />
|
<ChemDispenserChemicals
|
||||||
|
sectionTitle={
|
||||||
|
glass ? 'Drink Dispenser' : 'Chemical Dispenser'
|
||||||
|
}
|
||||||
|
chemicals={chemicals}
|
||||||
|
dispenseAct={(reagentId) =>
|
||||||
|
act('dispense', { reagent: reagentId })
|
||||||
|
}
|
||||||
|
buttons={<RecordingBlinker />}
|
||||||
|
/>
|
||||||
</Stack.Item>
|
</Stack.Item>
|
||||||
</Stack>
|
</Stack>
|
||||||
</Stack.Item>
|
</Stack.Item>
|
||||||
|
|||||||
@@ -9,12 +9,12 @@ export type Data = {
|
|||||||
amount: number;
|
amount: number;
|
||||||
isBeakerLoaded: BooleanLike;
|
isBeakerLoaded: BooleanLike;
|
||||||
glass: BooleanLike;
|
glass: BooleanLike;
|
||||||
beakerContents: reagent[];
|
beakerContents: Reagent[];
|
||||||
beakerCurrentVolume: number | null;
|
beakerCurrentVolume: number | null;
|
||||||
beakerMaxVolume: number | null;
|
beakerMaxVolume: number | null;
|
||||||
chemicals: reagent[];
|
chemicals: Reagent[];
|
||||||
recipes: Record<string, Recipe[]>;
|
recipes: Record<string, Recipe[]>;
|
||||||
recordingRecipe: Recipe[];
|
recordingRecipe: Recipe[];
|
||||||
};
|
};
|
||||||
|
|
||||||
type reagent = { name: string; id: string; volume: number };
|
export type Reagent = { name: string; id: string; volume: number };
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ export type Data = {
|
|||||||
law_sets: law_pack[];
|
law_sets: law_pack[];
|
||||||
active_ais: DropdownEntry[];
|
active_ais: DropdownEntry[];
|
||||||
selected_ai: string | null;
|
selected_ai: string | null;
|
||||||
theme?: string;
|
theme: string | null;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type DropdownEntry = {
|
export type DropdownEntry = {
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ export type Data = {
|
|||||||
possible_sprites?: spriteOption[];
|
possible_sprites?: spriteOption[];
|
||||||
currentName: string;
|
currentName: string;
|
||||||
isDefaultName: boolean;
|
isDefaultName: boolean;
|
||||||
theme?: string;
|
theme: string | null;
|
||||||
selected_module?: string;
|
selected_module?: string;
|
||||||
sprite_datum?: string | null;
|
sprite_datum?: string | null;
|
||||||
sprite_datum_class?: string | null;
|
sprite_datum_class?: string | null;
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import { Box, Button, Input, Section, Stack } from 'tgui-core/components';
|
|||||||
import { createSearch } from 'tgui-core/string';
|
import { createSearch } from 'tgui-core/string';
|
||||||
|
|
||||||
type Data = {
|
type Data = {
|
||||||
theme?: string;
|
theme: string | null;
|
||||||
all_decals?: string[] | null;
|
all_decals?: string[] | null;
|
||||||
all_animations?: string[] | null;
|
all_animations?: string[] | null;
|
||||||
active_decals: string[];
|
active_decals: string[];
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import type { BooleanLike } from 'tgui-core/react';
|
|||||||
|
|
||||||
export type Data = {
|
export type Data = {
|
||||||
name?: string;
|
name?: string;
|
||||||
theme?: string;
|
theme: string | null;
|
||||||
chems?: RobotChem[];
|
chems?: RobotChem[];
|
||||||
|
|
||||||
our_patient: Patient | null;
|
our_patient: Patient | null;
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ export type Data = {
|
|||||||
max_health: number;
|
max_health: number;
|
||||||
|
|
||||||
light_color: string;
|
light_color: string;
|
||||||
theme?: string;
|
theme: string | null;
|
||||||
|
|
||||||
// Modules
|
// Modules
|
||||||
modules_static: Module[];
|
modules_static: Module[];
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { Box, Stack } from 'tgui-core/components';
|
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
|
* Displays a beaker's contents
|
||||||
|
|||||||
Reference in New Issue
Block a user