From cbbcd71d784fc2a8ddaaff22944ded8d428e1cc3 Mon Sep 17 00:00:00 2001 From: necromanceranne <40847847+necromanceranne@users.noreply.github.com> Date: Tue, 21 Oct 2025 12:25:53 +1100 Subject: [PATCH] Cyberbrawlening 2: Electric Boogaloo: Strongarm implants now ACTUALLY improve arm strength (#93442) ## About The Pull Request Strongarm implants now directly improve your arm's unarmed stats, improving the upper damage (+2), accuracy (+10) and stamina damage inflicted while a grabbed target fails to escape a grab (+10). As a result, strongarm implants do not tack on an extra +5 to the damage inflicted by its super slam attack. However, this attack now uses similar math to your standard punches; improved by athletics and the Strength mutation, [as explained in this PR](https://github.com/tgstation/tgstation/pull/93125). Strongarms do not get their damage bonus against Beast mob biotypes, but retain Mining and Special biotype effectiveness. Strongarms do not go on cooldown, nor consider the cooldown for the super slam except when punching human targets. When not punching a human target, strongarms will always use the super slam. Adds support for limbs to alter grab states, grab escape chances and grab punishment damage. This is only actually able to be used by arms as of the moment. (Maybe one day we'll get tail constrictions if someone is insane enough to add lamia-like creatures/limbs or something) ## Why It's Good For The Game The strongarms worked like a pseudo-punch and this often threw players for a loop as to what they actually do. Many people assumed they improved the actual power of the arm itself, rather than just being a vehicle for a special attack. This basically affirms player assumptions about the implant. Yes, it does improve the arm strength. Yes, most of the generic improvements to unarmed attacks improve this one's special attack. They're now closer in functionality. This is also my less than thrilling workaround for enabling the borg punching that [lucy has been craving](https://github.com/Monkestation/Monkestation2.0/pull/6748) :U ## Changelog :cl: balance: The strongarm implants now directly improves the unarmed power of the arm it is implanted into. balance: The strongarm implant's super slam now uses similar calculations to normal unarmed attacks, including improvements from mutations and athletics. balance: The strongarm always uses the super slam against nonhuman opponents. It will only go on cooldown, and care about that cooldown, when used against humans. /:cl: --- code/modules/mob/living/living.dm | 4 +- code/modules/surgery/bodyparts/_bodyparts.dm | 14 ++- .../organs/internal/cyberimp/augments_arms.dm | 113 ++++++++++++------ 3 files changed, 90 insertions(+), 41 deletions(-) diff --git a/code/modules/mob/living/living.dm b/code/modules/mob/living/living.dm index 463652877f4..7938b8f5447 100644 --- a/code/modules/mob/living/living.dm +++ b/code/modules/mob/living/living.dm @@ -1190,7 +1190,9 @@ var/mob/living/carbon/human/human_puller = pulledby var/obj/item/bodypart/grabbing_bodypart = human_puller.get_active_hand() if(grabbing_bodypart) - damage_on_resist_fail += rand(grabbing_bodypart.unarmed_damage_low, grabbing_bodypart.unarmed_damage_high) + damage_on_resist_fail += (rand(grabbing_bodypart.unarmed_damage_low, grabbing_bodypart.unarmed_damage_high)) + grabbing_bodypart.unarmed_grab_damage_bonus + effective_grab_state += grabbing_bodypart.unarmed_grab_state_bonus + escape_chance += grabbing_bodypart.unarmed_grab_escape_chance_bonus //If our puller is a drunken brawler, they add more damage based on their own damage taken so long as they're drunk and treat the grab state as one higher var/puller_drunkenness = human_puller.get_drunk_amount() diff --git a/code/modules/surgery/bodyparts/_bodyparts.dm b/code/modules/surgery/bodyparts/_bodyparts.dm index 638208b9934..4ea1445d631 100644 --- a/code/modules/surgery/bodyparts/_bodyparts.dm +++ b/code/modules/surgery/bodyparts/_bodyparts.dm @@ -183,14 +183,22 @@ /// Sounds when this bodypart is used in an umarmed attack var/sound/unarmed_attack_sound = 'sound/items/weapons/punch1.ogg' var/sound/unarmed_miss_sound = 'sound/items/weapons/punchmiss.ogg' - ///Lowest possible punch damage this bodypart can give. If this is set to 0, unarmed attacks will always miss. + /// Lowest possible punch damage this bodypart can give. If this is set to 0, unarmed attacks will always miss. var/unarmed_damage_low = 1 - ///Highest possible punch damage this bodypart can ive. + /// Highest possible punch damage this bodypart can ive. var/unarmed_damage_high = 1 - ///Determines the accuracy bonus, armor penetration and knockdown probability. + /// Determines the accuracy bonus, armor penetration and knockdown probability. var/unarmed_effectiveness = 10 /// Multiplier applied to effectiveness and damage when attacking a grabbed target. var/unarmed_pummeling_bonus = 1 + /// If this limb is used to grab (which is only arms right now), how much more effective is the limb? Every integer above or below 0 is one effective grab level. Higher is a bonus, lower is a malus. + /// This is more powerful than adjusting grab escape chance. If the arm has both an increased grab level and a chance reduction, it is very hard to escape a grab. + var/unarmed_grab_state_bonus = 0 + /// If this limb is used to grab (which is only arms right now), how much additional damage does it deal to the grabbed individual when they fail to escape the grab? + var/unarmed_grab_damage_bonus = 0 + /// If this limb is used to grab (which is only arms right now), how much more difficult is it to escape the grab, before accounting for grab state? Values above 0 is a malus, values below 0 is a bonus. + /// This is less powerful than adjusting grab state. If the arm has both an increased grab level and a chance reduction, it is very hard to escape a grab. + var/unarmed_grab_escape_chance_bonus = 0 /// The 'sharpness' of the limb. Could indicate claws, teeth or spines. Should default to NONE, or blunt. var/unarmed_sharpness = NONE diff --git a/code/modules/surgery/organs/internal/cyberimp/augments_arms.dm b/code/modules/surgery/organs/internal/cyberimp/augments_arms.dm index 5f338749f40..5d176683eda 100644 --- a/code/modules/surgery/organs/internal/cyberimp/augments_arms.dm +++ b/code/modules/surgery/organs/internal/cyberimp/augments_arms.dm @@ -438,25 +438,31 @@ ) aug_overlay = "strongarm" - ///The amount of damage the implant adds to our unarmed attacks. - var/punch_damage = 5 - ///Biotypes we apply an additional amount of damage too - var/biotype_bonus_targets = MOB_BEAST | MOB_SPECIAL | MOB_MINING - ///Extra damage dealt to our targeted mobs + /// The amount of damage the implant adds to the lower punching force of our arm. + var/lower_punch_damage = 0 + /// The amount of damage the implant adds to the upper punching force of our arm. + var/upper_punch_damage = 2 + /// The amount of punch effectiveness (AKA accuracy and crit potential) the implant adds to our arm + var/punch_effectiveness_added = 10 + /// How much extra damage does our implant allow the implanted while grabbing someone and they are unable to break the grapple? + var/bonus_grab_damage = 20 + /// Biotypes we apply an additional amount of damage too + var/biotype_bonus_targets = MOB_SPECIAL | MOB_MINING + /// Extra damage dealt to our targeted mobs var/biotype_bonus_damage = 20 - ///IF true, the throw attack will not smash people into walls + /// IF true, the throw attack will not smash people into walls var/non_harmful_throw = TRUE - ///How far away your attack will throw your oponent + /// How far away your attack will throw your oponent var/attack_throw_range = 1 - ///Minimum throw power of the attack + /// Minimum throw power of the attack var/throw_power_min = 1 - ///Maximum throw power of the attack + /// Maximum throw power of the attack var/throw_power_max = 4 - ///How long will the implant malfunction if it is EMP'd + /// How long will the implant malfunction if it is EMP'd var/emp_base_duration = 9 SECONDS - ///How long before we get another slam punch; consider that these usually come in pairs of two + /// How long before we get another slam punch against a human; consider that these usually come in pairs var/slam_cooldown_duration = 5 SECONDS - ///Tracks how soon we can perform another slam attack + /// Tracks how soon we can perform another slam attack COOLDOWN_DECLARE(slam_cooldown) /obj/item/organ/cyberimp/arm/strongarm/Initialize(mapload) @@ -472,6 +478,20 @@ . = ..() UnregisterSignal(arm_owner, COMSIG_LIVING_EARLY_UNARMED_ATTACK) +/obj/item/organ/cyberimp/arm/strongarm/on_bodypart_insert(obj/item/bodypart/arm) + . = ..() + arm.unarmed_damage_low += lower_punch_damage + arm.unarmed_damage_high += upper_punch_damage + arm.unarmed_effectiveness += punch_effectiveness_added + arm.unarmed_grab_damage_bonus += bonus_grab_damage + +/obj/item/organ/cyberimp/arm/strongarm/on_bodypart_remove(obj/item/bodypart/arm) + . = ..() + arm.unarmed_damage_low += lower_punch_damage + arm.unarmed_damage_high += upper_punch_damage + arm.unarmed_effectiveness += punch_effectiveness_added + arm.unarmed_grab_damage_bonus += bonus_grab_damage + /obj/item/organ/cyberimp/arm/strongarm/emp_act(severity) . = ..() if((organ_flags & ORGAN_FAILING) || . & EMP_PROTECT_SELF) @@ -495,13 +515,12 @@ return NONE if(HAS_TRAIT(source, TRAIT_HULK)) //NO HULK return NONE - if(!COOLDOWN_FINISHED(src, slam_cooldown)) + if(!COOLDOWN_FINISHED(src, slam_cooldown) && ishuman(target)) return NONE if(!source.can_unarmed_attack()) return COMPONENT_SKIP_ATTACK var/mob/living/living_target = target - source.changeNext_move(CLICK_CD_MELEE) var/picked_hit_type = pick("punch", "smash", "pummel", "bash", "slam") if(organ_flags & ORGAN_FAILING) @@ -514,22 +533,28 @@ source.Paralyze(1 SECONDS) return COMPONENT_CANCEL_ATTACK_CHAIN - if(ishuman(target)) - var/mob/living/carbon/human/human_target = target - if(human_target.check_block(source, punch_damage, "[source]'s' [picked_hit_type]")) - source.do_attack_animation(target) - playsound(living_target.loc, 'sound/items/weapons/punchmiss.ogg', 25, TRUE, -1) - log_combat(source, target, "attempted to [picked_hit_type]", "muscle implant") - return COMPONENT_CANCEL_ATTACK_CHAIN - var/ground_bounce = FALSE // funny flavor. if you hit someone who's floored you slam them into the ground, breaking tiles var/turf/target_turf = get_turf(living_target) + /* Damage calculations operate on the same math used in /datum/species/proc/harm(): + * Unarmed damage is randomized between an upper and lower value. + * The lower value is determined by taking the damage value from the limb, and then increasing that value based on athletics level (min upper value) + * The upper value is determiend by taking the damage value from the limb, and then seeing if we have the strength trait, providing extra damage. + * The end result is that the more investment into athletics, the more precise the damage is, without necessarily increasing the potential damage. + */ + + // Our attacking limb var/obj/item/bodypart/attacking_bodypart = hand - var/potential_damage = punch_damage + // The upper damage, calculated first as it will be used to cap our potential lower damage. + var/potential_upper_damage = attacking_bodypart.unarmed_damage_high + (HAS_TRAIT(source, TRAIT_STRENGTH) ? 2 : 0) + // The lower damage, which is capped by potential_upper_damage + var/potential_lower_damage = min(attacking_bodypart.unarmed_damage_low + (source.mind?.get_skill_level(/datum/skill/athletics) || 0), potential_upper_damage) + // Finally, we determine the actual damage roll. + var/potential_damage = rand(potential_lower_damage, potential_upper_damage) + // This value is used to determine armour penetration. var/potential_effectiveness = attacking_bodypart.unarmed_effectiveness + // This is a damage and penetration multiplier if our target is grabbed when we deliver our punch. var/potential_pummel_bonus = attacking_bodypart.unarmed_pummeling_bonus - potential_damage += rand(attacking_bodypart.unarmed_damage_low, attacking_bodypart.unarmed_damage_high) if(living_target.pulledby && living_target.pulledby.grab_state >= GRAB_AGGRESSIVE) // get pummeled idiot potential_damage *= potential_pummel_bonus @@ -541,21 +566,18 @@ if(biotype_bonus_targets && is_correct_biotype) //If we are punching one of our special biotype targets, increase the damage floor by a factor of two. potential_damage += biotype_bonus_damage + if(ishuman(target)) + var/mob/living/carbon/human/human_target = target + if(human_target.check_block(source, potential_damage, "[source]'s' [picked_hit_type]")) + source.do_attack_animation(target) + playsound(living_target.loc, 'sound/items/weapons/punchmiss.ogg', 25, TRUE, -1) + log_combat(source, target, "attempted to [picked_hit_type]", "muscle implant") + return COMPONENT_CANCEL_ATTACK_CHAIN + source.do_attack_animation(target, ATTACK_EFFECT_SMASH) playsound(living_target.loc, 'sound/items/weapons/punch1.ogg', 25, TRUE, -1) - var/target_zone = living_target.get_random_valid_zone(source.zone_selected) - var/armor_block = living_target.run_armor_check(target_zone, MELEE, armour_penetration = potential_effectiveness) - living_target.apply_damage(potential_damage * 2, attacking_bodypart.attack_type, target_zone, armor_block) - - if(source.body_position != LYING_DOWN) //Throw them if we are standing - var/atom/throw_target = get_edge_target_turf(living_target, source.dir) - living_target.throw_at(throw_target, attack_throw_range, rand(throw_power_min,throw_power_max), source, gentle = non_harmful_throw) - if(ground_bounce) - if(isfloorturf(target_turf)) - var/turf/open/floor/crunched = target_turf - crunched.crush() // crunch - + // Some mobs gib when killed, so we're logging early. At this point, we're definitely hitting, so... living_target.visible_message( span_danger("[source] [picked_hit_type]ed [living_target][ground_bounce ? " into [target_turf]" : ""]!"), span_userdanger("You're [picked_hit_type]ed by [source][ground_bounce ? " into [target_turf]" : ""]!"), @@ -568,7 +590,24 @@ log_combat(source, target, "[picked_hit_type]ed", "muscle implant") - COOLDOWN_START(src, slam_cooldown, slam_cooldown_duration) + if(ishuman(target)) + COOLDOWN_START(src, slam_cooldown, slam_cooldown_duration) + + var/target_zone = living_target.get_random_valid_zone(source.zone_selected) + var/armor_block = living_target.run_armor_check(target_zone, MELEE, armour_penetration = potential_effectiveness) + living_target.apply_damage(potential_damage * 2, attacking_bodypart.attack_type, target_zone, armor_block) + + if(source.body_position != LYING_DOWN && !QDELETED(living_target)) //Throw them if we are standing and we didn't somehow just completely obliterate the target + var/atom/throw_target = get_edge_target_turf(living_target, source.dir) + living_target.throw_at(throw_target, attack_throw_range, rand(throw_power_min,throw_power_max), source, gentle = non_harmful_throw) + if(ground_bounce) + if(isfloorturf(target_turf)) + var/turf/open/floor/crunched = target_turf + crunched.crush() // crunch + else if (ground_bounce) // Just in case our target mob somehow evaporated during this process, we still leave an obliterated tile in their wake + if(isfloorturf(target_turf)) + var/turf/open/floor/crunched = target_turf + crunched.crush() // again, crunch return COMPONENT_CANCEL_ATTACK_CHAIN