mirror of
https://github.com/Bubberstation/Bubberstation.git
synced 2025-12-14 19:51:59 +00:00
## About The Pull Request People can now pet held mothroaches and pugs if they want to, or use items on them, hopefully without causing many issues. After all, it only took about a couple dozen lines of code to make... ...Oh, did the 527 files changed or the 850~ lines added/removed perhaps catch your eye? Made you wonder if I accidentally pushed the wrong branch? or skewed something up big time? Well, nuh uh. I just happen to be fed up with the melee attack chain still using stringized params instead of an array/list. It was frankly revolting to see how I'd have had to otherwise call `list2params` for what I'm trying to accomplish here, and make this PR another tessera to the immense stupidity of our attack chain procs calling `params2list` over and over and over instead of just using that one call instance from `ClickOn` as an argument. It's 2025, honey, wake up! I also tried to replace some of those single letter vars/args but there are just way too many of them. ## Why It's Good For The Game Improving old code. And I want to be able to pet mobroaches while holding them too. ## Changelog 🆑 qol: You can now interact with held mobs in more ways beside wearing them. /🆑
385 lines
15 KiB
Plaintext
385 lines
15 KiB
Plaintext
/**
|
|
* # 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,
|
|
list/experiment_signals
|
|
)
|
|
. = ..()
|
|
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
|
|
|
|
for(var/signal in experiment_signals)
|
|
RegisterSignal(parent, signal, experiment_signals[signal])
|
|
|
|
// Determine UI display mode
|
|
switch(config_mode)
|
|
if(EXPERIMENT_CONFIG_ATTACKSELF)
|
|
RegisterSignal(parent, COMSIG_ITEM_ATTACK_SELF, PROC_REF(configure_experiment))
|
|
if(EXPERIMENT_CONFIG_ALTCLICK)
|
|
RegisterSignal(parent, COMSIG_CLICK_ALT, PROC_REF(configure_experiment))
|
|
if(EXPERIMENT_CONFIG_CLICK)
|
|
RegisterSignal(parent, COMSIG_ATOM_UI_INTERACT, PROC_REF(configure_experiment_click))
|
|
if(EXPERIMENT_CONFIG_UI)
|
|
RegisterSignal(parent, COMSIG_UI_ACT, PROC_REF(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))
|
|
CONNECT_TO_RND_SERVER_ROUNDSTART(linked_web, parent)
|
|
|
|
GLOB.experiment_handlers += src
|
|
|
|
/datum/component/experiment_handler/Destroy(force)
|
|
. = ..()
|
|
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, list/modifiers)
|
|
SIGNAL_HANDLER
|
|
if (!should_run_handheld_experiment(source, target, user))
|
|
return
|
|
INVOKE_ASYNC(src, PROC_REF(try_run_handheld_experiment_async), source, target, user)
|
|
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, list/modifiers)
|
|
SIGNAL_HANDLER
|
|
if ((isnull(selected_experiment) && !(config_flags & EXPERIMENT_CONFIG_ALWAYS_ACTIVE)) || (config_flags & EXPERIMENT_CONFIG_SILENT_FAIL))
|
|
return
|
|
playsound(user, 'sound/machines/buzz/buzz-sigh.ogg', 25)
|
|
to_chat(user, span_notice("[target] is not related to your currently selected experiment."))
|
|
|
|
/**
|
|
* 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)
|
|
// Check that there is actually an experiment selected
|
|
if (selected_experiment == null && !(config_flags & EXPERIMENT_CONFIG_ALWAYS_ACTIVE))
|
|
return
|
|
if (!linked_web)
|
|
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)
|
|
if (selected_experiment == null && !(config_flags & EXPERIMENT_CONFIG_ALWAYS_ACTIVE))
|
|
if(!(config_flags & EXPERIMENT_CONFIG_SILENT_FAIL))
|
|
to_chat(user, span_notice("You do not have an experiment selected!"))
|
|
return
|
|
if(!(config_flags & EXPERIMENT_CONFIG_IMMEDIATE_ACTION) && !do_after(user, 1 SECONDS, target = target))
|
|
return
|
|
if(action_experiment(source, target))
|
|
playsound(user, 'sound/machines/ping.ogg', 25)
|
|
to_chat(user, span_notice("You scan [target]."))
|
|
else if(!(config_flags & EXPERIMENT_CONFIG_SILENT_FAIL))
|
|
playsound(user, 'sound/machines/buzz/buzz-sigh.ogg', 25)
|
|
to_chat(user, span_notice("[target] is not related to your currently selected experiment."))
|
|
|
|
/**
|
|
* 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)
|
|
if(!(config_flags & EXPERIMENT_CONFIG_SILENT_FAIL))
|
|
playsound(our_scanner, 'sound/machines/buzz/buzz-sigh.ogg', 25)
|
|
to_chat(our_scanner, span_notice("No experiment selected!"))
|
|
return
|
|
var/successful_scan
|
|
for(var/scan_target in scanned_atoms)
|
|
if(action_experiment(source, scan_target))
|
|
successful_scan = TRUE
|
|
if(successful_scan)
|
|
playsound(our_scanner, 'sound/machines/ping.ogg', 25)
|
|
to_chat(our_scanner, span_notice("The scan succeeds."))
|
|
else if(!(config_flags & EXPERIMENT_CONFIG_SILENT_FAIL))
|
|
playsound(src, 'sound/machines/buzz/buzz-sigh.ogg', 25)
|
|
our_scanner.say("The scan did not result in anything.")
|
|
|
|
/// Hooks on a successful autopsy experiment
|
|
/datum/component/experiment_handler/proc/try_run_autopsy_experiment(obj/source, mob/living/target)
|
|
SIGNAL_HANDLER
|
|
|
|
if (action_experiment(source, target))
|
|
playsound(source, 'sound/machines/ping.ogg', 25)
|
|
source.say("New unique autopsy successfully catalogued.")
|
|
|
|
|
|
/**
|
|
* 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/datum/component/experiment_handler/experi_handler as anything in GLOB.experiment_handlers)
|
|
if(experi_handler.linked_web != linked_web)
|
|
continue
|
|
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 successfuly 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_REF(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)
|
|
SIGNAL_HANDLER
|
|
INVOKE_ASYNC(src, PROC_REF(ui_interact), user)
|
|
return CLICK_ACTION_SUCCESS
|
|
|
|
/**
|
|
* 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)
|
|
SIGNAL_HANDLER
|
|
INVOKE_ASYNC(src, TYPE_PROC_REF(/datum, 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?.on_unselected(src)
|
|
selected_experiment = null
|
|
linked_web = new_web
|
|
|
|
/**
|
|
* Unlinks this handler from the selected techweb
|
|
*/
|
|
/datum/component/experiment_handler/proc/unlink_techweb()
|
|
selected_experiment?.on_unselected(src)
|
|
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 (can_select_experiment(experiment))
|
|
unlink_experiment()
|
|
selected_experiment = experiment
|
|
selected_experiment.on_selected(src)
|
|
|
|
/**
|
|
* Unlinks this handler from the selected experiment
|
|
*/
|
|
/datum/component/experiment_handler/proc/unlink_experiment()
|
|
selected_experiment?.on_unselected(src)
|
|
selected_experiment = null
|
|
|
|
/**
|
|
* 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 (length(experiment.allowed_experimentors) && !is_type_in_list(parent, experiment.allowed_experimentors))
|
|
return FALSE
|
|
|
|
// Check that this experiment is visible currently
|
|
if (!(experiment in linked_web?.available_experiments))
|
|
return FALSE
|
|
|
|
// Check that this experiment type isn't blacklisted
|
|
if(is_type_in_list(experiment, blacklisted_experiments))
|
|
return FALSE
|
|
|
|
// Finally, check against the allowed experiment types
|
|
return is_type_in_list(experiment, allowed_experiments)
|
|
|
|
/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),
|
|
)
|
|
.["techwebs"] = list()
|
|
for (var/datum/techweb/techwebs as anything in SSresearch.techwebs)
|
|
if(!length(techwebs.techweb_servers)) //no servers, we don't care
|
|
if(techwebs == linked_web) //disconnect if OUR techweb lost their servers.
|
|
unlink_techweb()
|
|
continue
|
|
if(!length(SSresearch.find_valid_servers(get_turf(parent), techwebs)))
|
|
continue
|
|
var/list/data = list(
|
|
web_id = techwebs.id,
|
|
web_org = techwebs.organization,
|
|
selected = (techwebs == linked_web),
|
|
ref = REF(techwebs),
|
|
all_servers = techwebs.techweb_servers,
|
|
)
|
|
.["techwebs"] += list(data)
|
|
.["experiments"] = list()
|
|
if (linked_web)
|
|
for (var/datum/experiment/experiment as anything in linked_web.available_experiments)
|
|
if(!can_select_experiment(experiment))
|
|
continue
|
|
var/list/data = list(
|
|
name = experiment.name,
|
|
description = experiment.description,
|
|
tag = experiment.exp_tag,
|
|
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, list/params, datum/tgui/ui, datum/ui_state/state)
|
|
. = ..()
|
|
if (.)
|
|
return
|
|
switch (action)
|
|
if ("select_server")
|
|
. = TRUE
|
|
var/datum/techweb/new_techweb = locate(params["ref"])
|
|
if (new_techweb)
|
|
link_techweb(new_techweb)
|
|
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)
|