From 0cfde8c27266087ee409822b90669664fdb0d753 Mon Sep 17 00:00:00 2001 From: SkyratBot <59378654+SkyratBot@users.noreply.github.com> Date: Tue, 14 Nov 2023 15:19:08 +0100 Subject: [PATCH] [MIRROR] TGUI Destructive Analyzer [MDB IGNORE] (#25005) * TGUI Destructive Analyzer (#79572) ## About The Pull Request I made this to help me move more towards my goals [laid out here](https://hackmd.io/XLt5MoRvRxuhFbwtk4VAUA) which currently doesn't have much interest. This makes the Destructive Analyzer use a little neat TGUI menu instead of its old HTML one. I also touch a lot of science stuff and a little experimentor stuff, so let me explain a bit: Old iterations of Science had different items that you can use to boost nodes through deconstruction. This has been removed, and its only feature is the auto-unlocking of nodes (that is; making them visible to the R&D console). I thought that instead of keeping this deprecated code around, I would rework it a little to make it clear what we actually use it for (unhiding nodes). All vars and procs that mentioned this have been renamed or reworked to make more sense now. Experimentor stuff shares a lot with the destructive analyzer, so I had to mess with that a bit to keep its decayed corpse of deprecated code, functional. I also added context tips to the destructive analyzer, and added the ability to AltClick to remove the inserted item. Removing items now also plays a little sound because it was kinda lame. Also, balloon alerts. ## Why It's Good For The Game Moves a shitty machine to TGUI so it is slightly less shitty, now it's more direct and compact with more player-feedback. Helps me with a personal project and yea ### Video demonstration I show off connecting the machine to R&D Servers, but I haven't changed the behavior of that and the roundstart analyzers are connected to servers by default. https://github.com/tgstation/tgstation/assets/53777086/65295600-4fae-42d1-9bae-eccefe337a2b ## Changelog :cl: refactor: Destructive Analyzers now have a TGUI menu. /:cl: * TGUI Destructive Analyzer * Modular --------- Co-authored-by: John Willard <53777086+JohnFulpWillard@users.noreply.github.com> Co-authored-by: Giz <13398309+vinylspiders@users.noreply.github.com> --- code/__DEFINES/research.dm | 4 - code/controllers/subsystem/research.dm | 42 +-- code/game/objects/items.dm | 5 +- .../machines/machine_circuitboards.dm | 3 +- .../file_system/programs/frontier.dm | 6 +- code/modules/research/destructive_analyzer.dm | 354 ++++++++---------- code/modules/research/experimentor.dm | 41 +- code/modules/research/ordnance/_scipaper.dm | 11 +- .../research/ordnance/scipaper_partner.dm | 10 +- code/modules/research/rdmachines.dm | 29 +- .../research/techweb/__techweb_helpers.dm | 9 +- code/modules/research/techweb/_techweb.dm | 14 +- .../modules/research/techweb/_techweb_node.dm | 4 +- code/modules/research/techweb/all_nodes.dm | 10 +- .../modules/vehicles/mecha/mech_fabricator.dm | 10 - .../modules/mutants/code/mutant_cure.dm | 10 +- .../tgui/interfaces/DestructiveAnalyzer.tsx | 135 +++++++ 17 files changed, 374 insertions(+), 323 deletions(-) create mode 100644 tgui/packages/tgui/interfaces/DestructiveAnalyzer.tsx diff --git a/code/__DEFINES/research.dm b/code/__DEFINES/research.dm index c1427fcb67a..005c67ae1a3 100644 --- a/code/__DEFINES/research.dm +++ b/code/__DEFINES/research.dm @@ -1,10 +1,6 @@ -#define RDSCREEN_NOBREAK "" - /// For instances where we don't want a design showing up due to it being for debug/sanity purposes #define DESIGN_ID_IGNORE "IGNORE_THIS_DESIGN" -#define RESEARCH_MATERIAL_DESTROY_ID "__destroy" - //! Techweb names for new point types. Can be used to define specific point values for specific types of research (science, security, engineering, etc.) #define TECHWEB_POINT_TYPE_GENERIC "General Research" diff --git a/code/controllers/subsystem/research.dm b/code/controllers/subsystem/research.dm index 1cc3468fb7d..1d31582fc5f 100644 --- a/code/controllers/subsystem/research.dm +++ b/code/controllers/subsystem/research.dm @@ -27,15 +27,15 @@ SUBSYSTEM_DEF(research) var/list/techweb_nodes_starting = list() ///category name = list(node.id = TRUE) var/list/techweb_categories = list() - ///associative double-layer path = list(id = list(point_type = point_discount)) - var/list/techweb_boost_items = list() + ///List of all items that can unlock a node. (node.id = list(items)) + var/list/techweb_unlock_items = list() ///Node ids that should be hidden by default. var/list/techweb_nodes_hidden = list() ///Node ids that are exclusive to the BEPIS. 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 = 10000) ) var/list/errored_datums = list() var/list/point_types = list() //typecache style type = TRUE list @@ -64,7 +64,7 @@ SUBSYSTEM_DEF(research) /// Lookup list for ordnance briefers. var/list/ordnance_experiments = list() /// Lookup list for scipaper partners. - var/list/scientific_partners = list() + var/list/datum/scientific_partner/scientific_partners = list() /datum/controller/subsystem/research/Initialize() point_types = TECHWEB_POINT_TYPE_LIST_ASSOCIATIVE_NAMES @@ -153,7 +153,7 @@ SUBSYSTEM_DEF(research) if (!verify_techweb_nodes()) //Verify all nodes have ids and such. stack_trace("Invalid techweb nodes detected") calculate_techweb_nodes() - calculate_techweb_boost_list() + calculate_techweb_item_unlocking_requirements() if (!verify_techweb_nodes()) //Verify nodes and designs have been crosslinked properly. CRASH("Invalid techweb nodes detected") @@ -209,25 +209,15 @@ SUBSYSTEM_DEF(research) N.unlock_ids -= u research_node_id_error(u) . = FALSE - for(var/p in N.boost_item_paths) + for(var/p in N.required_items_to_unlock) if(!ispath(p)) - N.boost_item_paths -= p + N.required_items_to_unlock -= p WARNING("[p] is not a valid path.") node_boost_error(N.id, "[p] is not a valid path.") . = FALSE - var/list/points = N.boost_item_paths[p] - if(islist(points)) - for(var/i in points) - if(!isnum(points[i])) - WARNING("[points[i]] is not a valid number.") - node_boost_error(N.id, "[points[i]] is not a valid number.") - . = FALSE - else if(!point_types[i]) - WARNING("[i] is not a valid point type.") - node_boost_error(N.id, "[i] is not a valid point type.") - . = FALSE - else if(!isnull(points)) - N.boost_item_paths -= p + var/list/points = N.required_items_to_unlock[p] + if(!isnull(points)) + N.required_items_to_unlock -= p node_boost_error(N.id, "No valid list.") WARNING("No valid list.") . = FALSE @@ -281,18 +271,16 @@ SUBSYSTEM_DEF(research) var/datum/techweb_node/prereq_node = techweb_node_by_id(prereq_id) prereq_node.unlock_ids[node.id] = node -/datum/controller/subsystem/research/proc/calculate_techweb_boost_list(clearall = FALSE) - if(clearall) - techweb_boost_items = list() +/datum/controller/subsystem/research/proc/calculate_techweb_item_unlocking_requirements() for(var/node_id in techweb_nodes) var/datum/techweb_node/node = techweb_nodes[node_id] - for(var/path in node.boost_item_paths) + for(var/path in node.required_items_to_unlock) if(!ispath(path)) continue - if(length(techweb_boost_items[path])) - techweb_boost_items[path][node.id] = node.boost_item_paths[path] + if(length(techweb_unlock_items[path])) + techweb_unlock_items[path][node.id] = node.required_items_to_unlock[path] else - techweb_boost_items[path] = list(node.id = node.boost_item_paths[path]) + techweb_unlock_items[path] = list(node.id = node.required_items_to_unlock[path]) CHECK_TICK /datum/controller/subsystem/research/proc/populate_ordnance_experiments() diff --git a/code/game/objects/items.dm b/code/game/objects/items.dm index 1e874fed1a6..5088db752fe 100644 --- a/code/game/objects/items.dm +++ b/code/game/objects/items.dm @@ -443,7 +443,7 @@ ///Separator between the items on the list var/sep = "" ///Nodes that can be boosted - var/list/boostable_nodes = techweb_item_boost_check(src) + var/list/boostable_nodes = techweb_item_unlock_check(src) if (boostable_nodes) for(var/id in boostable_nodes) var/datum/techweb_node/node = SSresearch.techweb_node_by_id(id) @@ -618,9 +618,6 @@ R.activate_module(src) R.hud_used.update_robot_modules_display() -/obj/item/proc/GetDeconstructableContents() - return get_all_contents() - src - // afterattack() and attack() prototypes moved to _onclick/item_attack.dm for consistency /obj/item/proc/hit_reaction(mob/living/carbon/human/owner, atom/movable/hitby, attack_text = "the attack", final_block_chance = 0, damage = 0, attack_type = MELEE_ATTACK, damage_type = BRUTE) diff --git a/code/game/objects/items/circuitboards/machines/machine_circuitboards.dm b/code/game/objects/items/circuitboards/machines/machine_circuitboards.dm index ea62ad98090..00db42bad50 100644 --- a/code/game/objects/items/circuitboards/machines/machine_circuitboards.dm +++ b/code/game/objects/items/circuitboards/machines/machine_circuitboards.dm @@ -968,7 +968,8 @@ req_components = list( /datum/stock_part/scanning_module = 1, /datum/stock_part/servo = 1, - /datum/stock_part/micro_laser = 1) + /datum/stock_part/micro_laser = 1, + ) /obj/item/circuitboard/machine/experimentor name = "E.X.P.E.R.I-MENTOR" diff --git a/code/modules/modular_computers/file_system/programs/frontier.dm b/code/modules/modular_computers/file_system/programs/frontier.dm index df57302871b..3b001e9f39d 100644 --- a/code/modules/modular_computers/file_system/programs/frontier.dm +++ b/code/modules/modular_computers/file_system/programs/frontier.dm @@ -65,9 +65,9 @@ singular_partner["path"] = partner.type singular_partner["boostedNodes"] = list() singular_partner["acceptedExperiments"] = list() - for (var/node_id in partner.boosted_nodes) + for (var/node_id in partner.boostable_nodes) var/datum/techweb_node/node = SSresearch.techweb_node_by_id(node_id) - singular_partner["boostedNodes"] += list(list("name" = node.display_name, "discount" = partner.boosted_nodes[node_id], "id"=node_id)) + singular_partner["boostedNodes"] += list(list("name" = node.display_name, "discount" = partner.boostable_nodes[node_id], "id" = node_id)) for (var/datum/experiment/ordnance/ordnance_experiment as anything in partner.accepted_experiments) singular_partner["acceptedExperiments"] += initial(ordnance_experiment.name) parsed_partners += list(singular_partner) @@ -154,7 +154,7 @@ data["purchaseableBoosts"][partner.type] = list() for(var/node_id in linked_techweb.get_available_nodes()) // Not from our partner - if(!(node_id in partner.boosted_nodes)) + if(!(node_id in partner.boostable_nodes)) continue if(!partner.allowed_to_boost(linked_techweb, node_id)) continue diff --git a/code/modules/research/destructive_analyzer.dm b/code/modules/research/destructive_analyzer.dm index 2d1e786208c..03a6d9934f5 100644 --- a/code/modules/research/destructive_analyzer.dm +++ b/code/modules/research/destructive_analyzer.dm @@ -1,225 +1,189 @@ -/* -Destructive Analyzer - -It is used to destroy hand-held objects and advance technological research. Controls are in the linked R&D console. - -Note: Must be placed within 3 tiles of the R&D Console -*/ +///How much power it costs to deconstruct an item. +#define DESTRUCTIVE_ANALYZER_POWER_USAGE (BASE_MACHINE_IDLE_CONSUMPTION * 2.5) +///The 'ID' for deconstructing items for Research points instead of nodes. +#define DESTRUCTIVE_ANALYZER_DESTROY_POINTS "research_points" + +/** + * ## Destructive Analyzer + * It is used to destroy hand-held objects and advance technological research. + */ /obj/machinery/rnd/destructive_analyzer name = "destructive analyzer" desc = "Learn science by destroying things!" icon_state = "d_analyzer" base_icon_state = "d_analyzer" circuit = /obj/item/circuitboard/machine/destructive_analyzer - var/decon_mod = 0 -/obj/machinery/rnd/destructive_analyzer/RefreshParts() +/obj/machinery/rnd/destructive_analyzer/Initialize(mapload) . = ..() - var/T = 0 - for(var/datum/stock_part/stock_part in component_parts) - T += stock_part.tier - decon_mod = T - -/obj/machinery/rnd/destructive_analyzer/proc/ConvertReqString2List(list/source_list) - var/list/temp_list = params2list(source_list) - for(var/O in temp_list) - temp_list[O] = text2num(temp_list[O]) - return temp_list - -/obj/machinery/rnd/destructive_analyzer/Insert_Item(obj/item/O, mob/living/user) - if(!user.combat_mode) - . = 1 - if(!is_insertion_ready(user)) - return - if(!user.transferItemToLoc(O, src)) - to_chat(user, span_warning("\The [O] is stuck to your hand, you cannot put it in the [src.name]!")) - return - busy = TRUE - loaded_item = O - to_chat(user, span_notice("You add the [O.name] to the [src.name]!")) - flick("d_analyzer_la", src) - addtimer(CALLBACK(src, PROC_REF(finish_loading)), 10) - updateUsrDialog() + register_context() + +/obj/machinery/rnd/destructive_analyzer/add_context(atom/source, list/context, obj/item/held_item, mob/living/user) + if(loaded_item) + context[SCREENTIP_CONTEXT_ALT_LMB] = "Remove Item" + else if(!isnull(held_item)) + context[SCREENTIP_CONTEXT_LMB] = "Insert Item" + return CONTEXTUAL_SCREENTIP_SET + +/obj/machinery/rnd/destructive_analyzer/attackby(obj/item/weapon, mob/living/user, params) + if(user.combat_mode) + return ..() + if(!is_insertion_ready(user)) + return ..() + if(!user.transferItemToLoc(weapon, src)) + to_chat(user, span_warning("\The [weapon] is stuck to your hand, you cannot put it in the [name]!")) + return TRUE + busy = TRUE + loaded_item = weapon + to_chat(user, span_notice("You add the [weapon.name] to the [name]!")) + flick("[base_icon_state]_la", src) + addtimer(CALLBACK(src, PROC_REF(finish_loading)), 1 SECONDS) + return TRUE -/obj/machinery/rnd/destructive_analyzer/proc/finish_loading() - update_appearance() - reset_busy() +/obj/machinery/rnd/destructive_analyzer/AltClick(mob/user) + . = ..() + unload_item() /obj/machinery/rnd/destructive_analyzer/update_icon_state() icon_state = "[base_icon_state][loaded_item ? "_l" : null]" return ..() -/obj/machinery/rnd/destructive_analyzer/proc/destroy_item(obj/item/thing, innermode = FALSE) - if(QDELETED(thing) || QDELETED(src)) - return FALSE - if(!innermode) - flick("d_analyzer_process", src) - busy = TRUE - addtimer(CALLBACK(src, PROC_REF(reset_busy)), 24) - use_power(250) - if(thing == loaded_item) - loaded_item = null - var/list/food = thing.GetDeconstructableContents() - for(var/obj/item/innerthing in food) - destroy_item(innerthing, TRUE) - for(var/mob/living/victim in thing) - if(victim.stat != DEAD) - victim.investigate_log("has been killed by a destructive analyzer.", INVESTIGATE_DEATHS) - victim.death() - - qdel(thing) - loaded_item = null - if (!innermode) - update_appearance() - return TRUE +/obj/machinery/rnd/destructive_analyzer/ui_interact(mob/user, datum/tgui/ui) + ui = SStgui.try_update_ui(user, src, ui) + if(!ui) + ui = new(user, src, "DestructiveAnalyzer") + ui.open() + +/obj/machinery/rnd/destructive_analyzer/ui_data(mob/user) + var/list/data = list() + data["server_connected"] = !!stored_research + data["node_data"] = list() + if(loaded_item) + data["item_icon"] = icon2base64(getFlatIcon(image(icon = loaded_item.icon, icon_state = loaded_item.icon_state), no_anim = TRUE)) + data["indestructible"] = !(loaded_item.resistance_flags & INDESTRUCTIBLE) + data["loaded_item"] = loaded_item + data["already_deconstructed"] = !!stored_research.deconstructed_items[loaded_item.type] + var/list/points = techweb_item_point_check(loaded_item) + data["recoverable_points"] = techweb_point_display_generic(points) + + var/list/boostable_nodes = techweb_item_unlock_check(loaded_item) + for(var/id in boostable_nodes) + var/datum/techweb_node/unlockable_node = SSresearch.techweb_node_by_id(id) + var/list/node_data = list() + node_data["node_name"] = unlockable_node.display_name + node_data["node_id"] = unlockable_node.id + node_data["node_hidden"] = !!stored_research.hidden_nodes[unlockable_node.id] + data["node_data"] += list(node_data) + else + data["loaded_item"] = null + return data -/obj/machinery/rnd/destructive_analyzer/proc/user_try_decon_id(id, mob/user) - if(!istype(loaded_item)) - return FALSE +/obj/machinery/rnd/destructive_analyzer/ui_static_data(mob/user) + var/list/data = list() + data["research_point_id"] = DESTRUCTIVE_ANALYZER_DESTROY_POINTS + return data - if (id && id != RESEARCH_MATERIAL_DESTROY_ID) - var/datum/techweb_node/TN = SSresearch.techweb_node_by_id(id) - if(!istype(TN)) - return FALSE - var/dpath = loaded_item.type - var/list/worths = TN.boost_item_paths[dpath] - var/list/differences = list() - var/list/already_boosted = stored_research.boosted_nodes[TN.id] - for(var/i in worths) - var/used = already_boosted? already_boosted[i] : 0 - var/value = min(worths[i], TN.research_costs[i]) - used - if(value > 0) - differences[i] = value - if(length(worths) && !length(differences)) - return FALSE - var/choice = tgui_alert(user, "Are you sure you want to destroy [loaded_item] to [!length(worths) ? "reveal [TN.display_name]" : "boost [TN.display_name] by [json_encode(differences)] point\s"]?", "Destructive Analyzer", list("Proceed", "Cancel")) - if(choice != "Proceed") - return FALSE - if(QDELETED(loaded_item) || QDELETED(src)) - return FALSE - SSblackbox.record_feedback("nested tally", "item_deconstructed", 1, list("[TN.id]", "[loaded_item.type]")) - if(destroy_item(loaded_item)) - stored_research.boost_with_item(SSresearch.techweb_node_by_id(TN.id), dpath) +/obj/machinery/rnd/destructive_analyzer/ui_act(action, params, datum/tgui/ui) + . = ..() + if(.) + return - else - var/list/point_value = techweb_item_point_check(loaded_item) - if(stored_research.deconstructed_items[loaded_item.type]) - point_value = list() - var/user_mode_string = "" - if(length(point_value)) - user_mode_string = " for [json_encode(point_value)] points" - var/choice = tgui_alert(usr, "Are you sure you want to destroy [loaded_item][user_mode_string]?",, list("Proceed", "Cancel")) - if(choice == "Cancel") - return FALSE - if(QDELETED(loaded_item) || QDELETED(src)) - return FALSE - destroy_item(loaded_item) - return TRUE + var/mob/user = usr + switch(action) + if("eject_item") + if(busy) + balloon_alert(user, "already busy!") + return TRUE + if(loaded_item) + unload_item() + return TRUE + if("deconstruct") + if(!user_try_decon_id(params["deconstruct_id"])) + say("Destructive analysis failed!") + return TRUE + +//This allows people to put syndicate screwdrivers in the machine. Secondary act still passes. +/obj/machinery/rnd/destructive_analyzer/screwdriver_act(mob/living/user, obj/item/tool) + return FALSE +///Drops the loaded item where it can and nulls it. /obj/machinery/rnd/destructive_analyzer/proc/unload_item() if(!loaded_item) return FALSE - loaded_item.forceMove(get_turf(src)) + playsound(loc, 'sound/machines/terminal_insert_disc.ogg', 30, FALSE) + loaded_item.forceMove(drop_location()) loaded_item = null - update_appearance() + update_appearance(UPDATE_ICON) return TRUE -/obj/machinery/rnd/destructive_analyzer/ui_interact(mob/user) - . = ..() - var/datum/browser/popup = new(user, "destructive_analyzer", name, 900, 600) - popup.set_content(ui_deconstruct()) - popup.open() +///Called in a timer callback after loading something into it, this handles resetting the 'busy' state back to its initial state +///So the machine can be used. +/obj/machinery/rnd/destructive_analyzer/proc/finish_loading() + update_appearance(UPDATE_ICON) + reset_busy() -/obj/machinery/rnd/destructive_analyzer/proc/ui_deconstruct() //Legacy code - var/list/l = list() - if(!loaded_item) - l += "
No item loaded. Standing-by...
" - else - l += "
[RDSCREEN_NOBREAK]" - l += "
[icon2html(loaded_item, usr)][loaded_item.name] Eject
[RDSCREEN_NOBREAK]" - l += "Select a node to boost by deconstructing this item. This item can boost:" +/** + * Destroys an item by going through all its contents (including itself) and calling destroy_item_individual + * Args: + * gain_research_points - Whether deconstructing each individual item should check for research points to boost. + */ +/obj/machinery/rnd/destructive_analyzer/proc/destroy_item(gain_research_points = FALSE) + if(QDELETED(loaded_item) || QDELETED(src)) + return FALSE + flick("[base_icon_state]_process", src) + busy = TRUE + addtimer(CALLBACK(src, PROC_REF(reset_busy)), 2.4 SECONDS) + use_power(DESTRUCTIVE_ANALYZER_POWER_USAGE) + var/list/all_contents = loaded_item.get_all_contents() + for(var/innerthing in all_contents) + destroy_item_individual(innerthing, gain_research_points) - var/anything = FALSE - var/list/boostable_nodes = techweb_item_boost_check(loaded_item) - for(var/id in boostable_nodes) - anything = TRUE - var/list/worth = boostable_nodes[id] - var/datum/techweb_node/N = SSresearch.techweb_node_by_id(id) - - l += "
[RDSCREEN_NOBREAK]" - if (stored_research.researched_nodes[N.id]) // already researched - l += "[N.display_name]" - l += "This node has already been researched." - else if(!length(worth)) // reveal only - if (stored_research.hidden_nodes[N.id]) - l += "[N.display_name]" - l += "This node will be revealed." - else - l += "[N.display_name]" - l += "This node has already been revealed." - else // boost by the difference - var/list/differences = list() - var/list/already_boosted = stored_research.boosted_nodes[N.id] - for(var/i in worth) - var/already_boosted_amount = already_boosted? stored_research.boosted_nodes[N.id][i] : 0 - var/amt = min(worth[i], N.research_costs[i]) - already_boosted_amount - if(amt > 0) - differences[i] = amt - if (length(differences)) - l += "[N.display_name]" - l += "This node will be boosted with the following:
[techweb_point_display_generic(differences)]" - else - l += "[N.display_name]" - l += "This node has already been boosted." - l += "
[RDSCREEN_NOBREAK]" - - var/list/point_values = techweb_item_point_check(loaded_item) - if(point_values) - anything = TRUE - l += "
[RDSCREEN_NOBREAK]" - if (stored_research.deconstructed_items[loaded_item.type]) - l += "Point Deconstruction" - l += "This item's points have already been claimed." - else - l += "Point Deconstruction" - l += "This item is worth:
[techweb_point_display_generic(point_values)]!" - l += "
[RDSCREEN_NOBREAK]" - - if(!(loaded_item.resistance_flags & INDESTRUCTIBLE)) - l += "
Destroy Item" - l += "
[RDSCREEN_NOBREAK]" - anything = TRUE - - if (!anything) - l += "Nothing!" - - l += "
" - - for(var/i in 1 to length(l)) - if(!findtextEx(l[i], RDSCREEN_NOBREAK)) - l[i] += "
" - . = l.Join("") - return replacetextEx(., RDSCREEN_NOBREAK, "") - -/obj/machinery/rnd/destructive_analyzer/Topic(raw, ls) - . = ..() - if(.) - return + loaded_item = null + update_appearance(UPDATE_ICON) + return TRUE + +/** + * Destroys the individual provided item + * Args: + * thing - The thing being destroyed. Generally an object, but it can be a mob too, such as intellicards and pAIs. + * gain_research_points - Whether deconstructing this should give research points to the stored techweb, if applicable. + */ +/obj/machinery/rnd/destructive_analyzer/proc/destroy_item_individual(obj/item/thing, gain_research_points = FALSE) + if(isliving(thing)) + var/mob/living/mob_thing = thing + if(mob_thing.stat != DEAD) + mob_thing.investigate_log("has been killed by a destructive analyzer.", INVESTIGATE_DEATHS) + mob_thing.death() + var/list/point_value = techweb_item_point_check(thing) + if(point_value && !stored_research.deconstructed_items[thing.type]) + stored_research.deconstructed_items[thing.type] = TRUE + stored_research.add_point_list(list(TECHWEB_POINT_TYPE_GENERIC = point_value)) + qdel(thing) - add_fingerprint(usr) - usr.set_machine(src) +/** + * Attempts to destroy the loaded item using a provided research id. + * Args: + * id - The techweb ID node that we're meant to unlock if applicable. + */ +/obj/machinery/rnd/destructive_analyzer/proc/user_try_decon_id(id) + if(!istype(loaded_item)) + return FALSE + if(isnull(id)) + return FALSE - if(ls["eject_item"]) //Eject the item inside the destructive analyzer. - if(busy) - to_chat(usr, span_danger("The destructive analyzer is busy at the moment.")) - return - if(loaded_item) - unload_item() - if(ls["deconstruct"]) - if(!user_try_decon_id(ls["deconstruct"], usr)) - say("Destructive analysis failed!") + if(id == DESTRUCTIVE_ANALYZER_DESTROY_POINTS) + if(!destroy_item(gain_research_points = TRUE)) + return FALSE + return TRUE - updateUsrDialog() + var/datum/techweb_node/node_to_discover = SSresearch.techweb_node_by_id(id) + if(!istype(node_to_discover)) + return FALSE + SSblackbox.record_feedback("nested tally", "item_deconstructed", 1, list("[node_to_discover.id]", "[loaded_item.type]")) + if(!destroy_item()) + return FALSE + stored_research.unhide_node(SSresearch.techweb_node_by_id(node_to_discover.id)) + return TRUE -/obj/machinery/rnd/destructive_analyzer/screwdriver_act(mob/living/user, obj/item/tool) - return FALSE +#undef DESTRUCTIVE_ANALYZER_DESTROY_POINTS +#undef DESTRUCTIVE_ANALYZER_POWER_USAGE diff --git a/code/modules/research/experimentor.dm b/code/modules/research/experimentor.dm index 23c3c45507a..3acfe8f5294 100644 --- a/code/modules/research/experimentor.dm +++ b/code/modules/research/experimentor.dm @@ -42,12 +42,6 @@ var/static/list/valid_items //valid items for special reactions like transforming var/list/critical_items_typecache //items that can cause critical reactions -/obj/machinery/rnd/experimentor/proc/ConvertReqString2List(list/source_list) - var/list/temp_list = params2list(source_list) - for(var/O in temp_list) - temp_list[O] = text2num(temp_list[O]) - return temp_list - /obj/machinery/rnd/experimentor/proc/valid_items() RETURN_TYPE(/list) @@ -131,20 +125,22 @@ return FALSE return TRUE -/obj/machinery/rnd/experimentor/Insert_Item(obj/item/O, mob/living/user) - if(!user.combat_mode) - . = 1 - if(!is_insertion_ready(user)) - return - if(!user.transferItemToLoc(O, src)) - return - loaded_item = O - to_chat(user, span_notice("You add [O] to the machine.")) - flick("h_lathe_load", src) +/obj/machinery/rnd/experimentor/attackby(obj/item/weapon, mob/living/user, params) + if(user.combat_mode) + return ..() + if(!is_insertion_ready(user)) + return ..() + if(!user.transferItemToLoc(weapon, src)) + to_chat(user, span_warning("\The [weapon] is stuck to your hand, you cannot put it in the [name]!")) + return TRUE + loaded_item = weapon + to_chat(user, span_notice("You add [weapon] to the machine.")) + flick("h_lathe_load", src) + return TRUE /obj/machinery/rnd/experimentor/default_deconstruction_crowbar(obj/item/O) ejectItem() - . = ..(O) + return ..(O) /obj/machinery/rnd/experimentor/ui_interact(mob/user) var/list/dat = list("
") @@ -161,22 +157,19 @@ if(istype(loaded_item,/obj/item/relic)) dat += "Discover" dat += "Eject" - var/list/listin = techweb_item_boost_check(src) + var/list/listin = techweb_item_unlock_check(src) if(listin) var/list/output = list("Research Boost Data:") var/list/res = list("Already researched:") - var/list/boosted = list("Already boosted:") for(var/node_id in listin) var/datum/techweb_node/N = SSresearch.techweb_node_by_id(node_id) var/str = "[N.display_name]: [listin[N]] points." var/datum/techweb/science_web = locate(/datum/techweb/science) in SSresearch.techwebs if(science_web.researched_nodes[N.id]) res += str - else if(science_web.boosted_nodes[N.id]) - boosted += str if(science_web.visible_nodes[N.id]) //JOY OF DISCOVERY! output += str - output += boosted + res + output += res dat += output else dat += "Nothing loaded." @@ -218,9 +211,9 @@ experiment(dotype,process) use_power(750) if(dotype != FAIL) - var/list/nodes = techweb_item_boost_check(process) + var/list/nodes = techweb_item_unlock_check(process) var/picked = pick_weight(nodes) //This should work. - stored_research.boost_with_item(SSresearch.techweb_node_by_id(picked), process.type) + stored_research.unhide_node(SSresearch.techweb_node_by_id(picked)) updateUsrDialog() /obj/machinery/rnd/experimentor/proc/matchReaction(matching,reaction) diff --git a/code/modules/research/ordnance/_scipaper.dm b/code/modules/research/ordnance/_scipaper.dm index 38f35e75a77..f1d94af7631 100644 --- a/code/modules/research/ordnance/_scipaper.dm +++ b/code/modules/research/ordnance/_scipaper.dm @@ -287,21 +287,20 @@ /// List of ordnance experiments that our partner is willing to accept. If this list is not filled it means the partner will accept everything. var/list/accepted_experiments = list() /// Associative list of which technology the partner might be able to boost and by how much. - var/list/boosted_nodes = list() - + var/list/boostable_nodes = list() /datum/scientific_partner/proc/purchase_boost(datum/techweb/purchasing_techweb, datum/techweb_node/node) if(!allowed_to_boost(purchasing_techweb, node.id)) return FALSE - purchasing_techweb.boost_techweb_node(node, list(TECHWEB_POINT_TYPE_GENERIC=boosted_nodes[node.id])) - purchasing_techweb.scientific_cooperation[type] -= boosted_nodes[node.id] * SCIENTIFIC_COOPERATION_PURCHASE_MULTIPLIER + purchasing_techweb.boost_techweb_node(node, list(TECHWEB_POINT_TYPE_GENERIC = boostable_nodes[node.id])) + purchasing_techweb.scientific_cooperation[type] -= boostable_nodes[node.id] * SCIENTIFIC_COOPERATION_PURCHASE_MULTIPLIER return TRUE /datum/scientific_partner/proc/allowed_to_boost(datum/techweb/purchasing_techweb, node_id) - if(purchasing_techweb.scientific_cooperation[type] < (boosted_nodes[node_id] * SCIENTIFIC_COOPERATION_PURCHASE_MULTIPLIER)) // Too expensive + if(purchasing_techweb.scientific_cooperation[type] < (boostable_nodes[node_id] * SCIENTIFIC_COOPERATION_PURCHASE_MULTIPLIER)) // Too expensive return FALSE if(!(node_id in purchasing_techweb.get_available_nodes())) // Not currently available return FALSE - if((TECHWEB_POINT_TYPE_GENERIC in purchasing_techweb.boosted_nodes[node_id]) && (purchasing_techweb.boosted_nodes[node_id][TECHWEB_POINT_TYPE_GENERIC] >= boosted_nodes[node_id])) // Already bought or we have a bigger discount + 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 return FALSE return TRUE diff --git a/code/modules/research/ordnance/scipaper_partner.dm b/code/modules/research/ordnance/scipaper_partner.dm index fe302c73bb0..712ec4b4127 100644 --- a/code/modules/research/ordnance/scipaper_partner.dm +++ b/code/modules/research/ordnance/scipaper_partner.dm @@ -3,7 +3,7 @@ flufftext = "A local group of miners are looking for ways to improve their mining output. They are interested in smaller scale explosives." accepted_experiments = list(/datum/experiment/ordnance/explosive/lowyieldbomb) multipliers = list(SCIPAPER_COOPERATION_INDEX = 0.75, SCIPAPER_FUNDING_INDEX = 0.75) - boosted_nodes = list( + boostable_nodes = list( "bluespace_basic" = 2000, "NVGtech" = 1500, "practical_bluespace" = 2500, @@ -16,7 +16,7 @@ name = "Ghost Writing" flufftext = "A nearby research station ran by a very wealthy captain seems to be struggling with their scientific output. They might reward us handsomely if we ghostwrite for them." multipliers = list(SCIPAPER_COOPERATION_INDEX = 0.25, SCIPAPER_FUNDING_INDEX = 2) - boosted_nodes = list( + boostable_nodes = list( "comp_recordkeeping" = 500, "computer_data_disks" = 500, ) @@ -29,7 +29,7 @@ /datum/experiment/ordnance/explosive/pressurebomb, /datum/experiment/ordnance/explosive/hydrogenbomb, ) - boosted_nodes = list( + boostable_nodes = list( "adv_weaponry" = 5000, "weaponry" = 2500, "sec_basic" = 1250, @@ -47,7 +47,7 @@ /datum/experiment/ordnance/gaseous/nitrous_oxide, /datum/experiment/ordnance/gaseous/bz, ) - boosted_nodes = list( + boostable_nodes = list( "cyber_organs" = 750, "cyber_organs_upgraded" = 1000, "genetics" = 500, @@ -63,7 +63,7 @@ /datum/experiment/ordnance/gaseous/noblium, /datum/experiment/ordnance/explosive/nobliumbomb, ) - boosted_nodes = list( + boostable_nodes = list( "engineering" = 5000, "adv_engi" = 5000, "emp_super" = 3000, diff --git a/code/modules/research/rdmachines.dm b/code/modules/research/rdmachines.dm index 3047c3e1a9a..4ab9d73dca7 100644 --- a/code/modules/research/rdmachines.dm +++ b/code/modules/research/rdmachines.dm @@ -11,9 +11,10 @@ var/hacked = FALSE var/console_link = TRUE //allow console link. var/disabled = FALSE - var/obj/item/loaded_item = null //the item loaded inside the machine (currently only used by experimentor and destructive analyzer) /// Ref to global science techweb. var/datum/techweb/stored_research + ///The item loaded inside the machine, used by experimentors and destructive analyzers only. + var/obj/item/loaded_item /obj/machinery/rnd/proc/reset_busy() busy = FALSE @@ -59,14 +60,6 @@ else return FALSE -/obj/machinery/rnd/attackby(obj/item/O, mob/user, params) - if(is_refillable() && O.is_drainable()) - return FALSE //inserting reagents into the machine - if(Insert_Item(O, user)) - return TRUE - - return ..() - /obj/machinery/rnd/crowbar_act(mob/living/user, obj/item/tool) return default_deconstruction_crowbar(tool) @@ -103,36 +96,32 @@ wires.interact(user) return TRUE -//proc used to handle inserting items or reagents into rnd machines -/obj/machinery/rnd/proc/Insert_Item(obj/item/I, mob/user) - return - //whether the machine can have an item inserted in its current state. /obj/machinery/rnd/proc/is_insertion_ready(mob/user) if(panel_open) - to_chat(user, span_warning("You can't load [src] while it's opened!")) + balloon_alert(user, "panel open!") return FALSE if(disabled) - to_chat(user, span_warning("The insertion belts of [src] won't engage!")) + balloon_alert(user, "belts disabled!") return FALSE if(busy) - to_chat(user, span_warning("[src] is busy right now.")) + balloon_alert(user, "still busy!") return FALSE if(machine_stat & BROKEN) - to_chat(user, span_warning("[src] is broken.")) + balloon_alert(user, "machine broken!") return FALSE if(machine_stat & NOPOWER) - to_chat(user, span_warning("[src] has no power.")) + balloon_alert(user, "no power!") return FALSE if(loaded_item) - to_chat(user, span_warning("[src] is already loaded.")) + balloon_alert(user, "item already loaded!") return FALSE return TRUE //we eject the loaded item when deconstructing the machine /obj/machinery/rnd/on_deconstruction() if(loaded_item) - loaded_item.forceMove(loc) + loaded_item.forceMove(drop_location()) ..() /obj/machinery/rnd/proc/AfterMaterialInsert(item_inserted, id_inserted, amount_inserted) diff --git a/code/modules/research/techweb/__techweb_helpers.dm b/code/modules/research/techweb/__techweb_helpers.dm index 2b0a294c606..469c467bc13 100644 --- a/code/modules/research/techweb/__techweb_helpers.dm +++ b/code/modules/research/techweb/__techweb_helpers.dm @@ -10,14 +10,15 @@ WARNING("Invalid boost information for node \[[id]\]: [message]") SSresearch.invalid_node_boost[id] = message -///Returns an associative list of techweb node datums with values of the boost it gives. var/list/returned = list() -/proc/techweb_item_boost_check(obj/item/I) - if(SSresearch.techweb_boost_items[I.type]) - return SSresearch.techweb_boost_items[I.type] //It should already be formatted in node datum = list(point type = value) +///Returns an associative list of techweb node datums with values of the nodes it unlocks. +/proc/techweb_item_unlock_check(obj/item/I) + if(SSresearch.techweb_unlock_items[I.type]) + return SSresearch.techweb_unlock_items[I.type] //It should already be formatted in node datum = list(point type = value) /proc/techweb_item_point_check(obj/item/I) if(SSresearch.techweb_point_items[I.type]) return SSresearch.techweb_point_items[I.type] + return FALSE /proc/techweb_point_display_generic(pointlist) var/list/ret = list() diff --git a/code/modules/research/techweb/_techweb.dm b/code/modules/research/techweb/_techweb.dm index bd1e5cc4a80..8aeb63a9f8a 100644 --- a/code/modules/research/techweb/_techweb.dm +++ b/code/modules/research/techweb/_techweb.dm @@ -26,7 +26,7 @@ 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() - /// Items already deconstructed for a generic point boost, path = list(point_type = points) + /// List of items already deconstructed for research points, preventing infinite research point generation. var/list/deconstructed_items = list() /// Available research points, type = number var/list/research_points = list() @@ -400,17 +400,15 @@ 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]) - if(node.autounlock_by_boost) - hidden_nodes -= node.id + unhide_node(node) update_node_status(node) return TRUE -/// Boosts a techweb node by using items. -/datum/techweb/proc/boost_with_item(datum/techweb_node/node, itempath) - if(!istype(node) || !ispath(itempath)) +///Removes a node from the hidden_nodes list, making it viewable and researchable (if no experiments are required). +/datum/techweb/proc/unhide_node(datum/techweb_node/node) + if(!istype(node)) return FALSE - var/list/boost_amount = node.boost_item_paths[itempath] - boost_techweb_node(node, boost_amount) + hidden_nodes -= node.id return TRUE /datum/techweb/proc/update_tiers(datum/techweb_node/base) diff --git a/code/modules/research/techweb/_techweb_node.dm b/code/modules/research/techweb/_techweb_node.dm index f5e1481e62c..c36eb886271 100644 --- a/code/modules/research/techweb/_techweb_node.dm +++ b/code/modules/research/techweb/_techweb_node.dm @@ -24,8 +24,8 @@ var/list/design_ids = list() /// CALCULATED FROM OTHER NODE'S PREREQUISITIES. Associated list id = TRUE var/list/unlock_ids = list() - /// Associative list, path = list(point type = point_value) - var/list/boost_item_paths = list() + /// List of items you need to deconstruct to unlock this node. + var/list/required_items_to_unlock = list() /// Boosting this will autounlock this node var/autounlock_by_boost = TRUE /// The points cost to research the node, type = amount diff --git a/code/modules/research/techweb/all_nodes.dm b/code/modules/research/techweb/all_nodes.dm index e70fc1974aa..f6a5345aaa4 100644 --- a/code/modules/research/techweb/all_nodes.dm +++ b/code/modules/research/techweb/all_nodes.dm @@ -2161,7 +2161,7 @@ display_name = "Alien Technology" description = "Things used by the greys." prereq_ids = list("biotech","engineering") - boost_item_paths = list( + required_items_to_unlock = list( /obj/item/stack/sheet/mineral/abductor, /obj/item/abductor, /obj/item/cautery/alien, @@ -2204,7 +2204,7 @@ "alien_scalpel", ) - boost_item_paths = list( + required_items_to_unlock = list( /obj/item/abductor, /obj/item/cautery/alien, /obj/item/circuitboard/machine/abductor, @@ -2243,7 +2243,7 @@ "alien_wrench", ) - boost_item_paths = list( + required_items_to_unlock = list( /obj/item/abductor, /obj/item/circuitboard/machine/abductor, /obj/item/crowbar/abductor, @@ -2296,12 +2296,12 @@ /datum/techweb_node/syndicate_basic/proc/register_uplink_items() SIGNAL_HANDLER UnregisterSignal(SSearly_assets, COMSIG_SUBSYSTEM_POST_INITIALIZE) - boost_item_paths = list() + required_items_to_unlock = list() for(var/datum/uplink_item/item_path as anything in SStraitor.uplink_items_by_type) var/datum/uplink_item/item = SStraitor.uplink_items_by_type[item_path] if(!item.item || !item.illegal_tech) continue - boost_item_paths |= item.item //allows deconning to unlock. + required_items_to_unlock |= item.item //allows deconning to unlock. ////////////////////////B.E.P.I.S. Locked Techs//////////////////////// diff --git a/code/modules/vehicles/mecha/mech_fabricator.dm b/code/modules/vehicles/mecha/mech_fabricator.dm index a840f75ad86..c3e5abc39f0 100644 --- a/code/modules/vehicles/mecha/mech_fabricator.dm +++ b/code/modules/vehicles/mecha/mech_fabricator.dm @@ -496,15 +496,5 @@ return FALSE return default_deconstruction_crowbar(I) -/obj/machinery/mecha_part_fabricator/proc/is_insertion_ready(mob/user) - if(panel_open) - to_chat(user, span_warning("You can't load [src] while it's opened!")) - return FALSE - if(being_built) - to_chat(user, span_warning("\The [src] is currently processing! Please wait until completion.")) - return FALSE - - return TRUE - /obj/machinery/mecha_part_fabricator/maint link_on_init = FALSE diff --git a/modular_skyrat/modules/mutants/code/mutant_cure.dm b/modular_skyrat/modules/mutants/code/mutant_cure.dm index 1766ef5634d..f6c72454c55 100644 --- a/modular_skyrat/modules/mutants/code/mutant_cure.dm +++ b/modular_skyrat/modules/mutants/code/mutant_cure.dm @@ -163,17 +163,17 @@ timer_id = null . = ..() -/obj/machinery/rnd/rna_recombinator/Insert_Item(obj/item/O, mob/living/user) +/obj/machinery/rnd/rna_recombinator/attackby(obj/item/weapon, mob/living/user, params) if(user.combat_mode) return FALSE if(!is_insertion_ready(user)) return FALSE - if(!istype(O, /obj/item/rna_vial)) + if(!istype(weapon, /obj/item/rna_vial)) return FALSE - if(!user.transferItemToLoc(O, src)) + if(!user.transferItemToLoc(weapon, src)) return FALSE - loaded_item = O - to_chat(user, span_notice("You insert [O] to into [src] reciprocal.")) + loaded_item = weapon + to_chat(user, span_notice("You insert [weapon] to into [src] reciprocal.")) flick("h_lathe_load", src) update_appearance() playsound(loc, 'sound/weapons/autoguninsert.ogg', 35, 1) diff --git a/tgui/packages/tgui/interfaces/DestructiveAnalyzer.tsx b/tgui/packages/tgui/interfaces/DestructiveAnalyzer.tsx new file mode 100644 index 00000000000..5bfd7353cc7 --- /dev/null +++ b/tgui/packages/tgui/interfaces/DestructiveAnalyzer.tsx @@ -0,0 +1,135 @@ +import { BooleanLike } from 'common/react'; +import { useBackend } from '../backend'; +import { Button, Box, Section, NoticeBox } from '../components'; +import { Window } from '../layouts'; + +type Data = { + server_connected: BooleanLike; + loaded_item: string; + item_icon: string; + indestructible: BooleanLike; + already_deconstructed: BooleanLike; + recoverable_points: string; + node_data: NodeData[]; + research_point_id: string; +}; + +type NodeData = { + node_name: string; + node_id: string; + node_hidden: BooleanLike; +}; + +export const DestructiveAnalyzer = (props, context) => { + const { act, data } = useBackend(context); + const { + server_connected, + indestructible, + loaded_item, + item_icon, + already_deconstructed, + recoverable_points, + research_point_id, + node_data = [], + } = data; + if (!server_connected) { + return ( + + + + Not connected to a server. Please sync one using a multitool. + + + + ); + } + if (!loaded_item) { + return ( + + + + No item loaded!
+ Put any item inside to see what it's capable of! +
+
+
+ ); + } + return ( + + +
act('eject_item')} + /> + }> + +
+
+ {!indestructible && ( + + This item can't be deconstructed! + + )} + {!!indestructible && ( + <> + {!!recoverable_points && ( + <> + Research points from deconstruction + {recoverable_points} + + )} + + act('deconstruct', { deconstruct_id: research_point_id }) + } + /> + + )} + {node_data.map((node) => ( + + act('deconstruct', { deconstruct_id: node.node_id }) + } + /> + ))} +
+
+
+ ); +};