[MIRROR] Rebuild the Destructive Analyzer For Experisci - Part1 (#11816)

Co-authored-by: Will <7099514+Willburd@users.noreply.github.com>
Co-authored-by: C.L. <killer65311@gmail.com>
Co-authored-by: Kashargul <144968721+Kashargul@users.noreply.github.com>
This commit is contained in:
CHOMPStation2StaffMirrorBot
2025-10-14 13:12:54 -07:00
committed by GitHub
parent a9e8ab2b6d
commit ef45ecf681
19 changed files with 561 additions and 174 deletions

View File

@@ -196,3 +196,12 @@
RND_CATEGORY_MACHINE
)
departmental_flags = DEPARTMENT_BITFLAG_SCIENCE | DEPARTMENT_BITFLAG_ENGINEERING
/datum/design_techweb/board/destructive_analyzer
name = "destructive analyzer"
id = "destructive_analyzer"
build_path = /obj/item/circuitboard/destructive_analyzer
category = list(
RND_CATEGORY_MACHINE + RND_SUBCATEGORY_MACHINE_RESEARCH
)
departmental_flags = DEPARTMENT_BITFLAG_SCIENCE

View File

@@ -139,7 +139,7 @@
/datum/component/experiment_handler/proc/try_run_destructive_experiment(datum/source, list/scanned_atoms)
SIGNAL_HANDLER
var/atom/movable/our_scanner = parent
if (selected_experiment == null)
if (selected_experiment == null && !(config_flags & EXPERIMENT_CONFIG_ALWAYS_ACTIVE) )
if(!(config_flags & EXPERIMENT_CONFIG_SILENT_FAIL))
playsound(our_scanner, 'sound/machines/buzz-sigh.ogg', 25)
to_chat(our_scanner, span_notice("No experiment selected!"))

View File

@@ -1,3 +1,17 @@
///Example of a destructive experiment.
/datum/experiment/scanning/random/artifact_destruction
name = "Artifact Analysis"
description = "Destroy some artifacts"
possible_types = list(/obj/item/research_sample/common)
total_requirement = 3
traits = EXPERIMENT_TRAIT_DESTRUCTIVE
exp_tag = "Physical Experiment"
/datum/experiment/scanning/random/artifact_destruction/serialize_progress_stage(atom/target, list/seen_instances)
return EXPERIMENT_PROG_INT("Destroy artifacts.", \
traits & EXPERIMENT_TRAIT_DESTRUCTIVE ? scanned[target] : seen_instances.len, required_atoms[target])
/datum/experiment/scanning/random/janitor_trash
name = "Station Hygiene Inspection"
description = "To learn how to clean, we must first learn what it is to have filth. We need you to scan some filth around the station."

View File

@@ -10,7 +10,7 @@
name = "Scanning Experiment"
description = "Base experiment for scanning atoms"
exp_tag = "Scan"
allowed_experimentors = list(/obj/item/experi_scanner)
allowed_experimentors = list(/obj/item/experi_scanner, /obj/machinery/rnd/destructive_analyzer)
performance_hint = "Perform scanning experiments using a handheld experi-scanner."
/// The typepaths and number of atoms that must be scanned
var/list/required_atoms = list()

View File

