tags: Appended, Science
title: "Experi-Sci (Bobbah, Floyd, and Arcane)"
**Coders, wait a second!** Considering designing your own experiments or handlers? Make sure to check out our [Ethical and Moral Considerations to Avoid Angering Players](#Ethical-and-Moral-Considerations-to-Avoid-Angering-Players) section!
Hello and welcome to an exciting new DLC for techwebs, the Experi-Sci™ system.
## What is it
In short, the Experi-Sci system is an addition to our existing techwebs. It aims to add experiments that the crew may perform, and these experiments are used as a discount or requirement for certain techweb nodes. The experiments are designed to be elements to encourage players to interact with all facets of the station and game, which we hope will help to reduce burnout and increase cooperation within the crew.
In a technical description, this system implements a framework of components and base classes (the experiments) that are the foundation of the Experi-Sci system. Techweb nodes may now have experiments, ``/datum/experiment``, added as a requirement for their research or a discount towards the points required for it. Once an experiment is marked as complete by the research subsystem, the node can then be researched (if required), or the point cost will be discounted. These experiments are handled using components, ``/datum/component/experiment_handler``, which provides both the functionality of performing experiments as well as a generic UI which greatly simplifies their implementation on objects. That's right, to add experiment-handling devices and brand new experiment types, you likely won't need to make a UI!
## The Framework
As noted above, the core of the Experi-Sci system is a framework. This framework is described below in a brief technical manner, though I would recommend consulting the extensive documentation on the Experi-Sci module for up-to-date information.
### Experiment Datums
The base experiment datum is akin to an interface, in that it is designed to have several methods that must be overridden to produce a functional experiment. These methods are intended to be very abstract concepts in order to allow for a great deal of creativity in performing them. They must implement the following...
A boolean method which describes if the experiment is complete or not.
A method which returns a collection of experiment stages, the stages are simple lists of a stage type (boolean, integer, float), a description, and value(s) which are used to display the experiment's progress on UI elements. These are produced using the experiment progress macros (see ``code\_DEFINES\experisci.dm``).
A boolean method which returns true or false depending on if the experiment can be actioned (performed) with the provided arguments. This is intended to be used to ensure an experiment can be performed prior to initiating the task.
#### ``perform_experiment_actions(datum/component/experiment_handler/experiment_handler, ...)``
A boolean method which attempts to perform the experiment with the provided arguments, and returns true or false based on if the experiment was performed successfully.
### Experiment Handlers
Experiment handlers are the component which interacts with the techweb, and handles the UI for the Experi-Sci system. This generic component can be attached to any object, and allows for a rapid addition of experiment-performing capabilities to virtually any object. Note that these handlers use signals to communicate and perform their functions.
There are a few important things to note about experiment handlers, namely the constructor for the class. The arguments for this constructor are explained below:
- ``allowed_experiments``: a list of /datum/experiment types which can be performed by this handler.
- ``blacklisted_experiments``: A list of /datum/experiment types that cannot be performed by this handler. This blacklisting system allows for disallowing certain subtypes of experiments (like destructive scanning experiments) that otherwise would fall under the set of allowed experiments.
- ``config_mode``: A defined constant, used for determining the behaviour of how the UI is opened.
- ``config_flags``: Flags that control the operation behaviour of the experiment handler, see experisci defines.
- ``start_experiment_callback``: When provided adds a UI button to use this callback to start the experiment.
As well as this, it is also important to note that the constructor of this component is where the signals for how experiments are performed are registered.
## Example Implementations
To help exemplify the simplicity of this system, I would like to show two different implementations below.
### The Experi-Scanner
The hand-held experiment scanner is a great example of how simple an experiment handler can be to implement. Say that we want a hand-held tool that upon attacking an object performs our experiment, and this experiment is scanning. Not only this, but we don't want this hand-held scanner to be capable of performing destructive experiments, as it's too simple of an item to do such! To accomplish this, the code is very simple as we are essentially creating an object to attach our experiment handler component to.
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'
// Late initialize to allow for the rnd servers to initialize first
. = ..()
allowed_experiments = list(/datum/experiment/scanning, /datum/experiment/physical),\
disallowed_traits = EXPERIMENT_TRAIT_DESTRUCTIVE)
As well as this, we must add some code in ``/datum/component/experiment_handler`` to specify how to handle hand-held scanning experiments, so we add the following into the constructor (``Initialize(...)``)
RegisterSignal(parent, COMSIG_ITEM_PRE_ATTACK, .proc/try_run_handheld_experiment)
RegisterSignal(parent, COMSIG_ITEM_AFTERATTACK, .proc/ignored_handheld_experiment_attempt)
and later on, defining our two procs to handle these experiments within the same file, ``try_run_handheld_experiment`` and ``ignored_handheld_experiment_attempt``
* 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)
if (!should_run_handheld_experiment(source, target, user, params))
INVOKE_ASYNC(src, .proc/try_run_handheld_experiment_async, source, target, user, params)
* 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)
if (!proximity_flag || (selected_experiment == null && !(config_flags & EXPERIMENT_CONFIG_ALWAYS_ACTIVE)))
playsound(user, 'sound/machines/buzz-sigh.ogg', 25)
to_chat(user, "<span class='notice'>[target] is not related to your currently selected experiment.</span>")
That's it, we now have a working hand-held scanner!
### Vat-Growing Scanning Experiments
Let's say we want to sub-class our scanning experiment type to add new experiments for vat-growing. This requires slight modification to the scanning experiment's ``get_contributing_index(atom/target)`` proc as we need to ensure that all of our scanned targets have the ``TRAIT_VATGROWN`` trait. This is necessary because vat-grown creatures are often regular mobs, but we don't want players to merely be able to scan the regular mobs. They MUST be vat-grown! To do this is a simple modification...
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
return ..() && HAS_TRAIT(target, TRAIT_VATGROWN)
// note that we have to overload the serialize_progress_stage proc, as the progress differs depending on if an experiment
// is destructive or not.
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])
Now we have a working sub-type of scanning experiments which allows us to only scan vat-grown creatures. Perfect!
### Explosive Experiments
A slightly more complex example is that of explosive experiments. Say that we want to add a type of experiment for recording explosions of certain sizes, and we want to add the ability to perform these experiments to the doppler scanner in the toxins testing lab. It may sound daunting, but complex ideas like these are very easy to implement with a few small code changes which are mostly just implementing the experiment datum "interface".
First, the experiment itself...
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
/// The last measured heavy range
/// The last measured light range
return required_devastation <= last_devastation \
&& required_heavy <= last_heavy \
&& required_light <= last_light
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
There is nothing terribly interesting to note here, it's rather simple!
Following this, we have to implement a slight change to the handler component. This will allow us to register to recieve the signal for performing our experiment on the doppler array. As well as this, we need to define a new signal for the handler component to recieve when an explosion is detected on the doppler array, as there is not yet one. We merely define a new signal in ``_DEFINES/dcs/signals.dm``, insert two new lines into the constructor of the handler component, and quickly define what performing a doppler array experiment looks like.
/* Inserted into the signals file to define our new signal */
///from /obj/machinery/doppler_array/proc/sense_explosion(...): Runs when an explosion is succesfully detected by a doppler array(turf/epicenter, devastation_range, heavy_impact_range, light_impact_range, took, orig_dev_range, orig_heavy_range, orig_light_range)
#define COMSIG_DOPPLER_ARRAY_EXPLOSION_DETECTED "atom_dopplerarray_explosion_detected"
/* Inserted into Initialize for the experiment handler datum */
RegisterSignal(parent, COMSIG_DOPPLER_ARRAY_EXPLOSION_DETECTED, .proc/try_run_doppler_experiment)
/* Inserted into the experiment handler datum */
* 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
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)
playsound(src, 'sound/machines/buzz-sigh.ogg', 25)
our_array.say("Insufficient explosion to contribute to current experiment.")
Finally the last thing we have to do is implement the registration of the component on the doppler array, and implement the new signal we had to add so that the handler can know when an experiment is performed.
/* Added to the existing proc, sense_explosion(), on the doppler array */
SEND_SIGNAL(src, COMSIG_DOPPLER_ARRAY_EXPLOSION_DETECTED, epicenter, devastation_range, heavy_impact_range, light_impact_range, took, orig_dev_range, orig_heavy_range, orig_light_range)
/* Added to the research variant of the doppler array */
linked_techweb = SSresearch.science_tech
// Late initialize to allow the server machinery to initialize first
. = ..()
allowed_experiments = list(/datum/experiment/explosion), \
config_mode = EXPERIMENT_CONFIG_UI, \
config_flags = EXPERIMENT_CONFIG_ALWAYS_ACTIVE)
That's it. We now have a machine with a working UI for performing experiments, and the experiments for that machine as well.
I've included a couple of examples of an implemented explosion experiment below, to give you an idea of what those may look like.
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
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."
required_devastation = GLOB.MAX_EX_DEVESTATION_RANGE
required_heavy = GLOB.MAX_EX_HEAVY_RANGE
required_light = GLOB.MAX_EX_LIGHT_RANGE
## Ethical and Moral Considerations to Avoid Angering Players
During the time which Experi-Sci was being developed and tested alongside our playerbase at /tg/ several important guidelines became evident to ensure that the experiments and other features you produce in this system coincide well with gameplay. We have tried to summarize these below, though note that this list is not final, and you should always use caution with the player mob; they can be very angry fellows! Additionally, if you have any further suggestions from your own experience, please feel free to suggest them, or contact me on Discord: ``bobbahbrown#0001``
### 1. Why Experiments Exist
First and foremost, I feel it is very important to re-state why experiments exist. We want to create a **fun** and **engaging** system that hopefully forces some more player interaction with content they otherwise may not have seen, as well as encouraging co-operation between crewmen.
To this extent, experiments should **never** be created for tedium; they should always aim to express some content or job feature. For example, experiments that have players scan slime cores aim to encourage peforming xenobiology. As well as this, experiments that have players create bombs hope to bring more creative minds to toxins, and provide more legitimacy to this often-disregarded division.
Perhaps then, for inspiration, you can leverage experiments to bring back your favourite dead departments, or encourage new (_fun_) gameplay styles!
### 2. We Won't Always Have High-Pop
So, you might be thinking at this point, "*let's start time-gating all the fun content with required experiments to really force our players to work together!*" Well, hold your horses fine coder, because we won't always have a bazillion crewmen ready to readily perform all of our busywork. What happens when the late-night deadpop skeleton crew is running the entire station? Is science abandoned? Is all hope lost?
It is for this reason that we must **embrace the discount experiments more than the required experiments**. Discount experiments seek to encourage performing the related experiments, whilst allowing a low-pop or otherwise unproductive science division to still have some degree of progress within the techweb. Leverage this tool which is available to you, and you will see far less resistance to your changes!
### 3. We Shouldn't Complete Everything
To help produce a system which provides a *depth* of content for players to engage and explore, **we shouldn't design for every single technology to be researched inside a given (*average*) round**. Base technologies which are critical to jobs on-board should always be accessible, but we should not reward a crew who does not perform their duties, or provide those same benefits of a high-pop crew to those at low-pop. To this extent, consider 'balancing' the placement of your experiments and point costs to allow the techweb to require multiple rounds for any given player to experience all technologies. This helps to ensure that high-level technologies can actually feel like a reward rather than just another small step in a never-changing game loop.
*This note on design falls more into the overall design focus for science within /tg/, but we feel it is important to consider in all codebases. This is a way you can encourage players to engage with more content in a meaningful manner.*
### 4. We Can't Require Everything
Very closely related to the point above, what happens when key technologies like mining tech or industrial engineering find themselves locked behind some (hopefully minor) tedium? Riots, that's what!
**Always be careful when making an experiment required instead of discounted**, and ask yourself if the node it is blocking could cause major gameplay disruptions. Required experiments can be the achilles heel to an otherwise fun mechanic, or even cripple a crew outright if placed poorly. Discounts are generally the way to go, dear coder.
### 5. ...But Some Things Are OK To Require
As the title implies, you should not disregard required experiments altogether. It would be wise to use these as a tool to prevent the crew from progressing too fast over a 'section' of content which the experiment (or a group of experiments) targets. If used sparingly, we feel that required experiments can help to represent content which must always be performed, helping to effect player mentality around an area of content, or even tasks which can be seen as achievements for the crew.