diff --git a/code/datums/datum.dm b/code/datums/datum.dm index b7362deaa9..762bfa722c 100644 --- a/code/datums/datum.dm +++ b/code/datums/datum.dm @@ -102,7 +102,7 @@ */ /datum/proc/Destroy(force=FALSE) SHOULD_CALL_PARENT(TRUE) - //SHOULD_NOT_SLEEP(TRUE) + SHOULD_NOT_SLEEP(TRUE) tag = null weak_reference = null //ensure prompt GCing of weakref. diff --git a/code/game/gamemodes/events/holidays/Holidays.dm b/code/game/gamemodes/events/holidays/Holidays.dm index 2af9e9d3b8..16a873073e 100644 --- a/code/game/gamemodes/events/holidays/Holidays.dm +++ b/code/game/gamemodes/events/holidays/Holidays.dm @@ -244,7 +244,11 @@ GLOBAL_LIST_EMPTY(Holiday) //Holidays are lists now, so we can have more than on GLOB.Holiday = list() var/H = tgui_input_text(src,"What holiday is it today?","Set Holiday") + if(!H) + return var/B = tgui_input_text(src,"Now explain what the holiday is about","Set Holiday", multiline = TRUE, prevent_enter = TRUE) + if(!B) + return GLOB.Holiday[H] = B diff --git a/code/modules/admin/modify_robot.dm b/code/modules/admin/modify_robot.dm index 25cab53eb9..29b2c562d7 100644 --- a/code/modules/admin/modify_robot.dm +++ b/code/modules/admin/modify_robot.dm @@ -57,6 +57,9 @@ var/datum/asset/spritesheet_batched/robot_icons/spritesheet = GLOB.robot_sprite_sheets[target.modtype] if(target) + var/ui_theme = target.get_ui_theme() + if(ui_theme) + .["theme"] = ui_theme .["target"] = list() .["target"]["name"] = target.name .["target"]["ckey"] = target.ckey diff --git a/code/modules/mob/living/silicon/robot/dogborg/dog_sleeper.dm b/code/modules/mob/living/silicon/robot/dogborg/dog_sleeper.dm index b294bfd082..649ea0c683 100644 --- a/code/modules/mob/living/silicon/robot/dogborg/dog_sleeper.dm +++ b/code/modules/mob/living/silicon/robot/dogborg/dog_sleeper.dm @@ -16,7 +16,6 @@ var/list/injection_chems = list(REAGENT_ID_INAPROVALINE, REAGENT_ID_BICARIDINE, REAGENT_ID_KELOTANE, REAGENT_ID_ANTITOXIN, REAGENT_ID_DEXALIN, REAGENT_ID_TRICORDRAZINE, REAGENT_ID_SPACEACILLIN, REAGENT_ID_TRAMADOL) //The borg is able to heal every damage type. As a nerf, they use 750 charge per injection. var/eject_port = "ingestion" var/list/items_preserved = list() - var/UI_open = FALSE var/stabilizer = TRUE var/compactor = FALSE var/analyzer = FALSE @@ -199,13 +198,8 @@ dlist.Cut() if(length(contents) > 0) hound.visible_message(span_warning("[hound.name] empties out their contents via their [eject_port] port."), span_notice("You empty your contents via your [eject_port] port.")) - for(var/C in contents) - if(ishuman(C)) - var/mob/living/carbon/human/person = C - person.forceMove(get_turf(src)) - else - var/obj/T = C - T.loc = hound.loc + for(var/atom/movable/content in contents) + content.forceMove(get_turf(src)) playsound(src, 'sound/effects/splat.ogg', 50, 1) update_patient() @@ -231,206 +225,143 @@ /obj/item/dogborg/sleeper/attack_self(mob/user) if(..()) return - sleeperUI(user) + tgui_interact(user) -/obj/item/dogborg/sleeper/proc/sleeperUI(mob/user) - var/dat = "[name] Console
" +/obj/item/dogborg/sleeper/tgui_state(mob/user) + return GLOB.tgui_conscious_state - if(islist(injection_chems)) //Only display this if we're a drug-dispensing doggo. - dat += "

Injector

