[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

@@ -9,23 +9,23 @@
},
},
"packages": {
"@biomejs/biome": ["@biomejs/biome@2.2.5", "", { "optionalDependencies": { "@biomejs/cli-darwin-arm64": "2.2.5", "@biomejs/cli-darwin-x64": "2.2.5", "@biomejs/cli-linux-arm64": "2.2.5", "@biomejs/cli-linux-arm64-musl": "2.2.5", "@biomejs/cli-linux-x64": "2.2.5", "@biomejs/cli-linux-x64-musl": "2.2.5", "@biomejs/cli-win32-arm64": "2.2.5", "@biomejs/cli-win32-x64": "2.2.5" }, "bin": { "biome": "bin/biome" } }, "sha512-zcIi+163Rc3HtyHbEO7CjeHq8DjQRs40HsGbW6vx2WI0tg8mYQOPouhvHSyEnCBAorfYNnKdR64/IxO7xQ5faw=="],
"@biomejs/biome": ["@biomejs/biome@2.2.6", "", { "optionalDependencies": { "@biomejs/cli-darwin-arm64": "2.2.6", "@biomejs/cli-darwin-x64": "2.2.6", "@biomejs/cli-linux-arm64": "2.2.6", "@biomejs/cli-linux-arm64-musl": "2.2.6", "@biomejs/cli-linux-x64": "2.2.6", "@biomejs/cli-linux-x64-musl": "2.2.6", "@biomejs/cli-win32-arm64": "2.2.6", "@biomejs/cli-win32-x64": "2.2.6" }, "bin": { "biome": "bin/biome" } }, "sha512-yKTCNGhek0rL5OEW1jbLeZX8LHaM8yk7+3JRGv08my+gkpmtb5dDE+54r2ZjZx0ediFEn1pYBOJSmOdDP9xtFw=="],
"@biomejs/cli-darwin-arm64": ["@biomejs/cli-darwin-arm64@2.2.5", "", { "os": "darwin", "cpu": "arm64" }, "sha512-MYT+nZ38wEIWVcL5xLyOhYQQ7nlWD0b/4mgATW2c8dvq7R4OQjt/XGXFkXrmtWmQofaIM14L7V8qIz/M+bx5QQ=="],
"@biomejs/cli-darwin-arm64": ["@biomejs/cli-darwin-arm64@2.2.6", "", { "os": "darwin", "cpu": "arm64" }, "sha512-UZPmn3M45CjTYulgcrFJFZv7YmK3pTxTJDrFYlNElT2FNnkkX4fsxjExTSMeWKQYoZjvekpH5cvrYZZlWu3yfA=="],
"@biomejs/cli-darwin-x64": ["@biomejs/cli-darwin-x64@2.2.5", "", { "os": "darwin", "cpu": "x64" }, "sha512-FLIEl73fv0R7dI10EnEiZLw+IMz3mWLnF95ASDI0kbx6DDLJjWxE5JxxBfmG+udz1hIDd3fr5wsuP7nwuTRdAg=="],
"@biomejs/cli-darwin-x64": ["@biomejs/cli-darwin-x64@2.2.6", "", { "os": "darwin", "cpu": "x64" }, "sha512-HOUIquhHVgh/jvxyClpwlpl/oeMqntlteL89YqjuFDiZ091P0vhHccwz+8muu3nTyHWM5FQslt+4Jdcd67+xWQ=="],
"@biomejs/cli-linux-arm64": ["@biomejs/cli-linux-arm64@2.2.5", "", { "os": "linux", "cpu": "arm64" }, "sha512-5DjiiDfHqGgR2MS9D+AZ8kOfrzTGqLKywn8hoXpXXlJXIECGQ32t+gt/uiS2XyGBM2XQhR6ztUvbjZWeccFMoQ=="],
"@biomejs/cli-linux-arm64": ["@biomejs/cli-linux-arm64@2.2.6", "", { "os": "linux", "cpu": "arm64" }, "sha512-BpGtuMJGN+o8pQjvYsUKZ+4JEErxdSmcRD/JG3mXoWc6zrcA7OkuyGFN1mDggO0Q1n7qXxo/PcupHk8gzijt5g=="],
"@biomejs/cli-linux-arm64-musl": ["@biomejs/cli-linux-arm64-musl@2.2.5", "", { "os": "linux", "cpu": "arm64" }, "sha512-5Ov2wgAFwqDvQiESnu7b9ufD1faRa+40uwrohgBopeY84El2TnBDoMNXx6iuQdreoFGjwW8vH6k68G21EpNERw=="],
"@biomejs/cli-linux-arm64-musl": ["@biomejs/cli-linux-arm64-musl@2.2.6", "", { "os": "linux", "cpu": "arm64" }, "sha512-TjCenQq3N6g1C+5UT3jE1bIiJb5MWQvulpUngTIpFsL4StVAUXucWD0SL9MCW89Tm6awWfeXBbZBAhJwjyFbRQ=="],
"@biomejs/cli-linux-x64": ["@biomejs/cli-linux-x64@2.2.5", "", { "os": "linux", "cpu": "x64" }, "sha512-fq9meKm1AEXeAWan3uCg6XSP5ObA6F/Ovm89TwaMiy1DNIwdgxPkNwxlXJX8iM6oRbFysYeGnT0OG8diCWb9ew=="],
"@biomejs/cli-linux-x64": ["@biomejs/cli-linux-x64@2.2.6", "", { "os": "linux", "cpu": "x64" }, "sha512-1HaM/dpI/1Z68zp8ZdT6EiBq+/O/z97a2AiHMl+VAdv5/ELckFt9EvRb8hDHpk8hUMoz03gXkC7VPXOVtU7faA=="],
"@biomejs/cli-linux-x64-musl": ["@biomejs/cli-linux-x64-musl@2.2.5", "", { "os": "linux", "cpu": "x64" }, "sha512-AVqLCDb/6K7aPNIcxHaTQj01sl1m989CJIQFQEaiQkGr2EQwyOpaATJ473h+nXDUuAcREhccfRpe/tu+0wu0eQ=="],
"@biomejs/cli-linux-x64-musl": ["@biomejs/cli-linux-x64-musl@2.2.6", "", { "os": "linux", "cpu": "x64" }, "sha512-1ZcBux8zVM3JhWN2ZCPaYf0+ogxXG316uaoXJdgoPZcdK/rmRcRY7PqHdAos2ExzvjIdvhQp72UcveI98hgOog=="],
"@biomejs/cli-win32-arm64": ["@biomejs/cli-win32-arm64@2.2.5", "", { "os": "win32", "cpu": "arm64" }, "sha512-xaOIad4wBambwJa6mdp1FigYSIF9i7PCqRbvBqtIi9y29QtPVQ13sDGtUnsRoe6SjL10auMzQ6YAe+B3RpZXVg=="],
"@biomejs/cli-win32-arm64": ["@biomejs/cli-win32-arm64@2.2.6", "", { "os": "win32", "cpu": "arm64" }, "sha512-h3A88G8PGM1ryTeZyLlSdfC/gz3e95EJw9BZmA6Po412DRqwqPBa2Y9U+4ZSGUAXCsnSQE00jLV8Pyrh0d+jQw=="],
"@biomejs/cli-win32-x64": ["@biomejs/cli-win32-x64@2.2.5", "", { "os": "win32", "cpu": "x64" }, "sha512-F/jhuXCssPFAuciMhHKk00xnCAxJRS/pUzVfXYmOMUp//XW7mO6QeCjsjvnm8L4AO/dG2VOB0O+fJPiJ2uXtIw=="],
"@biomejs/cli-win32-x64": ["@biomejs/cli-win32-x64@2.2.6", "", { "os": "win32", "cpu": "x64" }, "sha512-yx0CqeOhPjYQ5ZXgPfu8QYkgBhVJyvWe36as7jRuPrKPO5ylVDfwVtPQ+K/mooNTADW0IhxOZm3aPu16dP8yNQ=="],
"prettier": ["prettier@3.6.2", "", { "bin": { "prettier": "bin/prettier.cjs" } }, "sha512-I7AIg5boAr5R0FFtJ6rCfD+LFsWHp81dolrFD8S79U9tb8Az2nGrJncnMSnys+bpQJfRUzqs9hnA81OAA3hCuQ=="],
}

