mirror of
https://github.com/Bubberstation/Bubberstation.git
synced 2026-08-24 21:48:39 +01:00
Experi-Sci: Techweb nodes may now require you to perform "scientific" experiments (#54093)
Co-authored-by: Brett Williams <bobbahbrown@gmail.com> Co-authored-by: Jordan Brown <Cyberboss@users.noreply.github.com> Co-authored-by: ArcaneMusic <41715314+ArcaneMusic@users.noreply.github.com> Co-authored-by: Aleksej Komarov <stylemistake@gmail.com> Co-authored-by: Mothblocks <35135081+Mothblocks@users.noreply.github.com>
This commit is contained in:
co-authored by
Brett Williams
Jordan Brown
ArcaneMusic
Aleksej Komarov
Mothblocks
parent
381b09fbd6
commit
931a32ffb3
@@ -0,0 +1,101 @@
|
||||
/**
|
||||
* # Destructive scanner
|
||||
*
|
||||
* Placed machine that handles destructive experiments (but can also do the normal ones)
|
||||
*/
|
||||
/obj/machinery/destructive_scanner
|
||||
name = "Experimental Destructive Scanner"
|
||||
desc = "A much larger version of the hand-held scanner, a charred label warns about its destructive capabilities."
|
||||
icon = 'icons/obj/machines/experisci.dmi'
|
||||
icon_state = "tube_open"
|
||||
circuit = /obj/item/circuitboard/machine/destructive_scanner
|
||||
layer = MOB_LAYER
|
||||
var/scanning = FALSE
|
||||
|
||||
/obj/machinery/destructive_scanner/Initialize()
|
||||
..()
|
||||
return INITIALIZE_HINT_LATELOAD
|
||||
|
||||
// Late load to ensure the component initialization occurs after the machines are initialized
|
||||
/obj/machinery/destructive_scanner/LateInitialize()
|
||||
. = ..()
|
||||
AddComponent(/datum/component/experiment_handler, \
|
||||
allowed_experiments = list(/datum/experiment/scanning),\
|
||||
config_mode = EXPERIMENT_CONFIG_CLICK, \
|
||||
start_experiment_callback = CALLBACK(src, .proc/activate))
|
||||
|
||||
///Activates the machine; checks if it can actually scan, then starts.
|
||||
/obj/machinery/destructive_scanner/proc/activate()
|
||||
var/atom/pickup_zone = drop_location()
|
||||
var/aggressive = FALSE
|
||||
for(var/mob/living/living_mob in pickup_zone)
|
||||
if(!(obj_flags & EMAGGED) && ishuman(living_mob)) //Can only kill humans when emagged.
|
||||
playsound(src, 'sound/machines/buzz-sigh.ogg', 25)
|
||||
say("Cannot scan with humans inside.")
|
||||
return
|
||||
aggressive = TRUE
|
||||
start_closing(aggressive)
|
||||
|
||||
///Closes the machine to kidnap everything in the turf into it.
|
||||
/obj/machinery/destructive_scanner/proc/start_closing(aggressive)
|
||||
if(scanning)
|
||||
return
|
||||
var/atom/pickup_zone = drop_location()
|
||||
for(var/atom/movable/to_pickup in pickup_zone)
|
||||
to_pickup.forceMove(src)
|
||||
flick("tube_down", src)
|
||||
scanning = TRUE
|
||||
update_icon()
|
||||
playsound(src, 'sound/machines/destructive_scanner/TubeDown.ogg', 100)
|
||||
addtimer(CALLBACK(src, .proc/start_scanning, aggressive), 1.2 SECONDS)
|
||||
|
||||
///Starts scanning the fancy scanning effects
|
||||
/obj/machinery/destructive_scanner/proc/start_scanning(aggressive)
|
||||
if(aggressive)
|
||||
playsound(src, 'sound/machines/destructive_scanner/ScanDangerous.ogg', 100, extrarange = 5)
|
||||
else
|
||||
playsound(src, 'sound/machines/destructive_scanner/ScanSafe.ogg', 100)
|
||||
addtimer(CALLBACK(src, .proc/finish_scanning, aggressive), 6 SECONDS)
|
||||
|
||||
|
||||
///Performs the actual scan, happens once the tube effects are done
|
||||
/obj/machinery/destructive_scanner/proc/finish_scanning(aggressive)
|
||||
flick("tube_up", src)
|
||||
scanning = FALSE
|
||||
update_icon()
|
||||
playsound(src, 'sound/machines/destructive_scanner/TubeUp.ogg', 100)
|
||||
addtimer(CALLBACK(src, .proc/open, aggressive), 1.2 SECONDS)
|
||||
|
||||
///Opens the machine to let out any contents. If the scan had mobs it'll gib them.
|
||||
/obj/machinery/destructive_scanner/proc/open(aggressive)
|
||||
var/turf/this_turf = get_turf(src)
|
||||
var/list/scanned_atoms = list()
|
||||
|
||||
for(var/atom/movable/movable_atom in contents)
|
||||
if(movable_atom in component_parts)
|
||||
continue
|
||||
scanned_atoms += movable_atom
|
||||
movable_atom.forceMove(this_turf)
|
||||
if(isliving(movable_atom))
|
||||
var/mob/living/fucked_up_thing = movable_atom
|
||||
fucked_up_thing.gib()
|
||||
|
||||
SEND_SIGNAL(src, COMSIG_MACHINERY_DESTRUCTIVE_SCAN, scanned_atoms)
|
||||
|
||||
|
||||
/obj/machinery/destructive_scanner/emag_act(mob/user)
|
||||
if(obj_flags & EMAGGED)
|
||||
return
|
||||
obj_flags |= EMAGGED
|
||||
playsound(src, "sparks", 75, TRUE, SILENCED_SOUND_EXTRARANGE)
|
||||
to_chat(user, "<span class='notice'>You disable the safety sensor BIOS on [src].</span>")
|
||||
|
||||
/obj/machinery/destructive_scanner/update_icon_state()
|
||||
. = ..()
|
||||
icon_state = scanning ? "tube_on" : "tube_open"
|
||||
|
||||
/obj/machinery/destructive_scanner/attackby(obj/item/object, mob/user, params)
|
||||
if (!scanning && default_deconstruction_screwdriver(user, "tube_open", "tube_open", object) || default_deconstruction_crowbar(object))
|
||||
update_icon()
|
||||
return
|
||||
return ..()
|
||||
@@ -0,0 +1,140 @@
|
||||
/datum/experiment/scanning/points/slime
|
||||
name = "Base Slime Experiment"
|
||||
required_points = 1
|
||||
|
||||
/datum/experiment/scanning/points/slime/calibration
|
||||
name = "Slime Sample Test"
|
||||
description = "Let's see if our scanners can pick up the genetic data from a simple slime extract."
|
||||
required_atoms = list(/obj/item/slime_extract/grey = 1)
|
||||
|
||||
/datum/experiment/scanning/points/slime/easy
|
||||
name = "Easy Slime Survey"
|
||||
description = "A wealthy client has requested that we provide samples of data from several basic slime cores."
|
||||
required_points = 3
|
||||
required_atoms = list(/obj/item/slime_extract/orange = 1,
|
||||
/obj/item/slime_extract/purple = 1,
|
||||
/obj/item/slime_extract/blue = 1,
|
||||
/obj/item/slime_extract/metal = 1)
|
||||
|
||||
/datum/experiment/scanning/points/slime/moderate
|
||||
name = "Moderate Slime Survey"
|
||||
description = "Central Command has asked that you collect data from several common slime cores."
|
||||
required_points = 5
|
||||
required_atoms = list(/obj/item/slime_extract/yellow = 1,
|
||||
/obj/item/slime_extract/darkpurple = 1,
|
||||
/obj/item/slime_extract/darkblue = 1,
|
||||
/obj/item/slime_extract/silver = 1)
|
||||
|
||||
/datum/experiment/scanning/points/slime/hard
|
||||
name = "Challenging Slime Survey"
|
||||
description = "Another station has challenged your research team to collect several challenging slime cores, \
|
||||
are you up to the task?"
|
||||
required_points = 10
|
||||
required_atoms = list(/obj/item/slime_extract/bluespace = 1,
|
||||
/obj/item/slime_extract/sepia = 1,
|
||||
/obj/item/slime_extract/cerulean = 1,
|
||||
/obj/item/slime_extract/pyrite = 1,
|
||||
/obj/item/slime_extract/red = 2,
|
||||
/obj/item/slime_extract/green = 2,
|
||||
/obj/item/slime_extract/pink = 2,
|
||||
/obj/item/slime_extract/gold = 2)
|
||||
|
||||
/datum/experiment/scanning/points/slime/expert
|
||||
name = "Expert Slime Survey"
|
||||
description = "The intergalactic society of xenobiologists are currently looking for samples of the most complex \
|
||||
slime cores, we are tasking your station with providing them with everything they need."
|
||||
required_points = 10
|
||||
required_atoms = list(/obj/item/slime_extract/adamantine = 1,
|
||||
/obj/item/slime_extract/oil = 1,
|
||||
/obj/item/slime_extract/black = 1,
|
||||
/obj/item/slime_extract/lightpink = 1,
|
||||
/obj/item/slime_extract/rainbow = 10)
|
||||
|
||||
/datum/experiment/scanning/random/cytology/easy
|
||||
name = "Basic Cytology Scanning Experiment"
|
||||
description = "A scientist needs vermin to test on, use the cytology equipment to grow some of these simple critters!"
|
||||
total_requirement = 3
|
||||
max_requirement_per_type = 2
|
||||
possible_types = list(/mob/living/simple_animal/hostile/cockroach, /datum/micro_organism/cell_line/mouse)
|
||||
|
||||
/datum/experiment/scanning/random/cytology/medium
|
||||
name = "Advanced Cytology Scanning Experiment"
|
||||
description = "We need to see how the body functions from the earliest moments. Some cytology experiments will help us gain this understanding."
|
||||
total_requirement = 3
|
||||
max_requirement_per_type = 2
|
||||
possible_types = list(/mob/living/simple_animal/hostile/carp, /mob/living/simple_animal/hostile/retaliate/poison/snake, /mob/living/simple_animal/pet/cat, /mob/living/simple_animal/pet/dog/corgi, /mob/living/simple_animal/cow, /mob/living/simple_animal/chicken)
|
||||
|
||||
/datum/experiment/scanning/random/cytology/medium/one
|
||||
name = "Advanced Cytology Scanning Experiment One"
|
||||
|
||||
/datum/experiment/scanning/random/cytology/medium/two
|
||||
name = "Advanced Cytology Scanning Experiment Two"
|
||||
|
||||
/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."
|
||||
possible_types = list(/obj/effect/decal/cleanable/vomit,
|
||||
/obj/effect/decal/cleanable/blood)
|
||||
total_requirement = 3
|
||||
|
||||
/datum/experiment/explosion/calibration
|
||||
name = "Is This Thing On?"
|
||||
description = "The engineers from last shift left a notice for us that the doppler array seemed to be malfunctioning. \
|
||||
Could you check that it is still working? Any explosion will do!"
|
||||
required_light = 1
|
||||
|
||||
/datum/experiment/explosion/maxcap
|
||||
name = "Mother of God"
|
||||
description = "A recent outbreak of a blood-cult in a nearby sector necessitates the development of a large explosive. \
|
||||
Create a large enough explosion to prove your bomb, we'll be watching."
|
||||
|
||||
/datum/experiment/explosion/medium
|
||||
name = "Explosive Ordinance Experiment"
|
||||
description = "Alright, can we really call ourselves professionals if we can't make shit blow up?"
|
||||
required_heavy = 2
|
||||
required_light = 6
|
||||
|
||||
/datum/experiment/explosion/maxcap/New()
|
||||
required_devastation = GLOB.MAX_EX_DEVESTATION_RANGE
|
||||
required_heavy = GLOB.MAX_EX_HEAVY_RANGE
|
||||
required_light = GLOB.MAX_EX_LIGHT_RANGE
|
||||
|
||||
/datum/experiment/scanning/random/material/meat
|
||||
name = "Biological Material Scanning Experiment"
|
||||
description = "They told us we couldn't make chairs out of every material in the world. You're here to prove those nay-sayers wrong."
|
||||
possible_material_types = list(/datum/material/meat)
|
||||
|
||||
/datum/experiment/scanning/random/material/easy
|
||||
name = "Low Grade Material Scanning Experiment"
|
||||
description = "Material science is all about a basic understanding of the universe, and how it's built. To explain this, build something basic and we'll show you how to break it."
|
||||
total_requirement = 6
|
||||
possible_types = list(/obj/structure/chair, /obj/structure/toilet, /obj/structure/table)
|
||||
possible_material_types = list(/datum/material/iron, /datum/material/glass)
|
||||
|
||||
/datum/experiment/scanning/random/material/medium
|
||||
name = "Medium Grade Material Scanning Experiment"
|
||||
description = "Not all materials are strong enough to hold together a space station. Look at these materials for example, and see what makes them useful for our electronics and equipment."
|
||||
possible_material_types = list(/datum/material/silver, /datum/material/gold, /datum/material/plastic, /datum/material/titanium)
|
||||
|
||||
/datum/experiment/scanning/random/material/medium/one
|
||||
name = "Medium Grade Material Scanning Experiment One"
|
||||
|
||||
/datum/experiment/scanning/random/material/medium/two
|
||||
name = "Medium Grade Material Scanning Experiment Two"
|
||||
|
||||
/datum/experiment/scanning/random/material/medium/three
|
||||
name = "Medium Grade Material Scanning Experiment Three"
|
||||
|
||||
/datum/experiment/scanning/random/material/hard
|
||||
name = "High Grade Material Scanning Experiment"
|
||||
description = "NT spares no expense to test even the most valuable of materials for their qualities as construction materials. Go build us some of these exotic creations and collect the data."
|
||||
possible_material_types = list(/datum/material/diamond, /datum/material/plasma, /datum/material/uranium)
|
||||
|
||||
/datum/experiment/scanning/random/material/hard/one
|
||||
name = "High Grade Material Scanning Experiment One"
|
||||
|
||||
/datum/experiment/scanning/random/material/hard/two
|
||||
name = "High Grade Material Scanning Experiment Two"
|
||||
|
||||
/datum/experiment/scanning/random/material/hard/three
|
||||
name = "High Grade Material Scanning Experiment Three"
|
||||
@@ -0,0 +1,405 @@
|
||||
/**
|
||||
* # Experiment Handler
|
||||
*
|
||||
* This is the component for interacting with experiments from a connected techweb. It is generic
|
||||
* and should be set-up to automatically work on any class it is attached to without outside code
|
||||
* (Excluding potential callbacks)
|
||||
*/
|
||||
/datum/component/experiment_handler
|
||||
/// Holds the currently linked techweb to get experiments from
|
||||
var/datum/techweb/linked_web
|
||||
/// Holds the currently selected experiment
|
||||
var/datum/experiment/selected_experiment
|
||||
/// Holds the list of types of experiments that this experiment_handler can interact with
|
||||
var/list/allowed_experiments
|
||||
/// Holds the list of types of experiments that this experimennt_handler should NOT interact with
|
||||
var/list/blacklisted_experiments
|
||||
/// A set of optional experiment traits (see defines) that are disallowed for any experiments
|
||||
var/disallowed_traits
|
||||
/// Additional configuration flags for how the experiment_handler operates
|
||||
var/config_flags
|
||||
/// Callback that, when supplied, can be called from the UI
|
||||
var/datum/callback/start_experiment_callback
|
||||
|
||||
/**
|
||||
* Initializes a new instance of the experiment_handler component
|
||||
*
|
||||
* Arguments:
|
||||
* * allowed_experiments - The list of /datum/experiment types that can be performed with this component
|
||||
* * blacklisted_experiments - The list of /datum/experiment types that explicitly cannot be performed with this component
|
||||
* * config_mode - The define that determines how the experiment_handler should display the configuration UI
|
||||
* * disallowed_traits - Flags that control what experiment traits are blacklisted by this experiment handler
|
||||
* * config_flags - Flags that control the operational behaviour of the experiment handler, see experiment defines
|
||||
* * start_experiment_callback - When provided adds a UI button to use this callback to the start the experiment
|
||||
*/
|
||||
/datum/component/experiment_handler/Initialize(allowed_experiments = list(),
|
||||
blacklisted_experiments = list(),
|
||||
config_mode = EXPERIMENT_CONFIG_ATTACKSELF,
|
||||
disallowed_traits = null,
|
||||
config_flags = null,
|
||||
datum/callback/start_experiment_callback = null,
|
||||
)
|
||||
. = ..()
|
||||
if(!ismovable(parent))
|
||||
return COMPONENT_INCOMPATIBLE
|
||||
|
||||
src.allowed_experiments = allowed_experiments
|
||||
src.blacklisted_experiments = blacklisted_experiments
|
||||
src.disallowed_traits = disallowed_traits
|
||||
src.config_flags = config_flags
|
||||
src.start_experiment_callback = start_experiment_callback
|
||||
|
||||
if(isitem(parent))
|
||||
RegisterSignal(parent, COMSIG_ITEM_PRE_ATTACK, .proc/try_run_handheld_experiment)
|
||||
RegisterSignal(parent, COMSIG_ITEM_AFTERATTACK, .proc/ignored_handheld_experiment_attempt)
|
||||
if(istype(parent, /obj/machinery/doppler_array))
|
||||
RegisterSignal(parent, COMSIG_DOPPLER_ARRAY_EXPLOSION_DETECTED, .proc/try_run_doppler_experiment)
|
||||
if(istype(parent, /obj/machinery/destructive_scanner))
|
||||
RegisterSignal(parent, COMSIG_MACHINERY_DESTRUCTIVE_SCAN, .proc/try_run_destructive_experiment)
|
||||
|
||||
// Determine UI display mode
|
||||
switch(config_mode)
|
||||
if(EXPERIMENT_CONFIG_ATTACKSELF)
|
||||
RegisterSignal(parent, COMSIG_ITEM_ATTACK_SELF, .proc/configure_experiment)
|
||||
if(EXPERIMENT_CONFIG_ALTCLICK)
|
||||
RegisterSignal(parent, COMSIG_CLICK_ALT, .proc/configure_experiment)
|
||||
if(EXPERIMENT_CONFIG_CLICK)
|
||||
RegisterSignal(parent, COMSIG_ATOM_UI_INTERACT, .proc/configure_experiment_click)
|
||||
if(EXPERIMENT_CONFIG_UI)
|
||||
RegisterSignal(parent, COMSIG_UI_ACT, .proc/ui_handle_experiment)
|
||||
|
||||
// Auto connect to the first visible techweb (useful for always active handlers)
|
||||
// Note this won't work at the moment for non-machines that have been included
|
||||
// on the map as the servers aren't initialized when the non-machines are initializing
|
||||
if (!(config_flags & EXPERIMENT_CONFIG_NO_AUTOCONNECT))
|
||||
var/list/found_servers = get_available_servers(parent)
|
||||
var/obj/machinery/rnd/server/selected_server = found_servers.len ? found_servers[1] : null
|
||||
if (selected_server)
|
||||
link_techweb(selected_server.stored_research)
|
||||
|
||||
GLOB.experiment_handlers += src
|
||||
|
||||
/datum/component/experiment_handler/Destroy(force, silent)
|
||||
. = ..()
|
||||
GLOB.experiment_handlers -= src
|
||||
|
||||
/**
|
||||
* Hooks on attack to try and run an experiment (When using a handheld handler)
|
||||
*/
|
||||
/datum/component/experiment_handler/proc/try_run_handheld_experiment(datum/source, atom/target, mob/user, params)
|
||||
SIGNAL_HANDLER
|
||||
if (!should_run_handheld_experiment(source, target, user, params))
|
||||
return
|
||||
INVOKE_ASYNC(src, .proc/try_run_handheld_experiment_async, source, target, user, params)
|
||||
return COMPONENT_CANCEL_ATTACK_CHAIN
|
||||
|
||||
/**
|
||||
* Provides feedback when an item isn't related to an experiment, and has fully passed the attack chain
|
||||
*/
|
||||
/datum/component/experiment_handler/proc/ignored_handheld_experiment_attempt(datum/source, atom/target, mob/user, proximity_flag, params)
|
||||
SIGNAL_HANDLER
|
||||
if (!proximity_flag || (selected_experiment == null && !(config_flags & EXPERIMENT_CONFIG_ALWAYS_ACTIVE)))
|
||||
return
|
||||
playsound(user, 'sound/machines/buzz-sigh.ogg', 25)
|
||||
to_chat(user, "<span class='notice'>[target] is not related to your currently selected experiment.</span>")
|
||||
|
||||
/**
|
||||
* Checks that an experiment can be run using the provided target, used for preventing the cancellation of the attack chain inappropriately
|
||||
*/
|
||||
/datum/component/experiment_handler/proc/should_run_handheld_experiment(datum/source, atom/target, mob/user, params)
|
||||
// Check that there is actually an experiment selected
|
||||
if (selected_experiment == null && !(config_flags & EXPERIMENT_CONFIG_ALWAYS_ACTIVE))
|
||||
return
|
||||
|
||||
// Determine if this experiment is actionable with this target
|
||||
var/list/arguments = list(src)
|
||||
arguments = args.len > 1 ? arguments + args.Copy(2) : arguments
|
||||
if (config_flags & EXPERIMENT_CONFIG_ALWAYS_ACTIVE)
|
||||
for (var/datum/experiment/experiment in linked_web.available_experiments)
|
||||
if (experiment.actionable(arglist(arguments)))
|
||||
return TRUE
|
||||
else
|
||||
return selected_experiment.actionable(arglist(arguments))
|
||||
|
||||
/**
|
||||
* This proc exists because Jared Fogle really likes async
|
||||
*/
|
||||
/datum/component/experiment_handler/proc/try_run_handheld_experiment_async(datum/source, atom/target, mob/user, params)
|
||||
if (selected_experiment == null && !(config_flags & EXPERIMENT_CONFIG_ALWAYS_ACTIVE))
|
||||
to_chat(user, "<span class='notice'>You do not have an experiment selected!</span>")
|
||||
return
|
||||
if(!do_after(user, 1 SECONDS, target = target))
|
||||
return
|
||||
if(action_experiment(source, target))
|
||||
playsound(user, 'sound/machines/ping.ogg', 25)
|
||||
to_chat(user, "<span class='notice'>You scan [target].</span>")
|
||||
else
|
||||
playsound(user, 'sound/machines/buzz-sigh.ogg', 25)
|
||||
to_chat(user, "<span class='notice'>[target] is not related to your currently selected experiment.</span>")
|
||||
|
||||
|
||||
/**
|
||||
* Hooks on destructive scans to try and run an experiment (When using a handheld handler)
|
||||
*/
|
||||
/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)
|
||||
playsound(our_scanner, 'sound/machines/buzz-sigh.ogg', 25)
|
||||
to_chat(our_scanner, "<span class='notice'>No experiment selected!</span>")
|
||||
return
|
||||
var/successful_scan
|
||||
for(var/scan_target in scanned_atoms)
|
||||
if(action_experiment(source, scan_target))
|
||||
successful_scan = TRUE
|
||||
break
|
||||
if(successful_scan)
|
||||
playsound(our_scanner, 'sound/machines/ping.ogg', 25)
|
||||
to_chat(our_scanner, "<span class='notice'>The scan succeeds.</span>")
|
||||
else
|
||||
playsound(src, 'sound/machines/buzz-sigh.ogg', 25)
|
||||
our_scanner.say("The scan did not result in anything.")
|
||||
/**
|
||||
* Hooks on successful explosions on the doppler array this is attached to
|
||||
*/
|
||||
/datum/component/experiment_handler/proc/try_run_doppler_experiment(datum/source, turf/epicenter, devastation_range,
|
||||
heavy_impact_range, light_impact_range, took, orig_dev_range, orig_heavy_range, orig_light_range
|
||||
)
|
||||
SIGNAL_HANDLER
|
||||
var/atom/movable/our_array = parent
|
||||
if(action_experiment(source, devastation_range, heavy_impact_range, light_impact_range))
|
||||
playsound(src, 'sound/machines/ping.ogg', 25)
|
||||
else
|
||||
playsound(src, 'sound/machines/buzz-sigh.ogg', 25)
|
||||
our_array.say("Insufficient explosion to contribute to current experiment.")
|
||||
|
||||
/**
|
||||
* Announces a message to all experiment handlers
|
||||
*
|
||||
* Arguments:
|
||||
* * message - The message to announce
|
||||
*/
|
||||
/datum/component/experiment_handler/proc/announce_message_to_all(message)
|
||||
for(var/experiment in GLOB.experiment_handlers)
|
||||
var/datum/component/experiment_handler/experi_handler = experiment
|
||||
var/atom/movable/experi_parent = experi_handler.parent
|
||||
experi_parent.say(message)
|
||||
|
||||
/**
|
||||
* Announces a message to this experiment handler
|
||||
*
|
||||
* Arguments:
|
||||
* * message - The message to announce
|
||||
*/
|
||||
/datum/component/experiment_handler/proc/announce_message(message)
|
||||
var/atom/movable/experi_parent = parent
|
||||
experi_parent.say(message)
|
||||
|
||||
/**
|
||||
* Attempts to perform the selected experiment given some arguments
|
||||
*/
|
||||
/datum/component/experiment_handler/proc/action_experiment(datum/source, ...)
|
||||
// Check if an experiment is selected
|
||||
if (selected_experiment == null && !(config_flags & EXPERIMENT_CONFIG_ALWAYS_ACTIVE))
|
||||
return FALSE
|
||||
|
||||
// Get arguments for passing to the experiment[s]
|
||||
var/list/arguments = list(src)
|
||||
arguments = args.len > 1 ? arguments + args.Copy(2) : arguments
|
||||
|
||||
// Check if this handler is configured to be always active, in which case we
|
||||
// attempt to action every experiment that is available to this handler.
|
||||
if (config_flags & EXPERIMENT_CONFIG_ALWAYS_ACTIVE)
|
||||
var/any_success
|
||||
for (var/datum/experiment/experiment in linked_web.available_experiments)
|
||||
// Because this checks any experiment, we have to ensure it is allowable to be selected with can_select_experiment(...)
|
||||
// this handles the handler's blacklist, whitelist, etc (potentially refactor this in the future if possible because this could be expensive)
|
||||
if (can_select_experiment(experiment) && experiment.actionable(arglist(arguments)) && experiment.perform_experiment(arglist(arguments)))
|
||||
any_success = TRUE
|
||||
return any_success
|
||||
else
|
||||
// Returns true if the experiment was succesfuly handled
|
||||
return selected_experiment.actionable(arglist(arguments)) && selected_experiment.perform_experiment(arglist(arguments))
|
||||
|
||||
/**
|
||||
* Hook for handling UI interaction via signals
|
||||
*/
|
||||
/datum/component/experiment_handler/proc/ui_handle_experiment(datum/source, mob/user, action)
|
||||
SIGNAL_HANDLER
|
||||
switch(action)
|
||||
if("open_experiments")
|
||||
INVOKE_ASYNC(src, .proc/configure_experiment, null, usr)
|
||||
|
||||
/**
|
||||
* Attempts to show the user the experiment configuration panel
|
||||
*
|
||||
* Arguments:
|
||||
* * user - The user to show the experiment configuration panel to
|
||||
*/
|
||||
/datum/component/experiment_handler/proc/configure_experiment(datum/source, mob/user)
|
||||
ui_interact(user)
|
||||
|
||||
/**
|
||||
* Attempts to show the user the experiment configuration panel
|
||||
*
|
||||
* Arguments:
|
||||
* * user - The user to show the experiment configuration panel to
|
||||
*/
|
||||
/datum/component/experiment_handler/proc/configure_experiment_click(datum/source, mob/user)
|
||||
ui_interact(user)
|
||||
|
||||
/**
|
||||
* Attempts to link this experiment_handler to a provided techweb
|
||||
*
|
||||
* This proc attempts to link the handler to a provided techweb, overriding the existing techweb if relevant
|
||||
*
|
||||
* Arguments:
|
||||
* * new_web - The new techweb to link to
|
||||
*/
|
||||
/datum/component/experiment_handler/proc/link_techweb(datum/techweb/new_web)
|
||||
if (new_web == linked_web)
|
||||
return
|
||||
selected_experiment = null
|
||||
linked_web = new_web
|
||||
|
||||
/**
|
||||
* Unlinks this handler from the selected techweb
|
||||
*/
|
||||
/datum/component/experiment_handler/proc/unlink_techweb()
|
||||
selected_experiment = null
|
||||
linked_web = null
|
||||
|
||||
/**
|
||||
* Attempts to link this experiment_handler to a provided experiment
|
||||
*
|
||||
* Arguments:
|
||||
* * experiment - The experiment to attempt to link to
|
||||
*/
|
||||
/datum/component/experiment_handler/proc/link_experiment(datum/experiment/experiment)
|
||||
if (experiment && can_select_experiment(experiment))
|
||||
selected_experiment = experiment
|
||||
|
||||
/**
|
||||
* Unlinks this handler from the selected experiment
|
||||
*/
|
||||
/datum/component/experiment_handler/proc/unlink_experiment()
|
||||
selected_experiment = null
|
||||
|
||||
/**
|
||||
* Attempts to get rnd servers on the same z-level as a provided turf
|
||||
*
|
||||
* Arguments:
|
||||
* * turf_to_check_for_servers - The turf to get servers on the same z-level of
|
||||
*/
|
||||
/datum/component/experiment_handler/proc/get_available_servers(turf/turf_to_check_for_servers = null)
|
||||
if (!turf_to_check_for_servers)
|
||||
turf_to_check_for_servers = get_turf(parent)
|
||||
var/list/local_servers = list()
|
||||
for (var/obj/machinery/rnd/server/server in SSresearch.servers)
|
||||
var/turf/position_of_this_server_machine = get_turf(server)
|
||||
if (turf_to_check_for_servers && position_of_this_server_machine && position_of_this_server_machine.z == turf_to_check_for_servers.z)
|
||||
local_servers += server
|
||||
return local_servers
|
||||
|
||||
/**
|
||||
* Checks if an experiment is valid to be selected by this handler
|
||||
*
|
||||
* Arguments:
|
||||
* * experiment - The experiment to check
|
||||
*/
|
||||
/datum/component/experiment_handler/proc/can_select_experiment(datum/experiment/experiment)
|
||||
// Check that this experiments has no disallowed traits
|
||||
if (experiment.traits & disallowed_traits)
|
||||
return FALSE
|
||||
|
||||
// Check against the list of allowed experimentors
|
||||
if (experiment.allowed_experimentors && experiment.allowed_experimentors.len)
|
||||
var/matched = FALSE
|
||||
for (var/experimentor in experiment.allowed_experimentors)
|
||||
if (istype(parent, experimentor))
|
||||
matched = TRUE
|
||||
break
|
||||
if (!matched)
|
||||
return FALSE
|
||||
|
||||
// Check that this experiment is visible currently
|
||||
if (!linked_web || !(experiment in linked_web.available_experiments))
|
||||
return FALSE
|
||||
|
||||
// Check that this experiment type isn't blacklisted
|
||||
for (var/badsci in blacklisted_experiments)
|
||||
if (istype(experiment, badsci))
|
||||
return FALSE
|
||||
|
||||
// Check against the allowed experiment types
|
||||
for (var/goodsci in allowed_experiments)
|
||||
if (istype(experiment, goodsci))
|
||||
return TRUE
|
||||
|
||||
// If we haven't returned yet then this shouldn't be allowed
|
||||
return FALSE
|
||||
|
||||
/datum/component/experiment_handler/ui_interact(mob/user, datum/tgui/ui)
|
||||
ui = SStgui.try_update_ui(user, src, ui)
|
||||
if (!ui)
|
||||
var/atom/parent_atom = parent
|
||||
ui = new(user, src, "ExperimentConfigure", "[parent_atom ? "[parent_atom.name] | " : ""]Experiment Configuration")
|
||||
ui.open()
|
||||
|
||||
/datum/component/experiment_handler/ui_data(mob/user)
|
||||
. = list(
|
||||
"always_active" = config_flags & EXPERIMENT_CONFIG_ALWAYS_ACTIVE,
|
||||
"has_start_callback" = !isnull(start_experiment_callback))
|
||||
.["servers"] = list()
|
||||
for (var/obj/machinery/rnd/server/server in get_available_servers())
|
||||
var/list/data = list(
|
||||
name = server.name,
|
||||
web_id = server.stored_research?.id,
|
||||
web_org = server.stored_research?.organization,
|
||||
location = get_area(server),
|
||||
selected = !isnull(linked_web) && server.stored_research == linked_web,
|
||||
ref = REF(server)
|
||||
)
|
||||
.["servers"] += list(data)
|
||||
.["experiments"] = list()
|
||||
if (linked_web)
|
||||
for (var/datum/experiment/experiment in linked_web.available_experiments)
|
||||
var/list/data = list(
|
||||
name = experiment.name,
|
||||
description = experiment.description,
|
||||
tag = experiment.exp_tag,
|
||||
selectable = can_select_experiment(experiment),
|
||||
selected = selected_experiment == experiment,
|
||||
progress = experiment.check_progress(),
|
||||
performance_hint = experiment.performance_hint,
|
||||
ref = REF(experiment)
|
||||
)
|
||||
.["experiments"] += list(data)
|
||||
|
||||
/datum/component/experiment_handler/ui_act(action, params)
|
||||
. = ..()
|
||||
if (.)
|
||||
return
|
||||
switch (action)
|
||||
if ("select_server")
|
||||
. = TRUE
|
||||
var/obj/machinery/rnd/server/server = locate(params["ref"])
|
||||
if (server)
|
||||
link_techweb(server.stored_research)
|
||||
return
|
||||
if ("clear_server")
|
||||
. = TRUE
|
||||
unlink_techweb()
|
||||
if ("select_experiment")
|
||||
. = TRUE
|
||||
// Don't allow selection for always actives (no concept of active)
|
||||
if (config_flags & EXPERIMENT_CONFIG_ALWAYS_ACTIVE)
|
||||
return
|
||||
var/datum/experiment/experiment = locate(params["ref"])
|
||||
if (experiment)
|
||||
link_experiment(experiment)
|
||||
if ("clear_experiment")
|
||||
. = TRUE
|
||||
unlink_experiment()
|
||||
if("start_experiment_callback")
|
||||
start_experiment_callback.Invoke(selected_experiment)
|
||||
@@ -0,0 +1,56 @@
|
||||
/datum/experiment/physical/meat_wall_explosion
|
||||
name = "Extreme Cooking Experiment"
|
||||
description = "There has been interest in using our engineering equipment to see what kind of new cooking appliances we can create"
|
||||
|
||||
/datum/experiment/physical/meat_wall_explosion/register_events()
|
||||
if(!istype(currently_scanned_atom, /turf/closed/wall))
|
||||
linked_experiment_handler.announce_message("Incorrect object for experiment.")
|
||||
return FALSE
|
||||
|
||||
if(!currently_scanned_atom.has_material_type(/datum/material/meat))
|
||||
linked_experiment_handler.announce_message("Object is not made out of the correct materials.")
|
||||
return FALSE
|
||||
|
||||
RegisterSignal(currently_scanned_atom, COMSIG_ATOM_BULLET_ACT, .proc/check_experiment)
|
||||
linked_experiment_handler.announce_message("Experiment ready to start.")
|
||||
return TRUE
|
||||
|
||||
/datum/experiment/physical/meat_wall_explosion/unregister_events()
|
||||
UnregisterSignal(currently_scanned_atom, COMSIG_ATOM_BULLET_ACT)
|
||||
|
||||
/datum/experiment/physical/meat_wall_explosion/check_progress()
|
||||
. += EXPERIMENT_PROG_BOOL("Fire an emitter at a tracked meat wall", is_complete())
|
||||
|
||||
/datum/experiment/physical/meat_wall_explosion/proc/check_experiment(datum/source, obj/projectile/Proj)
|
||||
if(istype(Proj, /obj/projectile/beam/emitter))
|
||||
finish_experiment(linked_experiment_handler)
|
||||
|
||||
/datum/experiment/physical/meat_wall_explosion/finish_experiment(datum/component/experiment_handler/experiment_handler)
|
||||
. = ..()
|
||||
new /obj/effect/gibspawner/generic(currently_scanned_atom)
|
||||
var/turf/meat_wall = currently_scanned_atom
|
||||
var/turf/new_turf = meat_wall.ScrapeAway()
|
||||
new /obj/effect/gibspawner/generic(new_turf)
|
||||
new /obj/item/food/meat/steak/plain(new_turf)
|
||||
|
||||
/datum/experiment/physical/arcade_winner
|
||||
name = "Playtesting Experiences"
|
||||
description = "How do they make these arcade games so fun? Let's play one and win it to find out."
|
||||
|
||||
/datum/experiment/physical/arcade_winner/register_events()
|
||||
if(!istype(currently_scanned_atom, /obj/machinery/computer/arcade))
|
||||
linked_experiment_handler.announce_message("Incorrect object for experiment.")
|
||||
return FALSE
|
||||
|
||||
RegisterSignal(currently_scanned_atom, COMSIG_ARCADE_PRIZEVEND, .proc/win_arcade)
|
||||
linked_experiment_handler.announce_message("Experiment ready to start.")
|
||||
return TRUE
|
||||
|
||||
/datum/experiment/physical/arcade_winner/unregister_events()
|
||||
UnregisterSignal(currently_scanned_atom, COMSIG_ARCADE_PRIZEVEND)
|
||||
|
||||
/datum/experiment/physical/arcade_winner/check_progress()
|
||||
. += EXPERIMENT_PROG_BOOL("Win an arcade game at a tracked arcade cabinet.", is_complete())
|
||||
|
||||
/datum/experiment/physical/arcade_winner/proc/win_arcade(datum/source)
|
||||
finish_experiment(linked_experiment_handler)
|
||||
@@ -0,0 +1,88 @@
|
||||
/**
|
||||
* # Experiment
|
||||
*
|
||||
* This is the base datum for experiments, storing the base definition.
|
||||
*
|
||||
* This class should be subclassed for producing actual experiments. The
|
||||
* proc stubs should be implemented in whole.
|
||||
*/
|
||||
/datum/experiment
|
||||
/// Name that distinguishes the experiment
|
||||
var/name = "Experiment"
|
||||
/// A brief description of the experiment to be shown as details
|
||||
var/description = "Base experiment"
|
||||
/// A descriptive tag used on UI elements to denote 'types' of experiments
|
||||
var/exp_tag = "Base"
|
||||
/// A list of types that are allowed to experiment with this dastum
|
||||
var/list/allowed_experimentors
|
||||
/// Whether the experiment has been completed
|
||||
var/completed = FALSE
|
||||
/// Traits related to the experiment
|
||||
var/traits
|
||||
/// A textual hint shown on the UI in a tooltip to help a user determine how to perform
|
||||
/// the experiment
|
||||
var/performance_hint
|
||||
|
||||
/**
|
||||
* Performs any necessary initialization of tags and other variables
|
||||
*/
|
||||
/datum/experiment/New()
|
||||
if (traits & EXPERIMENT_TRAIT_DESTRUCTIVE)
|
||||
exp_tag = "Destructive [exp_tag]"
|
||||
|
||||
/**
|
||||
* Checks if the experiment is complete
|
||||
*
|
||||
* This proc should be overridden such that it returns TRUE/FALSE to
|
||||
* state if the experiment is complete or not.
|
||||
*/
|
||||
/datum/experiment/proc/is_complete()
|
||||
return
|
||||
|
||||
/**
|
||||
* Gets the current progress towards the goal of the experiment
|
||||
*
|
||||
* This proc should be overridden such that the return value is a
|
||||
* list of lists, wherein each inner list represents a stage. Stages have
|
||||
* types, see _DEFINES/experisci.dm. Each stage should be constructed using
|
||||
* one of several available macros in that file.
|
||||
*/
|
||||
/datum/experiment/proc/check_progress()
|
||||
. = list()
|
||||
|
||||
/**
|
||||
* Gets if the experiment is actionable provided some arguments
|
||||
*
|
||||
* This proc should be overridden such that the return value is a
|
||||
* boolean value representing if the experiment could be actioned with
|
||||
* the provided arguments.
|
||||
*/
|
||||
/datum/experiment/proc/actionable(...)
|
||||
return !is_complete()
|
||||
|
||||
/**
|
||||
* Proc that tries to perform the experiment, and then checks if its completed.
|
||||
*/
|
||||
/datum/experiment/proc/perform_experiment(datum/component/experiment_handler/experiment_handler, ...)
|
||||
var/action_succesful = perform_experiment_actions(arglist(args))
|
||||
if(is_complete())
|
||||
finish_experiment(experiment_handler)
|
||||
return action_succesful
|
||||
|
||||
/**
|
||||
* Attempts to perform the experiment provided some arguments
|
||||
*
|
||||
* This proc should be overridden such that the experiment will be actioned
|
||||
* with some defined arguments
|
||||
*/
|
||||
/datum/experiment/proc/perform_experiment_actions(datum/component/experiment_handler/experiment_handler, ...)
|
||||
return
|
||||
|
||||
/**
|
||||
* Called when you complete an experiment, makes sure the techwebs knows the experiment was finished, and tells everyone it happend, yay!
|
||||
*/
|
||||
/datum/experiment/proc/finish_experiment(datum/component/experiment_handler/experiment_handler)
|
||||
completed = TRUE
|
||||
experiment_handler.announce_message_to_all("The [name] has been completed!")
|
||||
experiment_handler.selected_experiment = null
|
||||
experiment_handler.linked_web.complete_experiment(src)
|
||||
@@ -0,0 +1,39 @@
|
||||
/datum/experiment/explosion
|
||||
name = "Explosive Experiment"
|
||||
description = "An experiment requiring an explosion to progress"
|
||||
exp_tag = "Explosion"
|
||||
performance_hint = "Perform explosive experiments using the research doppler array in the toxins lab."
|
||||
/// The required devastation range to complete the experiment
|
||||
var/required_devastation = 0
|
||||
/// The required heavy impact range to complete the experiment
|
||||
var/required_heavy = 0
|
||||
/// The required light impact range to complete the experiment
|
||||
var/required_light = 0
|
||||
/// The last measured devastation range
|
||||
var/last_devastation
|
||||
/// The last measured heavy range
|
||||
var/last_heavy
|
||||
/// The last measured light range
|
||||
var/last_light
|
||||
|
||||
/datum/experiment/explosion/is_complete()
|
||||
return required_devastation <= last_devastation \
|
||||
&& required_heavy <= last_heavy \
|
||||
&& required_light <= last_light
|
||||
|
||||
/datum/experiment/explosion/check_progress()
|
||||
var/status_message = "You must record an explosion with ranges of at least \
|
||||
[required_devastation] devastation, [required_heavy] heavy, and [required_light] light."
|
||||
if (last_devastation || last_heavy || last_light)
|
||||
status_message += " The last attempt had ranges of [last_devastation]D/[last_heavy]H/[last_light]L."
|
||||
. += EXPERIMENT_PROG_BOOL(status_message, is_complete())
|
||||
|
||||
/datum/experiment/explosion/perform_experiment_actions(datum/component/experiment_handler/experiment_handler, devastation, heavy, light)
|
||||
last_devastation = devastation
|
||||
last_heavy = heavy
|
||||
last_light = light
|
||||
return is_complete()
|
||||
|
||||
/datum/experiment/explosion/actionable(datum/component/experiment_handler/experiment_handler, devastation, heavy, light)
|
||||
return !is_complete()
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
/datum/experiment/physical
|
||||
name = "Physical Experiment"
|
||||
description = "An experiment requiring a physical reaction to continue"
|
||||
exp_tag = "Physical Experiment"
|
||||
performance_hint = "To perform physical experiments you must use a hand-held scanner unit to track objects in our world relevant to \
|
||||
your experiment. Activate the experiment on your scanner, scan the object to track, and then complete the objective."
|
||||
/// The atom that is currently being watched by this experiment
|
||||
var/atom/currently_scanned_atom
|
||||
/// Linked experiment handler
|
||||
var/datum/component/experiment_handler/linked_experiment_handler
|
||||
|
||||
/datum/experiment/physical/is_complete()
|
||||
return completed
|
||||
|
||||
/datum/experiment/physical/perform_experiment_actions(datum/component/experiment_handler/experiment_handler, atom/target)
|
||||
unregister_events()
|
||||
currently_scanned_atom = target
|
||||
linked_experiment_handler = experiment_handler
|
||||
if(register_events())
|
||||
return TRUE
|
||||
currently_scanned_atom = null
|
||||
linked_experiment_handler = null
|
||||
return FALSE
|
||||
|
||||
/**
|
||||
* Handles registering to events relevant to the experiment
|
||||
*/
|
||||
/datum/experiment/physical/proc/register_events()
|
||||
return FALSE
|
||||
|
||||
/**
|
||||
* Handles unregistering to events relevant to the experiment
|
||||
*/
|
||||
/datum/experiment/physical/proc/unregister_events()
|
||||
return
|
||||
@@ -0,0 +1,21 @@
|
||||
/datum/experiment/scanning/random
|
||||
name = "Base random scanning experiment"
|
||||
description = "This experiment's contents will be randomized. Good luck!"
|
||||
///list of types which that can be included in the experiment. Randomly picked from on New
|
||||
var/list/possible_types = list()
|
||||
/// The total desired number of atoms to have scanned
|
||||
var/total_requirement = 0
|
||||
/// Max amount of a requirement per type
|
||||
var/max_requirement_per_type = 100
|
||||
|
||||
/datum/experiment/scanning/random/New()
|
||||
// Generate random contents
|
||||
if (possible_types.len)
|
||||
var/picked = 0
|
||||
while (picked < total_requirement)
|
||||
var/randum = min(rand(1, total_requirement - picked), max_requirement_per_type) //Short for "random num" and not "this is dum that I have to rename 100 different variable names"
|
||||
required_atoms[pick(possible_types)] += randum
|
||||
picked += randum
|
||||
|
||||
// Fill the experiment as per usual
|
||||
..()
|
||||
@@ -0,0 +1,140 @@
|
||||
/**
|
||||
* # Scanning Experiment
|
||||
*
|
||||
* This is the base implementation of scanning experiments.
|
||||
*
|
||||
* This class should be subclassed for producing actual experiments. The
|
||||
* procs should be extended where necessary.
|
||||
*/
|
||||
/datum/experiment/scanning
|
||||
name = "Scanning Experiment"
|
||||
description = "Base experiment for scanning atoms"
|
||||
exp_tag = "Scan"
|
||||
allowed_experimentors = list(/obj/item/experi_scanner, /obj/machinery/destructive_scanner)
|
||||
performance_hint = "Perform scanning experiments using a handheld experi-scanner, or the stationary experimental destructive scanner. \
|
||||
Destructive scans can only be performed with the experimental destructive scanner."
|
||||
/// The typepaths and number of atoms that must be scanned
|
||||
var/list/required_atoms = list()
|
||||
/// The list of atoms with sub-lists of atom references for scanned atoms contributing to the experiment
|
||||
var/list/scanned = list()
|
||||
|
||||
/**
|
||||
* Initializes the scanned atoms lists
|
||||
*
|
||||
* Initializes the internal scanned atoms list to keep track of which atoms have already been scanned
|
||||
*/
|
||||
/datum/experiment/scanning/New()
|
||||
. = ..()
|
||||
for (var/req_atom in required_atoms)
|
||||
scanned[req_atom] = traits & EXPERIMENT_TRAIT_DESTRUCTIVE ? 0 : list()
|
||||
|
||||
/**
|
||||
* Checks if the scanning experiment is complete
|
||||
*
|
||||
* Returns TRUE/FALSE as to if the necessary number of atoms have been
|
||||
* scanned.
|
||||
*/
|
||||
/datum/experiment/scanning/is_complete()
|
||||
. = TRUE
|
||||
var/destructive = traits & EXPERIMENT_TRAIT_DESTRUCTIVE
|
||||
for (var/req_atom in required_atoms)
|
||||
var/list/seen = scanned[req_atom]
|
||||
if (destructive && (!(req_atom in scanned) || scanned[req_atom] != required_atoms[req_atom]))
|
||||
return FALSE
|
||||
if (!destructive && (!seen || seen.len != required_atoms[req_atom]))
|
||||
return FALSE
|
||||
|
||||
/**
|
||||
* Gets the number of atoms that have been scanned and the goal
|
||||
*
|
||||
* This proc returns a string describing the number of atoms that
|
||||
* have been scanned as well as the target number of atoms.
|
||||
*/
|
||||
/datum/experiment/scanning/check_progress()
|
||||
. = list()
|
||||
for (var/atom_type in required_atoms)
|
||||
var/atom/required_atom = atom_type
|
||||
var/list/seen_instances = scanned[required_atom]
|
||||
. += serialize_progress_stage(required_atom, seen_instances)
|
||||
|
||||
/**
|
||||
* Serializes a progress stage into a list to be sent to the UI
|
||||
*
|
||||
* Arguments:
|
||||
* * target - The targeted atom for this progress stage
|
||||
* * seen_instances - The number of instances seen of this atom
|
||||
*/
|
||||
/datum/experiment/scanning/proc/serialize_progress_stage(atom/target, list/seen_instances)
|
||||
var/scanned_total = traits & EXPERIMENT_TRAIT_DESTRUCTIVE ? scanned[target] : seen_instances.len
|
||||
return EXPERIMENT_PROG_INT("Scan samples of \a [initial(target.name)]", scanned_total, required_atoms[target])
|
||||
|
||||
/**
|
||||
* Attempts to scan an atom towards the experiment's goal
|
||||
*
|
||||
* This proc attempts to scan an atom towards the experiment's goal,
|
||||
* and returns TRUE/FALSE based on success.
|
||||
* Arguments:
|
||||
* * target - The atom to attempt to scan
|
||||
*/
|
||||
/datum/experiment/scanning/perform_experiment_actions(datum/component/experiment_handler/experiment_handler, atom/target)
|
||||
var/contributing_index_value = get_contributing_index(target)
|
||||
if (contributing_index_value)
|
||||
scanned[contributing_index_value] += traits & EXPERIMENT_TRAIT_DESTRUCTIVE ? 1 : target
|
||||
if(traits & EXPERIMENT_TRAIT_DESTRUCTIVE && !isliving(target))//only qdel things when destructive scanning and they're not living (living things get gibbed)
|
||||
qdel(target)
|
||||
do_after_experiment(target, contributing_index_value)
|
||||
return TRUE
|
||||
|
||||
/datum/experiment/scanning/actionable(datum/component/experiment_handler/experiment_handler, atom/target)
|
||||
return ..() && get_contributing_index(target)
|
||||
|
||||
/**
|
||||
* Attempts to get the typepath for an atom that would contribute to the experiment
|
||||
*
|
||||
* This proc checks the required atoms for a typepath that this target atom can contribute to
|
||||
* and if found returns that typepath, otherwise returns null
|
||||
* Arguments:
|
||||
* * target - The atom to attempt to scan
|
||||
*/
|
||||
/datum/experiment/scanning/proc/get_contributing_index(atom/target)
|
||||
var/destructive = traits & EXPERIMENT_TRAIT_DESTRUCTIVE
|
||||
for (var/req_atom in required_atoms)
|
||||
if (!istype(target, req_atom))
|
||||
continue
|
||||
|
||||
// Try to select a required atom that this scanned atom would contribute towards
|
||||
var/selected
|
||||
var/list/seen = scanned[req_atom]
|
||||
if (destructive && (req_atom in scanned) && scanned[req_atom] < required_atoms[req_atom])
|
||||
selected = req_atom
|
||||
else if (!destructive && seen.len < required_atoms[req_atom] && !(target in seen))
|
||||
selected = req_atom
|
||||
|
||||
// Run any additonal checks if necessary
|
||||
if (selected && final_contributing_index_checks(target, selected))
|
||||
return selected
|
||||
|
||||
/**
|
||||
* Performs any additional checks against the atom being considered for selection as a contributing index
|
||||
*
|
||||
* This proc is intended to be used to add additional functionality to contributing index checks
|
||||
* without having to duplicate the iteration structure of get_contributing_index()
|
||||
* Arguments:
|
||||
* * target - The atom being scanned
|
||||
* * typepath - The typepath (selected index) of the target atom
|
||||
*/
|
||||
/datum/experiment/scanning/proc/final_contributing_index_checks(atom/target, typepath)
|
||||
return TRUE
|
||||
|
||||
/**
|
||||
* Performs actions following a successful experiment action
|
||||
*
|
||||
* This proc is intended to be used to add additional functionality to follow experiment
|
||||
* actions without having to change the perform_experiment_actions proc to get access to the
|
||||
* selected typepath index
|
||||
* Arguments:
|
||||
* * target - The atom being scanned
|
||||
* * typepath - The typepath (selected index) of the target atom
|
||||
*/
|
||||
/datum/experiment/scanning/proc/do_after_experiment(atom/target, typepath)
|
||||
return TRUE
|
||||
@@ -0,0 +1,24 @@
|
||||
/datum/experiment/scanning/random/material
|
||||
name = "Material Scanning Experiment"
|
||||
description = "Base experiment for scanning atoms with materials"
|
||||
exp_tag = "Material Scan"
|
||||
total_requirement = 8
|
||||
possible_types = list(/obj/structure/chair, /obj/structure/toilet, /obj/structure/table, /turf/closed/wall, /turf/open/floor)
|
||||
///List of materials that can be required.
|
||||
var/possible_material_types = list()
|
||||
///List of materials actually required, indexed by the atom that is required.
|
||||
var/required_materials = list()
|
||||
|
||||
/datum/experiment/scanning/random/material/New()
|
||||
. = ..()
|
||||
for(var/req_atom in required_atoms)
|
||||
var/chosen_material = pick(possible_material_types)
|
||||
required_materials[req_atom] = chosen_material
|
||||
|
||||
/datum/experiment/scanning/random/material/final_contributing_index_checks(atom/target, typepath)
|
||||
return ..() && target.custom_materials && target.has_material_type(required_materials[typepath])
|
||||
|
||||
/datum/experiment/scanning/random/material/serialize_progress_stage(atom/target, list/seen_instances)
|
||||
var/datum/material/required_material = GET_MATERIAL_REF(required_materials[target])
|
||||
return EXPERIMENT_PROG_INT("Scan samples of \a [required_material.name] [initial(target.name)]", \
|
||||
traits & EXPERIMENT_TRAIT_DESTRUCTIVE ? scanned[target] : seen_instances.len, required_atoms[target])
|
||||
@@ -0,0 +1,46 @@
|
||||
/datum/experiment/scanning/points
|
||||
name = "Point Scanning Experiment"
|
||||
description = "Base experiment for scanning experiments tracked by points"
|
||||
/// The current points gained on this experiment
|
||||
var/points = 0
|
||||
/// The total required points for this experiment
|
||||
var/required_points
|
||||
|
||||
/datum/experiment/scanning/points/is_complete()
|
||||
return points >= required_points
|
||||
|
||||
/datum/experiment/scanning/points/check_progress()
|
||||
. = EXPERIMENT_PROG_INT("Scan samples of the following objects to accumulate enough points to complete this experiment.", points, required_points)
|
||||
var/complete = is_complete()
|
||||
var/point_val_cache = list()
|
||||
for (var/a_type in required_atoms)
|
||||
var/atom/req_atom = a_type
|
||||
if (!point_val_cache["[required_atoms[a_type]]"])
|
||||
point_val_cache["[required_atoms[a_type]]"] = list()
|
||||
point_val_cache["[required_atoms[a_type]]"] += initial(req_atom.name)
|
||||
|
||||
for (var/point_amt in point_val_cache)
|
||||
var/list/types = point_val_cache[point_amt]
|
||||
var/types_joined = types.Join(", ")
|
||||
. += EXPERIMENT_PROG_DETAIL("[text2num(point_amt)] point\s: [types_joined]", complete)
|
||||
|
||||
/datum/experiment/scanning/points/get_contributing_index(atom/target)
|
||||
var/destructive = traits & EXPERIMENT_TRAIT_DESTRUCTIVE
|
||||
for (var/req_atom in required_atoms)
|
||||
if (!istype(target, req_atom))
|
||||
continue
|
||||
|
||||
// Try to select a required atom that this scanned atom would contribute towards
|
||||
var/selected
|
||||
if (destructive && (req_atom in scanned))
|
||||
selected = req_atom
|
||||
else if (!destructive && !(target in scanned[req_atom]))
|
||||
selected = req_atom
|
||||
|
||||
// Run any additonal checks if necessary
|
||||
if (selected && final_contributing_index_checks(target, selected))
|
||||
return selected
|
||||
|
||||
/datum/experiment/scanning/points/do_after_experiment(atom/target, typepath)
|
||||
. = ..()
|
||||
points = min(required_points, points + required_atoms[typepath])
|
||||
@@ -0,0 +1,14 @@
|
||||
/datum/experiment/scanning/random/cytology
|
||||
name = "Cytology Scanning Experiment"
|
||||
description = "Base experiment for scanning atoms that were vatgrown"
|
||||
exp_tag = "Cytology Scan"
|
||||
total_requirement = 1
|
||||
possible_types = list(/mob/living/simple_animal/hostile/cockroach)
|
||||
traits = EXPERIMENT_TRAIT_DESTRUCTIVE
|
||||
|
||||
/datum/experiment/scanning/random/cytology/final_contributing_index_checks(atom/target, typepath)
|
||||
return ..() && HAS_TRAIT(target, TRAIT_VATGROWN)
|
||||
|
||||
/datum/experiment/scanning/random/cytology/serialize_progress_stage(atom/target, list/seen_instances)
|
||||
return EXPERIMENT_PROG_INT("Scan samples of \a vat-grown [initial(target.name)]", \
|
||||
traits & EXPERIMENT_TRAIT_DESTRUCTIVE ? scanned[target] : seen_instances.len, required_atoms[target])
|
||||
@@ -0,0 +1,24 @@
|
||||
/**
|
||||
* # Experi-Scanner
|
||||
*
|
||||
* Handheld scanning unit to perform scanning experiments
|
||||
*/
|
||||
/obj/item/experi_scanner
|
||||
name = "Experi-Scanner"
|
||||
desc = "A handheld scanner used for completing the many experiments of modern science."
|
||||
w_class = WEIGHT_CLASS_SMALL
|
||||
icon = 'icons/obj/device.dmi'
|
||||
icon_state = "experiscanner"
|
||||
lefthand_file = 'icons/mob/inhands/misc/devices_lefthand.dmi'
|
||||
righthand_file = 'icons/mob/inhands/misc/devices_righthand.dmi'
|
||||
|
||||
/obj/item/experi_scanner/Initialize()
|
||||
..()
|
||||
return INITIALIZE_HINT_LATELOAD
|
||||
|
||||
// Late initialize to allow for the rnd servers to initialize first
|
||||
/obj/item/experi_scanner/LateInitialize()
|
||||
. = ..()
|
||||
AddComponent(/datum/component/experiment_handler, \
|
||||
allowed_experiments = list(/datum/experiment/scanning, /datum/experiment/physical),\
|
||||
disallowed_traits = EXPERIMENT_TRAIT_DESTRUCTIVE)
|
||||
Reference in New Issue
Block a user