" - if(patient)// && patient.health > min_health) //Not necessary, leave the buttons on, but the feedback during injection will give more information. - for(var/re in injection_chems) - var/datum/reagent/C = SSchemistry.chemical_reagents[re] - if(C) - dat += "Inject [C.name]
" - else - for(var/re in injection_chems) - var/datum/reagent/C = SSchemistry.chemical_reagents[re] - if(C) - dat += span_linkOff("Inject [C.name]") + "
" +/obj/item/dogborg/sleeper/tgui_interact(mob/user, datum/tgui/ui) + ui = SStgui.try_update_ui(user, src, ui) + if(!ui) + ui = new(user, src, "RobotSleeper", "[name] Console") + ui.open() - dat += "

[name] Status

" - dat += "
" - dat += "Refresh" - dat += "Eject All" - dat += "Eject port: [eject_port]" - dat += "Vore All" //might as well make it obvious - if(!cleaning) - dat += "Self-Clean" - else - dat += span_linkOff("Self-Clean") - if(medsensor) - dat += "Analyze Patient" - if(delivery) - dat += "

Cargo Compartment


" - dat += "Active Slot: [delivery_tag]" - if(islist(deliverylists[delivery_tag])) - dat += "Eject Slot" - dat += "
" - dat += "
" +/obj/item/dogborg/sleeper/tgui_static_data(mob/user) + var/list/data = ..() - if(!delivery && compactor && length(contents))//garbage counter for trashpup - dat += span_red(span_bold("Current load:") + " [length(contents)] / [max_item_count] objects.") + "
" - dat += span_gray("([contents.Join(", ")])") + "

" + if(!isrobot(user)) + return data - if(ore_storage) - dat += "Current ore capacity: [current_capacity] / [max_ore_storage].
" + var/mob/living/silicon/robot/robot_user = user + var/list/robot_chems = list() + for(var/re in injection_chems) + var/datum/reagent/possible_reagent = SSchemistry.chemical_reagents[re] + UNTYPED_LIST_ADD(robot_chems, list("id" = possible_reagent.id, "name" = possible_reagent.name)) - if(delivery && length(contents)) - dat += span_red(span_bold("Current load:") + " [length(contents)] / [max_item_count] objects.") + "
" - dat += span_gray("Cargo compartment slot: Cargo 1.") + "
" - if(length(deliveryslot_1)) - dat += span_gray("([deliveryslot_1.Join(", ")])") + "
" - dat += span_gray("Cargo compartment slot: Cargo 2.") + "
" - if(length(deliveryslot_2)) - dat += span_gray("([deliveryslot_2.Join(", ")])") + "
" - dat += span_gray("Cargo compartment slot: Cargo 3.") + "
" - if(length(deliveryslot_3)) - dat += span_gray("([deliveryslot_3.Join(", ")])") + "
" - dat += span_red("Cargo compartment slot: Fuel.") + "
" - dat += span_red("([jointext(contents - (deliveryslot_1 + deliveryslot_2 + deliveryslot_3),", ")])") + "

" + data["name"] = name + var/robot_theme = robot_user.get_ui_theme() + if(robot_theme) + data["theme"] = robot_theme + data["chems"] = robot_chems + return data - //Cleaning and there are still un-preserved items - if(cleaning && length(contents - items_preserved)) - dat += span_red(span_bold("Self-cleaning mode.") + " [length(contents - items_preserved)] object(s) remaining.") + "
" +/obj/item/dogborg/sleeper/tgui_data(mob/user, datum/tgui/ui, datum/tgui_state/state) + var/list/patient_data - //There are no items to be processed other than un-preserved items - else if(cleaning && length(items_preserved)) - dat += span_red(span_bold("Self-cleaning done. Eject remaining objects now.")) + "
" - - //Preserved items count when the list is populated - if(length(items_preserved)) - dat += span_red("[length(items_preserved)] uncleanable object(s).") + "
" - - if(!patient) - dat += "[src.name] Unoccupied" - else - dat += "[patient.name] => " - - switch(patient.stat) - if(0) - dat += span_green("Conscious") - if(1) - dat += span_orange("Unconscious") - else - dat += span_red("DEAD") - - var/pulse = "\t-Pulse, bpm: [patient.get_pulse(GETPULSE_TOOL)]" - dat += (patient.pulse == PULSE_NONE || patient.pulse == PULSE_THREADY ? span_red(pulse) : span_white(pulse)) - dat += "
" - var/health = "\t-Overall Health %: [round(100 * (patient.health / patient.getMaxHealth()))]" - dat += (patient.health > 0 ? span_white(health) : span_red(health)) - dat += "
" - var/brute = "\t-Brute Damage %: [patient.getBruteLoss()]" - dat += (patient.getBruteLoss() < 60 ? span_gray(brute) : span_red(brute)) - dat += "
" - var/oxygen = "\t-Respiratory Damage %: [patient.getOxyLoss()]" - dat += (patient.getOxyLoss() < 60 ? span_gray(oxygen) : span_red(oxygen)) - dat += "
" - var/toxic = "\t-Toxin Content %: [patient.getToxLoss()]" - dat += (patient.getToxLoss() < 60 ? span_gray(toxic) : span_red(toxic)) - dat += "
" - var/burn = "\t-Burn Severity %: [patient.getFireLoss()]" - dat += (patient.getFireLoss() < 60 ? span_gray(burn) : span_red(burn)) - dat += "
" - - if(round(patient.paralysis / 4) >= 1) - dat += text("
Patient paralyzed for: []
", round(patient.paralysis / 4) >= 1 ? "[round(patient.paralysis / 4)] seconds" : "None") - if(patient.getBrainLoss()) - dat += "
" + span_orange("Significant brain damage detected.") + "