@@ -0,0 +1,319 @@
///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. Used to perform /datum/experiment/physical/destructive_analysis experiments.
*/
/obj/machinery/rnd/destructive_analyzer
name = "destructive analyzer"
icon_state = "d_analyzer"
var/decon_mod = 0
circuit = /obj/item/circuitboard/destructive_analyzer
use_power = USE_POWER_IDLE
idle_power_usage = 30
active_power_usage = 2500
var/rped_recycler_ready = TRUE
var/datum/component/remote_materials/rmat
/obj/machinery/rnd/destructive_analyzer/Initialize(mapload)
rmat = AddComponent(
/datum/component/remote_materials, \
mapload, \
mat_container_flags = MATCONTAINER_NO_INSERT \
)
//Destructive analysis
var/static/list/destructive_signals = list(
COMSIG_MACHINERY_DESTRUCTIVE_SCAN = TYPE_PROC_REF(/datum/component/experiment_handler, try_run_destructive_experiment),
)
AddComponent(/datum/component/experiment_handler, \
allowed_experiments = list(/datum/experiment/scanning),\
config_flags = EXPERIMENT_CONFIG_ALWAYS_ACTIVE|EXPERIMENT_CONFIG_SILENT_FAIL,\
experiment_signals = destructive_signals, \
)
. = ..()
default_apply_parts()
/obj/machinery/rnd/destructive_analyzer/Destroy()
rmat = null
. = ..()
/obj/machinery/rnd/destructive_analyzer/RefreshParts()
var/T = 0
for(var/obj/item/stock_parts/S in component_parts)
T += S.rating
T *= 0.1
decon_mod = clamp(T, 0, 1)
/obj/machinery/rnd/destructive_analyzer/update_icon()
var/current_item = loaded_item?.resolve()
if(panel_open)
icon_state = "d_analyzer_t"
else if(current_item)
icon_state = "d_analyzer_l"
else
icon_state = "d_analyzer"
/obj/machinery/rnd/destructive_analyzer/attackby(var/obj/item/O as obj, var/mob/user as mob)
if(busy)
to_chat(user, span_notice("\The [src] is busy right now."))
return
if(default_deconstruction_screwdriver(user, O))
return
if(default_deconstruction_crowbar(user, O))
return
if(default_part_replacement(user, O))
return
if(!panel_open)
var/current_item = loaded_item?.resolve()
if(current_item)
to_chat(user, span_notice("There is something already loaded into \the [src]."))
else
if(isrobot(user)) //Don't put your module items in there!
return
if(is_type_in_list(O, GLOB.item_deconstruction_blacklist))
to_chat(user, span_notice("The machine rejects \the [O]!"))
return
if((O.item_flags & DROPDEL) || (O.item_flags & NOSTRIP))
to_chat(user, span_notice("The machine rejects \the [O]!"))
return
if(O.tethered_host_item)
to_chat(user, span_notice("The machine rejects \the [O]!"))
return
if(LAZYLEN(O.contents))
to_chat(user, span_notice("The machine rejects \the [O]! You need to clear it of all items first!"))
return
busy = TRUE
loaded_item = WEAKREF(O)
user.drop_item()
O.forceMove(src)
SStgui.update_uis(src)
to_chat(user, span_notice("You add \the [O] to \the [src]."))
flick("d_analyzer_la", src)
addtimer(CALLBACK(src, PROC_REF(analyze_finish)), 1 SECONDS, TIMER_DELETE_ME)
return TRUE
// Handle signal to remote_materials so we can link the DA to the silo
. = ..()
/obj/machinery/rnd/destructive_analyzer/proc/analyze_finish()
SHOULD_NOT_OVERRIDE(TRUE)
PRIVATE_PROC(TRUE)
update_icon()
reset_busy()
///////////////////////////////////////////////////////////////////////////////////////////////////////
// RPED recycling
///////////////////////////////////////////////////////////////////////////////////////////////////////
/obj/machinery/rnd/destructive_analyzer/MouseDrop_T(atom/dropping, mob/living/user)
if(istype(dropping, /obj/item/storage/part_replacer))
var/obj/item/storage/part_replacer/replacer = dropping
replacer.hide_from(user)
if(!rped_recycler_ready)
to_chat(user, span_notice("\The [src]'s stock parts recycler isn't ready yet."))
return FALSE
// We want the lowest-part tier rating in the RPED so we only recycle the lowest-tier parts.
var/lowest_rating = INFINITY
for(var/obj/item/B in replacer.contents)
if(B.rped_rating() < lowest_rating)
lowest_rating = B.rped_rating()
if(lowest_rating == INFINITY)
atom_say("Mass part deconstruction attempt canceled - no valid parts for recycling detected.")
return FALSE
// Sending salvaged materials to the silo
var/datum/component/material_container/materials = get_silo_material_container_datum(TRUE)
if(!materials)
return FALSE
for(var/obj/item/B in replacer.contents)
if(B.rped_rating() > lowest_rating)
continue
materials.insert_item(B, decon_mod, src)
// Feedback
playsound(get_turf(src), 'sound/machines/click.ogg', 50, 1)
rped_recycler_ready = FALSE
addtimer(CALLBACK(src, PROC_REF(rped_ready)), 5 SECONDS, TIMER_DELETE_ME)
to_chat(user, span_notice("You deconstruct all the parts of rating [lowest_rating] in [replacer] with [src]."))
return TRUE
. = ..()
/obj/machinery/rnd/destructive_analyzer/proc/rped_ready()
PRIVATE_PROC(TRUE)
SHOULD_NOT_OVERRIDE(TRUE)
rped_recycler_ready = TRUE
playsound(get_turf(src), 'sound/machines/chime.ogg', 50, 1)
/obj/machinery/rnd/destructive_analyzer/proc/get_silo_material_container_datum(verbose)
var/datum/component/material_container/materials = rmat.mat_container
if(!materials)
if(verbose)
atom_say("No access to material storage, please contact the quartermaster.")
return null
if(rmat.on_hold())
if(verbose)
atom_say("Mineral access is on hold, please contact the quartermaster.")
return null
return materials
///////////////////////////////////////////////////////////////////////////////////////////////////////
// Handling deconstruction
///////////////////////////////////////////////////////////////////////////////////////////////////////
/obj/machinery/rnd/destructive_analyzer/attack_hand(mob/user as mob)
tgui_interact(user)
/obj/machinery/rnd/destructive_analyzer/tgui_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/tgui_data(mob/user)
var/list/data = list()
data["server_connected"] = !!stored_research
data["node_data"] = null
var/obj/item/current_item = loaded_item?.resolve()
if(current_item)
data["item_icon"] = icon2base64(getFlatIcon(image(icon = current_item.icon, icon_state = current_item.icon_state), no_anim = TRUE))
data["indestructible"] = is_type_in_list(current_item, GLOB.item_deconstruction_blacklist)
data["loaded_item"] = current_item
data["already_deconstructed"] = !!stored_research.deconstructed_items[current_item.type]
var/list/points = techweb_item_point_check(current_item)
data["recoverable_points"] = techweb_point_display_generic(points)
var/list/boostable_nodes = techweb_item_unlock_check(current_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/tgui_static_data(mob/user)
var/list/data = list()
data["research_point_id"] = DESTRUCTIVE_ANALYZER_DESTROY_POINTS
return data
/obj/machinery/rnd/destructive_analyzer/tgui_act(action, params, datum/tgui/ui)
if(..())
return TRUE
var/mob/user = usr
var/current_item = loaded_item?.resolve()
switch(action)
if("eject_item")
if(busy)
balloon_alert(user, "already busy!")
return TRUE
if(current_item)
unload_item()
return TRUE
if("deconstruct")
if(!user_try_decon_id(params["deconstruct_id"]))
balloon_alert(user, "analysis failed!")
return TRUE
///Drops the loaded item where it can and nulls it.
/obj/machinery/rnd/destructive_analyzer/proc/unload_item()
var/obj/item/current_item = loaded_item?.resolve()
if(!current_item)
loaded_item = null
return FALSE
//playsound(loc, 'sound/machines/terminal/terminal_insert_disc.ogg', 30, FALSE)
current_item.forceMove(drop_location())
loaded_item = null
update_icon()
return TRUE
/**
* 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)
var/obj/item/current_item = loaded_item?.resolve()
if(!current_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(active_power_usage)
//Failsafe.
for(var/atom/movable/AM in current_item.contents)
AM.forceMove(get_turf(src))
playsound(src, 'sound/machines/destructive_analyzer.ogg', 50, 1)
SEND_SIGNAL(src, COMSIG_MACHINERY_DESTRUCTIVE_SCAN, current_item)
destroy_item_individual(current_item, gain_research_points)
loaded_item = null
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
var/turf/turf_to_dump_to = get_turf(src)
log_and_message_admins("made an attempt to kill [mob_thing] in a destructive analyzer was made at [ADMIN_VERBOSEJMP(turf_to_dump_to)]")
visible_message(span_warning("A loud buzz sounds out from \the [src] as it rejects and spits out \the [mob_thing]!"))
mob_thing.forceMove(turf_to_dump_to)
return
//Safety.
if(is_type_in_list(thing, GLOB.item_deconstruction_blacklist))
var/turf/turf_to_dump_to = get_turf(src)
log_and_message_admins("made an attempt to destroy [thing] in a destructive analyzer was made at [ADMIN_VERBOSEJMP(turf_to_dump_to)]")
visible_message(span_warning("A loud buzz sounds out from \the [src] as it rejects and spits out \the [thing]!"))
thing.forceMove(turf_to_dump_to)
return
var/list/point_value = techweb_item_point_check(thing)
//If it has a point value and we haven't deconstructed it OR we've deconstructed it but it's a repeatable.
if(point_value && (!stored_research.deconstructed_items[thing.type] || (stored_research.deconstructed_items[thing.type] && (thing.type in SSresearch.techweb_repeatable_items))))
stored_research.deconstructed_items[thing.type] = TRUE
stored_research.add_point_list(point_value)
//Finally, let's add it to the material silo, if applicable.
var/datum/component/material_container/materials = get_silo_material_container_datum(FALSE)
materials.insert_item(thing, decon_mod, src, FALSE)
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)
var/obj/item/current_item = loaded_item?.resolve()
if(!istype(current_item))
return FALSE
if(LAZYLEN(current_item.contents))
visible_message(span_notice("A warning blares from \the [src]: The [current_item] still has items inside it!"))
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
if(!destroy_item())
return FALSE
stored_research.unhide_node(node_to_discover)
return TRUE
#undef DESTRUCTIVE_ANALYZER_DESTROY_POINTS

View File

@@ -14,7 +14,7 @@
///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
var/datum/weakref/loaded_item
/obj/machinery/rnd/Initialize(mapload)
. = ..()
@@ -59,6 +59,8 @@
return ..()
/obj/machinery/rnd/dismantle()
if(loaded_item)
loaded_item.forceMove(drop_location())
var/obj/item/our_item = loaded_item?.resolve()
if(our_item)
our_item.forceMove(drop_location())
loaded_item = null
. = ..()

View File

@@ -12,7 +12,7 @@
// "rdservercontrol",
// "doppler_array",
// "experimentor",
// "destructive_analyzer",
"destructive_analyzer",
// "destructive_scanner",
// "laptop",
// "portadrive_basic",