[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

🆑
refactor: Destructive Analyzers now have a TGUI menu.
/🆑

* 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>
This commit is contained in:
SkyratBot
2023-11-14 15:19:08 +01:00
committed by GitHub
parent 64dd06c716
commit 73bdd7341a
17 changed files with 375 additions and 324 deletions

View File

@@ -1,10 +1,6 @@
#define RDSCREEN_NOBREAK "<NO_HTML_BREAK>"
/// 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"

View File

@@ -27,8 +27,8 @@ 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.
@@ -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()

View File

@@ -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)

View File

@@ -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"

View File

@@ -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

View File

@@ -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
/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(O, src))
to_chat(user, span_warning("\The [O] is stuck to your hand, you cannot put it in the [src.name]!"))
return
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 = 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()
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 += "<div class='statusDisplay'>No item loaded. Standing-by...</div>"
else
l += "<div class='statusDisplay'>[RDSCREEN_NOBREAK]"
l += "<table><tr><td>[icon2html(loaded_item, usr)]</td><td><b>[loaded_item.name]</b> <A href='?src=[REF(src)];eject_item=1'>Eject</A></td></tr></table>[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)
l += "<div class='statusDisplay'>[RDSCREEN_NOBREAK]"
if (stored_research.researched_nodes[N.id]) // already researched
l += "<span class='linkOff'>[N.display_name]</span>"
l += "This node has already been researched."
else if(!length(worth)) // reveal only
if (stored_research.hidden_nodes[N.id])
l += "<A href='?src=[REF(src)];deconstruct=[N.id]'>[N.display_name]</A>"
l += "This node will be revealed."
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
l += "<span class='linkOff'>[N.display_name]</span>"
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 += "<A href='?src=[REF(src)];deconstruct=[N.id]'>[N.display_name]</A>"
l += "This node will be boosted with the following:<BR>[techweb_point_display_generic(differences)]"
else
l += "<span class='linkOff'>[N.display_name]</span>"
l += "This node has already been boosted.</span>"
l += "</div>[RDSCREEN_NOBREAK]"
data["loaded_item"] = null
return data
var/list/point_values = techweb_item_point_check(loaded_item)
if(point_values)
anything = TRUE
l += "<div class='statusDisplay'>[RDSCREEN_NOBREAK]"
if (stored_research.deconstructed_items[loaded_item.type])
l += "<span class='linkOff'>Point Deconstruction</span>"
l += "This item's points have already been claimed."
else
l += "<A href='?src=[REF(src)];deconstruct=[RESEARCH_MATERIAL_DESTROY_ID]'>Point Deconstruction</A>"
l += "This item is worth: <BR>[techweb_point_display_generic(point_values)]!"
l += "</div>[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
if(!(loaded_item.resistance_flags & INDESTRUCTIBLE))
l += "<div class='statusDisplay'><A href='?src=[REF(src)];deconstruct=[RESEARCH_MATERIAL_DESTROY_ID]'>Destroy Item</A>"
l += "</div>[RDSCREEN_NOBREAK]"
anything = TRUE
if (!anything)
l += "Nothing!"
l += "</div>"
for(var/i in 1 to length(l))
if(!findtextEx(l[i], RDSCREEN_NOBREAK))
l[i] += "<br>"
. = 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.
var/mob/user = usr
switch(action)
if("eject_item")
if(busy)
to_chat(usr, span_danger("The destructive analyzer is busy at the moment."))
return
balloon_alert(user, "already busy!")
return TRUE
if(loaded_item)
unload_item()
if(ls["deconstruct"])
if(!user_try_decon_id(ls["deconstruct"], usr))
return TRUE
if("deconstruct")
if(!user_try_decon_id(params["deconstruct_id"]))
say("Destructive analysis failed!")
return TRUE
updateUsrDialog()
//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

View File

