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 699cac8dccb..a93d2b4b56d 100644 --- a/code/game/objects/items.dm +++ b/code/game/objects/items.dm @@ -421,7 +421,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) @@ -596,9 +596,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 6f76861ae21..533a4c111ad 100644 --- a/code/game/objects/items/circuitboards/machines/machine_circuitboards.dm +++ b/code/game/objects/items/circuitboards/machines/machine_circuitboards.dm @@ -961,7 +961,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 +///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" -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 -*/ +/** + * ## 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 + register_context() -/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/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/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() +/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() +/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() - qdel(thing) - loaded_item = null - if (!innermode) - update_appearance() - return TRUE +/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) -/obj/machinery/rnd/destructive_analyzer/proc/user_try_decon_id(id, mob/user) - if(!istype(loaded_item)) - return FALSE - - 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) - - 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 - -/obj/machinery/rnd/destructive_analyzer/proc/unload_item() - if(!loaded_item) - return FALSE - loaded_item.forceMove(get_turf(src)) - loaded_item = null - update_appearance() - 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() - -/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:" - - var/anything = FALSE - var/list/boostable_nodes = techweb_item_boost_check(loaded_item) + var/list/boostable_nodes = techweb_item_unlock_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) + 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 - 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]" +/obj/machinery/rnd/destructive_analyzer/ui_static_data(mob/user) + var/list/data = list() + data["research_point_id"] = DESTRUCTIVE_ANALYZER_DESTROY_POINTS + return data - 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) +/obj/machinery/rnd/destructive_analyzer/ui_act(action, params, datum/tgui/ui) . = ..() if(.) return - add_fingerprint(usr) - usr.set_machine(src) - - 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!") - - updateUsrDialog() + 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 + playsound(loc, 'sound/machines/terminal_insert_disc.ogg', 30, FALSE) + loaded_item.forceMove(drop_location()) + loaded_item = null + update_appearance(UPDATE_ICON) + return TRUE + +///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() + +/** + * 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) + + 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) + +/** + * 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(id == DESTRUCTIVE_ANALYZER_DESTROY_POINTS) + if(!destroy_item(gain_research_points = TRUE)) + return FALSE + return TRUE + + 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 + +#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 9ffd5448a5d..9d2e200b672 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 1f1dd423d1a..9a3d9ac5261 100644 --- a/code/modules/research/techweb/all_nodes.dm +++ b/code/modules/research/techweb/all_nodes.dm @@ -2160,7 +2160,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, @@ -2203,7 +2203,7 @@ "alien_scalpel", ) - boost_item_paths = list( + required_items_to_unlock = list( /obj/item/abductor, /obj/item/cautery/alien, /obj/item/circuitboard/machine/abductor, @@ -2242,7 +2242,7 @@ "alien_wrench", ) - boost_item_paths = list( + required_items_to_unlock = list( /obj/item/abductor, /obj/item/circuitboard/machine/abductor, /obj/item/crowbar/abductor, @@ -2295,12 +2295,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/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 }) + } + /> + ))} +
+
+
+ ); +};