View File

@@ -194,6 +194,7 @@
#define COMSIG_CLICK_CTRL "ctrl_click"
///from base of atom/AltClick(): (/mob)
#define COMSIG_CLICK_ALT "alt_click"
#define COMPONENT_CANCEL_CLICK_ALT (1<<0)
///from base of atom/CtrlShiftClick(/mob)
#define COMSIG_CLICK_CTRL_SHIFT "ctrl_shift_click"
///from base of atom/MouseDrop(): (/atom/over, /mob/user)

View File

@@ -1,2 +1,4 @@
///from /obj/machinery/computer/arcade/prizevend(mob/user, prizes = 1)
#define COMSIG_ARCADE_PRIZEVEND "arcade_prizevend"
///from /obj/machinery/rnd/destructive_analyzer/proc/destroy_item_individual(gain_research_points = FALSE): Runs when the destructive scanner scans a group of objects. (obj/item)
#define COMSIG_MACHINERY_DESTRUCTIVE_SCAN "machinery_destructive_scan"

View File

@@ -398,6 +398,20 @@ GLOBAL_LIST_INIT(item_digestion_blacklist, list(
/obj/item/mmi/digital/robot,
/obj/item/rig/protean))
///A list of stuff we do NOT want being deconstructed. Either due to how critical it is (ID/nuke disk) or buggy (holders and paicards)
GLOBAL_LIST_INIT(item_deconstruction_blacklist, list(
/obj/item/card/id,
/obj/item/areaeditor/blueprints,
/obj/item/disk/nuclear,
/obj/item/perfect_tele_beacon,
/obj/item/organ/internal/brain,
/obj/item/mmi,
/obj/item/rig/protean,
/obj/item/holder,
/obj/item/paicard,
/obj/item/stack/material/cyborg,
/obj/item/storage))
///A list of chemicals that are banned from being obtainable through means that generate chemicals. These chemicals are either lame, annoying, pref-breaking, or OP (This list does NOT include reactions)
GLOBAL_LIST_INIT(obtainable_chemical_blacklist, list(
REAGENT_ID_ADMINORDRAZINE,

View File

@@ -303,8 +303,8 @@
// if(!user.can_interact_with(src))
// return FALSE
// if(SEND_SIGNAL(src, COMSIG_CLICK_ALT, user) & COMPONENT_CANCEL_CLICK_ALT)
// return TRUE
if(SEND_SIGNAL(src, COMSIG_CLICK_ALT, user) & COMPONENT_CANCEL_CLICK_ALT)
return TRUE
if(HAS_TRAIT(src, TRAIT_ALT_CLICK_BLOCKER) && !isobserver(user))
return TRUE

View File

@@ -41,7 +41,17 @@ SUBSYSTEM_DEF(research)
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 = TECHWEB_TIER_5_POINTS)
/obj/item/research_sample/common = list(TECHWEB_POINT_TYPE_GENERIC = TECHWEB_TIER_1_POINTS*0.5),
/obj/item/research_sample/uncommon = list(TECHWEB_POINT_TYPE_GENERIC = TECHWEB_TIER_2_POINTS*0.5),
/obj/item/research_sample/rare = list(TECHWEB_POINT_TYPE_GENERIC = TECHWEB_TIER_3_POINTS*0.5),
/obj/item/research_sample/bluespace = list(TECHWEB_POINT_TYPE_GENERIC = TECHWEB_TIER_4_POINTS*0.5)
)
///Allows repeated deconstruction of these items for points. These items MUST be in techweb_point_items as well.
var/list/techweb_repeatable_items = list(
/obj/item/research_sample/common,
/obj/item/research_sample/uncommon,
/obj/item/research_sample/rare,
/obj/item/research_sample/bluespace
)
var/list/errored_datums = list()
///Associated list of all point types that techwebs will have and their respective 'abbreviated' name.

