diff --git a/code/__defines/_planes+layers_vr.dm b/code/__defines/_planes+layers_vr.dm
index 125f492cd7a..a9266aa701d 100644
--- a/code/__defines/_planes+layers_vr.dm
+++ b/code/__defines/_planes+layers_vr.dm
@@ -3,5 +3,6 @@
#define PLANE_CH_STATUS_R 27 //Right-side status icon
#define PLANE_CH_BACKUP 28 //Backup implant
#define PLANE_CH_VANTAG 29 //Vore Antag hud
+#define PLANE_CH_STOMACH -11 //Stomach Plane
#define PLANE_AUGMENTED 40 //Augmented-reality plane
diff --git a/code/__defines/chemistry_vr.dm b/code/__defines/chemistry_vr.dm
index 0058985adcb..3ce868c5b82 100644
--- a/code/__defines/chemistry_vr.dm
+++ b/code/__defines/chemistry_vr.dm
@@ -1,4 +1,5 @@
// More for our custom races
#define IS_CHIMERA 12
#define IS_SHADEKIN 13
-#define IS_ALRAUNE 14
\ No newline at end of file
+#define IS_ALRAUNE 14
+#define IS_LLEILL 15
\ No newline at end of file
diff --git a/code/__defines/is_helpers.dm b/code/__defines/is_helpers.dm
index dfb8b9246a8..b4e180deb09 100644
--- a/code/__defines/is_helpers.dm
+++ b/code/__defines/is_helpers.dm
@@ -51,6 +51,7 @@
#define issilicon(A) istype(A, /mob/living/silicon)
#define isAI(A) istype(A, /mob/living/silicon/ai)
#define isrobot(A) istype(A, /mob/living/silicon/robot)
+#define isshell(A) istype(A, /mob/living/silicon/robot/ai_shell)
#define ispAI(A) istype(A, /mob/living/silicon/pai)
#define isbot(A) istype(A, /mob/living/bot)
diff --git a/code/__defines/mobs_vr.dm b/code/__defines/mobs_vr.dm
index 183921ecde7..ae87b1c624e 100644
--- a/code/__defines/mobs_vr.dm
+++ b/code/__defines/mobs_vr.dm
@@ -7,7 +7,9 @@
#define VIS_AUGMENTED 32
-#define VIS_COUNT 32
+#define VIS_CH_STOMACH 33
+
+#define VIS_COUNT 33
//Protean organs
#define O_ORCH "orchestrator"
@@ -33,6 +35,7 @@
#define SPECIES_ZORREN_HIGH "Zorren"
#define SPECIES_CUSTOM "Custom Species"
#define SPECIES_TAJARAN "Tajara"
+#define SPECIES_LLEILL "Lleill"
//monkey species
#define SPECIES_MONKEY_AKULA "Sobaka"
#define SPECIES_MONKEY_NEVREAN "Sparra"
diff --git a/code/__defines/species_languages_vr.dm b/code/__defines/species_languages_vr.dm
index ec9165653d7..146fcb86ab9 100644
--- a/code/__defines/species_languages_vr.dm
+++ b/code/__defines/species_languages_vr.dm
@@ -17,3 +17,4 @@
#define LANGUAGE_MOUSE "Mouse"
#define LANGUAGE_SHADEKIN "Shadekin Empathy"
+#define LANGUAGE_LLEILL "Glamour Speak"
diff --git a/code/_helpers/unsorted.dm b/code/_helpers/unsorted.dm
index 20c38938a59..8a722ac7676 100644
--- a/code/_helpers/unsorted.dm
+++ b/code/_helpers/unsorted.dm
@@ -1580,6 +1580,7 @@ GLOBAL_REAL_VAR(list/stack_trace_storage)
. += new /obj/screen/plane_master{plane = PLANE_CH_HEALTH_VR} //Health bar but transparent at 100
. += new /obj/screen/plane_master{plane = PLANE_CH_BACKUP} //Backup implant status
. += new /obj/screen/plane_master{plane = PLANE_CH_VANTAG} //Vore Antags
+ . += new /obj/screen/plane_master{plane = PLANE_CH_STOMACH} //Stomachs
. += new /obj/screen/plane_master{plane = PLANE_AUGMENTED} //Augmented reality
//VOREStation Add End
/proc/CallAsync(datum/source, proctype, list/arguments)
diff --git a/code/controllers/configuration.dm b/code/controllers/configuration.dm
index edfe9059ff1..da18b6c5706 100644
--- a/code/controllers/configuration.dm
+++ b/code/controllers/configuration.dm
@@ -1015,6 +1015,12 @@ var/list/gamemode_cache = list()
if("asset_cdn_url")
config.asset_cdn_url = value
+ if("allow_robot_recolor")
+ config.allow_robot_recolor = TRUE
+
+ if("allow_simple_mob_recolor")
+ config.allow_simple_mob_recolor = TRUE
+
else
log_misc("Unknown setting in configuration: '[name]'")
@@ -1083,10 +1089,6 @@ var/list/gamemode_cache = list()
if("loadout_whitelist")
config.loadout_whitelist = text2num(value)
- if("allow_robot_recolor")
- config.allow_robot_recolor = 1
- if("allow_simple_mob_recolor")
- config.allow_simple_mob_recolor = 1
else
log_misc("Unknown setting in configuration: '[name]'")
diff --git a/code/datums/datacore.dm b/code/datums/datacore.dm
index 17d8fb17edc..7c37c10f520 100644
--- a/code/datums/datacore.dm
+++ b/code/datums/datacore.dm
@@ -486,7 +486,6 @@ var/global/list/PDA_Manifest = list()
R.fields["ma_crim"] = "None"
R.fields["ma_crim_d"] = "No major crime convictions."
R.fields["notes"] = "No notes."
- R.fields["notes"] = "No notes."
if(hidden)
hidden_security += R
else
diff --git a/code/game/machinery/adv_med.dm b/code/game/machinery/adv_med.dm
index b73df5ca3ab..87d4146259d 100644
--- a/code/game/machinery/adv_med.dm
+++ b/code/game/machinery/adv_med.dm
@@ -205,6 +205,11 @@
occupantData["bodyTempF"] = (((H.bodytemperature-T0C) * 1.8) + 32)
occupantData["hasBorer"] = H.has_brain_worms()
+ occupantData["colourblind"] = null
+ for(var/datum/modifier/M in H.modifiers)
+ if(!isnull(M.wire_colors_replace))
+ occupantData["colourblind"] = LAZYLEN(M.wire_colors_replace)
+ break
var/bloodData[0]
if(H.vessel)
diff --git a/code/game/machinery/cryopod.dm b/code/game/machinery/cryopod.dm
index 38b48ee76a4..49dcdbafe76 100644
--- a/code/game/machinery/cryopod.dm
+++ b/code/game/machinery/cryopod.dm
@@ -86,7 +86,7 @@
/obj/machinery/computer/cryopod/tgui_interact(mob/user, datum/tgui/ui, datum/tgui/parent_ui)
ui = SStgui.try_update_ui(user, src, ui)
if(!ui)
- ui = new(user, src, "CryoStorageVr", storage_name) // VOREStation Edit - Use our own template for our custom data
+ ui = new(user, src, "CryoStorage", storage_name) // VOREStation Edit - Use our own template for our custom data
ui.open()
/obj/machinery/computer/cryopod/tgui_data(mob/user, datum/tgui/ui, datum/tgui_state/state)
diff --git a/code/game/objects/items/robot/robot_upgrades.dm b/code/game/objects/items/robot/robot_upgrades.dm
index fe41bc658ff..ace8fde6360 100644
--- a/code/game/objects/items/robot/robot_upgrades.dm
+++ b/code/game/objects/items/robot/robot_upgrades.dm
@@ -62,6 +62,7 @@
/obj/item/borg/upgrade/utility/rename/action(var/mob/living/silicon/robot/R)
if(..()) return 0
+ if(isshell(R)) return 0
R.notify_ai(ROBOT_NOTIFICATION_NEW_NAME, R.name, heldname)
R.name = heldname
R.custom_name = heldname
diff --git a/code/game/objects/random/maintenance.dm b/code/game/objects/random/maintenance.dm
index 56366d7cdd9..25f70920591 100644
--- a/code/game/objects/random/maintenance.dm
+++ b/code/game/objects/random/maintenance.dm
@@ -112,7 +112,8 @@ something, make sure it's not in one of the other lists.*/
prob(2);/obj/item/toy/tennis/blue,
prob(2);/obj/item/toy/tennis/purple,
prob(1);/obj/item/toy/baseball,
- prob(1);/obj/item/pizzavoucher
+ prob(1);/obj/item/pizzavoucher,
+ prob(2);/obj/item/weapon/cracker
/* VOREStation Edit End */
)
diff --git a/code/game/objects/random/misc.dm b/code/game/objects/random/misc.dm
index 3f0f80593ca..a02ac5de5a1 100644
--- a/code/game/objects/random/misc.dm
+++ b/code/game/objects/random/misc.dm
@@ -1132,5 +1132,13 @@
prob(8);/obj/item/capture_crystal/random,
prob(10);/obj/item/weapon/bluespace_harpoon,
prob(10);/obj/item/weapon/bluespace_crystal,
- prob(1);/obj/item/clothing/glasses/graviton
+ prob(1);/obj/item/clothing/glasses/graviton,
+ prob(10);/obj/item/weapon/cracker,
+ prob(1);/obj/item/weapon/cracker/shrinking,
+ prob(1);/obj/item/weapon/cracker/growing,
+ prob(1);/obj/item/weapon/cracker/invisibility,
+ prob(1);/obj/item/weapon/cracker/drugged,
+ prob(1);/obj/item/weapon/cracker/knockover,
+ prob(1);/obj/item/weapon/cracker/vore,
+ prob(1);/obj/item/weapon/cracker/money
)
\ No newline at end of file
diff --git a/code/game/objects/structures/morgue.dm b/code/game/objects/structures/morgue.dm
index d4b679e742a..fb18083562e 100644
--- a/code/game/objects/structures/morgue.dm
+++ b/code/game/objects/structures/morgue.dm
@@ -227,8 +227,7 @@ GLOBAL_LIST_BOILERPLATE(all_crematoriums, /obj/structure/morgue/crematorium)
if (!( A.anchored ))
A.forceMove(src)
playsound(src, 'sound/items/Deconstruct.ogg', 50, 1)
- //src.connected = null
- qdel(src.connected)
+ qdel_null(connected)
else if (src.locked == 0)
playsound(src, 'sound/items/Deconstruct.ogg', 50, 1)
src.connected = new /obj/structure/m_tray/c_tray( src.loc )
@@ -242,8 +241,7 @@ GLOBAL_LIST_BOILERPLATE(all_crematoriums, /obj/structure/morgue/crematorium)
A.forceMove(src.connected.loc)
src.connected.icon_state = "cremat"
else
- //src.connected = null
- qdel(src.connected)
+ qdel_null(connected)
src.add_fingerprint(user)
update()
diff --git a/code/game/objects/structures/props/rocks.dm b/code/game/objects/structures/props/rocks.dm
index 220bc832150..253a36bda55 100644
--- a/code/game/objects/structures/props/rocks.dm
+++ b/code/game/objects/structures/props/rocks.dm
@@ -80,3 +80,28 @@
/obj/structure/prop/rock/ice/small/alt
icon_state = "icesmall2"
+
+// glamour things for whitespace
+/obj/structure/prop/rock/glamour
+ name = "glamour"
+ icon = 'icons/obj/glamour.dmi'
+ icon_state = "pillar_1"
+
+/obj/structure/prop/rock/glamour/double
+ icon_state = "pillar_3"
+
+/obj/structure/prop/rock/glamour/triple
+ icon_state = "pillar_2"
+
+/obj/structure/prop/rock/glamour/small
+ icon_state = "gems_1"
+ density = FALSE
+
+/obj/structure/prop/rock/glamour/small/alt1
+ icon_state = "gems_2"
+
+/obj/structure/prop/rock/glamour/small/alt2
+ icon_state = "gems_3"
+
+/obj/structure/prop/rock/glamour/small/alt3
+ icon_state = "gems_4"
\ No newline at end of file
diff --git a/code/game/objects/structures/trash_pile_vr.dm b/code/game/objects/structures/trash_pile_vr.dm
index 4fcfc0619a7..535b0198a86 100644
--- a/code/game/objects/structures/trash_pile_vr.dm
+++ b/code/game/objects/structures/trash_pile_vr.dm
@@ -281,6 +281,7 @@
prob(4);/obj/item/weapon/gun/energy/sizegun,
prob(4);/obj/item/device/slow_sizegun,
prob(4);/obj/item/clothing/accessory/collar/shock/bluespace,
+ prob(3);/obj/item/weapon/cracker,
prob(3);/obj/item/weapon/material/butterfly,
prob(3);/obj/item/weapon/material/butterfly/switchblade,
prob(3);/obj/item/clothing/gloves/knuckledusters,
@@ -313,7 +314,8 @@
prob(1);/obj/item/device/perfect_tele/one_beacon,
prob(1);/obj/item/clothing/gloves/bluespace,
prob(1);/obj/item/weapon/gun/energy/mouseray,
- prob(1);/obj/item/clothing/accessory/collar/shock/bluespace/modified)
+ prob(1);/obj/item/clothing/accessory/collar/shock/bluespace/modified,
+ prob(1);/obj/item/weapon/gun/energy/sizegun/backfire)
var/obj/item/I = new path()
return I
diff --git a/code/game/turfs/simulated/water.dm b/code/game/turfs/simulated/water.dm
index ae136ec0e62..fc6f5b261e4 100644
--- a/code/game/turfs/simulated/water.dm
+++ b/code/game/turfs/simulated/water.dm
@@ -263,3 +263,14 @@ var/list/shoreline_icon_cache = list()
to_chat(L, "You get drenched in blood from entering \the [src]!")
AM.water_act(5)
..()
+
+/turf/simulated/floor/water/glamour
+ name = "glamour"
+ desc = "A body of glamour. It seems shallow enough to walk through, if needed."
+ icon = 'icons/turf/flooring/glamour.dmi'
+ icon_state = "water"
+ water_icon = 'icons/turf/flooring/glamour.dmi'
+ water_state = "water"
+ under_state = "glamour"
+ reagent_type = "water"
+
diff --git a/code/modules/client/preference_setup/loadout/loadout.dm b/code/modules/client/preference_setup/loadout/loadout.dm
index 61603361f59..21e6602863e 100644
--- a/code/modules/client/preference_setup/loadout/loadout.dm
+++ b/code/modules/client/preference_setup/loadout/loadout.dm
@@ -155,17 +155,17 @@ var/list/gear_datums = list()
. += "
"
for(var/datum/gear_tweak/tweak in G.gear_tweaks)
@@ -252,6 +252,7 @@ var/list/gear_datums = list()
var/cost = 1 //Number of points used. Items in general cost 1 point, storage/armor/gloves/special use costs 2 points.
var/slot //Slot to equip to.
var/list/allowed_roles //Roles that can spawn with this item.
+ var/show_roles = TRUE //Show the role restrictions on this item?
var/whitelisted //Term to check the whitelist for..
var/sort_category = "General"
var/list/gear_tweaks = list() //List of datums which will alter the item after it has been spawned.
diff --git a/code/modules/client/preference_setup/loadout/loadout_accessories.dm b/code/modules/client/preference_setup/loadout/loadout_accessories.dm
index a971c1f17a7..762cca238e1 100644
--- a/code/modules/client/preference_setup/loadout/loadout_accessories.dm
+++ b/code/modules/client/preference_setup/loadout/loadout_accessories.dm
@@ -75,7 +75,7 @@
gear_tweaks += new/datum/gear_tweak/path(sortAssoc(wcoats))
/datum/gear/accessory/holster
- display_name = "holster selection (Security, CD, HoP)"
+ display_name = "holster selection"
path = /obj/item/clothing/accessory/holster
allowed_roles = list("Site Manager", "Head of Personnel", "Security Officer", "Warden", "Head of Security","Detective")
diff --git a/code/modules/client/preference_setup/loadout/loadout_accessories_vr.dm b/code/modules/client/preference_setup/loadout/loadout_accessories_vr.dm
index 71245acf0ef..beb7ccdeada 100644
--- a/code/modules/client/preference_setup/loadout/loadout_accessories_vr.dm
+++ b/code/modules/client/preference_setup/loadout/loadout_accessories_vr.dm
@@ -57,35 +57,35 @@
path = /obj/item/clothing/accessory/collar/holo/indigestible
/datum/gear/accessory/holster
- display_name = "holster selection (Security, SM, HoP)"
+ display_name = "holster selection"
allowed_roles = list("Site Manager", "Head of Personnel", "Security Officer", "Warden", "Head of Security","Detective","Talon Captain","Talon Guard")
/datum/gear/accessory/brown_vest
- display_name = "webbing, brown (Eng, Sec, Med, Miner)"
+ display_name = "webbing, brown"
allowed_roles = list("Engineer","Atmospheric Technician","Chief Engineer","Security Officer","Detective","Head of Security","Warden","Paramedic","Chief Medical Officer","Medical Doctor","Chemist","Shaft Miner","Talon Captain","Talon Doctor","Talon Engineer","Talon Guard", "Talon Miner")
/datum/gear/accessory/black_vest
- display_name = "webbing, black (Eng, Sec, Med, Miner)"
+ display_name = "webbing, black"
allowed_roles = list("Engineer","Atmospheric Technician","Chief Engineer","Security Officer","Detective","Head of Security","Warden","Paramedic","Chief Medical Officer","Medical Doctor","Chemist","Shaft Miner","Talon Captain","Talon Doctor","Talon Engineer","Talon Guard", "Talon Miner")
/datum/gear/accessory/white_vest
- display_name = "webbing, white (Medical)"
+ display_name = "webbing, white"
allowed_roles = list("Paramedic","Chief Medical Officer","Medical Doctor","Chemist","Talon Doctor")
/datum/gear/accessory/brown_drop_pouches
- display_name = "drop pouches, brown (Eng, Sec, Med, Miner)"
+ display_name = "drop pouches, brown"
allowed_roles = list("Engineer","Atmospheric Technician","Chief Engineer","Security Officer","Detective","Head of Security","Warden","Paramedic","Chief Medical Officer","Medical Doctor","Chemist","Shaft Miner","Talon Captain","Talon Doctor","Talon Engineer","Talon Guard", "Talon Miner")
/datum/gear/accessory/black_drop_pouches
- display_name = "drop pouches, black (Eng, Sec, Med, Miner)"
+ display_name = "drop pouches, black"
allowed_roles = list("Engineer","Atmospheric Technician","Chief Engineer","Security Officer","Detective","Head of Security","Warden","Paramedic","Chief Medical Officer","Medical Doctor","Chemist","Shaft Miner","Talon Captain","Talon Doctor","Talon Engineer","Talon Guard", "Talon Miner")
/datum/gear/accessory/white_drop_pouches
- display_name = "drop pouches, white (Medical)"
+ display_name = "drop pouches, white"
allowed_roles = list("Paramedic","Chief Medical Officer","Medical Doctor","Chemist","Talon Doctor")
/datum/gear/accessory/bluespace
- display_name = "bluespace badge (Eng, Sec, Med, Miner, Pilot)"
+ display_name = "bluespace badge"
path = /obj/item/clothing/accessory/storage/bluespace
allowed_roles = list("Engineer","Atmospheric Technician","Chief Engineer","Security Officer","Detective","Head of Security","Warden","Paramedic","Chief Medical Officer","Medical Doctor","Chemist","Shaft Miner","Talon Captain","Talon Doctor","Talon Engineer","Talon Guard","Talon Miner","Pilot")
cost = 2
diff --git a/code/modules/client/preference_setup/loadout/loadout_eyes.dm b/code/modules/client/preference_setup/loadout/loadout_eyes.dm
index 101ba17368b..6e9f81a9d5b 100644
--- a/code/modules/client/preference_setup/loadout/loadout_eyes.dm
+++ b/code/modules/client/preference_setup/loadout/loadout_eyes.dm
@@ -68,76 +68,76 @@
path = /obj/item/clothing/glasses/science
/datum/gear/eyes/security
- display_name = "Security HUD (Security)"
+ display_name = "Security HUD"
path = /obj/item/clothing/glasses/hud/security
allowed_roles = list("Security Officer","Head of Security","Warden", "Detective")
/datum/gear/eyes/security/prescriptionsec
- display_name = "Security HUD, prescription (Security)"
+ display_name = "Security HUD, prescription"
path = /obj/item/clothing/glasses/hud/security/prescription
/datum/gear/eyes/security/sunglasshud
- display_name = "Security HUD, sunglasses (Security)"
+ display_name = "Security HUD, sunglasses"
path = /obj/item/clothing/glasses/sunglasses/sechud
/datum/gear/eyes/security/aviator
- display_name = "Security HUD Aviators (Security)"
+ display_name = "Security HUD Aviators"
path = /obj/item/clothing/glasses/sunglasses/sechud/aviator
/datum/gear/eyes/security/aviator/prescription
- display_name = "Security HUD Aviators, prescription (Security)"
+ display_name = "Security HUD Aviators, prescription"
path = /obj/item/clothing/glasses/sunglasses/sechud/aviator/prescription
/datum/gear/eyes/medical
- display_name = "Medical HUD (Medical)"
+ display_name = "Medical HUD"
path = /obj/item/clothing/glasses/hud/health
allowed_roles = list("Medical Doctor","Chief Medical Officer","Chemist","Paramedic","Geneticist", "Psychiatrist", "Search and Rescue")
/datum/gear/eyes/medical/prescriptionmed
- display_name = "Medical HUD, prescription (Medical)"
+ display_name = "Medical HUD, prescription"
path = /obj/item/clothing/glasses/hud/health/prescription
/datum/gear/eyes/medical/aviator
- display_name = "Medical HUD Aviators (Medical)"
+ display_name = "Medical HUD Aviators"
path = /obj/item/clothing/glasses/hud/health/aviator
/datum/gear/eyes/medical/aviator/prescription
- display_name = "Medical HUD Aviators, prescription (Medical)"
+ display_name = "Medical HUD Aviators, prescription"
path = /obj/item/clothing/glasses/hud/health/aviator/prescription
/datum/gear/eyes/janitor
- display_name = "Contaminant HUD (Janitor)"
+ display_name = "Contaminant HUD"
path = /obj/item/clothing/glasses/hud/janitor
allowed_roles = list("Janitor")
/datum/gear/eyes/janitor/prescriptionjan
- display_name = "Contaminant HUD, prescription (Janitor)"
+ display_name = "Contaminant HUD, prescription"
path = /obj/item/clothing/glasses/hud/janitor/prescription
/datum/gear/eyes/meson
- display_name = "Optical Meson Scanners (Engineering, Science, Mining)"
+ display_name = "Optical Meson Scanners"
path = /obj/item/clothing/glasses/meson
allowed_roles = list("Engineer","Chief Engineer","Atmospheric Technician", "Scientist", "Research Director", "Shaft Miner")
/datum/gear/eyes/meson/prescription
- display_name = "Optical Meson Scanners, prescription (Engineering, Science, Mining)"
+ display_name = "Optical Meson Scanners, prescription"
path = /obj/item/clothing/glasses/meson/prescription
/datum/gear/eyes/material
- display_name = "Optical Material Scanners (Mining)"
+ display_name = "Optical Material Scanners"
path = /obj/item/clothing/glasses/material
allowed_roles = list("Shaft Miner","Quartermaster")
/datum/gear/eyes/material/prescription
- display_name = "Prescription Optical Material Scanners (Mining)"
+ display_name = "Prescription Optical Material Scanners"
path = /obj/item/clothing/glasses/material/prescription
/datum/gear/eyes/meson/aviator
- display_name = "Optical Meson Aviators, (Engineering, Science, Mining)"
+ display_name = "Optical Meson Aviators"
path = /obj/item/clothing/glasses/meson/aviator
/datum/gear/eyes/meson/aviator/prescription
- display_name = "Optical Meson Aviators, prescription (Engineering, Science, Mining)"
+ display_name = "Optical Meson Aviators, prescription"
path = /obj/item/clothing/glasses/meson/aviator/prescription
/datum/gear/eyes/glasses/fakesun
@@ -149,16 +149,16 @@
path = /obj/item/clothing/glasses/fakesunglasses/aviator
/datum/gear/eyes/sun
- display_name = "Sunglasses (Security/Command)"
+ display_name = "sunglasses (Security/Command)"
path = /obj/item/clothing/glasses/sunglasses
allowed_roles = list("Security Officer","Head of Security","Warden","Site Manager","Head of Personnel","Quartermaster","Internal Affairs Agent","Detective")
/datum/gear/eyes/sun/shades
- display_name = "Sunglasses, fat (Security/Command)"
+ display_name = "sunglasses, fat (Security/Command)"
path = /obj/item/clothing/glasses/sunglasses/big
/datum/gear/eyes/sun/aviators
- display_name = "Sunglasses, aviators (Security/Command)"
+ display_name = "sunglasses, aviators (Security/Command)"
path = /obj/item/clothing/glasses/sunglasses/aviator
/datum/gear/eyes/sun/prescriptionsun
@@ -170,17 +170,17 @@
path = /obj/item/clothing/glasses/circuitry
/datum/gear/eyes/glasses/rimless
- display_name = "Glasses, rimless"
+ display_name = "glasses, rimless"
path = /obj/item/clothing/glasses/rimless
/datum/gear/eyes/glasses/prescriptionrimless
- display_name = "Glasses, prescription rimless"
+ display_name = "glasses, prescription rimless"
path = /obj/item/clothing/glasses/regular/rimless
/datum/gear/eyes/glasses/thin
- display_name = "Glasses, thin frame"
+ display_name = "glasses, thin frame"
path = /obj/item/clothing/glasses/thin
/datum/gear/eyes/glasses/prescriptionthin
- display_name = "Glasses, prescription thin frame"
+ display_name = "glasses, prescription thin frame"
path = /obj/item/clothing/glasses/regular/thin
diff --git a/code/modules/client/preference_setup/loadout/loadout_eyes_vr.dm b/code/modules/client/preference_setup/loadout/loadout_eyes_vr.dm
index bc545291def..6bc9e9b1377 100644
--- a/code/modules/client/preference_setup/loadout/loadout_eyes_vr.dm
+++ b/code/modules/client/preference_setup/loadout/loadout_eyes_vr.dm
@@ -21,27 +21,27 @@
path = /obj/item/clothing/glasses/omnihud/prescription
/datum/gear/eyes/arglasses/sec
- display_name = "AR-S glasses (Sec)"
+ display_name = "AR-Security glasses"
path = /obj/item/clothing/glasses/omnihud/sec
allowed_roles = list("Security Officer","Head of Security","Warden","Detective")
/datum/gear/eyes/arglasses/sci
- display_name = "AR-R glasses (Sci)"
+ display_name = "AR-Research glasses"
path = /obj/item/clothing/glasses/omnihud/rnd
allowed_roles = list("Research Director","Scientist","Xenobiologist","Xenobotanist","Roboticist")
/datum/gear/eyes/arglasses/eng
- display_name = "AR-E glasses (Eng)"
+ display_name = "AR-Engineering glasses"
path = /obj/item/clothing/glasses/omnihud/eng
allowed_roles = list("Engineer","Chief Engineer","Atmospheric Technician")
/datum/gear/eyes/arglasses/med
- display_name = "AR-M glasses (Medical)"
+ display_name = "AR-Medical glasses"
path = /obj/item/clothing/glasses/omnihud/med
allowed_roles = list("Medical Doctor","Chief Medical Officer","Chemist","Paramedic","Geneticist", "Psychiatrist")
/datum/gear/eyes/arglasses/all
- display_name = "AR-B glasses (SM, HoP)"
+ display_name = "AR-Command glasses"
path = /obj/item/clothing/glasses/omnihud/all
cost = 2
allowed_roles = list("Site Manager","Head of Personnel")
@@ -55,7 +55,7 @@
path = /obj/item/clothing/glasses/fluff/science_proper
/datum/gear/eyes/meson/retinal
- display_name = "retinal projector, meson (Eng, Sci, Explo)"
+ display_name = "retinal projector, meson"
path = /obj/item/clothing/glasses/omnihud/eng/meson
/datum/gear/eyes/security/secpatch
@@ -67,7 +67,7 @@
path = /obj/item/clothing/glasses/hud/security/eyepatch2
/datum/gear/eyes/security/tac_sec_visor
- display_name = "Tactical AR visor (Security)"
+ display_name = "Tactical AR visor"
path = /obj/item/clothing/glasses/sunglasses/sechud/tactical_sec_vis
/datum/gear/eyes/medical/medpatch
diff --git a/code/modules/client/preference_setup/loadout/loadout_fluffitems_vr.dm b/code/modules/client/preference_setup/loadout/loadout_fluffitems_vr.dm
index 1720d503153..1cc15975e27 100644
--- a/code/modules/client/preference_setup/loadout/loadout_fluffitems_vr.dm
+++ b/code/modules/client/preference_setup/loadout/loadout_fluffitems_vr.dm
@@ -10,16 +10,16 @@
ckeywhitelist = list("This entry should never be choosable with this variable set.") //If it does, then that means somebody fucked up the whitelist system pretty hard
character_name = list("This entry should never be choosable with this variable set.")
cost = 0
+
/*
/datum/gear/fluff/testhorn
path = /obj/item/weapon/bikehorn
display_name = "Airhorn - Example Item"
description = "An example item that you probably shouldn't see!"
- ckeywhitelist = list("mewchild")
+ ckeywhitelist = list("your_ckey_here")
allowed_roles = list("Engineer")
*/
-
/datum/gear/fluff/collar //Use this as a base path for collars if you'd like to set tags in loadout. Make sure you don't use apostrophes in the display name or this breaks!
slot = slot_tie
diff --git a/code/modules/client/preference_setup/loadout/loadout_gloves.dm b/code/modules/client/preference_setup/loadout/loadout_gloves.dm
index 6530edf3215..35711424481 100644
--- a/code/modules/client/preference_setup/loadout/loadout_gloves.dm
+++ b/code/modules/client/preference_setup/loadout/loadout_gloves.dm
@@ -52,7 +52,7 @@
cost = 3
/datum/gear/gloves/forensic
- display_name = "gloves, forensic (Detective)"
+ display_name = "gloves, forensic"
path = /obj/item/clothing/gloves/forensic
allowed_roles = list("Detective")
diff --git a/code/modules/client/preference_setup/loadout/loadout_suit.dm b/code/modules/client/preference_setup/loadout/loadout_suit.dm
index a2bde3aaa93..3d5793acaee 100644
--- a/code/modules/client/preference_setup/loadout/loadout_suit.dm
+++ b/code/modules/client/preference_setup/loadout/loadout_suit.dm
@@ -144,6 +144,7 @@
display_name = "labcoat selection, cmo"
path = /obj/item/clothing/suit/storage/toggle/labcoat/cmo
allowed_roles = list("Chief Medical Officer")
+ show_roles = FALSE
/datum/gear/suit/labcoat_cmo/New()
..()
@@ -163,6 +164,7 @@
display_name = "labcoat, research director"
path = /obj/item/clothing/suit/storage/toggle/labcoat/rd
allowed_roles = list("Research Director")
+ show_roles = FALSE
/datum/gear/suit/miscellaneous/labcoat
display_name = "plague doctor's coat"
@@ -254,42 +256,49 @@
display_name = "cloak, head of security"
path = /obj/item/clothing/accessory/poncho/roles/cloak/hos
allowed_roles = list("Head of Security")
+ show_roles = FALSE
cost = 1
/datum/gear/suit/roles/cloak_cmo
display_name = "cloak, chief medical officer"
path = /obj/item/clothing/accessory/poncho/roles/cloak/cmo
allowed_roles = list("Chief Medical Officer")
+ show_roles = FALSE
cost = 1
/datum/gear/suit/roles/cloak_ce
display_name = "cloak, chief engineer"
path = /obj/item/clothing/accessory/poncho/roles/cloak/ce
allowed_roles = list("Chief Engineer")
+ show_roles = FALSE
cost = 1
/datum/gear/suit/roles/cloak_rd
display_name = "cloak, research director"
path = /obj/item/clothing/accessory/poncho/roles/cloak/rd
allowed_roles = list("Research Director")
+ show_roles = FALSE
cost = 1
/datum/gear/suit/roles/cloak_qm
display_name = "cloak, quartermaster"
path = /obj/item/clothing/accessory/poncho/roles/cloak/qm
allowed_roles = list("Quartermaster")
+ show_roles = FALSE
cost = 1
/datum/gear/suit/roles/cloak_captain
display_name = "cloak, site manager"
path = /obj/item/clothing/accessory/poncho/roles/cloak/captain
allowed_roles = list("Site Manager")
+ show_roles = FALSE
cost = 1
/datum/gear/suit/roles/cloak_hop
display_name = "cloak, head of personnel"
path = /obj/item/clothing/accessory/poncho/roles/cloak/hop
allowed_roles = list("Head of Personnel")
+ show_roles = FALSE
cost = 1
/datum/gear/suit/cloak_custom //A colorable cloak
@@ -344,7 +353,7 @@
path = /obj/item/clothing/suit/suspenders
/datum/gear/suit/forensics
- display_name = "forensics uniform selection (Detective)"
+ display_name = "forensics uniform selection"
path = /obj/item/clothing/suit/storage/forensics/red/long
allowed_roles = list("Detective")
@@ -362,11 +371,13 @@
display_name = "coat, quartermaster"
path = /obj/item/clothing/suit/storage/qm
allowed_roles = list("Quartermaster")
+ show_roles = FALSE
/datum/gear/suit/cargo_coat
display_name = "coat, cargo tech"
path = /obj/item/clothing/suit/storage/cargo
allowed_roles = list("Quartermaster","Shaft Miner","Cargo Technician","Head of Personnel")
+ show_roles = FALSE
// winter coats go here
/datum/gear/suit/wintercoat
@@ -377,116 +388,139 @@
display_name = "winter coat, site manager"
path = /obj/item/clothing/suit/storage/hooded/wintercoat/captain
allowed_roles = list("Site Manager")
+ show_roles = FALSE
/datum/gear/suit/wintercoat/hop
display_name = "winter coat, head of personnel"
path = /obj/item/clothing/suit/storage/hooded/wintercoat/hop
allowed_roles = list("Head of Personnel")
+ show_roles = FALSE
/datum/gear/suit/wintercoat/security
display_name = "winter coat, security"
path = /obj/item/clothing/suit/storage/hooded/wintercoat/security
allowed_roles = list("Security Officer", "Head of Security", "Warden", "Detective")
+ show_roles = FALSE
/datum/gear/suit/wintercoat/security/hos
display_name = "winter coat, head of security"
path = /obj/item/clothing/suit/storage/hooded/wintercoat/security/hos
allowed_roles = list("Head of Security")
+ show_roles = FALSE
/datum/gear/suit/wintercoat/medical
display_name = "winter coat, medical"
path = /obj/item/clothing/suit/storage/hooded/wintercoat/medical
allowed_roles = list("Medical Doctor","Chief Medical Officer","Chemist","Paramedic","Geneticist", "Psychiatrist")
+ show_roles = FALSE
/datum/gear/suit/wintercoat/medical/alt
display_name = "winter coat, medical alt"
path = /obj/item/clothing/suit/storage/hooded/wintercoat/medical/alt
allowed_roles = list("Medical Doctor","Chief Medical Officer","Chemist","Paramedic","Geneticist", "Psychiatrist")
+ show_roles = FALSE
/datum/gear/suit/wintercoat/medical/viro
display_name = "winter coat, virologist"
path = /obj/item/clothing/suit/storage/hooded/wintercoat/medical/viro
allowed_roles = list("Medical Doctor","Chief Medical Officer")
+ show_roles = FALSE
/datum/gear/suit/wintercoat/medical/para
display_name = "winter coat, paramedic"
path = /obj/item/clothing/suit/storage/hooded/wintercoat/medical/para
allowed_roles = list("Medical Doctor","Chief Medical Officer","Paramedic")
+ show_roles = FALSE
/datum/gear/suit/wintercoat/medical/chemist
display_name = "winter coat, chemist"
path = /obj/item/clothing/suit/storage/hooded/wintercoat/medical/chemist
allowed_roles = list("Chief Medical Officer","Chemist")
+ show_roles = FALSE
/datum/gear/suit/wintercoat/medical/cmo
display_name = "winter coat, chief medical officer"
path = /obj/item/clothing/suit/storage/hooded/wintercoat/medical/cmo
allowed_roles = list("Chief Medical Officer")
+ show_roles = FALSE
/datum/gear/suit/wintercoat/medical/sar
display_name = "winter coat, search and rescue"
path = /obj/item/clothing/suit/storage/hooded/wintercoat/medical/sar
allowed_roles = list("Chief Medical Officer","Paramedic")
+ show_roles = FALSE
/datum/gear/suit/wintercoat/science
display_name = "winter coat, science"
path = /obj/item/clothing/suit/storage/hooded/wintercoat/science
allowed_roles = list("Research Director","Scientist", "Roboticist", "Xenobiologist", "Xenobotanist")
+ show_roles = FALSE
/datum/gear/suit/wintercoat/science/robotics
display_name = "winter coat, robotics"
path = /obj/item/clothing/suit/storage/hooded/wintercoat/science/robotics
allowed_roles = list("Research Director", "Roboticist")
+ show_roles = FALSE
/datum/gear/suit/wintercoat/science/rd
display_name = "winter coat, research director"
path = /obj/item/clothing/suit/storage/hooded/wintercoat/science/rd
allowed_roles = list("Research Director")
+ show_roles = FALSE
/datum/gear/suit/wintercoat/engineering
display_name = "winter coat, engineering"
path = /obj/item/clothing/suit/storage/hooded/wintercoat/engineering
allowed_roles = list("Chief Engineer","Atmospheric Technician", "Engineer")
+ show_roles = FALSE
/datum/gear/suit/wintercoat/engineering/atmos
display_name = "winter coat, atmospherics"
path = /obj/item/clothing/suit/storage/hooded/wintercoat/engineering/atmos
allowed_roles = list("Chief Engineer", "Atmospheric Technician")
+ show_roles = FALSE
/datum/gear/suit/wintercoat/engineering/ce
display_name = "winter coat, chief engineer"
path = /obj/item/clothing/suit/storage/hooded/wintercoat/engineering/ce
allowed_roles = list("Chief Engineer")
+ show_roles = FALSE
/datum/gear/suit/wintercoat/hydro
display_name = "winter coat, hydroponics"
path = /obj/item/clothing/suit/storage/hooded/wintercoat/hydro
allowed_roles = list("Botanist", "Xenobotanist")
+ show_roles = FALSE
/datum/gear/suit/wintercoat/cargo
display_name = "winter coat, cargo"
path = /obj/item/clothing/suit/storage/hooded/wintercoat/cargo
allowed_roles = list("Quartermaster","Cargo Technician")
+ show_roles = FALSE
/datum/gear/suit/wintercoat/miner
display_name = "winter coat, mining"
path = /obj/item/clothing/suit/storage/hooded/wintercoat/miner
allowed_roles = list("Shaft Miner")
+ show_roles = FALSE
/datum/gear/suit/wintercoat/cargo/qm
display_name = "winter coat, quartermaster"
path = /obj/item/clothing/suit/storage/hooded/wintercoat/cargo/qm
allowed_roles = list("Quartermaster")
+ show_roles = FALSE
/datum/gear/suit/wintercoat/bar
display_name = "winter coat, bartender"
path = /obj/item/clothing/suit/storage/hooded/wintercoat/bar
allowed_roles = list("Bartender")
+ show_roles = FALSE
/datum/gear/suit/wintercoat/janitor
display_name = "winter coat, janitor"
path = /obj/item/clothing/suit/storage/hooded/wintercoat/janitor
allowed_roles = list("Janitor")
+ show_roles = FALSE
/datum/gear/suit/wintercoat/aformal
display_name = "winter coat, assistant formal"
@@ -613,31 +647,37 @@
display_name = "snowsuit, command"
path = /obj/item/clothing/suit/storage/snowsuit/command
allowed_roles = list("Site Manager","Research Director","Head of Personnel","Head of Security","Chief Engineer","Command Secretary")
+ show_roles = FALSE
/datum/gear/suit/snowsuit/security
display_name = "snowsuit, security"
path = /obj/item/clothing/suit/storage/snowsuit/security
allowed_roles = list("Security Officer", "Head of Security", "Warden", "Detective")
+ show_roles = FALSE
/datum/gear/suit/snowsuit/medical
display_name = "snowsuit, medical"
path = /obj/item/clothing/suit/storage/snowsuit/medical
allowed_roles = list("Medical Doctor","Chief Medical Officer","Chemist","Paramedic","Geneticist", "Psychiatrist", "Search and Rescue")
+ show_roles = FALSE
/datum/gear/suit/snowsuit/science
display_name = "snowsuit, science"
path = /obj/item/clothing/suit/storage/snowsuit/science
allowed_roles = list("Research Director","Scientist", "Roboticist", "Xenobiologist")
+ show_roles = FALSE
/datum/gear/suit/snowsuit/engineering
display_name = "snowsuit, engineering"
path = /obj/item/clothing/suit/storage/snowsuit/engineering
allowed_roles = list("Chief Engineer","Atmospheric Technician", "Engineer")
+ show_roles = FALSE
/datum/gear/suit/snowsuit/cargo
display_name = "snowsuit, supply"
path = /obj/item/clothing/suit/storage/snowsuit/cargo
allowed_roles = list("Quartermaster","Shaft Miner","Cargo Technician","Head of Personnel")
+ show_roles = FALSE
/datum/gear/suit/miscellaneous/cardigan
display_name = "cardigan, colorable"
@@ -651,6 +691,7 @@
display_name = "command dress jacket"
path = /obj/item/clothing/suit/storage/toggle/cmddressjacket
allowed_roles = list("Site Manager", "Head of Personnel", "Command Secretary")
+ show_roles = FALSE
/datum/gear/suit/miscellaneous/kimono
display_name = "traditional kimono, colorable"
diff --git a/code/modules/client/preference_setup/loadout/loadout_suit_vr.dm b/code/modules/client/preference_setup/loadout/loadout_suit_vr.dm
index 26f079d653f..74a2f3f08a7 100644
--- a/code/modules/client/preference_setup/loadout/loadout_suit_vr.dm
+++ b/code/modules/client/preference_setup/loadout/loadout_suit_vr.dm
@@ -55,7 +55,7 @@
//Detective alternative
/datum/gear/suit/detective_alt
- display_name = "sleek modern coat selection, detective"
+ display_name = "sleek modern coat selection"
path = /obj/item/clothing/suit/storage/det_trench/alt
allowed_roles = list("Head of Security", "Detective")
@@ -112,7 +112,7 @@ Talon winter coat
/datum/gear/suit/armor/combat/crusader_explo
display_name = "knight, explo"
path = /obj/item/clothing/suit/armor/combat/crusader_explo
- allowed_roles = list("Explorer","Pathfinder")
+ //allowed_roles = list("Explorer","Pathfinder")
/datum/gear/suit/armor/combat/crusader_explo/FM
display_name = "knight, Field Medic"
@@ -232,11 +232,13 @@ Talon winter coat
display_name = "boat cloak, site manager"
path = /obj/item/clothing/accessory/poncho/roles/cloak/boat/cap
allowed_roles = list("Site Manager")
+ show_roles = FALSE
/datum/gear/suit/roles/hopboatcloak
display_name = "boat cloak, head of personnel"
path = /obj/item/clothing/accessory/poncho/roles/cloak/boat/hop
allowed_roles = list("Head of Personnel")
+ show_roles = FALSE
/datum/gear/suit/roles/boatcloaks
display_name = "boat cloak selection"
@@ -268,11 +270,13 @@ Talon winter coat
display_name = "shroud, site manager"
path = /obj/item/clothing/accessory/poncho/roles/cloak/shroud/cap
allowed_roles = list("Site Manager")
+ show_roles = FALSE
/datum/gear/suit/roles/hopshroud
display_name = "shroud, head of personnel"
path = /obj/item/clothing/accessory/poncho/roles/cloak/shroud/hop
allowed_roles = list("Head of Personnel")
+ show_roles = FALSE
/datum/gear/suit/roles/shrouds
display_name = "shroud selection"
diff --git a/code/modules/client/preference_setup/loadout/loadout_uni_selector.dm b/code/modules/client/preference_setup/loadout/loadout_uni_selector.dm
index bcd32eb9c45..624ab0692fa 100644
--- a/code/modules/client/preference_setup/loadout/loadout_uni_selector.dm
+++ b/code/modules/client/preference_setup/loadout/loadout_uni_selector.dm
@@ -5,6 +5,7 @@
display_name = "DEPT - BLANK's Uniforms"
description = "Select from a range of outfits available to all BLANK personnel."
allowed_roles = list("")
+ show_roles = FALSE
path =
slot = slot_w_uniform
sort_category = "Uniforms"
@@ -23,6 +24,7 @@
display_name = "Command - Site Manager's Uniforms"
description = "Select from a range of outfits available to all Site Managers."
allowed_roles = list("Site Manager")
+ show_roles = FALSE
path = /obj/item/clothing/under/rank/neo_captain
sort_category = "Uniforms"
cost = 1
@@ -52,6 +54,7 @@
display_name = "Command - Head of Personnel's Uniforms"
description = "Select from a range of outfits available to all Heads of Personnel."
allowed_roles = list("Head of Personnel")
+ show_roles = FALSE
path = /obj/item/clothing/under/rank/neo_hop
sort_category = "Uniforms"
cost = 1
@@ -83,6 +86,7 @@
display_name = "Civilian - Pilot's Uniforms"
description = "Select from a range of outfits available to all Pilots."
allowed_roles = list("Pilot")
+ show_roles = FALSE
path = /obj/item/clothing/under/rank/neo_pilot
sort_category = "Uniforms"
cost = 1
@@ -101,6 +105,7 @@
display_name = "Civilian - Janitor's Uniforms"
description = "Select from a range of outfits available to all Janitorial personnel."
allowed_roles = list("Janitor")
+ show_roles = FALSE
path = /obj/item/clothing/under/rank/neo_janitor
sort_category = "Uniforms"
cost = 1
@@ -184,6 +189,7 @@
display_name = "Security - Basic Uniforms"
description = "Select from a range of outfits available to all Security personnel."
allowed_roles = list("Head of Security", "Warden", "Detective", "Security Officer")
+ show_roles = FALSE
path = /obj/item/clothing/under/rank/security/corp
sort_category = "Uniforms"
cost = 1
@@ -229,6 +235,7 @@
display_name = "Security - Warden's Uniforms"
description = "Select from a range of outfits available to Wardens."
allowed_roles = list("Head of Security","Warden")
+ show_roles = FALSE
path = /obj/item/clothing/under/rank/warden/corp
sort_category = "Uniforms"
cost = 1
@@ -250,6 +257,7 @@
display_name = "Security - Detective's Uniforms"
description = "Select from a range of outfits available to all Detectives."
allowed_roles = list("Head of Security","Detective")
+ show_roles = FALSE
path = /obj/item/clothing/under/det/corporate
sort_category = "Uniforms"
cost = 1
@@ -266,6 +274,7 @@
display_name = "Security - Head's Uniforms"
description = "Select from a range of outfits available to all Heads of Security."
allowed_roles = list("Head of Security")
+ show_roles = FALSE
path = /obj/item/clothing/under/rank/head_of_security/corp
sort_category = "Uniforms"
cost = 1
@@ -305,6 +314,7 @@
display_name = "Cargo - Quartermaster's Uniforms"
description = "Select from a range of outfits available to all Quartermasters."
allowed_roles = list("Quartermaster")
+ show_roles = FALSE
path = /obj/item/clothing/under/rank/cargo/jeans
sort_category = "Uniforms"
cost = 1
@@ -336,6 +346,7 @@
display_name = "Cargo - Basic Uniforms"
description = "Select from a range of outfits available to all Cargo personnel."
allowed_roles = list("Cargo Technician","Shaft Miner","Quartermaster")
+ show_roles = FALSE
path = /obj/item/clothing/under/rank/cargotech/jeans
sort_category = "Uniforms"
cost = 1
@@ -364,6 +375,7 @@
display_name = "Cargo - Miner's Uniforms"
description = "Select from a range of outfits available to all Mining personnel."
allowed_roles = list("Shaft Miner","Quartermaster")
+ show_roles = FALSE
path = /obj/item/clothing/under/rank/neo_miner
sort_category = "Uniforms"
cost = 1
@@ -382,6 +394,7 @@
display_name = "Engineering - Chief Engineer's Uniforms"
description = "Select from a range of outfits available to all Chief Engineers."
allowed_roles = list("Chief Engineer")
+ show_roles = FALSE
path = /obj/item/clothing/under/rank/neo_chiefengi
sort_category = "Uniforms"
cost = 1
@@ -406,6 +419,7 @@
display_name = "Engineering - Basic Uniforms"
description = "Select from a range of outfits available to all Engineering personnel."
allowed_roles = list("Chief Engineer","Engineer","Atmospheric Technician")
+ show_roles = FALSE
path = /obj/item/clothing/under/rank/neo_engi
sort_category = "Uniforms"
cost = 1
@@ -433,6 +447,7 @@
display_name = "Engineering - Atmos Tech's Uniforms"
description = "Select from a range of outfits available to all Atmospherics Technicians."
allowed_roles = list("Chief Engineer","Atmospheric Technician")
+ show_roles = FALSE
path = /obj/item/clothing/under/rank/atmospheric_technician/skirt
sort_category = "Uniforms"
cost = 1
@@ -452,6 +467,7 @@
display_name = "Medical - Basic Uniforms"
description = "Select from a range of outfits available to all Medical personnel."
allowed_roles = list("Chief Medical Officer","Medical Doctor","Chemist","Psychiatrist","Paramedic")
+ show_roles = FALSE
path = /obj/item/clothing/under/rank/neo_med
sort_category = "Uniforms"
cost = 1
@@ -484,6 +500,7 @@
display_name = "Medical - Chemist's Uniforms"
description = "Select from a range of outfits available to all Chemists."
allowed_roles = list("Chief Medical Officer","Chemist")
+ show_roles = FALSE
path = /obj/item/clothing/under/rank/neo_chem
sort_category = "Uniforms"
cost = 1
@@ -503,6 +520,7 @@
display_name = "Medical - Paramedic's Uniforms"
description = "Select from a range of outfits available to all Paramedics."
allowed_roles = list("Medical Doctor","Chief Medical Officer","Paramedic")
+ show_roles = FALSE
path = /obj/item/clothing/under/rank/paramedunidark
sort_category = "Uniforms"
cost = 1
@@ -525,6 +543,7 @@
display_name = "Medical - Chief Medical Officer's Uniforms"
description = "Select from a range of outfits available to all Chief Medical Officers."
allowed_roles = list("Chief Medical Officer")
+ show_roles = FALSE
path = /obj/item/clothing/under/rank/neo_cmo
sort_category = "Uniforms"
cost = 2
@@ -553,6 +572,7 @@
display_name = "Science - Research Director's Uniforms"
description = "Select from a range of outfits available to all Research Directors."
allowed_roles = list("Research Director")
+ show_roles = FALSE
path = /obj/item/clothing/under/rank/neo_rd_suit
sort_category = "Uniforms"
cost = 1
@@ -578,6 +598,7 @@
display_name = "Science - Basic Uniforms"
description = "Select from a range of outfits available to all Science personnel."
allowed_roles = list("Scientist","Research Director","Roboticist","Xenobiologist","Xenobotanist")
+ show_roles = FALSE
path = /obj/item/clothing/under/rank/neo_science
sort_category = "Uniforms"
cost = 1
@@ -602,6 +623,7 @@
display_name = "Science - Roboticist's Uniforms"
description = "Select from a range of outfits available to all Roboticists."
allowed_roles = list("Research Director","Roboticist")
+ show_roles = FALSE
path = /obj/item/clothing/under/rank/neo_robo
sort_category = "Uniforms"
cost = 1
diff --git a/code/modules/client/preference_setup/loadout/loadout_xeno.dm b/code/modules/client/preference_setup/loadout/loadout_xeno.dm
index 3c46d70eeca..317aa09d602 100644
--- a/code/modules/client/preference_setup/loadout/loadout_xeno.dm
+++ b/code/modules/client/preference_setup/loadout/loadout_xeno.dm
@@ -554,16 +554,19 @@
display_name = "warden belted cloak (Teshari)"
path = /obj/item/clothing/suit/storage/teshari/beltcloak/jobs/wrdn
allowed_roles = list("Head of Security","Warden")
+ sort_category = "Xenowear"
/datum/gear/suit/dept/beltcloak/jani
display_name = "janitor belted cloak (Teshari)"
path = /obj/item/clothing/suit/storage/teshari/beltcloak/jobs/jani
allowed_roles = list("Janitor")
+ sort_category = "Xenowear"
/datum/gear/suit/dept/beltcloak/cmd
display_name = "command belted cloak (Teshari)"
path = /obj/item/clothing/suit/storage/teshari/beltcloak/jobs/command
allowed_roles = list("Site Manager","Head of Personnel","Head of Security","Chief Engineer","Chief Medical Officer","Research Director")
+ sort_category = "Xenowear"
/datum/gear/suit/cloak_hood
display_name = "hooded cloak selection (Teshari)"
diff --git a/code/modules/client/preference_setup/vore/09_misc.dm b/code/modules/client/preference_setup/vore/09_misc.dm
index bde733d4c88..963354baea4 100644
--- a/code/modules/client/preference_setup/vore/09_misc.dm
+++ b/code/modules/client/preference_setup/vore/09_misc.dm
@@ -11,6 +11,7 @@
S["capture_crystal"] >> pref.capture_crystal
S["auto_backup_implant"] >> pref.auto_backup_implant
S["borg_petting"] >> pref.borg_petting
+ S["stomach_vision"] >> pref.stomach_vision
/datum/category_item/player_setup_item/vore/misc/save_character(var/savefile/S)
S["show_in_directory"] << pref.show_in_directory
@@ -21,12 +22,21 @@
S["capture_crystal"] << pref.capture_crystal
S["auto_backup_implant"] << pref.auto_backup_implant
S["borg_petting"] << pref.borg_petting
+ S["stomach_vision"] << pref.stomach_vision
/datum/category_item/player_setup_item/vore/misc/copy_to_mob(var/mob/living/carbon/human/character)
if(pref.sensorpref > 5 || pref.sensorpref < 1)
pref.sensorpref = 5
character.sensorpref = pref.sensorpref
character.capture_crystal = pref.capture_crystal
+ //Vore Stomach Sprite Preference
+ character.stomach_vision = pref.stomach_vision
+ if((character && !istype(character,/mob/living/carbon/human/dummy)) && character.stomach_vision && !(VIS_CH_STOMACH in character.vis_enabled))
+ character.plane_holder.set_vis(VIS_CH_STOMACH,TRUE)
+ character.vis_enabled += VIS_CH_STOMACH
+ else if((character && !istype(character,/mob/living/carbon/human/dummy)) && !character.stomach_vision && (VIS_CH_STOMACH in character.vis_enabled))
+ character.plane_holder.set_vis(VIS_CH_STOMACH,FALSE)
+ character.vis_enabled -= VIS_CH_STOMACH
/datum/category_item/player_setup_item/vore/misc/sanitize_character()
pref.show_in_directory = sanitize_integer(pref.show_in_directory, 0, 1, initial(pref.show_in_directory))
@@ -36,6 +46,7 @@
pref.capture_crystal = sanitize_integer(pref.capture_crystal, 0, 1, initial(pref.capture_crystal))
pref.auto_backup_implant = sanitize_integer(pref.auto_backup_implant, 0, 1, initial(pref.auto_backup_implant))
pref.borg_petting = sanitize_integer(pref.borg_petting, 0, 1, initial(pref.borg_petting))
+ pref.stomach_vision = sanitize_integer(pref.stomach_vision, 0, 1, initial(pref.stomach_vision))
/datum/category_item/player_setup_item/vore/misc/content(var/mob/user)
. += " "
@@ -47,6 +58,7 @@
. += "Capture Crystal Preference: [pref.capture_crystal ? "Yes" : "No"] "
. += "Spawn With Backup Implant: [pref.auto_backup_implant ? "Yes" : "No"] "
. += "Allow petting as robot: [pref.borg_petting ? "Yes" : "No"] "
+ . += "Enable Stomach Sprites: [pref.stomach_vision ? "Yes" : "No"] "
/datum/category_item/player_setup_item/vore/misc/OnTopic(var/href, var/list/href_list, var/mob/user)
if(href_list["toggle_show_in_directory"])
@@ -84,4 +96,7 @@
else if(href_list["toggle_borg_petting"])
pref.borg_petting = pref.borg_petting ? 0 : 1;
return TOPIC_REFRESH
+ else if(href_list["toggle_stomach_vision"])
+ pref.stomach_vision = pref.stomach_vision ? 0 : 1;
+ return TOPIC_REFRESH
return ..();
diff --git a/code/modules/client/preferences_vr.dm b/code/modules/client/preferences_vr.dm
index ee3363dff59..f1cfc4413ab 100644
--- a/code/modules/client/preferences_vr.dm
+++ b/code/modules/client/preferences_vr.dm
@@ -7,6 +7,7 @@
var/capture_crystal = 1 //Whether or not someone is able to be caught with capture crystals
var/auto_backup_implant = FALSE //Whether someone starts with a backup implant or not.
var/borg_petting = TRUE //Whether someone can be petted as a borg or not.
+ var/stomach_vision = TRUE //Whether or not someone can view stomach sprites
var/job_talon_high = 0
var/job_talon_med = 0
diff --git a/code/modules/clothing/gloves/miscellaneous.dm b/code/modules/clothing/gloves/miscellaneous.dm
index ebf17c84b61..cd26d6da781 100644
--- a/code/modules/clothing/gloves/miscellaneous.dm
+++ b/code/modules/clothing/gloves/miscellaneous.dm
@@ -210,6 +210,13 @@
if(Adjacent(user) && src.loc == user)
. += "You are currently facing [dir2text(user.dir)]. The micro beacon is [gps.tracking ? "on" : "off"]."
var/TB = src.loc.loc
+ if(istype(TB, /turf/)) //no point returning light level if we're not on a turf (might be *in* someone!)
+ var/turf/TL = TB
+ var/light_level = TL.get_lumcount()
+ if(light_level)
+ . += "Light Level: [TL.get_lumcount()]"
+ else
+ . += "It's too dark to see the light level!"
if(istype(TB, /turf/simulated)) //no point returning atmospheric data from unsimulated tiles (they don't track pressure anyway, only temperature)
var/turf/simulated/T = TB
var/datum/gas_mixture/env = T.return_air()
diff --git a/code/modules/clothing/spacesuits/void/ert_vr.dm b/code/modules/clothing/spacesuits/void/ert_vr.dm
index 8b147a4dc33..9d98e3273cb 100644
--- a/code/modules/clothing/spacesuits/void/ert_vr.dm
+++ b/code/modules/clothing/spacesuits/void/ert_vr.dm
@@ -171,6 +171,7 @@
sprite_sheets = list(
SPECIES_HUMAN = 'icons/inventory/suit/mob_vr.dmi',
SPECIES_TAJ = 'icons/inventory/suit/mob_vr_tajaran.dmi',
+ SPECIES_LLEILL = 'icons/inventory/suit/mob_vr_tajaran.dmi',
SPECIES_SKRELL = 'icons/inventory/suit/mob_vr_skrell.dmi',
SPECIES_UNATHI = 'icons/inventory/suit/mob_vr_unathi.dmi',
SPECIES_XENOHYBRID = 'icons/inventory/suit/mob_vr_unathi.dmi',
@@ -201,13 +202,15 @@
SPECIES_NEVREAN = 'icons/inventory/suit/item_vr.dmi',
SPECIES_RAPALA = 'icons/inventory/suit/item_vr.dmi',
SPECIES_ALRAUNE = 'icons/inventory/suit/item_vr.dmi',
- SPECIES_ZADDAT = 'icons/inventory/suit/item_vr.dmi'
+ SPECIES_ZADDAT = 'icons/inventory/suit/item_vr.dmi',
+ SPECIES_LLEILL = 'icons/inventory/suit/item_vr.dmi'
)
/obj/item/clothing/head/helmet/space/void/responseteam
sprite_sheets = list(
SPECIES_HUMAN = 'icons/inventory/head/mob_vr.dmi',
SPECIES_TAJ = 'icons/inventory/head/mob_vr_tajaran.dmi',
+ SPECIES_LLEILL = 'icons/inventory/suit/mob_vr_tajaran.dmi',
SPECIES_SKRELL = 'icons/inventory/head/mob_vr_skrell.dmi',
SPECIES_UNATHI = 'icons/inventory/head/mob_vr_unathi.dmi',
SPECIES_XENOHYBRID = 'icons/inventory/head/mob_vr_unathi.dmi',
@@ -238,5 +241,6 @@
SPECIES_NEVREAN = 'icons/inventory/head/item_vr.dmi',
SPECIES_RAPALA = 'icons/inventory/head/item_vr.dmi',
SPECIES_ALRAUNE = 'icons/inventory/head/item_vr.dmi',
- SPECIES_ZADDAT = 'icons/inventory/head/item_vr.dmi'
+ SPECIES_ZADDAT = 'icons/inventory/head/item_vr.dmi',
+ SPECIES_LLEILL = 'icons/inventory/suit/item_vr.dmi'
)
diff --git a/code/modules/examine/descriptions/armor.dm b/code/modules/examine/descriptions/armor.dm
index b1cf96af6ed..e83c739473d 100644
--- a/code/modules/examine/descriptions/armor.dm
+++ b/code/modules/examine/descriptions/armor.dm
@@ -43,7 +43,6 @@
else
return "It's difficult to tell how much it'll influence your speed."
-
/obj/item/clothing/get_description_info()
var/armor_stats = description_info + "\
"
@@ -70,28 +69,18 @@
if(flags & AIRTIGHT)
armor_stats += "It is airtight. \n"
- if(min_pressure_protection == 0 && max_pressure_protection >= WARNING_HIGH_PRESSURE) //0 to 325
- armor_stats += "Wearing this will protect you from the vacuum of space and from high pressures. \n"
- else if(min_pressure_protection <= WARNING_LOW_PRESSURE && max_pressure_protection >= WARNING_HIGH_PRESSURE) //50 to 325
- armor_stats += "Wearing this will protect you from both low and high pressures, but not the vacuum of space. \n"
- else if(min_pressure_protection == 0)
- armor_stats += "Wearing this will protect you from the vacuum of space. \n"
- else if(min_pressure_protection <= WARNING_LOW_PRESSURE) //50 or below
- armor_stats += "Wearing this will protect you from low pressures, but not the vacuum of space. \n"
- else if(max_pressure_protection >= WARNING_HIGH_PRESSURE) //325 or higher
- armor_stats += "Wearing this will protect you from high pressures. \n"
+ if(min_pressure_protection != null)
+ armor_stats += "It is rated for pressures as low as [min_pressure_protection] kPa. \n"
+ if(max_pressure_protection)
+ armor_stats += "It is rated for pressures as high as [max_pressure_protection] kPa. \n"
if(flags & THICKMATERIAL) //stops syringes
armor_stats += "The material is exceptionally thick. \n"
- if(max_heat_protection_temperature >= FIRESUIT_MAX_HEAT_PROTECTION_TEMPERATURE && min_cold_protection_temperature <= SPACE_SUIT_MIN_COLD_PROTECTION_TEMPERATURE) //30000 or higher and as low as 2
- armor_stats += "It provides exceptional protection from extremely high and low temperatures alike. \n"
- else if(max_heat_protection_temperature >= SPACE_SUIT_MAX_HEAT_PROTECTION_TEMPERATURE && min_cold_protection_temperature <= SPACE_SUIT_MIN_COLD_PROTECTION_TEMPERATURE) //5000 or above, but less than 30000
- armor_stats += "It provides very good protection against hazardous temperatures at both extremes, but may not be sufficient for very high-intensity situations. \n"
- else if(max_heat_protection_temperature >= FIRESUIT_MAX_HEAT_PROTECTION_TEMPERATURE) //30000 or above
- armor_stats += "It provides exceptional protection from extremely high temperatures. \n"
- else if(min_cold_protection_temperature <= SPACE_SUIT_MIN_COLD_PROTECTION_TEMPERATURE) //2 or less
- armor_stats += "It provides exceptional protection against very low temperatures. \n"
+ if(min_cold_protection_temperature)
+ armor_stats += "It is rated for temperatures as low as [min_cold_protection_temperature] Kelvin. \n"
+ if(max_heat_protection_temperature)
+ armor_stats += "It is rated for temperatures as high as [max_heat_protection_temperature] Kelvin. \n"
var/list/covers = list()
var/list/slots = list()
diff --git a/code/modules/food/food/drinks.dm b/code/modules/food/food/drinks.dm
index 84a651ba3c1..9c2944df265 100644
--- a/code/modules/food/food/drinks.dm
+++ b/code/modules/food/food/drinks.dm
@@ -179,7 +179,16 @@
return ..()
/obj/item/weapon/reagent_containers/food/drinks/self_feed_message(var/mob/user)
- to_chat(user, "You swallow a gulp from \the [src].")
+ if(amount_per_transfer_from_this == volume) //I wanted to use a switch, but switch statements can't use vars and the maximum volume of containers varies
+ to_chat(user, "You knock back the entire [src] in one go!")
+ else if(amount_per_transfer_from_this <= 4) //below the standard 5
+ to_chat(user, "You take a modest sip from \the [src].")
+ else if(amount_per_transfer_from_this <= 10) //the standard five to a bit more
+ to_chat(user, "You swallow a gulp from \the [src].")
+ else if(amount_per_transfer_from_this <= 30)
+ to_chat(user, "You take a long drag from \the [src].")
+ else //default message as a fallback
+ to_chat(user, "You swallow a gulp from \the [src].")
/obj/item/weapon/reagent_containers/food/drinks/feed_sound(var/mob/user)
playsound(src, 'sound/items/drink.ogg', rand(10, 50), 1)
diff --git a/code/modules/food/food/drinks/bottle.dm b/code/modules/food/food/drinks/bottle.dm
index 2b25369016b..5c2889c5a96 100644
--- a/code/modules/food/food/drinks/bottle.dm
+++ b/code/modules/food/food/drinks/bottle.dm
@@ -180,6 +180,25 @@
var/obj/item/weapon/broken_bottle/B = smash(target.loc, target)
user.put_in_active_hand(B)
+/obj/item/weapon/reagent_containers/food/drinks/bottle/verb/spin_bottle()
+ set name = "Spin The Bottle"
+ set category = "Object"
+ set src in view(1)
+
+ if(isobserver(usr) || usr.stat)
+ return
+
+ if(!isturf(src.loc))
+ to_chat(usr, "\The [src] needs to be on the floor to spin.")
+ return
+
+ var/spin_rotation = (rand(0,359))
+ usr.visible_message("\The [usr] spins \the [src]!","You spin \the [src]!")
+ SpinAnimation(3,10)
+ spawn(30)
+ icon_rotation = spin_rotation
+ update_transform()
+
//Keeping this here for now, I'll ask if I should keep it here.
/obj/item/weapon/broken_bottle
name = "Broken Bottle"
diff --git a/code/modules/hydroponics/seedtypes/flowers.dm b/code/modules/hydroponics/seedtypes/flowers.dm
index e71e707e636..e6c2e86f597 100644
--- a/code/modules/hydroponics/seedtypes/flowers.dm
+++ b/code/modules/hydroponics/seedtypes/flowers.dm
@@ -65,7 +65,7 @@
set_trait(TRAIT_MATURATION,7)
set_trait(TRAIT_PRODUCTION,5)
set_trait(TRAIT_YIELD,5)
- set_trait(TRAIT_PRODUCT_ICON,"lavender")
+ set_trait(TRAIT_PRODUCT_ICON,"flower6")
set_trait(TRAIT_PRODUCT_COLOUR,"#B57EDC")
set_trait(TRAIT_PLANT_COLOUR,"#6B8C5E")
set_trait(TRAIT_PLANT_ICON,"flower4")
diff --git a/code/modules/instruments/songs/editor.dm b/code/modules/instruments/songs/editor.dm
index f38420fd376..2927b0ddbaf 100644
--- a/code/modules/instruments/songs/editor.dm
+++ b/code/modules/instruments/songs/editor.dm
@@ -191,7 +191,7 @@
stop_playing()
else if(href_list["setlinearfalloff"])
- var/amount = tgui_input_number(usr, "Set linear sustain duration in seconds", "Linear Sustain Duration")
+ var/amount = tgui_input_number(usr, "Set linear sustain duration in seconds", "Linear Sustain Duration", round_value=FALSE)
if(!isnull(amount))
set_linear_falloff_duration(round(amount * 10, world.tick_lag))
diff --git a/code/modules/media/mediamanager.dm b/code/modules/media/mediamanager.dm
index a03cfaf35de..adc522546f6 100644
--- a/code/modules/media/mediamanager.dm
+++ b/code/modules/media/mediamanager.dm
@@ -59,7 +59,7 @@
set_new_volume(usr)
/client/proc/set_new_volume(var/mob/user)
- if(!QDELETED(src.media) || !istype(src.media))
+ if(QDELETED(src.media) || !istype(src.media))
to_chat(user, "You have no media datum to change, if you're not in the lobby tell an admin.")
return
var/value = input(usr, "Choose your Jukebox volume.", "Jukebox volume", media.volume)
diff --git a/code/modules/mob/language/station_vr.dm b/code/modules/mob/language/station_vr.dm
index df525dbe382..797e5778c16 100644
--- a/code/modules/mob/language/station_vr.dm
+++ b/code/modules/mob/language/station_vr.dm
@@ -143,6 +143,21 @@
/datum/language/echosong/scramble(var/input, var/list/known_languages)
return stars(input)
+/datum/language/lleill
+ name = LANGUAGE_LLEILL
+ desc = "An ancient, gutteral language involving a lot of spitting."
+ speech_verb = "speaks"
+ ask_verb = "ponders"
+ exclaim_verb = "calls"
+ colour = "echosong"
+ key = "L"
+ syllables = list(
+ "llyn", "bren", "gwyn", "gwyr", "ddys", "dath", "llio", "cym", "ddrai", "ffyr", "lle", "dy", "eto", "uno", "dydno", "llego", "bryth", "ffair",
+ "ynys", "ed", "fore", "oe", "hen", "wladd", "ty", "nha", "dwy", "mae", "dros", "pob", "ia", "wyll", "gwdd", "fi"
+ )
+ machine_understands = FALSE
+ flags = WHITELISTED
+
/datum/language/echosong/broadcast(var/mob/living/speaker, var/message, var/speaker_mask)
log_say("(INAUDIBLE) [message]", speaker)
speaker.say_signlang(format_message(message), pick(signlang_verb), pick(signlang_verb_understood), src, 2)
diff --git a/code/modules/mob/living/bot/cleanbot.dm b/code/modules/mob/living/bot/cleanbot.dm
index ffbd20a6f12..7c9870a79ca 100644
--- a/code/modules/mob/living/bot/cleanbot.dm
+++ b/code/modules/mob/living/bot/cleanbot.dm
@@ -15,6 +15,7 @@
var/cleaning = 0
var/wet_floors = 0
var/spray_blood = 0
+ var/blood = 1
var/list/target_types = list()
/mob/living/bot/cleanbot/New()
@@ -138,7 +139,8 @@
if(prob(20))
custom_emote(2, "begins to clean up \the [loc]")
if(do_after(src, cleantime * cTimeMult))
- clean_blood()
+ if(blood)
+ clean_blood()
if(istype(loc, /turf/simulated))
var/turf/simulated/T = loc
T.dirt = 0
@@ -186,6 +188,7 @@
data["on"] = on
data["open"] = open
data["locked"] = locked
+ data["blood"] = blood
data["patrol"] = will_patrol
data["vocal"] = vocal
@@ -207,6 +210,9 @@
else
turn_on()
. = TRUE
+ if("blood")
+ blood = !blood
+ . = TRUE
if("patrol")
will_patrol = !will_patrol
patrol_path = null
diff --git a/code/modules/mob/living/bot/edCLNbot.dm b/code/modules/mob/living/bot/edCLNbot.dm
index 3a8265f3521..0d976fef89f 100644
--- a/code/modules/mob/living/bot/edCLNbot.dm
+++ b/code/modules/mob/living/bot/edCLNbot.dm
@@ -213,4 +213,4 @@
user.drop_item()
qdel(W)
user.drop_from_inventory(src)
- qdel(src)
\ No newline at end of file
+ qdel(src)
diff --git a/code/modules/mob/living/carbon/human/human.dm b/code/modules/mob/living/carbon/human/human.dm
index 38427453e3a..712f549626e 100644
--- a/code/modules/mob/living/carbon/human/human.dm
+++ b/code/modules/mob/living/carbon/human/human.dm
@@ -7,6 +7,17 @@
has_huds = TRUE //We do have HUDs (like health, wanted, status, not inventory slots)
+
+ var/vore_capacity = 3
+ var/vore_capacity_ex = list("stomach" = 3, "taur belly" = 3)
+ var/vore_fullness_ex = list("stomach" = 0, "taur belly" = 0)
+ var/vore_icon_bellies = list("stomach", "taur belly")
+ var/struggle_anim_stomach = FALSE
+ var/struggle_anim_taur = FALSE
+ var/vore_sprite_color = list("stomach" = "#FFFFFF", "taur belly" = "#FFFFFF")
+ var/vore_sprite_multiply = list("stomach" = TRUE, "taur belly" = TRUE)
+ var/vore_fullness = 0
+
var/embedded_flag //To check if we've need to roll for damage on movement while an item is imbedded in us.
var/obj/item/weapon/rig/wearing_rig // This is very not good, but it's much much better than calling get_rig() every update_canmove() call.
var/last_push_time //For human_attackhand.dm, keeps track of the last use of disarm
@@ -19,6 +30,7 @@
var/can_defib = 1 //Horrible damage (like beheadings) will prevent defibbing organics.
var/active_regen = FALSE //Used for the regenerate proc in human_powers.dm
var/active_regen_delay = 300
+ var/list/teleporters = list() //Used for lleill abilities
/mob/living/carbon/human/Initialize(mapload, var/new_species = null)
if(!dna)
@@ -1777,3 +1789,19 @@
/mob/living/carbon/human/get_mob_riding_slots()
return list(back, head, wear_suit)
+
+/mob/living/carbon/human/proc/update_fullness()
+ var/list/new_fullness = list()
+ vore_fullness = 0
+ for(var/belly_class in vore_icon_bellies)
+ new_fullness[belly_class] = 0
+ for(var/obj/belly/B as anything in vore_organs)
+ new_fullness[B.belly_sprite_to_affect] += B.GetFullnessFromBelly()
+ for(var/belly_class in vore_icon_bellies)
+ new_fullness[belly_class] /= size_multiplier //Divided by pred's size so a macro mob won't get macro belly from a regular prey.
+ new_fullness[belly_class] = round(new_fullness[belly_class], 1) // Because intervals of 0.25 are going to make sprite artists cry.
+ vore_fullness_ex[belly_class] = min(vore_capacity_ex[belly_class], new_fullness[belly_class])
+ vore_fullness += new_fullness[belly_class]
+ vore_fullness = min(vore_capacity, vore_fullness)
+ update_vore_belly_sprite()
+ update_vore_tail_sprite()
diff --git a/code/modules/mob/living/carbon/human/human_helpers.dm b/code/modules/mob/living/carbon/human/human_helpers.dm
index 692f121ba82..c531ef5086f 100644
--- a/code/modules/mob/living/carbon/human/human_helpers.dm
+++ b/code/modules/mob/living/carbon/human/human_helpers.dm
@@ -192,6 +192,15 @@
compiled_vis |= VIS_CH_VANTAG
//VOREStation Add End
+ //Vore Stomach addition start. This goes here.
+ if(stomach_vision && !(VIS_CH_STOMACH in vis_enabled))
+ plane_holder.set_vis(VIS_CH_STOMACH,TRUE)
+ compiled_vis += VIS_CH_STOMACH
+ else if(!stomach_vision && (VIS_CH_STOMACH in vis_enabled))
+ plane_holder.set_vis(VIS_CH_STOMACH,FALSE)
+ compiled_vis -= VIS_CH_STOMACH
+ //Vore Stomach addition end
+
if(!compiled_vis.len && !vis_enabled.len)
return //Nothin' doin'.
diff --git a/code/modules/mob/living/carbon/human/human_species_vr.dm b/code/modules/mob/living/carbon/human/human_species_vr.dm
index a747280df8d..dd3134420a6 100644
--- a/code/modules/mob/living/carbon/human/human_species_vr.dm
+++ b/code/modules/mob/living/carbon/human/human_species_vr.dm
@@ -31,3 +31,6 @@
/mob/living/carbon/human/altevian/New(var/new_loc)
..(new_loc, SPECIES_ALTEVIAN)
+
+/mob/living/carbon/human/lleill/New(var/new_loc)
+ ..(new_loc, SPECIES_LLEILL)
diff --git a/code/modules/mob/living/carbon/human/species/lleill/lleill.dm b/code/modules/mob/living/carbon/human/species/lleill/lleill.dm
new file mode 100644
index 00000000000..48146df8e8a
--- /dev/null
+++ b/code/modules/mob/living/carbon/human/species/lleill/lleill.dm
@@ -0,0 +1,178 @@
+/datum/species/lleill
+
+ name = SPECIES_LLEILL
+ name_plural = "Lleill"
+ icobase = 'icons/mob/human_races/r_lleill.dmi'
+ deform = 'icons/mob/human_races/r_lleill.dmi'
+ color_mult = 1
+ tail = "tail"
+ icobase_tail = 1
+ blurb = "A species that appears to originate somewhere in redspace. Their forms are not consistent, \
+ they do not care for consistency, and are working under a constant never-ending drive to improve themselves \
+ and the world around them. With little care for whether the world itself wants to change." //PLACEHOLDER
+
+ blood_color = "#FFFFFF"
+ blood_name = "glamour"
+ flesh_color = "#FFFFFF"
+ reagent_tag = IS_LLEILL
+
+ num_alternate_languages = 3
+ species_language = LANGUAGE_LLEILL
+ language = LANGUAGE_LLEILL
+ name_language = LANGUAGE_LLEILL
+
+ flags = NO_SCAN | NO_MINOR_CUT | NO_INFECT | NO_HALLUCINATION
+ spawn_flags = SPECIES_CAN_JOIN | SPECIES_IS_WHITELISTED | SPECIES_WHITELIST_SELECTABLE
+ appearance_flags = HAS_HAIR_COLOR | HAS_LIPS | HAS_SKIN_COLOR | HAS_EYE_COLOR | HAS_UNDERWEAR
+
+ max_age = 200
+
+ economic_modifier = 15
+
+ digi_allowed = TRUE
+
+ //Specific abilities
+
+ darksight = 10 //Can see in dark
+
+ burn_mod = 0.25 //Very resistant to fire
+ pain_mod = 0.25 //Whilst not resistant to brute or stunning, they are quite resistant to pain, making them tanky in their own way.
+
+ warning_low_pressure = 50
+ hazard_low_pressure = -1
+ warning_high_pressure = 300
+ hazard_high_pressure = 10000 //Can be killed by pressure but you're going to need a hell of a lot
+
+ cold_level_1 = -1 //Safe in space
+ cold_level_2 = -1
+ cold_level_3 = -1
+
+ heat_level_1 = 1500 //Very resiliant to heat
+ heat_level_2 = 2500
+ heat_level_3 = 5000
+
+ can_space_freemove = TRUE //Have no issue moving through space.
+ can_zero_g_move = TRUE
+
+ chem_strength_alcohol = 0 //Can't get drunk
+
+ oxy_mod = 0.25 //Suffocates very slowly, but does ultimately need to breathe, will outpace heal most oxygen damage.
+ poison_type = null //Not harmed by phoron.
+ water_breather = TRUE
+
+ var/list/valid_transform_species = list(
+ "Human", "Unathi", "Tajara", "Skrell",
+ "Diona", "Teshari", "Monkey","Sergal",
+ "Akula","Nevrean","Zorren",
+ "Fennec", "Vulpkanin", "Vasilissan",
+ "Rapala", "Neaera", "Stok", "Farwa", "Sobaka",
+ "Wolpin", "Saru", "Sparra", "Lleill")
+
+ // Looks like a lot but the majority of these are just to change their appearance.
+ inherent_verbs = list(
+ /mob/living/carbon/human/proc/lleill_select_colour,
+ /mob/living/carbon/human/proc/lleill_select_shape,
+ /mob/living/carbon/human/proc/shapeshifter_select_hair,
+ /mob/living/carbon/human/proc/shapeshifter_select_hair_colors,
+ /mob/living/carbon/human/proc/shapeshifter_select_gender,
+ /mob/living/carbon/human/proc/shapeshifter_select_wings,
+ /mob/living/carbon/human/proc/shapeshifter_select_tail,
+ /mob/living/carbon/human/proc/shapeshifter_select_ears,
+ /mob/living/proc/set_size,
+ /mob/living/carbon/human/proc/lleill_invisibility,
+ /mob/living/carbon/human/proc/lleill_transmute,
+ /mob/living/carbon/human/proc/lleill_rings)
+
+ //organs, going with just the basics for now
+
+ has_organ = list(
+ O_HEART = /obj/item/organ/internal/heart,
+ O_LUNGS = /obj/item/organ/internal/lungs,
+ O_VOICE = /obj/item/organ/internal/voicebox,
+ O_LIVER = /obj/item/organ/internal/liver,
+ O_KIDNEYS = /obj/item/organ/internal/kidneys,
+ O_BRAIN = /obj/item/organ/internal/brain,
+ O_APPENDIX = /obj/item/organ/internal/appendix,
+ O_SPLEEN = /obj/item/organ/internal/spleen,
+ O_EYES = /obj/item/organ/internal/eyes,
+ O_STOMACH = /obj/item/organ/internal/stomach,
+ O_INTESTINE = /obj/item/organ/internal/intestine
+ )
+
+ has_limbs = list(
+ BP_TORSO = list("path" = /obj/item/organ/external/chest),
+ BP_GROIN = list("path" = /obj/item/organ/external/groin),
+ BP_HEAD = list("path" = /obj/item/organ/external/head/lleill),
+ BP_L_ARM = list("path" = /obj/item/organ/external/arm),
+ BP_R_ARM = list("path" = /obj/item/organ/external/arm/right),
+ BP_L_LEG = list("path" = /obj/item/organ/external/leg),
+ BP_R_LEG = list("path" = /obj/item/organ/external/leg/right),
+ BP_L_HAND = list("path" = /obj/item/organ/external/hand),
+ BP_R_HAND = list("path" = /obj/item/organ/external/hand/right),
+ BP_L_FOOT = list("path" = /obj/item/organ/external/foot),
+ BP_R_FOOT = list("path" = /obj/item/organ/external/foot/right)
+ )
+
+ base_species = SPECIES_LLEILL
+
+// Shapeshifters have some behaviour that doesn't play well with this species so I have taken the main parts needed for here.
+
+/datum/species/lleill/get_valid_shapeshifter_forms(var/mob/living/carbon/human/H)
+ return valid_transform_species
+
+/datum/species/lleill/get_icobase(var/mob/living/carbon/human/H, var/get_deform)
+ if(!H) return ..(null, get_deform)
+ var/datum/species/S = GLOB.all_species[wrapped_species_by_ref["\ref[H]"]]
+ if(!S || S.type == src.type) return ..(H, get_deform)
+ return S.get_icobase(H,get_deform)
+
+/datum/species/lleill/get_race_key(var/mob/living/carbon/human/H)
+ return "[..()]-[wrapped_species_by_ref["\ref[H]"]]"
+
+/datum/species/lleill/get_bodytype(var/mob/living/carbon/human/H)
+ var/datum/species/S = GLOB.all_species[wrapped_species_by_ref["\ref[H]"]]
+ if(!H || !S) return ..()
+ if(S.type == src.type) return ..(H)
+ return S.get_bodytype(H)
+
+/datum/species/lleill/get_blood_mask(var/mob/living/carbon/human/H)
+ if(!H) return ..()
+ var/datum/species/S = GLOB.all_species[wrapped_species_by_ref["\ref[H]"]]
+ if(!S || S.name == src.name)
+ return ..()
+ return S?.get_blood_mask(H)
+
+/datum/species/lleill/get_damage_mask(var/mob/living/carbon/human/H)
+ if(!H) return ..()
+ var/datum/species/S = GLOB.all_species[wrapped_species_by_ref["\ref[H]"]]
+ if(!S || S.name == src.name)
+ return ..()
+ return S?.get_damage_mask(H)
+
+/datum/species/lleill/get_damage_overlays(var/mob/living/carbon/human/H)
+ if(!H) return ..()
+ var/datum/species/S = GLOB.all_species[wrapped_species_by_ref["\ref[H]"]]
+ if(!S || S.name == src.name)
+ return ..()
+ return S?.get_damage_overlays(H)
+
+/datum/species/lleill/get_tail(var/mob/living/carbon/human/H)
+ if(!H) return ..()
+ var/datum/species/S = GLOB.all_species[wrapped_species_by_ref["\ref[H]"]]
+ if(!S || S.name == src.name)
+ return ..()
+ return S?.get_tail(H)
+
+/datum/species/lleill/get_tail_animation(var/mob/living/carbon/human/H)
+ if(!H) return ..()
+ var/datum/species/S = GLOB.all_species[wrapped_species_by_ref["\ref[H]"]]
+ if(!S || S.name == src.name)
+ return ..()
+ return S?.get_tail_animation(H)
+
+/datum/species/lleill/get_tail_hair(var/mob/living/carbon/human/H)
+ if(!H) return ..()
+ var/datum/species/S = GLOB.all_species[wrapped_species_by_ref["\ref[H]"]]
+ if(!S || S.name == src.name)
+ return ..()
+ return S?.get_tail_hair(H)
diff --git a/code/modules/mob/living/carbon/human/species/lleill/lleill_abilities.dm b/code/modules/mob/living/carbon/human/species/lleill/lleill_abilities.dm
new file mode 100644
index 00000000000..97ee036c8a7
--- /dev/null
+++ b/code/modules/mob/living/carbon/human/species/lleill/lleill_abilities.dm
@@ -0,0 +1,179 @@
+/datum/power/lleill
+
+// Simple ability to become invisible. Does not phase you out of the world, you can still interact with things and can not pass through walls.
+// Essentially the same as traitor cloaking, using the same proc for it.
+
+/datum/power/lleill/invisibility
+ name = "Invisibility"
+ desc = "Change your appearance to match your surroundings, becoming completely invisible to the naked eye."
+ verbpath = /mob/living/carbon/human/proc/lleill_invisibility
+
+/mob/living/carbon/human/proc/lleill_invisibility()
+ set name = "Invisibility"
+ set desc = "Change your appearance to match your surroundings, becoming completely invisible to the naked eye."
+ set category = "Abilities"
+
+ if(stat)
+ to_chat(src, "You can't go invisible when weakened like this.")
+ return
+
+ if(!cloaked)
+ cloak()
+ to_chat(src, "Your fur shimmers and shifts around you, hiding you from the naked eye.")
+ else
+ uncloak()
+ to_chat(src, "The brustling of your fur settles down and you become visible once again.")
+
+/mob/living/carbon/human/proc/lleill_select_shape()
+
+ set name = "Select Body Shape"
+ set category = "Abilities"
+
+ if(stat || world.time < last_special)
+ return
+
+ last_special = world.time + 50
+
+ var/new_species = null
+ new_species = tgui_input_list(usr, "Please select a species to emulate.", "Shapeshifter Body", species.get_valid_shapeshifter_forms(src))
+
+ if(!new_species || !GLOB.all_species[new_species] || wrapped_species_by_ref["\ref[src]"] == new_species)
+ return
+ lleill_change_shape(new_species)
+
+/mob/living/carbon/human/proc/lleill_change_shape(var/new_species = null)
+ if(!new_species)
+ return
+
+ wrapped_species_by_ref["\ref[src]"] = new_species
+ dna.base_species = new_species
+ species.base_species = new_species
+ visible_message("\The [src] shifts and contorts, taking the form of \a [new_species]!")
+ regenerate_icons()
+
+/mob/living/carbon/human/proc/lleill_select_colour()
+
+ set name = "Select Body Colour"
+ set category = "Abilities"
+
+ if(stat || world.time < last_special)
+ return
+
+ last_special = world.time + 50
+
+ var/new_skin = input(usr, "Please select a new body color.", "Shapeshifter Colour", rgb(r_skin, g_skin, b_skin)) as null|color
+ if(!new_skin)
+ return
+ lleill_set_colour(new_skin)
+
+/mob/living/carbon/human/proc/lleill_set_colour(var/new_skin)
+
+ r_skin = hex2num(copytext(new_skin, 2, 4))
+ g_skin = hex2num(copytext(new_skin, 4, 6))
+ b_skin = hex2num(copytext(new_skin, 6, 8))
+ r_synth = r_skin
+ g_synth = g_skin
+ b_synth = b_skin
+
+ for(var/obj/item/organ/external/E in organs)
+ E.sync_colour_to_human(src)
+
+ regenerate_icons()
+
+/mob/living/carbon/human/proc/lleill_transmute()
+ set name = "Transmute Object"
+ set desc = "Convert an object into a piece of glamour."
+ set category = "Abilities"
+
+ var/list/transmute_list = list(
+ /obj/item/weapon/potion_material/glamour_transparent,
+ /obj/item/weapon/potion_material/glamour_shrinking,
+ /obj/item/weapon/potion_material/glamour_twinkling,
+ /obj/item/weapon/potion_material/glamour_shard,
+ /obj/item/capture_crystal/glamour,
+ /obj/item/glamour_face,
+ /obj/item/device/universal_translator/glamour
+ )
+
+ if(stat)
+ to_chat(src, "You can't go do that when weakened like this.")
+ return
+
+ var/obj/item/I = get_active_hand()
+ if(!I)
+ to_chat(src, "You have no item in your active hand.")
+ return
+
+ var/obj/item/transmute_product = tgui_input_list(src, "Choose an glamour to transmute the item into:", "Transmutation", transmute_list)
+ if(!get_active_hand(I))
+ to_chat(src, "The item is no longer in your hands.")
+ return
+ else
+ visible_message("\The [src] begins to change the form of \the [I].")
+ if(!do_after(usr, 10 SECONDS, I, exclusive = TASK_USER_EXCLUSIVE))
+ visible_message("\The [src] leaves \the [I] in its original form.")
+ return 0
+ visible_message("\The [src] transmutes \the [I] into a \the [transmute_product.name].")
+ drop_item(I)
+ qdel(I)
+ var/spawnloc = get_turf(usr)
+ var/obj/item/N = new transmute_product(spawnloc)
+ put_in_active_hand(N)
+
+/mob/living/carbon/human/proc/lleill_rings()
+ set name = "Place/Use Rings"
+ set desc = "Place or teleport to a glamour ring."
+ set category = "Abilities"
+
+ if(stat)
+ to_chat(src, "You can't go do that when weakened like this.")
+ return
+ if(buckled)
+ to_chat(src,"You can't do that when restrained.")
+
+ var/r_action = tgui_alert(src, "What would you like to do with your rings?", "Actions", list("Spawn New Ring", "Teleport to Ring", "Cancel"))
+ if(r_action == "Cancel")
+ return
+ if(r_action == "Spawn New Ring")
+ if(!do_after(src, 10 SECONDS, src, exclusive = TASK_USER_EXCLUSIVE))
+ src.visible_message("\The [src] begins to form white rings on the ground.")
+ return 0
+ to_chat(src, "You place a new glamour ring at your feet.")
+ var/spawnloc = get_turf(src)
+ var/obj/structure/glamour_ring/R = new(spawnloc)
+ src.teleporters |= R
+ if(r_action == "Teleport to Ring")
+ if(!src.teleporters.len)
+ to_chat(src, "You need to place rings to teleport to them.")
+ return
+ else
+ var/obj/structure/glamour_ring/R = tgui_input_list(src, "Where do you wish to teleport?", "Teleport", src.teleporters)
+
+ var/datum/effect/effect/system/spark_spread/spk
+ spk = new(src)
+
+ var/T = get_turf(src)
+ spk.set_up(5, 0, src)
+ spk.attach(src)
+ playsound(T, "sparks", 50, 1)
+ anim(T,src,'icons/mob/mob.dmi',,"phaseout",,src.dir)
+
+ var/S = get_turf(R)
+ src.forceMove(S)
+
+ spk.start()
+ playsound(S, 'sound/effects/phasein.ogg', 25, 1)
+ playsound(S, 'sound/effects/sparks2.ogg', 50, 1)
+ anim(S,src,'icons/mob/mob.dmi',,"phasein",,src.dir)
+ spk.set_up(5, 0, src)
+ spk.attach(src)
+
+ //Would be fun to eat people standing on your ring...
+ if(can_be_drop_pred && vore_selected)
+ var/list/target_list = src.living_mobs(0)
+ if(target_list.len)
+ for(var/mob/living/M in target_list)
+ if(M.devourable && M.can_be_drop_prey)
+ M.forceMove(vore_selected)
+ to_chat(M,"In a bright flash of white light, you suddenly find yourself trapped in \the [src]'s [vore_selected.name]!")
+
diff --git a/code/modules/mob/living/carbon/human/species/lleill/lleill_items.dm b/code/modules/mob/living/carbon/human/species/lleill/lleill_items.dm
new file mode 100644
index 00000000000..8b724fe8b6b
--- /dev/null
+++ b/code/modules/mob/living/carbon/human/species/lleill/lleill_items.dm
@@ -0,0 +1,198 @@
+//Transparent Glamour (invisibility potion)
+
+/obj/item/weapon/potion_material/glamour_transparent
+ name = "transparent glamour"
+ desc = "A shard of hardened white crystal that is clearly translucent."
+ icon = 'icons/obj/glamour.dmi'
+ icon_state = "transparent"
+ base_reagent = /obj/item/weapon/potion_base/aqua_regia
+ product_potion = /obj/item/weapon/reagent_containers/glass/bottle/potion/invisibility
+
+/obj/item/weapon/reagent_containers/glass/bottle/potion/invisibility
+ name = "transparent potion"
+ desc = "A small white potion, the clear liquid inside can barely be seen at all."
+ prefill = list("transparent glamour" = 1)
+
+/datum/reagent/glamour_transparent
+ name = "Clear Glamour"
+ id = "transparent glamour"
+ description = "This material is from somewhere else, it can barely be seen by the naked eye."
+ taste_description = "nothingness"
+ reagent_state = LIQUID
+ color = "#ffffff"
+ scannable = 1
+
+/datum/reagent/glamour_transparent/affect_blood(var/mob/living/carbon/target, var/removed)
+ if(!target.cloaked)
+ target.visible_message("\The [target] vanishes from sight.")
+ target.cloak()
+ target.bloodstr.clear_reagents() //instantly clears reagents afterwards
+ target.ingested.clear_reagents()
+ target.touching.clear_reagents()
+ spawn(600)
+ if(target.cloaked)
+ target.uncloak()
+ target.visible_message("\The [target] appears as if from thin air.")
+
+//Shrinking Glamour (scaling potion)
+
+/obj/item/weapon/potion_material/glamour_shrinking
+ name = "shrinking glamour"
+ desc = "A soft clump of white material that seems to shrink at your touch."
+ icon = 'icons/obj/glamour.dmi'
+ icon_state = "shrinking"
+ base_reagent = /obj/item/weapon/potion_base/aqua_regia
+ product_potion = /obj/item/weapon/reagent_containers/glass/bottle/potion/scaling
+
+/obj/item/weapon/reagent_containers/glass/bottle/potion/scaling
+ name = "scaling potion"
+ desc = "A small white potion, the clear liquid inside can barely be seen at all."
+ prefill = list("scaling glamour" = 1)
+
+/datum/reagent/glamour_scaling
+ name = "Scaling Glamour"
+ id = "scaling glamour"
+ description = "This material is from somewhere else, it appears to change volumes readily at a glance."
+ taste_description = "difficult to discern"
+ reagent_state = LIQUID
+ color = "#ffffff"
+ scannable = 1
+
+/datum/reagent/glamour_scaling/affect_blood(var/mob/living/carbon/target, var/removed)
+ if(!(/mob/living/proc/set_size in target.verbs))
+ to_chat(target, "You feel as though you could change size at any moment.")
+ target.verbs |= /mob/living/proc/set_size
+ target.bloodstr.clear_reagents() //instantly clears reagents afterwards
+ target.ingested.clear_reagents()
+ target.touching.clear_reagents()
+
+//Twinkling Glamour (Sparkling potion - Gives darksight)
+
+/obj/item/weapon/potion_material/glamour_twinkling
+ name = "twinkling glamour"
+ desc = "A sheet of white material that twinkles on its own accord."
+ icon = 'icons/obj/glamour.dmi'
+ icon_state = "twinkling"
+ base_reagent = /obj/item/weapon/potion_base/aqua_regia
+ product_potion = /obj/item/weapon/reagent_containers/glass/bottle/potion/darksight
+
+/obj/item/weapon/reagent_containers/glass/bottle/potion/darksight
+ name = "twinling potion"
+ desc = "A small white potion, the thin white liquid inside twinkles brightly."
+ prefill = list("twinkling glamour" = 1)
+
+/datum/reagent/glamour_twinkling
+ name = "Twinkling Glamour"
+ id = "twinkling glamour"
+ description = "This material is from somewhere else, it appears to be twinkling."
+ taste_description = "bright"
+ reagent_state = LIQUID
+ color = "#ffffff"
+ scannable = 1
+
+/datum/reagent/glamour_twinkling/affect_blood(var/mob/living/carbon/human/target, var/removed)
+ if(target.species.darksight < 10)
+ to_chat(target, "You can suddenly see much better than before.")
+ target.species.darksight = 10
+ if(target.disabilities & NEARSIGHTED)
+ target.disabilities &= ~NEARSIGHTED
+ to_chat(target, "Everything is much less blurry.")
+ target.bloodstr.clear_reagents() //instantly clears reagents afterwards
+ target.ingested.clear_reagents()
+ target.touching.clear_reagents()
+
+//Glamour Cell (variant of capture crystal)
+
+/obj/item/capture_crystal/glamour
+ name = "glamour cell"
+ desc = "A large but light round ball of glamour that glows from somewhere within."
+ icon = 'icons/obj/glamour.dmi'
+
+/obj/item/capture_crystal/glamour/animate_action(atom/thing)
+ var/image/coolanimation = image('icons/obj/glamour.dmi', null, "animation")
+ coolanimation.plane = PLANE_LIGHTING_ABOVE
+ thing.overlays += coolanimation
+ sleep(14)
+ thing.overlays -= coolanimation
+
+//Face of Glamour (creates a clone of a target)
+
+/obj/item/glamour_face
+ name = "face of glamour"
+ desc = "A piece of glamour that is formed vaguely into the shape of a face."
+ icon = 'icons/obj/glamour.dmi'
+ icon_state = "face"
+ var/mob/living/homunculus = 0
+
+/obj/item/glamour_face/attack_self(var/mob/user)
+ if(!homunculus)
+ var/list/targets = list()
+ for(var/mob/living/carbon/human/M in mob_list)
+ if(M.z != user.z || get_dist(user,M) > 10)
+ continue
+ if(istype(M) && M.resleeve_lock && M.ckey != M.resleeve_lock)
+ continue
+ targets |= M
+
+ if(!targets)
+ to_chat(user, "There are no appropriate targets in range.")
+ return
+
+ var/mob/living/carbon/human/chosen_target = tgui_input_list(user, "Which target do you wish to create a homunculus of?", "homunculus", targets)
+
+ var/spawnloc = get_turf(user)
+ var/mob/living/simple_mob/homunculus/H = new(spawnloc)
+ H.name = chosen_target.name
+ H.desc = chosen_target.desc
+ H.icon = chosen_target.icon
+ H.icon_state = chosen_target.icon_state
+ H.copy_overlays(chosen_target, TRUE)
+ H.resize(chosen_target.size_multiplier, ignore_prefs = TRUE)
+ homunculus = H
+ H.owner = src
+ return
+ if(homunculus)
+ var/mob/living/simple_mob/homunculus/H = homunculus
+ var/h_action = tgui_alert(user, "What would you like to do with your homunculus?", "Actions", list("Recall", "Speak Through", "Cancel"))
+ if(h_action == "Cancel")
+ return
+ if(h_action == "Recall")
+ H.visible_message("\The [H] returns to the face.")
+ qdel(H)
+ homunculus = 0
+ return
+ if(h_action == "Speak Through")
+ var/words_to_say = tgui_input_text(user, "What should the homunculus say:", "Speak Through")
+ H.say(words_to_say)
+ return
+
+
+//Speaking Glamour (universal translator)
+
+/obj/item/device/universal_translator/glamour
+ name = "speaking glamour"
+ desc = "A shard of glamour that translates all known language for the user."
+ icon = 'icons/obj/glamour.dmi'
+ icon_state = "translator"
+
+//Teleporter ring
+
+/obj/structure/glamour_ring
+ name = "glamour ring"
+ desc = "A ring of glowing white, oddly reflective material."
+ icon = 'icons/obj/glamour.dmi'
+ icon_state = "ring"
+ density = 0
+ anchored = 1
+
+ var/connected_mob
+ var/area_name
+
+/obj/structure/glamour_ring/Initialize()
+ . = ..()
+ var/area/A = get_area(src)
+ area_name = A.name
+ name = "[area_name] glamour ring"
+
+//Glamour Floor
+//Glamour Wall
\ No newline at end of file
diff --git a/code/modules/mob/living/carbon/human/species/species.dm b/code/modules/mob/living/carbon/human/species/species.dm
index 9f68957ceeb..3b471a84a3f 100644
--- a/code/modules/mob/living/carbon/human/species/species.dm
+++ b/code/modules/mob/living/carbon/human/species/species.dm
@@ -108,7 +108,7 @@
var/chem_strength_heal = 1 // Multiplier to most beneficial chem strength
var/chem_strength_pain = 1 // Multiplier to painkiller strength (could be used in a negative trait to simulate long-term addiction reducing effects, etc.)
var/chem_strength_tox = 1 // Multiplier to toxic chem strength (inc. chloral/sopo/mindbreaker/etc. thresholds)
- var/chem_strength_alcohol = 1 // Multiplier to alcohol strength; 0.5 = half, 0 = no effect at all, 2 = double, etc.
+ var/chem_strength_alcohol = 1 // Multiplier to alcohol effect thresholds; higher means more is needed to reach a given effect tier
var/chemOD_threshold = 1 // Multiplier to overdose threshold; lower = easier overdosing
var/chemOD_mod = 1 // Damage modifier for overdose; higher = more damage from ODs
@@ -222,6 +222,8 @@
var/rarity_value = 1 // Relative rarity/collector value for this species.
var/economic_modifier = 2 // How much money this species makes
+ var/vore_belly_default_variant = "H"
+
// Determines the organs that the species spawns with and
var/list/has_organ = list( // which required-organ checks are conducted.
O_HEART = /obj/item/organ/internal/heart,
diff --git a/code/modules/mob/living/carbon/human/species/station/prometheans.dm b/code/modules/mob/living/carbon/human/species/station/prometheans.dm
index 7e5f579592a..06ef4270d29 100644
--- a/code/modules/mob/living/carbon/human/species/station/prometheans.dm
+++ b/code/modules/mob/living/carbon/human/species/station/prometheans.dm
@@ -61,7 +61,7 @@ var/datum/species/shapeshifter/promethean/prometheans
item_slowdown_mod = 1.33
throwforce_absorb_threshold = 10
- chem_strength_alcohol = 2
+ chem_strength_alcohol = 0.5
cloning_modifier = /datum/modifier/cloning_sickness/promethean
diff --git a/code/modules/mob/living/carbon/human/species/station/station.dm b/code/modules/mob/living/carbon/human/species/station/station.dm
index 6fda9ef6804..43b199f96ae 100644
--- a/code/modules/mob/living/carbon/human/species/station/station.dm
+++ b/code/modules/mob/living/carbon/human/species/station/station.dm
@@ -71,7 +71,7 @@
name_language = LANGUAGE_UNATHI
species_language = LANGUAGE_UNATHI
health_hud_intensity = 2.5
- chem_strength_alcohol = 0.75
+ chem_strength_alcohol = 1.25
throwforce_absorb_threshold = 10
digi_allowed = TRUE
@@ -198,7 +198,7 @@
name_language = LANGUAGE_SIIK
species_language = LANGUAGE_SIIK
health_hud_intensity = 2.5
- chem_strength_alcohol = 1.25
+ chem_strength_alcohol = 0.75
digi_allowed = TRUE
min_age = 17
@@ -303,7 +303,7 @@
species_language = LANGUAGE_SKRELLIAN
assisted_langs = list(LANGUAGE_EAL, LANGUAGE_ROOTLOCAL, LANGUAGE_ROOTGLOBAL, LANGUAGE_VOX, LANGUAGE_PROMETHEAN)
health_hud_intensity = 2
- chem_strength_alcohol = 5
+ chem_strength_alcohol = 0.2
water_movement = -3
@@ -522,7 +522,7 @@
show_ssd = "completely quiescent"
health_hud_intensity = 2.5
item_slowdown_mod = 0.1
- chem_strength_alcohol = 0
+ chem_strength_alcohol = 10000 //a little hacky, maybe? but whatever. nobody plays diona anyway.
throwforce_absorb_threshold = 5
num_alternate_languages = 3
diff --git a/code/modules/mob/living/carbon/human/species/station/station_special_abilities_vr.dm b/code/modules/mob/living/carbon/human/species/station/station_special_abilities_vr.dm
index f4b80235620..93897421cac 100644
--- a/code/modules/mob/living/carbon/human/species/station/station_special_abilities_vr.dm
+++ b/code/modules/mob/living/carbon/human/species/station/station_special_abilities_vr.dm
@@ -1529,3 +1529,145 @@
if(Adjacent(target)) //We leapt at them but we didn't manage to hit them, let's see if we're next to them
target.Weaken(2) //get knocked down, idiot
+
+
+/mob/living/proc/injection() // Allows the user to inject reagents into others somehow, like stinging, or biting.
+ set name = "Injection"
+ set category = "Abilities"
+ set desc = "Inject another being with something!"
+
+ if(stat || paralysis || weakened || stunned || world.time < last_special) //Epic copypasta from tongue grabbing.
+ to_chat(src, "You can't do that in your current state.")
+ return
+
+ last_special = world.time + 10 //Anti-spam.
+
+ var/list/choices = list("Inject")
+
+ if(trait_injection_reagents.len > 1) //Should never happen, but who knows!
+ choices += "Change reagent"
+ else if(!trait_injection_selected)
+ trait_injection_selected = trait_injection_reagents[1]
+
+ choices += "Change amount"
+ choices += "Change verb"
+ choices += "Chemical Refresher"
+
+ var/choice = tgui_alert(src, "Do you wish to inject somebody, or adjust settings?", "Selection List", choices)
+
+ if(choice == "Change reagent")
+ var/reagent_choice = tgui_input_list(usr, "Choose which reagent to inject!", "Select reagent", trait_injection_reagents)
+ if(reagent_choice)
+ trait_injection_selected = reagent_choice
+ to_chat(src, "You prepare to inject [trait_injection_amount] units of [trait_injection_selected ? "[trait_injection_selected]" : "...nothing. Select a reagent before trying to inject anything."]")
+ return
+ if(choice == "Change amount")
+ var/amount_choice = tgui_input_number(usr, "How much of the reagent do you want to inject? (Up to 5 units) (Can select 0 for a bite that doesn't inject venom!)", "How much?", trait_injection_amount, 5, 0)
+ if(amount_choice >= 0)
+ trait_injection_amount = amount_choice
+ to_chat(src, "You prepare to inject [trait_injection_amount] units of [trait_injection_selected ? "[trait_injection_selected]" : "...nothing. Select a reagent before trying to inject anything."]")
+ return
+ if(choice == "Change verb")
+ var/verb_choice = tgui_input_text(usr, "Choose the percieved manner of injection, such as 'bites' or 'stings', don't be misleading or abusive. This will show up in game as ('X' 'Verb' 'Y'. Example: X bites Y.)", "How are you injecting?", trait_injection_verb, max_length = 60) //Whoaa there cowboy don't put a novel in there.
+ if(verb_choice)
+ trait_injection_verb = verb_choice
+ to_chat(src, "You will [trait_injection_verb] your targets.")
+ return
+ if(choice == "Chemical Refresher")
+ var/output = {"Chemical Refresher!
+ Options for venoms
+
+ Size Chemicals
+ Microcillin: Will make someone shrink.
+ Macrocillin: Will make someone grow.
+ Normalcillin: Will make someone normal size.
+ Note: 1 unit = 100% size diff. 0.01 unit = 1% size diff.
+ Note: Normacillin stops at 100% size.
+
+ Gender Chemicals
+ Androrovir: Will transform someone's sex to male.
+ Gynorovir: Will transform someone's sex to female.
+ Androgynorovir: Will transform someone's sex to plural.
+
+ Special Chemicals
+ Stoxin: Will make someone drowsy.
+ Rainbow Toxin: Will make someone see rainbows.
+ Paralysis Toxin: Will make someone paralyzed.
+ Numbing Enzyme: Will make someone unable to feel pain.
+ Pain Enzyme: Will make someone feel amplified pain.
+
+ Side Notes
+ You can select a value of 0 to inject nothing!
+ Overdose threshold for most chemicals is 30 units.
+ Exceptions to OD is: (Numbing Enzyme:20)
+ You can also bite synthetics, but due to how synths work, they won't have anything injected into them.
+
+ "}
+ usr << browse(output,"window=chemicalrefresher")
+ return
+ else
+ var/list/targets = list() //IF IT IS NOT BROKEN. DO NOT FIX IT. AND KEEP COPYPASTING IT (Pointing Rick Dalton: "That's my code!" ~CL)
+
+ for(var/mob/living/carbon/L in living_mobs(1, TRUE)) //Noncarbons don't even process reagents so don't bother listing others.
+ if(!istype(L, /mob/living/carbon))
+ continue
+ if(L == src) //no getting high off your own supply, get a nif or something, nerd.
+ continue
+ if(!L.resizable && (trait_injection_selected == "macrocillin" || trait_injection_selected == "microcillin" || trait_injection_selected == "normalcillin")) // If you're using a size reagent, ignore those with pref conflicts.
+ continue
+ if(!L.allow_spontaneous_tf && (trait_injection_selected == "androrovir" || trait_injection_selected == "gynorovir" || trait_injection_selected == "androgynorovir")) // If you're using a TF reagent, ignore those with pref conflicts.
+ continue
+ targets += L
+
+ if(!(targets.len))
+ to_chat(src, "No eligible targets found.")
+ return
+
+ var/mob/living/target = tgui_input_list(src, "Please select a target.", "Victim", targets)
+
+ if(!target)
+ return
+
+ if(!istype(target, /mob/living/carbon)) //Safety.
+ to_chat(src, "That won't work on that kind of creature! (Only works on crew/monkeys)")
+ return
+
+
+ var/synth = 0
+ if(target.isSynthetic())
+ synth = 1
+
+ if(!trait_injection_selected)
+ to_chat(src, "You need to select a reagent.")
+ return
+
+ if(!trait_injection_verb)
+ to_chat(src, "Somehow, you forgot your means of injecting. (Select a verb!)")
+ return
+
+ if(do_after(src, 50, target)) //A decent enough timer.
+ add_attack_logs(src,target,"Injection trait ([trait_injection_selected], [trait_injection_amount])")
+ if(target.reagents && (trait_injection_amount > 0) && !synth)
+ target.reagents.add_reagent(trait_injection_selected, trait_injection_amount)
+ var/ourmsg = "[usr] [trait_injection_verb] [target] "
+ switch(zone_sel.selecting)
+ if(BP_HEAD)
+ ourmsg += "on the head!"
+ if(BP_TORSO)
+ ourmsg += "on the chest!"
+ if(BP_GROIN)
+ ourmsg += "on the groin!"
+ if(BP_R_ARM, BP_L_ARM)
+ ourmsg += "on the arm!"
+ if(BP_R_HAND, BP_L_HAND)
+ ourmsg += "on the hand!"
+ if(BP_R_LEG, BP_L_LEG)
+ ourmsg += "on the leg!"
+ if(BP_R_FOOT, BP_L_FOOT)
+ ourmsg += "on the foot!"
+ if("mouth")
+ ourmsg += "on the mouth!"
+ if("eyes")
+ ourmsg += "on the eyes!"
+ ourmsg += ""
+ visible_message(ourmsg)
diff --git a/code/modules/mob/living/carbon/human/species/station/traits_vr/neutral.dm b/code/modules/mob/living/carbon/human/species/station/traits_vr/neutral.dm
index 89a17e9a91c..2575d52dcfa 100644
--- a/code/modules/mob/living/carbon/human/species/station/traits_vr/neutral.dm
+++ b/code/modules/mob/living/carbon/human/species/station/traits_vr/neutral.dm
@@ -143,6 +143,45 @@
H.verbs |= /mob/living/carbon/human/proc/succubus_drain_finalize
H.verbs |= /mob/living/carbon/human/proc/succubus_drain_lethal
+/datum/trait/neutral/venom_bite
+ name = "Venomous Injection"
+ desc = "Allows for injecting prey through one method or another to inject them with a variety of chemicals with varying effects!"
+ tutorial = "This trait allows you to bite prey with varying effects! \
+ Options for venoms: \
+ =====Size Chemicals ===== \
+ Microcillin: Will make someone shrink. (This is 1% per 0.01 units. So 1 unit = 100% size change) \
+ Macrocillin: Will make someone grow. (This is 1% per 0.01 units. So 1 unit = 100% size change) \
+ Normalcillin: Will make someone normal size. (This is 1% per 0.01 units. So 1 unit = 100% size change) Stops at 100% size. \
+ ===== Gender Chemicals ===== \
+ Androrovir: Will transform someone's sex to male. \
+ Gynorovir: Will transform someone's sex to female. \
+ Androgynorovir: Will transform someone's sex to pleural. \
+ ===== Special Chemicals ===== \
+ Stoxin: Will make someone drowsy. \
+ Rainbow Toxin: Will make someone see rainbows. \
+ Paralysis Toxin: Will make someone paralyzed. \
+ Numbing Enzyme: Will make someone unable to feel pain. \
+ Pain Enzyme: Will make someone feel pain, amplifieed \
+ ===== Side Notes ===== \
+ You aren't required to inject anything if you prefer to just use it as a normal bite!"
+ cost = 0
+ custom_only = FALSE
+
+/datum/trait/neutral/venom_bite/apply(var/datum/species/S,var/mob/living/carbon/human/H)
+ ..()
+ H.verbs |= /mob/living/proc/injection
+ H.trait_injection_reagents += "microcillin" // get small
+ H.trait_injection_reagents += "macrocillin" // get BIG
+ H.trait_injection_reagents += "normalcillin" // normal
+ H.trait_injection_reagents += "numbenzyme" // no feelings
+ H.trait_injection_reagents += "androrovir" // -> MALE
+ H.trait_injection_reagents += "gynorovir" // -> FEMALE
+ H.trait_injection_reagents += "androgynorovir" // -> PLURAL
+ H.trait_injection_reagents += "stoxin" // night night chem
+ H.trait_injection_reagents += "rainbowtoxin" // Funny flashing lights.
+ H.trait_injection_reagents += "paralysistoxin" // Paralysis!
+ H.trait_injection_reagents += "painenzyme" // Pain INCREASER
+
/datum/trait/neutral/long_vore
name = "Long Predatorial Reach"
desc = "Makes you able to use an unspecified appendage to grab creatures."
@@ -482,21 +521,21 @@
desc = "The only way you can hold a drink is if it's in your own two hands, and even then you'd best not inhale too deeply near it. Alcohol hits you three times as hard as they do other people."
cost = 0
custom_only = FALSE
- var_changes = list("chem_strength_alcohol" = 3)
+ var_changes = list("chem_strength_alcohol" = 0.33)
/datum/trait/neutral/alcohol_intolerance_basic
name = "Liver of Lilies"
desc = "You have a hard time with alcohol. Maybe you just never took to it, or maybe it doesn't agree with your system... either way, alcohol hits you twice as hard."
cost = 0
custom_only = FALSE
- var_changes = list("chem_strength_alcohol" = 2)
+ var_changes = list("chem_strength_alcohol" = 0.5)
/datum/trait/neutral/alcohol_intolerance_slight
name = "Liver of Tulips"
desc = "You are what some might call 'a bit of a lightweight', but you can still keep your drinks down... most of the time. Alcohol hits you fifty percent harder."
cost = 0
custom_only = FALSE
- var_changes = list("chem_strength_alcohol" = 1.5)
+ var_changes = list("chem_strength_alcohol" = 0.75)
/datum/trait/neutral/alcohol_tolerance_reset
name = "Liver of Unremarkableness"
@@ -511,21 +550,21 @@
desc = "You can hold drinks much better than those lily-livered land-lubbers! Arr! Alcohol's effects on you are reduced by about a quarter."
cost = 0
custom_only = FALSE
- var_changes = list("chem_strength_alcohol" = 0.75)
+ var_changes = list("chem_strength_alcohol" = 1.25)
/datum/trait/neutral/alcohol_tolerance_advanced
name = "Liver of Steel"
desc = "Drinks tremble before your might! You can hold your alcohol twice as well as those blue-bellied barnacle boilers! Alcohol has just half the effect on you as it does on others."
cost = 0
custom_only = FALSE
- var_changes = list("chem_strength_alcohol" = 0.5)
+ var_changes = list("chem_strength_alcohol" = 2)
/datum/trait/neutral/alcohol_immunity
name = "Liver of Durasteel"
desc = "You've drunk so much that most booze doesn't even faze you. It takes something like a Pan-Galactic or a pint of Deathbell for you to even get slightly buzzed."
cost = 0
custom_only = FALSE
- var_changes = list("chem_strength_alcohol" = 0.25)
+ var_changes = list("chem_strength_alcohol" = 4)
// Alcohol Traits End Here.
/datum/trait/neutral/colorblind/mono
diff --git a/code/modules/mob/living/carbon/human/update_icons.dm b/code/modules/mob/living/carbon/human/update_icons.dm
index 804d9ad78bd..3a1441f334a 100644
--- a/code/modules/mob/living/carbon/human/update_icons.dm
+++ b/code/modules/mob/living/carbon/human/update_icons.dm
@@ -97,7 +97,10 @@ var/global/list/damage_icon_parts = list() //see UpdateDamageIcon()
#define FIRE_LAYER 35 //'Mob on fire' overlay layer
// # define MOB_WATER_LAYER 36 //'Mob submerged' overlay layer // Moved to global defines
#define TARGETED_LAYER 37 //'Aimed at' overlay layer
-#define TOTAL_LAYERS 37 //VOREStation edit. <---- KEEP THIS UPDATED, should always equal the highest number here, used to initialize a list.
+#define VORE_BELLY_LAYER 38
+#define VORE_TAIL_LAYER 39
+
+#define TOTAL_LAYERS 39 //VOREStation edit. <---- KEEP THIS UPDATED, should always equal the highest number here, used to initialize a list.
//////////////////////////////////
/mob/living/carbon/human
@@ -421,6 +424,8 @@ var/global/list/damage_icon_parts = list() //see UpdateDamageIcon()
//tail
update_tail_showing()
update_wing_showing()
+ update_vore_belly_sprite()
+ update_vore_tail_sprite()
/mob/living/carbon/human/proc/update_skin()
if(QDESTROYING(src))
@@ -1060,7 +1065,7 @@ var/global/list/damage_icon_parts = list() //see UpdateDamageIcon()
apply_layer(tail_layer)
return
- var/species_tail = species.get_tail(src) // Species tail icon_state prefix.
+ var/species_tail = species?.get_tail(src) // Species tail icon_state prefix.
//This one is actually not that bad I guess.
if(species_tail && !(wear_suit && wear_suit.flags_inv & HIDETAIL))
@@ -1393,6 +1398,74 @@ var/global/list/damage_icon_parts = list() //see UpdateDamageIcon()
if((. = ..()))
update_wing_showing()
+/mob/living/carbon/human/proc/update_vore_belly_sprite()
+ if(QDESTROYING(src))
+ return
+
+ remove_layer(VORE_BELLY_LAYER)
+
+ var/image/vore_belly_image = get_vore_belly_image()
+ if(vore_belly_image)
+ vore_belly_image.layer = BODY_LAYER+VORE_BELLY_LAYER
+ overlays_standing[VORE_BELLY_LAYER] = vore_belly_image
+ vore_belly_image.plane = PLANE_CH_STOMACH //This one line of code. This ONE LINE OF CODE TOOK 6 HOURS TO FIGURE OUT. THANK YOU REDCAT.
+
+ apply_layer(VORE_BELLY_LAYER)
+
+/mob/living/carbon/human/proc/get_vore_belly_image()
+ if(!(wear_suit && wear_suit.flags_inv & HIDETAIL))
+ var/vs_fullness = vore_fullness_ex["stomach"]
+ var/icon/vorebelly_s = new/icon(icon = 'icons/mob/vore/Bellies.dmi', icon_state = "[species.vore_belly_default_variant]Belly[vs_fullness][struggle_anim_stomach ? "" : " idle"]")
+ vorebelly_s.Blend(vore_sprite_color["stomach"], vore_sprite_multiply["stomach"] ? ICON_MULTIPLY : ICON_ADD)
+ var/image/working = image(vorebelly_s)
+ working.overlays += em_block_image_generic(working)
+ return working
+ return null
+
+/mob/living/carbon/human/proc/vore_belly_animation()
+ if(!struggle_anim_stomach)
+ struggle_anim_stomach = TRUE
+ update_vore_belly_sprite()
+ spawn(12)
+ struggle_anim_stomach = FALSE
+ update_vore_belly_sprite()
+
+/mob/living/carbon/human/proc/update_vore_tail_sprite()
+ if(QDESTROYING(src))
+ return
+
+ remove_layer(VORE_TAIL_LAYER)
+
+ var/image/vore_tail_image = get_vore_tail_image()
+ if(vore_tail_image)
+ vore_tail_image.layer = BODY_LAYER+VORE_TAIL_LAYER
+ overlays_standing[VORE_TAIL_LAYER] = vore_tail_image
+ vore_tail_image.plane = PLANE_CH_STOMACH //This one line of code. This ONE LINE OF CODE TOOK 6 HOURS TO FIGURE OUT. THANK YOU REDCAT.
+
+ apply_layer(VORE_TAIL_LAYER)
+
+/mob/living/carbon/human/proc/get_vore_tail_image()
+ if(tail_style && istaurtail(tail_style) && tail_style:vore_tail_sprite_variant)
+ var/vs_fullness = vore_fullness_ex["taur belly"]
+ var/loaf_alt = lying && tail_style:belly_variant_when_loaf
+ var/fullness_icons = min(tail_style.fullness_icons, vs_fullness)
+ var/icon/vorebelly_s = new/icon(icon = tail_style.bellies_icon_path, icon_state = "Taur[tail_style:vore_tail_sprite_variant]-Belly-[fullness_icons][loaf_alt ? " loaf" : (struggle_anim_taur ? "" : " idle")]")
+ vorebelly_s.Blend(vore_sprite_color["taur belly"], vore_sprite_multiply["taur belly"] ? ICON_MULTIPLY : ICON_ADD)
+ var/image/working = image(vorebelly_s)
+ working.pixel_x = -16
+ if(tail_style.em_block)
+ working.overlays += em_block_image_generic(working)
+ return working
+ return null
+
+/mob/living/carbon/human/proc/vore_tail_animation()
+ if(tail_style.struggle_anim && !struggle_anim_taur)
+ struggle_anim_taur = TRUE
+ update_vore_tail_sprite()
+ spawn(12)
+ struggle_anim_taur = FALSE
+ update_vore_tail_sprite()
+
//Human Overlays Indexes/////////
#undef MUTATIONS_LAYER
#undef SKIN_LAYER
diff --git a/code/modules/mob/living/silicon/robot/analyzer.dm b/code/modules/mob/living/silicon/robot/analyzer.dm
index 9e6fbdb2d2c..c7476f2ab71 100644
--- a/code/modules/mob/living/silicon/robot/analyzer.dm
+++ b/code/modules/mob/living/silicon/robot/analyzer.dm
@@ -56,8 +56,8 @@
user.show_message("\t Damage Specifics: [span_orange("[BU]")] - [span_red("[BR]")]")
if(M.tod && M.stat == DEAD)
user.show_message(span_blue("Time of Disable: [M.tod]"))
- var/mob/living/silicon/robot/H = M
- var/obj/item/weapon/cell/cell = H.get_cell()
+ var/mob/living/silicon/robot/R = M
+ var/obj/item/weapon/cell/cell = R.get_cell()
if(cell)
var/cell_charge = round(cell.percent())
var/cell_text
@@ -72,7 +72,7 @@
else
cell_text = span_red("[cell_charge]")
user.show_message("\t Power Cell Status: [span_blue("[capitalize(cell.name)]")] at [cell_text]% charge")
- var/list/damaged = H.get_damaged_components(1,1,1)
+ var/list/damaged = R.get_damaged_components(1,1,1)
user.show_message(span_blue("Localized Damage:"),1)
if(length(damaged)>0)
for(var/datum/robot_component/org in damaged)
@@ -85,12 +85,12 @@
(org.powered) ? "Power ON" : "[span_red("Power OFF")]")),1)
else
user.show_message(span_blue("\t Components are OK."),1)
- if(H.emagged && prob(5))
+ if(R.emagged && prob(5))
user.show_message(span_red("\t ERROR: INTERNAL SYSTEMS COMPROMISED"),1)
user.show_message(span_blue("Operating Temperature: [M.bodytemperature-T0C]°C ([M.bodytemperature*1.8-459.67]°F)"), 1)
else
- var/mob/living/silicon/robot/H = M
- var/obj/item/weapon/cell/cell = H.get_cell()
+ var/mob/living/silicon/robot/R = M
+ var/obj/item/weapon/cell/cell = R.get_cell()
user.show_message(span_blue("Upgrade Analyzing Results for [M]:"))
if(cell)
user.show_message("\t Power Cell Details: [span_blue("[capitalize(cell.name)]")] with a capacity of [cell.maxcharge] at [round(cell.percent())]% charge")
@@ -98,12 +98,12 @@
for(var/datum/design/item/prosfab/robot_upgrade/utility/upgrade)
var/obj/item/borg/upgrade/utility/upgrade_type = initial(upgrade.build_path)
var/needs_module = initial(upgrade_type.require_module)
- if((!H.module && needs_module) || !initial(upgrade.name) || (H.stat != DEAD && initial(upgrade.name) == "Emergency Restart Module"))
+ if((!R.module && needs_module) || !initial(upgrade.name) || (R.stat != DEAD && (upgrade_type == /obj/item/borg/upgrade/utility/restart)) || (isshell(R) && (upgrade_type == /obj/item/borg/upgrade/utility/rename)))
continue
if(show_title)
user.show_message("\t Utility Modules, used for modifying purposes:")
show_title = FALSE
- if(H.stat == DEAD)
+ if(R.stat == DEAD)
if(initial(upgrade.name) == "Emergency Restart Module")
user.show_message(span_blue("\t\t [capitalize(initial(upgrade.name))]: [span_green("Usable")]"))
else
@@ -112,55 +112,55 @@
for(var/datum/design/item/prosfab/robot_upgrade/basic/upgrade)
var/obj/item/borg/upgrade/basic/upgrade_type = initial(upgrade.build_path)
var/needs_module = initial(upgrade_type.require_module)
- if((!H.module && needs_module) || !initial(upgrade.name) || H.stat == DEAD)
+ if((!R.module && needs_module) || !initial(upgrade.name) || R.stat == DEAD)
continue
if(show_title)
user.show_message("\t Basic Modules, used for direct upgrade purposes:")
show_title = FALSE
- if(H.has_basic_upgrade(initial(upgrade.build_path)) == "")
+ if(R.has_basic_upgrade(initial(upgrade.build_path)) == "")
user.show_message(span_blue("\t\t [capitalize(initial(upgrade.name))]: [span_red("ERROR")]"))
else
- user.show_message(span_blue("\t\t [capitalize(initial(upgrade.name))]: [H.has_basic_upgrade(initial(upgrade.build_path)) ? span_green("Installed") : span_red("Missing")]"))
+ user.show_message(span_blue("\t\t [capitalize(initial(upgrade.name))]: [R.has_basic_upgrade(initial(upgrade.build_path)) ? span_green("Installed") : span_red("Missing")]"))
show_title = TRUE
for(var/datum/design/item/prosfab/robot_upgrade/advanced/upgrade)
var/obj/item/borg/upgrade/advanced/upgrade_type = initial(upgrade.build_path)
var/needs_module = initial(upgrade_type.require_module)
- if((!H.module && needs_module) || !initial(upgrade.name) || H.stat == DEAD)
+ if((!R.module && needs_module) || !initial(upgrade.name) || R.stat == DEAD)
continue
if(show_title)
user.show_message("\t Advanced Modules, used for module upgrade purposes:")
show_title = FALSE
- if(H.has_advanced_upgrade(initial(upgrade.build_path)) == "")
+ if(R.has_advanced_upgrade(initial(upgrade.build_path)) == "")
user.show_message(span_blue("\t\t [capitalize(initial(upgrade.name))]: [span_red("ERROR")]"))
else
- user.show_message(span_blue("\t\t [capitalize(initial(upgrade.name))]: [H.has_advanced_upgrade(initial(upgrade.build_path)) ? span_green("Installed") : span_red("Missing")]"))
+ user.show_message(span_blue("\t\t [capitalize(initial(upgrade.name))]: [R.has_advanced_upgrade(initial(upgrade.build_path)) ? span_green("Installed") : span_red("Missing")]"))
show_title = TRUE
for(var/datum/design/item/prosfab/robot_upgrade/restricted/upgrade)
var/obj/item/borg/upgrade/restricted/upgrade_type = initial(upgrade.build_path)
var/needs_module = initial(upgrade_type.require_module)
- if((!H.module && needs_module) || !initial(upgrade.name) || !H.supports_upgrade(initial(upgrade.build_path)) || H.stat == DEAD)
+ if((!R.module && needs_module) || !initial(upgrade.name) || !R.supports_upgrade(initial(upgrade.build_path)) || R.stat == DEAD)
continue
if(show_title)
user.show_message("\t Restricted Modules, used for module upgrade purposes on specific chassis:")
show_title = FALSE
- if(H.has_restricted_upgrade(initial(upgrade.build_path)) == "")
+ if(R.has_restricted_upgrade(initial(upgrade.build_path)) == "")
user.show_message(span_blue("\t\t [capitalize(initial(upgrade.name))]: [span_red("ERROR")]"))
else
- user.show_message(span_blue("\t\t [capitalize(initial(upgrade.name))]: [H.has_restricted_upgrade(initial(upgrade.build_path)) ? span_green("Installed") : span_red("Missing")]"))
+ user.show_message(span_blue("\t\t [capitalize(initial(upgrade.name))]: [R.has_restricted_upgrade(initial(upgrade.build_path)) ? span_green("Installed") : span_red("Missing")]"))
show_title = TRUE
for(var/datum/design/item/prosfab/robot_upgrade/no_prod/upgrade)
var/obj/item/borg/upgrade/no_prod/upgrade_type = initial(upgrade.build_path)
var/needs_module = initial(upgrade_type.require_module)
var/hidden = initial(upgrade_type.hidden_from_scan)
- if((!H.module && needs_module) || !initial(upgrade.name) || hidden || H.stat == DEAD)
+ if((!R.module && needs_module) || !initial(upgrade.name) || hidden || R.stat == DEAD)
continue
if(show_title)
user.show_message("\t Special Modules, used for recreation purposes:")
show_title = FALSE
- if(H.has_no_prod_upgrade(initial(upgrade.build_path)) == "")
+ if(R.has_no_prod_upgrade(initial(upgrade.build_path)) == "")
user.show_message(span_blue("\t\t [capitalize(initial(upgrade.name))]: [span_red("ERROR")]"))
else
- user.show_message(span_blue("\t\t [capitalize(initial(upgrade.name))]: [H.has_no_prod_upgrade(initial(upgrade.build_path)) ? span_green("Installed") : span_red("Missing")]"))
+ user.show_message(span_blue("\t\t [capitalize(initial(upgrade.name))]: [R.has_no_prod_upgrade(initial(upgrade.build_path)) ? span_green("Installed") : span_red("Missing")]"))
if("prosthetics")
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 2b8d37c7ae5..fd092f30039 100644
--- a/code/modules/mob/living/silicon/robot/drone/drone_console.dm
+++ b/code/modules/mob/living/silicon/robot/drone/drone_console.dm
@@ -42,7 +42,7 @@
//VOREStation Edit - multiz lol
if(D.foreign_droid)
continue
-
+
drones.Add(list(list(
"name" = D.real_name,
"active" = D.stat != 2,
@@ -56,7 +56,10 @@
data["fabricator"] = dronefab
data["fabPower"] = dronefab?.produce_drones
- data["areas"] = GLOB.tagger_locations
+ var/list/areas = list()
+ for(var/area in GLOB.tagger_locations)
+ areas += area
+ data["areas"] = areas
data["selected_area"] = "[drone_call_area]"
return data
diff --git a/code/modules/mob/living/silicon/robot/robot.dm b/code/modules/mob/living/silicon/robot/robot.dm
index 695a3dd6310..6d850186ba6 100644
--- a/code/modules/mob/living/silicon/robot/robot.dm
+++ b/code/modules/mob/living/silicon/robot/robot.dm
@@ -427,7 +427,7 @@
set category = "Robot Commands"
if(custom_name)
- to_chat(usr, "You can't pick another custom name. Go ask for a name change.")
+ to_chat(usr, "You can't pick another custom name. [isshell(src) ? "" : "Go ask for a name change."]")
return 0
spawn(0)
diff --git a/code/modules/mob/living/simple_mob/subtypes/glamour/homunculus.dm b/code/modules/mob/living/simple_mob/subtypes/glamour/homunculus.dm
new file mode 100644
index 00000000000..3a65edf7b6f
--- /dev/null
+++ b/code/modules/mob/living/simple_mob/subtypes/glamour/homunculus.dm
@@ -0,0 +1,35 @@
+/mob/living/simple_mob/homunculus
+ name = "homunculus"
+ desc = "A strange misshapen humanoid creature made purely from glamour!"
+
+ icon_dead = "homunculus"
+ icon_living = "homunculus"
+ icon_state = "homunculus"
+ icon = 'icons/mob/vore.dmi'
+
+ ai_holder_type = /datum/ai_holder/simple_mob/passive
+
+ min_oxy = 0
+ max_oxy = 0
+ min_tox = 0
+ max_tox = 0
+ min_co2 = 0
+ max_co2 = 0
+ min_n2 = 0
+ max_n2 = 0
+ minbodytemp = 0
+
+ var/owner
+
+/mob/living/simple_mob/homunculus/death()
+ if(owner)
+ var/obj/item/glamour_face/O = owner
+ O.homunculus = 0
+ qdel(src)
+
+/mob/living/simple_mob/homunculus/update_icon()
+ return
+
+/mob/living/simple_mob/homunculus/update_icons()
+ return
+
diff --git a/code/modules/mob/mob_defines_vr.dm b/code/modules/mob/mob_defines_vr.dm
index 619cfe59403..0eaed29131d 100644
--- a/code/modules/mob/mob_defines_vr.dm
+++ b/code/modules/mob/mob_defines_vr.dm
@@ -1,5 +1,6 @@
/mob
var/vantag_hud = 0 // Do I have the HUD enabled?
+ var/stomach_vision = 1 // By default, you will see stomachs.
var/mob/temporary_form // For holding onto a temporary form
var/disconnect_time = null //Time of client loss, set by Logout(), for timekeeping
diff --git a/code/modules/mob/mob_helpers_vr.dm b/code/modules/mob/mob_helpers_vr.dm
index 663cfc7f4a2..81ae7da305f 100644
--- a/code/modules/mob/mob_helpers_vr.dm
+++ b/code/modules/mob/mob_helpers_vr.dm
@@ -1,5 +1,13 @@
/mob/recalculate_vis()
. = ..()
+
+ if(stomach_vision && !(VIS_CH_STOMACH in vis_enabled))
+ plane_holder.set_vis(VIS_CH_STOMACH,TRUE)
+ vis_enabled += VIS_CH_STOMACH
+ else if(!stomach_vision && (VIS_CH_STOMACH in vis_enabled))
+ plane_holder.set_vis(VIS_CH_STOMACH,FALSE)
+ vis_enabled -= VIS_CH_STOMACH
+
if(!plane_holder || !vis_enabled)
return
@@ -12,3 +20,33 @@
plane_holder.set_vis(VIS_CH_VANTAG,FALSE)
vis_enabled -= VIS_CH_VANTAG
return
+
+
+/mob/verb/toggle_stomach_vision()
+ set name = "Toggle Stomach Sprites"
+ set category = "Preferences"
+ set desc = "Toggle the ability to see stomachs or not"
+
+ var/toggle
+ toggle = tgui_alert(src, "Would you like to see visible stomachs?", "Visible Tummy?", list("Yes", "No"))
+ if(toggle =="Yes")
+ stomach_vision = 1 //Simple! Easy!
+ if(!(VIS_CH_STOMACH in vis_enabled))
+ plane_holder.set_vis(VIS_CH_STOMACH,TRUE)
+ vis_enabled += VIS_CH_STOMACH
+ to_chat("You can now see stomachs!")
+ else
+ stomach_vision = 0
+ if(VIS_CH_STOMACH in vis_enabled)
+ plane_holder.set_vis(VIS_CH_STOMACH,FALSE)
+ vis_enabled -= VIS_CH_STOMACH
+ to_chat("You will no longer see stomachs!")
+
+/* //Leaving this in as an example of 'how to properly enable a plane to hide/show itself' for future PRs.
+if(stomach_vision && !(VIS_CH_STOMACH in vis_enabled))
+ plane_holder.set_vis(VIS_CH_STOMACH,TRUE)
+ vis_enabled += VIS_CH_STOMACH
+else if(!stomach_vision && (VIS_CH_STOMACH in vis_enabled))
+ plane_holder.set_vis(VIS_CH_STOMACH,FALSE)
+ vis_enabled -= VIS_CH_STOMACH
+*/
diff --git a/code/modules/mob/mob_planes_vr.dm b/code/modules/mob/mob_planes_vr.dm
index b23067733f3..9f5b30f5793 100644
--- a/code/modules/mob/mob_planes_vr.dm
+++ b/code/modules/mob/mob_planes_vr.dm
@@ -4,6 +4,7 @@
plane_masters[VIS_CH_HEALTH_VR] = new /obj/screen/plane_master{plane = PLANE_CH_HEALTH_VR} //Health bar but transparent at 100
plane_masters[VIS_CH_BACKUP] = new /obj/screen/plane_master{plane = PLANE_CH_BACKUP} //Backup implant status
plane_masters[VIS_CH_VANTAG] = new /obj/screen/plane_master{plane = PLANE_CH_VANTAG} //Vore Antags
+ plane_masters[VIS_CH_STOMACH] = new /obj/screen/plane_master{plane = PLANE_CH_STOMACH} //Stomach
plane_masters[VIS_AUGMENTED] = new /obj/screen/plane_master/augmented(M = my_mob) //Augmented reality
..()
diff --git a/code/modules/mob/new_player/sprite_accessories_ear_vr.dm b/code/modules/mob/new_player/sprite_accessories_ear_vr.dm
index 4f62456cd20..d2932c0b258 100644
--- a/code/modules/mob/new_player/sprite_accessories_ear_vr.dm
+++ b/code/modules/mob/new_player/sprite_accessories_ear_vr.dm
@@ -9,7 +9,7 @@
name = "You should not see this..."
icon = 'icons/mob/vore/ears_vr.dmi'
do_colouration = 0 // Set to 1 to blend (ICON_ADD) hair color
- species_allowed = list(SPECIES_HUMAN, SPECIES_SKRELL, SPECIES_UNATHI, SPECIES_TAJ, SPECIES_TESHARI, SPECIES_NEVREAN, SPECIES_AKULA, SPECIES_SERGAL, SPECIES_FENNEC, SPECIES_ZORREN_HIGH, SPECIES_VULPKANIN, SPECIES_XENOCHIMERA, SPECIES_XENOHYBRID, SPECIES_VASILISSAN, SPECIES_RAPALA, SPECIES_PROTEAN, SPECIES_ALRAUNE, SPECIES_WEREBEAST, SPECIES_SHADEKIN, SPECIES_SHADEKIN_CREW, SPECIES_ALTEVIAN) //This lets all races use
+ species_allowed = list(SPECIES_HUMAN, SPECIES_SKRELL, SPECIES_UNATHI, SPECIES_TAJ, SPECIES_TESHARI, SPECIES_NEVREAN, SPECIES_AKULA, SPECIES_SERGAL, SPECIES_FENNEC, SPECIES_ZORREN_HIGH, SPECIES_VULPKANIN, SPECIES_XENOCHIMERA, SPECIES_XENOHYBRID, SPECIES_VASILISSAN, SPECIES_RAPALA, SPECIES_PROTEAN, SPECIES_ALRAUNE, SPECIES_WEREBEAST, SPECIES_SHADEKIN, SPECIES_SHADEKIN_CREW, SPECIES_ALTEVIAN, SPECIES_LLEILL) //This lets all races use
color_blend_mode = ICON_ADD // Only appliciable if do_coloration = 1
// Species-unique ears
@@ -30,6 +30,17 @@
do_colouration = 1
extra_overlay = "shadekin-round-inner"
+/datum/sprite_accessory/ears/lleill
+ name = "Lleill Ears, colorable"
+ desc = ""
+ icon = 'icons/mob/vore/ears_32x64.dmi'
+ icon_state = "lleill"
+ do_colouration = 1
+ color_blend_mode = ICON_MULTIPLY
+ extra_overlay = "lleill-antlers"
+ species_allowed = list(SPECIES_LLEILL)
+
+
// Ears avaliable to anyone
/datum/sprite_accessory/ears/alien_slug
diff --git a/code/modules/mob/new_player/sprite_accessories_tail.dm b/code/modules/mob/new_player/sprite_accessories_tail.dm
index a4f1e457f5a..b1f6a930a73 100644
--- a/code/modules/mob/new_player/sprite_accessories_tail.dm
+++ b/code/modules/mob/new_player/sprite_accessories_tail.dm
@@ -1058,3 +1058,12 @@
extra_overlay2 = "zorgoia_fluff_top"
do_colouration = 1
color_blend_mode = ICON_MULTIPLY
+
+/datum/sprite_accessory/tail/lleill
+ name = "Lleill tail"
+ desc = ""
+ icon = 'icons/mob/species/lleill/tail.dmi'
+ icon_state = "tail"
+ do_colouration = 1
+ color_blend_mode = ICON_MULTIPLY
+ extra_overlay = "tail_marking"
diff --git a/code/modules/mob/new_player/sprite_accessories_tail_vr.dm b/code/modules/mob/new_player/sprite_accessories_tail_vr.dm
index b71cede427b..67f07f57b55 100644
--- a/code/modules/mob/new_player/sprite_accessories_tail_vr.dm
+++ b/code/modules/mob/new_player/sprite_accessories_tail_vr.dm
@@ -13,7 +13,7 @@
var/mob_offset_x = 0
var/mob_offset_y = 0
do_colouration = 0 //Set to 1 to enable coloration using the tail color.
- species_allowed = list(SPECIES_HUMAN, SPECIES_SKRELL, SPECIES_UNATHI, SPECIES_TAJ, SPECIES_TESHARI, SPECIES_NEVREAN, SPECIES_AKULA, SPECIES_SERGAL, SPECIES_FENNEC, SPECIES_ZORREN_HIGH, SPECIES_VULPKANIN, SPECIES_XENOCHIMERA, SPECIES_XENOHYBRID, SPECIES_VASILISSAN, SPECIES_RAPALA, SPECIES_PROTEAN, SPECIES_ALRAUNE, SPECIES_WEREBEAST, SPECIES_SHADEKIN, SPECIES_SHADEKIN_CREW, SPECIES_ALTEVIAN) //This lets all races use
+ species_allowed = list(SPECIES_HUMAN, SPECIES_SKRELL, SPECIES_UNATHI, SPECIES_TAJ, SPECIES_TESHARI, SPECIES_NEVREAN, SPECIES_AKULA, SPECIES_SERGAL, SPECIES_FENNEC, SPECIES_ZORREN_HIGH, SPECIES_VULPKANIN, SPECIES_XENOCHIMERA, SPECIES_XENOHYBRID, SPECIES_VASILISSAN, SPECIES_RAPALA, SPECIES_PROTEAN, SPECIES_ALRAUNE, SPECIES_WEREBEAST, SPECIES_SHADEKIN, SPECIES_SHADEKIN_CREW, SPECIES_ALTEVIAN, SPECIES_LLEILL) //This lets all races use
var/list/lower_layer_dirs = list(SOUTH)
var/icon_loaf = null
diff --git a/code/modules/mob/new_player/sprite_accessories_taur.dm b/code/modules/mob/new_player/sprite_accessories_taur.dm
index 0e052d9c50a..89a264c82e4 100644
--- a/code/modules/mob/new_player/sprite_accessories_taur.dm
+++ b/code/modules/mob/new_player/sprite_accessories_taur.dm
@@ -1,3 +1,10 @@
+/datum/sprite_accessory/tail
+ var/vore_tail_sprite_variant = ""
+ var/belly_variant_when_loaf = FALSE
+ var/fullness_icons = 0
+ var/struggle_anim = FALSE
+ var/bellies_icon_path = 'icons/mob/vore/Taur_Bellies.dmi'
+
/datum/riding/taur
keytype = /obj/item/weapon/material/twohanded/riding_crop // Crack!
nonhuman_key_exemption = FALSE // If true, nonhumans who can't hold keys don't need them, like borgs and simplemobs.
@@ -355,4 +362,70 @@
/datum/sprite_accessory/tail/taur/zorgoia/fat
name = "Zorgoia (Fat Taur)"
- extra_overlay = "zorgoia_fat"
\ No newline at end of file
+ extra_overlay = "zorgoia_fat"
+
+
+/datum/sprite_accessory/tail/taur/wolf
+ vore_tail_sprite_variant = "N"
+ fullness_icons = 3
+ struggle_anim = TRUE
+
+/datum/sprite_accessory/tail/taur/naga/naga_2c
+ vore_tail_sprite_variant = "Naga"
+ fullness_icons = 1
+ struggle_anim = TRUE
+
+/datum/sprite_accessory/tail/taur/horse
+ vore_tail_sprite_variant = "Horse"
+ fullness_icons = 1
+ struggle_anim = TRUE
+
+/datum/sprite_accessory/tail/taur/cow
+ vore_tail_sprite_variant = "Cow"
+ fullness_icons = 1
+ struggle_anim = TRUE
+
+/datum/sprite_accessory/tail/taur/lizard
+ vore_tail_sprite_variant = "Lizard"
+ fullness_icons = 1
+ struggle_anim = TRUE
+
+/datum/sprite_accessory/tail/taur/lizard/synthlizard
+ vore_tail_sprite_variant = "SynthLiz"
+ fullness_icons = 1
+ struggle_anim = TRUE
+
+/datum/sprite_accessory/tail/taur/feline
+ vore_tail_sprite_variant = "Feline"
+ belly_variant_when_loaf = TRUE
+ fullness_icons = 1
+ struggle_anim = TRUE
+
+/datum/sprite_accessory/tail/taur/slug
+ vore_tail_sprite_variant = "Slug"
+ fullness_icons = 1
+ struggle_anim = TRUE
+
+/datum/sprite_accessory/tail/taur/drake
+ vore_tail_sprite_variant = "Drake"
+ belly_variant_when_loaf = TRUE
+ fullness_icons = 1
+ struggle_anim = TRUE
+
+/datum/sprite_accessory/tail/taur/otie
+ vore_tail_sprite_variant = "Otie"
+ belly_variant_when_loaf = TRUE
+ fullness_icons = 1
+ struggle_anim = TRUE
+
+/datum/sprite_accessory/tail/taur/deer
+ vore_tail_sprite_variant = "Deer"
+ belly_variant_when_loaf = TRUE
+ fullness_icons = 1
+ struggle_anim = TRUE
+
+/datum/sprite_accessory/tail/taur/skunk
+ vore_tail_sprite_variant = "Skunk"
+ belly_variant_when_loaf = TRUE
+ fullness_icons = 1
+ struggle_anim = TRUE
diff --git a/code/modules/mob/new_player/sprite_accessories_taur_vr.dm b/code/modules/mob/new_player/sprite_accessories_taur_vr.dm
index 17e716fd4e5..2a85dda15f4 100644
--- a/code/modules/mob/new_player/sprite_accessories_taur_vr.dm
+++ b/code/modules/mob/new_player/sprite_accessories_taur_vr.dm
@@ -60,6 +60,9 @@
can_loaf = TRUE
icon_loaf = 'icons/mob/vore/taurs_vr_loaf.dmi'
loaf_offset = 4
+ vore_tail_sprite_variant = "N"
+ fullness_icons = 3
+ struggle_anim = TRUE
/datum/sprite_accessory/tail/taur/wolf/fatwolf
name = "Fat Wolf (Taur)"
@@ -131,6 +134,10 @@
can_loaf = TRUE
icon_loaf = 'icons/mob/vore/taurs_vr_loaf.dmi'
loaf_offset = 3
+ vore_tail_sprite_variant = "Skunk" //Sadly there appears to be no sprites... For now!
+ belly_variant_when_loaf = TRUE
+ fullness_icons = 1
+ struggle_anim = TRUE
/datum/sprite_accessory/tail/taur/naga
name = "Naga (Taur)"
@@ -165,34 +172,43 @@
msg_prey_stepunder = "You jump over %prey's thick tail."
msg_owner_stepunder = "%owner bounds over your tail."
-/datum/sprite_accessory/tail/taur/naga/naga_2c
+/datum/sprite_accessory/tail/taur/naga/vore_compatable
+ name = "Naga (Taur) (Vore Compatable)"
+ vore_tail_sprite_variant = "Naga"
+ fullness_icons = 1
+ struggle_anim = TRUE
+
+/datum/sprite_accessory/tail/taur/naga/vore_compatable/naga_2c
name = "Naga dual-color (Taur)"
icon_state = "naga_s"
extra_overlay = "naga_markings"
//icon_sprite_tag = "naga2c"
+ vore_tail_sprite_variant = "Naga"
+ fullness_icons = 1
+ struggle_anim = TRUE
-/datum/sprite_accessory/tail/taur/naga/alt_2c
+/datum/sprite_accessory/tail/taur/naga/vore_compatable/alt_2c
name = "Naga alt style dual-color (Taur)"
suit_sprites = 'icons/mob/taursuits_naga_alt_vr.dmi'
icon_state = "altnaga_s"
extra_overlay = "altnaga_markings"
//icon_sprite_tag = "altnaga2c"
-/datum/sprite_accessory/tail/taur/naga/alt_3c
+/datum/sprite_accessory/tail/taur/naga/vore_compatable/alt_3c
name = "Naga alt style tri-color (Taur)"
suit_sprites = 'icons/mob/taursuits_naga_alt_vr.dmi'
icon_state = "altnaga_s"
extra_overlay = "altnaga_markings"
extra_overlay2 = "altnaga_stripes"
-/datum/sprite_accessory/tail/taur/naga/alt_3c_rattler
+/datum/sprite_accessory/tail/taur/naga/vore_compatable/alt_3c_rattler
name = "Naga alt style tri-color, rattler (Taur)"
suit_sprites = 'icons/mob/taursuits_naga_alt_vr.dmi'
icon_state = "altnaga_s"
extra_overlay = "altnaga_markings"
extra_overlay2 = "altnaga_rattler"
-/datum/sprite_accessory/tail/taur/naga/alt_3c_tailmaw
+/datum/sprite_accessory/tail/taur/naga/vore_compatable/alt_3c_tailmaw
name = "Naga alt style tri-color, tailmaw (Taur)"
suit_sprites = 'icons/mob/taursuits_naga_alt_vr.dmi'
icon_state = "altnagatailmaw_s"
@@ -208,6 +224,9 @@
can_loaf = TRUE
icon_loaf = 'icons/mob/vore/taurs_vr_loaf.dmi'
loaf_offset = 4
+ vore_tail_sprite_variant = "Horse"
+ fullness_icons = 1
+ struggle_anim = TRUE
msg_owner_disarm_run = "You quickly push %prey to the ground with your hoof!"
msg_prey_disarm_run = "%owner pushes you down to the ground with their hoof!"
@@ -242,6 +261,9 @@
can_loaf = TRUE
icon_loaf = 'icons/mob/vore/taurs_vr_loaf.dmi'
loaf_offset = 3
+ vore_tail_sprite_variant = "Cow"
+ fullness_icons = 1
+ struggle_anim = TRUE
msg_owner_disarm_run = "You quickly push %prey to the ground with your hoof!"
msg_prey_disarm_run = "%owner pushes you down to the ground with their hoof!"
@@ -267,6 +289,10 @@
can_loaf = TRUE
icon_loaf = 'icons/mob/vore/taurs_vr_loaf.dmi'
loaf_offset = 7
+ vore_tail_sprite_variant = "Deer"
+ belly_variant_when_loaf = TRUE
+ fullness_icons = 1
+ struggle_anim = TRUE
msg_owner_disarm_run = "You quickly push %prey to the ground with your hoof!"
msg_prey_disarm_run = "%owner pushes you down to the ground with their hoof!"
@@ -303,6 +329,9 @@
can_loaf = TRUE
icon_loaf = 'icons/mob/vore/taurs_vr_loaf.dmi'
loaf_offset = 5
+ vore_tail_sprite_variant = "Lizard"
+ fullness_icons = 1
+ struggle_anim = TRUE
/datum/sprite_accessory/tail/taur/lizard/fatlizard
name = "Fat Lizard (Taur)"
@@ -349,6 +378,9 @@
can_loaf = TRUE
icon_loaf = 'icons/mob/vore/taurs_vr_loaf.dmi'
loaf_offset = 3
+ vore_tail_sprite_variant = "SynthLiz"
+ fullness_icons = 1
+ struggle_anim = TRUE
/datum/sprite_accessory/tail/taur/lizard/fatsynthlizard
name = "Fat SynthLizard dual-color (Taur)"
@@ -424,6 +456,10 @@
can_loaf = TRUE
icon_loaf = 'icons/mob/vore/taurs_vr_loaf.dmi'
loaf_offset = 5
+ vore_tail_sprite_variant = "Feline"
+ belly_variant_when_loaf = TRUE
+ fullness_icons = 1
+ struggle_anim = TRUE
/datum/sprite_accessory/tail/taur/fatfeline
name = "Fat Feline (Taur)"
@@ -529,6 +565,9 @@
icon_state = "slug_s"
suit_sprites = 'icons/mob/taursuits_slug_vr.dmi'
icon_sprite_tag = "slug"
+ vore_tail_sprite_variant = "Slug"
+ fullness_icons = 1
+ struggle_anim = TRUE
msg_owner_help_walk = "You carefully slither around %prey."
msg_prey_help_walk = "%owner's huge tail slithers past beside you!"
@@ -599,6 +638,10 @@
can_loaf = TRUE
icon_loaf = 'icons/mob/vore/taurs_vr_loaf.dmi'
loaf_offset = 6
+ vore_tail_sprite_variant = "Drake"
+ belly_variant_when_loaf = TRUE
+ fullness_icons = 1
+ struggle_anim = TRUE
/datum/sprite_accessory/tail/taur/drake/fat
name = "Fat Drake (Taur)"
@@ -626,6 +669,10 @@
can_loaf = TRUE
icon_loaf = 'icons/mob/vore/taurs_vr_loaf.dmi'
loaf_offset = 5
+ vore_tail_sprite_variant = "Otie"
+ belly_variant_when_loaf = TRUE
+ fullness_icons = 1
+ struggle_anim = TRUE
/datum/sprite_accessory/tail/taur/alraune/alraune_2c
name = "Alraune (dual color)"
@@ -1071,4 +1118,4 @@
offset_x = -32
offset_y = -11
mob_offset_y = 11
-*/
\ No newline at end of file
+*/
diff --git a/code/modules/mob/new_player/sprite_accessories_vr.dm b/code/modules/mob/new_player/sprite_accessories_vr.dm
index d6e61062bfc..ac03b0692d4 100644
--- a/code/modules/mob/new_player/sprite_accessories_vr.dm
+++ b/code/modules/mob/new_player/sprite_accessories_vr.dm
@@ -6,7 +6,7 @@
/datum/sprite_accessory/hair
- species_allowed = list(SPECIES_HUMAN, SPECIES_SKRELL, SPECIES_UNATHI, SPECIES_TAJ, SPECIES_TESHARI, SPECIES_NEVREAN, SPECIES_AKULA, SPECIES_SERGAL, SPECIES_FENNEC, SPECIES_ZORREN_HIGH, SPECIES_VULPKANIN, SPECIES_XENOCHIMERA, SPECIES_XENOHYBRID, SPECIES_VASILISSAN, SPECIES_RAPALA, SPECIES_PROTEAN, SPECIES_ALRAUNE, SPECIES_WEREBEAST, SPECIES_SHADEKIN, SPECIES_SHADEKIN_CREW, SPECIES_ALTEVIAN) //This lets all races use the default hairstyles.
+ species_allowed = list(SPECIES_HUMAN, SPECIES_SKRELL, SPECIES_UNATHI, SPECIES_TAJ, SPECIES_TESHARI, SPECIES_NEVREAN, SPECIES_AKULA, SPECIES_SERGAL, SPECIES_FENNEC, SPECIES_ZORREN_HIGH, SPECIES_VULPKANIN, SPECIES_XENOCHIMERA, SPECIES_XENOHYBRID, SPECIES_VASILISSAN, SPECIES_RAPALA, SPECIES_PROTEAN, SPECIES_ALRAUNE, SPECIES_WEREBEAST, SPECIES_SHADEKIN, SPECIES_SHADEKIN_CREW, SPECIES_ALTEVIAN, SPECIES_LLEILL) //This lets all races use the default hairstyles.
/datum/sprite_accessory/hair/astolfo
name = "Astolfo"
@@ -485,20 +485,20 @@
/datum/sprite_accessory/facial_hair
icon = 'icons/mob/human_face_or_vr.dmi'
color_blend_mode = ICON_MULTIPLY
- species_allowed = list(SPECIES_HUMAN, SPECIES_SKRELL, SPECIES_UNATHI, SPECIES_TAJ, SPECIES_TESHARI, SPECIES_NEVREAN, SPECIES_AKULA, SPECIES_SERGAL, SPECIES_FENNEC, SPECIES_ZORREN_HIGH, SPECIES_VULPKANIN, SPECIES_XENOCHIMERA, SPECIES_XENOHYBRID, SPECIES_VASILISSAN, SPECIES_RAPALA, SPECIES_PROTEAN, SPECIES_ALRAUNE, SPECIES_WEREBEAST, SPECIES_SHADEKIN, SPECIES_SHADEKIN_CREW, SPECIES_ALTEVIAN) //This lets all races use the facial hair styles.
+ species_allowed = list(SPECIES_HUMAN, SPECIES_SKRELL, SPECIES_UNATHI, SPECIES_TAJ, SPECIES_TESHARI, SPECIES_NEVREAN, SPECIES_AKULA, SPECIES_SERGAL, SPECIES_FENNEC, SPECIES_ZORREN_HIGH, SPECIES_VULPKANIN, SPECIES_XENOCHIMERA, SPECIES_XENOHYBRID, SPECIES_VASILISSAN, SPECIES_RAPALA, SPECIES_PROTEAN, SPECIES_ALRAUNE, SPECIES_WEREBEAST, SPECIES_SHADEKIN, SPECIES_SHADEKIN_CREW, SPECIES_ALTEVIAN, SPECIES_LLEILL) //This lets all races use the facial hair styles.
/datum/sprite_accessory/facial_hair/shaved
name = "Shaved"
icon_state = "bald"
gender = NEUTER
- species_allowed = list(SPECIES_HUMAN, SPECIES_SKRELL, SPECIES_UNATHI, SPECIES_TAJ, SPECIES_TESHARI, SPECIES_NEVREAN, SPECIES_AKULA, SPECIES_SERGAL, SPECIES_FENNEC, SPECIES_ZORREN_HIGH, SPECIES_VULPKANIN, SPECIES_XENOCHIMERA, SPECIES_XENOHYBRID, SPECIES_VASILISSAN, SPECIES_RAPALA, SPECIES_PROTEAN, SPECIES_ALRAUNE, SPECIES_WEREBEAST, SPECIES_SHADEKIN, SPECIES_SHADEKIN_CREW, SPECIES_ALTEVIAN) //This needed to be manually defined, apparantly.
+ species_allowed = list(SPECIES_HUMAN, SPECIES_SKRELL, SPECIES_UNATHI, SPECIES_TAJ, SPECIES_TESHARI, SPECIES_NEVREAN, SPECIES_AKULA, SPECIES_SERGAL, SPECIES_FENNEC, SPECIES_ZORREN_HIGH, SPECIES_VULPKANIN, SPECIES_XENOCHIMERA, SPECIES_XENOHYBRID, SPECIES_VASILISSAN, SPECIES_RAPALA, SPECIES_PROTEAN, SPECIES_ALRAUNE, SPECIES_WEREBEAST, SPECIES_SHADEKIN, SPECIES_SHADEKIN_CREW, SPECIES_ALTEVIAN, SPECIES_LLEILL) //This needed to be manually defined, apparantly.
/datum/sprite_accessory/facial_hair/neck_fluff
name = "Neck Fluff"
icon = 'icons/mob/human_face_or_vr.dmi'
icon_state = "facial_neckfluff"
gender = NEUTER
- species_allowed = list(SPECIES_HUMAN, SPECIES_SKRELL, SPECIES_UNATHI, SPECIES_TAJ, SPECIES_TESHARI, SPECIES_NEVREAN, SPECIES_AKULA, SPECIES_SERGAL, SPECIES_FENNEC, SPECIES_ZORREN_HIGH, SPECIES_VULPKANIN, SPECIES_XENOCHIMERA, SPECIES_XENOHYBRID, SPECIES_VASILISSAN, SPECIES_RAPALA, SPECIES_PROTEAN, SPECIES_ALRAUNE, SPECIES_WEREBEAST, SPECIES_SHADEKIN, SPECIES_SHADEKIN_CREW, SPECIES_ALTEVIAN)
+ species_allowed = list(SPECIES_HUMAN, SPECIES_SKRELL, SPECIES_UNATHI, SPECIES_TAJ, SPECIES_TESHARI, SPECIES_NEVREAN, SPECIES_AKULA, SPECIES_SERGAL, SPECIES_FENNEC, SPECIES_ZORREN_HIGH, SPECIES_VULPKANIN, SPECIES_XENOCHIMERA, SPECIES_XENOHYBRID, SPECIES_VASILISSAN, SPECIES_RAPALA, SPECIES_PROTEAN, SPECIES_ALRAUNE, SPECIES_WEREBEAST, SPECIES_SHADEKIN, SPECIES_SHADEKIN_CREW, SPECIES_ALTEVIAN, SPECIES_LLEILL)
/datum/sprite_accessory/facial_hair/vulp_none
name = "None"
diff --git a/code/modules/mob/new_player/sprite_accessories_wing_vr.dm b/code/modules/mob/new_player/sprite_accessories_wing_vr.dm
index 1e8f6d49191..8bd27ee7460 100644
--- a/code/modules/mob/new_player/sprite_accessories_wing_vr.dm
+++ b/code/modules/mob/new_player/sprite_accessories_wing_vr.dm
@@ -9,7 +9,7 @@
name = "You should not see this..."
icon = 'icons/mob/vore/wings_vr.dmi'
do_colouration = 0 //Set to 1 to enable coloration using the tail color.
- species_allowed = list(SPECIES_HUMAN, SPECIES_SKRELL, SPECIES_UNATHI, SPECIES_TAJ, SPECIES_TESHARI, SPECIES_NEVREAN, SPECIES_AKULA, SPECIES_SERGAL, SPECIES_FENNEC, SPECIES_ZORREN_HIGH, SPECIES_VULPKANIN, SPECIES_XENOCHIMERA, SPECIES_XENOHYBRID, SPECIES_VASILISSAN, SPECIES_RAPALA, SPECIES_PROTEAN, SPECIES_ALRAUNE, SPECIES_WEREBEAST, SPECIES_SHADEKIN, SPECIES_SHADEKIN_CREW, SPECIES_ALTEVIAN) //This lets all races use
+ species_allowed = list(SPECIES_HUMAN, SPECIES_SKRELL, SPECIES_UNATHI, SPECIES_TAJ, SPECIES_TESHARI, SPECIES_NEVREAN, SPECIES_AKULA, SPECIES_SERGAL, SPECIES_FENNEC, SPECIES_ZORREN_HIGH, SPECIES_VULPKANIN, SPECIES_XENOCHIMERA, SPECIES_XENOHYBRID, SPECIES_VASILISSAN, SPECIES_RAPALA, SPECIES_PROTEAN, SPECIES_ALRAUNE, SPECIES_WEREBEAST, SPECIES_SHADEKIN, SPECIES_SHADEKIN_CREW, SPECIES_ALTEVIAN, SPECIES_LLEILL) //This lets all races use
color_blend_mode = ICON_ADD // Only appliciable if do_coloration = 1
var/wing_offset = 0
var/multi_dir = FALSE // Does it use different sprites at different layers? _front will be added for sprites on low layer, _back to high layer
diff --git a/code/modules/multiz/portals_vr.dm b/code/modules/multiz/portals_vr.dm
index 4f6756ad496..a7a4c9c75bd 100644
--- a/code/modules/multiz/portals_vr.dm
+++ b/code/modules/multiz/portals_vr.dm
@@ -93,7 +93,7 @@
if(portal_type == "Weird Green")
portal_icon_selection = "type-b-portal"
if(portal_type == "Pulsing")
- var/portal_subtype = tgui_alert(user, "Which subtype would you prefer?", "Subtype Selection", list("Blue","Red","Blue/Red Mix", "Yellow"))
+ var/portal_subtype = tgui_alert(user, "Which subtype would you prefer?", "Subtype Selection", list("Blue","Red","Blue/Red Mix", "Yellow", "White"))
if(portal_subtype == "Blue")
portal_icon_selection = "type-c-blue-portal"
if(portal_subtype == "Red")
@@ -102,6 +102,8 @@
portal_icon_selection = "type-c-mix-portal"
if(portal_subtype == "Yellow")
portal_icon_selection = "type-c-yellow-portal"
+ if(portal_subtype == "White")
+ portal_icon_selection = "type-c-white-portal"
return portal_icon_selection
/obj/structure/portal_event/proc/teleport(atom/movable/M as mob|obj)
diff --git a/code/modules/nifsoft/nif.dm b/code/modules/nifsoft/nif.dm
index 13c37fd1c47..fc3884d0753 100644
--- a/code/modules/nifsoft/nif.dm
+++ b/code/modules/nifsoft/nif.dm
@@ -293,7 +293,7 @@ You can also set the stat of a NIF to NIF_TEMPFAIL without any issues to disable
notify("Adjoining optic [human.isSynthetic() ? "interface" : "nerve"], please be patient.",TRUE)
else
notify("You are not an authorized user for this device. Please contact [owner].",TRUE)
- unimplant()
+ unimplant(human)
stat = NIF_TEMPFAIL
return FALSE
diff --git a/code/modules/organs/subtypes/lleill.dm b/code/modules/organs/subtypes/lleill.dm
new file mode 100644
index 00000000000..bd50b6ebff6
--- /dev/null
+++ b/code/modules/organs/subtypes/lleill.dm
@@ -0,0 +1,3 @@
+/obj/item/organ/external/head/lleill
+ eye_icon_location = 'icons/mob/human_face_vr.dmi'
+ eye_icon = "eyes_lleill"
diff --git a/code/modules/pda/cart_apps.dm b/code/modules/pda/cart_apps.dm
index c0f24ed9df6..eaf0db739de 100644
--- a/code/modules/pda/cart_apps.dm
+++ b/code/modules/pda/cart_apps.dm
@@ -229,7 +229,7 @@
for(var/datum/supply_order/SO as anything in SSsupply.shoppinglist)
supplyOrderCount++
- supplyOrderData[++supplyOrderData.len] = list("Number" = SO.ordernum, "Name" = html_encode(SO.object.name), "ApprovedBy" = SO.ordered_by, "Comment" = html_encode(SO.comment))
+ supplyOrderData[++supplyOrderData.len] = list("Number" = SO.ordernum, "Name" = html_encode(SO.object.name), "ApprovedBy" = SO.approved_by, "Comment" = html_encode(SO.comment))
supplyData["approved"] = supplyOrderData
supplyData["approved_count"] = supplyOrderCount
diff --git a/code/modules/pda/core_apps.dm b/code/modules/pda/core_apps.dm
index 94c30cea652..6e6ab500761 100644
--- a/code/modules/pda/core_apps.dm
+++ b/code/modules/pda/core_apps.dm
@@ -314,7 +314,9 @@
// Compile all the newscasts
for(var/datum/feed_channel/channel in news_network.network_channels)
if(!channel.censored)
+ var/index = 0
for(var/datum/feed_message/FM in channel.messages)
+ index++
var/body = replacetext(FM.body, "\n", " ")
news[++news.len] = list(
"channel" = channel.channel_name,
@@ -324,7 +326,8 @@
"time_stamp" = FM.time_stamp,
"has_image" = (FM.img != null),
"caption" = FM.caption,
- "time" = FM.post_time
+ "time" = FM.post_time,
+ "index" = index
)
// Cut out all but the youngest three
diff --git a/code/modules/power/apc.dm b/code/modules/power/apc.dm
index 4af59cedb8a..3c509574c67 100644
--- a/code/modules/power/apc.dm
+++ b/code/modules/power/apc.dm
@@ -805,7 +805,7 @@ GLOBAL_LIST_EMPTY(apcs)
"emagged" = emagged,
"isOperating" = operating,
"externalPower" = main_status,
- "powerCellStatus" = cell ? cell.percent() : null,
+ "powerCellStatus" = cell ? cell.percent() : 0,
"chargeMode" = chargemode,
"chargingStatus" = charging,
"totalLoad" = round(lastused_total),
diff --git a/code/modules/reagents/machinery/dispenser/dispenser2.dm b/code/modules/reagents/machinery/dispenser/dispenser2.dm
index 43a19e7ac01..be5090e2e70 100644
--- a/code/modules/reagents/machinery/dispenser/dispenser2.dm
+++ b/code/modules/reagents/machinery/dispenser/dispenser2.dm
@@ -152,7 +152,7 @@
var/chemicals[0]
for(var/label in cartridges)
var/obj/item/weapon/reagent_containers/chem_disp_cartridge/C = cartridges[label]
- chemicals.Add(list(list("title" = label, "id" = label, "amount" = C.reagents.total_volume))) // list in a list because Byond merges the first list...
+ chemicals.Add(list(list("name" = label, "id" = label, "volume" = C.reagents.total_volume))) // list in a list because Byond merges the first list...
data["chemicals"] = chemicals
return data
diff --git a/code/modules/reagents/reagent_containers/patch.dm b/code/modules/reagents/reagent_containers/patch.dm
index 007669286e0..e7ed52ea846 100644
--- a/code/modules/reagents/reagent_containers/patch.dm
+++ b/code/modules/reagents/reagent_containers/patch.dm
@@ -40,7 +40,7 @@
to_chat(H, "\The [src] is placed on your [affecting].")
M.drop_from_inventory(src) //icon update
if(reagents.total_volume)
- reagents.trans_to_mob(M, reagents.total_volume, CHEM_TOUCH)
+ reagents.trans_to_mob(M, reagents.total_volume, CHEM_BLOOD) //CHEM_TOUCH
qdel(src)
return 1
@@ -74,7 +74,7 @@
M.drop_from_inventory(src) //icon update
if(reagents.total_volume)
- reagents.trans_to_mob(M, reagents.total_volume, CHEM_TOUCH)
+ reagents.trans_to_mob(M, reagents.total_volume, CHEM_BLOOD) //CHEM_TOUCH
qdel(src)
return 1
diff --git a/code/modules/reagents/reagents/dispenser.dm b/code/modules/reagents/reagents/dispenser.dm
index f69d7d451ee..84beb5266e3 100644
--- a/code/modules/reagents/reagents/dispenser.dm
+++ b/code/modules/reagents/reagents/dispenser.dm
@@ -113,7 +113,7 @@
if(issmall(M))
removed *= 2
- var/strength_mod = 3 * M.species.chem_strength_alcohol //Alcohol is 3x stronger when injected into the veins.
+ var/strength_mod = 3 //Alcohol is 3x stronger when injected into the veins.
if(!strength_mod)
return
@@ -121,19 +121,19 @@
M.add_chemical_effect(CE_ALCOHOL, 1)
var/effective_dose = dose * strength_mod * (1 + volume/60) //drinking a LOT will make you go down faster
- if(effective_dose >= strength) // Early warning
+ if(effective_dose >= (strength * M.species.chem_strength_alcohol)) // Early warning
M.make_dizzy(18) // It is decreased at the speed of 3 per tick
- if(effective_dose >= strength * 2) // Slurring
+ if(effective_dose >= (strength * M.species.chem_strength_alcohol) * 2) // Slurring
M.slurring = max(M.slurring, 90)
- if(effective_dose >= strength * 3) // Confusion - walking in random directions
+ if(effective_dose >= (strength * M.species.chem_strength_alcohol) * 3) // Confusion - walking in random directions
M.Confuse(60)
- if(effective_dose >= strength * 4) // Blurry vision
+ if(effective_dose >= (strength * M.species.chem_strength_alcohol) * 4) // Blurry vision
M.eye_blurry = max(M.eye_blurry, 30)
- if(effective_dose >= strength * 5) // Drowsyness - periodically falling asleep
+ if(effective_dose >= (strength * M.species.chem_strength_alcohol) * 5) // Drowsyness - periodically falling asleep
M.drowsyness = max(M.drowsyness, 60)
- if(effective_dose >= strength * 6) // Toxic dose
+ if(effective_dose >= (strength * M.species.chem_strength_alcohol) * 6) // Toxic dose
M.add_chemical_effect(CE_ALCOHOL_TOXIC, toxicity*3)
- if(effective_dose >= strength * 7) // Pass out
+ if(effective_dose >= (strength * M.species.chem_strength_alcohol) * 7) // Pass out
M.Paralyse(60)
M.Sleeping(90)
@@ -160,26 +160,26 @@
if(M.isSynthetic() && M.nutrition < 500 && M.species.robo_ethanol_proc)
M.adjust_nutrition(round(max(0,ep_base_power - strength) * removed)/ep_final_mod) //the stronger it is, the more juice you gain
- var/effective_dose = dose * M.species.chem_strength_alcohol
+ var/effective_dose = dose
if(!effective_dose)
return
if(M.species.robo_ethanol_drunk || !(M.isSynthetic()))
M.add_chemical_effect(CE_ALCOHOL, 1)
- if(effective_dose >= strength) // Early warning
+ if(effective_dose >= (strength * M.species.chem_strength_alcohol)) // Early warning
M.make_dizzy(6) // It is decreased at the speed of 3 per tick
- if(effective_dose >= strength * 2) // Slurring
+ if(effective_dose >= (strength * M.species.chem_strength_alcohol) * 2) // Slurring
M.slurring = max(M.slurring, 30)
- if(effective_dose >= strength * 3) // Confusion - walking in random directions
+ if(effective_dose >= (strength * M.species.chem_strength_alcohol) * 3) // Confusion - walking in random directions
M.Confuse(20)
- if(effective_dose >= strength * 4) // Blurry vision
+ if(effective_dose >= (strength * M.species.chem_strength_alcohol) * 4) // Blurry vision
M.eye_blurry = max(M.eye_blurry, 10)
- if(effective_dose >= strength * 5) // Drowsyness - periodically falling asleep
+ if(effective_dose >= (strength * M.species.chem_strength_alcohol) * 5) // Drowsyness - periodically falling asleep
M.drowsyness = max(M.drowsyness, 20)
- if(effective_dose >= strength * 6) // Toxic dose
+ if(effective_dose >= (strength * M.species.chem_strength_alcohol) * 6) // Toxic dose
M.add_chemical_effect(CE_ALCOHOL_TOXIC, toxicity)
- if(effective_dose >= strength * 7) // Pass out
+ if(effective_dose >= (strength * M.species.chem_strength_alcohol) * 7) // Pass out
M.Paralyse(20)
M.Sleeping(30)
diff --git a/code/modules/reagents/reagents/vore_vr.dm b/code/modules/reagents/reagents/vore_vr.dm
index 42e39065628..4b56d19fc9b 100644
--- a/code/modules/reagents/reagents/vore_vr.dm
+++ b/code/modules/reagents/reagents/vore_vr.dm
@@ -186,3 +186,59 @@
H.change_gender_identity(PLURAL)
H.visible_message("[H] suddenly twitches as some of their features seem to contort and reshape, adjusting... In the end, it seems they are now of mixed gender.",
"Your body suddenly contorts, feeling very different in various ways... By the time the rushing feeling is over it seems you just became of mixed gender.")
+
+
+////////////////////////// Misc Drugs //////////////////////////
+
+/datum/reagent/drugs/rainbow_toxin /// Replaces Space Drugs.
+ name = "Rainbow Toxin"
+ id = "rainbowtoxin"
+ description = "Known for providing a euphoric high, this psychoactive drug is often injected into unknowing prey by serpents and other fanged beasts. Highly valuable and frequently sought after by hypno-enthusiasts and party-goers."
+ taste_description = "mixed euphoria"
+ taste_mult = 0.8 //You ARE going to taste this!
+ scannable = 1 //Sure! If you manage to milk a snake for some of this, go ahead and scan it and mass produce it. Your local club will love you!
+
+/datum/reagent/drugs/rainbow_toxin/affect_blood(mob/living/carbon/M, var/alien, var/removed)
+ ..()
+ var/drug_strength = 20
+ M.druggy = max(M.druggy, drug_strength)
+
+/datum/reagent/drugs/bliss/overdose(var/mob/living/M as mob)
+ if(prob_proc == TRUE && prob(20))
+ M.hallucination = max(M.hallucination, 5)
+ prob_proc = FALSE
+ M.adjustBrainLoss(0.25*REM) //Too much isn't good for your long term health...
+ M.adjustToxLoss(0.01*REM) //Enough that it'll make your HUD dummy update, but not enough that you'll vomit mid scene. (Sorry emetophiliacs!)
+ ..()
+
+/datum/reagent/paralysis_toxin
+ name = "Tetrodotoxin"
+ id = "paralysistoxin"
+ description = "A potent toxin commonly found in a plethora of species. When exposed to the toxin, causes extreme, paralysis for a prolonged period, with only essential functions of the body being unhindered. Commonly used by covert operatives and used as a crowd control tool."
+ taste_description = "bitterness"
+ reagent_state = LIQUID
+ color = "#37007f"
+ metabolism = REM * 0.25
+ overdose = REAGENTS_OVERDOSE
+ scannable = 0 //YOU ARE NOT SCANNING THE FUNNY PARALYSIS TOXIN. NO. BAD. STAY AWAY.
+
+/datum/reagent/paralysis_toxin/affect_blood(var/mob/living/carbon/M, var/alien, var/removed)
+ if(M.weakened < 50) //Let's not leave them PERMA stuck, after all.
+ M.AdjustWeakened(5) //Stand in for paralyze so you can still talk/emote/see
+
+/datum/reagent/pain_enzyme
+ name = "Pain Enzyme"
+ id = "painenzyme"
+ description = "An enzyme found in a variety of species. When exposed to the toxin, will cause severe, agonizing pain. The effects can last for hours depending on the dose. Only known cure is an equally strong painkiller or dialysis."
+ taste_description = "sourness"
+ reagent_state = LIQUID
+ color = "#04b8fa" //Light blue in honor of Perry.
+ metabolism = 0.1 //Lasts up to 50 seconds if you give 5 units.
+ mrate_static = TRUE
+ overdose = 100 //There is no OD. You already are taking the worst of it.
+ scannable = 0 //Let's not have medical mechs able to make an extremely strong 'I hit you you fall down in agony' chem.
+
+/datum/reagent/pain_enzyme/affect_blood(var/mob/living/carbon/M, var/alien, var/removed)
+ M.add_chemical_effect(CE_PAINKILLER, -200)
+ if(prob(0.01)) //1 in 10000 chance per tick. Extremely rare.
+ to_chat(M,"Your body feels as though it's on fire!")
diff --git a/code/modules/recycling/disposal.dm b/code/modules/recycling/disposal.dm
index cd6753ba648..5bc3241e008 100644
--- a/code/modules/recycling/disposal.dm
+++ b/code/modules/recycling/disposal.dm
@@ -1265,7 +1265,7 @@
/obj/structure/disposalpipe/tagger/New()
. = ..()
dpdir = dir | turn(dir, 180)
- if(sort_tag) GLOB.tagger_locations |= sort_tag
+ if(sort_tag) GLOB.tagger_locations |= list("[sort_tag]" = get_z(src))
updatename()
updatedesc()
update()
@@ -1331,7 +1331,7 @@
/obj/structure/disposalpipe/sortjunction/New()
. = ..()
- if(sortType) GLOB.tagger_locations |= sortType
+ if(sortType) GLOB.tagger_locations |= list("[sortType]" = get_z(src))
updatedir()
updatename()
diff --git a/code/modules/recycling/sortingmachinery.dm b/code/modules/recycling/sortingmachinery.dm
index b8b091444ef..a341e073368 100755
--- a/code/modules/recycling/sortingmachinery.dm
+++ b/code/modules/recycling/sortingmachinery.dm
@@ -348,11 +348,23 @@
ui = new(user, src, "DestinationTagger", name)
ui.open()
+/obj/item/device/destTagger/tgui_static_data(mob/user)
+ var/list/data = ..()
+ var/list/taggers = list()
+ var/list/tagger_levels = list()
+ for(var/tag in GLOB.tagger_locations)
+ var/z_level = GLOB.tagger_locations[tag]
+ taggers += list(list("tag" = tag, "level" = z_level))
+ tagger_levels += list(list("z" = z_level, "location" = using_map.get_zlevel_name(z_level)))
+ data["taggerLevels"] = tagger_levels
+ data["taggerLocs"] = taggers
+
+ return data
+
/obj/item/device/destTagger/tgui_data(mob/user, datum/tgui/ui)
var/list/data = ..()
data["currTag"] = currTag
- data["taggerLocs"] = GLOB.tagger_locations
return data
diff --git a/code/modules/shuttles/escape_pods.dm b/code/modules/shuttles/escape_pods.dm
index 7490887c20e..aa966b582a8 100644
--- a/code/modules/shuttles/escape_pods.dm
+++ b/code/modules/shuttles/escape_pods.dm
@@ -55,9 +55,9 @@
. = list(
"docking_status" = docking_program.get_docking_status(),
"override_enabled" = docking_program.override_enabled,
- "exterior_status" = docking_program.memory["door_status"],
- "can_force" = pod.can_force() || (emergency_shuttle.departed && pod.can_launch()), //allow players to manually launch ahead of time if the shuttle leaves
- "armed" = pod.arming_controller.armed,
+ "exterior_status" = docking_program.memory["door_status"], // TGUI DATA fails silently when there's no linked pod, leading to UI crashes
+ "can_force" = pod?.can_force() || (emergency_shuttle.departed && pod?.can_launch()), //allow players to manually launch ahead of time if the shuttle leaves
+ "armed" = pod?.arming_controller.armed,
"internalTemplateName" = "EscapePodConsole",
)
diff --git a/code/modules/tgui/modules/appearance_changer.dm b/code/modules/tgui/modules/appearance_changer.dm
index e55296c902f..9eeb4de6641 100644
--- a/code/modules/tgui/modules/appearance_changer.dm
+++ b/code/modules/tgui/modules/appearance_changer.dm
@@ -1,4 +1,3 @@
-
/datum/tgui_module/appearance_changer
name = "Appearance Editor"
tgui_id = "AppearanceChanger"
@@ -63,6 +62,9 @@
cam_background.del_on_map_removal = FALSE
update_active_camera_screen()
+ if(customize_usr)
+ if(ishuman(usr))
+ H = usr
owner = H
if(owner)
owner.AddComponent(/datum/component/recursive_move)
@@ -184,8 +186,8 @@
instance = null
if(!istype(instance) && !params["clear"])
return FALSE
- owner.ear_style = instance
- owner.update_hair()
+ target.ear_style = instance
+ target.update_hair()
update_dna()
changed_hook(APPEARANCECHANGER_CHANGED_HAIRSTYLE)
return TRUE
@@ -197,7 +199,7 @@
target.g_ears = hex2num(copytext(new_hair, 4, 6))
target.b_ears = hex2num(copytext(new_hair, 6, 8))
update_dna()
- owner.update_hair()
+ target.update_hair()
changed_hook(APPEARANCECHANGER_CHANGED_HAIRCOLOR)
return 1
if("ears2_color")
@@ -208,7 +210,7 @@
target.g_ears2 = hex2num(copytext(new_hair, 4, 6))
target.b_ears2 = hex2num(copytext(new_hair, 6, 8))
update_dna()
- owner.update_hair()
+ target.update_hair()
changed_hook(APPEARANCECHANGER_CHANGED_HAIRCOLOR)
return 1
if("tail")
@@ -218,8 +220,8 @@
instance = null
if(!istype(instance) && !params["clear"])
return FALSE
- owner.tail_style = instance
- owner.update_tail_showing()
+ target.tail_style = instance
+ target.update_tail_showing()
update_dna()
changed_hook(APPEARANCECHANGER_CHANGED_HAIRSTYLE)
return TRUE
@@ -231,7 +233,7 @@
target.g_tail = hex2num(copytext(new_hair, 4, 6))
target.b_tail = hex2num(copytext(new_hair, 6, 8))
update_dna()
- owner.update_tail_showing()
+ target.update_tail_showing()
changed_hook(APPEARANCECHANGER_CHANGED_HAIRCOLOR)
return 1
if("tail2_color")
@@ -242,7 +244,7 @@
target.g_tail2 = hex2num(copytext(new_hair, 4, 6))
target.b_tail2 = hex2num(copytext(new_hair, 6, 8))
update_dna()
- owner.update_tail_showing()
+ target.update_tail_showing()
changed_hook(APPEARANCECHANGER_CHANGED_HAIRCOLOR)
return 1
if("wing")
@@ -252,8 +254,8 @@
instance = null
if(!istype(instance) && !params["clear"])
return FALSE
- owner.wing_style = instance
- owner.update_wing_showing()
+ target.wing_style = instance
+ target.update_wing_showing()
update_dna()
changed_hook(APPEARANCECHANGER_CHANGED_HAIRSTYLE)
return TRUE
@@ -265,7 +267,7 @@
target.g_wing = hex2num(copytext(new_hair, 4, 6))
target.b_wing = hex2num(copytext(new_hair, 6, 8))
update_dna()
- owner.update_wing_showing()
+ target.update_wing_showing()
changed_hook(APPEARANCECHANGER_CHANGED_HAIRCOLOR)
return 1
if("wing2_color")
@@ -276,7 +278,7 @@
target.g_wing2 = hex2num(copytext(new_hair, 4, 6))
target.b_wing2 = hex2num(copytext(new_hair, 6, 8))
update_dna()
- owner.update_wing_showing()
+ target.update_wing_showing()
changed_hook(APPEARANCECHANGER_CHANGED_HAIRCOLOR)
return 1
if("marking")
diff --git a/code/modules/tgui_panel/tgui_panel.dm b/code/modules/tgui_panel/tgui_panel.dm
index 5dd06e3c36c..e9f6429f25e 100644
--- a/code/modules/tgui_panel/tgui_panel.dm
+++ b/code/modules/tgui_panel/tgui_panel.dm
@@ -48,13 +48,13 @@
assets = list(
get_asset_datum(/datum/asset/simple/tgui_panel),
))
- window.reinitialize() // Workaround for an early init fail...
window.send_asset(get_asset_datum(/datum/asset/simple/namespaced/fontawesome))
window.send_asset(get_asset_datum(/datum/asset/simple/namespaced/tgfont))
window.send_asset(get_asset_datum(/datum/asset/spritesheet/chat))
// Other setup
request_telemetry()
addtimer(CALLBACK(src, PROC_REF(on_initialize_timed_out)), 5 SECONDS)
+ window.send_message("testTelemetryCommand")
/**
* private
diff --git a/code/modules/virus2/diseasesplicer.dm b/code/modules/virus2/diseasesplicer.dm
index afc875a76e6..cdb7948bdd0 100644
--- a/code/modules/virus2/diseasesplicer.dm
+++ b/code/modules/virus2/diseasesplicer.dm
@@ -82,7 +82,7 @@
if(dish.growth >= 50)
var/list/effects[0]
for (var/datum/disease2/effectholder/e in dish.virus2.effects)
- effects.Add(list(list("name" = (dish.analysed ? e.effect.name : "Unknown"), "stage" = (e.stage), "reference" = "\ref[e]")))
+ effects.Add(list(list("name" = (dish.analysed ? e.effect.name : "Unknown"), "stage" = (e.stage), "reference" = "\ref[e]"), "badness" = e.effect.badness))
data["effects"] = effects
else
data["info"] = "Insufficient cell growth for gene splicing."
@@ -191,4 +191,3 @@
if("disk")
burning = 10
. = TRUE
-
diff --git a/code/modules/vore/eating/belly_obj_vr.dm b/code/modules/vore/eating/belly_obj_vr.dm
index 21602a1c27f..23b07967893 100644
--- a/code/modules/vore/eating/belly_obj_vr.dm
+++ b/code/modules/vore/eating/belly_obj_vr.dm
@@ -8,6 +8,12 @@
//
// Parent type of all the various "belly" varieties.
//
+
+#define DM_FLAG_VORESPRITE_TAIL 0x2
+#define DM_FLAG_VORESPRITE_MARKING 0x4
+#define DM_FLAG_VORESPRITE_ARTICLE 0x8
+
+
/obj/belly
name = "belly" // Name of this location
desc = "It's a belly! You're in it!" // Flavor text description of inside sight/sound/smells/feels.
@@ -60,6 +66,30 @@
var/belly_item_mult = 1 //Multiplier for how filling items are in borg borg bellies. Items are also weighted on item size
var/belly_overall_mult = 1 //Multiplier applied ontop of any other specific multipliers
+
+ var/vore_sprite_flags = DM_FLAG_VORESPRITE_ARTICLE
+ var/tmp/static/list/vore_sprite_flag_list= list(
+ "Normal Belly Sprite" = DM_FLAG_VORESPRITE_ARTICLE,
+ //"Tail adjustment" = DM_FLAG_VORESPRITE_TAIL,
+ //"Marking addition" = DM_FLAG_VORESPRITE_MARKING
+ )
+ var/affects_vore_sprites = FALSE
+ var/count_absorbed_prey_for_sprite = TRUE
+ var/absorbed_multiplier = 1
+ var/count_liquid_for_sprite = FALSE
+ var/liquid_multiplier = 1
+ var/count_items_for_sprite = FALSE
+ var/item_multiplier = 1
+ var/health_impacts_size = TRUE
+ var/resist_triggers_animation = TRUE
+ var/size_factor_for_sprite = 1
+ var/belly_sprite_to_affect = "stomach"
+ var/datum/sprite_accessory/tail/tail_to_change_to = FALSE
+ var/tail_colouration = FALSE
+ var/tail_extra_overlay = FALSE
+ var/tail_extra_overlay2 = FALSE
+ var/undergarment_chosen = "Underwear, bottom"
+
// Generally just used by AI
var/autotransferchance = 0 // % Chance of prey being autotransferred to transfer location
var/autotransferwait = 10 // Time between trying to transfer.
@@ -345,6 +375,15 @@
"belly_item_mult",
"belly_overall_mult",
"drainmode",
+ "vore_sprite_flags",
+ "affects_vore_sprites",
+ "count_absorbed_prey_for_sprite",
+ "resist_triggers_animation",
+ "size_factor_for_sprite",
+ "belly_sprite_to_affect",
+ "health_impacts_size",
+ "count_items_for_sprite",
+ "item_multiplier"
)
if (save_digest_mode == 1)
@@ -426,6 +465,10 @@
if(M.ai_holder)
M.ai_holder.handle_eaten()
+ if (istype(owner, /mob/living/carbon/human))
+ var/mob/living/carbon/human/hum = owner
+ hum.update_fullness()
+
// Intended for simple mobs
if(!owner.client && autotransferlocation && autotransferchance > 0)
addtimer(CALLBACK(src, TYPE_PROC_REF(/obj/belly, check_autotransfer), thing, autotransferlocation), autotransferwait)
@@ -444,6 +487,10 @@
L.toggle_hud_vis()
if((L.stat != DEAD) && L.ai_holder)
L.ai_holder.go_wake()
+ if (istype(owner, /mob/living/carbon/human))
+ var/mob/living/carbon/human/hum = owner
+ hum.update_fullness()
+
/obj/belly/proc/vore_fx(mob/living/L)
if(!istype(L))
@@ -1757,6 +1804,15 @@
dupe.belly_mob_mult = belly_mob_mult
dupe.belly_item_mult = belly_item_mult
dupe.belly_overall_mult = belly_overall_mult
+ dupe.vore_sprite_flags = vore_sprite_flags
+ dupe.affects_vore_sprites = affects_vore_sprites
+ dupe.count_absorbed_prey_for_sprite = count_absorbed_prey_for_sprite
+ dupe.resist_triggers_animation = resist_triggers_animation
+ dupe.size_factor_for_sprite = size_factor_for_sprite
+ dupe.belly_sprite_to_affect = belly_sprite_to_affect
+ dupe.health_impacts_size = health_impacts_size
+ dupe.count_items_for_sprite = count_items_for_sprite
+ dupe.item_multiplier = item_multiplier
//// Object-holding variables
//struggle_messages_outside - strings
@@ -1955,3 +2011,38 @@
/obj/belly/container_resist(mob/M)
return relay_resist(M)
+
+/obj/belly/proc/GetFullnessFromBelly()
+ if(!affects_vore_sprites)
+ return 0
+ var/belly_fullness = 0
+ for(var/mob/living/M in src)
+ if(count_absorbed_prey_for_sprite || !M.absorbed)
+ var/fullness_to_add = M.size_multiplier
+ fullness_to_add *= M.mob_size / 20
+ if(M.absorbed)
+ fullness_to_add *= absorbed_multiplier
+ if(health_impacts_size)
+ fullness_to_add *= M.health / M.getMaxHealth()
+ belly_fullness += fullness_to_add
+ if(count_liquid_for_sprite)
+ belly_fullness += (reagents.total_volume / 100) * liquid_multiplier
+ if(count_items_for_sprite)
+ for(var/obj/item/I in src)
+ var/fullness_to_add = 0
+ if(I.w_class == ITEMSIZE_TINY)
+ fullness_to_add = ITEMSIZE_COST_TINY
+ else if(I.w_class == ITEMSIZE_SMALL)
+ fullness_to_add = ITEMSIZE_COST_SMALL
+ else if(I.w_class == ITEMSIZE_NORMAL)
+ fullness_to_add = ITEMSIZE_COST_NORMAL
+ else if(I.w_class == ITEMSIZE_LARGE)
+ fullness_to_add = ITEMSIZE_COST_LARGE
+ else if(I.w_class == ITEMSIZE_HUGE)
+ fullness_to_add = ITEMSIZE_COST_HUGE
+ else
+ fullness_to_add = ITEMSIZE_COST_NO_CONTAINER
+ fullness_to_add /= 32
+ belly_fullness += fullness_to_add * item_multiplier
+ belly_fullness *= size_factor_for_sprite
+ return belly_fullness
diff --git a/code/modules/vore/eating/bellymodes_datum_vr.dm b/code/modules/vore/eating/bellymodes_datum_vr.dm
index b372dc2ce95..9c49d8f93f0 100644
--- a/code/modules/vore/eating/bellymodes_datum_vr.dm
+++ b/code/modules/vore/eating/bellymodes_datum_vr.dm
@@ -35,6 +35,10 @@ GLOBAL_LIST_INIT(digest_modes, list())
else
SEND_SOUND(L, sound(get_sfx("fancy_death_prey")))
B.handle_digestion_death(L)
+ if(!L)
+ if (istype(B.owner, /mob/living/carbon/human))
+ var/mob/living/carbon/human/howner = B.owner
+ howner.update_fullness()
if(!B.fancy_vore)
return list("to_update" = TRUE, "soundToPlay" = sound(get_sfx("classic_death_sounds")))
return list("to_update" = TRUE, "soundToPlay" = sound(get_sfx("fancy_death_pred")))
@@ -61,6 +65,11 @@ GLOBAL_LIST_INIT(digest_modes, list())
var/offset = (1 + ((L.weight - 137) / 137)) // 130 pounds = .95 140 pounds = 1.02
var/difference = B.owner.size_multiplier / L.size_multiplier
+ if(B.health_impacts_size)
+ if (istype(B.owner, /mob/living/carbon/human))
+ var/mob/living/carbon/human/howner = B.owner
+ howner.update_fullness()
+
consider_healthbar(L, old_health, B.owner)
if(isrobot(B.owner))
diff --git a/code/modules/vore/eating/living_vr.dm b/code/modules/vore/eating/living_vr.dm
index f4bf8733160..9e78aebd3d2 100644
--- a/code/modules/vore/eating/living_vr.dm
+++ b/code/modules/vore/eating/living_vr.dm
@@ -34,6 +34,10 @@
'sound/effects/mob_effects/xenochimera/regen_5.ogg'
)
var/trash_catching = FALSE //Toggle for trash throw vore from chompstation
+ var/list/trait_injection_reagents = list() //List of all the reagents allowed to be used for injection via venom bite
+ var/trait_injection_selected = null //RSEdit: What trait reagent you're injecting.
+ var/trait_injection_amount = 5 //RSEdit: How much you're injecting with traits.
+ var/trait_injection_verb = "bites" //RSEdit: Which fluffy manner you're doing the injecting.
//
// Hook for generic creation of stuff on new creatures
@@ -256,6 +260,8 @@
P.weight_message_visible = src.weight_message_visible
P.weight_messages = src.weight_messages
+ P.vore_sprite_color = istype(src, /mob/living/carbon/human) ? src:vore_sprite_color : null
+
var/list/serialized = list()
for(var/obj/belly/B as anything in src.vore_organs)
serialized += list(B.serialize()) //Can't add a list as an object to another list in Byond. Thanks.
@@ -306,6 +312,10 @@
weight_message_visible = P.weight_message_visible
weight_messages = P.weight_messages
+
+ if (istype(src, /mob/living/carbon/human))
+ src:vore_sprite_color = P.vore_sprite_color
+
if(bellies)
if(isliving(src))
var/mob/living/L = src
diff --git a/code/modules/vore/eating/vore_vr.dm b/code/modules/vore/eating/vore_vr.dm
index 7888cdb010a..d7fe76ed5a2 100644
--- a/code/modules/vore/eating/vore_vr.dm
+++ b/code/modules/vore/eating/vore_vr.dm
@@ -69,6 +69,8 @@ V::::::V V::::::VO:::::::OOO:::::::ORR:::::R R:::::REE::::::EEEEEE
var/step_mechanics_pref = FALSE
var/pickup_pref = TRUE
+ var/vore_sprite_color = list("stomach" = "#000", "taur belly" = "#000")
+
var/list/belly_prefs = list()
var/vore_taste = "nothing in particular"
var/vore_smell = "nothing in particular"
@@ -193,6 +195,7 @@ V::::::V V::::::VO:::::::OOO:::::::ORR:::::R R:::::REE::::::EEEEEE
weight_message_visible = json_from_file["weight_message_visible"]
weight_messages = json_from_file["weight_messages"]
eating_privacy_global = json_from_file["eating_privacy_global"]
+ vore_sprite_color = json_from_file["vore_sprite_color"]
//Quick sanitize
if(isnull(digestable))
@@ -279,7 +282,8 @@ V::::::V V::::::VO:::::::OOO:::::::ORR:::::R R:::::REE::::::EEEEEE
else if(weight_messages.len < 10)
while(weight_messages.len < 10)
weight_messages.Add("")
-
+ if(isnull(vore_sprite_color))
+ vore_sprite_color = list("stomach" = "#000", "taur belly" = "#000")
return TRUE
/datum/vore_preferences/proc/save_vore()
@@ -320,6 +324,7 @@ V::::::V V::::::VO:::::::OOO:::::::ORR:::::R R:::::REE::::::EEEEEE
"weight_message_visible" = weight_message_visible,
"weight_messages" = weight_messages,
"eating_privacy_global" = eating_privacy_global,
+ "vore_sprite_color" = vore_sprite_color,
)
//List to JSON
diff --git a/code/modules/vore/eating/vorepanel_vr.dm b/code/modules/vore/eating/vorepanel_vr.dm
index bb59b7c7adb..d2286f78b59 100644
--- a/code/modules/vore/eating/vorepanel_vr.dm
+++ b/code/modules/vore/eating/vorepanel_vr.dm
@@ -227,7 +227,23 @@ var/global/list/belly_colorable_only_fullscreens = list("a_synth_flesh_mono",
"belly_item_mult" = selected.belly_item_mult,
"belly_overall_mult" = selected.belly_overall_mult,
"drainmode" = selected.drainmode,
-
+ "affects_voresprite" = selected.affects_vore_sprites,
+ "absorbed_voresprite" = selected.count_absorbed_prey_for_sprite,
+ "absorbed_multiplier" = selected.absorbed_multiplier,
+ "liquid_voresprite" = selected.count_liquid_for_sprite,
+ "liquid_multiplier" = selected.liquid_multiplier,
+ "item_voresprite" = selected.count_items_for_sprite,
+ "item_multiplier" = selected.item_multiplier,
+ "health_voresprite" = selected.health_impacts_size,
+ "resist_animation" = selected.resist_triggers_animation,
+ "voresprite_size_factor" = selected.size_factor_for_sprite,
+ "belly_sprite_to_affect" = selected.belly_sprite_to_affect,
+ "belly_sprite_option_shown" = istype(host, /mob/living/carbon/human) ? (LAZYLEN(host:vore_icon_bellies) >= 1 ? TRUE : FALSE) : FALSE, // TODO: FIX THIS (It won't be fixed)
+ "tail_option_shown" = istype(host, /mob/living/carbon/human),
+ "tail_to_change_to" = selected.tail_to_change_to,
+ "tail_colouration" = selected.tail_colouration,
+ "tail_extra_overlay" = selected.tail_extra_overlay,
+ "tail_extra_overlay2" = selected.tail_extra_overlay2
)
var/list/addons = list()
@@ -236,6 +252,13 @@ var/global/list/belly_colorable_only_fullscreens = list("a_synth_flesh_mono",
addons.Add(flag_name)
selected_list["addons"] = addons
+ var/list/vs_flags = list()
+ for(var/flag_name in selected.vore_sprite_flag_list)
+ if(selected.vore_sprite_flags & selected.vore_sprite_flag_list[flag_name])
+ vs_flags.Add(flag_name)
+ selected_list["vore_sprite_flags"] = vs_flags
+
+
selected_list["egg_type"] = selected.egg_type
selected_list["contaminates"] = selected.contaminates
selected_list["contaminate_flavor"] = null
@@ -609,6 +632,20 @@ var/global/list/belly_colorable_only_fullscreens = list("a_synth_flesh_mono",
host.weight_message_visible = !host.weight_message_visible
unsaved_changes = TRUE
return TRUE
+ if("set_vs_color")
+ if (istype(host, /mob/living/carbon/human))
+ var/mob/living/carbon/human/hhost = host
+ var/belly_choice = tgui_input_list(usr, "Which vore sprite are you going to edit the color of?", "Vore Sprite Color", hhost.vore_icon_bellies)
+ var/newcolor = input(usr, "Choose a color.", "", hhost.vore_sprite_color[belly_choice]) as color|null
+ if(newcolor)
+ hhost.vore_sprite_color[belly_choice] = newcolor
+ var/multiply = tgui_input_list(usr, "Set the color to be applied multiplicatively or additively? Currently in [hhost.vore_sprite_multiply[belly_choice] ? "Multiply" : "Add"]", "Vore Sprite Color", list("Multiply", "Add"))
+ if(multiply == "Multiply")
+ hhost.vore_sprite_multiply[belly_choice] = TRUE
+ else if(multiply == "Add")
+ hhost.vore_sprite_multiply[belly_choice] = FALSE
+ hhost.update_icons_body()
+ return TRUE
/datum/vore_look/proc/pick_from_inside(mob/user, params)
var/atom/movable/target = locate(params["pick"])
@@ -1680,6 +1717,98 @@ var/global/list/belly_colorable_only_fullscreens = list("a_synth_flesh_mono",
qdel(host.vore_selected)
host.vore_selected = host.vore_organs[1]
. = TRUE
-
+ if("b_belly_sprite_to_affect")
+ if (istype(host, /mob/living/carbon/human))
+ var/mob/living/carbon/human/hhost = host
+ var/belly_choice = tgui_input_list(usr, "Which belly sprite do you want your [lowertext(hhost.vore_selected.name)] to affect?","Select Region", hhost.vore_icon_bellies)
+ if(!belly_choice) //They cancelled, no changes
+ return FALSE
+ else
+ hhost.vore_selected.belly_sprite_to_affect = belly_choice
+ hhost.update_fullness()
+ . = TRUE
+ if("b_affects_vore_sprites")
+ if (istype(host, /mob/living/carbon/human))
+ var/mob/living/carbon/human/hhost = host
+ hhost.vore_selected.affects_vore_sprites = !hhost.vore_selected.affects_vore_sprites
+ hhost.update_fullness()
+ . = TRUE
+ if("b_count_absorbed_prey_for_sprites")
+ if (istype(host, /mob/living/carbon/human))
+ var/mob/living/carbon/human/hhost = host
+ hhost.vore_selected.count_absorbed_prey_for_sprite = !hhost.vore_selected.count_absorbed_prey_for_sprite
+ hhost.update_fullness()
+ . = TRUE
+ if("b_absorbed_multiplier")
+ if (istype(host, /mob/living/carbon/human))
+ var/mob/living/carbon/human/hhost = host
+ var/absorbed_multiplier_input = input(user, "Set the impact absorbed prey's size have on your vore sprite. 1 means no scaling, 0.5 means absorbed prey count half as much, 2 means absorbed prey count double. (Range from 0.1 - 3)", "Absorbed Multiplier") as num|null
+ if(!isnull(absorbed_multiplier_input))
+ hhost.vore_selected.absorbed_multiplier = CLAMP(absorbed_multiplier_input, 0.1, 3)
+ hhost.update_fullness()
+ . = TRUE
+ if("b_count_items_for_sprites")
+ if (istype(host, /mob/living/carbon/human))
+ var/mob/living/carbon/human/hhost = host
+ hhost.vore_selected.count_items_for_sprite = !hhost.vore_selected.count_items_for_sprite
+ hhost.update_fullness()
+ . = TRUE
+ if("b_item_multiplier")
+ if (istype(host, /mob/living/carbon/human))
+ var/mob/living/carbon/human/hhost = host
+ var/item_multiplier_input = input(user, "Set the impact items will have on your vore sprite. 1 means a belly with 8 normal-sized items will count as 1 normal sized prey-thing's worth, 0.5 means items count half as much, 2 means items count double. (Range from 0.1 - 10)", "Item Multiplier") as num|null
+ if(!isnull(item_multiplier_input))
+ hhost.vore_selected.item_multiplier = CLAMP(item_multiplier_input, 0.1, 10)
+ hhost.update_fullness()
+ . = TRUE
+ if("b_health_impacts_size")
+ if (istype(host, /mob/living/carbon/human))
+ var/mob/living/carbon/human/hhost = host
+ hhost.vore_selected.health_impacts_size = !hhost.vore_selected.health_impacts_size
+ hhost.update_fullness()
+ . = TRUE
+ if("b_resist_animation")
+ if (istype(host, /mob/living/carbon/human))
+ var/mob/living/carbon/human/hhost = host
+ hhost.vore_selected.resist_triggers_animation = !hhost.vore_selected.resist_triggers_animation
+ . = TRUE
+ if("b_size_factor_sprites")
+ if (istype(host, /mob/living/carbon/human))
+ var/mob/living/carbon/human/hhost = host
+ var/size_factor_input = input(user, "Set the impact all belly content's collective size has on your vore sprite. 1 means no scaling, 0.5 means content counts half as much, 2 means contents count double. (Range from 0.1 - 3)", "Size Factor") as num|null
+ if(!isnull(size_factor_input))
+ hhost.vore_selected.size_factor_for_sprite = CLAMP(size_factor_input, 0.1, 3)
+ hhost.update_fullness()
+ . = TRUE
+ if("b_tail_to_change_to")
+ if (istype(host, /mob/living/carbon/human))
+ var/mob/living/carbon/human/hhost = host
+ var/tail_choice = tgui_input_list(usr, "Which tail sprite do you want to use when your [lowertext(host.vore_selected.name)] is filled?","Select Sprite", global.tail_styles_list)
+ if(!tail_choice) //They cancelled, no changes
+ return FALSE
+ else
+ hhost.vore_selected.tail_to_change_to = tail_choice
+ . = TRUE
+ if("b_tail_color")
+ if (istype(host, /mob/living/carbon/human))
+ var/mob/living/carbon/human/hhost = host
+ var/newcolor = input(usr, "Choose tail color.", "", hhost.vore_selected.tail_colouration) as color|null
+ if(newcolor)
+ hhost.vore_selected.tail_colouration = newcolor
+ . = TRUE
+ if("b_tail_color2")
+ if (istype(host, /mob/living/carbon/human))
+ var/mob/living/carbon/human/hhost = host
+ var/newcolor = input(usr, "Choose tail secondary color.", "", hhost.vore_selected.tail_extra_overlay) as color|null
+ if(newcolor)
+ hhost.vore_selected.tail_extra_overlay = newcolor
+ . = TRUE
+ if("b_tail_color3")
+ if (istype(host, /mob/living/carbon/human))
+ var/mob/living/carbon/human/hhost = host
+ var/newcolor = input(usr, "Choose tail tertiary color.", "", hhost.vore_selected.tail_extra_overlay2) as color|null
+ if(newcolor)
+ hhost.vore_selected.tail_extra_overlay2 = newcolor
+ . = TRUE
if(.)
unsaved_changes = TRUE
diff --git a/code/modules/vore/resizing/crackers.dm b/code/modules/vore/resizing/crackers.dm
new file mode 100644
index 00000000000..b88a87cb2a7
--- /dev/null
+++ b/code/modules/vore/resizing/crackers.dm
@@ -0,0 +1,177 @@
+//A bluespace cracker item that can be pulled between two characters.
+//The winner of the pull has an effect applied to them.
+//Crackers do already exist, but these ones are a more memey scene item.
+
+/obj/item/weapon/cracker
+ name = "bluespace cracker" //I have no idea why this was called shrink ray when this increased and decreased size.
+ desc = "A celebratory little game with a bluespace twist! Pull it between two people until it snaps, and the person who recieves the larger end gets a prize!"
+ icon = 'icons/obj/crackers.dmi'
+ icon_state = "blue"
+ item_icons = list(
+ slot_l_hand_str = 'icons/mob/items/lefthand_cracker.dmi',
+ slot_r_hand_str = 'icons/mob/items/righthand_cracker.dmi',
+ )
+ item_state = "blue"
+ var/rigged = 0 //So that they can be rigged by varedits to go one way or the other. positive values mean holder always wins, negative values mean target always wins.
+ var/list/prizes = list("shrinking","growing","drugged","invisibility","knocked over","teleport","wealth")
+ var/list/jokes = list(
+ "When is a boat just like snow? When it’s adrift.",
+ "What happens to naughty elves? Santa gives them the sack.",
+ "What do you call an old snowman? Water.",
+ "Why has Santa been banned from sooty chimneys? Carbon footprints.",
+ "What goes down but doesn't come up? A yo.",
+ "What's green and fuzzy, has four legs and would kill you if it fell from a tree? A pool table.",
+ "Why did the blind man fall into the well? Because he couldn't see that well.",
+ "What did the pirate get on his report card? Seven Cs",
+ "What do you call a fish with no eyes? Fsh",
+ "How do you make an egg roll? You push it.",
+ "What do you call a deer with no eyes? NO EYED DEER!",
+ "What's red, and smells like blue paint? Red paint.",
+ "Where do cows go to dance? A meat ball.",
+ "What do you call a person who steals all your toenail clippings? A cliptoemaniac.",
+ "What’s brown and sticky? A stick.",
+ "What's the best way to kill a circus? Go for the juggler.",
+ "What do you call a cow with no legs? Ground Beef.",
+ "Why'd the scarecrow win the Nobel prize? He was outstanding in his field.")
+
+/obj/item/weapon/cracker/attack(atom/A, mob/living/user, adjacent, params)
+ var/mob/living/carbon/human/target = A
+ if(!istype(target))
+ return
+ if(target.stat)
+ return
+ if(target == user)
+ to_chat(user, "You can't pull \the [src] by yourself, that would just be sad!")
+ return
+ var/check_pull = tgui_alert(target, "\The [user] is offering to pull \the [src] with you, do you want to pull it?", "Pull Cracker", list("Yes", "No"))
+ if(check_pull == "No")
+ to_chat(user, "\The [target] chose not to pull \the [src]!")
+ return
+ if(!adjacent)
+ to_chat(user, "\The [target] is not standing close enough to pull \the [src]!")
+ return
+ var/prize = pick(prizes)
+ var/joke = pick(jokes)
+ var/mob/living/carbon/human/winner
+ var/mob/living/carbon/human/loser
+ if(!rigged)
+ if(prob(50))
+ winner = user
+ loser = target
+ else
+ winner = target
+ loser = user
+ else
+ if(rigged > 0)
+ winner = user
+ loser = target
+ else
+ winner = target
+ loser = user
+
+ var/spawnloc = get_turf(winner)
+
+ winner.visible_message("\The [winner] wins the cracker prize!","You win the cracker prize!")
+ if(prize == "shrinking")
+ winner.resize(0.25)
+ winner.visible_message("\The [winner] shrinks suddenly!")
+ if(prize == "growing")
+ winner.resize(2)
+ winner.visible_message("\The [winner] grows in height suddenly.")
+ if(prize == "drugged")
+ winner.druggy = max(winner.druggy, 50)
+ if(prize == "invisibility")
+ if(!winner.cloaked)
+ winner.visible_message("\The [winner] vanishes from sight.")
+ winner.cloak()
+ spawn(600)
+ if(winner.cloaked)
+ winner.uncloak()
+ winner.visible_message("\The [winner] appears as if from thin air.")
+ if(prize == "knocked over")
+ winner.visible_message("\The [winner] is suddenly knocked to the ground.")
+ winner.weakened = max(winner.weakened,50)
+ if(prize == "teleport")
+ if(loser.can_be_drop_pred && loser.vore_selected)
+ if(winner.devourable && winner.can_be_drop_prey)
+ winner.visible_message("\The [winner] is teleported to somewhere nearby...")
+ var/datum/effect/effect/system/spark_spread/spk
+ spk = new(winner)
+
+ var/T = get_turf(winner)
+ spk.set_up(5, 0, winner)
+ spk.attach(winner)
+ playsound(T, "sparks", 50, 1)
+ anim(T,winner,'icons/mob/mob.dmi',,"phaseout",,winner.dir)
+ winner.forceMove(loser.vore_selected)
+ if(prize == "wealth")
+ new /obj/random/cash/huge(spawnloc)
+ new /obj/random/cash/huge(spawnloc)
+ winner.visible_message("\The [winner] has a whole load of cash fall at their feet!")
+
+ playsound(user, 'sound/effects/snap.ogg', 50, 1)
+ user.drop_item(src)
+ new /obj/random/toy(spawnloc)
+ new /obj/item/clothing/head/paper_crown(spawnloc)
+ var/obj/item/weapon/paper/cracker_joke/J = new(spawnloc)
+ J.info = joke
+ qdel(src)
+
+/obj/item/weapon/cracker/Initialize()
+ var/list/styles = list("blue","green","yellow","red","heart","hazard")
+ var/style = pick(styles)
+ icon_state = style
+ item_state = style
+ ..()
+
+/obj/item/weapon/cracker/shrinking
+ name = "shrinking bluespace cracker"
+ prizes = list("shrinking")
+
+/obj/item/weapon/cracker/growing
+ name = "growing bluespace cracker"
+ prizes = list("growing")
+
+/obj/item/weapon/cracker/invisibility
+ name = "cloaking bluespace cracker"
+ prizes = list("invisibility")
+
+/obj/item/weapon/cracker/drugged
+ name = "psychedelic bluespace cracker"
+ prizes = list("drugged")
+
+/obj/item/weapon/cracker/knockover
+ name = "forceful bluespace cracker"
+ prizes = list("knocked over")
+
+/obj/item/weapon/cracker/vore
+ name = "teleporting bluespace cracker"
+ prizes = list("teleport")
+
+/obj/item/weapon/cracker/money
+ name = "fortuitous bluespace cracker"
+ prizes = list("wealth")
+
+/obj/item/clothing/head/paper_crown
+ name = "paper crown"
+ icon_state = "paper_crown_blue"
+ item_state = "paper_crown_blue"
+ item_icons = list(slot_head_str = 'icons/inventory/head/mob.dmi')
+ desc = "A paper crown so thin that you can see through it."
+ flags_inv = 0
+ body_parts_covered = 0
+ armor = list(melee = 0, bullet = 0, laser = 0,energy = 0, bomb = 0, bio = 0, rad = 0)
+
+/obj/item/clothing/head/paper_crown/Initialize()
+ var/list/styles = list("paper_crown_blue","paper_crown_green","paper_crown_yellow","paper_crown_red","paper_crown_pink")
+ var/style = pick(styles)
+ icon_state = style
+ item_state = style
+ ..()
+
+/obj/item/weapon/paper/cracker_joke
+ name = "joke"
+ icon_state = "joke"
+
+/obj/item/weapon/paper/cracker_joke/update_icon()
+ return
diff --git a/code/modules/vore/resizing/sizegun_vr.dm b/code/modules/vore/resizing/sizegun_vr.dm
index 4bbaaff0433..2bbd6010354 100644
--- a/code/modules/vore/resizing/sizegun_vr.dm
+++ b/code/modules/vore/resizing/sizegun_vr.dm
@@ -15,6 +15,7 @@
origin_tech = list(TECH_BLUESPACE = 4)
modifystate = "sizegun-shrink"
battery_lock = 1
+ var/backfire = 0
var/size_set_to = 1
firemodes = list(
list(mode_name = "select size",
@@ -133,6 +134,54 @@
size_set_to = clamp((size_select/100), 0, 1000) //eheh
to_chat(usr, "You set the size to [size_select]%")
+/obj/item/weapon/gun/energy/sizegun/afterattack(atom/A, mob/living/user, adjacent, params)
+ if(adjacent) return //A is adjacent, is the user, or is on the user's person
+
+ if(backfire)
+ if(prob(50))
+ to_chat(user, "\The [src] backfires and consumes its entire charge!")
+ Fire(user, user)
+ power_supply.charge = 0
+ var/mob/living/M = loc // TGMC Ammo HUD
+ if(istype(M)) // TGMC Ammo HUD
+ M?.hud_used.update_ammo_hud(M, src)
+ return
+ else
+ return ..()
+ else
+ return ..()
+
+/obj/item/weapon/gun/energy/sizegun/attack(atom/A, mob/living/user, adjacent, params)
+ if(backfire)
+ if(prob(50))
+ to_chat(user, "\The [src] backfires and consumes its entire charge!")
+ Fire(user, user)
+ power_supply.charge = 0
+ var/mob/living/M = loc // TGMC Ammo HUD
+ if(istype(M)) // TGMC Ammo HUD
+ M?.hud_used.update_ammo_hud(M, src)
+ return
+ else
+ return ..()
+ else
+ return ..()
+
+
+/obj/item/weapon/gun/energy/sizegun/attackby(var/obj/item/A as obj, mob/user as mob)
+ if(A.has_tool_quality(TOOL_WIRECUTTER))
+ if(backfire)
+ to_chat(user, "You repair the damage to the \the [src].")
+ backfire = 0
+ name = "size gun"
+ else
+ to_chat(user, "You snip a wire on \the [src], making it less reliable.")
+ backfire = 1
+ name = "unstable size gun"
+ ..()
+
+/obj/item/weapon/gun/energy/sizegun/backfire
+ name = "unstable size gun"
+ backfire = 1
/obj/item/weapon/gun/energy/sizegun/mounted
name = "mounted size gun"
diff --git a/code/modules/xenobio2/machinery/injector_computer.dm b/code/modules/xenobio2/machinery/injector_computer.dm
index 196543a2560..e4ac65403a4 100644
--- a/code/modules/xenobio2/machinery/injector_computer.dm
+++ b/code/modules/xenobio2/machinery/injector_computer.dm
@@ -46,6 +46,7 @@
..()
+//Is missing a tgui interface?
/obj/machinery/computer/xenobio2/ui_interact(mob/user, ui_key = "main", var/datum/nanoui/ui = null, var/force_open = 1)
if(!user)
return
diff --git a/icons/inventory/head/item.dmi b/icons/inventory/head/item.dmi
index 538c3a57ee2..7897ed22072 100644
Binary files a/icons/inventory/head/item.dmi and b/icons/inventory/head/item.dmi differ
diff --git a/icons/inventory/head/mob.dmi b/icons/inventory/head/mob.dmi
index 635188fbada..8f7e232b3d2 100644
Binary files a/icons/inventory/head/mob.dmi and b/icons/inventory/head/mob.dmi differ
diff --git a/icons/mob/human_face_vr.dmi b/icons/mob/human_face_vr.dmi
index ea687def736..5da8720d000 100644
Binary files a/icons/mob/human_face_vr.dmi and b/icons/mob/human_face_vr.dmi differ
diff --git a/icons/mob/human_races/r_lleill.dmi b/icons/mob/human_races/r_lleill.dmi
new file mode 100644
index 00000000000..a3f6d1429b5
Binary files /dev/null and b/icons/mob/human_races/r_lleill.dmi differ
diff --git a/icons/mob/items/lefthand_cracker.dmi b/icons/mob/items/lefthand_cracker.dmi
new file mode 100644
index 00000000000..6e2d5776fce
Binary files /dev/null and b/icons/mob/items/lefthand_cracker.dmi differ
diff --git a/icons/mob/items/righthand_cracker.dmi b/icons/mob/items/righthand_cracker.dmi
new file mode 100644
index 00000000000..0818ed3ebc6
Binary files /dev/null and b/icons/mob/items/righthand_cracker.dmi differ
diff --git a/icons/mob/species/lleill/tail.dmi b/icons/mob/species/lleill/tail.dmi
new file mode 100644
index 00000000000..3ff90038904
Binary files /dev/null and b/icons/mob/species/lleill/tail.dmi differ
diff --git a/icons/mob/vore.dmi b/icons/mob/vore.dmi
index 2aabf0438b7..e11ef54c8d7 100644
Binary files a/icons/mob/vore.dmi and b/icons/mob/vore.dmi differ
diff --git a/icons/mob/vore/Bellies.dmi b/icons/mob/vore/Bellies.dmi
new file mode 100644
index 00000000000..4f0d94f0083
Binary files /dev/null and b/icons/mob/vore/Bellies.dmi differ
diff --git a/icons/mob/vore/Taur_Bellies.dmi b/icons/mob/vore/Taur_Bellies.dmi
new file mode 100644
index 00000000000..e606c64d183
Binary files /dev/null and b/icons/mob/vore/Taur_Bellies.dmi differ
diff --git a/icons/mob/vore/ears_32x64.dmi b/icons/mob/vore/ears_32x64.dmi
index cfae3acbcb1..1e73072d946 100644
Binary files a/icons/mob/vore/ears_32x64.dmi and b/icons/mob/vore/ears_32x64.dmi differ
diff --git a/icons/obj/bureaucracy.dmi b/icons/obj/bureaucracy.dmi
index 5c0531f5b7d..61365297d24 100644
Binary files a/icons/obj/bureaucracy.dmi and b/icons/obj/bureaucracy.dmi differ
diff --git a/icons/obj/crackers.dmi b/icons/obj/crackers.dmi
new file mode 100644
index 00000000000..ac657f46f92
Binary files /dev/null and b/icons/obj/crackers.dmi differ
diff --git a/icons/obj/glamour.dmi b/icons/obj/glamour.dmi
new file mode 100644
index 00000000000..2682d7badcf
Binary files /dev/null and b/icons/obj/glamour.dmi differ
diff --git a/icons/obj/seeds.dmi b/icons/obj/seeds.dmi
index e181299f135..6b5dc5dab58 100644
Binary files a/icons/obj/seeds.dmi and b/icons/obj/seeds.dmi differ
diff --git a/icons/obj/stationobjs_vr.dmi b/icons/obj/stationobjs_vr.dmi
index aa6df06395e..8ce777ec974 100644
Binary files a/icons/obj/stationobjs_vr.dmi and b/icons/obj/stationobjs_vr.dmi differ
diff --git a/icons/turf/flooring/glamour.dmi b/icons/turf/flooring/glamour.dmi
index 74ee6430659..8f60748a652 100644
Binary files a/icons/turf/flooring/glamour.dmi and b/icons/turf/flooring/glamour.dmi differ
diff --git a/maps/redgate/fantasy_dungeon.dmm b/maps/redgate/fantasy_dungeon.dmm
index 02cbdea6c88..96aef51a37d 100644
--- a/maps/redgate/fantasy_dungeon.dmm
+++ b/maps/redgate/fantasy_dungeon.dmm
@@ -559,10 +559,7 @@
/turf/simulated/floor/wmarble,
/area/redgate/fantasy/dungeon)
"me" = (
-/mob/living/simple_mob/vore/demon/engorge{
- ai_holder_type = /datum/ai_holder/simple_mob/vore;
- vore_pounce_chance = 50
- },
+/mob/living/simple_mob/vore/sonadile,
/turf/simulated/floor/cult,
/area/redgate/fantasy/dungeon)
"ml" = (
@@ -1625,14 +1622,7 @@
/turf/simulated/floor/concrete,
/area/redgate/fantasy/ratbasement)
"Hn" = (
-/mob/living/simple_mob/vore/demon{
- ai_holder_type = /datum/ai_holder/simple_mob/vore;
- harm_intent_damage = 8;
- health = 300;
- melee_damage_lower = 5;
- melee_damage_upper = 2;
- vore_pounce_chance = 50
- },
+/mob/living/simple_mob/vore/devil,
/turf/simulated/floor/cult,
/area/redgate/fantasy/dungeon)
"Hx" = (
@@ -11776,7 +11766,7 @@ kn
jI
ah
ah
-Hn
+ah
ah
Xp
hR
@@ -12346,7 +12336,7 @@ ah
ah
ah
ah
-Hn
+ah
ah
kn
kn
diff --git a/maps/redgate/jungle.dmm b/maps/redgate/jungle.dmm
index 4c5e0f0f1e3..9308753680f 100644
--- a/maps/redgate/jungle.dmm
+++ b/maps/redgate/jungle.dmm
@@ -728,14 +728,7 @@
/turf/simulated/shuttle/wall,
/area/redgate/jungle/westcaves)
"ik" = (
-/mob/living/simple_mob/vore/demon{
- ai_holder_type = /datum/ai_holder/simple_mob/vore;
- harm_intent_damage = 8;
- health = 300;
- melee_damage_lower = 5;
- melee_damage_upper = 2;
- vore_pounce_chance = 50
- },
+/mob/living/simple_mob/vore/devil,
/turf/simulated/floor/gorefloor,
/area/redgate/jungle/temple)
"il" = (
@@ -3510,13 +3503,6 @@
},
/turf/simulated/floor/outdoors/grass/forest,
/area/redgate/jungle/aboveground)
-"PG" = (
-/mob/living/simple_mob/vore/demon/engorge{
- ai_holder_type = /datum/ai_holder/simple_mob/vore;
- vore_pounce_chance = 50
- },
-/turf/simulated/floor/gorefloor2,
-/area/redgate/jungle/temple)
"PK" = (
/obj/item/weapon/bedsheet/pillow,
/turf/simulated/mineral/floor/cave,
@@ -9402,7 +9388,7 @@ Ji
Ji
pL
GT
-ik
+GT
GT
GT
rI
@@ -9545,7 +9531,7 @@ Ji
ue
GT
GT
-GT
+ik
GT
rI
rI
@@ -9689,7 +9675,7 @@ GT
GT
GT
rI
-PG
+rI
rI
pL
pL
diff --git a/tgui/packages/common/type-utils.ts b/tgui/packages/common/type-utils.ts
new file mode 100644
index 00000000000..a73c0c1d595
--- /dev/null
+++ b/tgui/packages/common/type-utils.ts
@@ -0,0 +1,41 @@
+/**
+ * Helps visualize highly complex ui data on the fly.
+ * @example
+ * ```tsx
+ * const { data } = useBackend();
+ * logger.log(getShallowTypes(data));
+ * ```
+ */
+export function getShallowTypes(
+ data: Record,
+): Record {
+ const output = {};
+
+ for (const key in data) {
+ if (Array.isArray(data[key])) {
+ const arr: any[] = data[key];
+
+ // Return the first array item if it exists
+ if (data[key].length > 0) {
+ output[key] = arr[0];
+ continue;
+ }
+
+ output[key] = 'emptyarray';
+ } else if (typeof data[key] === 'object' && data[key] !== null) {
+ // Please inspect it further and make a new type for it
+ output[key] = 'object (inspect) || Record';
+ } else if (typeof data[key] === 'number') {
+ const num = Number(data[key]);
+
+ // 0 and 1 could be booleans from byond
+ if (num === 1 || num === 0) {
+ output[key] = `${num}, BooleanLike?`;
+ continue;
+ }
+ output[key] = data[key];
+ }
+ }
+
+ return output;
+}
diff --git a/tgui/packages/tgui-dev-server/package.json b/tgui/packages/tgui-dev-server/package.json
index 5f1b5be3e54..3a92ec35c95 100644
--- a/tgui/packages/tgui-dev-server/package.json
+++ b/tgui/packages/tgui-dev-server/package.json
@@ -8,6 +8,6 @@
"glob": "^7.2.3",
"source-map": "^0.7.4",
"stacktrace-parser": "^0.1.10",
- "ws": "^8.16.0"
+ "ws": "^8.17.1"
}
}
diff --git a/tgui/packages/tgui-panel/chat/middleware.js b/tgui/packages/tgui-panel/chat/middleware.js
index 57dab303b5f..f1745b353bf 100644
--- a/tgui/packages/tgui-panel/chat/middleware.js
+++ b/tgui/packages/tgui-panel/chat/middleware.js
@@ -170,7 +170,7 @@ export const chatMiddleware = (store) => {
settings.interleaveColor,
);
// Load the chat once settings are loaded
- if (!initialized && settings.initialized) {
+ if (!initialized && (settings.initialized || settings.firstLoad)) {
initialized = true;
setInterval(() => {
saveChatToStorage(store);
diff --git a/tgui/packages/tgui-panel/settings/reducer.js b/tgui/packages/tgui-panel/settings/reducer.js
index cf5467831cd..c34ccf554a1 100644
--- a/tgui/packages/tgui-panel/settings/reducer.js
+++ b/tgui/packages/tgui-panel/settings/reducer.js
@@ -57,6 +57,7 @@ const initialState = {
exportEnd: 0,
lastId: null,
initialized: false,
+ firstLoad: false,
storedTypes: {},
hideImportantInAdminTab: false,
interleave: false,
@@ -82,6 +83,7 @@ export const settingsReducer = (state = initialState, action) => {
if (type === loadSettings.type) {
// Validate version and/or migrate state
if (!payload?.version) {
+ state.firstLoad = true;
return state;
}
diff --git a/tgui/packages/tgui-panel/telemetry.js b/tgui/packages/tgui-panel/telemetry.js
index 3b1eb9a9500..581d32f3069 100644
--- a/tgui/packages/tgui-panel/telemetry.js
+++ b/tgui/packages/tgui-panel/telemetry.js
@@ -36,6 +36,14 @@ export const telemetryMiddleware = (store) => {
Byond.sendMessage('telemetry', { connections });
return;
}
+ // For whatever reason we didn't get the telemetry, re-request
+ if (type === 'testTelemetryCommand') {
+ setTimeout(() => {
+ if (!telemetry) {
+ Byond.sendMessage('ready');
+ }
+ }, 500);
+ }
// Keep telemetry up to date
if (type === 'backend/update') {
next(action);
diff --git a/tgui/packages/tgui/backend.ts b/tgui/packages/tgui/backend.ts
index 4af2e7418c2..9d10d3f41f3 100644
--- a/tgui/packages/tgui/backend.ts
+++ b/tgui/packages/tgui/backend.ts
@@ -262,6 +262,8 @@ type BackendState = {
status: number;
interface: string;
refreshing: boolean;
+ map: string; // Vorestation Add
+ mapZLevel: number; // Vorestation Add
window: {
key: string;
size: [number, number];
diff --git a/tgui/packages/tgui/components/Collapsible.tsx b/tgui/packages/tgui/components/Collapsible.tsx
index b470ed5ce6d..8f53e7d92b7 100644
--- a/tgui/packages/tgui/components/Collapsible.tsx
+++ b/tgui/packages/tgui/components/Collapsible.tsx
@@ -13,11 +13,12 @@ type Props = Partial<{
buttons: ReactNode;
open: boolean;
title: ReactNode;
+ child_mt: number; // Vorestation Add
}> &
BoxProps;
export function Collapsible(props: Props) {
- const { children, color, title, buttons, ...rest } = props;
+ const { children, color, title, buttons, child_mt = 1, ...rest } = props;
const [open, setOpen] = useState(props.open);
return (
@@ -38,7 +39,7 @@ export function Collapsible(props: Props) {
{buttons}
)}
- {open && {children}}
+ {open && {children}}
);
}
diff --git a/tgui/packages/tgui/components/NoticeBox.tsx b/tgui/packages/tgui/components/NoticeBox.tsx
index db4f821d6b6..73abc743223 100644
--- a/tgui/packages/tgui/components/NoticeBox.tsx
+++ b/tgui/packages/tgui/components/NoticeBox.tsx
@@ -11,7 +11,7 @@ import { Box, BoxProps } from './Box';
type Props = ExclusiveProps & BoxProps;
/** You MUST use only one or none */
-type NoticeType = 'info' | 'success' | 'danger';
+type NoticeType = 'info' | 'success' | 'warning' | 'danger';
type None = {
[K in NoticeType]?: undefined;
@@ -25,12 +25,15 @@ type ExclusiveProps =
| (Omit & {
success: boolean;
})
+ | (Omit & {
+ warning: boolean;
+ })
| (Omit & {
danger: boolean;
});
export function NoticeBox(props: Props) {
- const { className, color, info, success, danger, ...rest } = props;
+ const { className, color, info, success, danger, warning, ...rest } = props;
return (
void;
rightSlot: ReactNode;
@@ -57,6 +58,7 @@ const Tab = (props: TabProps) => {
selected,
color,
icon,
+ iconSpin, // Vorestation Add
leftSlot,
rightSlot,
children,
@@ -87,7 +89,7 @@ const Tab = (props: TabProps) => {
{(canRender(leftSlot) && {leftSlot} ) ||
(!!icon && (
-
+
))}
{children}
diff --git a/tgui/packages/tgui/interfaces/AICard.jsx b/tgui/packages/tgui/interfaces/AICard.tsx
similarity index 86%
rename from tgui/packages/tgui/interfaces/AICard.jsx
rename to tgui/packages/tgui/interfaces/AICard.tsx
index 1c2125e5d7e..2d4dc59ad79 100644
--- a/tgui/packages/tgui/interfaces/AICard.jsx
+++ b/tgui/packages/tgui/interfaces/AICard.tsx
@@ -1,11 +1,26 @@
+import { BooleanLike } from 'common/react';
+
import { useBackend } from '../backend';
import { Box, Button, LabeledList, ProgressBar, Section } from '../components';
import { Window } from '../layouts';
+type Data = {
+ name: string;
+ has_ai: BooleanLike;
+ integrity: number;
+ backup_capacitor: number;
+ flushing: BooleanLike;
+ has_laws: BooleanLike;
+ laws: string[];
+ wireless: BooleanLike;
+ radio: BooleanLike;
+};
+
export const AICard = (props) => {
- const { act, data } = useBackend();
+ const { act, data } = useBackend();
const {
+ name,
has_ai,
integrity,
backup_capacitor,
@@ -16,7 +31,7 @@ export const AICard = (props) => {
radio,
} = data;
- if (has_ai === 0) {
+ if (!has_ai) {
return (
@@ -29,7 +44,7 @@ export const AICard = (props) => {
);
} else {
- let integrityColor = null; // Handles changing color of the integrity bar
+ let integrityColor: string | undefined; // Handles changing color of the integrity bar
if (integrity >= 75) {
integrityColor = 'green';
} else if (integrity >= 25) {
@@ -38,7 +53,7 @@ export const AICard = (props) => {
integrityColor = 'red';
}
- let powerColor = null;
+ let powerColor: string | undefined;
if (backup_capacitor >= 75) {
powerColor = 'green';
}
@@ -52,7 +67,7 @@ export const AICard = (props) => {
-
+
{name}
@@ -77,7 +92,7 @@ export const AICard = (props) => {
{(!!has_laws && (
{laws.map((value, key) => (
-
+
{value}
))}
diff --git a/tgui/packages/tgui/interfaces/APC.jsx b/tgui/packages/tgui/interfaces/APC.tsx
similarity index 67%
rename from tgui/packages/tgui/interfaces/APC.jsx
rename to tgui/packages/tgui/interfaces/APC.tsx
index 032e5fb8bbc..9df33fc68a9 100644
--- a/tgui/packages/tgui/interfaces/APC.jsx
+++ b/tgui/packages/tgui/interfaces/APC.tsx
@@ -1,3 +1,5 @@
+import { BooleanLike } from 'common/react';
+
import { useBackend } from '../backend';
import {
Box,
@@ -12,14 +14,46 @@ import { Window } from '../layouts';
import { FullscreenNotice } from './common/FullscreenNotice';
import { InterfaceLockNoticeBox } from './common/InterfaceLockNoticeBox';
+type Data = {
+ gridCheck: BooleanLike;
+ failTime: number;
+ locked: BooleanLike;
+ normallyLocked: BooleanLike;
+ siliconUser: BooleanLike;
+ externalPower;
+ chargingStatus;
+ powerChannels: {
+ title: string;
+ powerLoad: number;
+ status: number;
+ topicParams: {
+ auto: Record;
+ on: Record;
+ off: Record;
+ }[];
+ };
+ powerCellStatus: number;
+ emagged: BooleanLike;
+ isOperating: BooleanLike;
+ chargeMode: BooleanLike;
+ totalCharging: number;
+ totalLoad: number;
+ coverLocked: BooleanLike;
+ nightshiftLights: BooleanLike;
+ nightshiftSetting: number;
+ emergencyLights: boolean;
+};
+
export const APC = (props) => {
- const { act, data } = useBackend();
+ const { act, data } = useBackend();
+
+ const { gridCheck, failTime } = data;
let body = ;
- if (data.gridCheck) {
+ if (gridCheck) {
body = ;
- } else if (data.failTime) {
+ } else if (failTime) {
body = ;
}
@@ -30,7 +64,13 @@ export const APC = (props) => {
);
};
-const powerStatusMap = {
+type powerStatus = {
+ color: string;
+ externalPowerText: string;
+ chargingText: string;
+};
+
+const powerStatusMap: Record = {
2: {
color: 'good',
externalPowerText: 'External Power',
@@ -48,7 +88,10 @@ const powerStatusMap = {
},
};
-const malfMap = {
+const malfMap: Record<
+ number,
+ { icon: string; content: string; action: string }
+> = {
1: {
icon: 'terminal',
content: 'Override Programming',
@@ -72,21 +115,38 @@ const malfMap = {
};
const ApcContent = (props) => {
- const { act, data } = useBackend();
- const locked = data.locked && !data.siliconUser;
- const normallyLocked = data.normallyLocked;
- const externalPowerStatus =
- powerStatusMap[data.externalPower] || powerStatusMap[0];
- const chargingStatus =
- powerStatusMap[data.chargingStatus] || powerStatusMap[0];
- const channelArray = data.powerChannels || [];
+ const { act, data } = useBackend();
+
+ const {
+ locked,
+ siliconUser,
+ externalPower,
+ chargingStatus,
+ powerChannels,
+ powerCellStatus,
+ emagged,
+ isOperating,
+ chargeMode,
+ totalCharging,
+ totalLoad,
+ coverLocked,
+ nightshiftSetting,
+ emergencyLights,
+ } = data;
+
+ const is_locked: BooleanLike = locked && !siliconUser;
+ const externalPowerStatus: powerStatus =
+ powerStatusMap[externalPower] || powerStatusMap[0];
+ const chargingPowerStatus: powerStatus =
+ powerStatusMap[chargingStatus] || powerStatusMap[0];
+ const channelArray: any = powerChannels || [];
// const malfStatus = malfMap[data.malfStatus] || null;
- const adjustedCellChange = data.powerCellStatus / 100;
+ const adjustedCellChange: number = powerCellStatus / 100;
return (
<>
@@ -103,13 +163,13 @@ const ApcContent = (props) => {
color={externalPowerStatus.color}
buttons={
}
>
@@ -120,19 +180,19 @@ const ApcContent = (props) => {
act('charge')}
>
- {data.chargeMode ? 'Auto' : 'Off'}
+ {chargeMode ? 'Auto' : 'Off'}
}
>
- [ {chargingStatus.chargingText} ]
+ [ {chargingPowerStatus.chargingText} ]
@@ -156,26 +216,26 @@ const ApcContent = (props) => {
- Automatic reboot in {data.failTime} seconds...
+ Automatic reboot in {failTime} seconds...
{rebootOptions}
);
diff --git a/tgui/packages/tgui/interfaces/AccountsTerminal.jsx b/tgui/packages/tgui/interfaces/AccountsTerminal.tsx
similarity index 82%
rename from tgui/packages/tgui/interfaces/AccountsTerminal.jsx
rename to tgui/packages/tgui/interfaces/AccountsTerminal.tsx
index 9831a560639..ba2fd43bb53 100644
--- a/tgui/packages/tgui/interfaces/AccountsTerminal.jsx
+++ b/tgui/packages/tgui/interfaces/AccountsTerminal.tsx
@@ -1,3 +1,5 @@
+import { BooleanLike } from 'common/react';
+
import { useBackend, useSharedState } from '../backend';
import {
Box,
@@ -10,8 +12,36 @@ import {
} from '../components';
import { Window } from '../layouts';
+type Data = {
+ id_inserted: BooleanLike;
+ id_card: string;
+ access_level: number;
+ machine_id: string;
+ creating_new_account: BooleanLike;
+ detailed_account_view: boolean;
+ station_account_number: number;
+ account_number: number | null;
+ owner_name: string | null;
+ money: number | null;
+ suspended: BooleanLike;
+ transactions: {
+ date: string;
+ time: string;
+ target_name: string;
+ purpose: string;
+ amount: number;
+ source_terminal: string;
+ }[];
+ accounts: {
+ account_number: number;
+ owner_name: string;
+ suspended: string;
+ account_index: number;
+ }[];
+};
+
export const AccountsTerminal = (props) => {
- const { act, data } = useBackend();
+ const { act, data } = useBackend();
const { id_inserted, id_card, access_level, machine_id } = data;
@@ -41,7 +71,7 @@ export const AccountsTerminal = (props) => {
};
const AccountTerminalContent = (props) => {
- const { act, data } = useBackend();
+ const { act, data } = useBackend();
const { creating_new_account, detailed_account_view } = data;
@@ -56,18 +86,14 @@ const AccountTerminalContent = (props) => {
Home
act('create_account')}
>
New Account
{!creating_new_account ? (
- act('print')}
- >
+ act('print')}>
Print
) : (
@@ -81,13 +107,13 @@ const AccountTerminalContent = (props) => {
};
const NewAccountView = (props) => {
- const { act } = useBackend();
+ const { act } = useBackend();
const [holder, setHolder] = useSharedState('holder', '');
const [newMoney, setMoney] = useSharedState('money', '');
return (
-
+
setHolder(val)} />
@@ -115,7 +141,7 @@ const NewAccountView = (props) => {
};
const DetailedView = (props) => {
- const { act, data } = useBackend();
+ const { act, data } = useBackend();
const {
access_level,
@@ -130,7 +156,6 @@ const DetailedView = (props) => {
return (
{
{suspended ? 'SUSPENDED' : 'Active'}
-
+
{access_level >= 2 && (
-
+
act('add_funds')}>
Add Funds
@@ -178,7 +203,7 @@ const DetailedView = (props) => {
)}
-
+
Timestamp
@@ -205,18 +230,18 @@ const DetailedView = (props) => {
};
const ListView = (props) => {
- const { act, data } = useBackend();
+ const { act, data } = useBackend();
const { accounts } = data;
return (
-
+
{(accounts.length && (
{accounts.map((acc) => (
{
{State[state]}
{State[state] === State.open ? (
- {opened_at_date} ({Math.round((opened_at / 600) * 10) / 10}{' '}
- minutes ago.)
+ {opened_at_date +
+ ' (' +
+ toFixed(round((opened_at / 600) * 10, 0) / 10, 1) +
+ ' minutes ago.)'}
) : (
- {closed_at_date} ({Math.round((closed_at / 600) * 10) / 10}{' '}
- minutes ago.){' '}
+ {closed_at_date +
+ ' (' +
+ toFixed(round((closed_at / 600) * 10, 0) / 10, 1) +
+ ' minutes ago.)'}
act('reopen')}>Reopen
)}
diff --git a/tgui/packages/tgui/interfaces/AiAirlock.jsx b/tgui/packages/tgui/interfaces/AiAirlock.tsx
similarity index 54%
rename from tgui/packages/tgui/interfaces/AiAirlock.jsx
rename to tgui/packages/tgui/interfaces/AiAirlock.tsx
index e13cf1331b6..169a0ea8977 100644
--- a/tgui/packages/tgui/interfaces/AiAirlock.jsx
+++ b/tgui/packages/tgui/interfaces/AiAirlock.tsx
@@ -1,8 +1,40 @@
+import { BooleanLike } from 'common/react';
+
import { useBackend } from '../backend';
import { Button, LabeledList, Section } from '../components';
import { Window } from '../layouts';
-const dangerMap = {
+type Data = {
+ power: {
+ main: number;
+ main_timeleft: number;
+ backup: number;
+ backup_timeleft: number;
+ };
+ wires: {
+ main_1: BooleanLike;
+ main_2: BooleanLike;
+ backup_1: BooleanLike;
+ backup_2: BooleanLike;
+ shock: BooleanLike;
+ id_scanner: BooleanLike;
+ bolts: BooleanLike;
+ lights: BooleanLike;
+ safe: BooleanLike;
+ timing: BooleanLike;
+ };
+ shock: number;
+ shock_timeleft: number;
+ id_scanner: BooleanLike;
+ lights: BooleanLike;
+ locked: BooleanLike;
+ safe: BooleanLike;
+ speed: BooleanLike;
+ opened: BooleanLike;
+ welded: BooleanLike;
+};
+
+const dangerMap: Record = {
2: {
color: 'good',
localStatusText: 'Optimal',
@@ -18,10 +50,25 @@ const dangerMap = {
};
export const AiAirlock = (props) => {
- const { act, data } = useBackend();
- const statusMain = dangerMap[data.power.main] || dangerMap[0];
- const statusBackup = dangerMap[data.power.backup] || dangerMap[0];
- const statusElectrify = dangerMap[data.shock] || dangerMap[0];
+ const { act, data } = useBackend();
+
+ const {
+ power,
+ wires,
+ shock,
+ shock_timeleft,
+ id_scanner,
+ lights,
+ locked,
+ safe,
+ speed,
+ opened,
+ welded,
+ } = data;
+
+ const statusMain = dangerMap[power.main] || dangerMap[0];
+ const statusBackup = dangerMap[power.backup] || dangerMap[0];
+ const statusElectrify = dangerMap[shock] || dangerMap[0];
return (
@@ -33,18 +80,16 @@ export const AiAirlock = (props) => {
buttons={
act('disrupt-main')}
>
Disrupt
}
>
- {data.power.main ? 'Online' : 'Offline'}{' '}
- {((!data.wires.main_1 || !data.wires.main_2) &&
- '[Wires have been cut!]') ||
- (data.power.main_timeleft > 0 &&
- `[${data.power.main_timeleft}s]`)}
+ {power.main ? 'Online' : 'Offline'}{' '}
+ {((!wires.main_1 || !wires.main_2) && '[Wires have been cut!]') ||
+ (power.main_timeleft > 0 && `[${power.main_timeleft}s]`)}
{
buttons={
act('disrupt-backup')}
>
Disrupt
}
>
- {data.power.backup ? 'Online' : 'Offline'}{' '}
- {((!data.wires.backup_1 || !data.wires.backup_2) &&
+ {power.backup ? 'Online' : 'Offline'}{' '}
+ {((!wires.backup_1 || !wires.backup_2) &&
'[Wires have been cut!]') ||
- (data.power.backup_timeleft > 0 &&
- `[${data.power.backup_timeleft}s]`)}
+ (power.backup_timeleft > 0 && `[${power.backup_timeleft}s]`)}
{
<>
act('shock-restore')}
>
Restore
act('shock-temp')}
>
Temporary
act('shock-perm')}
>
Permanent
@@ -94,10 +138,10 @@ export const AiAirlock = (props) => {
>
}
>
- {data.shock === 2 ? 'Safe' : 'Electrified'}{' '}
- {(!data.wires.shock && '[Wires have been cut!]') ||
- (data.shock_timeleft > 0 && `[${data.shock_timeleft}s]`) ||
- (data.shock_timeleft === -1 && '[Permanent]')}
+ {shock === 2 ? 'Safe' : 'Electrified'}{' '}
+ {(!wires.shock && '[Wires have been cut!]') ||
+ (shock_timeleft > 0 && `[${shock_timeleft}s]`) ||
+ (shock_timeleft === -1 && '[Permanent]')}
@@ -108,16 +152,16 @@ export const AiAirlock = (props) => {
color="bad"
buttons={
act('idscan-toggle')}
>
- {data.id_scanner ? 'Enabled' : 'Disabled'}
+ {id_scanner ? 'Enabled' : 'Disabled'}
}
>
- {!data.wires.id_scanner && '[Wires have been cut!]'}
+ {!wires.id_scanner && '[Wires have been cut!]'}
{
color="bad"
buttons={
act('bolt-toggle')}
>
- {data.locked ? 'Lowered' : 'Raised'}
+ {locked ? 'Lowered' : 'Raised'}
}
>
- {!data.wires.bolts && '[Wires have been cut!]'}
+ {!wires.bolts && '[Wires have been cut!]'}
act('light-toggle')}
>
- {data.lights ? 'Enabled' : 'Disabled'}
+ {lights ? 'Enabled' : 'Disabled'}
}
>
- {!data.wires.lights && '[Wires have been cut!]'}
+ {!wires.lights && '[Wires have been cut!]'}
act('safe-toggle')}
>
- {data.safe ? 'Enabled' : 'Disabled'}
+ {safe ? 'Enabled' : 'Disabled'}
}
>
- {!data.wires.safe && '[Wires have been cut!]'}
+ {!wires.safe && '[Wires have been cut!]'}
act('speed-toggle')}
>
- {data.speed ? 'Enabled' : 'Disabled'}
+ {speed ? 'Enabled' : 'Disabled'}
}
>
- {!data.wires.timing && '[Wires have been cut!]'}
+ {!wires.timing && '[Wires have been cut!]'}
{
color="bad"
buttons={
act('open-close')}
>
- {data.opened ? 'Open' : 'Closed'}
+ {opened ? 'Open' : 'Closed'}
}
>
- {!!(data.locked || data.welded) && (
+ {!!(locked || welded) && (
- [Door is {data.locked ? 'bolted' : ''}
- {data.locked && data.welded ? ' and ' : ''}
- {data.welded ? 'welded' : ''}!]
+ [Door is {locked ? 'bolted' : ''}
+ {locked && welded ? ' and ' : ''}
+ {welded ? 'welded' : ''}!]
)}
diff --git a/tgui/packages/tgui/interfaces/AiRestorer.jsx b/tgui/packages/tgui/interfaces/AiRestorer.tsx
similarity index 87%
rename from tgui/packages/tgui/interfaces/AiRestorer.jsx
rename to tgui/packages/tgui/interfaces/AiRestorer.tsx
index c6b4c81cca5..7db847a58c8 100644
--- a/tgui/packages/tgui/interfaces/AiRestorer.jsx
+++ b/tgui/packages/tgui/interfaces/AiRestorer.tsx
@@ -1,3 +1,5 @@
+import { BooleanLike } from 'common/react';
+
import { useBackend } from '../backend';
import {
Box,
@@ -9,6 +11,17 @@ import {
} from '../components';
import { Window } from '../layouts';
+type Data = {
+ AI_present: boolean;
+ error: string | null;
+ name: string;
+ laws: string[];
+ isDead: boolean;
+ restoring: BooleanLike;
+ health: number;
+ ejectable: boolean;
+};
+
export const AiRestorer = () => {
return (
@@ -20,7 +33,7 @@ export const AiRestorer = () => {
};
export const AiRestorerContent = (props) => {
- const { act, data } = useBackend();
+ const { act, data } = useBackend();
const {
AI_present,
error,
@@ -81,7 +94,7 @@ export const AiRestorerContent = (props) => {
>
Begin Reconstruction
-
+
{laws.map((law) => (
{law}
diff --git a/tgui/packages/tgui/interfaces/AiSupermatter.jsx b/tgui/packages/tgui/interfaces/AiSupermatter.tsx
similarity index 87%
rename from tgui/packages/tgui/interfaces/AiSupermatter.jsx
rename to tgui/packages/tgui/interfaces/AiSupermatter.tsx
index 0e8105c65cc..bf469604460 100644
--- a/tgui/packages/tgui/interfaces/AiSupermatter.jsx
+++ b/tgui/packages/tgui/interfaces/AiSupermatter.tsx
@@ -1,13 +1,21 @@
+import { BooleanLike } from 'common/react';
+
import { useBackend } from '../backend';
import { Box, Icon, LabeledList, ProgressBar, Section } from '../components';
import { Window } from '../layouts';
import { FullscreenNotice } from './common/FullscreenNotice';
-export const AiSupermatter = (props) => {
- const { data } = useBackend();
+type Data = {
+ detonating: BooleanLike;
+ integrity_percentage: number;
+ ambient_temp: number;
+ ambient_pressure: number;
+};
- const { integrity_percentage, ambient_temp, ambient_pressure, detonating } =
- data;
+export const AiSupermatter = (props) => {
+ const { data } = useBackend();
+
+ const { detonating } = data;
let body = ;
if (detonating) {
@@ -38,7 +46,7 @@ const AiSupermatterDetonation = (props) => (
);
const AiSupermatterContent = (props) => {
- const { data } = useBackend();
+ const { data } = useBackend();
const { integrity_percentage, ambient_temp, ambient_pressure } = data;
diff --git a/tgui/packages/tgui/interfaces/AirAlarm.jsx b/tgui/packages/tgui/interfaces/AirAlarm.tsx
similarity index 78%
rename from tgui/packages/tgui/interfaces/AirAlarm.jsx
rename to tgui/packages/tgui/interfaces/AirAlarm.tsx
index 451c1c1b612..ebdbdaf3316 100644
--- a/tgui/packages/tgui/interfaces/AirAlarm.jsx
+++ b/tgui/packages/tgui/interfaces/AirAlarm.tsx
@@ -1,4 +1,5 @@
import { toFixed } from 'common/math';
+import { BooleanLike } from 'common/react';
import { Fragment, useState } from 'react';
import { useBackend } from '../backend';
@@ -6,10 +7,57 @@ import { Box, Button, LabeledList, Section } from '../components';
import { getGasColor, getGasLabel } from '../constants';
import { Window } from '../layouts';
import { Scrubber, Vent } from './common/AtmosControls';
+import { single_scrubber, single_vent } from './common/CommonTypes';
import { InterfaceLockNoticeBox } from './common/InterfaceLockNoticeBox';
+type Data = {
+ locked: BooleanLike;
+ siliconUser: BooleanLike;
+ remoteUser: BooleanLike;
+ environment_data: {
+ name: string;
+ value: number;
+ unit: string;
+ danger_level: number;
+ }[];
+ danger_level: number;
+ target_temperature: string;
+ rcon: number;
+ atmos_alarm: BooleanLike;
+ fire_alarm: BooleanLike;
+ emagged: BooleanLike; // Seems unused
+ mode: number;
+ thresholds: thresholds[];
+ scrubbers: single_scrubber[];
+ vents: single_vent[];
+ modes: {
+ name: string;
+ mode: number;
+ selected: BooleanLike;
+ danger: BooleanLike;
+ }[];
+};
+
+type thresholds = {
+ name: string;
+ settings: {
+ env: string;
+ val: number;
+ selected: {
+ oxygen: number[];
+ carbon_dioxide: number;
+ phoron: number;
+ other: number;
+ pressure: number;
+ temperature: number;
+ };
+ }[];
+};
+
export const AirAlarm = (props) => {
- const { act, data } = useBackend();
+ const { act, data } = useBackend();
+
+ const { locked, siliconUser, remoteUser } = data;
const [screen, setScreen] = useState('');
@@ -17,14 +65,14 @@ export const AirAlarm = (props) => {
setScreen(value);
}
- const locked = data.locked && !data.siliconUser && !data.remoteUser;
+ const is_locked: BooleanLike = locked && !siliconUser && !remoteUser;
return (
- {!locked && (
+ {!is_locked && (
)}
@@ -33,24 +81,28 @@ export const AirAlarm = (props) => {
};
const AirAlarmStatus = (props) => {
- const { data } = useBackend();
- const entries = (data.environment_data || []).filter(
+ const { data } = useBackend();
+
+ const { environment_data, atmos_alarm, fire_alarm, emagged } = data;
+
+ const entries = (environment_data || []).filter(
(entry) => entry.value >= 0.01,
);
- const dangerMap = {
- 0: {
- color: 'good',
- localStatusText: 'Optimal',
- },
- 1: {
- color: 'average',
- localStatusText: 'Caution',
- },
- 2: {
- color: 'bad',
- localStatusText: 'Danger (Internals Required)',
- },
- };
+ const dangerMap: Record =
+ {
+ 0: {
+ color: 'good',
+ localStatusText: 'Optimal',
+ },
+ 1: {
+ color: 'average',
+ localStatusText: 'Caution',
+ },
+ 2: {
+ color: 'bad',
+ localStatusText: 'Danger (Internals Required)',
+ },
+ };
const localStatus = dangerMap[data.danger_level] || dangerMap[0];
return (
@@ -75,10 +127,10 @@ const AirAlarmStatus = (props) => {
- {(data.atmos_alarm && 'Atmosphere Alarm') ||
- (data.fire_alarm && 'Fire Alarm') ||
+ {(atmos_alarm && 'Atmosphere Alarm') ||
+ (fire_alarm && 'Fire Alarm') ||
'Nominal'}
>
@@ -87,7 +139,7 @@ const AirAlarmStatus = (props) => {
Cannot obtain air sample for analysis.
)}
- {!!data.emagged && (
+ {!!emagged && (
Safety measures offline. Device may exhibit abnormal behavior.
@@ -98,7 +150,7 @@ const AirAlarmStatus = (props) => {
};
const AirAlarmUnlockedControl = (props) => {
- const { act, data } = useBackend();
+ const { act, data } = useBackend();
const { target_temperature, rcon } = data;
return (
@@ -179,7 +231,7 @@ const AirAlarmControl = (props) => {
// --------------------------------------------------------
const AirAlarmControlHome = (props) => {
- const { act, data } = useBackend();
+ const { act, data } = useBackend();
const { mode, atmos_alarm } = data;
return (
<>
@@ -226,7 +278,7 @@ const AirAlarmControlHome = (props) => {
// --------------------------------------------------------
const AirAlarmControlVents = (props) => {
- const { data } = useBackend();
+ const { data } = useBackend();
const { vents } = data;
if (!vents || vents.length === 0) {
return 'Nothing to show';
@@ -238,7 +290,7 @@ const AirAlarmControlVents = (props) => {
// --------------------------------------------------------
const AirAlarmControlScrubbers = (props) => {
- const { data } = useBackend();
+ const { data } = useBackend();
const { scrubbers } = data;
if (!scrubbers || scrubbers.length === 0) {
return 'Nothing to show';
@@ -252,7 +304,7 @@ const AirAlarmControlScrubbers = (props) => {
// --------------------------------------------------------
const AirAlarmControlModes = (props) => {
- const { act, data } = useBackend();
+ const { act, data } = useBackend();
const { modes } = data;
if (!modes || modes.length === 0) {
return 'Nothing to show';
@@ -276,7 +328,7 @@ const AirAlarmControlModes = (props) => {
// --------------------------------------------------------
const AirAlarmControlThresholds = (props) => {
- const { act, data } = useBackend();
+ const { act, data } = useBackend();
const { thresholds } = data;
return (
diff --git a/tgui/packages/tgui/interfaces/AlgaeFarm.jsx b/tgui/packages/tgui/interfaces/AlgaeFarm.tsx
similarity index 88%
rename from tgui/packages/tgui/interfaces/AlgaeFarm.jsx
rename to tgui/packages/tgui/interfaces/AlgaeFarm.tsx
index a8a219f82d1..3774fdd5ce8 100644
--- a/tgui/packages/tgui/interfaces/AlgaeFarm.jsx
+++ b/tgui/packages/tgui/interfaces/AlgaeFarm.tsx
@@ -12,8 +12,28 @@ import {
} from '../components';
import { Window } from '../layouts';
+type Data = {
+ usePower: number;
+ materials: {
+ name: string;
+ display: string;
+ qty: number;
+ max: number;
+ percent: number;
+ }[];
+ last_flow_rate: number;
+ last_power_draw: number;
+ inputDir: string;
+ outputDir: string;
+ input: gas;
+ output: gas;
+ errorText: string | null;
+};
+
+type gas = { pressure: number; name: string; percent: number; moles: number };
+
export const AlgaeFarm = (props) => {
- const { act, data } = useBackend();
+ const { act, data } = useBackend();
const {
usePower,
materials,
@@ -31,7 +51,7 @@ export const AlgaeFarm = (props) => {
{errorText && (
-
+
{errorText}
diff --git a/tgui/packages/tgui/interfaces/AppearanceChanger.jsx b/tgui/packages/tgui/interfaces/AppearanceChanger.jsx
deleted file mode 100644
index 9a7d0fb83ba..00000000000
--- a/tgui/packages/tgui/interfaces/AppearanceChanger.jsx
+++ /dev/null
@@ -1,523 +0,0 @@
-import { sortBy } from 'common/collections';
-import { capitalize, decodeHtmlEntities } from 'common/string';
-import { useState } from 'react';
-
-import { useBackend } from '../backend';
-import {
- Box,
- Button,
- ByondUi,
- ColorBox,
- Flex,
- LabeledList,
- Section,
- Tabs,
-} from '../components';
-import { Window } from '../layouts';
-
-export const AppearanceChanger = (props) => {
- const { act, config, data } = useBackend();
-
- const {
- name,
- specimen,
- gender,
- gender_id,
- hair_style,
- facial_hair_style,
- ear_style,
- tail_style,
- wing_style,
- markings,
- change_race,
- change_gender,
- change_eye_color,
- change_skin_tone,
- change_skin_color,
- change_hair_color,
- change_facial_hair_color,
- change_hair,
- change_facial_hair,
- mapRef,
- } = data;
-
- const { title } = config;
-
- const change_color =
- change_eye_color ||
- change_skin_tone ||
- change_skin_color ||
- change_hair_color ||
- change_facial_hair_color;
-
- let firstAccesibleTab = -1;
- if (change_race) {
- firstAccesibleTab = 0;
- } else if (change_gender) {
- firstAccesibleTab = 1;
- } else if (change_color) {
- firstAccesibleTab = 2;
- } else if (change_hair) {
- firstAccesibleTab = 4;
- } else if (change_facial_hair) {
- firstAccesibleTab = 5;
- }
-
- const [tabIndex, setTabIndex] = useState(firstAccesibleTab);
-
- return (
-
-
-
-
-
-
- {name}
-
- {specimen}
-
-
- {gender ? capitalize(gender) : 'Not Set'}
-
-
- {gender_id ? capitalize(gender_id) : 'Not Set'}
-
-
- {hair_style ? capitalize(hair_style) : 'Not Set'}
-
-
- {facial_hair_style
- ? capitalize(facial_hair_style)
- : 'Not Set'}
-
-
- {ear_style ? capitalize(ear_style) : 'Not Set'}
-
-
- {tail_style ? capitalize(tail_style) : 'Not Set'}
-
-
- {wing_style ? capitalize(wing_style) : 'Not Set'}
-
-
-
-
-
-
-
-
-
- {change_race ? (
- setTabIndex(0)}>
- Race
-
- ) : null}
- {change_gender ? (
- setTabIndex(1)}>
- Gender & Sex
-
- ) : null}
- {change_color ? (
- setTabIndex(2)}>
- Colors
-
- ) : null}
- {change_hair ? (
- <>
- setTabIndex(3)}
- >
- Hair
-
- setTabIndex(5)}
- >
- Ear
-
- setTabIndex(6)}
- >
- Tail
-
- setTabIndex(7)}
- >
- Wing
-
- setTabIndex(8)}
- >
- Markings
-
- >
- ) : null}
- {change_facial_hair ? (
- setTabIndex(4)}>
- Facial Hair
-
- ) : null}
-
-
- {change_race && tabIndex === 0 ? : null}
- {change_gender && tabIndex === 1 ? : null}
- {change_color && tabIndex === 2 ? : null}
- {change_hair && tabIndex === 3 ? : null}
- {change_facial_hair && tabIndex === 4 ? (
-
- ) : null}
- {change_hair && tabIndex === 5 ? : null}
- {change_hair && tabIndex === 6 ? : null}
- {change_hair && tabIndex === 7 ? : null}
- {change_hair && tabIndex === 8 ? : null}
-
-
-
- );
-};
-
-const AppearanceChangerSpecies = (props) => {
- const { act, data } = useBackend();
- const { species, specimen } = data;
-
- const sortedSpecies = sortBy((val) => val.specimen)(species || []);
-
- return (
-
- {sortedSpecies.map((spec) => (
- act('race', { race: spec.specimen })}
- >
- {spec.specimen}
-
- ))}
-
- );
-};
-
-const AppearanceChangerGender = (props) => {
- const { act, data } = useBackend();
-
- const { gender, gender_id, genders, id_genders } = data;
-
- return (
-
-
-
- {genders.map((g) => (
- act('gender', { gender: g.gender_key })}
- >
- {g.gender_name}
-
- ))}
-
-
- {id_genders.map((g) => (
- act('gender_id', { gender_id: g.gender_key })}
- >
- {g.gender_name}
-
- ))}
-
-
-
- );
-};
-
-const AppearanceChangerColors = (props) => {
- const { act, data } = useBackend();
-
- const {
- change_eye_color,
- change_skin_tone,
- change_skin_color,
- change_hair_color,
- change_facial_hair_color,
- eye_color,
- skin_color,
- hair_color,
- facial_hair_color,
- ears_color,
- ears2_color,
- tail_color,
- tail2_color,
- wing_color,
- wing2_color,
- } = data;
-
- return (
-
- {change_eye_color ? (
-
-
- act('eye_color')}>Change Eye Color
-
- ) : null}
- {change_skin_tone ? (
-
- act('skin_tone')}>Change Skin Tone
-
- ) : null}
- {change_skin_color ? (
-
-
- act('skin_color')}>Change Skin Color
-
- ) : null}
- {change_hair_color ? (
- <>
-
-
- act('hair_color')}>Change Hair Color
-
-
-
- act('ears_color')}>Change Ears Color
-
-
-
- act('ears2_color')}>
- Change Secondary Ears Color
-
-
-
-
- act('tail_color')}>Change Tail Color
-
-
-
- act('tail2_color')}>
- Change Secondary Tail Color
-
-
-
-
- act('wing_color')}>Change Wing Color
-
-
-
- act('wing2_color')}>
- Change Secondary Wing Color
-
-
- >
- ) : null}
- {change_facial_hair_color ? (
-
-
- act('facial_hair_color')}>
- Change Facial Hair Color
-
-
- ) : null}
-
- );
-};
-
-const AppearanceChangerHair = (props) => {
- const { act, data } = useBackend();
-
- const { hair_style, hair_styles } = data;
-
- return (
-
- {hair_styles.map((hair) => (
- act('hair', { hair: hair.hairstyle })}
- selected={hair.hairstyle === hair_style}
- >
- {hair.hairstyle}
-
- ))}
-
- );
-};
-
-const AppearanceChangerFacialHair = (props) => {
- const { act, data } = useBackend();
-
- const { facial_hair_style, facial_hair_styles } = data;
-
- return (
-
- {facial_hair_styles.map((hair) => (
-
- act('facial_hair', { facial_hair: hair.facialhairstyle })
- }
- selected={hair.facialhairstyle === facial_hair_style}
- >
- {hair.facialhairstyle}
-
- ))}
-
- );
-};
-
-const AppearanceChangerEars = (props) => {
- const { act, data } = useBackend();
-
- const { ear_style, ear_styles } = data;
-
- return (
-
- act('ear', { clear: true })}
- selected={ear_style === null}
- >
- -- Not Set --
-
- {sortBy((e) => e.name.toLowerCase())(ear_styles).map((ear) => (
- act('ear', { ref: ear.instance })}
- selected={ear.name === ear_style}
- >
- {ear.name}
-
- ))}
-
- );
-};
-
-const AppearanceChangerTails = (props) => {
- const { act, data } = useBackend();
-
- const { tail_style, tail_styles } = data;
-
- return (
-
- act('tail', { clear: true })}
- selected={tail_style === null}
- >
- -- Not Set --
-
- {sortBy((e) => e.name.toLowerCase())(tail_styles).map((tail) => (
- act('tail', { ref: tail.instance })}
- selected={tail.name === tail_style}
- >
- {tail.name}
-
- ))}
-
- );
-};
-
-const AppearanceChangerWings = (props) => {
- const { act, data } = useBackend();
-
- const { wing_style, wing_styles } = data;
-
- return (
-
- act('wing', { clear: true })}
- selected={wing_style === null}
- >
- -- Not Set --
-
- {sortBy((e) => e.name.toLowerCase())(wing_styles).map((wing) => (
- act('wing', { ref: wing.instance })}
- selected={wing.name === wing_style}
- >
- {wing.name}
-
- ))}
-
- );
-};
-
-const AppearanceChangerMarkings = (props) => {
- const { act, data } = useBackend();
-
- const { markings } = data;
-
- return (
-
-
- act('marking', { todo: 1, name: 'na' })}>
- Add Marking
-
-
-
- {markings.map((m) => (
-
-
- act('marking', { todo: 4, name: m.marking_name })}
- >
- Change Color
-
- act('marking', { todo: 0, name: m.marking_name })}
- >
- -
-
- act('marking', { todo: 3, name: m.marking_name })}
- >
- Move down
-
- act('marking', { todo: 2, name: m.marking_name })}
- >
- Move up
-
-
- ))}
-
-
- );
-};
diff --git a/tgui/packages/tgui/interfaces/AppearanceChanger/AppearanceChangerBody.tsx b/tgui/packages/tgui/interfaces/AppearanceChanger/AppearanceChangerBody.tsx
new file mode 100644
index 00000000000..66cfcbb8866
--- /dev/null
+++ b/tgui/packages/tgui/interfaces/AppearanceChanger/AppearanceChangerBody.tsx
@@ -0,0 +1,139 @@
+import { sortBy } from 'common/collections';
+
+import { useBackend } from '../../backend';
+import { Button, LabeledList, Section } from '../../components';
+import { Data, species, styles } from './types';
+
+export const AppearanceChangerSpecies = (props) => {
+ const { act, data } = useBackend();
+ const { species, specimen } = data;
+
+ const sortedSpecies = sortBy((val: species) => val.specimen)(species || []);
+
+ return (
+
+ {sortedSpecies.map((spec) => (
+ act('race', { race: spec.specimen })}
+ >
+ {spec.specimen}
+
+ ))}
+
+ );
+};
+
+export const AppearanceChangerGender = (props) => {
+ const { act, data } = useBackend();
+
+ const { gender, gender_id, genders, id_genders } = data;
+
+ return (
+
+
+
+ {genders.map((g) => (
+ act('gender', { gender: g.gender_key })}
+ >
+ {g.gender_name}
+
+ ))}
+
+
+ {id_genders.map((g) => (
+ act('gender_id', { gender_id: g.gender_key })}
+ >
+ {g.gender_name}
+
+ ))}
+
+
+
+ );
+};
+
+export const AppearanceChangerEars = (props) => {
+ const { act, data } = useBackend();
+
+ const { ear_style, ear_styles } = data;
+
+ return (
+
+ act('ear', { clear: true })}
+ selected={ear_style === null}
+ >
+ -- Not Set --
+
+ {sortBy((e: styles) => e.name.toLowerCase())(ear_styles).map((ear) => (
+ act('ear', { ref: ear.instance })}
+ selected={ear.name === ear_style}
+ >
+ {ear.name}
+
+ ))}
+
+ );
+};
+
+export const AppearanceChangerTails = (props) => {
+ const { act, data } = useBackend();
+
+ const { tail_style, tail_styles } = data;
+
+ return (
+
+ act('tail', { clear: true })}
+ selected={tail_style === null}
+ >
+ -- Not Set --
+
+ {sortBy((e: styles) => e.name.toLowerCase())(tail_styles).map((tail) => (
+ act('tail', { ref: tail.instance })}
+ selected={tail.name === tail_style}
+ >
+ {tail.name}
+
+ ))}
+
+ );
+};
+
+export const AppearanceChangerWings = (props) => {
+ const { act, data } = useBackend();
+
+ const { wing_style, wing_styles } = data;
+
+ return (
+
+ act('wing', { clear: true })}
+ selected={wing_style === null}
+ >
+ -- Not Set --
+
+ {sortBy((e: styles) => e.name.toLowerCase())(wing_styles).map((wing) => (
+ act('wing', { ref: wing.instance })}
+ selected={wing.name === wing_style}
+ >
+ {wing.name}
+
+ ))}
+
+ );
+};
diff --git a/tgui/packages/tgui/interfaces/AppearanceChanger/AppearanceChangerDetails.tsx b/tgui/packages/tgui/interfaces/AppearanceChanger/AppearanceChangerDetails.tsx
new file mode 100644
index 00000000000..c5d2f403b7b
--- /dev/null
+++ b/tgui/packages/tgui/interfaces/AppearanceChanger/AppearanceChangerDetails.tsx
@@ -0,0 +1,142 @@
+import { useBackend } from '../../backend';
+import { Box, Button, ColorBox, LabeledList, Section } from '../../components';
+import { Data } from './types';
+
+export const AppearanceChangerColors = (props) => {
+ const { act, data } = useBackend();
+
+ const {
+ change_eye_color,
+ change_skin_tone,
+ change_skin_color,
+ change_hair_color,
+ change_facial_hair_color,
+ eye_color,
+ skin_color,
+ hair_color,
+ facial_hair_color,
+ ears_color,
+ ears2_color,
+ tail_color,
+ tail2_color,
+ wing_color,
+ wing2_color,
+ } = data;
+
+ return (
+
+ {change_eye_color ? (
+
+
+ act('eye_color')}>Change Eye Color
+
+ ) : (
+ ''
+ )}
+ {change_skin_tone ? (
+
+ act('skin_tone')}>Change Skin Tone
+
+ ) : (
+ ''
+ )}
+ {change_skin_color ? (
+
+
+ act('skin_color')}>Change Skin Color
+
+ ) : (
+ ''
+ )}
+ {change_hair_color ? (
+ <>
+
+
+ act('hair_color')}>Change Hair Color
+
+
+
+ act('ears_color')}>Change Ears Color
+
+
+
+ act('ears2_color')}>
+ Change Secondary Ears Color
+
+
+
+
+ act('tail_color')}>Change Tail Color
+
+
+
+ act('tail2_color')}>
+ Change Secondary Tail Color
+
+
+
+
+ act('wing_color')}>Change Wing Color
+
+
+
+ act('wing2_color')}>
+ Change Secondary Wing Color
+
+
+ >
+ ) : null}
+ {change_facial_hair_color ? (
+
+
+ act('facial_hair_color')}>
+ Change Facial Hair Color
+
+
+ ) : null}
+
+ );
+};
+
+export const AppearanceChangerMarkings = (props) => {
+ const { act, data } = useBackend();
+
+ const { markings } = data;
+
+ return (
+
+
+ act('marking', { todo: 1, name: 'na' })}>
+ Add Marking
+
+
+
+ {markings.map((m) => (
+
+
+ act('marking', { todo: 4, name: m.marking_name })}
+ >
+ Change Color
+
+ act('marking', { todo: 0, name: m.marking_name })}
+ >
+ -
+
+ act('marking', { todo: 3, name: m.marking_name })}
+ >
+ Move down
+
+ act('marking', { todo: 2, name: m.marking_name })}
+ >
+ Move up
+
+
+ ))}
+
+
+ );
+};
diff --git a/tgui/packages/tgui/interfaces/AppearanceChanger/AppearanceChangerHairs.tsx b/tgui/packages/tgui/interfaces/AppearanceChanger/AppearanceChangerHairs.tsx
new file mode 100644
index 00000000000..9e61a2c1a76
--- /dev/null
+++ b/tgui/packages/tgui/interfaces/AppearanceChanger/AppearanceChangerHairs.tsx
@@ -0,0 +1,45 @@
+import { useBackend } from '../../backend';
+import { Button, Section } from '../../components';
+import { Data } from './types';
+
+export const AppearanceChangerHair = (props) => {
+ const { act, data } = useBackend();
+
+ const { hair_style, hair_styles } = data;
+
+ return (
+
+ {hair_styles.map((hair) => (
+ act('hair', { hair: hair.hairstyle })}
+ selected={hair.hairstyle === hair_style}
+ >
+ {hair.hairstyle}
+
+ ))}
+
+ );
+};
+
+export const AppearanceChangerFacialHair = (props) => {
+ const { act, data } = useBackend();
+
+ const { facial_hair_style, facial_hair_styles } = data;
+
+ return (
+
+ {facial_hair_styles.map((hair) => (
+
+ act('facial_hair', { facial_hair: hair.facialhairstyle })
+ }
+ selected={hair.facialhairstyle === facial_hair_style}
+ >
+ {hair.facialhairstyle}
+
+ ))}
+
+ );
+};
diff --git a/tgui/packages/tgui/interfaces/AppearanceChanger/index.tsx b/tgui/packages/tgui/interfaces/AppearanceChanger/index.tsx
new file mode 100644
index 00000000000..48935191fa6
--- /dev/null
+++ b/tgui/packages/tgui/interfaces/AppearanceChanger/index.tsx
@@ -0,0 +1,269 @@
+import { capitalize, decodeHtmlEntities } from 'common/string';
+import { useState } from 'react';
+
+import { useBackend } from '../../backend';
+import {
+ Box,
+ ByondUi,
+ Flex,
+ LabeledList,
+ Section,
+ Tabs,
+} from '../../components';
+import { Window } from '../../layouts';
+import {
+ AppearanceChangerEars,
+ AppearanceChangerGender,
+ AppearanceChangerSpecies,
+ AppearanceChangerTails,
+ AppearanceChangerWings,
+} from './AppearanceChangerBody';
+import {
+ AppearanceChangerColors,
+ AppearanceChangerMarkings,
+} from './AppearanceChangerDetails';
+import {
+ AppearanceChangerFacialHair,
+ AppearanceChangerHair,
+} from './AppearanceChangerHairs';
+import { Data } from './types';
+
+export const AppearanceChanger = (props) => {
+ const { act, config, data } = useBackend();
+
+ const {
+ name,
+ specimen,
+ gender,
+ gender_id,
+ hair_style,
+ facial_hair_style,
+ ear_style,
+ tail_style,
+ wing_style,
+ change_race,
+ change_gender,
+ change_eye_color,
+ change_skin_tone,
+ change_skin_color,
+ change_hair_color,
+ change_facial_hair_color,
+ change_hair,
+ change_facial_hair,
+ mapRef,
+ } = data;
+
+ const { title } = config;
+
+ const tab: React.JSX.Element[] = [];
+
+ const change_color =
+ change_eye_color ||
+ change_skin_tone ||
+ change_skin_color ||
+ change_hair_color ||
+ change_facial_hair_color;
+
+ const disabled = ;
+
+ tab[-1] = ;
+ tab[0] = change_race ? (
+
+ ) : (
+
+ );
+ tab[1] = change_gender ? (
+
+ ) : (
+
+ );
+ tab[2] = change_color ? (
+
+ ) : (
+
+ );
+ tab[3] = change_hair ? (
+
+ ) : (
+
+ );
+ tab[4] = change_facial_hair ? (
+
+ ) : (
+
+ );
+ tab[5] = change_hair ? (
+
+ ) : (
+
+ );
+ tab[6] = change_hair ? (
+
+ ) : (
+
+ );
+ tab[7] = change_hair ? (
+
+ ) : (
+
+ );
+ tab[8] = change_hair ? (
+
+ ) : (
+
+ );
+
+ let firstAccesibleTab = -1;
+ if (change_race) {
+ firstAccesibleTab = 0;
+ } else if (change_gender) {
+ firstAccesibleTab = 1;
+ } else if (change_color) {
+ firstAccesibleTab = 2;
+ } else if (change_hair) {
+ firstAccesibleTab = 4;
+ } else if (change_facial_hair) {
+ firstAccesibleTab = 5;
+ }
+
+ const [tabIndex, setTabIndex] = useState(firstAccesibleTab);
+
+ return (
+
+
+
+
+
+
+ {name}
+
+ {specimen}
+
+
+ {gender ? capitalize(gender) : 'Not Set'}
+
+
+ {gender_id ? capitalize(gender_id) : 'Not Set'}
+
+
+ {hair_style ? capitalize(hair_style) : 'Not Set'}
+
+
+ {facial_hair_style
+ ? capitalize(facial_hair_style)
+ : 'Not Set'}
+
+
+ {ear_style ? capitalize(ear_style) : 'Not Set'}
+
+
+ {tail_style ? capitalize(tail_style) : 'Not Set'}
+
+
+ {wing_style ? capitalize(wing_style) : 'Not Set'}
+
+
+
+
+
+
+
+
+
+ {change_race ? (
+ setTabIndex(0)}>
+ Race
+
+ ) : null}
+ {change_gender ? (
+ setTabIndex(1)}>
+ Gender & Sex
+
+ ) : null}
+ {change_color ? (
+ setTabIndex(2)}>
+ Colors
+
+ ) : null}
+ {change_hair ? (
+ <>
+ setTabIndex(3)}
+ >
+ Hair
+
+ setTabIndex(5)}
+ >
+ Ear
+
+ setTabIndex(6)}
+ >
+ Tail
+
+ setTabIndex(7)}
+ >
+ Wing
+
+ setTabIndex(8)}
+ >
+ Markings
+
+ >
+ ) : null}
+ {change_facial_hair ? (
+ setTabIndex(4)}>
+ Facial Hair
+
+ ) : null}
+
+ {tab[tabIndex]}
+
+
+ );
+};
+
+export const AppearanceChangerDefaultError = (props) => {
+ return Disabled;
+};
diff --git a/tgui/packages/tgui/interfaces/AppearanceChanger/types.ts b/tgui/packages/tgui/interfaces/AppearanceChanger/types.ts
new file mode 100644
index 00000000000..4d62e131771
--- /dev/null
+++ b/tgui/packages/tgui/interfaces/AppearanceChanger/types.ts
@@ -0,0 +1,53 @@
+import { BooleanLike } from 'common/react';
+
+export type Data = {
+ name: string;
+ specimen: string;
+ species: species[];
+ gender: string;
+ gender_id: string;
+ hair_style: string;
+ facial_hair_style: string;
+ ear_style: string;
+ ear_styles: styles[];
+ wing_style: string;
+ wing_styles: styles[];
+ tail_style: string;
+ tail_styles: styles[];
+ markings: { marking_name: string; marking_color: string }[];
+ change_race: BooleanLike;
+ change_gender: BooleanLike;
+ genders: genders;
+ id_genders: genders;
+ change_eye_color: BooleanLike;
+ change_skin_tone: BooleanLike;
+ change_skin_color: BooleanLike;
+ change_hair_color: BooleanLike;
+ change_facial_hair_color: BooleanLike;
+ change_hair: BooleanLike;
+ change_facial_hair: BooleanLike;
+ mapRef: string;
+ eye_color: string;
+ skin_color: string;
+ hair_color: string;
+ facial_hair_color: string;
+ ears_color: string;
+ ears2_color: string;
+ tail_color: string;
+ tail2_color: string;
+ wing_color: string;
+ wing2_color: string;
+ facial_hair_styles: { facialhairstyle: string }[];
+ hair_styles: { hairstyle: string }[];
+};
+
+type genders = { gender_name: string; gender_key: string }[];
+
+export type styles = {
+ name: string;
+ instance: string;
+ color: boolean;
+ second_color: boolean;
+};
+
+export type species = { specimen: string };
diff --git a/tgui/packages/tgui/interfaces/ArcadeBattle.jsx b/tgui/packages/tgui/interfaces/ArcadeBattle.tsx
similarity index 93%
rename from tgui/packages/tgui/interfaces/ArcadeBattle.jsx
rename to tgui/packages/tgui/interfaces/ArcadeBattle.tsx
index 9ffead3baf6..f1300c73380 100644
--- a/tgui/packages/tgui/interfaces/ArcadeBattle.jsx
+++ b/tgui/packages/tgui/interfaces/ArcadeBattle.tsx
@@ -1,3 +1,5 @@
+import { BooleanLike } from 'common/react';
+
import { useBackend } from '../backend';
import {
Box,
@@ -9,18 +11,27 @@ import {
} from '../components';
import { Window } from '../layouts';
+type Data = {
+ name: string;
+ temp: string;
+ enemyAction: string;
+ enemyName: string;
+ playerHP: number;
+ playerMP: number;
+ enemyHP: number;
+ gameOver: BooleanLike;
+};
+
export const ArcadeBattle = (props) => {
- const { act, data } = useBackend();
+ const { act, data } = useBackend();
const {
- name,
temp,
enemyAction,
enemyName,
playerHP,
playerMP,
enemyHP,
- enemyMP,
gameOver,
} = data;
diff --git a/tgui/packages/tgui/interfaces/AreaScrubberControl.jsx b/tgui/packages/tgui/interfaces/AreaScrubberControl.tsx
similarity index 88%
rename from tgui/packages/tgui/interfaces/AreaScrubberControl.jsx
rename to tgui/packages/tgui/interfaces/AreaScrubberControl.tsx
index e9aa25cc8ab..22e404dc84a 100644
--- a/tgui/packages/tgui/interfaces/AreaScrubberControl.jsx
+++ b/tgui/packages/tgui/interfaces/AreaScrubberControl.tsx
@@ -1,3 +1,4 @@
+import { BooleanLike } from 'common/react';
import { toTitleCase } from 'common/string';
import { useState } from 'react';
@@ -5,10 +6,24 @@ import { useBackend } from '../backend';
import { Box, Button, Flex, LabeledList, Section } from '../components';
import { Window } from '../layouts';
-export const AreaScrubberControl = (props) => {
- const { act, data } = useBackend();
+type scrubber = {
+ id: string;
+ name: string;
+ on: BooleanLike;
+ pressure: number;
+ flow_rate: number;
+ load: number;
+ area: string;
+};
- const [showArea, setShowArea] = useState(false);
+type Data = {
+ scrubbers: scrubber[];
+};
+
+export const AreaScrubberControl = (props) => {
+ const { act, data } = useBackend();
+
+ const [showArea, setShowArea] = useState(false);
const { scrubbers } = data;
@@ -83,7 +98,7 @@ export const AreaScrubberControl = (props) => {
);
};
-const BigScrubber = (props) => {
+const BigScrubber = (props: { scrubber: scrubber; showArea: boolean }) => {
const { act } = useBackend();
const { scrubber, showArea } = props;
diff --git a/tgui/packages/tgui/interfaces/AssemblyProx.jsx b/tgui/packages/tgui/interfaces/AssemblyProx.tsx
similarity index 81%
rename from tgui/packages/tgui/interfaces/AssemblyProx.jsx
rename to tgui/packages/tgui/interfaces/AssemblyProx.tsx
index 7e1761bef72..a08e6f768a4 100644
--- a/tgui/packages/tgui/interfaces/AssemblyProx.jsx
+++ b/tgui/packages/tgui/interfaces/AssemblyProx.tsx
@@ -1,12 +1,21 @@
import { round } from 'common/math';
+import { BooleanLike } from 'common/react';
import { useBackend } from '../backend';
import { Button, LabeledList, NumberInput, Section } from '../components';
import { formatTime } from '../format';
import { Window } from '../layouts';
+type Data = {
+ timing: number;
+ time: number;
+ range: number;
+ maxRange: number;
+ scanning: BooleanLike;
+};
+
export const AssemblyProx = (props) => {
- const { act, data } = useBackend();
+ const { act, data } = useBackend();
const { timing, time, range, maxRange, scanning } = data;
return (
@@ -31,8 +40,8 @@ export const AssemblyProx = (props) => {
value={time}
minValue={0}
maxValue={600}
- format={(val) => formatTime(round(val * 10))}
- onDrag={(e, val) => act('set_time', { time: val })}
+ format={(val: number) => formatTime(round(val * 10, 0))}
+ onDrag={(e, val: string) => act('set_time', { time: val })}
/>
@@ -44,7 +53,7 @@ export const AssemblyProx = (props) => {
minValue={1}
value={range}
maxValue={maxRange}
- onDrag={(e, val) => act('range', { range: val })}
+ onDrag={(e, val: string) => act('range', { range: val })}
/>
diff --git a/tgui/packages/tgui/interfaces/AssemblyTimer.jsx b/tgui/packages/tgui/interfaces/AssemblyTimer.tsx
similarity index 81%
rename from tgui/packages/tgui/interfaces/AssemblyTimer.jsx
rename to tgui/packages/tgui/interfaces/AssemblyTimer.tsx
index 02d6f8b0e66..9fd6c8fb94e 100644
--- a/tgui/packages/tgui/interfaces/AssemblyTimer.jsx
+++ b/tgui/packages/tgui/interfaces/AssemblyTimer.tsx
@@ -5,8 +5,10 @@ import { Button, LabeledList, NumberInput, Section } from '../components';
import { formatTime } from '../format';
import { Window } from '../layouts';
+type Data = { timing: number; time: number };
+
export const AssemblyTimer = (props) => {
- const { act, data } = useBackend();
+ const { act, data } = useBackend();
const { timing, time } = data;
return (
@@ -31,8 +33,8 @@ export const AssemblyTimer = (props) => {
value={time}
minValue={0}
maxValue={600}
- format={(val) => formatTime(round(val * 10))}
- onDrag={(e, val) => act('set_time', { time: val })}
+ format={(val: number) => formatTime(round(val * 10, 0))}
+ onDrag={(e, val: string) => act('set_time', { time: val })}
/>
diff --git a/tgui/packages/tgui/interfaces/AtmosAlertConsole.jsx b/tgui/packages/tgui/interfaces/AtmosAlertConsole.tsx
similarity index 74%
rename from tgui/packages/tgui/interfaces/AtmosAlertConsole.jsx
rename to tgui/packages/tgui/interfaces/AtmosAlertConsole.tsx
index c722c954c0c..b2658aa66b4 100644
--- a/tgui/packages/tgui/interfaces/AtmosAlertConsole.jsx
+++ b/tgui/packages/tgui/interfaces/AtmosAlertConsole.tsx
@@ -2,19 +2,24 @@ import { useBackend } from '../backend';
import { Button, Section } from '../components';
import { Window } from '../layouts';
+type alarm = { name: string; ref: string };
+
+type Data = { priority_alarms: alarm[]; minor_alarms: alarm[] };
+
export const AtmosAlertConsole = (props) => {
- const { act, data } = useBackend();
- const priorityAlerts = data.priority_alarms || [];
- const minorAlerts = data.minor_alarms || [];
+ const { act, data } = useBackend();
+
+ const { priority_alarms = [], minor_alarms = [] } = data;
+
return (
- {priorityAlerts.length === 0 && (
+ {priority_alarms.length === 0 && (
- No Priority Alerts
)}
- {priorityAlerts.map((alert) => (
+ {priority_alarms.map((alert) => (
-
{
))}
- {minorAlerts.length === 0 && (
+ {minor_alarms.length === 0 && (
- No Minor Alerts
)}
- {minorAlerts.map((alert) => (
+ {minor_alarms.map((alert) => (
-
{
return (
@@ -18,9 +30,9 @@ export const AtmosControl = (props) => {
};
export const AtmosControlContent = (props) => {
- const { act, data, config } = useBackend();
+ const { act, data, config } = useBackend();
- let sortedAlarms = sortBy((alarm) => alarm.name)(data.alarms || []);
+ let sortedAlarms = sortBy((alarm: alarm) => alarm.name)(data.alarms || []);
// sortedAlarms = sortedAlarms.slice(1, 3);
diff --git a/tgui/packages/tgui/interfaces/AtmosFilter.jsx b/tgui/packages/tgui/interfaces/AtmosFilter.tsx
similarity index 73%
rename from tgui/packages/tgui/interfaces/AtmosFilter.jsx
rename to tgui/packages/tgui/interfaces/AtmosFilter.tsx
index 615ef09107c..a2ed22a2e01 100644
--- a/tgui/packages/tgui/interfaces/AtmosFilter.jsx
+++ b/tgui/packages/tgui/interfaces/AtmosFilter.tsx
@@ -1,3 +1,5 @@
+import { BooleanLike } from 'common/react';
+
import { useBackend } from '../backend';
import {
AnimatedNumber,
@@ -9,9 +11,19 @@ import {
} from '../components';
import { Window } from '../layouts';
+type Data = {
+ on: BooleanLike;
+ rate: number;
+ max_rate: number;
+ last_flow_rate: number;
+ filter_types: { name: string; f_type: number; selected: number }[];
+};
+
export const AtmosFilter = (props) => {
- const { act, data } = useBackend();
- const filterTypes = data.filter_types || [];
+ const { act, data } = useBackend();
+
+ const { on, rate, max_rate, last_flow_rate, filter_types = [] } = data;
+
return (
@@ -19,28 +31,28 @@ export const AtmosFilter = (props) => {
act('power')}
>
- {data.on ? 'On' : 'Off'}
+ {on ? 'On' : 'Off'}
val + ' L/s'}
/>
+ onDrag={(e, value: number) =>
act('rate', {
rate: value,
})
@@ -49,7 +61,7 @@ export const AtmosFilter = (props) => {
act('rate', {
rate: 'max',
@@ -60,7 +72,7 @@ export const AtmosFilter = (props) => {
- {filterTypes.map((filter) => (
+ {filter_types.map((filter) => (
{
- const { act, data } = useBackend();
+ const { act, data } = useBackend();
+
+ const {
+ on,
+ set_pressure,
+ max_pressure,
+ node1_concentration,
+ node2_concentration,
+ node1_dir,
+ node2_dir,
+ } = data;
+
return (
@@ -11,23 +34,23 @@ export const AtmosMixer = (props) => {
act('power')}
>
- {data.on ? 'On' : 'Off'}
+ {on ? 'On' : 'Off'}
+ onChange={(e, value: number) =>
act('pressure', {
pressure: value,
})
@@ -36,7 +59,7 @@ export const AtmosMixer = (props) => {
act('pressure', {
pressure: 'max',
@@ -50,32 +73,32 @@ export const AtmosMixer = (props) => {
Concentrations
-
+
+ onDrag={(e, value: number) =>
act('node1', {
concentration: value,
})
}
/>
-
+
+ onDrag={(e, value: number) =>
act('node2', {
concentration: value,
})
diff --git a/tgui/packages/tgui/interfaces/Autolathe.jsx b/tgui/packages/tgui/interfaces/Autolathe.tsx
similarity index 81%
rename from tgui/packages/tgui/interfaces/Autolathe.jsx
rename to tgui/packages/tgui/interfaces/Autolathe.tsx
index 9fd18051ca2..97c00ad02ee 100644
--- a/tgui/packages/tgui/interfaces/Autolathe.jsx
+++ b/tgui/packages/tgui/interfaces/Autolathe.tsx
@@ -1,21 +1,23 @@
import { filter, sortBy } from 'common/collections';
import { flow } from 'common/fp';
+import { BooleanLike } from 'common/react';
import { createSearch, toTitleCase } from 'common/string';
import { useBackend, useSharedState } from '../backend';
import { Box, Button, Dropdown, Flex, Input, Section } from '../components';
import { Window } from '../layouts';
+import { mat } from './common/CommonTypes';
import { Materials } from './ExosuitFabricator';
-const canBeMade = (recipe, materials, mult = 1) => {
+const canBeMade = (recipe, materials, mult: number = 1) => {
if (recipe.requirements === null) {
return true;
}
- let recipeRequiredMaterials = Object.keys(recipe.requirements);
+ let recipeRequiredMaterials: string[] = Object.keys(recipe.requirements);
for (let mat_id of recipeRequiredMaterials) {
- let material = materials.find((val) => val.name === mat_id);
+ let material = materials.find((val: mat) => val.name === mat_id);
if (!material) {
continue; // yes, if we cannot find the material, we just ignore it :V
}
@@ -26,9 +28,26 @@ const canBeMade = (recipe, materials, mult = 1) => {
return true;
};
+type Data = {
+ recipes: recipe[];
+ categories: string[];
+ busy: string;
+ materials: mat[];
+};
+
+type recipe = {
+ category: string;
+ name: string;
+ ref: string;
+ requirements: Record;
+ hidden: BooleanLike;
+ coeff_applies: BooleanLike;
+ is_stack: BooleanLike;
+};
export const Autolathe = (props) => {
- const { act, data } = useBackend();
+ const { act, data } = useBackend();
+
const { recipes, busy, materials, categories } = data;
const [category, setCategory] = useSharedState('category', 0);
@@ -37,9 +56,9 @@ export const Autolathe = (props) => {
const testSearch = createSearch(searchText, (recipe) => recipe.name);
const recipesToShow = flow([
- filter((recipe) => recipe.category === categories[category]),
+ filter((recipe: recipe) => recipe.category === categories[category]),
searchText && filter(testSearch),
- sortBy((recipe) => recipe.name.toLowerCase()),
+ sortBy((recipe: recipe) => recipe.name.toLowerCase()),
])(recipes);
return (
@@ -63,7 +82,7 @@ export const Autolathe = (props) => {
fluid
placeholder="Search for..."
value={searchText}
- onInput={(e, v) => setSearchText(v)}
+ onInput={(e, v: string) => setSearchText(v)}
mb={1}
/>
{recipesToShow.map((recipe) => (
diff --git a/tgui/packages/tgui/interfaces/Batteryrack.jsx b/tgui/packages/tgui/interfaces/Batteryrack.tsx
similarity index 89%
rename from tgui/packages/tgui/interfaces/Batteryrack.jsx
rename to tgui/packages/tgui/interfaces/Batteryrack.tsx
index 24a29f21579..0adbbbbf169 100644
--- a/tgui/packages/tgui/interfaces/Batteryrack.jsx
+++ b/tgui/packages/tgui/interfaces/Batteryrack.tsx
@@ -1,3 +1,5 @@
+import { BooleanLike } from 'common/react';
+
import { useBackend } from '../backend';
import {
AnimatedNumber,
@@ -10,8 +12,25 @@ import {
} from '../components';
import { Window } from '../layouts';
+type Data = {
+ mode: number;
+ transfer_max: number;
+ output_load: number;
+ input_load: number;
+ equalise: BooleanLike;
+ blink_tick: BooleanLike;
+ cells_max: number;
+ cells_cur: number;
+ cells_list: {
+ slot: number;
+ used: BooleanLike;
+ percentage: number;
+ id: number;
+ }[];
+};
+
export const Batteryrack = (props) => {
- const { act, data } = useBackend();
+ const { act, data } = useBackend();
const {
mode,
@@ -20,8 +39,6 @@ export const Batteryrack = (props) => {
input_load,
equalise,
blink_tick,
- cells_max,
- cells_cur,
cells_list,
} = data;
diff --git a/tgui/packages/tgui/interfaces/BeaconLocator.jsx b/tgui/packages/tgui/interfaces/BeaconLocator.tsx
similarity index 86%
rename from tgui/packages/tgui/interfaces/BeaconLocator.jsx
rename to tgui/packages/tgui/interfaces/BeaconLocator.tsx
index 362dac53083..c177e3b4571 100644
--- a/tgui/packages/tgui/interfaces/BeaconLocator.jsx
+++ b/tgui/packages/tgui/interfaces/BeaconLocator.tsx
@@ -11,8 +11,16 @@ import {
} from '../components';
import { Window } from '../layouts';
+type Data = {
+ scan_ticks: number;
+ degrees: number | null;
+ rawfreq: number;
+ minFrequency: number;
+ maxFrequency: number;
+};
+
export const BeaconLocator = (props) => {
- const { act, data } = useBackend();
+ const { act, data } = useBackend();
const { scan_ticks, degrees, rawfreq, minFrequency, maxFrequency } = data;
@@ -49,9 +57,9 @@ export const BeaconLocator = (props) => {
maxValue={maxFrequency / 10}
value={rawfreq / 10}
format={(value) => toFixed(value, 1)}
- onDrag={(e, value) =>
+ onDrag={(e, value: number) =>
act('setFrequency', {
- freq: round(value * 10),
+ freq: round(value * 10, 0),
})
}
/>
diff --git a/tgui/packages/tgui/interfaces/Biogenerator.jsx b/tgui/packages/tgui/interfaces/Biogenerator.tsx
similarity index 66%
rename from tgui/packages/tgui/interfaces/Biogenerator.jsx
rename to tgui/packages/tgui/interfaces/Biogenerator.tsx
index ed31a5ed5fe..8669b01eb76 100644
--- a/tgui/packages/tgui/interfaces/Biogenerator.jsx
+++ b/tgui/packages/tgui/interfaces/Biogenerator.tsx
@@ -1,3 +1,4 @@
+import { BooleanLike } from 'common/react';
import { createSearch } from 'common/string';
import { useState } from 'react';
@@ -13,48 +14,66 @@ import {
} from '../components';
import { Window } from '../layouts';
+type sortable = {
+ name: string;
+ affordable: number;
+ price: number;
+ reagent: BooleanLike;
+};
+type Data = {
+ items: Record;
+ build_eff: number;
+ points: number;
+ processing: BooleanLike;
+ beaker: boolean;
+};
+
const sortTypes = {
- Alphabetical: (a, b) => a.name > b.name,
- 'By availability': (a, b) => -(a.affordable - b.affordable),
- 'By price': (a, b) => a.price - b.price,
+ Alphabetical: (a: sortable, b: sortable) => a.name > b.name,
+ 'By availability': (a: sortable, b: sortable) =>
+ -(a.affordable - b.affordable),
+ 'By price': (a: sortable, b: sortable) => a.price - b.price,
};
export const Biogenerator = (props) => {
- const { act, data } = useBackend();
- const [searchText, setSearchText] = useState('');
- const [sortOrder, setSortOrder] = useState('Alphabetical');
- const [descending, setDescending] = useState(false);
+ const { act, data } = useBackend();
- function handleSearchText(value) {
+ const { processing, points, beaker } = data;
+
+ const [searchText, setSearchText] = useState('');
+ const [sortOrder, setSortOrder] = useState('Alphabetical');
+ const [descending, setDescending] = useState(false);
+
+ function handleSearchText(value: string) {
setSearchText(value);
}
- function handleSortOrder(value) {
+ function handleSortOrder(value: string) {
setSortOrder(value);
}
- function handleDescending(value) {
+ function handleDescending(value: boolean) {
setDescending(value);
}
return (
- {(data.processing && (
+ {(processing && (
The biogenerator is processing reagents!
)) || (
<>
- {data.points} points available.
+ {points} points available.
act('activate')}>
Activate
act('detach')}
>
Eject Beaker
@@ -72,9 +91,6 @@ export const Biogenerator = (props) => {
searchText={searchText}
sortOrder={sortOrder}
descending={descending}
- onSearchText={handleSearchText}
- onSortOrder={handleSortOrder}
- onDescending={handleDescending}
/>
>
)}
@@ -83,20 +99,24 @@ export const Biogenerator = (props) => {
);
};
-const BiogeneratorItems = (props) => {
- const { act, data } = useBackend();
- const { points, items } = data;
+const BiogeneratorItems = (props: {
+ searchText: string;
+ sortOrder: string;
+ descending: boolean;
+}) => {
+ const { act, data } = useBackend();
+ const { points, items = [], build_eff, beaker } = data;
// Search thingies
- const searcher = createSearch(props.searchText, (item) => {
+ const searcher = createSearch(props.searchText, (item: sortable) => {
return item[0];
});
let has_contents = false;
- let contents = Object.entries(items).map((kv, _i) => {
+ let contents = Object.entries(items).map((kv) => {
let items_in_cat = Object.entries(kv[1])
.filter(searcher)
.map((kv2) => {
- kv2[1].affordable = points >= kv2[1].price / data.build_eff;
+ kv2[1].affordable = +(points >= kv2[1].price / build_eff);
return kv2[1];
})
.sort(sortTypes[props.sortOrder]);
@@ -113,6 +133,8 @@ const BiogeneratorItems = (props) => {
key={kv[0]}
title={kv[0]}
items={items_in_cat}
+ build_eff={build_eff}
+ beaker={beaker}
/>
);
});
@@ -129,7 +151,14 @@ const BiogeneratorItems = (props) => {
);
};
-const BiogeneratorSearch = (props) => {
+const BiogeneratorSearch = (props: {
+ searchText: string;
+ sortOrder: string;
+ descending: boolean;
+ onSearchText: Function;
+ onSortOrder: Function;
+ onDescending: Function;
+}) => {
return (
@@ -138,7 +167,7 @@ const BiogeneratorSearch = (props) => {
placeholder="Search by item name.."
value={props.searchText}
width="100%"
- onInput={(_e, value) => props.onSearchText(value)}
+ onInput={(e, value: string) => props.onSearchText(value)}
/>
@@ -165,25 +194,31 @@ const BiogeneratorSearch = (props) => {
);
};
-const canBuyItem = (item, data) => {
+const canBuyItem = (item: sortable, beaker: BooleanLike) => {
if (!item.affordable) {
return false;
}
- if (item.reagent && !data.beaker) {
+ if (item.reagent && !beaker) {
return false;
}
return true;
};
-const BiogeneratorItemsCategory = (props) => {
+const BiogeneratorItemsCategory = (props: {
+ key: string;
+ title: string;
+ items: sortable[];
+ build_eff: number;
+ beaker: BooleanLike;
+}) => {
const { act, data } = useBackend();
- const { title, items, ...rest } = props;
+ const { title, items, build_eff, beaker, ...rest } = props;
return (
{items.map((item) => (
{
{item.name}
{
})
}
>
- {(item.price / data.build_eff).toLocaleString('en-US')}
+ {(item.price / build_eff).toLocaleString('en-US')}
{
+ const { act } = useBackend();
+ const { bodyrecords } = props;
+ return (
+ act('menu', { menu: 'Main' })}>
+ Back
+
+ }
+ >
+ {bodyrecords
+ ? bodyrecords.map((record) => (
+ act('view_brec', { view_brec: record.recref })}
+ >
+ {record.name}
+
+ ))
+ : ''}
+
+ );
+};
diff --git a/tgui/packages/tgui/interfaces/BodyDesigner/BodyDesignerMain.tsx b/tgui/packages/tgui/interfaces/BodyDesigner/BodyDesignerMain.tsx
new file mode 100644
index 00000000000..5da0f605165
--- /dev/null
+++ b/tgui/packages/tgui/interfaces/BodyDesigner/BodyDesignerMain.tsx
@@ -0,0 +1,16 @@
+import { useBackend } from '../../backend';
+import { Button, Section } from '../../components';
+
+export const BodyDesignerMain = (props) => {
+ const { act } = useBackend();
+ return (
+
+ act('menu', { menu: 'Body Records' })}>
+ View Individual Body Records
+
+ act('menu', { menu: 'Stock Records' })}>
+ View Stock Body Records
+
+
+ );
+};
diff --git a/tgui/packages/tgui/interfaces/BodyDesigner/BodyDesignerOOCNotes.tsx b/tgui/packages/tgui/interfaces/BodyDesigner/BodyDesignerOOCNotes.tsx
new file mode 100644
index 00000000000..f57b2f868cf
--- /dev/null
+++ b/tgui/packages/tgui/interfaces/BodyDesigner/BodyDesignerOOCNotes.tsx
@@ -0,0 +1,29 @@
+import { useBackend } from '../../backend';
+import { Button, Section } from '../../components';
+import { activeBodyRecord } from './types';
+
+export const BodyDesignerOOCNotes = (props: {
+ activeBodyRecord: activeBodyRecord;
+}) => {
+ const { act } = useBackend();
+ const { activeBodyRecord } = props;
+ return (
+ act('menu', { menu: 'Specific Record' })}
+ >
+ Back
+
+ }
+ style={{ wordBreak: 'break-all' }}
+ >
+ {(activeBodyRecord && activeBodyRecord.booc) ||
+ 'ERROR: Body record not found!'}
+
+ );
+};
diff --git a/tgui/packages/tgui/interfaces/BodyDesigner.jsx b/tgui/packages/tgui/interfaces/BodyDesigner/BodyDesignerSpecificRecord.tsx
similarity index 66%
rename from tgui/packages/tgui/interfaces/BodyDesigner.jsx
rename to tgui/packages/tgui/interfaces/BodyDesigner/BodyDesignerSpecificRecord.tsx
index 3a9fcd4483e..b6fd5e67f44 100644
--- a/tgui/packages/tgui/interfaces/BodyDesigner.jsx
+++ b/tgui/packages/tgui/interfaces/BodyDesigner/BodyDesignerSpecificRecord.tsx
@@ -1,6 +1,6 @@
import { capitalize } from 'common/string';
-import { useBackend } from '../backend';
+import { useBackend } from '../../backend';
import {
Box,
Button,
@@ -9,115 +9,15 @@ import {
Flex,
LabeledList,
Section,
-} from '../components';
-import { Window } from '../layouts';
+} from '../../components';
+import { activeBodyRecord } from './types';
-export const BodyDesigner = (props) => {
- const { act, data } = useBackend();
-
- const { menu, disk, diskStored, activeBodyRecord } = data;
-
- let body = MenuToTemplate[menu];
-
- return (
-
-
- {disk ? (
-
- act('savetodisk')}
- disabled={!activeBodyRecord}
- >
- Save To Disk
-
- act('loadfromdisk')}
- disabled={!diskStored}
- >
- Load From Disk
-
- act('ejectdisk')}>
- Eject
-
-
- ) : null}
- {body}
-
-
- );
-};
-
-const BodyDesignerMain = (props) => {
- const { act, data } = useBackend();
- return (
-
- act('menu', { menu: 'Body Records' })}>
- View Individual Body Records
-
- act('menu', { menu: 'Stock Records' })}>
- View Stock Body Records
-
-
- );
-};
-
-const BodyDesignerBodyRecords = (props) => {
- const { act, data } = useBackend();
- const { bodyrecords } = data;
- return (
- act('menu', { menu: 'Main' })}>
- Back
-
- }
- >
- {bodyrecords
- ? bodyrecords.map((record) => (
- act('view_brec', { view_brec: record.recref })}
- >
- {record.name}
-
- ))
- : ''}
-
- );
-};
-
-const BodyDesignerStockRecords = (props) => {
- const { act, data } = useBackend();
- const { stock_bodyrecords } = data;
- return (
- act('menu', { menu: 'Main' })}>
- Back
-
- }
- >
- {stock_bodyrecords.map((record) => (
- act('view_stock_brec', { view_stock_brec: record })}
- >
- {record}
-
- ))}
-
- );
-};
-
-const BodyDesignerSpecificRecord = (props) => {
- const { act, data } = useBackend();
- const { activeBodyRecord, mapRef } = data;
+export const BodyDesignerSpecificRecord = (props: {
+ activeBodyRecord: activeBodyRecord;
+ mapRef: string;
+}) => {
+ const { act } = useBackend();
+ const { activeBodyRecord, mapRef } = props;
return activeBodyRecord ? (
@@ -325,35 +225,3 @@ const BodyDesignerSpecificRecord = (props) => {
ERROR: Record Not Found!
);
};
-
-const BodyDesignerOOCNotes = (props) => {
- const { act, data } = useBackend();
- const { activeBodyRecord } = data;
- return (
- act('menu', { menu: 'Specific Record' })}
- >
- Back
-
- }
- style={{ 'word-break': 'break-all' }}
- >
- {(activeBodyRecord && activeBodyRecord.booc) ||
- 'ERROR: Body record not found!'}
-
- );
-};
-
-const MenuToTemplate = {
- Main: ,
- 'Body Records': ,
- 'Stock Records': ,
- 'Specific Record': ,
- 'OOC Notes': ,
-};
diff --git a/tgui/packages/tgui/interfaces/BodyDesigner/BodyDesignerStockRecords.tsx b/tgui/packages/tgui/interfaces/BodyDesigner/BodyDesignerStockRecords.tsx
new file mode 100644
index 00000000000..71195d4f6dd
--- /dev/null
+++ b/tgui/packages/tgui/interfaces/BodyDesigner/BodyDesignerStockRecords.tsx
@@ -0,0 +1,29 @@
+import { useBackend } from '../../backend';
+import { Button, Section } from '../../components';
+
+export const BodyDesignerStockRecords = (props: {
+ stock_bodyrecords: string[];
+}) => {
+ const { act } = useBackend();
+ const { stock_bodyrecords } = props;
+ return (
+ act('menu', { menu: 'Main' })}>
+ Back
+
+ }
+ >
+ {stock_bodyrecords.map((record) => (
+ act('view_stock_brec', { view_stock_brec: record })}
+ >
+ {record}
+
+ ))}
+
+ );
+};
diff --git a/tgui/packages/tgui/interfaces/BodyDesigner/index.tsx b/tgui/packages/tgui/interfaces/BodyDesigner/index.tsx
new file mode 100644
index 00000000000..be2178696f7
--- /dev/null
+++ b/tgui/packages/tgui/interfaces/BodyDesigner/index.tsx
@@ -0,0 +1,71 @@
+import { useBackend } from '../../backend';
+import { Box, Button } from '../../components';
+import { Window } from '../../layouts';
+import { BodyDesignerBodyRecords } from './BodyDesignerBodyRecords';
+import { BodyDesignerMain } from './BodyDesignerMain';
+import { BodyDesignerOOCNotes } from './BodyDesignerOOCNotes';
+import { BodyDesignerSpecificRecord } from './BodyDesignerSpecificRecord';
+import { BodyDesignerStockRecords } from './BodyDesignerStockRecords';
+import { Data } from './types';
+
+export const BodyDesigner = (props) => {
+ const { act, data } = useBackend();
+
+ const {
+ menu,
+ disk,
+ diskStored,
+ activeBodyRecord,
+ stock_bodyrecords,
+ bodyrecords,
+ mapRef,
+ } = data;
+
+ const MenuToTemplate = {
+ Main: ,
+ 'Body Records': ,
+ 'Stock Records': (
+
+ ),
+ 'Specific Record': (
+
+ ),
+ 'OOC Notes': ,
+ };
+
+ let body = MenuToTemplate[menu];
+
+ return (
+
+
+ {disk ? (
+
+ act('savetodisk')}
+ disabled={!activeBodyRecord}
+ >
+ Save To Disk
+
+ act('loadfromdisk')}
+ disabled={!diskStored}
+ >
+ Load From Disk
+
+ act('ejectdisk')}>
+ Eject
+
+
+ ) : (
+ ''
+ )}
+ {body}
+
+
+ );
+};
diff --git a/tgui/packages/tgui/interfaces/BodyDesigner/types.ts b/tgui/packages/tgui/interfaces/BodyDesigner/types.ts
new file mode 100644
index 00000000000..3ec032ee1e5
--- /dev/null
+++ b/tgui/packages/tgui/interfaces/BodyDesigner/types.ts
@@ -0,0 +1,63 @@
+import { BooleanLike } from 'common/react';
+
+export type Data = {
+ mapRef: string;
+ bodyrecords: bodyrecord[];
+ stock_bodyrecords: string[];
+ activeBodyRecord: activeBodyRecord;
+ menu: string;
+ temp: {
+ styleHref: string;
+ style: string;
+ color: string | undefined;
+ colorHref: string | undefined;
+ color2?: string | undefined;
+ colorHref2?: string | undefined;
+ };
+ disk: BooleanLike;
+ diskStored: BooleanLike;
+};
+
+export type bodyrecord = { name: string; recref: string };
+
+export type activeBodyRecord = {
+ real_name: string;
+ speciesname: string;
+ gender: string;
+ synthetic: string;
+ locked: string;
+ scale: string;
+ booc: string;
+ styles: {
+ Ears: colourableStyle;
+ Tail: colourableStyle;
+ Wing: colourableStyle;
+ Hair: simpleStyle;
+ Facial: simpleStyle;
+ Eyes: colourStyle;
+ 'Body Color': colourStyle;
+ Bodytype: { styleHref: string; style: string };
+ };
+ markings: { name: Record }; // Record entries match BP regions
+};
+
+type colourableStyle = {
+ styleHref: string;
+ style: string;
+ color: string | undefined;
+ colorHref: string | undefined;
+ color2: string | undefined;
+ colorHref2: string | undefined;
+};
+
+type simpleStyle = {
+ styleHref: string;
+ style: string;
+ colorHref: string;
+ color: string;
+};
+
+type colourStyle = {
+ colorHref: string;
+ color: string;
+};
diff --git a/tgui/packages/tgui/interfaces/BodyScanner.jsx b/tgui/packages/tgui/interfaces/BodyScanner.jsx
deleted file mode 100644
index 9afdb718231..00000000000
--- a/tgui/packages/tgui/interfaces/BodyScanner.jsx
+++ /dev/null
@@ -1,500 +0,0 @@
-import { round } from 'common/math';
-
-import { useBackend } from '../backend';
-import {
- AnimatedNumber,
- Box,
- Button,
- Flex,
- Icon,
- LabeledList,
- ProgressBar,
- Section,
- Table,
- Tooltip,
-} from '../components';
-import { Window } from '../layouts';
-
-const stats = [
- ['good', 'Alive'],
- ['average', 'Unconscious'],
- ['bad', 'DEAD'],
-];
-
-const abnormalities = [
- [
- 'hasBorer',
- 'bad',
- (occupant) =>
- 'Large growth detected in frontal lobe,' +
- ' possibly cancerous. Surgical removal is recommended.',
- ],
- ['hasVirus', 'bad', (occupant) => 'Viral pathogen detected in blood stream.'],
- ['blind', 'average', (occupant) => 'Cataracts detected.'],
- [
- 'colourblind',
- 'average',
- (occupant) => 'Photoreceptor abnormalities detected.',
- ],
- ['nearsighted', 'average', (occupant) => 'Retinal misalignment detected.'],
- /* VOREStation Add */
- [
- 'humanPrey',
- 'average',
- (occupant) => {
- return 'Foreign Humanoid(s) detected: ' + occupant.humanPrey;
- },
- ],
- [
- 'livingPrey',
- 'average',
- (occupant) => {
- return 'Foreign Creature(s) detected: ' + occupant.livingPrey;
- },
- ],
- [
- 'objectPrey',
- 'average',
- (occupant) => {
- return 'Foreign Object(s) detected: ' + occupant.objectPrey;
- },
- ],
- /* VOREStation Add End */
-];
-
-const damages = [
- ['Respiratory', 'oxyLoss'],
- ['Brain', 'brainLoss'],
- ['Toxin', 'toxLoss'],
- ['Radiation', 'radLoss'],
- ['Brute', 'bruteLoss'],
- ['Genetic', 'cloneLoss'],
- ['Burn', 'fireLoss'],
- ['Paralysis', 'paralysis'],
-];
-
-const damageRange = {
- average: [0.25, 0.5],
- bad: [0.5, Infinity],
-};
-
-const mapTwoByTwo = (a, c) => {
- let result = [];
- for (let i = 0; i < a.length; i += 2) {
- result.push(c(a[i], a[i + 1], i));
- }
- return result;
-};
-
-const reduceOrganStatus = (A) => {
- return A.length > 0
- ? A.reduce((a, s) =>
- a === null ? (
- s
- ) : (
- <>
- {a}
- {!!s && {s}}
- >
- ),
- )
- : null;
-};
-
-const germStatus = (i) => {
- if (i > 100) {
- if (i < 300) {
- return 'mild infection';
- }
- if (i < 400) {
- return 'mild infection+';
- }
- if (i < 500) {
- return 'mild infection++';
- }
- if (i < 700) {
- return 'acute infection';
- }
- if (i < 800) {
- return 'acute infection+';
- }
- if (i < 900) {
- return 'acute infection++';
- }
- if (i >= 900) {
- return 'septic';
- }
- }
-
- return '';
-};
-
-export const BodyScanner = (props) => {
- const { data } = useBackend();
- const { occupied, occupant = {} } = data;
- const body = occupied ? (
-
- ) : (
-
- );
- return (
-
-
- {body}
-
-
- );
-};
-
-const BodyScannerMain = (props) => {
- const { occupant } = props;
- return (
-
-
-
-
-
-
-
-
- );
-};
-
-const BodyScannerMainOccupant = (props) => {
- const { act, data } = useBackend();
- const { occupant } = data;
- return (
-
- act('ejectify')}>
- Eject
-
- act('print_p')}>
- Print Report
-
- >
- }
- >
-
- {occupant.name}
-
-
-
-
- {stats[occupant.stat][1]}
-
-
-
- °C,
-
- °F
-
-
- {' '}
- units (
-
- %)
-
- {/* VOREStation Add */}
-
- {round(data.occupant.weight) +
- 'lbs, ' +
- round(data.occupant.weight / 2.20463) +
- 'kgs'}
-
- {/* VOREStation Add End */}
-
-
- );
-};
-
-const BodyScannerMainReagents = (props) => {
- const { occupant } = props;
-
- return (
- <>
-
- {occupant.reagents ? (
-
-
- Reagent
- Amount
-
- {occupant.reagents.map((reagent) => (
-
- {reagent.name}
-
- {reagent.amount} Units{' '}
- {reagent.overdose ? OVERDOSING : null}
-
-
- ))}
-
- ) : (
- No Blood Reagents Detected
- )}
-
-
- {occupant.ingested ? (
-
-
- Reagent
- Amount
-
- {occupant.ingested.map((reagent) => (
-
- {reagent.name}
-
- {reagent.amount} Units{' '}
- {reagent.overdose ? OVERDOSING : null}
-
-
- ))}
-
- ) : (
- No Stomach Reagents Detected
- )}
-
- >
- );
-};
-
-const BodyScannerMainAbnormalities = (props) => {
- const { occupant } = props;
-
- let hasAbnormalities =
- occupant.hasBorer ||
- occupant.blind ||
- occupant.colourblind ||
- occupant.nearsighted ||
- occupant.hasVirus;
-
- /* VOREStation Add */
- hasAbnormalities =
- hasAbnormalities ||
- occupant.humanPrey ||
- occupant.livingPrey ||
- occupant.objectPrey;
- /* VOREStation Add End */
-
- if (!hasAbnormalities) {
- return (
-
- No abnormalities found.
-
- );
- }
-
- return (
-
- {abnormalities.map((a, i) => {
- if (occupant[a[0]]) {
- return (
-
- {a[2](occupant)}
-
- );
- }
- })}
-
- );
-};
-
-const BodyScannerMainDamage = (props) => {
- const { occupant } = props;
- return (
-
-
- {mapTwoByTwo(damages, (d1, d2, i) => (
- <>
-
- {d1[0]}:
- {!!d2 && d2[0] + ':'}
-
-
-
-
-
-
- {!!d2 && }
-
-
- >
- ))}
-
-
- );
-};
-
-const BodyScannerMainDamageBar = (props) => {
- return (
-
- {round(props.value, 0)}
-
- );
-};
-
-const BodyScannerMainOrgansExternal = (props) => {
- if (props.organs.length === 0) {
- return (
-
- );
- }
-
- return (
-
-
-
- Name
- Damage
- Injuries
-
- {props.organs.map((o, i) => (
-
- {o.name}
-
- 0 && '0.5rem'}
- value={o.totalLoss / 100}
- ranges={damageRange}
- >
-
- {!!o.bruteLoss && (
-
-
- {round(o.bruteLoss, 0)}
-
-
- )}
- {!!o.fireLoss && (
-
-
- {round(o.fireLoss, 0)}
-
-
- )}
-
- {round(o.totalLoss, 0)}
-
-
-
-
- {reduceOrganStatus([
- o.internalBleeding && 'Internal bleeding',
- !!o.status.bleeding && 'External bleeding',
- o.lungRuptured && 'Ruptured lung',
- o.destroyed && 'Destroyed',
- !!o.status.broken && o.status.broken,
- germStatus(o.germ_level),
- !!o.open && 'Open incision',
- ])}
-
-
- {reduceOrganStatus([
- !!o.status.splinted && 'Splinted',
- !!o.status.robotic && 'Robotic',
- !!o.status.dead && DEAD,
- ])}
- {reduceOrganStatus(
- o.implants.map((s) => (s.known ? s.name : 'Unknown object')),
- )}
-
-
-
- ))}
-
-
- );
-};
-
-const BodyScannerMainOrgansInternal = (props) => {
- if (props.organs.length === 0) {
- return (
-
- );
- }
-
- return (
-
-
-
- Name
- Damage
- Injuries
-
- {props.organs.map((o, i) => (
-
- {o.name}
-
- 0 && '0.5rem'}
- ranges={damageRange}
- >
- {round(o.damage, 0)}
-
-
-
-
- {reduceOrganStatus([
- germStatus(o.germ_level),
- !!o.inflamed && 'Appendicitis detected.',
- ])}
-
-
- {reduceOrganStatus([
- o.robotic === 1 && 'Robotic',
- o.robotic === 2 && 'Assisted',
- !!o.dead && DEAD,
- ])}
-
-
-
- ))}
-
-
- );
-};
-
-const BodyScannerEmpty = () => {
- return (
-
-
-
-
-
- No occupant detected.
-
-
-
- );
-};
diff --git a/tgui/packages/tgui/interfaces/BodyScanner/BodyScannerEmpty.tsx b/tgui/packages/tgui/interfaces/BodyScanner/BodyScannerEmpty.tsx
new file mode 100644
index 00000000000..e6f0f4bae97
--- /dev/null
+++ b/tgui/packages/tgui/interfaces/BodyScanner/BodyScannerEmpty.tsx
@@ -0,0 +1,15 @@
+import { Flex, Icon, Section } from '../../components';
+
+export const BodyScannerEmpty = () => {
+ return (
+
+
+
+
+
+ No occupant detected.
+
+
+
+ );
+};
diff --git a/tgui/packages/tgui/interfaces/BodyScanner/BodyScannerMain.tsx b/tgui/packages/tgui/interfaces/BodyScanner/BodyScannerMain.tsx
new file mode 100644
index 00000000000..28237b0b04f
--- /dev/null
+++ b/tgui/packages/tgui/interfaces/BodyScanner/BodyScannerMain.tsx
@@ -0,0 +1,22 @@
+import { Box } from '../../components';
+import { BodyScannerMainAbnormalities } from './BodyScannerMainAbnormalities';
+import { BodyScannerMainDamage } from './BodyScannerMainDamage';
+import { BodyScannerMainOccupant } from './BodyScannerMainOccupant';
+import { BodyScannerMainOrgansExternal } from './BodyScannerMainOrgansExternal';
+import { BodyScannerMainOrgansInternal } from './BodyScannerMainOrgansInternal';
+import { BodyScannerMainReagents } from './BodyScannerMainReagents';
+import { occupant } from './types';
+
+export const BodyScannerMain = (props: { occupant: occupant }) => {
+ const { occupant } = props;
+ return (
+
+
+
+
+
+
+
+
+ );
+};
diff --git a/tgui/packages/tgui/interfaces/BodyScanner/BodyScannerMainAbnormalities.tsx b/tgui/packages/tgui/interfaces/BodyScanner/BodyScannerMainAbnormalities.tsx
new file mode 100644
index 00000000000..e68604d909b
--- /dev/null
+++ b/tgui/packages/tgui/interfaces/BodyScanner/BodyScannerMainAbnormalities.tsx
@@ -0,0 +1,42 @@
+import { Box, Section } from '../../components';
+import { abnormalities } from './constants';
+import { occupant } from './types';
+
+export const BodyScannerMainAbnormalities = (props: { occupant: occupant }) => {
+ const { occupant } = props;
+
+ let hasAbnormalities =
+ occupant.hasBorer ||
+ occupant.blind ||
+ occupant.colourblind ||
+ occupant.nearsighted ||
+ occupant.hasVirus;
+
+ hasAbnormalities =
+ hasAbnormalities ||
+ occupant.humanPrey ||
+ occupant.livingPrey ||
+ occupant.objectPrey;
+
+ if (!hasAbnormalities) {
+ return (
+
+ No abnormalities found.
+
+ );
+ }
+
+ return (
+
+ {abnormalities.map((a, i) => {
+ if (occupant[a[0] as string]) {
+ return (
+
+ {(a[2] as (occupant: occupant) => string)(occupant)}
+
+ );
+ }
+ })}
+
+ );
+};
diff --git a/tgui/packages/tgui/interfaces/BodyScanner/BodyScannerMainDamage.tsx b/tgui/packages/tgui/interfaces/BodyScanner/BodyScannerMainDamage.tsx
new file mode 100644
index 00000000000..6b84636138a
--- /dev/null
+++ b/tgui/packages/tgui/interfaces/BodyScanner/BodyScannerMainDamage.tsx
@@ -0,0 +1,54 @@
+import { toFixed } from 'common/math';
+
+import { ProgressBar, Section, Table } from '../../components';
+import { damageRange, damages } from './constants';
+import { mapTwoByTwo } from './functions';
+import { occupant } from './types';
+
+export const BodyScannerMainDamage = (props: { occupant: occupant }) => {
+ const { occupant } = props;
+ return (
+
+
+ {mapTwoByTwo(damages, (d1: string[], d2: string[], i: number) => (
+ <>
+
+ {d1[0]}:
+ {!!d2 && d2[0] + ':'}
+
+
+
+
+
+
+ {!!d2 && }
+
+
+ >
+ ))}
+
+
+ );
+};
+
+const BodyScannerMainDamageBar = (props: {
+ value: number;
+ marginBottom?: boolean;
+}) => {
+ const { value, marginBottom } = props;
+ return (
+
+ {toFixed(value)}
+
+ );
+};
diff --git a/tgui/packages/tgui/interfaces/BodyScanner/BodyScannerMainOccupant.tsx b/tgui/packages/tgui/interfaces/BodyScanner/BodyScannerMainOccupant.tsx
new file mode 100644
index 00000000000..95f54a5cd97
--- /dev/null
+++ b/tgui/packages/tgui/interfaces/BodyScanner/BodyScannerMainOccupant.tsx
@@ -0,0 +1,81 @@
+import { toFixed } from 'common/math';
+
+import { useBackend } from '../../backend';
+import {
+ AnimatedNumber,
+ Button,
+ LabeledList,
+ ProgressBar,
+ Section,
+} from '../../components';
+import { stats } from './constants';
+import { occupant } from './types';
+
+export const BodyScannerMainOccupant = (props: { occupant: occupant }) => {
+ const { act } = useBackend();
+ const { occupant } = props;
+ return (
+
+ act('ejectify')}>
+ Eject
+
+ act('print_p')}>
+ Print Report
+
+ >
+ }
+ >
+
+ {occupant.name}
+
+
+
+
+ {stats[occupant.stat][1]}
+
+
+ toFixed(value)}
+ />
+ °C,
+ toFixed(value)}
+ />
+ °F
+
+
+ toFixed(value)}
+ />
+ units (
+ toFixed(value)}
+ />
+ %)
+
+
+ {toFixed(occupant.weight) +
+ 'lbs, ' +
+ toFixed(occupant.weight / 2.20463) +
+ 'kgs'}
+
+
+
+ );
+};
diff --git a/tgui/packages/tgui/interfaces/BodyScanner/BodyScannerMainOrgansExternal.tsx b/tgui/packages/tgui/interfaces/BodyScanner/BodyScannerMainOrgansExternal.tsx
new file mode 100644
index 00000000000..cd32c84a4a8
--- /dev/null
+++ b/tgui/packages/tgui/interfaces/BodyScanner/BodyScannerMainOrgansExternal.tsx
@@ -0,0 +1,99 @@
+import { toFixed } from 'common/math';
+
+import {
+ Box,
+ Icon,
+ ProgressBar,
+ Section,
+ Table,
+ Tooltip,
+} from '../../components';
+import { damageRange } from './constants';
+import { germStatus, reduceOrganStatus } from './functions';
+import { externalOrgan } from './types';
+
+export const BodyScannerMainOrgansExternal = (props: {
+ organs: externalOrgan[];
+}) => {
+ const { organs } = props;
+
+ if (organs.length === 0) {
+ return (
+
+ );
+ }
+
+ return (
+
+
+
+ Name
+ Damage
+ Injuries
+
+ {organs.map((o, i) => (
+
+ {o.name}
+
+ 0 && '0.5rem'}
+ value={o.totalLoss / 100}
+ ranges={damageRange}
+ >
+
+ {!!o.bruteLoss && (
+
+
+ {toFixed(o.bruteLoss)}
+
+
+ )}
+ {!!o.fireLoss && (
+
+
+ {toFixed(o.fireLoss)}
+
+
+ )}
+
+ {toFixed(o.totalLoss)}
+
+
+
+
+ {reduceOrganStatus([
+ o.internalBleeding && 'Internal bleeding',
+ !!o.status.bleeding && 'External bleeding',
+ o.lungRuptured && 'Ruptured lung',
+ o.status.destroyed && 'Destroyed',
+ !!o.status.broken && o.status.broken,
+ germStatus(o.germ_level),
+ !!o.open && 'Open incision',
+ ])}
+
+
+ {reduceOrganStatus([
+ !!o.status.splinted && 'Splinted',
+ !!o.status.robotic && 'Robotic',
+ !!o.status.dead && DEAD,
+ ])}
+ {reduceOrganStatus(
+ o.implants.map((s) => (s.known ? s.name : 'Unknown object')),
+ )}
+
+
+
+ ))}
+
+
+ );
+};
diff --git a/tgui/packages/tgui/interfaces/BodyScanner/BodyScannerMainOrgansInternal.tsx b/tgui/packages/tgui/interfaces/BodyScanner/BodyScannerMainOrgansInternal.tsx
new file mode 100644
index 00000000000..af44217f2d6
--- /dev/null
+++ b/tgui/packages/tgui/interfaces/BodyScanner/BodyScannerMainOrgansInternal.tsx
@@ -0,0 +1,63 @@
+import { toFixed } from 'common/math';
+
+import { Box, ProgressBar, Section, Table } from '../../components';
+import { damageRange } from './constants';
+import { germStatus, reduceOrganStatus } from './functions';
+import { internalOrgan } from './types';
+
+export const BodyScannerMainOrgansInternal = (props: {
+ organs: internalOrgan[];
+}) => {
+ const { organs } = props;
+
+ if (organs.length === 0) {
+ return (
+
+ );
+ }
+
+ return (
+
+
+
+ Name
+ Damage
+ Injuries
+
+ {organs.map((o, i) => (
+
+ {o.name}
+
+ 0 && '0.5rem'}
+ ranges={damageRange}
+ >
+ {toFixed(o.damage)}
+
+
+
+
+ {reduceOrganStatus([
+ germStatus(o.germ_level),
+ !!o.inflamed && 'Appendicitis detected.',
+ ])}
+
+
+ {reduceOrganStatus([
+ o.robotic === 1 && 'Robotic',
+ o.robotic === 2 && 'Assisted',
+ !!o.dead && DEAD,
+ ])}
+
+
+
+ ))}
+
+
+ );
+};
diff --git a/tgui/packages/tgui/interfaces/BodyScanner/BodyScannerMainReagents.tsx b/tgui/packages/tgui/interfaces/BodyScanner/BodyScannerMainReagents.tsx
new file mode 100644
index 00000000000..922cb4059ad
--- /dev/null
+++ b/tgui/packages/tgui/interfaces/BodyScanner/BodyScannerMainReagents.tsx
@@ -0,0 +1,53 @@
+import { Box, Section, Table } from '../../components';
+import { occupant } from './types';
+
+export const BodyScannerMainReagents = (props: { occupant: occupant }) => {
+ const { occupant } = props;
+
+ return (
+ <>
+
+ {occupant.reagents ? (
+
+
+ Reagent
+ Amount
+
+ {occupant.reagents.map((reagent) => (
+
+ {reagent.name}
+
+ {reagent.amount} Units{' '}
+ {reagent.overdose ? OVERDOSING : null}
+
+
+ ))}
+
+ ) : (
+ No Blood Reagents Detected
+ )}
+
+
+ {occupant.ingested ? (
+
+
+ Reagent
+ Amount
+
+ {occupant.ingested.map((reagent) => (
+
+ {reagent.name}
+
+ {reagent.amount} Units{' '}
+ {reagent.overdose ? OVERDOSING : null}
+
+
+ ))}
+
+ ) : (
+ No Stomach Reagents Detected
+ )}
+
+ >
+ );
+};
diff --git a/tgui/packages/tgui/interfaces/BodyScanner/constants.ts b/tgui/packages/tgui/interfaces/BodyScanner/constants.ts
new file mode 100644
index 00000000000..14dfe607709
--- /dev/null
+++ b/tgui/packages/tgui/interfaces/BodyScanner/constants.ts
@@ -0,0 +1,62 @@
+import { occupant } from './types';
+
+export const stats: string[][] = [
+ ['good', 'Alive'],
+ ['average', 'Unconscious'],
+ ['bad', 'DEAD'],
+];
+
+export const abnormalities: (string | ((occupant: occupant) => string))[][] = [
+ [
+ 'hasBorer',
+ 'bad',
+ (occupant) =>
+ 'Large growth detected in frontal lobe,' +
+ ' possibly cancerous. Surgical removal is recommended.',
+ ],
+ ['hasVirus', 'bad', (occupant) => 'Viral pathogen detected in blood stream.'],
+ ['blind', 'average', (occupant) => 'Cataracts detected.'],
+ [
+ 'colourblind',
+ 'average',
+ (occupant) => 'Photoreceptor abnormalities detected.',
+ ],
+ ['nearsighted', 'average', (occupant) => 'Retinal misalignment detected.'],
+ [
+ 'humanPrey',
+ 'average',
+ (occupant) => {
+ return 'Foreign Humanoid(s) detected: ' + occupant.humanPrey;
+ },
+ ],
+ [
+ 'livingPrey',
+ 'average',
+ (occupant) => {
+ return 'Foreign Creature(s) detected: ' + occupant.livingPrey;
+ },
+ ],
+ [
+ 'objectPrey',
+ 'average',
+ (occupant) => {
+ return 'Foreign Object(s) detected: ' + occupant.objectPrey;
+ },
+ ],
+];
+
+export const damages: string[][] = [
+ ['Respiratory', 'oxyLoss'],
+ ['Brain', 'brainLoss'],
+ ['Toxin', 'toxLoss'],
+ ['Radiation', 'radLoss'],
+ ['Brute', 'bruteLoss'],
+ ['Genetic', 'cloneLoss'],
+ ['Burn', 'fireLoss'],
+ ['Paralysis', 'paralysis'],
+];
+
+export const damageRange: Record = {
+ average: [0.25, 0.5],
+ bad: [0.5, Infinity],
+};
diff --git a/tgui/packages/tgui/interfaces/BodyScanner/functions.tsx b/tgui/packages/tgui/interfaces/BodyScanner/functions.tsx
new file mode 100644
index 00000000000..3c43d892f08
--- /dev/null
+++ b/tgui/packages/tgui/interfaces/BodyScanner/functions.tsx
@@ -0,0 +1,58 @@
+import { BooleanLike } from 'common/react';
+
+import { Box } from '../../components';
+
+/*
+ */
+export function mapTwoByTwo(a: any[][], c: any) {
+ let result: any[] = [];
+ for (let i = 0; i < a.length; i += 2) {
+ result.push(c(a[i], a[i + 1], i));
+ }
+ return result;
+}
+
+export function reduceOrganStatus(
+ A: (string | BooleanLike | React.ReactElement)[],
+) {
+ return A.length > 0
+ ? A.reduce((a, s) =>
+ a === null ? (
+ s
+ ) : (
+ <>
+ {a}
+ {!!s && {s}}
+ >
+ ),
+ )
+ : null;
+}
+
+export function germStatus(i: number): string {
+ if (i > 100) {
+ if (i < 300) {
+ return 'mild infection';
+ }
+ if (i < 400) {
+ return 'mild infection+';
+ }
+ if (i < 500) {
+ return 'mild infection++';
+ }
+ if (i < 700) {
+ return 'acute infection';
+ }
+ if (i < 800) {
+ return 'acute infection+';
+ }
+ if (i < 900) {
+ return 'acute infection++';
+ }
+ if (i >= 900) {
+ return 'septic';
+ }
+ }
+
+ return '';
+}
diff --git a/tgui/packages/tgui/interfaces/BodyScanner/index.tsx b/tgui/packages/tgui/interfaces/BodyScanner/index.tsx
new file mode 100644
index 00000000000..d44bc0d78bf
--- /dev/null
+++ b/tgui/packages/tgui/interfaces/BodyScanner/index.tsx
@@ -0,0 +1,23 @@
+import { useBackend } from '../../backend';
+import { Window } from '../../layouts';
+import { BodyScannerEmpty } from './BodyScannerEmpty';
+import { BodyScannerMain } from './BodyScannerMain';
+import { Data, occupant } from './types';
+
+export const BodyScanner = (props) => {
+ const { data } = useBackend();
+ const { occupied, occupant = {} as occupant } = data;
+ const body = occupied ? (
+
+ ) : (
+
+ );
+ return (
+
+
+ {body}
+
+
+ );
+};
+6;
diff --git a/tgui/packages/tgui/interfaces/BodyScanner/types.ts b/tgui/packages/tgui/interfaces/BodyScanner/types.ts
new file mode 100644
index 00000000000..45aacd61ce9
--- /dev/null
+++ b/tgui/packages/tgui/interfaces/BodyScanner/types.ts
@@ -0,0 +1,77 @@
+import { BooleanLike } from 'common/react';
+
+export type Data = {
+ occupied: BooleanLike;
+ occupant: occupant;
+};
+
+export type occupant = {
+ name: string;
+ stat: number;
+ health: number;
+ maxHealth: number;
+ hasVirus: number;
+ bruteLoss: number;
+ oxyLoss: number;
+ toxLoss: number;
+ fireLoss: number;
+ radLoss: number;
+ cloneLoss: number;
+ brainLoss: number;
+ paralysis: number;
+ paralysisSeconds: number;
+ bodyTempC: number;
+ bodyTempF: number;
+ hasBorer: BooleanLike;
+ colourblind: BooleanLike;
+ blood: { volume: number; percent: number };
+ reagents: reagent[];
+ ingested: reagent[];
+ extOrgan: externalOrgan[];
+ intOrgan: internalOrgan[];
+ blind: BooleanLike;
+ nearsighted: BooleanLike;
+ livingPrey: number;
+ humanPrey: number;
+ objectPrey: number;
+ weight: number;
+};
+
+type reagent = { name: string; amount: number; overdose: BooleanLike };
+
+export type internalOrgan = {
+ name: string;
+ desc: string | null;
+ germ_level: number;
+ damage: number;
+ maxHealth: number;
+ bruised: number;
+ broken: number;
+ robotic: BooleanLike;
+ dead: BooleanLike;
+ inflamed: BooleanLike;
+};
+
+export type externalOrgan = {
+ name: string;
+ open: BooleanLike;
+ germ_level: number;
+ bruteLoss: number;
+ fireLoss: number;
+ totalLoss: number;
+ maxHealth: number;
+ bruised: number;
+ broken: number;
+ implants: { name: string; known: BooleanLike }[];
+ implants_len: number;
+ status: {
+ destroyed: BooleanLike;
+ broken: string;
+ robotic: BooleanLike;
+ splinted: BooleanLike;
+ bleeding: BooleanLike;
+ dead: BooleanLike;
+ };
+ lungRuptured: BooleanLike;
+ internalBleeding: BooleanLike;
+};
diff --git a/tgui/packages/tgui/interfaces/BombTester.jsx b/tgui/packages/tgui/interfaces/BombTester.tsx
similarity index 88%
rename from tgui/packages/tgui/interfaces/BombTester.jsx
rename to tgui/packages/tgui/interfaces/BombTester.tsx
index c1e1a2afe9d..1fe140210f5 100644
--- a/tgui/packages/tgui/interfaces/BombTester.jsx
+++ b/tgui/packages/tgui/interfaces/BombTester.tsx
@@ -1,11 +1,23 @@
+import { BooleanLike } from 'common/react';
import { Component } from 'react';
import { useBackend } from '../backend';
import { Box, Button, Icon, LabeledList, Section, Slider } from '../components';
import { Window } from '../layouts';
+type Data = {
+ simulating: BooleanLike;
+ mode: number;
+ tank1: string;
+ tank1ref: string;
+ tank2: string;
+ tank2ref: string;
+ canister: string | null;
+ sim_canister_output: number;
+};
+
export const BombTester = (props) => {
- const { act, data } = useBackend();
+ const { act, data } = useBackend();
const {
simulating,
@@ -96,7 +108,7 @@ export const BombTester = (props) => {
minValue={0}
value={sim_canister_output}
maxValue={1013.25}
- onDrag={(e, val) =>
+ onDrag={(e, val: number) =>
act('set_can_pressure', { pressure: val })
}
/>
@@ -120,16 +132,26 @@ export const BombTester = (props) => {
);
};
+type stateType = {
+ reverseX: boolean;
+ reverseY: boolean;
+ x: number;
+ y: number;
+};
+
class BombTesterSimulation extends Component {
+ process: NodeJS.Timeout;
+ state: stateType;
+
constructor(props) {
super(props);
- const BOUND_X = 340;
- const BOUND_Y = 205;
- const MOVEMENT_SPEED = 2;
+ const BOUND_X: number = 340;
+ const BOUND_Y: number = 205;
+ const MOVEMENT_SPEED: number = 2;
- let startRight = Math.random() > 0.5;
- let startBottom = Math.random() > 0.5;
+ let startRight: boolean = Math.random() > 0.5;
+ let startBottom: boolean = Math.random() > 0.5;
this.state = {
x: startRight ? BOUND_X : 0,
@@ -139,7 +161,7 @@ class BombTesterSimulation extends Component {
};
this.process = setInterval(() => {
- this.setState((prevState) => {
+ this.setState((prevState: stateType) => {
const state = { ...prevState };
if (state.reverseX) {
if (state.x - MOVEMENT_SPEED < -5) {
diff --git a/tgui/packages/tgui/interfaces/BotanyEditor.jsx b/tgui/packages/tgui/interfaces/BotanyEditor.tsx
similarity index 88%
rename from tgui/packages/tgui/interfaces/BotanyEditor.jsx
rename to tgui/packages/tgui/interfaces/BotanyEditor.tsx
index 5210e31dd79..b8709812896 100644
--- a/tgui/packages/tgui/interfaces/BotanyEditor.jsx
+++ b/tgui/packages/tgui/interfaces/BotanyEditor.tsx
@@ -1,9 +1,20 @@
+import { BooleanLike } from 'common/react';
+
import { useBackend } from '../backend';
import { Box, Button, LabeledList, NoticeBox, Section } from '../components';
import { Window } from '../layouts';
+type Data = {
+ activity: BooleanLike;
+ degradation: number;
+ disk: BooleanLike;
+ loaded: string | number;
+ sourceName: string;
+ locus: string[];
+};
+
export const BotanyEditor = (props) => {
- const { act, data } = useBackend();
+ const { act, data } = useBackend();
const { activity, degradation, disk, sourceName, locus, loaded } = data;
diff --git a/tgui/packages/tgui/interfaces/BotanyIsolator.jsx b/tgui/packages/tgui/interfaces/BotanyIsolator.tsx
similarity index 91%
rename from tgui/packages/tgui/interfaces/BotanyIsolator.jsx
rename to tgui/packages/tgui/interfaces/BotanyIsolator.tsx
index b46e434268a..12c8e45e2c2 100644
--- a/tgui/packages/tgui/interfaces/BotanyIsolator.jsx
+++ b/tgui/packages/tgui/interfaces/BotanyIsolator.tsx
@@ -1,9 +1,21 @@
+import { BooleanLike } from 'common/react';
+
import { useBackend } from '../backend';
import { Box, Button, LabeledList, NoticeBox, Section } from '../components';
import { Window } from '../layouts';
+type Data = {
+ geneMasks: { tag: string; mask: string }[];
+ activity: BooleanLike;
+ degradation: number;
+ disk: BooleanLike;
+ loaded: string | number;
+ hasGenetics: BooleanLike;
+ sourceName: string;
+};
+
export const BotanyIsolator = (props) => {
- const { act, data } = useBackend();
+ const { act, data } = useBackend();
const {
geneMasks,
diff --git a/tgui/packages/tgui/interfaces/BrigTimer.jsx b/tgui/packages/tgui/interfaces/BrigTimer.tsx
similarity index 60%
rename from tgui/packages/tgui/interfaces/BrigTimer.jsx
rename to tgui/packages/tgui/interfaces/BrigTimer.tsx
index 44e693a6093..a0ab78e35d7 100644
--- a/tgui/packages/tgui/interfaces/BrigTimer.jsx
+++ b/tgui/packages/tgui/interfaces/BrigTimer.tsx
@@ -1,12 +1,36 @@
import { round } from 'common/math';
+import { BooleanLike } from 'common/react';
import { useBackend } from '../backend';
import { Button, Flex, NumberInput, Section } from '../components';
import { formatTime } from '../format';
import { Window } from '../layouts';
+type Data = {
+ time_left: number;
+ max_time_left: number;
+ timing: BooleanLike;
+ flash_found: BooleanLike;
+ flash_charging: BooleanLike;
+ preset_short: number;
+ preset_medium: number;
+ preset_long: number;
+};
+
export const BrigTimer = (props) => {
- const { act, data } = useBackend();
+ const { act, data } = useBackend();
+
+ const {
+ time_left,
+ max_time_left,
+ timing,
+ flash_found,
+ flash_charging,
+ preset_short,
+ preset_medium,
+ preset_long,
+ } = data;
+
return (
@@ -16,18 +40,18 @@ export const BrigTimer = (props) => {
<>
act(data.timing ? 'stop' : 'start')}
+ selected={timing}
+ onClick={() => act(timing ? 'stop' : 'start')}
>
- {data.timing ? 'Stop' : 'Start'}
+ {timing ? 'Stop' : 'Start'}
- {(data.flash_found && (
+ {(flash_found && (
act('flash')}
>
- {data.flash_charging ? 'Recharging' : 'Flash'}
+ {flash_charging ? 'Recharging' : 'Flash'}
)) ||
null}
@@ -37,11 +61,11 @@ export const BrigTimer = (props) => {
formatTime(round(val * 10))}
- onDrag={(e, val) => act('time', { time: val })}
+ maxValue={max_time_left / 10}
+ format={(val: number) => formatTime(round(val * 10, 0))}
+ onDrag={(e, val: number) => act('time', { time: val })}
/>
@@ -50,7 +74,7 @@ export const BrigTimer = (props) => {
icon="hourglass-start"
onClick={() => act('preset', { preset: 'short' })}
>
- {'Add ' + formatTime(data.preset_short)}
+ {'Add ' + formatTime(preset_short)}
@@ -59,7 +83,7 @@ export const BrigTimer = (props) => {
icon="hourglass-start"
onClick={() => act('preset', { preset: 'medium' })}
>
- {'Add ' + formatTime(data.preset_medium)}
+ {'Add ' + formatTime(preset_medium)}
@@ -68,7 +92,7 @@ export const BrigTimer = (props) => {
icon="hourglass-start"
onClick={() => act('preset', { preset: 'long' })}
>
- {'Add ' + formatTime(data.preset_long)}
+ {'Add ' + formatTime(preset_long)}
diff --git a/tgui/packages/tgui/interfaces/CameraConsole.jsx b/tgui/packages/tgui/interfaces/CameraConsole.tsx
similarity index 74%
rename from tgui/packages/tgui/interfaces/CameraConsole.jsx
rename to tgui/packages/tgui/interfaces/CameraConsole.tsx
index 29ee68a38aa..f558de6398f 100644
--- a/tgui/packages/tgui/interfaces/CameraConsole.jsx
+++ b/tgui/packages/tgui/interfaces/CameraConsole.tsx
@@ -1,6 +1,6 @@
import { filter, sortBy } from 'common/collections';
import { flow } from 'common/fp';
-import { classes } from 'common/react';
+import { BooleanLike, classes } from 'common/react';
import { createSearch } from 'common/string';
import { useState } from 'react';
@@ -8,11 +8,25 @@ import { useBackend } from '../backend';
import { Button, ByondUi, Dropdown, Flex, Input, Section } from '../components';
import { Window } from '../layouts';
+type activeCamera = { name: string; status: BooleanLike } | null;
+
+type camera = { name: string; networks: string[] };
+
+export type Data = {
+ activeCamera: activeCamera;
+ mapRef: string;
+ cameras: camera[];
+ allNetworks: string[];
+};
+
/**
* Returns previous and next camera names relative to the currently
* active camera.
*/
-export const prevNextCamera = (cameras, activeCamera) => {
+export const prevNextCamera = (
+ cameras: camera[],
+ activeCamera: activeCamera,
+) => {
if (!activeCamera) {
return [];
}
@@ -22,32 +36,43 @@ export const prevNextCamera = (cameras, activeCamera) => {
return [cameras[index - 1]?.name, cameras[index + 1]?.name];
};
+function notEmpty(value: TValue | null | undefined): value is TValue {
+ return value !== null && value !== undefined;
+}
+
/**
* Camera selector.
*
* Filters cameras, applies search terms and sorts the alphabetically.
*/
-export const selectCameras = (cameras, searchText = '', networkFilter = '') => {
- const testSearch = createSearch(searchText, (camera) => camera.name);
+export const selectCameras = (
+ cameras: camera[],
+ searchText: string = '',
+ networkFilter: string = '',
+) => {
+ const testSearch = createSearch(searchText, (camera: camera) => camera.name);
return flow([
// Null camera filter
- filter((camera) => camera?.name),
+ filter((camera: camera) => notEmpty(camera?.name)),
// Optional search term
searchText && filter(testSearch),
// Optional network filter
networkFilter &&
- filter((camera) => camera.networks.includes(networkFilter)),
+ filter((camera: camera) => camera.networks.includes(networkFilter)),
// Slightly expensive, but way better than sorting in BYOND
- sortBy((camera) => camera.name),
+ sortBy((camera: camera) => camera.name),
])(cameras);
};
export const CameraConsole = (props) => {
- const { act, data } = useBackend();
+ const { act, data } = useBackend();
const { mapRef, activeCamera } = data;
- const cameras = selectCameras(data.cameras);
- const [prevCameraName, nextCameraName] = prevNextCamera(
- cameras,
+
+ const { cameras } = data;
+
+ const selected_cameras: camera[] = selectCameras(cameras);
+ const [prevCameraName, nextCameraName]: string[] = prevNextCamera(
+ selected_cameras,
activeCamera,
);
return (
@@ -101,12 +126,16 @@ export const CameraConsole = (props) => {
};
export const CameraConsoleContent = (props) => {
- const { act, data } = useBackend();
- const [searchText, setSearchText] = useState('');
- const [networkFilter, setNetworkFilter] = useState('');
- const { activeCamera, allNetworks } = data;
+ const { act, data } = useBackend();
+ const [searchText, setSearchText] = useState('');
+ const [networkFilter, setNetworkFilter] = useState('');
+ const { activeCamera, allNetworks, cameras } = data;
allNetworks.sort();
- const cameras = selectCameras(data.cameras, searchText, networkFilter);
+ const selected_cameras: camera[] = selectCameras(
+ cameras,
+ searchText,
+ networkFilter,
+ );
return (
@@ -115,7 +144,7 @@ export const CameraConsoleContent = (props) => {
fluid
mt={1}
placeholder="Search for a camera"
- onInput={(e, value) => setSearchText(value)}
+ onInput={(e, value: string) => setSearchText(value)}
/>
@@ -147,7 +176,7 @@ export const CameraConsoleContent = (props) => {
- {cameras.map((camera) => (
+ {selected_cameras.map((camera) => (
// We're not using the component here because performance
// would be absolutely abysmal (50+ ms for each re-render).
{
- const { act, data } = useBackend();
+ const { act, data } = useBackend ();
const {
connected,
can_relabel,
@@ -66,7 +79,7 @@ export const Canister = (props) => {
minValue={minReleasePressure}
maxValue={maxReleasePressure}
stepPixelSize={1}
- onDrag={(e, value) =>
+ onDrag={(e, value: number) =>
act('pressure', {
pressure: value,
})
diff --git a/tgui/packages/tgui/interfaces/Canvas.jsx b/tgui/packages/tgui/interfaces/Canvas.tsx
similarity index 69%
rename from tgui/packages/tgui/interfaces/Canvas.jsx
rename to tgui/packages/tgui/interfaces/Canvas.tsx
index 4d5d7015ee3..d54eb7e66cd 100644
--- a/tgui/packages/tgui/interfaces/Canvas.jsx
+++ b/tgui/packages/tgui/interfaces/Canvas.tsx
@@ -1,4 +1,5 @@
-import { Component, createRef } from 'react';
+import { BooleanLike } from 'common/react';
+import { Component, createRef, RefObject } from 'react';
import { useBackend } from '../backend';
import { Box, Button } from '../components';
@@ -6,7 +7,17 @@ import { Window } from '../layouts';
const PX_PER_UNIT = 24;
-class PaintCanvas extends Component {
+type PaintCanvasProps = Partial<{
+ onCanvasClick: (x: number, y: number) => void;
+ value: string[][];
+ dotsize: number;
+ res: number;
+}>;
+
+class PaintCanvas extends Component {
+ canvasRef: RefObject;
+ onCVClick: (x: number, y: number) => void;
+
constructor(props) {
super(props);
this.canvasRef = createRef();
@@ -21,16 +32,20 @@ class PaintCanvas extends Component {
this.drawCanvas(this.props);
}
- drawCanvas(propSource) {
- const ctx = this.canvasRef.current.getContext('2d');
+ drawCanvas(propSource: PaintCanvasProps) {
+ const canvas = this.canvasRef.current!;
+ const ctx = canvas.getContext('2d')!;
const grid = propSource.value;
+ if (!grid) {
+ return;
+ }
const x_size = grid.length;
if (!x_size) {
return;
}
const y_size = grid[0].length;
- const x_scale = Math.round(this.canvasRef.current.width / x_size);
- const y_scale = Math.round(this.canvasRef.current.height / y_size);
+ const x_scale = Math.round(canvas.width / x_size);
+ const y_scale = Math.round(canvas.height / y_size);
ctx.save();
ctx.scale(x_scale, y_scale);
for (let x = 0; x < grid.length; x++) {
@@ -44,14 +59,19 @@ class PaintCanvas extends Component {
ctx.restore();
}
- clickwrapper(event) {
- const x_size = this.props.value.length;
+ clickwrapper(event: React.MouseEvent) {
+ const value = this.props.value;
+ if (!value) {
+ return;
+ }
+ const x_size = value.length;
if (!x_size) {
return;
}
const y_size = this.props.value[0].length;
- const x_scale = this.canvasRef.current.width / x_size;
- const y_scale = this.canvasRef.current.height / y_size;
+ const canvas = this.canvasRef.current!;
+ const x_scale = canvas.width / x_size;
+ const y_scale = canvas.height / y_size;
const x = Math.floor(event.nativeEvent.offsetX / x_scale) + 1;
const y = Math.floor(event.nativeEvent.offsetY / y_scale) + 1;
this.onCVClick(x, y);
@@ -80,8 +100,15 @@ const getImageSize = (value) => {
return [width, height];
};
+type Data = {
+ grid: string[][];
+ name: string;
+ finalized: BooleanLike;
+};
+
export const Canvas = (props) => {
- const { act, data } = useBackend();
+ const { act, data } = useBackend();
+
const dotsize = PX_PER_UNIT;
const [width, height] = getImageSize(data.grid);
return (
diff --git a/tgui/packages/tgui/interfaces/CasinoPrizeDispenser.jsx b/tgui/packages/tgui/interfaces/CasinoPrizeDispenser.tsx
similarity index 81%
rename from tgui/packages/tgui/interfaces/CasinoPrizeDispenser.jsx
rename to tgui/packages/tgui/interfaces/CasinoPrizeDispenser.tsx
index 8e03666a517..6c13f446db2 100644
--- a/tgui/packages/tgui/interfaces/CasinoPrizeDispenser.jsx
+++ b/tgui/packages/tgui/interfaces/CasinoPrizeDispenser.tsx
@@ -13,26 +13,36 @@ import {
} from '../components';
import { Window } from '../layouts';
+type Data = {
+ items: Record;
+};
+
+type sortable = {
+ name: string;
+ affordable: number;
+ price: number;
+ restriction: string;
+};
+
const sortTypes = {
- Alphabetical: (a, b) => a.name > b.name,
- 'By availability': (a, b) => -(a.affordable - b.affordable),
- 'By price': (a, b) => a.price - b.price,
+ Alphabetical: (a: sortable, b: sortable) => a.name > b.name,
+ 'By price': (a: sortable, b: sortable) => a.price - b.price,
};
export const CasinoPrizeDispenser = () => {
- const [searchText, setSearchText] = useState('');
- const [sortOrder, setSortOrder] = useState('Alphabetical');
- const [descending, setDescending] = useState(false);
+ const [searchText, setSearchText] = useState('');
+ const [sortOrder, setSortOrder] = useState('Alphabetical');
+ const [descending, setDescending] = useState(false);
- function handleSearchText(value) {
+ function handleSearchText(value: string) {
setSearchText(value);
}
- function handleSortOrder(value) {
+ function handleSortOrder(value: string) {
setSortOrder(value);
}
- function handleDescending(value) {
+ function handleDescending(value: boolean) {
setDescending(value);
}
@@ -52,9 +62,6 @@ export const CasinoPrizeDispenser = () => {
searchText={searchText}
sortOrder={sortOrder}
descending={descending}
- onSearchText={handleSearchText}
- onSortOrder={handleSortOrder}
- onDescending={handleDescending}
/>
>
@@ -98,19 +105,18 @@ const CasinoPrizeDispenserSearch = (props) => {
};
const CasinoPrizeDispenserItems = (props) => {
- const { act, data } = useBackend();
- const { points, items } = data;
+ const { act, data } = useBackend();
+ const { items } = data;
// Search thingies
const searcher = createSearch(props.searchText, (item) => {
return item[0];
});
let has_contents = false;
- let contents = Object.entries(items).map((kv, _i) => {
+ let contents = Object.entries(items).map((kv) => {
let items_in_cat = Object.entries(kv[1])
.filter(searcher)
.map((kv2) => {
- kv2[1].affordable = points >= kv2[1].price;
return kv2[1];
})
.sort(sortTypes[props.sortOrder]);
@@ -143,15 +149,19 @@ const CasinoPrizeDispenserItems = (props) => {
);
};
-const CasinoPrizeDispenserItemsCategory = (props) => {
- const { act, data } = useBackend();
+const CasinoPrizeDispenserItemsCategory = (props: {
+ key: string;
+ title: string;
+ items: sortable[];
+}) => {
+ const { act } = useBackend();
const { title, items, ...rest } = props;
return (
{items.map((item) => (
{
+const getTagColor = (tag: string) => {
switch (tag) {
case 'Unset':
return 'label';
@@ -23,19 +24,36 @@ const getTagColor = (tag) => {
}
};
+type Data = {
+ personalVisibility: BooleanLike;
+ personalTag: string;
+ personalErpTag: string;
+ directory: character[];
+};
+
+type character = {
+ name: string;
+ species: string;
+ ooc_notes: string;
+ tag: string;
+ erptag: string;
+ character_ad: string;
+ flavor_text: string;
+};
+
export const CharacterDirectory = (props) => {
- const { act, data } = useBackend();
+ const { act, data } = useBackend();
const { personalVisibility, personalTag, personalErpTag } = data;
- const [overlay, setOverlay] = useState(null);
- const [overwritePrefs, setOverwritePrefs] = useState(false);
+ const [overlay, setOverlay] = useState(null);
+ const [overwritePrefs, setOverwritePrefs] = useState(false);
- function handleOverlay(value) {
+ function handleOverlay(value: character | null) {
setOverlay(value);
}
return (
-
+
{(overlay && (
@@ -119,29 +137,29 @@ const ViewCharacter = (props) => {
}
>
-
+
-
+
-
+
-
-
+
+
{props.overlay.character_ad || 'Unset.'}
-
-
+
+
{props.overlay.ooc_notes || 'Unset.'}
-
-
+
+
{props.overlay.flavor_text || 'Unset.'}
@@ -150,17 +168,17 @@ const ViewCharacter = (props) => {
};
const CharacterDirectoryList = (props) => {
- const { act, data } = useBackend();
+ const { act, data } = useBackend();
const { directory } = data;
- const [sortId, setSortId] = useState('name');
- const [sortOrder, setSortOrder] = useState('name');
+ const [sortId, setSortId] = useState('name');
+ const [sortOrder, setSortOrder] = useState('name');
- function handleSortId(value) {
+ function handleSortId(value: string) {
setSortId(value);
}
- function handleSortOrder(value) {
+ function handleSortOrder(value: string) {
setSortOrder(value);
}
@@ -243,9 +261,14 @@ const CharacterDirectoryList = (props) => {
);
};
-const SortButton = (props) => {
- const { act, data } = useBackend();
-
+const SortButton = (props: {
+ id: string;
+ sortId: string;
+ sortOrder: string;
+ onSortId: Function;
+ onSortOrder: Function;
+ children: ReactNode | string;
+}) => {
const { id, children } = props;
// Hey, same keys mean same data~
diff --git a/tgui/packages/tgui/interfaces/CheckboxInput.tsx b/tgui/packages/tgui/interfaces/CheckboxInput.tsx
index 30ed63d08b6..de6fe94eff9 100644
--- a/tgui/packages/tgui/interfaces/CheckboxInput.tsx
+++ b/tgui/packages/tgui/interfaces/CheckboxInput.tsx
@@ -97,7 +97,7 @@ export const CheckboxInput = (props) => {
setSearchQuery(value)}
+ onInput={(e, value: string) => setSearchQuery(value)}
/>
diff --git a/tgui/packages/tgui/interfaces/ChemDispenser.jsx b/tgui/packages/tgui/interfaces/ChemDispenser.jsx
deleted file mode 100644
index 801fe33dd38..00000000000
--- a/tgui/packages/tgui/interfaces/ChemDispenser.jsx
+++ /dev/null
@@ -1,175 +0,0 @@
-import { useBackend } from '../backend';
-import { Box, Button, Flex, LabeledList, Section, Slider } from '../components';
-import { BeakerContents } from '../interfaces/common/BeakerContents';
-import { Window } from '../layouts';
-
-const dispenseAmounts = [5, 10, 20, 30, 40, 60];
-const removeAmounts = [1, 5, 10];
-
-export const ChemDispenser = (props) => {
- return (
-
-
-
-
-
-
-
- );
-};
-
-const ChemDispenserSettings = (properties) => {
- const { act, data } = useBackend();
- const { amount } = data;
- return (
-
-
-
- {dispenseAmounts.map((a, i) => (
-
- act('amount', {
- amount: a,
- })
- }
- >
- {a + 'u'}
-
- ))}
-
-
-
- act('amount', {
- amount: value,
- })
- }
- />
-
-
-
- );
-};
-
-const ChemDispenserChemicals = (properties) => {
- const { act, data } = useBackend();
- const { chemicals = [] } = data;
- const flexFillers = [];
- for (let i = 0; i < (chemicals.length + 1) % 3; i++) {
- flexFillers.push(true);
- }
- return (
-
-
- {chemicals.map((c, i) => (
-
-
- act('dispense', {
- reagent: c.id,
- })
- }
- >
- {c.title + ' (' + c.amount + ')'}
-
-
- ))}
- {flexFillers.map((_, i) => (
-
- ))}
-
-
- );
-};
-
-const ChemDispenserBeaker = (properties) => {
- const { act, data } = useBackend();
- const {
- isBeakerLoaded,
- beakerCurrentVolume,
- beakerMaxVolume,
- beakerContents = [],
- } = data;
- return (
-
- {!!isBeakerLoaded && (
-
- {beakerCurrentVolume} / {beakerMaxVolume} units
-
- )}
- act('ejectBeaker')}
- >
- Eject
-
-
- }
- >
- (
- <>
-
- act('remove', {
- reagent: chemical.id,
- amount: -1,
- })
- }
- >
- Isolate
-
- {removeAmounts.map((a, i) => (
-
- act('remove', {
- reagent: chemical.id,
- amount: a,
- })
- }
- >
- {a}
-
- ))}
-
- act('remove', {
- reagent: chemical.id,
- amount: chemical.volume,
- })
- }
- >
- ALL
-
- >
- )}
- />
-
- );
-};
diff --git a/tgui/packages/tgui/interfaces/ChemDispenser/ChemDispenserBeaker.tsx b/tgui/packages/tgui/interfaces/ChemDispenser/ChemDispenserBeaker.tsx
new file mode 100644
index 00000000000..b9946802ac4
--- /dev/null
+++ b/tgui/packages/tgui/interfaces/ChemDispenser/ChemDispenserBeaker.tsx
@@ -0,0 +1,81 @@
+import { useBackend } from '../../backend';
+import { Box, Button, Section } from '../../components';
+import { BeakerContents } from '.././common/BeakerContents';
+import { removeAmounts } from './constants';
+import { Data } from './types';
+
+export const ChemDispenserBeaker = (props) => {
+ const { act, data } = useBackend();
+ const {
+ isBeakerLoaded,
+ beakerCurrentVolume,
+ beakerMaxVolume,
+ beakerContents = [],
+ } = data;
+ return (
+
+ {!!isBeakerLoaded && (
+
+ {beakerCurrentVolume} / {beakerMaxVolume} units
+
+ )}
+ act('ejectBeaker')}
+ >
+ Eject
+
+
+ }
+ >
+ (
+ <>
+
+ act('remove', {
+ reagent: chemical.id,
+ amount: -1,
+ })
+ }
+ >
+ Isolate
+
+ {removeAmounts.map((a, i) => (
+
+ act('remove', {
+ reagent: chemical.id,
+ amount: a,
+ })
+ }
+ >
+ {a}
+
+ ))}
+
+ act('remove', {
+ reagent: chemical.id,
+ amount: chemical.volume,
+ })
+ }
+ >
+ ALL
+
+ >
+ )}
+ />
+
+ );
+};
diff --git a/tgui/packages/tgui/interfaces/ChemDispenser/ChemDispenserChemicals.tsx b/tgui/packages/tgui/interfaces/ChemDispenser/ChemDispenserChemicals.tsx
new file mode 100644
index 00000000000..06160da65f3
--- /dev/null
+++ b/tgui/packages/tgui/interfaces/ChemDispenser/ChemDispenserChemicals.tsx
@@ -0,0 +1,41 @@
+import { useBackend } from '../../backend';
+import { Button, Flex, Section } from '../../components';
+import { Data } from './types';
+
+export const ChemDispenserChemicals = (props) => {
+ const { act, data } = useBackend();
+ const { chemicals = [] } = data;
+ const flexFillers: boolean[] = [];
+ for (let i = 0; i < (chemicals.length + 1) % 3; i++) {
+ flexFillers.push(true);
+ }
+ return (
+
+
+ {chemicals.map((c, i) => (
+
+
+ act('dispense', {
+ reagent: c.id,
+ })
+ }
+ >
+ {c.name + ' (' + c.volume + ')'}
+
+
+ ))}
+ {flexFillers.map((_, i) => (
+
+ ))}
+
+
+ );
+};
diff --git a/tgui/packages/tgui/interfaces/ChemDispenser/ChemDispenserSettings.tsx b/tgui/packages/tgui/interfaces/ChemDispenser/ChemDispenserSettings.tsx
new file mode 100644
index 00000000000..2a55775822c
--- /dev/null
+++ b/tgui/packages/tgui/interfaces/ChemDispenser/ChemDispenserSettings.tsx
@@ -0,0 +1,46 @@
+import { useBackend } from '../../backend';
+import { Button, LabeledList, Section, Slider } from '../../components';
+import { dispenseAmounts } from './constants';
+import { Data } from './types';
+
+export const ChemDispenserSettings = (props) => {
+ const { act, data } = useBackend();
+ const { amount } = data;
+ return (
+
+
+
+ {dispenseAmounts.map((a, i) => (
+
+ act('amount', {
+ amount: a,
+ })
+ }
+ >
+ {a + 'u'}
+
+ ))}
+
+
+
+ act('amount', {
+ amount: value,
+ })
+ }
+ />
+
+
+
+ );
+};
diff --git a/tgui/packages/tgui/interfaces/ChemDispenser/constants.ts b/tgui/packages/tgui/interfaces/ChemDispenser/constants.ts
new file mode 100644
index 00000000000..8555508f50e
--- /dev/null
+++ b/tgui/packages/tgui/interfaces/ChemDispenser/constants.ts
@@ -0,0 +1,2 @@
+export const dispenseAmounts: number[] = [5, 10, 20, 30, 40, 60];
+export const removeAmounts: number[] = [1, 5, 10];
diff --git a/tgui/packages/tgui/interfaces/ChemDispenser/index.tsx b/tgui/packages/tgui/interfaces/ChemDispenser/index.tsx
new file mode 100644
index 00000000000..9e47296c17f
--- /dev/null
+++ b/tgui/packages/tgui/interfaces/ChemDispenser/index.tsx
@@ -0,0 +1,16 @@
+import { Window } from '../../layouts';
+import { ChemDispenserBeaker } from './ChemDispenserBeaker';
+import { ChemDispenserChemicals } from './ChemDispenserChemicals';
+import { ChemDispenserSettings } from './ChemDispenserSettings';
+
+export const ChemDispenser = (props) => {
+ return (
+
+
+
+
+
+
+
+ );
+};
diff --git a/tgui/packages/tgui/interfaces/ChemDispenser/types.ts b/tgui/packages/tgui/interfaces/ChemDispenser/types.ts
new file mode 100644
index 00000000000..f558816663e
--- /dev/null
+++ b/tgui/packages/tgui/interfaces/ChemDispenser/types.ts
@@ -0,0 +1,13 @@
+import { BooleanLike } from 'common/react';
+
+export type Data = {
+ amount: number;
+ isBeakerLoaded: BooleanLike;
+ glass: BooleanLike;
+ beakerContents: reagent[];
+ beakerCurrentVolume: number | null;
+ beakerMaxVolume: number | null;
+ chemicals: reagent[];
+};
+
+type reagent = { name: string; id: string; volume: number };
diff --git a/tgui/packages/tgui/interfaces/ChemMaster.jsx b/tgui/packages/tgui/interfaces/ChemMaster.jsx
deleted file mode 100644
index 8e154bf0b9b..00000000000
--- a/tgui/packages/tgui/interfaces/ChemMaster.jsx
+++ /dev/null
@@ -1,496 +0,0 @@
-import { classes } from '../.././common/react';
-import { useBackend } from '../backend';
-import { Box, Button, Flex, Icon, LabeledList, Section } from '../components';
-import { Window } from '../layouts';
-import { BeakerContents } from './common/BeakerContents';
-import {
- ComplexModal,
- modalOpen,
- modalRegisterBodyOverride,
-} from './common/ComplexModal';
-
-const transferAmounts = [1, 5, 10, 30, 60];
-const bottleStyles = [
- 'bottle.png',
- 'small_bottle.png',
- 'wide_bottle.png',
- 'round_bottle.png',
- 'reagent_bottle.png',
-];
-
-const analyzeModalBodyOverride = (modal) => {
- const { act, data } = useBackend();
- const result = modal.args.analysis;
- return (
-
-
-
- {result.name}
-
- {(result.desc || '').length > 0 ? result.desc : 'N/A'}
-
- {result.blood_type && (
- <>
-
- {result.blood_type}
-
-
- {result.blood_dna}
-
- >
- )}
- {!data.condi && (
-
- act('print', {
- idx: result.idx,
- beaker: modal.args.beaker,
- })
- }
- >
- Print
-
- )}
-
-
-
- );
-};
-
-export const ChemMaster = (props) => {
- const { data } = useBackend();
- const {
- condi,
- beaker,
- beaker_reagents = [],
- buffer_reagents = [],
- mode,
- } = data;
- return (
-
-
-
- 0}
- />
-
- 0}
- />
- {/* */}
-
-
- );
-};
-
-const ChemMasterBeaker = (props) => {
- const { act, data } = useBackend();
- const { beaker, beakerReagents, bufferNonEmpty } = props;
-
- let headerButton = bufferNonEmpty ? (
- act('eject')}
- >
- Eject and Clear Buffer
-
- ) : (
- act('eject')}>
- Eject and Clear Buffer
-
- );
-
- return (
-
- {beaker ? (
- (
-
-
- modalOpen('analyze', {
- idx: i + 1,
- beaker: 1,
- })
- }
- >
- Analyze
-
- {transferAmounts.map((am, j) => (
-
- act('add', {
- id: chemical.id,
- amount: am,
- })
- }
- >
- {am}
-
- ))}
-
- act('add', {
- id: chemical.id,
- amount: chemical.volume,
- })
- }
- >
- All
-
-
- modalOpen('addcustom', {
- id: chemical.id,
- })
- }
- >
- Custom..
-
-
- )}
- />
- ) : (
- No beaker loaded.
- )}
-
- );
-};
-
-const ChemMasterBuffer = (props) => {
- const { act } = useBackend();
- const { mode, bufferReagents = [] } = props;
- return (
-
- Transferring to
- act('toggle')}
- >
- {mode ? 'Beaker' : 'Disposal'}
-
-
- }
- >
- {bufferReagents.length > 0 ? (
- (
-
-
- modalOpen('analyze', {
- idx: i + 1,
- beaker: 0,
- })
- }
- >
- Analyze
-
- {transferAmounts.map((am, i) => (
-
- act('remove', {
- id: chemical.id,
- amount: am,
- })
- }
- >
- {am}
-
- ))}
-
- act('remove', {
- id: chemical.id,
- amount: chemical.volume,
- })
- }
- >
- All
-
-
- modalOpen('removecustom', {
- id: chemical.id,
- })
- }
- >
- Custom..
-
-
- )}
- />
- ) : (
- Buffer is empty.
- )}
-
- );
-};
-
-const ChemMasterProduction = (props) => {
- const { act, data } = useBackend();
- if (!props.bufferNonEmpty) {
- return (
- act('ejectp')}
- >
- {data.loaded_pill_bottle
- ? data.loaded_pill_bottle_name +
- ' (' +
- data.loaded_pill_bottle_contents_len +
- '/' +
- data.loaded_pill_bottle_storage_slots +
- ')'
- : 'No pill bottle loaded'}
-
- }
- >
-
-
-
-
- Buffer is empty.
-
-
-
- );
- }
-
- return (
- act('ejectp')}
- >
- {data.loaded_pill_bottle
- ? data.loaded_pill_bottle_name +
- ' (' +
- data.loaded_pill_bottle_contents_len +
- '/' +
- data.loaded_pill_bottle_storage_slots +
- ')'
- : 'No pill bottle loaded'}
-
- }
- >
- {!props.isCondiment ? (
-
- ) : (
-
- )}
-
- );
-};
-
-const ChemMasterProductionChemical = (props) => {
- const { act, data } = useBackend();
- return (
-
-
- modalOpen('create_pill')}
- >
- One (60u max)
-
- modalOpen('create_pill_multiple')}
- >
- Multiple
-
-
- modalOpen('change_pill_style')}>
-
-
-
- Style
-
-
-
- modalOpen('create_patch')}
- >
- One (60u max)
-
- modalOpen('create_patch_multiple')}
- >
- Multiple
-
-
-
- modalOpen('create_bottle')}
- >
- Create bottle (60u max)
-
- modalOpen('create_bottle_multiple')}
- >
- Multiple
-
-
- modalOpen('change_bottle_style')}>
-
-
-
- Style
-
-
-
- );
-};
-
-const ChemMasterProductionCondiment = (props) => {
- const { act } = useBackend();
- return (
- <>
- modalOpen('create_condi_pack')}
- >
- Create condiment pack (10u max)
-
-
- act('create_condi_bottle')}
- >
- Create bottle (60u max)
-
- >
- );
-};
-
-// const ChemMasterCustomization = (props) => {
-// const { act, data } = useBackend();
-// if (!data.loaded_pill_bottle) {
-// return (
-//
-//
-// None loaded.
-//
-//
-// );
-// }
-
-// return (
-//
-// act('ejectp')}
-// >
-// {data.loaded_pill_bottle
-// ? (
-// data.loaded_pill_bottle_name
-// + " ("
-// + data.loaded_pill_bottle_contents_len
-// + "/"
-// + data.loaded_pill_bottle_storage_slots
-// + ")"
-// )
-// : "None loaded"}
-//
-//
-// );
-// };
-
-modalRegisterBodyOverride('analyze', analyzeModalBodyOverride);
diff --git a/tgui/packages/tgui/interfaces/ChemMaster/ChemMasterAnalyzeModalBodyOverride.tsx b/tgui/packages/tgui/interfaces/ChemMaster/ChemMasterAnalyzeModalBodyOverride.tsx
new file mode 100644
index 00000000000..f51100d3e1b
--- /dev/null
+++ b/tgui/packages/tgui/interfaces/ChemMaster/ChemMasterAnalyzeModalBodyOverride.tsx
@@ -0,0 +1,53 @@
+import { useBackend } from '../../backend';
+import { Box, Button, LabeledList, Section } from '../../components';
+import { Data, modalData } from './types';
+
+export const analyzeModalBodyOverride = (modal: modalData) => {
+ const { act, data } = useBackend();
+ const result = modal.args.analysis;
+ return (
+
+
+
+ {result.name}
+
+ {(result.desc || '').length > 0 ? result.desc : 'N/A'}
+
+ {result.blood_type && (
+ <>
+
+ {result.blood_type}
+
+
+ {result.blood_dna}
+
+ >
+ )}
+ {!data.condi && (
+
+ act('print', {
+ idx: result.idx,
+ beaker: modal.args.beaker,
+ })
+ }
+ >
+ Print
+
+ )}
+
+
+
+ );
+};
diff --git a/tgui/packages/tgui/interfaces/ChemMaster/ChemMasterBeaker.tsx b/tgui/packages/tgui/interfaces/ChemMaster/ChemMasterBeaker.tsx
new file mode 100644
index 00000000000..e42b4a32073
--- /dev/null
+++ b/tgui/packages/tgui/interfaces/ChemMaster/ChemMasterBeaker.tsx
@@ -0,0 +1,94 @@
+import { BooleanLike } from 'common/react';
+
+import { useBackend } from '../../backend';
+import { Box, Button, Section } from '../../components';
+import { BeakerContents } from '.././common/BeakerContents';
+import { modalOpen } from '.././common/ComplexModal';
+import { transferAmounts } from './constants';
+import { reagent } from './types';
+
+export const ChemMasterBeaker = (props: {
+ beaker: BooleanLike;
+ beakerReagents: reagent[];
+ bufferNonEmpty: BooleanLike;
+}) => {
+ const { act } = useBackend();
+ const { beaker, beakerReagents, bufferNonEmpty } = props;
+
+ let headerButton = bufferNonEmpty ? (
+ act('eject')}
+ >
+ Eject and Clear Buffer
+
+ ) : (
+ act('eject')}>
+ Eject and Clear Buffer
+
+ );
+
+ return (
+
+ {beaker ? (
+ (
+
+
+ modalOpen('analyze', {
+ idx: i + 1,
+ beaker: 1,
+ })
+ }
+ >
+ Analyze
+
+ {transferAmounts.map((am, j) => (
+
+ act('add', {
+ id: chemical.id,
+ amount: am,
+ })
+ }
+ >
+ {am}
+
+ ))}
+
+ act('add', {
+ id: chemical.id,
+ amount: chemical.volume,
+ })
+ }
+ >
+ All
+
+
+ modalOpen('addcustom', {
+ id: chemical.id,
+ })
+ }
+ >
+ Custom..
+
+
+ )}
+ />
+ ) : (
+ No beaker loaded.
+ )}
+
+ );
+};
diff --git a/tgui/packages/tgui/interfaces/ChemMaster/ChemMasterBuffer.tsx b/tgui/packages/tgui/interfaces/ChemMaster/ChemMasterBuffer.tsx
new file mode 100644
index 00000000000..3bd477b6855
--- /dev/null
+++ b/tgui/packages/tgui/interfaces/ChemMaster/ChemMasterBuffer.tsx
@@ -0,0 +1,92 @@
+import { BooleanLike } from 'common/react';
+
+import { useBackend } from '../../backend';
+import { Box, Button, Section } from '../../components';
+import { BeakerContents } from '.././common/BeakerContents';
+import { modalOpen } from '.././common/ComplexModal';
+import { transferAmounts } from './constants';
+import { reagent } from './types';
+
+export const ChemMasterBuffer = (props: {
+ mode: BooleanLike;
+ bufferReagents: reagent[];
+}) => {
+ const { act } = useBackend();
+ const { mode, bufferReagents = [] } = props;
+ return (
+
+ Transferring to
+ act('toggle')}
+ >
+ {mode ? 'Beaker' : 'Disposal'}
+
+
+ }
+ >
+ {bufferReagents.length > 0 ? (
+ (
+
+
+ modalOpen('analyze', {
+ idx: i + 1,
+ beaker: 0,
+ })
+ }
+ >
+ Analyze
+
+ {transferAmounts.map((am, i) => (
+
+ act('remove', {
+ id: chemical.id,
+ amount: am,
+ })
+ }
+ >
+ {am}
+
+ ))}
+
+ act('remove', {
+ id: chemical.id,
+ amount: chemical.volume,
+ })
+ }
+ >
+ All
+
+
+ modalOpen('removecustom', {
+ id: chemical.id,
+ })
+ }
+ >
+ Custom..
+
+
+ )}
+ />
+ ) : (
+ Buffer is empty.
+ )}
+
+ );
+};
diff --git a/tgui/packages/tgui/interfaces/ChemMaster/ChemMasterCustomization.tsx b/tgui/packages/tgui/interfaces/ChemMaster/ChemMasterCustomization.tsx
new file mode 100644
index 00000000000..3e70da5f8e1
--- /dev/null
+++ b/tgui/packages/tgui/interfaces/ChemMaster/ChemMasterCustomization.tsx
@@ -0,0 +1,55 @@
+import { BooleanLike } from 'common/react';
+
+import { useBackend } from '../../backend';
+import { Box, Button, Section } from '../../components';
+import { modalOpen } from '.././common/ComplexModal';
+
+export const ChemMasterCustomization = (props: {
+ loaded_pill_bottle: BooleanLike;
+ loaded_pill_bottle_name: string;
+ loaded_pill_bottle_contents_len: number;
+ loaded_pill_bottle_storage_slots: number;
+}) => {
+ const { act } = useBackend();
+
+ const {
+ loaded_pill_bottle,
+ loaded_pill_bottle_name,
+ loaded_pill_bottle_contents_len,
+ loaded_pill_bottle_storage_slots,
+ } = props;
+
+ if (!loaded_pill_bottle) {
+ return (
+
+ );
+ }
+
+ return (
+
+ modalOpen('change_pill_bottle_style')}
+ >
+ Customize Bottle Color
+
+ act('ejectp')}
+ >
+ {loaded_pill_bottle
+ ? loaded_pill_bottle_name +
+ ' (' +
+ loaded_pill_bottle_contents_len +
+ '/' +
+ loaded_pill_bottle_storage_slots +
+ ')'
+ : 'None loaded'}
+
+
+ );
+};
diff --git a/tgui/packages/tgui/interfaces/ChemMaster/ChemMasterProduction.tsx b/tgui/packages/tgui/interfaces/ChemMaster/ChemMasterProduction.tsx
new file mode 100644
index 00000000000..59f9619c645
--- /dev/null
+++ b/tgui/packages/tgui/interfaces/ChemMaster/ChemMasterProduction.tsx
@@ -0,0 +1,97 @@
+import { BooleanLike } from 'common/react';
+
+import { useBackend } from '../../backend';
+import { Button, Flex, Icon, Section } from '../../components';
+import { ChemMasterProductionChemical } from './ChemMasterProductionChemical';
+import { ChemMasterProductionCondiment } from './ChemMasterProductionCondiment';
+
+export const ChemMasterProduction = (props: {
+ bufferNonEmpty: boolean;
+ isCondiment: BooleanLike;
+ loaded_pill_bottle: BooleanLike;
+ loaded_pill_bottle_name: string;
+ loaded_pill_bottle_contents_len: number;
+ loaded_pill_bottle_storage_slots: number;
+ pillsprite: string;
+ bottlesprite: string;
+}) => {
+ const { act } = useBackend();
+
+ const {
+ bufferNonEmpty,
+ isCondiment,
+ loaded_pill_bottle,
+ loaded_pill_bottle_name,
+ loaded_pill_bottle_contents_len,
+ loaded_pill_bottle_storage_slots,
+ pillsprite,
+ bottlesprite,
+ } = props;
+
+ if (!bufferNonEmpty) {
+ return (
+ act('ejectp')}
+ >
+ {loaded_pill_bottle
+ ? loaded_pill_bottle_name +
+ ' (' +
+ loaded_pill_bottle_contents_len +
+ '/' +
+ loaded_pill_bottle_storage_slots +
+ ')'
+ : 'No pill bottle loaded'}
+
+ }
+ >
+
+
+
+
+ Buffer is empty.
+
+
+
+ );
+ }
+
+ return (
+ act('ejectp')}
+ >
+ {loaded_pill_bottle
+ ? loaded_pill_bottle_name +
+ ' (' +
+ loaded_pill_bottle_contents_len +
+ '/' +
+ loaded_pill_bottle_storage_slots +
+ ')'
+ : 'No pill bottle loaded'}
+
+ }
+ >
+ {!isCondiment ? (
+
+ ) : (
+
+ )}
+
+ );
+};
diff --git a/tgui/packages/tgui/interfaces/ChemMaster/ChemMasterProductionChemical.tsx b/tgui/packages/tgui/interfaces/ChemMaster/ChemMasterProductionChemical.tsx
new file mode 100644
index 00000000000..3b7a4c2e136
--- /dev/null
+++ b/tgui/packages/tgui/interfaces/ChemMaster/ChemMasterProductionChemical.tsx
@@ -0,0 +1,111 @@
+import { classes } from '../../.././common/react';
+import { Box, Button, LabeledList } from '../../components';
+import { modalOpen } from '.././common/ComplexModal';
+
+export const ChemMasterProductionChemical = (props: {
+ pillsprite: string;
+ bottlesprite: string;
+}) => {
+ const { pillsprite, bottlesprite } = props;
+
+ return (
+
+
+ modalOpen('create_pill')}
+ >
+ One (60u max)
+
+ modalOpen('create_pill_multiple')}
+ >
+ Multiple
+
+
+ modalOpen('change_pill_style')}>
+
+
+
+ Style
+
+
+
+ modalOpen('create_patch')}
+ >
+ One (60u max)
+
+ modalOpen('create_patch_multiple')}
+ >
+ Multiple
+
+
+
+ modalOpen('create_bottle')}
+ >
+ Create bottle (60u max)
+
+ modalOpen('create_bottle_multiple')}
+ >
+ Multiple
+
+
+ modalOpen('change_bottle_style')}>
+
+
+
+ Style
+
+
+
+ );
+};
diff --git a/tgui/packages/tgui/interfaces/ChemMaster/ChemMasterProductionCondiment.tsx b/tgui/packages/tgui/interfaces/ChemMaster/ChemMasterProductionCondiment.tsx
new file mode 100644
index 00000000000..fd58ec8228c
--- /dev/null
+++ b/tgui/packages/tgui/interfaces/ChemMaster/ChemMasterProductionCondiment.tsx
@@ -0,0 +1,26 @@
+import { useBackend } from '../../backend';
+import { Button } from '../../components';
+import { modalOpen } from '.././common/ComplexModal';
+
+export const ChemMasterProductionCondiment = (props) => {
+ const { act } = useBackend();
+ return (
+ <>
+ modalOpen('create_condi_pack')}
+ >
+ Create condiment pack (10u max)
+
+
+ act('create_condi_bottle')}
+ >
+ Create bottle (60u max)
+
+ >
+ );
+};
diff --git a/tgui/packages/tgui/interfaces/ChemMaster/constants.ts b/tgui/packages/tgui/interfaces/ChemMaster/constants.ts
new file mode 100644
index 00000000000..8fa1d18fa32
--- /dev/null
+++ b/tgui/packages/tgui/interfaces/ChemMaster/constants.ts
@@ -0,0 +1,8 @@
+export const transferAmounts: number[] = [1, 5, 10, 30, 60];
+export const bottleStyles: string[] = [
+ 'bottle.png',
+ 'small_bottle.png',
+ 'wide_bottle.png',
+ 'round_bottle.png',
+ 'reagent_bottle.png',
+];
diff --git a/tgui/packages/tgui/interfaces/ChemMaster/index.tsx b/tgui/packages/tgui/interfaces/ChemMaster/index.tsx
new file mode 100644
index 00000000000..658ffc8f3c0
--- /dev/null
+++ b/tgui/packages/tgui/interfaces/ChemMaster/index.tsx
@@ -0,0 +1,65 @@
+import { useBackend } from '../../backend';
+import { Window } from '../../layouts';
+import {
+ ComplexModal,
+ modalRegisterBodyOverride,
+} from '.././common/ComplexModal';
+import { analyzeModalBodyOverride } from './ChemMasterAnalyzeModalBodyOverride';
+import { ChemMasterBeaker } from './ChemMasterBeaker';
+import { ChemMasterBuffer } from './ChemMasterBuffer';
+import { ChemMasterProduction } from './ChemMasterProduction';
+import { Data } from './types';
+
+export const ChemMaster = (props) => {
+ const { data } = useBackend();
+ const {
+ condi,
+ beaker,
+ beaker_reagents = [],
+ buffer_reagents = [],
+ mode,
+ loaded_pill_bottle,
+ loaded_pill_bottle_name,
+ loaded_pill_bottle_contents_len,
+ loaded_pill_bottle_storage_slots,
+ pillsprite,
+ bottlesprite,
+ } = data;
+ return (
+
+
+
+ 0}
+ />
+
+ 0}
+ loaded_pill_bottle={loaded_pill_bottle}
+ loaded_pill_bottle_name={loaded_pill_bottle_name || ''}
+ loaded_pill_bottle_contents_len={loaded_pill_bottle_contents_len || 0}
+ loaded_pill_bottle_storage_slots={
+ loaded_pill_bottle_storage_slots || 0
+ }
+ pillsprite={pillsprite}
+ bottlesprite={bottlesprite}
+ />
+ {/* Vorestation Remove
+
+ */}
+
+
+ );
+};
+
+modalRegisterBodyOverride('analyze', analyzeModalBodyOverride);
diff --git a/tgui/packages/tgui/interfaces/ChemMaster/types.ts b/tgui/packages/tgui/interfaces/ChemMaster/types.ts
new file mode 100644
index 00000000000..a5b6dcdca09
--- /dev/null
+++ b/tgui/packages/tgui/interfaces/ChemMaster/types.ts
@@ -0,0 +1,40 @@
+import { BooleanLike } from 'common/react';
+
+export type Data = {
+ condi: BooleanLike;
+ loaded_pill_bottle: BooleanLike;
+ loaded_pill_bottle_name: string | undefined;
+ loaded_pill_bottle_contents_len: number | undefined;
+ loaded_pill_bottle_storage_slots: number | undefined;
+ beaker: BooleanLike;
+ beaker_reagents: reagent[] | undefined;
+ buffer_reagents: reagent[] | undefined;
+ pillsprite: string;
+ bottlesprite: string;
+ mode: BooleanLike;
+ printing: BooleanLike;
+ modal: modalData;
+};
+
+export type reagent = {
+ name: string;
+ volume: number;
+ description: string;
+ id: string;
+};
+
+export type modalData = {
+ id: string;
+ text: string;
+ args: {
+ analysis: {
+ idx: string;
+ name: string;
+ desc: string;
+ blood_type: string | null;
+ blood_dna: string | null;
+ };
+ beaker: number;
+ };
+ type: string;
+};
diff --git a/tgui/packages/tgui/interfaces/ClawMachine.jsx b/tgui/packages/tgui/interfaces/ClawMachine.tsx
similarity index 91%
rename from tgui/packages/tgui/interfaces/ClawMachine.jsx
rename to tgui/packages/tgui/interfaces/ClawMachine.tsx
index a25e9e0395e..3b43c19352a 100644
--- a/tgui/packages/tgui/interfaces/ClawMachine.jsx
+++ b/tgui/packages/tgui/interfaces/ClawMachine.tsx
@@ -2,8 +2,15 @@ import { useBackend } from '../backend';
import { Box, Button, LabeledList, ProgressBar } from '../components';
import { Window } from '../layouts';
+type Data = {
+ wintick: number;
+ instructions: string;
+ gameStatus: string;
+ winscreen: string;
+};
+
export const ClawMachine = (props) => {
- const { act, data } = useBackend();
+ const { act, data } = useBackend();
const { wintick, instructions, gameStatus, winscreen } = data;
let body;
@@ -39,7 +46,7 @@ export const ClawMachine = (props) => {
average: [1, 7],
good: [8, Infinity],
}}
- value={data.wintick}
+ value={wintick}
minValue={0}
maxValue={10}
/>
diff --git a/tgui/packages/tgui/interfaces/Cleanbot.jsx b/tgui/packages/tgui/interfaces/Cleanbot.tsx
similarity index 91%
rename from tgui/packages/tgui/interfaces/Cleanbot.jsx
rename to tgui/packages/tgui/interfaces/Cleanbot.tsx
index 09632130d36..e66624a11ee 100644
--- a/tgui/packages/tgui/interfaces/Cleanbot.jsx
+++ b/tgui/packages/tgui/interfaces/Cleanbot.tsx
@@ -1,9 +1,27 @@
+import { BooleanLike } from 'common/react';
+
import { useBackend } from '../backend';
import { Box, Button, LabeledList, Section } from '../components';
import { Window } from '../layouts';
+type Data = {
+ on: BooleanLike;
+ open: BooleanLike;
+ locked: BooleanLike;
+ patrol: BooleanLike;
+ vocal: BooleanLike;
+ wet_floors: BooleanLike;
+ spray_blood: BooleanLike;
+ version: string;
+ blood: BooleanLike;
+ rgbpanel: BooleanLike;
+ red_switch: BooleanLike;
+ green_switch: BooleanLike;
+ blue_switch: BooleanLike;
+};
+
export const Cleanbot = (props) => {
- const { act, data } = useBackend();
+ const { act, data } = useBackend();
const {
on,
@@ -11,7 +29,6 @@ export const Cleanbot = (props) => {
locked,
version,
blood,
- patrol,
vocal,
wet_floors,
spray_blood,
diff --git a/tgui/packages/tgui/interfaces/CloningConsole.jsx b/tgui/packages/tgui/interfaces/CloningConsole.jsx
deleted file mode 100644
index 99d10942eff..00000000000
--- a/tgui/packages/tgui/interfaces/CloningConsole.jsx
+++ /dev/null
@@ -1,458 +0,0 @@
-import { round } from 'common/math';
-
-import { resolveAsset } from '../assets';
-import { useBackend } from '../backend';
-import {
- Box,
- Button,
- Flex,
- Icon,
- LabeledList,
- NoticeBox,
- ProgressBar,
- Section,
- Tabs,
-} from '../components';
-import { COLORS } from '../constants';
-import {
- ComplexModal,
- modalRegisterBodyOverride,
-} from '../interfaces/common/ComplexModal';
-import { Window } from '../layouts';
-
-const viewRecordModalBodyOverride = (modal) => {
- const { act, data } = useBackend();
- const { activerecord, realname, health, unidentity, strucenzymes } =
- modal.args;
- const damages = health.split(' - ');
- return (
-
-
- {realname}
-
- {damages.length > 1 ? (
- <>
-
- {damages[0]}
-
- |
-
- {damages[2]}
-
- |
-
- {damages[3]}
-
- |
-
- {damages[1]}
-
- >
- ) : (
- Unknown
- )}
-
-
- {unidentity}
-
-
- {strucenzymes}
-
-
-
- act('disk', {
- option: 'load',
- })
- }
- >
- Import
-
-
- act('disk', {
- option: 'save',
- savetype: 'ui',
- })
- }
- >
- Export UI
-
-
- act('disk', {
- option: 'save',
- savetype: 'ue',
- })
- }
- >
- Export UI and UE
-
-
- act('disk', {
- option: 'save',
- savetype: 'se',
- })
- }
- >
- Export SE
-
-
-
-
- act('clone', {
- ref: activerecord,
- })
- }
- >
- Clone
-
- act('del_rec')}>
- Delete
-
-
-
-
- );
-};
-
-export const CloningConsole = (props) => {
- const { act, data } = useBackend();
- const { menu } = data;
- modalRegisterBodyOverride('view_rec', viewRecordModalBodyOverride);
- return (
-
-
-
-
-
-
-
-
-
- );
-};
-
-const CloningConsoleNavigation = (props) => {
- const { act, data } = useBackend();
- const { menu } = data;
- return (
-
-
- act('menu', {
- num: 1,
- })
- }
- >
- Main
-
-
- act('menu', {
- num: 2,
- })
- }
- >
- Records
-
-
- );
-};
-
-const CloningConsoleBody = (props) => {
- const { data } = useBackend();
- const { menu } = data;
- let body;
- if (menu === 1) {
- body = ;
- } else if (menu === 2) {
- body = ;
- }
- return body;
-};
-
-const CloningConsoleMain = (props) => {
- const { act, data } = useBackend();
- const {
- loading,
- scantemp,
- occupant,
- locked,
- can_brainscan,
- scan_mode,
- numberofpods,
- pods,
- selected_pod,
- } = data;
- const isLocked = locked && !!occupant;
- return (
- <>
-
-
- Scanner Lock:
-
- act('lock')}
- >
- {isLocked ? 'Engaged' : 'Disengaged'}
-
- act('eject')}
- >
- Eject Occupant
-
- >
- }
- >
-
-
- {loading ? (
-
-
- Scanning...
-
- ) : (
- {scantemp.text}
- )}
-
- {!!can_brainscan && (
-
- act('toggle_mode')}
- >
- {scan_mode ? 'Brain' : 'Body'}
-
-
- )}
-
- act('scan')}
- >
- Scan Occupant
-
-
-
- {numberofpods ? (
- pods.map((pod, i) => {
- let podAction;
- if (pod.status === 'cloning') {
- podAction = (
-
- {round(pod.progress, 0) + '%'}
-
- );
- } else if (pod.status === 'mess') {
- podAction = (
-
- ERROR
-
- );
- } else {
- podAction = (
-
- act('selectpod', {
- ref: pod.pod,
- })
- }
- >
- Select
-
- );
- }
-
- return (
-
-
- Pod #{i + 1}
- = 150 ? 'good' : 'bad'} inline>
- = 150 ? 'circle' : 'circle-o'} />
-
- {pod.biomass}
-
- {podAction}
-
- );
- })
- ) : (
- No pods detected. Unable to clone.
- )}
-
- >
- );
-};
-
-const CloningConsoleRecords = (props) => {
- const { act, data } = useBackend();
- const { records } = data;
- if (!records.length) {
- return (
-
-
-
-
- No records found.
-
-
- );
- }
- return (
-
- {records.map((record, i) => (
-
- act('view_rec', {
- ref: record.record,
- })
- }
- >
- {record.realname}
-
- ))}
-
- );
-};
-
-const CloningConsoleTemp = (props) => {
- const { act, data } = useBackend();
- const { temp } = data;
- if (!temp || !temp.text || temp.text.length <= 0) {
- return;
- }
-
- const tempProp = { [temp.style]: true };
- return (
-
-
- {temp.text}
-
- act('cleartemp')}
- />
-
-
- );
-};
-
-const CloningConsoleStatus = (props) => {
- const { act, data } = useBackend();
- const { scanner, numberofpods, autoallowed, autoprocess, disk } = data;
- return (
-
- {!!autoallowed && (
- <>
-
- Auto-processing:
-
-
- act('autoprocess', {
- on: autoprocess ? 0 : 1,
- })
- }
- >
- {autoprocess ? 'Enabled' : 'Disabled'}
-
- >
- )}
-
- act('disk', {
- option: 'eject',
- })
- }
- >
- Eject Disk
-
- >
- }
- >
-
-
- {scanner ? (
- Connected
- ) : (
- Not connected!
- )}
-
-
- {numberofpods ? (
- {numberofpods} connected
- ) : (
- None connected!
- )}
-
-
-
- );
-};
diff --git a/tgui/packages/tgui/interfaces/CloningConsole/CloningConsoleBodyOverride.tsx b/tgui/packages/tgui/interfaces/CloningConsole/CloningConsoleBodyOverride.tsx
new file mode 100644
index 00000000000..fb6a6491523
--- /dev/null
+++ b/tgui/packages/tgui/interfaces/CloningConsole/CloningConsoleBodyOverride.tsx
@@ -0,0 +1,115 @@
+import { useBackend } from '../../backend';
+import { Box, Button, LabeledList, Section } from '../../components';
+import { COLORS } from '../../constants';
+import { Data, modalData } from './types';
+
+export const viewRecordModalBodyOverride = (modal: modalData) => {
+ const { act, data } = useBackend();
+
+ const { disk, podready } = data;
+
+ const { activerecord, realname, health, unidentity, strucenzymes } =
+ modal.args;
+ const damages = health.split(' - ');
+ return (
+
+
+ {realname}
+
+ {damages.length > 1 ? (
+ <>
+
+ {damages[0]}
+
+ |
+
+ {damages[2]}
+
+ |
+
+ {damages[3]}
+
+ |
+
+ {damages[1]}
+
+ >
+ ) : (
+ Unknown
+ )}
+
+
+ {unidentity}
+
+
+ {strucenzymes}
+
+
+
+ act('disk', {
+ option: 'load',
+ })
+ }
+ >
+ Import
+
+
+ act('disk', {
+ option: 'save',
+ savetype: 'ui',
+ })
+ }
+ >
+ Export UI
+
+
+ act('disk', {
+ option: 'save',
+ savetype: 'ue',
+ })
+ }
+ >
+ Export UI and UE
+
+
+ act('disk', {
+ option: 'save',
+ savetype: 'se',
+ })
+ }
+ >
+ Export SE
+
+
+
+
+ act('clone', {
+ ref: activerecord,
+ })
+ }
+ >
+ Clone
+
+ act('del_rec')}>
+ Delete
+
+
+
+
+ );
+};
diff --git a/tgui/packages/tgui/interfaces/CloningConsole/CloningConsoleNavigation.tsx b/tgui/packages/tgui/interfaces/CloningConsole/CloningConsoleNavigation.tsx
new file mode 100644
index 00000000000..d06866ca50f
--- /dev/null
+++ b/tgui/packages/tgui/interfaces/CloningConsole/CloningConsoleNavigation.tsx
@@ -0,0 +1,34 @@
+import { useBackend } from '../../backend';
+import { Tabs } from '../../components';
+import { Data } from './types';
+
+export const CloningConsoleNavigation = (props) => {
+ const { act, data } = useBackend();
+ const { menu } = data;
+ return (
+
+
+ act('menu', {
+ num: 1,
+ })
+ }
+ >
+ Main
+
+
+ act('menu', {
+ num: 2,
+ })
+ }
+ >
+ Records
+
+
+ );
+};
diff --git a/tgui/packages/tgui/interfaces/CloningConsole/CloningConsoleStatus.tsx b/tgui/packages/tgui/interfaces/CloningConsole/CloningConsoleStatus.tsx
new file mode 100644
index 00000000000..4034d3fed63
--- /dev/null
+++ b/tgui/packages/tgui/interfaces/CloningConsole/CloningConsoleStatus.tsx
@@ -0,0 +1,92 @@
+import { useBackend } from '../../backend';
+import { Box, Button, LabeledList, NoticeBox, Section } from '../../components';
+import { Data } from './types';
+
+export const CloningConsoleTemp = (props) => {
+ const { act, data } = useBackend();
+ const { temp } = data;
+ if (!temp || !temp.text || temp.text.length <= 0) {
+ return;
+ }
+
+ const tempProp = { [temp.style]: true };
+ return (
+
+
+ {temp.text}
+
+ act('cleartemp')}
+ />
+
+
+ );
+};
+
+export const CloningConsoleStatus = (props) => {
+ const { act, data } = useBackend();
+ const { scanner, numberofpods, autoallowed, autoprocess, disk } = data;
+ return (
+
+ {!!autoallowed && (
+ <>
+
+ Auto-processing:
+
+
+ act('autoprocess', {
+ on: autoprocess ? 0 : 1,
+ })
+ }
+ >
+ {autoprocess ? 'Enabled' : 'Disabled'}
+
+ >
+ )}
+
+ act('disk', {
+ option: 'eject',
+ })
+ }
+ >
+ Eject Disk
+
+ >
+ }
+ >
+
+
+ {scanner ? (
+ Connected
+ ) : (
+ Not connected!
+ )}
+
+
+ {numberofpods ? (
+ {numberofpods} connected
+ ) : (
+ None connected!
+ )}
+
+
+
+ );
+};
diff --git a/tgui/packages/tgui/interfaces/CloningConsole/CloningConsoleTabs.tsx b/tgui/packages/tgui/interfaces/CloningConsole/CloningConsoleTabs.tsx
new file mode 100644
index 00000000000..4399d2a3b53
--- /dev/null
+++ b/tgui/packages/tgui/interfaces/CloningConsole/CloningConsoleTabs.tsx
@@ -0,0 +1,192 @@
+import { toFixed } from 'common/math';
+
+import { resolveAsset } from '../../assets';
+import { useBackend } from '../../backend';
+import {
+ Box,
+ Button,
+ Flex,
+ Icon,
+ Image,
+ LabeledList,
+ ProgressBar,
+ Section,
+} from '../../components';
+import { Data } from './types';
+
+export const CloningConsoleMain = (props) => {
+ const { act, data } = useBackend();
+ const {
+ loading,
+ scantemp,
+ occupant,
+ locked,
+ can_brainscan,
+ scan_mode,
+ numberofpods,
+ pods,
+ selected_pod,
+ } = data;
+ const isLocked = locked && !!occupant;
+ return (
+ <>
+
+
+ Scanner Lock:
+
+ act('lock')}
+ >
+ {isLocked ? 'Engaged' : 'Disengaged'}
+
+ act('eject')}
+ >
+ Eject Occupant
+
+ >
+ }
+ >
+
+
+ {loading ? (
+
+
+ Scanning...
+
+ ) : (
+ {scantemp.text}
+ )}
+
+ {!!can_brainscan && (
+
+ act('toggle_mode')}
+ >
+ {scan_mode ? 'Brain' : 'Body'}
+
+
+ )}
+
+ act('scan')}
+ >
+ Scan Occupant
+
+
+
+ {numberofpods ? (
+ pods.map((pod, i) => {
+ let podAction;
+ if (pod.status === 'cloning') {
+ podAction = (
+
+ {toFixed(pod.progress) + '%'}
+
+ );
+ } else if (pod.status === 'mess') {
+ podAction = (
+
+ ERROR
+
+ );
+ } else {
+ podAction = (
+
+ act('selectpod', {
+ ref: pod.pod,
+ })
+ }
+ >
+ Select
+
+ );
+ }
+
+ return (
+
+
+ Pod #{i + 1}
+ = 150 ? 'good' : 'bad'} inline>
+ = 150 ? 'circle' : 'circle-o'} />
+
+ {pod.biomass}
+
+ {podAction}
+
+ );
+ })
+ ) : (
+ No pods detected. Unable to clone.
+ )}
+
+ >
+ );
+};
+
+export const CloningConsoleRecords = (props) => {
+ const { act, data } = useBackend();
+ const { records } = data;
+ if (!records.length) {
+ return (
+
+
+
+
+ No records found.
+
+
+ );
+ }
+ return (
+
+ {records.map((record, i) => (
+
+ act('view_rec', {
+ ref: record.record,
+ })
+ }
+ >
+ {record.realname}
+
+ ))}
+
+ );
+};
diff --git a/tgui/packages/tgui/interfaces/CloningConsole/index.tsx b/tgui/packages/tgui/interfaces/CloningConsole/index.tsx
new file mode 100644
index 00000000000..7957732fd7d
--- /dev/null
+++ b/tgui/packages/tgui/interfaces/CloningConsole/index.tsx
@@ -0,0 +1,44 @@
+import { useBackend } from '../../backend';
+import { Box, Section } from '../../components';
+import {
+ ComplexModal,
+ modalRegisterBodyOverride,
+} from '../../interfaces/common/ComplexModal';
+import { Window } from '../../layouts';
+import { viewRecordModalBodyOverride } from './CloningConsoleBodyOverride';
+import { CloningConsoleNavigation } from './CloningConsoleNavigation';
+import {
+ CloningConsoleStatus,
+ CloningConsoleTemp,
+} from './CloningConsoleStatus';
+import {
+ CloningConsoleMain,
+ CloningConsoleRecords,
+} from './CloningConsoleTabs';
+import { Data } from './types';
+
+export const CloningConsole = (props) => {
+ const { data } = useBackend();
+
+ const { menu } = data;
+
+ const tab: React.JSX.Element[] = [];
+
+ tab[1] = ;
+ tab[2] = ;
+
+ modalRegisterBodyOverride('view_rec', viewRecordModalBodyOverride);
+ return (
+
+
+
+
+
+
+
+
+
+ );
+};
diff --git a/tgui/packages/tgui/interfaces/CloningConsole/types.ts b/tgui/packages/tgui/interfaces/CloningConsole/types.ts
new file mode 100644
index 00000000000..5621fd1055c
--- /dev/null
+++ b/tgui/packages/tgui/interfaces/CloningConsole/types.ts
@@ -0,0 +1,41 @@
+import { BooleanLike } from 'common/react';
+
+export type Data = {
+ menu: number;
+ scanner: string;
+ numberofpods: number | null;
+ pods: {
+ pod: string;
+ name: string;
+ biomass: number;
+ status: string;
+ progress: number;
+ }[];
+ loading: BooleanLike;
+ autoprocess: BooleanLike;
+ can_brainscan: BooleanLike;
+ scan_mode: BooleanLike;
+ autoallowed: BooleanLike;
+ occupant: string;
+ locked: BooleanLike;
+ temp: { text: string; style: string };
+ scantemp: { text: string; color: string };
+ disk: string | null;
+ selected_pod: string;
+ records: { record: string; realname: string }[];
+ podready: BooleanLike;
+ modal: modalData;
+};
+
+export type modalData = {
+ id: string;
+ text: string;
+ args: {
+ activerecord: string;
+ realname: string;
+ health: string;
+ unidentity: string;
+ strucenzymes: string;
+ };
+ modal_type: string;
+};
diff --git a/tgui/packages/tgui/interfaces/ColorMate.jsx b/tgui/packages/tgui/interfaces/ColorMate.jsx
deleted file mode 100644
index dab5b5dd378..00000000000
--- a/tgui/packages/tgui/interfaces/ColorMate.jsx
+++ /dev/null
@@ -1,401 +0,0 @@
-import { useBackend } from '../backend';
-import {
- Button,
- Icon,
- NoticeBox,
- NumberInput,
- Section,
- Slider,
- Table,
- Tabs,
-} from '../components';
-import { Window } from '../layouts';
-
-export const ColorMate = (props, context) => {
- const { act, data } = useBackend(context);
- const { activemode, temp } = data;
- const item = data.item || [];
- return (
-
-
-
- {temp ? {temp} : null}
- {Object.keys(item).length ? (
- <>
-
-
-
- Item:
-
-
-
-
-
- Preview:
-
-
-
-
-
-
- act('switch_modes', {
- mode: 1,
- })
- }
- >
- Tint coloring (Simple)
-
-
- act('switch_modes', {
- mode: 2,
- })
- }
- >
- HSV coloring (Normal)
-
-
- act('switch_modes', {
- mode: 3,
- })
- }
- >
- Matrix coloring (Advanced)
-
-
- Coloring: {item.name}
-
-
- act('paint')}>
- Paint
-
- act('clear')}>
- Clear
-
- act('drop')}>
- Eject
-
-
-
- {activemode === 1 ? (
-
- ) : activemode === 2 ? (
-
- ) : (
-
- )}
-
-
- >
- ) : (
- No item inserted.
- )}
-
-
-
- );
-};
-
-export const ColorMateTint = (props, context) => {
- const { act, data } = useBackend(context);
- return (
- act('choose_color')}>
- Select new color
-
- );
-};
-
-export const ColorMateMatrix = (props, context) => {
- const { act, data } = useBackend(context);
- const matrixcolors = data.matrixcolors || [];
- return (
-
-
-
- RR:{' '}
-
- act('set_matrix_color', {
- color: 1,
- value,
- })
- }
- />
-
-
- GR:{' '}
-
- act('set_matrix_color', {
- color: 4,
- value,
- })
- }
- />
-
-
- BR:{' '}
-
- act('set_matrix_color', {
- color: 7,
- value,
- })
- }
- />
-
-
-
-
- RG:{' '}
-
- act('set_matrix_color', {
- color: 2,
- value,
- })
- }
- />
-
-
- GG:{' '}
-
- act('set_matrix_color', {
- color: 5,
- value,
- })
- }
- />
-
-
- BG:{' '}
-
- act('set_matrix_color', {
- color: 8,
- value,
- })
- }
- />
-
-
-
-
- RB:{' '}
-
- act('set_matrix_color', {
- color: 3,
- value,
- })
- }
- />
-
-
- GB:{' '}
-
- act('set_matrix_color', {
- color: 6,
- value,
- })
- }
- />
-
-
- BB:{' '}
-
- act('set_matrix_color', {
- color: 9,
- value,
- })
- }
- />
-
-
-
-
- CR:{' '}
-
- act('set_matrix_color', {
- color: 10,
- value,
- })
- }
- />
-
-
- CG:{' '}
-
- act('set_matrix_color', {
- color: 11,
- value,
- })
- }
- />
-
-
- CB:{' '}
-
- act('set_matrix_color', {
- color: 12,
- value,
- })
- }
- />
-
-
-
- RG means red will become
- this much green.
-
- CR means this much red will
- be added.
-
-
- );
-};
-
-export const ColorMateHSV = (props, context) => {
- const { act, data } = useBackend(context);
- const { buildhue, buildsat, buildval } = data;
- return (
-
-
- Hue:
-
-
- act('set_hue', {
- buildhue: value,
- })
- }
- />
-
-
-
- Saturation:
-
-
- act('set_sat', {
- buildsat: value,
- })
- }
- />
-
-
-
- Value:
-
-
- act('set_val', {
- buildval: value,
- })
- }
- />
-
-
-
- );
-};
diff --git a/tgui/packages/tgui/interfaces/ColorMate/ColorMateColor.tsx b/tgui/packages/tgui/interfaces/ColorMate/ColorMateColor.tsx
new file mode 100644
index 00000000000..a5efd4024ae
--- /dev/null
+++ b/tgui/packages/tgui/interfaces/ColorMate/ColorMateColor.tsx
@@ -0,0 +1,76 @@
+import { toFixed } from 'common/math';
+
+import { useBackend } from '../../backend';
+import { Button, Slider, Table } from '../../components';
+import { Data } from './types';
+
+export const ColorMateTint = (props) => {
+ const { act } = useBackend();
+
+ return (
+ act('choose_color')}>
+ Select new color
+
+ );
+};
+
+export const ColorMateHSV = (props) => {
+ const { act, data } = useBackend();
+
+ const { buildhue, buildsat, buildval } = data;
+ return (
+
+
+ Hue:
+
+ toFixed(value)}
+ onDrag={(e, value: number) =>
+ act('set_hue', {
+ buildhue: value,
+ })
+ }
+ />
+
+
+
+ Saturation:
+
+ toFixed(value, 2)}
+ onDrag={(e, value: number) =>
+ act('set_sat', {
+ buildsat: value,
+ })
+ }
+ />
+
+
+
+ Value:
+
+ toFixed(value, 2)}
+ onDrag={(e, value: number) =>
+ act('set_val', {
+ buildval: value,
+ })
+ }
+ />
+
+
+
+ );
+};
diff --git a/tgui/packages/tgui/interfaces/ColorMate/ColorMateMatrix.tsx b/tgui/packages/tgui/interfaces/ColorMate/ColorMateMatrix.tsx
new file mode 100644
index 00000000000..c9c0c5fc0d1
--- /dev/null
+++ b/tgui/packages/tgui/interfaces/ColorMate/ColorMateMatrix.tsx
@@ -0,0 +1,235 @@
+import { toFixed } from 'common/math';
+
+import { useBackend } from '../../backend';
+import { Icon, NumberInput, Table } from '../../components';
+import { Data } from './types';
+
+export const ColorMateMatrix = (props) => {
+ const { act, data } = useBackend();
+
+ const { matrixcolors } = data;
+
+ return (
+
+
+
+ RR:
+ toFixed(value, 2)}
+ onChange={(e, value: number) =>
+ act('set_matrix_color', {
+ color: 1,
+ value,
+ })
+ }
+ />
+
+
+ GR:
+ toFixed(value, 2)}
+ onChange={(e, value: number) =>
+ act('set_matrix_color', {
+ color: 4,
+ value,
+ })
+ }
+ />
+
+
+ BR:
+ toFixed(value, 2)}
+ onChange={(e, value: number) =>
+ act('set_matrix_color', {
+ color: 7,
+ value,
+ })
+ }
+ />
+
+
+
+
+ RG:
+ toFixed(value, 2)}
+ onChange={(e, value: number) =>
+ act('set_matrix_color', {
+ color: 2,
+ value,
+ })
+ }
+ />
+
+
+ GG:
+ toFixed(value, 2)}
+ onChange={(e, value: number) =>
+ act('set_matrix_color', {
+ color: 5,
+ value,
+ })
+ }
+ />
+
+
+ BG:
+ toFixed(value, 2)}
+ onChange={(e, value: number) =>
+ act('set_matrix_color', {
+ color: 8,
+ value,
+ })
+ }
+ />
+
+
+
+
+ RB:
+ toFixed(value, 2)}
+ onChange={(e, value: number) =>
+ act('set_matrix_color', {
+ color: 3,
+ value,
+ })
+ }
+ />
+
+
+ GB:
+ toFixed(value, 2)}
+ onChange={(e, value: number) =>
+ act('set_matrix_color', {
+ color: 6,
+ value,
+ })
+ }
+ />
+
+
+ BB:
+ toFixed(value, 2)}
+ onChange={(e, value: number) =>
+ act('set_matrix_color', {
+ color: 9,
+ value,
+ })
+ }
+ />
+
+
+
+
+ CR:
+ toFixed(value, 2)}
+ onChange={(e, value: number) =>
+ act('set_matrix_color', {
+ color: 10,
+ value,
+ })
+ }
+ />
+
+
+ CG:
+ toFixed(value, 2)}
+ onChange={(e, value: number) =>
+ act('set_matrix_color', {
+ color: 11,
+ value,
+ })
+ }
+ />
+
+
+ CB:
+ toFixed(value, 2)}
+ onChange={(e, value: number) =>
+ act('set_matrix_color', {
+ color: 12,
+ value,
+ })
+ }
+ />
+
+
+
+ RG means red will become
+ this much green.
+
+ CR means this much red will
+ be added.
+
+
+ );
+};
diff --git a/tgui/packages/tgui/interfaces/ColorMate/index.tsx b/tgui/packages/tgui/interfaces/ColorMate/index.tsx
new file mode 100644
index 00000000000..8bbe62e7052
--- /dev/null
+++ b/tgui/packages/tgui/interfaces/ColorMate/index.tsx
@@ -0,0 +1,120 @@
+import { useBackend } from '../../backend';
+import {
+ Box,
+ Button,
+ Image,
+ NoticeBox,
+ Section,
+ Table,
+ Tabs,
+} from '../../components';
+import { Window } from '../../layouts';
+import { ColorMateHSV, ColorMateTint } from './ColorMateColor';
+import { ColorMateMatrix } from './ColorMateMatrix';
+import { Data } from './types';
+
+export const ColorMate = (props) => {
+ const { act, data } = useBackend();
+
+ const { activemode, temp, item } = data;
+
+ const tab: React.JSX.Element[] = [];
+
+ tab[1] = ;
+ tab[2] = ;
+ tab[3] = ;
+
+ return (
+
+
+
+ {temp ? {temp} : null}
+ {item && Object.keys(item).length ? (
+ <>
+
+
+
+ act('switch_modes', {
+ mode: 1,
+ })
+ }
+ >
+ Tint coloring (Simple)
+
+
+ act('switch_modes', {
+ mode: 2,
+ })
+ }
+ >
+ HSV coloring (Normal)
+
+
+ act('switch_modes', {
+ mode: 3,
+ })
+ }
+ >
+ Matrix coloring (Advanced)
+
+
+ Coloring: {item.name}
+
+
+ act('paint')}>
+ Paint
+
+ act('clear')}>
+ Clear
+
+ act('drop')}>
+ Eject
+
+
+
+ {tab[activemode] || Error}
+
+
+ >
+ ) : (
+ No item inserted.
+ )}
+
+
+
+ );
+};
diff --git a/tgui/packages/tgui/interfaces/ColorMate/types.ts b/tgui/packages/tgui/interfaces/ColorMate/types.ts
new file mode 100644
index 00000000000..a56bab9c7b1
--- /dev/null
+++ b/tgui/packages/tgui/interfaces/ColorMate/types.ts
@@ -0,0 +1,22 @@
+export type Data = {
+ activemode: number;
+ matrixcolors: {
+ rr: number;
+ rg: number;
+ rb: number;
+ gr: number;
+ gg: number;
+ gb: number;
+ br: number;
+ bg: number;
+ bb: number;
+ cr: number;
+ cg: number;
+ cb: number;
+ };
+ buildhue: number;
+ buildsat: number;
+ buildval: number;
+ temp: string | null;
+ item: { name: string; sprite: string; preview: string } | null;
+};
diff --git a/tgui/packages/tgui/interfaces/CommunicationsConsole.jsx b/tgui/packages/tgui/interfaces/CommunicationsConsole.jsx
deleted file mode 100644
index 857364e55d1..00000000000
--- a/tgui/packages/tgui/interfaces/CommunicationsConsole.jsx
+++ /dev/null
@@ -1,364 +0,0 @@
-import { useBackend } from '../backend';
-import { Box, Button, LabeledList, Section } from '../components';
-import { Window } from '../layouts';
-
-export const CommunicationsConsole = (props) => {
- return (
-
-
-
-
-
- );
-};
-
-export const CommunicationsConsoleContent = (props) => {
- const { act, data } = useBackend();
-
- const { menu_state } = data;
-
- let mainTemplate = (
-
- ERRROR. Unknown menu_state: {menu_state}
- Please report this to NT Technical Support.
-
- );
-
- // 1 = main screen
- if (menu_state === 1) {
- mainTemplate = ;
- } else if (menu_state === 2) {
- // 2 = status screen
- mainTemplate = ;
- } else if (menu_state === 3) {
- // 3 = messages screen
- mainTemplate = ;
- }
-
- return (
- <>
-
- {mainTemplate}
- >
- );
-};
-
-const CommunicationsConsoleMain = (props) => {
- const { act, data } = useBackend();
-
- const {
- messages,
- msg_cooldown,
- emagged,
- cc_cooldown,
- str_security_level,
- levels,
- authmax,
- security_level,
- security_level_color,
- authenticated,
- atcsquelch,
- boss_short,
- } = data;
-
- let reportText = 'View (' + messages.length + ')';
- let announceText = 'Make Priority Announcement';
- if (msg_cooldown > 0) {
- announceText += ' (' + msg_cooldown + 's)';
- }
- let ccMessageText = emagged ? 'Message [UNKNOWN]' : 'Message ' + boss_short;
- if (cc_cooldown > 0) {
- ccMessageText += ' (' + cc_cooldown + 's)';
- }
-
- let alertLevelText = str_security_level;
- let alertLevelButtons = levels.map((slevel) => {
- return (
- act('newalertlevel', { level: slevel.id })}
- >
- {slevel.name}
-
- );
- });
-
- return (
- <>
-
-
-
- 0}
- onClick={() => act('announce')}
- >
- {announceText}
-
-
- {(!!emagged && (
-
- 0}
- onClick={() => act('MessageSyndicate')}
- >
- {ccMessageText}
-
- act('RestoreBackup')}
- >
- Reset Relays
-
-
- )) || (
-
- 0}
- onClick={() => act('MessageCentCom')}
- >
- {ccMessageText}
-
-
- )}
-
-
-
-
-
- {alertLevelText}
-
-
- {alertLevelButtons}
-
-
- act('status')}
- >
- Change Status Displays
-
-
-
- act('messagelist')}
- >
- {reportText}
-
-
-
- act('toggleatc')}
- >
- {!atcsquelch ? 'ATC Relay Enabled' : 'ATC Relay Disabled'}
-
-
-
-
- >
- );
-};
-
-const CommunicationsConsoleAuth = (props) => {
- const { act, data } = useBackend();
-
- const { authenticated, is_ai, esc_status, esc_callable, esc_recallable } =
- data;
-
- let authReadable;
- if (!authenticated) {
- authReadable = 'Not Logged In';
- } else if (is_ai) {
- authReadable = 'AI';
- } else if (authenticated === 1) {
- authReadable = 'Command';
- } else if (authenticated === 2) {
- authReadable = 'Site Director';
- } else {
- authReadable = 'ERROR: Report This Bug!';
- }
-
- return (
- <>
-
-
- {(is_ai && (
- AI
- )) || (
-
- act('auth')}
- >
- {authenticated ? 'Log Out (' + authReadable + ')' : 'Log In'}
-
-
- )}
-
-
-
-
- {!!esc_status && (
- {esc_status}
- )}
- {!!esc_callable && (
-
- act('callshuttle')}
- >
- Call Shuttle
-
-
- )}
- {!!esc_recallable && (
-
- act('cancelshuttle')}
- >
- Recall Shuttle
-
-
- )}
-
-
- >
- );
-};
-
-const CommunicationsConsoleMessage = (props) => {
- const { act, data } = useBackend();
-
- const { message_current, message_deletion_allowed, authenticated, messages } =
- data;
-
- if (message_current) {
- return (
- act('messagelist')}
- >
- Return To Message List
-
- }
- >
- {message_current.contents}
-
- );
- }
-
- let messageRows = messages.map((m) => {
- return (
-
- act('messagelist', { msgid: m.id })}
- >
- View
-
- act('delmessage', { msgid: m.id })}
- >
- Delete
-
-
- );
- });
-
- return (
- act('main')}>
- Back To Main Menu
-
- }
- >
-
- {(messages.length && messageRows) || (
-
- No messages.
-
- )}
-
-
- );
-};
-
-const CommunicationsConsoleStatusDisplay = (props) => {
- const { act, data } = useBackend();
-
- const { stat_display, authenticated } = data;
-
- let presetButtons = stat_display['presets'].map((pb) => {
- return (
- act('setstat', { statdisp: pb.name })}
- >
- {pb.label}
-
- );
- });
- return (
- act('main')}>
- Back To Main Menu
-
- }
- >
-
- {presetButtons}
-
- act('setmsg1')}
- >
- {stat_display.line_1}
-
-
-
- act('setmsg2')}
- >
- {stat_display.line_2}
-
-
-
-
- );
-};
diff --git a/tgui/packages/tgui/interfaces/CommunicationsConsole/CommunicationsConsoleAuth.tsx b/tgui/packages/tgui/interfaces/CommunicationsConsole/CommunicationsConsoleAuth.tsx
new file mode 100644
index 00000000000..24b814ebee5
--- /dev/null
+++ b/tgui/packages/tgui/interfaces/CommunicationsConsole/CommunicationsConsoleAuth.tsx
@@ -0,0 +1,74 @@
+import { useBackend } from '../../backend';
+import { Button, LabeledList, Section } from '../../components';
+import { Data } from './types';
+
+export const CommunicationsConsoleAuth = (props) => {
+ const { act, data } = useBackend();
+
+ const { authenticated, is_ai, esc_status, esc_callable, esc_recallable } =
+ data;
+
+ let authReadable;
+ if (!authenticated) {
+ authReadable = 'Not Logged In';
+ } else if (is_ai) {
+ authReadable = 'AI';
+ } else if (authenticated === 1) {
+ authReadable = 'Command';
+ } else if (authenticated === 2) {
+ authReadable = 'Site Director';
+ } else {
+ authReadable = 'ERROR: Report This Bug!';
+ }
+
+ return (
+ <>
+
+
+ {(is_ai && (
+ AI
+ )) || (
+
+ act('auth')}
+ >
+ {authenticated ? 'Log Out (' + authReadable + ')' : 'Log In'}
+
+
+ )}
+
+
+
+
+ {!!esc_status && (
+ {esc_status}
+ )}
+ {!!esc_callable && (
+
+ act('callshuttle')}
+ >
+ Call Shuttle
+
+
+ )}
+ {!!esc_recallable && (
+
+ act('cancelshuttle')}
+ >
+ Recall Shuttle
+
+
+ )}
+
+
+ >
+ );
+};
diff --git a/tgui/packages/tgui/interfaces/CommunicationsConsole/CommunicationsConsoleContent.tsx b/tgui/packages/tgui/interfaces/CommunicationsConsole/CommunicationsConsoleContent.tsx
new file mode 100644
index 00000000000..57826b88f34
--- /dev/null
+++ b/tgui/packages/tgui/interfaces/CommunicationsConsole/CommunicationsConsoleContent.tsx
@@ -0,0 +1,37 @@
+import { useBackend } from '../../backend';
+import { Box } from '../../components';
+import { CommunicationsConsoleAuth } from './CommunicationsConsoleAuth';
+import { CommunicationsConsoleMain } from './CommunicationsConsoleMain';
+import { CommunicationsConsoleMessage } from './CommunicationsConsoleMessage';
+import { CommunicationsConsoleStatusDisplay } from './CommunicationsConsoleStatusDisplay';
+import { Data } from './types';
+
+export const CommunicationsConsoleContent = (props) => {
+ const { data } = useBackend();
+
+ const { menu_state } = data;
+
+ const tab: React.JSX.Element[] = [];
+
+ tab[1] = ;
+ tab[2] = ;
+ tab[3] = ;
+
+ return (
+ <>
+
+ {tab[menu_state] || }
+ >
+ );
+};
+
+const DefaultError = (props: { menu_state: string }) => {
+ const { menu_state } = props;
+
+ return (
+
+ ERRROR. Unknown menu_state: {menu_state}
+ Please report this to NT Technical Support.
+
+ );
+};
diff --git a/tgui/packages/tgui/interfaces/CommunicationsConsole/CommunicationsConsoleMain.tsx b/tgui/packages/tgui/interfaces/CommunicationsConsole/CommunicationsConsoleMain.tsx
new file mode 100644
index 00000000000..caa937b1fcc
--- /dev/null
+++ b/tgui/packages/tgui/interfaces/CommunicationsConsole/CommunicationsConsoleMain.tsx
@@ -0,0 +1,132 @@
+import { useBackend } from '../../backend';
+import { Button, LabeledList, Section } from '../../components';
+import { Data } from './types';
+
+export const CommunicationsConsoleMain = (props) => {
+ const { act, data } = useBackend();
+
+ const {
+ messages,
+ msg_cooldown,
+ emagged,
+ cc_cooldown,
+ str_security_level,
+ levels,
+ authmax,
+ security_level,
+ security_level_color,
+ authenticated,
+ atcsquelch,
+ boss_short,
+ } = data;
+
+ const reportText = 'View (' + messages.length + ')';
+ let announceText = 'Make Priority Announcement';
+ if (msg_cooldown > 0) {
+ announceText += ' (' + msg_cooldown + 's)';
+ }
+ let ccMessageText = emagged ? 'Message [UNKNOWN]' : 'Message ' + boss_short;
+ if (cc_cooldown > 0) {
+ ccMessageText += ' (' + cc_cooldown + 's)';
+ }
+
+ const alertLevelText = str_security_level;
+ const alertLevelButtons = levels.map((slevel) => {
+ return (
+ act('newalertlevel', { level: slevel.id })}
+ >
+ {slevel.name}
+
+ );
+ });
+
+ return (
+ <>
+
+
+
+ 0}
+ onClick={() => act('announce')}
+ >
+ {announceText}
+
+
+ {(!!emagged && (
+
+ 0}
+ onClick={() => act('MessageSyndicate')}
+ >
+ {ccMessageText}
+
+ act('RestoreBackup')}
+ >
+ Reset Relays
+
+
+ )) || (
+
+ 0}
+ onClick={() => act('MessageCentCom')}
+ >
+ {ccMessageText}
+
+
+ )}
+
+
+
+
+
+ {alertLevelText}
+
+
+ {alertLevelButtons}
+
+
+ act('status')}
+ >
+ Change Status Displays
+
+
+
+ act('messagelist')}
+ >
+ {reportText}
+
+
+
+ act('toggleatc')}
+ >
+ {!atcsquelch ? 'ATC Relay Enabled' : 'ATC Relay Disabled'}
+
+
+
+
+ >
+ );
+};
diff --git a/tgui/packages/tgui/interfaces/CommunicationsConsole/CommunicationsConsoleMessage.tsx b/tgui/packages/tgui/interfaces/CommunicationsConsole/CommunicationsConsoleMessage.tsx
new file mode 100644
index 00000000000..e70a61bae5f
--- /dev/null
+++ b/tgui/packages/tgui/interfaces/CommunicationsConsole/CommunicationsConsoleMessage.tsx
@@ -0,0 +1,69 @@
+import { useBackend } from '../../backend';
+import { Box, Button, LabeledList, Section } from '../../components';
+import { Data } from './types';
+
+export const CommunicationsConsoleMessage = (props) => {
+ const { act, data } = useBackend();
+
+ const { message_current, message_deletion_allowed, authenticated, messages } =
+ data;
+
+ if (message_current) {
+ return (
+ act('messagelist')}
+ >
+ Return To Message List
+
+ }
+ >
+ {message_current.contents}
+
+ );
+ }
+
+ let messageRows = messages.map((m) => {
+ return (
+
+ act('messagelist', { msgid: m.id })}
+ >
+ View
+
+ act('delmessage', { msgid: m.id })}
+ >
+ Delete
+
+
+ );
+ });
+
+ return (
+ act('main')}>
+ Back To Main Menu
+
+ }
+ >
+
+ {(messages.length && messageRows) || (
+
+ No messages.
+
+ )}
+
+
+ );
+};
diff --git a/tgui/packages/tgui/interfaces/CommunicationsConsole/CommunicationsConsoleStatusDisplay.tsx b/tgui/packages/tgui/interfaces/CommunicationsConsole/CommunicationsConsoleStatusDisplay.tsx
new file mode 100644
index 00000000000..6ea5a7c3197
--- /dev/null
+++ b/tgui/packages/tgui/interfaces/CommunicationsConsole/CommunicationsConsoleStatusDisplay.tsx
@@ -0,0 +1,54 @@
+import { useBackend } from '../../backend';
+import { Button, LabeledList, Section } from '../../components';
+import { Data } from './types';
+
+export const CommunicationsConsoleStatusDisplay = (props) => {
+ const { act, data } = useBackend();
+
+ const { stat_display, authenticated } = data;
+
+ let presetButtons = stat_display['presets'].map((pb) => {
+ return (
+ act('setstat', { statdisp: pb.name })}
+ >
+ {pb.label}
+
+ );
+ });
+ return (
+ act('main')}>
+ Back To Main Menu
+
+ }
+ >
+
+ {presetButtons}
+
+ act('setmsg1')}
+ >
+ {stat_display.line_1}
+
+
+
+ act('setmsg2')}
+ >
+ {stat_display.line_2}
+
+
+
+
+ );
+};
diff --git a/tgui/packages/tgui/interfaces/CommunicationsConsole/index.tsx b/tgui/packages/tgui/interfaces/CommunicationsConsole/index.tsx
new file mode 100644
index 00000000000..891947d2783
--- /dev/null
+++ b/tgui/packages/tgui/interfaces/CommunicationsConsole/index.tsx
@@ -0,0 +1,12 @@
+import { Window } from '../../layouts';
+import { CommunicationsConsoleContent } from './CommunicationsConsoleContent';
+
+export const CommunicationsConsole = (props) => {
+ return (
+
+
+
+
+
+ );
+};
diff --git a/tgui/packages/tgui/interfaces/CommunicationsConsole/types.ts b/tgui/packages/tgui/interfaces/CommunicationsConsole/types.ts
new file mode 100644
index 00000000000..3b3d1ac327a
--- /dev/null
+++ b/tgui/packages/tgui/interfaces/CommunicationsConsole/types.ts
@@ -0,0 +1,33 @@
+import { BooleanLike } from 'common/react';
+
+export type Data = {
+ is_ai: BooleanLike;
+ menu_state: string;
+ emagged: BooleanLike;
+ authenticated: BooleanLike;
+ authmax: BooleanLike;
+ atcsquelch: BooleanLike;
+ boss_short: string;
+ stat_display: {
+ type: string;
+ line_1: string;
+ line_2: string;
+ presets: { name: string; label: string; desc: string }[];
+ };
+ security_level: number;
+ security_level_color: string;
+ str_security_level: string;
+ levels: { id: number; name: string; icon: string }[];
+ messages: message[];
+ message_deletion_allowed: BooleanLike;
+ message_current_id: number;
+ message_current: message;
+ current_viewing_message: number;
+ msg_cooldown: number;
+ cc_cooldown: number;
+ esc_callable: BooleanLike;
+ esc_recallable: BooleanLike;
+ esc_status: BooleanLike | string;
+};
+
+type message = { id: string; title: String; contents: string };
diff --git a/tgui/packages/tgui/interfaces/Communicator.tsx b/tgui/packages/tgui/interfaces/Communicator.tsx
deleted file mode 100644
index 0bd2dd4d6bb..00000000000
--- a/tgui/packages/tgui/interfaces/Communicator.tsx
+++ /dev/null
@@ -1,1307 +0,0 @@
-import { filter } from 'common/collections';
-import { BooleanLike } from 'common/react';
-import { decodeHtmlEntities, toTitleCase } from 'common/string';
-import { useState } from 'react';
-
-import { useBackend } from '../backend';
-import {
- Box,
- Button,
- ByondUi,
- Flex,
- Icon,
- Input,
- LabeledList,
- Section,
- Table,
-} from '../components';
-import { Window } from '../layouts';
-import { CrewManifestContent } from './CrewManifest';
-
-const HOMETAB = 1;
-const PHONTAB = 2;
-const CONTTAB = 3;
-const MESSTAB = 4;
-const MESSSUBTAB = 40;
-const NEWSTAB = 5;
-const NOTETAB = 6;
-const WTHRTAB = 7;
-const MANITAB = 8;
-const SETTTAB = 9;
-
-let TABS = [
- HOMETAB,
- PHONTAB,
- CONTTAB,
- MESSTAB,
- MESSSUBTAB,
- NEWSTAB,
- NOTETAB,
- WTHRTAB,
- MANITAB,
- SETTTAB,
-];
-
-let TabToTemplate = {}; // Populated under each template
-
-type Data = {
- // GENERAL
- currentTab: number;
- video_comm: BooleanLike;
- mapRef: string;
-
- // FOOTER
- time: string;
- connectionStatus: BooleanLike;
- owner: string;
- occupation: string;
-
- // HEADER
- flashlight: BooleanLike;
-
- // HOMETAB
- homeScreen: { number: number; module: string; icon: string }[];
-
- // PHONETAB
- targetAddress: string;
- voice_mobs: { name: string; true_name: string; ref: string }[];
- communicating: {
- address: string;
- name: string;
- true_name: string;
- ref: string;
- }[];
- requestsReceived: { address: string; name: string; ref: string }[];
- invitesSent: { address: string; name: string }[];
- phone_video_comm: string;
- selfie_mode: BooleanLike;
-
- // MESSAGING
- imContacts: { address: string; name: string }[];
- targetAddressName: string;
- imList: { address: string; to_address: string; im: string }[];
-
- // SETTINGS
- address: string;
- visible: BooleanLike;
- ring: BooleanLike;
-
- // NEWSTAB
- feeds: { index: number; name: string }[];
- target_feed: { name: string; author: string; messages: NewsMessage[] };
- latest_news: NewsMessage[];
-
- // NOTETAB
- note: string;
-};
-
-type NewsMessage = {
- ref: string;
- body: string;
- img: string;
- caption: string;
- message_type: string;
- author: string;
- time_stamp: string;
-
- index: number;
- channel: string;
-};
-
-function notFound(val) {
- return TABS.includes(val);
-}
-
-export const Communicator = (props) => {
- const { act, data } = useBackend();
-
- const {
- currentTab,
- video_comm,
- owner,
- occupation,
- connectionStatus,
- address,
- visible,
- ring,
- selfie_mode,
- homeScreen,
- targetAddress,
- voice_mobs,
- phone_video_comm,
- communicating,
- requestsReceived,
- invitesSent,
- imContacts,
- targetAddressName,
- imList,
- feeds,
- target_feed,
- latest_news,
- note,
- } = data;
-
- const validCharacters = [
- '0',
- '1',
- '2',
- '3',
- '4',
- '5',
- '6',
- '7',
- '8',
- '9',
- 'A',
- 'B',
- 'C',
- 'D',
- 'E',
- 'F',
- ];
-
- let buttonArray = validCharacters.map((char) => (
- act('add_hex', { add_hex: char })}
- >
- {char}
-
- ));
-
- let finalArray: any[] = [];
-
- for (let i = 0; i < buttonArray.length; i += 4) {
- finalArray.push(
-
- {buttonArray[i]}
- {buttonArray[i + 1]}
- {buttonArray[i + 2]}
- {buttonArray[i + 3]}
- ,
- );
- }
- /* 0: Fullscreen Video
- * 1: Popup Video
- * 2: Minimized Video
- */
- const [videoSetting, setVideoSetting] = useState(0);
- const [clipboardMode, setClipboardMode] = useState(false);
-
- return (
-
-
- {video_comm && (
-
- )}
- {(!video_comm || videoSetting !== 0) && (
- <>
-
-
- {(currentTab === HOMETAB && (
-
- {homeScreen.map((app) => (
-
-
- act('switch_tab', { switch_tab: app.number })
- }
- >
-
-
- {app.module}
-
- ))}
-
- )) ||
- (currentTab === PHONTAB && (
-
-
-
-
-
-
- act('write_target_address', { val: val })
- }
- />
-
-
- act('clear_target_address')}
- />
-
-
-
-
-
-
-
-
-
- {/* Dial */}
-
-
- act('dial', { dial: targetAddress })
- }
- >
-
-
- Dial
-
- {/* Message */}
-
- {
- act('message', { message: targetAddress });
- act('switch_tab', { switch_tab: MESSTAB });
- }}
- >
-
-
- Message
-
- {/* Hang Up */}
-
- act('hang_up')}
- >
-
-
- Hang Up
-
-
-
-
-
-
-
- act('selfie_mode')}>
- {selfie_mode
- ? 'Front-facing Camera'
- : 'Rear-facing Camera'}
-
-
-
-
- {(!!voice_mobs.length && (
-
- {voice_mobs.map((mob) => (
-
-
- act('disconnect', {
- disconnect: mob.true_name,
- })
- }
- >
- Disconnect
-
-
- ))}
-
- )) || No connections}
-
-
- {(!!communicating.length && (
-
- {communicating.map((comm) => (
-
-
- {decodeHtmlEntities(comm.name)}
-
-
-
- act('disconnect', {
- disconnect: comm.true_name,
- })
- }
- >
- Disconnect
-
- {(video_comm === null && (
-
- act('startvideo', {
- startvideo: comm.ref,
- })
- }
- >
- Start Video
-
- )) ||
- (phone_video_comm === comm.ref && (
-
- act('endvideo', {
- endvideo: comm.true_name,
- })
- }
- >
- Stop Video
-
- ))}
-
-
- ))}
-
- )) || No connections}
-
-
- {(!!requestsReceived.length && (
-
- {requestsReceived.map((request) => (
-
- {decodeHtmlEntities(request.address)}
-
-
- act('dial', { dial: request.address })
- }
- >
- Accept
-
-
- act('decline', { decline: request.ref })
- }
- >
- Decline
-
-
-
- ))}
-
- )) || No requests received.}
-
-
- {(!!invitesSent.length && (
-
- {invitesSent.map((invite) => (
-
- {decodeHtmlEntities(invite.address)}
-
- {
- act('copy', { copy: invite.address });
- }}
- >
- Copy
-
-
-
- ))}
-
- )) || No invites sent.}
-
-
-
- )) ||
- (currentTab === CONTTAB && ) ||
- (currentTab === MESSTAB && (
-
- {(imContacts.length && (
-
- {imContacts.map((device) => (
-
-
- {decodeHtmlEntities(device.name)}:
-
-
- {device.address}
-
- {
- act('copy', { copy: device.address });
- act('copy_name', {
- copy_name: device.name,
- });
- act('switch_tab', {
- switch_tab: MESSSUBTAB,
- });
- }}
- >
- View Conversation
-
-
-
-
- ))}
-
- )) || (
-
- You haven't sent any messages yet.
-
- act('switch_tab', { switch_tab: CONTTAB })
- }
- >
- Contacts
-
-
- )}
-
- )) ||
- (currentTab === MESSSUBTAB &&
- (clipboardMode ? (
-
- {enforceLengthLimit(
- 'Conversation with ',
- decodeHtmlEntities(targetAddressName),
- 30,
- )}
-
- }
- buttons={
- setClipboardMode(!clipboardMode)}
- />
- }
- height="100%"
- stretchContents
- >
-
- {imList.map(
- (im, i) =>
- (im.to_address === targetAddress ||
- im.address === targetAddress) && (
-
- {IsIMOurs(im, targetAddress) ? 'You' : 'Them'}:{' '}
- {im.im}
-
- ),
- )}
-
-
- act('message', { message: targetAddress })
- }
- >
- Message
-
-
- ) : (
-
- {enforceLengthLimit(
- 'Conversation with ',
- decodeHtmlEntities(targetAddressName),
- 30,
- )}
-
- }
- buttons={
- setClipboardMode(!clipboardMode)}
- />
- }
- height="100%"
- stretchContents
- >
-
- {imList.map(
- (im, i, filterArr) =>
- (im.to_address === targetAddress ||
- im.address === targetAddress) && (
-
-
- {decodeHtmlEntities(im.im)}
-
-
- ),
- )}
-
-
- act('message', { message: targetAddress })
- }
- >
- Message
-
-
- ))) ||
- (currentTab === NEWSTAB && (
-
- {(!feeds.length && (
-
- Error: No newsfeeds available. Please try again later.
-
- )) ||
- (target_feed && (
-
- act('newsfeed', { newsfeed: null })
- }
- >
- Back
-
- }
- >
- {target_feed.messages.map((message) => (
-
- - {decodeHtmlEntities(message.body)}
- {!!message.img && (
-
-
- {decodeHtmlEntities(message.caption) || null}
-
- )}
-
- [{message.message_type} by{' '}
- {decodeHtmlEntities(message.author)} -{' '}
- {message.time_stamp}]
-
-
- ))}
-
- )) || (
- <>
-
-
- {latest_news.map((news) => (
-
-
- {decodeHtmlEntities(news.channel)}
-
- act('newsfeed', {
- newsfeed: news.index,
- })
- }
- >
- Go to
-
-
- - {decodeHtmlEntities(news.body)}
- {!!news.img && (
-
- [image omitted, view story for more
- details]
- {news.caption || null}
-
- )}
-
- [{news.message_type} by{' '}
-
- {news.author}
- {' '}
- - {news.time_stamp}]
-
-
- ))}
-
-
-
- {feeds.map((feed) => (
-
- act('newsfeed', { newsfeed: feed.index })
- }
- >
- {feed.name}
-
- ))}
-
- >
- )}
-
- )) ||
- (currentTab === NOTETAB && (
- act('edit')}>
- Edit Notes
-
- }
- >
-
-
- )) ||
- (currentTab === WTHRTAB && ) ||
- (currentTab === MANITAB && ) ||
- (currentTab === SETTTAB && (
-
-
-
- act('rename')}>
- {decodeHtmlEntities(owner)}
-
-
-
- act('selfie_mode')}>
- {selfie_mode
- ? 'Front-facing Camera'
- : 'Rear-facing Camera'}
-
-
-
- {decodeHtmlEntities(occupation)}
-
-
- {connectionStatus === 1 ? (
- Connected
- ) : (
- Disconnected
- )}
-
-
- {address}
-
-
- act('toggle_visibility')}
- >
- {visible
- ? 'This device can be seen by other devices.'
- : 'This device is invisible to other devices.'}
-
-
-
- act('toggle_ringer')}
- >
- {ring ? 'Ringer on.' : 'Ringer off.'}
-
- act('set_ringer_tone')}>
- Set Ringer Tone
-
-
-
-
- )) ||
- (notFound(currentTab) && )}
-
-
- >
- )}
-
-
- );
-};
-
-const VideoComm = (props) => {
- const { act, data } = useBackend();
-
- const { video_comm, mapRef } = data;
-
- const { videoSetting, setVideoSetting } = props;
-
- if (videoSetting === 0) {
- return (
-
-
-
-
- setVideoSetting(1)}
- />
-
-
- act('endvideo')}
- />
-
-
- act('hang_up')}
- />
-
-
-
- );
- } else if (videoSetting === 1) {
- return (
-
-
-
-
- setVideoSetting(2)}
- />
-
-
- setVideoSetting(0)}
- />
-
-
- act('endvideo')}
- />
-
-
- act('hang_up')}
- />
-
-
-
-
-
- );
- }
- return null;
-};
-
-const TemplateError = (props) => {
- const { act, data } = useBackend();
-
- const { currentTab } = data;
-
- return (
-
- You tried to access tab #{currentTab}, but there was no template defined!
-
- );
-};
-
-const CommunicatorHeader = (props) => {
- const { act, data } = useBackend();
-
- const { time, connectionStatus, owner, occupation } = data;
-
- return (
-
-
- {time}
-
-
-
- {decodeHtmlEntities(owner)}
- {decodeHtmlEntities(occupation)}
-
-
- );
-};
-
-const CommunicatorFooter = (props) => {
- const { act, data } = useBackend();
-
- const { flashlight } = data;
-
- const { videoSetting, setVideoSetting } = props;
-
- return (
-
-
- act('switch_tab', { switch_tab: HOMETAB })}
- />
-
-
- act('Light')}
- />
-
- {videoSetting === 2 && (
-
- setVideoSetting(1)}
- />
-
- )}
-
- );
-};
-
-/* Helper for notifications (yes this is a mess, but whatever, it works) */
-const hasNotifications = (app) => {
- const { data } = useBackend();
-
- const {
- /* Phone Notifications */
- voice_mobs,
- communicating,
- requestsReceived,
- invitesSent,
- video_comm,
- } = data;
-
- if (app === 'Phone') {
- if (
- voice_mobs.length ||
- communicating.length ||
- requestsReceived.length ||
- invitesSent.length ||
- video_comm
- ) {
- return true;
- }
- }
-
- return false;
-};
-
-type ContactsTabData = {
- knownDevices: { address: string; name: string }[];
-};
-
-/* Contacts */
-const ContactsTab = (props) => {
- const { act, data } = useBackend();
-
- const { knownDevices } = data;
-
- return (
-
- {(knownDevices.length && (
-
- {knownDevices.map((device) => (
-
-
- {decodeHtmlEntities(device.name)}
-
-
- {device.address}
-
- {
- act('copy', { copy: device.address });
- act('switch_tab', { switch_tab: PHONTAB });
- }}
- >
- Copy
-
- {
- act('dial', { dial: device.address });
- act('copy', { copy: device.address });
- act('switch_tab', { switch_tab: PHONTAB });
- }}
- >
- Call
-
- {
- act('copy', { copy: device.address });
- act('copy_name', { copy_name: device.name });
- act('switch_tab', { switch_tab: MESSSUBTAB });
- }}
- >
- Msg
-
-
-
-
- ))}
-
- )) || No devices detected on your local NTNet region.}
-
- );
-};
-/* Actual messaging conversation */
-const IsIMOurs = (im, targetAddress) => {
- return im.address !== targetAddress;
-};
-
-const enforceLengthLimit = (prefix: string, name: string, length: number) => {
- if ((prefix + name).length > length) {
- if (name.length > length) {
- return name.slice(0, length) + '...';
- }
- return name;
- }
- return prefix + name;
-};
-
-const findClassMessage = (im, targetAddress, lastIndex, filterArray) => {
- if (lastIndex < 0 || lastIndex > filterArray.length) {
- return IsIMOurs(im, targetAddress)
- ? 'TinderMessage_First_Sent'
- : 'TinderMessage_First_Received';
- }
-
- let thisSent = IsIMOurs(im, targetAddress);
- let lastSent = IsIMOurs(filterArray[lastIndex], targetAddress);
- if (thisSent && lastSent) {
- return 'TinderMessage_Subsequent_Sent';
- } else if (!thisSent && !lastSent) {
- return 'TinderMessage_Subsequent_Received';
- }
- return thisSent ? 'TinderMessage_First_Sent' : 'TinderMessage_First_Received';
-};
-
-/* Weather App */
-const getItemColor = (value, min2, min1, max1, max2) => {
- if (value < min2) {
- return 'bad';
- } else if (value < min1) {
- return 'average';
- } else if (value > max1) {
- return 'average';
- } else if (value > max2) {
- return 'bad';
- }
- return 'good';
-};
-
-type WeatherTabData = {
- aircontents: AirContent[];
- weather: Weather[];
-};
-
-type AirContent = {
- entry: string;
- val;
- bad_low: number;
- poor_low: number;
- poor_high: number;
- bad_high: number;
- units;
-};
-
-type Weather = {
- Planet: string;
- Time: string;
- Weather: string;
- Temperature;
- High;
- Low;
- WindDir;
- WindSpeed;
- Forecast: string;
-};
-
-const WeatherTab = (props) => {
- const { act, data } = useBackend();
-
- const { aircontents, weather } = data;
-
- let deg = '\u00B0';
-
- return (
-
-
-
- {filter(
- (i: AirContent) =>
- i.val !== '0' ||
- i.entry === 'Pressure' ||
- i.entry === 'Temperature',
- )(aircontents).map((item: AirContent) => (
-
- {item.val}
- {decodeHtmlEntities(item.units)}
-
- ))}
-
-
-
- {(!!weather.length && (
-
- {weather.map((wr) => (
-
-
- {wr.Time}
-
- {toTitleCase(wr.Weather)}
-
-
- Current: {wr.Temperature.toFixed()} {deg}C | High:{' '}
- {wr.High.toFixed()} {deg}C | Low: {wr.Low.toFixed()} {deg}C
-
-
- {wr.WindDir}
-
-
- {wr.WindSpeed}
-
-
- {decodeHtmlEntities(wr.Forecast)}
-
-
-
- ))}
-
- )) || (
-
- No weather reports available. Please check back later.
-
- )}
-
-
- );
-};
diff --git a/tgui/packages/tgui/interfaces/Communicator/CommunicatorContactTab.tsx b/tgui/packages/tgui/interfaces/Communicator/CommunicatorContactTab.tsx
new file mode 100644
index 00000000000..d5fb473ca60
--- /dev/null
+++ b/tgui/packages/tgui/interfaces/Communicator/CommunicatorContactTab.tsx
@@ -0,0 +1,67 @@
+import { decodeHtmlEntities } from 'common/string';
+
+import { useBackend } from '../../backend';
+import { Box, Button, Section, Table } from '../../components';
+import { MESSSUBTAB, PHONTAB } from './constants';
+import { ContactsTabData } from './types';
+
+export const CommunicatorContactTab = (props) => {
+ const { act, data } = useBackend();
+
+ const { knownDevices } = data;
+
+ return (
+
+ {(knownDevices.length && (
+
+ {knownDevices.map((device) => (
+
+
+ {decodeHtmlEntities(device.name)}
+
+
+ {device.address}
+
+ {
+ act('copy', { copy: device.address });
+ act('switch_tab', { switch_tab: PHONTAB });
+ }}
+ >
+ Copy
+
+ {
+ act('dial', { dial: device.address });
+ act('copy', { copy: device.address });
+ act('switch_tab', { switch_tab: PHONTAB });
+ }}
+ >
+ Call
+
+ {
+ act('copy', { copy: device.address });
+ act('copy_name', { copy_name: device.name });
+ act('switch_tab', { switch_tab: MESSSUBTAB });
+ }}
+ >
+ Msg
+
+
+
+
+ ))}
+
+ )) || No devices detected on your local NTNet region.}
+
+ );
+};
diff --git a/tgui/packages/tgui/interfaces/Communicator/CommunicatorGeneral.tsx b/tgui/packages/tgui/interfaces/Communicator/CommunicatorGeneral.tsx
new file mode 100644
index 00000000000..7a752a13f93
--- /dev/null
+++ b/tgui/packages/tgui/interfaces/Communicator/CommunicatorGeneral.tsx
@@ -0,0 +1,209 @@
+import { decodeHtmlEntities } from 'common/string';
+
+import { useBackend } from '../../backend';
+import { Box, Button, ByondUi, Flex, Icon, Section } from '../../components';
+import { HOMETAB } from './constants';
+import { Data } from './types';
+
+export const TemplateError = (props: { currentTab: number }) => {
+ return (
+
+ You tried to access tab #{props.currentTab}, but there was no template
+ defined!
+
+ );
+};
+export const CommunicatorHeader = (props) => {
+ const { act, data } = useBackend();
+
+ const { time, connectionStatus, owner, occupation } = data;
+
+ return (
+
+
+ {time}
+
+
+
+ {decodeHtmlEntities(owner)}
+ {decodeHtmlEntities(occupation)}
+
+
+ );
+};
+
+export const CommunicatorFooter = (props: {
+ videoSetting: number;
+ setVideoSetting: Function;
+}) => {
+ const { act, data } = useBackend();
+
+ const { flashlight } = data;
+
+ const { videoSetting, setVideoSetting } = props;
+
+ return (
+
+
+ act('switch_tab', { switch_tab: HOMETAB })}
+ />
+
+
+ act('Light')}
+ />
+
+ {videoSetting === 2 && (
+
+ setVideoSetting(1)}
+ />
+
+ )}
+
+ );
+};
+
+export const VideoComm = (props: {
+ videoSetting: number;
+ setVideoSetting: Function;
+}) => {
+ const { act, data } = useBackend();
+
+ const { mapRef } = data;
+
+ const { videoSetting, setVideoSetting } = props;
+
+ if (videoSetting === 0) {
+ return (
+
+
+
+
+ setVideoSetting(1)}
+ />
+
+
+ act('endvideo')}
+ />
+
+
+ act('hang_up')}
+ />
+
+
+
+ );
+ } else if (videoSetting === 1) {
+ return (
+
+
+
+
+ setVideoSetting(2)}
+ />
+
+
+ setVideoSetting(0)}
+ />
+
+
+ act('endvideo')}
+ />
+
+
+ act('hang_up')}
+ />
+
+
+
+
+
+ );
+ }
+ return null;
+};
diff --git a/tgui/packages/tgui/interfaces/Communicator/CommunicatorHomeTab.tsx b/tgui/packages/tgui/interfaces/Communicator/CommunicatorHomeTab.tsx
new file mode 100644
index 00000000000..9e8809cc77f
--- /dev/null
+++ b/tgui/packages/tgui/interfaces/Communicator/CommunicatorHomeTab.tsx
@@ -0,0 +1,67 @@
+import { useBackend } from '../../backend';
+import { Box, Button, Flex, Icon } from '../../components';
+import { Data } from './types';
+
+export const CommunicatorHomeTab = (props) => {
+ const { act, data } = useBackend();
+
+ const { homeScreen } = data;
+
+ return (
+
+ {homeScreen.map((app) => (
+
+ act('switch_tab', { switch_tab: app.number })}
+ >
+
+
+ {app.module}
+
+ ))}
+
+ );
+};
+
+/* Helper for notifications (yes this is a mess, but whatever, it works) */
+const hasNotifications = (app: string | null) => {
+ const { data } = useBackend();
+
+ const {
+ /* Phone Notifications */
+ voice_mobs,
+ communicating,
+ requestsReceived,
+ invitesSent,
+ video_comm,
+ } = data;
+
+ if (app === 'Phone') {
+ if (
+ voice_mobs.length ||
+ communicating.length ||
+ requestsReceived.length ||
+ invitesSent.length ||
+ video_comm
+ ) {
+ return true;
+ }
+ }
+
+ return false;
+};
diff --git a/tgui/packages/tgui/interfaces/Communicator/CommunicatorMessageSubTab.tsx b/tgui/packages/tgui/interfaces/Communicator/CommunicatorMessageSubTab.tsx
new file mode 100644
index 00000000000..5e15ccfb36b
--- /dev/null
+++ b/tgui/packages/tgui/interfaces/Communicator/CommunicatorMessageSubTab.tsx
@@ -0,0 +1,190 @@
+import { decodeHtmlEntities } from 'common/string';
+
+import { useBackend } from '../../backend';
+import { Box, Button, Section } from '../../components';
+import { Data } from './types';
+
+export const CommunicatorMessageSubTab = (props: {
+ clipboardMode: boolean;
+ onClipboardMode: Function;
+}) => {
+ const { act, data } = useBackend();
+
+ const { clipboardMode, onClipboardMode } = props;
+
+ const { targetAddress, targetAddressName, imList } = data;
+
+ return clipboardMode ? (
+
+ {enforceLengthLimit(
+ 'Conversation with ',
+ decodeHtmlEntities(targetAddressName),
+ 30,
+ )}
+
+ }
+ buttons={
+ onClipboardMode(!clipboardMode)}
+ />
+ }
+ height="100%"
+ stretchContents
+ >
+
+ {imList.map(
+ (im, i) =>
+ (im.to_address === targetAddress ||
+ im.address === targetAddress) && (
+
+ {IsIMOurs(im, targetAddress) ? 'You' : 'Them'}: {im.im}
+
+ ),
+ )}
+
+ act('message', { message: targetAddress })}
+ >
+ Message
+
+
+ ) : (
+
+ {enforceLengthLimit(
+ 'Conversation with ',
+ decodeHtmlEntities(targetAddressName),
+ 30,
+ )}
+
+ }
+ buttons={
+ onClipboardMode(!clipboardMode)}
+ />
+ }
+ height="100%"
+ stretchContents
+ >
+
+ {imList.map(
+ (im, i, filterArr) =>
+ (im.to_address === targetAddress ||
+ im.address === targetAddress) && (
+
+
+ {decodeHtmlEntities(im.im)}
+
+
+ ),
+ )}
+
+ act('message', { message: targetAddress })}
+ >
+ Message
+
+
+ );
+};
+
+/* Actual messaging conversation */
+const IsIMOurs = (
+ im: { address: string; to_address: string; im: string },
+ targetAddress: string,
+) => {
+ return im.address !== targetAddress;
+};
+
+const enforceLengthLimit = (prefix: string, name: string, length: number) => {
+ if ((prefix + name).length > length) {
+ if (name.length > length) {
+ return name.slice(0, length) + '...';
+ }
+ return name;
+ }
+ return prefix + name;
+};
+
+const findClassMessage = (
+ im: { address: string; to_address: string; im: string },
+ targetAddress: string,
+ lastIndex: number,
+ filterArray: {
+ address: string;
+ to_address: string;
+ im: string;
+ }[],
+) => {
+ if (lastIndex < 0 || lastIndex > filterArray.length) {
+ return IsIMOurs(im, targetAddress)
+ ? 'TinderMessage_First_Sent'
+ : 'TinderMessage_First_Received';
+ }
+
+ let thisSent = IsIMOurs(im, targetAddress);
+ let lastSent = IsIMOurs(filterArray[lastIndex], targetAddress);
+ if (thisSent && lastSent) {
+ return 'TinderMessage_Subsequent_Sent';
+ } else if (!thisSent && !lastSent) {
+ return 'TinderMessage_Subsequent_Received';
+ }
+ return thisSent ? 'TinderMessage_First_Sent' : 'TinderMessage_First_Received';
+};
diff --git a/tgui/packages/tgui/interfaces/Communicator/CommunicatorMessageTab.tsx b/tgui/packages/tgui/interfaces/Communicator/CommunicatorMessageTab.tsx
new file mode 100644
index 00000000000..6fe344719ae
--- /dev/null
+++ b/tgui/packages/tgui/interfaces/Communicator/CommunicatorMessageTab.tsx
@@ -0,0 +1,63 @@
+import { decodeHtmlEntities } from 'common/string';
+
+import { useBackend } from '../../backend';
+import { Box, Button, Section, Table } from '../../components';
+import { CONTTAB, MESSSUBTAB } from './constants';
+import { Data } from './types';
+
+export const CommunicatorMessageTab = (props) => {
+ const { act, data } = useBackend();
+
+ const { imContacts } = data;
+
+ return (
+
+ {(imContacts.length && (
+
+ {imContacts.map((device) => (
+
+
+ {decodeHtmlEntities(device.name)}:
+
+
+ {device.address}
+
+ {
+ act('copy', { copy: device.address });
+ act('copy_name', {
+ copy_name: device.name,
+ });
+ act('switch_tab', {
+ switch_tab: MESSSUBTAB,
+ });
+ }}
+ >
+ View Conversation
+
+
+
+
+ ))}
+
+ )) || (
+
+ You haven't sent any messages yet.
+ act('switch_tab', { switch_tab: CONTTAB })}
+ >
+ Contacts
+
+
+ )}
+
+ );
+};
diff --git a/tgui/packages/tgui/interfaces/Communicator/CommunicatorNewsTab.tsx b/tgui/packages/tgui/interfaces/Communicator/CommunicatorNewsTab.tsx
new file mode 100644
index 00000000000..5ed6049bfd9
--- /dev/null
+++ b/tgui/packages/tgui/interfaces/Communicator/CommunicatorNewsTab.tsx
@@ -0,0 +1,105 @@
+import { decodeHtmlEntities } from 'common/string';
+
+import { useBackend } from '../../backend';
+import { Box, Button, Section } from '../../components';
+import { Data } from './types';
+
+export const CommunicatorNewsTab = (props) => {
+ const { act, data } = useBackend();
+
+ const { feeds, target_feed, latest_news } = data;
+
+ return (
+
+ {(!feeds.length && (
+
+ Error: No newsfeeds available. Please try again later.
+
+ )) ||
+ (target_feed && (
+ act('newsfeed', { newsfeed: null })}
+ >
+ Back
+
+ }
+ >
+ {target_feed.messages.map((message) => (
+
+ - {decodeHtmlEntities(message.body)}
+ {!!message.img && (
+
+
+ {decodeHtmlEntities(message.caption) || null}
+
+ )}
+
+ [{message.message_type} by{' '}
+ {decodeHtmlEntities(message.author)} - {message.time_stamp}]
+
+
+ ))}
+
+ )) || (
+ <>
+
+
+ {latest_news.map((news) => (
+
+
+ {decodeHtmlEntities(news.channel)}
+
+ act('newsfeed', {
+ newsfeed: news.index,
+ })
+ }
+ >
+ Go to
+
+
+ - {decodeHtmlEntities(news.body)}
+ {!!news.img && (
+
+ [image omitted, view story for more details]
+ {news.caption || null}
+
+ )}
+
+ [{news.message_type} by{' '}
+
+ {news.author}
+ {' '}
+ - {news.time_stamp}]
+
+
+ ))}
+
+
+
+ {feeds.map((feed) => (
+ act('newsfeed', { newsfeed: feed.index })}
+ >
+ {feed.name}
+
+ ))}
+
+ >
+ )}
+
+ );
+};
diff --git a/tgui/packages/tgui/interfaces/Communicator/CommunicatorNoteTab.tsx b/tgui/packages/tgui/interfaces/Communicator/CommunicatorNoteTab.tsx
new file mode 100644
index 00000000000..35edfc6a61c
--- /dev/null
+++ b/tgui/packages/tgui/interfaces/Communicator/CommunicatorNoteTab.tsx
@@ -0,0 +1,34 @@
+import { useBackend } from '../../backend';
+import { Button, Section } from '../../components';
+import { Data } from './types';
+
+export const CommunicatorNoteTab = (props) => {
+ const { act, data } = useBackend();
+
+ const { note } = data;
+
+ return (
+ act('edit')}>
+ Edit Notes
+
+ }
+ >
+
+
+ );
+};
diff --git a/tgui/packages/tgui/interfaces/Communicator/CommunicatorPhoneTab.tsx b/tgui/packages/tgui/interfaces/Communicator/CommunicatorPhoneTab.tsx
new file mode 100644
index 00000000000..4e50303131b
--- /dev/null
+++ b/tgui/packages/tgui/interfaces/Communicator/CommunicatorPhoneTab.tsx
@@ -0,0 +1,328 @@
+import { decodeHtmlEntities } from 'common/string';
+
+import { useBackend } from '../../backend';
+import {
+ Box,
+ Button,
+ Flex,
+ Icon,
+ Input,
+ LabeledList,
+ Section,
+ Table,
+} from '../../components';
+import { MESSTAB } from './constants';
+import { Data } from './types';
+
+export const CommunicatorPhoneTab = (props) => {
+ const { act, data } = useBackend();
+
+ const { selfie_mode, targetAddress } = data;
+
+ const validCharacters = [
+ '0',
+ '1',
+ '2',
+ '3',
+ '4',
+ '5',
+ '6',
+ '7',
+ '8',
+ '9',
+ 'A',
+ 'B',
+ 'C',
+ 'D',
+ 'E',
+ 'F',
+ ];
+
+ const buttonArray = validCharacters.map((char) => (
+ act('add_hex', { add_hex: char })}
+ >
+ {char}
+
+ ));
+
+ let finalArray: React.JSX.Element[] = [];
+
+ for (let i = 0; i < buttonArray.length; i += 4) {
+ finalArray.push(
+
+ {buttonArray[i]}
+ {buttonArray[i + 1]}
+ {buttonArray[i + 2]}
+ {buttonArray[i + 3]}
+ ,
+ );
+ }
+
+ return (
+
+ );
+};
+
+const CommunicatorPhoneTabExternal = (props) => {
+ const { act, data } = useBackend();
+
+ const { voice_mobs } = data;
+
+ return (
+
+ {(!!voice_mobs.length && (
+
+ {voice_mobs.map((mob) => (
+
+
+ act('disconnect', {
+ disconnect: mob.true_name,
+ })
+ }
+ >
+ Disconnect
+
+
+ ))}
+
+ )) || No connections}
+
+ );
+};
+
+const CommunicatorPhoneTabInternal = (props) => {
+ const { act, data } = useBackend();
+
+ const { communicating, video_comm, phone_video_comm } = data;
+
+ return (
+
+ {(!!communicating.length && (
+
+ {communicating.map((comm) => (
+
+
+ {decodeHtmlEntities(comm.name)}
+
+
+
+ act('disconnect', {
+ disconnect: comm.true_name,
+ })
+ }
+ >
+ Disconnect
+
+ {(video_comm === null && (
+
+ act('startvideo', {
+ startvideo: comm.ref,
+ })
+ }
+ >
+ Start Video
+
+ )) ||
+ (phone_video_comm === comm.ref && (
+
+ act('endvideo', {
+ endvideo: comm.true_name,
+ })
+ }
+ >
+ Stop Video
+
+ ))}
+
+
+ ))}
+
+ )) || No connections}
+
+ );
+};
+
+const CommunicatorPhoneTabRequest = (props) => {
+ const { act, data } = useBackend();
+
+ const { requestsReceived } = data;
+
+ return (
+
+ {(!!requestsReceived.length && (
+
+ {requestsReceived.map((request) => (
+
+ {decodeHtmlEntities(request.address)}
+
+ act('dial', { dial: request.address })}
+ >
+ Accept
+
+ act('decline', { decline: request.ref })}
+ >
+ Decline
+
+
+
+ ))}
+
+ )) || No requests received.}
+
+ );
+};
+
+const CommunicatorPhoneTabInvite = (props) => {
+ const { act, data } = useBackend();
+
+ const { invitesSent } = data;
+
+ return (
+
+ {(!!invitesSent.length && (
+
+ {invitesSent.map((invite) => (
+
+ {decodeHtmlEntities(invite.address)}
+
+ {
+ act('copy', { copy: invite.address });
+ }}
+ >
+ Copy
+
+
+
+ ))}
+
+ )) || No invites sent.}
+
+ );
+};
diff --git a/tgui/packages/tgui/interfaces/Communicator/CommunicatorSettingsTab.tsx b/tgui/packages/tgui/interfaces/Communicator/CommunicatorSettingsTab.tsx
new file mode 100644
index 00000000000..3cfcb217b11
--- /dev/null
+++ b/tgui/packages/tgui/interfaces/Communicator/CommunicatorSettingsTab.tsx
@@ -0,0 +1,74 @@
+import { decodeHtmlEntities } from 'common/string';
+
+import { useBackend } from '../../backend';
+import { Box, Button, LabeledList, Section } from '../../components';
+import { Data } from './types';
+
+export const CommunicatorSettingsTab = (props) => {
+ const { act, data } = useBackend();
+
+ const {
+ address,
+ selfie_mode,
+ owner,
+ occupation,
+ connectionStatus,
+ visible,
+ ring,
+ } = data;
+
+ return (
+
+
+
+ act('rename')}>
+ {decodeHtmlEntities(owner)}
+
+
+
+ act('selfie_mode')}>
+ {selfie_mode ? 'Front-facing Camera' : 'Rear-facing Camera'}
+
+
+
+ {decodeHtmlEntities(occupation)}
+
+
+ {connectionStatus === 1 ? (
+ Connected
+ ) : (
+ Disconnected
+ )}
+
+
+ {address}
+
+
+ act('toggle_visibility')}
+ >
+ {visible
+ ? 'This device can be seen by other devices.'
+ : 'This device is invisible to other devices.'}
+
+
+
+ act('toggle_ringer')}
+ >
+ {ring ? 'Ringer on.' : 'Ringer off.'}
+
+ act('set_ringer_tone')}>
+ Set Ringer Tone
+
+
+
+
+ );
+};
diff --git a/tgui/packages/tgui/interfaces/Communicator/CommunicatorWeatherTab.tsx b/tgui/packages/tgui/interfaces/Communicator/CommunicatorWeatherTab.tsx
new file mode 100644
index 00000000000..5c7412253da
--- /dev/null
+++ b/tgui/packages/tgui/interfaces/Communicator/CommunicatorWeatherTab.tsx
@@ -0,0 +1,97 @@
+import { filter } from 'common/collections';
+import { decodeHtmlEntities, toTitleCase } from 'common/string';
+
+import { useBackend } from '../../backend';
+import { Box, LabeledList, Section } from '../../components';
+import { AirContent, WeatherTabData } from './types';
+
+export const CommunicatorWeatherTab = (props) => {
+ const { act, data } = useBackend();
+
+ const { aircontents, weather } = data;
+
+ const deg: string = '\u00B0';
+
+ return (
+
+
+
+ {filter(
+ (i: AirContent) =>
+ i.val !== '0' ||
+ i.entry === 'Pressure' ||
+ i.entry === 'Temperature',
+ )(aircontents).map((item: AirContent) => (
+
+ {item.val}
+ {decodeHtmlEntities(item.units)}
+
+ ))}
+
+
+
+ {(!!weather.length && (
+
+ {weather.map((wr) => (
+
+
+ {wr.Time}
+
+ {toTitleCase(wr.Weather)}
+
+
+ Current: {wr.Temperature.toFixed()} {deg}C | High:{' '}
+ {wr.High.toFixed()} {deg}C | Low: {wr.Low.toFixed()} {deg}C
+
+
+ {wr.WindDir}
+
+
+ {wr.WindSpeed}
+
+
+ {decodeHtmlEntities(wr.Forecast)}
+
+
+
+ ))}
+
+ )) || (
+
+ No weather reports available. Please check back later.
+
+ )}
+
+
+ );
+};
+
+/* Weather App */
+const getItemColor = (
+ value: number,
+ min2: number,
+ min1: number,
+ max1: number,
+ max2: number,
+) => {
+ if (value < min2) {
+ return 'bad';
+ } else if (value < min1) {
+ return 'average';
+ } else if (value > max1) {
+ return 'average';
+ } else if (value > max2) {
+ return 'bad';
+ }
+ return 'good';
+};
diff --git a/tgui/packages/tgui/interfaces/Communicator/constants.ts b/tgui/packages/tgui/interfaces/Communicator/constants.ts
new file mode 100644
index 00000000000..f37d974320a
--- /dev/null
+++ b/tgui/packages/tgui/interfaces/Communicator/constants.ts
@@ -0,0 +1,27 @@
+export const HOMETAB = 1;
+export const PHONTAB = 2;
+export const CONTTAB = 3;
+export const MESSTAB = 4;
+export const MESSSUBTAB = 40;
+export const NEWSTAB = 5;
+export const NOTETAB = 6;
+export const WTHRTAB = 7;
+export const MANITAB = 8;
+export const SETTTAB = 9;
+
+export const tabs = [
+ HOMETAB,
+ PHONTAB,
+ CONTTAB,
+ MESSTAB,
+ MESSSUBTAB,
+ NEWSTAB,
+ NOTETAB,
+ WTHRTAB,
+ MANITAB,
+ SETTTAB,
+];
+
+export function notFound(val) {
+ return tabs.includes(val);
+}
diff --git a/tgui/packages/tgui/interfaces/Communicator/index.tsx b/tgui/packages/tgui/interfaces/Communicator/index.tsx
new file mode 100644
index 00000000000..01941690715
--- /dev/null
+++ b/tgui/packages/tgui/interfaces/Communicator/index.tsx
@@ -0,0 +1,88 @@
+import { useState } from 'react';
+
+import { useBackend } from '../../backend';
+import { Box } from '../../components';
+import { Window } from '../../layouts';
+import { CrewManifestContent } from '../CrewManifest';
+import { CommunicatorContactTab } from './CommunicatorContactTab';
+import {
+ CommunicatorFooter,
+ CommunicatorHeader,
+ TemplateError,
+ VideoComm,
+} from './CommunicatorGeneral';
+import { CommunicatorHomeTab } from './CommunicatorHomeTab';
+import { CommunicatorMessageSubTab } from './CommunicatorMessageSubTab';
+import { CommunicatorMessageTab } from './CommunicatorMessageTab';
+import { CommunicatorNewsTab } from './CommunicatorNewsTab';
+import { CommunicatorNoteTab } from './CommunicatorNoteTab';
+import { CommunicatorPhoneTab } from './CommunicatorPhoneTab';
+import { CommunicatorSettingsTab } from './CommunicatorSettingsTab';
+import { CommunicatorWeatherTab } from './CommunicatorWeatherTab';
+import { notFound, tabs } from './constants';
+import { Data } from './types';
+
+export const Communicator = () => {
+ const { act, data } = useBackend();
+
+ const { currentTab, video_comm } = data;
+
+ const tab: React.JSX.Element[] = [];
+
+ const [videoSetting, setVideoSetting] = useState(0);
+ const [clipboardMode, setClipboardMode] = useState(false);
+
+ function handleClipboardMode(value: boolean) {
+ setClipboardMode(value);
+ }
+
+ tab[tabs[0]] = ;
+ tab[tabs[1]] = ;
+ tab[tabs[2]] = ;
+ tab[tabs[3]] = ;
+ tab[tabs[4]] = (
+
+ );
+ tab[tabs[5]] = ;
+ tab[tabs[6]] = ;
+ tab[tabs[7]] = ;
+ tab[tabs[8]] = ;
+ tab[tabs[9]] = ;
+
+ return (
+
+
+ {video_comm && (
+
+ )}
+ {(!video_comm || videoSetting !== 0) && (
+ <>
+
+
+ {tab[currentTab] ||
+ (notFound(currentTab) && (
+
+ ))}
+
+
+ >
+ )}
+
+
+ );
+};
diff --git a/tgui/packages/tgui/interfaces/Communicator/types.ts b/tgui/packages/tgui/interfaces/Communicator/types.ts
new file mode 100644
index 00000000000..7d32b0e34b0
--- /dev/null
+++ b/tgui/packages/tgui/interfaces/Communicator/types.ts
@@ -0,0 +1,96 @@
+import { BooleanLike } from 'common/react';
+
+export type Data = {
+ // GENERAL
+ currentTab: number;
+ video_comm: BooleanLike;
+ mapRef: string;
+
+ // FOOTER
+ time: string;
+ connectionStatus: BooleanLike;
+ owner: string;
+ occupation: string;
+
+ // HEADER
+ flashlight: BooleanLike;
+
+ // HOMETAB
+ homeScreen: { number: number; module: string; icon: string }[];
+
+ // PHONETAB
+ targetAddress: string;
+ voice_mobs: { name: string; true_name: string; ref: string }[];
+ communicating: {
+ address: string;
+ name: string;
+ true_name: string;
+ ref: string;
+ }[];
+ requestsReceived: { address: string; name: string; ref: string }[];
+ invitesSent: { address: string; name: string }[];
+ phone_video_comm: string;
+ selfie_mode: BooleanLike;
+
+ // MESSAGING
+ imContacts: { address: string; name: string }[];
+ targetAddressName: string;
+ imList: { address: string; to_address: string; im: string }[];
+
+ // SETTINGS
+ address: string;
+ visible: BooleanLike;
+ ring: BooleanLike;
+
+ // NEWSTAB
+ feeds: { index: number; name: string }[];
+ target_feed: { name: string; author: string; messages: NewsMessage[] } | null;
+ latest_news: NewsMessage[];
+
+ // NOTETAB
+ note: string;
+};
+
+type NewsMessage = {
+ ref: string;
+ body: string;
+ img: string;
+ caption: string;
+ message_type: string;
+ author: string;
+ time_stamp: string;
+
+ index: number;
+ channel: string;
+};
+
+export type ContactsTabData = {
+ knownDevices: { address: string; name: string }[];
+};
+
+export type WeatherTabData = {
+ aircontents: AirContent[];
+ weather: Weather[];
+};
+
+export type AirContent = {
+ entry: string;
+ val;
+ bad_low: number;
+ poor_low: number;
+ poor_high: number;
+ bad_high: number;
+ units;
+};
+
+type Weather = {
+ Planet: string;
+ Time: string;
+ Weather: string;
+ Temperature;
+ High;
+ Low;
+ WindDir;
+ WindSpeed;
+ Forecast: string;
+};
diff --git a/tgui/packages/tgui/interfaces/ComputerFabricator/CfStep1.tsx b/tgui/packages/tgui/interfaces/ComputerFabricator/CfStep1.tsx
new file mode 100644
index 00000000000..e950bdad7c3
--- /dev/null
+++ b/tgui/packages/tgui/interfaces/ComputerFabricator/CfStep1.tsx
@@ -0,0 +1,53 @@
+import { useBackend } from '../../backend';
+import { Box, Button, Section, Table } from '../../components';
+
+// This had a pretty gross backend so this was unfortunately one of the
+// best ways of doing it.
+export const CfStep1 = (props) => {
+ const { act } = useBackend();
+ return (
+
+
+ Choose your Device
+
+
+
+
+
+
+ act('pick_device', {
+ pick: '1',
+ })
+ }
+ >
+ Laptop
+
+
+
+
+ act('pick_device', {
+ pick: '2',
+ })
+ }
+ >
+ Tablet
+
+
+
+
+
+
+ );
+};
diff --git a/tgui/packages/tgui/interfaces/ComputerFabricator.jsx b/tgui/packages/tgui/interfaces/ComputerFabricator/CfStep2.tsx
similarity index 67%
rename from tgui/packages/tgui/interfaces/ComputerFabricator.jsx
rename to tgui/packages/tgui/interfaces/ComputerFabricator/CfStep2.tsx
index 619f5c0feb4..6975129674a 100644
--- a/tgui/packages/tgui/interfaces/ComputerFabricator.jsx
+++ b/tgui/packages/tgui/interfaces/ComputerFabricator/CfStep2.tsx
@@ -1,91 +1,31 @@
import { multiline } from 'common/string';
-import { useBackend } from '../backend';
-import { Box, Button, Section, Table, Tooltip } from '../components';
-import { Window } from '../layouts';
+import { useBackend } from '../../backend';
+import { Box, Button, Section, Table, Tooltip } from '../../components';
+import { Data } from './types';
-export const ComputerFabricator = (props) => {
- const { act, data } = useBackend();
- return (
-
-
-
- Your perfect device, only three steps away...
-
- {data.state !== 0 && (
- act('clean_order')}>
- Clear Order
-
- )}
- {data.state === 0 && }
- {data.state === 1 && }
- {data.state === 2 && }
- {data.state === 3 && }
-
-
- );
-};
+export const CfStep2 = (props) => {
+ const { act, data } = useBackend();
-// This had a pretty gross backend so this was unfortunately one of the
-// best ways of doing it.
-const CfStep1 = (props) => {
- const { act, data } = useBackend();
- return (
-
-
- Choose your Device
-
-
-
-
-
-
- act('pick_device', {
- pick: '1',
- })
- }
- >
- Laptop
-
-
-
-
- act('pick_device', {
- pick: '2',
- })
- }
- >
- Tablet
-
-
-
-
-
-
- );
-};
+ const {
+ totalprice,
+ hw_battery,
+ hw_disk,
+ hw_netcard,
+ hw_nanoprint,
+ hw_card,
+ devtype,
+ hw_cpu,
+ hw_tesla,
+ } = data;
-const CfStep2 = (props) => {
- const { act, data } = useBackend();
return (
- {data.totalprice}â‚®
+ {totalprice}â‚®
}
>
@@ -103,7 +43,7 @@ const CfStep2 = (props) => {
act('hw_battery', {
battery: '1',
@@ -115,7 +55,7 @@ const CfStep2 = (props) => {
act('hw_battery', {
battery: '2',
@@ -127,7 +67,7 @@ const CfStep2 = (props) => {
act('hw_battery', {
battery: '3',
@@ -151,7 +91,7 @@ const CfStep2 = (props) => {
act('hw_disk', {
disk: '1',
@@ -163,7 +103,7 @@ const CfStep2 = (props) => {
act('hw_disk', {
disk: '2',
@@ -175,7 +115,7 @@ const CfStep2 = (props) => {
act('hw_disk', {
disk: '3',
@@ -201,7 +141,7 @@ const CfStep2 = (props) => {
act('hw_netcard', {
netcard: '0',
@@ -213,7 +153,7 @@ const CfStep2 = (props) => {
act('hw_netcard', {
netcard: '1',
@@ -225,7 +165,7 @@ const CfStep2 = (props) => {
act('hw_netcard', {
netcard: '2',
@@ -251,7 +191,7 @@ const CfStep2 = (props) => {
act('hw_nanoprint', {
print: '0',
@@ -263,7 +203,7 @@ const CfStep2 = (props) => {
act('hw_nanoprint', {
print: '1',
@@ -290,7 +230,7 @@ const CfStep2 = (props) => {
act('hw_card', {
card: '0',
@@ -302,7 +242,7 @@ const CfStep2 = (props) => {
act('hw_card', {
card: '1',
@@ -313,7 +253,7 @@ const CfStep2 = (props) => {
- {data.devtype !== 2 && (
+ {devtype !== 2 && (
Processor Unit:
@@ -329,7 +269,7 @@ const CfStep2 = (props) => {
act('hw_cpu', {
cpu: '1',
@@ -341,7 +281,7 @@ const CfStep2 = (props) => {
act('hw_cpu', {
cpu: '2',
@@ -368,7 +308,7 @@ const CfStep2 = (props) => {
act('hw_tesla', {
tesla: '0',
@@ -380,7 +320,7 @@ const CfStep2 = (props) => {
act('hw_tesla', {
tesla: '1',
@@ -406,35 +346,3 @@ const CfStep2 = (props) => {
);
};
-
-const CfStep3 = (props) => {
- const { act, data } = useBackend();
- return (
-
-
- Your device is ready for fabrication...
-
-
- Please swipe your ID now to authorize payment of:
-
-
- {data.totalprice}â‚®
-
-
-
- );
-};
-
-const CfStep4 = (props) => {
- return (
-
-
- Thank you for your purchase!
-
-
- If you experience any difficulties with your new device, please contact
- your local network administrator.
-
-
- );
-};
diff --git a/tgui/packages/tgui/interfaces/ComputerFabricator/CfStep3.tsx b/tgui/packages/tgui/interfaces/ComputerFabricator/CfStep3.tsx
new file mode 100644
index 00000000000..668b739f29f
--- /dev/null
+++ b/tgui/packages/tgui/interfaces/ComputerFabricator/CfStep3.tsx
@@ -0,0 +1,19 @@
+import { Box, Section } from '../../components';
+
+export const CfStep3 = (props: { totalprice: number }) => {
+ const { totalprice } = props;
+ return (
+
+
+ Your device is ready for fabrication...
+
+
+ Please swipe your ID now to authorize payment of:
+
+
+ {totalprice}â‚®
+
+
+
+ );
+};
diff --git a/tgui/packages/tgui/interfaces/ComputerFabricator/CfStep4.tsx b/tgui/packages/tgui/interfaces/ComputerFabricator/CfStep4.tsx
new file mode 100644
index 00000000000..62fb59fa851
--- /dev/null
+++ b/tgui/packages/tgui/interfaces/ComputerFabricator/CfStep4.tsx
@@ -0,0 +1,15 @@
+import { Box, Section } from '../../components';
+
+export const CfStep4 = (props) => {
+ return (
+
+
+ Thank you for your purchase!
+
+
+ If you experience any difficulties with your new device, please contact
+ your local network administrator.
+
+
+ );
+};
diff --git a/tgui/packages/tgui/interfaces/ComputerFabricator/index.tsx b/tgui/packages/tgui/interfaces/ComputerFabricator/index.tsx
new file mode 100644
index 00000000000..b3919e0d0ac
--- /dev/null
+++ b/tgui/packages/tgui/interfaces/ComputerFabricator/index.tsx
@@ -0,0 +1,37 @@
+import { useBackend } from '../../backend';
+import { Button, Section } from '../../components';
+import { Window } from '../../layouts';
+import { CfStep1 } from './CfStep1';
+import { CfStep2 } from './CfStep2';
+import { CfStep3 } from './CfStep3';
+import { CfStep4 } from './CfStep4';
+import { Data } from './types';
+
+export const ComputerFabricator = (props) => {
+ const { act, data } = useBackend();
+
+ const { state, totalprice } = data;
+
+ const tab: React.JSX.Element[] = [];
+
+ tab[0] = ;
+ tab[1] = ;
+ tab[2] = ;
+ tab[3] = ;
+
+ return (
+
+
+
+ Your perfect device, only three steps away...
+
+ {state !== 0 && (
+ act('clean_order')}>
+ Clear Order
+
+ )}
+ {tab[state]}
+
+
+ );
+};
diff --git a/tgui/packages/tgui/interfaces/ComputerFabricator/types.ts b/tgui/packages/tgui/interfaces/ComputerFabricator/types.ts
new file mode 100644
index 00000000000..405a1007bd2
--- /dev/null
+++ b/tgui/packages/tgui/interfaces/ComputerFabricator/types.ts
@@ -0,0 +1,14 @@
+import { BooleanLike } from 'common/react';
+
+export type Data = {
+ state: number;
+ devtype: number | undefined;
+ hw_battery: number | undefined;
+ hw_disk: number | undefined;
+ hw_netcard: number | undefined;
+ hw_tesla: BooleanLike;
+ hw_nanoprint: BooleanLike;
+ hw_card: BooleanLike;
+ hw_cpu: number | undefined;
+ totalprice: number | undefined;
+};
diff --git a/tgui/packages/tgui/interfaces/CookingAppliance.jsx b/tgui/packages/tgui/interfaces/CookingAppliance.tsx
similarity index 87%
rename from tgui/packages/tgui/interfaces/CookingAppliance.jsx
rename to tgui/packages/tgui/interfaces/CookingAppliance.tsx
index 4efa443a6a0..7b951cd5db1 100644
--- a/tgui/packages/tgui/interfaces/CookingAppliance.jsx
+++ b/tgui/packages/tgui/interfaces/CookingAppliance.tsx
@@ -1,3 +1,5 @@
+import { BooleanLike } from 'common/react';
+
import { useBackend } from '../backend';
import {
AnimatedNumber,
@@ -9,8 +11,22 @@ import {
} from '../components';
import { Window } from '../layouts';
+type Data = {
+ temperature: number;
+ optimalTemp: number;
+ temperatureEnough: BooleanLike;
+ efficiency: number;
+ containersRemovable: BooleanLike;
+ our_contents: {
+ empty: BooleanLike;
+ progress: number;
+ progressText: string;
+ container: string | null;
+ }[];
+};
+
export const CookingAppliance = (props) => {
- const { act, data } = useBackend();
+ const { act, data } = useBackend();
const {
temperature,
diff --git a/tgui/packages/tgui/interfaces/CrewMonitor.jsx b/tgui/packages/tgui/interfaces/CrewMonitor.jsx
deleted file mode 100644
index 0dfb9c3955e..00000000000
--- a/tgui/packages/tgui/interfaces/CrewMonitor.jsx
+++ /dev/null
@@ -1,188 +0,0 @@
-import { sortBy } from 'common/collections';
-import { flow } from 'common/fp';
-import { useState } from 'react';
-
-import { useBackend } from '../backend';
-import { Box, Button, Icon, NanoMap, Table, Tabs } from '../components';
-import { Window } from '../layouts';
-
-const getStatText = (cm) => {
- if (cm.dead) {
- return 'Deceased';
- }
- if (parseInt(cm.stat, 10) === 1) {
- // Unconscious
- return 'Unconscious';
- }
- return 'Living';
-};
-
-const getStatColor = (cm) => {
- if (cm.dead) {
- return 'red';
- }
- if (parseInt(cm.stat, 10) === 1) {
- // Unconscious
- return 'orange';
- }
- return 'green';
-};
-
-export const CrewMonitor = () => {
- const [tabIndex, setTabIndex] = useState(0);
- const [zoom, setZoom] = useState(1);
-
- function handleTabIndex(value) {
- setTabIndex(value);
- }
-
- function handleZoom(value) {
- setZoom(value);
- }
-
- return (
-
-
-
-
-
- );
-};
-
-export const CrewMonitorContent = (props) => {
- const { act, data, config } = useBackend();
-
- const crew = flow([
- sortBy((cm) => cm.name),
- sortBy((cm) => cm?.x),
- sortBy((cm) => cm?.y),
- sortBy((cm) => cm?.realZ),
- ])(data.crewmembers || []);
-
- let body;
- // Data view
- if (props.tabIndex === 0) {
- body = (
-
-
- Name
- Status
- Location
-
- {crew.map((cm) => (
-
-
- {cm.name} ({cm.assignment})
-
-
-
- {getStatText(cm)}
-
- {cm.sensor_type >= 2 ? (
-
- {'('}
-
- {cm.brute}
-
- {'|'}
-
- {cm.fire}
-
- {'|'}
-
- {cm.tox}
-
- {'|'}
-
- {cm.oxy}
-
- {')'}
-
- ) : null}
-
-
- {cm.sensor_type === 3 ? (
- data.isAI ? (
-
- act('track', {
- track: cm.ref,
- })
- }
- >
- {cm.area + ' (' + cm.x + ', ' + cm.y + ')'}
-
- ) : (
- cm.area + ' (' + cm.x + ', ' + cm.y + ', ' + cm.z + ')'
- )
- ) : (
- 'Not Available'
- )}
-
-
- ))}
-
- );
- } else if (props.tabIndex === 1) {
- // Please note, if you ever change the zoom values,
- // you MUST update styles/components/Tooltip.scss
- // and change the @for scss to match.
- body = ;
- } else {
- body = 'ERROR';
- }
-
- return (
- <>
-
- props.onTabIndex(0)}
- >
- Data View
-
- props.onTabIndex(1)}
- >
- Map View
-
-
- {body}
- >
- );
-};
-
-const CrewMonitorMapView = (props) => {
- const { act, config, data } = useBackend();
- return (
-
- props.onZoom(v)}>
- {data.crewmembers
- .filter(
- (x) => x.sensor_type === 3 && ~~x.realZ === ~~config.mapZLevel,
- )
- .map((cm) => (
-
- ))}
-
-
- );
-};
diff --git a/tgui/packages/tgui/interfaces/CrewMonitor.tsx b/tgui/packages/tgui/interfaces/CrewMonitor.tsx
new file mode 100644
index 00000000000..385f158dd83
--- /dev/null
+++ b/tgui/packages/tgui/interfaces/CrewMonitor.tsx
@@ -0,0 +1,231 @@
+import { sortBy } from 'common/collections';
+import { flow } from 'common/fp';
+import { BooleanLike } from 'common/react';
+import { useState } from 'react';
+
+import { useBackend } from '../backend';
+import { Box, Button, Icon, NanoMap, Table, Tabs } from '../components';
+import { Window } from '../layouts';
+
+type Data = {
+ zoomScale: number;
+ isAI: BooleanLike;
+ map_levels: number[];
+ crewmembers: crewmember[];
+};
+
+type crewmember = {
+ sensor_type: number;
+ name: string;
+ rank: string;
+ assignment: string;
+ dead: BooleanLike;
+ stat: number;
+ oxy: number;
+ tox: number;
+ fire: number;
+ brute: number;
+ area: string;
+ x: number;
+ y: number;
+ realZ: number;
+ z: number;
+ ref: string;
+};
+
+const getStatText = (cm: crewmember) => {
+ if (cm.dead) {
+ return 'Deceased';
+ }
+ if (cm.stat === 1) {
+ // Unconscious
+ return 'Unconscious';
+ }
+ return 'Living';
+};
+
+const getStatColor = (cm: crewmember) => {
+ if (cm.dead) {
+ return 'red';
+ }
+ if (cm.stat === 1) {
+ // Unconscious
+ return 'orange';
+ }
+ return 'green';
+};
+
+export const CrewMonitor = () => {
+ const [tabIndex, setTabIndex] = useState(0);
+ const [zoom, setZoom] = useState(1);
+
+ function handleTabIndex(value: number) {
+ setTabIndex(value);
+ }
+
+ function handleZoom(value: number) {
+ setZoom(value);
+ }
+
+ return (
+
+
+
+
+
+ );
+};
+
+export const CrewMonitorContent = (props: {
+ tabIndex: number;
+ zoom: number;
+ onTabIndex: Function;
+ onZoom: Function;
+}) => {
+ const { data } = useBackend();
+
+ const { crewmembers = [] } = data;
+
+ const crew: crewmember[] = flow([
+ sortBy((cm: crewmember) => cm.name),
+ sortBy((cm: crewmember) => cm?.x),
+ sortBy((cm: crewmember) => cm?.y),
+ sortBy((cm: crewmember) => cm?.realZ),
+ ])(crewmembers);
+
+ const tab: React.JSX.Element[] = [];
+ // Data view
+ // Please note, if you ever change the zoom values,
+ // you MUST update styles/components/Tooltip.scss
+ // and change the @for scss to match.
+ tab[0] = ;
+
+ tab[1] = ;
+
+ return (
+ <>
+
+ props.onTabIndex(0)}
+ >
+ Data View
+
+ props.onTabIndex(1)}
+ >
+ Map View
+
+
+ {tab[props.tabIndex] || ERROR}
+ >
+ );
+};
+
+const CrewMonitorCrew = (props: { crew: crewmember[] }) => {
+ const { act, data } = useBackend();
+
+ const { crew } = props;
+
+ const { isAI } = data;
+
+ return (
+
+
+ Name
+ Status
+ Location
+
+ {crew.map((cm) => (
+
+
+ {cm.name} ({cm.assignment})
+
+
+
+ {getStatText(cm)}
+
+ {cm.sensor_type >= 2 ? (
+
+ {'('}
+
+ {cm.brute}
+
+ {'|'}
+
+ {cm.fire}
+
+ {'|'}
+
+ {cm.tox}
+
+ {'|'}
+
+ {cm.oxy}
+
+ {')'}
+
+ ) : null}
+
+
+ {cm.sensor_type === 3 ? (
+ isAI ? (
+
+ act('track', {
+ track: cm.ref,
+ })
+ }
+ >
+ {cm.area + ' (' + cm.x + ', ' + cm.y + ')'}
+
+ ) : (
+ cm.area + ' (' + cm.x + ', ' + cm.y + ', ' + cm.z + ')'
+ )
+ ) : (
+ 'Not Available'
+ )}
+
+
+ ))}
+
+ );
+};
+
+const CrewMonitorMapView = (props: { zoom: number; onZoom: Function }) => {
+ const { config, data } = useBackend();
+
+ const { zoomScale, crewmembers } = data;
+
+ return (
+
+ props.onZoom(v)}>
+ {crewmembers
+ .filter(
+ (x) => x.sensor_type === 3 && ~~x.realZ === ~~config.mapZLevel,
+ )
+ .map((cm) => (
+
+ ))}
+
+
+ );
+};
diff --git a/tgui/packages/tgui/interfaces/Cryo.jsx b/tgui/packages/tgui/interfaces/Cryo/CryoContent.tsx
similarity index 70%
rename from tgui/packages/tgui/interfaces/Cryo.jsx
rename to tgui/packages/tgui/interfaces/Cryo/CryoContent.tsx
index 5ec9994bb38..9ac1a8dff4b 100644
--- a/tgui/packages/tgui/interfaces/Cryo.jsx
+++ b/tgui/packages/tgui/interfaces/Cryo/CryoContent.tsx
@@ -1,4 +1,6 @@
-import { useBackend } from '../backend';
+import { toFixed } from 'common/math';
+
+import { useBackend } from '../../backend';
import {
AnimatedNumber,
Box,
@@ -8,50 +10,16 @@ import {
LabeledList,
ProgressBar,
Section,
-} from '../components';
-import { Window } from '../layouts';
+} from '../../components';
+import { damageTypes, statNames } from './constants';
+import { Data } from './types';
-const damageTypes = [
- {
- label: 'Resp.',
- type: 'oxyLoss',
- },
- {
- label: 'Toxin',
- type: 'toxLoss',
- },
- {
- label: 'Brute',
- type: 'bruteLoss',
- },
- {
- label: 'Burn',
- type: 'fireLoss',
- },
-];
-
-const statNames = [
- ['good', 'Conscious'],
- ['average', 'Unconscious'],
- ['bad', 'DEAD'],
-];
-
-export const Cryo = (props) => {
- return (
-
-
-
-
-
- );
-};
-
-const CryoContent = (props) => {
- const { act, data } = useBackend();
+export const CryoContent = (props) => {
+ const { act, data } = useBackend();
const {
isOperating,
hasOccupant,
- occupant = [],
+ occupant,
cellTemperature,
cellTemperatureStatus,
isBeakerLoaded,
@@ -60,7 +28,7 @@ const CryoContent = (props) => {
<>
{
0 ? 'good' : 'average'}
>
-
+ toFixed(value)}
+ />
{
{statNames[occupant.stat][1]}
-
- {' K'}
+ toFixed(value) + ' K'}
+ />
- {damageTypes.map((damageType) => (
-
+ {damageTypes.map((damageType, i) => (
+
toFixed(value)}
/>
@@ -113,7 +87,7 @@ const CryoContent = (props) => {
) : (
-
+
No occupant detected.
@@ -155,7 +129,7 @@ const CryoContent = (props) => {
};
const CryoBeaker = (props) => {
- const { act, data } = useBackend();
+ const { act, data } = useBackend();
const { isBeakerLoaded, beakerLabel, beakerVolume } = data;
if (isBeakerLoaded) {
return (
@@ -165,7 +139,7 @@ const CryoBeaker = (props) => {
{beakerVolume ? (
Math.round(v) + ' units remaining'}
+ format={(v) => toFixed(v) + ' units remaining'}
/>
) : (
'Beaker is empty'
diff --git a/tgui/packages/tgui/interfaces/Cryo/constants.ts b/tgui/packages/tgui/interfaces/Cryo/constants.ts
new file mode 100644
index 00000000000..b300a5f9822
--- /dev/null
+++ b/tgui/packages/tgui/interfaces/Cryo/constants.ts
@@ -0,0 +1,24 @@
+export const damageTypes: { label: string; type: string }[] = [
+ {
+ label: 'Resp.',
+ type: 'oxyLoss',
+ },
+ {
+ label: 'Toxin',
+ type: 'toxLoss',
+ },
+ {
+ label: 'Brute',
+ type: 'bruteLoss',
+ },
+ {
+ label: 'Burn',
+ type: 'fireLoss',
+ },
+];
+
+export const statNames: string[][] = [
+ ['good', 'Conscious'],
+ ['average', 'Unconscious'],
+ ['bad', 'DEAD'],
+];
diff --git a/tgui/packages/tgui/interfaces/Cryo/index.tsx b/tgui/packages/tgui/interfaces/Cryo/index.tsx
new file mode 100644
index 00000000000..481740ce719
--- /dev/null
+++ b/tgui/packages/tgui/interfaces/Cryo/index.tsx
@@ -0,0 +1,12 @@
+import { Window } from '../../layouts';
+import { CryoContent } from './CryoContent';
+
+export const Cryo = (props) => {
+ return (
+
+
+
+
+
+ );
+};
diff --git a/tgui/packages/tgui/interfaces/Cryo/types.ts b/tgui/packages/tgui/interfaces/Cryo/types.ts
new file mode 100644
index 00000000000..49d2d2e3031
--- /dev/null
+++ b/tgui/packages/tgui/interfaces/Cryo/types.ts
@@ -0,0 +1,23 @@
+import { BooleanLike } from 'common/react';
+
+export type Data = {
+ isOperating: BooleanLike;
+ hasOccupant: BooleanLike;
+ occupant: {
+ name: string;
+ stat: number;
+ health: number;
+ maxHealth: number;
+ minHealth: number;
+ bruteLoss: number;
+ oxyLoss: number;
+ toxLoss: number;
+ fireLoss: number;
+ bodyTemperature: number;
+ };
+ cellTemperature: number;
+ cellTemperatureStatus: string;
+ isBeakerLoaded: BooleanLike;
+ beakerLabel: string | null;
+ beakerVolume: number;
+};
diff --git a/tgui/packages/tgui/interfaces/CryoStorage.jsx b/tgui/packages/tgui/interfaces/CryoStorage.tsx
similarity index 61%
rename from tgui/packages/tgui/interfaces/CryoStorage.jsx
rename to tgui/packages/tgui/interfaces/CryoStorage.tsx
index d947810e38e..b564e95578b 100644
--- a/tgui/packages/tgui/interfaces/CryoStorage.jsx
+++ b/tgui/packages/tgui/interfaces/CryoStorage.tsx
@@ -1,16 +1,29 @@
+import { BooleanLike } from 'common/react';
import { useState } from 'react';
import { useBackend } from '../backend';
-import { Box, Button, NoticeBox, Section, Tabs } from '../components';
+import { Box, NoticeBox, Section, Tabs } from '../components';
import { Window } from '../layouts';
+export type Data = {
+ real_name: string;
+ allow_items: BooleanLike;
+ crew: string[];
+ items: string[];
+};
+
export const CryoStorage = (props) => {
- const { act, data } = useBackend();
+ const { data } = useBackend();
const { real_name, allow_items } = data;
const [tab, setTab] = useState(0);
+ const tabs: React.JSX.Element[] = [];
+
+ tabs[0] = ;
+ tabs[1] = allow_items ? : ;
+
return (
@@ -25,15 +38,14 @@ export const CryoStorage = (props) => {
)}
Welcome, {real_name}.
- {tab === 0 && }
- {!!allow_items && tab === 1 && }
+ {tabs[tab]}
);
};
export const CryoStorageCrew = (props) => {
- const { act, data } = useBackend();
+ const { data } = useBackend();
const { crew } = data;
@@ -50,7 +62,29 @@ export const CryoStorageCrew = (props) => {
};
export const CryoStorageItems = (props) => {
- const { act, data } = useBackend();
+ const { data } = useBackend();
+
+ const { items } = data;
+
+ return (
+
+ {(items.length &&
+ items.map((item) => (
+
+ {item}
+
+ ))) || No items stored.}
+
+ );
+};
+
+export const CryoStorageDefaultError = (props) => {
+ return Disabled;
+};
+
+/* Unused here
+export const CryoStorageItems = (props) => {
+ const { act, data } = useBackend();
const { items } = data;
@@ -76,3 +110,4 @@ export const CryoStorageItems = (props) => {
);
};
+*/
diff --git a/tgui/packages/tgui/interfaces/CryoStorageVr.jsx b/tgui/packages/tgui/interfaces/CryoStorageVr.jsx
deleted file mode 100644
index 173842a7a8a..00000000000
--- a/tgui/packages/tgui/interfaces/CryoStorageVr.jsx
+++ /dev/null
@@ -1,51 +0,0 @@
-import { useState } from 'react';
-
-import { useBackend } from '../backend';
-import { Box, NoticeBox, Section, Tabs } from '../components';
-import { Window } from '../layouts';
-import { CryoStorageCrew } from './CryoStorage';
-
-export const CryoStorageVr = (props) => {
- const { act, data } = useBackend();
-
- const { real_name, allow_items } = data;
-
- const [tab, setTab] = useState(0);
-
- return (
-
-
-
- setTab(0)}>
- Crew
-
- {!!allow_items && (
- setTab(1)}>
- Items
-
- )}
-
- Welcome, {real_name}.
- {tab === 0 && }
- {!!allow_items && tab === 1 && }
-
-
- );
-};
-
-export const CryoStorageItemsVr = (props) => {
- const { act, data } = useBackend();
-
- const { items } = data;
-
- return (
-
- {(items.length &&
- items.map((item) => (
-
- {item}
-
- ))) || No items stored.}
-
- );
-};
diff --git a/tgui/packages/tgui/interfaces/DNAForensics.jsx b/tgui/packages/tgui/interfaces/DNAForensics.tsx
similarity index 89%
rename from tgui/packages/tgui/interfaces/DNAForensics.jsx
rename to tgui/packages/tgui/interfaces/DNAForensics.tsx
index b1dc785183c..ab160dc8ef5 100644
--- a/tgui/packages/tgui/interfaces/DNAForensics.jsx
+++ b/tgui/packages/tgui/interfaces/DNAForensics.tsx
@@ -1,9 +1,18 @@
+import { BooleanLike } from 'common/react';
+
import { useBackend } from '../backend';
import { Box, Button, LabeledList, ProgressBar, Section } from '../components';
import { Window } from '../layouts';
+type Data = {
+ scan_progress: number;
+ scanning: BooleanLike;
+ bloodsamp: string;
+ bloodsamp_desc: string;
+};
+
export const DNAForensics = (props) => {
- const { act, data } = useBackend();
+ const { act, data } = useBackend();
const { scan_progress, scanning, bloodsamp, bloodsamp_desc } = data;
return (
diff --git a/tgui/packages/tgui/interfaces/DNAModifier.jsx b/tgui/packages/tgui/interfaces/DNAModifier.jsx
deleted file mode 100644
index 72560e14705..00000000000
--- a/tgui/packages/tgui/interfaces/DNAModifier.jsx
+++ /dev/null
@@ -1,701 +0,0 @@
-import { useBackend } from '../backend';
-import {
- Box,
- Button,
- Dimmer,
- Flex,
- Icon,
- Knob,
- LabeledList,
- ProgressBar,
- Section,
- Tabs,
-} from '../components';
-import { Window } from '../layouts';
-import { ComplexModal } from './common/ComplexModal';
-
-const stats = [
- ['good', 'Alive'],
- ['average', 'Unconscious'],
- ['bad', 'DEAD'],
-];
-
-const operations = [
- ['ui', 'Modify U.I.', 'dna'],
- ['se', 'Modify S.E.', 'dna'],
- ['buffer', 'Transfer Buffers', 'syringe'],
- ['rejuvenators', 'Rejuvenators', 'flask'],
-];
-
-const rejuvenatorsDoses = [5, 10, 20, 30, 50];
-
-export const DNAModifier = (props) => {
- const { act, data } = useBackend();
- const { irradiating, dnaBlockSize, occupant } = data;
- const isDNAInvalid =
- !occupant.isViableSubject ||
- !occupant.uniqueIdentity ||
- !occupant.structuralEnzymes;
- let radiatingModal;
- if (irradiating) {
- radiatingModal = ;
- }
- return (
-
-
- {radiatingModal}
-
-
-
-
-
- );
-};
-
-const DNAModifierOccupant = (props) => {
- const { act, data } = useBackend();
- const { locked, hasOccupant, occupant } = data;
- return (
-
-
- Door Lock:
-
- act('toggleLock')}
- >
- {locked ? 'Engaged' : 'Disengaged'}
-
- act('ejectOccupant')}
- >
- Eject
-
- >
- }
- >
- {hasOccupant ? (
- <>
-
-
- {occupant.name}
-
-
-
-
- {stats[occupant.stat][1]}
-
-
-
-
- {props.isDNAInvalid ? (
-
-
- The occupant's DNA structure is ruined beyond
- recognition, please insert a subject with an intact DNA structure.
-
- ) : (
-
-
-
-
-
- {data.occupant.uniqueEnzymes ? (
- data.occupant.uniqueEnzymes
- ) : (
-
-
- Unknown
-
- )}
-
-
- )}
- >
- ) : (
- Cell unoccupied.
- )}
-
- );
-};
-
-const DNAModifierMain = (props) => {
- const { act, data } = useBackend();
- const { selectedMenuKey, hasOccupant, occupant } = data;
- if (!hasOccupant) {
- return (
-
-
-
-
-
- No occupant in DNA modifier.
-
-
-
- );
- } else if (props.isDNAInvalid) {
- return (
-
-
-
-
-
- No operation possible on this subject.
-
-
-
- );
- }
- let body;
- if (selectedMenuKey === 'ui') {
- body = (
- <>
-
-
- >
- );
- } else if (selectedMenuKey === 'se') {
- body = (
- <>
-
-
- >
- );
- } else if (selectedMenuKey === 'buffer') {
- body = ;
- } else if (selectedMenuKey === 'rejuvenators') {
- body = ;
- }
- return (
-
-
- {operations.map((op, i) => (
- act('selectMenuKey', { key: op[0] })}
- >
-
- {op[1]}
-
- ))}
-
- {body}
-
- );
-};
-
-const DNAModifierMainUI = (props) => {
- const { act, data } = useBackend();
- const {
- selectedUIBlock,
- selectedUISubBlock,
- selectedUITarget,
- dnaBlockSize,
- occupant,
- } = data;
- return (
-
-
-
-
- value.toString(16).toUpperCase()}
- ml="0"
- onChange={(e, val) => act('changeUITarget', { value: val })}
- />
-
-
- act('pulseUIRadiation')}
- >
- Irradiate Block
-
-
- );
-};
-
-const DNAModifierMainSE = (props) => {
- const { act, data } = useBackend();
- const { selectedSEBlock, selectedSESubBlock, dnaBlockSize, occupant } = data;
- return (
-
-
- act('pulseSERadiation')}>
- Irradiate Block
-
-
- );
-};
-
-const DNAModifierMainRadiationEmitter = (props) => {
- const { act, data } = useBackend();
- const { radiationIntensity, radiationDuration } = data;
- return (
-
-
-
- act('radiationIntensity', { value: val })}
- />
-
-
- act('radiationDuration', { value: val })}
- />
-
-
- act('pulseRadiation')}
- >
- Pulse Radiation
-
-
- );
-};
-
-const DNAModifierMainBuffers = (props) => {
- const { act, data } = useBackend();
- const { buffers } = data;
- let bufferElements = buffers.map((buffer, i) => (
-
- ));
- return (
- <>
-
-
- >
- );
-};
-
-const DNAModifierMainBuffersElement = (props) => {
- const { act, data } = useBackend();
- const { id, name, buffer } = props;
- const isInjectorReady = data.isInjectorReady;
- const realName = name + (buffer.data ? ' - ' + buffer.label : '');
- return (
-
-
-
- act('bufferOption', {
- option: 'clear',
- id: id,
- })
- }
- >
- Clear
-
-
- act('bufferOption', {
- option: 'changeLabel',
- id: id,
- })
- }
- >
- Rename
-
-
- act('bufferOption', {
- option: 'saveDisk',
- id: id,
- })
- }
- >
- Export
-
- >
- }
- >
-
-
-
- act('bufferOption', {
- option: 'saveUI',
- id: id,
- })
- }
- >
- Subject U.I
-
-
- act('bufferOption', {
- option: 'saveUIAndUE',
- id: id,
- })
- }
- >
- Subject U.I and U.E.
-
-
- act('bufferOption', {
- option: 'saveSE',
- id: id,
- })
- }
- >
- Subject S.E.
-
-
- act('bufferOption', {
- option: 'loadDisk',
- id: id,
- })
- }
- >
- From Disk
-
-
- {!!buffer.data && (
- <>
-
- {buffer.owner || Unknown}
-
-
- {buffer.type === 'ui'
- ? 'Unique Identifiers'
- : 'Structural Enzymes'}
- {!!buffer.ue && ' and Unique Enzymes'}
-
-
-
- act('bufferOption', {
- option: 'createInjector',
- id: id,
- })
- }
- >
- Injector
-
-
- act('bufferOption', {
- option: 'createInjector',
- id: id,
- block: 1,
- })
- }
- >
- Block Injector
-
-
- act('bufferOption', {
- option: 'transfer',
- id: id,
- })
- }
- >
- Subject
-
-
- >
- )}
-
- {!buffer.data && (
-
- This buffer is empty.
-
- )}
-
-
- );
-};
-
-const DNAModifierMainBuffersDisk = (props) => {
- const { act, data } = useBackend();
- const { hasDisk, disk } = data;
- return (
-
- act('wipeDisk')}
- >
- Wipe
-
- act('ejectDisk')}
- >
- Eject
-
- >
- }
- >
- {hasDisk ? (
- disk.data ? (
-
-
- {disk.label ? disk.label : 'No label'}
-
-
- {disk.owner ? disk.owner : Unknown}
-
-
- {disk.type === 'ui' ? 'Unique Identifiers' : 'Structural Enzymes'}
- {!!disk.ue && ' and Unique Enzymes'}
-
-
- ) : (
- Disk is blank.
- )
- ) : (
-
-
-
- No disk inserted.
-
- )}
-
- );
-};
-
-const DNAModifierMainRejuvenators = (props) => {
- const { act, data } = useBackend();
- const { isBeakerLoaded, beakerVolume, beakerLabel } = data;
- return (
- act('ejectBeaker')}
- >
- Eject
-
- }
- >
- {isBeakerLoaded ? (
-
-
- {rejuvenatorsDoses.map((a, i) => (
- beakerVolume}
- icon="syringe"
- onClick={() =>
- act('injectRejuvenators', {
- amount: a,
- })
- }
- >
- {a}
-
- ))}
-
- act('injectRejuvenators', {
- amount: beakerVolume,
- })
- }
- >
- All
-
-
-
- {beakerLabel ? beakerLabel : 'No label'}
- {beakerVolume ? (
-
- {beakerVolume} unit{beakerVolume === 1 ? '' : 's'} remaining
-
- ) : (
- Empty
- )}
-
-
- ) : (
-
-
-
- No beaker loaded.
-
- )}
-
- );
-};
-
-const DNAModifierIrradiating = (props) => {
- return (
-
-
-
-
-
-
- Irradiating occupant
-
-
-
-
-
- For {props.duration} second{props.duration === 1 ? '' : 's'}
-
-
-
- );
-};
-
-const DNAModifierBlocks = (props) => {
- const { act, data } = useBackend();
- const { dnaString, selectedBlock, selectedSubblock, blockSize, action } =
- props;
-
- const characters = dnaString.split('');
- let curBlock = 0;
- let dnaBlocks = [];
- for (let block = 0; block < characters.length; block += blockSize) {
- const realBlock = block / blockSize + 1;
- let subBlocks = [];
- for (let subblock = 0; subblock < blockSize; subblock++) {
- const realSubblock = subblock + 1;
- subBlocks.push(
-
- act(action, {
- block: realBlock,
- subblock: realSubblock,
- })
- }
- >
- {characters[block + subblock]}
- ,
- );
- }
- dnaBlocks.push(
-
-
- {realBlock}
-
- {subBlocks}
- ,
- );
- }
- return {dnaBlocks};
-};
diff --git a/tgui/packages/tgui/interfaces/DNAModifier/DNAModifierBlocks.tsx b/tgui/packages/tgui/interfaces/DNAModifier/DNAModifierBlocks.tsx
new file mode 100644
index 00000000000..1d465d3e5a2
--- /dev/null
+++ b/tgui/packages/tgui/interfaces/DNAModifier/DNAModifierBlocks.tsx
@@ -0,0 +1,59 @@
+import { useBackend } from '../../backend';
+import { Box, Button, Flex } from '../../components';
+
+export const DNAModifierBlocks = (props: {
+ dnaString: string;
+ selectedBlock: number;
+ selectedSubblock: number;
+ blockSize: number;
+ action: string;
+}) => {
+ const { act } = useBackend();
+
+ const { dnaString, selectedBlock, selectedSubblock, blockSize, action } =
+ props;
+
+ const characters: string[] = dnaString.split('');
+ let dnaBlocks: React.JSX.Element[] = [];
+ for (let block = 0; block < characters.length; block += blockSize) {
+ const realBlock: number = block / blockSize + 1;
+ let subBlocks: React.JSX.Element[] = [];
+ for (let subblock = 0; subblock < blockSize; subblock++) {
+ const realSubblock: number = subblock + 1;
+ subBlocks.push(
+
+ act(action, {
+ block: realBlock,
+ subblock: realSubblock,
+ })
+ }
+ >
+ {characters[block + subblock]}
+ ,
+ );
+ }
+ dnaBlocks.push(
+
+
+ {realBlock}
+
+ {subBlocks}
+ ,
+ );
+ }
+ return {dnaBlocks};
+};
diff --git a/tgui/packages/tgui/interfaces/DNAModifier/DNAModifierIrradiating.tsx b/tgui/packages/tgui/interfaces/DNAModifier/DNAModifierIrradiating.tsx
new file mode 100644
index 00000000000..1b6c04d76eb
--- /dev/null
+++ b/tgui/packages/tgui/interfaces/DNAModifier/DNAModifierIrradiating.tsx
@@ -0,0 +1,22 @@
+import { Box, Dimmer, Icon } from '../../components';
+
+export const DNAModifierIrradiating = (props: { duration: number }) => {
+ return (
+
+
+
+
+
+
+ Irradiating occupant
+
+
+
+
+
+ For {props.duration} second{props.duration === 1 ? '' : 's'}
+
+
+
+ );
+};
diff --git a/tgui/packages/tgui/interfaces/DNAModifier/DNAModifierMain.tsx b/tgui/packages/tgui/interfaces/DNAModifier/DNAModifierMain.tsx
new file mode 100644
index 00000000000..aa08e16013f
--- /dev/null
+++ b/tgui/packages/tgui/interfaces/DNAModifier/DNAModifierMain.tsx
@@ -0,0 +1,265 @@
+import { BooleanLike } from 'common/react';
+
+import { useBackend } from '../../backend';
+import {
+ Box,
+ Button,
+ Flex,
+ Icon,
+ Knob,
+ LabeledList,
+ Section,
+ Tabs,
+} from '../../components';
+import { operations, rejuvenatorsDoses } from './constants';
+import { DNAModifierBlocks } from './DNAModifierBlocks';
+import { DNAModifierMainBuffers } from './DNAModifierMainBuffers';
+import { Data } from './types';
+
+export const DNAModifierMain = (props: { isDNAInvalid: BooleanLike }) => {
+ const { act, data } = useBackend();
+
+ const { selectedMenuKey, hasOccupant } = data;
+
+ if (!hasOccupant) {
+ return (
+
+
+
+
+
+ No occupant in DNA modifier.
+
+
+
+ );
+ } else if (props.isDNAInvalid) {
+ return (
+
+
+
+
+
+ No operation possible on this subject.
+
+
+
+ );
+ }
+ let body;
+ if (selectedMenuKey === 'ui') {
+ body = (
+ <>
+
+
+ >
+ );
+ } else if (selectedMenuKey === 'se') {
+ body = (
+ <>
+
+
+ >
+ );
+ } else if (selectedMenuKey === 'buffer') {
+ body = ;
+ } else if (selectedMenuKey === 'rejuvenators') {
+ body = ;
+ }
+ return (
+
+
+ {operations.map((op, i) => (
+ act('selectMenuKey', { key: op[0] })}
+ >
+
+ {op[1]}
+
+ ))}
+
+ {body}
+
+ );
+};
+
+const DNAModifierMainUI = (props) => {
+ const { act, data } = useBackend();
+
+ const {
+ selectedUIBlock,
+ selectedUISubBlock,
+ selectedUITarget,
+ dnaBlockSize,
+ occupant,
+ } = data;
+
+ return (
+
+
+
+
+ value.toString(16).toUpperCase()}
+ ml="0"
+ onChange={(e, val) => act('changeUITarget', { value: val })}
+ />
+
+
+ act('pulseUIRadiation')}
+ >
+ Irradiate Block
+
+
+ );
+};
+
+const DNAModifierMainSE = (props) => {
+ const { act, data } = useBackend();
+
+ const { selectedSEBlock, selectedSESubBlock, dnaBlockSize, occupant } = data;
+
+ return (
+
+
+ act('pulseSERadiation')}>
+ Irradiate Block
+
+
+ );
+};
+
+const DNAModifierMainRadiationEmitter = (props) => {
+ const { act, data } = useBackend();
+
+ const { radiationIntensity, radiationDuration } = data;
+
+ return (
+
+
+
+ act('radiationIntensity', { value: val })}
+ />
+
+
+ act('radiationDuration', { value: val })}
+ />
+
+
+ act('pulseRadiation')}
+ >
+ Pulse Radiation
+
+
+ );
+};
+
+const DNAModifierMainRejuvenators = (props) => {
+ const { act, data } = useBackend();
+
+ const { isBeakerLoaded, beakerVolume, beakerLabel } = data;
+
+ return (
+ act('ejectBeaker')}
+ >
+ Eject
+
+ }
+ >
+ {isBeakerLoaded ? (
+
+
+ {rejuvenatorsDoses.map((a, i) => (
+ beakerVolume}
+ icon="syringe"
+ onClick={() =>
+ act('injectRejuvenators', {
+ amount: a,
+ })
+ }
+ >
+ {a}
+
+ ))}
+
+ act('injectRejuvenators', {
+ amount: beakerVolume,
+ })
+ }
+ >
+ All
+
+
+
+ {beakerLabel ? beakerLabel : 'No label'}
+ {beakerVolume ? (
+
+ {beakerVolume} unit{beakerVolume === 1 ? '' : 's'} remaining
+
+ ) : (
+ Empty
+ )}
+
+
+ ) : (
+
+
+
+ No beaker loaded.
+
+ )}
+
+ );
+};
diff --git a/tgui/packages/tgui/interfaces/DNAModifier/DNAModifierMainBuffers.tsx b/tgui/packages/tgui/interfaces/DNAModifier/DNAModifierMainBuffers.tsx
new file mode 100644
index 00000000000..d3efe7678d7
--- /dev/null
+++ b/tgui/packages/tgui/interfaces/DNAModifier/DNAModifierMainBuffers.tsx
@@ -0,0 +1,254 @@
+import { useBackend } from '../../backend';
+import { Box, Button, Icon, LabeledList, Section } from '../../components';
+import { buffData, Data } from './types';
+
+export const DNAModifierMainBuffers = (props) => {
+ const { data } = useBackend();
+
+ const { buffers } = data;
+
+ let bufferElements = buffers.map((buffer, i) => (
+
+ ));
+ return (
+ <>
+
+
+ >
+ );
+};
+
+const DNAModifierMainBuffersElement = (props: {
+ id: number;
+ name: string;
+ buffer: buffData;
+}) => {
+ const { act, data } = useBackend();
+ const { id, name, buffer } = props;
+ const { isInjectorReady } = data;
+ const realName: string = name + (buffer.data ? ' - ' + buffer.label : '');
+ return (
+
+
+
+ act('bufferOption', {
+ option: 'clear',
+ id: id,
+ })
+ }
+ >
+ Clear
+
+
+ act('bufferOption', {
+ option: 'changeLabel',
+ id: id,
+ })
+ }
+ >
+ Rename
+
+
+ act('bufferOption', {
+ option: 'saveDisk',
+ id: id,
+ })
+ }
+ >
+ Export
+
+ >
+ }
+ >
+
+
+
+ act('bufferOption', {
+ option: 'saveUI',
+ id: id,
+ })
+ }
+ >
+ Subject U.I
+
+
+ act('bufferOption', {
+ option: 'saveUIAndUE',
+ id: id,
+ })
+ }
+ >
+ Subject U.I and U.E.
+
+
+ act('bufferOption', {
+ option: 'saveSE',
+ id: id,
+ })
+ }
+ >
+ Subject S.E.
+
+
+ act('bufferOption', {
+ option: 'loadDisk',
+ id: id,
+ })
+ }
+ >
+ From Disk
+
+
+ {!!buffer.data && (
+ <>
+
+ {buffer.owner || Unknown}
+
+
+ {buffer.type === 'ui'
+ ? 'Unique Identifiers'
+ : 'Structural Enzymes'}
+ {!!buffer.ue && ' and Unique Enzymes'}
+
+
+
+ act('bufferOption', {
+ option: 'createInjector',
+ id: id,
+ })
+ }
+ >
+ Injector
+
+
+ act('bufferOption', {
+ option: 'createInjector',
+ id: id,
+ block: 1,
+ })
+ }
+ >
+ Block Injector
+
+
+ act('bufferOption', {
+ option: 'transfer',
+ id: id,
+ })
+ }
+ >
+ Subject
+
+
+ >
+ )}
+
+ {!buffer.data && (
+
+ This buffer is empty.
+
+ )}
+
+
+ );
+};
+
+const DNAModifierMainBuffersDisk = (props) => {
+ const { act, data } = useBackend();
+ const { hasDisk, disk } = data;
+ return (
+
+ act('wipeDisk')}
+ >
+ Wipe
+
+ act('ejectDisk')}
+ >
+ Eject
+
+ >
+ }
+ >
+ {hasDisk ? (
+ disk.data ? (
+
+
+ {disk.label ? disk.label : 'No label'}
+
+
+ {disk.owner ? disk.owner : Unknown}
+
+
+ {disk.type === 'ui' ? 'Unique Identifiers' : 'Structural Enzymes'}
+ {!!disk.ue && ' and Unique Enzymes'}
+
+
+ ) : (
+ Disk is blank.
+ )
+ ) : (
+
+
+
+ No disk inserted.
+
+ )}
+
+ );
+};
diff --git a/tgui/packages/tgui/interfaces/DNAModifier/DNAModifierOccupant.tsx b/tgui/packages/tgui/interfaces/DNAModifier/DNAModifierOccupant.tsx
new file mode 100644
index 00000000000..b8fd28b5ce8
--- /dev/null
+++ b/tgui/packages/tgui/interfaces/DNAModifier/DNAModifierOccupant.tsx
@@ -0,0 +1,103 @@
+import { BooleanLike } from 'common/react';
+
+import { useBackend } from '../../backend';
+import {
+ Box,
+ Button,
+ Icon,
+ LabeledList,
+ ProgressBar,
+ Section,
+} from '../../components';
+import { stats } from './constants';
+import { Data } from './types';
+
+export const DNAModifierOccupant = (props: { isDNAInvalid: BooleanLike }) => {
+ const { act, data } = useBackend();
+
+ const { locked, hasOccupant, occupant } = data;
+
+ return (
+
+
+ Door Lock:
+
+ act('toggleLock')}
+ >
+ {locked ? 'Engaged' : 'Disengaged'}
+
+ act('ejectOccupant')}
+ >
+ Eject
+
+ >
+ }
+ >
+ {hasOccupant ? (
+ <>
+
+
+ {occupant.name}
+
+
+
+
+ {stats[occupant.stat!][1]}
+
+
+
+
+ {props.isDNAInvalid ? (
+
+
+ The occupant's DNA structure is ruined beyond
+ recognition, please insert a subject with an intact DNA structure.
+
+ ) : (
+
+
+
+
+
+ {data.occupant.uniqueEnzymes ? (
+ data.occupant.uniqueEnzymes
+ ) : (
+
+
+ Unknown
+
+ )}
+
+
+ )}
+ >
+ ) : (
+ Cell unoccupied.
+ )}
+
+ );
+};
diff --git a/tgui/packages/tgui/interfaces/DNAModifier/constants.ts b/tgui/packages/tgui/interfaces/DNAModifier/constants.ts
new file mode 100644
index 00000000000..d247c8a51ac
--- /dev/null
+++ b/tgui/packages/tgui/interfaces/DNAModifier/constants.ts
@@ -0,0 +1,14 @@
+export const stats: string[][] = [
+ ['good', 'Alive'],
+ ['average', 'Unconscious'],
+ ['bad', 'DEAD'],
+];
+
+export const operations: string[][] = [
+ ['ui', 'Modify U.I.', 'dna'],
+ ['se', 'Modify S.E.', 'dna'],
+ ['buffer', 'Transfer Buffers', 'syringe'],
+ ['rejuvenators', 'Rejuvenators', 'flask'],
+];
+
+export const rejuvenatorsDoses: number[] = [5, 10, 20, 30, 50];
diff --git a/tgui/packages/tgui/interfaces/DNAModifier/index.tsx b/tgui/packages/tgui/interfaces/DNAModifier/index.tsx
new file mode 100644
index 00000000000..220f72de667
--- /dev/null
+++ b/tgui/packages/tgui/interfaces/DNAModifier/index.tsx
@@ -0,0 +1,29 @@
+import { useBackend } from '../../backend';
+import { Window } from '../../layouts';
+import { ComplexModal } from '../common/ComplexModal';
+import { DNAModifierIrradiating } from './DNAModifierIrradiating';
+import { DNAModifierMain } from './DNAModifierMain';
+import { DNAModifierOccupant } from './DNAModifierOccupant';
+import { Data } from './types';
+
+export const DNAModifier = (props) => {
+ const { data } = useBackend();
+
+ const { irradiating, occupant } = data;
+
+ const isDNAInvalid: boolean =
+ !occupant.isViableSubject ||
+ !occupant.uniqueIdentity ||
+ !occupant.structuralEnzymes;
+
+ return (
+
+
+ {irradiating && }
+
+
+
+
+
+ );
+};
diff --git a/tgui/packages/tgui/interfaces/DNAModifier/types.ts b/tgui/packages/tgui/interfaces/DNAModifier/types.ts
new file mode 100644
index 00000000000..73907fe9e70
--- /dev/null
+++ b/tgui/packages/tgui/interfaces/DNAModifier/types.ts
@@ -0,0 +1,54 @@
+import { BooleanLike } from 'common/react';
+
+export type Data = {
+ selectedMenuKey: string;
+ locked: BooleanLike;
+ hasOccupant: BooleanLike;
+ isInjectorReady: BooleanLike;
+ hasDisk: BooleanLike;
+ disk: buffData;
+ buffers: buffData[];
+ radiationIntensity: number;
+ radiationDuration: number;
+ irradiating: number;
+ dnaBlockSize: number;
+ selectedUIBlock: number;
+ selectedUISubBlock: number;
+ selectedSEBlock: number;
+ selectedSESubBlock: number;
+ selectedUITarget: number;
+ selectedUITargetHex: string;
+ occupant: {
+ name: string | null;
+ stat: number | null;
+ isViableSubject: BooleanLike | null;
+ health: number | null;
+ maxHealth: number | null;
+ minHealth: number | null;
+ uniqueEnzymes: string | null;
+ uniqueIdentity: string | null;
+ structuralEnzymes: string | null;
+ radiationLevel: number | null;
+ };
+ isBeakerLoaded: BooleanLike;
+ beakerLabel: string | null;
+ beakerVolume: number;
+ modal: modalData;
+};
+
+type modalData = {
+ id: string;
+ text: string;
+ args: {
+ id: string;
+ };
+ modal_type: string;
+};
+
+export type buffData = {
+ data: number[] | null;
+ owner: string | null;
+ label: string | null;
+ type: string | null;
+ ue: BooleanLike;
+};
diff --git a/tgui/packages/tgui/interfaces/DestinationTagger.jsx b/tgui/packages/tgui/interfaces/DestinationTagger.jsx
deleted file mode 100644
index ee46aa1fba8..00000000000
--- a/tgui/packages/tgui/interfaces/DestinationTagger.jsx
+++ /dev/null
@@ -1,31 +0,0 @@
-import { useBackend } from '../backend';
-import { Button, Flex, Section } from '../components';
-import { Window } from '../layouts';
-
-export const DestinationTagger = (props) => {
- const { act, data } = useBackend();
-
- const { currTag, taggerLocs } = data;
-
- return (
-
-
-
-
- {taggerLocs.sort().map((tag) => (
-
- act('set_tag', { tag: tag })}
- >
- {tag}
-
-
- ))}
-
-
-
-
- );
-};
diff --git a/tgui/packages/tgui/interfaces/DestinationTagger.tsx b/tgui/packages/tgui/interfaces/DestinationTagger.tsx
new file mode 100644
index 00000000000..a55eef5fc48
--- /dev/null
+++ b/tgui/packages/tgui/interfaces/DestinationTagger.tsx
@@ -0,0 +1,50 @@
+import { useBackend } from '../backend';
+import { Button, Flex, Section } from '../components';
+import { Window } from '../layouts';
+
+type Data = {
+ currTag: string;
+ taggerLevels: { z: number; location: string }[];
+ taggerLocs: { tag: string; level: number }[];
+};
+
+export const DestinationTagger = (props) => {
+ const { act, data } = useBackend();
+
+ const { currTag, taggerLevels = [], taggerLocs } = data;
+
+ const unique_levels = taggerLevels.filter((obj, index) => {
+ return index === taggerLevels.findIndex((o) => obj.location === o.location);
+ });
+
+ return (
+
+
+
+ {unique_levels.map((level) => (
+
+
+ {taggerLocs.map(
+ (tag) =>
+ level.z === tag.level && (
+
+ act('set_tag', { tag: tag.tag })}
+ >
+ {tag.tag}
+
+
+ ),
+ )}
+
+
+ ))}
+
+
+
+ );
+};
diff --git a/tgui/packages/tgui/interfaces/DiseaseSplicer.jsx b/tgui/packages/tgui/interfaces/DiseaseSplicer.tsx
similarity index 76%
rename from tgui/packages/tgui/interfaces/DiseaseSplicer.jsx
rename to tgui/packages/tgui/interfaces/DiseaseSplicer.tsx
index 4db3567cffe..f962aa1628e 100644
--- a/tgui/packages/tgui/interfaces/DiseaseSplicer.jsx
+++ b/tgui/packages/tgui/interfaces/DiseaseSplicer.tsx
@@ -1,9 +1,24 @@
+import { BooleanLike } from 'common/react';
+
import { useBackend } from '../backend';
import { Box, Button, LabeledList, ProgressBar, Section } from '../components';
import { Window } from '../layouts';
+type Data = {
+ dish_inserted: BooleanLike;
+ buffer: { name: string; stage: number } | null;
+ species_buffer: string | null;
+ busy: string | null;
+ growth: number;
+ effects:
+ | { name: string; stage: number; reference: string; badness: number }[]
+ | null;
+ info: string;
+ affected_species: string[] | null;
+};
+
export const DiseaseSplicer = (props) => {
- const { act, data } = useBackend();
+ const { act, data } = useBackend();
const { busy } = data;
@@ -28,7 +43,7 @@ export const DiseaseSplicer = (props) => {
};
const DiseaseSplicerVirusDish = (props) => {
- const { act, data } = useBackend();
+ const { act, data } = useBackend();
const { dish_inserted, effects, info, growth, affected_species } = data;
@@ -60,12 +75,12 @@ const DiseaseSplicerVirusDish = (props) => {
{info ? (
-
+
) : (
<>
-
+
{(effects &&
effects.map((effect) => (
@@ -74,23 +89,25 @@ const DiseaseSplicerVirusDish = (props) => {
))) || No virus sample loaded.}
-
- {!affected_species || !affected_species.length ? 'None' : null}
- {affected_species.sort().join(', ')}
+
+ {!affected_species || !affected_species.length
+ ? 'None'
+ : affected_species.sort().join(', ')}
-
+
CAUTION: Reverse engineering will destroy the viral sample.
- {effects.map((e) => (
- act('grab', { grab: e.reference })}
- >
- {e.stage}
-
- ))}
+ {effects &&
+ effects.map((e) => (
+ act('grab', { grab: e.reference })}
+ >
+ {e.stage}
+
+ ))}
act('affected_species')}>
Species
@@ -102,18 +119,9 @@ const DiseaseSplicerVirusDish = (props) => {
};
const DiseaseSplicerStorage = (props) => {
- const { act, data } = useBackend();
+ const { act, data } = useBackend();
- const {
- dish_inserted,
- buffer,
- species_buffer,
- effects,
- info,
- growth,
- affected_species,
- busy,
- } = data;
+ const { buffer, species_buffer, info } = data;
return (
@@ -173,7 +181,7 @@ const DiseaseSplicerStorage = (props) => {
act('splice', { splice: 5 })}
>
Splice Species
diff --git a/tgui/packages/tgui/interfaces/DishIncubator.jsx b/tgui/packages/tgui/interfaces/DishIncubator.tsx
similarity index 91%
rename from tgui/packages/tgui/interfaces/DishIncubator.jsx
rename to tgui/packages/tgui/interfaces/DishIncubator.tsx
index 004de9c1010..da30b0aaef6 100644
--- a/tgui/packages/tgui/interfaces/DishIncubator.jsx
+++ b/tgui/packages/tgui/interfaces/DishIncubator.tsx
@@ -1,3 +1,5 @@
+import { BooleanLike } from 'common/react';
+
import { useBackend } from '../backend';
import {
Box,
@@ -10,8 +12,26 @@ import {
import { formatCommaNumber } from '../format';
import { Window } from '../layouts';
+type Data = {
+ chemicals_inserted: BooleanLike;
+ dish_inserted: BooleanLike;
+ food_supply: number;
+ radiation: number;
+ toxins: number;
+ on: BooleanLike;
+ system_in_use: BooleanLike;
+ chemical_volume: number;
+ max_chemical_volume: number;
+ virus: string | null;
+ growth: number;
+ infection_rate: number;
+ analysed: BooleanLike;
+ can_breed_virus: BooleanLike;
+ blood_already_infected: string | null;
+};
+
export const DishIncubator = (props) => {
- const { act, data } = useBackend();
+ const { act, data } = useBackend();
const {
on,
diff --git a/tgui/packages/tgui/interfaces/DroneConsole.jsx b/tgui/packages/tgui/interfaces/DroneConsole.tsx
similarity index 88%
rename from tgui/packages/tgui/interfaces/DroneConsole.jsx
rename to tgui/packages/tgui/interfaces/DroneConsole.tsx
index ecc75c5e39b..2670a9cf593 100644
--- a/tgui/packages/tgui/interfaces/DroneConsole.jsx
+++ b/tgui/packages/tgui/interfaces/DroneConsole.tsx
@@ -1,9 +1,26 @@
+import { BooleanLike } from 'common/react';
+
import { useBackend } from '../backend';
import { Box, Button, Dropdown, LabeledList, Section } from '../components';
import { Window } from '../layouts';
+type Data = {
+ drones: {
+ name: string;
+ active: BooleanLike;
+ charge: number;
+ maxCharge: number;
+ loc: string;
+ ref: string;
+ }[];
+ fabricator: string;
+ fabPower: BooleanLike;
+ areas: string[];
+ selected_area: string;
+};
+
export const DroneConsole = (props) => {
- const { act, data } = useBackend();
+ const { act, data } = useBackend();
const { drones, areas, selected_area, fabricator, fabPower } = data;
@@ -36,7 +53,7 @@ export const DroneConsole = (props) => {
act('set_dcall_area', { area: val })}
diff --git a/tgui/packages/tgui/interfaces/EmbeddedController.jsx b/tgui/packages/tgui/interfaces/EmbeddedController.jsx
deleted file mode 100644
index 8c52e3514e6..00000000000
--- a/tgui/packages/tgui/interfaces/EmbeddedController.jsx
+++ /dev/null
@@ -1,757 +0,0 @@
-import { useBackend } from '../backend';
-import {
- Box,
- Button,
- Flex,
- Icon,
- LabeledList,
- ProgressBar,
- Section,
-} from '../components';
-import { Window } from '../layouts';
-import { createLogger } from '../logging';
-const logger = createLogger('fuck');
-
-// This UI uses an internal routing system for the many different variants of
-// embedded controllers in use.
-let primaryRoutes = {};
-
-/**
- * This is an all-in-one replacement for the following NanoUI Templates:
- * - advanced_airlock_console.tmpl
- * - docking_airlock_console.tmpl
- * - door_access_console.tmpl
- * - escape_pod_console.tmpl
- * - escape_pod_berth_console.tmpl
- * - multi_docking_console.tmpl
- * - phoron_airlock_console.tmpl
- * - simple_airlock_console.tmpl
- * - simple_docking_console.tmpl
- * - simple_docking_console_pod.tmpl -- Funny enough, wasn't used anywhere.
- */
-
-/**
- * Let's cover all of the attributes of `data` for this UI right here.
- * For those unfamiliar with JSDoc syntax, [param] indicates
- * an optional parameter.
- */
-
-/**
- * Interior/Exterior Door Status
- * @typedef {Object} doorStatus
- * @property {('open'|'closed')} state
- * @property {('locked'|'unlocked')} lock
- */
-
-/**
- * Dock Status
- * @typedef {('undocked'|'undocking'|'docking'|'docked')} dockStatus
- */
-
-/**
- * All possible data attributes.
- * @typedef {Object} Data
- * @property {string} internalTemplateName - to use.
- * @property {number} chamber_pressure - The current pressure of the airlock.
- * @property {boolean} processing - Whether or not the airlock is currently
- * cycling.
- * @property {number} [external_pressure] - Pressure on the "external" side.
- * @property {number} [internal_pressure] - Pressure on the "internal" side.
- * @property {boolean} [purge] - Airlock currently purging?
- * @property {boolean} [secure] - Airlock doors locked?
- * @property {doorStatus} [exterior_status] - Describes the status of the
- * exterior-side door.
- * @property {doorStatus} [interior_status] - Describes the status of the
- * interior-side door.
- *
- * @property {dockStatus} [docking_status] - Used exclusivly for "Docking" type
- * controllers, describes the state of the dock.
- * @property {boolean} [airlock_disabled] - Airlock disabled?
- * @property {boolean} [override_enabled] - Forces the shuttle to undock.
- * @property {string} [docking_codes] - The secret codes to dock a shuttle here
- * @property {string} [name] - Name of the dock.
- */
-
-/**
- * Entrypoint of the UI. This handles finding the correct route to use.
- */
-export const EmbeddedController = (props) => {
- const { act, data } = useBackend();
- const { internalTemplateName } = data;
-
- const Component = primaryRoutes[internalTemplateName];
- if (!Component) {
- throw Error(
- 'Unable to find Component for template name: ' + internalTemplateName,
- );
- }
-
- return (
-
-
-
-
-
- );
-};
-
-/** ***************************************************************************\
-* HELPER COMPONENTS *
-\******************************************************************************/
-
-/**
- * @typedef {Object} BarProp
- * @property {number} minValue - Minimum value of the bar.
- * @property {number} maxValue - Maximum value of the bar.
- * @property {number} value - Current value between min/max.
- * @property {string} label - Label next to the bar.
- * @property {string} textValue - Value in text.
- */
-
-/**
- * @typedef {Object} StatusDisplayProps
- * @property {array[BarProp]} bars - The bars to display.
- */
-
-/**
- * Used for the upper status display that is used on 90% of these UIs.
- * @param {StatusDisplayProps} props
- */
-const StatusDisplay = (props) => {
- const { bars } = props;
-
- return (
-
-
- {bars.map((bar) => (
-
-
- {bar.textValue}
-
-
- ))}
-
-
- );
-};
-
-/**
- * This is just a quick helper for most airlock controllers. They usually all
- * have the "Cycle out, cycle in, force out, force in" buttons, so we just have
- * a single component that adjusts for the mild data structure differences
- * on it's own.
- */
-const StandardControls = (props) => {
- const { data, act } = useBackend();
-
- let externalForceSafe = true;
- if (data['interior_status'] && data.interior_status.state === 'open') {
- externalForceSafe = false;
- } else if (data['external_pressure'] && data['chamber_pressure']) {
- externalForceSafe = !(
- Math.abs(data['external_pressure'] - data['chamber_pressure']) > 5
- );
- }
-
- let internalForceSafe = true;
- if (data['exterior_status'] && data.exterior_status.state === 'open') {
- internalForceSafe = false;
- } else if (data['internal_pressure'] && data['chamber_pressure']) {
- internalForceSafe = !(
- Math.abs(data['internal_pressure'] - data['chamber_pressure']) > 5
- );
- }
-
- return (
- <>
-
- act('cycle_ext')}
- >
- Cycle to Exterior
-
- act('cycle_int')}
- >
- Cycle to Interior
-
-
-
- act('force_ext')}
- >
- Force Exterior Door
-
- act('force_int')}
- >
- Force Interior Door
-
-
- >
- );
-};
-
-/**
- * This is a shared component between the EscapePodConsole
- * and the EscapePodBerthConsole. They previously had different data structures
- * but I got rid of that stupid shit.
- */
-const EscapePodStatus = (props) => {
- const { data, act } = useBackend();
-
- const statusToHtml = {
- docked: ,
- undocking: EJECTING-STAND CLEAR!,
- undocked: POD EJECTED,
- docking: INITIALIZING...,
- };
-
- let dockHatch = ERROR;
-
- if (data.exterior_status.state === 'open') {
- dockHatch = OPEN;
- } else if (data.exterior_status.lock === 'unlocked') {
- dockHatch = UNSECURED;
- } else if (data.exterior_status.lock === 'locked') {
- dockHatch = SECURED;
- }
-
- return (
-
-
-
- {statusToHtml[data.docking_status]}
-
- {dockHatch}
-
-
- );
-};
-
-/**
- * Sub-subcomponent for escape pods.
- * Just shows "ARMED" or "SYSTEMS OK" depending on armed status.
- * Keeps me from having to write like, two lines of code.
- */
-const Armed = (props) => {
- const { data, act } = useBackend();
- return data.armed ? (
- ARMED
- ) : (
- SYSTEMS OK
- );
-};
-
-/**
- * Shared controls between the berth and the pod itself.
- * Basically just external door control.
- */
-const EscapePodControls = (props) => {
- const { data, act } = useBackend();
-
- return (
-
- act('force_door')}
- >
- Force Exterior Door
-
- act('toggle_override')}
- >
- Override
-
-
- );
-};
-
-/**
- * Just a neat little helper for all the different states of dock.
- */
-const DockStatus = (props) => {
- const { data, act } = useBackend();
-
- const statusToHtml = {
- docked: DOCKED,
- docking: DOCKING,
- undocking: UNDOCKING,
- undocked: NOT IN USE,
- };
-
- let dockStatus = statusToHtml[data.docking_status];
-
- if (data.override_enabled) {
- dockStatus = (
-
- {data.docking_status.toUpperCase()}-OVERRIDE ENABLED
-
- );
- }
-
- return dockStatus;
-};
-
-/** ***************************************************************************\
-* ROUTES *
-\******************************************************************************/
-
-/**
- * Advanced airlock consoles display the external pressure,
- * the internal pressure, and the chamber pressure separately.
- * They also have a PURGE and SECURE option for safety.
- * Replaces advanced_airlock_console.tmpl
- */
-const AirlockConsoleAdvanced = (props) => {
- const { act, data } = useBackend();
-
- const color = (value) => {
- return value < 80 || value > 120
- ? 'bad'
- : value < 95 || value > 110
- ? 'average'
- : 'good';
- };
-
- const bars = [
- {
- minValue: 0,
- maxValue: 202,
- value: data.external_pressure,
- label: 'External Pressure',
- textValue: data.external_pressure + ' kPa',
- color: color,
- },
- {
- minValue: 0,
- maxValue: 202,
- value: data.chamber_pressure,
- label: 'Chamber Pressure',
- textValue: data.chamber_pressure + ' kPa',
- color: color,
- },
- {
- minValue: 0,
- maxValue: 202,
- value: data.internal_pressure,
- label: 'Internal Pressure',
- textValue: data.internal_pressure + ' kPa',
- color: color,
- },
- ];
-
- return (
- <>
-
-
-
-
- act('purge')}>
- Purge
-
- act('secure')}>
- Secure
-
-
-
- act('abort')}
- >
- Abort
-
-
-
- >
- );
-};
-primaryRoutes['AirlockConsoleAdvanced'] = AirlockConsoleAdvanced;
-
-/**
- * Simple airlock consoles are the least complicated airlock controller.
- * They show the current chamber pressure, two cycle buttons, and two
- * force door buttons. That's it.
- * Replaces simple_airlock_console.tmpl
- */
-const AirlockConsoleSimple = (props) => {
- const { act, data } = useBackend();
-
- const bars = [
- {
- minValue: 0,
- maxValue: 202,
- value: data.chamber_pressure,
- label: 'Chamber Pressure',
- textValue: data.chamber_pressure + ' kPa',
- color: (value) => {
- return value < 80 || value > 120
- ? 'bad'
- : value < 95 || value > 110
- ? 'average'
- : 'good';
- },
- },
- ];
-
- return (
- <>
-
-
-
-
- act('abort')}
- >
- Abort
-
-
-
- >
- );
-};
-primaryRoutes['AirlockConsoleSimple'] = AirlockConsoleSimple;
-
-/**
- * Phoron airlock consoles don't actually cycle *pressure*, they cycle
- * phoron, for use on transitioning to the outside environment of a phoron
- * atmosphere planet.
- * Replaces phoron_airlock_console.tmpl
- */
-const AirlockConsolePhoron = (props) => {
- const { act, data } = useBackend();
-
- const bars = [
- {
- minValue: 0,
- maxValue: 202,
- value: data.chamber_pressure,
- label: 'Chamber Pressure',
- textValue: data.chamber_pressure + ' kPa',
- color: (value) => {
- return value < 80 || value > 120
- ? 'bad'
- : value < 95 || value > 110
- ? 'average'
- : 'good';
- },
- },
- {
- minValue: 0,
- maxValue: 100,
- value: data.chamber_phoron,
- label: 'Chamber Phoron',
- textValue: data.chamber_phoron + ' mol',
- color: (value) => {
- return value > 5 ? 'bad' : value > 0.5 ? 'average' : 'good';
- },
- },
- ];
-
- return (
- <>
-
-
-
-
- act('abort')}
- >
- Abort
-
-
-
- >
- );
-};
-primaryRoutes['AirlockConsolePhoron'] = AirlockConsolePhoron;
-
-/**
- * This is a mix airlock & docking console. It lets you control the dock status
- * as well as the attached airlock.
- * Replaces docking_airlock_console.tmpl
- */
-const AirlockConsoleDocking = (props) => {
- const { act, data } = useBackend();
-
- const bars = [
- {
- minValue: 0,
- maxValue: 202,
- value: data.chamber_pressure,
- label: 'Chamber Pressure',
- textValue: data.chamber_pressure + ' kPa',
- color: (value) => {
- return value < 80 || value > 120
- ? 'bad'
- : value < 95 || value > 110
- ? 'average'
- : 'good';
- },
- },
- ];
-
- return (
- <>
- act('toggle_override')}
- >
- Override
-
- ) : null
- }
- >
-
-
-
-
-
-
- act('abort')}
- >
- Abort
-
-
-
- >
- );
-};
-primaryRoutes['AirlockConsoleDocking'] = AirlockConsoleDocking;
-
-/**
- * Simple docking consoles do not allow you to cycle the airlock. They can
- * force the doors in an emergency, but there is no facility for cycling.
- * They're primarily just there to display the status of the dock.
- * Replaces simple_docking_console.tmpl
- */
-const DockingConsoleSimple = (props) => {
- const { act, data } = useBackend();
-
- let dockHatch = ERROR;
-
- if (data.exterior_status.state === 'open') {
- dockHatch = OPEN;
- } else if (data.exterior_status.lock === 'unlocked') {
- dockHatch = UNSECURED;
- } else if (data.exterior_status.lock === 'locked') {
- dockHatch = SECURED;
- }
-
- return (
-
- act('force_door')}
- >
- Force exterior door
-
- act('toggle_override')}
- >
- Override
-
- >
- }
- >
-
-
-
-
- {dockHatch}
-
-
- );
-};
-primaryRoutes['DockingConsoleSimple'] = DockingConsoleSimple;
-
-/**
- * Shockingly, the multi docking console is the simplest docking console.
- * It has no functionality except to display the status of multiple airlocks,
- * for bigger shuttles.
- * Replaces multi_docking_console.tmpl
- */
-const DockingConsoleMulti = (props) => {
- const { data } = useBackend();
- return (
- <>
-
-
- {data.airlocks.length ? (
-
- {data.airlocks.map((airlock) => (
-
- {airlock.override_enabled ? 'OVERRIDE ENABLED' : 'STATUS OK'}
-
- ))}
-
- ) : (
-
-
-
-
- No airlocks found.
-
-
- )}
-
- >
- );
-};
-primaryRoutes['DockingConsoleMulti'] = DockingConsoleMulti;
-
-/**
- * Airlock but without anything other than doors. Separates clean rooms.
- * Replaces door_access_console.tmpl
- */
-const DoorAccessConsole = (props) => {
- const { act, data } = useBackend();
-
- let interiorOpen =
- data.interior_status.state === 'open' ||
- data.exterior_status.state === 'closed';
- let exteriorOpen =
- data.exterior_status.state === 'open' ||
- data.interior_status.state === 'closed';
-
- return (
-
- {/* Interior Button */}
- {
- act(interiorOpen ? 'cycle_ext_door' : 'force_ext');
- }}
- >
- {interiorOpen ? 'Cycle To Exterior' : 'Lock Exterior Door'}
-
- {/* Exterior Button */}
- {
- act(exteriorOpen ? 'cycle_int_door' : 'force_int');
- }}
- >
- {exteriorOpen ? 'Cycle To Interior' : 'Lock Interior Door'}
-
- >
- }
- >
-
-
- {data.exterior_status.state === 'closed' ? 'Locked' : 'Open'}
-
-
- {data.interior_status.state === 'closed' ? 'Locked' : 'Open'}
-
-
-
- );
-};
-primaryRoutes['DoorAccessConsole'] = DoorAccessConsole;
-
-/**
- * These are the least airlock-like UIs here, but they're "close enough".
- * Replaces escape_pod_console.tmpl
- */
-const EscapePodConsole = (props) => {
- const { act, data } = useBackend();
- return (
- <>
-
-
-
-
- act('manual_arm')}
- >
- ARM
-
- act('force_launch')}
- >
- MANUAL EJECT
-
-
-
- >
- );
-};
-primaryRoutes['EscapePodConsole'] = EscapePodConsole;
-
-/**
- * These are the least airlock-like UIs here, but they're "close enough".
- * Replaces escape_pod_berth_console.tmpl
- */
-const EscapePodBerthConsole = (props) => {
- const { data } = useBackend();
- return (
- <>
-
-
- >
- );
-};
-primaryRoutes['EscapePodBerthConsole'] = EscapePodBerthConsole;
diff --git a/tgui/packages/tgui/interfaces/EmbeddedController/AirlockConsoleAdvanced.tsx b/tgui/packages/tgui/interfaces/EmbeddedController/AirlockConsoleAdvanced.tsx
new file mode 100644
index 00000000000..0977fc9d54d
--- /dev/null
+++ b/tgui/packages/tgui/interfaces/EmbeddedController/AirlockConsoleAdvanced.tsx
@@ -0,0 +1,85 @@
+import { useBackend } from '../../backend';
+import { Box, Button, Section } from '../../components';
+import { StandardControls, StatusDisplay } from './EmbeddedControllerHelpers';
+import { AirlockConsoleAdvancedData } from './types';
+
+/**
+ * Advanced airlock consoles display the external pressure,
+ * the internal pressure, and the chamber pressure separately.
+ * They also have a PURGE and SECURE option for safety.
+ * Replaces advanced_airlock_console.tmpl
+ */
+export const AirlockConsoleAdvanced = (props) => {
+ const { act, data } = useBackend();
+
+ const { external_pressure, chamber_pressure, internal_pressure, processing } =
+ data;
+
+ const pressure_range = {
+ external_pressure,
+ internal_pressure,
+ chamber_pressure,
+ };
+
+ function color(value: number): string {
+ return value < 80 || value > 120
+ ? 'bad'
+ : value < 95 || value > 110
+ ? 'average'
+ : 'good';
+ }
+
+ const bars = [
+ {
+ minValue: 0,
+ maxValue: 202,
+ value: external_pressure,
+ label: 'External Pressure',
+ textValue: external_pressure + ' kPa',
+ color: color,
+ },
+ {
+ minValue: 0,
+ maxValue: 202,
+ value: chamber_pressure,
+ label: 'Chamber Pressure',
+ textValue: chamber_pressure + ' kPa',
+ color: color,
+ },
+ {
+ minValue: 0,
+ maxValue: 202,
+ value: internal_pressure,
+ label: 'Internal Pressure',
+ textValue: internal_pressure + ' kPa',
+ color: color,
+ },
+ ];
+
+ return (
+ <>
+
+
+
+
+ act('purge')}>
+ Purge
+
+ act('secure')}>
+ Secure
+
+
+
+ act('abort')}
+ >
+ Abort
+
+
+
+ >
+ );
+};
diff --git a/tgui/packages/tgui/interfaces/EmbeddedController/AirlockConsoleDocking.tsx b/tgui/packages/tgui/interfaces/EmbeddedController/AirlockConsoleDocking.tsx
new file mode 100644
index 00000000000..b17d1f08d5c
--- /dev/null
+++ b/tgui/packages/tgui/interfaces/EmbeddedController/AirlockConsoleDocking.tsx
@@ -0,0 +1,84 @@
+import { useBackend } from '../../backend';
+import { Box, Button, Section } from '../../components';
+import {
+ DockStatus,
+ StandardControls,
+ StatusDisplay,
+} from './EmbeddedControllerHelpers';
+import { AirlockConsoleDockingData } from './types';
+
+/**
+ * This is a mix airlock & docking console. It lets you control the dock status
+ * as well as the attached airlock.
+ * Replaces docking_airlock_console.tmpl
+ */
+export const AirlockConsoleDocking = (props) => {
+ const { act, data } = useBackend();
+
+ const {
+ interior_status,
+ exterior_status,
+ chamber_pressure,
+ airlock_disabled,
+ override_enabled,
+ docking_status,
+ processing,
+ } = data;
+
+ const status_range = { interior_status, exterior_status };
+
+ const bars = [
+ {
+ minValue: 0,
+ maxValue: 202,
+ value: chamber_pressure,
+ label: 'Chamber Pressure',
+ textValue: chamber_pressure + ' kPa',
+ color: (value: number) => {
+ return value < 80 || value > 120
+ ? 'bad'
+ : value < 95 || value > 110
+ ? 'average'
+ : 'good';
+ },
+ },
+ ];
+
+ return (
+ <>
+ act('toggle_override')}
+ >
+ Override
+
+ ) : null
+ }
+ >
+
+
+
+
+
+
+ act('abort')}
+ >
+ Abort
+
+
+
+ >
+ );
+};
diff --git a/tgui/packages/tgui/interfaces/EmbeddedController/AirlockConsolePhoron.tsx b/tgui/packages/tgui/interfaces/EmbeddedController/AirlockConsolePhoron.tsx
new file mode 100644
index 00000000000..860f5805838
--- /dev/null
+++ b/tgui/packages/tgui/interfaces/EmbeddedController/AirlockConsolePhoron.tsx
@@ -0,0 +1,70 @@
+import { useBackend } from '../../backend';
+import { Box, Button, Section } from '../../components';
+import { StandardControls, StatusDisplay } from './EmbeddedControllerHelpers';
+import { AirlockConsolePhoronData } from './types';
+
+/**
+ * Phoron airlock consoles don't actually cycle *pressure*, they cycle
+ * phoron, for use on transitioning to the outside environment of a phoron
+ * atmosphere planet.
+ * Replaces phoron_airlock_console.tmpl
+ */
+export const AirlockConsolePhoron = (props) => {
+ const { act, data } = useBackend();
+
+ const {
+ chamber_pressure,
+ chamber_phoron,
+ interior_status,
+ exterior_status,
+ processing,
+ } = data;
+
+ const status_range = { interior_status, exterior_status };
+
+ const bars = [
+ {
+ minValue: 0,
+ maxValue: 202,
+ value: chamber_pressure,
+ label: 'Chamber Pressure',
+ textValue: chamber_pressure + ' kPa',
+ color: (value: number) => {
+ return value < 80 || value > 120
+ ? 'bad'
+ : value < 95 || value > 110
+ ? 'average'
+ : 'good';
+ },
+ },
+ {
+ minValue: 0,
+ maxValue: 100,
+ value: chamber_phoron,
+ label: 'Chamber Phoron',
+ textValue: chamber_phoron + ' mol',
+ color: (value: number) => {
+ return value > 5 ? 'bad' : value > 0.5 ? 'average' : 'good';
+ },
+ },
+ ];
+
+ return (
+ <>
+
+
+
+
+ act('abort')}
+ >
+ Abort
+
+
+
+ >
+ );
+};
diff --git a/tgui/packages/tgui/interfaces/EmbeddedController/AirlockConsoleSimple.tsx b/tgui/packages/tgui/interfaces/EmbeddedController/AirlockConsoleSimple.tsx
new file mode 100644
index 00000000000..bed9c734727
--- /dev/null
+++ b/tgui/packages/tgui/interfaces/EmbeddedController/AirlockConsoleSimple.tsx
@@ -0,0 +1,55 @@
+import { useBackend } from '../../backend';
+import { Box, Button, Section } from '../../components';
+import { StandardControls, StatusDisplay } from './EmbeddedControllerHelpers';
+import { AirlockConsoleSimpleData } from './types';
+
+/**
+ * Simple airlock consoles are the least complicated airlock controller.
+ * They show the current chamber pressure, two cycle buttons, and two
+ * force door buttons. That's it.
+ * Replaces simple_airlock_console.tmpl
+ */
+export const AirlockConsoleSimple = (props) => {
+ const { act, data } = useBackend();
+
+ const { exterior_status, chamber_pressure, processing, interior_status } =
+ data;
+
+ const status_range = { interior_status, exterior_status };
+
+ const bars = [
+ {
+ minValue: 0,
+ maxValue: 202,
+ value: chamber_pressure,
+ label: 'Chamber Pressure',
+ textValue: chamber_pressure + ' kPa',
+ color: (value: number) => {
+ return value < 80 || value > 120
+ ? 'bad'
+ : value < 95 || value > 110
+ ? 'average'
+ : 'good';
+ },
+ },
+ ];
+
+ return (
+ <>
+
+
+
+
+ act('abort')}
+ >
+ Abort
+
+
+
+ >
+ );
+};
diff --git a/tgui/packages/tgui/interfaces/EmbeddedController/DockingConsoleMulti.tsx b/tgui/packages/tgui/interfaces/EmbeddedController/DockingConsoleMulti.tsx
new file mode 100644
index 00000000000..5f13b3c0360
--- /dev/null
+++ b/tgui/packages/tgui/interfaces/EmbeddedController/DockingConsoleMulti.tsx
@@ -0,0 +1,47 @@
+import { useBackend } from '../../backend';
+import { Flex, Icon, LabeledList, Section } from '../../components';
+import { DockStatus } from './EmbeddedControllerHelpers';
+import { DockingConsoleMultiData } from './types';
+
+/**
+ * Shockingly, the multi docking console is the simplest docking console.
+ * It has no functionality except to display the status of multiple airlocks,
+ * for bigger shuttles.
+ * Replaces multi_docking_console.tmpl
+ */
+export const DockingConsoleMulti = (props) => {
+ const { data } = useBackend();
+
+ const { docking_status } = data;
+
+ return (
+ <>
+
+
+ {data.airlocks.length ? (
+
+ {data.airlocks.map((airlock) => (
+
+ {airlock.override_enabled ? 'OVERRIDE ENABLED' : 'STATUS OK'}
+
+ ))}
+
+ ) : (
+
+
+
+
+ No airlocks found.
+
+
+ )}
+
+ >
+ );
+};
diff --git a/tgui/packages/tgui/interfaces/EmbeddedController/DockingConsoleSimple.tsx b/tgui/packages/tgui/interfaces/EmbeddedController/DockingConsoleSimple.tsx
new file mode 100644
index 00000000000..5358fc49ce9
--- /dev/null
+++ b/tgui/packages/tgui/interfaces/EmbeddedController/DockingConsoleSimple.tsx
@@ -0,0 +1,50 @@
+import { useBackend } from '../../backend';
+import { Button, LabeledList, Section } from '../../components';
+import { DockingStatus, DockStatus } from './EmbeddedControllerHelpers';
+import { DockingConsoleSimpleData } from './types';
+
+/**
+ * Simple docking consoles do not allow you to cycle the airlock. They can
+ * force the doors in an emergency, but there is no facility for cycling.
+ * They're primarily just there to display the status of the dock.
+ * Replaces simple_docking_console.tmpl
+ */
+export const DockingConsoleSimple = (props) => {
+ const { act, data } = useBackend();
+
+ const { exterior_status, override_enabled, docking_status } = data;
+
+ return (
+
+ act('force_door')}
+ >
+ Force exterior door
+
+ act('toggle_override')}
+ >
+ Override
+
+ >
+ }
+ >
+
+
+
+
+
+
+
+ );
+};
diff --git a/tgui/packages/tgui/interfaces/EmbeddedController/DoorAccessConsole.tsx b/tgui/packages/tgui/interfaces/EmbeddedController/DoorAccessConsole.tsx
new file mode 100644
index 00000000000..9de6aa36c5f
--- /dev/null
+++ b/tgui/packages/tgui/interfaces/EmbeddedController/DoorAccessConsole.tsx
@@ -0,0 +1,55 @@
+import { useBackend } from '../../backend';
+import { Button, LabeledList, Section } from '../../components';
+import { DoorAccessConsoleData } from './types';
+
+/**
+ * Airlock but without anything other than doors. Separates clean rooms.
+ * Replaces door_access_console.tmpl
+ */
+export const DoorAccessConsole = (props) => {
+ const { act, data } = useBackend();
+
+ const { interior_status, exterior_status } = data;
+
+ const interiorOpen =
+ interior_status.state === 'open' || exterior_status.state === 'closed';
+ const exteriorOpen =
+ exterior_status.state === 'open' || interior_status.state === 'closed';
+
+ return (
+
+ {/* Interior Button */}
+ {
+ act(interiorOpen ? 'cycle_ext_door' : 'force_ext');
+ }}
+ >
+ {interiorOpen ? 'Cycle To Exterior' : 'Lock Exterior Door'}
+
+ {/* Exterior Button */}
+ {
+ act(exteriorOpen ? 'cycle_int_door' : 'force_int');
+ }}
+ >
+ {exteriorOpen ? 'Cycle To Interior' : 'Lock Interior Door'}
+
+ >
+ }
+ >
+
+
+ {exterior_status.state === 'closed' ? 'Locked' : 'Open'}
+
+
+ {interior_status.state === 'closed' ? 'Locked' : 'Open'}
+
+
+
+ );
+};
diff --git a/tgui/packages/tgui/interfaces/EmbeddedController/EmbeddedControllerHelpers.tsx b/tgui/packages/tgui/interfaces/EmbeddedController/EmbeddedControllerHelpers.tsx
new file mode 100644
index 00000000000..cd5f7fb6742
--- /dev/null
+++ b/tgui/packages/tgui/interfaces/EmbeddedController/EmbeddedControllerHelpers.tsx
@@ -0,0 +1,284 @@
+import { BooleanLike } from 'common/react';
+
+import { useBackend } from '../../backend';
+import {
+ Box,
+ Button,
+ LabeledList,
+ ProgressBar,
+ Section,
+} from '../../components';
+import { status } from './types';
+
+/** ***************************************************************************\
+* HELPER COMPONENTS *
+\******************************************************************************/
+
+/**
+ * @typedef {Object} BarProp
+ * @property {number} minValue - Minimum value of the bar.
+ * @property {number} maxValue - Maximum value of the bar.
+ * @property {number} value - Current value between min/max.
+ * @property {string} label - Label next to the bar.
+ * @property {string} textValue - Value in text.
+ */
+
+/**
+ * @typedef {Object} StatusDisplayProps
+ * @property {array[BarProp]} bars - The bars to display.
+ */
+
+/**
+ * Used for the upper status display that is used on 90% of these UIs.
+ * @param {StatusDisplayProps} props
+ */
+export const StatusDisplay = (props: {
+ bars: {
+ minValue: number;
+ maxValue: number;
+ value: number;
+ label: string;
+ textValue: string;
+ color: (value: number) => string;
+ }[];
+}) => {
+ const { bars } = props;
+
+ return (
+
+
+ {bars.map((bar) => (
+
+
+ {bar.textValue}
+
+
+ ))}
+
+
+ );
+};
+
+/**
+ * This is just a quick helper for most airlock controllers. They usually all
+ * have the "Cycle out, cycle in, force out, force in" buttons, so we just have
+ * a single component that adjusts for the mild data structure differences
+ * on it's own.
+ */
+export const StandardControls = (props: {
+ status_range?:
+ | {
+ interior_status: status;
+ exterior_status: status;
+ }
+ | undefined;
+ pressure_range?:
+ | {
+ external_pressure: number;
+ internal_pressure: number;
+ chamber_pressure: number;
+ }
+ | undefined;
+ airlock_disabled?: BooleanLike;
+}) => {
+ const { act } = useBackend();
+
+ const { status_range, pressure_range, airlock_disabled } = props;
+
+ const { interior_status, exterior_status } =
+ status_range ||
+ ({} as {
+ interior_status: status;
+ exterior_status: status;
+ });
+
+ const { external_pressure, internal_pressure, chamber_pressure } =
+ pressure_range ||
+ ({} as {
+ external_pressure: number;
+ internal_pressure: number;
+ chamber_pressure: number;
+ });
+
+ let externalForceSafe = true;
+ if (interior_status && interior_status.state === 'open') {
+ externalForceSafe = false;
+ } else if (external_pressure && chamber_pressure) {
+ externalForceSafe = !(Math.abs(external_pressure - chamber_pressure) > 5);
+ }
+
+ let internalForceSafe = true;
+ if (exterior_status && exterior_status.state === 'open') {
+ internalForceSafe = false;
+ } else if (internal_pressure && chamber_pressure) {
+ internalForceSafe = !(Math.abs(internal_pressure - chamber_pressure) > 5);
+ }
+
+ return (
+ <>
+
+ act('cycle_ext')}
+ >
+ Cycle to Exterior
+
+ act('cycle_int')}
+ >
+ Cycle to Interior
+
+
+
+ act('force_ext')}
+ >
+ Force Exterior Door
+
+ act('force_int')}
+ >
+ Force Interior Door
+
+
+ >
+ );
+};
+
+/**
+ * This is a shared component between the EscapePodConsole
+ * and the EscapePodBerthConsole. They previously had different data structures
+ * but I got rid of that stupid shit.
+ */
+export const EscapePodStatus = (props: {
+ exterior_status: status;
+ docking_status: string;
+ armed: BooleanLike;
+}) => {
+ const { exterior_status, docking_status, armed } = props;
+
+ const statusToHtml = {
+ docked: ,
+ undocking: EJECTING-STAND CLEAR!,
+ undocked: POD EJECTED,
+ docking: INITIALIZING...,
+ };
+
+ return (
+
+
+
+ {statusToHtml[docking_status]}
+
+
+
+
+ );
+};
+
+export const DockingStatus = (props: { state: string }) => {
+ const { state } = props;
+
+ const dockHatch: React.JSX.Element[] = [];
+
+ dockHatch['open'] = OPEN;
+ dockHatch['unlocked'] = UNSECURED;
+ dockHatch['locked'] = SECURED;
+ return (
+
+ {dockHatch[state] || ERROR}
+
+ );
+};
+
+/**
+ * Sub-subcomponent for escape pods.
+ * Just shows "ARMED" or "SYSTEMS OK" depending on armed status.
+ * Keeps me from having to write like, two lines of code.
+ */
+const Armed = (props: { armed: BooleanLike }) => {
+ const { armed } = props;
+
+ return armed ? (
+ ARMED
+ ) : (
+ SYSTEMS OK
+ );
+};
+
+/**
+ * Shared controls between the berth and the pod itself.
+ * Basically just external door control.
+ */
+export const EscapePodControls = (props: {
+ docking_status: string;
+ override_enabled: BooleanLike;
+}) => {
+ const { act } = useBackend();
+
+ const { docking_status, override_enabled } = props;
+
+ return (
+
+ act('force_door')}
+ >
+ Force Exterior Door
+
+ act('toggle_override')}
+ >
+ Override
+
+
+ );
+};
+
+/**
+ * Just a neat little helper for all the different states of dock.
+ */
+export const DockStatus = (props: {
+ docking_status: string;
+ override_enabled: BooleanLike;
+}) => {
+ const { docking_status, override_enabled } = props;
+
+ const statusToHtml = {
+ docked: DOCKED,
+ docking: DOCKING,
+ undocking: UNDOCKING,
+ undocked: NOT IN USE,
+ };
+
+ let dockStatus = statusToHtml[docking_status];
+
+ if (override_enabled) {
+ dockStatus = (
+ {docking_status.toUpperCase()}-OVERRIDE ENABLED
+ );
+ }
+
+ return dockStatus;
+};
diff --git a/tgui/packages/tgui/interfaces/EmbeddedController/EscapePodBerthConsole.tsx b/tgui/packages/tgui/interfaces/EmbeddedController/EscapePodBerthConsole.tsx
new file mode 100644
index 00000000000..729c886f9e8
--- /dev/null
+++ b/tgui/packages/tgui/interfaces/EmbeddedController/EscapePodBerthConsole.tsx
@@ -0,0 +1,33 @@
+import { useBackend } from '../../backend';
+import { Section } from '../../components';
+import {
+ EscapePodControls,
+ EscapePodStatus,
+} from './EmbeddedControllerHelpers';
+import { EscapePodBerthConsoleData } from './types';
+
+/**
+ * These are the least airlock-like UIs here, but they're "close enough".
+ * Replaces escape_pod_berth_console.tmpl
+ */
+export const EscapePodBerthConsole = (props) => {
+ const { data } = useBackend();
+
+ const { exterior_status, docking_status, armed, override_enabled } = data;
+
+ return (
+ <>
+
+
+ >
+ );
+};
diff --git a/tgui/packages/tgui/interfaces/EmbeddedController/EscapePodConsole.tsx b/tgui/packages/tgui/interfaces/EmbeddedController/EscapePodConsole.tsx
new file mode 100644
index 00000000000..547542b8b10
--- /dev/null
+++ b/tgui/packages/tgui/interfaces/EmbeddedController/EscapePodConsole.tsx
@@ -0,0 +1,57 @@
+import { useBackend } from '../../backend';
+import { Box, Button, Section } from '../../components';
+import {
+ EscapePodControls,
+ EscapePodStatus,
+} from './EmbeddedControllerHelpers';
+import { EscapePodConsoleData } from './types';
+
+/**
+ * These are the least airlock-like UIs here, but they're "close enough".
+ * Replaces escape_pod_console.tmpl
+ */
+export const EscapePodConsole = (props) => {
+ const { act, data } = useBackend();
+
+ const {
+ exterior_status,
+ docking_status,
+ override_enabled,
+ armed,
+ can_force,
+ } = data;
+
+ return (
+ <>
+
+
+
+
+ act('manual_arm')}
+ >
+ ARM
+
+ act('force_launch')}
+ >
+ MANUAL EJECT
+
+
+
+ >
+ );
+};
diff --git a/tgui/packages/tgui/interfaces/EmbeddedController/index.tsx b/tgui/packages/tgui/interfaces/EmbeddedController/index.tsx
new file mode 100644
index 00000000000..eb4504e3510
--- /dev/null
+++ b/tgui/packages/tgui/interfaces/EmbeddedController/index.tsx
@@ -0,0 +1,105 @@
+import { useBackend } from '../../backend';
+import { Window } from '../../layouts';
+import { AirlockConsoleAdvanced } from './AirlockConsoleAdvanced';
+import { AirlockConsoleDocking } from './AirlockConsoleDocking';
+import { AirlockConsolePhoron } from './AirlockConsolePhoron';
+import { AirlockConsoleSimple } from './AirlockConsoleSimple';
+import { DockingConsoleMulti } from './DockingConsoleMulti';
+import { DockingConsoleSimple } from './DockingConsoleSimple';
+import { DoorAccessConsole } from './DoorAccessConsole';
+import { EscapePodBerthConsole } from './EscapePodBerthConsole';
+import { EscapePodConsole } from './EscapePodConsole';
+import { Data } from './types';
+
+/**
+ * This is an all-in-one replacement for the following NanoUI Templates:
+ * - advanced_airlock_console.tmpl
+ * - docking_airlock_console.tmpl
+ * - door_access_console.tmpl
+ * - escape_pod_console.tmpl
+ * - escape_pod_berth_console.tmpl
+ * - multi_docking_console.tmpl
+ * - phoron_airlock_console.tmpl
+ * - simple_airlock_console.tmpl
+ * - simple_docking_console.tmpl
+ * - simple_docking_console_pod.tmpl -- Funny enough, wasn't used anywhere.
+ */
+
+/**
+ * Let's cover all of the attributes of `data` for this UI right here.
+ * For those unfamiliar with JSDoc syntax, [param] indicates
+ * an optional parameter.
+ */
+
+/**
+ * Interior/Exterior Door Status
+ * @typedef {Object} doorStatus
+ * @property {('open'|'closed')} state
+ * @property {('locked'|'unlocked')} lock
+ */
+
+/**
+ * Dock Status
+ * @typedef {('undocked'|'undocking'|'docking'|'docked')} dockStatus
+ */
+
+/**
+ * All possible data attributes.
+ * @typedef {Object} Data
+ * @property {string} internalTemplateName - to use.
+ * @property {number} chamber_pressure - The current pressure of the airlock.
+ * @property {boolean} processing - Whether or not the airlock is currently
+ * cycling.
+ * @property {number} [external_pressure] - Pressure on the "external" side.
+ * @property {number} [internal_pressure] - Pressure on the "internal" side.
+ * @property {boolean} [purge] - Airlock currently purging?
+ * @property {boolean} [secure] - Airlock doors locked?
+ * @property {doorStatus} [exterior_status] - Describes the status of the
+ * exterior-side door.
+ * @property {doorStatus} [interior_status] - Describes the status of the
+ * interior-side door.
+ *
+ * @property {dockStatus} [docking_status] - Used exclusivly for "Docking" type
+ * controllers, describes the state of the dock.
+ * @property {boolean} [airlock_disabled] - Airlock disabled?
+ * @property {boolean} [override_enabled] - Forces the shuttle to undock.
+ * @property {string} [docking_codes] - The secret codes to dock a shuttle here
+ * @property {string} [name] - Name of the dock.
+ */
+
+/**
+ * Entrypoint of the UI. This handles finding the correct route to use.
+ */
+export const EmbeddedController = (props) => {
+ const { data } = useBackend();
+ const { internalTemplateName } = data;
+
+ /** ***************************************************************************\
+ * ROUTES *
+ \******************************************************************************/
+
+ const primaryRoutes: Record = {};
+
+ primaryRoutes['AirlockConsoleAdvanced'] = ;
+ primaryRoutes['AirlockConsoleSimple'] = ;
+ primaryRoutes['AirlockConsolePhoron'] = ;
+ primaryRoutes['AirlockConsoleDocking'] = ;
+ primaryRoutes['DockingConsoleSimple'] = ;
+ primaryRoutes['DockingConsoleMulti'] = ;
+ primaryRoutes['DoorAccessConsole'] = ;
+ primaryRoutes['EscapePodConsole'] = ;
+ primaryRoutes['EscapePodBerthConsole'] = ;
+
+ const Component: React.JSX.Element = primaryRoutes[internalTemplateName];
+ if (!Component) {
+ throw Error(
+ 'Unable to find Component for template name: ' + internalTemplateName,
+ );
+ }
+
+ return (
+
+ {Component}
+
+ );
+};
diff --git a/tgui/packages/tgui/interfaces/EmbeddedController/types.ts b/tgui/packages/tgui/interfaces/EmbeddedController/types.ts
new file mode 100644
index 00000000000..d6b9c374873
--- /dev/null
+++ b/tgui/packages/tgui/interfaces/EmbeddedController/types.ts
@@ -0,0 +1,82 @@
+import { BooleanLike } from 'common/react';
+
+export type Data = { internalTemplateName: string };
+
+export type AirlockConsoleDockingData = {
+ chamber_pressure: number;
+ exterior_status: status;
+ interior_status: status;
+ processing: BooleanLike;
+ docking_status: string;
+ airlock_disabled: BooleanLike;
+ override_enabled: BooleanLike;
+ docking_codes?: string;
+ name?: string;
+ internalTemplateName: string;
+};
+
+export type DockingConsoleSimpleData = {
+ docking_status: string;
+ override_enabled: BooleanLike;
+ exterior_status: status;
+ internalTemplateName: string;
+};
+
+export type EscapePodBerthConsoleData = {
+ docking_status: string;
+ override_enabled: BooleanLike;
+ exterior_status: status;
+ armed: BooleanLike;
+ internalTemplateName: string;
+};
+
+export type DockingConsoleMultiData = {
+ docking_status: string;
+ airlocks: { name: string; override_enabled: BooleanLike }[];
+ internalTemplateName: string;
+};
+
+export type AirlockConsoleAdvancedData = {
+ chamber_pressure: number;
+ external_pressure: number;
+ internal_pressure: number;
+ processing: BooleanLike;
+ purge: BooleanLike;
+ secure: BooleanLike;
+ internalTemplateName: string;
+};
+
+export type AirlockConsoleSimpleData = {
+ chamber_pressure: number;
+ exterior_status: status;
+ interior_status: status;
+ processing: BooleanLike;
+ internalTemplateName: string;
+};
+
+export type AirlockConsolePhoronData = {
+ chamber_pressure: number;
+ chamber_phoron: number;
+ exterior_status: status;
+ interior_status: status;
+ processing: BooleanLike;
+ internalTemplateName: string;
+};
+
+export type DoorAccessConsoleData = {
+ exterior_status: status;
+ interior_status: status;
+ processing: BooleanLike;
+ internalTemplateName: string;
+};
+
+export type EscapePodConsoleData = {
+ docking_status: string;
+ override_enabled: BooleanLike;
+ exterior_status: status;
+ can_force: BooleanLike;
+ armed: BooleanLike;
+ internalTemplateName: string;
+};
+
+export type status = { state: string; lock: string };
diff --git a/tgui/packages/tgui/interfaces/Farmbot.jsx b/tgui/packages/tgui/interfaces/Farmbot.tsx
similarity index 87%
rename from tgui/packages/tgui/interfaces/Farmbot.jsx
rename to tgui/packages/tgui/interfaces/Farmbot.tsx
index e5a66ec96d5..f61df965234 100644
--- a/tgui/packages/tgui/interfaces/Farmbot.jsx
+++ b/tgui/packages/tgui/interfaces/Farmbot.tsx
@@ -1,9 +1,25 @@
+import { BooleanLike } from 'common/react';
+
import { useBackend } from '../backend';
import { Box, Button, LabeledList, ProgressBar, Section } from '../components';
import { Window } from '../layouts';
+type Data = {
+ on: BooleanLike;
+ tank: BooleanLike;
+ tankVolume: number;
+ tankMaxVolume: number;
+ locked: BooleanLike;
+ waters_trays: BooleanLike;
+ refills_water: BooleanLike;
+ uproots_weeds: BooleanLike;
+ replaces_nutriment: BooleanLike;
+ collects_produce: BooleanLike;
+ removes_dead: BooleanLike;
+};
+
export const Farmbot = (props) => {
- const { act, data } = useBackend();
+ const { act, data } = useBackend();
const {
on,
@@ -48,7 +64,7 @@ export const Farmbot = (props) => {
{(!locked && (
-
+
-
+
-
+
{/* VOREStation Edit: No automatic hydroponics with the lagbot */}
- {/*
+ {/*
{
- const { data } = useBackend();
+ const { data } = useBackend();
const { authenticated, copyItem } = data;
- let variableHeight = 340;
+ let variableHeight: number = 340;
if (copyItem) {
variableHeight = 358;
}
@@ -37,10 +52,9 @@ export const Fax = (props) => {
};
export const FaxContent = (props) => {
- const { act, data } = useBackend();
+ const { act, data } = useBackend();
- const { bossName, copyItem, cooldown, destination, adminDepartments } = data;
- const staffRequestDepartment = new Set(adminDepartments);
+ const { bossName, copyItem, cooldown, destination } = data;
return (
@@ -86,7 +100,7 @@ export const FaxContent = (props) => {
};
const RemoveItem = (props) => {
- const { act, data } = useBackend();
+ const { act, data } = useBackend();
const { copyItem } = data;
@@ -104,10 +118,10 @@ const RemoveItem = (props) => {
};
const AutomatedStaffRequest = (props) => {
- const { act, data } = useBackend();
+ const { act, data } = useBackend();
const { adminDepartments, destination, copyItem } = data;
- const staffRequestDepartment = new Set(adminDepartments);
+ const staffRequestDepartment: Set = new Set(adminDepartments);
let flexiblePadding = '1rem';
if (copyItem) {
diff --git a/tgui/packages/tgui/interfaces/FileCabinet.jsx b/tgui/packages/tgui/interfaces/FileCabinet.tsx
similarity index 78%
rename from tgui/packages/tgui/interfaces/FileCabinet.jsx
rename to tgui/packages/tgui/interfaces/FileCabinet.tsx
index c799575b74b..b8deaa03c52 100644
--- a/tgui/packages/tgui/interfaces/FileCabinet.jsx
+++ b/tgui/packages/tgui/interfaces/FileCabinet.tsx
@@ -4,13 +4,17 @@ import { useBackend } from '../backend';
import { Button, Section } from '../components';
import { Window } from '../layouts';
+type Data = { contents: content[] };
+
+type content = { name: string; ref: string };
+
export const FileCabinet = (props) => {
- const { act, data } = useBackend();
+ const { act, data } = useBackend();
const { contents } = data;
// Wow, the filing cabinets sort themselves in 2320.
- const sortedContents = sortBy((val) => val.name)(contents || []);
+ const sortedContents = sortBy((val: content) => val.name)(contents || []);
return (
diff --git a/tgui/packages/tgui/interfaces/Floorbot.jsx b/tgui/packages/tgui/interfaces/Floorbot.tsx
similarity index 89%
rename from tgui/packages/tgui/interfaces/Floorbot.jsx
rename to tgui/packages/tgui/interfaces/Floorbot.tsx
index eac49797495..7326902fa4f 100644
--- a/tgui/packages/tgui/interfaces/Floorbot.jsx
+++ b/tgui/packages/tgui/interfaces/Floorbot.tsx
@@ -1,3 +1,5 @@
+import { BooleanLike } from 'common/react';
+
import { useBackend } from '../backend';
import {
AnimatedNumber,
@@ -8,8 +10,21 @@ import {
} from '../components';
import { Window } from '../layouts';
+type Data = {
+ on: BooleanLike;
+ open: BooleanLike;
+ locked: BooleanLike;
+ vocal: BooleanLike;
+ amount: number;
+ possible_bmode: string[];
+ improvefloors: BooleanLike;
+ eattiles: BooleanLike;
+ maketiles: BooleanLike;
+ bmode: string | null;
+};
+
export const Floorbot = (props) => {
- const { act, data } = useBackend();
+ const { act, data } = useBackend();
const {
on,
@@ -96,8 +111,7 @@ export const Floorbot = (props) => {
act('bridgemode', { dir: val })}
/>
diff --git a/tgui/packages/tgui/interfaces/GasPump.jsx b/tgui/packages/tgui/interfaces/GasPump.tsx
similarity index 79%
rename from tgui/packages/tgui/interfaces/GasPump.jsx
rename to tgui/packages/tgui/interfaces/GasPump.tsx
index ab29ba9d21c..7242932cea9 100644
--- a/tgui/packages/tgui/interfaces/GasPump.jsx
+++ b/tgui/packages/tgui/interfaces/GasPump.tsx
@@ -1,3 +1,6 @@
+import { toFixed } from 'common/math';
+import { BooleanLike } from 'common/react';
+
import { useBackend } from '../backend';
import {
AnimatedNumber,
@@ -9,8 +12,16 @@ import {
} from '../components';
import { Window } from '../layouts';
+type Data = {
+ on: BooleanLike;
+ pressure_set: number;
+ last_flow_rate: number;
+ last_power_draw: number;
+ max_power_draw: number;
+};
+
export const GasPump = (props) => {
- const { act, data } = useBackend();
+ const { act, data } = useBackend();
const { on, pressure_set, last_flow_rate, last_power_draw, max_power_draw } =
data;
@@ -21,7 +32,10 @@ export const GasPump = (props) => {
- L/s
+ toFixed(value, 1) + ' L/s'}
+ />
{
}
>
-
+
act('set_press', { press: 'min' })}
@@ -67,7 +81,7 @@ export const GasPump = (props) => {
- {pressure_set / 100} kPa
+ {toFixed(pressure_set / 100, 2)} kPa
diff --git a/tgui/packages/tgui/interfaces/GasTemperatureSystem.jsx b/tgui/packages/tgui/interfaces/GasTemperatureSystem.tsx
similarity index 83%
rename from tgui/packages/tgui/interfaces/GasTemperatureSystem.jsx
rename to tgui/packages/tgui/interfaces/GasTemperatureSystem.tsx
index 14ea4321d45..1291bfa1e11 100644
--- a/tgui/packages/tgui/interfaces/GasTemperatureSystem.jsx
+++ b/tgui/packages/tgui/interfaces/GasTemperatureSystem.tsx
@@ -1,4 +1,5 @@
-import { round } from 'common/math';
+import { toFixed } from 'common/math';
+import { BooleanLike } from 'common/react';
import { useBackend } from '../backend';
import {
@@ -12,8 +13,19 @@ import {
} from '../components';
import { Window } from '../layouts';
+type Data = {
+ on: BooleanLike;
+ gasPressure: number;
+ gasTemperature: number;
+ minGasTemperature: number;
+ maxGasTemperature: number;
+ targetGasTemperature: number;
+ powerSetting: number;
+ gasTemperatureClass: string;
+};
+
export const GasTemperatureSystem = (props) => {
- const { act, data } = useBackend();
+ const { act, data } = useBackend();
const {
on,
@@ -27,7 +39,7 @@ export const GasTemperatureSystem = (props) => {
} = data;
return (
-
+
{
maxValue={maxGasTemperature}
fillValue={gasTemperature}
value={targetGasTemperature}
- format={(value) => gasTemperature + ' / ' + round(value)}
+ format={(value) => gasTemperature + ' / ' + toFixed(value)}
unit="K"
color={gasTemperatureClass}
onChange={(e, val) => act('setGasTemperature', { temp: val })}
diff --git a/tgui/packages/tgui/interfaces/Gps.jsx b/tgui/packages/tgui/interfaces/Gps.tsx
similarity index 81%
rename from tgui/packages/tgui/interfaces/Gps.jsx
rename to tgui/packages/tgui/interfaces/Gps.tsx
index ee734d82736..5730bee979a 100644
--- a/tgui/packages/tgui/interfaces/Gps.jsx
+++ b/tgui/packages/tgui/interfaces/Gps.tsx
@@ -1,6 +1,9 @@
+// Currently not used!
+
import { map, sortBy } from 'common/collections';
import { flow } from 'common/fp';
import { clamp } from 'common/math';
+import { BooleanLike } from 'common/react';
import { vecLength, vecSubtract } from 'common/vector';
import { useBackend } from '../backend';
@@ -9,31 +12,44 @@ import { Window } from '../layouts';
const coordsToVec = (coords) => map(parseFloat)(coords.split(', '));
+type Data = {
+ currentArea: string;
+ power: BooleanLike;
+ tag: string;
+ updating: BooleanLike;
+ currentCoords: string; // "x, y, z"
+ globalmode: BooleanLike;
+ signals: signal[];
+};
+
+type signal = {
+ entrytag: string;
+ coords: string;
+ dist: number;
+ degrees: number;
+};
+
export const Gps = (props) => {
- const { act, data } = useBackend();
- const {
- currentArea,
- currentCoords,
- currentCoordsText,
- globalmode,
- power,
- tag,
- updating,
- } = data;
+ const { act, data } = useBackend();
+ const { currentArea, currentCoords, globalmode, power, tag, updating } = data;
const signals = flow([
- map((signal, index) => {
+ map((signal: signal, index) => {
// Calculate distance to the target. BYOND distance is capped to 127,
// that's why we roll our own calculations here.
const dist =
signal.dist &&
- Math.round(vecLength(vecSubtract(currentCoords, signal.coords)));
+ Math.round(
+ vecLength(
+ vecSubtract(coordsToVec(currentCoords), coordsToVec(signal.coords)),
+ ),
+ );
return { ...signal, dist, index };
}),
sortBy(
// Signals with distance metric go first
- (signal) => signal.dist === undefined,
+ (signal: signal) => signal.dist === undefined,
// Sort alphabetically
- (signal) => signal.entrytag,
+ (signal: signal) => signal.entrytag,
),
])(data.signals || []);
return (
@@ -81,7 +97,7 @@ export const Gps = (props) => {
<>
- {currentArea} ({currentCoordsText})
+ {currentArea} ({currentCoords})
@@ -116,7 +132,7 @@ export const Gps = (props) => {
)}
{signal.dist !== undefined && signal.dist + 'm'}
- {signal.coordsText}
+ {signal.coords}
))}
diff --git a/tgui/packages/tgui/interfaces/GravityGenerator.jsx b/tgui/packages/tgui/interfaces/GravityGenerator.tsx
similarity index 85%
rename from tgui/packages/tgui/interfaces/GravityGenerator.jsx
rename to tgui/packages/tgui/interfaces/GravityGenerator.tsx
index 9dec3c52899..15d3ee9c881 100644
--- a/tgui/packages/tgui/interfaces/GravityGenerator.jsx
+++ b/tgui/packages/tgui/interfaces/GravityGenerator.tsx
@@ -1,11 +1,21 @@
+import { BooleanLike } from 'common/react';
+
import { useBackend } from '../backend';
import { Button, LabeledList, Section } from '../components';
import { Window } from '../layouts';
-export const GravityGenerator = (props) => {
- const { act, data } = useBackend();
+type Data = {
+ breaker: BooleanLike;
+ charge_count: number;
+ charging_state: number;
+ on: BooleanLike;
+ operational: BooleanLike;
+};
- const { breaker, charge_count, charging_state, on, operational } = data;
+export const GravityGenerator = (props) => {
+ const { act, data } = useBackend();
+
+ const { breaker, charge_count } = data;
let genstatus = 'Offline';
if (breaker && charge_count < 100) {
diff --git a/tgui/packages/tgui/interfaces/GuestPass.jsx b/tgui/packages/tgui/interfaces/GuestPass.tsx
similarity index 82%
rename from tgui/packages/tgui/interfaces/GuestPass.jsx
rename to tgui/packages/tgui/interfaces/GuestPass.tsx
index 89538442d52..82e2cf0bbb2 100644
--- a/tgui/packages/tgui/interfaces/GuestPass.jsx
+++ b/tgui/packages/tgui/interfaces/GuestPass.tsx
@@ -1,15 +1,29 @@
/* eslint react/no-danger: "off" */
import { sortBy } from 'common/collections';
+import { BooleanLike } from 'common/react';
import { useBackend } from '../backend';
import { Box, Button, LabeledList, Section } from '../components';
import { Window } from '../layouts';
-export const GuestPass = (props) => {
- const { act, data } = useBackend();
+type Data = {
+ access: number[] | null;
+ area: area[];
+ giver: string | null;
+ giveName: string;
+ reason: String;
+ duration: number;
+ mode: BooleanLike;
+ log: string[];
+ uid: number;
+};
- const { access, area, giver, giveName, reason, duration, mode, log, uid } =
- data;
+type area = { area: string; area_name: string; on: BooleanLike };
+
+export const GuestPass = (props) => {
+ const { act, data } = useBackend();
+
+ const { area, giver, giveName, reason, duration, mode, log, uid } = data;
return (
@@ -30,7 +44,7 @@ export const GuestPass = (props) => {
act('print')} fluid mb={1}>
Print
-
+
{/* These are internally generated only. */}
{(log.length &&
log.map((l) => (
@@ -66,8 +80,8 @@ export const GuestPass = (props) => {
act('issue')}>
Issue Pass
-
- {sortBy((a) => a.area_name)(area).map((a) => (
+
+ {sortBy((a: area) => a.area_name)(area).map((a) => (
{
- const { act, data } = useBackend();
+ const { act, data } = useBackend();
const {
supportedPrograms,
diff --git a/tgui/packages/tgui/interfaces/ICAssembly.jsx b/tgui/packages/tgui/interfaces/ICAssembly.tsx
similarity index 77%
rename from tgui/packages/tgui/interfaces/ICAssembly.jsx
rename to tgui/packages/tgui/interfaces/ICAssembly.tsx
index 6bd079efcd6..8672bbf175b 100644
--- a/tgui/packages/tgui/interfaces/ICAssembly.jsx
+++ b/tgui/packages/tgui/interfaces/ICAssembly.tsx
@@ -1,4 +1,4 @@
-import { round } from 'common/math';
+import { toFixed } from 'common/math';
import { useBackend } from '../backend';
import {
@@ -12,8 +12,22 @@ import {
import { formatPower } from '../format';
import { Window } from '../layouts';
+type Data = {
+ total_parts: number;
+ max_components: number;
+ total_complexity: number;
+ max_complexity: number;
+ battery_charge: number;
+ battery_max: number;
+ net_power: number;
+ unremovable_circuits: circuit[];
+ removable_circuits: circuit[];
+};
+
+type circuit = { name: string; ref: string };
+
export const ICAssembly = (props) => {
- const { act, data } = useBackend();
+ const { data } = useBackend();
const {
total_parts,
@@ -42,8 +56,12 @@ export const ICAssembly = (props) => {
value={total_parts / max_components}
maxValue={1}
>
- {total_parts} / {max_components} (
- {round((total_parts / max_components) * 100, 1)}%)
+ {total_parts +
+ ' / ' +
+ max_components +
+ ' (' +
+ toFixed((total_parts / max_components) * 100, 1) +
+ '%)'}
@@ -56,8 +74,12 @@ export const ICAssembly = (props) => {
value={total_complexity / max_complexity}
maxValue={1}
>
- {total_complexity} / {max_complexity} (
- {round((total_complexity / max_complexity) * 100, 1)}%)
+ {total_complexity +
+ ' / ' +
+ max_complexity +
+ ' (' +
+ toFixed((total_complexity / max_complexity) * 100, 1) +
+ '%)'}
@@ -71,8 +93,12 @@ export const ICAssembly = (props) => {
value={battery_charge / battery_max}
maxValue={1}
>
- {battery_charge} / {battery_max} (
- {round((battery_charge / battery_max) * 100, 1)}%)
+ {battery_charge +
+ ' / ' +
+ battery_max +
+ ' (' +
+ toFixed((battery_charge / battery_max) * 100, 1) +
+ '%)'}
)) || No cell detected.}
@@ -105,7 +131,7 @@ export const ICAssembly = (props) => {
);
};
-const ICAssemblyCircuits = (props) => {
+const ICAssemblyCircuits = (props: { title: string; circuits: circuit[] }) => {
const { act } = useBackend();
const { title, circuits } = props;
diff --git a/tgui/packages/tgui/interfaces/ICCircuit.jsx b/tgui/packages/tgui/interfaces/ICCircuit.tsx
similarity index 82%
rename from tgui/packages/tgui/interfaces/ICCircuit.jsx
rename to tgui/packages/tgui/interfaces/ICCircuit.tsx
index e47fdf934e9..35ce453179e 100644
--- a/tgui/packages/tgui/interfaces/ICCircuit.jsx
+++ b/tgui/packages/tgui/interfaces/ICCircuit.tsx
@@ -1,3 +1,4 @@
+import { BooleanLike } from 'common/react';
import { decodeHtmlEntities } from 'common/string';
import { useBackend } from '../backend';
@@ -5,14 +6,48 @@ import { Box, Button, Flex, LabeledList, Section } from '../components';
import { formatPower } from '../format';
import { Window } from '../layouts';
+type Data = {
+ name: string;
+ desc: string;
+ displayed_name: string;
+ removable: BooleanLike;
+ complexity: number;
+ power_draw_idle: number;
+ power_draw_per_use: number;
+ extended_desc: string | null;
+ inputs: pin[];
+ outputs: pin[];
+ activators: activator[];
+};
+
+type pin = {
+ type: string;
+ name: string;
+ data: string;
+ ref: string;
+ linked: link[];
+};
+
+type activator = {
+ ref: string;
+ name: string;
+ pulse_out: string;
+ linked: link[];
+};
+
+type link = {
+ ref: string;
+ name: string;
+ holder_ref: string;
+ holder_name: string;
+};
+
export const ICCircuit = (props) => {
- const { act, data } = useBackend();
+ const { act, data } = useBackend();
const {
- name,
desc,
displayed_name,
- removable,
complexity,
power_draw_idle,
power_draw_per_use,
@@ -23,7 +58,7 @@ export const ICCircuit = (props) => {
} = data;
return (
-
+
{
);
};
-const ICIODisplay = (props) => {
+const ICIODisplay = (props: { list: pin[] }) => {
const { act } = useBackend();
const { list } = props;
@@ -118,7 +153,7 @@ const ICIODisplay = (props) => {
));
};
-const ICLinkDisplay = (props) => {
+const ICLinkDisplay = (props: { pin: activator | pin }) => {
const { act } = useBackend();
const { pin } = props;
diff --git a/tgui/packages/tgui/interfaces/ICDetailer.jsx b/tgui/packages/tgui/interfaces/ICDetailer.tsx
similarity index 84%
rename from tgui/packages/tgui/interfaces/ICDetailer.jsx
rename to tgui/packages/tgui/interfaces/ICDetailer.tsx
index 542b8763364..4afaf934983 100644
--- a/tgui/packages/tgui/interfaces/ICDetailer.jsx
+++ b/tgui/packages/tgui/interfaces/ICDetailer.tsx
@@ -4,8 +4,10 @@ import { useBackend } from '../backend';
import { Button, Section } from '../components';
import { Window } from '../layouts';
+type Data = { detail_color: string; color_list: Record };
+
export const ICDetailer = (props) => {
- const { act, data } = useBackend();
+ const { act, data } = useBackend();
const { detail_color, color_list } = data;
@@ -29,10 +31,10 @@ export const ICDetailer = (props) => {
color_list[key] === detail_color
? {
border: '4px solid black',
- 'border-radius': 0,
+ borderRadius: '0',
}
: {
- 'border-radius': 0,
+ borderRadius: '0',
}
}
backgroundColor={color_list[key]}
diff --git a/tgui/packages/tgui/interfaces/ICPrinter.jsx b/tgui/packages/tgui/interfaces/ICPrinter.tsx
similarity index 71%
rename from tgui/packages/tgui/interfaces/ICPrinter.jsx
rename to tgui/packages/tgui/interfaces/ICPrinter.tsx
index 9991f14f1b6..3fec1ae2a13 100644
--- a/tgui/packages/tgui/interfaces/ICPrinter.jsx
+++ b/tgui/packages/tgui/interfaces/ICPrinter.tsx
@@ -1,4 +1,5 @@
import { filter, sortBy } from 'common/collections';
+import { BooleanLike } from 'common/react';
import { useBackend, useSharedState } from '../backend';
import {
@@ -12,19 +13,31 @@ import {
} from '../components';
import { Window } from '../layouts';
-export const ICPrinter = (props) => {
- const { act, data } = useBackend();
+type Data = {
+ categories: category[];
+ metal: number;
+ max_metal: number;
+ metal_per_sheet: number;
+ debug: BooleanLike;
+ upgraded: BooleanLike;
+ can_clone: BooleanLike;
+ assembly_to_clone: string;
+};
- const {
- metal,
- max_metal,
- metal_per_sheet,
- debug,
- upgraded,
- can_clone,
- assembly_to_clone,
- categories,
- } = data;
+type category = { name: string; items: item[] };
+
+type item = {
+ name: string;
+ desc: string;
+ can_build: BooleanLike;
+ cost: number;
+ path: string;
+};
+
+export const ICPrinter = (props) => {
+ const { data } = useBackend();
+
+ const { metal, max_metal, metal_per_sheet, upgraded, can_clone } = data;
return (
@@ -54,7 +67,7 @@ export const ICPrinter = (props) => {
);
};
-const canBuild = (item, data) => {
+function canBuild(item: item, data: Data) {
if (!item.can_build) {
return false;
}
@@ -64,28 +77,28 @@ const canBuild = (item, data) => {
}
return true;
-};
+}
const ICPrinterCategories = (props) => {
- const { act, data } = useBackend();
+ const { act, data } = useBackend();
- const { categories, debug } = data;
+ const { categories } = data;
- const [categoryTarget, setcategoryTarget] = useSharedState(
+ const [categoryTarget, setcategoryTarget] = useSharedState(
'categoryTarget',
- null,
+ '',
);
- const selectedCategory = filter((cat) => cat.name === categoryTarget)(
- categories,
- )[0];
+ const selectedCategory = filter(
+ (cat: category) => cat.name === categoryTarget,
+ )(categories)[0];
return (
- {sortBy((cat) => cat.name)(categories).map((cat) => (
+ {sortBy((cat: category) => cat.name)(categories).map((cat) => (
setcategoryTarget(cat.name)}
@@ -97,10 +110,10 @@ const ICPrinterCategories = (props) => {
- {(selectedCategory && (
+ {selectedCategory ? (
- {sortBy((item) => item.name)(selectedCategory.items).map(
+ {sortBy((item: item) => item.name)(selectedCategory.items).map(
(item) => (
{
)}
- )) ||
- 'No category selected.'}
+ ) : (
+ No category selected.
+ )}
diff --git a/tgui/packages/tgui/interfaces/IDCard.jsx b/tgui/packages/tgui/interfaces/IDCard.tsx
similarity index 77%
rename from tgui/packages/tgui/interfaces/IDCard.jsx
rename to tgui/packages/tgui/interfaces/IDCard.tsx
index cdb0cef0ddc..3c05e478bb6 100644
--- a/tgui/packages/tgui/interfaces/IDCard.jsx
+++ b/tgui/packages/tgui/interfaces/IDCard.tsx
@@ -1,10 +1,22 @@
import { useBackend } from '../backend';
-import { Box, Flex, Icon, LabeledList, Section } from '../components';
+import { Box, Flex, Icon, Image, LabeledList, Section } from '../components';
import { Window } from '../layouts';
import { RankIcon } from './common/RankIcon';
+type Data = {
+ registered_name: string;
+ sex: string;
+ species: string;
+ age: string | number;
+ assignment: string;
+ fingerprint_hash: string;
+ blood_type: string;
+ dna_hash: string;
+ photo_front: string;
+};
+
export const IDCard = (props) => {
- const { data } = useBackend();
+ const { data } = useBackend();
const {
registered_name,
@@ -18,7 +30,10 @@ export const IDCard = (props) => {
photo_front,
} = data;
- const dataIter = [
+ const dataIter: {
+ name: string;
+ val: string | number;
+ }[] = [
{ name: 'Sex', val: sex },
{ name: 'Species', val: species },
{ name: 'Age', val: age },
@@ -43,12 +58,11 @@ export const IDCard = (props) => {
}}
>
{(photo_front && (
-
)) || }
@@ -74,7 +88,7 @@ export const IDCard = (props) => {
-
+
diff --git a/tgui/packages/tgui/interfaces/IdentificationComputer.jsx b/tgui/packages/tgui/interfaces/IdentificationComputer.tsx
similarity index 69%
rename from tgui/packages/tgui/interfaces/IdentificationComputer.jsx
rename to tgui/packages/tgui/interfaces/IdentificationComputer.tsx
index f22c1426de4..0335e0f85b5 100644
--- a/tgui/packages/tgui/interfaces/IdentificationComputer.jsx
+++ b/tgui/packages/tgui/interfaces/IdentificationComputer.tsx
@@ -1,4 +1,5 @@
import { sortBy } from 'common/collections';
+import { BooleanLike } from 'common/react';
import { decodeHtmlEntities } from 'common/string';
import { Fragment } from 'react';
@@ -16,6 +17,40 @@ import {
import { Window } from '../layouts';
import { CrewManifestContent } from './CrewManifest';
+type Data = {
+ manifest: { cat: string; elems: manifestEntry[] }[];
+ station_name: string;
+ mode: BooleanLike;
+ printing: BooleanLike;
+ have_id_slot: BooleanLike;
+ have_printer: BooleanLike;
+ target_name: string | null;
+ target_owner: string | null;
+ target_rank: string;
+ scan_name: string;
+ authenticated: BooleanLike;
+ has_modify: BooleanLike;
+ account_number: number | null;
+ centcom_access: BooleanLike;
+ all_centcom_access: access[];
+ regions: region[] | null;
+ id_rank: string | null;
+ departments: {
+ department_name: string;
+ jobs: { display_name: string; target_rank: string; job: string }[];
+ }[];
+};
+
+type manifestEntry = { name: string; rank: string; active: string };
+
+type region = { name: string; accesses: access[] };
+
+type access = {
+ desc: string;
+ ref: string;
+ allowed: BooleanLike;
+};
+
export const IdentificationComputer = () => {
return (
@@ -27,14 +62,16 @@ export const IdentificationComputer = () => {
};
export const IdentificationComputerContent = (props) => {
- const { act, data } = useBackend();
+ const { act, data } = useBackend();
const { ntos } = props;
- const { mode, has_modify, printing } = data;
+ const { mode, has_modify, printing, have_id_slot, have_printer } = data;
- let body = ;
- if (ntos && !data.have_id_slot) {
+ let body: React.JSX.Element = (
+
+ );
+ if (ntos && !have_id_slot) {
body = ;
} else if (printing) {
body = ;
@@ -45,7 +82,7 @@ export const IdentificationComputerContent = (props) => {
return (
<>
- {(!ntos || !!data.have_id_slot) && (
+ {(!ntos || !!have_id_slot) && (
{
Crew Manifest
{!ntos ||
- (!!data.have_printer && (
+ (!!have_printer && (
act('print')}
- disabled={!mode && !has_modify}
- color=""
+ onClick={() => (mode || has_modify) && act('print')}
+ color={!mode && !has_modify ? 'transparent' : ''}
>
Print
@@ -83,8 +121,10 @@ export const IdentificationComputerPrinting = (props) => {
return ;
};
-export const IdentificationComputerAccessModification = (props) => {
- const { act, data } = useBackend();
+export const IdentificationComputerAccessModification = (props: {
+ ntos: boolean;
+}) => {
+ const { act, data } = useBackend();
const { ntos } = props;
@@ -98,7 +138,6 @@ export const IdentificationComputerAccessModification = (props) => {
account_number,
centcom_access,
all_centcom_access,
- regions,
id_rank,
departments,
} = data;
@@ -126,7 +165,7 @@ export const IdentificationComputerAccessModification = (props) => {
{!!authenticated && !!has_modify && (
<>
-
+
-
+
{departments.map((dept) => (
@@ -199,7 +238,7 @@ export const IdentificationComputerAccessModification = (props) => {
{(!!centcom_access && (
-
+
{all_centcom_access.map((access) => (
{
))}
)) || (
-
+
)}
@@ -228,8 +267,8 @@ export const IdentificationComputerAccessModification = (props) => {
);
};
-export const IdentificationComputerRegions = (props) => {
- const { act, data } = useBackend();
+export const IdentificationComputerRegions = (props: { actName: string }) => {
+ const { act, data } = useBackend();
const { actName } = props;
@@ -237,28 +276,29 @@ export const IdentificationComputerRegions = (props) => {
return (
- {sortBy((r) => r.name)(regions).map((region) => (
-
-
- {sortBy((a) => a.desc)(region.accesses).map((access) => (
-
-
- act(actName, {
- access_target: access.ref,
- allowed: access.allowed,
- })
- }
- >
- {decodeHtmlEntities(access.desc)}
-
-
- ))}
-
-
- ))}
+ {regions &&
+ sortBy((r: region) => r.name)(regions).map((region) => (
+
+
+ {sortBy((a: access) => a.desc)(region.accesses).map((access) => (
+
+
+ act(actName, {
+ access_target: access.ref,
+ allowed: access.allowed,
+ })
+ }
+ >
+ {decodeHtmlEntities(access.desc)}
+
+
+ ))}
+
+
+ ))}
);
};
diff --git a/tgui/packages/tgui/interfaces/InventoryPanelHuman.jsx b/tgui/packages/tgui/interfaces/InventoryPanelHuman.tsx
similarity index 87%
rename from tgui/packages/tgui/interfaces/InventoryPanelHuman.jsx
rename to tgui/packages/tgui/interfaces/InventoryPanelHuman.tsx
index b393c3f18bd..0d139706389 100644
--- a/tgui/packages/tgui/interfaces/InventoryPanelHuman.jsx
+++ b/tgui/packages/tgui/interfaces/InventoryPanelHuman.tsx
@@ -1,14 +1,35 @@
+import { BooleanLike } from 'common/react';
+
import { useBackend } from '../backend';
import { Button, LabeledList, Section } from '../components';
import { Window } from '../layouts';
+type Data = {
+ slots: slot[];
+ specialSlots: slot[];
+ internals: string;
+ internalsValid: BooleanLike;
+ sensors: BooleanLike;
+ handcuffed: BooleanLike;
+ handcuffedParams: { slot: number };
+ legcuffed: BooleanLike;
+ legcuffedParams: { slot: number };
+ accessory: BooleanLike;
+};
+
+type slot = {
+ name: string;
+ item: string;
+ act: string;
+ params: { slot: number };
+};
+
export const InventoryPanelHuman = (props) => {
- const { act, data } = useBackend();
+ const { act, data } = useBackend();
const {
slots,
specialSlots,
- internals,
internalsValid,
sensors,
handcuffed,
diff --git a/tgui/packages/tgui/interfaces/IsolationCentrifuge.jsx b/tgui/packages/tgui/interfaces/IsolationCentrifuge.tsx
similarity index 84%
rename from tgui/packages/tgui/interfaces/IsolationCentrifuge.jsx
rename to tgui/packages/tgui/interfaces/IsolationCentrifuge.tsx
index 9b2d608cdb2..0c42919d1f2 100644
--- a/tgui/packages/tgui/interfaces/IsolationCentrifuge.jsx
+++ b/tgui/packages/tgui/interfaces/IsolationCentrifuge.tsx
@@ -1,9 +1,19 @@
+import { BooleanLike } from 'common/react';
+
import { useBackend } from '../backend';
import { Box, Button, Flex, LabeledList, Section } from '../components';
import { Window } from '../layouts';
+type Data = {
+ antibodies: string | null;
+ pathogens: { name: string; spread_type: string; reference: string }[];
+ is_antibody_sample: BooleanLike;
+ busy: string | null;
+ sample_inserted: BooleanLike;
+};
+
export const IsolationCentrifuge = (props) => {
- const { act, data } = useBackend();
+ const { act, data } = useBackend();
const { busy, antibodies, pathogens, is_antibody_sample, sample_inserted } =
data;
@@ -18,9 +28,7 @@ export const IsolationCentrifuge = (props) => {
} else {
blood_sample = (
<>
- {antibodies ? (
-
- ) : null}
+ {antibodies ? : ''}
{pathogens.length ? (
@@ -31,7 +39,9 @@ export const IsolationCentrifuge = (props) => {
))}
- ) : null}
+ ) : (
+ ''
+ )}
>
);
}
@@ -84,7 +94,9 @@ export const IsolationCentrifuge = (props) => {
{antibodies}
- ) : null}
+ ) : (
+ ''
+ )}
{pathogens.length ? (
{pathogens.map((virus) => (
@@ -99,10 +111,14 @@ export const IsolationCentrifuge = (props) => {
))}
- ) : null}
+ ) : (
+ ''
+ )}
- ) : null}
+ ) : (
+ ''
+ )}
>
)}
diff --git a/tgui/packages/tgui/interfaces/JanitorCart.jsx b/tgui/packages/tgui/interfaces/JanitorCart.tsx
similarity index 75%
rename from tgui/packages/tgui/interfaces/JanitorCart.jsx
rename to tgui/packages/tgui/interfaces/JanitorCart.tsx
index ec136982fa7..03cba6190c0 100644
--- a/tgui/packages/tgui/interfaces/JanitorCart.jsx
+++ b/tgui/packages/tgui/interfaces/JanitorCart.tsx
@@ -1,11 +1,21 @@
import { useBackend } from '../backend';
-import { Button, Icon } from '../components';
+import { Button, Icon, Image } from '../components';
import { Window } from '../layouts';
-export const JanitorCart = (props) => {
- const { act, data } = useBackend();
+type Data = {
+ mybag: string | null;
+ mybucket: string | null;
+ mymop: string | null;
+ myspray: string | null;
+ myreplacer: string | null;
+ signs: string | null;
+ icons: Record;
+};
- const { mybag, mybucket, mymop, myspray, myreplacer, signs, icons } = data;
+export const JanitorCart = (props) => {
+ const { act, data } = useBackend();
+
+ const { mybag, mybucket, mymop, myspray, myreplacer, signs } = data;
return (
@@ -18,7 +28,7 @@ export const JanitorCart = (props) => {
tooltipPosition="bottom-end"
color={mybag ? 'grey' : 'transparent'}
style={{
- border: mybag ? null : '2px solid grey',
+ border: mybag ? undefined : '2px solid grey',
}}
onClick={() => act('bag')}
>
@@ -32,7 +42,7 @@ export const JanitorCart = (props) => {
tooltipPosition="bottom"
color={mybucket ? 'grey' : 'transparent'}
style={{
- border: mybucket ? null : '2px solid grey',
+ border: mybucket ? undefined : '2px solid grey',
}}
onClick={() => act('bucket')}
>
@@ -46,7 +56,7 @@ export const JanitorCart = (props) => {
tooltipPosition="bottom-end"
color={mymop ? 'grey' : 'transparent'}
style={{
- border: mymop ? null : '2px solid grey',
+ border: mymop ? undefined : '2px solid grey',
}}
onClick={() => act('mop')}
>
@@ -60,7 +70,7 @@ export const JanitorCart = (props) => {
tooltipPosition="top-end"
color={myspray ? 'grey' : 'transparent'}
style={{
- border: myspray ? null : '2px solid grey',
+ border: myspray ? undefined : '2px solid grey',
}}
onClick={() => act('spray')}
>
@@ -74,7 +84,7 @@ export const JanitorCart = (props) => {
tooltipPosition="top"
color={myreplacer ? 'grey' : 'transparent'}
style={{
- border: myreplacer ? null : '2px solid grey',
+ border: myreplacer ? undefined : '2px solid grey',
}}
onClick={() => act('replacer')}
>
@@ -88,7 +98,7 @@ export const JanitorCart = (props) => {
tooltipPosition="top-start"
color={signs ? 'grey' : 'transparent'}
style={{
- border: signs ? null : '2px solid grey',
+ border: signs ? undefined : '2px solid grey',
}}
onClick={() => act('sign')}
>
@@ -99,7 +109,7 @@ export const JanitorCart = (props) => {
);
};
-const iconkeysToIcons = {
+const iconkeysToIcons: Record = {
mybag: 'trash',
mybucket: 'fill',
mymop: 'broom',
@@ -108,8 +118,8 @@ const iconkeysToIcons = {
signs: 'sign',
};
-const JanicartIcon = (props) => {
- const { data } = useBackend();
+const JanicartIcon = (props: { iconkey: string }) => {
+ const { data } = useBackend();
const { iconkey } = props;
@@ -117,17 +127,16 @@ const JanicartIcon = (props) => {
if (iconkey in icons) {
return (
-
);
@@ -138,9 +147,9 @@ const JanicartIcon = (props) => {
style={{
position: 'absolute',
left: '4px',
- right: 0,
+ right: '0',
top: '20px',
- bottom: 0,
+ bottom: '0',
width: '64px',
height: '64px',
}}
diff --git a/tgui/packages/tgui/interfaces/Jukebox.jsx b/tgui/packages/tgui/interfaces/Jukebox.tsx
similarity index 85%
rename from tgui/packages/tgui/interfaces/Jukebox.jsx
rename to tgui/packages/tgui/interfaces/Jukebox.tsx
index d265ad7c803..5328135c054 100644
--- a/tgui/packages/tgui/interfaces/Jukebox.jsx
+++ b/tgui/packages/tgui/interfaces/Jukebox.tsx
@@ -1,4 +1,5 @@
-import { round } from 'common/math';
+import { round, toFixed } from 'common/math';
+import { BooleanLike } from 'common/react';
import { capitalize } from 'common/string';
import { useState } from 'react';
@@ -19,8 +20,28 @@ import {
import { formatTime } from '../format';
import { Window } from '../layouts';
+type Data = {
+ playing: BooleanLike;
+ loop_mode: number;
+ volume: number;
+ current_track_ref: string | null;
+ current_track: track | null;
+ current_genre: string | null;
+ percent: number;
+ tracks: track[];
+ admin: BooleanLike;
+};
+
+type track = {
+ ref: string;
+ title: string;
+ artist: string;
+ genre: string;
+ duration: number;
+};
+
export const Jukebox = (props) => {
- const { act, data } = useBackend();
+ const { act, data } = useBackend();
const {
playing,
@@ -47,14 +68,14 @@ export const Jukebox = (props) => {
let true_genre = playing && (current_genre || 'Uncategorized');
- const [newTitle, setNewTitle] = useState('Unknown');
- const [newUrl, setNewUrl] = useState('');
- const [newDuration, setNewDuration] = useState(0);
- const [newArtist, setNewArtist] = useState('Unknown');
- const [newGenre, setNewGenre] = useState('Admin');
- const [newSecret, setNewSecret] = useState(false);
- const [newLobby, setNewLobby] = useState(false);
- const [unlockGenre, setUnlockGenre] = useState(false);
+ const [newTitle, setNewTitle] = useState('Unknown');
+ const [newUrl, setNewUrl] = useState('');
+ const [newDuration, setNewDuration] = useState(0);
+ const [newArtist, setNewArtist] = useState('Unknown');
+ const [newGenre, setNewGenre] = useState('Admin');
+ const [newSecret, setNewSecret] = useState(false);
+ const [newLobby, setNewLobby] = useState(false);
+ const [unlockGenre, setUnlockGenre] = useState(false);
function handleUnlockGenre() {
if (unlockGenre) {
@@ -135,7 +156,7 @@ export const Jukebox = (props) => {
average: [25, 75],
bad: [0, 25],
}}
- format={(val) => round(val, 1) + '%'}
+ format={(val) => toFixed(val, 1) + '%'}
onChange={(e, val) =>
act('volume', { val: round(val / 100, 2) })
}
@@ -156,7 +177,7 @@ export const Jukebox = (props) => {
color={true_genre === genre ? 'green' : 'default'}
child_mt={0}
>
- |