Food Synthesizers (#19569)

* Food Synthesizers

Initial port, need to figure out what madness you people have done to tgui... and sprite sheets.

At this moment, it compiles but I haven't tested it yet.  Too eepy

* iconforge spritesheet nonsense

* Cleaning up tgui menu

full disclosure, After six plus hours of trying to figure out how to do the spritesheets  and the tgui stuff I just gave up on this particular bit and threw it into Gemini for a looksie over.

As it's originally my code I figure it's worth at least a reasonable proof read. The side-by-side comparison is pretty wild.

* Passes tgu-lint building

* Appease Gripe err Grep checks

* Oh these for the grep as well whoops

* Sorts out the backend

also piss off linter check, it's COMMENTED OUT aaaaaaaaaa.

* changes from will

* changes from will and some UI splitting, needs proper testing and final adjustments

* passadata

* .

* can be combined

* .

* ml={1}

* id

* Crew category

* id handling

* .

* more cleanup

* smaller cleanup

* why not

* deps up, minor tgui core fix

* timer

* .

* .

* missed to put that back

* order

* this

* Change preference category for foodsynth cookies

* Enforce south facement for cookie image

* Maurice I am no longer asking

* .

---------

Co-authored-by: Kashargul <144968721+Kashargul@users.noreply.github.com>
This commit is contained in:
Poojawa
2026-07-09 13:50:35 -05:00
committed by GitHub
parent 9a8be3b591
commit ba0fd0be33
26 changed files with 2378 additions and 47 deletions
+4
View File
@@ -724,6 +724,10 @@
#define REAGENT_ID_GELATIN "gelatin"
#define REAGENT_MUSTARDPODS "Mustard Pods"
#define REAGENT_ID_MUSTARDPODS "mustardpods"
#define REAGENT_NUTRIPASTE "Nutriment Paste"
#define REAGENT_ID_NUTRIPASTE "nutripaste"
#define REAGENT_NUTRIPASTE_SOYLENT "Soylent Agent Green"
#define REAGENT_ID_NUTRIPASTE_SOYLENT "synthsoygreen"
#define REAGENT_DRINK "Drink"
#define REAGENT_ID_DRINK "drink"
+69
View File
@@ -0,0 +1,69 @@
//Totally not just a blatant copypasta. Nooooo...
/datum/wires/synthesizer
holder_type = /obj/machinery/synthesizer
wire_count = 6
proper_name = "Food Synthesizer"
/datum/wires/synthesizer/New(atom/_holder)
wires = list(WIRE_LATHE_HACK, WIRE_ELECTRIFY, WIRE_LATHE_DISABLE)
var/obj/machinery/synthesizer/A = _holder
if(A.hacked)
cut_wires += WIRE_LATHE_HACK
return ..()
/datum/wires/synthesizer/get_status()
. = ..()
var/obj/machinery/synthesizer/A = holder
. += "The red light is [A.disabled ? "off" : "on"]."
. += "The green light is [A.shocked ? "off" : "on"]."
. += "The blue light is [A.hacked ? "off" : "on"]."
/datum/wires/synthesizer/interactable(mob/user)
var/obj/machinery/synthesizer/A = holder
if(A.panel_open)
return TRUE
return FALSE
/datum/wires/synthesizer/on_cut(wire, mend)
var/obj/machinery/synthesizer/A = holder
switch(wire)
if(WIRE_LATHE_HACK)
A.hacked = !mend
A.update_tgui_static_data(usr)
if(WIRE_ELECTRIFY)
A.shocked = !mend
if(WIRE_LATHE_DISABLE)
A.disabled = !mend
..()
/datum/wires/synthesizer/on_pulse(wire)
if(is_cut(wire))
return
var/obj/machinery/synthesizer/A = holder
switch(wire)
if(WIRE_LATHE_HACK)
A.hacked = !A.hacked
A.update_tgui_static_data(usr)
addtimer(CALLBACK(src, PROC_REF(reset_hacked), WIRE_LATHE_HACK, usr), 5 SECONDS)
if(WIRE_ELECTRIFY)
A.shocked = !A.shocked
addtimer(CALLBACK(src, PROC_REF(reset_electrify), WIRE_ELECTRIFY), 5 SECONDS)
if(WIRE_LATHE_DISABLE)
A.disabled = !A.disabled
addtimer(CALLBACK(src, PROC_REF(reset_disable), WIRE_LATHE_DISABLE), 5 SECONDS)
/datum/wires/synthesizer/proc/reset_hacked(wire, mob/user)
var/obj/machinery/synthesizer/A = holder
if(A && !is_cut(wire))
A.hacked = FALSE
A.update_tgui_static_data(user)
/datum/wires/synthesizer/proc/reset_electrify(wire)
var/obj/machinery/synthesizer/A = holder
if(A && !is_cut(wire))
A.shocked = FALSE
/datum/wires/synthesizer/proc/reset_disable(wire)
var/obj/machinery/synthesizer/A = holder
if(A && !is_cut(wire))
A.disabled = FALSE
+18 -1
View File
@@ -24,6 +24,7 @@ GLOBAL_LIST(construction_frame_floor)
var/frame_style = FRAME_STYLE_FLOOR // "floor" or "wall"
var/x_offset // For wall frames: pixel_x
var/y_offset // For wall frames: pixel_y
var/is_dense = TRUE // Lazy workaround for wall-mounted, upgradable machines
// Get the icon state to use at a given state. Default implementation is based on the frame's name
/datum/frame/frame_types/proc/get_icon_state(state)
@@ -217,6 +218,22 @@ GLOBAL_LIST(construction_frame_floor)
circuit = /obj/item/circuitboard/injector_maker
frame_size = 3
/datum/frame/frame_types/foodsynthesizer
name = "Food Synthesizer"
icon_override = 'icons/obj/machines/foodsynthesizer.dmi'
frame_class = FRAME_CLASS_MACHINE
frame_size = 5
frame_style = FRAME_STYLE_WALL
x_offset = 24
y_offset = 30
is_dense = FALSE
/datum/frame/frame_types/foodsynthesizer/mini
name = "Mini Food Synthesizer"
frame_class = FRAME_CLASS_MACHINE
frame_size = 3 // smaller, so slightly fewer sheets
frame_style = FRAME_STYLE_WALL
// Refinery machines
/datum/frame/frame_types/industrial_reagent_grinder
name = "Industrial Chemical Grinder"
@@ -350,7 +367,7 @@ GLOBAL_LIST(construction_frame_floor)
if(frame_type.name == "Computer")
density = TRUE
if(frame_type.frame_class == FRAME_CLASS_MACHINE)
if(frame_type.frame_class == FRAME_CLASS_MACHINE && frame_type.is_dense)
density = TRUE
update_icon()
@@ -0,0 +1,15 @@
//Let's be optimal with our menu preview icons, shall we?
/datum/asset/spritesheet_batched/synthesizer
name = "synthesizer"
/datum/asset/spritesheet_batched/synthesizer/create_spritesheets()
for(var/datum/category_item/synthesizer/path as anything in subtypesof(/datum/category_item/synthesizer))
var/datum/universal_icon/icon = get_display_icon_for(path::build_path)
if(!icon || !icon.icon_file)
continue
icon.scale(128, 128) //enbiggen for the menu UI
var/key = sanitize_css_class_name("[path::type]")
insert_icon(key, icon)
@@ -1,10 +1,10 @@
// Define a place to save in character setup
/datum/preferences
var/vantag_volunteer = 0 // What state I want to be in, in terms of being affected by antags.
var/vantag_volunteer = FALSE // What state I want to be in, in terms of being affected by antags.
var/vantag_preference = VANTAG_NONE // Whether I'd like to volunteer to be an antag at some point.
var/resleeve_lock = 0 // Whether movs should have OOC reslieving protection. Default false.
var/resleeve_scan = 1 // Whether mob should start with a pre-spawn body scan. Default true.
var/mind_scan = 1 // Whether mob should start with a pre-spawn mind scan. Default true.
var/resleeve_lock = FALSE // Whether movs should have OOC reslieving protection. Default false.
var/resleeve_scan = TRUE // Whether mob should start with a pre-spawn body scan. Default true.
var/mind_scan = TRUE // Whether mob should start with a pre-spawn mind scan. Default true.
var/custom_species // Custom species name, can't be changed due to it having been used in savefiles already.
@@ -95,7 +95,7 @@
var/want_body_save = pref.resleeve_scan
var/want_mind_save = pref.mind_scan
spawn(50)
spawn(5 SECONDS)
if(QDELETED(character) || QDELETED(pref))
return // They might have been deleted during the wait
if(!character.virtual_reality_mob && !(/mob/living/carbon/human/proc/perform_exit_vr in character.verbs)) //Janky fix to prevent resleeving VR avatars but beats refactoring transcore
@@ -155,6 +155,7 @@
data["borg_petting"] = pref.borg_petting
data["ignore_shoes"] = pref.read_preference(/datum/preference/toggle/human/ignore_shoes)
data["synth_cookie"] = pref.read_preference(/datum/preference/toggle/living/foodsynth_cookies)
data["resleeve_lock"] = pref.resleeve_lock
data["resleeve_scan"] = pref.resleeve_scan
@@ -236,6 +237,9 @@
if("toggle_resleeve_scan")
pref.resleeve_scan = pref.resleeve_scan ? 0 : 1;
return TOPIC_REFRESH
if("toggle_synth_cookie")
pref.update_preference_by_type(/datum/preference/toggle/living/foodsynth_cookies, !pref.read_preference(/datum/preference/toggle/living/foodsynth_cookies))
return TOPIC_REFRESH
if("toggle_mind_scan")
pref.mind_scan = pref.mind_scan ? 0 : 1;
return TOPIC_REFRESH
@@ -7,3 +7,14 @@
/datum/preference/toggle/human/ignore_shoes/apply_to_human(mob/living/carbon/human/target, value)
return
// Whether mob can be printed as a snack. requires body scan. Default false.
/datum/preference/toggle/living/foodsynth_cookies
category = PREFERENCE_CATEGORY_MANUALLY_RENDERED
savefile_key = "vore_fsynth_cookie"
default_value = FALSE
savefile_identifier = PREFERENCE_CHARACTER
can_randomize = FALSE
/datum/preference/toggle/living/foodsynth_cookies/apply_to_living(mob/living/target, value)
return
@@ -0,0 +1,715 @@
//Poojy's miracle 'I don't want generic pizza' / there's noone working kitchen machine
//Yes it's a generic food 3d printer. ~
// in here because makes sense, if really it's just a refillable autolathe of food
#define SYNTH_FOOD_COST 5
/obj/machinery/synthesizer
name = "Food Synthesizer"
desc = "Sabresnacks brand device able to produce an incredible array of conventional foods. Although only the most ascetic of users claim it produces truly good tasting products."
icon = 'icons/obj/machines/foodsynthesizer.dmi'
icon_state = "synthesizer"
density = FALSE
anchored = TRUE
use_power = USE_POWER_IDLE
idle_power_usage = 10
active_power_usage = 2000
clicksound = "keyboard"
clickvol = 30
var/hacked = FALSE
var/disabled = FALSE
var/shocked = FALSE
var/busy = FALSE
var/usage_amt = SYNTH_FOOD_COST
light_system = STATIC_LIGHT
light_range = 3
light_power = 1
light_on = FALSE
var/menu_grade //how tasty is it?
var/speed_grade //how fast can it be?
var/filtertext
circuit = /obj/item/circuitboard/synthesizer
//loaded cartridge
var/obj/item/reagent_containers/synthdispcart/cart = /obj/item/reagent_containers/synthdispcart
var/cart_type = ITEMSIZE_LARGE
//all of our food
var/static/datum/category_collection/synthesizer/synthesizer_recipes
var/active_menu = "appasnacc"
var/activefood
//Voice activation stuff
var/activator = "computer"
var/list/voicephrase
//crew printing required stuff.
var/tgui_icons
var/icon/crewpicture
var/activecrew
var/refresh_delay = 10 SECONDS
/obj/machinery/synthesizer/Initialize(mapload)
. = ..()
if(!synthesizer_recipes)
synthesizer_recipes = new()
cart = new cart(src)
wires = new/datum/wires/synthesizer(src)
default_apply_parts()
update_icon()
if(!pixel_x && !pixel_y)
offset_synth()
/obj/machinery/synthesizer/mini
name = "Mini Food Synthesizer"
icon_state = "portsynth"
cart_type = ITEMSIZE_NORMAL
circuit = /obj/item/circuitboard/synthesizer/mini
cart = /obj/item/reagent_containers/synthdispcart/small
/obj/machinery/synthesizer/Destroy()
QDEL_NULL(wires)
if(cart)
QDEL_NULL(cart)
clear_tgui_icons()
return ..()
/obj/machinery/synthesizer/examine(mob/user)
. = ..()
if(panel_open)
. += "A cartridge is [cart ? "installed" : "missing"]."
if(cart && (!(stat & (NOPOWER|BROKEN))))
var/obj/item/reagent_containers/synthdispcart/C = cart
if(istype(C) && C.reagents && C.reagents.total_volume)
var/percent = round((C.reagents.total_volume / C.volume) * 100)
. += "The installed cartridge has [percent]% remaining."
//offsets to make the machine squish to a wall. They're all south facing so it looks weird but every other direction is AWFUL
/obj/machinery/synthesizer/proc/offset_synth()
pixel_x = (dir & 3) ? 0 : (dir == 4 ? -24 : 24)
pixel_y = (dir & 3) ? (dir == 1 ? -22 : 30) : 0
// TGUI
// Crew Cookie backend stuff... I can't even fuckin' believe this is Janicart stuff
/obj/machinery/synthesizer/proc/set_tgui_icon(mob/L)
if(!isliving(L))
return
if(ishuman(L))
crewpicture = getFlatIcon(L, defdir = SOUTH, no_anim = TRUE, force_south = TRUE)
tgui_icons = "'data:image/png;base64,[icon2base64(crewpicture)]'"
else //Simple animals, Silicons, etc don't have records, so we'll just grab their current state.
var/icon/F = getFlatIcon(L, defdir = SOUTH, no_anim = TRUE, force_south = TRUE)
crewpicture = F
tgui_icons = "'data:image/png;base64,[icon2base64(F)]'"
SStgui.update_uis(src)
/obj/machinery/synthesizer/proc/clear_tgui_icons()
tgui_icons = null
crewpicture = null
// And literally this is all that's needed for conventional meals. lmao.
/obj/machinery/synthesizer/ui_assets(mob/user)
return list(
get_asset_datum(/datum/asset/spritesheet_batched/synthesizer),
)
/obj/machinery/synthesizer/tgui_interact(mob/user, datum/tgui/ui)
if(stat & (BROKEN|NOPOWER))
return
if(shocked)
shock(user, 100)
ui = SStgui.try_update_ui(user, src, ui)
if(!ui)
ui = new(user, src, "FoodSynthesizer")
ui.open()
/obj/machinery/synthesizer/tgui_status(mob/user)
if(disabled)
return STATUS_CLOSE
return ..()
/obj/machinery/synthesizer/tgui_data(mob/user, datum/tgui/ui, datum/tgui_state/state)
var/list/data = list(
"busy" = busy,
"isThereCart" = cart,
"active_menu" = active_menu,
"activefood" = activefood,
"activecrew" = activecrew,
"crewicon" = tgui_icons
)
if(cart)
var/percent = round((cart.reagents.total_volume / cart.reagents.maximum_volume) * 100)
data["cartFillStatus"] = percent
return data
/obj/machinery/synthesizer/tgui_static_data(mob/user)
var/list/data = ..()
var/list/menucatagories = list()
for(var/datum/category_group/synthesizer/menulist in synthesizer_recipes.categories)
var/list/recipes = list()
for(var/datum/category_item/synthesizer/food in menulist.items)
UNTYPED_LIST_ADD(recipes, list(
"name" = food.name,
"type" = food.type,
"desc" = food.desc,
"hidden" = food.hidden,
"ref" = text_ref(food)
))
UNTYPED_LIST_ADD(menucatagories, list(
"name" = menulist.name,
"id" = menulist.id,
"sortorder" = menulist.sortorder,
"ref" = text_ref(menulist),
"recipes" = recipes
))
data["menucatagories"] = menucatagories
var/list/crew_cookies = list()
for(var/client/C in GLOB.clients)
// Check if the client allows crew cookies in their currently toggled prefs. Clients must be ingame to even be in GLOB.clients, so logged out players won't show up here.
if(!C.prefs?.read_preference(/datum/preference/toggle/living/foodsynth_cookies))
continue
var/name = null
var/species = null
if(ishuman(C.mob))
var/mob/living/carbon/human/H = C.mob
//Utilize the body records for humans to avoid metagaming problems (EX: using the cookie printer to check if someone is ghosting from the main menu)
var/datum/transcore_db/db = SStranscore.db_by_key()
if(db)
var/datum/transhuman/body_record/b_rec = db.body_scans[H.mind.name]
if(!b_rec) //extra check to make sure people have a body record, and no-one will immediately on start.
continue
name = H.real_name
species = "[H.custom_species ? H.custom_species : H.species.name]"
if(issilicon(C.mob))
if(isAI(C.mob))
var/mob/living/silicon/ai/A = C.mob
name = A.name
species = "Artificial Intelligence"
if(isrobot(C.mob))
var/mob/living/silicon/robot/R = C.mob
if(R.scrambledcodes || (R.module && R.module.hide_on_manifest)) //Not sure if admeme events want valid cookie print outs
continue
name = R.name
species = "[R.modtype] [R.braintype]"
if(isanimal(C.mob))
var/mob/living/simple_mob/SM = C.mob
name = SM.name
species = initial(SM.name) //most mobs are simply named the species they are, so this ought to be useful for named critters.
if(!name)
continue
// our crew cookies are only applicable on the crew menu, and we're reusing our category sorting as much as possible!
crew_cookies.Add(list(list(
"category" = "crew",
"name" = name,
"species" = species
)))
data["crew_cookies"] = crew_cookies
return data
/obj/machinery/synthesizer/tgui_act(action, list/params, datum/tgui/ui, datum/tgui_state/state)
if(..())
return TRUE
if(stat & (BROKEN|NOPOWER))
return FALSE
if(ui.user.stat || ui.user.restrained())
return FALSE
add_fingerprint(ui.user)
if(busy)
to_chat(ui.user, span_notice("The synthesizer is busy. Please wait for completion of previous operation."))
return FALSE
switch(action)
if("setactive_menu")
if(active_menu == params["setactive_menu"])
return FALSE
active_menu = params["setactive_menu"]
activecrew = null
activefood = null
if(tgui_icons)
clear_tgui_icons()
return TRUE
if("setactive_food")
if(activefood == params["setactive_food"])
return FALSE
activefood = params["setactive_food"]
return TRUE
if("setactive_crew")
if(activecrew == params["setactive_crew"])
return FALSE
activecrew = params["setactive_crew"]
if(tgui_icons)
clear_tgui_icons()
var/mob/found
for(var/mob/living/L in GLOB.player_list)
if(L.real_name == activecrew)
found = L
break
if(!found)
return FALSE
if(!get_mob_for_picture(ui.user, found))
return FALSE
set_tgui_icon(found)
return TRUE
if("make")
var/datum/category_item/synthesizer/making = locate(params["make"])
if(!istype(making))
return
if(making.hidden && !hacked)
return
//Check if we still have the materials.
var/obj/item/reagent_containers/synthdispcart/C = cart
if(check_cart(C, ui.user))
//Sanity check.
if(!making || !src)
return
busy = TRUE
update_use_power(USE_POWER_ACTIVE)
update_icon() // light up time
C.reagents.remove_reagent(REAGENT_ID_NUTRIPASTE_SOYLENT, SYNTH_FOOD_COST) //Drain our fuel
var/obj/item/reagent_containers/food/snacks/food_mimic = new making.build_path(src) //Let's get this on a tray
addtimer(CALLBACK(src, PROC_REF(finish_production), food_mimic, ui.user), speed_grade, TIMER_DELETE_ME)
return TRUE
audible_message(span_notice("Error: Insufficent Materials. SabreSnacks recommends you have a genuine replacement cartridge available to install."), runemessage = "Error: Insufficent Materials!")
return FALSE
if("refresh")
update_tgui_static_data(ui.user, ui)
return TRUE
if("crewprint")
var/active_crew = params["crewprint"]
var/mob/found
for(var/mob/living/L in GLOB.player_list)
if(L.real_name == active_crew)
found = L
break
if(found && !get_mob_for_picture(ui.user, found)) //verify that we can still print this person
to_chat(ui.user, "Warning: Invalid selection.")
return FALSE
//Check if we still have the materials.
var/obj/item/reagent_containers/synthdispcart/C = cart
if(check_cart(C, ui.user))
//Sanity check.
busy = TRUE
update_use_power(USE_POWER_ACTIVE)
update_icon() // light up time
C.reagents.remove_reagent(REAGENT_ID_NUTRIPASTE_SOYLENT, SYNTH_FOOD_COST) //Drain our fuel
sleep(speed_grade) //machine go brrr
//Create the cookie base.
var/obj/item/reagent_containers/food/snacks/synthsized_meal/crewblock/meal = new /obj/item/reagent_containers/food/snacks/synthsized_meal/crewblock(loc)
//Begin mimicking the micro
var/vore_flavor
if(found?.vore_taste)
vore_flavor = found.vore_taste
else
vore_flavor = "Something impalpable"
meal.name = found.real_name
meal.desc = "A tiny replica of a crewmate!"
var/icon/F = crewpicture
F.Scale(16, 16) //Half size
meal.icon = F
meal.icon_state = null
//flavor mixing, make the cookie taste somewhat like the real thing!
for(var/datum/reagent/foodpaste in meal.reagents.reagent_list)
if(foodpaste.id == REAGENT_ID_NUTRIPASTE) //This should be the only reagent, actually.
foodpaste.taste_description += " as well as [vore_flavor]"
foodpaste.data = list(foodpaste.taste_description = 1)
meal.nutriment_desc = list(foodpaste.taste_description = 1)
if(menu_grade >= 2) //Is the machine upgraded?
meal.reagents.add_reagent(REAGENT_ID_NUTRIPASTE, ((1 + menu_grade) - 1)) //add the missing Nutriment bonus, subtracting the one we've already added in.
audible_message(span_notice("Please take your miniature [meal.name]."), runemessage = "Minature [meal.name] is complete!")
if(Adjacent(ui.user))
ui.user.put_in_any_hand_if_possible(meal) //Autoplace in hands to save a click
else
meal.forceMove(get_turf(src)) //otherwise we anti-clump layer onto the floor
meal.randpixel_xy()
busy = FALSE
update_icon() //turn off lights, please.
else
audible_message(span_notice("Error: Insufficent Materials. SabreSnacks recommends you have a genuine replacement cartridge available to install."), runemessage = "Error: Insufficent Materials!")
return FALSE
return TRUE
/obj/machinery/synthesizer/proc/finish_production(obj/item/reagent_containers/food/snacks/food_mimic, mob/user)
//Create the desired item.
var/obj/item/reagent_containers/food/snacks/synthsized_meal/meal = new /obj/item/reagent_containers/food/snacks/synthsized_meal(loc)
//Begin mimicking the food
meal.name = food_mimic.name
meal.desc = food_mimic.desc
meal.icon = food_mimic.icon
meal.icon_state = food_mimic.icon_state
meal.center_of_mass_x = food_mimic.center_of_mass_x
meal.center_of_mass_y = food_mimic.center_of_mass_y
//flavor mixing
var/taste_output = food_mimic.reagents.generate_taste_message()
for(var/datum/reagent/F in meal.reagents.reagent_list)
if(F.id == REAGENT_ID_NUTRIPASTE) //This should be the only reagent, actually.
F.taste_description += " as well as [taste_output]"
F.data = list(F.taste_description = 1)
meal.nutriment_desc = list(F.taste_description = 1)
if(menu_grade >= 2) //Is the machine upgraded?
meal.reagents.add_reagent(REAGENT_ID_NUTRIPASTE, ((1 + menu_grade) - 1)) //add the missing Nutriment bonus, subtracting the one we've already added in.
meal.bitesize = food_mimic?.bitesize //suffer your aerogel like 1 Nutriment turkey, nerds.
meal.filling_color = food_mimic?.filling_color
meal.trash = food_mimic?.trash //If this can lead to exploits then we'll remove it, but I like the idea.
qdel(food_mimic)
audible_message(span_notice("Please take your [meal.name]."), runemessage = "[meal.name] is complete!")
if(Adjacent(user))
user.put_in_any_hand_if_possible(meal) //Autoplace in hands to save a click
else
meal.forceMove(get_turf(src)) //otherwise we anti-clump layer onto the floor
meal.randpixel_xy()
busy = FALSE
update_icon() //turn off lights, please.
/obj/machinery/synthesizer/update_icon()
cut_overlays()
set_light_on(FALSE)
icon_state = initial(icon_state) //we use this to reduce code bloat. It's nice.
if(panel_open) //add service panels just above our machine
if(!(stat & (NOPOWER|BROKEN)))
add_overlay("[initial(icon_state)]_ppanel")
else
add_overlay("[initial(icon_state)]_panel")
if(cart)
var/obj/item/reagent_containers/synthdispcart/C = cart
if(C.reagents && C.reagents.total_volume)
var/image/filling_overlay = image("[icon]", src, "[initial(icon_state)]fill_0") //Modular filling
var/percent = round((C.reagents.total_volume / C.volume) * 100)
switch(percent)
if(0 to 9) filling_overlay.icon_state = "[initial(icon_state)]fill_0"
if(10 to 35) filling_overlay.icon_state = "[initial(icon_state)]fill_25"
if(36 to 74) filling_overlay.icon_state = "[initial(icon_state)]fill_50"
if(75 to 90) filling_overlay.icon_state = "[initial(icon_state)]fill_75"
if(91 to INFINITY) filling_overlay.icon_state = "[initial(icon_state)]fill_100"
filling_overlay.color = C.reagents.get_color()
//Add our filling, if any.
add_overlay(filling_overlay)
//Then add our cart so the filling is inside of the canister.
add_overlay("[initial(icon_state)]_cart")
return //don't stack additional panel screen states, please.
if((stat && BROKEN))
icon_state = "[initial(icon_state)]x"
return
if(stat & NOPOWER)
return
if(!cart)
add_overlay("[initial(icon_state)]_error")
return
if(cart && !busy)
var/obj/item/reagent_containers/synthdispcart/C = cart
if(C.reagents && C.reagents.total_volume)
add_overlay("[initial(icon_state)]_on")
else
add_overlay("[initial(icon_state)]_error")
return
if(busy)
add_overlay("[initial(icon_state)]_busy")
set_light_color("#faebd7") // "antique white"
set_light_on(TRUE)
//Cartridge Interactions in Machine
/obj/machinery/synthesizer/proc/add_cart(obj/item/C, mob/user)
if(!Adjacent(user))
return //How did you even try?
if(!panel_open) //just in case
to_chat(user, "The hatch must be open to insert a [C].")
return
if(cart) // let's hot swap that bad boy.
remove_cart(user)
return
user.drop_from_inventory(C)
cart = C
C.forceMove(src)
C.add_fingerprint(user)
to_chat(user, span_notice("You add [C] to \the [src]."))
update_icon()
SStgui.update_uis(src)
return
/obj/machinery/synthesizer/proc/remove_cart(mob/user)
var/obj/item/reagent_containers/synthdispcart/C = cart
if(!C)
to_chat(user, span_notice("There's no cartridge here...")) //Sanity checks aren't ever a bad thing
return
if(!Adjacent(user)) //gotta, y'know, be in touch range to pull a physical canister out
return
C.forceMove(get_turf(loc))
C.update_icon()
cart = null
to_chat(user, span_notice("You remove [C] from \the [src]."))
if(Adjacent(user))
user.put_in_hands(C) //pick up your trash, nerd. and don't hand it to the AI. They will be upset.
update_icon()
SStgui.update_uis(src)
/obj/machinery/synthesizer/proc/check_cart(obj/item/reagent_containers/synthdispcart/C, mob/user)
if(!istype(C))
to_chat(user, span_notice("The synthesizer cartridge is nonexistant."))
return FALSE
if((!(C.reagents)) || (C.reagents.total_volume <= 0) || (!C.reagents.has_reagent(REAGENT_ID_NUTRIPASTE_SOYLENT)))
to_chat(user, span_notice("The synthesizer cartridge depleted, replace with a genuine Sabresnack Co cartridge."))
return FALSE
if((C.reagents) && (C.reagents.total_volume <= 0) && (!C.reagents.has_reagent(REAGENT_ID_NUTRIPASTE_SOYLENT)))
to_chat(user, span_notice("Used or Counterfeit synthesizer cartridge detected."))
return FALSE
else if(C.reagents && C.reagents.has_reagent(REAGENT_ID_NUTRIPASTE_SOYLENT) && (C.reagents.total_volume >= SYNTH_FOOD_COST))
SStgui.update_uis(src)
return TRUE
/obj/machinery/synthesizer/proc/get_mob_for_picture(mob/user, mob/living/LM)
if(!istype(LM))
to_chat(user, span_warning("Warning: Invalid selection. Please refresh the database."))
return FALSE
if(LM.client?.prefs?.read_preference(/datum/preference/toggle/living/foodsynth_cookies))
return TRUE
to_chat(user, span_warning("Warning: Invalid selection. Please refresh the database."))
return FALSE
/obj/machinery/synthesizer/attackby(obj/item/W, mob/user)
if(busy)
audible_message(span_notice("\The [src] is busy. Please wait for completion of previous operation."), runemessage = "The Synthesizer is busy.")
return
if(default_part_replacement(user, W))
return
if(stat)
update_icon()
return
if(W.is_screwdriver())
panel_open = !panel_open
playsound(src, W.usesound, 50, 1)
user.visible_message(span_notice("[user] [panel_open ? "opens" : "closes"] the hatch on the [src]."), span_notice("You [panel_open ? "open" : "close"] the hatch on the [src]."))
update_icon()
return
if(panel_open)
if(istype(W, /obj/item/reagent_containers/synthdispcart))
if(!anchored)
to_chat(user, span_warning("Anchor its bolts first."))
return
if(W.w_class > cart_type) //since we confirmed it's a Cart, make sure it fits!
to_chat(user, span_warning("\The [src] only accepts smaller synthiziser cartridges."))
return
if(cart)
var/choice = alert(user, "Replace the loaded cartridge?", "", "Yes", "Cancel")
switch(choice)
if("Cancel")
return FALSE
if("Yes")
add_cart(W, user)
else
add_cart(W, user)
if(W.is_wrench())
playsound(src, W.usesound, 50, 1)
to_chat(user, span_notice("You begin to [anchored ? "un" : ""]fasten \the [src]."))
if (do_after(user, 20 * W.toolspeed))
user.visible_message(
span_notice("\The [user] [anchored ? "un" : ""]fastens \the [src]."),
span_notice("You have [anchored ? "un" : ""]fastened \the [src]."),
"You hear a ratchet.")
anchored = !anchored
update_icon()
else
to_chat(user, span_notice("You decide not to [anchored ? "un" : ""]fasten \the [src]."))
if(default_deconstruction_crowbar(user, W))
return
return ..()
/obj/machinery/synthesizer/attack_hand(mob/user as mob)
if(stat & (BROKEN|NOPOWER))
return
if(!panel_open)
tgui_interact(user)
else if(panel_open)
if(cart)
var/choice = alert(user, "Removing the Cartridge?", "", "Yes", "Cancel", "Wires Menu")
switch(choice)
if("Cancel")
return FALSE
if("Yes")
remove_cart(user)
if("Wires Menu")
wires.Interact(user)
else
wires.Interact(user)
/obj/machinery/synthesizer/attack_ai(mob/user)
return attack_hand(user)
/obj/machinery/synthesizer/interact(mob/user)
if(panel_open)
return wires.Interact(user)
if(disabled)
to_chat(user, span_danger("\The [src] is disabled!"))
return
if(shocked)
shock(user, 50)
tgui_interact(user)
//Updates performance
/obj/machinery/synthesizer/RefreshParts()
..()
menu_grade = 0
speed_grade = 0
for(var/obj/item/stock_parts/manipulator/M in component_parts)
speed_grade = (10 SECONDS) / M.rating //let's try to make it worthwhile to upgrade 'em 10s, 5s, 3.3s, 2.5s
for(var/obj/item/stock_parts/scanning_module/S in component_parts)
menu_grade = S.rating //how much bonus Nutriment is added to the printed food. the regular wafer is only 5.
// Science parts will be of help if they bother.
#undef SYNTH_FOOD_COST
//Cartridge Item handling
/obj/item/reagent_containers/synthdispcart
name = "Synthesizer cartridge"
desc = "Genuine replacement cartridge for SabreSnacks brand Food Synthesizers. It's too large for the Portable models."
icon = 'icons/obj/machines/foodsynthesizer.dmi'
icon_state = "bigcart"
w_class = ITEMSIZE_LARGE
volume = 250 //enough for feeding folk, but not so much it won't be needing replacment
//No way to refill or drain them. Like printer ink, it's only good in the machine!
amount_per_transfer_from_this = 0
max_transfer_amount = 0
min_transfer_amount = 0
flags = NONE
/obj/item/reagent_containers/synthdispcart/small
name = "Portable Synthesizer Cartridge"
desc = "Genuine replacement cartrifge SabreSnacks brand Portable Food Synthesizers. It can also fit within standard sized models."
icon_state = "Scart"
w_class = ITEMSIZE_NORMAL
volume = 100
/obj/item/reagent_containers/synthdispcart/Initialize(mapload)
. = ..()
reagents.add_reagent(REAGENT_ID_NUTRIPASTE_SOYLENT, volume)
update_icon()
/obj/item/reagent_containers/synthdispcart/update_icon()
cut_overlays()
if(reagents.total_volume)
var/image/filling_overlay = image("[icon]", src, "[initial(icon_state)]fill_0", layer = layer - 0.1)
var/percent = round((reagents.total_volume / volume) * 100)
switch(percent)
if(0 to 9) filling_overlay.icon_state = "[initial(icon_state)]fill_0"
if(10 to 35) filling_overlay.icon_state = "[initial(icon_state)]fill_25"
if(36 to 74) filling_overlay.icon_state = "[initial(icon_state)]fill_50"
if(75 to 90) filling_overlay.icon_state = "[initial(icon_state)]fill_75"
if(91 to 100) filling_overlay.icon_state = "[initial(icon_state)]fill_100"
if(100 to INFINITY) filling_overlay.icon_state = "[initial(icon_state)]fill_100"
filling_overlay.color = reagents.get_color()
add_overlay(filling_overlay)
/obj/item/reagent_containers/synthdispcart/examine(mob/user)
. = ..()
if(reagents && reagents.total_volume)
var/percent = round((reagents.total_volume / volume) * 100)
. += "The cartridge has [percent]% remaining."
/obj/item/reagent_containers/synthdispcart/is_open_container()
return FALSE //sealed, proprietary container. aka preventing alternative beaker memes.
//Circuits for contruction
/datum/design_techweb/board/synthesizer
SET_CIRCUIT_DESIGN_NAMEDESC("food synthesizer")
id = "food_synthesizer"
// req_tech = list(TECH_DATA = 3, TECH_BLUESPACE = 3, TECH_ENGINEERING = 2)
build_path = /obj/item/circuitboard/synthesizer
category = list(
RND_CATEGORY_MACHINE + RND_SUBCATEGORY_MACHINE_KITCHEN
)
departmental_flags = DEPARTMENT_BITFLAG_SERVICE | DEPARTMENT_BITFLAG_SCIENCE
/datum/design_techweb/board/synthesizer/mini
SET_CIRCUIT_DESIGN_NAMEDESC("compact food synthesizer")
id = "compactfood_synthesizer"
// req_tech = list(TECH_DATA = 3, TECH_BLUESPACE = 3, TECH_ENGINEERING = 2)
build_path = /obj/item/circuitboard/synthesizer/mini
category = list(
RND_CATEGORY_MACHINE + RND_SUBCATEGORY_MACHINE_KITCHEN
)
departmental_flags = DEPARTMENT_BITFLAG_SERVICE | DEPARTMENT_BITFLAG_SCIENCE
// Physical Boards for Food Synthesizer Construction
/obj/item/circuitboard/synthesizer
name = T_BOARD("Food Synthesizer")
desc = "The circuitboard for a Food Synthesizer."
build_path = /obj/machinery/synthesizer
board_type = new /datum/frame/frame_types/foodsynthesizer
matter = list(MAT_STEEL = 50, MAT_GLASS = 50)
req_components = list(
/obj/item/stock_parts/manipulator = 1,
/obj/item/stock_parts/scanning_module = 1)
/obj/item/circuitboard/synthesizer/mini
name = T_BOARD("Compact Food Synthesizer")
desc = "The circuitboard for a compact food synthesizer."
build_path = /obj/machinery/synthesizer/mini
board_type = new /datum/frame/frame_types/foodsynthesizer/mini
matter = list(MAT_STEEL = 50, MAT_GLASS = 50)
req_components = list(
/obj/item/stock_parts/manipulator = 1,
/obj/item/stock_parts/scanning_module = 1)
File diff suppressed because it is too large Load Diff
+1 -1
View File
@@ -38,7 +38,7 @@ calculate text size per text.
for(var/datum/reagent/R in reagent_list)
if(!R.taste_mult)
continue
if(R.id == REAGENT_ID_NUTRIMENT) //this is ugly but apparently only nutriment (not subtypes) has taste data TODO figure out why
if(R.id == REAGENT_ID_NUTRIMENT || R.id == REAGENT_ID_NUTRIPASTE) //this is ugly but apparently only nutriment (not subtypes) has taste data TODO figure out why
var/list/taste_data = R.get_data()
for(var/taste in taste_data)
if(taste in tastes)
+2 -3
View File
@@ -101,7 +101,7 @@
var/aflags
var/breath_type = GAS_O2
/datum/transhuman/body_record/New(copyfrom, add_to_db = 0, ckeylock = 0)
/datum/transhuman/body_record/New(copyfrom, add_to_db = FALSE, ckeylock = FALSE)
..()
if(istype(copyfrom, /datum/transhuman/body_record))
init_from_br(copyfrom)
@@ -118,7 +118,7 @@
..()
return QDEL_HINT_HARDDEL // For now at least there is no easy way to clear references to this in GLOB.machines etc.
/datum/transhuman/body_record/proc/init_from_mob(mob/living/carbon/human/M, add_to_db = 0, ckeylock = 0, database_key)
/datum/transhuman/body_record/proc/init_from_mob(mob/living/carbon/human/M, add_to_db = FALSE, ckeylock = FALSE, database_key)
ASSERT(!QDELETED(M))
ASSERT(istype(M))
@@ -212,7 +212,6 @@
for(var/datum/modifier/mod as anything in M.modifiers)
if(mod.flags & MODIFIER_GENETIC)
genetic_modifiers.Add(mod.type)
if(add_to_db)
SStranscore.add_body(src, database_key = database_key)
Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

+30 -30
View File
@@ -6,8 +6,8 @@
"name": "tgui-workspace",
"devDependencies": {
"@happy-dom/global-registrator": "^20.10.6",
"@rspack/cli": "^2.1.1",
"@rspack/core": "^2.1.1",
"@rspack/cli": "^2.1.2",
"@rspack/core": "^2.1.2",
"@testing-library/react": "^16.3.2",
"@types/bun": "^1.3.14",
"@types/react": "^19.2.17",
@@ -33,7 +33,7 @@
"version": "2.0.0",
"dependencies": {
"svgo": "^4.0.1",
"svgtofont": "^6.5.2",
"svgtofont": "^6.5.3",
},
},
"packages/tgui": {
@@ -46,14 +46,14 @@
"es-toolkit": "^1.49.0",
"highlight.js": "^11.11.1",
"jotai": "^2.20.1",
"js-yaml": "^5.2.0",
"js-yaml": "^5.2.1",
"marked": "^18.0.5",
"marked-base-url": "^1.1.9",
"marked-smartypants": "^1.1.12",
"react": "^19.2.7",
"react-dom": "^19.2.7",
"react-json-tree": "^0.20.0",
"tgui-core": "^6.1.0",
"tgui-core": "^6.1.1",
"tgui-dev-server": "workspace:*",
},
},
@@ -77,7 +77,7 @@
"react": "^19.2.7",
"react-dom": "^19.2.7",
"tgui": "workspace:*",
"tgui-core": "^6.1.0",
"tgui-core": "^6.1.1",
"tgui-dev-server": "workspace:*",
"zod": "^4.4.3",
},
@@ -90,7 +90,7 @@
"react": "^19.2.7",
"react-dom": "^19.2.7",
"tgui": "workspace:*",
"tgui-core": "^6.1.0",
"tgui-core": "^6.1.1",
},
},
"packages/tgui-setup": {
@@ -143,7 +143,7 @@
"@jridgewell/trace-mapping": ["@jridgewell/trace-mapping@0.3.31", "", { "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" } }, "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw=="],
"@napi-rs/wasm-runtime": ["@napi-rs/wasm-runtime@1.1.5", "", { "dependencies": { "@tybys/wasm-util": "^0.10.2" }, "peerDependencies": { "@emnapi/core": "^1.7.1", "@emnapi/runtime": "^1.7.1" } }, "sha512-AWPoBRJ9tsnVhor4sjO7rkni+7p+2IAEFj6cx06UgP10jkQHqay/36uRV/bFkgrh18D9vb4cr8Q0Pthskgzy+Q=="],
"@napi-rs/wasm-runtime": ["@napi-rs/wasm-runtime@1.1.6", "", { "dependencies": { "@tybys/wasm-util": "^0.10.3" }, "peerDependencies": { "@emnapi/core": "^1.7.1", "@emnapi/runtime": "^1.7.1" } }, "sha512-ZLv/JdUfkvOy9eCnnBaGfiO+XimbjebAeO+MRQqD/B+FR1tnRN0tpKSJHRbE8sFfS6aqsXZ67TQjfwfsxULVbg=="],
"@nozbe/microfuzz": ["@nozbe/microfuzz@1.0.0", "", {}, "sha512-XKIg/guk+s1tkPTkHch9hfGOWgsKojT7BqSQddXTppOfVr3SWQhhTCqbgQaPTbppf9gc2kFeG0gpBZZ612UXHA=="],
@@ -181,35 +181,35 @@
"@pkgjs/parseargs": ["@pkgjs/parseargs@0.11.0", "", {}, "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg=="],
"@rspack/binding": ["@rspack/binding@2.1.1", "", { "optionalDependencies": { "@rspack/binding-darwin-arm64": "2.1.1", "@rspack/binding-darwin-x64": "2.1.1", "@rspack/binding-linux-arm64-gnu": "2.1.1", "@rspack/binding-linux-arm64-musl": "2.1.1", "@rspack/binding-linux-riscv64-gnu": "2.1.1", "@rspack/binding-linux-riscv64-musl": "2.1.1", "@rspack/binding-linux-x64-gnu": "2.1.1", "@rspack/binding-linux-x64-musl": "2.1.1", "@rspack/binding-wasm32-wasi": "2.1.1", "@rspack/binding-win32-arm64-msvc": "2.1.1", "@rspack/binding-win32-ia32-msvc": "2.1.1", "@rspack/binding-win32-x64-msvc": "2.1.1" } }, "sha512-6062hZP33fn1w51ClpQnum19GGXl/3eS754xrRYbQ7wwBNZk94HVwfSyiqcNCtY/pB1lsFjnaTKiW/Q+jv0axQ=="],
"@rspack/binding": ["@rspack/binding@2.1.2", "", { "optionalDependencies": { "@rspack/binding-darwin-arm64": "2.1.2", "@rspack/binding-darwin-x64": "2.1.2", "@rspack/binding-linux-arm64-gnu": "2.1.2", "@rspack/binding-linux-arm64-musl": "2.1.2", "@rspack/binding-linux-riscv64-gnu": "2.1.2", "@rspack/binding-linux-riscv64-musl": "2.1.2", "@rspack/binding-linux-x64-gnu": "2.1.2", "@rspack/binding-linux-x64-musl": "2.1.2", "@rspack/binding-wasm32-wasi": "2.1.2", "@rspack/binding-win32-arm64-msvc": "2.1.2", "@rspack/binding-win32-ia32-msvc": "2.1.2", "@rspack/binding-win32-x64-msvc": "2.1.2" } }, "sha512-/mFcRSUW7Pl19KeaBIujJvZYNJQu0wD5D3aa5h+Qcph26v7nmLYlX7eajIHGi8tt2qTZX1lXifw2KLIXKwYaRQ=="],
"@rspack/binding-darwin-arm64": ["@rspack/binding-darwin-arm64@2.1.1", "", { "os": "darwin", "cpu": "arm64" }, "sha512-6K8jA3pZphBwLt5DU78wl0IzY1myHrqNSvFVCd9CHa+szlOwv2iMQpZdYdpupIBxwiZ39rrDyQKtoZw7lsAG8w=="],
"@rspack/binding-darwin-arm64": ["@rspack/binding-darwin-arm64@2.1.2", "", { "os": "darwin", "cpu": "arm64" }, "sha512-IYcxareUOYJZz+uNMSIwn+iDRiVyjZNOjoxO/zL4OFaPK8Ncrw0ka/9DqL9Gd7OpnAXN1zK3uS8yD0O1yIYI3Q=="],
"@rspack/binding-darwin-x64": ["@rspack/binding-darwin-x64@2.1.1", "", { "os": "darwin", "cpu": "x64" }, "sha512-RdqtEfIbSe5ucLNVvMac1k88UwmL4Gil8uXqlEFcSZ3VC2jTOkecadd+B0RYqJ+PirSInrRmKm1cr0740zppOA=="],
"@rspack/binding-darwin-x64": ["@rspack/binding-darwin-x64@2.1.2", "", { "os": "darwin", "cpu": "x64" }, "sha512-aoifkILvx/XEHyvg8yW57xu95nx7f9f/3ah1+RguHSNKcJMcoCep9VX1Ct1N0ftqg8MC0JUObc7xWL5W14hmjA=="],
"@rspack/binding-linux-arm64-gnu": ["@rspack/binding-linux-arm64-gnu@2.1.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-ROKtqXoLpbZO1vYEcNxQg6COvRhdlJcoib1Z4pzG1nIyctEAUuFmnD7pIvJiVe6M7YuJNXW+1p0BhpptT2XEUQ=="],
"@rspack/binding-linux-arm64-gnu": ["@rspack/binding-linux-arm64-gnu@2.1.2", "", { "os": "linux", "cpu": "arm64" }, "sha512-My4m40tyJSgiCEf3bB2KIEX710q3nZg99LIjy+8Zxgi3oZTkg1bFmFRusFU5U4eN5408zfSqDDGvjDE3Yv7o4w=="],
"@rspack/binding-linux-arm64-musl": ["@rspack/binding-linux-arm64-musl@2.1.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-QdQpG9dK0GfPASjBd/8rUwDNW0ShfFfYJfcoGLNTM6A8EqmgvWwklXDiCVF5dXALbZIksAuRznIKnLDsUoXi9Q=="],
"@rspack/binding-linux-arm64-musl": ["@rspack/binding-linux-arm64-musl@2.1.2", "", { "os": "linux", "cpu": "arm64" }, "sha512-yt+GGWUH7WPE8K97cRc8OpZhH7Pbj1vU+lkvKbDtF/rR8X9a/bJsA/nBqyUV2oBKOVbrp5I8rFZlnDskMqgvKw=="],
"@rspack/binding-linux-riscv64-gnu": ["@rspack/binding-linux-riscv64-gnu@2.1.1", "", { "os": "linux", "cpu": "none" }, "sha512-LGdYCAvblZp2qtHk9UseYftIRyaBzSWqEzd1toaS08sYESXERm+YBbrYKKiyq1yXiU1L7BkhMUxiWc3GCYmKcA=="],
"@rspack/binding-linux-riscv64-gnu": ["@rspack/binding-linux-riscv64-gnu@2.1.2", "", { "os": "linux", "cpu": "none" }, "sha512-uys8Jyw8Z3ralvICbN/L/nZfy5qELIwpOY72rhIqhoDYwFcL4fmMaY7WsvUcJOjCB2rqOcWPaWKuF2oPvo9iDQ=="],
"@rspack/binding-linux-riscv64-musl": ["@rspack/binding-linux-riscv64-musl@2.1.1", "", { "os": "linux", "cpu": "none" }, "sha512-87phiiPGFT1p4gy3Oz6YdNijCPXdjJy43LFdE8AFiZsV4ChFXQPqnVCF2aMRLybYa51A+9wGYlTvxt09yUQUww=="],
"@rspack/binding-linux-riscv64-musl": ["@rspack/binding-linux-riscv64-musl@2.1.2", "", { "os": "linux", "cpu": "none" }, "sha512-JYNVQwqCaRGQWvjHQYzZkIzQiwllMaJwh4Rdu3ww6W2OJcJUqT08sL1pkOtU0iCxT4VUYiRRcp93VGTGpHr8fg=="],
"@rspack/binding-linux-x64-gnu": ["@rspack/binding-linux-x64-gnu@2.1.1", "", { "os": "linux", "cpu": "x64" }, "sha512-om0/PvZzxISsnyz3hdmI3xBi3Qsn6faRGrfp6WJpGHa5JYYaRVWz1MJ9fe+/cQE9ON9r32HpMOeuataCLv+Owg=="],
"@rspack/binding-linux-x64-gnu": ["@rspack/binding-linux-x64-gnu@2.1.2", "", { "os": "linux", "cpu": "x64" }, "sha512-KDoPy0Msf/JLhxgPPrJQzZeB4Qpqd32em8AP5lSW2s6jR5I35dHgAe9xc2A++EQtnSrU4GTn6DBvFC7q84SihQ=="],
"@rspack/binding-linux-x64-musl": ["@rspack/binding-linux-x64-musl@2.1.1", "", { "os": "linux", "cpu": "x64" }, "sha512-YgAyTOM5ZWvWT0Exwcg7QkGUv9PsHT4yIsEyJbH+a99uAKliGMwkHIOP/aJgrCF8G+lTfigY2wyl9cWo4TA4aw=="],
"@rspack/binding-linux-x64-musl": ["@rspack/binding-linux-x64-musl@2.1.2", "", { "os": "linux", "cpu": "x64" }, "sha512-66hWmIGvn4zCKAYXJE9Bp5SNSLYnLFq2Ke/efE+ZtWy43Dd5vk9AAOmThVGBwdwmIxmGtHGCp+cAuS4G0wu0TA=="],
"@rspack/binding-wasm32-wasi": ["@rspack/binding-wasm32-wasi@2.1.1", "", { "dependencies": { "@emnapi/core": "1.11.1", "@emnapi/runtime": "1.11.1", "@napi-rs/wasm-runtime": "1.1.5" }, "cpu": "none" }, "sha512-OicgaB3Dcd+KXsH/I3DkJG7XQyREJScg/jLkCtycFn9BhT0IVJvgi37F8QNJKl8Bp9YZIGehsqK9HpQWav6SLw=="],
"@rspack/binding-wasm32-wasi": ["@rspack/binding-wasm32-wasi@2.1.2", "", { "dependencies": { "@emnapi/core": "1.11.1", "@emnapi/runtime": "1.11.1", "@napi-rs/wasm-runtime": "1.1.6" }, "cpu": "none" }, "sha512-EB4SqH8DW/E/OmqssNQvnIVGQiVUyYNlA/pcc6Ia4MlTNwu6eNDppcNLrToH+kSZpL4CpHSFfSM3eIsSuar2Rw=="],
"@rspack/binding-win32-arm64-msvc": ["@rspack/binding-win32-arm64-msvc@2.1.1", "", { "os": "win32", "cpu": "arm64" }, "sha512-fwL4P3SsC0r/vVGgnmexnc4vkvf+9vWMcGw5fdlPBJNR/zWlZRSXR5V6eXcHrpYVLhBsLYvZYE3NZa9paIUGxw=="],
"@rspack/binding-win32-arm64-msvc": ["@rspack/binding-win32-arm64-msvc@2.1.2", "", { "os": "win32", "cpu": "arm64" }, "sha512-T6Fs/g32MRja/UpCq4AdyPRj8tA0cOkcEa4PrAcn/ztUgK8b/qMVxj5mhMI+n7k+kHZQnpeB1Q4HqdSJi6OocA=="],
"@rspack/binding-win32-ia32-msvc": ["@rspack/binding-win32-ia32-msvc@2.1.1", "", { "os": "win32", "cpu": "ia32" }, "sha512-j3BISXbjPyI57HeHxW9Jx+UP7RebNQ4t62uCbRP29caf9aghYJP+ZA5nJ2X7pol7N5Je2/V6WNahuNd0zQFDOA=="],
"@rspack/binding-win32-ia32-msvc": ["@rspack/binding-win32-ia32-msvc@2.1.2", "", { "os": "win32", "cpu": "ia32" }, "sha512-OtxkFVz14mVL4QK8QriSELn9B6PaYGHw1jGJwVDEzpu2ZxSHCTQPz9dVE1ekYtREEqZUkRU7Fp7VfhJSmjTt2Q=="],
"@rspack/binding-win32-x64-msvc": ["@rspack/binding-win32-x64-msvc@2.1.1", "", { "os": "win32", "cpu": "x64" }, "sha512-aPNNFuZT5iBM8+S9J4P5ywJbf3tUvZN3Ppe6mXJ8/jTVDUDhe0YVj3AgD/AuxnUvl+yHrLfQkN1nNwUfIcomfA=="],
"@rspack/binding-win32-x64-msvc": ["@rspack/binding-win32-x64-msvc@2.1.2", "", { "os": "win32", "cpu": "x64" }, "sha512-Am+nx9fLF3nzgD/K05Bs1Bb+WO8SFLWAYRbXkymaL1r+RQxjRj7jd5ap2PhGOCcfaNA4yVWkAFvmFP92eRu7bQ=="],
"@rspack/cli": ["@rspack/cli@2.1.1", "", { "peerDependencies": { "@rspack/core": "^2.0.0-0", "@rspack/dev-server": "^2.0.0-0" }, "optionalPeers": ["@rspack/dev-server"], "bin": { "rspack": "./bin/rspack.js" } }, "sha512-zsJFurwCDesy0dud7cejObagbbXsvKcQ6j485nmnytBsXV17Uf6XfedQMxnOXBZeGIel+I7Jl/Jp3Sn08TsZuw=="],
"@rspack/cli": ["@rspack/cli@2.1.2", "", { "peerDependencies": { "@rspack/core": "^2.0.0-0", "@rspack/dev-server": "^2.0.0-0" }, "optionalPeers": ["@rspack/dev-server"], "bin": { "rspack": "./bin/rspack.js" } }, "sha512-GTBsKuaeqDii6NaGPkQwvycMSjqMUMMRGiAwH0sXsFIn8X7sFa4xpoL7pIhbebVBDGzYhUBqWzNQFuL5u2FeoQ=="],
"@rspack/core": ["@rspack/core@2.1.1", "", { "dependencies": { "@rspack/binding": "2.1.1" }, "peerDependencies": { "@module-federation/runtime-tools": "^0.24.1 || ^2.0.0", "@swc/helpers": "^0.5.23" }, "optionalPeers": ["@module-federation/runtime-tools", "@swc/helpers"] }, "sha512-Rgz6xZcD0rm7ejZhYNi13qvW2dSnIQQt/7D3xbYoT5hOU838JbkF0P3SAMFGKE+FyLZswnyRpxGfnYHZQqDmJA=="],
"@rspack/core": ["@rspack/core@2.1.2", "", { "dependencies": { "@rspack/binding": "2.1.2" }, "peerDependencies": { "@module-federation/runtime-tools": "^0.24.1 || ^2.0.0", "@swc/helpers": "^0.5.23" }, "optionalPeers": ["@module-federation/runtime-tools", "@swc/helpers"] }, "sha512-crpNQKhHfnzrIl4Sa4fjH30Ho5aAPgyqpmJZ41SkUFOzyKHdZKYfE5LF3CMh7MiFQFPPxiiKf5BcpxmtZZx4MQ=="],
"@testing-library/dom": ["@testing-library/dom@10.4.1", "", { "dependencies": { "@babel/code-frame": "^7.10.4", "@babel/runtime": "^7.12.5", "@types/aria-query": "^5.0.1", "aria-query": "5.3.0", "dom-accessibility-api": "^0.5.9", "lz-string": "^1.5.0", "picocolors": "1.1.1", "pretty-format": "^27.0.2" } }, "sha512-o4PXJQidqJl82ckFaXUeoAW+XysPLauYI43Abki5hABd853iMhitooc6znOnczgbTYmEP6U6/y1ZyKAIsvMKGg=="],
@@ -507,7 +507,7 @@
"js-tokens": ["js-tokens@4.0.0", "", {}, "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="],
"js-yaml": ["js-yaml@5.2.0", "", { "dependencies": { "argparse": "^2.0.1" }, "bin": { "js-yaml": "bin/js-yaml.mjs" } }, "sha512-YeLUMlvR4Ou1B119LIaM0r65JvbOBooJDc9yEu0dClb/uSC5P4FrLU8OCCz/HXWvtPoIrR0dRzABTjo1sTN9Bw=="],
"js-yaml": ["js-yaml@5.2.1", "", { "dependencies": { "argparse": "^2.0.1" }, "bin": { "js-yaml": "bin/js-yaml.mjs" } }, "sha512-zfLtNfQqxVqq3uaTqSkh4x4hZw3KHobGUA0fJUj4wawW8bsQLTVqpHdXSIzidh7o+4lEW36tANuAGdaFx6Zgnw=="],
"jsonc-eslint-parser": ["jsonc-eslint-parser@2.4.2", "", { "dependencies": { "acorn": "^8.5.0", "eslint-visitor-keys": "^3.0.0", "espree": "^9.0.0", "semver": "^7.3.5" } }, "sha512-1e4qoRgnn448pRuMvKGsFFymUCquZV0mpGgOyIKNgD3JVDTsVJyRBGH/Fm0tBb8WsWGgmB1mDe6/yJMQM37DUA=="],
@@ -609,7 +609,7 @@
"picocolors": ["picocolors@1.1.1", "", {}, "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="],
"picomatch": ["picomatch@2.3.2", "", {}, "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA=="],
"picomatch": ["picomatch@4.0.4", "", {}, "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A=="],
"pirates": ["pirates@4.0.7", "", {}, "sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA=="],
@@ -757,7 +757,7 @@
"svgpath": ["svgpath@2.6.0", "", {}, "sha512-OIWR6bKzXvdXYyO4DK/UWa1VA1JeKq8E+0ug2DG98Y/vOmMpfZNj+TIG988HjfYSqtcy/hFOtZq/n/j5GSESNg=="],
"svgtofont": ["svgtofont@6.5.2", "", { "dependencies": { "auto-config-loader": "^2.0.0", "cheerio": "~1.0.0", "colors-cli": "~1.0.28", "fs-extra": "~11.2.0", "image2uri": "^2.1.2", "nunjucks": "^3.2.4", "svg2ttf": "~6.1.0", "svgicons2svgfont": "~15.0.0", "svgo": "~3.3.0", "ttf2eot": "~3.1.0", "ttf2woff": "~3.0.0", "ttf2woff2": "~8.0.0", "yargs": "^17.7.2" }, "peerDependencies": { "@types/svg2ttf": "~5.0.1" }, "optionalPeers": ["@types/svg2ttf"], "bin": { "svgtofont": "lib/cli.js" } }, "sha512-D9aqybvt3yXIi4RLPbPDXwX9fvoTKN8HeiBeRA7Yb+5TtfdF9Bz9/TGZHEu+hlRtAmlZ3YBBluElfy9BMitcoA=="],
"svgtofont": ["svgtofont@6.5.3", "", { "dependencies": { "auto-config-loader": "^2.0.0", "cheerio": "~1.0.0", "colors-cli": "~1.0.28", "fs-extra": "~11.2.0", "image2uri": "^2.1.2", "nunjucks": "^3.2.4", "svg2ttf": "~6.1.0", "svgicons2svgfont": "~15.0.0", "svgo": "~3.3.0", "ttf2eot": "~3.1.0", "ttf2woff": "~3.0.0", "ttf2woff2": "~8.0.0", "yargs": "^17.7.2" }, "peerDependencies": { "@types/svg2ttf": "~5.0.1" }, "optionalPeers": ["@types/svg2ttf"], "bin": { "svgtofont": "lib/cli.js" } }, "sha512-koKMwoA+olMx7qY2LzdVF1+5/y0c1Tu1iZinTCClF4c5UVrSpExayMoyHqLB/58aTe5EUuE6htg5S2Y/MN4Azw=="],
"sync-child-process": ["sync-child-process@1.0.2", "", { "dependencies": { "sync-message-port": "^1.0.0" } }, "sha512-8lD+t2KrrScJ/7KXCSyfhT3/hRq78rC0wBFqNJXv3mZyn6hW2ypM05JmlSvtqRbeq6jqA94oHbxAr2vYsJ8vDA=="],
@@ -773,7 +773,7 @@
"tgui": ["tgui@workspace:packages/tgui"],
"tgui-core": ["tgui-core@6.1.0", "", { "dependencies": { "@floating-ui/react": "^0.27.16", "@nozbe/microfuzz": "^1.0.0" }, "peerDependencies": { "react": "^19.1.0", "react-dom": "^19.1.0" } }, "sha512-j512gHwvK8aPILIxS26LLHMRpO/EXn5pIiIdGphpqbDIe6ir3SpXDK+wfuEZ/5LxXlP6UQCxSSduR3JI8frIIg=="],
"tgui-core": ["tgui-core@6.1.1", "", { "dependencies": { "@floating-ui/react": "^0.27.16", "@nozbe/microfuzz": "^1.0.0" }, "peerDependencies": { "react": "^19.1.0", "react-dom": "^19.1.0" } }, "sha512-RiINJPsKLvaC4qkKtPdwoe9t08bTuUW8bpHDEIv3P6eZYJZfNSJwPy/hvdiVSeBA1V4ZtDTd+XADWsUzUVlMZA=="],
"tgui-dev-server": ["tgui-dev-server@workspace:packages/tgui-dev-server"],
@@ -861,7 +861,7 @@
"@npmcli/agent/lru-cache": ["lru-cache@10.4.3", "", {}, "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ=="],
"@parcel/watcher/picomatch": ["picomatch@4.0.4", "", {}, "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A=="],
"anymatch/picomatch": ["picomatch@2.3.2", "", {}, "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA=="],
"cacache/glob": ["glob@10.5.0", "", { "dependencies": { "foreground-child": "^3.1.0", "jackspeak": "^3.1.2", "minimatch": "^9.0.4", "minipass": "^7.1.2", "package-json-from-dist": "^1.0.0", "path-scurry": "^1.11.1" }, "bin": { "glob": "dist/esm/bin.mjs" } }, "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg=="],
@@ -893,6 +893,8 @@
"parse5/entities": ["entities@6.0.1", "", {}, "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g=="],
"readdirp/picomatch": ["picomatch@2.3.2", "", {}, "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA=="],
"sass/chokidar": ["chokidar@5.0.0", "", { "dependencies": { "readdirp": "^5.0.0" } }, "sha512-TQMmc3w+5AxjpL8iIiwebF73dRDF4fBIieAqGn9RGCWaEVwQ6Fb2cGe31Yns0RRIzii5goJ1Y7xbMwo1TxMplw=="],
"socks-proxy-agent/agent-base": ["agent-base@7.1.4", "", {}, "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ=="],
@@ -909,8 +911,6 @@
"terser/commander": ["commander@2.20.3", "", {}, "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ=="],
"tinyglobby/picomatch": ["picomatch@4.0.4", "", {}, "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A=="],
"wrap-ansi/ansi-styles": ["ansi-styles@4.3.0", "", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="],
"wrap-ansi-cjs/ansi-styles": ["ansi-styles@4.3.0", "", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="],
+2 -2
View File
@@ -4,8 +4,8 @@
"type": "module",
"devDependencies": {
"@happy-dom/global-registrator": "^20.10.6",
"@rspack/cli": "^2.1.1",
"@rspack/core": "^2.1.1",
"@rspack/cli": "^2.1.2",
"@rspack/core": "^2.1.2",
"@testing-library/react": "^16.3.2",
"@types/bun": "^1.3.14",
"@types/react": "^19.2.17",
+1 -1
View File
@@ -8,6 +8,6 @@
},
"dependencies": {
"svgo": "^4.0.1",
"svgtofont": "^6.5.2"
"svgtofont": "^6.5.3"
}
}
+1 -1
View File
@@ -9,7 +9,7 @@
"react": "^19.2.7",
"react-dom": "^19.2.7",
"tgui": "workspace:*",
"tgui-core": "^6.1.0",
"tgui-core": "^6.1.1",
"tgui-dev-server": "workspace:*",
"zod": "^4.4.3"
},
+1 -1
View File
@@ -6,7 +6,7 @@
"react": "^19.2.7",
"react-dom": "^19.2.7",
"tgui": "workspace:*",
"tgui-core": "^6.1.0"
"tgui-core": "^6.1.1"
},
"private": true
}
@@ -0,0 +1,22 @@
import { useBackend } from 'tgui/backend';
import { Icon, Image } from 'tgui-core/components';
import type { Data } from './types';
export const CrewCookieIcon = (props) => {
const { act, data } = useBackend<Data>();
const { crewicon } = data;
return (
<>
{crewicon ? (
<Image
src={crewicon.substring(1, crewicon.length - 1)}
width="128px"
height="128px"
/>
) : (
<Icon width="128px" height="128px" fontSize={4} name="camera" />
)}
</>
);
};
@@ -0,0 +1,31 @@
import { useBackend } from 'tgui/backend';
import { Stack, Tabs } from 'tgui-core/components';
import type { Data } from '../types';
export const FoodMenuTabs = (props) => {
const { act, data } = useBackend<Data>();
const { active_menu, menucatagories } = data;
const menusToShow = [...menucatagories].sort(
(a, b) => a.sortorder - b.sortorder,
);
return (
<Stack>
<Stack.Item>
<Tabs fluid textAlign="center">
{menusToShow.map((menu) => (
<Tabs.Tab
key={menu.ref}
icon="list"
selected={menu.id === active_menu}
onClick={() => act('setactive_menu', { setactive_menu: menu.id })}
>
{menu.name}
</Tabs.Tab>
))}
</Tabs>
</Stack.Item>
</Stack>
);
};
@@ -0,0 +1,182 @@
import { sanitizeCssClassName } from 'common/css_sanity';
import { useEffect } from 'react';
import { useBackend } from 'tgui/backend';
import {
Box,
Button,
LabeledList,
Section,
Stack,
Tabs,
} from 'tgui-core/components';
import { classes } from 'tgui-core/react';
import { CrewCookieIcon } from '../CrewCookieIcon';
import type { Data } from '../types';
export const FoodSelectionMenu = (props) => {
const { act, data } = useBackend<Data>();
const {
busy,
menucatagories,
active_menu,
activefood,
crew_cookies,
activecrew,
} = data;
crew_cookies;
const recipesToShow = menucatagories
.find((category) => category.id === active_menu)
?.recipes.filter((recipe) => !recipe.hidden)
.sort((a, b) => a.name.localeCompare(b.name));
const selectedFood = recipesToShow?.find((c) => c.type === activefood);
const selectedCrew = crew_cookies.find((c) => c.name === activecrew);
useEffect(() => {
if (active_menu !== 'crew' && !selectedFood && recipesToShow?.length) {
act('setactive_food', { setactive_food: recipesToShow[0].type });
}
}, [active_menu]);
if (!recipesToShow) {
return <Box color="bad">No recipes found!</Box>;
}
const cookiesToShow = crew_cookies
.filter((cookie) => cookie.category === active_menu)
.sort((a, b) => a.name.localeCompare(b.name));
return (
<Section fill>
<Stack fill>
<Stack.Item basis="30%">
<Section
title="Food Selection"
scrollable
fill
buttons={
active_menu === 'crew' && (
<Button
onClick={() => act('refresh')}
tooltip="Refresh"
icon="arrows-rotate"
/>
)
}
>
<Tabs vertical>
{active_menu === 'crew'
? cookiesToShow.map((cookie) => (
<Tabs.Tab key={cookie.name}>
<Button
fluid
selected={
selectedCrew && cookie.name === selectedCrew.name
}
onClick={() => {
act('setactive_crew', {
setactive_crew: cookie.name,
});
}}
>
{cookie.name}
</Button>
</Tabs.Tab>
))
: recipesToShow.map((recipe) => (
<Tabs.Tab key={recipe.ref}>
<Button
fluid
selected={
selectedFood && recipe.ref === selectedFood.ref
}
onClick={() =>
act('setactive_food', {
setactive_food: recipe.type,
})
}
>
{recipe.name}
</Button>
</Tabs.Tab>
))}
</Tabs>
</Section>
</Stack.Item>
<Stack.Item grow ml={1}>
{active_menu === 'crew' ? (
selectedCrew ? (
<Section title="Product Details" fill scrollable>
<Stack vertical>
<Stack.Item>
<LabeledList>
<LabeledList.Item label="Name">
{selectedCrew.name}
</LabeledList.Item>
<LabeledList.Item label="Species">
{selectedCrew.species}
</LabeledList.Item>
</LabeledList>
</Stack.Item>
<Stack.Item>
<Button
disabled={busy}
align="center"
color="transparent"
onClick={() =>
act('crewprint', {
crewprint: selectedCrew.name,
})
}
>
<CrewCookieIcon />
<Box>Print this Cookie</Box>
</Button>
</Stack.Item>
</Stack>
</Section>
) : (
<Box color="label">Please select an offering.</Box>
)
) : selectedFood ? (
<Section title="Product Details" fill scrollable>
<Stack vertical>
<Stack.Item>
<LabeledList>
<LabeledList.Item label="Name">
{selectedFood.name}
</LabeledList.Item>
<LabeledList.Divider />
<LabeledList.Item label="Description">
{selectedFood.desc || 'No description available.'}
</LabeledList.Item>
<LabeledList.Divider />
</LabeledList>
</Stack.Item>
<Stack.Item>
<Button
disabled={busy}
align="center"
color="transparent"
onClick={() => act('make', { make: selectedFood.ref })}
>
<Box
className={classes([
'synthesizer128x128',
sanitizeCssClassName(selectedFood.type),
])}
/>
<Box>Print this meal</Box>
</Button>
</Stack.Item>
</Stack>
</Section>
) : (
<Box color="label">Please select an offering.</Box>
)}
</Stack.Item>
</Stack>
</Section>
);
};
@@ -0,0 +1,33 @@
import { useBackend } from 'tgui/backend';
import { Box, LabeledList, ProgressBar, Section } from 'tgui-core/components';
import type { Data } from '../types';
/** Displays the current Cartridge status. */
export const SynthCartGuage = (props) => {
const { data } = useBackend<Data>();
const { isThereCart, cartFillStatus = 0 } = data;
const adjustedCartChange = cartFillStatus / 100;
return (
<Section title="Cartridge Status">
{isThereCart ? (
<LabeledList.Item label="Product Remaining">
<ProgressBar
color={cartFillStatus ? 'purple' : 'red'}
value={adjustedCartChange}
width={20}
/>
</LabeledList.Item>
) : (
<LabeledList.Item label="Cartridge Problem">
<Box color="label">
One or more cartridges are missing or damaged. <br />
<br />
Sabresnacks Co. recommends ordering a genuine Sabresnacks
replacement cartridge through your local logistical cargo service.
</Box>
</LabeledList.Item>
)}
</Section>
);
};
@@ -0,0 +1,29 @@
import { Window } from 'tgui/layouts';
import { Section, Stack } from 'tgui-core/components';
import { FoodMenuTabs } from './SubTabs/FoodMenuTabs';
import { FoodSelectionMenu } from './SubTabs/FoodSelectionMenu';
import { SynthCartGuage } from './SubTabs/SynthCartGuage';
export const FoodSynthesizer = (props) => {
return (
<Window width={765} height={520}>
<Window.Content>
<Stack fill vertical>
<Stack.Item>
<Section>
<SynthCartGuage />
</Section>
</Stack.Item>
<Stack.Item>
<Section title="Menu Selection">
<FoodMenuTabs />
</Section>
</Stack.Item>
<Stack.Item grow>
<FoodSelectionMenu />
</Stack.Item>
</Stack>
</Window.Content>
</Window>
);
};
@@ -0,0 +1,35 @@
import type { BooleanLike } from 'tgui-core/react';
type MenuCategory = {
id: string;
name: string;
ref: string;
recipes: Recipe[];
sortorder: number;
};
type Recipe = {
type: string;
name: string;
ref: string;
desc?: string;
hidden?: BooleanLike;
};
type CrewCookie = {
name: string;
species: string;
category: string;
};
export type Data = {
busy: BooleanLike;
isThereCart: BooleanLike;
cartFillStatus?: number;
active_menu: string;
menucatagories: MenuCategory[];
activefood: string | null;
crew_cookies: CrewCookie[];
activecrew: string | null;
crewicon?: string;
};
@@ -66,6 +66,7 @@ export const SubtabSettings = (props: {
borg_petting,
resleeve_lock,
resleeve_scan,
synth_cookie,
mind_scan,
vantag_volunteer,
vantag_preference,
@@ -239,6 +240,15 @@ export const SubtabSettings = (props: {
{resleeve_scan ? 'Yes' : 'No'}
</Button>
</LabeledList.Item>
<LabeledList.Item label="Allow Synth cookie replicas">
<Button
onClick={() => act('toggle_synth_cookie')}
selected={synth_cookie}
tooltip="Opt-in to allow crewmembers to print minature replica cookies from food synthesizers of yourself."
>
{synth_cookie ? 'Yes' : 'No'}
</Button>
</LabeledList.Item>
<LabeledList.Item label="Start With Mind Scan">
<Button
onClick={() => act('toggle_mind_scan')}
@@ -292,6 +292,7 @@ export type MiscData = {
resleeve_lock: BooleanLike;
resleeve_scan: BooleanLike;
synth_cookie: BooleanLike;
mind_scan: BooleanLike;
vantag_volunteer: BooleanLike;
+2 -2
View File
@@ -8,14 +8,14 @@
"es-toolkit": "^1.49.0",
"highlight.js": "^11.11.1",
"jotai": "^2.20.1",
"js-yaml": "^5.2.0",
"js-yaml": "^5.2.1",
"marked": "^18.0.5",
"marked-base-url": "^1.1.9",
"marked-smartypants": "^1.1.12",
"react": "^19.2.7",
"react-dom": "^19.2.7",
"react-json-tree": "^0.20.0",
"tgui-core": "^6.1.0",
"tgui-core": "^6.1.1",
"tgui-dev-server": "workspace:*"
},
"private": true
+4
View File
@@ -1034,6 +1034,7 @@
#include "code\datums\wires\circuit_imprinter.dm"
#include "code\datums\wires\disposals.dm"
#include "code\datums\wires\explosive.dm"
#include "code\datums\wires\foodsynth.dm"
#include "code\datums\wires\grid_checker.dm"
#include "code\datums\wires\jukebox.dm"
#include "code\datums\wires\mines.dm"
@@ -2379,6 +2380,7 @@
#include "code\modules\asset_cache\assets\vv.dm"
#include "code\modules\asset_cache\assets\spritesheets\chat.dm"
#include "code\modules\asset_cache\assets\spritesheets\chem_master.dm"
#include "code\modules\asset_cache\assets\spritesheets\foodsynth.dm"
#include "code\modules\asset_cache\assets\spritesheets\kitchen_recipes.dm"
#include "code\modules\asset_cache\assets\spritesheets\pai_icons.dm"
#include "code\modules\asset_cache\assets\spritesheets\pipes.dm"
@@ -2931,6 +2933,7 @@
#include "code\modules\food\food.dm"
#include "code\modules\food\recipe.dm"
#include "code\modules\food\recipe_dump.dm"
#include "code\modules\food\recipes_foodsynthesizer.dm"
#include "code\modules\food\recipes_fryer.dm"
#include "code\modules\food\recipes_fryer_vr.dm"
#include "code\modules\food\recipes_grill.dm"
@@ -2977,6 +2980,7 @@
#include "code\modules\food\kitchen\cooking_machines\candy.dm"
#include "code\modules\food\kitchen\cooking_machines\cereal.dm"
#include "code\modules\food\kitchen\cooking_machines\container.dm"
#include "code\modules\food\kitchen\cooking_machines\foodsynthesizer.dm"
#include "code\modules\food\kitchen\cooking_machines\fryer.dm"
#include "code\modules\food\kitchen\cooking_machines\grill.dm"
#include "code\modules\food\kitchen\cooking_machines\oven.dm"