Files
Bubberstation/code/datums/mutations/hulk.dm
necromanceranne d556e92d52 Special version of hulk (wizard, warlord superhuman powers) no longer suffer recoil damage from breaking walls. Recoil actually uses wound rolls and not forced wounds (#92992)
## About The Pull Request

### Special versions are no longer vulnerable to recoil

There are two versions of hulk that are not available under normal
circumstances. The version given by wizard's Transformation, and the
version that Warlord pirates spawn with. These versions no longer break
their arms when destroying walls.

Normal versions, like the genetic hulks and the orc variant, still
suffer this effect.

### oof ow my bones

Hulk recoil damage now utilizes RNG wound determination. This allows for
the wound to escalate normally if the wound meets the threshold. The
damage passed means that there is a roughly 50/50 chance to cause a
dislocation, a very slim chance to roll a fracture, and this probability
will grow worse once the arm is wounded and threshold penalties start to
take effect.

This also means that the mutation respects wound resistance and wound
vulnerability, where as the previous behavior did not.

### Cleans up some mutation code a smidge

There is clearly some leftovers from prior refactors still littered
through mutation code, so I did some maintenance while I was here.

## Why It's Good For The Game

https://github.com/tgstation/tgstation/pull/51389 introduced this
weakness to dissuade hulks from flattening walls all the way to the
armory (for the fuckbillionth time). However, it applies to all versions
of hulk, including instances where it is an antagonist's ability or
power. Rather than have antagonists suffer from balance considerations
largely aimed at crew/tiders, we make them exempt so that they can SMASH
to their heart's content.

Hulk wound determination was kind of weird. For one, it relied heavily
on the arm health consistently being 50. When it wasn't, you started
getting into less reliable behaviour. In addition, it does not at all
respect any kind of vulnerability or resistance to wounding.

It is possible that utilizing this system was so that the wounding
effects would be staggered out rather than sporadic, and so slightly
fairer on the user by being more reliable. However, I think letting it
operate similarly to how our natural wound determination effects work
provides some more interesting outcomes for those who might want to use
the mutation in an earnest fashion, and still otherwise limits those
people who are just looking to low effort grief.

I think in retrospect this might have been a pretty heavy-handed nerf,
but I'm not wholly reversing it, I'm just making it...different.
Outcomes should be largely the same.
2025-09-22 19:54:07 -05:00

280 lines
12 KiB
Plaintext

//Hulk turns your skin green, makes you strong, and allows you to shrug off stun effect.
/datum/mutation/hulk
name = "Hulk"
desc = "A poorly understood genome that causes the holder's muscles to expand, inhibit speech and gives the person a bad skin condition."
quality = POSITIVE
locked = TRUE
difficulty = 16
text_gain_indication = span_notice("Your muscles hurt!")
species_allowed = list(SPECIES_HUMAN) //no skeleton/lizard hulk
health_req = 25
instability = POSITIVE_INSTABILITY_MAJOR
conflicts = list(/datum/mutation/hulk/ork)
var/scream_delay = 50
var/last_scream = 0
var/bodypart_color = COLOR_DARK_LIME
/// Determines whether or not our version of hulk breaks their arm when destroying walls.
var/no_recoil = TRUE
mutation_traits = list(
TRAIT_CHUNKYFINGERS,
TRAIT_HULK,
TRAIT_PUSHIMMUNE,
TRAIT_STUNIMMUNE,
)
/datum/mutation/hulk/New(datum/mutation/copymut)
. = ..()
add_speechmod()
/datum/mutation/hulk/proc/add_speechmod()
AddComponent(/datum/component/speechmod, replacements = list("." = "!"), end_string = "!!", uppercase = TRUE)
/datum/mutation/hulk/on_acquiring(mob/living/carbon/human/owner)
. = ..()
if(!.)
return
for(var/obj/item/bodypart/part as anything in owner.bodyparts)
part.add_color_override(bodypart_color, LIMB_COLOR_HULK)
owner.update_body_parts()
owner.add_mood_event("hulk", /datum/mood_event/hulk)
RegisterSignal(owner, COMSIG_LIVING_EARLY_UNARMED_ATTACK, PROC_REF(on_attack_hand))
RegisterSignal(owner, COMSIG_MOB_CLICKON, PROC_REF(check_swing))
owner.add_movespeed_mod_immunities("hulk", /datum/movespeed_modifier/damage_slowdown)
/datum/mutation/hulk/proc/on_attack_hand(mob/living/carbon/human/source, atom/target, proximity, modifiers)
SIGNAL_HANDLER
if(!source.combat_mode || !proximity || LAZYACCESS(modifiers, RIGHT_CLICK))
return NONE
if(!source.can_unarmed_attack())
return COMPONENT_SKIP_ATTACK
if(!target.attack_hulk(owner))
return NONE
if(world.time > (last_scream + scream_delay))
last_scream = world.time
INVOKE_ASYNC(src, PROC_REF(scream_attack), source)
log_combat(source, target, "punched", "hulk powers")
source.do_attack_animation(target, ATTACK_EFFECT_SMASH)
source.changeNext_move(CLICK_CD_MELEE)
return COMPONENT_CANCEL_ATTACK_CHAIN
/datum/mutation/hulk/proc/scream_attack(mob/living/carbon/human/source)
source.say("WAAAAAAAAAAAAAAGH!", forced="hulk")
/datum/mutation/hulk/on_life(seconds_per_tick, times_fired)
if(owner.health < owner.crit_threshold)
on_losing(owner)
to_chat(owner, span_danger("You suddenly feel very weak."))
qdel(src)
/datum/mutation/hulk/on_losing(mob/living/carbon/human/owner)
if(..())
return
for(var/obj/item/bodypart/part as anything in owner.bodyparts)
part.remove_color_override(LIMB_COLOR_HULK)
owner.update_body_parts()
owner.clear_mood_event("hulk")
UnregisterSignal(owner, COMSIG_LIVING_EARLY_UNARMED_ATTACK)
UnregisterSignal(owner, COMSIG_MOB_CLICKON)
owner.remove_movespeed_mod_immunities("hulk", /datum/movespeed_modifier/damage_slowdown)
/// How many steps it takes to throw the mob
#define HULK_TAILTHROW_STEPS 28
/// Run a barrage of checks to see if any given click is actually able to swing
/datum/mutation/hulk/proc/check_swing(mob/living/carbon/human/user, atom/clicked_atom, list/modifiers)
SIGNAL_HANDLER
/// Basically, we only proceed if we're in throw mode with a tailed carbon in our grasp with at least a neck grab and we're not restrained in some way
if(LAZYACCESS(modifiers, ALT_CLICK) || LAZYACCESS(modifiers, SHIFT_CLICK) || LAZYACCESS(modifiers, CTRL_CLICK) || LAZYACCESS(modifiers, MIDDLE_CLICK))
return
if(!user.throw_mode || user.get_active_held_item() || user.zone_selected != BODY_ZONE_PRECISE_GROIN)
return
if(user.grab_state < GRAB_NECK || !iscarbon(user.pulling) || user.buckled || user.incapacitated)
return
var/mob/living/carbon/possible_throwable = user.pulling
if(!possible_throwable.get_organ_slot(ORGAN_SLOT_EXTERNAL_TAIL))
return
if(ishuman(possible_throwable))
var/mob/living/carbon/human/human_throwable = possible_throwable
if(human_throwable.obscured_slots & HIDEJUMPSUIT)
to_chat(user, span_warning("You can't reach [human_throwable]'s tail through [human_throwable.p_their()] [human_throwable.wear_suit.name]!"))
return
user.face_atom(clicked_atom)
INVOKE_ASYNC(src, PROC_REF(setup_swing), user, possible_throwable)
return(COMSIG_MOB_CANCEL_CLICKON)
/// Do a short 2 second do_after before starting the actual swing
/datum/mutation/hulk/proc/setup_swing(mob/living/carbon/human/the_hulk, mob/living/carbon/yeeted_person)
var/original_dir = the_hulk.dir // so no matter if the hulk tries to mess up their direction, they always face where they started when they throw
yeeted_person.forceMove(the_hulk.loc)
yeeted_person.setDir(get_dir(yeeted_person, the_hulk))
log_combat(the_hulk, yeeted_person, "has started swinging by tail")
yeeted_person.Stun(2 SECONDS)
yeeted_person.visible_message(span_danger("[the_hulk] starts grasping [yeeted_person] by the tail..."), \
span_userdanger("[the_hulk] begins grasping your tail!"), span_hear("You hear aggressive shuffling!"), null, the_hulk)
to_chat(the_hulk, span_danger("You start grasping [yeeted_person] by the tail..."))
if(!do_after(the_hulk, 2 SECONDS, yeeted_person))
yeeted_person.visible_message(span_danger("[yeeted_person] breaks free of [the_hulk]'s grasp!"), \
span_userdanger("You break free from [the_hulk]'s grasp!"), span_hear("You hear aggressive shuffling!"), null, the_hulk)
to_chat(the_hulk, span_danger("You lose your grasp on [yeeted_person]'s tail!"))
return
// we're officially a-go!
yeeted_person.Paralyze(8 SECONDS)
yeeted_person.visible_message(span_danger("[the_hulk] starts spinning [yeeted_person] around by [yeeted_person.p_their()] tail!"), \
span_userdanger("[the_hulk] starts spinning you around by your tail!"), span_hear("You hear wooshing sounds!"), null, the_hulk)
to_chat(the_hulk, span_danger("You start spinning [yeeted_person] around by [yeeted_person.p_their()] tail!"))
the_hulk.emote("scream")
yeeted_person.emote("scream")
swing_loop(the_hulk, yeeted_person, 0, original_dir)
/**
* Does the animations for the hulk swing loop
*
* This code is based in part on the wrestling swing code ported from goon, see [code/datums/martial/wrestling.dm]
* credit to: cogwerks, pistoleer, spyguy, angriestibm, marquesas, and stuntwaffle.
* For each step of the swinging, with the delay getting shorter along the way. Checks to see we still have them in our grasp at each step.
*/
/datum/mutation/hulk/proc/swing_loop(mob/living/carbon/human/the_hulk, mob/living/carbon/yeeted_person, step, original_dir)
if(!yeeted_person || !the_hulk || the_hulk.incapacitated)
return
if(get_dist(the_hulk, yeeted_person) > 1 || !isturf(the_hulk.loc) || !isturf(yeeted_person.loc))
to_chat(the_hulk, span_warning("You lose your grasp on [yeeted_person]!"))
return
var/delay = 5
switch (step)
if(24 to INFINITY)
delay = 0.1
if(20 to 23)
delay = 0.5
if(16 to 19)
delay = 1
if(13 to 15)
delay = 2
if(8 to 12)
delay = 3
if(4 to 7)
delay = 3.5
if(0 to 3)
delay = 4
the_hulk.setDir(turn(the_hulk.dir, 90))
var/turf/current_spin_turf = yeeted_person.loc
var/turf/intermediate_spin_turf = get_step(yeeted_person, the_hulk.dir) // the diagonal
var/turf/next_spin_turf = get_step(the_hulk, the_hulk.dir)
var/direction = get_dir(current_spin_turf, intermediate_spin_turf)
if((isturf(current_spin_turf) && current_spin_turf.Exit(yeeted_person, direction)) && (isturf(next_spin_turf) && next_spin_turf.Enter(yeeted_person)))
yeeted_person.forceMove(next_spin_turf)
yeeted_person.face_atom(the_hulk)
var/list/collateral_check = intermediate_spin_turf.contents + next_spin_turf.contents // check the cardinal and the diagonal tiles we swung past
var/turf/collat_throw_target = get_edge_target_turf(yeeted_person, get_dir(current_spin_turf, next_spin_turf)) // what direction we're swinging
for(var/mob/living/collateral_mob in collateral_check)
if(!collateral_mob.density || collateral_mob == yeeted_person)
continue
yeeted_person.adjustBruteLoss(step*0.5)
playsound(collateral_mob,'sound/items/weapons/punch1.ogg',50,TRUE)
log_combat(the_hulk, collateral_mob, "has smacked with tail swing victim")
log_combat(the_hulk, yeeted_person, "has smacked this person into someone while tail swinging") // i have no idea how to better word this
if(collateral_mob == the_hulk) // if the hulk moves wrong and crosses himself
the_hulk.visible_message(span_warning("[the_hulk] smacks [the_hulk.p_them()]self with [yeeted_person]!"), span_userdanger("You end up smacking [yeeted_person] into yourself!"), ignored_mobs = yeeted_person)
to_chat(yeeted_person, span_userdanger("[the_hulk] smacks you into [the_hulk.p_them()]self, turning you free!"))
the_hulk.adjustBruteLoss(step)
return
yeeted_person.visible_message(span_warning("[the_hulk] swings [yeeted_person] directly into [collateral_mob], sending [collateral_mob.p_them()] flying!"), \
span_userdanger("You're smacked into [collateral_mob]!"), ignored_mobs = collateral_mob)
to_chat(collateral_mob, span_userdanger("[the_hulk] swings [yeeted_person] directly into you, sending you flying!"))
collateral_mob.adjustBruteLoss(step*0.5)
collateral_mob.throw_at(collat_throw_target, round(step * 0.25) + 1, round(step * 0.25) + 1)
step -= 5
delay += 5
step++
if(step >= HULK_TAILTHROW_STEPS)
finish_swing(the_hulk, yeeted_person, original_dir)
else if(step < 0)
the_hulk.visible_message(span_danger("[the_hulk] loses [the_hulk.p_their()] momentum on [yeeted_person]!"), span_warning("You lose your momentum on swinging [yeeted_person]!"), ignored_mobs = yeeted_person)
to_chat(yeeted_person, span_userdanger("[the_hulk] loses [the_hulk.p_their()] momentum and lets go of you!"))
else
addtimer(CALLBACK(src, PROC_REF(swing_loop), the_hulk, yeeted_person, step, original_dir), delay)
/// Time to toss the victim at high speed
/datum/mutation/hulk/proc/finish_swing(mob/living/carbon/human/the_hulk, mob/living/carbon/yeeted_person, original_dir)
if(!yeeted_person || !the_hulk || the_hulk.incapacitated)
return
if(get_dist(the_hulk, yeeted_person) > 1 || !isturf(the_hulk.loc) || !isturf(yeeted_person.loc))
to_chat(the_hulk, span_warning("You lose your grasp on [yeeted_person]!"))
return
the_hulk.setDir(original_dir)
yeeted_person.forceMove(the_hulk.loc) // Maybe this will help with the wallthrowing bug.
yeeted_person.visible_message(span_danger("[the_hulk] throws [yeeted_person]!"), \
span_userdanger("You're thrown by [the_hulk]!"), span_hear("You hear aggressive shuffling and a loud thud!"), null, the_hulk)
to_chat(the_hulk, span_danger("You throw [yeeted_person]!"))
playsound(the_hulk.loc, SFX_SWING_HIT, 50, TRUE)
var/turf/T = get_edge_target_turf(the_hulk, the_hulk.dir)
if(!isturf(T))
return
if(!yeeted_person.stat)
yeeted_person.emote("scream")
yeeted_person.throw_at(T, 10, 6, the_hulk, TRUE, TRUE)
log_combat(the_hulk, yeeted_person, "has thrown by tail")
/datum/mutation/hulk/wizardly
name = "Hulk (Magical)"
species_allowed = null //yes skeleton/lizard hulk - note that species that dont have skintone changing (like skellies) get custom handling
health_req = 0
instability = 0
scream_delay = 2.5 SECONDS // halved to be more annoying (spell doesn't last long anyways)
no_recoil = FALSE
mutation_traits = list(
TRAIT_HULK,
TRAIT_PUSHIMMUNE,
TRAIT_STUNIMMUNE,
) // no chunk
/datum/mutation/hulk/superhuman
name = "Hulk (Super)"
health_req = 0
instability = 0
no_recoil = FALSE
mutation_traits = list(
TRAIT_CHUNKYFINGERS,
TRAIT_HULK,
TRAIT_NOSOFTCRIT,
TRAIT_NOHARDCRIT,
TRAIT_PUSHIMMUNE,
TRAIT_STUNIMMUNE,
TRAIT_ANALGESIA,
) // fight till your last breath
/datum/mutation/hulk/superhuman/on_life(seconds_per_tick, times_fired)
return
/datum/mutation/hulk/ork
name = "Ork"
desc = "A mutation caused by a mixup of hulk genes which severely impacts speech centers in owners' brains."
text_gain_indication = span_notice("You feel significantly dumber!")
bodypart_color = COLOR_ASSISTANT_OLIVE
conflicts = list(/datum/mutation/hulk)
/datum/mutation/hulk/ork/add_speechmod()
AddComponent(/datum/component/speechmod, replacements = strings("ork_replacement.json", "ork"), end_string = "!!", uppercase = TRUE)
#undef HULK_TAILTHROW_STEPS