From 8407cfbab1e49a68f248e50f6fb6602484cfcb43 Mon Sep 17 00:00:00 2001
From: Sun-Soaked <45698967+Sun-Soaked@users.noreply.github.com>
Date: Thu, 18 Apr 2024 23:48:08 -0400
Subject: [PATCH] Cogbar Port (#2899)
## About The Pull Request
Do_afters have no visual feedback for nearby people, which is bad (and
leads to a lot of unintentional interruptions)
This adds a visual indicator in the form of a spinning cog that displays
to nearby players while you do a Do_after()
Ports [This Guy](https://github.com/tgstation/tgstation/pull/82416) and
assorted. Thanks @jlsnow301
(plus some assorted stuff)
Implements it on do_mob()'s(which tg rightfully decided to kill.)
Some of our equip handling code makes me want to explode, but that's a
problem for another day
## Why It's Good For The Game
Action feedback 4 nearby players, less accidental tileswaps
## Changelog
:cl: jlsnow301, sun-soaked
add: do_afters and do_mob actions now show nearby players a spinning cog
while in progress.
fix: the progressbar.dmm file is no longer misspelled "progess"bar
/:cl:
---------
Co-authored-by: Sun-Soaked <45698967+MemedHams@users.noreply.github.com>
---
code/__DEFINES/layers.dm | 3 +
code/__HELPERS/mobs.dm | 15 ++-
code/datums/cogbar.dm | 88 ++++++++++++++++++
code/datums/progressbar.dm | 2 +-
.../structures/beds_chairs/alien_nest.dm | 2 +-
code/game/objects/structures/kitchen_spike.dm | 2 +-
.../nukeop/equipment/borgchameleon.dm | 2 +-
.../nukeop/equipment/nuclearbomb.dm | 2 +-
.../components/unary_devices/cryo.dm | 2 +-
code/modules/clothing/shoes/_shoes.dm | 2 +-
code/modules/mob/living/carbon/carbon.dm | 2 +-
code/modules/mob/living/carbon/human/human.dm | 2 +-
code/modules/ninja/suit/ninjaDrainAct.dm | 16 ++--
icons/effects/progessbar.dmi | Bin 1013 -> 0 bytes
icons/effects/progressbar.dmi | Bin 0 -> 1246 bytes
shiptest.dme | 1 +
tgui/packages/tgui/interfaces/Cloner.js | 2 +-
17 files changed, 122 insertions(+), 21 deletions(-)
create mode 100644 code/datums/cogbar.dm
delete mode 100644 icons/effects/progessbar.dmi
create mode 100644 icons/effects/progressbar.dmi
diff --git a/code/__DEFINES/layers.dm b/code/__DEFINES/layers.dm
index fd61f4f1123b..6f76dae9769f 100644
--- a/code/__DEFINES/layers.dm
+++ b/code/__DEFINES/layers.dm
@@ -130,6 +130,9 @@
#define CAMERA_STATIC_LAYER 19
#define CAMERA_STATIC_RENDER_TARGET "CAMERA_STATIC_PLANE"
+///Wants to be part of the game plane, but also wants to draw above literally everything else
+#define HIGH_GAME_PLANE 30
+
//HUD layer defines
#define FULLSCREEN_PLANE 31
diff --git a/code/__HELPERS/mobs.dm b/code/__HELPERS/mobs.dm
index f307aa952804..81ba9ec06dd2 100644
--- a/code/__HELPERS/mobs.dm
+++ b/code/__HELPERS/mobs.dm
@@ -242,7 +242,7 @@ GLOBAL_LIST_EMPTY(species_list)
return "unknown"
///Timed action involving two mobs, the user and the target.
-/proc/do_mob(mob/user , mob/target, time = 3 SECONDS, uninterruptible = FALSE, progress = TRUE, datum/callback/extra_checks = null, ignore_loc_change = FALSE)
+/proc/do_mob(mob/user , mob/target, time = 3 SECONDS, uninterruptible = FALSE, progress = TRUE, datum/callback/extra_checks = null, ignore_loc_change = FALSE, hidden = FALSE)
if(!user || !target)
return FALSE
@@ -262,8 +262,11 @@ GLOBAL_LIST_EMPTY(species_list)
LAZYADD(target.targeted_by, user)
var/holding = user.get_active_held_item()
var/datum/progressbar/progbar
+ var/datum/cogbar/cog
if (progress)
progbar = new(user, time, target)
+ if(!hidden && time >= 1 SECONDS)
+ cog = new(user)
var/endtime = world.time+time
var/starttime = world.time
@@ -292,6 +295,8 @@ GLOBAL_LIST_EMPTY(species_list)
break
if(!QDELETED(progbar))
progbar.end_progress()
+
+ cog?.remove()
if(!QDELETED(target))
LAZYREMOVE(user.do_afters, target)
LAZYREMOVE(target.targeted_by, user)
@@ -311,7 +316,7 @@ GLOBAL_LIST_EMPTY(species_list)
return ..()
///Timed action involving one mob user. Target is optional.
-/proc/do_after(mob/user, delay, needhand = TRUE, atom/target = null, progress = TRUE, datum/callback/extra_checks = null)
+/proc/do_after(mob/user, delay, needhand = TRUE, atom/target = null, progress = TRUE, datum/callback/extra_checks = null, hidden = FALSE)
if(!user)
return FALSE
@@ -342,9 +347,11 @@ GLOBAL_LIST_EMPTY(species_list)
delay *= user.do_after_coefficent()
var/datum/progressbar/progbar
+ var/datum/cogbar/cog
if(progress)
progbar = new(user, delay, target || user)
-
+ if(!hidden && delay >= 1 SECONDS)
+ cog = new(user)
var/endtime = world.time + delay
var/starttime = world.time
. = TRUE
@@ -389,6 +396,8 @@ GLOBAL_LIST_EMPTY(species_list)
if(!QDELETED(progbar))
progbar.end_progress()
+ cog?.remove()
+
if(!QDELETED(target))
LAZYREMOVE(user.do_afters, target)
LAZYREMOVE(target.targeted_by, user)
diff --git a/code/datums/cogbar.dm b/code/datums/cogbar.dm
new file mode 100644
index 000000000000..c03daa33a6ab
--- /dev/null
+++ b/code/datums/cogbar.dm
@@ -0,0 +1,88 @@
+#define COGBAR_ANIMATION_TIME (0.5 SECONDS)
+
+/**
+ * ### Cogbar
+ * Represents that the user is busy doing something.
+ */
+/datum/cogbar
+ /// Who's doing the thing
+ var/mob/user
+ /// The user client
+ var/client/user_client
+ /// The visible element to other players
+ var/obj/effect/overlay/vis/cog
+ /// The blank image that overlaps the cog - hides it from the source user
+ var/image/blank
+ /// The offset of the icon
+ //var/offset_y
+
+
+/datum/cogbar/New(mob/user)
+ src.user = user
+ src.user_client = user.client
+
+//Porting oversized icon offsets later, they have too many other unported dependencies. sorry zephyr
+ //var/list/icon_offsets = user.get_oversized_icon_offsets()
+ //offset_y = icon_offsets["y"]
+
+ add_cog_to_user()
+
+ RegisterSignal(user, COMSIG_PARENT_QDELETING, PROC_REF(on_user_delete))
+
+
+/datum/cogbar/Destroy()
+ if(user)
+ SSvis_overlays.remove_vis_overlay(user, user.managed_vis_overlays)
+ user_client?.images -= blank
+
+ user = null
+ user_client = null
+ cog = null
+ QDEL_NULL(blank)
+
+ return ..()
+
+
+/// Adds the cog to the user, visible by other players
+/datum/cogbar/proc/add_cog_to_user()
+ cog = SSvis_overlays.add_vis_overlay(user,
+ icon = 'icons/effects/progressbar.dmi',
+ iconstate = "cog",
+ plane = HIGH_GAME_PLANE,
+ add_appearance_flags = APPEARANCE_UI_IGNORE_ALPHA,
+ unique = TRUE,
+ alpha = 0,
+ )
+ cog.pixel_y = world.icon_size// + offset_y
+ animate(cog, alpha = 255, time = COGBAR_ANIMATION_TIME)
+
+ if(isnull(user_client))
+ return
+
+ blank = image('icons/blanks/32x32.dmi', cog, "nothing")
+ blank.plane = HIGH_GAME_PLANE
+ blank.appearance_flags = APPEARANCE_UI_IGNORE_ALPHA
+ blank.override = TRUE
+
+ user_client.images += blank
+
+
+/// Removes the cog from the user
+/datum/cogbar/proc/remove()
+ if(isnull(cog))
+ qdel(src)
+ return
+
+ animate(cog, alpha = 0, time = COGBAR_ANIMATION_TIME)
+
+ QDEL_IN(src, COGBAR_ANIMATION_TIME)
+
+
+/// When the user is deleted, remove the cog
+/datum/cogbar/proc/on_user_delete(datum/source)
+ SIGNAL_HANDLER
+
+ qdel(src)
+
+
+#undef COGBAR_ANIMATION_TIME
diff --git a/code/datums/progressbar.dm b/code/datums/progressbar.dm
index 25621a613eeb..7134d2e8ecef 100644
--- a/code/datums/progressbar.dm
+++ b/code/datums/progressbar.dm
@@ -32,7 +32,7 @@
return
goal = goal_number
bar_loc = target
- bar = image('icons/effects/progessbar.dmi', bar_loc, "prog_bar_0", HUD_LAYER)
+ bar = image('icons/effects/progressbar.dmi', bar_loc, "prog_bar_0", HUD_LAYER)
bar.plane = ABOVE_HUD_PLANE
bar.appearance_flags = APPEARANCE_UI_IGNORE_ALPHA
user = User
diff --git a/code/game/objects/structures/beds_chairs/alien_nest.dm b/code/game/objects/structures/beds_chairs/alien_nest.dm
index 532b4385f2cf..4f132b11af99 100644
--- a/code/game/objects/structures/beds_chairs/alien_nest.dm
+++ b/code/game/objects/structures/beds_chairs/alien_nest.dm
@@ -34,7 +34,7 @@
M.visible_message("[M.name] struggles to break free from the gelatinous resin!",\
"You struggle to break free from the gelatinous resin... (Stay still for two minutes.)",\
"You hear squelching...")
- if(!do_after(M, 1200, target = src))
+ if(!do_after(M, 1200, target = src, hidden = TRUE))
if(M && M.buckled)
to_chat(M, "You fail to unbuckle yourself!")
return
diff --git a/code/game/objects/structures/kitchen_spike.dm b/code/game/objects/structures/kitchen_spike.dm
index 65479f171334..d863b693604f 100644
--- a/code/game/objects/structures/kitchen_spike.dm
+++ b/code/game/objects/structures/kitchen_spike.dm
@@ -112,7 +112,7 @@
"You struggle to break free from [src], exacerbating your wounds! (Stay still for two minutes.)",\
"You hear a wet squishing noise..")
M.adjustBruteLoss(30)
- if(!do_after(M, 1200, target = src))
+ if(!do_after(M, 1200, target = src, hidden = TRUE))
if(M && M.buckled)
to_chat(M, "You fail to free yourself!")
return
diff --git a/code/modules/antagonists/nukeop/equipment/borgchameleon.dm b/code/modules/antagonists/nukeop/equipment/borgchameleon.dm
index ddc895060b0c..17cd8fd99bff 100644
--- a/code/modules/antagonists/nukeop/equipment/borgchameleon.dm
+++ b/code/modules/antagonists/nukeop/equipment/borgchameleon.dm
@@ -65,7 +65,7 @@
to_chat(user, "You activate \the [src].")
playsound(src, 'sound/effects/seedling_chargeup.ogg', 100, TRUE, -6)
apply_wibbly_filters(user)
- if (do_after(user, 50, target=user) && user.cell.use(activationCost))
+ if (do_after(user, 50, target=user, hidden = TRUE) && user.cell.use(activationCost))
playsound(src, 'sound/effects/bamf.ogg', 100, TRUE, -6)
to_chat(user, "You are now disguised as the Nanotrasen engineering borg \"[friendlyName]\".")
activate(user)
diff --git a/code/modules/antagonists/nukeop/equipment/nuclearbomb.dm b/code/modules/antagonists/nukeop/equipment/nuclearbomb.dm
index d0019eb19cc2..f3d6bb31abf4 100644
--- a/code/modules/antagonists/nukeop/equipment/nuclearbomb.dm
+++ b/code/modules/antagonists/nukeop/equipment/nuclearbomb.dm
@@ -126,7 +126,7 @@
if(istype(I, /obj/item/nuke_core_container))
var/obj/item/nuke_core_container/core_box = I
to_chat(user, "You start loading the plutonium core into [core_box]...")
- if(do_after(user,50,target=src))
+ if(do_after(user,50,target=src, hidden = TRUE))
if(core_box.load(core, user))
to_chat(user, "You load the plutonium core into [core_box].")
deconstruction_state = NUKESTATE_CORE_REMOVED
diff --git a/code/modules/atmospherics/machinery/components/unary_devices/cryo.dm b/code/modules/atmospherics/machinery/components/unary_devices/cryo.dm
index 8f547335e9e0..3705cb361d82 100644
--- a/code/modules/atmospherics/machinery/components/unary_devices/cryo.dm
+++ b/code/modules/atmospherics/machinery/components/unary_devices/cryo.dm
@@ -273,7 +273,7 @@
user.visible_message("You see [user] kicking against the glass of [src]!", \
"You struggle inside [src], kicking the release with your foot... (this will take about [DisplayTimeText(breakout_time)].)", \
"You hear a thump from [src].")
- if(do_after(user, breakout_time, target = src))
+ if(do_after(user, breakout_time, target = src, hidden = TRUE))
if(!user || user.stat != CONSCIOUS || user.loc != src)
return
user.visible_message("[user] successfully broke out of [src]!", \
diff --git a/code/modules/clothing/shoes/_shoes.dm b/code/modules/clothing/shoes/_shoes.dm
index 7b2ded27e269..1b5f0ae58fae 100644
--- a/code/modules/clothing/shoes/_shoes.dm
+++ b/code/modules/clothing/shoes/_shoes.dm
@@ -157,7 +157,7 @@
if(HAS_TRAIT(user, TRAIT_CLUMSY)) // based clowns trained their whole lives for this
mod_time *= 0.75
- if(do_after(user, mod_time, needhand=TRUE, target=our_guy, extra_checks=CALLBACK(src, PROC_REF(still_shoed), our_guy)))
+ if(do_after(user, mod_time, needhand=TRUE, target=our_guy, extra_checks=CALLBACK(src, PROC_REF(still_shoed), our_guy), hidden = TRUE))
to_chat(user, "You [tied ? "untie" : "knot"] the laces on [loc]'s [src.name].")
if(tied == SHOES_UNTIED)
adjust_laces(SHOES_KNOTTED, user)
diff --git a/code/modules/mob/living/carbon/carbon.dm b/code/modules/mob/living/carbon/carbon.dm
index 5b316dad9cb6..36fd8e1e6704 100644
--- a/code/modules/mob/living/carbon/carbon.dm
+++ b/code/modules/mob/living/carbon/carbon.dm
@@ -255,7 +255,7 @@
buckle_cd = O.breakouttime
visible_message("[src] attempts to unbuckle [p_them()]self!", \
"You attempt to unbuckle yourself... (This will take around [round(buckle_cd/600,1)] minute\s, and you need to stay still.)")
- if(do_after(src, buckle_cd, 0, target = src))
+ if(do_after(src, buckle_cd, 0, target = src, hidden = TRUE))
if(!buckled)
return
buckled.user_unbuckle_mob(src,src)
diff --git a/code/modules/mob/living/carbon/human/human.dm b/code/modules/mob/living/carbon/human/human.dm
index ecad7982b83a..292047676575 100644
--- a/code/modules/mob/living/carbon/human/human.dm
+++ b/code/modules/mob/living/carbon/human/human.dm
@@ -247,7 +247,7 @@
else
return
- if(do_mob(usr, src, POCKET_STRIP_DELAY/delay_denominator)) //placing an item into the pocket is 4 times faster
+ if(do_mob(usr, src, POCKET_STRIP_DELAY/delay_denominator, hidden = TRUE)) //placing an item into the pocket is 4 times faster
if(pocket_item)
if(pocket_item == (pocket_id == ITEM_SLOT_RPOCKET ? r_store : l_store)) //item still in the pocket we search
dropItemToGround(pocket_item)
diff --git a/code/modules/ninja/suit/ninjaDrainAct.dm b/code/modules/ninja/suit/ninjaDrainAct.dm
index 2e3dac4fddbe..4939686ee9fd 100644
--- a/code/modules/ninja/suit/ninjaDrainAct.dm
+++ b/code/modules/ninja/suit/ninjaDrainAct.dm
@@ -41,7 +41,7 @@ They *could* go in their appropriate files, but this is supposed to be modular
drain = S.cell.maxcharge - S.cell.charge
maxcapacity = 1//Reached maximum battery capacity.
- if (do_after(H,10, target = src))
+ if (do_after(H,10, target = src, hidden = TRUE))
spark_system.start()
playsound(loc, "sparks", 50, TRUE, SHORT_RANGE_SOUND_EXTRARANGE)
cell.use(drain)
@@ -85,7 +85,7 @@ They *could* go in their appropriate files, but this is supposed to be modular
drain = S.cell.maxcharge - S.cell.charge
maxcapacity = 1
- if (do_after(H,10, target = src))
+ if (do_after(H,10, target = src, hidden = TRUE))
spark_system.start()
playsound(loc, "sparks", 50, TRUE, SHORT_RANGE_SOUND_EXTRARANGE)
charge -= drain
@@ -104,7 +104,7 @@ They *could* go in their appropriate files, but this is supposed to be modular
. = 0
if(charge)
- if(G.candrain && do_after(H,30, target = src))
+ if(G.candrain && do_after(H,30, target = src, hidden = TRUE))
. = charge
if(S.cell.charge + charge > S.cell.maxcharge)
S.cell.charge = S.cell.maxcharge
@@ -131,7 +131,7 @@ They *could* go in their appropriate files, but this is supposed to be modular
if(stored_research)
to_chat(H, "Copying files...")
- if(do_after(H, S.s_delay, target = src) && G.candrain && src)
+ if(do_after(H, S.s_delay, target = src, hidden = TRUE) && G.candrain && src)
stored_research.copy_research_to(S.stored_research)
to_chat(H, "Data analyzed. Process finished.")
@@ -148,7 +148,7 @@ They *could* go in their appropriate files, but this is supposed to be modular
if(stored_research)
to_chat(H, "Copying files...")
- if(do_after(H, S.s_delay, target = src) && G.candrain && src)
+ if(do_after(H, S.s_delay, target = src, hidden = TRUE) && G.candrain && src)
stored_research.copy_research_to(S.stored_research)
to_chat(H, "Data analyzed. Process finished.")
@@ -167,7 +167,7 @@ They *could* go in their appropriate files, but this is supposed to be modular
while(G.candrain && !maxcapacity && src)
drain = (round((rand(G.mindrain, G.maxdrain))/2))
var/drained = 0
- if(PN && do_after(H,10, target = src))
+ if(PN && do_after(H,10, target = src, hidden = TRUE))
drained = min(drain, delayed_surplus())
add_delayedload(drained)
if(drained < drain)//if no power on net, drain apcs
@@ -207,7 +207,7 @@ They *could* go in their appropriate files, but this is supposed to be modular
if(S.cell.charge + drain > S.cell.maxcharge)
drain = S.cell.maxcharge - S.cell.charge
maxcapacity = 1
- if (do_after(H,10, target = src))
+ if (do_after(H,10, target = src, hidden = TRUE))
spark_system.start()
playsound(loc, "sparks", 50, TRUE, SHORT_RANGE_SOUND_EXTRARANGE)
cell.use(drain)
@@ -235,7 +235,7 @@ They *could* go in their appropriate files, but this is supposed to be modular
if(S.cell.charge+drain > S.cell.maxcharge)
drain = S.cell.maxcharge - S.cell.charge
maxcapacity = 1
- if (do_after(H,10))
+ if (do_after(H,10, hidden = TRUE))
spark_system.start()
playsound(loc, "sparks", 50, TRUE, SHORT_RANGE_SOUND_EXTRARANGE)
cell.use(drain)
diff --git a/icons/effects/progessbar.dmi b/icons/effects/progessbar.dmi
deleted file mode 100644
index f055a07ba1492a36912ed6b9fdbf1b8e278e4864..0000000000000000000000000000000000000000
GIT binary patch
literal 0
HcmV?d00001
literal 1013
zcmV{hG&VIm%m6bYAt(O;Gt6c)|CuuY%rgN0Gc#uYW|{wws`2W@R{!XX_Jw>-00001
zbW%=J06^y0W&i*H`+8JZbVOxyV{&P5bZKvH004NLt=78=!Y~vD;JJBzP}a&<)8%MEhWm?j3)Zra)=6xBl?EerTz7
z2YY{}C!ZDnI3odCOJc@_05D+)6(Cd$p#p@8A=H3SGlUutYKG7NLc!i*uz0b$M%=72C~2n#@1FoXplEEvKP5S9#K2?$Hhi~pB6mdowp
z_0xF(AJ~Na{wENT0007PNklv0t6x9nR}0$u{oB{)U_tBr^7Wp8-tS=Ve1xxX
z-y8q{0001B9=)5H{`uJ5-8Y;M*c?^b)l|-F?d&Gl;Cg;4=6BBDl=2j8a8{3Rk1G9o
zF6Xs=ej99XJwF%oJLhjpc@8#MJOQU0F!(#_e897F000000DyV)Zf5%DV|RB0{QrPW
zP`^g)YbxY-&)>v(3N~n&zdH%r;k*0yZ6VJ=0slz9|8;|Kevb3n{QXIMK4A7IVDNX;
z`G9BV00000008so-OTjQ$L{XF;e0>}>emO`*IksSV1q~J?<(_C^Zc{^J+JNG_fejM
z4IZ7puguTQ^F0B*-@)Gb2w&m8IRF3v006)|dN(uu^Rc_TZ#W;Y3+mTm`9QR@@89>8{ynek_dho%&(F>C-!4?<=d1Gpvp)fYzoX6vJUa&f00000
zm`Cqsrhh(mcQ?TA3tYCZhf4dJR<^qz8{C|q*3Lh){l36u|Nd6#-}B0T|6_xj^YhyI
zZ*9LX@Y|n&!QWBm1D>4&000000L)|d=H>a_+u)-1H8r)b{{qWTx&hbydv5CA{|A25
j4Ve82nEe|t^Iw5?ZsBHuRElq(00000NkvXXu0mjf6jA0r
diff --git a/icons/effects/progressbar.dmi b/icons/effects/progressbar.dmi
new file mode 100644
index 0000000000000000000000000000000000000000..3eed14db704a76ab77d9ea02c39e290c85e428c1
GIT binary patch
literal 1246
zcmZvadr(pd6vi(w$y%X1uCAmFW;==Y&=kuax@5vCYHI4HlNPsH35(1W^Mx0BnCosE
zX>2PgQZ|*gDLrUd#}=uSMi!-aPOQ;GMM@q?hIoa$*iL`#o`1eG-<+btJbRl(&9iO0+7@U
z5+Wo4Y7&5uNDPD_L|kLH?rQ1CLq>j4@!-z^zHU1D$ksFbY)ZGB&=v9^ks`A0i;~yxke+Y6
z(_1>GefvY4PZ%W=UYRVa{qYPKX7WK+Y&!Jt@-=5PoxHE@;O@#wwq(?esA#Tl*y_G$
ziGYR*q6)pibO8+$v`H8VJ{0r;L2<(QV1yuqIEG;(7r9
zL!nV(Gs~KcL&g)0E5EFMwY)@>+FM{ocKRp#t=^&h3VW!1q)Pf$jEMtr?d-H0+Swa@
zDwgUuW>Ps3FPk#hO~1oS#*`=#X`&NezvD=YUFl32JPSi8XrA2D^4+(k+%Q&Jq;60*
z&!2sT_Z}Sy@k5>SnJ+_fqp+WOgD0N%Cp&H#O&!%X%yr`)713XCv&-%fU5zzxsu0<<
zEw{UQqR%bQ0YS64b#$HBCk-ZC5y>px9gVY3^QlImYqr
zYZPpGhBZoWzWKL1w(Rmf&)Nr)${5+S{YW9tz>$pZA0RV3eLe19Lo4UqpNdhzs3iSo
zB%s~gC2SMRhK5#I5bSvM^!Iz{qWbO_ZkRr0&4#|2Hwmm7ok{1JXsSj#PeBNb>alMh
zXt!Ugn}HNhvo^f31)H(UMgJc$rIk)*(fR47qZrOQgsCp=wTh=p$ucgP=E`F+%$y3>
z`D%90c`V27)vt)IemDBxSnU=_vALE0js!i4|P90KFGWc=f+
zNBq+@u$vNa0QUEj+sxAFVJkR{==G-)wgn<6iE>WGu!;xbTHBGaa5p1H8QARz6oX {
beakerContents={data.beakerContents}
/>
-