Skip to content

Commit

Permalink
[MIRROR] A comprehensive refactor / cleanup of bullet_hit and `on_h…
Browse files Browse the repository at this point in the history
…it` to cut out a single bad species / mob proc [MDB IGNORE] (#24430)

* A comprehensive refactor / cleanup of `bullet_hit` and `on_hit` to cut out a single bad species / mob proc (#79024)

## About The Pull Request

- Refactored `bullet_act`. Adds `should_call_parent` and refactors
associated children to support that.
   - Fixes silicons sparking off when hit by disabler fire.
- Desnowflakes firing range target integrity and cleans up its
bullet-hole code a bit.
- Cleans up changeling tentacle code a fair bit and fixes it not taking
off throw mode if you fail to catch something.
   - The Sleeping Carp deflection is now signalized
- Nightmare projectile dodging is now signalized and sourced from the
Nightmare's brain rather than species
- Refactored how cardboard cutouts get knocked over to be less
snowflaked / use integrity
- Also adds projectile `on_hit` `should_call_parent` and cleans up a bit
of that, particularly their arguments.
- On hit arguments were passed wrong this entire time, it's a good thing
nothing relied on that.

## Why It's Good For The Game

This is cringe.

https://github.com/tgstation/tgstation/blob/1863eb2cd82e7cee4fdfff37b42d3fd0c7edd797/code/modules/mob/living/carbon/human/_species.dm#L1430-L1442

Bullets should overall act more consistent across mob types and objects.

## Changelog

:cl: Melbert
fix: Silicons don't spark when shot by disablers
fix: Changelings who fail to catch something with a tencacle will have
throw mode disabled automatically
fix: Fixes occasions where you can reflect with Sleeping Carp when you
shouldn't be able to
fix: Fixes some projectiles causing like 20x less eye blur than they
should be
refactor: Refactored bullet-mob interactions
refactor: Nightmare "shadow dodge" projectile ability is now sourced
from their brain
/:cl:

* A comprehensive refactor / cleanup of `bullet_hit` and `on_hit` to cut out a single bad species / mob proc

* Modular changes

---------

Co-authored-by: MrMelbert <[email protected]>
Co-authored-by: Giz <[email protected]>
  • Loading branch information
3 people authored and FFMirrorBot committed Oct 20, 2023
1 parent dc68835 commit f2884ac
Show file tree
Hide file tree
Showing 108 changed files with 697 additions and 540 deletions.
8 changes: 8 additions & 0 deletions code/__DEFINES/dcs/signals/signals_atom/signals_atom_x_act.dm
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,14 @@
///from base of atom/fire_act(): (exposed_temperature, exposed_volume)
#define COMSIG_ATOM_FIRE_ACT "atom_fire_act"
///from base of atom/bullet_act(): (/obj/projectile, def_zone)
#define COMSIG_ATOM_PRE_BULLET_ACT "pre_atom_bullet_act"
/// All this does is prevent default bullet on_hit from being called, [BULLET_ACT_HIT] being return is implied
#define COMPONENT_BULLET_ACTED (1<<0)
/// Forces bullet act to return [BULLET_ACT_BLOCK], takes priority over above
#define COMPONENT_BULLET_BLOCKED (1<<1)
/// Forces bullet act to return [BULLET_ACT_FORCE_PIERCE], takes priority over above
#define COMPONENT_BULLET_PIERCED (1<<2)
///from base of atom/bullet_act(): (/obj/projectile, def_zone)
#define COMSIG_ATOM_BULLET_ACT "atom_bullet_act"
///from base of atom/CheckParts(): (list/parts_list, datum/crafting_recipe/R)
#define COMSIG_ATOM_CHECKPARTS "atom_checkparts"
Expand Down
5 changes: 3 additions & 2 deletions code/datums/components/singularity.dm
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@
)
AddComponent(/datum/component/connect_loc_behalf, parent, loc_connections)

RegisterSignal(parent, COMSIG_ATOM_BULLET_ACT, PROC_REF(consume_bullets))
RegisterSignal(parent, COMSIG_ATOM_PRE_BULLET_ACT, PROC_REF(consume_bullets))

if (notify_admins)
admin_investigate_setup()
Expand All @@ -127,7 +127,7 @@
COMSIG_ATOM_ATTACK_PAW,
COMSIG_ATOM_BLOB_ACT,
COMSIG_ATOM_BSA_BEAM,
COMSIG_ATOM_BULLET_ACT,
COMSIG_ATOM_PRE_BULLET_ACT,
COMSIG_ATOM_BUMPED,
COMSIG_MOVABLE_PRE_MOVE,
COMSIG_ATOM_ATTACKBY,
Expand Down Expand Up @@ -180,6 +180,7 @@
SIGNAL_HANDLER

qdel(projectile)
return COMPONENT_BULLET_BLOCKED

/// Calls singularity_act on the thing passed, usually destroying the object
/datum/component/singularity/proc/default_singularity_act(atom/thing)
Expand Down
4 changes: 0 additions & 4 deletions code/datums/martial/_martial.dm
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,3 @@
if(help_verb)
remove_verb(holder_living, help_verb)
return

///Gets called when a projectile hits the owner. Returning anything other than BULLET_ACT_HIT will stop the projectile from hitting the mob.
/datum/martial_art/proc/on_projectile_hit(mob/living/attacker, obj/projectile/P, def_zone)
return BULLET_ACT_HIT
27 changes: 17 additions & 10 deletions code/datums/martial/sleeping_carp.dm
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,13 @@
return
target.add_traits(list(TRAIT_NOGUNS, TRAIT_HARDLY_WOUNDED, TRAIT_NODISMEMBER), SLEEPING_CARP_TRAIT)
RegisterSignal(target, COMSIG_ATOM_ATTACKBY, PROC_REF(on_attackby))
RegisterSignal(target, COMSIG_ATOM_PRE_BULLET_ACT, PROC_REF(hit_by_projectile))
target.faction |= FACTION_CARP //:D

/datum/martial_art/the_sleeping_carp/on_remove(mob/living/target)
target.remove_traits(list(TRAIT_NOGUNS, TRAIT_HARDLY_WOUNDED, TRAIT_NODISMEMBER), SLEEPING_CARP_TRAIT)
UnregisterSignal(target, COMSIG_ATOM_ATTACKBY)
UnregisterSignal(target, COMSIG_ATOM_PRE_BULLET_ACT)
target.faction -= FACTION_CARP //:(
. = ..()

Expand Down Expand Up @@ -113,6 +115,8 @@
return ..()

/datum/martial_art/the_sleeping_carp/proc/can_deflect(mob/living/carp_user)
if(!can_use(carp_user) || !carp_user.throw_mode)
return FALSE
if(carp_user.incapacitated(IGNORE_GRAB)) //NO STUN
return FALSE
if(!(carp_user.mobility_flags & MOBILITY_USE)) //NO UNABLE TO USE
Expand All @@ -124,17 +128,20 @@
return FALSE
return TRUE

/datum/martial_art/the_sleeping_carp/on_projectile_hit(mob/living/carp_user, obj/projectile/P, def_zone)
. = ..()
/datum/martial_art/the_sleeping_carp/proc/hit_by_projectile(mob/living/carp_user, obj/projectile/hitting_projectile, def_zone)
SIGNAL_HANDLER

if(!can_deflect(carp_user))
return BULLET_ACT_HIT
if(carp_user.throw_mode)
carp_user.visible_message(span_danger("[carp_user] effortlessly swats the projectile aside! They can block bullets with their bare hands!"), span_userdanger("You deflect the projectile!"))
playsound(get_turf(carp_user), pick('sound/weapons/bulletflyby.ogg', 'sound/weapons/bulletflyby2.ogg', 'sound/weapons/bulletflyby3.ogg'), 75, TRUE)
P.firer = carp_user
P.set_angle(rand(0, 360))//SHING
return BULLET_ACT_FORCE_PIERCE
return BULLET_ACT_HIT
return NONE

carp_user.visible_message(
span_danger("[carp_user] effortlessly swats [hitting_projectile] aside! [carp_user.p_They()] can block bullets with [carp_user.p_their()] bare hands!"),
span_userdanger("You deflect [hitting_projectile]!"),
)
playsound(carp_user, pick('sound/weapons/bulletflyby.ogg', 'sound/weapons/bulletflyby2.ogg', 'sound/weapons/bulletflyby3.ogg'), 75, TRUE)
hitting_projectile.firer = carp_user
hitting_projectile.set_angle(rand(0, 360))//SHING
return COMPONENT_BULLET_PIERCED

///Signal from getting attacked with an item, for a special interaction with touch spells
/datum/martial_art/the_sleeping_carp/proc/on_attackby(mob/living/carp_user, obj/item/attack_weapon, mob/attacker, params)
Expand Down
2 changes: 2 additions & 0 deletions code/game/atom_defense.dm
Original file line number Diff line number Diff line change
Expand Up @@ -151,4 +151,6 @@

/// A cut-out proc for [/atom/proc/bullet_act] so living mobs can have their own armor behavior checks without causing issues with needing their own on_hit call
/atom/proc/check_projectile_armor(def_zone, obj/projectile/impacting_projectile, is_silent)
if(uses_integrity)
return clamp(PENETRATE_ARMOUR(get_armor_rating(impacting_projectile.armor_flag), impacting_projectile.armour_penetration), 0, 100)
return 0
32 changes: 23 additions & 9 deletions code/game/atoms.dm
Original file line number Diff line number Diff line change
Expand Up @@ -586,19 +586,33 @@
/**
* React to a hit by a projectile object
*
* Default behaviour is to send the [COMSIG_ATOM_BULLET_ACT] and then call [on_hit][/obj/projectile/proc/on_hit] on the projectile.
*
* @params
* hitting_projectile - projectile
* def_zone - zone hit
* piercing_hit - is this hit piercing or normal?
* * hitting_projectile - projectile
* * def_zone - zone hit
* * piercing_hit - is this hit piercing or normal?
*/
/atom/proc/bullet_act(obj/projectile/hitting_projectile, def_zone, piercing_hit = FALSE)
SHOULD_CALL_PARENT(TRUE)

var/sigreturn = SEND_SIGNAL(src, COMSIG_ATOM_PRE_BULLET_ACT, hitting_projectile, def_zone)
if(sigreturn & COMPONENT_BULLET_PIERCED)
return BULLET_ACT_FORCE_PIERCE
if(sigreturn & COMPONENT_BULLET_BLOCKED)
return BULLET_ACT_BLOCK
if(sigreturn & COMPONENT_BULLET_ACTED)
return BULLET_ACT_HIT

SEND_SIGNAL(src, COMSIG_ATOM_BULLET_ACT, hitting_projectile, def_zone)
// This armor check only matters for the visuals and messages in on_hit(), it's not actually used to reduce damage since
// only living mobs use armor to reduce damage, but on_hit() is going to need the value no matter what is shot.
var/visual_armor_check = check_projectile_armor(def_zone, hitting_projectile)
. = hitting_projectile.on_hit(src, visual_armor_check, def_zone, piercing_hit)
if(QDELETED(hitting_projectile)) // Signal deleted it?
return BULLET_ACT_BLOCK

return hitting_projectile.on_hit(
target = src,
// This armor check only matters for the visuals and messages in on_hit(), it's not actually used to reduce damage since
// only living mobs use armor to reduce damage, but on_hit() is going to need the value no matter what is shot.
blocked = check_projectile_armor(def_zone, hitting_projectile),
pierce_hit = piercing_hit,
)

///Return true if we're inside the passed in atom
/atom/proc/in_contents_of(container)//can take class or object instance as argument
Expand Down
3 changes: 2 additions & 1 deletion code/game/objects/effects/phased_mob.dm
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,8 @@
/obj/effect/dummy/phased_mob/ex_act()
return FALSE

/obj/effect/dummy/phased_mob/bullet_act(blah)
/obj/effect/dummy/phased_mob/bullet_act(obj/projectile/hitting_projectile, def_zone, piercing_hit = FALSE)
SHOULD_CALL_PARENT(FALSE)
return BULLET_ACT_FORCE_PIERCE

/obj/effect/dummy/phased_mob/relaymove(mob/living/user, direction)
Expand Down
41 changes: 15 additions & 26 deletions code/game/objects/items/cardboard_cutouts.dm
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
icon_state = "cutout_basic"
w_class = WEIGHT_CLASS_BULKY
resistance_flags = FLAMMABLE
obj_flags = CAN_BE_HIT
item_flags = NO_PIXEL_RANDOM_DROP
/// If the cutout is pushed over and has to be righted
var/pushed_over = FALSE
Expand Down Expand Up @@ -35,7 +36,7 @@

//ATTACK HAND IGNORING PARENT RETURN VALUE
/obj/item/cardboard_cutout/attack_hand(mob/living/user, list/modifiers)
if(!user.combat_mode || pushed_over)
if(!user.combat_mode || pushed_over || !isturf(loc))
return ..()
user.visible_message(span_warning("[user] pushes over [src]!"), span_danger("You push over [src]!"))
playsound(src, 'sound/weapons/genhit.ogg', 50, TRUE)
Expand All @@ -60,32 +61,20 @@
/obj/item/cardboard_cutout/attackby(obj/item/I, mob/living/user, params)
if(istype(I, /obj/item/toy/crayon))
change_appearance(I, user)
return
// Why yes, this does closely resemble mob and object attack code.
if(I.item_flags & NOBLUDGEON)
return
if(!I.force)
playsound(loc, 'sound/weapons/tap.ogg', get_clamped_volume(), TRUE, -1)
else if(I.hitsound)
playsound(loc, I.hitsound, get_clamped_volume(), TRUE, -1)

user.changeNext_move(CLICK_CD_MELEE)
user.do_attack_animation(src)

if(I.force)
user.visible_message(span_danger("[user] hits [src] with [I]!"), \
span_danger("You hit [src] with [I]!"))
if(prob(I.force))
push_over()

/obj/item/cardboard_cutout/bullet_act(obj/projectile/P, def_zone, piercing_hit = FALSE)
if(istype(P, /obj/projectile/bullet))
P.on_hit(src, 0, piercing_hit)
visible_message(span_danger("[src] is hit by [P]!"))
playsound(src, 'sound/weapons/slice.ogg', 50, TRUE)
if(prob(P.damage))
return TRUE

return ..()

/obj/item/cardboard_cutout/take_damage(damage_amount, damage_type, damage_flag, sound_effect, attack_dir, armour_penetration)
. = ..()
var/damage_sustained = . || 0
if((damage_flag == BULLET || damage_flag == MELEE) && (damage_type == BRUTE) && prob(damage_sustained))
push_over()
return BULLET_ACT_HIT

/obj/item/cardboard_cutout/deconstruct(disassembled)
if(!(flags_1 & (HOLOGRAM_1|NODECONSTRUCT_1)))
new /obj/item/stack/sheet/cardboard(loc, 1)
return ..()

/proc/get_cardboard_cutout_instance(datum/cardboard_cutout/cardboard_cutout)
ASSERT(ispath(cardboard_cutout), "[cardboard_cutout] is not a path of /datum/cardboard_cutout")
Expand Down
3 changes: 2 additions & 1 deletion code/game/objects/items/crayons.dm
Original file line number Diff line number Diff line change
Expand Up @@ -815,7 +815,8 @@
carbon_target.set_eye_blur_if_lower(6 SECONDS)
carbon_target.adjust_temp_blindness(2 SECONDS)
if(carbon_target.get_eye_protection() <= 0) // no eye protection? ARGH IT BURNS. Warning: don't add a stun here. It's a roundstart item with some quirks.
carbon_target.apply_effects(eyeblur = 5, jitter = 10)
carbon_target.adjust_jitter(1 SECONDS)
carbon_target.adjust_eye_blur(0.5 SECONDS)
flash_color(carbon_target, flash_color=paint_color, flash_time=40)
if(ishuman(carbon_target) && actually_paints)
var/mob/living/carbon/human/human_target = carbon_target
Expand Down
16 changes: 11 additions & 5 deletions code/game/objects/items/melee/misc.dm
Original file line number Diff line number Diff line change
Expand Up @@ -235,6 +235,7 @@
shard.countdown = null
START_PROCESSING(SSobj, src)
visible_message(span_warning("[src] appears, balanced ever so perfectly on its hilt. This isn't ominous at all."))
RegisterSignal(src, COMSIG_ATOM_PRE_BULLET_ACT, PROC_REF(eat_bullets))

/obj/item/melee/supermatter_sword/process()
if(balanced || throwing || ismob(src.loc) || isnull(src.loc))
Expand Down Expand Up @@ -283,11 +284,16 @@
consume_everything()
return TRUE

/obj/item/melee/supermatter_sword/bullet_act(obj/projectile/projectile)
visible_message(span_danger("[projectile] smacks into [src] and rapidly flashes to ash."),\
span_hear("You hear a loud crack as you are washed with a wave of heat."))
consume_everything(projectile)
return BULLET_ACT_HIT
/obj/item/melee/supermatter_sword/proc/eat_bullets(datum/source, obj/projectile/hitting_projectile)
SIGNAL_HANDLER

visible_message(
span_danger("[hitting_projectile] smacks into [source] and rapidly flashes to ash."),
null,
span_hear("You hear a loud crack as you are washed with a wave of heat."),
)
consume_everything(hitting_projectile)
return COMPONENT_BULLET_BLOCKED

/obj/item/melee/supermatter_sword/suicide_act(mob/living/user)
user.visible_message(span_suicide("[user] touches [src]'s blade. It looks like [user.p_theyre()] tired of waiting for the radiation to kill [user.p_them()]!"))
Expand Down
107 changes: 59 additions & 48 deletions code/game/objects/items/shooting_range.dm
Original file line number Diff line number Diff line change
Expand Up @@ -4,73 +4,84 @@
icon = 'icons/obj/structures.dmi'
icon_state = "target_h"
density = FALSE
var/hp = 1800
max_integrity = 1800
item_flags = CAN_BE_HIT
/// Lazylist to keep track of bullet-hole overlays.
var/list/bullethole_overlays

/obj/item/target/welder_act(mob/living/user, obj/item/I)
..()
if(I.use_tool(src, user, 0, volume=40))
for (var/bullethole in bullethole_overlays)
cut_overlay(bullethole)
bullethole_overlays = null
to_chat(user, span_notice("You slice off [src]'s uneven chunks of aluminium and scorch marks."))
/obj/item/target/welder_act(mob/living/user, obj/item/tool)
if(tool.use_tool(src, user, 0 SECONDS, volume = 40))
LAZYNULL(bullethole_overlays)
balloon_alert(user, "target repaired")
update_appearance(UPDATE_OVERLAYS)
return TRUE

/obj/item/target/update_overlays()
. = ..()
. |= bullethole_overlays

/obj/item/target/bullet_act(obj/projectile/hitting_projectile, def_zone, piercing_hit = FALSE)
if(prob(25))
return ..() // RNG change to just not leave a mark, like walls
if(length(overlays) > 35)
return ..() // Too many bullets, we're done here

// Projectiles which do not deal damage will not leave dent / scorch mark graphics.
// However we snowflake some projectiles to leave them anyway, because they're appropriate.
var/static/list/always_leave_marks
if(isnull(always_leave_marks))
always_leave_marks = typecacheof(list(
/obj/projectile/beam/practice,
/obj/projectile/beam/laser/carbine/practice,
))

var/is_invalid_damage = hitting_projectile.damage_type != BRUTE && hitting_projectile.damage_type != BURN
var/is_safe = !hitting_projectile.is_hostile_projectile()
var/is_generic_projectile = !is_type_in_typecache(hitting_projectile, always_leave_marks)
if(is_generic_projectile && (is_invalid_damage || is_safe))
return ..() // Don't bother unless it's real shit

var/p_x = hitting_projectile.p_x + pick(0, 0, 0, 0, 0, -1, 1) // really ugly way of coding "sometimes offset p_x!"
var/p_y = hitting_projectile.p_y + pick(0, 0, 0, 0, 0, -1, 1)
var/icon/our_icon = icon(icon, icon_state)
if(!our_icon.GetPixel(p_x, p_y) || hitting_projectile.original != src)
return BULLET_ACT_FORCE_PIERCE // We, "missed", I guess?

. = ..()
if(. != BULLET_ACT_HIT)
return

var/image/bullet_hole = image('icons/effects/effects.dmi', "dent", OBJ_LAYER + 0.5)
bullet_hole.pixel_x = p_x - 1 //offset correction
bullet_hole.pixel_y = p_y - 1
if(hitting_projectile.damage_type != BRUTE)
bullet_hole.setDir(pick(GLOB.cardinals))// random scorch design
if(hitting_projectile.damage < 20 && is_generic_projectile)
bullet_hole.icon_state = "light_scorch"
else
bullet_hole.icon_state = "scorch"

LAZYADD(bullethole_overlays, bullet_hole)
update_appearance(UPDATE_OVERLAYS)

/obj/item/target/syndicate
icon_state = "target_s"
desc = "A shooting target that looks like syndicate scum."
hp = 2600
max_integrity = 2600

/obj/item/target/alien
icon_state = "target_q"
desc = "A shooting target that looks like a xenomorphic alien."
hp = 2350
max_integrity = 2350

/obj/item/target/alien/anchored
anchored = TRUE

/obj/item/target/clown
icon_state = "target_c"
desc = "A shooting target that looks like a useless clown."
hp = 2000

#define DECALTYPE_SCORCH 1
#define DECALTYPE_BULLET 2
max_integrity = 2000

/obj/item/target/clown/bullet_act(obj/projectile/P)
. = ..()
playsound(src.loc, 'sound/items/bikehorn.ogg', 50, TRUE)

/obj/item/target/bullet_act(obj/projectile/P)
if(istype(P, /obj/projectile/bullet)) // If it's a foam dart, don't bother with any of this other shit
return P.on_hit(src, 0)
var/p_x = P.p_x + pick(0,0,0,0,0,-1,1) // really ugly way of coding "sometimes offset P.p_x!"
var/p_y = P.p_y + pick(0,0,0,0,0,-1,1)
var/decaltype = DECALTYPE_SCORCH
if(istype(P, /obj/projectile/bullet))
decaltype = DECALTYPE_BULLET
var/icon/C = icon(icon,icon_state)
if(C.GetPixel(p_x, p_y) && P.original == src && overlays.len <= 35) // if the located pixel isn't blank (null)
hp -= P.damage
if(hp <= 0)
visible_message(span_danger("[src] breaks into tiny pieces and collapses!"))
qdel(src)
var/image/bullet_hole = image('icons/effects/effects.dmi', "scorch", OBJ_LAYER + 0.5)
bullet_hole.pixel_x = p_x - 1 //offset correction
bullet_hole.pixel_y = p_y - 1
if(decaltype == DECALTYPE_SCORCH)
bullet_hole.setDir(pick(NORTH,SOUTH,EAST,WEST))// random scorch design
if(P.damage >= 20 || istype(P, /obj/projectile/beam/practice))
bullet_hole.setDir(pick(NORTH,SOUTH,EAST,WEST))
else
bullet_hole.icon_state = "light_scorch"
else
bullet_hole.icon_state = "dent"
LAZYADD(bullethole_overlays, bullet_hole)
add_overlay(bullet_hole)
return BULLET_ACT_HIT
return BULLET_ACT_FORCE_PIERCE

#undef DECALTYPE_SCORCH
#undef DECALTYPE_BULLET
playsound(src, 'sound/items/bikehorn.ogg', 50, TRUE)
Loading

0 comments on commit f2884ac

Please sign in to comment.