diff --git a/code/_helpers/global_lists.dm b/code/_helpers/global_lists.dm
index 003098511b6..7fc15ca8f27 100644
--- a/code/_helpers/global_lists.dm
+++ b/code/_helpers/global_lists.dm
@@ -72,6 +72,9 @@ var/global/list/rune_list = new()
var/global/list/syndicate_access = list(access_maint_tunnels, access_syndicate, access_external_airlocks)
+/// Implants
+GLOBAL_LIST_EMPTY(implants_list)
+
/// Associative list of string -> string, where key is armor class and value is an attack type it protects against.
GLOBAL_LIST_INIT(descriptive_attack_types, list(
"melee" = "blunt force",
diff --git a/code/game/machinery/computer/guestpass.dm b/code/game/machinery/computer/guestpass.dm
index b58c54d2ba5..22fb613edd9 100644
--- a/code/game/machinery/computer/guestpass.dm
+++ b/code/game/machinery/computer/guestpass.dm
@@ -70,7 +70,6 @@
if(istype(O, /obj/item/card/id))
if(!giver && user.drop(O, src))
giver = O
- updateUsrDialog()
else if(giver)
to_chat(user, "There is already ID card inside.")
return
@@ -79,114 +78,162 @@
/obj/machinery/computer/guestpass/attack_ai(mob/user as mob)
return attack_hand(user)
-/obj/machinery/computer/guestpass/attack_hand(mob/user as mob)
- if(..())
- return
+/obj/machinery/computer/guestpass/attack_hand(mob/user)
+ . = ..()
- user.set_machine(src)
- var/dat = ""
+ tgui_interact(user)
- if (mode == 1) //Logs
- dat += "
Activity log
"
- for (var/entry in internal_log)
- dat += "[entry]
"
- dat += "Print
"
- dat += "Back
"
- else
- dat += "Guest pass terminal #[uid]
"
- dat += "View activity log
"
- dat += "Issuing ID: [giver]
"
- dat += "Issued to: [giv_name]
"
- dat += "Reason: [reason]
"
- dat += "Duration (minutes): [duration] m
"
- dat += "Access to areas:
"
- if (giver && giver.access)
- for (var/A in giver.access)
- var/area = get_access_desc(A)
- if (A in accesses)
- area = "[area]"
- dat += "[area]
"
- dat += "
Issue pass
"
-
- show_browser(user, dat, "window=guestpass;size=400x520")
- onclose(user, "guestpass")
-
-
-/obj/machinery/computer/guestpass/OnTopic(mob/user, href_list, state)
- if (href_list["mode"])
- mode = text2num(href_list["mode"])
- . = TOPIC_REFRESH
-
- else if (href_list["choice"])
- switch(href_list["choice"])
- if ("giv_name")
- var/nam = sanitize(input(user, "Person pass is issued to", "Name", giv_name) as text|null)
- if (nam && CanUseTopic(user, state))
- giv_name = nam
- if ("reason")
- var/reas = sanitize(input(user, "Reason why pass is issued", "Reason", reason) as text|null)
- if(reas && CanUseTopic(user, state))
- reason = reas
- if ("duration")
- var/dur = input(user, "Duration (in minutes) during which pass is valid (up to 30 minutes).", "Duration") as num|null
- if (dur && CanUseTopic(user, state))
- if (dur > 0 && dur <= 30)
- duration = dur
- else
- to_chat(user, "Invalid duration.")
- if ("access")
- var/A = text2num(href_list["access"])
- if (A in accesses)
- accesses.Remove(A)
- else if(giver && (A in giver.access))
- accesses.Add(A)
- . = TOPIC_REFRESH
- else if (href_list["action"])
- switch(href_list["action"])
- if ("id")
- if (giver)
- giver.dropInto(user.loc)
- if(ishuman(user))
- user.pick_or_drop(giver)
+/obj/machinery/computer/guestpass/tgui_interact(mob/user, datum/tgui/ui)
+ ui = SStgui.try_update_ui(user, src, ui)
+
+ if (!ui)
+ ui = new(user, src, "Guestpass", src)
+ ui.open()
+
+/obj/machinery/computer/guestpass/tgui_data(mob/user)
+ var/list/data = list(
+ "scanName" = giver?.registered_name,
+ "areas",
+ "issueLog" = list(internal_log),
+ )
+
+ var/list/regions = list()
+ for(var/i in ACCESS_REGION_SECURITY to ACCESS_REGION_SUPPLY) // code/game/jobs/_access_defs.dm
+ var/list/region = list(
+ "name" = get_region_accesses_name(i),
+ "id" = i,
+ )
+ var/list/accessess_data = list()
+ for(var/j in get_region_accesses(i))
+ var/list/access = list()
+ access["name"] = get_access_desc(j)
+ access["id"] = j
+ access["req"] = (j in accesses) ? TRUE : FALSE
+ accessess_data[++accessess_data.len] = access
+
+ region["accesses"] = accessess_data
+ regions[++regions.len] = region
+
+ data["regions"] = regions
+
+ return data
+
+/obj/machinery/computer/guestpass/tgui_act(action, params)
+ . = ..()
+ if(.)
+ return
+
+ switch(action)
+ if("select_access")
+ if(isnull(params["area"]))
+ return
+
+ if(LAZYISIN(accesses, text2num(params["area"])))
+ accesses -= text2num(params["area"])
+ else
+ accesses |= text2num(params["area"])
+ return TRUE
+
+ if("scan")
+ if(giver)
+ if(ishuman(usr))
+ usr.pick_or_drop(giver, get_turf(usr))
giver = null
- accesses.Cut()
else
- var/obj/item/I = user.get_active_hand()
- if(istype(I, /obj/item/card/id) && user.drop(I, src))
+ giver.forceMove(get_turf(src))
+ giver = null
+ accesses.Cut()
+ else
+ var/obj/item/I = usr.get_active_hand()
+ if(istype(I, /obj/item/card/id))
+ if(usr.drop(I, src))
giver = I
- . = TOPIC_REFRESH
- if ("print")
- var/dat = "Activity log of guest pass terminal #[uid]
"
- for (var/entry in internal_log)
- dat += "[entry]
"
- var/obj/item/paper/P = new /obj/item/paper(loc)
- P.set_content(dat, "activity log", TRUE)
- . = TOPIC_REFRESH
-
- if ("issue")
- if (giver && accesses.len)
- var/number = add_zero(random_id("guestpass_id_number",1000,9999), 4)
- var/entry = "\[[stationtime2text()]\] Pass #[number] issued by [giver.registered_name] ([giver.assignment]) to [giv_name]. Reason: [reason]. Granted access to following areas: "
- var/list/access_descriptors = list()
- for (var/A in accesses)
- if (A in giver.access)
- access_descriptors += get_access_desc(A)
- entry += english_list(access_descriptors, and_text = ", ")
- entry += ". Expires at [worldtime2stationtime(world.time + duration MINUTES)]."
- internal_log.Add(entry)
-
- var/obj/item/card/id/guest/pass = new(src.loc)
- pass.temp_access = accesses.Copy()
- pass.registered_name = giv_name
- pass.expiration_time = world.time + duration MINUTES
- pass.reason = reason
- pass.SetName("guest pass #[number]")
- pass.assignment = "Guest"
- playsound(src.loc, 'sound/machines/ping.ogg', 25, 0)
- . = TOPIC_REFRESH
- else if(!giver)
- to_chat(user, "Cannot issue pass without issuing ID.")
- else if(!accesses.len)
- to_chat(user, "Cannot issue pass without at least one granted access permission.")
- if(.)
- attack_hand(user)
+ return TRUE
+
+ if("deselect_region")
+ var/region = text2num(params["region"])
+ if(isnull(region))
+ return
+
+ accesses -= get_region_accesses(region)
+ return TRUE
+
+ if("select_region")
+ var/region = text2num(params["region"])
+ if(isnull(region))
+ return
+
+ var/list/new_accesses = get_region_accesses(region)
+ for(var/A in new_accesses)
+ if(A in giver.access)
+ accesses |= A
+ return TRUE
+
+ if("print")
+ var/obj/item/paper/P = new /obj/item/paper(src)
+ var/dat = "Activity log of guest pass terminal #[any2ref(src)]
"
+ for(var/entry in internal_log)
+ dat += "[entry]
"
+ P.name = "activity log"
+ P.info = dat
+ usr.pick_or_drop(P, get_turf(usr))
+ return TRUE
+
+ if("select_access")
+ var/access = text2num(params["access"])
+ if(isnull(access))
+ return
+
+ accesses |= access
+ return TRUE
+
+ if("select_region")
+ var/region = text2num(params["region"])
+ if(isnull(region))
+ return
+
+ var/list/new_accesses = get_region_accesses(region)
+ for(var/A in new_accesses)
+ if(A in giver.access)
+ accesses.Add(A)
+ return TRUE
+
+ if("deselect_region")
+ var/region = text2num(params["region"])
+ if(isnull(region))
+ return
+ accesses -= get_region_accesses(region)
+ return TRUE
+
+ if("deselect_all")
+ accesses = list()
+ return TRUE
+
+ if("select_all")
+ for(var/A in get_all_accesses())
+ if(A in giver.access)
+ accesses += A
+ return TRUE
+
+ if("issue")
+ if(!giver || !LAZYLEN(accesses))
+ return
+
+ var/number = add_zero(random_id("guestpass_id_number", 1000, 9999), 4)
+ var/entry = "\[[stationtime2text()]\] Pass #[number] issued by [giver.registered_name] ([giver.assignment]) to [giv_name]. Reason: [reason]. Granted access to following areas: "
+ var/list/access_descriptors = list()
+ for(var/A in accesses)
+ if(A in giver.access)
+ access_descriptors += get_access_desc(A)
+ entry += english_list(access_descriptors, and_text = ", ")
+ entry += ". Expires at [worldtime2stationtime(world.time + duration MINUTES)]."
+ internal_log.Add(entry)
+
+ var/obj/item/card/id/guest/pass = new(loc)
+ pass.temp_access = accesses.Copy()
+ pass.registered_name = giv_name
+ pass.expiration_time = world.time + duration MINUTES
+ pass.reason = reason
+ pass.SetName("guest pass #[number]")
+ pass.assignment = "Guest"
+ playsound(get_turf(src), 'sound/machines/ping.ogg', 25, 0)
diff --git a/code/game/machinery/computer/prisoner.dm b/code/game/machinery/computer/prisoner.dm
index 793dcf6ecbe..df39d375436 100644
--- a/code/game/machinery/computer/prisoner.dm
+++ b/code/game/machinery/computer/prisoner.dm
@@ -1,5 +1,3 @@
-//This file was auto-corrected by findeclaration.exe on 25.5.2012 20:42:31
-
/obj/machinery/computer/prisoner
name = "prisoner management console"
icon = 'icons/obj/computer.dmi'
@@ -8,97 +6,86 @@
light_color = "#a91515"
req_access = list(access_armory)
circuit = /obj/item/circuitboard/prisoner
- var/id = 0.0
+ var/id = 0
var/temp = null
var/status = 0
var/timeleft = 60
var/stop = 0.0
- var/screen = 0 // 0 - No Access Denied, 1 - Access allowed
-
-
- attack_ai(mob/user as mob)
- return src.attack_hand(user)
-
- attack_hand(mob/user as mob)
- if(..())
- return
- user.set_machine(src)
- var/dat = ""
- dat += "Prisoner Implant Manager System
"
- if(screen == 0)
- dat += "
Unlock Console"
- else if(screen == 1)
- dat += "
Chemical Implants
"
- var/turf/Tr = null
- for(var/obj/item/implant/chem/C in world)
- Tr = get_turf(C)
- if((Tr) && !AreConnectedZLevels(Tr.z, src.z)) continue // Out of range
- if(!C.implanted) continue
- dat += "[C.imp_in.name] | Remaining Units: [C.reagents.total_volume] | Inject: "
- dat += "((1))"
- dat += "((5))"
- dat += "((10))
"
- dat += "********************************
"
- dat += "
Tracking Implants
"
- for(var/obj/item/implant/tracking/T in world)
- Tr = get_turf(T)
- if((Tr) && !AreConnectedZLevels(Tr.z, src.z)) continue // Out of range
- if(!T.implanted) continue
- var/loc_display = "Space"
- var/mob/living/carbon/M = T.imp_in
- if(!istype(M.loc, /turf/space))
- var/turf/mob_loc = get_turf(M)
- loc_display = mob_loc.loc
- if(T.malfunction)
- loc_display = pick(playerlocs)
- dat += "ID: [T.id] | Location: [loc_display]
"
- dat += "(Message Holder) |
"
- dat += "********************************
"
- dat += "
Lock Console"
-
- show_browser(user, dat, "window=computer;size=400x500")
- onclose(user, "computer")
- return
+/obj/machinery/computer/prisoner/tgui_state(mob/user)
+ return GLOB.tgui_default_state
- Process()
- if(!..())
- src.updateDialog()
+/obj/machinery/computer/prisoner/attack_hand(mob/user)
+ . = ..()
+ if(.)
return
+ tgui_interact(user)
+
+/obj/machinery/computer/prisoner/tgui_interact(mob/user, datum/tgui/ui = null)
+ ui = SStgui.try_update_ui(user, src, ui)
+ if(!ui)
+ ui = new(user, src, "PrisonerImplantManager", name)
+ ui.open()
+
+/obj/machinery/computer/prisoner/tgui_data(mob/user)
+ var/list/data = list()
+
+ for(var/obj/item/implant/I in GLOB.implants_list)
+ if(!istype(I, /obj/item/implant/tracking) && !istype(I, /obj/item/implant/chem))
+ continue
+
+ var/turf/T = get_turf(I)
+ if(!istype(T) || !AreConnectedZLevels(T?.z, z))
+ continue
+
+ if(!I.implanted)
+ continue
+
+ var/list/implant_data = list(
+ "implantee" = I.imp_in?.name,
+ "ref" = any2ref(I),
+ )
- Topic(href, href_list)
- if(..())
- return
- if((usr.contents.Find(src) || (in_range(src, usr) && istype(src.loc, /turf))) || (istype(usr, /mob/living/silicon)))
- usr.set_machine(src)
-
- if(href_list["inject1"])
- var/obj/item/implant/I = locate(href_list["inject1"])
- if(I) I.activate(1)
-
- else if(href_list["inject5"])
- var/obj/item/implant/I = locate(href_list["inject5"])
- if(I) I.activate(5)
-
- else if(href_list["inject10"])
- var/obj/item/implant/I = locate(href_list["inject10"])
- if(I) I.activate(10)
-
- else if(href_list["lock"])
- if(src.allowed(usr))
- screen = !screen
- else
- to_chat(usr, "Unauthorized Access.")
- playsound(src.loc, 'sound/signals/error29.ogg', 50, 0)
-
- else if(href_list["warn"])
- var/warning = sanitize(input(usr,"Message:","Enter your message here!",""))
- if(!warning) return
- var/obj/item/implant/I = locate(href_list["warn"])
- if((I)&&(I.imp_in))
- var/mob/living/carbon/R = I.imp_in
- to_chat(R, "You hear a voice in your head saying: '[warning]'")
-
- src.updateUsrDialog()
+ if(istype(I, /obj/item/implant/tracking))
+ var/obj/item/implant/tracking/tracking = I
+ var/loc_display = tracking.malfunction ? pick(playerlocs) : "Space"
+ var/coordinates = "error not found"
+ if(!tracking.malfunction && !isspaceturf(T))
+ loc_display = get_area(T)
+ coordinates = "X: [T.x], Y: [T.y]"
+ implant_data["location"] = loc_display
+ implant_data["id"] = tracking.id
+ implant_data["coordinates"] = coordinates
+ data["trackingImplants"] += list(implant_data)
+
+ if(istype(I, /obj/item/implant/chem))
+ implant_data["remainingUnits"] = I.reagents?.total_volume
+ data["chemImplants"] += list(implant_data)
+
+ return data
+
+/obj/machinery/computer/prisoner/tgui_act(action, params)
+ . = ..()
+ if(.)
return
+
+ switch(action)
+ if("inject")
+ var/obj/item/implant/I = locate(params["ref"]) in GLOB.implants_list
+ I?.activate(text2num(params["amt"]))
+ return TRUE
+
+ if("warn")
+ var/warning = tgui_input_text(usr, "Message:", "Enter your message")
+ if(!warning)
+ return
+
+ var/obj/item/implant/I = locate(params["ref"]) in GLOB.implants_list
+ var/mob/living/warned = I?.imp_in
+ if(!istype(warned))
+ return
+
+ to_chat(warned, SPAN_WARNING("You hear a voice in your head saying: '[warning]'"))
+ INVOKE_ASYNC(GLOBAL_PROC, /proc/tgui_alert, warned, warning, "You hear a voice in your head!")
+ return TRUE
diff --git a/code/game/machinery/spaceheater.dm b/code/game/machinery/spaceheater.dm
index 0f5385ce49b..f866091ed22 100644
--- a/code/game/machinery/spaceheater.dm
+++ b/code/game/machinery/spaceheater.dm
@@ -14,7 +14,8 @@
atom_flags = ATOM_FLAG_CLIMBABLE
clicksound = SFX_USE_LARGE_SWITCH
turf_height_offset = 16
-
+ var/min_temperature = 0 CELSIUS
+ var/max_temperature = 90 CELSIUS
/obj/machinery/space_heater/New()
..()
@@ -85,75 +86,67 @@
shake_animation(stime = 4)
return
-/obj/machinery/space_heater/attack_hand(mob/user as mob)
- ..()
- interact(user)
-
-/obj/machinery/space_heater/interact(mob/user as mob)
-
+/obj/machinery/space_heater/attack_hand(mob/user)
+ . = ..()
if(panel_open)
-
- var/list/dat = list()
- dat += "Power cell: "
- if(cell)
- dat += "Installed
"
- else
- dat += "Removed
"
-
- dat += "Power Level: [cell ? round(CELL_PERCENT(cell),1) : 0]%
"
-
- dat += "Set Temperature: "
-
- dat += "-"
-
- dat += " [set_temperature]K ([CONV_KELVIN_CELSIUS(set_temperature)]°C)"
- dat += "+
"
-
- var/datum/browser/popup = new(usr, "spaceheater", "Space Heater Control Panel")
- popup.set_content(jointext(dat, null))
- popup.set_title_image(usr.browse_rsc_icon(src.icon, "sheater-standby"))
- popup.open()
+ tgui_interact(user)
else
on = !on
- user.visible_message("[user] switches [on ? "on" : "off"] the [src].","You switch [on ? "on" : "off"] the [src].")
+ user.visible_message(SPAN_NOTICE("[user] switches [on ? "on" : "off"] \the [src]."), \
+ SPAN_NOTICE("You switch [on ? "on" : "off"] \the [src]."))
update_icon()
- return
+/obj/machinery/space_heater/tgui_interact(mob/user, datum/tgui/ui)
+ ui = SStgui.try_update_ui(user, src, ui)
-/obj/machinery/space_heater/Topic(href, href_list, state = GLOB.physical_state)
- if (..())
- close_browser(usr, "window=spaceheater")
- usr.unset_machine()
- return 1
+ if(!ui)
+ ui = new(user, src, "SpaceHeater", src)
+ ui.open()
- switch(href_list["op"])
+/obj/machinery/space_heater/tgui_data(mob/user)
+ var/list/data = list(
+ "cell" = istype(cell),
+ "charge" = istype(cell) ? round(cell.percent(), 1) : 0,
+ "temperature" = set_temperature,
+ "minTemperature" = min_temperature,
+ "maxTemperature" = max_temperature,
+ )
+ return data
- if("temp")
- var/value = text2num(href_list["val"])
+/obj/machinery/space_heater/tgui_act(action, params)
+ . = ..()
+ if(.)
+ return
- // limit to 0-90 degC
- set_temperature = dd_range(0 CELSIUS, 90 CELSIUS, set_temperature + value)
+ switch(action)
+ if("changeTemperature")
+ var/new_temperature = params["useKelvin"] ? text2num(params["newTemp"]) : CONV_CELSIUS_KELVIN(text2num(params["newTemp"]))
+ set_temperature = dd_range(min_temperature, max_temperature, new_temperature)
+ return TRUE
- if("cellremove")
- if(panel_open && cell && !usr.get_active_hand())
- usr.visible_message("\The usr] removes \the [cell] from \the [src].", "You remove \the [cell] from \the [src].")
- cell.update_icon()
- usr.pick_or_drop(cell)
- cell.add_fingerprint(usr)
- cell = null
- power_change()
+ if("cell")
+ if(!panel_open)
+ return
+
+ switch(istype(cell))
+ if(TRUE)
+ cell.update_icon()
+ usr.pick_or_drop(cell, usr.loc)
+ cell.add_fingerprint(usr)
+ usr.visible_message(SPAN_NOTICE("[usr] removes \the [cell] from \the [src]."), SPAN_NOTICE("You remove \the [cell] from \the [src]."))
+ cell = null
+
+ if(FALSE)
+ var/obj/item/cell/C = usr.get_active_hand()
+ if(!istype(C))
+ return
- if("cellinstall")
- if(panel_open && !cell)
- var/obj/item/cell/C = usr.get_active_hand()
- if(usr.drop(C, src))
- cell = C
C.add_fingerprint(usr)
- power_change()
- usr.visible_message("[usr] inserts \the [C] into \the [src].", "You insert \the [C] into \the [src].")
+ usr.drop(C, src)
+ cell = C
+ usr.visible_message(SPAN_NOTICE("[usr] inserts \a [cell] into \the [src]."), SPAN_NOTICE("You insert \the [cell] into \the [src]."))
- updateDialog()
- return TOPIC_REFRESH
+ return TRUE
/obj/machinery/space_heater/Process()
if(on)
diff --git a/code/game/mecha/mecha.dm b/code/game/mecha/mecha.dm
index 89ae98554de..f790a1fa3ba 100644
--- a/code/game/mecha/mecha.dm
+++ b/code/game/mecha/mecha.dm
@@ -1511,11 +1511,21 @@
output += ""
return output
+/obj/mecha/proc/get_mecha_log()
+ var/list/data = list()
+
+ for(var/list/entry in log)
+ data.Add(list(list(
+ "time" = time2text(entry["time"], "DDD MMM DD hh:mm:ss"),
+ "message" = entry["message"],
+ )))
+
+ return data
/obj/mecha/proc/get_log_html()
- var/output = "[src.name] Log"
+ var/output = "[name] Log"
for(var/list/entry in log)
- output += {"[time2text(entry["time"],"DDD MMM DD hh:mm:ss")] [game_year]
+ output += {"[time2text(entry["time"], "DDD MMM DD hh:mm:ss")] [game_year]
[entry["message"]]
"}
output += ""
diff --git a/code/game/mecha/mecha_control_console.dm b/code/game/mecha/mecha_control_console.dm
index ab15d6eab8f..caeb052106d 100644
--- a/code/game/mecha/mecha_control_console.dm
+++ b/code/game/mecha/mecha_control_console.dm
@@ -1,3 +1,5 @@
+GLOBAL_LIST_EMPTY(mecha_tracker_list)
+
/obj/machinery/computer/mecha
name = "Exosuit Control"
icon = 'icons/obj/computer.dmi'
@@ -6,63 +8,63 @@
light_color = "#a97faa"
req_access = list(access_robotics)
circuit = /obj/item/circuitboard/mecha_control
- var/list/located = list()
+ var/list/located
var/screen = 0
- var/stored_data
-
- attack_ai(mob/user as mob)
- return src.attack_hand(user)
-
- attack_hand(mob/user as mob)
- if(..())
- return
- user.set_machine(src)
- var/dat = "[src.name]"
- if(screen == 0)
- dat += "Tracking beacons data
"
- for(var/obj/item/mecha_parts/mecha_tracking/TR in world)
- var/answer = TR.get_mecha_info()
- if(answer)
- dat += {"
[answer]
- Send message
- Show exosuit log | (EMP pulse)
"}
-
- if(screen==1)
- dat += "Log contents
"
- dat += "Return
"
- dat += "[stored_data]"
-
- dat += "(Refresh)
"
- dat += ""
-
- show_browser(user, dat, "window=computer;size=400x500")
- onclose(user, "computer")
+
+/obj/machinery/computer/mecha/attack_hand(mob/user)
+ . = ..()
+ if(.)
return
- Topic(href, href_list)
- if(..())
- return
- var/datum/topic_input/F = new /datum/topic_input(href,href_list)
- if(href_list["send_message"])
- var/obj/item/mecha_parts/mecha_tracking/MT = F.getObj("send_message")
- var/message = sanitize(input(usr,"Input message","Transmit message") as text)
- var/obj/mecha/M = MT.in_mecha()
- if(message && M)
- M.occupant_message(message)
- return
- if(href_list["shock"])
- var/obj/item/mecha_parts/mecha_tracking/MT = F.getObj("shock")
- MT.shock()
- if(href_list["get_log"])
- var/obj/item/mecha_parts/mecha_tracking/MT = F.getObj("get_log")
- stored_data = MT.get_mecha_log()
- screen = 1
- if(href_list["return"])
- screen = 0
- src.updateUsrDialog()
+ tgui_interact(user)
+
+/obj/machinery/computer/mecha/tgui_state(mob/user)
+ return GLOB.default_state
+
+/obj/machinery/computer/mecha/tgui_interact(mob/user, datum/tgui/ui)
+ ui = SStgui.try_update_ui(user, src, ui)
+ if(!ui)
+ ui = new(user, src, "MechaControlConsole", name)
+ ui.open()
+
+/obj/machinery/computer/mecha/tgui_data(mob/user)
+ var/list/data = list(
+ "beacons" = list()
+ )
+
+ for(var/thing in GLOB.mecha_tracker_list)
+ var/obj/item/mecha_parts/mecha_tracking/TR = thing
+ var/list/tr_data = TR.get_mecha_info()
+ if(tr_data)
+ data["beacons"] += list(tr_data)
+
+ return data
+
+/obj/machinery/computer/mecha/tgui_act(action, params)
+ . = ..()
+ if(.)
return
+ switch(action)
+ if("send_message")
+ var/obj/item/mecha_parts/mecha_tracking/MT = locate(params["mt_ref"]) in GLOB.mecha_tracker_list
+ if(istype(MT))
+ var/message = tgui_input_text(usr, "Input message", "Transmit message")
+ if(!message || !trim(message))
+ return
+
+ var/obj/mecha/M = MT.in_mecha()
+ if(M)
+ M.occupant_message(message)
+ return TRUE
+ if("shock")
+ var/obj/item/mecha_parts/mecha_tracking/MT = locate(params["mt_ref"]) in GLOB.mecha_tracker_list
+ if(!istype(MT))
+ return
+
+ MT.shock()
+ return TRUE
/obj/item/mecha_parts/mecha_tracking
name = "Exosuit tracking beacon"
@@ -71,49 +73,56 @@
icon_state = "motion2"
origin_tech = list(TECH_DATA = 2, TECH_MAGNET = 2)
- proc/get_mecha_info()
- if(!in_mecha())
- return 0
- var/obj/mecha/M = src.loc
- var/cell_charge = M.get_charge()
- var/answer = {"Name: [M.name]
- Integrity: [M.health/initial(M.health)*100]%
- Cell charge: [isnull(cell_charge)?"Not found":"[CELL_PERCENT(M.cell)]%"]
- Airtank: [M.return_pressure()]kPa
- Pilot: [M.occupant||"None"]
- Location: [get_area(M)||"Unknown"]
- Active equipment: [M.selected||"None"]"}
- if(istype(M, /obj/mecha/working/ripley))
- var/obj/mecha/working/ripley/RM = M
- answer += "
Used cargo space: [length(RM.cargo)/RM.cargo_capacity*100]%
"
-
- return answer
-
- emp_act()
- qdel(src)
- return
-
- ex_act()
- qdel(src)
- return
-
- proc/in_mecha()
- if(istype(src.loc, /obj/mecha))
- return src.loc
- return 0
-
- proc/shock()
- var/obj/mecha/M = in_mecha()
- if(M)
- M.emp_act(2)
- qdel(src)
-
- proc/get_mecha_log()
- if(!src.in_mecha())
- return 0
- var/obj/mecha/M = src.loc
- return M.get_log_html()
-
+/obj/item/mecha_parts/mecha_tracking/Initialize()
+ . = ..()
+ GLOB.mecha_tracker_list += src
+
+/obj/item/mecha_parts/mecha_tracking/Destroy()
+ GLOB.mecha_tracker_list -= src
+ return ..()
+
+/obj/item/mecha_parts/mecha_tracking/proc/get_mecha_info()
+ var/obj/mecha/M = in_mecha()
+ if(!istype(M))
+ return FALSE
+
+ var/list/data = list(
+ "name" = M.name,
+ "cellCharge" = M.get_charge(),
+ "integrity" = M.health,
+ "integrityMax" = initial(M.health),
+ "airtank" = M.return_pressure(),
+ "pilot" = M.occupant,
+ "location" = get_area(M),
+ "equipment" = M.selected,
+ "ref" = any2ref(src),
+ "logs" = M.get_mecha_log(),
+ )
+
+ if(istype(M, /obj/mecha/working/ripley))
+ var/obj/mecha/working/ripley/RM = M
+ data["cargo"] = length(RM.cargo)
+ data["cargoCapacity"] = RM.cargo_capacity
+
+ return data
+
+/obj/item/mecha_parts/mecha_tracking/emp_act()
+ qdel_self()
+
+/obj/item/mecha_parts/mecha_tracking/ex_act()
+ qdel_self()
+
+/obj/item/mecha_parts/mecha_tracking/proc/in_mecha()
+ if(ismech(loc))
+ return loc
+
+ return FALSE
+
+/obj/item/mecha_parts/mecha_tracking/proc/shock()
+ var/obj/mecha/M = in_mecha()
+ if(M)
+ M.emp_act(2)
+ qdel_self()
/obj/structure/closet/crate/mechabeacons
name = "exosuit tracking beacons crate"
diff --git a/code/game/objects/items/implants/implant.dm b/code/game/objects/items/implants/implant.dm
index 9456b62b69d..5b326b68ee3 100644
--- a/code/game/objects/items/implants/implant.dm
+++ b/code/game/objects/items/implants/implant.dm
@@ -14,6 +14,14 @@
var/malfunction = 0
var/known //if advanced scanners would name these in results
+/obj/item/implant/Initialize()
+ . = ..()
+ GLOB.implants_list += src
+
+/obj/item/implant/Destroy()
+ GLOB.implants_list -= src
+ return ..()
+
/obj/item/implant/proc/trigger(emote, source)
return
diff --git a/code/game/objects/structures/under_wardrobe.dm b/code/game/objects/structures/under_wardrobe.dm
index 6c68ea75122..0ddc4ac4333 100644
--- a/code/game/objects/structures/under_wardrobe.dm
+++ b/code/game/objects/structures/under_wardrobe.dm
@@ -39,77 +39,101 @@
LAZYREMOVE(amount_of_underwear_by_id_card, id_card)
unregister_signal(id_card, SIGNAL_QDELETING)
+/obj/structure/undies_wardrobe/proc/human_who_can_use_underwear(mob/living/carbon/human/H)
+ if(!istype(H) || !H.species || !(H.species.species_appearance_flags & HAS_UNDERWEAR))
+ return FALSE
+ return TRUE
+
/obj/structure/undies_wardrobe/attack_hand(mob/user)
if(!human_who_can_use_underwear(user))
- to_chat(user, "Sadly there's nothing in here for you to wear.")
+ to_chat(user, SPAN_WARNING("Sadly there's nothing in here for you to wear."))
return
- interact(user)
-/obj/structure/undies_wardrobe/interact(mob/living/carbon/human/H)
- var/id = H.get_id_card()
+ tgui_interact(user)
+
+/obj/structure/undies_wardrobe/tgui_interact(mob/user, datum/tgui/ui)
+ . = ..()
+ ui = SStgui.try_update_ui(user, src, ui)
+ if(!ui)
+ ui = new(user, src, "UnderWardrobe", name)
+ ui.open()
+
+/obj/structure/undies_wardrobe/tgui_data(mob/user)
+ var/mob/living/carbon/human/H = user
+ var/obj/item/card/id/id = H?.get_id_card()
+
+ var/list/data = list(
+ "user" = id? id.name : null,
+ "mayClaim" = id ? length(GLOB.underwear.categories) - LAZYACCESS(amount_of_underwear_by_id_card, id) : 0,
+ )
+
+ return data
+
+/obj/structure/undies_wardrobe/tgui_static_data(mob/user)
+ var/list/data = list(
+ "underwearCategories" = list()
+ )
- var/dat = list()
- dat += "Underwear
"
- dat += "You may claim [id ? length(GLOB.underwear.categories) - LAZYACCESS(amount_of_underwear_by_id_card, id) : 0] more article\s this shift.
"
- dat += "Available Categories
"
for(var/datum/category_group/underwear/UWC in GLOB.underwear.categories)
- dat += "[UWC.name] (Select)
"
- dat = jointext(dat,null)
- show_browser(H, dat, "window=wardrobe;size=400x250")
+ var/list/cat_data = list("name" = UWC.name)
-/obj/structure/undies_wardrobe/proc/human_who_can_use_underwear(mob/living/carbon/human/H)
- if(!istype(H) || !H.species || !(H.species.species_appearance_flags & HAS_UNDERWEAR))
- return FALSE
- return TRUE
+ for(var/datum/category_item/underwear/UWI in UWC.items)
+ if(!UWI.underwear_type)
+ continue
-/obj/structure/undies_wardrobe/CanUseTopic(user)
- if(!human_who_can_use_underwear(user))
- return STATUS_CLOSE
+ var/list/item_data = list(
+ "name" = UWI.name
+ )
+ for(var/tweak in UWI.tweaks)
+ item_data["tweaks"] += list(tweak)
- return ..()
+ cat_data["catItems"] += list(item_data)
-/obj/structure/undies_wardrobe/Topic(href, href_list, state)
- if(..())
- return TRUE
+ data["underwearCategories"] += list(cat_data)
- var/mob/living/carbon/human/H = usr
- if(href_list["select_underwear"])
- var/datum/category_group/underwear/UWC = GLOB.underwear.categories_by_name[href_list["select_underwear"]]
- if(!UWC)
- return
- var/datum/category_item/underwear/UWI = input("Select your desired underwear:", "Choose underwear") as null|anything in exlude_none(UWC.items)
- if(!UWI)
- return
+ return data
- var/list/metadata_list = list()
- for(var/tweak in UWI.tweaks)
- var/datum/gear_tweak/gt = tweak
- var/metadata = gt.get_metadata(H, "Adjust underwear")
- if(!metadata)
- return
- metadata_list["[gt]"] = metadata
+/obj/structure/undies_wardrobe/tgui_act(action, list/params, datum/tgui/ui, datum/ui_state/state)
+ . = ..()
+ if(.)
+ return
- if(!CanInteract(H, state))
- return
+ switch(action)
+ if("equip")
+ var/datum/category_group/underwear/UWC = GLOB.underwear.categories_by_name[params["underwearCat"]]
+ if(!istype(UWC))
+ return
- var/id = H.get_id_card()
- if(!id)
- audible_message("No ID card detected. Unable to acquire your underwear quota for this shift.", WARDROBE_BLIND_MESSAGE(H))
- return
+ var/datum/category_item/underwear/UWI = UWC.items_by_name[params["underwearItem"]]
+ if(!istype(UWI))
+ return
- var/current_quota = LAZYACCESS(amount_of_underwear_by_id_card, id)
- if(current_quota >= length(GLOB.underwear.categories))
- audible_message("You have already used up your underwear quota for this shift. Please return previously acquired items to increase it.", WARDROBE_BLIND_MESSAGE(H))
- return
- LAZYSET(amount_of_underwear_by_id_card, id, ++current_quota)
+ var/mob/living/carbon/human/H = usr
+ if(!istype(H))
+ return
- var/obj/UW = UWI.create_underwear(metadata_list)
- H.pick_or_drop(UW, loc)
+ var/id = H.get_id_card()
+ if(!id)
+ audible_message("No ID card detected. Unable to acquire your underwear quota for this shift.", WARDROBE_BLIND_MESSAGE(H))
+ return
- . = TRUE
+ var/list/metadata_list = list()
+ for(var/tweak in UWI.tweaks)
+ var/datum/gear_tweak/gt = tweak
+ var/metadata = gt.get_metadata(H, "Adjust underwear")
+ if(!metadata)
+ return
+ metadata_list["[gt]"] = metadata
+
+ var/current_quota = LAZYACCESS(amount_of_underwear_by_id_card, id)
+ if(current_quota >= length(GLOB.underwear.categories))
+ audible_message("You have already used up your underwear quota for this shift. Please return previously acquired items to increase it.", WARDROBE_BLIND_MESSAGE(H))
+ return
- if(.)
- interact(H)
+ LAZYSET(amount_of_underwear_by_id_card, id, ++current_quota)
+ var/obj/UW = UWI.create_underwear(metadata_list)
+ H.pick_or_drop(UW, loc)
+ return TRUE
/obj/structure/undies_wardrobe/proc/exlude_none(list/L)
. = L.Copy()
diff --git a/code/modules/hydroponics/seed_storage.dm b/code/modules/hydroponics/seed_storage.dm
index 69bc042a046..88d2cd1fb15 100644
--- a/code/modules/hydroponics/seed_storage.dm
+++ b/code/modules/hydroponics/seed_storage.dm
@@ -206,148 +206,162 @@
)
/obj/machinery/seed_storage/attack_hand(mob/user as mob)
- user.set_machine(src)
- interact(user)
+ tgui_interact(user)
-/obj/machinery/seed_storage/interact(mob/user as mob)
- if (..())
- return
+/obj/machinery/seed_storage/tgui_interact(mob/user, datum/tgui/ui)
+ ui = SStgui.try_update_ui(user, src, ui)
+ if(!ui)
+ ui = new(user, src, "SeedStorage")
+ ui.open()
- var/dat = "Seed storage contents
"
- if (piles.len == 0)
- dat += "No seeds"
- else
- dat += "Name | "
- dat += "Variety | "
- if ("stats" in scanner)
- dat += "E | Y | M | Pr | Pt | Harvest | "
- if ("temperature" in scanner)
- dat += "Temp | "
- if ("light" in scanner)
- dat += "Light | "
- if ("soil" in scanner)
- dat += "Nutri | Water | "
- dat += "Notes | Amount | |
"
- for (var/datum/seed_pile/S in piles)
- var/datum/seed/seed = S.seed_type
- if(!seed)
- continue
- dat += ""
- dat += "[seed.seed_name] | "
- dat += "#[seed.uid] | "
- if ("stats" in scanner)
- dat += "[seed.get_trait(TRAIT_ENDURANCE)] | [seed.get_trait(TRAIT_YIELD)] | [seed.get_trait(TRAIT_MATURATION)] | [seed.get_trait(TRAIT_PRODUCTION)] | [seed.get_trait(TRAIT_POTENCY)] | "
- if(seed.get_trait(TRAIT_HARVEST_REPEAT))
- dat += "Multiple | "
- else
- dat += "Single | "
- if ("temperature" in scanner)
- dat += "[seed.get_trait(TRAIT_IDEAL_HEAT)] K | "
- if ("light" in scanner)
- dat += "[seed.get_trait(TRAIT_IDEAL_LIGHT)] L | "
- if ("soil" in scanner)
- if(seed.get_trait(TRAIT_REQUIRES_NUTRIENTS))
- if(seed.get_trait(TRAIT_NUTRIENT_CONSUMPTION) < 0.05)
- dat += "Low | "
- else if(seed.get_trait(TRAIT_NUTRIENT_CONSUMPTION) > 0.2)
- dat += "High | "
- else
- dat += "Norm | "
+/obj/machinery/seed_storage/tgui_data(mob/user)
+ var/list/data = list(
+ "seeds" = list()
+ )
+ for(var/datum/seed_pile/S in piles)
+ var/datum/seed/seed = S.seed_type
+ if(!seed)
+ continue
+ var/list/seed_type = list("name" = seed.seed_name, "uid" = seed.uid, "pile_id" = S.ID)
+ var/list/traits = list()
+
+ if("stats" in scanner)
+ data["scan_stats"] = TRUE
+ seed_type["endurance"] = seed.get_trait(TRAIT_ENDURANCE)
+ seed_type["yield"] = seed.get_trait(TRAIT_YIELD)
+ seed_type["maturation"] = seed.get_trait(TRAIT_MATURATION)
+ seed_type["production"] = seed.get_trait(TRAIT_PRODUCTION)
+ seed_type["potency"] = seed.get_trait(TRAIT_POTENCY)
+ if(seed.get_trait(TRAIT_HARVEST_REPEAT))
+ seed_type["harvest"] = "multiple"
+ else
+ seed_type["harvest"] = "single"
+
+ if("temperature" in scanner)
+ data["scan_temperature"] = TRUE
+ seed_type["ideal_heat"] = "[seed.get_trait(TRAIT_IDEAL_HEAT)] K"
+
+ if("light" in scanner)
+ data["scan_light"] = TRUE
+ seed_type["ideal_light"] = "[seed.get_trait(TRAIT_IDEAL_LIGHT)] L"
+
+ if("soil" in scanner)
+ data["scan_soil"] = TRUE
+ if(seed.get_trait(TRAIT_REQUIRES_NUTRIENTS))
+ if(seed.get_trait(TRAIT_NUTRIENT_CONSUMPTION) < 0.05)
+ seed_type["nutrient_consumption"] = "Low"
+ else if(seed.get_trait(TRAIT_NUTRIENT_CONSUMPTION) > 0.2)
+ seed_type["nutrient_consumption"] = "High"
else
- dat += "No | "
- if(seed.get_trait(TRAIT_REQUIRES_WATER))
- if(seed.get_trait(TRAIT_WATER_CONSUMPTION) < 1)
- dat += "Low | "
- else if(seed.get_trait(TRAIT_WATER_CONSUMPTION) > 5)
- dat += "High | "
- else
- dat += "Norm | "
+ seed_type["nutrient_consumption"] = "Average"
+ else
+ seed_type["nutrient_consumption"] = "No"
+
+ if(seed.get_trait(TRAIT_REQUIRES_WATER))
+ if(seed.get_trait(TRAIT_WATER_CONSUMPTION) < 1)
+ seed_type["water_consumption"] = "Low"
+ else if(seed.get_trait(TRAIT_WATER_CONSUMPTION) > 5)
+ seed_type["water_consumption"] = "High"
else
- dat += "No | "
-
- dat += ""
- switch(seed.get_trait(TRAIT_CARNIVOROUS))
- if(1)
- dat += "CARN "
- if(2)
- dat += "CARN "
- switch(seed.get_trait(TRAIT_SPREAD))
- if(1)
- dat += "VINE "
- if(2)
- dat += "VINE "
- if ("pressure" in scanner)
- if(seed.get_trait(TRAIT_LOWKPA_TOLERANCE) < 20)
- dat += "LP "
- if(seed.get_trait(TRAIT_HIGHKPA_TOLERANCE) > 220)
- dat += "HP "
- if ("temperature" in scanner)
- if(seed.get_trait(TRAIT_HEAT_TOLERANCE) > 30)
- dat += "TEMRES "
- else if(seed.get_trait(TRAIT_HEAT_TOLERANCE) < 10)
- dat += "TEMSEN "
- if ("light" in scanner)
- if(seed.get_trait(TRAIT_LIGHT_TOLERANCE) > 10)
- dat += "LIGRES "
- else if(seed.get_trait(TRAIT_LIGHT_TOLERANCE) < 3)
- dat += "LIGSEN "
- if(seed.get_trait(TRAIT_TOXINS_TOLERANCE) < 3)
- dat += "TOXSEN "
- else if(seed.get_trait(TRAIT_TOXINS_TOLERANCE) > 6)
- dat += "TOXRES "
- if(seed.get_trait(TRAIT_PEST_TOLERANCE) < 3)
- dat += "PESTSEN "
- else if(seed.get_trait(TRAIT_PEST_TOLERANCE) > 6)
- dat += "PESTRES "
- if(seed.get_trait(TRAIT_WEED_TOLERANCE) < 3)
- dat += "WEEDSEN "
- else if(seed.get_trait(TRAIT_WEED_TOLERANCE) > 6)
- dat += "WEEDRES "
- if(seed.get_trait(TRAIT_PARASITE))
- dat += "PAR "
- if ("temperature" in scanner)
- if(seed.get_trait(TRAIT_ALTER_TEMP) > 0)
- dat += "TEMP+ "
- if(seed.get_trait(TRAIT_ALTER_TEMP) < 0)
- dat += "TEMP- "
- if(seed.get_trait(TRAIT_BIOLUM))
- dat += "LUM "
- dat += " | "
- dat += "[S.amount] | "
- dat += "Vend Purge | "
- dat += "
"
- dat += "
"
-
- show_browser(user, dat, "window=seedstorage")
- onclose(user, "seedstorage")
-
-/obj/machinery/seed_storage/Topic(href, list/href_list)
- if (..())
+ seed_type["water_consumption"] = "Average"
+ else
+ seed_type["water_consumption"] = "No"
+
+ switch(seed.get_trait(TRAIT_CARNIVOROUS))
+ if(1)
+ traits += "CARN"
+ if(2)
+ traits += "CARN (!)"
+
+ switch(seed.get_trait(TRAIT_SPREAD))
+ if(1)
+ traits += "VINE"
+ if(2)
+ traits += "VINE (!)"
+
+ if ("pressure" in scanner)
+ if(seed.get_trait(TRAIT_LOWKPA_TOLERANCE) < 20)
+ traits += "LP"
+ if(seed.get_trait(TRAIT_HIGHKPA_TOLERANCE) > 220)
+ traits += "HP"
+
+ if ("temperature" in scanner)
+ if(seed.get_trait(TRAIT_HEAT_TOLERANCE) > 30)
+ traits += "TEMRES"
+ else if(seed.get_trait(TRAIT_HEAT_TOLERANCE) < 10)
+ traits += "TEMSEN"
+
+ if ("light" in scanner)
+ if(seed.get_trait(TRAIT_LIGHT_TOLERANCE) > 10)
+ traits += "LIGRES"
+ else if(seed.get_trait(TRAIT_LIGHT_TOLERANCE) < 3)
+ traits += "LIGSEN"
+
+ if(seed.get_trait(TRAIT_TOXINS_TOLERANCE) < 3)
+ traits += "TOXSEN"
+ else if(seed.get_trait(TRAIT_TOXINS_TOLERANCE) > 6)
+ traits += "TOXRES"
+
+ if(seed.get_trait(TRAIT_PEST_TOLERANCE) < 3)
+ traits += "PESTSEN"
+ else if(seed.get_trait(TRAIT_PEST_TOLERANCE) > 6)
+ traits += "PESTRES"
+
+ if(seed.get_trait(TRAIT_WEED_TOLERANCE) < 3)
+ traits += "WEEDSEN"
+ else if(seed.get_trait(TRAIT_WEED_TOLERANCE) > 6)
+ traits += "WEEDRES"
+
+ if(seed.get_trait(TRAIT_PARASITE))
+ traits += "PAR"
+
+ if("temperature" in scanner)
+ if(seed.get_trait(TRAIT_ALTER_TEMP) > 0)
+ traits += "TEMP+"
+ if(seed.get_trait(TRAIT_ALTER_TEMP) < 0)
+ traits += "TEMP-"
+
+ if(seed.get_trait(TRAIT_BIOLUM))
+ traits += "LUM"
+
+ seed_type["amount"] = S.amount
+ seed_type["traits"] = english_list(traits)
+ data["seeds"] += list(seed_type)
+
+ return data
+
+/obj/machinery/seed_storage/tgui_act(action, params)
+ . = ..()
+ if(.)
return
- var/task = href_list["task"]
- var/ID = text2num(href_list["id"])
- for (var/datum/seed_pile/N in piles)
- if (N.ID == ID)
- if (task == "vend")
+ switch(action)
+ if("vend")
+ for(var/datum/seed_pile/N in piles)
+ if(N.ID != text2num(params["ID"]))
+ continue
+
var/obj/O = pick(N.seeds)
- if (O)
+ if(istype(O))
--N.amount
N.seeds -= O
- if (N.amount <= 0 || N.seeds.len <= 0)
+ if(N.amount <=0 || N.seeds.len <= 0)
piles -= N
qdel(N)
O.dropInto(loc)
else
piles -= N
qdel(N)
- else if (task == "purge")
- for (var/obj/O in N.seeds)
+
+ return TRUE
+
+ if("purge")
+ for(var/datum/seed_pile/N in piles)
+ for(var/obj/O in N.seeds)
qdel(O)
piles -= N
qdel(N)
- break
- updateUsrDialog()
+ return TRUE
/obj/machinery/seed_storage/attackby(obj/item/O as obj, mob/user as mob)
if (istype(O, /obj/item/seeds))
diff --git a/code/modules/mob/living/silicon/robot/drone/drone_console.dm b/code/modules/mob/living/silicon/robot/drone/drone_console.dm
index 419ce04de8d..4848a05748d 100644
--- a/code/modules/mob/living/silicon/robot/drone/drone_console.dm
+++ b/code/modules/mob/living/silicon/robot/drone/drone_console.dm
@@ -8,116 +8,122 @@
req_access = list(access_engine_equip)
circuit = /obj/item/circuitboard/drone_control
- //Used when pinging drones.
+ /// Used when pinging drones.
var/drone_call_area = "Engineering"
- //Used to enable or disable drone fabrication.
+ /// Used to enable or disable drone fabrication.
var/obj/machinery/drone_fabricator/dronefab
+ /// Cooldown for area pings
+ var/ping_cooldown = 0
-/obj/machinery/computer/drone_control/attack_ai(mob/user as mob)
- return src.attack_hand(user)
-/obj/machinery/computer/drone_control/attack_hand(mob/user as mob)
- if(..())
+/obj/machinery/computer/drone_control/attack_hand(mob/user)
+ . = ..()
+ if(.)
return
- if(!allowed(user))
- to_chat(user, "Access denied.")
+ tgui_interact(user)
+
+/obj/machinery/computer/drone_control/tgui_state(mob/user)
+ return GLOB.default_state
+
+/obj/machinery/computer/drone_control/tgui_interact(mob/user, datum/tgui/ui = null)
+ ui = SStgui.try_update_ui(user, src, ui)
+ if(!ui)
+ ui = new(user, src, "DroneConsole", src)
+ ui.open()
+
+/obj/machinery/computer/drone_control/tgui_data(mob/user)
+ var/list/data = list(
+ "drone_fab" = istype(dronefab),
+ "fab_power" = (dronefab?.stat & NOPOWER) ? FALSE : TRUE,
+ "drone_prod" = dronefab?.produce_drones,
+ "drone_progress" = clamp(dronefab?.drone_progress, 0, 100),
+ )
+ data["selected_area"] = drone_call_area
+ data["ping_cd"] = ping_cooldown > world.time ? TRUE : FALSE
+
+ data["drones"] = list()
+ for(var/mob/living/silicon/robot/drone/D in GLOB.silicon_mob_list)
+ var/area/A = get_area(D)
+ var/turf/T = get_turf(D)
+ var/list/drone_data = list(
+ name = D.real_name,
+ ref = any2ref(D),
+ stat = D.stat,
+ client = D.client ? TRUE : FALSE,
+ health = round(D.health / D.maxHealth, 0.1),
+ charge = round(D.cell.charge / D.cell.maxcharge, 0.1),
+ location = "[A] ([T.x], [T.y])",
+ )
+ data["drones"] += list(drone_data)
+ return data
+
+/obj/machinery/computer/drone_control/tgui_static_data(mob/user)
+ var/list/data = list()
+ data["area_list"] = GLOB.tagger_locations
+ return data
+
+/obj/machinery/computer/drone_control/tgui_act(action, list/params, datum/tgui/ui, datum/ui_state/state)
+ if(..())
return
-
- user.set_machine(src)
- var/dat = ""
- dat += "Maintenance Units
"
-
- for(var/mob/living/silicon/robot/drone/D in world)
- if(D.z != src.z)
- continue
- dat += "
[D.real_name] ([D.stat == 2 ? "INACTIVE" : "ACTIVE"])"
- dat += "
Cell charge: [D.cell.charge]/[D.cell.maxcharge]."
- dat += "
Currently located in: [get_area(D)]."
- dat += "
Resync | Shutdown"
-
- dat += "
Request drone presence in area: [drone_call_area] (Send ping)"
-
- dat += "
Drone fabricator: "
- dat += "[dronefab ? "[(dronefab.produce_drones && !(dronefab.stat & NOPOWER)) ? "ACTIVE" : "INACTIVE"]" : "FABRICATOR NOT DETECTED. (search)"]"
- show_browser(user, dat, "window=computer;size=400x500")
- onclose(user, "computer")
- return
-
-
-/obj/machinery/computer/drone_control/Topic(href, href_list)
- if((. = ..()))
+ . = TRUE
+
+ switch(action)
+ if("find_fab")
+ find_fab(usr)
+ return TRUE
+
+ if("toggle_fab")
+ if(QDELETED(dronefab))
+ dronefab = null
+ return
+
+ dronefab.produce_drones = !dronefab.produce_drones
+ var/toggle = dronefab.produce_drones ? "enable" : "disable"
+ show_splash_text(usr, "Drone production toggled", SPAN_NOTICE("You [toggle] drone production in the nearby fabricator."))
+ message_admins("[key_name_admin(usr)] [toggle]d maintenance drone production from the control console.")
+ log_game("[key_name(usr)] [toggle]d maintenance drone production from the control console.")
+ return TRUE
+
+ if("set_area")
+ drone_call_area = params["area"]
+ return TRUE
+
+ if("ping")
+ ping_cooldown = world.time + 15 SECONDS
+ show_splash_text(usr, "Maintenance request issued!", SPAN_NOTICE("You issue a maintenance request for all active drones, highlighting [drone_call_area]."))
+ for(var/mob/living/silicon/robot/drone/D in GLOB.silicon_mob_list)
+ if(D.client && D.stat == CONSCIOUS)
+ to_chat(usr, SPAN_NOTICE("Maintenance drone presence requested in: [drone_call_area]."))
+ return TRUE
+
+ if("resync")
+ var/mob/living/silicon/robot/drone/D = locate(params["ref"]) in GLOB.silicon_mob_list
+ if(D)
+ show_splash_text(usr, "Laws synced", SPAN_NOTICE("You issue a law synchronization directive for the drone."))
+ D.law_resync()
+ return TRUE
+
+ if("shutdown")
+ var/mob/living/silicon/robot/drone/D = locate(params["ref"]) in GLOB.silicon_mob_list
+ if(D)
+ show_splash_text(usr, "Drone shut down", SPAN_WARNING("You issue a recall command for the unfortunate drone."))
+ if(D != usr) // Don't need to bug admins about a suicide
+ message_admins("[key_name_admin(usr)] issued recall order for drone [key_name_admin(D)] from control console.")
+ log_game("[key_name(usr)] issued recall order for [key_name(D)] from control console.")
+ D.shut_down()
+ return TRUE
+
+/obj/machinery/computer/drone_control/proc/find_fab(mob/user)
+ if(dronefab)
return
- if(!allowed(usr))
- to_chat(usr, "Access denied.")
+ for(var/obj/machinery/drone_fabricator/fab in get_area(src))
+ if(fab.stat & NOPOWER)
+ continue
+ dronefab = fab
+ if(user)
+ show_splash_text(user, "Dronefab located!", SPAN_NOTICE("Drone fabricator located."))
return
-
- if ((usr.contents.Find(src) || (in_range(src, usr) && istype(src.loc, /turf))) || (istype(usr, /mob/living/silicon)))
- usr.set_machine(src)
-
- if (href_list["setarea"])
-
- //Probably should consider using another list, but this one will do.
- var/t_area = input("Select the area to ping.", "Set Target Area", null) as null|anything in GLOB.tagger_locations
-
- if(!t_area)
- return
-
- drone_call_area = t_area
- to_chat(usr, "You set the area selector to [drone_call_area].")
-
- else if (href_list["ping"])
-
- to_chat(usr, "You issue a maintenance request for all active drones, highlighting [drone_call_area].")
- for(var/mob/living/silicon/robot/drone/D in world)
- if(D.client && D.stat == 0)
- to_chat(D, "-- Maintenance drone presence requested in: [drone_call_area].")
-
- else if (href_list["resync"])
-
- var/mob/living/silicon/robot/drone/D = locate(href_list["resync"])
-
- if(D.stat != 2)
- to_chat(usr, "You issue a law synchronization directive for the drone.")
- D.law_resync()
-
- else if (href_list["shutdown"])
-
- var/mob/living/silicon/robot/drone/D = locate(href_list["shutdown"])
-
- if(D.stat != 2)
- to_chat(usr, "You issue a kill command for the unfortunate drone.")
- message_admins("[key_name_admin(usr)] issued kill order for drone [key_name_admin(D)] from control console.")
- log_game("[key_name(usr)] issued kill order for [key_name(src)] from control console.")
- D.shut_down()
-
- else if (href_list["search_fab"])
- if(dronefab)
- return
-
- for(var/obj/machinery/drone_fabricator/fab in oview(3,src))
-
- if(fab.stat & NOPOWER)
- continue
-
- dronefab = fab
- to_chat(usr, "Drone fabricator located.")
- return
-
- to_chat(usr, "Unable to locate drone fabricator.")
-
- else if (href_list["toggle_fab"])
-
- if(!dronefab)
- return
-
- if(get_dist(src,dronefab) > 3)
- dronefab = null
- to_chat(usr, "Unable to locate drone fabricator.")
- return
-
- dronefab.produce_drones = !dronefab.produce_drones
- to_chat(usr, "You [dronefab.produce_drones ? "enable" : "disable"] drone production in the nearby fabricator.")
-
- src.updateUsrDialog()
+ if(user)
+ show_splash_text(user, "Unable to locate!", SPAN_WARNING("Unable to locate drone fabricator."))
diff --git a/tgui/packages/tgui/interfaces/DroneConsole.tsx b/tgui/packages/tgui/interfaces/DroneConsole.tsx
new file mode 100644
index 00000000000..d59072678a6
--- /dev/null
+++ b/tgui/packages/tgui/interfaces/DroneConsole.tsx
@@ -0,0 +1,232 @@
+import { toTitleCase } from "common/string";
+import { useBackend } from "../backend";
+import {
+ Box,
+ Button,
+ Divider,
+ Dropdown,
+ Stack,
+ LabeledList,
+ NoticeBox,
+ ProgressBar,
+ Section,
+} from "../components";
+import { Window } from "../layouts";
+
+export const DroneConsole = (props, context) => {
+ return (
+
+
+
+
+
+
+
+
+ );
+};
+
+interface Drone {
+ name: string;
+ ref: string;
+ stat: number;
+ client: boolean;
+ health: number;
+ charge: number;
+ location: string;
+}
+
+interface DroneConsoleData {
+ drone_fab: boolean;
+ fab_power: boolean;
+ drone_prod: boolean;
+ drone_progress: number;
+ drones: Drone[];
+ area_list: String[];
+ selected_area: string;
+ ping_cd: number;
+}
+
+const Fabricator = (props, context) => {
+ const { act, data } = useBackend(context);
+ const { drone_fab, fab_power, drone_prod, drone_progress } = data;
+
+ let FabDetected = () => {
+ if (drone_fab) {
+ return (
+
+
+
+ [ {fab_power ? "Online" : "Offline"} ]
+
+
+
+
+
+
+ );
+ } else {
+ return (
+
+
+ FABRICATOR NOT DETECTED.
+
+
+
+
+ );
+ }
+ };
+
+ return (
+ act("toggle_fab")}
+ />
+ }
+ >
+ {FabDetected()}
+
+ );
+};
+
+const DroneList = (props: any, context: any) => {
+ const { act, data } = useBackend(context);
+ const { drones, area_list, selected_area, ping_cd } = data;
+
+ let status = (stat, client) => {
+ let box_color;
+ let text;
+ if (stat === 2) {
+ // Dead
+ box_color = "bad";
+ text = "Disabled";
+ } else if (stat === 1 || !client) {
+ // Unconscious or SSD
+ box_color = "average";
+ text = "Inactive";
+ } else {
+ // Alive
+ box_color = "good";
+ text = "Active";
+ }
+ return {text};
+ };
+
+ return (
+ <>
+
+
+ Request Drone presence in area:
+
+
+ act("set_area", {
+ area: value,
+ })
+ }
+ />
+
+
+
+
+ {drones.map((drone) => (
+
+
+
+
+
+ act("shutdown", {
+ ref: drone.ref,
+ })
+ }
+ />
+
+
+ }
+ >
+
+
+ {status(drone.stat, drone.client)}
+
+
+
+
+
+
+
+
+ {drone.location}
+
+
+
+ ))}
+
+ >
+ );
+};
diff --git a/tgui/packages/tgui/interfaces/Guestpass.tsx b/tgui/packages/tgui/interfaces/Guestpass.tsx
new file mode 100644
index 00000000000..c47c2e4f236
--- /dev/null
+++ b/tgui/packages/tgui/interfaces/Guestpass.tsx
@@ -0,0 +1,343 @@
+import { useBackend } from "../backend";
+import {
+ Input,
+ Tabs,
+ Button,
+ Section,
+ Stack,
+ LabeledList,
+ NumberInput,
+ Divider,
+} from "../components";
+import { Window } from "../layouts";
+import { useLocalState } from "../backend";
+
+interface Access {
+ name: string;
+ id: number;
+ req: string;
+}
+
+interface Region {
+ name: string;
+ id: number;
+ accesses: Access[];
+}
+
+interface Data {
+ areas: Access[];
+ showlogs: false;
+ IssueLog: string[];
+ scanName: string;
+ giv_name: string;
+ duration: number;
+ reason: string;
+ printmsg: string;
+ canprint: boolean;
+ regions: Region[];
+ selectedAccess: string;
+}
+
+export const Guestpass = (props: any, context: any) => {
+ const { act, data } = useBackend(context);
+
+ const [currentPage, setCurrentPage] = useLocalState(
+ context,
+ "currentPage",
+ 0
+ );
+
+ const [name, setName] = useLocalState(context, "name", "");
+ const [reason, setReason] = useLocalState(context, "reason", "");
+ const [duration, setDuration] = useLocalState(context, "duration", 0);
+
+ return (
+
+
+
+
+
+ setCurrentPage(0)}
+ >
+ Issue Pass
+
+ setCurrentPage(1)}
+ >
+ Records ({data.IssueLog?.length})
+
+
+
+
+
+
+
+
+
+
+
+
+ {currentPage === 0 && (
+
+ )}
+
+ {currentPage === 0 &&
+ (!data.scanName ? (
+
+
+
+
+ Please, insert ID Card
+
+
+
+
+ ) : (
+
+
+ act("set", {
+ access: id,
+ })
+ }
+ />
+
+ ))}
+ {currentPage === 1 && (
+
+ act("print")}
+ />
+ }
+ >
+ {(!!data.IssueLog?.length && (
+
+ {data.IssueLog.map((a, i) => (
+ {a}
+ ))}
+
+ )) || (
+
+
+ No logs
+
+
+ )}
+
+
+ )}
+
+
+
+ );
+};
+
+const diffMap = {
+ 0: {
+ icon: "times-circle",
+ color: "bad",
+ },
+ 1: {
+ icon: "stop-circle",
+ color: null,
+ },
+ 2: {
+ icon: "check-circle",
+ color: "good",
+ },
+};
+
+const AirlockAccessList = (props: any, context: any) => {
+ const { act, data } = useBackend(context);
+ const [selectedRegionName, setSelectedRegionName] = useLocalState(
+ context,
+ "accessName",
+ data.regions[0]?.name
+ );
+ const selectedAccess = data.regions.find(
+ (region) => region.name === selectedRegionName
+ );
+ const selectedAccessEntries = selectedAccess?.accesses || [];
+
+ const checkAccessIcon = (accesses: Access[]) => {
+ let oneAccess = false;
+ let oneInaccess = false;
+ for (const element of accesses) {
+ if (data.areas?.includes(element)) {
+ oneAccess = true;
+ } else {
+ oneInaccess = true;
+ }
+ }
+ if (!oneAccess && oneInaccess) {
+ return 0;
+ } else if (oneAccess && oneInaccess) {
+ return 1;
+ } else {
+ return 2;
+ }
+ };
+
+ return (
+
+
+ );
+};
diff --git a/tgui/packages/tgui/interfaces/MechaControlConsole.tsx b/tgui/packages/tgui/interfaces/MechaControlConsole.tsx
new file mode 100644
index 00000000000..d54e939b035
--- /dev/null
+++ b/tgui/packages/tgui/interfaces/MechaControlConsole.tsx
@@ -0,0 +1,196 @@
+import { useBackend } from "../backend";
+import {
+ Button,
+ Stack,
+ LabeledList,
+ ProgressBar,
+ Section,
+ NoticeBox,
+} from "../components";
+import { Window } from "../layouts";
+import { toTitleCase, decodeHtmlEntities } from "common/string";
+import { useLocalState } from "../backend";
+import { decode } from "punycode";
+
+interface Message {
+ time: string;
+ message: string;
+}
+
+interface Beacon {
+ name: string;
+ cellCharge: number;
+ integrity: number;
+ integrityMax: number;
+ airtank: number;
+ pilot: string;
+ location: string;
+ equipment: string;
+ cargo: number;
+ cargoCapacity: number;
+ ref: string;
+ logs: Message[];
+}
+
+interface InputData {
+ beacons: Beacon[];
+}
+
+export const MechaControlConsole = (props: InputData, context: any) => {
+ const { act, data } = useBackend(context);
+ const { beacons } = data;
+ const [currentBeaconLog, setCurrentBeaconLog] = useLocalState(
+ context,
+ "currentBeaconLog",
+ null
+ );
+
+ return (
+
+
+ {beacons?.length ? (
+ <>
+ {currentBeaconLog ? (
+ {
+ setCurrentBeaconLog(null);
+ }}
+ >
+ Close Log
+
+ }
+ >
+
+ {currentBeaconLog.logs.map((message: Message) => (
+
+ {message.time}{" "}
+ {decodeHtmlEntities(message.message)}
+
+ ))}
+
+
+ ) : (
+ <>
+ {beacons.map((beacon) => (
+
+
+ act("send_message", { mt_ref: beacon.ref })
+ }
+ >
+ Message
+
+ {
+ act("get_log", { mt_ref: beacon.ref }),
+ setCurrentBeaconLog(beacon);
+ }}
+ >
+ View Log
+
+ act("shock", { mt_ref: beacon.ref })}
+ />
+ >
+ }
+ >
+
+
+
+
+
+ {(beacon?.cellCharge && (
+
+ )) || No Cell Installed}
+
+
+ {beacon.airtank}kPa
+
+
+ {beacon.pilot || "Unoccupied"}
+
+
+ {toTitleCase(beacon.location) || "Unknown"}
+
+
+ {beacon.equipment || "None"}
+
+ {(beacon.cargoCapacity && (
+
+
+
+ )) ||
+ null}
+
+
+ ))}
+ >
+ )}
+ >
+ ) : (
+ <>
+
+
+ No beacons detected!
+
+
+ >
+ )}
+
+
+ );
+};
diff --git a/tgui/packages/tgui/interfaces/PrisonerImplantManager.tsx b/tgui/packages/tgui/interfaces/PrisonerImplantManager.tsx
new file mode 100644
index 00000000000..9e89aadf0fd
--- /dev/null
+++ b/tgui/packages/tgui/interfaces/PrisonerImplantManager.tsx
@@ -0,0 +1,157 @@
+import { useBackend } from "../backend";
+import { Window } from "../layouts";
+import {
+ Box,
+ Section,
+ Stack,
+ LabeledList,
+ Button,
+ Divider,
+ LabeledControls,
+} from "../components";
+
+interface Implant {
+ implantee: string;
+ ref: string;
+ location: string;
+ id: number;
+ remainingUnits: number;
+ coordinates: string;
+}
+
+interface InputData {
+ chemImplants: Implant[];
+ trackingImplants: Implant[];
+}
+
+export const PrisonerImplantManager = (props: any, context: any) => {
+ const { act, data } = useBackend(context);
+ const { trackingImplants, chemImplants } = data;
+
+ return (
+
+
+
+
+
+ {chemImplants?.length ? (
+
+ {chemImplants?.map((implant) => (
+
+
+ Subject: {implant.implantee}
+
+
+
+
+ {implant.remainingUnits} u.
+
+
+
+
+ act("inject", { ref: implant.ref, amt: 1 })
+ }
+ >
+ 1 u
+
+
+ act("inject", { ref: implant.ref, amt: 5 })
+ }
+ >
+ 5 u
+
+
+ act("inject", { ref: implant.ref, amt: 10 })
+ }
+ >
+ 10 u
+
+
+
+
+
+
+ ))}
+
+ ) : (
+ <>
+
+
+ No implants.
+
+
+ >
+ )}
+
+
+
+
+ {trackingImplants?.length ? (
+
+ {trackingImplants?.map((implant) => (
+
+
+ Subject: {implant.implantee}
+
+
+
+
+ {implant.id}
+
+
+ {implant.location}
+
+
+ {implant.coordinates}
+
+
+
+ act("warn", { ref: implant.ref })}
+ >
+ Warn
+
+
+
+
+ ))}
+
+ ) : (
+ <>
+
+
+ No implants.
+
+
+ >
+ )}
+
+
+
+
+
+ );
+};
diff --git a/tgui/packages/tgui/interfaces/SeedStorage.tsx b/tgui/packages/tgui/interfaces/SeedStorage.tsx
new file mode 100644
index 00000000000..58223cc8586
--- /dev/null
+++ b/tgui/packages/tgui/interfaces/SeedStorage.tsx
@@ -0,0 +1,161 @@
+import { BooleanLike } from "../../common/react";
+import { useBackend } from "../backend";
+import {
+ Button,
+ Dimmer,
+ Divider,
+ LabeledList,
+ Section,
+ Stack,
+ Table,
+} from "../components";
+import { Window } from "../layouts";
+import { useLocalState } from "../backend";
+import { capitalize } from "../../common/string";
+
+export type SeedData = {
+ scan_stats: BooleanLike;
+ scan_temperature: BooleanLike;
+ scan_light: BooleanLike;
+ scan_soil: BooleanLike;
+ seeds: Seed[];
+};
+
+type Seed = {
+ name: string;
+ uid: number;
+ pile_id: number;
+ endurance: any;
+ yield: any;
+ maturation: any;
+ production: any;
+ potency: any;
+ harvest: string;
+ ideal_heat: string;
+ ideal_light: string;
+ nutrient_consumption: string;
+ water_consumption: string;
+ traits: string;
+ amount: number;
+};
+
+export const SeedStorage = (props, context) => {
+ const { act, data } = useBackend(context);
+
+ const [selectedSeed, setSelectedSeed] = useLocalState(
+ context,
+ "spellsNameFilter",
+ null
+ );
+
+ return (
+
+
+ act("purge")}
+ />
+ }
+ >
+ {data.seeds.map((seed: Seed) => (
+ setSelectedSeed(seed)}
+ >
+
+ {capitalize(seed.name)}
+
+ {
+ <>
+ {seed.amount}
+
+ >
+ }
+
+
+
+ ))}
+
+ {selectedSeed && (
+
+
+
+
+ {selectedSeed.endurance}
+
+
+ {selectedSeed.yield}
+
+
+ {selectedSeed.maturation}
+
+
+ {selectedSeed.production}
+
+
+ {selectedSeed.potency}
+
+
+ {selectedSeed.harvest}
+
+ {data.scan_temperature && (
+
+ {selectedSeed.ideal_heat}
+
+ )}
+ {data.scan_light && (
+
+ {selectedSeed.ideal_light}
+
+ )}
+ {data.scan_soil && (
+ <>
+
+ {selectedSeed.nutrient_consumption}
+
+
+ {selectedSeed.water_consumption}
+
+ >
+ )}
+
+ {selectedSeed.traits}
+
+
+ {selectedSeed.amount}
+
+
+
+
+
+ act("vend", { ID: selectedSeed.pile_id })}
+ >
+ Vend
+
+ setSelectedSeed(null)}>
+ Cancel
+
+
+
+
+
+ )}
+
+
+ );
+};
diff --git a/tgui/packages/tgui/interfaces/SpaceHeater.tsx b/tgui/packages/tgui/interfaces/SpaceHeater.tsx
new file mode 100644
index 00000000000..2d5a8e8b968
--- /dev/null
+++ b/tgui/packages/tgui/interfaces/SpaceHeater.tsx
@@ -0,0 +1,76 @@
+import { useBackend } from "../backend";
+import { Button, LabeledList, NumberInput, Section } from "../components";
+import { Window } from "../layouts";
+import { useLocalState } from "../backend";
+
+function kelvinToCelsius(kelvin: number) {
+ return kelvin - 273.15;
+}
+
+type InputData = {
+ cell: boolean;
+ charge: number;
+ temperature: number;
+ minTemperature: number;
+ maxTemperature: number;
+};
+
+export const SpaceHeater = (props: any, context: any) => {
+ const { act, data } = useBackend(context);
+ const [useKelvin, setUseKelvin] = useLocalState(
+ context,
+ "useKelvin",
+ true
+ );
+
+ return (
+
+
+ setUseKelvin(!useKelvin)}>
+ Switch Units
+
+ }
+ >
+
+
+ {
+ act("changeTemperature", {
+ newTemp: value,
+ useKelvin: useKelvin,
+ });
+ }}
+ >
+
+
+ act("cell")}>
+ {data.cell ? "Remove" : "Install"}
+
+
+
+
+
+
+ );
+};
diff --git a/tgui/packages/tgui/interfaces/UnderWardrobe.tsx b/tgui/packages/tgui/interfaces/UnderWardrobe.tsx
new file mode 100644
index 00000000000..57736177e60
--- /dev/null
+++ b/tgui/packages/tgui/interfaces/UnderWardrobe.tsx
@@ -0,0 +1,150 @@
+import { BooleanLike } from "common/react";
+import { useBackend, useLocalState } from "../backend";
+import {
+ Button,
+ Section,
+ NoticeBox,
+ Stack,
+ Divider,
+ Table,
+ Tabs,
+} from "../components";
+import { Window } from "../layouts";
+
+interface UnderwearItem {
+ name: string;
+}
+
+interface UnderwearCategory {
+ name: string;
+ catItems: UnderwearItem[];
+}
+
+interface InputData {
+ mayClaim: number;
+ underwearCategories: UnderwearCategory[];
+}
+
+const MAX_PER_PAGE = 18;
+
+const numberWithinRange = (min: number, n: number, max: number) =>
+ Math.min(Math.max(n, min), max);
+
+export const UnderWardrobe = (props: any, context: any) => {
+ const { data, act } = useBackend(context);
+
+ const [selectedUndieCategory, setSelectedUndieCategory] = useLocalState(
+ context,
+ "itemCategory",
+ data.underwearCategories[0]?.name
+ );
+
+ const [currentPage, setCurrentPage] = useLocalState(
+ context,
+ "currentPage",
+ 1
+ );
+
+ const underwear =
+ data.underwearCategories.find(
+ (category) => category.name === selectedUndieCategory
+ )?.catItems || [];
+
+ const totalPages = Math.ceil(underwear?.length / MAX_PER_PAGE);
+
+ return (
+
+
+
+
+
+
+ {data.underwearCategories?.map(
+ (category: UnderwearCategory) => (
+ {
+ setSelectedUndieCategory(category.name),
+ setCurrentPage(1);
+ }}
+ >
+ {category.name} ({category.catItems?.length || 0})
+
+ )
+ )}
+
+
+
+
+
+
+
+
+ {underwear
+ .slice(
+ (currentPage - 1) * MAX_PER_PAGE,
+ currentPage * MAX_PER_PAGE
+ )
+ .map((item: UnderwearItem) => (
+ <>
+
+ {item.name}
+
+
+ act("equip", {
+ underwearCat: selectedUndieCategory,
+ underwearItem: item.name,
+ })
+ }
+ >
+ Equip
+
+
+
+ >
+ ))}
+
+
+ {underwear?.length >= MAX_PER_PAGE ? (
+ <>
+
+
+
+
+ setCurrentPage(
+ numberWithinRange(1, currentPage - 1, totalPages)
+ )
+ }
+ align="right"
+ >
+ Previous Page
+
+
+
+
+ setCurrentPage(
+ numberWithinRange(1, currentPage + 1, totalPages)
+ )
+ }
+ >
+ Next Page
+
+
+
+ >
+ ) : (
+ <>>
+ )}
+
+
+
+
+
+
+ );
+};