@@ -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
/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(O, src))
return
loaded_item = O
to_chat(user, span_notice("You add [O] to the machine."))
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("<center>")
@@ -161,22 +157,19 @@
if(istype(loaded_item,/obj/item/relic))
dat += "<b><a href='byond://?src=[REF(src)];item=[REF(loaded_item)];function=[SCANTYPE_DISCOVER]'>Discover</A></b>"
dat += "<b><a href='byond://?src=[REF(src)];function=eject'>Eject</A>"
var/list/listin = techweb_item_boost_check(src)
var/list/listin = techweb_item_unlock_check(src)
if(listin)
var/list/output = list("<b><font color='purple'>Research Boost Data:</font></b>")
var/list/res = list("<b><font color='blue'>Already researched:</font></b>")
var/list/boosted = list("<b><font color='red'>Already boosted:</font></b>")
for(var/node_id in listin)
var/datum/techweb_node/N = SSresearch.techweb_node_by_id(node_id)
var/str = "<b>[N.display_name]</b>: [listin[N]] points.</b>"
var/datum/techweb/science_web = locate(/datum/techweb/science) in SSresearch.techwebs
if(science_web.researched_nodes[N.id])
res += str
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 += "<b>Nothing loaded.</b>"
@@ -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)

View File

@@ -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

View File

@@ -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,

View File

@@ -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)

View File

@@ -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()

View File

@@ -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)

View File

@@ -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

View File

@@ -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////////////////////////

View File

@@ -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

View File

@@ -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)

View File

@@ -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<Data>(context);
const {
server_connected,
indestructible,
loaded_item,
item_icon,
already_deconstructed,
recoverable_points,
research_point_id,
node_data = [],
} = data;
if (!server_connected) {
return (
<Window width={400} height={260} title="Destructive Analyzer">
<Window.Content>
<NoticeBox textAlign="center" danger>
Not connected to a server. Please sync one using a multitool.
</NoticeBox>
</Window.Content>
</Window>
);
}
if (!loaded_item) {
return (
<Window width={400} height={260} title="Destructive Analyzer">
<Window.Content>
<NoticeBox textAlign="center" danger>
No item loaded! <br />
Put any item inside to see what it&apos;s capable of!
</NoticeBox>
</Window.Content>
</Window>
);
}
return (
<Window
width={400}
height={260}
scrollable
fill
title="Destructive Analyzer">
<Window.Content scrollable>
<Section
title={loaded_item}
buttons={
<Button
icon="eject"
tooltip="Ejects the item currently inside the machine."
onClick={() => act('eject_item')}
/>
}>
<Box
as="img"
src={`data:image/jpeg;base64,${item_icon}`}
height="64px"
width="64px"
style={{
'-ms-interpolation-mode': 'nearest-neighbor',
'vertical-align': 'middle',
}}
/>
</Section>
<Section title="Deconstruction Methods">
{!indestructible && (
<NoticeBox textAlign="center" danger>
This item can&apos;t be deconstructed!
</NoticeBox>
)}
{!!indestructible && (
<>
{!!recoverable_points && (
<>
<Box fontSize="14px">Research points from deconstruction</Box>
<Box>{recoverable_points}</Box>
</>
)}
<Button.Confirm
content="Deconstruct"
icon="hammer"
tooltip={
already_deconstructed
? 'This item item has already been deconstructed, and will not give any additional information.'
: 'Destroys the object currently residing in the machine.'
}
onClick={() =>
act('deconstruct', { deconstruct_id: research_point_id })
}
/>
</>
)}
{node_data.map((node) => (
<Button.Confirm
content={node.node_name}
icon="cash-register"
mt={1}
disabled={!node.node_hidden}
key={node.node_id}
tooltip={
node.node_hidden
? 'Deconstructing this will allow you to research the node in question by making it visible to R&D consoles.'
: 'This node has already been researched, and does not need to be deconstructed.'
}
onClick={() =>
act('deconstruct', { deconstruct_id: node.node_id })
}
/>
))}
</Section>
</Window.Content>
</Window>
);
};