diff --git a/code/__DEFINES/dcs/signals.dm b/code/__DEFINES/dcs/signals.dm index 6fb1cd24435..3c54e10d098 100644 --- a/code/__DEFINES/dcs/signals.dm +++ b/code/__DEFINES/dcs/signals.dm @@ -683,9 +683,9 @@ // /mob/living/simple_animal/hostile signals ///before attackingtarget has happened, source is the attacker and target is the attacked #define COMSIG_HOSTILE_PRE_ATTACKINGTARGET "hostile_pre_attackingtarget" + #define COMPONENT_HOSTILE_NO_ATTACK (1<<0) //cancel the attack, only works before attack happens ///after attackingtarget has happened, source is the attacker and target is the attacked, extra argument for if the attackingtarget was successful #define COMSIG_HOSTILE_POST_ATTACKINGTARGET "hostile_post_attackingtarget" - #define COMPONENT_HOSTILE_NO_ATTACK (1<<0) ///from base of mob/living/simple_animal/hostile/regalrat: (mob/living/simple_animal/hostile/regalrat/king) #define COMSIG_RAT_INTERACT "rat_interaction" @@ -700,7 +700,7 @@ #define COMSIG_OBJ_TAKE_DAMAGE "obj_take_damage" /// Return bitflags for the above signal which prevents the object taking any damage. #define COMPONENT_NO_TAKE_DAMAGE (1<<0) -///from base of [/obj/proc/update_integrity]: () +///from base of [/obj/proc/update_integrity]: (old_value, new_value) #define COMSIG_OBJ_INTEGRITY_CHANGED "obj_integrity_changed" ///from base of obj/deconstruct(): (disassembled) #define COMSIG_OBJ_DECONSTRUCT "obj_deconstruct" @@ -1249,6 +1249,8 @@ #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/proc/attack_secondary()]: (atom/target, mob/user, params) +#define COMSIG_ITEM_ATTACK_SECONDARY "item_pre_attack_secondary" ///from base of obj/item/afterattack(): (atom/target, mob/user, params) #define COMSIG_ITEM_AFTERATTACK "item_afterattack" ///from base of obj/item/attack_qdeleted(): (atom/target, mob/user, params) diff --git a/code/_onclick/item_attack.dm b/code/_onclick/item_attack.dm index 4110c0ad9a9..e4af58a1d3e 100644 --- a/code/_onclick/item_attack.dm +++ b/code/_onclick/item_attack.dm @@ -209,6 +209,14 @@ /// The equivalent of [/obj/item/proc/attack] but for alternate attacks, AKA right clicking /obj/item/proc/attack_secondary(mob/living/victim, mob/living/user, params) + var/signal_result = SEND_SIGNAL(src, COMSIG_ITEM_ATTACK_SECONDARY, victim, user, params) + + if(signal_result & COMPONENT_SECONDARY_CANCEL_ATTACK_CHAIN) + return SECONDARY_ATTACK_CANCEL_ATTACK_CHAIN + + if(signal_result & COMPONENT_SECONDARY_CONTINUE_ATTACK_CHAIN) + return SECONDARY_ATTACK_CONTINUE_CHAIN + return SECONDARY_ATTACK_CALL_NORMAL /// The equivalent of the standard version of [/obj/item/proc/attack] but for object targets. diff --git a/code/datums/components/kneecapping.dm b/code/datums/components/kneecapping.dm new file mode 100644 index 00000000000..0bf5822a28b --- /dev/null +++ b/code/datums/components/kneecapping.dm @@ -0,0 +1,90 @@ +/** + * Kneecapping element replaces the item's secondary attack with an aimed attack at the kneecaps under certain circumstances. + * + * Element is incompatible with non-items. Requires the parent item to have a force equal to or greater than WOUND_MINIMUM_DAMAGE. + * Also requires that the parent can actually get past pre_secondary_attack without the attack chain cancelling. + * + * Kneecapping attacks have a wounding bonus between severe and critical+10 wound thresholds. Without some serious wound protecting + * armour this all but guarantees a wound of some sort. The attack is directed specifically at a limb and the limb takes the damage. + * + * Requires the attacker to be aiming for either leg zone, which will be targetted specifically. They will than have a 3-second long + * do_mob before executing the attack. + * + * Kneecapping requires the target to either be on the floor, immobilised or buckled to something. And also to have an appropriate leg. + * + * Passing all the checks will cancel the entire attack chain. + */ +/datum/element/kneecapping + element_flags = ELEMENT_DETACH + +/datum/element/kneecapping/Attach(datum/target) + if(!isitem(target)) + stack_trace("Kneecapping element added to non-item object: \[[target]\]") + return ELEMENT_INCOMPATIBLE + + var/obj/item/target_item = target + + if(target_item.force < WOUND_MINIMUM_DAMAGE) + stack_trace("Kneecapping element added to item with too little force to wound: \[[target]\]") + return ELEMENT_INCOMPATIBLE + + . = ..() + + if(. == ELEMENT_INCOMPATIBLE) + return + + RegisterSignal(target, COMSIG_ITEM_ATTACK_SECONDARY , .proc/try_kneecap_target) + +/datum/element/kneecapping/Detach(datum/target) + UnregisterSignal(target, COMSIG_ITEM_ATTACK_SECONDARY) + + return ..() + +/** + * Signal handler for COMSIG_ITEM_ATTACK_SECONDARY. Does checks for pacifism, zones and target state before either returning nothing + * if the special attack could not be attempted, performing the ordinary attack procs instead - Or cancelling the attack chain if + * the attack can be started. + */ +/datum/element/kneecapping/proc/try_kneecap_target(obj/item/source, mob/living/carbon/target, mob/attacker, params) + SIGNAL_HANDLER + + if((attacker.zone_selected != BODY_ZONE_L_LEG) && (attacker.zone_selected != BODY_ZONE_R_LEG)) + return + + if(HAS_TRAIT(attacker, TRAIT_PACIFISM)) + return + + if(!iscarbon(target)) + return + + if(!target.buckled && !HAS_TRAIT(target, TRAIT_FLOORED) && !HAS_TRAIT(target, TRAIT_IMMOBILIZED)) + return + + var/obj/item/bodypart/leg = target.get_bodypart(attacker.zone_selected) + + if(!leg) + return + + . = COMPONENT_SECONDARY_CANCEL_ATTACK_CHAIN + + INVOKE_ASYNC(src, .proc/do_kneecap_target, source, leg, target, attacker) + +/** + * After a short do_mob, attacker applies damage to the given leg with a significant wounding bonus, applying the weapon's force as damage. + */ +/datum/element/kneecapping/proc/do_kneecap_target(obj/item/weapon, obj/item/bodypart/leg, mob/living/carbon/target, mob/attacker) + if(LAZYACCESS(attacker.do_afters, weapon)) + return + + attacker.visible_message(span_warning("[attacker] carefully aims [attacker.p_their()] [weapon] for a swing at [target]'s kneecaps!"), span_danger("You carefully aim \the [weapon] for a swing at [target]'s kneecaps!")) + log_combat(attacker, target, "started aiming a swing to break the kneecaps of", weapon) + + if(do_mob(attacker, target, 3 SECONDS, interaction_key = weapon)) + attacker.visible_message(span_warning("[attacker] swings [attacker.p_their()] [weapon] at [target]'s kneecaps!"), span_danger("You swing \the [weapon] at [target]'s kneecaps!")) + var/datum/wound/blunt/severe/severe_wound_type = /datum/wound/blunt/severe + var/datum/wound/blunt/critical/critical_wound_type = /datum/wound/blunt/critical + leg.receive_damage(brute = weapon.force, wound_bonus = rand(initial(severe_wound_type.threshold_minimum), initial(critical_wound_type.threshold_minimum) + 10)) + log_combat(attacker, target, "broke the kneecaps of", weapon) + target.update_damage_overlays() + attacker.do_attack_animation(target, used_item = weapon) + playsound(source = get_turf(weapon), soundin = weapon.hitsound, vol = weapon.get_clamped_volume(), vary = TRUE) diff --git a/code/game/objects/items/weaponry.dm b/code/game/objects/items/weaponry.dm index f536b8af844..0705baf57f1 100644 --- a/code/game/objects/items/weaponry.dm +++ b/code/game/objects/items/weaponry.dm @@ -661,6 +661,8 @@ for further reading, please see: https://github.com/tgstation/tgstation/pull/301 icon_state = "baseball_bat_brit" inhand_icon_state = "baseball_bat_brit" + AddElement(/datum/element/kneecapping) + /obj/item/melee/baseball_bat/homerun name = "home run bat" desc = "This thing looks dangerous... Dangerously good at baseball, that is." diff --git a/tgstation.dme b/tgstation.dme index d60d5c2b8a4..3767e461ada 100644 --- a/tgstation.dme +++ b/tgstation.dme @@ -560,6 +560,7 @@ #include "code\datums\components\infective.dm" #include "code\datums\components\itempicky.dm" #include "code\datums\components\jousting.dm" +#include "code\datums\components\kneecapping.dm" #include "code\datums\components\knockback.dm" #include "code\datums\components\knockoff.dm" #include "code\datums\components\label.dm"