View File

@@ -111,3 +111,13 @@
req_components = list(
/obj/item/stock_parts/manipulator = 1,
/obj/item/stock_parts/console_screen = 1)
/obj/item/circuitboard/destructive_analyzer
name = T_BOARD("destructive analyzer")
build_path = /obj/machinery/rnd/destructive_analyzer
board_type = new /datum/frame/frame_types/machine
origin_tech = list(TECH_MAGNET = 2, TECH_ENGINEERING = 2, TECH_DATA = 2)
req_components = list(
/obj/item/stock_parts/scanning_module = 1,
/obj/item/stock_parts/manipulator = 1,
/obj/item/stock_parts/micro_laser = 1)

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

View File

@@ -1,6 +1,6 @@
{
"devDependencies": {
"@biomejs/biome": "^2.2.5",
"@biomejs/biome": "^2.2.6",
"prettier": "^3.6.2"
},
"scripts": {

View File

@@ -0,0 +1,159 @@
import { useBackend } from 'tgui/backend';
import { Window } from 'tgui/layouts';
import {
Box,
Button,
Image,
NoticeBox,
Section,
Stack,
} from 'tgui-core/components';
import type { BooleanLike } from 'tgui-core/react';
type Data = {
server_connected: BooleanLike;
loaded_item: string;
item_icon: string;
indestructible: BooleanLike;
already_deconstructed: BooleanLike;
recoverable_points: string;
node_data: NodeData[] | null;
research_point_id: string;
};
type NodeData = {
node_name: string;
node_id: string;
node_hidden: BooleanLike;
};
export const DestructiveAnalyzer = () => {
const { act, data } = useBackend<Data>();
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={270} title="Destructive Analyzer">
<Window.Content>
<Stack vertical fill>
<Stack.Item grow>
<Section
title={loaded_item}
fill
buttons={
<Button
icon="eject"
tooltip="Ejects the item currently inside the machine."
onClick={() => act('eject_item')}
/>
}
>
<Image
src={`data:image/jpeg;base64,${item_icon}`}
height="64px"
width="64px"
verticalAlign="middle"
/>
</Section>
</Stack.Item>
<Stack.Item grow>
<Section fill title="Deconstruction Methods">
<Stack vertical fill>
{!!indestructible && (
<Stack.Item>
<NoticeBox textAlign="center" danger>
This item can&apos;t be deconstructed!
</NoticeBox>
</Stack.Item>
)}
{!indestructible && !!recoverable_points && (
<>
<Stack.Item>
<Box fontSize="14px">
Research points from deconstruction
</Box>
</Stack.Item>
<Stack.Item>
<Box>{recoverable_points}</Box>
</Stack.Item>
</>
)}
<Stack.Item>
<Stack>
{!indestructible && (
<Stack.Item>
<Button.Confirm
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,
})
}
>
Deconstruct
</Button.Confirm>
</Stack.Item>
)}
{node_data?.map((node) => (
<Stack.Item key={node.node_id}>
<Button.Confirm
icon="cash-register"
disabled={!node.node_hidden}
tooltip={
node.node_hidden
? 'Deconstruct this to research the selected node.'
: 'This node has already been researched.'
}
onClick={() =>
act('deconstruct', { deconstruct_id: node.node_id })
}
>
{node.node_name}
</Button.Confirm>
</Stack.Item>
))}
</Stack>
</Stack.Item>
</Stack>
</Section>
</Stack.Item>
</Stack>
</Window.Content>
</Window>
);
};

