[MIRROR] Research queue (#28846)

* Research queue (#84731)

## About The Pull Request

<img alt="2dZbpr8MK1"
src="https://github.com/tgstation/tgstation/assets/3625094/dd78feba-224a-41a1-8d4a-83af3a8b68df">

Added an ability to queue up to one node per player in a techweb for an
automatic research.

You can queue up a node only when all requirements are met, but there
are not enough points.

People can't research when there is something in the queue, only add
things to the queue. So a 40 points node can't be researched if someone
queued up a 200 points node ahead of it.

When a node is enqueued by RD, it is placed in front of the queue.

The research button is available when the queue is empty.

TODO:

- [x] Bypass queue when the node cost is zero

## Why It's Good For The Game

No need to stay at the console to wait for the points. No "Research"
button spamming.

## Changelog

🆑
qol: Research nodes can be queued, one per player. RDs can place their
node at the beginning of the queue.
/🆑

* Research queue

---------

Co-authored-by: Andrew <mt.forspam@gmail.com>
This commit is contained in:
SkyratBot
2024-07-15 10:05:42 +05:30
committed by GitHub
co-authored by Andrew
parent a0a2280f68
commit a585b40a1c
6 changed files with 195 additions and 33 deletions
+8 -1
View File
@@ -36,7 +36,7 @@ SUBSYSTEM_DEF(research)
var/list/techweb_nodes_experimental = list()
///path = list(point type = value)
var/list/techweb_point_items = list(
/obj/item/assembly/signaler/anomaly = list(TECHWEB_POINT_TYPE_GENERIC = 10000)
/obj/item/assembly/signaler/anomaly = list(TECHWEB_POINT_TYPE_GENERIC = TECHWEB_TIER_5_POINTS)
)
var/list/errored_datums = list()
///Associated list of all point types that techwebs will have and their respective 'abbreviated' name.
@@ -104,6 +104,13 @@ SUBSYSTEM_DEF(research)
techweb_list.last_income = world.time
if(techweb_list.research_queue_nodes.len)
techweb_list.research_node_id(techweb_list.research_queue_nodes[1]) // Attempt to research the first node in queue if possible
for(var/datum/techweb_node/node as anything in techweb_list.research_queue_nodes)
if(node.is_free(techweb_list)) // Automatically research all free nodes in queue if any
techweb_list.research_node(node)
/datum/controller/subsystem/research/proc/autosort_categories()
for(var/i in techweb_nodes)
var/datum/techweb_node/I = techweb_nodes[i]
@@ -50,6 +50,7 @@
return data
data += list(
"nodes" = list(),
"queue_nodes" = stored_research.research_queue_nodes,
"experiments" = list(),
"researched_designs" = stored_research.researched_designs,
"points" = stored_research.research_points,
@@ -64,6 +65,10 @@
// Serialize all nodes to display
for(var/tier in stored_research.tiers)
var/datum/techweb_node/node = SSresearch.techweb_node_by_id(tier)
var/enqueued_by_user = FALSE
if((tier in stored_research.research_queue_nodes) && stored_research.research_queue_nodes[tier] == user)
enqueued_by_user = TRUE
// Ensure node is supposed to be visible
if (stored_research.hidden_nodes[tier])
@@ -71,8 +76,11 @@
data["nodes"] += list(list(
"id" = node.id,
"is_free" = node.is_free(stored_research),
"can_unlock" = stored_research.can_unlock_node(node),
"tier" = stored_research.tiers[node.id]
"have_experiments_done" = stored_research.have_experiments_for_node(node),
"tier" = stored_research.tiers[node.id],
"enqueued_by_user" = enqueued_by_user
))
// Get experiments and serialize them
@@ -111,6 +119,12 @@
if ("researchNode")
research_node(params["node_id"], usr)
return TRUE
if ("enqueueNode")
enqueue_node(params["node_id"], usr)
return TRUE
if ("dequeueNode")
dequeue_node(params["node_id"], usr)
return TRUE
/datum/computer_file/program/science/ui_static_data(mob/user)
. = list(
@@ -188,6 +202,20 @@
id_cache_seq += 1
return id_cache[id]
/datum/computer_file/program/science/proc/enqueue_node(id, mob/user)
if(!stored_research || !stored_research.available_nodes[id] || stored_research.researched_nodes[id])
computer.say("Node enqueue failed: Either no techweb is found, node is already researched or is not available!")
return FALSE
stored_research.enqueue_node(id, user)
return TRUE
/datum/computer_file/program/science/proc/dequeue_node(id, mob/user)
if(!stored_research || !stored_research.available_nodes[id] || stored_research.researched_nodes[id])
computer.say("Node dequeue failed: Either no techweb is found, node is already researched or is not available!")
return FALSE
stored_research.dequeue_node(id, user)
return TRUE
/datum/computer_file/program/science/proc/research_node(id, mob/user)
if(!stored_research || !stored_research.available_nodes[id] || stored_research.researched_nodes[id])
computer.say("Node unlock failed: Either no techweb is found, node is already researched or is not available!")
+28
View File
@@ -96,6 +96,20 @@ Nothing else in the console has ID requirements.
stored_research = tool.buffer
return TRUE
/obj/machinery/computer/rdconsole/proc/enqueue_node(id, mob/user)
if(!stored_research || !stored_research.available_nodes[id] || stored_research.researched_nodes[id])
say("Node enqueue failed: Either no techweb is found, node is already researched or is not available!")
return FALSE
stored_research.enqueue_node(id, user)
return TRUE
/obj/machinery/computer/rdconsole/proc/dequeue_node(id, mob/user)
if(!stored_research || !stored_research.available_nodes[id] || stored_research.researched_nodes[id])
say("Node dequeue failed: Either no techweb is found, node is already researched or is not available!")
return FALSE
stored_research.dequeue_node(id, user)
return TRUE
/obj/machinery/computer/rdconsole/proc/research_node(id, mob/user)
if(!stored_research || !stored_research.available_nodes[id] || stored_research.researched_nodes[id])
say("Node unlock failed: Either no techweb is found, node is already researched or is not available!")
@@ -171,6 +185,7 @@ Nothing else in the console has ID requirements.
return data
data += list(
"nodes" = list(),
"queue_nodes" = stored_research.research_queue_nodes,
"experiments" = list(),
"researched_designs" = stored_research.researched_designs,
"points" = stored_research.research_points,
@@ -194,6 +209,10 @@ Nothing else in the console has ID requirements.
// Serialize all nodes to display
for(var/v in stored_research.tiers)
var/datum/techweb_node/n = SSresearch.techweb_node_by_id(v)
var/enqueued_by_user = FALSE
if((v in stored_research.research_queue_nodes) && stored_research.research_queue_nodes[v] == user)
enqueued_by_user = TRUE
// Ensure node is supposed to be visible
if (stored_research.hidden_nodes[v])
@@ -201,8 +220,11 @@ Nothing else in the console has ID requirements.
data["nodes"] += list(list(
"id" = n.id,
"is_free" = n.is_free(stored_research),
"can_unlock" = stored_research.can_unlock_node(n),
"have_experiments_done" = stored_research.have_experiments_for_node(n),
"tier" = stored_research.tiers[n.id],
"enqueued_by_user" = enqueued_by_user
))
// Get experiments and serialize them
@@ -322,6 +344,12 @@ Nothing else in the console has ID requirements.
if ("researchNode")
research_node(params["node_id"], usr)
return TRUE
if ("enqueueNode")
enqueue_node(params["node_id"], usr)
return TRUE
if ("dequeueNode")
dequeue_node(params["node_id"], usr)
return TRUE
if ("ejectDisk")
eject_disk(params["type"])
return TRUE
+44
View File
@@ -70,6 +70,12 @@
* Filled with nulls on init, populated only on publication.
*/
var/list/published_papers
/**
* Assoc list of nodes queued for automatic research when there are enough points available
* research_queue_nodes[node_id] = user_enqueued
*/
var/list/research_queue_nodes = list()
/datum/techweb/New()
SSresearch.techwebs += src
@@ -325,6 +331,40 @@
/datum/techweb/proc/printout_points()
return techweb_point_display_generic(research_points)
/datum/techweb/proc/enqueue_node(id, mob/user)
var/mob/living/carbon/human/human_user = user
var/is_rd = FALSE
if(human_user.wear_id)
var/list/access = human_user.wear_id.GetAccess()
if(ACCESS_RD in access)
is_rd = TRUE
if(id in research_queue_nodes)
if(is_rd)
research_queue_nodes.Remove(id)
else
return FALSE
for(var/node_id in research_queue_nodes)
if(research_queue_nodes[node_id] == user)
research_queue_nodes.Remove(node_id)
if (is_rd)
research_queue_nodes.Insert(1, id)
research_queue_nodes[id] = user
return TRUE
/datum/techweb/proc/dequeue_node(id, mob/user)
if(!(id in research_queue_nodes))
return FALSE
if(research_queue_nodes[id] != user)
return FALSE
research_queue_nodes.Remove(id)
return TRUE
/datum/techweb/proc/research_node_id(id, force, auto_update_points, get_that_dosh_id)
return research_node(SSresearch.techweb_node_by_id(id), force, auto_update_points, get_that_dosh_id)
@@ -377,6 +417,10 @@
if (MC_RUNNING())
log_research(log_message)
// Dequeue
if(node.id in research_queue_nodes)
research_queue_nodes.Remove(node.id)
return TRUE
/datum/techweb/proc/unresearch_node_id(id)
@@ -95,6 +95,17 @@
return actual_costs
/datum/techweb_node/proc/is_free(datum/techweb/host)
var/list/costs = get_price(host)
var/total_points = 0
for(var/point_type in costs)
total_points += costs[point_type]
if(total_points == 0)
return TRUE
return FALSE
/datum/techweb_node/proc/price_display(datum/techweb/TN)
return techweb_point_display_generic(get_price(TN))
+75 -31
View File
@@ -10,6 +10,7 @@ import {
Flex,
Icon,
Input,
LabeledList,
Modal,
ProgressBar,
Section,
@@ -147,6 +148,8 @@ export const TechwebContent = (props) => {
t_disk,
d_disk,
locked,
queue_nodes = [],
node_cache,
} = data;
const [techwebRoute, setTechwebRoute] = useLocalState('techwebRoute', null);
const [lastPoints, setLastPoints] = useState({});
@@ -156,27 +159,35 @@ export const TechwebContent = (props) => {
<Flex.Item className="Techweb__HeaderSection">
<Flex className="Techweb__HeaderContent">
<Flex.Item>
<Box>
Available points:
<ul className="Techweb__PointSummary">
{Object.keys(points).map((k) => (
<li key={k}>
<b>{k}</b>: {points[k]}
{!!points_last_tick[k] && ` (+${points_last_tick[k]}/sec)`}
</li>
))}
</ul>
</Box>
<Box>
Security protocols:
<span
className={`Techweb__SecProtocol ${
!!sec_protocols && 'engaged'
}`}
>
{sec_protocols ? 'Engaged' : 'Disengaged'}
</span>
</Box>
<LabeledList>
<LabeledList.Item label="Security">
<span
className={`Techweb__SecProtocol ${
!!sec_protocols && 'engaged'
}`}
>
{sec_protocols ? 'Engaged' : 'Disengaged'}
</span>
</LabeledList.Item>
{Object.keys(points).map((k) => (
<LabeledList.Item key={k} label="Points">
<b>{points[k]}</b>
{!!points_last_tick[k] && ` (+${points_last_tick[k]}/sec)`}
</LabeledList.Item>
))}
<LabeledList.Item label="Queue">
{queue_nodes.length !== 0
? Object.keys(queue_nodes).map((node_id) => (
<Button
key={node_id}
tooltip={`Added by: ${queue_nodes[node_id]}`}
>
{node_cache[node_id].name}
</Button>
))
: 'Empty'}
</LabeledList.Item>
</LabeledList>
</Flex.Item>
<Flex.Item grow={1} />
<Flex.Item>
@@ -493,9 +504,17 @@ const TechNode = (props) => {
points = [],
nodes,
point_types_abbreviations = [],
queue_nodes = [],
} = data;
const { node, nodetails, nocontrols } = props;
const { id, can_unlock, tier } = node;
const {
id,
can_unlock,
have_experiments_done,
tier,
enqueued_by_user,
is_free,
} = node;
const {
name,
description,
@@ -555,6 +574,40 @@ const TechNode = (props) => {
buttons={
!nocontrols && (
<>
{tier > 0 &&
(!!can_unlock && (is_free || queue_nodes.length === 0) ? (
<Button
icon="lightbulb"
disabled={!can_unlock || tier > 1 || queue_nodes.length > 0}
onClick={() => act('researchNode', { node_id: id })}
>
Research
</Button>
) : enqueued_by_user ? (
<Button
icon="trash"
color="bad"
onClick={() => act('dequeueNode', { node_id: id })}
>
Dequeue
</Button>
) : id in queue_nodes && !enqueued_by_user ? (
<Button icon="check" color="good">
Queued
</Button>
) : (
<Button
icon="lightbulb"
disabled={
!have_experiments_done ||
id in queue_nodes ||
techcompl < prereq_ids.length
}
onClick={() => act('enqueueNode', { node_id: id })}
>
Enqueue
</Button>
))}
{!nodetails && (
<Button
icon="tasks"
@@ -566,15 +619,6 @@ const TechNode = (props) => {
Details
</Button>
)}
{tier > 0 && (
<Button
icon="lightbulb"
disabled={!can_unlock || tier > 1}
onClick={() => act('researchNode', { node_id: id })}
>
Research
</Button>
)}
</>
)
}