Skip to content

Commit

Permalink
[MIRROR] Living Limb fixes (feat: Basic mobs attack random body zones…
Browse files Browse the repository at this point in the history
… again) (#1969) (#2873)

* Living Limb fixes (feat: Basic mobs attack random body zones again) (#82556)

## About The Pull Request

Reworks Living Limb code to fix a bunch of runtimes and issues I saw
while testing Bioscrambler.
Specifically, the contained mobs are now initialised via element
following attachment so that signal registration can occur at the
correct time. This allows limbs to function correctly when added from
nullspace via admin panel or bioscrambler.

Secondarily (and more wide-ranging) at some point (probably #79563) we
inadvertently made basic mobs only attack the target's chest instead of
spreading damage.
This is problematic for Living Flesh which can only attach itself to
damaged limbs but was left unable to attack damaged limbs.

I've fixed this in a way which is maybe stupid: adding an element which
randomises attack zone pre-attack.
Living limbs also limit this to _only_ limbs (although it will fall back
to chest if you have no limbs at all).
This is _technically_ still different, the previous behaviour used
`adjustBruteLoss` and `adjustFireLoss` and would spread the damage
across your entire body, but there isn't a route to that via the new
interface and this seems close enough.

## Changelog

:cl:
fix: Living Limbs created by Bioscrambler will be alive.
fix: Living Limbs can once more attach themselves to your body.
balance: Living Limbs will prioritise attacking your limbs.
fix: Basic Mobs will once again spread their damage across body zones
instead of only attacking your chest.
/:cl:

* Living Limb fixes (feat: Basic mobs attack random body zones again)

---------

Co-authored-by: NovaBot <[email protected]>
Co-authored-by: Jacquerel <[email protected]>
  • Loading branch information
3 people authored Apr 14, 2024
1 parent e7ce24b commit 8c17f50
Show file tree
Hide file tree
Showing 13 changed files with 86 additions and 21 deletions.
2 changes: 2 additions & 0 deletions code/__DEFINES/basic_mobs.dm
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@
#define IMMUNE_TO_FISTS (1<<4)
/// Mob is immune to getting wet
#define IMMUNE_TO_GETTING_WET (1<<5)
/// Disables the function of attacking random body zones
#define PRECISE_ATTACK_ZONES (1<<6)

/// Temporary trait applied when an attack forecast animation has completed
#define TRAIT_BASIC_ATTACK_FORECAST "trait_basic_attack_forecast"
Expand Down
2 changes: 2 additions & 0 deletions code/__DEFINES/combat.dm
Original file line number Diff line number Diff line change
Expand Up @@ -282,6 +282,8 @@ GLOBAL_LIST_INIT(shove_disarming_types, typecacheof(list(
#define BODY_ZONE_L_LEG "l_leg"
#define BODY_ZONE_R_LEG "r_leg"

GLOBAL_LIST_INIT(all_body_zones, list(BODY_ZONE_HEAD, BODY_ZONE_CHEST, BODY_ZONE_R_ARM, BODY_ZONE_L_ARM, BODY_ZONE_R_LEG, BODY_ZONE_L_LEG))
GLOBAL_LIST_INIT(limb_zones, list(BODY_ZONE_R_ARM, BODY_ZONE_L_ARM, BODY_ZONE_R_LEG, BODY_ZONE_L_LEG))
GLOBAL_LIST_INIT(arm_zones, list(BODY_ZONE_L_ARM, BODY_ZONE_R_ARM))

#define BODY_ZONE_PRECISE_EYES "eyes"
Expand Down
33 changes: 33 additions & 0 deletions code/datums/elements/attack_zone_randomiser.dm
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
/// Pick a random attack zone before you attack something
/datum/element/attack_zone_randomiser
element_flags = ELEMENT_BESPOKE
argument_hash_start_idx = 2
/// List of attack zones you can select, should be a subset of GLOB.all_body_zones
var/list/valid_attack_zones

/datum/element/attack_zone_randomiser/Attach(datum/target, list/valid_attack_zones = GLOB.all_body_zones)
. = ..()
if (!isliving(target))
return ELEMENT_INCOMPATIBLE
RegisterSignals(target, list(COMSIG_HOSTILE_PRE_ATTACKINGTARGET, COMSIG_LIVING_UNARMED_ATTACK), PROC_REF(randomise))
src.valid_attack_zones = valid_attack_zones

/datum/element/attack_zone_randomiser/Detach(datum/source)
UnregisterSignal(source, list (COMSIG_HOSTILE_PRE_ATTACKINGTARGET, COMSIG_LIVING_UNARMED_ATTACK))
return ..()

/// If we're attacking a carbon, pick a random defence zone
/datum/element/attack_zone_randomiser/proc/randomise(mob/living/source, atom/target)
SIGNAL_HANDLER
if (!iscarbon(target))
return
var/mob/living/living_target = target
var/list/blacklist_zones = GLOB.all_body_zones - valid_attack_zones
var/new_zone = living_target.get_random_valid_zone(blacklisted_parts = blacklist_zones, bypass_warning = TRUE)
if (isnull(new_zone))
new_zone = BODY_ZONE_CHEST
var/atom/movable/screen/zone_sel/zone_selector = source.hud_used?.zone_select
if (isnull(zone_selector))
source.zone_selected = new_zone
else
zone_selector.set_selected_zone(new_zone, source, should_log = FALSE)
19 changes: 19 additions & 0 deletions code/datums/elements/living_limb_initialiser.dm
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
/// Spawns a living limb mob inside a limb upon attachment if it doesn't have one
/datum/element/living_limb_initialiser

/datum/element/living_limb_initialiser/Attach(atom/target)
. = ..()
if(!isbodypart(target))
return ELEMENT_INCOMPATIBLE
RegisterSignal(target, COMSIG_BODYPART_CHANGED_OWNER, PROC_REF(try_animate_limb))

/datum/element/living_limb_initialiser/Detach(atom/target)
UnregisterSignal(target, COMSIG_BODYPART_CHANGED_OWNER)
return ..()

/// Create a living limb mob inside the limb if it doesn't already have one
/datum/element/living_limb_initialiser/proc/try_animate_limb(obj/item/bodypart/part)
SIGNAL_HANDLER
if (locate(/mob/living/basic/living_limb_flesh) in part)
return
new /mob/living/basic/living_limb_flesh(part, part)
2 changes: 1 addition & 1 deletion code/modules/mining/equipment/mining_tools.dm
Original file line number Diff line number Diff line change
Expand Up @@ -372,7 +372,7 @@
var/atom/throw_target = get_edge_target_turf(target_mob, get_dir(user, get_step_away(target_mob, user)))
target_mob.throw_at(throw_target, 2, 2, user, gentle = TRUE)
target_mob.Knockdown(2 SECONDS)
var/body_zone = pick(BODY_ZONE_HEAD, BODY_ZONE_CHEST, BODY_ZONE_R_ARM, BODY_ZONE_L_ARM, BODY_ZONE_R_LEG, BODY_ZONE_L_LEG)
var/body_zone = pick(GLOB.all_body_zones)
user.apply_damage(force / recoil_factor, BRUTE, body_zone, user.run_armor_check(body_zone, MELEE))
to_chat(user, span_danger("The weight of the Big Slappy recoils!"))
log_combat(user, user, "recoiled Big Slappy into")
6 changes: 6 additions & 0 deletions code/modules/mob/living/basic/basic.dm
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,7 @@
return
apply_atmos_requirements(mapload)
apply_temperature_requirements(mapload)
apply_target_randomisation()

/mob/living/basic/proc/on_ssair_init(datum/source)
SIGNAL_HANDLER
Expand All @@ -142,6 +143,11 @@
return
AddElement(/datum/element/body_temp_sensitive, minimum_survivable_temperature, maximum_survivable_temperature, unsuitable_cold_damage, unsuitable_heat_damage, mapload)

/mob/living/basic/proc/apply_target_randomisation()
if (basic_mob_flags & PRECISE_ATTACK_ZONES)
return
AddElement(/datum/element/attack_zone_randomiser)

/mob/living/basic/Life(seconds_per_tick = SSMOBS_DT, times_fired)
. = ..()
if(staminaloss > 0)
Expand Down
9 changes: 7 additions & 2 deletions code/modules/mob/living/basic/ruin_defender/flesh.dm
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,9 @@
if(!isnull(limb))
register_to_limb(limb)

/mob/living/basic/living_limb_flesh/apply_target_randomisation()
AddElement(/datum/element/attack_zone_randomiser, GLOB.limb_zones)

/mob/living/basic/living_limb_flesh/Destroy(force)
. = ..()
if(current_bodypart)
Expand Down Expand Up @@ -71,6 +74,8 @@
if(!victim.CanReach(movable))
continue
candidates += movable
if(!length(candidates))
return
var/atom/movable/candidate = pick(candidates)
if(isnull(candidate))
return
Expand Down Expand Up @@ -123,9 +128,9 @@
part_type = /obj/item/bodypart/leg/right/flesh

target.visible_message(span_danger("[src] [target_part ? "tears off and attaches itself" : "attaches itself"] to where [target][target.p_s()] limb used to be!"))
var/obj/item/bodypart/new_bodypart = new part_type(TRUE) //dont_spawn_flesh, we cant use named arguments here
new_bodypart.replace_limb(target, TRUE)
var/obj/item/bodypart/new_bodypart = new part_type()
forceMove(new_bodypart)
new_bodypart.replace_limb(target, TRUE)
register_to_limb(new_bodypart)

/mob/living/basic/living_limb_flesh/proc/owner_shocked(datum/source, shock_damage, shock_source, siemens_coeff, flags)
Expand Down
2 changes: 1 addition & 1 deletion code/modules/religion/burdened/psyker.dm
Original file line number Diff line number Diff line change
Expand Up @@ -341,7 +341,7 @@
else
times_dry_fired = 0
var/turf/target_turf = get_offset_target_turf(get_ranged_target_turf(owner, owner.dir, 7), dx = rand(-1, 1), dy = rand(-1, 1))
held_gun.process_fire(target_turf, owner, TRUE, null, pick(BODY_ZONE_HEAD, BODY_ZONE_CHEST, BODY_ZONE_R_ARM, BODY_ZONE_L_ARM, BODY_ZONE_R_LEG, BODY_ZONE_L_LEG))
held_gun.process_fire(target_turf, owner, TRUE, null, pick(GLOB.all_body_zones))
held_gun.semicd = FALSE

/datum/action/cooldown/spell/charged/psychic_booster
Expand Down
4 changes: 2 additions & 2 deletions code/modules/surgery/bodyparts/_bodyparts.dm
Original file line number Diff line number Diff line change
Expand Up @@ -775,14 +775,14 @@
if(owner == new_owner)
return FALSE //`null` is a valid option, so we need to use a num var to make it clear no change was made.

SEND_SIGNAL(src, COMSIG_BODYPART_CHANGED_OWNER, new_owner, owner)

if(owner)
. = owner //return value is old owner
clear_ownership(owner)
if(new_owner)
apply_ownership(new_owner)

SEND_SIGNAL(src, COMSIG_BODYPART_CHANGED_OWNER, new_owner, owner)

refresh_bleed_rate()
return .

Expand Down
2 changes: 1 addition & 1 deletion code/modules/surgery/bodyparts/dismemberment.dm
Original file line number Diff line number Diff line change
Expand Up @@ -377,7 +377,7 @@

/mob/living/carbon/proc/regenerate_limbs(list/excluded_zones = list())
SEND_SIGNAL(src, COMSIG_CARBON_REGENERATE_LIMBS, excluded_zones)
var/list/zone_list = list(BODY_ZONE_HEAD, BODY_ZONE_CHEST, BODY_ZONE_R_ARM, BODY_ZONE_L_ARM, BODY_ZONE_R_LEG, BODY_ZONE_L_LEG)
var/list/zone_list = GLOB.all_body_zones.Copy()

var/list/dismembered_by_copy = body_zone_dismembered_by?.Copy()

Expand Down
4 changes: 2 additions & 2 deletions code/modules/surgery/bodyparts/helpers.dm
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@

/mob/living/carbon/proc/get_missing_limbs()
RETURN_TYPE(/list)
var/list/full = list(BODY_ZONE_HEAD, BODY_ZONE_CHEST, BODY_ZONE_R_ARM, BODY_ZONE_L_ARM, BODY_ZONE_R_LEG, BODY_ZONE_L_LEG)
var/list/full = GLOB.all_body_zones.Copy()
for(var/zone in full)
if(get_bodypart(zone))
full -= zone
Expand All @@ -100,7 +100,7 @@
return list()

/mob/living/carbon/get_disabled_limbs()
var/list/full = list(BODY_ZONE_HEAD, BODY_ZONE_CHEST, BODY_ZONE_R_ARM, BODY_ZONE_L_ARM, BODY_ZONE_R_LEG, BODY_ZONE_L_LEG)
var/list/full = GLOB.all_body_zones.Copy()
var/list/disabled = list()
for(var/zone in full)
var/obj/item/bodypart/affecting = get_bodypart(zone)
Expand Down
20 changes: 8 additions & 12 deletions code/modules/surgery/bodyparts/species_parts/misc_bodyparts.dm
Original file line number Diff line number Diff line change
Expand Up @@ -584,38 +584,34 @@
limb_id = BODYPART_ID_MEAT
should_draw_greyscale = FALSE

/obj/item/bodypart/arm/left/flesh/Initialize(mapload, dont_spawn_flesh = FALSE)
/obj/item/bodypart/arm/left/flesh/Initialize(mapload)
. = ..()
if(!dont_spawn_flesh)
new /mob/living/basic/living_limb_flesh(src, src)
ADD_TRAIT(src, TRAIT_IGNORED_BY_LIVING_FLESH, BODYPART_TRAIT)
AddElement(/datum/element/living_limb_initialiser)

/obj/item/bodypart/arm/right/flesh
limb_id = BODYPART_ID_MEAT
should_draw_greyscale = FALSE

/obj/item/bodypart/arm/right/flesh/Initialize(mapload, dont_spawn_flesh = FALSE)
/obj/item/bodypart/arm/right/flesh/Initialize(mapload)
. = ..()
if(!dont_spawn_flesh)
new /mob/living/basic/living_limb_flesh(src, src)
ADD_TRAIT(src, TRAIT_IGNORED_BY_LIVING_FLESH, BODYPART_TRAIT)
AddElement(/datum/element/living_limb_initialiser)

/obj/item/bodypart/leg/left/flesh
limb_id = BODYPART_ID_MEAT
should_draw_greyscale = FALSE

/obj/item/bodypart/leg/left/flesh/Initialize(mapload, dont_spawn_flesh = FALSE)
/obj/item/bodypart/leg/left/flesh/Initialize(mapload)
. = ..()
if(!dont_spawn_flesh)
new /mob/living/basic/living_limb_flesh(src, src)
ADD_TRAIT(src, TRAIT_IGNORED_BY_LIVING_FLESH, BODYPART_TRAIT)
AddElement(/datum/element/living_limb_initialiser)

/obj/item/bodypart/leg/right/flesh
limb_id = BODYPART_ID_MEAT
should_draw_greyscale = FALSE

/obj/item/bodypart/leg/right/flesh/Initialize(mapload, dont_spawn_flesh = FALSE)
/obj/item/bodypart/leg/right/flesh/Initialize(mapload)
. = ..()
if(!dont_spawn_flesh)
new /mob/living/basic/living_limb_flesh(src, src)
ADD_TRAIT(src, TRAIT_IGNORED_BY_LIVING_FLESH, BODYPART_TRAIT)
AddElement(/datum/element/living_limb_initialiser)
2 changes: 2 additions & 0 deletions tgstation.dme
Original file line number Diff line number Diff line change
Expand Up @@ -1467,6 +1467,7 @@
#include "code\datums\elements\atmos_requirements.dm"
#include "code\datums\elements\atmos_sensitive.dm"
#include "code\datums\elements\attack_equip.dm"
#include "code\datums\elements\attack_zone_randomiser.dm"
#include "code\datums\elements\backblast.dm"
#include "code\datums\elements\bane.dm"
#include "code\datums\elements\basic_eating.dm"
Expand Down Expand Up @@ -1548,6 +1549,7 @@
#include "code\datums\elements\light_blocking.dm"
#include "code\datums\elements\light_eaten.dm"
#include "code\datums\elements\light_eater.dm"
#include "code\datums\elements\living_limb_initialiser.dm"
#include "code\datums\elements\loomable.dm"
#include "code\datums\elements\mirage_border.dm"
#include "code\datums\elements\mob_access.dm"
Expand Down

0 comments on commit 8c17f50

Please sign in to comment.