" - if(patient.getCloneLoss()) - dat += "
" + span_orange("Patient may be improperly cloned.") + "

" + if(patient) + var/list/ingested_reagents = list() if(patient.reagents.reagent_list.len) - for(var/datum/reagent/R in patient.reagents.reagent_list) - dat += "
[R.name]:
[round(R.volume, 0.1)] units

" - dat += "
" + for(var/datum/reagent/ingested in patient.reagents.reagent_list) + UNTYPED_LIST_ADD(ingested_reagents, list("name" = ingested.name, "volume" = ingested.volume)) - var/datum/browser/popup = new(user, "sleeper_b", "[name] Console", 450, 500, src) - popup.set_content(dat) - popup.open() - UI_open = TRUE - return + patient_data = list( + "name" = patient.name, + "stat" = patient.stat, + "pulse" = patient.get_pulse(GETPULSE_TOOL), + "crit_pulse" = (patient.pulse == PULSE_NONE || patient.pulse == PULSE_THREADY), + "health" = patient.health, + "max_health" = patient.getMaxHealth(), + "brute" = patient.getBruteLoss(), + "oxy" = patient.getOxyLoss(), + "tox" = patient.getToxLoss(), + "burn" = patient.getFireLoss(), + "paralysis" = patient.paralysis, + "braindamage" = !!patient.getBrainLoss(), + "clonedamage" = !!patient.getCloneLoss(), + "ingested_reagents" = ingested_reagents + ) -/obj/item/dogborg/sleeper/Topic(href, href_list) - if(..() || usr == patient) - return - usr.set_machine(src) - if(href_list["refresh"]) - update_patient() - src.updateUsrDialog(usr) - sleeperUI(usr) - return - if(href_list["eject"]) - go_out() - sleeperUI(usr) - return - if(href_list["close"]) - UI_open = FALSE - return - if(href_list["clean"]) - if(!cleaning) - var/confirm = tgui_alert(usr, "You are about to engage self-cleaning mode. This will fill your [src] with caustic enzymes to remove any objects or biomatter, and convert them into energy. Are you sure?", "Confirmation", list("Self-Clean", "Cancel")) - if(confirm == "Self-Clean") - if(cleaning) - return - else - cleaning = 1 - drain(startdrain) - START_PROCESSING(SSobj, src) - update_patient() - if(patient) - to_chat(patient, span_danger("[hound.name]'s [src.name] fills with caustic enzymes around you!")) - return - if(cleaning) - sleeperUI(usr) - return - if(href_list["analyze"]) //DO HEALTH ANALYZER STUFF HERE. - med_analyzer.scan_mob(patient,hound) - if(href_list["port"]) - switch(eject_port) - if("ingestion") - eject_port = "disposal" - if("disposal") - eject_port = "ingestion" - sleeperUI(usr) - return - if (href_list["ingest"]) - vore_ingest_all() - sleeperUI(usr) - return - if(href_list["deliveryslot"]) - var/tag = tgui_input_list(usr, "Select active delivery slot:", "Slot Choice", deliverylists) - if(!tag) - return 0 - delivery_tag = tag - sleeperUI(usr) - return - if(href_list["slot_eject"]) - if(length(deliverylists[delivery_tag]) > 0) + var/list/data = list( + "our_patient" = patient_data, + "eject_port" = eject_port, + "cleaning" = cleaning, + "medsensor" = medsensor, + "delivery" = delivery, + "delivery_tag" = delivery_tag, + "delivery_lists" = deliverylists, + "compactor" = compactor, + "max_item_count" = max_item_count, + "ore_storage" = ore_storage, + "current_capacity" = current_capacity, + "max_ore_storage" = max_ore_storage, + "contents" = contents, + "deliveryslot_1" = deliveryslot_1, + "deliveryslot_2" = deliveryslot_2, + "deliveryslot_3" = deliveryslot_3, + "items_preserved" = items_preserved, + ) + return data +/obj/item/dogborg/sleeper/tgui_act(action, list/params, datum/tgui/ui, datum/tgui_state/state) + if(..()) + return TRUE + + if(ui.user == patient) + return FALSE + + switch(action) + if("eject") + go_out() + return TRUE + if("clean") + if(cleaning) + return FALSE + cleaning = TRUE + drain(startdrain) + START_PROCESSING(SSobj, src) + update_patient() + if(patient) + to_chat(patient, span_danger("[hound.name]'s [src.name] fills with caustic enzymes around you!")) + return TRUE + if("analyze") + med_analyzer.scan_mob(patient,hound) + return TRUE + if("port") + var/new_port = params["value"] + if(!(new_port in list("disposal", "ingestion"))) + return FALSE + eject_port = new_port + return TRUE + if("ingest") + vore_ingest_all() + return TRUE + if("deliveryslot") + var/new_tag = params["value"] + if(!(new_tag in deliverylists)) + return FALSE + delivery_tag = new_tag + return TRUE + if("slot_eject") + if(!length(deliverylists[delivery_tag])) + return FALSE hound.visible_message(span_warning("[hound.name] empties out their cargo compartment via their [eject_port] port."), span_notice("You empty your cargo compartment via your [eject_port] port.")) - for(var/C in deliverylists[delivery_tag]) - if(ishuman(C)) - var/mob/living/carbon/human/person = C - person.forceMove(get_turf(src)) - else - var/obj/T = C - T.loc = hound.loc + for(var/atom/movable/content in deliverylists[delivery_tag]) + content.forceMove(get_turf(src)) playsound(src, 'sound/effects/splat.ogg', 50, 1) update_patient() deliverylists[delivery_tag].Cut() - sleeperUI(usr) - return - if(patient && !(patient.stat & DEAD)) //What is bitwise NOT? ... Thought it was tilde. - if(href_list["inject"] == REAGENT_ID_INAPROVALINE || patient.health > min_health) - inject_chem(usr, href_list["inject"]) - else - to_chat(usr, span_notice("ERROR: Subject is not in stable condition for injections.")) - else - to_chat(usr, span_notice("ERROR: Subject cannot metabolise chemicals.")) - - updateUsrDialog(usr) - sleeperUI(usr) //Needs a callback to boop the page to refresh. - return + return TRUE + if("inject") + if(!patient || (patient.stat & DEAD)) + to_chat(ui.user, span_notice("ERROR: Subject cannot metabolise chemicals.")) + return FALSE + var/selected_reagent = params["value"] + if(!(selected_reagent in injection_chems)) + return FALSE + if(selected_reagent == REAGENT_ID_INAPROVALINE || patient.health > min_health) + inject_chem(ui.user, selected_reagent) + else + to_chat(ui.user, span_notice("ERROR: Subject is not in stable condition for injections.")) + return TRUE /obj/item/dogborg/sleeper/proc/inject_chem(mob/user, chem) if(patient && patient.reagents) @@ -451,8 +382,6 @@ hound = src.loc if(!istype(hound,/mob/living/silicon/robot)) return - if(UI_open == TRUE) - sleeperUI(hound) //Cleaning looks better with red on, even with nobody in it if(cleaning || (length(contents) > 10) || (decompiler && (length(contents) > 5)) || (analyzer && (length(contents) > 1))) diff --git a/code/modules/mob/living/silicon/robot/robot.dm b/code/modules/mob/living/silicon/robot/robot.dm index 2a3755d381..1c5a1c3e8a 100644 --- a/code/modules/mob/living/silicon/robot/robot.dm +++ b/code/modules/mob/living/silicon/robot/robot.dm @@ -1674,3 +1674,8 @@ nutrition = 1000 to_chat(src, span_warning("You have purged most of the nutrition lingering in your systems.")) return TRUE + +/mob/living/silicon/robot/proc/get_ui_theme() + if(emagged) + return "syndicate" + return ui_theme diff --git a/tgui/packages/tgui/interfaces/ModifyRobot/index.tsx b/tgui/packages/tgui/interfaces/ModifyRobot/index.tsx index 93d8d013f3..8e3714e21f 100644 --- a/tgui/packages/tgui/interfaces/ModifyRobot/index.tsx +++ b/tgui/packages/tgui/interfaces/ModifyRobot/index.tsx @@ -12,7 +12,7 @@ import { Tabs, } from 'tgui-core/components'; -import { LawManagerLaws, LawManagerLawSets } from '../LawManager'; +import { LawManagerLawSets, LawManagerLaws } from '../LawManager'; import { ModifyRobotNoModule } from './ModifyRobotNoModule'; import { ModifyRobotAccess } from './ModifyRobotTabs/ModifyRobotAccess'; import { ModifyRobotComponent } from './ModifyRobotTabs/ModifyRobotComponent'; @@ -63,6 +63,7 @@ export const ModifyRobot = (props) => { comms_options, armour_options, current_gear, + theme, } = data; const [tab, setTab] = useState(0); @@ -146,7 +147,11 @@ export const ModifyRobot = (props) => { ); return ( - + diff --git a/tgui/packages/tgui/interfaces/ModifyRobot/types.ts b/tgui/packages/tgui/interfaces/ModifyRobot/types.ts index d9a21f5d84..7008dd7817 100644 --- a/tgui/packages/tgui/interfaces/ModifyRobot/types.ts +++ b/tgui/packages/tgui/interfaces/ModifyRobot/types.ts @@ -38,6 +38,7 @@ export type Data = { law_sets: law_pack[]; active_ais: DropdownEntry[]; selected_ai: string | null; + theme?: string; }; export type DropdownEntry = { diff --git a/tgui/packages/tgui/interfaces/RobotSleeper/SubElements/CargoCompartment.tsx b/tgui/packages/tgui/interfaces/RobotSleeper/SubElements/CargoCompartment.tsx new file mode 100644 index 0000000000..f13c4ecdde --- /dev/null +++ b/tgui/packages/tgui/interfaces/RobotSleeper/SubElements/CargoCompartment.tsx @@ -0,0 +1,32 @@ +import { useBackend } from 'tgui/backend'; +import { Button, Dropdown, Section, Stack } from 'tgui-core/components'; +import type { Data } from '../types'; + +export const CargoCompartment = (props) => { + const { act, data } = useBackend(); + const { delivery_lists, delivery_tag } = data; + + return ( +
+ + + + act('deliveryslot', { value: value }) + } + options={Object.keys(delivery_lists)} + selected={delivery_tag} + /> + + + + + +
+ ); +}; diff --git a/tgui/packages/tgui/interfaces/RobotSleeper/SubElements/InjectorPanel.tsx b/tgui/packages/tgui/interfaces/RobotSleeper/SubElements/InjectorPanel.tsx new file mode 100644 index 0000000000..16f989e2f6 --- /dev/null +++ b/tgui/packages/tgui/interfaces/RobotSleeper/SubElements/InjectorPanel.tsx @@ -0,0 +1,31 @@ +import { useBackend } from 'tgui/backend'; +import { Box, Button, Section, Stack } from 'tgui-core/components'; +import type { Data, RobotChem } from '../types'; + +export const InjectorPanel = (props: { robotChems: RobotChem[] }) => { + const { act, data } = useBackend(); + const { our_patient } = data; + const { robotChems } = props; + + return ( +
+ {robotChems.length ? ( + + {robotChems.map((chem) => ( + + + + ))} + + ) : ( + No chems found. + )} +
+ ); +}; diff --git a/tgui/packages/tgui/interfaces/RobotSleeper/SubElements/SleeperStatus.tsx b/tgui/packages/tgui/interfaces/RobotSleeper/SubElements/SleeperStatus.tsx new file mode 100644 index 0000000000..b830f8d931 --- /dev/null +++ b/tgui/packages/tgui/interfaces/RobotSleeper/SubElements/SleeperStatus.tsx @@ -0,0 +1,19 @@ +import { Section, Stack } from 'tgui-core/components'; +import { SleeperButtons } from './SleeperStatusElements/SleeperButtons'; +import { SleeperStatusPanel } from './SleeperStatusElements/SleeperStatusPanel'; + +export const SleeperStatus = (props: { name: string }) => { + const { name } = props; + + return ( +
+ + + + + + + +
+ ); +}; diff --git a/tgui/packages/tgui/interfaces/RobotSleeper/SubElements/SleeperStatusElements/SleeperButtons.tsx b/tgui/packages/tgui/interfaces/RobotSleeper/SubElements/SleeperStatusElements/SleeperButtons.tsx new file mode 100644 index 0000000000..7418bf7455 --- /dev/null +++ b/tgui/packages/tgui/interfaces/RobotSleeper/SubElements/SleeperStatusElements/SleeperButtons.tsx @@ -0,0 +1,68 @@ +import { useBackend } from 'tgui/backend'; +import { Box, Button, Dropdown, Stack } from 'tgui-core/components'; +import { EJECTION_OPTIONS } from '../../constants'; +import type { Data } from '../../types'; + +export const SleeperButtons = (props) => { + const { act, data } = useBackend(); + const { eject_port, cleaning, medsensor, name } = data; + + return ( + <> + + + + Eject Port: + + + act('port', { value: value })} + options={EJECTION_OPTIONS} + selected={eject_port} + /> + + + + + + + act('eject')} + tooltip="Eject all your sleeper contents." + > + Eject All + + + + act('ingest')} + tooltip="Moves all your sleepr contents into your currently selected vorebelly." + > + Vore All + + + + act('clean')} + disabled={cleaning} + color={cleaning ? 'red' : undefined} + tooltip={`Self-Cleaning mode will fill your ${name} with causic enzymes to remove any objects or biomatter, and convert them into energy.`} + > + Self-Clean + + + {!!medsensor && ( + + + + )} + + + + ); +}; diff --git a/tgui/packages/tgui/interfaces/RobotSleeper/SubElements/SleeperStatusElements/SleeperCargoStatus.tsx b/tgui/packages/tgui/interfaces/RobotSleeper/SubElements/SleeperStatusElements/SleeperCargoStatus.tsx new file mode 100644 index 0000000000..1af1aabb4a --- /dev/null +++ b/tgui/packages/tgui/interfaces/RobotSleeper/SubElements/SleeperStatusElements/SleeperCargoStatus.tsx @@ -0,0 +1,36 @@ +import { Fragment } from 'react'; +import { useBackend } from 'tgui/backend'; +import { Box, Stack } from 'tgui-core/components'; +import { filterFuel, summarizeItems } from '../../functions'; +import type { Data } from '../../types'; + +export const SleeperCargoStatus = (props) => { + const { data } = useBackend(); + const { deliveryslot_1, deliveryslot_2, deliveryslot_3, contents } = data; + + const cargoSlots = [deliveryslot_1, deliveryslot_2, deliveryslot_3]; + + return ( + <> + {cargoSlots.map((slot, index) => ( + + + Cargo compartment slot: Cargo {index}. + + + {summarizeItems(slot)} + + + ))} + + Cargo compartment slot: Fuel. + + + + {summarizeItems(filterFuel(contents, cargoSlots.flat()))} + + + + + ); +}; diff --git a/tgui/packages/tgui/interfaces/RobotSleeper/SubElements/SleeperStatusElements/SleeperPatient.tsx b/tgui/packages/tgui/interfaces/RobotSleeper/SubElements/SleeperStatusElements/SleeperPatient.tsx new file mode 100644 index 0000000000..610001f3fe --- /dev/null +++ b/tgui/packages/tgui/interfaces/RobotSleeper/SubElements/SleeperStatusElements/SleeperPatient.tsx @@ -0,0 +1,92 @@ +import { useBackend } from 'tgui/backend'; +import { Box, Stack } from 'tgui-core/components'; +import { STAT_TO_COLOR } from '../../constants'; +import type { Data } from '../../types'; + +export const SleeperPatient = (props: { name: string }) => { + const { data } = useBackend(); + const { our_patient } = data; + const { name } = props; + + if (!our_patient) { + return `${name} Unoccupied.`; + } + + const isParalysed = Math.round(our_patient.paralysis / 4) >= 1; + + return ( + <> + + + {our_patient.name} + {`=>`} + + + {Object.keys(STAT_TO_COLOR)[our_patient.stat]} + + + + + - Pulse, bpm: {our_patient.pulse} + + + + + + 0 ? undefined : 'red'}> + {`- Overall Health: ${((100 * our_patient.health) / our_patient.max_health).toFixed(0)}`} + + + + + {`- Brute Damage: ${our_patient.brute.toFixed(0)}`} + + + + + {`- Respiratory Damage: ${our_patient.oxy.toFixed(0)}`} + + + + + {`- Toxin Content: ${our_patient.tox.toFixed(0)}`} + + + + + {`- Burn Severity: ${our_patient.burn.toFixed(0)}`} + + + {(isParalysed || + !!our_patient.braindamage || + !!our_patient.clonedamage || + !!our_patient.ingested_reagents) && } + {isParalysed && ( + + + {`Patient paralyzed for: ${Math.round(our_patient.paralysis / 4)} seonds`} + + + )} + {!!our_patient.braindamage && ( + + Significant brain damage detected. + + )} + {!!our_patient.clonedamage && ( + + Patient may be improperly cloned. + + )} + {!!our_patient.ingested_reagents && + our_patient.ingested_reagents.map((reagent) => ( + + + {`${reagent.name}: `} + + {Math.round(reagent.volume * 10) / 10} units + + ))} + + ); +}; diff --git a/tgui/packages/tgui/interfaces/RobotSleeper/SubElements/SleeperStatusElements/SleeperSelfClean.tsx b/tgui/packages/tgui/interfaces/RobotSleeper/SubElements/SleeperStatusElements/SleeperSelfClean.tsx new file mode 100644 index 0000000000..895b72ebc4 --- /dev/null +++ b/tgui/packages/tgui/interfaces/RobotSleeper/SubElements/SleeperStatusElements/SleeperSelfClean.tsx @@ -0,0 +1,45 @@ +import { useBackend } from 'tgui/backend'; +import { Box, Stack } from 'tgui-core/components'; +import { filterFuel } from '../../functions'; +import type { Data } from '../../types'; + +export const SleeperSelfClean = (props) => { + const { data } = useBackend(); + const { cleaning, items_preserved, contents } = data; + + const perservedCount = items_preserved.length; + const cleanableCount = filterFuel(contents, items_preserved).length; + + return ( + <> + {!!cleaning && ( + + {cleanableCount ? ( + <> + + Self-cleaning mode. + + + {`${cleanableCount} object${cleanableCount > 1 ? 's' : ''} remaining.`} + + + ) : ( + !!items_preserved && ( + + Self-cleaning done. Eject remaining objects now. + + ) + )} + + )} + {!!perservedCount && ( + + + {`${perservedCount} uncleanable object${perservedCount > 1 ? 's' : ''}.`} + + + )} + {!!cleaning || (!!perservedCount && )} + + ); +}; diff --git a/tgui/packages/tgui/interfaces/RobotSleeper/SubElements/SleeperStatusElements/SleeperStatusPanel.tsx b/tgui/packages/tgui/interfaces/RobotSleeper/SubElements/SleeperStatusElements/SleeperStatusPanel.tsx new file mode 100644 index 0000000000..f41f9009d7 --- /dev/null +++ b/tgui/packages/tgui/interfaces/RobotSleeper/SubElements/SleeperStatusElements/SleeperStatusPanel.tsx @@ -0,0 +1,71 @@ +import { useBackend } from 'tgui/backend'; +import { Box, Section, Stack } from 'tgui-core/components'; +import { currentLoadToColor, summarizeItems } from '../../functions'; +import type { Data } from '../../types'; +import { SleeperCargoStatus } from './SleeperCargoStatus'; +import { SleeperPatient } from './SleeperPatient'; +import { SleeperSelfClean } from './SleeperSelfClean'; + +export const SleeperStatusPanel = (props: { name: string }) => { + const { data } = useBackend(); + const { + delivery, + compactor, + contents, + max_item_count, + ore_storage, + current_capacity, + max_ore_storage, + } = data; + const { name } = props; + + return ( +
+ + {(!!delivery || !!compactor) && !!contents.length && ( + <> + + + Current load: + + + {` ${contents.length} / ${max_item_count} objects.`} + + + {delivery ? ( + + ) : ( + + {summarizeItems(contents)} + + )} + + + )} + {!!ore_storage && ( + <> + + + Current ore capacity: + + + {` ${current_capacity} / ${max_ore_storage} ores.`} + + + + + )} + + + +
+ ); +}; diff --git a/tgui/packages/tgui/interfaces/RobotSleeper/constants.ts b/tgui/packages/tgui/interfaces/RobotSleeper/constants.ts new file mode 100644 index 0000000000..6fbcbb77f5 --- /dev/null +++ b/tgui/packages/tgui/interfaces/RobotSleeper/constants.ts @@ -0,0 +1,6 @@ +export const EJECTION_OPTIONS = ['disposal', 'ingestion']; +export const STAT_TO_COLOR = { + Conscious: 'green', + Unconscious: 'orange', + DEAD: 'red', +}; diff --git a/tgui/packages/tgui/interfaces/RobotSleeper/functions.ts b/tgui/packages/tgui/interfaces/RobotSleeper/functions.ts new file mode 100644 index 0000000000..3f112495f3 --- /dev/null +++ b/tgui/packages/tgui/interfaces/RobotSleeper/functions.ts @@ -0,0 +1,38 @@ +export function currentLoadToColor( + current: number, + maximum: number, +): string | undefined { + const fillState = current / maximum; + if (fillState < 0.75) { + return undefined; + } + if (fillState < 1) { + return 'yellow'; + } + return 'red'; +} + +export function summarizeItems(items: string[]): string { + const countMap = items.reduce>((acc, item) => { + acc[item] = (acc[item] || 0) + 1; + return acc; + }, {}); + + return [...new Set(items)] + .map((item) => (countMap[item] > 1 ? `${item} x${countMap[item]}` : item)) + .join(', '); +} + +export function filterFuel(contents: string[], cargo: string[]): string[] { + const cargoCopy = [...cargo]; + + return contents.reduce((acc, item) => { + const index = cargoCopy.indexOf(item); + if (index !== -1) { + cargoCopy.splice(index, 1); + } else { + acc.push(item); + } + return acc; + }, []); +} diff --git a/tgui/packages/tgui/interfaces/RobotSleeper/index.tsx b/tgui/packages/tgui/interfaces/RobotSleeper/index.tsx new file mode 100644 index 0000000000..3fcb3be84c --- /dev/null +++ b/tgui/packages/tgui/interfaces/RobotSleeper/index.tsx @@ -0,0 +1,34 @@ +import { useBackend } from 'tgui/backend'; +import { Window } from 'tgui/layouts'; +import { Stack } from 'tgui-core/components'; +import { CargoCompartment } from './SubElements/CargoCompartment'; +import { InjectorPanel } from './SubElements/InjectorPanel'; +import { SleeperStatus } from './SubElements/SleeperStatus'; +import type { Data } from './types'; + +export const RobotSleeper = (props) => { + const { data } = useBackend(); + + const { theme, chems, delivery, name } = data; + return ( + + + + {!!chems && ( + + + + )} + {!!delivery && ( + + + + )} + + + + + + + ); +}; diff --git a/tgui/packages/tgui/interfaces/RobotSleeper/types.ts b/tgui/packages/tgui/interfaces/RobotSleeper/types.ts new file mode 100644 index 0000000000..91b206c097 --- /dev/null +++ b/tgui/packages/tgui/interfaces/RobotSleeper/types.ts @@ -0,0 +1,44 @@ +import type { BooleanLike } from 'tgui-core/react'; + +export type Data = { + name?: string; + theme?: string; + chems?: RobotChem[]; + + our_patient: Patient | null; + eject_port: string; + cleaning: BooleanLike; + medsensor: BooleanLike; + delivery: BooleanLike; + delivery_tag: string; + delivery_lists: Record; + compactor: BooleanLike; + max_item_count: number; + ore_storage: BooleanLike; + current_capacity: number; + max_ore_storage: number; + contents: string[]; + deliveryslot_1: string[]; + deliveryslot_2: string[]; + deliveryslot_3: string[]; + items_preserved: string[]; +}; + +export type RobotChem = { id: string; name: string }; + +export type Patient = { + name: string; + stat: number; + pulse: number; + crit_pulse: BooleanLike; + health: number; + max_health: number; + brute: number; + oxy: number; + tox: number; + burn: number; + paralysis: number; + braindamage: BooleanLike; + clonedamage: BooleanLike; + ingested_reagents: { name: string; volume: number }[]; +};