mirror of
https://github.com/Bubberstation/Bubberstation.git
synced 2026-08-23 13:10:02 +01:00
[MIRROR] New Experimentor UI (#26324)
* New Experimentor UI (#81157)  ## About The Pull Request This project rewrites experimentor UI from browser to TGUI. Refactored `ejectItem`, moved experiment handling logic out of `ui_act` and reordered proc calls, thus fixing a null reference runtime during attempt to unlock any techweb node. In addition, removed `checkCircumstances` due to being unused. ## Why It's Good For The Game The New UI is more responsive and displays much more information in a more organized fashion. Also, it removes `updateUsrDialog` and helps to bring https://hackmd.io/XLt5MoRvRxuhFbwtk4VAUA to a closure. ## Changelog 🆑 refactor: refactored experimentor UI to TGUI. /🆑 * New Experimentor UI --------- Co-authored-by: Interception&? <137328283+intercepti0n@users.noreply.github.com>
This commit is contained in:
co-authored by
Interception&?
parent
f06bacfd8d
commit
5d0145fcee
@@ -26,7 +26,7 @@
|
||||
density = TRUE
|
||||
use_power = IDLE_POWER_USE
|
||||
circuit = /obj/item/circuitboard/machine/experimentor
|
||||
interaction_flags_machine = INTERACT_MACHINE_WIRES_IF_OPEN|INTERACT_MACHINE_ALLOW_SILICON|INTERACT_MACHINE_OPEN_SILICON|INTERACT_MACHINE_SET_MACHINE
|
||||
interaction_flags_machine = INTERACT_MACHINE_WIRES_IF_OPEN|INTERACT_MACHINE_ALLOW_SILICON|INTERACT_MACHINE_OPEN_SILICON
|
||||
var/recentlyExperimented = 0
|
||||
/// Weakref to the first ian we can find at init
|
||||
var/datum/weakref/tracked_ian_ref
|
||||
@@ -117,14 +117,6 @@
|
||||
if(in_range(user, src) || isobserver(user))
|
||||
. += span_notice("The status display reads: Malfunction probability reduced by <b>[malfunction_probability_coeff]%</b>.<br>Cooldown interval between experiments at <b>[resetTime*0.1]</b> seconds.")
|
||||
|
||||
/obj/machinery/rnd/experimentor/proc/checkCircumstances(obj/item/O)
|
||||
//snowflake check to only take "made" bombs
|
||||
if(istype(O, /obj/item/transfer_valve))
|
||||
var/obj/item/transfer_valve/T = O
|
||||
if(!T.tank_one || !T.tank_two || !T.attached_device)
|
||||
return FALSE
|
||||
return TRUE
|
||||
|
||||
/obj/machinery/rnd/experimentor/attackby(obj/item/weapon, mob/living/user, params)
|
||||
if(user.combat_mode)
|
||||
return ..()
|
||||
@@ -142,117 +134,115 @@
|
||||
ejectItem()
|
||||
return ..(O)
|
||||
|
||||
/obj/machinery/rnd/experimentor/ui_interact(mob/user)
|
||||
var/list/dat = list("<center>")
|
||||
if(loaded_item)
|
||||
dat += "<b>Loaded Item:</b> [loaded_item]"
|
||||
/obj/machinery/rnd/experimentor/ui_interact(mob/user, datum/tgui/ui)
|
||||
ui = SStgui.try_update_ui(user, src, ui)
|
||||
if(!ui)
|
||||
ui = new (user, src, "Experimentator")
|
||||
ui.open()
|
||||
|
||||
dat += "<div>Available tests:"
|
||||
dat += "<b><a href='byond://?src=[REF(src)];item=[REF(loaded_item)];function=[SCANTYPE_POKE]'>Poke</A></b>"
|
||||
dat += "<b><a href='byond://?src=[REF(src)];item=[REF(loaded_item)];function=[SCANTYPE_IRRADIATE];'>Irradiate</A></b>"
|
||||
dat += "<b><a href='byond://?src=[REF(src)];item=[REF(loaded_item)];function=[SCANTYPE_GAS]'>Gas</A></b>"
|
||||
dat += "<b><a href='byond://?src=[REF(src)];item=[REF(loaded_item)];function=[SCANTYPE_HEAT]'>Burn</A></b>"
|
||||
dat += "<b><a href='byond://?src=[REF(src)];item=[REF(loaded_item)];function=[SCANTYPE_COLD]'>Freeze</A></b>"
|
||||
dat += "<b><a href='byond://?src=[REF(src)];item=[REF(loaded_item)];function=[SCANTYPE_OBLITERATE]'>Destroy</A></b></div>"
|
||||
if(istype(loaded_item,/obj/item/relic))
|
||||
dat += "<b><a href='byond://?src=[REF(src)];item=[REF(loaded_item)];function=[SCANTYPE_DISCOVER]'>Discover</A></b>"
|
||||
dat += "<b><a href='byond://?src=[REF(src)];function=eject'>Eject</A>"
|
||||
var/list/listin = techweb_item_unlock_check(src)
|
||||
if(listin)
|
||||
var/list/output = list("<b><font color='purple'>Research Boost Data:</font></b>")
|
||||
var/list/res = list("<b><font color='blue'>Already researched:</font></b>")
|
||||
for(var/node_id in listin)
|
||||
var/datum/techweb_node/N = SSresearch.techweb_node_by_id(node_id)
|
||||
var/str = "<b>[N.display_name]</b>: [listin[N]] points.</b>"
|
||||
var/datum/techweb/science_web = locate(/datum/techweb/science) in SSresearch.techwebs
|
||||
if(science_web.researched_nodes[N.id])
|
||||
res += str
|
||||
if(science_web.visible_nodes[N.id]) //JOY OF DISCOVERY!
|
||||
output += str
|
||||
output += res
|
||||
dat += output
|
||||
else
|
||||
dat += "<b>Nothing loaded.</b>"
|
||||
dat += "<a href='byond://?src=[REF(src)];function=refresh'>Refresh</A>"
|
||||
dat += "<a href='byond://?src=[REF(src)];close=1'>Close</A></center>"
|
||||
var/datum/browser/popup = new(user, "experimentor","Experimentor", 700, 400, src)
|
||||
popup.set_content(dat.Join("<br>"))
|
||||
popup.open()
|
||||
onclose(user, "experimentor")
|
||||
/obj/machinery/rnd/experimentor/ui_data(mob/user)
|
||||
var/list/data = list()
|
||||
|
||||
/obj/machinery/rnd/experimentor/Topic(href, href_list)
|
||||
if(..())
|
||||
data["hasItem"] = !!loaded_item
|
||||
data["isOnCooldown"] = recentlyExperimented
|
||||
data["isServerConnected"] = !!stored_research
|
||||
|
||||
if(!isnull(loaded_item))
|
||||
var/list/item_data = list()
|
||||
|
||||
item_data["name"] = loaded_item.name
|
||||
item_data["icon"] = icon2base64(getFlatIcon(loaded_item, no_anim = TRUE))
|
||||
item_data["isRelic"] = istype(loaded_item, /obj/item/relic)
|
||||
|
||||
item_data["associatedNodes"] = list()
|
||||
var/list/unlockable_nodes = techweb_item_unlock_check(loaded_item)
|
||||
for(var/node_id in unlockable_nodes)
|
||||
var/datum/techweb_node/node = SSresearch.techweb_node_by_id(node_id)
|
||||
|
||||
item_data["associatedNodes"] += list(list(
|
||||
"name" = node.display_name,
|
||||
"isUnlocked" = !(node_id in stored_research.hidden_nodes),
|
||||
))
|
||||
|
||||
data["loadedItem"] = item_data
|
||||
|
||||
return data
|
||||
|
||||
/obj/machinery/rnd/experimentor/ui_act(action, list/params)
|
||||
. = ..()
|
||||
if(.)
|
||||
return
|
||||
usr.set_machine(src)
|
||||
|
||||
var/scantype = href_list["function"]
|
||||
var/obj/item/process = locate(href_list["item"]) in src
|
||||
switch(action)
|
||||
if("eject")
|
||||
ejectItem()
|
||||
return TRUE
|
||||
|
||||
if(href_list["close"])
|
||||
usr << browse(null, "window=experimentor")
|
||||
if("experiment")
|
||||
var/reaction = text2num(params["id"])
|
||||
if(isnull(reaction))
|
||||
return
|
||||
|
||||
try_perform_experiment(reaction)
|
||||
return TRUE
|
||||
|
||||
/obj/machinery/rnd/experimentor/proc/ejectItem(delete = FALSE)
|
||||
if(isnull(loaded_item))
|
||||
return
|
||||
else if(scantype == "eject")
|
||||
ejectItem()
|
||||
else if(scantype == "refresh")
|
||||
updateUsrDialog()
|
||||
else
|
||||
if(recentlyExperimented)
|
||||
to_chat(usr, span_warning("[src] has been used too recently!"))
|
||||
else if(!loaded_item)
|
||||
to_chat(usr, span_warning("[src] is not currently loaded!"))
|
||||
else if(!process || process != loaded_item) //Interface exploit protection (such as hrefs or swapping items with interface set to old item)
|
||||
to_chat(usr, span_danger("Interface failure detected in [src]. Please try again."))
|
||||
else
|
||||
var/dotype
|
||||
if(text2num(scantype) == SCANTYPE_DISCOVER)
|
||||
dotype = SCANTYPE_DISCOVER
|
||||
else
|
||||
dotype = matchReaction(process,scantype)
|
||||
experiment(dotype,process)
|
||||
use_power(750)
|
||||
if(dotype != FAIL)
|
||||
var/list/nodes = techweb_item_unlock_check(process)
|
||||
var/picked = pick_weight(nodes) //This should work.
|
||||
stored_research.unhide_node(SSresearch.techweb_node_by_id(picked))
|
||||
updateUsrDialog()
|
||||
|
||||
/obj/machinery/rnd/experimentor/proc/matchReaction(matching,reaction)
|
||||
var/obj/item/D = matching
|
||||
if(D)
|
||||
var/list/item_reactions = item_reactions()
|
||||
if(item_reactions.Find("[D.type]"))
|
||||
var/tor = item_reactions["[D.type]"]
|
||||
if(tor == text2num(reaction))
|
||||
return tor
|
||||
else
|
||||
return FAIL
|
||||
else
|
||||
return FAIL
|
||||
else
|
||||
if(delete)
|
||||
QDEL_NULL(loaded_item)
|
||||
return
|
||||
|
||||
var/atom/drop_atom = get_step(src, EAST) || drop_location()
|
||||
if(cloneMode)
|
||||
visible_message(span_notice("A duplicate of \the [loaded_item] pops out!"))
|
||||
new loaded_item.type(drop_atom)
|
||||
cloneMode = FALSE
|
||||
return
|
||||
|
||||
loaded_item.forceMove(drop_atom)
|
||||
loaded_item = null
|
||||
|
||||
/obj/machinery/rnd/experimentor/proc/match_reaction(obj/item/matching, target_reaction)
|
||||
PRIVATE_PROC(TRUE)
|
||||
if(isnull(matching) || isnull(target_reaction))
|
||||
return FAIL
|
||||
|
||||
/obj/machinery/rnd/experimentor/proc/ejectItem(delete=FALSE)
|
||||
if(loaded_item)
|
||||
if(cloneMode)
|
||||
visible_message(span_notice("A duplicate [loaded_item] pops out!"))
|
||||
var/type_to_make = loaded_item.type
|
||||
new type_to_make(get_turf(pick(oview(1,src))))
|
||||
cloneMode = FALSE
|
||||
return
|
||||
var/turf/dropturf = get_turf(pick(view(1,src)))
|
||||
if(!dropturf) //Failsafe to prevent the object being lost in the void forever.
|
||||
dropturf = drop_location()
|
||||
loaded_item.forceMove(dropturf)
|
||||
if(delete)
|
||||
qdel(loaded_item)
|
||||
loaded_item = null
|
||||
var/list/item_reactions = item_reactions()
|
||||
if("[matching.type]" in item_reactions)
|
||||
var/associated_reaction = item_reactions["[matching.type]"]
|
||||
if(associated_reaction == target_reaction)
|
||||
return associated_reaction
|
||||
|
||||
return FAIL
|
||||
|
||||
/obj/machinery/rnd/experimentor/proc/try_perform_experiment(reaction)
|
||||
PRIVATE_PROC(TRUE)
|
||||
if(isnull(stored_research))
|
||||
return
|
||||
|
||||
if(recentlyExperimented)
|
||||
return
|
||||
|
||||
if(isnull(loaded_item))
|
||||
return
|
||||
|
||||
if(reaction != SCANTYPE_DISCOVER)
|
||||
reaction = match_reaction(loaded_item, reaction)
|
||||
|
||||
if(reaction != FAIL)
|
||||
var/picked_node_id = pick(techweb_item_unlock_check(loaded_item))
|
||||
stored_research.unhide_node(SSresearch.techweb_node_by_id(picked_node_id))
|
||||
|
||||
experiment(reaction, loaded_item)
|
||||
use_power(750)
|
||||
|
||||
/obj/machinery/rnd/experimentor/proc/throwSmoke(turf/where)
|
||||
var/datum/effect_system/fluid_spread/smoke/smoke = new
|
||||
smoke.set_up(0, holder = src, location = where)
|
||||
smoke.start()
|
||||
|
||||
|
||||
/obj/machinery/rnd/experimentor/proc/experiment(exp,obj/item/exp_on)
|
||||
recentlyExperimented = 1
|
||||
icon_state = "[base_icon_state]_wloop"
|
||||
|
||||
@@ -0,0 +1,246 @@
|
||||
import { BooleanLike } from 'common/react';
|
||||
import { toTitleCase } from 'common/string';
|
||||
|
||||
import { useBackend } from '../backend';
|
||||
import {
|
||||
Button,
|
||||
Icon,
|
||||
Image,
|
||||
LabeledList,
|
||||
NoticeBox,
|
||||
Section,
|
||||
Stack,
|
||||
} from '../components';
|
||||
import { Window } from '../layouts';
|
||||
|
||||
type Data = {
|
||||
hasItem: BooleanLike;
|
||||
isOnCooldown: BooleanLike;
|
||||
isServerConnected: BooleanLike;
|
||||
loadedItem: Item;
|
||||
};
|
||||
|
||||
type Item = {
|
||||
name: string;
|
||||
icon: string;
|
||||
isRelic: BooleanLike;
|
||||
associatedNodes: Node[];
|
||||
};
|
||||
|
||||
type Node = {
|
||||
name: string;
|
||||
isUnlocked: BooleanLike;
|
||||
};
|
||||
|
||||
export const Experimentator = (props: any) => {
|
||||
const { act, data } = useBackend<Data>();
|
||||
const { hasItem, isOnCooldown, isServerConnected, loadedItem } = data;
|
||||
|
||||
return (
|
||||
<Window width={450} height={325} title="E.X.P.E.R.I-MENTOR">
|
||||
<Window.Content>
|
||||
{isServerConnected ? (
|
||||
hasItem && loadedItem ? (
|
||||
<ExperimentScreen
|
||||
item={loadedItem}
|
||||
isOnCooldown={isOnCooldown}
|
||||
onEject={() => act('eject')}
|
||||
onExperiment={(id) => act('experiment', { id: id })}
|
||||
/>
|
||||
) : (
|
||||
<NoticeBox danger textAlign="center">
|
||||
No item present in experimentation chamber. Please insert one.
|
||||
</NoticeBox>
|
||||
)
|
||||
) : (
|
||||
<NoticeBox danger textAlign="center">
|
||||
Not connected to a server. Please sync one using a multitool.
|
||||
</NoticeBox>
|
||||
)}
|
||||
</Window.Content>
|
||||
</Window>
|
||||
);
|
||||
};
|
||||
|
||||
enum Experiment {
|
||||
Poke = 'Poke',
|
||||
Irradiate = 'Irradiate',
|
||||
Gas = 'Gas',
|
||||
Heat = 'Heat',
|
||||
Cold = 'Freeze',
|
||||
Obliterate = 'Obliterate',
|
||||
}
|
||||
|
||||
const EXPERIMENT2ICON = {
|
||||
Poke: 'hand',
|
||||
Irradiate: 'radiation',
|
||||
Gas: 'cloud',
|
||||
Heat: 'fire',
|
||||
Cold: 'snowflake',
|
||||
Obliterate: 'trash',
|
||||
};
|
||||
|
||||
type ExperimentScreenProps = {
|
||||
item: Item;
|
||||
isOnCooldown: BooleanLike;
|
||||
onEject: () => void;
|
||||
onExperiment: (id: number) => void;
|
||||
};
|
||||
|
||||
const ExperimentScreen = (props: ExperimentScreenProps) => {
|
||||
const { item, isOnCooldown, onEject, onExperiment } = props;
|
||||
const { name, icon, isRelic, associatedNodes } = item;
|
||||
|
||||
return (
|
||||
<Stack fill vertical>
|
||||
<Stack.Item grow>
|
||||
<Stack fill>
|
||||
<Stack.Item grow>
|
||||
<ItemPreview name={name} icon={icon} onEject={onEject} />
|
||||
</Stack.Item>
|
||||
<Stack.Item grow>
|
||||
<NodePreview nodes={associatedNodes} />
|
||||
</Stack.Item>
|
||||
</Stack>
|
||||
</Stack.Item>
|
||||
<Stack.Item>
|
||||
<ExperimentButtons
|
||||
isRelic={isRelic}
|
||||
disabled={isOnCooldown}
|
||||
onExperiment={onExperiment}
|
||||
/>
|
||||
</Stack.Item>
|
||||
</Stack>
|
||||
);
|
||||
};
|
||||
|
||||
type ItemPreviewProps = {
|
||||
name: string;
|
||||
icon: string;
|
||||
onEject: () => void;
|
||||
};
|
||||
|
||||
const ItemPreview = (props: ItemPreviewProps) => {
|
||||
const { name, icon, onEject } = props;
|
||||
|
||||
return (
|
||||
<Stack fill vertical align="center">
|
||||
<Stack.Item align="stretch">
|
||||
<Stack fill>
|
||||
<Stack.Item>
|
||||
<Button
|
||||
fluid
|
||||
color="bad"
|
||||
icon="eject"
|
||||
height="100%"
|
||||
fontSize={1.5}
|
||||
tooltip="Eject"
|
||||
textAlign="center"
|
||||
onClick={() => onEject()}
|
||||
verticalAlignContent="middle"
|
||||
/>
|
||||
</Stack.Item>
|
||||
<Stack.Item grow>
|
||||
<Section fill bold textAlign="center">
|
||||
{toTitleCase(name)}
|
||||
</Section>
|
||||
</Stack.Item>
|
||||
</Stack>
|
||||
</Stack.Item>
|
||||
<Stack.Item grow>
|
||||
<Stack fill vertical align="center" justify="center">
|
||||
<Stack.Item>
|
||||
<Image
|
||||
width="128px"
|
||||
height="128px"
|
||||
src={`data:image/jpeg;base64,${icon}`}
|
||||
/>
|
||||
</Stack.Item>
|
||||
</Stack>
|
||||
</Stack.Item>
|
||||
</Stack>
|
||||
);
|
||||
};
|
||||
|
||||
type NodePreviewProps = {
|
||||
nodes: Node[];
|
||||
};
|
||||
|
||||
const NodePreview = (props: NodePreviewProps) => {
|
||||
const { nodes } = props;
|
||||
|
||||
return (
|
||||
<Section fill title="Affected Nodes">
|
||||
{nodes.length > 0 ? (
|
||||
<LabeledList>
|
||||
{nodes.map((node, index) => (
|
||||
<LabeledList.Item
|
||||
key={index}
|
||||
label={node.name}
|
||||
color={node.isUnlocked ? 'good' : 'bad'}
|
||||
>
|
||||
{node.isUnlocked ? 'Unlocked' : 'Locked'}
|
||||
</LabeledList.Item>
|
||||
))}
|
||||
</LabeledList>
|
||||
) : (
|
||||
<Stack fill vertical align="center" justify="center">
|
||||
<Stack.Item className="hypertorus__unselectable">
|
||||
<Icon
|
||||
fontSize={4}
|
||||
name="circle-question"
|
||||
className={'FabricatorRecipe__Title--disabled'}
|
||||
/>
|
||||
</Stack.Item>
|
||||
</Stack>
|
||||
)}
|
||||
</Section>
|
||||
);
|
||||
};
|
||||
|
||||
type ExperimentButtonsProps = {
|
||||
isRelic: BooleanLike;
|
||||
disabled: BooleanLike;
|
||||
onExperiment: (id: number) => void;
|
||||
};
|
||||
|
||||
const ExperimentButtons = (props: ExperimentButtonsProps) => {
|
||||
const { isRelic, disabled, onExperiment } = props;
|
||||
|
||||
return (
|
||||
<Section fill>
|
||||
<Stack fill>
|
||||
{Object.keys(Experiment).map((value, index) => (
|
||||
<Stack.Item key={index}>
|
||||
<Button
|
||||
width={3}
|
||||
height={3}
|
||||
fontSize={1.6}
|
||||
textAlign="center"
|
||||
disabled={disabled}
|
||||
tooltip={Experiment[value]}
|
||||
verticalAlignContent="middle"
|
||||
icon={EXPERIMENT2ICON[value]}
|
||||
onClick={() => onExperiment(index + 1)}
|
||||
/>
|
||||
</Stack.Item>
|
||||
))}
|
||||
<Stack.Item grow>
|
||||
<Button
|
||||
bold
|
||||
fluid
|
||||
height={3}
|
||||
fontSize={1.6}
|
||||
textAlign="center"
|
||||
icon="magnifying-glass"
|
||||
verticalAlignContent="middle"
|
||||
disabled={!isRelic || disabled}
|
||||
onClick={() => onExperiment(7)}
|
||||
>
|
||||
Discover!
|
||||
</Button>
|
||||
</Stack.Item>
|
||||
</Stack>
|
||||
</Section>
|
||||
);
|
||||
};
|
||||
Reference in New Issue
Block a user