diff --git a/citadel.dme b/citadel.dme index 010038d2f8ea..d4c30f09945e 100644 --- a/citadel.dme +++ b/citadel.dme @@ -43,6 +43,7 @@ #include "code\__DEFINES\damage_organs.dm" #include "code\__DEFINES\directional.dm" #include "code\__DEFINES\dna.dm" +#include "code\__DEFINES\event_args.dm" #include "code\__DEFINES\fonts.dm" #include "code\__DEFINES\gamemode.dm" #include "code\__DEFINES\holidays.dm" @@ -161,12 +162,12 @@ #include "code\__DEFINES\dcs\signals\signals_global.dm" #include "code\__DEFINES\dcs\signals\signals_object.dm" #include "code\__DEFINES\dcs\signals\signals_storage.dm" -#include "code\__DEFINES\dcs\signals\signals_tool_system.dm" #include "code\__DEFINES\dcs\signals\signals_turf.dm" #include "code\__DEFINES\dcs\signals\datums\signals_beam.dm" #include "code\__DEFINES\dcs\signals\datums\signals_perspective.dm" #include "code\__DEFINES\dcs\signals\elements\conflict.dm" #include "code\__DEFINES\dcs\signals\items\signals_inducer.dm" +#include "code\__DEFINES\dcs\signals\signals_atom\context_system.dm" #include "code\__DEFINES\dcs\signals\signals_atom\signals_atom_appearance.dm" #include "code\__DEFINES\dcs\signals\signals_atom\signals_atom_attack.dm" #include "code\__DEFINES\dcs\signals\signals_atom\signals_atom_buckling.dm" @@ -180,6 +181,7 @@ #include "code\__DEFINES\dcs\signals\signals_atom\signals_atom_throwing.dm" #include "code\__DEFINES\dcs\signals\signals_atom\signals_atom_visuals.dm" #include "code\__DEFINES\dcs\signals\signals_atom\signals_atom_x_act.dm" +#include "code\__DEFINES\dcs\signals\signals_atom\tool_system.dm" #include "code\__DEFINES\dcs\signals\signals_item\signals_item_economy.dm" #include "code\__DEFINES\dcs\signals\signals_item\signals_item_inventory.dm" #include "code\__DEFINES\dcs\signals\signals_mob\signals_mob_appearance.dm" @@ -744,6 +746,9 @@ #include "code\datums\elements\clothing\dynamic_recolor.dm" #include "code\datums\elements\items\darksight_granter.dm" #include "code\datums\elements\items\hud_granter.dm" +#include "code\datums\event_args\_event_args.dm" +#include "code\datums\event_args\actor.dm" +#include "code\datums\event_args\clickchain.dm" #include "code\datums\helper_datums\construction_datum.dm" #include "code\datums\helper_datums\events.dm" #include "code\datums\helper_datums\getrev.dm" @@ -937,6 +942,7 @@ #include "code\game\click\adjacency_legacy.dm" #include "code\game\click\ai.dm" #include "code\game\click\click.dm" +#include "code\game\click\context.dm" #include "code\game\click\cyborg.dm" #include "code\game\click\drag_drop.dm" #include "code\game\click\item_attack.dm" @@ -1869,6 +1875,8 @@ #include "code\game\objects\structures\stool_bed_chair_nest\chairs_vr.dm" #include "code\game\objects\structures\stool_bed_chair_nest\stools.dm" #include "code\game\objects\structures\stool_bed_chair_nest\wheelchair.dm" +#include "code\game\objects\systems\_system.dm" +#include "code\game\objects\systems\cell_slot.dm" #include "code\game\rendering\client.dm" #include "code\game\rendering\mob.dm" #include "code\game\rendering\screen.dm" @@ -4686,6 +4694,7 @@ #include "code\modules\tools\_tool_system.dm" #include "code\modules\tools\items.dm" #include "code\modules\tools\multitool.dm" +#include "code\modules\tools\visuals.dm" #include "code\modules\tools\wrappers.dm" #include "code\modules\tools\z_legacy.dm" #include "code\modules\tooltip\tooltip.dm" diff --git a/code/__DEFINES/admin/admin.dm b/code/__DEFINES/admin/admin.dm index 2dc222d92e88..a22547f2273d 100644 --- a/code/__DEFINES/admin/admin.dm +++ b/code/__DEFINES/admin/admin.dm @@ -60,6 +60,8 @@ #define ADMIN_LOOKUP(user) ("[key_name_admin(user)][ADMIN_QUE(user)]") #define ADMIN_LOOKUPFLW(user) ("[key_name_admin(user)][ADMIN_QUE(user)] [ADMIN_FLW(user)]") #define COORD(src) ("[src ? src.Admin_Coordinates_Readable() : "nonexistent location"]") +// todo: this should be made faster/better, and support stuff like inventory / storage awareness +#define AUDIT_COORD(src) ("[src ? src.Admin_Coordinates_Readable() : "nonexistent location"]") #define AREACOORD(src) ("[src ? src.Admin_Coordinates_Readable(TRUE) : "nonexistent location"]") #define ADMIN_COORDJMP(src) ("[src ? src.Admin_Coordinates_Readable(FALSE, TRUE) : "nonexistent location"]") #define ADMIN_VERBOSEJMP(src) ("[src ? src.Admin_Coordinates_Readable(TRUE, TRUE) : "nonexistent location"]") diff --git a/code/__DEFINES/dcs/signals/signals_atom/context_system.dm b/code/__DEFINES/dcs/signals/signals_atom/context_system.dm new file mode 100644 index 000000000000..e765fd72e577 --- /dev/null +++ b/code/__DEFINES/dcs/signals/signals_atom/context_system.dm @@ -0,0 +1,20 @@ +//* This file is explicitly licensed under the MIT license. *// +//* Copyright (c) 2023 Citadel Station developers. *// + +/// from base of /atom/proc/context_query: (list/options, datum/event_args/actor/e_args) +/// options list is the same format as /atom/proc/context_query, insert directly to it. +#define COMSIG_ATOM_CONTEXT_QUERY "atom_context_query" +/// from base of /atom/proc/context_act: (key, datum/event_args/actor/e_args) +#define COMSIG_ATOM_CONTEXT_ACT "atom_context_act" + #define RAISE_ATOM_CONTEXT_ACT_HANDLED (1<<0) + +/// create context +/// * name: name +/// * image: context menu image +/// * distance: distance where this is valid; much be reachable or actable; null = requires adjacency or adjacency-equivalence +/// * mobility: mobility flags required +#define ATOM_CONTEXT_TUPLE(name, image, distance, mobility) list(name, image, distance, mobility) + +/// when used as distance, telekinetics and other things do not count as adjacency +// todo: currently not implemented +#define ATOM_CONTEXT_FORCE_PHYSICAL_ADJACENCY null diff --git a/code/__DEFINES/dcs/signals/signals_atom/tool_system.dm b/code/__DEFINES/dcs/signals/signals_atom/tool_system.dm new file mode 100644 index 000000000000..5d9c7ab9c1de --- /dev/null +++ b/code/__DEFINES/dcs/signals/signals_atom/tool_system.dm @@ -0,0 +1,10 @@ +//* This file is explicitly licensed under the MIT license. *// +//* Copyright (c) 2023 Citadel Station developers. *// + +/// from base of _tool_act: (I, user, function, flags, hint) where I = item, e_args = clickchain data, function = tool behaviour, flags = tool operation flags, hint = set by dynamic tool system +/// return CLICKCHAIN_COMPONENT_SIGNAL_HANDLED to abort normal tool_act handling. +#define COMSIG_ATOM_TOOL_ACT "tool_act" +/// from base of dynamic_tool_query: (I, datum/event_args/actor/clickchain/e_args, functions) where I = item, e_args = clickchain data. +/// inject by merging into functions +/// remember to use merge_double_lazy_assoc_list() to merge function lists! +#define COMSIG_ATOM_TOOL_QUERY "tool_functions" diff --git a/code/__DEFINES/dcs/signals/signals_tool_system.dm b/code/__DEFINES/dcs/signals/signals_tool_system.dm deleted file mode 100644 index 24163dae2d1b..000000000000 --- a/code/__DEFINES/dcs/signals/signals_tool_system.dm +++ /dev/null @@ -1,2 +0,0 @@ -/// from base of _tool_act: (I, user, function, flags, hint) where I = item, user = user, function = tool behaviour, flags = tool operation flags, hint = set by dynamic tool system -#define COMSIG_ATOM_TOOL_ACT "tool_act" diff --git a/code/__DEFINES/event_args.dm b/code/__DEFINES/event_args.dm new file mode 100644 index 000000000000..cfbbc03c37c3 --- /dev/null +++ b/code/__DEFINES/event_args.dm @@ -0,0 +1,7 @@ +//? for /datum/event_args/actor + +#define WRAP_MOB_TO_ACTOR_EVENT_ARGS(VARNAME) VARNAME = ismob(VARNAME)? new /datum/event_args/actor(VARNAME) : VARNAME + +//? for /datum/event_args/actor/clickchain + +#define WRAP_MOB_TO_CLICKCHAIN_EVENT_ARGS(VARNAME) VARNAME = ismob(VARNAME)? new /datum/event_args/actor/clickchain(VARNAME) : VARNAME diff --git a/code/__DEFINES/procs/clickcode.dm b/code/__DEFINES/procs/clickcode.dm index f8cc82c644c7..9e38b340ea9c 100644 --- a/code/__DEFINES/procs/clickcode.dm +++ b/code/__DEFINES/procs/clickcode.dm @@ -24,6 +24,7 @@ /// person can reach us normally #define CLICKCHAIN_HAS_PROXIMITY (1<<1) /// in tool act - used to check if we should do default proximity checks when none are specified +/// this is added to clickchain flags by tool_attack_chain. #define CLICKCHAIN_TOOL_ACT (1<<2) /// redirected by something - like when a switchtool to another item #define CLICKCHAIN_REDIRECTED (1<<3) @@ -33,7 +34,17 @@ #define CLICKCHAIN_DID_SOMETHING (1<<5) /// completely block attacking (notably, attack_mob, attack_obj) from happening by halting standard_melee_attack. #define CLICKCHAIN_DO_NOT_ATTACK (1<<6) +/// intercepted by component +#define CLICKCHAIN_COMPONENT_SIGNAL_HANDLED (1<<7) //! Reachability Depths - checked from level of DirectAccess and turf adjacency. /// default reachability depth #define DEFAULT_REACHABILITY_DEPTH 3 // enough to reach into pill bottles in box in backpack + +//! Reachability +/// can't reach - this *must* be a fals-y value. +#define REACH_FAILED 0 +/// can physically reach normally +#define REACH_PHYSICAL 1 +/// can reach with something like telekinesis +#define REACH_INDIRECT 2 diff --git a/code/__DEFINES/tools/functionality.dm b/code/__DEFINES/tools/functionality.dm index 1f6a4e3cc896..989989538382 100644 --- a/code/__DEFINES/tools/functionality.dm +++ b/code/__DEFINES/tools/functionality.dm @@ -38,16 +38,6 @@ GLOBAL_REAL_VAR(_dyntool_image_states) = list( //* None yet! Waiting on skill-system design. -//? Tool hints - make these human readable! - -#define TOOL_HINT_UNSCREWING_WINDOW_FRAME "unsecure frame" -#define TOOL_HINT_SCREWING_WINDOW_FRAME "secure frame" -#define TOOL_HINT_UNSCREWING_WINDOW_PANE "unfasten pane" -#define TOOL_HINT_SCREWING_WINDOW_PANE "fasten pane" -#define TOOL_HINT_CROWBAR_WINDOW_IN "pane in" -#define TOOL_HINT_CROWBAR_WINDOW_OUT "pane out" -#define TOOL_HINT_WRENCH_WINDOW_DISASSEMBLY "dismantle" - //? tool_locked var /// unlocked - use dynamic tool system diff --git a/code/__HELPERS/do_after.dm b/code/__HELPERS/do_after.dm index 32dc1a7da37a..a0365eeb132a 100644 --- a/code/__HELPERS/do_after.dm +++ b/code/__HELPERS/do_after.dm @@ -68,8 +68,9 @@ * * max_distance - if not null, the user is required to be get_dist() <= max_distance from target. * * additional_checks - a callback that allows for custom checks. this is invoked with our args directly, allowing us to modify delay. * * progress_anchor - override progressbar anchor location + * * progress_instance - override progressbar instance */ -/proc/do_after(mob/user, delay, atom/target, flags, mobility_flags = MOBILITY_CAN_USE, max_distance, datum/callback/additional_checks, atom/progress_anchor) +/proc/do_after(mob/user, delay, atom/target, flags, mobility_flags = MOBILITY_CAN_USE, max_distance, datum/callback/additional_checks, atom/progress_anchor, datum/progressbar/progress_instance) if(isnull(user)) return FALSE if(!delay) @@ -97,10 +98,10 @@ var/obj/item/active_held_item = user.get_active_held_item() - var/datum/progressbar/progress + var/datum/progressbar/progress = progress_instance var/original_delay = delay var/delay_factor = 1 - if(!(flags & DO_AFTER_NO_PROGRESS) && (!isnull(progress_anchor || !isnull(target)))) + if(isnull(progress) && !(flags & DO_AFTER_NO_PROGRESS) && (!isnull(progress_anchor || !isnull(target)))) progress = new(user, delay, progress_anchor || target) var/start_time = world.time diff --git a/code/datums/event_args/_event_args.dm b/code/datums/event_args/_event_args.dm new file mode 100644 index 000000000000..d2569f99fff3 --- /dev/null +++ b/code/datums/event_args/_event_args.dm @@ -0,0 +1,5 @@ +/** + * datums used to hold data for procs without having to pass too many args around + */ +/datum/event_args + abstract_type = /datum/event_args diff --git a/code/datums/event_args/actor.dm b/code/datums/event_args/actor.dm new file mode 100644 index 000000000000..c5186ef2ed96 --- /dev/null +++ b/code/datums/event_args/actor.dm @@ -0,0 +1,57 @@ +/** + * used to hold semantic data about an action being done by an actor vs initiator (controller) + */ +/datum/event_args/actor + /// the mob performing the action + var/mob/performer + /// the mob actually initiating the action, e.g. a remote controller. + var/mob/initiator + +/datum/event_args/actor/New(mob/performer, mob/initiator) + src.performer = performer + src.initiator = isnull(initiator)? performer : initiator + +/datum/event_args/actor/proc/chat_feedback(msg, atom/target) + performer.action_feedback(msg, target) + if(performer != initiator) + initiator.action_feedback(msg, target) + +/datum/event_args/actor/proc/bubble_feedback(msg, atom/target) + performer.bubble_action_feedback(msg, target) + if(performer != initiator) + initiator.bubble_action_feedback(msg, target) + +/** + * It is highly recommended to use named parameters with this. + */ +/datum/event_args/actor/proc/visible_feedback(atom/target, range, visible, audible, visible_self, otherwise_self, visible_them, otherwise_them) + performer.visible_action_feedback( + target = target, + initiator = initiator, + hard_range = range, + visible_hard = visible, + audible_hard = audible, + visible_self = visible_self, + otherwise_self = otherwise_self, + visible_them = visible_them, + otherwise_them = otherwise_them, + ) + +/** + * It is highly recommended to use named parameters with this. + */ +/datum/event_args/actor/proc/visible_dual_feedback(atom/target, range_hard, range_soft, visible_hard, visible_soft, audible_hard, audible_soft, visible_self, otherwise_self, visible_them, otherwise_them) + performer.visible_action_feedback( + target = target, + initiator = initiator, + hard_range = range_hard, + soft_range = range_soft, + visible_hard = visible_hard, + visible_soft = visible_soft, + audible_hard = audible_hard, + audible_soft = audible_soft, + visible_self = visible_self, + otherwise_self = otherwise_self, + visible_them = visible_them, + otherwise_them = otherwise_them, + ) diff --git a/code/datums/event_args/clickchain.dm b/code/datums/event_args/clickchain.dm new file mode 100644 index 000000000000..ce2039376a3f --- /dev/null +++ b/code/datums/event_args/clickchain.dm @@ -0,0 +1,13 @@ +/** + * used to hold data about a click action + */ +/datum/event_args/actor/clickchain + /// a_intent + var/intent + /// click params + var/list/params + +/datum/event_args/actor/clickchain/New(mob/performer, mob/initiator, intent, list/params) + ..() + src.intent = isnull(intent)? performer.a_intent : intent + src.params = isnull(params)? list() : params diff --git a/code/datums/progressbar.dm b/code/datums/progressbar.dm index 8813b1be798f..ee4a71eee143 100644 --- a/code/datums/progressbar.dm +++ b/code/datums/progressbar.dm @@ -1,3 +1,7 @@ +/proc/create_actor_progress_bar(datum/event_args/actor/e_args, goal_number, atom/target) + // todo: also show initiator the progress bar + return new /datum/progressbar(e_args.performer, goal_number, target) + /datum/progressbar var/goal = 1 var/image/bar diff --git a/code/game/atoms/action_feedback.dm b/code/game/atoms/action_feedback.dm index e3e2d4065a00..096a003c3c15 100644 --- a/code/game/atoms/action_feedback.dm +++ b/code/game/atoms/action_feedback.dm @@ -7,6 +7,7 @@ * * @params * * target - target atom + * * initiator - additional thing to show a message to as self * * hard_range - how far to display hard message; defaults to MESSAGE_RANGE_COMBAT_LOUD. if doesn't exist we use soft. * * soft_range - how far to display soft message; defaults to MESSAGE_RANGE_COMBAT_LOUD. overrides hard range if smaller. * * visible_hard - hard message. if doesn't exist we use soft message. @@ -14,11 +15,11 @@ * * visible_soft - soft message. * * audible_soft - what blind people hear when inside soft range (overridden by self and them if specified) * * visible_self - what we see - * * audible_self - override if self is blind. if null, defaults to 'self. + * * otherwise_self - override if self is blind. if null, defaults to 'self. * * visible_them - what the target see - * * audible_them - what the target sees if they are blind. if null, defaults to 'them'. + * * otherwise_them - what the target sees if they are blind. if null, defaults to 'them'. */ -/atom/proc/visible_action_feedback(atom/target, hard_range = MESSAGE_RANGE_COMBAT_LOUD, soft_range, visible_hard, audible_hard, audible_soft, visible_soft, visible_self, audible_self, visible_them, audible_them) +/atom/proc/visible_action_feedback(atom/target, atom/initiator, hard_range = MESSAGE_RANGE_COMBAT_LOUD, soft_range, visible_hard, audible_hard, audible_soft, visible_soft, visible_self, otherwise_self, visible_them, otherwise_them) var/list/viewing var/viewing_range = max(soft_range, hard_range) //! LEGACY @@ -30,6 +31,9 @@ //! end var/hard_visible = visible_hard || visible_soft var/hard_audible = audible_hard || audible_soft + visible_self = visible_self || otherwise_self + visible_them = visible_them || otherwise_them + // todo: all of this needs rewritten oh my god for(var/atom/movable/AM as anything in viewing) if(get_dist(AM, src) <= hard_range) if(ismob(AM)) diff --git a/code/game/atoms/atom.dm b/code/game/atoms/atom.dm index 9c9e630ee57d..3a00472a35d2 100644 --- a/code/game/atoms/atom.dm +++ b/code/game/atoms/atom.dm @@ -41,6 +41,10 @@ /// armor datum type var/armor_type = /datum/armor/none + //? Context + /// open context menus by mob + var/list/context_menus + //? Economy /// intrinsic worth without accounting containing reagents / materials - applies in static and dynamic mode. var/worth_intrinsic = 0 @@ -873,7 +877,7 @@ return reagents && (reagents.reagents_holder_flags & DRAINABLE) -/atom/proc/get_cell() +/atom/proc/get_cell(inducer) return //? Radiation diff --git a/code/game/click/click.dm b/code/game/click/click.dm index d58539cf6565..fdf533a4fe32 100644 --- a/code/game/click/click.dm +++ b/code/game/click/click.dm @@ -289,6 +289,8 @@ /atom/proc/AltClick(var/mob/user) SEND_SIGNAL(src, COMSIG_CLICK_ALT, user) + if(context_menu(new /datum/event_args/actor(user))) + return TRUE return FALSE // todo: rework diff --git a/code/game/click/context.dm b/code/game/click/context.dm new file mode 100644 index 000000000000..81c18774cdc2 --- /dev/null +++ b/code/game/click/context.dm @@ -0,0 +1,97 @@ +//* This file is explicitly licensed under the MIT license. *// +//* Copyright (c) 2023 Citadel Station developers. *// + +/** + * get context options + * + * key is a text string + * value are tuples; use ATOM_CONTEXT_TUPLE to create. + * + * @return list(key = value) + */ +/atom/proc/context_query(datum/event_args/actor/e_args) + . = list() + SEND_SIGNAL(src, COMSIG_ATOM_CONTEXT_QUERY, ., e_args) + +/** + * act on a context option + * + * things in this should re-check validity / sanity! + * + * @return TRUE / FALSE; TRUE if handled. + */ +/atom/proc/context_act(datum/event_args/actor/e_args, key) + if(SEND_SIGNAL(src, COMSIG_ATOM_CONTEXT_ACT, key, e_args) & RAISE_ATOM_CONTEXT_ACT_HANDLED) + return TRUE + return FALSE + +/atom/proc/context_menu(datum/event_args/actor/e_args) + set waitfor = FALSE + // admin proccall support + WRAP_MOB_TO_ACTOR_EVENT_ARGS(e_args) + // todo: dynamically rebuild menu based on distance? + var/client/receiving = e_args.initiator.client + if(isnull(receiving)) + // well what the hell are we doing here? + // automated functions should be using context_query and context_act directly + return FALSE + if(context_menus?[receiving]) + // close + log_click_context(e_args, src, "menu close") + qdel(context_menus[receiving]) + return TRUE + var/list/menu_options = context_query(e_args) + if(!length(menu_options)) + return FALSE + // open + log_click_context(e_args, src, "menu open") + . = TRUE + blocking_context_menu(e_args, receiving, menu_options, e_args.performer) + +/atom/proc/blocking_context_menu(datum/event_args/actor/e_args, client/receiving, list/menu_options, mob/actor) + // for now, we just filter without auto-updating/rebuilding when things change + var/list/transformed = list() + var/list/inverse_lookup = list() + for(var/key as anything in menu_options) + var/list/data = menu_options[key] + if(!CHECK_ALL_MOBILITY(actor, data[4])) + continue + if(isnull(data[3])? !actor.Adjacent(src) : get_dist(actor, src) > data[3]) + continue + var/image/I = data[2] + // todo: why isn't radial menu doing this procesisng? + if(I) + I.maptext_x = -16 + I.maptext_y = 32 + I.maptext_width = 64 + I.maptext = MAPTEXT_CENTER(data[1]) + transformed[data[1]] = I + inverse_lookup[data[1]] = key + + var/datum/radial_menu/context_menu/menu = new + var/id = "context_[REF(e_args.initiator)]" + GLOB.radial_menus[id] = menu + LAZYSET(context_menus, receiving, menu) + + menu.radius = 32 + menu.host = src + menu.anchor = src + menu.check_screen_border(receiving.mob) + menu.set_choices(transformed, FALSE) + menu.show_to(receiving.mob) + menu.wait(receiving.mob, src, TRUE) + + var/chosen_name = menu.selected_choice + + qdel(menu) + GLOB.radial_menus -= id + + if(isnull(chosen_name)) + return + + var/key = inverse_lookup[chosen_name] + context_act(e_args, key) + +/atom/proc/context_close() + for(var/client/C as anything in context_menus) + qdel(context_menus[C]) diff --git a/code/game/click/item_attack.dm b/code/game/click/item_attack.dm index f249a92261b9..f64c8bb68fa4 100644 --- a/code/game/click/item_attack.dm +++ b/code/game/click/item_attack.dm @@ -29,10 +29,10 @@ avoid code duplication. This includes items that may sometimes act as a standard return A.attackby(src, user, params, clickchain_flags, attack_modifier) // No comment -/atom/proc/attackby(obj/item/I, mob/living/user, list/params, clickchain_flags, damage_multiplier) +/atom/proc/attackby(obj/item/I, mob/user, list/params, clickchain_flags, damage_multiplier) return I.standard_melee_attack(src, user, clickchain_flags, params, damage_multiplier, user.zone_sel?.selecting, user.a_intent) -/mob/living/attackby(obj/item/I, mob/living/user, list/params, clickchain_flags, damage_multiplier) +/mob/living/attackby(obj/item/I, mob/user, list/params, clickchain_flags, damage_multiplier) if(can_operate(src) && user.a_intent != INTENT_HARM && I.do_surgery(src,user)) return NONE if(attempt_vr(src,"vore_attackby",args)) diff --git a/code/game/click/items.dm b/code/game/click/items.dm index c703faac0e45..eb53a2568388 100644 --- a/code/game/click/items.dm +++ b/code/game/click/items.dm @@ -54,7 +54,7 @@ // are we on harm intent? if so, lol no if(user && (user.a_intent == INTENT_HARM)) return NONE - return target.tool_interaction(src, user, clickchain_flags | CLICKCHAIN_TOOL_ACT) + return target.tool_interaction(src, new /datum/event_args/actor/clickchain(user, params = params), clickchain_flags | CLICKCHAIN_TOOL_ACT) /** * called at the start of melee attack chains diff --git a/code/game/click/other_mobs.dm b/code/game/click/other_mobs.dm index d54b5226b340..0506362b2318 100644 --- a/code/game/click/other_mobs.dm +++ b/code/game/click/other_mobs.dm @@ -31,9 +31,23 @@ A.attack_hand(src) /// Return TRUE to cancel other attack hand effects that respect it. +// todo: /datum/event_args/actor/clickchain /atom/proc/attack_hand(mob/user, list/params) + if(on_attack_hand(new /datum/event_args/actor/clickchain(user, intent = user.a_intent, params = params))) + return TRUE . = _try_interact(user) +/** + * Override this instead of attack_hand. + * + * Return TRUE to cancel other attack hand effects that respect it. + * + * @params + * * e_args - click data + */ +/atom/proc/on_attack_hand(datum/event_args/actor/clickchain/e_args) + return FALSE + //Return a non FALSE value to cancel whatever called this from propagating, if it respects it. /atom/proc/_try_interact(mob/user) // if(isAdminGhostAI(user)) //admin abuse diff --git a/code/game/click/reachability.dm b/code/game/click/reachability.dm index 6627d906b6d4..9623a1cd899f 100644 --- a/code/game/click/reachability.dm +++ b/code/game/click/reachability.dm @@ -27,6 +27,10 @@ * - for loop runs 3 times, hits turf, doesn't add turf area * - TurfAdjacency is checked since it isn't a ranged attack * + * Caveats: + * * This is not enough for 'can we physically reach'; that should be Adjacency. This is because telekinesis is a thing, and will be added later. + * * For this reason, the cost is higher than just checking 'can telekinesis', because telekinesis is checked after physical, as physical is stronger. + * * @params * - target - the target * - depth - max depth - should be at least 1 in most cases. @@ -36,12 +40,12 @@ /atom/movable/proc/Reachability(atom/target, depth = DEFAULT_REACHABILITY_DEPTH, range = 1, obj/item/tool) if(!target) // apologies sir, you may not grasp the void... - return FALSE + return REACH_FAILED // direct cache - check if we can access something using if dc[atom] var/list/dc = DirectAccessCache() // optimization - a lot of the time we're clicking on ourselves/things on ourselves if(dc[target]) - return TRUE + return REACH_PHYSICAL // turf adjacency enabled? stores if we can try to path to our turf var/turf/tadj // loc checking @@ -105,7 +109,7 @@ continue if(dc[l]) // found - return TRUE + return REACH_PHYSICAL if(isturf(l) && !th) // is turf; turf adjacency enabled th = l @@ -117,7 +121,7 @@ ++i if(!(tadj && th)) // didn't hit both, fail - return FALSE + return REACH_FAILED // at this point, we're on a turf if(range == 1) // most common case: reach directly aronud yourself @@ -136,18 +140,18 @@ if(n.TurfAdjacency(th)) // succeeded qdel(D) - return TRUE + return REACH_PHYSICAL // dumb directional pathfinding both for cheapness and for practical purposes // so you can't snake-arms round a row of windows or something crazy n = get_step(D, get_dir(D, th)) if(!D.Move(n)) // failed qdel(D) - return FALSE + return REACH_FAILED // keep going // at this point, we failed qdel(D) - return FALSE + return REACH_FAILED /** * quick and dirty reachability check @@ -160,18 +164,18 @@ if(isturf(curr)) source = get_turf(src) if(!source) - return FALSE + return REACH_FAILED return curr.TurfAdjacency(source) do if(curr == src) - return TRUE + return REACH_PHYSICAL if(!curr) - return FALSE + return REACH_FAILED curr = curr.loc while(!isturf(curr)) source = get_turf(src) if(!source) - return FALSE + return REACH_FAILED return curr.TurfAdjacency(source) /atom/movable/reachability_delegate diff --git a/code/game/machinery/_machinery_construction.dm b/code/game/machinery/_machinery_construction.dm index b2e8dc5afae6..025d3d1b213c 100644 --- a/code/game/machinery/_machinery_construction.dm +++ b/code/game/machinery/_machinery_construction.dm @@ -1,103 +1,101 @@ //* This file is explicitly licensed under the MIT license. *// //* Copyright (c) 2023 Citadel Station developers. *// -/obj/machinery/dynamic_tool_functions(obj/item/I, mob/user) +/obj/machinery/dynamic_tool_query(obj/item/I, datum/event_args/actor/clickchain/e_args, list/hint_images = list()) . = list() if(tool_deconstruct && !isnull(default_deconstruct) && panel_open) - LAZYADD(.[tool_panel], "deconstruct") + LAZYSET(.[tool_deconstruct], "deconstruct", dyntool_image_backward(tool_deconstruct)) if(tool_unanchor && !isnull(default_unanchor)) - LAZYADD(.[tool_panel], anchored? "unanchor" : "anchor") + LAZYSET(.[tool_unanchor], anchored? "unanchor" : "anchor", anchored? dyntool_image_backward(tool_unanchor) : dyntool_image_forward(tool_unanchor)) if(tool_panel && !isnull(default_panel)) - LAZYADD(.[tool_panel], panel_open? "close panel" : "open panel") + LAZYSET(.[tool_panel], panel_open? "close panel" : "open panel", panel_open? dyntool_image_forward(tool_panel) : dyntool_image_backward(tool_panel)) return merge_double_lazy_assoc_list(., ..()) -/obj/machinery/dynamic_tool_image(function, hint) - switch(hint) - if("anchor", "close panel") - return dyntool_image_backward(function) - if("unanchor", "open panel", "deconstruct") - return dyntool_image_backward(function) - return ..() - -/obj/machinery/tool_act(obj/item/I, mob/user, function, flags, hint) - if(INTERACTING_WITH_FOR(user, src, INTERACTING_FOR_CONSTRUCTION)) +/obj/machinery/tool_act(obj/item/I, datum/event_args/actor/clickchain/e_args, function, flags, hint) + if(INTERACTING_WITH_FOR(e_args.performer, src, INTERACTING_FOR_CONSTRUCTION)) return CLICKCHAIN_DO_NOT_PROPAGATE - START_INTERACTING_WITH(user, src, INTERACTING_FOR_CONSTRUCTION) + START_INTERACTING_WITH(e_args.performer, src, INTERACTING_FOR_CONSTRUCTION) if(function == tool_deconstruct && !isnull(default_deconstruct)) - if(default_deconstruction_dismantle(I, user, flags = flags)) + if(default_deconstruction_dismantle(I, e_args, flags = flags)) . = CLICKCHAIN_DID_SOMETHING | CLICKCHAIN_DO_NOT_PROPAGATE . = CLICKCHAIN_DO_NOT_PROPAGATE else if(function == tool_unanchor && !isnull(default_unanchor)) - if(default_deconstruction_anchor(I, user, flags = flags)) + if(default_deconstruction_anchor(I, e_args, flags = flags)) . = CLICKCHAIN_DID_SOMETHING | CLICKCHAIN_DO_NOT_PROPAGATE . = CLICKCHAIN_DO_NOT_PROPAGATE else if(function == tool_panel && !isnull(default_panel)) - if(default_deconstruction_panel(I, user, flags = flags)) + if(default_deconstruction_panel(I, e_args, flags = flags)) . = CLICKCHAIN_DID_SOMETHING | CLICKCHAIN_DO_NOT_PROPAGATE . = CLICKCHAIN_DO_NOT_PROPAGATE - STOP_INTERACTING_WITH(user, src,INTERACTING_FOR_CONSTRUCTION) + STOP_INTERACTING_WITH(e_args.performer, src,INTERACTING_FOR_CONSTRUCTION) if(isnull(.)) return ..() // todo: better verb/message support -/obj/machinery/proc/default_deconstruction_panel(obj/item/tool, mob/user, speed_mult = 1, flags) +/obj/machinery/proc/default_deconstruction_panel(obj/item/tool, datum/event_args/actor/clickchain/e_args, speed_mult = 1, flags) var/needed_time = default_panel * speed_mult * (isnull(tool)? 1 : tool.tool_speed) if(needed_time) - user.visible_action_feedback( + e_args.visible_feedback( target = src, - soft_range = MESSAGE_RANGE_CONSTRUCTION, - visible_soft = SPAN_WARNING("[user] starts to [panel_open? "close" : "open"] [src]'s maintenance panel."), - audible_soft = SPAN_WARNING("You hear something being (un)fastened."), + range = MESSAGE_RANGE_CONSTRUCTION, + visible = SPAN_WARNING("[e_args.performer] starts to [panel_open? "close" : "open"] [src]'s maintenance panel."), + audible = SPAN_WARNING("You hear something being (un)fastened."), + otherwise_self = SPAN_WARNING("You start to [panel_open? "close" : "open"] [src]'s panel."), ) - if(!use_tool(tool_panel, tool, user, flags, needed_time)) + if(!use_tool(tool_panel, tool, e_args, flags, needed_time)) return FALSE - user.visible_action_feedback( + e_args.visible_feedback( target = src, - soft_range = MESSAGE_RANGE_CONSTRUCTION, - visible_soft = SPAN_WARNING("[user] [panel_open? "closes" : "opens"] [src]'s maintenance panel."), - audible_soft = SPAN_WARNING("You hear something being (un)fastened."), + range = MESSAGE_RANGE_CONSTRUCTION, + visible = SPAN_WARNING("[e_args.performer] [panel_open? "closes" : "opens"] [src]'s maintenance panel."), + audible = SPAN_WARNING("You hear something being (un)fastened."), + otherwise_self = SPAN_WARNING("You [panel_open? "close" : "open"] [src]'s panel."), ) set_panel_open(!panel_open) return TRUE // todo: better verb/message support -/obj/machinery/proc/default_deconstruction_dismantle(obj/item/tool, mob/user, speed_mult = 1, flags) +/obj/machinery/proc/default_deconstruction_dismantle(obj/item/tool, datum/event_args/actor/clickchain/e_args, speed_mult = 1, flags) var/needed_time = default_deconstruct * speed_mult * (isnull(tool)? 1 : tool.tool_speed) if(needed_time) - user.visible_action_feedback( + e_args.visible_feedback( target = src, - soft_range = MESSAGE_RANGE_CONSTRUCTION, - visible_soft = SPAN_WARNING("[user] starts to dismantle [src]."), - audible_soft = SPAN_WARNING("You hear a series of small parts being removed from something."), + range = MESSAGE_RANGE_CONSTRUCTION, + visible = SPAN_WARNING("[e_args.performer] starts to dismantle [src]."), + audible = SPAN_WARNING("You hear a series of small parts being removed from something."), + otherwise_self = SPAN_WARNING("You start to dismantle [src]."), ) - if(!use_tool(tool_deconstruct, tool, user, flags, needed_time)) + if(!use_tool(tool_deconstruct, tool, e_args, flags, needed_time)) return FALSE - user.visible_action_feedback( + e_args.visible_feedback( target = src, - soft_range = MESSAGE_RANGE_CONSTRUCTION, - visible_soft = SPAN_WARNING("[user] dismantles [src]."), - audible_soft = SPAN_WARNING("You hear something getting dismantled."), + range = MESSAGE_RANGE_CONSTRUCTION, + visible = SPAN_WARNING("[e_args.performer] dismantles [src]."), + audible = SPAN_WARNING("You hear something getting dismantled."), + otherwise_self = SPAN_WARNING("You dismantle [src]."), ) dismantle() return TRUE // todo: better verb/message support -/obj/machinery/proc/default_deconstruction_anchor(obj/item/tool, mob/user, speed_mult = 1, flags) +/obj/machinery/proc/default_deconstruction_anchor(obj/item/tool, datum/event_args/actor/clickchain/e_args, speed_mult = 1, flags) var/needed_time = default_unanchor * speed_mult * (isnull(tool)? 1 : tool.tool_speed) if(needed_time) - user.visible_action_feedback( + e_args.visible_feedback( target = src, - soft_range = MESSAGE_RANGE_CONSTRUCTION, - visible_soft = SPAN_WARNING("[user] starts to [anchored? "unbolt" : "bolt"] [src] [anchored? "from" : "to"] the floor."), - audible_soft = SPAN_WARNING("You hear something heavy being (un)fastened."), + range = MESSAGE_RANGE_CONSTRUCTION, + visible = SPAN_WARNING("[e_args.performer] starts to [anchored? "unbolt" : "bolt"] [src] [anchored? "from" : "to"] the floor."), + audible = SPAN_WARNING("You hear something heavy being (un)fastened."), + otherwise_self = SPAN_WARNING("You start to [anchored? "unbolt" : "bolt"] [src] [anchored? "from" : "to"] the floor."), ) - if(!use_tool(tool_unanchor, tool, user, flags, needed_time)) + if(!use_tool(tool_unanchor, tool, e_args, flags, needed_time)) return FALSE - user.visible_action_feedback( + e_args.visible_feedback( target = src, - soft_range = MESSAGE_RANGE_CONSTRUCTION, - visible_soft = SPAN_WARNING("[user] [anchored? "bolts" : "unbolts"] [src] [anchored? "to" : "from"] from the floor."), - audible_soft = SPAN_WARNING("You hear something heavy being (un)fastened."), + range = MESSAGE_RANGE_CONSTRUCTION, + visible = SPAN_WARNING("[e_args.performer] [anchored? "bolts" : "unbolts"] [src] [anchored? "to" : "from"] from the floor."), + audible = SPAN_WARNING("You hear something heavy being (un)fastened."), + otherwise_self = SPAN_WARNING("You [anchored? "unbolt" : "bolt"] [src] [anchored? "from" : "to"] the floor."), ) set_anchored(!anchored) return TRUE diff --git a/code/game/machinery/misc/bioscan_antenna.dm b/code/game/machinery/misc/bioscan_antenna.dm index e7d0c52c06d8..286bd68c62b0 100644 --- a/code/game/machinery/misc/bioscan_antenna.dm +++ b/code/game/machinery/misc/bioscan_antenna.dm @@ -34,17 +34,22 @@ GLOBAL_LIST_EMPTY(bioscan_antenna_list) change_network(null) return ..() -/obj/machinery/bioscan_antenna/multitool_act(obj/item/I, mob/user, flags, hint) +/obj/machinery/bioscan_antenna/multitool_act(obj/item/I, datum/event_args/actor/clickchain/e_args, flags, hint) if(!network_mutable) return ..() . = TRUE - var/new_network = default_input_text(user, "What do you want to set the network key to?", "Modify Network", network_key) - if(!user.Reachability(src) || isnull(new_network)) + var/new_network = default_input_text(e_args.initiator, "What do you want to set the network key to?", "Modify Network", network_key) + if(!e_args.performer.Reachability(src) || isnull(new_network)) return - user.visible_message(SPAN_NOTICE("[user] reprograms the network on [src]."), range = MESSAGE_RANGE_CONFIGURATION) + e_args.visible_feedback( + target = src, + visible = SPAN_NOTICE("[e_args.performer] reprograms the network on [src]."), + range = MESSAGE_RANGE_CONFIGURATION, + otherwise_self = SPAN_NOTICE("You reprogram the network on [src]."), + ) change_network(new_network) -/obj/machinery/bioscan_antenna/dynamic_tool_functions(obj/item/I, mob/user) +/obj/machinery/bioscan_antenna/dynamic_tool_query(obj/item/I, datum/event_args/actor/clickchain/e_args, list/hint_images = list()) . = list() if(network_mutable) .[TOOL_MULTITOOL] = "change network" diff --git a/code/game/machinery/pipe/construction.dm b/code/game/machinery/pipe/construction.dm index ac6b4eb4122b..d56a9340f005 100644 --- a/code/game/machinery/pipe/construction.dm +++ b/code/game/machinery/pipe/construction.dm @@ -175,28 +175,23 @@ Buildable meters else return ..() -/obj/item/pipe/attackby(var/obj/item/W as obj, var/mob/user as mob) - if(W.is_wrench()) - return wrench_act(W, user) - return ..() - -/obj/item/pipe/wrench_act(obj/item/I, mob/user, flags, hint) +/obj/item/pipe/wrench_act(obj/item/I, datum/event_args/actor/clickchain/e_args, flags, hint) if(!isturf(loc)) return TRUE - add_fingerprint(user) + add_fingerprint(e_args.performer) fixdir() var/obj/machinery/atmospherics/fakeA = pipe_type var/initial_flags = initial(fakeA.pipe_flags) for(var/obj/machinery/atmospherics/M in loc) if((M.pipe_flags & initial_flags & PIPING_ONE_PER_TURF)) //Only one dense/requires density object per tile, eg connectors/cryo/heater/coolers. - to_chat(user, "Something is hogging the tile!") + e_args.chat_feedback(SPAN_WARNING("Something is hogging the tile!"), src) return TRUE if((M.piping_layer != piping_layer) && !((M.pipe_flags | initial_flags) & PIPING_ALL_LAYER)) // Pipes on different layers can't block each other unless they are ALL_LAYER continue if(M.get_init_dirs() & SSmachines.get_init_dirs(pipe_type, dir)) // matches at least one direction on either type of pipe - to_chat(user, "There is already a pipe at that location!") + e_args.chat_feedback(SPAN_WARNING("There is already a pipe at that location!"), src) return TRUE // no conflicts found @@ -205,15 +200,17 @@ Buildable meters // TODO - Evaluate and remove the "need at least one thing to connect to" thing ~Leshana // With how the pipe code works, at least one end needs to be connected to something, otherwise the game deletes the segment. if (QDELETED(A)) - to_chat(user, "There's nothing to connect this pipe section to!") + e_args.chat_feedback(SPAN_WARNING("There's nothing to connect this pipe section to!"), src) return TRUE transfer_fingerprints_to(A) playsound(src, I.tool_sound, 50, 1) - user.visible_message( \ - "[user] fastens \the [src].", \ - "You fasten \the [src].", \ - "You hear ratcheting.") + e_args.visible_feedback( + target = src, + visible = SPAN_NOTICE("[e_args.performer] fastens \the [src]."), + audible = SPAN_WARNING("You hear ratcheting."), + otherwise_self = SPAN_NOTICE("You fasten \the [src].") + ) qdel(src) @@ -269,18 +266,18 @@ Buildable meters return wrench_act(W, user) return ..() -/obj/item/pipe_meter/wrench_act(obj/item/I, mob/user, flags, hint) +/obj/item/pipe_meter/wrench_act(obj/item/I, datum/event_args/actor/clickchain/e_args, flags, hint) var/obj/machinery/atmospherics/pipe/pipe for(var/obj/machinery/atmospherics/pipe/P in loc) if(P.piping_layer == piping_layer) pipe = P break if(!pipe) - to_chat(user, "You need to fasten it to a pipe!") + e_args.chat_feedback(SPAN_WARNING("You need to fasten it to a pipe!"), src) return TRUE new /obj/machinery/meter(loc, piping_layer) playsound(src, I.tool_sound, 50, 1) - to_chat(user, "You fasten the meter to the pipe.") + e_args.chat_feedback(SPAN_NOTICE("You fasten the meter to the pipe."), src) qdel(src) /obj/item/pipe_meter/dropped(mob/user, flags, atom/newLoc) diff --git a/code/game/mecha/mecha.dm b/code/game/mecha/mecha.dm index 04dbf514158d..410669e08932 100644 --- a/code/game/mecha/mecha.dm +++ b/code/game/mecha/mecha.dm @@ -401,7 +401,7 @@ return cell = new /obj/item/cell/high(src) -/obj/mecha/get_cell() +/obj/mecha/get_cell(inducer) return cell /obj/mecha/proc/add_cabin() @@ -2853,5 +2853,5 @@ occupant.clear_alert("mech damage") // Various sideways-defined get_cells -/obj/mecha/get_cell() +/obj/mecha/get_cell(inducer) return cell diff --git a/code/game/objects/items.dm b/code/game/objects/items.dm index eab4a93c53a5..ecf0da767c20 100644 --- a/code/game/objects/items.dm +++ b/code/game/objects/items.dm @@ -378,9 +378,9 @@ R.activate_module(src) R.hud_used.update_robot_modules_display() -/obj/item/attackby(obj/item/W as obj, mob/user as mob) - if(istype(W, /obj/item/storage)) - var/obj/item/storage/S = W +/obj/item/attackby(obj/item/I, mob/user, list/params, clickchain_flags, damage_multiplier) + if(istype(I, /obj/item/storage)) + var/obj/item/storage/S = I if(S.use_to_pickup) if(S.collection_mode) //Mode is set to collect all items if(isturf(src.loc)) @@ -388,7 +388,19 @@ else if(S.can_be_inserted(src)) S.handle_item_insertion(src, user) - return + if(istype(I, /obj/item/cell) && !isnull(obj_cell_slot) && isnull(obj_cell_slot.cell) && obj_cell_slot.interaction_active(user)) + if(!user.transfer_item_to_loc(I, src)) + user.action_feedback(SPAN_WARNING("[I] is stuck to your hand!"), src) + return CLICKCHAIN_DO_NOT_PROPAGATE + user.visible_action_feedback( + target = src, + hard_range = obj_cell_slot.remove_is_discrete? 0 : MESSAGE_RANGE_CONSTRUCTION, + visible_hard = SPAN_NOTICE("[user] inserts [I] into [src]."), + audible_hard = SPAN_NOTICE("You hear something being slotted in."), + visible_self = SPAN_NOTICE("You insert [I] into [src]."), + ) + obj_cell_slot.insert_cell(I) + return CLICKCHAIN_DO_NOT_PROPAGATE | CLICKCHAIN_DID_SOMETHING /obj/item/proc/talk_into(mob/M as mob, text) return @@ -769,17 +781,33 @@ modules/mob/living/carbon/human/life.dm if you die, you will be zoomed out. // SHOULD_CALL_PARENT(TRUE) // attack_self isn't really part of the item attack chain. SEND_SIGNAL(src, COMSIG_ITEM_ATTACK_SELF, user) + if(on_attack_self(new /datum/event_args/actor(user))) + return TRUE if(interaction_flags_item & INTERACT_ITEM_ATTACK_SELF) interact(user) - on_attack_self(user) /** * Called after we attack self * Used to allow for attack_self to be interrupted by signals in nearly all cases. * You should usually override this instead of attack_self. + * + * You should do . = ..() and check ., if it's TRUE, it means a parent proc requested the call chain to stop. + * + * @return TRUE to signal to overrides to stop the chain and do nothing. */ -/obj/item/proc/on_attack_self(mob/user) - return +/obj/item/proc/on_attack_self(datum/event_args/actor/e_args) + if(!isnull(obj_cell_slot?.cell) && obj_cell_slot.remove_yank_inhand && obj_cell_slot.interaction_active(src)) + e_args.visible_feedback( + target = src, + range = obj_cell_slot.remove_is_discrete? 0 : MESSAGE_RANGE_CONSTRUCTION, + visible = SPAN_NOTICE("[e_args.performer] removes the cell from [src]."), + audible = SPAN_NOTICE("You hear fasteners falling out and something being removed."), + otherwise_self = SPAN_NOTICE("You remove the cell from [src]."), + ) + log_construction(e_args, src, "removed cell [obj_cell_slot.cell] ([obj_cell_slot.cell.type])") + e_args.performer.put_in_hands_or_drop(obj_cell_slot.remove_cell(e_args.performer)) + return TRUE + return FALSE //? Mob Armor diff --git a/code/game/objects/items/devices/flash.dm b/code/game/objects/items/devices/flash.dm index 9ece7196e696..605d91a181a3 100644 --- a/code/game/objects/items/devices/flash.dm +++ b/code/game/objects/items/devices/flash.dm @@ -72,7 +72,7 @@ icon_state = "[base_icon]" return -/obj/item/flash/get_cell() +/obj/item/flash/get_cell(inducer) return power_supply /obj/item/flash/proc/get_external_power_supply() diff --git a/code/game/objects/items/devices/flashlight.dm b/code/game/objects/items/devices/flashlight.dm index 6fcf18ad20bc..6c497c9b01e3 100644 --- a/code/game/objects/items/devices/flashlight.dm +++ b/code/game/objects/items/devices/flashlight.dm @@ -57,7 +57,7 @@ update_appearance() return PROCESS_KILL -/obj/item/flashlight/get_cell() +/obj/item/flashlight/get_cell(inducer) return cell /obj/item/flashlight/verb/toggle() diff --git a/code/game/objects/items/devices/radio/jammer.dm b/code/game/objects/items/devices/radio/jammer.dm index 24453693e9c5..69ef1cf538f8 100644 --- a/code/game/objects/items/devices/radio/jammer.dm +++ b/code/game/objects/items/devices/radio/jammer.dm @@ -39,7 +39,7 @@ var/global/list/active_radio_jammers = list() QDEL_NULL(power_source) return ..() -/obj/item/radio_jammer/get_cell() +/obj/item/radio_jammer/get_cell(inducer) return power_source /obj/item/radio_jammer/proc/turn_off(mob/user) diff --git a/code/game/objects/items/devices/suit_cooling.dm b/code/game/objects/items/devices/suit_cooling.dm index d32a4a0219a7..10718aac8cd0 100644 --- a/code/game/objects/items/devices/suit_cooling.dm +++ b/code/game/objects/items/devices/suit_cooling.dm @@ -217,7 +217,7 @@ /obj/item/suit_cooling_unit/emergency/updateicon() return -/obj/item/suit_cooling_unit/emergency/get_cell() +/obj/item/suit_cooling_unit/emergency/get_cell(inducer) if(on) return null // Don't let recharging happen while we're on return cell diff --git a/code/game/objects/items/inducer.dm b/code/game/objects/items/inducer.dm index d807d6a8631c..67b653310715 100644 --- a/code/game/objects/items/inducer.dm +++ b/code/game/objects/items/inducer.dm @@ -18,8 +18,6 @@ var/transfer_rate = 1000 /// type of cell to spawn var/cell_type = /obj/item/cell/high - /// our cell - var/obj/item/cell/cell /// panel open? var/opened = FALSE /// currently inducing? @@ -35,17 +33,24 @@ /obj/item/inducer/Initialize(mapload) . = ..() - if(!cell && cell_type) - cell = new cell_type + var/datum/object_system/cell_slot/cell_slot = init_cell_slot(cell_type) + cell_slot.receive_emp = TRUE + cell_slot.receive_inducer = TRUE + cell_slot.remove_yank_offhand = TRUE + cell_slot.remove_yank_context = TRUE + cell_slot.remove_yank_inhand = TRUE update_appearance() -/obj/item/inducer/get_cell() - return cell - -/obj/item/inducer/emp_act(severity) +/obj/item/inducer/examine(mob/user, dist) . = ..() - if(cell) - cell.emp_act(severity) + if(!isnull(obj_cell_slot.cell)) + . += "
Its display shows: [round(obj_cell_slot.cell.charge)] / [obj_cell_slot.cell.maxcharge]." + else + . += "
Its display is dark." + if(opened) + . += SPAN_NOTICE("Its battery compartment is open, and looks like it can be closed with a screwdriver") + else + . += SPAN_NOTICE("Its battery compartment is closed, and looks like it can be opened with a screwdriver") /obj/item/inducer/afterattack(atom/target, mob/user, clickchain_flags, list/params) if(user.a_intent == INTENT_HARM) @@ -59,16 +64,15 @@ to_chat(user, "You don't have the dexterity to use [src]!") return TRUE - if(!cell) + if(!obj_cell_slot.cell) to_chat(user, "[src] doesn't have a power cell installed!") return TRUE - if(!cell.charge) + if(!obj_cell_slot.cell.charge) to_chat(user, "[src]'s battery is dead!") return TRUE return FALSE - /obj/item/inducer/attackby(obj/item/W, mob/user) if(W.is_screwdriver()) playsound(src, W.tool_sound, 50, 1) @@ -82,24 +86,6 @@ opened = FALSE update_icon() return - if(istype(W, /obj/item/cell)) - if(opened) - if(!cell) - if(!user.attempt_insert_item_for_installation(W, src)) - return - to_chat(user, "You insert [W] into [src].") - cell = W - update_icon() - return - else - to_chat(user, "[src] already has \a [cell] installed!") - return - - if(cantbeused(user)) - return - - if(recharge(W, user)) - return return ..() @@ -142,8 +128,8 @@ var/datum/current = targets[1] targets.Cut(1, 2) - while(!QDELETED(A) && do_after(user, 2 SECONDS, A, DO_AFTER_IGNORE_MOVEMENT, max_distance = recharge_dist) && !QDELETED(cell)) - var/amount = min(cell.charge, transfer_rate * 2) // transfer rate is per second, we do this every 2 seconds + while(!QDELETED(A) && do_after(user, 2 SECONDS, A, DO_AFTER_IGNORE_MOVEMENT, max_distance = recharge_dist) && !QDELETED(obj_cell_slot.cell)) + var/amount = min(obj_cell_slot.cell.charge, transfer_rate * 2) // transfer rate is per second, we do this every 2 seconds var/charged = current.inducer_act(src, amount, inducer_flags) spark_system.start() if(charged == INDUCER_ACT_CONTINUE) @@ -152,7 +138,7 @@ break if(charged <= 0) break - cell.use(charged) + obj_cell_slot.cell.use(charged) used += charged qdel(spark_system) @@ -161,31 +147,22 @@ inducing = FALSE user.visible_message(SPAN_NOTICE("[user] recharged [A]."), SPAN_NOTICE("Rechraged [A] with [used] units of power.")) -/obj/item/inducer/attack_self(mob/user) +/obj/item/inducer/object_cell_slot_removed(obj/item/cell/cell, datum/object_system/cell_slot/slot) . = ..() - if(.) - return - if(opened && cell) - user.visible_message("[user] removes [cell] from [src]!", "You remove [cell].") - cell.update_icon() - user.put_in_hands_or_drop(cell) - cell = null - update_icon() + update_icon() -/obj/item/inducer/examine(mob/living/M) +/obj/item/inducer/object_cell_slot_inserted(obj/item/cell/cell, datum/object_system/cell_slot/slot) . = ..() - if(cell) - . += "
Its display shows: [round(cell.charge)] / [cell.maxcharge]." - else - . += "
Its display is dark." - if(opened) - . += "
Its battery compartment is open." + update_icon() + +/obj/item/inducer/object_cell_slot_mutable(mob/user, datum/object_system/cell_slot/slot) + return opened && ..() /obj/item/inducer/update_icon() ..() cut_overlays() if(opened) - if(!cell) + if(isnull(obj_cell_slot.cell)) add_overlay("inducer-nobat") else add_overlay("inducer-bat") @@ -237,7 +214,7 @@ * even if full, always add things, or the inducer might think we don't support induction when we do! */ /atom/proc/inducer_scan(obj/item/inducer/I, list/things_to_induce = list(), inducer_flags) - var/obj/item/cell/C = get_cell() + var/obj/item/cell/C = get_cell(TRUE) if(C) things_to_induce += C if(C.charge >= C.maxcharge) diff --git a/code/game/objects/items/tools/switchtool.dm b/code/game/objects/items/tools/switchtool.dm index 64a57623544d..e1420f552939 100644 --- a/code/game/objects/items/tools/switchtool.dm +++ b/code/game/objects/items/tools/switchtool.dm @@ -256,11 +256,11 @@ return "shield" //? tool redirection -/obj/item/switchtool/tool_check(function, mob/user, atom/target, flags, usage) +/obj/item/switchtool/tool_check(function, datum/event_args/actor/clickchain/e_args, atom/target, flags, usage) return (function in tool_functions)? tool_quality : null //? tool redirection -/obj/item/switchtool/tool_query(mob/user, atom/target, flags, usage) +/obj/item/switchtool/tool_query(datum/event_args/actor/clickchain/e_args, atom/target, flags, usage) . = list() for(var/i in tool_functions) .[i] = tool_quality diff --git a/code/game/objects/items/tools/weldingtool.dm b/code/game/objects/items/tools/weldingtool.dm index a080a6322f25..56273803514e 100644 --- a/code/game/objects/items/tools/weldingtool.dm +++ b/code/game/objects/items/tools/weldingtool.dm @@ -162,20 +162,20 @@ /obj/item/weldingtool/proc/get_max_fuel() return max_fuel -/obj/item/weldingtool/using_as_tool(function, flags, mob/user, atom/target, time, cost, usage) +/obj/item/weldingtool/using_as_tool(function, flags, datum/event_args/actor/clickchain/e_args, atom/target, time, cost, usage) . = ..() if(!. || function != TOOL_WELDER) return if(!isOn()) - user.action_feedback(SPAN_WARNING("[src] must be on to be used to weld!"), target) + e_args.chat_feedback(SPAN_WARNING("[src] must be on to be used to weld!"), target) return FALSE // floor var/computed = round(cost * time * TOOL_WELDING_FUEL_PER_DS) if(get_fuel() < computed) - user.action_feedback(SPAN_WARNING("[src] doesn't have enough fuel left to do that!"), target) + e_args.chat_feedback(SPAN_WARNING("[src] doesn't have enough fuel left to do that!"), target) return FALSE -/obj/item/weldingtool/used_as_tool(function, flags, mob/user, atom/target, time, cost, usage, success) +/obj/item/weldingtool/used_as_tool(function, flags, datum/event_args/actor/clickchain/e_args, atom/target, time, cost, usage, success) . = ..() if(!.) return @@ -570,7 +570,7 @@ power_supply = new /obj/item/cell/device(src) update_icon() -/obj/item/weldingtool/electric/get_cell() +/obj/item/weldingtool/electric/get_cell(inducer) return power_supply /obj/item/weldingtool/electric/examine(mob/user, dist) diff --git a/code/game/objects/items/uav.dm b/code/game/objects/items/uav.dm index 47b10a81b77f..43cca4dbc4b8 100644 --- a/code/game/objects/items/uav.dm +++ b/code/game/objects/items/uav.dm @@ -250,7 +250,7 @@ visible_message(SPAN_NOTICE("[nickname] gracefully settles onto the ground.")) //////////////// Helpers -/obj/item/uav/get_cell() +/obj/item/uav/get_cell(inducer) return cell /obj/item/uav/relaymove(var/mob/user, direction, signal = 1) diff --git a/code/game/objects/items/weapons/RCD.dm b/code/game/objects/items/weapons/RCD.dm index 7a0569b4c20e..da304f685f5e 100644 --- a/code/game/objects/items/weapons/RCD.dm +++ b/code/game/objects/items/weapons/RCD.dm @@ -291,7 +291,7 @@ QDEL_NULL(cell) return ..() -/obj/item/rcd/electric/get_cell() +/obj/item/rcd/electric/get_cell(inducer) return cell /obj/item/rcd/electric/can_afford(amount) // This makes it so borgs won't drain their last sliver of charge by mistake, as a bonus. @@ -323,7 +323,7 @@ desc = "A device used to rapidly build and deconstruct. It runs directly off of electricity from an external power source." make_cell = FALSE -/obj/item/rcd/electric/mounted/get_cell() +/obj/item/rcd/electric/mounted/get_cell(inducer) return get_external_power_supply() /obj/item/rcd/electric/mounted/proc/get_external_power_supply() diff --git a/code/game/objects/items/weapons/RPD.dm b/code/game/objects/items/weapons/RPD.dm index 0aa4275b216f..c8fc53f016af 100644 --- a/code/game/objects/items/weapons/RPD.dm +++ b/code/game/objects/items/weapons/RPD.dm @@ -352,9 +352,7 @@ playsound(src, 'sound/items/deconstruct.ogg', 50, TRUE) /obj/item/pipe_dispenser/proc/do_wrench(var/atom/target, mob/user) - var/resolved = target.attackby(tool,user) - if(!resolved && tool && target) - tool.afterattack(target,user,1) + tool.melee_attack_chain(target, user, CLICKCHAIN_HAS_PROXIMITY) /obj/item/pipe_dispenser/proc/mouse_wheeled(mob/user, atom/A, delta_x, delta_y, params) SIGNAL_HANDLER diff --git a/code/game/objects/items/weapons/melee/energy.dm b/code/game/objects/items/weapons/melee/energy.dm index 30eb2ca05c58..d2b96d3c40c0 100644 --- a/code/game/objects/items/weapons/melee/energy.dm +++ b/code/game/objects/items/weapons/melee/energy.dm @@ -145,7 +145,7 @@ return return ..() -/obj/item/melee/energy/get_cell() +/obj/item/melee/energy/get_cell(inducer) return bcell /obj/item/melee/energy/update_icon() diff --git a/code/game/objects/items/weapons/stunbaton.dm b/code/game/objects/items/weapons/stunbaton.dm index 97b65063c557..a2426135c332 100644 --- a/code/game/objects/items/weapons/stunbaton.dm +++ b/code/game/objects/items/weapons/stunbaton.dm @@ -30,7 +30,7 @@ . = ..() update_icon() -/obj/item/melee/baton/get_cell() +/obj/item/melee/baton/get_cell(inducer) return bcell /obj/item/melee/baton/suicide_act(mob/user) diff --git a/code/game/objects/objs.dm b/code/game/objects/objs.dm index 954db393c9b8..5019578d42aa 100644 --- a/code/game/objects/objs.dm +++ b/code/game/objects/objs.dm @@ -59,6 +59,10 @@ /// volume when breaking out using resist process var/breakout_volume = 100 + //? Systems - naming convention is 'object_[system]' + /// cell slot system + var/datum/object_system/cell_slot/obj_cell_slot + //? misc / legacy /// Set when a player renames a renamable object. var/renamed_by_player = FALSE @@ -105,6 +109,7 @@ unregister_dangerous_to_step() SStgui.close_uis(src) SSnanoui.close_uis(src) + QDEL_NULL(obj_cell_slot) return ..() /obj/Moved(atom/old_loc, movement_dir, forced, list/old_locs, momentum_change) @@ -238,6 +243,59 @@ add_fingerprint(user) ..() +//? Attacks + +/obj/attackby(obj/item/I, mob/user, list/params, clickchain_flags, damage_multiplier) + if(user.a_intent == INTENT_HARM) + return ..() + if(istype(I, /obj/item/cell) && !isnull(obj_cell_slot) && isnull(obj_cell_slot.cell) && obj_cell_slot.interaction_active(user)) + if(!user.transfer_item_to_loc(I, src)) + user.action_feedback(SPAN_WARNING("[I] is stuck to your hand!"), src) + return CLICKCHAIN_DO_NOT_PROPAGATE + user.visible_action_feedback( + target = src, + hard_range = obj_cell_slot.remove_is_discrete? 0 : MESSAGE_RANGE_CONSTRUCTION, + visible_hard = SPAN_NOTICE("[user] inserts [I] into [src]."), + audible_hard = SPAN_NOTICE("You hear something being slotted in."), + visible_self = SPAN_NOTICE("You insert [I] into [src]."), + ) + obj_cell_slot.insert_cell(I) + return CLICKCHAIN_DO_NOT_PROPAGATE | CLICKCHAIN_DID_SOMETHING + return ..() + +/obj/on_attack_hand(datum/event_args/actor/clickchain/e_args) + . = ..() + if(.) + return + if(!isnull(obj_cell_slot?.cell) && obj_cell_slot.remove_yank_offhand && e_args.performer.is_holding_inactive(src) && obj_cell_slot.interaction_active(e_args.performer)) + e_args.performer.visible_action_feedback( + target = src, + hard_range = obj_cell_slot.remove_is_discrete? 0 : MESSAGE_RANGE_CONSTRUCTION, + visible_hard = SPAN_NOTICE("[e_args.performer] removes the cell from [src]."), + audible_hard = SPAN_NOTICE("You hear fasteners falling out and something being removed."), + visible_self = SPAN_NOTICE("You remove the cell from [src]."), + ) + log_construction(e_args, src, "removed cell [obj_cell_slot.cell] ([obj_cell_slot.cell.type])") + e_args.performer.put_in_hands_or_drop(obj_cell_slot.remove_cell(e_args.performer)) + return TRUE + +//? Cells / Inducers + +/** + * get cell slot + */ +/obj/get_cell(inducer) + . = ..() + if(.) + return + if(obj_cell_slot?.primary && !isnull(obj_cell_slot.cell) && (!inducer || obj_cell_slot.receive_inducer)) + return obj_cell_slot.cell + +/obj/inducer_scan(obj/item/inducer/I, list/things_to_induce, inducer_flags) + . = ..() + if(!isnull(obj_cell_slot?.cell) && !obj_cell_slot.primary && obj_cell_slot.receive_inducer) + things_to_induce += obj_cell_slot.cell + //? Climbing /obj/MouseDroppedOn(atom/dropping, mob/user, proximity, params) @@ -368,7 +426,51 @@ H.update_health() */ -//* Hiding / Underfloor +//? Context + +/obj/context_query(datum/event_args/actor/e_args) + . = ..() + if(!isnull(obj_cell_slot?.cell) && obj_cell_slot.remove_yank_context && obj_cell_slot.interaction_active(e_args.performer)) + var/image/rendered = image(obj_cell_slot.cell) + .["obj_cell_slot"] = ATOM_CONTEXT_TUPLE("remove cell", rendered, null, MOBILITY_CAN_USE) + +/obj/context_act(datum/event_args/actor/e_args, key) + if(key == "obj_cell_slot") + var/reachability = e_args.performer.Reachability(src) + if(!reachability) + return TRUE + if(!CHECK_MOBILITY(e_args.performer, MOBILITY_CAN_USE)) + e_args.initiator.action_feedback(SPAN_WARNING("You can't do that right now!"), src) + return TRUE + if(isnull(obj_cell_slot.cell)) + e_args.initiator.action_feedback(SPAN_WARNING("[src] doesn't have a cell installed.")) + return TRUE + if(!obj_cell_slot.interaction_active(e_args.performer)) + return TRUE + e_args.visible_feedback( + target = src, + range = obj_cell_slot.remove_is_discrete? 0 : MESSAGE_RANGE_CONSTRUCTION, + visible = SPAN_NOTICE("[e_args.performer] removes the cell from [src]."), + audible = SPAN_NOTICE("You hear fasteners falling out and something being removed."), + otherwise_self = SPAN_NOTICE("You remove the cell from [src]."), + ) + log_construction(e_args, src, "removed cell [obj_cell_slot.cell] ([obj_cell_slot.cell.type])") + var/obj/item/cell/removed = obj_cell_slot.remove_cell(src) + if(reachability == REACH_PHYSICAL) + e_args.performer.put_in_hands_or_drop(removed) + else + removed.forceMove(drop_location()) + return TRUE + return ..() + +//? EMP + +/obj/emp_act(severity) + . = ..() + if(obj_cell_slot?.receive_emp) + obj_cell_slot?.cell?.emp_act(severity) + +//? Hiding / Underfloor /obj/proc/is_hidden_underfloor() return FALSE @@ -472,3 +574,38 @@ var/shake_dir = pick(-1, 1) animate(src, transform=turn(matrix(), 8*shake_dir), pixel_x=init_px + 2*shake_dir, time=1) animate(transform=null, pixel_x=init_px, time=6, easing=ELASTIC_EASING) + +//? Tool System + +/obj/dynamic_tool_query(obj/item/I, datum/event_args/actor/clickchain/e_args, list/hint_images = list()) + if(isnull(obj_cell_slot) || !obj_cell_slot.remove_tool_behavior || !obj_cell_slot.interaction_active(e_args.performer)) + return ..() + . = list() + LAZYSET(.[obj_cell_slot.remove_tool_behavior], "remove cell", dyntool_image_backward(obj_cell_slot.remove_tool_behavior)) + return merge_double_lazy_assoc_list(..(), .) + +/obj/tool_act(obj/item/I, datum/event_args/actor/clickchain/e_args, function, flags, hint) + if(isnull(obj_cell_slot) || (obj_cell_slot.remove_tool_behavior != function) || !obj_cell_slot.interaction_active(e_args.performer)) + return ..() + if(isnull(obj_cell_slot.cell)) + e_args.chat_feedback(SPAN_WARNING("[src] has no cell in it.")) + return CLICKCHAIN_DO_NOT_PROPAGATE + log_construction(e_args, src, "removing cell") + e_args.visible_feedback( + target = src, + range = obj_cell_slot.remove_is_discrete? 0 : MESSAGE_RANGE_CONSTRUCTION, + visible = SPAN_NOTICE("[e_args.performer] starts removing the cell from [src]."), + audible = SPAN_NOTICE("You hear fasteners being undone."), + otherwise_self = SPAN_NOTICE("You start removing the cell from [src]."), + ) + if(!use_tool(function, I, e_args, flags, obj_cell_slot.remove_tool_time, 1)) + return CLICKCHAIN_DO_NOT_PROPAGATE + log_construction(e_args, src, "removed cell") + e_args.visible_feedback( + target = src, + range = obj_cell_slot.remove_is_discrete? 0 : MESSAGE_RANGE_CONSTRUCTION, + visible = SPAN_NOTICE("[e_args.performer] removes the cell from [src]."), + audible = SPAN_NOTICE("You hear fasteners falling out and something being removed."), + otherwise_self = SPAN_NOTICE("You remove the cell from [src]."), + ) + return CLICKCHAIN_DID_SOMETHING | CLICKCHAIN_DO_NOT_PROPAGATE diff --git a/code/game/objects/structures/window.dm b/code/game/objects/structures/window.dm index f1d095eedbab..1e8b1d22cd9b 100644 --- a/code/game/objects/structures/window.dm +++ b/code/game/objects/structures/window.dm @@ -453,15 +453,15 @@ new shardtype(drop_location()) -/obj/structure/window/screwdriver_act(obj/item/I, mob/user, flags, hint) +/obj/structure/window/screwdriver_act(obj/item/I, datum/event_args/actor/clickchain/e_args, flags, hint) . = TRUE if (construction_state == WINDOW_STATE_UNSECURED || construction_state == WINDOW_STATE_SCREWED_TO_FLOOR || !considered_reinforced) - if (!use_screwdriver(I, user, flags)) + if (!use_screwdriver(I, e_args, flags)) return var/unsecuring = construction_state != WINDOW_STATE_UNSECURED - user.action_feedback(SPAN_NOTICE("You [unsecuring? "unfasten" : "fasten"] the frame [unsecuring? "from" : "to"] the floor."), src) + e_args.chat_feedback(SPAN_NOTICE("You [unsecuring? "unfasten" : "fasten"] the frame [unsecuring? "from" : "to"] the floor."), src) if (unsecuring) construction_state = WINDOW_STATE_UNSECURED set_anchored(FALSE) @@ -475,84 +475,82 @@ if (construction_state != WINDOW_STATE_CROWBRARED_IN && construction_state != WINDOW_STATE_SECURED_TO_FRAME) return - if (!use_screwdriver(I, user, flags)) + if (!use_screwdriver(I, e_args, flags)) return var/unsecuring = construction_state == WINDOW_STATE_SECURED_TO_FRAME - user.action_feedback(SPAN_NOTICE("You [unsecuring? "unfasten" : "fasten"] the window [unsecuring? "from" : "to"] the frame."), src) + e_args.chat_feedback(SPAN_NOTICE("You [unsecuring? "unfasten" : "fasten"] the window [unsecuring? "from" : "to"] the frame."), src) construction_state = unsecuring ? WINDOW_STATE_CROWBRARED_IN : WINDOW_STATE_SECURED_TO_FRAME -/obj/structure/window/crowbar_act(obj/item/I, mob/user, flags, hint) +/obj/structure/window/crowbar_act(obj/item/I, datum/event_args/actor/clickchain/e_args, flags, hint) . = TRUE if (!considered_reinforced) return if (construction_state != WINDOW_STATE_CROWBRARED_IN && construction_state != WINDOW_STATE_SCREWED_TO_FLOOR) return - if (!use_crowbar(I, user, flags)) + if (!use_crowbar(I, e_args, flags)) return var/unsecuring = construction_state == WINDOW_STATE_CROWBRARED_IN - user.action_feedback(SPAN_NOTICE("You pry [src] [unsecuring ? "out of" : "into"] the frame."), src) + e_args.chat_feedback(SPAN_NOTICE("You pry [src] [unsecuring ? "out of" : "into"] the frame."), src) construction_state = unsecuring ? WINDOW_STATE_SCREWED_TO_FLOOR : WINDOW_STATE_CROWBRARED_IN -/obj/structure/window/wrench_act(obj/item/I, mob/user, flags, hint) +/obj/structure/window/wrench_act(obj/item/I, datum/event_args/actor/clickchain/e_args, flags, hint) . = TRUE if (construction_state != WINDOW_STATE_UNSECURED) - user.action_feedback(SPAN_WARNING("[src] has to be entirely unfastened from the floor before you can disasemble it!")) + e_args.chat_feedback(SPAN_WARNING("[src] has to be entirely unfastened from the floor before you can disasemble it!")) return - if (!use_wrench(I, user, flags)) + if (!use_wrench(I, e_args, flags)) return - user.action_feedback(SPAN_NOTICE("You disassemble [src]."), src) + e_args.chat_feedback(SPAN_NOTICE("You disassemble [src]."), src) deconstruct(ATOM_DECONSTRUCT_DISASSEMBLED) -/obj/structure/window/dynamic_tool_functions(obj/item/I, mob/user) +/obj/structure/window/dynamic_tool_query(obj/item/I, datum/event_args/actor/clickchain/e_args, list/hint_images = list()) if (construction_state == WINDOW_STATE_UNSECURED) . = list( - TOOL_SCREWDRIVER = TOOL_HINT_SCREWING_WINDOW_FRAME, - TOOL_WRENCH + TOOL_SCREWDRIVER = list( + "fasten frame" = dyntool_image_forward(TOOL_SCREWDRIVER), + ), + TOOL_WRENCH = list( + "deconstruct" = dyntool_image_backward(TOOL_WRENCH), + ), ) else if (!considered_reinforced) . = list( - TOOL_SCREWDRIVER = TOOL_HINT_UNSCREWING_WINDOW_FRAME + TOOL_SCREWDRIVER = list( + "unfasten frame" = dyntool_image_backward(TOOL_SCREWDRIVER), + ), ) else switch (construction_state) if (WINDOW_STATE_SCREWED_TO_FLOOR) . = list( - TOOL_SCREWDRIVER = TOOL_HINT_UNSCREWING_WINDOW_FRAME, - TOOL_CROWBAR = TOOL_HINT_CROWBAR_WINDOW_IN + TOOL_SCREWDRIVER = list( + "unfasten frame" = dyntool_image_backward(TOOL_SCREWDRIVER), + ), + TOOL_CROWBAR = list( + "seat pane" = dyntool_image_forward(TOOL_CROWBAR), + ), ) if (WINDOW_STATE_CROWBRARED_IN) . = list( - TOOL_SCREWDRIVER = TOOL_HINT_SCREWING_WINDOW_PANE, - TOOL_CROWBAR = TOOL_HINT_CROWBAR_WINDOW_OUT + TOOL_SCREWDRIVER = list( + "fasten pane" = dyntool_image_forward(TOOL_SCREWDRIVER), + ), + TOOL_CROWBAR = list( + "unseat pane" = dyntool_image_backward(TOOL_CROWBAR), + ), ) if (WINDOW_STATE_SECURED_TO_FRAME) . = list( - TOOL_SCREWDRIVER = TOOL_HINT_UNSCREWING_WINDOW_PANE + TOOL_SCREWDRIVER = list( + "unfasten pane" = dyntool_image_backward(TOOL_SCREWDRIVER), + ), ) return merge_double_lazy_assoc_list(., ..()) - -/obj/structure/window/dynamic_tool_image(function, hint) - switch (hint) - if (TOOL_HINT_CROWBAR_WINDOW_IN) - return dyntool_image_forward(TOOL_CROWBAR) - if (TOOL_HINT_CROWBAR_WINDOW_OUT) - return dyntool_image_backward(TOOL_CROWBAR) - if (TOOL_HINT_SCREWING_WINDOW_FRAME) - return dyntool_image_forward(TOOL_SCREWDRIVER) - if (TOOL_HINT_UNSCREWING_WINDOW_FRAME) - return dyntool_image_backward(TOOL_SCREWDRIVER) - if (TOOL_HINT_SCREWING_WINDOW_PANE) - return dyntool_image_forward(TOOL_SCREWDRIVER) - if (TOOL_HINT_UNSCREWING_WINDOW_PANE) - return dyntool_image_backward(TOOL_SCREWDRIVER) - return ..() - - //This proc is used to update the icons of nearby windows. /obj/structure/window/proc/update_nearby_icons() update_appearance() diff --git a/code/game/objects/systems/_system.dm b/code/game/objects/systems/_system.dm new file mode 100644 index 000000000000..c47ad87d80ce --- /dev/null +++ b/code/game/objects/systems/_system.dm @@ -0,0 +1,16 @@ +/** + * just the base type of object systems + * + * components are just terrible API, inefficient, and obnoxious sometimes + * /obj systems are the replacement for stuff like storage, cell slots, etc + * + * they are singletons on /obj level. + */ +/datum/object_system + abstract_type = /datum/object_system + + /// owning object + var/obj/parent + +/datum/object_system/New(obj/parent) + src.parent = parent diff --git a/code/game/objects/systems/cell_slot.dm b/code/game/objects/systems/cell_slot.dm new file mode 100644 index 000000000000..bf4e4e138807 --- /dev/null +++ b/code/game/objects/systems/cell_slot.dm @@ -0,0 +1,100 @@ +/** + * cell slot + */ +/datum/object_system/cell_slot + /// held cell + var/obj/item/cell/cell + /// reserved - cell type accepted enum, for when we do large/medium/small/etc cells later. + var/cell_type + /// considered primary? if so, we get returned on get_cell() + var/primary = TRUE + /// allow inducer? + var/receive_inducer = FALSE + /// allow EMPs to hit? + var/receive_emp = FALSE + /// allow explosions to hit cell? + // todo: currently unused + var/recieve_explosion = FALSE + /// allow quick removal by clicking with hand? + var/remove_yank_offhand = FALSE + /// allow context menu removal? + var/remove_yank_context = FALSE + /// allow quick removal by using in hand? + var/remove_yank_inhand = FALSE + /// no-tool time for removal, if any + var/remove_yank_time = 0 + /// tool behavior for removal, if any + var/remove_tool_behavior = null + /// tool time for removal, if any + var/remove_tool_time = 0 + /// removal / insertion is discrete or loud + var/remove_is_discrete = TRUE + /// legacy + // todo: kill this + var/legacy_use_device_cells = FALSE + +/datum/object_system/cell_slot/proc/accepts_cell(obj/item/cell/cell) + return legacy_use_device_cells? istype(cell, /obj/item/cell/device) : TRUE + +/datum/object_system/cell_slot/proc/remove_cell(atom/new_loc) + if(isnull(cell)) + return + . = cell + if(cell.loc != new_loc) + cell.forceMove(new_loc) + cell = null + parent.object_cell_slot_removed(., src) + +/datum/object_system/cell_slot/proc/insert_cell(obj/item/cell/cell) + if(!isnull(cell)) + . = remove_cell(parent.drop_location()) + src.cell = cell + if(cell.loc != parent) + cell.forceMove(parent) + parent.object_cell_slot_inserted(cell, src) + +/datum/object_system/cell_slot/proc/interaction_active(mob/user) + return parent.object_cell_slot_mutable(user, src) + +//? Hooks + +/** + * hook called on cell slot removal + */ +/obj/proc/object_cell_slot_removed(obj/item/cell/cell, datum/object_system/cell_slot/slot) + return + +/** + * hook called on cell slot insertion + */ +/obj/proc/object_cell_slot_inserted(obj/item/cell/cell, datum/object_system/cell_slot/slot) + return + +/** + * hook called to check if cell slot removal behavior is active + */ +/obj/proc/object_cell_slot_mutable(mob/user, datum/object_system/cell_slot/slot) + return TRUE + +//? Lazy wrappers for init + +/obj/proc/init_cell_slot(initial_cell_path) + RETURN_TYPE(/datum/object_system/cell_slot) + ASSERT(isnull(obj_cell_slot)) + obj_cell_slot = new(src) + if(initial_cell_path) + obj_cell_slot.cell = new initial_cell_path + return obj_cell_slot + +/obj/proc/init_cell_slot_easy_tool(initial_cell_path, offhand_removal = TRUE, inhand_removal = FALSE) + RETURN_TYPE(/datum/object_system/cell_slot) + if(isnull(init_cell_slot(initial_cell_path))) + return + if(offhand_removal) + obj_cell_slot.remove_yank_offhand = TRUE + if(inhand_removal) + obj_cell_slot.remove_yank_inhand = FALSE + obj_cell_slot.remove_yank_context = TRUE + obj_cell_slot.remove_yank_time = 0 + obj_cell_slot.legacy_use_device_cells = TRUE + return obj_cell_slot diff --git a/code/game/rendering/legacy/radial.dm b/code/game/rendering/legacy/radial.dm index a75870ad3a52..e470eeaba274 100644 --- a/code/game/rendering/legacy/radial.dm +++ b/code/game/rendering/legacy/radial.dm @@ -234,8 +234,12 @@ GLOBAL_LIST_EMPTY(radial_menus) choices += id choices_values[id] = E if(new_choices[E]) - var/I = extract_image(new_choices[E]) + var/image/I = extract_image(new_choices[E]) if(I) + //! perform fixup + I.plane = FLOAT_PLANE + I.layer = FLOAT_LAYER + //! end choices_icons[id] = I setup_menu(use_tooltips) @@ -314,3 +318,13 @@ GLOBAL_LIST_EMPTY(radial_menus) QDEL_NULL(menu) GLOB.radial_menus -= uniqueid return answer + +/datum/radial_menu/context_menu + /// host atom + var/atom/host + // todo: this needs such a drastic fucking refactor along with the rest of this file i'm going to scream + +/datum/radial_menu/context_menu/Destroy() + LAZYREMOVE(host.context_menus, current_user) + host = null + return ..() diff --git a/code/game/vehicles/vehicle.dm b/code/game/vehicles/vehicle.dm index 16c57f5a5618..14d16ccbd38f 100644 --- a/code/game/vehicles/vehicle.dm +++ b/code/game/vehicles/vehicle.dm @@ -187,5 +187,5 @@ mecha.cell.maxcharge -= min(20,mecha.cell.maxcharge) return -/obj/vehicle/get_cell() +/obj/vehicle/get_cell(inducer) return cell diff --git a/code/modules/atmospherics/machinery/components/component.dm b/code/modules/atmospherics/machinery/components/component.dm index e2ffac8c1685..91d8305d24dc 100644 --- a/code/modules/atmospherics/machinery/components/component.dm +++ b/code/modules/atmospherics/machinery/components/component.dm @@ -125,21 +125,23 @@ ui = new(user, src, tgui_interface) ui.open() -/obj/machinery/atmospherics/component/multitool_act(obj/item/I, mob/user, flags, hint) +/obj/machinery/atmospherics/component/multitool_act(obj/item/I, datum/event_args/actor/clickchain/e_args, flags, hint) . = ..() if(.) return if(isnull(default_multitool_hijack)) return FALSE if(hijack_require_exposed && is_hidden_underfloor()) - user.action_feedback(SPAN_WARNING("You can't reach the controls of [src] while it's covered by flooring."), src) + e_args.chat_feedback(SPAN_WARNING("You can't reach the controls of [src] while it's covered by flooring."), src) return TRUE - user.visible_action_feedback( + e_args.visible_feedback( target = src, - hard_range = MESSAGE_RANGE_CONFIGURATION, - visible_hard = SPAN_WARNING("[user] starts tinkering with [src] using their [I]!"), + range = MESSAGE_RANGE_CONFIGURATION, + visible = SPAN_WARNING("[e_args.performer] starts tinkering with [src] using their [I]!"), + otherwise_self = SPAN_WARNING("You start tinkering with [src] using your [I]..."), ) - if(!do_after(user, default_multitool_hijack, src, mobility_flags = MOBILITY_CAN_USE)) + if(!do_after(e_args.performer, default_multitool_hijack, src, mobility_flags = MOBILITY_CAN_USE, progress_instance = create_actor_progress_bar(e_args))) return TRUE - ui_interact(user) + // todo: uh, this obviously needs a wrapper + ui_interact(e_args.initiator) return TRUE diff --git a/code/modules/client/client.dm b/code/modules/client/client.dm index 915bb8a0680d..57b2b0908a09 100644 --- a/code/modules/client/client.dm +++ b/code/modules/client/client.dm @@ -52,6 +52,10 @@ /// panic bunker is still resolving var/panic_bunker_pending = FALSE + //? Context Menus + /// open context menu + var/datum/radial_menu/context_menu/context_menu + //? Rendering /// Click catcher var/atom/movable/screen/click_catcher/click_catcher diff --git a/code/modules/client/client_procs.dm b/code/modules/client/client_procs.dm index 376641d2b80e..23dc06cc8aeb 100644 --- a/code/modules/client/client_procs.dm +++ b/code/modules/client/client_procs.dm @@ -359,6 +359,8 @@ return ..() /client/Destroy() + // get rid of context menus + QDEL_NULL(context_menu) // Unregister globals GLOB.clients -= src GLOB.directory -= ckey diff --git a/code/modules/fishing/aquarium/aquarium.dm b/code/modules/fishing/aquarium/aquarium.dm index 156c2f2fefc5..51ff4fd5641e 100644 --- a/code/modules/fishing/aquarium/aquarium.dm +++ b/code/modules/fishing/aquarium/aquarium.dm @@ -118,22 +118,24 @@ update_appearance() return TRUE -/obj/structure/aquarium/dynamic_tool_functions(obj/item/I, mob/user) +/obj/structure/aquarium/dynamic_tool_query(obj/item/I, datum/event_args/actor/clickchain/e_args, list/hint_images = list()) . = ..() if(allow_unanchor) - .[TOOL_WRENCH] = anchored? "anchor" : "unanchor" + LAZYSET(.[TOOL_WRENCH], anchored? "unanchor" : "anchor", anchored? dyntool_image_backward(TOOL_WRENCH) : dyntool_image_forward(TOOL_WRENCH)) -/obj/structure/aquarium/dynamic_tool_image(function, hint) - switch(function) - if(TOOL_WRENCH) - return anchored? dyntool_image_backward(TOOL_WRENCH) : dyntool_image_forward(TOOL_WRENCH) - return ..() - -/obj/structure/aquarium/wrench_act(obj/item/I, mob/user, flags, hint) +/obj/structure/aquarium/wrench_act(obj/item/I, datum/event_args/actor/clickchain/e_args, flags, hint) if(!allow_unanchor) return ..() - if(use_wrench(I, user, delay = 4 SECONDS)) - user.visible_message(SPAN_NOTICE("[user] [anchored? "fastens [src] to the ground" : "unfastens [src] from the ground"]."), range = MESSAGE_RANGE_CONSTRUCTION) + if(use_wrench(I, e_args, delay = 4 SECONDS)) + log_construction(e_args.performer, src, "fastened") + set_anchored(!anchored) + e_args.visible_feedback( + target = src, + range = MESSAGE_RANGE_CONSTRUCTION, + visible = SPAN_NOTICE("[e_args.performer] [anchored? "fastens [src] to the ground" : "unfastens [src] from the ground"]."), + audible = SPAN_WARNING("You hear bolts being [anchored? "fastened" : "unfastened"]"), + otherwise_self = SPAN_NOTICE("You [anchored? "fasten" : "unfasten"] [src]."), + ) return TRUE return ..() diff --git a/code/modules/fishing/equipment/rod.dm b/code/modules/fishing/equipment/rod.dm index d2e3ee710661..4af1ad639709 100644 --- a/code/modules/fishing/equipment/rod.dm +++ b/code/modules/fishing/equipment/rod.dm @@ -80,8 +80,11 @@ SStgui.update_uis(src) update_icon() -/obj/item/fishing_rod/on_attack_self(mob/user) - reel(user) +/obj/item/fishing_rod/on_attack_self(datum/event_args/actor/e_args) + . = ..() + if(.) + return + reel(e_args.performer) /obj/item/fishing_rod/proc/reel(mob/user, atom/target) // signal first for fishing minigame diff --git a/code/modules/hardsuits/_rig.dm b/code/modules/hardsuits/_rig.dm index e744deb0b9af..e7535c476e5f 100644 --- a/code/modules/hardsuits/_rig.dm +++ b/code/modules/hardsuits/_rig.dm @@ -117,7 +117,7 @@ var/sprint_slowdown_modifier = 0 // Sprinter module modifier. -/obj/item/hardsuit/get_cell() +/obj/item/hardsuit/get_cell(inducer) return cell /obj/item/hardsuit/examine(mob/user, dist) diff --git a/code/modules/integrated_electronics/core/assemblies.dm b/code/modules/integrated_electronics/core/assemblies.dm index 82a7de071c85..2f4f13d4d817 100644 --- a/code/modules/integrated_electronics/core/assemblies.dm +++ b/code/modules/integrated_electronics/core/assemblies.dm @@ -149,7 +149,7 @@ /obj/item/electronic_assembly/proc/check_interactivity(mob/user) return ui_status(user, GLOB.physical_state) == UI_INTERACTIVE -/obj/item/electronic_assembly/get_cell() +/obj/item/electronic_assembly/get_cell(inducer) return battery // TGUI diff --git a/code/modules/logging/logging.dm b/code/modules/logging/logging.dm index 53ef337d6fec..dd3ca429729d 100644 --- a/code/modules/logging/logging.dm +++ b/code/modules/logging/logging.dm @@ -22,12 +22,22 @@ /** * Log construction action + * + * todo: log initiator */ -/proc/log_construction(mob/user, atom/target, message) - log_game("CONSTRUCTION: [key_name(user)] [COORD(user)] -> [target] [COORD(target)]: [message]") +/proc/log_construction(datum/event_args/actor/e_args, atom/target, message) + log_game("CONSTRUCTION: [key_name(e_args.performer)] [COORD(e_args.performer)] -> [target] [COORD(target)]: [message]") + +/** + * log click - context menu + */ +/proc/log_click_context(datum/event_args/actor/e_args, atom/target, message) + log_click("CONTEXT: [key_name(e_args.initiator)][e_args.performer != e_args.initiator? " via [key_name(e_args.performer)]" : ""] -> [target] [AUDIT_COORD(target)]: [message]") /** * Log stack crafting + * + * todo: log initiator */ /proc/log_stackcrafting(mob/user, obj/item/stack/stack, name, amount, used, turf/where = get_turf(user)) log_game("STACKCRAFT: [key_name(user)] crafted [amount] of [name] with [used] of [stack] at [where]") diff --git a/code/modules/mining/drilling/drill.dm b/code/modules/mining/drilling/drill.dm index 59b97d13a2f6..2ef6a7d542f5 100644 --- a/code/modules/mining/drilling/drill.dm +++ b/code/modules/mining/drilling/drill.dm @@ -54,7 +54,7 @@ ) RefreshParts() -/obj/machinery/mining/drill/get_cell() +/obj/machinery/mining/drill/get_cell(inducer) return cell /obj/machinery/mining/drill/process(delta_time) diff --git a/code/modules/mob/inventory/items.dm b/code/modules/mob/inventory/items.dm index aa38b32e5929..2abb30e06e34 100644 --- a/code/modules/mob/inventory/items.dm +++ b/code/modules/mob/inventory/items.dm @@ -101,6 +101,9 @@ if(zoom) zoom() //binoculars, scope, etc + // close context menus + context_close() + return ((. & COMPONENT_ITEM_DROPPED_RELOCATE)? ITEM_RELOCATED_BY_DROPPED : NONE) /** diff --git a/code/modules/mob/living/silicon/robot/robot.dm b/code/modules/mob/living/silicon/robot/robot.dm index 80259f1ef5b4..740c7c9122a9 100644 --- a/code/modules/mob/living/silicon/robot/robot.dm +++ b/code/modules/mob/living/silicon/robot/robot.dm @@ -1071,7 +1071,7 @@ to_chat(src, "Module isn't activated") installed_modules() return 1 - + if(href_list["character_profile"]) if(!profile) profile = new(src) @@ -1403,7 +1403,7 @@ /mob/living/silicon/robot/is_sentient() return braintype != BORG_BRAINTYPE_DRONE -/mob/living/silicon/robot/get_cell() +/mob/living/silicon/robot/get_cell(inducer) return cell /mob/living/silicon/robot/verb/robot_nom(var/mob/living/T in living_mobs(1)) diff --git a/code/modules/mob/login.dm b/code/modules/mob/login.dm index 5d0c48e5e963..1820d5514afe 100644 --- a/code/modules/mob/login.dm +++ b/code/modules/mob/login.dm @@ -27,6 +27,9 @@ update_Login_details() world.update_status() + // get rid of old context menus + QDEL_NULL(client.context_menu) + client.images = list() //remove the images such as AIs being unable to see runes client.screen = list() //remove hud items just in case if(hud_used) diff --git a/code/modules/power/apc.dm b/code/modules/power/apc.dm index 2e33634e47b5..4c9448b1322d 100644 --- a/code/modules/power/apc.dm +++ b/code/modules/power/apc.dm @@ -400,7 +400,7 @@ GLOBAL_LIST_EMPTY(apcs) return ..() -/obj/machinery/power/apc/get_cell() +/obj/machinery/power/apc/get_cell(inducer) return cell // APCs are pixel-shifted, so they need to be updated. diff --git a/code/modules/power/cell.dm b/code/modules/power/cell.dm index f357acf48c95..27014e8c924c 100644 --- a/code/modules/power/cell.dm +++ b/code/modules/power/cell.dm @@ -48,7 +48,7 @@ /obj/item/cell/get_rating() return rating -/obj/item/cell/get_cell() +/obj/item/cell/get_cell(inducer) return src /obj/item/cell/process(delta_time) diff --git a/code/modules/power/lighting/lighting.dm b/code/modules/power/lighting/lighting.dm index 2487b2f2cecf..dd33a01955c4 100644 --- a/code/modules/power/lighting/lighting.dm +++ b/code/modules/power/lighting/lighting.dm @@ -632,7 +632,7 @@ var/global/list/light_type_cache = list() on = (s && status == LIGHT_OK) update() -/obj/machinery/light/get_cell() +/obj/machinery/light/get_cell(inducer) return cell // examine verb diff --git a/code/modules/projectiles/ammunition/ammo_casing.dm b/code/modules/projectiles/ammunition/ammo_casing.dm index af3afd358323..cb3a53a228dc 100644 --- a/code/modules/projectiles/ammunition/ammo_casing.dm +++ b/code/modules/projectiles/ammunition/ammo_casing.dm @@ -48,21 +48,23 @@ setDir(pick(GLOB.cardinal)) //spin spent casings update_icon() -/obj/item/ammo_casing/screwdriver_act(obj/item/I, mob/user, flags, hint) +/obj/item/ammo_casing/screwdriver_act(obj/item/I, datum/event_args/actor/clickchain/e_args, flags, hint) . = TRUE if(!stored) - user.action_feedback(SPAN_WARNING("There is no bullet in [src] to inscribe."), src) + e_args.chat_feedback(SPAN_WARNING("There is no bullet in [src] to inscribe."), src) + return + var/label_text = input(e_args.initiator, "Inscribe some text into [initial(stored.name)]", "Inscription", stored.name) + if(!e_args.performer.Adjacent(src)) return - var/label_text = input(user, "Inscribe some text into [initial(stored.name)]", "Inscription", stored.name) label_text = sanitize(label_text, MAX_NAME_LEN, extra = FALSE) if(!label_text) - user.action_feedback(SPAN_NOTICE("You scratch the inscription off of [initial(stored.name)]."), src) + e_args.chat_feedback(SPAN_NOTICE("You scratch the inscription off of [initial(stored.name)]."), src) stored.name = initial(stored.name) return - user.action_feedback(SPAN_NOTICE("You inscribe [label_text] into \the [initial(stored.name)]."), src) + e_args.chat_feedback(SPAN_NOTICE("You inscribe [label_text] into \the [initial(stored.name)]."), src) stored.name = "[initial(stored.name)] (\"[label_text]\")" -/obj/item/ammo_casing/dynamic_tool_functions(obj/item/I, mob/user) +/obj/item/ammo_casing/dynamic_tool_query(obj/item/I, datum/event_args/actor/clickchain/e_args, list/hint_images = list()) . = list( TOOL_SCREWDRIVER = list( "etch" diff --git a/code/modules/projectiles/guns/energy.dm b/code/modules/projectiles/guns/energy.dm index 34a4fa476934..5d3d7e0718bf 100644 --- a/code/modules/projectiles/guns/energy.dm +++ b/code/modules/projectiles/guns/energy.dm @@ -46,7 +46,7 @@ STOP_PROCESSING(SSobj, src) return ..() -/obj/item/gun/energy/get_cell() +/obj/item/gun/energy/get_cell(inducer) return power_supply /obj/item/gun/energy/process(delta_time) diff --git a/code/modules/projectiles/guns/magnetic/magnetic.dm b/code/modules/projectiles/guns/magnetic/magnetic.dm index fd78f7aee3d1..ec96d1d51869 100644 --- a/code/modules/projectiles/guns/magnetic/magnetic.dm +++ b/code/modules/projectiles/guns/magnetic/magnetic.dm @@ -35,7 +35,7 @@ QDEL_NULL(capacitor) . = ..() -/obj/item/gun/magnetic/get_cell() +/obj/item/gun/magnetic/get_cell(inducer) return cell /obj/item/gun/magnetic/process(delta_time) diff --git a/code/modules/projectiles/magazines/smartmag.dm b/code/modules/projectiles/magazines/smartmag.dm index 91abc9552a02..36f372c37b1f 100644 --- a/code/modules/projectiles/magazines/smartmag.dm +++ b/code/modules/projectiles/magazines/smartmag.dm @@ -129,7 +129,7 @@ attached_cell.emp_act(severity) // Finds the cell for the magazine, used by rechargers -/obj/item/ammo_magazine/smart/get_cell() +/obj/item/ammo_magazine/smart/get_cell(inducer) return attached_cell // Removes energy from the attached cell when creating new bullets diff --git a/code/modules/sculpting/sculpting_block.dm b/code/modules/sculpting/sculpting_block.dm index 899e34465699..c3cf53eb47ee 100644 --- a/code/modules/sculpting/sculpting_block.dm +++ b/code/modules/sculpting/sculpting_block.dm @@ -128,53 +128,53 @@ initiate_sculpting(user, tool = I) return CLICKCHAIN_DO_NOT_PROPAGATE -/obj/structure/sculpting_block/wrench_act(obj/item/I, mob/user, flags, hint) +/obj/structure/sculpting_block/wrench_act(obj/item/I, datum/event_args/actor/clickchain/e_args, flags, hint) . = ..() if(.) return - user.visible_action_feedback( + e_args.visible_feedback( target = src, - hard_range = MESSAGE_RANGE_CONSTRUCTION, - visible_hard = SPAN_NOTICE("[user] starts [anchored? "unbolting [src] from the floor" : "bolting [src] to the floor"]."), - visible_self = SPAN_NOTICE("You start [anchored? "unbolting [src] from the floor" : "bolting [src] to the floor"]."), - audible_hard = SPAN_WARNING("You hear bolts being [anchored? "unfastened" : "fastened"]."), + range = MESSAGE_RANGE_CONSTRUCTION, + visible = SPAN_NOTICE("[e_args.performer] starts [anchored? "unbolting [src] from the floor" : "bolting [src] to the floor"]."), + audible = SPAN_WARNING("You hear bolts being [anchored? "unfastened" : "fastened"]."), + otherwise_self = SPAN_NOTICE("You start [anchored? "unbolting [src] from the floor" : "bolting [src] to the floor"]."), ) - log_construction(user, src, "started [anchored? "unanchoring" : "anchoring"]") - if(!use_wrench(I, user, flags, 3 SECONDS)) + log_construction(e_args, src, "started [anchored? "unanchoring" : "anchoring"]") + if(!use_wrench(I, e_args, flags, 3 SECONDS)) return TRUE - user.visible_action_feedback( + e_args.visible_feedback( target = src, - hard_range = MESSAGE_RANGE_CONSTRUCTION, - visible_hard = SPAN_NOTICE("[user] finishes [anchored? "unbolting [src] from the floor" : "bolting [src] to the floor"]."), - visible_self = SPAN_NOTICE("You finish [anchored? "unbolting [src] from the floor" : "bolting [src] to the floor"]."), - audible_hard = SPAN_WARNING("You hear bolts [anchored? "falling out" : "clicking into place"]."), + range = MESSAGE_RANGE_CONSTRUCTION, + visible = SPAN_NOTICE("[e_args.performer] finishes [anchored? "unbolting [src] from the floor" : "bolting [src] to the floor"]."), + audible = SPAN_WARNING("You hear bolts [anchored? "falling out" : "clicking into place"]."), + otherwise_self = SPAN_NOTICE("You finish [anchored? "unbolting [src] from the floor" : "bolting [src] to the floor"]."), ) - log_construction(user, src, "[anchored? "unanchored" : "anchored"]") + log_construction(e_args, src, "[anchored? "unanchored" : "anchored"]") set_anchored(!anchored) return TRUE -/obj/structure/sculpting_block/welder_act(obj/item/I, mob/user, flags, hint) +/obj/structure/sculpting_block/welder_act(obj/item/I, datum/event_args/actor/clickchain/e_args, flags, hint) . = ..() if(.) return - user.visible_action_feedback( + e_args.visible_feedback( target = src, - hard_range = MESSAGE_RANGE_CONSTRUCTION, - visible_hard = SPAN_NOTICE("[user] starts slicing [src] apart."), - visible_self = SPAN_NOTICE("You start slicing [src] apart."), - audible_hard = SPAN_WARNING("You hear the sound of a welding torch being used on something metallic."), + range = MESSAGE_RANGE_CONSTRUCTION, + visible = SPAN_NOTICE("[e_args.performer] starts slicing [src] apart."), + audible = SPAN_WARNING("You hear the sound of a welding torch being used on something metallic."), + otherwise_self = SPAN_NOTICE("You start slicing [src] apart."), ) - log_construction(user, src, "started deconstructing") - if(!use_welder(I, user, flags, 7 SECONDS, 3)) + log_construction(e_args, src, "started deconstructing") + if(!use_welder(I, e_args, flags, 7 SECONDS, 3)) return TRUE - user.visible_action_feedback( + e_args.visible_feedback( target = src, - hard_range = MESSAGE_RANGE_CONSTRUCTION, - visible_hard = SPAN_NOTICE("[user] slices [src] apart."), - visible_self = SPAN_NOTICE("You slice [src] apart."), - audible_hard = SPAN_WARNING("You hear the sound of a welding torch moving back into open air, and a few pieces of metal falling apart."), + range = MESSAGE_RANGE_CONSTRUCTION, + visible = SPAN_NOTICE("[e_args.performer] slices [src] apart."), + audible = SPAN_WARNING("You hear the sound of a welding torch moving back into open air, and a few pieces of metal falling apart."), + otherwise_self = SPAN_NOTICE("You slice [src] apart."), ) - log_construction(user, src, "deconstructed") + log_construction(e_args, src, "deconstructed") set_anchored(!anchored) deconstruct(ATOM_DECONSTRUCT_DISASSEMBLED) return TRUE @@ -183,24 +183,12 @@ . = ..() material.place_sheet(drop_location(), 10) -/obj/structure/sculpting_block/dynamic_tool_functions(obj/item/I, mob/user) +/obj/structure/sculpting_block/dynamic_tool_query(obj/item/I, datum/event_args/actor/clickchain/e_args, list/hint_images = list()) . = list() - .[TOOL_WRENCH] = anchored? "unanchor" : "anchor" - .[TOOL_WELDER] = "deconstruct" + LAZYSET(.[TOOL_WRENCH], anchored? "unanchor" : "anchor", anchored? dyntool_image_backward(TOOL_WRENCH) : dyntool_image_forward(TOOL_WRENCH)) + LAZYSET(.[TOOL_WELDER], "deconstruct", dyntool_image_backward(TOOL_WELDER)) return merge_double_lazy_assoc_list(., ..()) -/obj/structure/sculpting_block/dynamic_tool_image(function, hint) - . = ..() - if(.) - return - switch(hint) - if("unanchor") - return dyntool_image_backward(TOOL_WRENCH) - if("anchor") - return dyntool_image_forward(TOOL_WRENCH) - if("deconstruct") - return dyntool_image_backward(TOOL_WELDER) - /** * returns speed multiplier, or null if not tool */ diff --git a/code/modules/shieldgen/handheld_defuser.dm b/code/modules/shieldgen/handheld_defuser.dm index edf509c4bda7..2c0d19c2b871 100644 --- a/code/modules/shieldgen/handheld_defuser.dm +++ b/code/modules/shieldgen/handheld_defuser.dm @@ -21,7 +21,7 @@ STOP_PROCESSING(SSobj, src) . = ..() -/obj/item/shield_diffuser/get_cell() +/obj/item/shield_diffuser/get_cell(inducer) return cell /obj/item/shield_diffuser/process(delta_time) diff --git a/code/modules/tools/_tool_system.dm b/code/modules/tools/_tool_system.dm index b9e2030c2d5d..e2d49b770d6a 100644 --- a/code/modules/tools/_tool_system.dm +++ b/code/modules/tools/_tool_system.dm @@ -1,3 +1,6 @@ +//* This file is explicitly licensed under the MIT license. *// +//* Copyright (c) 2023 Citadel Station developers. *// + /** * ? Atom Tool API * @@ -12,7 +15,7 @@ * * intended api for static tool usage: * - * - override necessary _act for that tool type + * * override necessary _act for that tool type * .../function_act(...) * if(use_(...)) * # success code @@ -21,11 +24,11 @@ * return TRUE // halt attack chain * * intended api for dynamic tool usage: - * - override dynamic_tool_functions() to return the functions and minimal qualities for a user - * - override dynamic_tool_act() if needed, otherwise it will simply go into tool_act - * - override dynamic_tool_image() to return the image to render for a specific tool function for radials - * - realistically, you just need to override dynamic_tool_functions. - * - if you don't override dynamic_tool_image you are a lemming and it'll probabl be ugly. + * * override dynamic_tool_query() to return the functions and minimal qualities for a user + * * override dynamic_tool_act() if needed, otherwise it will simply go into tool_act + * * override dynamic_tool_image() to return the image to render for a specific tool function for radials + * * realistically, you just need to override dynamic_tool_query. + * * if you don't override dynamic_tool_image you are a lemming and it'll probabl be ugly. * * It's That Simple (tm)! * @@ -41,47 +44,53 @@ * warning: this proc is not necessarily called only within clickcode. * * @params - * - I - the item - * - user - the user - * - clickchain_flags - the clickchain flags given - * - function - forced function - used in automation - * - hint - forced hint - used in automation - * - reachability_check - a callback used for reachability checks. if none, defaults to mob.Reachability when in clickcode, can always reach otherwise. + * * I - the item + * * user - the user + * * clickchain_flags - the clickchain flags given + * * function - forced function - used in automation + * * hint - forced hint - used in automation + * * reachability_check - a callback used for reachability checks. if none, defaults to mob.Reachability when in clickcode, can always reach otherwise. */ -/atom/proc/tool_interaction(obj/item/I, mob/user, clickchain_flags, function, hint, datum/callback/reachability_check) +/atom/proc/tool_interaction(obj/item/I, datum/event_args/actor/clickchain/e_args, clickchain_flags, function, hint, datum/callback/reachability_check) SHOULD_NOT_OVERRIDE(TRUE) - return _tool_interaction_entrypoint(I, user, clickchain_flags, function, hint, reachability_check) + return _tool_interaction_entrypoint(I, e_args, clickchain_flags, function, hint, reachability_check) -/atom/proc/_tool_interaction_entrypoint(obj/item/provided_item, mob/user, clickchain_flags, function, hint, datum/callback/reachability_check) +/atom/proc/_tool_interaction_entrypoint(obj/item/provided_item, datum/event_args/actor/clickchain/e_args, clickchain_flags, function, hint, datum/callback/reachability_check) SHOULD_NOT_OVERRIDE(TRUE) PRIVATE_PROC(TRUE) if(isnull(reachability_check)) if(clickchain_flags & CLICKCHAIN_TOOL_ACT) // provided_item should never be null - reachability_check = CALLBACK(user, TYPE_PROC_REF(/atom/movable, Reachability), src, null, provided_item.reach, provided_item) + // we inject our default reachability check if tool act is set by tool attack chain + // otherwise we just ignore it if it wasn't specified + reachability_check = CALLBACK(e_args.performer, TYPE_PROC_REF(/atom/movable, Reachability), src, null, provided_item.reach, provided_item) if(reachability_check && !reachability_check.Invoke()) return NONE // from click chain if(provided_item) if(function) // automation, just go - return _dynamic_tool_act(provided_item, user, function, TOOL_OP_AUTOPILOT | TOOL_OP_REAL, hint) + return _dynamic_tool_act(provided_item, e_args, function, TOOL_OP_AUTOPILOT | TOOL_OP_REAL, hint) // used in clickchain - var/list/possibilities = dynamic_tool_functions(provided_item, user) + // as of now, format is: + // function = hint OR list(hint, ...) + // hint images is passed in so it can be prebuilt by components + // more info is in comment of dynamic_tool_query + var/list/possibilities = dynamic_tool_query(provided_item, e_args) if(!length(possibilities) || (provided_item.tool_locked == TOOL_LOCKING_STATIC)) // no dynamic tool functionality, or dynamic functionality disabled, route normally. function = provided_item.tool_behaviour() if(!function) return NONE - return _tool_act(provided_item, user, function, TOOL_OP_REAL) + return _tool_act(provided_item, e_args, function, TOOL_OP_REAL) + var/list/functions = provided_item.tool_query(e_args, src) // enumerate - var/list/functions = provided_item.tool_query(user, src) if((provided_item.tool_locked == TOOL_LOCKING_AUTO) && (length(functions) == 1)) // use first function function = functions[1] - if(!(function in possibilities)) + if(isnull(possibilities[function])) // not found, route normally - return _tool_act(provided_item, user, function, TOOL_OP_REAL) + return _tool_act(provided_item, e_args, function, TOOL_OP_REAL) else for(var/i in possibilities) if(functions[i]) @@ -92,60 +101,66 @@ function = provided_item.tool_behaviour() if(!function) return NONE - return _tool_act(provided_item, user, function, TOOL_OP_REAL) + return _tool_act(provided_item, e_args, function, TOOL_OP_REAL) + // possibilities is now filtered // everything in possibilities is valid for the tool var/list/transformed = list() + // check if we have function locked already if(!function) // we're about to sleep; if we're already breaking from this, maybe like, don't - if(INTERACTING_WITH_FOR(user, src, INTERACTING_FOR_DYNAMIC_TOOL)) + if(INTERACTING_WITH_FOR(e_args.initiator, src, INTERACTING_FOR_DYNAMIC_TOOL)) return CLICKCHAIN_DO_NOT_PROPAGATE // if we didn't pick function already - for(var/i in possibilities) - // is there only one hint? - var/list/associated = possibilities[i] - if(associated && (!islist(associated) || (length(associated) == 1))) - // yes there is! - associated = islist(associated)? associated[1] : associated + for(var/potential_function in possibilities) + var/list/potential_hints = possibilities[potential_function] + var/image/radial_render + var/radial_text + if(length(potential_hints) == 1) + radial_text = potential_hints[1] + radial_render = potential_hints[radial_text] || dyntool_image_neutral(potential_function) else - associated = null - var/image/I = dynamic_tool_image(i, associated) - I.maptext = MAPTEXT_CENTER(associated || i) - I.maptext_x = -16 - I.maptext_y = 32 - I.maptext_width = 64 - transformed[i] = I + radial_text = potential_function + radial_render = dyntool_image_neutral(potential_function) + radial_render.maptext = MAPTEXT_CENTER(radial_text) + radial_render.maptext_x = -16 + radial_render.maptext_y = 32 + radial_render.maptext_width = 64 + transformed[potential_function] = radial_render // todo: radial menu at some point should be made to automatically close when they click something else. - START_INTERACTING_WITH(user, src, INTERACTING_FOR_DYNAMIC_TOOL) - function = show_radial_menu(user, src, transformed, custom_check = reachability_check) - STOP_INTERACTING_WITH(user, src, INTERACTING_FOR_DYNAMIC_TOOL) + // todo: the initiator/performer pattern doesn't work well with radial menu and interacting with + // todo: because there's no semantics for mutexing the performer vs the initator + // todo: in the future, we are going to want to get a proper mutex up for tool interactions + START_INTERACTING_WITH(e_args.initiator, src, INTERACTING_FOR_DYNAMIC_TOOL) + function = show_radial_menu(e_args.initiator, src, transformed, custom_check = reachability_check) + STOP_INTERACTING_WITH(e_args.initiator, src, INTERACTING_FOR_DYNAMIC_TOOL) if(!function || (reachability_check && !reachability_check.Invoke())) return CLICKCHAIN_DO_NOT_PROPAGATE // determine hint var/list/hints = possibilities[function] if(!islist(hints)) // is a direct hint or null - return _dynamic_tool_act(provided_item, user, function, TOOL_OP_REAL, hints) + return _dynamic_tool_act(provided_item, e_args, function, TOOL_OP_REAL, hints) else if(length(hints) <= 1) // no hint, or only one hint - return _dynamic_tool_act(provided_item, user, function, TOOL_OP_REAL, length(hints)? hints[1] : null) + return _dynamic_tool_act(provided_item, e_args, function, TOOL_OP_REAL, length(hints)? hints[1] : null) // we're about to sleep; if we're already breaking from this, maybe like, don't - if(INTERACTING_WITH_FOR(user, src, INTERACTING_FOR_DYNAMIC_TOOL)) + if(INTERACTING_WITH_FOR(e_args.initiator, src, INTERACTING_FOR_DYNAMIC_TOOL)) return CLICKCHAIN_DO_NOT_PROPAGATE transformed.len = 0 for(var/i in hints) - var/image/I = dynamic_tool_image(function, i) - I.maptext = MAPTEXT_CENTER(i) - I.maptext_x = -16 - I.maptext_y = 32 - I.maptext_width = 64 - transformed[i] = I - START_INTERACTING_WITH(user, src, INTERACTING_FOR_DYNAMIC_TOOL) - hint = show_radial_menu(user, src, transformed, custom_check = reachability_check) - STOP_INTERACTING_WITH(user, src, INTERACTING_FOR_DYNAMIC_TOOL) + var/image/radial_render = hints[i] || dyntool_image_neutral(function) + radial_render.maptext = MAPTEXT_CENTER(i) + radial_render.maptext_x = -16 + radial_render.maptext_y = 32 + radial_render.maptext_width = 64 + transformed[i] = radial_render + START_INTERACTING_WITH(e_args.initiator, src, INTERACTING_FOR_DYNAMIC_TOOL) + hint = show_radial_menu(e_args.initiator, src, transformed, custom_check = reachability_check) + STOP_INTERACTING_WITH(e_args.initiator, src, INTERACTING_FOR_DYNAMIC_TOOL) if(!hint || (reachability_check && !reachability_check.Invoke())) return CLICKCHAIN_DO_NOT_PROPAGATE // use hint - return _dynamic_tool_act(provided_item, user, function, TOOL_OP_REAL, hint) | CLICKCHAIN_DO_NOT_PROPAGATE + return _dynamic_tool_act(provided_item, e_args, function, TOOL_OP_REAL, hint) | CLICKCHAIN_DO_NOT_PROPAGATE else // in the future, we might have situations where clicking something with an empty hand // yet having organs that server as built-in tools can do something with @@ -153,98 +168,99 @@ return NONE //! Primary Tool API -/atom/proc/_tool_act(obj/item/I, mob/user, function, flags, hint) +/atom/proc/_tool_act(obj/item/I, datum/event_args/actor/clickchain/e_args, function, flags, hint) PRIVATE_PROC(TRUE) SHOULD_NOT_OVERRIDE(TRUE) - SEND_SIGNAL(src, COMSIG_ATOM_TOOL_ACT, I, user, function, flags, hint) - return tool_act(I, user, function, flags, hint) + if((. = SEND_SIGNAL(src, COMSIG_ATOM_TOOL_ACT, I, e_args, function, flags, hint)) & CLICKCHAIN_COMPONENT_SIGNAL_HANDLED) + return . & ~(CLICKCHAIN_COMPONENT_SIGNAL_HANDLED) + return tool_act(I, e_args, function, flags, hint) /** * primary proc to be used when calling an interaction with a tool with an atom * * everything in this proc and procs it calls: - * - should not verify that the item is on the user - * - can, but doesn't need to verify that the item has the function in question (assumed it does) - * - should not require a user to run (do not runtime without user) - * - should handle functions with helpers in tihs file whenever possibl + * * should not verify that the item is on the user + * * can, but doesn't need to verify that the item has the function in question (assumed it does) + * * should not require a user to run (do not runtime without user) + * * should handle functions with helpers in tihs file whenever possibl * * default behaviour: route the call to _act, which interrupts the melee attack chain if it returns TRUE. * * @params - * - I - the tool in question - * - user - the user in question, if they exist - * - function - the tool function used - * - flags - tool operation flags - * - hint - the operation hint, if the calling system is the dynamic tool system. + * * I - the tool in question + * * user - the user in question, if they exist + * * function - the tool function used + * * flags - tool operation flags + * * hint - the operation hint, if the calling system is the dynamic tool system. */ -/atom/proc/tool_act(obj/item/I, mob/user, function, flags, hint) +/atom/proc/tool_act(obj/item/I, datum/event_args/actor/clickchain/e_args, function, flags, hint) switch(function) if(TOOL_CROWBAR) - return crowbar_act(I, user, flags, hint)? CLICKCHAIN_DO_NOT_PROPAGATE : NONE + return crowbar_act(I, e_args, flags, hint)? CLICKCHAIN_DO_NOT_PROPAGATE : NONE if(TOOL_MULTITOOL) - return multitool_act(I, user, flags, hint)? CLICKCHAIN_DO_NOT_PROPAGATE : NONE + return multitool_act(I, e_args, flags, hint)? CLICKCHAIN_DO_NOT_PROPAGATE : NONE if(TOOL_SCREWDRIVER) - return screwdriver_act(I, user, flags, hint)? CLICKCHAIN_DO_NOT_PROPAGATE : NONE + return screwdriver_act(I, e_args, flags, hint)? CLICKCHAIN_DO_NOT_PROPAGATE : NONE if(TOOL_WRENCH) - return wrench_act(I, user, flags, hint)? CLICKCHAIN_DO_NOT_PROPAGATE : NONE + return wrench_act(I, e_args, flags, hint)? CLICKCHAIN_DO_NOT_PROPAGATE : NONE if(TOOL_WELDER) - return welder_act(I, user, flags, hint)? CLICKCHAIN_DO_NOT_PROPAGATE : NONE + return welder_act(I, e_args, flags, hint)? CLICKCHAIN_DO_NOT_PROPAGATE : NONE if(TOOL_WIRECUTTER) - return wirecutter_act(I, user, flags, hint)? CLICKCHAIN_DO_NOT_PROPAGATE : NONE + return wirecutter_act(I, e_args, flags, hint)? CLICKCHAIN_DO_NOT_PROPAGATE : NONE if(TOOL_ANALYZER) - return analyzer_act(I, user, flags, hint)? CLICKCHAIN_DO_NOT_PROPAGATE : NONE + return analyzer_act(I, e_args, flags, hint)? CLICKCHAIN_DO_NOT_PROPAGATE : NONE //? Add more tool_acts as necessary. /** * standard use tool * * @params - * - function - tool function - * - I - the tool - * - user - the person using it - * - flags - tool operation flags - * - delay - how long it'll take to use the tool - * - cost - optional; cost multiplier to the default cost of 1 per second. - * - usage - optional; usage flags for tool speed/quality checks. + * * function - tool function + * * I - the tool + * * user - the person using it + * * flags - tool operation flags + * * delay - how long it'll take to use the tool + * * cost - optional; cost multiplier to the default cost of 1 per second. + * * usage - optional; usage flags for tool speed/quality checks. */ -/atom/proc/use_tool_standard(function, obj/item/I, mob/user, flags, delay, cost, usage) - return use_tool(function, I, user, flags, delay, cost, usage) +/atom/proc/use_tool_standard(function, obj/item/I, datum/event_args/actor/clickchain/e_args, flags, delay, cost, usage) + return use_tool(function, I, e_args, flags, delay, cost, usage) /** * primary proc called by wrappers to use a tool on us * * @params - * - function - tool function - * - I - the tool - * - user - the person using it - * - flags - tool operation flags - * - delay - how long it'll take to use the tool - * - cost - optional; cost multiplier to the default cost of 1 per second. - * - usage - optional; usage flags for tool speed/quality checks. - * - volume - optional; volume override + * * function - tool function + * * I - the tool + * * user - the person using it + * * flags - tool operation flags + * * delay - how long it'll take to use the tool + * * cost - optional; cost multiplier to the default cost of 1 per second. + * * usage - optional; usage flags for tool speed/quality checks. + * * volume - optional; volume override */ -/atom/proc/use_tool(function, obj/item/I, mob/user, flags, delay, cost = 1, usage, volume) +/atom/proc/use_tool(function, obj/item/I, datum/event_args/actor/clickchain/e_args, flags, delay, cost = 1, usage, volume) SHOULD_NOT_OVERRIDE(TRUE) - var/quality = I.tool_check(function, user, src, flags, usage) + var/quality = I.tool_check(function, e_args, src, flags, usage) if(!quality) return FALSE - var/speed = I.tool_speed(function, user, src, flags, usage) + var/speed = I.tool_speed(function, e_args, src, flags, usage) //! this currently makes tools more efficient if you have more toolspeed. //! this is probably a bad thing. // todo: automatically adjust cost to rectify this. // todo: tool_cost()? potentially invert cost multiplier automagically, or invert it in this proc. delay = delay * speed - if(!I.using_as_tool(function, flags, user, src, delay, cost, usage)) + if(!I.using_as_tool(function, flags, e_args, src, delay, cost, usage)) return FALSE - I.tool_feedback_start(function, flags, user, src, delay, cost, usage, volume) - if(!do_after(user, delay, src)) - I.used_as_tool(function, flags, user, src, delay, cost, usage, FALSE) - I.tool_feedback_end(function, flags, user, src, delay, cost, usage, FALSE, volume) + I.tool_feedback_start(function, flags, e_args, src, delay, cost, usage, volume) + if(!do_after(e_args.performer, delay, src, progress_instance = create_actor_progress_bar(e_args, delay))) + I.used_as_tool(function, flags, e_args, src, delay, cost, usage, FALSE) + I.tool_feedback_end(function, flags, e_args, src, delay, cost, usage, FALSE, volume) return FALSE - if(!I.used_as_tool(function, flags, user, src, delay, cost, usage, TRUE)) - I.tool_feedback_end(function, flags, user, src, delay, cost, usage, FALSE, volume) + if(!I.used_as_tool(function, flags, e_args, src, delay, cost, usage, TRUE)) + I.tool_feedback_end(function, flags, e_args, src, delay, cost, usage, FALSE, volume) return FALSE - I.tool_feedback_end(function, flags, user, src, delay, cost, usage, TRUE, volume) + I.tool_feedback_end(function, flags, e_args, src, delay, cost, usage, TRUE, volume) return TRUE //! Dynamic Tool API @@ -252,25 +268,25 @@ * returns a list of behaviours that can be used on us in our current state * the behaviour may be associated to a list of "hints" for multiple possible actions per behaviour. * the hint should be human readable. - * associating directly to a single hint is allowed. + * hints can / should be associated to images for graphics, otherwise defaults to dyntool neutral images * * **warning**: by default, the provided list is mutable * if you're caching your own list, make sure to return cache.Copy()! * * @params - * - I - the tool used, if any - * - user - the user, if any + * * I - the tool used, if any + * * user - the user, if any */ -/atom/proc/dynamic_tool_functions(obj/item/I, mob/user) - // todo: signal - return list() +/atom/proc/dynamic_tool_query(obj/item/I, datum/event_args/actor/clickchain/e_args) + . = list() + SEND_SIGNAL(src, COMSIG_ATOM_TOOL_QUERY, I, e_args, .) -/atom/proc/_dynamic_tool_act(obj/item/I, mob/user, function, flags, hint) +/atom/proc/_dynamic_tool_act(obj/item/I, datum/event_args/actor/clickchain/e_args, function, flags, hint) PRIVATE_PROC(TRUE) SHOULD_NOT_OVERRIDE(TRUE) flags |= TOOL_OP_DYNAMIC - SEND_SIGNAL(src, COMSIG_ATOM_TOOL_ACT, I, user, function, flags, hint) - return dynamic_tool_act(I, user, function, flags, hint) + SEND_SIGNAL(src, COMSIG_ATOM_TOOL_ACT, I, e_args, function, flags, hint) + return dynamic_tool_act(I, e_args, function, flags, hint) /** * called when we are acted on by the dynamic tool system @@ -279,33 +295,11 @@ * this must return a set of clickchain flags! * * @params - * - I - the tool used - * - user - the user, if any - * - function - the tool behaviour used - * - flags - tool operation flags - * - hint - the hint of what operation to do + * * I - the tool used + * * user - the user, if any + * * function - the tool behaviour used + * * flags - tool operation flags + * * hint - the hint of what operation to do */ -/atom/proc/dynamic_tool_act(obj/item/I, mob/user, function, flags, hint) - return tool_act(I, user, function, flags, hint) - -/** - * builds the image used for the radial icon - * - * WARNING: If you use tool **and** hint, you need to implement a hintless, or return to base to use the default. - * - * @params - * - function - the tool behaviour - * - hint - the context provided when you want to implement multiple actions for a tool - */ -/atom/proc/dynamic_tool_image(function, hint) - return dyntool_image_neutral(function) - -//! Dynamic Tools - default images -/proc/dyntool_image_neutral(function) - return image('icons/screen/radial/tools/generic.dmi', icon_state = _dyntool_image_states[function] || "unknown") - -/proc/dyntool_image_forward(function) - return image('icons/screen/radial/tools/generic.dmi', icon_state = "[_dyntool_image_states[function] || "unknown"]_up") - -/proc/dyntool_image_backward(function) - return image('icons/screen/radial/tools/generic.dmi', icon_state = "[_dyntool_image_states[function] || "unknown"]_down") +/atom/proc/dynamic_tool_act(obj/item/I, datum/event_args/actor/clickchain/e_args, function, flags, hint) + return tool_act(I, e_args, function, flags, hint) diff --git a/code/modules/tools/items.dm b/code/modules/tools/items.dm index f35c57adfd94..a0c0bd467781 100644 --- a/code/modules/tools/items.dm +++ b/code/modules/tools/items.dm @@ -1,3 +1,6 @@ +//* This file is explicitly licensed under the MIT license. *// +//* Copyright (c) 2023 Citadel Station developers. *// + /** * item tool API: allows items to be one or more types of generic tool-functionalities * with arbitrary tool speeds and qualities, while allowing the item to hook usages. @@ -28,12 +31,12 @@ * that's where things like skill checks are done. * * @params - * - user - person using tool, can be null + * - e_args - person using tool, can be null * - target - atom being used on, can be null * - flags - tool operation flags * - usage - what we're being used for, bitfield */ -/obj/item/proc/tool_query(mob/user, atom/target, flags, usage) +/obj/item/proc/tool_query(datum/event_args/actor/clickchain/e_args, atom/target, flags, usage) RETURN_TYPE(/list) . = list() // if normal tool behavior @@ -43,7 +46,7 @@ if(tool_override) . |= tool_override for(var/i in .) - .[i] = tool_quality_transform(.[i], user, target, flags, usage) + .[i] = tool_quality_transform(.[i], e_args, target, flags, usage) /** * checks for a tool function @@ -54,16 +57,16 @@ * * @params * - function - tool function enum - * - user - person using tool, if any + * - e_args - person using tool, if any * - target - atom tool being used on, if any * - flags - tool operation flags * - usage - what we're being used for, bitfield */ -/obj/item/proc/tool_check(function, mob/user, atom/target, flags, usage) +/obj/item/proc/tool_check(function, datum/event_args/actor/clickchain/e_args, atom/target, flags, usage) ASSERT(function) if(tool_override && tool_override[function]) - return tool_quality_transform(tool_override[function], user, target, flags, usage) - return (function == tool_behaviour)? tool_quality_transform(tool_quality, user, target, flags, usage) : null + return tool_quality_transform(tool_override[function], e_args, target, flags, usage) + return (function == tool_behaviour)? tool_quality_transform(tool_quality, e_args, target, flags, usage) : null /** * transforms tool quality according to a user's skill @@ -71,12 +74,12 @@ * @params * - original - original quality * - * - user - user, if any + * - e_args - actor data, if any * - target - target, if any * - flags - tool operation flags * - usage - what we're being used for, bitfield */ -/obj/item/proc/tool_quality_transform(original, user, target, flags, usage) +/obj/item/proc/tool_quality_transform(original, datum/event_args/actor/clickchain/e_args, target, flags, usage) return original /** @@ -88,12 +91,12 @@ * * @params * - function - tool function enum - * - user - person using tool, if any + * - e_args - actor data for person using tool, if any * - target - atom tool being used on, if any * - flags - tool operation flags * - usage - what we're being used for, bitfield */ -/obj/item/proc/tool_speed(function, mob/user, atom/target, flags, usage) +/obj/item/proc/tool_speed(function, datum/event_args/actor/clickchain/e_args, atom/target, flags, usage) SHOULD_CALL_PARENT(TRUE) return (flags & TOOL_OP_INSTANT)? 0 : tool_speed @@ -113,14 +116,14 @@ * * @params * - function - tool function enum; if null, defaults to static tool behaviour. - * - user - person using tool, if any + * - e_args - actor data of who's using the tool * - target - atom tool being used on, if any * - flags - tool operation flags * - usage - what we're being used for */ -/obj/item/proc/tool_quality(function = tool_behaviour(), mob/user, atom/target, flags, usage) +/obj/item/proc/tool_quality(function = tool_behaviour(), datum/event_args/actor/clickchain/e_args, atom/target, flags, usage) // this is just a wrapper, the only difference is function is automatically provided. - return tool_check(function, user, target, flags, usage) + return tool_check(function, e_args, target, flags, usage) /** * called when we start being used as a tool @@ -129,13 +132,13 @@ * @params * - function - tool function enum * - flags - tool operation flags - * - user - person using tool, if any + * - e_args - actor data of who's using the tool * - target - atom tool being used on, if any * - time - approximated duration of the action in deciseconds * - cost - cost multiplier * - usage - usage flags, if any */ -/obj/item/proc/using_as_tool(function, flags, mob/user, atom/target, time, cost, usage) +/obj/item/proc/using_as_tool(function, flags, datum/event_args/actor/clickchain/e_args, atom/target, time, cost, usage) SHOULD_CALL_PARENT(TRUE) return TRUE @@ -146,14 +149,14 @@ * @params * - function - tool function enum * - flags - tool operation flags - * - user - person using tool, if any + * - e_args - actor data of who's using the tool * - target - atom tool being used on, if any * - time - duration of the action in deciseconds * - cost - cost multiplier * - usage - usage flags, if any * - success - was it successful? */ -/obj/item/proc/used_as_tool(function, flags, mob/user, atom/target, time, cost, usage, success) +/obj/item/proc/used_as_tool(function, flags, datum/event_args/actor/clickchain/e_args, atom/target, time, cost, usage, success) SHOULD_CALL_PARENT(TRUE) return TRUE @@ -163,17 +166,17 @@ * @params * - function - tool function enum * - flags - tool operation flags - * - user - person using tool, if any + * - e_args - actor data of who's using the tool * - target - atom tool being used on, if any * - time - duration of the action in deciseconds * - cost - cost multiplier * - usage - usage flags, if any * - volume - volume for sounds */ -/obj/item/proc/tool_feedback_start(function, flags, mob/user, atom/target, time, cost, usage, volume) +/obj/item/proc/tool_feedback_start(function, flags, datum/event_args/actor/clickchain/e_args, atom/target, time, cost, usage, volume) SHOULD_CALL_PARENT(TRUE) if(!(flags & TOOL_OP_NO_STANDARD_AUDIO)) - standard_tool_feedback_sound(function, flags, user, target, time, cost, usage, volume) + standard_tool_feedback_sound(function, flags, e_args, target, time, cost, usage, volume) /** * standard feedback for ending a tool usage @@ -181,7 +184,7 @@ * @params * - function - tool function enum * - flags - tool operation flags - * - user - person using tool, if any + * - e_args - actor data of who's using the tool * - target - atom tool being used on, if any * - time - duration of the action in deciseconds * - cost - cost multiplier @@ -189,10 +192,10 @@ * - success - was it successful? * - volume - volume for sounds */ -/obj/item/proc/tool_feedback_end(function, flags, mob/user, atom/target, time, cost, usage, success, volume) +/obj/item/proc/tool_feedback_end(function, flags, datum/event_args/actor/clickchain/e_args, atom/target, time, cost, usage, success, volume) SHOULD_CALL_PARENT(TRUE) if(!(flags & TOOL_OP_NO_STANDARD_AUDIO)) - standard_tool_feedback_sound(function, flags, user, target, time, cost, usage, success) + standard_tool_feedback_sound(function, flags, e_args, target, time, cost, usage, success) /** * plays tool sound @@ -200,7 +203,7 @@ * @params * - function - tool function enum * - flags - tool operation flags - * - user - person using tool, if any + * - e_args - actor data of who's using the tool * - target - atom tool being used on, if any * - time - duration of the action in deciseconds * - cost - cost multiplier @@ -208,14 +211,14 @@ * - success - was it successful? null if we're just starting * - volume - volume for sounds */ -/obj/item/proc/standard_tool_feedback_sound(function, flags, mob/user, atom/target, time, cost, usage, success, volume = 50) +/obj/item/proc/standard_tool_feedback_sound(function, flags, datum/event_args/actor/clickchain/e_args, atom/target, time, cost, usage, success, volume = 50) if(isnull(success)) // starting - playsound(src, tool_sound(function, flags, user, target, time, cost, usage, success), volume, TRUE) + playsound(src, tool_sound(function, flags, e_args, target, time, cost, usage, success), volume, TRUE) else // finishing if(time >= MIN_TOOL_SOUND_DELAY) - playsound(src, tool_sound(function, flags, user, target, time, cost, usage, success), volume, TRUE) + playsound(src, tool_sound(function, flags, e_args, target, time, cost, usage, success), volume, TRUE) /** * gets sound to play on tool usage @@ -223,14 +226,14 @@ * @params * - function - tool function enum * - flags - tool operation flags - * - user - person using tool, if any + * - e_args - actor data of who's using the tool * - target - atom tool being used on, if any * - time - duration of the action in deciseconds * - cost - cost multiplier * - usage - usage flags, if any * - success - was it successful? null if we're just starting */ -/obj/item/proc/tool_sound(function, flags, mob/user, atom/target, time, cost, usage, success) +/obj/item/proc/tool_sound(function, flags, datum/event_args/actor/clickchain/e_args, atom/target, time, cost, usage, success) if(tool_sound) return tool_sound // return default diff --git a/code/modules/tools/visuals.dm b/code/modules/tools/visuals.dm new file mode 100644 index 000000000000..90516413ec42 --- /dev/null +++ b/code/modules/tools/visuals.dm @@ -0,0 +1,8 @@ +/proc/dyntool_image_neutral(function) + return image('icons/screen/radial/tools/generic.dmi', icon_state = _dyntool_image_states[function] || "unknown") + +/proc/dyntool_image_forward(function) + return image('icons/screen/radial/tools/generic.dmi', icon_state = "[_dyntool_image_states[function] || "unknown"]_up") + +/proc/dyntool_image_backward(function) + return image('icons/screen/radial/tools/generic.dmi', icon_state = "[_dyntool_image_states[function] || "unknown"]_down") diff --git a/code/modules/tools/wrappers.dm b/code/modules/tools/wrappers.dm index 233664c9b3d8..0d8535103e99 100644 --- a/code/modules/tools/wrappers.dm +++ b/code/modules/tools/wrappers.dm @@ -1,3 +1,6 @@ +//* This file is explicitly licensed under the MIT license. *// +//* Copyright (c) 2023 Citadel Station developers. *// + //! ON GOD, READ tool_system.dm's use_tool_standard to learn how to use these! /** @@ -9,7 +12,7 @@ * - flags - tool operation flags * - hint - operation hint, if using dynamic tool system */ -/atom/proc/crowbar_act(obj/item/I, mob/user, flags, hint) +/atom/proc/crowbar_act(obj/item/I, datum/event_args/actor/clickchain/e_args, flags, hint) return FALSE /** @@ -21,14 +24,14 @@ * * @params * - I - the item - * - user - the user, if any + * - e_args - clickchain data, if any * - flags - tool operation flags * - delay - how long this should take * - cost - multiplier for cost, standard tool "cost" is 1 per second of usage. * - usage - usage flags for skill system checks. */ -/atom/proc/use_crowbar(obj/item/I, mob/user, flags, delay, cost, usage) - return use_tool_standard(TOOL_CROWBAR, I, user, flags, delay, cost, usage) +/atom/proc/use_crowbar(obj/item/I, datum/event_args/actor/clickchain/e_args, flags, delay, cost, usage) + return use_tool_standard(TOOL_CROWBAR, I, e_args, flags, delay, cost, usage) /** * Called when a wrench is used on us. @@ -39,7 +42,7 @@ * - flags - tool operation flags * - hint - operation hint, if using dynamic tool system */ -/atom/proc/wrench_act(obj/item/I, mob/user, flags, hint) +/atom/proc/wrench_act(obj/item/I, datum/event_args/actor/clickchain/e_args, flags, hint) return FALSE /** @@ -51,14 +54,14 @@ * * @params * - I - the item - * - user - the user, if any + * - e_args - clickchain data, if any * - flags - tool operation flags * - delay - how long this should take * - cost - multiplier for cost, standard tool "cost" is 1 per second of usage. * - usage - usage flags for skill system checks. */ -/atom/proc/use_wrench(obj/item/I, mob/user, flags, delay, cost, usage) - return use_tool_standard(TOOL_WRENCH, I, user, flags, delay, cost, usage) +/atom/proc/use_wrench(obj/item/I, datum/event_args/actor/clickchain/e_args, flags, delay, cost, usage) + return use_tool_standard(TOOL_WRENCH, I, e_args, flags, delay, cost, usage) /** * Called when a welder is used on us. @@ -69,7 +72,7 @@ * - flags - tool operation flags * - hint - operation hint, if using dynamic tool system */ -/atom/proc/welder_act(obj/item/I, mob/user, flags, hint) +/atom/proc/welder_act(obj/item/I, datum/event_args/actor/clickchain/e_args, flags, hint) return FALSE /** @@ -81,14 +84,14 @@ * * @params * - I - the item - * - user - the user, if any + * - e_args - clickchain data, if any * - flags - tool operation flags * - delay - how long this should take * - cost - multiplier for cost, standard tool "cost" is 1 per second of usage. * - usage - usage flags for skill system checks. */ -/atom/proc/use_welder(obj/item/I, mob/user, flags, delay, cost, usage) - return use_tool_standard(TOOL_WELDER, I, user, flags, delay, cost, usage) +/atom/proc/use_welder(obj/item/I, datum/event_args/actor/clickchain/e_args, flags, delay, cost, usage) + return use_tool_standard(TOOL_WELDER, I, e_args, flags, delay, cost, usage) /** * Called when a pair of wirecutters is used on us. @@ -99,7 +102,7 @@ * - flags - tool operation flags * - hint - operation hint, if using dynamic tool system */ -/atom/proc/wirecutter_act(obj/item/I, mob/user, flags, hint) +/atom/proc/wirecutter_act(obj/item/I, datum/event_args/actor/clickchain/e_args, flags, hint) return FALSE /** @@ -111,14 +114,14 @@ * * @params * - I - the item - * - user - the user, if any + * - e_args - clickchain data, if any * - flags - tool operation flags * - delay - how long this should take * - cost - multiplier for cost, standard tool "cost" is 1 per second of usage. * - usage - usage flags for skill system checks. */ -/atom/proc/use_wirecutter(obj/item/I, mob/user, flags, delay, cost, usage) - return use_tool_standard(TOOL_WIRECUTTER, I, user, flags, delay, cost, usage) +/atom/proc/use_wirecutter(obj/item/I, datum/event_args/actor/clickchain/e_args, flags, delay, cost, usage) + return use_tool_standard(TOOL_WIRECUTTER, I, e_args, flags, delay, cost, usage) /** * Called when a screwdriver is used on us. @@ -129,7 +132,7 @@ * - flags - tool operation flags * - hint - operation hint, if using dynamic tool system */ -/atom/proc/screwdriver_act(obj/item/I, mob/user, flags, hint) +/atom/proc/screwdriver_act(obj/item/I, datum/event_args/actor/clickchain/e_args, flags, hint) return FALSE /** @@ -141,14 +144,14 @@ * * @params * - I - the item - * - user - the user, if any + * - e_args - clickchain data, if any * - flags - tool operation flags * - delay - how long this should take * - cost - multiplier for cost, standard tool "cost" is 1 per second of usage. * - usage - usage flags for skill system checks. */ -/atom/proc/use_screwdriver(obj/item/I, mob/user, flags, delay, cost, usage) - return use_tool_standard(TOOL_SCREWDRIVER, I, user, flags, delay, cost, usage) +/atom/proc/use_screwdriver(obj/item/I, datum/event_args/actor/clickchain/e_args, flags, delay, cost, usage) + return use_tool_standard(TOOL_SCREWDRIVER, I, e_args, flags, delay, cost, usage) /** * Called when a analyzer is used on us. @@ -159,7 +162,7 @@ * - flags - tool operation flags * - hint - operation hint, if using dynamic tool system */ -/atom/proc/analyzer_act(obj/item/I, mob/user, flags, hint) +/atom/proc/analyzer_act(obj/item/I, datum/event_args/actor/clickchain/e_args, flags, hint) return FALSE /** @@ -171,14 +174,14 @@ * * @params * - I - the item - * - user - the user, if any + * - e_args - clickchain data, if any * - flags - tool operation flags * - delay - how long this should take * - cost - multiplier for cost, standard tool "cost" is 1 per second of usage. * - usage - usage flags for skill system checks. */ -/atom/proc/use_analyzer(obj/item/I, mob/user, flags, delay, cost, usage) - return use_tool_standard(TOOL_ANALYZER, I, user, flags, delay, cost, usage) +/atom/proc/use_analyzer(obj/item/I, datum/event_args/actor/clickchain/e_args, flags, delay, cost, usage) + return use_tool_standard(TOOL_ANALYZER, I, e_args, flags, delay, cost, usage) /** @@ -190,7 +193,7 @@ * - flags - tool operation flags * - hint - operation hint, if using dynamic tool system */ -/atom/proc/multitool_act(obj/item/I, mob/user, flags, hint) +/atom/proc/multitool_act(obj/item/I, datum/event_args/actor/clickchain/e_args, flags, hint) return FALSE /** @@ -202,12 +205,12 @@ * * @params * - I - the item - * - user - the user, if any + * - e_args - clickchain data, if any * - flags - tool operation flags * - delay - how long this should take * - cost - multiplier for cost, standard tool "cost" is 1 per second of usage. * - usage - usage flags for skill system checks. */ -/atom/proc/use_multitool(obj/item/I, mob/user, flags, delay, cost, usage) - return use_tool_standard(TOOL_MULTITOOL, I, user, flags, delay, cost, usage) +/atom/proc/use_multitool(obj/item/I, datum/event_args/actor/clickchain/e_args, flags, delay, cost, usage) + return use_tool_standard(TOOL_MULTITOOL, I, e_args, flags, delay, cost, usage) diff --git a/code/modules/tools/z_legacy.dm b/code/modules/tools/z_legacy.dm index 4f67004a445d..37ef1820cb2d 100644 --- a/code/modules/tools/z_legacy.dm +++ b/code/modules/tools/z_legacy.dm @@ -1,3 +1,6 @@ +//* This file is explicitly licensed under the MIT license. *// +//* Copyright (c) 2023 Citadel Station developers. *// + //! these wrappers are used for things that still check tools on attackby() //! new usages are prohibited.