View File

@@ -13,7 +13,7 @@ import {
import { ConstructorEnum, type Data, Tab } from './data';
import { Constructor } from './pages/Constructor';
import { DesignList } from './pages/DesignList';
import { DestructiveAnalyzer } from './pages/DestructiveAnalyzer';
import { DestructiveAnalyzer } from '../DestructiveAnalyzer';
import { LockScreen } from './pages/LockScreen';
import { Misc } from './pages/Misc';
import { ResearchList } from './pages/ResearchList';

View File

@@ -1,154 +0,0 @@
import { useEffect, useState } from 'react';
import { useBackend } from 'tgui/backend';
import { Box, Button, Icon, Section, Stack, Table } from 'tgui-core/components';
import { classes } from 'tgui-core/react';
import type { Data, LinkedDestroyer } from '../data';
export const DestructiveAnalyzer = (props) => {
const { act, data } = useBackend<Data>();
let inner;
if (!data.linked_destroy) {
inner = <Box color="bad">No Destructive Analyzer linked.</Box>;
} else if (!data.linked_destroy.loaded_item) {
inner = <Box color="average">No item loaded.</Box>;
} else {
inner = <DestroyButton destroyer={data.linked_destroy} />;
}
return (
<Section title="Destructive Analyzer" fill textAlign="center">
{inner}
</Section>
);
};
const DestroyButton = (props: { destroyer: LinkedDestroyer }) => {
const { act } = useBackend();
const { destroyer } = props;
const [buttonPressed, setButtonPressed] = useState(false);
return (
<Stack fill>
<Stack.Item basis="60%" textAlign="left">
<Section
title="Loaded Item"
fill
buttons={<Button onClick={() => act('eject_item')}>Eject</Button>}
fontSize={1.2}
mt={2}
ml={1}
backgroundColor="#0a0a0a"
height="95%"
style={{ borderRadius: '5px' }}
>
<Box
fontSize={2}
mb={2}
ml={2}
color="good"
style={{ textTransform: 'capitalize' }}
>
{destroyer.loaded_item}
</Box>
<Table>
<Table.Row header>
<Table.Cell header>Tech Levels - Name</Table.Cell>
<Table.Cell header textAlign="center">
Level
</Table.Cell>
<Table.Cell header textAlign="center">
Current
</Table.Cell>
</Table.Row>
{destroyer.origin_tech.map((tech) => (
<Table.Row key={tech.name}>
<Table.Cell>{tech.name}</Table.Cell>
<Table.Cell textAlign="center">{tech.level}</Table.Cell>
<Table.Cell textAlign="center" position="relative">
{tech.current}
{tech.level >= tech.current ? (
<AnimatedArrows
on
position="absolute"
top={0.25}
right={0}
style={{ transform: 'rotate(-90deg)' }}
/>
) : null}
</Table.Cell>
</Table.Row>
))}
</Table>
</Section>
</Stack.Item>
<Stack.Item grow>
<Stack fill align="center" justify="center">
<Stack.Item>
{/* https://codepen.io/ovr/pen/yLXBbB */}
<Button
color="danger"
className={classes(
buttonPressed
? ['BigButton', 'BigButton__Pressed']
: ['BigButton'],
)}
verticalAlignContent="middle"
onClick={() => {
setButtonPressed(true);
setTimeout(() => {
setButtonPressed(false);
act('deconstruct');
}, 200);
}}
>
Deconstruct
</Button>
</Stack.Item>
</Stack>
</Stack.Item>
</Stack>
);
};
export const AnimatedArrows = (props: { on: boolean } & any) => {
const { on, ...rest } = props;
const [activeArrow, setActiveArrow] = useState(0);
// Lower to make it animate faster
const SPEED = 200;
useEffect(() => {
const id = setInterval(() => {
setActiveArrow((arrow) => (arrow + 1) % 3);
}, SPEED);
return () => clearInterval(id);
}, []);
return (
<Box {...rest}>
<Icon
color={!on ? 'gray' : activeArrow === 0 ? 'green' : 'white'}
name="chevron-right"
size={0.8}
mr={-0.5}
/>
<Icon
color={!on ? 'gray' : activeArrow === 1 ? 'green' : 'white'}
name="chevron-right"
size={0.8}
mr={-0.5}
/>
<Icon
color={!on ? 'gray' : activeArrow === 2 ? 'green' : 'white'}
name="chevron-right"
size={0.8}
mr={-0.5}
/>
</Box>
);
};

View File

@@ -4541,6 +4541,7 @@
#include "code\modules\research\tg\machinery\_production.dm"
#include "code\modules\research\tg\machinery\circuit_imprinter.dm"
#include "code\modules\research\tg\machinery\departmental_protolathe.dm"
#include "code\modules\research\tg\machinery\destructive_analyzer.dm"
#include "code\modules\research\tg\machinery\mech_fabricator.dm"
#include "code\modules\research\tg\machinery\mech_prosfab.dm"
#include "code\modules\research\tg\machinery\protolathe.dm"