diff --git a/code/__DEFINES/dcs/signals/signals_object.dm b/code/__DEFINES/dcs/signals/signals_object.dm index d09d74c5098..726e30989d3 100644 --- a/code/__DEFINES/dcs/signals/signals_object.dm +++ b/code/__DEFINES/dcs/signals/signals_object.dm @@ -483,13 +483,17 @@ #define COMSIG_ITEM_ATTACK_SELF_SECONDARY "item_attack_self_secondary" ///from base of obj/item/attack_atom(): (/atom, /mob, list/modifiers) #define COMSIG_ITEM_ATTACK_ATOM "item_attack_atom" -///from base of obj/item/pre_attack(): (atom/target, mob/user, list/modifiers) +///from base of obj/item/pre_attack(): (atom/target, mob/user, list/modifiers, list/attack_modifiers) #define COMSIG_ITEM_PRE_ATTACK "item_pre_attack" +///from base of obj/item/pre_attack(): (obj/item/weapon, atom/target, list/modifiers, list/attack_modifiers) +#define COMSIG_USER_PRE_ITEM_ATTACK "user_pre_item_attack" /// From base of [/obj/item/proc/pre_attack_secondary()]: (atom/target, mob/user, list/modifiers, list/attack_modifiers) #define COMSIG_ITEM_PRE_ATTACK_SECONDARY "item_pre_attack_secondary" #define COMPONENT_SECONDARY_CANCEL_ATTACK_CHAIN (1<<0) #define COMPONENT_SECONDARY_CONTINUE_ATTACK_CHAIN (1<<1) #define COMPONENT_SECONDARY_CALL_NORMAL_ATTACK_CHAIN (1<<2) +///from base of obj/item/pre_attack_secondary(): (obj/item/weapon, atom/target, list/modifiers, list/attack_modifiers) +#define COMSIG_USER_PRE_ITEM_ATTACK_SECONDARY "user_pre_item_attack_secondary" /// From base of [/obj/item/proc/attack_secondary()]: (atom/target, mob/user, list/modifiers, list/attack_modifiers) #define COMSIG_ITEM_ATTACK_SECONDARY "item_attack_secondary" ///from base of [obj/item/attack()]: (atom/target, mob/user, proximity_flag, list/modifiers) diff --git a/code/_onclick/item_attack.dm b/code/_onclick/item_attack.dm index c5ef9b3cfc6..ba156d8180b 100644 --- a/code/_onclick/item_attack.dm +++ b/code/_onclick/item_attack.dm @@ -108,7 +108,8 @@ * See: [/obj/item/proc/melee_attack_chain] */ /obj/item/proc/pre_attack(atom/target, mob/living/user, list/modifiers, list/attack_modifiers) //do stuff before attackby! - if(SEND_SIGNAL(src, COMSIG_ITEM_PRE_ATTACK, target, user, modifiers, attack_modifiers) & COMPONENT_CANCEL_ATTACK_CHAIN) + var/signal_result = SEND_SIGNAL(src, COMSIG_ITEM_PRE_ATTACK, target, user, modifiers, attack_modifiers) | SEND_SIGNAL(user, COMSIG_USER_PRE_ITEM_ATTACK, src, target, modifiers, attack_modifiers) + if(signal_result & COMPONENT_CANCEL_ATTACK_CHAIN) return TRUE return FALSE //return TRUE to avoid calling attackby after this proc does stuff @@ -124,7 +125,7 @@ * See: [/obj/item/proc/melee_attack_chain] */ /obj/item/proc/pre_attack_secondary(atom/target, mob/living/user, list/modifiers, list/attack_modifiers) - var/signal_result = SEND_SIGNAL(src, COMSIG_ITEM_PRE_ATTACK_SECONDARY, target, user, modifiers, attack_modifiers) + var/signal_result = SEND_SIGNAL(src, COMSIG_ITEM_PRE_ATTACK_SECONDARY, target, user, modifiers, attack_modifiers) | SEND_SIGNAL(user, COMSIG_USER_PRE_ITEM_ATTACK_SECONDARY, src, target, modifiers, attack_modifiers) if(signal_result & COMPONENT_SECONDARY_CANCEL_ATTACK_CHAIN) return SECONDARY_ATTACK_CANCEL_ATTACK_CHAIN diff --git a/code/datums/components/style/style.dm b/code/datums/components/style/style.dm index 2e8fb5cd07c..7e156603e88 100644 --- a/code/datums/components/style/style.dm +++ b/code/datums/components/style/style.dm @@ -95,7 +95,7 @@ src.multitooled = multitooled /datum/component/style/RegisterWithParent() - RegisterSignal(parent, COMSIG_USER_ITEM_INTERACTION, PROC_REF(hotswap)) + RegisterSignal(parent, COMSIG_USER_PRE_ITEM_ATTACK, PROC_REF(hotswap)) RegisterSignal(parent, COMSIG_MOB_MINED, PROC_REF(on_mine)) RegisterSignal(parent, COMSIG_MOB_APPLY_DAMAGE, PROC_REF(on_take_damage)) RegisterSignal(parent, COMSIG_MOB_EMOTED("taunt"), PROC_REF(on_taunt)) @@ -110,7 +110,7 @@ ADD_TRAIT(parent, TRAIT_MINING_PARRYING, STYLE_TRAIT) /datum/component/style/UnregisterFromParent() - UnregisterSignal(parent, COMSIG_USER_ITEM_INTERACTION) + UnregisterSignal(parent, COMSIG_USER_PRE_ITEM_ATTACK) UnregisterSignal(parent, COMSIG_MOB_MINED) UnregisterSignal(parent, COMSIG_MOB_APPLY_DAMAGE) UnregisterSignal(parent, COMSIG_MOB_EMOTED("taunt")) @@ -292,27 +292,28 @@ return "#364866" /// A proc that lets a user, when their rank >= `hotswap_rank`, swap items in storage with what's in their hands, simply by clicking on the stored item with a held item -/datum/component/style/proc/hotswap(mob/living/source, atom/target, obj/item/weapon, list/modifiers) +/datum/component/style/proc/hotswap(mob/living/source, obj/item/weapon, atom/target, list/modifiers) SIGNAL_HANDLER - - if((rank < hotswap_rank) || !isitem(target) || !(target in source.get_all_contents())) + if((rank < hotswap_rank) || !isitem(target) || get(target, /mob/living) != source) return NONE var/obj/item/item_target = target - if(!(item_target.item_flags & IN_STORAGE)) return NONE - var/datum/storage/atom_storage = item_target.loc.atom_storage + INVOKE_ASYNC(src, PROC_REF(hotswap_interact), source, weapon, target, modifiers) + return COMPONENT_CANCEL_ATTACK_CHAIN +/datum/component/style/proc/hotswap_interact(mob/living/source, obj/item/weapon, atom/target, list/modifiers) + var/datum/storage/atom_storage = target.loc.atom_storage if(!atom_storage.can_insert(weapon, source, messages = FALSE)) source.balloon_alert(source, "unable to hotswap!") - return NONE + return - atom_storage.attempt_insert(weapon, source, override = TRUE) - INVOKE_ASYNC(source, TYPE_PROC_REF(/mob/living, put_in_hands), target) - source.visible_message(span_notice("[source] quickly swaps [weapon] out with [target]!"), span_notice("You quickly swap [weapon] with [target].")) - return ITEM_INTERACT_BLOCKING + if (atom_storage.attempt_insert(weapon, source, override = TRUE) && source.put_in_hands(target)) + source.visible_message(span_notice("[source] quickly swaps [weapon] out with [target]!"), span_notice("You quickly swap [weapon] with [target].")) + else + source.balloon_alert(source, "unable to hotswap!") // Point givers /datum/component/style/proc/on_punch(mob/living/carbon/human/punching_person, atom/attacked_atom, proximity) diff --git a/code/modules/unit_tests/_unit_tests.dm b/code/modules/unit_tests/_unit_tests.dm index b09a5ccdba5..8c65869093a 100644 --- a/code/modules/unit_tests/_unit_tests.dm +++ b/code/modules/unit_tests/_unit_tests.dm @@ -302,6 +302,7 @@ #include "strange_reagent.dm" #include "strippable.dm" #include "stuns.dm" +#include "style_hotswapping.dm" #include "subsystem_init.dm" #include "suit_storage_icons.dm" #include "surgeries.dm" diff --git a/code/modules/unit_tests/style_hotswapping.dm b/code/modules/unit_tests/style_hotswapping.dm new file mode 100644 index 00000000000..33063eb888f --- /dev/null +++ b/code/modules/unit_tests/style_hotswapping.dm @@ -0,0 +1,43 @@ +/// Ensures that hotswapping only occurs when hitting inventory items and allows item interactions to go through +/datum/unit_test/style_hotswapping + +/datum/unit_test/style_hotswapping/Run() + var/mob/living/carbon/human/john_ultrakill = allocate(/mob/living/carbon/human/consistent) + var/obj/item/storage/backpack/bag = allocate(__IMPLIED_TYPE__, john_ultrakill.loc) + john_ultrakill.equip_to_slot(bag, ITEM_SLOT_BACK) + TEST_ASSERT_EQUAL(john_ultrakill.get_item_by_slot(ITEM_SLOT_BACK), bag, "Human wasn't able to equip a backpack!") + var/datum/component/style/style = john_ultrakill.AddComponent(/datum/component/style) + try_stabilize(john_ultrakill, john_ultrakill.loc, "while it was on the user's turf") + try_stabilize(john_ultrakill, bag, "while it was in the user's bag") + var/obj/item/coin/gold/coin_one = allocate(__IMPLIED_TYPE__, john_ultrakill.loc) + var/obj/item/coin/silver/coin_two = allocate(__IMPLIED_TYPE__, john_ultrakill.loc) + coin_two.forceMove(bag) + john_ultrakill.put_in_active_hand(coin_one, TRUE) + TEST_ASSERT_EQUAL(john_ultrakill.get_active_held_item(), coin_one, "Human wasn't able to pick up a gold coin!") + john_ultrakill.ClickOn(coin_two) + TEST_ASSERT_EQUAL(john_ultrakill.get_active_held_item(), coin_one, "Human hotswapped from their storage despite not having enough points!") + john_ultrakill.next_click = 0 + john_ultrakill.next_move = 0 + john_ultrakill.temporarilyRemoveItemFromInventory(coin_one, TRUE) + QDEL_NULL(coin_one) + style.style_points = INFINITY + style.update_screen(style.point_to_rank()) + try_stabilize(john_ultrakill, john_ultrakill.loc, "while it was on the user's turf with hotswapping active") + try_stabilize(john_ultrakill, bag, "while it was in the user's bag with hotswapping active") + coin_one = allocate(__IMPLIED_TYPE__, john_ultrakill.loc) + john_ultrakill.put_in_active_hand(coin_one, TRUE) + TEST_ASSERT_EQUAL(john_ultrakill.get_active_held_item(), coin_one, "Human wasn't able to pick up a gold coin!") + john_ultrakill.ClickOn(coin_two) + TEST_ASSERT_EQUAL(john_ultrakill.get_active_held_item(), coin_two, "Human wasn't able to hotswap with their storage!") + +/datum/unit_test/style_hotswapping/proc/try_stabilize(mob/living/carbon/human/john_ultrakill, core_loc, desc = null) + var/obj/item/organ/monster_core/regenerative_core/legion/core = allocate(__IMPLIED_TYPE__, john_ultrakill.loc) + core.forceMove(core_loc) + TEST_ASSERT(core.decay_timer, "Legion core spawned without a decay timer!") + var/obj/item/mining_stabilizer/serum = allocate(__IMPLIED_TYPE__, john_ultrakill.loc) + john_ultrakill.put_in_active_hand(serum, TRUE) + TEST_ASSERT_EQUAL(john_ultrakill.get_active_held_item(), serum, "Human wasn't able to pick up a stabilizer serum!") + john_ultrakill.ClickOn(core) + TEST_ASSERT(!core.decay_timer, "Clicking on a legion core with a serum did not stabilize it [desc]!") + john_ultrakill.next_click = 0 + john_ultrakill.next_move = 0