From b7d5b2e3430aa05ed8123563d490d69f0ada7498 Mon Sep 17 00:00:00 2001 From: QuiteLiterallyAnything <154708292+QuiteLiterallyAnything@users.noreply.github.com> Date: Wed, 3 Jun 2026 19:44:37 -0500 Subject: [PATCH] Fix for toxins paper discounts not appearing in UI (#96222) ## About The Pull Request Fixes #68995 Hello! This is my third-first SS13 PR. Accordingly, I thought I'd go for a simple bugfix. It turned out to be a slight bit more difficult than expected, but as a whole it's relatively short. Essentially, the discounts gained from toxins papers do not show up on research consoles. They still apply in code, but a zero-cost node will sometimes appear at its regular price in UI. After a substantial amount of confusion, I determined that this was due to the discount data simply not being passed to UIs at all. For both ease of handling the discounts and their passing as static data, I have refactored discounts from a singular nested list on techwebs (`boosted_nodes`) to a less complex list per tech node (`discount_boosts`). While coding, I have attempted to preserve the (presumably unused) capacity for discounts to vary by research point type. It does seem that discounts were only ever applied as general research points, but it should be easier if anyone ever intends to implement other point types. ## Why It's Good For The Game Visual bugs cause confusion, and this PR fixes one. It's also a bit odd that toxins discounts weren't passed to UIs in the first place. ## Changelog :cl: fix: Made discounts from toxins papers show up in research UIs properly. refactor: Refactored research techwebs such that toxins discounts are now stored per techweb node rather than per techweb. /:cl: --- .../file_system/programs/techweb.dm | 5 ++++- code/modules/research/ordnance/_scipaper.dm | 11 ++++++++++- code/modules/research/rdconsole.dm | 5 ++++- code/modules/research/techweb/_techweb.dm | 9 ++++----- code/modules/research/techweb/_techweb_node.dm | 14 ++++++++++---- .../packages/tgui/interfaces/Techweb/helpers.ts | 1 + .../tgui/interfaces/Techweb/nodes/TechNode.tsx | 17 +++++++++++++++-- tgui/packages/tgui/interfaces/Techweb/types.ts | 2 ++ 8 files changed, 50 insertions(+), 14 deletions(-) diff --git a/code/modules/modular_computers/file_system/programs/techweb.dm b/code/modules/modular_computers/file_system/programs/techweb.dm index c01c865fbf7..3f9afa61079 100644 --- a/code/modules/modular_computers/file_system/programs/techweb.dm +++ b/code/modules/modular_computers/file_system/programs/techweb.dm @@ -80,7 +80,8 @@ "can_unlock" = stored_research.can_unlock_node(node), "have_experiments_done" = stored_research.have_experiments_for_node(node), "tier" = stored_research.tiers[node.id], - "enqueued_by_user" = enqueued_by_user + "enqueued_by_user" = enqueued_by_user, + "discount_boosted" = node.discount_boosted )) // Get experiments and serialize them @@ -156,6 +157,8 @@ node_cache[compressed_id]["required_experiments"] = node.required_experiments if (LAZYLEN(node.discount_experiments)) node_cache[compressed_id]["discount_experiments"] = node.discount_experiments + if (LAZYLEN(node.discount_boosts)) + node_cache[compressed_id]["discount_boosts"] = node.discount_boosts // Build design cache var/design_cache = list() diff --git a/code/modules/research/ordnance/_scipaper.dm b/code/modules/research/ordnance/_scipaper.dm index e5fa3c3c8f0..17cab50c6d2 100644 --- a/code/modules/research/ordnance/_scipaper.dm +++ b/code/modules/research/ordnance/_scipaper.dm @@ -289,6 +289,14 @@ /// Associative list of which technology the partner might be able to boost and by how much. var/list/boostable_nodes = list() +/datum/scientific_partner/New() + . = ..() + // Convey boosts to their associated nodes so that they can then be passed + // to techweb UIs as static data. + for(var/node_id in boostable_nodes) + var/datum/techweb_node/node = SSresearch.techweb_node_by_id(node_id) + node.discount_boosts[TECHWEB_POINT_TYPE_GENERIC] = boostable_nodes[node_id] + /datum/scientific_partner/proc/purchase_boost(datum/techweb/purchasing_techweb, datum/techweb_node/node) var/possible_boost = allowed_to_boost(purchasing_techweb, node.id) if(!possible_boost) @@ -301,9 +309,10 @@ return TRUE /datum/scientific_partner/proc/allowed_to_boost(datum/techweb/purchasing_techweb, node_id) + var/datum/techweb_node/boosting_node = SSresearch.techweb_node_by_id(node_id) if(purchasing_techweb.scientific_cooperation[type] < (boostable_nodes[node_id] * SCIENTIFIC_COOPERATION_PURCHASE_MULTIPLIER)) // Too expensive return FALSE - if((TECHWEB_POINT_TYPE_GENERIC in purchasing_techweb.boosted_nodes[node_id]) && (purchasing_techweb.boosted_nodes[node_id][TECHWEB_POINT_TYPE_GENERIC] >= boostable_nodes[node_id])) // Already bought or we have a bigger discount + if((boosting_node.discount_boosted) && (boosting_node.discount_boosts[TECHWEB_POINT_TYPE_GENERIC] >= boostable_nodes[node_id])) // Already bought or we have a bigger discount return FALSE if(node_id in purchasing_techweb.researched_nodes) return SCIPAPER_ALREADY_BOUGHT diff --git a/code/modules/research/rdconsole.dm b/code/modules/research/rdconsole.dm index 36c3a43a603..60fa5907556 100644 --- a/code/modules/research/rdconsole.dm +++ b/code/modules/research/rdconsole.dm @@ -243,7 +243,8 @@ Nothing else in the console has ID requirements. "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 + "enqueued_by_user" = enqueued_by_user, + "discount_boosted" = n.discount_boosted )) // Get experiments and serialize them @@ -305,6 +306,8 @@ Nothing else in the console has ID requirements. node_cache[compressed_id]["required_experiments"] = node.required_experiments if (LAZYLEN(node.discount_experiments)) node_cache[compressed_id]["discount_experiments"] = node.discount_experiments + if (LAZYLEN(node.discount_boosts)) + node_cache[compressed_id]["discount_boosts"] = node.discount_boosts // Build design cache var/design_cache = list() diff --git a/code/modules/research/techweb/_techweb.dm b/code/modules/research/techweb/_techweb.dm index ade382a0265..8d492aa4af9 100644 --- a/code/modules/research/techweb/_techweb.dm +++ b/code/modules/research/techweb/_techweb.dm @@ -22,8 +22,6 @@ var/list/researched_designs = list() /// Custom inserted designs like from disks that should survive recalculation. var/list/custom_designs = list() - /// Already boosted nodes that can't be boosted again. node id = path of boost object. - var/list/boosted_nodes = list() /// Hidden nodes. id = TRUE. Used for unhiding nodes when requirements are met by removing the entry of the node. var/list/hidden_nodes = list() /// List of items already deconstructed for research points, preventing infinite research point generation. @@ -433,9 +431,10 @@ /datum/techweb/proc/boost_techweb_node(datum/techweb_node/node, list/pointlist) if(!istype(node)) return FALSE - LAZYINITLIST(boosted_nodes[node.id]) - for(var/point_type in pointlist) - boosted_nodes[node.id][point_type] = max(boosted_nodes[node.id][point_type], pointlist[point_type]) + LAZYINITLIST(node.discount_boosts) + for(var/point_type in pointlist) // Essentially applies the greater boost(s) between the newer and any existing. + node.discount_boosts[point_type] = max(node.discount_boosts[point_type], pointlist[point_type]) + node.discount_boosted = TRUE unhide_node(node) update_node_status(node) return TRUE diff --git a/code/modules/research/techweb/_techweb_node.dm b/code/modules/research/techweb/_techweb_node.dm index 104e475b7e5..0eccb61c4ec 100644 --- a/code/modules/research/techweb/_techweb_node.dm +++ b/code/modules/research/techweb/_techweb_node.dm @@ -36,6 +36,13 @@ var/list/required_experiments = list() /// If completed, these experiments give a specific point amount discount to the node. var/list/discount_experiments = list() + /// Boost quantities from non-experiment sources (i.e., toxins papers). + /// Indexed by point type to boost amount (with only one boost per point type). + var/list/discount_boosts = list() + /// Boolean indicating whether or not this node is boosted by non-experiments. + /// This will need to be changed to a list of point types boosted if boosts + /// should ever need to vary by point type. + var/discount_boosted = FALSE /// When this node is completed, allows these experiments to be performed. var/list/experiments_to_unlock = list() /// Whether or not this node should show on the wiki @@ -92,11 +99,10 @@ if(host.completed_experiments[experiment_type]) //do we have this discount_experiment unlocked? actual_costs[cost_type] -= discount_experiments[experiment_type] - if(host.boosted_nodes[id]) // Boosts should be subservient to experiments. - var/list/boostlist = host.boosted_nodes[id] - for(var/booster in boostlist) + if(discount_boosts && discount_boosted) // Boosts should be subservient to experiments. + for(var/booster in discount_boosts) if(actual_costs[booster]) - actual_costs[booster] = max(actual_costs[booster] - boostlist[booster], 0) + actual_costs[booster] = max(actual_costs[booster] - discount_boosts[booster], 0) return actual_costs diff --git a/tgui/packages/tgui/interfaces/Techweb/helpers.ts b/tgui/packages/tgui/interfaces/Techweb/helpers.ts index 12e042963d7..8215572d64f 100644 --- a/tgui/packages/tgui/interfaces/Techweb/helpers.ts +++ b/tgui/packages/tgui/interfaces/Techweb/helpers.ts @@ -51,6 +51,7 @@ function selectRemappedStaticData(data: TechWebData) { unlock_ids: map(node.unlock_ids || [], remapId), required_experiments: node.required_experiments || [], discount_experiments: node.discount_experiments || [], + discount_boosts: node.discount_boosts || [], }; } diff --git a/tgui/packages/tgui/interfaces/Techweb/nodes/TechNode.tsx b/tgui/packages/tgui/interfaces/Techweb/nodes/TechNode.tsx index 6e4d7aadb40..613fe82708d 100644 --- a/tgui/packages/tgui/interfaces/Techweb/nodes/TechNode.tsx +++ b/tgui/packages/tgui/interfaces/Techweb/nodes/TechNode.tsx @@ -40,6 +40,7 @@ export function TechNode(props: Props) { tier, enqueued_by_user, is_free, + discount_boosted, } = node; const { name, @@ -49,6 +50,7 @@ export function TechNode(props: Props) { prereq_ids, required_experiments, discount_experiments, + discount_boosts, } = node_cache[id]; const [techwebRoute, setTechwebRoute] = useTechWebRoute(); @@ -86,12 +88,20 @@ export function TechNode(props: Props) { // Notice that this logic will have to be changed if we make the discounts // pool-specific - const nodeDiscount = Object.keys(discount_experiments) + const nodeDiscountExperiments = Object.keys(discount_experiments) .filter((x) => experiments[x]?.completed) .reduce((tot, curr) => { return tot + discount_experiments[curr]; }, 0); + // Will need to be changed (along with some backend/DM code) if boosts should + // ever vary by point type. As is, this simply adds up all discount boosts. + const nodeDiscountBoosts = discount_boosted + ? Object.keys(discount_boosts).reduce((tot, curr) => { + return tot + discount_boosts[curr]; + }, 0) + : 0; + return (
{costs.map((k) => { - const reqPts = Math.max(0, k.value - nodeDiscount); + const reqPts = Math.max( + 0, + k.value - nodeDiscountExperiments - nodeDiscountBoosts, + ); const nodeProg = Math.min(reqPts, points[k.type]) || 0; return ( diff --git a/tgui/packages/tgui/interfaces/Techweb/types.ts b/tgui/packages/tgui/interfaces/Techweb/types.ts index 0c40c46f47d..9ccfd0d3991 100644 --- a/tgui/packages/tgui/interfaces/Techweb/types.ts +++ b/tgui/packages/tgui/interfaces/Techweb/types.ts @@ -16,6 +16,7 @@ export type NodeCache = { description: string; design_ids: string[]; discount_experiments: Record; + discount_boosts: Record; name: string; prereq_ids: string[]; required_experiments?: string[]; @@ -32,6 +33,7 @@ export type TechwebNode = { can_unlock: BooleanLike; enqueued_by_user: BooleanLike; have_experiments_done: BooleanLike; + discount_boosted: BooleanLike; id: string; is_free: BooleanLike; tier: number;