From 160175ee8b2df4a6112fb404a2ddab907ca5a54d Mon Sep 17 00:00:00 2001
From: silicons <2003111+silicons@users.noreply.github.com>
Date: Wed, 9 Dec 2020 13:29:27 -0700
Subject: [PATCH] pass_flags handling refactor + rewrites a part of projectiles
for the n-th time (#54924)
Yeah uhh this'll probably need testmerging even after it's done because yeah it's a bit big.
If y'all want me to atomize this into two PRs (pass flags vs projectiles) tell me please. Pass flags would have to go in first though, in that case, as new projectile hit handling will rely on pass_flags_self.
Pass flags:
Pass flags handling now uses an atom variable named pass_flags_self.
If any of these match a pass_flag on a thing trying to pass through, it's allowed through by default.
This makes overriding CanAllowThrough unnecessary for the majority of things. I've however not removed overrides for very.. weird cases, like plastic flaps which uses a prob(60) for letting PASSGLASS things through for god knows why.
LETPASSTHROW is now on pass_flags_self
Projectiles:
Not finalized yet, need to do something to make the system I have in mind have less unneeded overhead + snowflake
Basically, for piercing/phasing/otherwise projectiles that go through things instead of hitting the first dense object, I have them use pass_flags flags for two new variables, projectile_phasing and projectile_piercing. Anything with pass_flags_self in the former gets phased through entirely. Anything in the latter gets hit, and the projectile then goes through. on_hit will also register a piercing hit vs a normal hit (so things like missiles can only explode on a normal hit or otherwise, instead of exploding multiple times. Not needed as missiles qdel(src) right now but it's nice to have for the future).
I still need to decide what to do for hit handling proper, as Bump() is still preferred due to it not being as high-overhead as something like scanning on Moved(). I'm thinking I'll make Moved() only scan for cases where it needs to hit a non-dense object - a prone human the user clicked on, anything special like that. Don't know the exact specifics yet, which is why this is still WIP.
Projectiles now use check_pierce() to determine if it goes through something and hits it, doesn't hit it, or doesn't go through something at all (should delete self after hitting). Will likely make an on_pierce proc to be called post-piercing something so you can have !fun! things like projectiles that go down in damage after piercing something. This will likely deprecate the process_hit proc, or at least make it less awful.
scan_for_hit() is now used to attempt to hit something and will return whether the projectile got deleted or not. It will delete the projectile if the projectile does hit something and fails to pierce through it.
scan_moved_turf() (WIP) will be used for handling moving onto a turf.
permutated has been renamed to impacted. Ricocheting projectiles get it reset, allowing projectiles to pierce and potentially hit something again if it goes back around.
A new unit test has been added checking for projectiles with movement type of PHASING. This is because PHASING completely causes projectiles to break down as projectiles mainly sense collisions through Bump. The small boost in performance from using PHASING instead of having all pass flags active/overriding check_pierce is in my opinion not worth the extra snowflake in scan_moved_turf() I'd have to do to deal with having to check for hits manually rather than Bump()ing things.
Movement types
UNSTOPPABLE renamed to PHASING to better describe what it is, going through and crossing everything but not actually bumping.
Why It's Good For The Game
Better pass flags handling allows for less proc overrides, bitflag checks are far less expensive in general.
Fixes penetrating projectiles like sniper penetrators
This system also allows for better handling of piercing projectiles (see above) without too much snowflake code, as you'd only need to modify on_pierce() if you needed to do special handling like dampening damage per target pierced, and otherwise you could just use the standardized system and just set pass flags to what's needed. If you really need a projectile that pierces almost everything, override check_pierce(), which is still going to be easier than what was done before (even with snowflake handling of UNSTOPPABLE flag process_hit() was extremely ugly, now we don't rely on movement types at all.)
---
code/__DEFINES/combat.dm | 1 -
code/__DEFINES/flags.dm | 5 +-
code/__DEFINES/projectiles.dm | 9 +
code/_globalvars/bitfields.dm | 2 +-
code/_onclick/adjacent.dm | 2 +-
code/controllers/subsystem/throwing.dm | 2 +-
code/datums/components/mirv.dm | 2 +-
code/datums/components/pellet_cloud.dm | 2 +-
code/game/atoms.dm | 22 +-
code/game/atoms_movable.dm | 1 +
code/game/machinery/_machinery.dm | 7 +-
code/game/machinery/deployable.dm | 4 +-
code/game/machinery/doors/door.dm | 2 +-
code/game/machinery/doors/firedoor.dm | 7 +-
code/game/machinery/doors/windowdoor.dm | 14 +-
.../effects/effect_system/effects_foam.dm | 6 +-
code/game/objects/items/cardboard_cutouts.dm | 4 +-
.../items/devices/forcefieldprojector.dm | 6 +-
code/game/objects/objs.dm | 6 +-
code/game/objects/structures.dm | 7 +-
code/game/objects/structures/grille.dm | 5 +-
code/game/objects/structures/holosign.dm | 4 +-
code/game/objects/structures/morgue.dm | 13 +-
code/game/objects/structures/railings.dm | 2 +-
code/game/objects/structures/tables_racks.dm | 15 +-
code/game/objects/structures/tank_holder.dm | 2 +-
.../structures/transit_tubes/transit_tube.dm | 6 +-
.../objects/structures/windoor_assembly.dm | 11 +-
code/game/objects/structures/window.dm | 5 +-
code/game/turfs/closed/_closed.dm | 6 +-
code/game/turfs/turf.dm | 13 +-
.../antagonists/blob/structures/_blob.dm | 12 +-
code/modules/assembly/infrared.dm | 3 +-
code/modules/awaymissions/capture_the_flag.dm | 6 +-
code/modules/hydroponics/grown/towercap.dm | 8 +-
.../mob/living/carbon/human/human_defense.dm | 9 +-
code/modules/mob/living/living_defense.dm | 4 +-
code/modules/mob/living/living_movement.dm | 10 +-
.../mob/living/silicon/silicon_defense.dm | 4 +-
.../living/simple_animal/animal_defense.dm | 4 +-
.../simple_animal/hostile/jungle/mook.dm | 2 +-
.../simple_animal/hostile/megafauna/legion.dm | 2 +-
.../mob/living/simple_animal/slime/slime.dm | 4 +-
code/modules/mob/mob_defines.dm | 1 +
.../guns/energy/kinetic_accelerator.dm | 4 +-
.../projectiles/guns/misc/beam_rifle.dm | 76 ++--
.../projectiles/guns/misc/blastcannon.dm | 3 +-
code/modules/projectiles/projectile.dm | 400 ++++++++++++------
.../projectiles/projectile/bullets/sniper.dm | 3 +-
.../projectiles/projectile/bullets/special.dm | 3 +-
code/modules/projectiles/projectile/magic.dm | 13 +-
.../projectiles/projectile/special/curse.dm | 11 +-
code/modules/religion/religion_structures.dm | 2 +-
code/modules/spells/spell_types/projectile.dm | 2 +-
code/modules/unit_tests/_unit_tests.dm | 1 +
code/modules/unit_tests/projectiles.dm | 5 +
code/modules/vehicles/ridden.dm | 11 +-
tgstation.dme | 1 +
58 files changed, 442 insertions(+), 355 deletions(-)
create mode 100644 code/__DEFINES/projectiles.dm
create mode 100644 code/modules/unit_tests/projectiles.dm
diff --git a/code/__DEFINES/combat.dm b/code/__DEFINES/combat.dm
index 05c732f45dd..6262592f7fd 100644
--- a/code/__DEFINES/combat.dm
+++ b/code/__DEFINES/combat.dm
@@ -281,6 +281,5 @@ GLOBAL_LIST_INIT(shove_disarming_types, typecacheof(list(
#define BULLET_ACT_HIT "HIT" //It's a successful hit, whatever that means in the context of the thing it's hitting.
#define BULLET_ACT_BLOCK "BLOCK" //It's a blocked hit, whatever that means in the context of the thing it's hitting.
#define BULLET_ACT_FORCE_PIERCE "PIERCE" //It pierces through the object regardless of the bullet being piercing by default.
-#define BULLET_ACT_TURF "TURF" //It hit us but it should hit something on the same turf too. Usually used for turfs.
#define NICE_SHOT_RICOCHET_BONUS 10 //if the shooter has the NICE_SHOT trait and they fire a ricocheting projectile, add this to the ricochet chance and auto aim angle
diff --git a/code/__DEFINES/flags.dm b/code/__DEFINES/flags.dm
index 04b67226664..2ee255e0167 100644
--- a/code/__DEFINES/flags.dm
+++ b/code/__DEFINES/flags.dm
@@ -107,6 +107,7 @@ GLOBAL_LIST_INIT(bitflags, list(1, 2, 4, 8, 16, 32, 64, 128, 256, 512, 1024, 204
#define PASSBLOB (1<<3)
#define PASSMOB (1<<4)
#define PASSCLOSEDTURF (1<<5)
+/// Let thrown things past us. **ONLY MEANINGFUL ON pass_flags_self!**
#define LETPASSTHROW (1<<6)
#define PASSMACHINE (1<<7)
#define PASSSTRUCTURE (1<<8)
@@ -117,8 +118,8 @@ GLOBAL_LIST_INIT(bitflags, list(1, 2, 4, 8, 16, 32, 64, 128, 256, 512, 1024, 204
#define FLYING (1<<1)
#define VENTCRAWLING (1<<2)
#define FLOATING (1<<3)
-/// When moving, will Bump()/Cross()/Uncross() everything, but won't be stopped.
-#define UNSTOPPABLE (1<<4)
+/// When moving, will Cross()/Uncross() everything, but won't stop or Bump() anything.
+#define PHASING (1<<4)
//Fire and Acid stuff, for resistance_flags
#define LAVA_PROOF (1<<0)
diff --git a/code/__DEFINES/projectiles.dm b/code/__DEFINES/projectiles.dm
new file mode 100644
index 00000000000..de6ee687cae
--- /dev/null
+++ b/code/__DEFINES/projectiles.dm
@@ -0,0 +1,9 @@
+// check_pierce() return values
+/// Default behavior: hit and delete self
+#define PROJECTILE_PIERCE_NONE 0
+/// Hit the thing but go through without deleting. Causes on_hit to be called with pierced = TRUE
+#define PROJECTILE_PIERCE_HIT 1
+/// Entirely phase through the thing without ever hitting.
+#define PROJECTILE_PIERCE_PHASE 2
+// Delete self without hitting
+#define PROJECTILE_DELETE_WITHOUT_HITTING 3
diff --git a/code/_globalvars/bitfields.dm b/code/_globalvars/bitfields.dm
index f291e07e75f..bae4ac0cc55 100644
--- a/code/_globalvars/bitfields.dm
+++ b/code/_globalvars/bitfields.dm
@@ -219,7 +219,7 @@ DEFINE_BITFIELD(movement_type, list(
"FLOATING" = FLOATING,
"FLYING" = FLYING,
"GROUND" = GROUND,
- "UNSTOPPABLE" = UNSTOPPABLE,
+ "PHASING" = PHASING,
"VENTCRAWLING" = VENTCRAWLING,
))
diff --git a/code/_onclick/adjacent.dm b/code/_onclick/adjacent.dm
index db2f62052f4..ddf6e6f41e7 100644
--- a/code/_onclick/adjacent.dm
+++ b/code/_onclick/adjacent.dm
@@ -94,7 +94,7 @@
for(var/obj/O in src)
if((mover && O.CanPass(mover,get_step(src,target_dir))) || (!mover && !O.density))
continue
- if(O == target_atom || O == mover || (O.pass_flags & LETPASSTHROW)) //check if there's a dense object present on the turf
+ if(O == target_atom || O == mover || (O.pass_flags_self & LETPASSTHROW)) //check if there's a dense object present on the turf
continue // LETPASSTHROW is used for anything you can click through (or the firedoor special case, see above)
if( O.flags_1&ON_BORDER_1) // windows are on border, check them first
diff --git a/code/controllers/subsystem/throwing.dm b/code/controllers/subsystem/throwing.dm
index ee6cb105451..ddde1daba0e 100644
--- a/code/controllers/subsystem/throwing.dm
+++ b/code/controllers/subsystem/throwing.dm
@@ -199,6 +199,6 @@ SUBSYSTEM_DEF(throwing)
var/atom/movable/AM = thing
if (AM == thrownthing || (AM == thrower && !ismob(thrownthing)))
continue
- if (AM.density && !(AM.pass_flags & LETPASSTHROW) && !(AM.flags_1 & ON_BORDER_1))
+ if (AM.density && !(AM.pass_flags_self & LETPASSTHROW) && !(AM.flags_1 & ON_BORDER_1))
finalize(hit=TRUE, target=AM)
return TRUE
diff --git a/code/datums/components/mirv.dm b/code/datums/components/mirv.dm
index 140e1134764..198a9336f24 100644
--- a/code/datums/components/mirv.dm
+++ b/code/datums/components/mirv.dm
@@ -39,5 +39,5 @@
P.range = override_projectile_range
P.preparePixelProjectile(shootat_turf, target)
P.firer = firer // don't hit ourself that would be really annoying
- P.permutated += target // don't hit the target we hit already with the flak
+ P.impacted = list(target = TRUE) // don't hit the target we hit already with the flak
P.fire()
diff --git a/code/datums/components/pellet_cloud.dm b/code/datums/components/pellet_cloud.dm
index 18f821b5f10..4e1258cc758 100644
--- a/code/datums/components/pellet_cloud.dm
+++ b/code/datums/components/pellet_cloud.dm
@@ -266,7 +266,7 @@
P.original = target
P.fired_from = parent
P.firer = parent // don't hit ourself that would be really annoying
- P.permutated += parent // don't hit the target we hit already with the flak
+ P.impacted = list(parent = TRUE) // don't hit the target we hit already with the flak
P.suppressed = SUPPRESSED_VERY // set the projectiles to make no message so we can do our own aggregate message
P.preparePixelProjectile(target, parent)
RegisterSignal(P, COMSIG_PROJECTILE_SELF_ON_HIT, .proc/pellet_hit)
diff --git a/code/game/atoms.dm b/code/game/atoms.dm
index aa2cab5ce4c..8eb0b40e893 100644
--- a/code/game/atoms.dm
+++ b/code/game/atoms.dm
@@ -9,6 +9,9 @@
plane = GAME_PLANE
appearance_flags = TILE_BOUND
+ /// pass_flags that we are. If any of this matches a pass_flag on a moving thing, by default, we let them through.
+ var/pass_flags_self = NONE
+
///If non-null, overrides a/an/some in all cases
var/article
@@ -294,7 +297,7 @@
return FALSE
if((P.flag in list(BULLET, BOMB)) && P.ricochet_incidence_leeway)
if((a_incidence_s < 90 && a_incidence_s < 90 - P.ricochet_incidence_leeway) || (a_incidence_s > 270 && a_incidence_s -270 > P.ricochet_incidence_leeway))
- return
+ return FALSE
var/new_angle_s = SIMPLIFY_DEGREES(face_angle + incidence_s)
P.setAngle(new_angle_s)
return TRUE
@@ -303,7 +306,7 @@
/atom/proc/CanPass(atom/movable/mover, turf/target)
SHOULD_CALL_PARENT(TRUE)
SHOULD_BE_PURE(TRUE)
- if(mover.movement_type & UNSTOPPABLE)
+ if(mover.movement_type & PHASING)
return TRUE
. = CanAllowThrough(mover, target)
// This is cheaper than calling the proc every time since most things dont override CanPassThrough
@@ -314,6 +317,10 @@
/atom/proc/CanAllowThrough(atom/movable/mover, turf/target)
SHOULD_CALL_PARENT(TRUE)
//SHOULD_BE_PURE(TRUE)
+ if(mover.pass_flags & pass_flags_self)
+ return TRUE
+ if(mover.throwing && (pass_flags_self & LETPASSTHROW))
+ return TRUE
return !density
/**
@@ -504,7 +511,7 @@
return FALSE
/atom/proc/CheckExit()
- return 1
+ return TRUE
///Is this atom within 1 tile of another atom
/atom/proc/HasProximity(atom/movable/AM as mob|obj)
@@ -530,10 +537,15 @@
* 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
+ * P - projectile
+ * def_zone - zone hit
+ * piercing_hit - is this hit piercing or normal?
*/
-/atom/proc/bullet_act(obj/projectile/P, def_zone)
+/atom/proc/bullet_act(obj/projectile/P, def_zone, piercing_hit = FALSE)
SEND_SIGNAL(src, COMSIG_ATOM_BULLET_ACT, P, def_zone)
- . = P.on_hit(src, 0, def_zone)
+ . = P.on_hit(src, 0, def_zone, 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
diff --git a/code/game/atoms_movable.dm b/code/game/atoms_movable.dm
index 3d5a3f20f28..7653f59afa9 100644
--- a/code/game/atoms_movable.dm
+++ b/code/game/atoms_movable.dm
@@ -26,6 +26,7 @@
var/inertia_moving = 0
var/inertia_next_move = 0
var/inertia_move_delay = 5
+ /// Things we can pass through while moving. If any of this matches the thing we're trying to pass's [pass_flags_self], then we can pass through.
var/pass_flags = NONE
/// If false makes [CanPass][/atom/proc/CanPass] call [CanPassThrough][/atom/movable/proc/CanPassThrough] on this type instead of using default behaviour
var/generic_canpass = TRUE
diff --git a/code/game/machinery/_machinery.dm b/code/game/machinery/_machinery.dm
index 9225fd4ebba..a573ebaaa99 100644
--- a/code/game/machinery/_machinery.dm
+++ b/code/game/machinery/_machinery.dm
@@ -87,6 +87,7 @@
verb_say = "beeps"
verb_yell = "blares"
pressure_resistance = 15
+ pass_flags_self = PASSMACHINE
max_integrity = 200
layer = BELOW_OBJ_LAYER //keeps shit coming out of the machine from ending up underneath it.
flags_ricochet = RICOCHET_HARD
@@ -532,12 +533,6 @@
deconstruct(FALSE)
return ..()
-/obj/machinery/CanAllowThrough(atom/movable/mover, turf/target)
- . = ..()
-
- if(mover.pass_flags & PASSMACHINE)
- return TRUE
-
/obj/machinery/proc/default_deconstruction_screwdriver(mob/user, icon_state_open, icon_state_closed, obj/item/I)
if(!(flags_1 & NODECONSTRUCT_1) && I.tool_behaviour == TOOL_SCREWDRIVER)
I.play_tool_sound(src, 50)
diff --git a/code/game/machinery/deployable.dm b/code/game/machinery/deployable.dm
index bf833c68cf7..99d64b00294 100644
--- a/code/game/machinery/deployable.dm
+++ b/code/game/machinery/deployable.dm
@@ -96,7 +96,6 @@
/obj/structure/barricade/wooden/make_debris()
new /obj/item/stack/sheet/mineral/wood(get_turf(src), drop_amount)
-
/obj/structure/barricade/sandbags
name = "sandbags"
desc = "Bags of sand. Self explanatory."
@@ -105,14 +104,13 @@
base_icon_state = "sandbags"
max_integrity = 280
proj_pass_rate = 20
- pass_flags = LETPASSTHROW
+ pass_flags_self = LETPASSTHROW
bar_material = SAND
climbable = TRUE
smoothing_flags = SMOOTH_BITMASK
smoothing_groups = list(SMOOTH_GROUP_SANDBAGS)
canSmoothWith = list(SMOOTH_GROUP_SANDBAGS, SMOOTH_GROUP_WALLS, SMOOTH_GROUP_SECURITY_BARRICADE)
-
/obj/structure/barricade/security
name = "security barrier"
desc = "A deployable barrier. Provides good cover in fire fights."
diff --git a/code/game/machinery/doors/door.dm b/code/game/machinery/doors/door.dm
index 6e6c2274b00..73e51a47854 100644
--- a/code/game/machinery/doors/door.dm
+++ b/code/game/machinery/doors/door.dm
@@ -149,7 +149,7 @@
. = ..()
if(.)
return
-
+ // Snowflake handling for PASSGLASS.
if(istype(mover) && (mover.pass_flags & PASSGLASS))
return !opacity
diff --git a/code/game/machinery/doors/firedoor.dm b/code/game/machinery/doors/firedoor.dm
index f590a7be092..e9592bf9d6b 100644
--- a/code/game/machinery/doors/firedoor.dm
+++ b/code/game/machinery/doors/firedoor.dm
@@ -228,18 +228,13 @@
/obj/machinery/door/firedoor/border_only/CanAllowThrough(atom/movable/mover, turf/target)
. = ..()
- if(istype(mover) && (mover.pass_flags & PASSGLASS))
- return TRUE
if(!(get_dir(loc, target) == dir)) //Make sure looking at appropriate border
return TRUE
/obj/machinery/door/firedoor/border_only/CheckExit(atom/movable/mover as mob|obj, turf/target)
- if(istype(mover) && (mover.pass_flags & PASSGLASS))
- return TRUE
if(get_dir(loc, target) == dir)
return !density
- else
- return TRUE
+ return TRUE
/obj/machinery/door/firedoor/border_only/CanAtmosPass(turf/T)
if(get_dir(loc, T) == dir)
diff --git a/code/game/machinery/doors/windowdoor.dm b/code/game/machinery/doors/windowdoor.dm
index 7ee8f70eca7..3d639d05c5e 100644
--- a/code/game/machinery/doors/windowdoor.dm
+++ b/code/game/machinery/doors/windowdoor.dm
@@ -13,6 +13,7 @@
visible = FALSE
flags_1 = ON_BORDER_1
opacity = FALSE
+ pass_flags_self = PASSGLASS
CanAtmosPass = ATMOS_PASS_PROC
interaction_flags_machine = INTERACT_MACHINE_WIRES_IF_OPEN | INTERACT_MACHINE_ALLOW_SILICON | INTERACT_MACHINE_OPEN_SILICON | INTERACT_MACHINE_REQUIRES_SILICON | INTERACT_MACHINE_OPEN
var/obj/item/electronics/airlock/electronics = null
@@ -101,8 +102,8 @@
/obj/machinery/door/window/CanAllowThrough(atom/movable/mover, turf/target)
. = ..()
- if(istype(mover) && (mover.pass_flags & PASSGLASS))
- return TRUE
+ if(.)
+ return
if(get_dir(loc, target) == dir) //Make sure looking at appropriate border
return
if(istype(mover, /obj/structure/window))
@@ -128,13 +129,12 @@
/obj/machinery/door/window/CanAStarPass(obj/item/card/id/ID, to_dir)
return !density || (dir != to_dir) || (check_access(ID) && hasPower())
-/obj/machinery/door/window/CheckExit(atom/movable/mover as mob|obj, turf/target)
- if(istype(mover) && (mover.pass_flags & PASSGLASS))
- return 1
+/obj/machinery/door/window/CheckExit(atom/movable/mover, turf/target)
+ if((pass_flags_self & mover.pass_flags) || ((pass_flags_self & LETPASSTHROW) && mover.throwing))
+ return TRUE
if(get_dir(loc, target) == dir)
return !density
- else
- return 1
+ return TRUE
/obj/machinery/door/window/open(forced=FALSE)
if (operating) //doors can still open when emag-disabled
diff --git a/code/game/objects/effects/effect_system/effects_foam.dm b/code/game/objects/effects/effect_system/effects_foam.dm
index 23e12d21786..2a83c0621f1 100644
--- a/code/game/objects/effects/effect_system/effects_foam.dm
+++ b/code/game/objects/effects/effect_system/effects_foam.dm
@@ -302,6 +302,7 @@
icon_state = "atmos_resin"
alpha = 120
max_integrity = 10
+ pass_flags_self = PASSGLASS
/obj/structure/foamedmetal/resin/Initialize()
. = ..()
@@ -330,11 +331,6 @@
for(var/obj/item/Item in O)
Item.extinguish()
-/obj/structure/foamedmetal/resin/CanAllowThrough(atom/movable/mover, turf/target)
- . = ..()
- if(istype(mover) && (mover.pass_flags & PASSGLASS))
- return TRUE
-
#undef ALUMINUM_FOAM
#undef IRON_FOAM
#undef RESIN_FOAM
diff --git a/code/game/objects/items/cardboard_cutouts.dm b/code/game/objects/items/cardboard_cutouts.dm
index 65e6f6be761..f6e6104c09e 100644
--- a/code/game/objects/items/cardboard_cutouts.dm
+++ b/code/game/objects/items/cardboard_cutouts.dm
@@ -84,9 +84,9 @@
if(prob(I.force))
push_over()
-/obj/item/cardboard_cutout/bullet_act(obj/projectile/P)
+/obj/item/cardboard_cutout/bullet_act(obj/projectile/P, def_zone, piercing_hit = FALSE)
if(istype(P, /obj/projectile/bullet/reusable))
- P.on_hit(src, 0)
+ P.on_hit(src, 0, piercing_hit)
visible_message("[src] is hit by [P]!")
playsound(src, 'sound/weapons/slice.ogg', 50, TRUE)
if(prob(P.damage))
diff --git a/code/game/objects/items/devices/forcefieldprojector.dm b/code/game/objects/items/devices/forcefieldprojector.dm
index 7a618fc1292..5fd886314ff 100644
--- a/code/game/objects/items/devices/forcefieldprojector.dm
+++ b/code/game/objects/items/devices/forcefieldprojector.dm
@@ -84,6 +84,7 @@
icon_state = "forcefield"
layer = ABOVE_ALL_MOB_LAYER
anchored = TRUE
+ pass_flags_self = PASSGLASS
density = TRUE
mouse_opacity = MOUSE_OPACITY_OPAQUE
resistance_flags = INDESTRUCTIBLE
@@ -102,11 +103,6 @@
generator = null
return ..()
-/obj/structure/projected_forcefield/CanAllowThrough(atom/movable/mover, turf/target)
- . = ..()
- if(istype(mover) && (mover.pass_flags & PASSGLASS))
- return TRUE
-
/obj/structure/projected_forcefield/play_attack_sound(damage_amount, damage_type = BRUTE, damage_flag = 0)
playsound(loc, 'sound/weapons/egloves.ogg', 80, TRUE)
diff --git a/code/game/objects/objs.dm b/code/game/objects/objs.dm
index e3fda7d65d9..c35e0ad7bc9 100644
--- a/code/game/objects/objs.dm
+++ b/code/game/objects/objs.dm
@@ -198,7 +198,11 @@
/obj/get_dumping_location(datum/component/storage/source,mob/user)
return get_turf(src)
-/obj/proc/CanAStarPass()
+/obj/proc/CanAStarPass(ID, dir, caller)
+ if(ismovable(caller))
+ var/atom/movable/AM = caller
+ if(AM.pass_flags & pass_flags_self)
+ return TRUE
. = !density
/obj/proc/check_uplink_validity()
diff --git a/code/game/objects/structures.dm b/code/game/objects/structures.dm
index 77e30bf8f7f..08d3fda3597 100644
--- a/code/game/objects/structures.dm
+++ b/code/game/objects/structures.dm
@@ -7,6 +7,7 @@
layer = BELOW_OBJ_LAYER
flags_ricochet = RICOCHET_HARD
receive_ricochet_chance_mod = 0.6
+ pass_flags_self = PASSSTRUCTURE
var/climb_time = 20
var/climb_stun = 20
@@ -115,12 +116,6 @@
if(examine_status)
. += examine_status
-/obj/structure/CanAllowThrough(atom/movable/mover, turf/target)
- . = ..()
-
- if(mover.pass_flags & PASSSTRUCTURE)
- return TRUE
-
/obj/structure/proc/examine_status(mob/user) //An overridable proc, mostly for falsewalls.
var/healthpercent = (obj_integrity/max_integrity) * 100
switch(healthpercent)
diff --git a/code/game/objects/structures/grille.dm b/code/game/objects/structures/grille.dm
index 71fa3c987ea..4732f0cb8fc 100644
--- a/code/game/objects/structures/grille.dm
+++ b/code/game/objects/structures/grille.dm
@@ -5,6 +5,7 @@
icon_state = "grille"
density = TRUE
anchored = TRUE
+ pass_flags_self = PASSGRILLE
flags_1 = CONDUCT_1 | RAD_PROTECT_CONTENTS_1 | RAD_NO_CONTAMINATE_1
pressure_resistance = 5*ONE_ATMOSPHERE
armor = list(MELEE = 50, BULLET = 70, LASER = 70, ENERGY = 100, BOMB = 10, BIO = 100, RAD = 100, FIRE = 0, ACID = 0)
@@ -118,9 +119,7 @@
/obj/structure/grille/CanAllowThrough(atom/movable/mover, turf/target)
. = ..()
- if(mover.pass_flags & PASSGRILLE)
- return TRUE
- else if(!. && istype(mover, /obj/projectile))
+ if(!. && istype(mover, /obj/projectile))
return prob(30)
/obj/structure/grille/CanAStarPass(ID, dir, caller)
diff --git a/code/game/objects/structures/holosign.dm b/code/game/objects/structures/holosign.dm
index 3db53c4b159..7e9c6610e6a 100644
--- a/code/game/objects/structures/holosign.dm
+++ b/code/game/objects/structures/holosign.dm
@@ -48,7 +48,7 @@
name = "holobarrier"
desc = "A short holographic barrier which can only be passed by walking."
icon_state = "holosign_sec"
- pass_flags = LETPASSTHROW
+ pass_flags_self = PASSTABLE | PASSGRILLE | PASSGLASS | LETPASSTHROW
density = TRUE
max_integrity = 20
var/allow_walk = TRUE //can we pass through it on walk intent
@@ -57,8 +57,6 @@
. = ..()
if(.)
return
- if(mover.pass_flags & (PASSGLASS|PASSTABLE|PASSGRILLE))
- return TRUE
if(iscarbon(mover))
var/mob/living/carbon/C = mover
if(C.stat) // Lets not prevent dragging unconscious/dead people.
diff --git a/code/game/objects/structures/morgue.dm b/code/game/objects/structures/morgue.dm
index e142c33f522..0197bface56 100644
--- a/code/game/objects/structures/morgue.dm
+++ b/code/game/objects/structures/morgue.dm
@@ -314,7 +314,7 @@ GLOBAL_LIST_EMPTY(crematoriums)
density = TRUE
var/obj/structure/bodycontainer/connected = null
anchored = TRUE
- pass_flags = LETPASSTHROW
+ pass_flags_self = LETPASSTHROW
max_integrity = 350
/obj/structure/tray/Destroy()
@@ -388,16 +388,11 @@ GLOBAL_LIST_EMPTY(crematoriums)
name = "morgue tray"
desc = "Apply corpse before closing."
icon_state = "morguet"
+ pass_flags_self = PASSTABLE
/obj/structure/tray/m_tray/CanAllowThrough(atom/movable/mover, turf/target)
. = ..()
- if(istype(mover) && (mover.pass_flags & PASSTABLE))
- return TRUE
+ if(.)
+ return
if(locate(/obj/structure/table) in get_turf(mover))
return TRUE
-
-/obj/structure/tray/m_tray/CanAStarPass(ID, dir, caller)
- . = !density
- if(ismovable(caller))
- var/atom/movable/mover = caller
- . = . || (mover.pass_flags & PASSTABLE)
diff --git a/code/game/objects/structures/railings.dm b/code/game/objects/structures/railings.dm
index 09e42276973..0a479b6556b 100644
--- a/code/game/objects/structures/railings.dm
+++ b/code/game/objects/structures/railings.dm
@@ -80,7 +80,7 @@
/obj/structure/railing/CheckExit(atom/movable/mover, turf/target)
..()
if(get_dir(loc, target) & dir)
- var/checking = UNSTOPPABLE | FLYING | FLOATING
+ var/checking = PHASING | FLYING | FLOATING
return !density || mover.throwing || mover.movement_type & checking || mover.move_force >= MOVE_FORCE_EXTREMELY_STRONG
return TRUE
diff --git a/code/game/objects/structures/tables_racks.dm b/code/game/objects/structures/tables_racks.dm
index 7a38d86a6e4..44e542f92eb 100644
--- a/code/game/objects/structures/tables_racks.dm
+++ b/code/game/objects/structures/tables_racks.dm
@@ -20,9 +20,9 @@
base_icon_state = "table"
density = TRUE
anchored = TRUE
+ pass_flags_self = PASSTABLE | LETPASSTHROW
layer = TABLE_LAYER
climbable = TRUE
- pass_flags = LETPASSTHROW //You can throw objects over this, despite it's density.")
var/frame = /obj/structure/table_frame
var/framestack = /obj/item/stack/rods
var/buildstack = /obj/item/stack/sheet/metal
@@ -100,9 +100,8 @@
/obj/structure/table/CanAllowThrough(atom/movable/mover, turf/target)
. = ..()
-
- if(istype(mover) && (mover.pass_flags & PASSTABLE))
- return TRUE
+ if(.)
+ return
if(mover.throwing)
return TRUE
if(locate(/obj/structure/table) in get_turf(mover))
@@ -610,7 +609,7 @@
layer = TABLE_LAYER
density = TRUE
anchored = TRUE
- pass_flags = LETPASSTHROW //You can throw objects over this, despite it's density.
+ pass_flags_self = LETPASSTHROW //You can throw objects over this, despite it's density.
max_integrity = 20
/obj/structure/rack/examine(mob/user)
@@ -624,12 +623,6 @@
if(istype(mover) && (mover.pass_flags & PASSTABLE))
return TRUE
-/obj/structure/rack/CanAStarPass(ID, dir, caller)
- . = !density
- if(ismovable(caller))
- var/atom/movable/mover = caller
- . = . || (mover.pass_flags & PASSTABLE)
-
/obj/structure/rack/MouseDrop_T(obj/O, mob/user)
. = ..()
if ((!( istype(O, /obj/item) ) || user.get_active_held_item() != O))
diff --git a/code/game/objects/structures/tank_holder.dm b/code/game/objects/structures/tank_holder.dm
index 3e9d0cbd4c5..9210cb17ae7 100644
--- a/code/game/objects/structures/tank_holder.dm
+++ b/code/game/objects/structures/tank_holder.dm
@@ -9,7 +9,7 @@
density = FALSE
anchored = FALSE
- pass_flags = LETPASSTHROW
+ pass_flags_self = LETPASSTHROW
max_integrity = 20
resistance_flags = FIRE_PROOF
diff --git a/code/game/objects/structures/transit_tubes/transit_tube.dm b/code/game/objects/structures/transit_tubes/transit_tube.dm
index 2669d12f1b5..223d690c2bc 100644
--- a/code/game/objects/structures/transit_tubes/transit_tube.dm
+++ b/code/game/objects/structures/transit_tubes/transit_tube.dm
@@ -8,16 +8,12 @@
layer = LOW_ITEM_LAYER
anchored = TRUE
climbable = TRUE
+ pass_flags_self = PASSGLASS
var/tube_construction = /obj/structure/c_transit_tube
var/list/tube_dirs //list of directions this tube section can connect to.
var/exit_delay = 1
var/enter_delay = 0
-/obj/structure/transit_tube/CanAllowThrough(atom/movable/mover, turf/target)
- . = ..()
- if(istype(mover) && (mover.pass_flags & PASSGLASS))
- return TRUE
-
/obj/structure/transit_tube/New(loc, newdirection)
..(loc)
if(newdirection)
diff --git a/code/game/objects/structures/windoor_assembly.dm b/code/game/objects/structures/windoor_assembly.dm
index a6312838f20..2f9debb0381 100644
--- a/code/game/objects/structures/windoor_assembly.dm
+++ b/code/game/objects/structures/windoor_assembly.dm
@@ -52,8 +52,6 @@
/obj/structure/windoor_assembly/CanAllowThrough(atom/movable/mover, turf/target)
. = ..()
- if(istype(mover) && (mover.pass_flags & PASSGLASS))
- return TRUE
if(get_dir(loc, target) == dir) //Make sure looking at appropriate border
return
if(istype(mover, /obj/structure/window))
@@ -73,14 +71,13 @@
else
return 1
-/obj/structure/windoor_assembly/CheckExit(atom/movable/mover as mob|obj, turf/target)
- if(istype(mover) && (mover.pass_flags & PASSGLASS))
- return 1
+/obj/structure/windoor_assembly/CheckExit(atom/movable/mover, turf/target)
+ if(mover.pass_flags & pass_flags_self)
+ return TRUE
if(get_dir(loc, target) == dir)
return !density
else
- return 1
-
+ return TRUE
/obj/structure/windoor_assembly/attackby(obj/item/W, mob/user, params)
//I really should have spread this out across more states but thin little windoors are hard to sprite.
diff --git a/code/game/objects/structures/window.dm b/code/game/objects/structures/window.dm
index 2c7cb426e57..c9904591d15 100644
--- a/code/game/objects/structures/window.dm
+++ b/code/game/objects/structures/window.dm
@@ -13,6 +13,7 @@
armor = list(MELEE = 0, BULLET = 0, LASER = 0, ENERGY = 0, BOMB = 0, BIO = 0, RAD = 0, FIRE = 80, ACID = 100)
CanAtmosPass = ATMOS_PASS_PROC
rad_insulation = RAD_VERY_LIGHT_INSULATION
+ pass_flags_self = PASSGLASS
var/ini_dir = null
var/state = WINDOW_OUT_OF_FRAME
var/reinf = FALSE
@@ -105,8 +106,8 @@
/obj/structure/window/CanAllowThrough(atom/movable/mover, turf/target)
. = ..()
- if(istype(mover) && (mover.pass_flags & PASSGLASS))
- return TRUE
+ if(.)
+ return
if(dir == FULLTILE_WINDOW_DIR)
return FALSE //full tile window, you can't move into it!
var/attempted_dir = get_dir(loc, target)
diff --git a/code/game/turfs/closed/_closed.dm b/code/game/turfs/closed/_closed.dm
index f597dc276e6..e0e5c382200 100644
--- a/code/game/turfs/closed/_closed.dm
+++ b/code/game/turfs/closed/_closed.dm
@@ -5,6 +5,7 @@
blocks_air = TRUE
flags_1 = RAD_PROTECT_CONTENTS_1 | RAD_NO_CONTAMINATE_1
rad_insulation = RAD_MEDIUM_INSULATION
+ pass_flags_self = PASSCLOSEDTURF
/turf/closed/AfterChange()
. = ..()
@@ -13,11 +14,6 @@
/turf/closed/get_smooth_underlay_icon(mutable_appearance/underlay_appearance, turf/asking_turf, adjacency_dir)
return FALSE
-/turf/closed/CanAllowThrough(atom/movable/mover, turf/target)
- . = ..()
- if(istype(mover) && (mover.pass_flags & PASSCLOSEDTURF))
- return TRUE
-
/turf/closed/indestructible
name = "wall"
desc = "Effectively impervious to conventional methods of destruction."
diff --git a/code/game/turfs/turf.dm b/code/game/turfs/turf.dm
index 6fa6678c03c..f87777248ec 100755
--- a/code/game/turfs/turf.dm
+++ b/code/game/turfs/turf.dm
@@ -269,7 +269,7 @@ GLOBAL_LIST_EMPTY(station_turfs)
// Here's hoping it doesn't stay like this for years before we finish conversion to step_
var/atom/firstbump
var/canPassSelf = CanPass(mover, src)
- if(canPassSelf || (mover.movement_type & UNSTOPPABLE))
+ if(canPassSelf || (mover.movement_type & PHASING))
for(var/i in contents)
if(QDELETED(mover))
return FALSE //We were deleted, do not attempt to proceed with movement.
@@ -279,7 +279,7 @@ GLOBAL_LIST_EMPTY(station_turfs)
if(!thing.Cross(mover))
if(QDELETED(mover)) //Mover deleted from Cross/CanPass, do not proceed.
return FALSE
- if((mover.movement_type & UNSTOPPABLE))
+ if((mover.movement_type & PHASING))
mover.Bump(thing)
continue
else
@@ -291,7 +291,7 @@ GLOBAL_LIST_EMPTY(station_turfs)
firstbump = src
if(firstbump)
mover.Bump(firstbump)
- return (mover.movement_type & UNSTOPPABLE)
+ return (mover.movement_type & PHASING)
return TRUE
/turf/Exit(atom/movable/mover, atom/newloc)
@@ -305,7 +305,7 @@ GLOBAL_LIST_EMPTY(station_turfs)
if(!thing.Uncross(mover, newloc))
if(thing.flags_1 & ON_BORDER_1)
mover.Bump(thing)
- if(!(mover.movement_type & UNSTOPPABLE))
+ if(!(mover.movement_type & PHASING))
return FALSE
if(QDELETED(mover))
return FALSE //We were deleted.
@@ -584,11 +584,6 @@ GLOBAL_LIST_EMPTY(station_turfs)
/turf/proc/Melt()
return ScrapeAway(flags = CHANGETURF_INHERIT_AIR)
-/turf/bullet_act(obj/projectile/P)
- . = ..()
- if(. != BULLET_ACT_FORCE_PIERCE)
- . = BULLET_ACT_TURF
-
/// Handles exposing a turf to reagents.
/turf/expose_reagents(list/reagents, datum/reagents/source, methods=TOUCH, volume_modifier=1, show_message=TRUE)
. = ..()
diff --git a/code/modules/antagonists/blob/structures/_blob.dm b/code/modules/antagonists/blob/structures/_blob.dm
index 6336fa4c7a1..bee8f01595a 100644
--- a/code/modules/antagonists/blob/structures/_blob.dm
+++ b/code/modules/antagonists/blob/structures/_blob.dm
@@ -8,6 +8,7 @@
opacity = FALSE
anchored = TRUE
layer = BELOW_MOB_LAYER
+ pass_flags_self = PASSBLOB
CanAtmosPass = ATMOS_PASS_PROC
var/point_return = 0 //How many points the blob gets back when it removes a blob of that type. If less than 0, blob cannot be removed.
max_integrity = 30
@@ -68,20 +69,9 @@
/obj/structure/blob/BlockSuperconductivity()
return atmosblock
-/obj/structure/blob/CanAllowThrough(atom/movable/mover, turf/target)
- . = ..()
- if(!(mover.pass_flags & PASSBLOB))
- return FALSE
-
/obj/structure/blob/CanAtmosPass(turf/T)
return !atmosblock
-/obj/structure/blob/CanAStarPass(ID, dir, caller)
- . = 0
- if(ismovable(caller))
- var/atom/movable/mover = caller
- . = . || (mover.pass_flags & PASSBLOB)
-
/obj/structure/blob/update_icon() //Updates color based on overmind color if we have an overmind.
if(overmind)
add_atom_colour(overmind.blobstrain.color, FIXED_COLOUR_PRIORITY)
diff --git a/code/modules/assembly/infrared.dm b/code/modules/assembly/infrared.dm
index 457b7fd98d7..8cad2ee7450 100644
--- a/code/modules/assembly/infrared.dm
+++ b/code/modules/assembly/infrared.dm
@@ -224,7 +224,8 @@
icon_state = "ibeam"
anchored = TRUE
density = FALSE
- pass_flags = PASSTABLE|PASSGLASS|PASSGRILLE|LETPASSTHROW
+ pass_flags = PASSTABLE|PASSGLASS|PASSGRILLE
+ pass_flags_self = LETPASSTHROW
var/obj/item/assembly/infra/master
/obj/effect/beam/i_beam/Crossed(atom/movable/AM as mob|obj)
diff --git a/code/modules/awaymissions/capture_the_flag.dm b/code/modules/awaymissions/capture_the_flag.dm
index bdf55ae4150..8911d83c447 100644
--- a/code/modules/awaymissions/capture_the_flag.dm
+++ b/code/modules/awaymissions/capture_the_flag.dm
@@ -402,9 +402,10 @@
/obj/projectile/bullet/ctf
damage = 0
-/obj/projectile/bullet/ctf/prehit(atom/target)
+/obj/projectile/bullet/ctf/prehit_pierce(atom/target)
if(is_ctf_target(target))
damage = 60
+ return PROJECTILE_PIERCE_NONE /// hey uhh don't hit anyone behind them
. = ..()
/obj/item/gun/ballistic/automatic/laser/ctf
@@ -438,9 +439,10 @@
damage = 0
icon_state = "omnilaser"
-/obj/projectile/beam/ctf/prehit(atom/target)
+/obj/projectile/beam/ctf/prehit_pierce(atom/target)
if(is_ctf_target(target))
damage = 150
+ return PROJECTILE_PIERCE_NONE /// hey uhhh don't hit anyone behind them
. = ..()
/proc/is_ctf_target(atom/target)
diff --git a/code/modules/hydroponics/grown/towercap.dm b/code/modules/hydroponics/grown/towercap.dm
index 4abdf60f56a..c8a6e31bbb5 100644
--- a/code/modules/hydroponics/grown/towercap.dm
+++ b/code/modules/hydroponics/grown/towercap.dm
@@ -156,6 +156,7 @@
density = FALSE
anchored = TRUE
buckle_lying = 0
+ pass_flags_self = PASSTABLE | LETPASSTHROW
var/burning = 0
var/burn_icon = "bonfire_on_fire" //for a softer more burning embers icon, use "bonfire_warm"
var/grill = FALSE
@@ -168,13 +169,6 @@
. = ..()
StartBurning()
-/obj/structure/bonfire/CanAllowThrough(atom/movable/mover, turf/target)
- . = ..()
- if(istype(mover) && (mover.pass_flags & PASSTABLE))
- return TRUE
- if(mover.throwing)
- return TRUE
-
/obj/structure/bonfire/attackby(obj/item/W, mob/user, params)
if(istype(W, /obj/item/stack/rods) && !can_buckle && !grill)
var/obj/item/stack/rods/R = W
diff --git a/code/modules/mob/living/carbon/human/human_defense.dm b/code/modules/mob/living/carbon/human/human_defense.dm
index 6b9d1183093..146a5235e79 100644
--- a/code/modules/mob/living/carbon/human/human_defense.dm
+++ b/code/modules/mob/living/carbon/human/human_defense.dm
@@ -53,7 +53,7 @@
dna.species.on_hit(P, src)
-/mob/living/carbon/human/bullet_act(obj/projectile/P, def_zone)
+/mob/living/carbon/human/bullet_act(obj/projectile/P, def_zone, piercing_hit = FALSE)
if(dna?.species)
var/spec_return = dna.species.bullet_act(P, src)
if(spec_return)
@@ -74,7 +74,7 @@
// Find a turf near or on the original location to bounce to
if(!isturf(loc)) //Open canopy mech (ripley) check. if we're inside something and still got hit
P.force_hit = TRUE //The thing we're in passed the bullet to us. Pass it back, and tell it to take the damage.
- loc.bullet_act(P)
+ loc.bullet_act(P, def_zone, piercing_hit)
return BULLET_ACT_HIT
if(P.starting)
var/new_x = P.starting.x + pick(0, 0, 0, 0, 0, -1, 1, -2, 2)
@@ -95,10 +95,11 @@
return BULLET_ACT_FORCE_PIERCE // complete projectile permutation
if(check_shields(P, P.damage, "the [P.name]", PROJECTILE_ATTACK, P.armour_penetration))
- P.on_hit(src, 100, def_zone)
+ P.on_hit(src, 100, def_zone, piercing_hit)
return BULLET_ACT_HIT
- return ..(P, def_zone)
+ return ..()
+
///Reflection checks for anything in your l_hand, r_hand, or wear_suit based on the reflection chance of the object
/mob/living/carbon/human/proc/check_reflect(def_zone)
if(wear_suit)
diff --git a/code/modules/mob/living/living_defense.dm b/code/modules/mob/living/living_defense.dm
index afa1058ee8c..dec3922f772 100644
--- a/code/modules/mob/living/living_defense.dm
+++ b/code/modules/mob/living/living_defense.dm
@@ -47,9 +47,9 @@
/mob/living/proc/on_hit(obj/projectile/P)
return BULLET_ACT_HIT
-/mob/living/bullet_act(obj/projectile/P, def_zone)
+/mob/living/bullet_act(obj/projectile/P, def_zone, piercing_hit = FALSE)
var/armor = run_armor_check(def_zone, P.flag, "","",P.armour_penetration)
- var/on_hit_state = P.on_hit(src, armor)
+ var/on_hit_state = P.on_hit(src, armor, piercing_hit)
if(!P.nodamage && on_hit_state != BULLET_ACT_BLOCK)
apply_damage(P.damage, P.damage_type, def_zone, armor, wound_bonus=P.wound_bonus, bare_wound_bonus=P.bare_wound_bonus, sharpness = P.sharpness)
apply_effects(P.stun, P.knockdown, P.unconscious, P.irradiate, P.slur, P.stutter, P.eyeblur, P.drowsy, armor, P.stamina, P.jitter, P.paralyze, P.immobilize)
diff --git a/code/modules/mob/living/living_movement.dm b/code/modules/mob/living/living_movement.dm
index fa809b563c6..709550cbc56 100644
--- a/code/modules/mob/living/living_movement.dm
+++ b/code/modules/mob/living/living_movement.dm
@@ -5,19 +5,15 @@
/mob/living/CanAllowThrough(atom/movable/mover, turf/target)
. = ..()
- if((mover.pass_flags & PASSMOB))
- return TRUE
- if(istype(mover, /obj/projectile))
- var/obj/projectile/P = mover
- return !P.can_hit_target(src, P.permutated, src == P.original, TRUE)
+ if(.)
+ return
if(mover.throwing)
return (!density || body_position == LYING_DOWN || (mover.throwing.thrower == src && !ismob(mover)))
if(buckled == mover)
return TRUE
if(ismob(mover) && (mover in buckled_mobs))
return TRUE
- return . || !mover.density || body_position == LYING_DOWN
-
+ return !mover.density || body_position == LYING_DOWN
/mob/living/toggle_move_intent()
. = ..()
diff --git a/code/modules/mob/living/silicon/silicon_defense.dm b/code/modules/mob/living/silicon/silicon_defense.dm
index 57e3ac5c8e7..9a8422210f5 100644
--- a/code/modules/mob/living/silicon/silicon_defense.dm
+++ b/code/modules/mob/living/silicon/silicon_defense.dm
@@ -109,7 +109,7 @@
M.visible_message("[M] is thrown off of [src]!")
flash_act(affect_silicon = 1)
-/mob/living/silicon/bullet_act(obj/projectile/Proj, def_zone)
+/mob/living/silicon/bullet_act(obj/projectile/Proj, def_zone, piercing_hit = FALSE)
SEND_SIGNAL(src, COMSIG_ATOM_BULLET_ACT, Proj, def_zone)
if((Proj.damage_type == BRUTE || Proj.damage_type == BURN))
adjustBruteLoss(Proj.damage)
@@ -122,7 +122,7 @@
for(var/mob/living/M in buckled_mobs)
unbuckle_mob(M)
M.visible_message("[M] is knocked off of [src] by the [Proj]!")
- Proj.on_hit(src)
+ Proj.on_hit(src, 0, piercing_hit)
return BULLET_ACT_HIT
/mob/living/silicon/flash_act(intensity = 1, override_blindness_check = 0, affect_silicon = 0, visual = 0, type = /atom/movable/screen/fullscreen/flash/static)
diff --git a/code/modules/mob/living/simple_animal/animal_defense.dm b/code/modules/mob/living/simple_animal/animal_defense.dm
index cad3d1e1277..e331283e48c 100644
--- a/code/modules/mob/living/simple_animal/animal_defense.dm
+++ b/code/modules/mob/living/simple_animal/animal_defense.dm
@@ -139,9 +139,9 @@
apply_damage(damage, damagetype, null, getarmor(null, armorcheck))
return TRUE
-/mob/living/simple_animal/bullet_act(obj/projectile/Proj)
+/mob/living/simple_animal/bullet_act(obj/projectile/Proj, def_zone, piercing_hit = FALSE)
apply_damage(Proj.damage, Proj.damage_type)
- Proj.on_hit(src)
+ Proj.on_hit(src, 0, piercing_hit)
return BULLET_ACT_HIT
/mob/living/simple_animal/ex_act(severity, target, origin)
diff --git a/code/modules/mob/living/simple_animal/hostile/jungle/mook.dm b/code/modules/mob/living/simple_animal/hostile/jungle/mook.dm
index ef31752fedd..88fb58836ca 100644
--- a/code/modules/mob/living/simple_animal/hostile/jungle/mook.dm
+++ b/code/modules/mob/living/simple_animal/hostile/jungle/mook.dm
@@ -24,7 +24,7 @@
melee_damage_upper = 30
ranged = TRUE
ranged_cooldown_time = 10
- pass_flags = LETPASSTHROW
+ pass_flags_self = LETPASSTHROW
robust_searching = TRUE
stat_attack = HARD_CRIT
attack_sound = 'sound/weapons/rapierhit.ogg'
diff --git a/code/modules/mob/living/simple_animal/hostile/megafauna/legion.dm b/code/modules/mob/living/simple_animal/hostile/megafauna/legion.dm
index 45d4fb7f0e5..e20ed2a3a96 100644
--- a/code/modules/mob/living/simple_animal/hostile/megafauna/legion.dm
+++ b/code/modules/mob/living/simple_animal/hostile/megafauna/legion.dm
@@ -388,7 +388,7 @@
muzzle_type = /obj/effect/projectile/tracer/legion
impact_type = /obj/effect/projectile/tracer/legion
hitscan = TRUE
- movement_type = UNSTOPPABLE
+ projectile_piercing = ALL
///Used for the legion turret tracer.
/obj/effect/projectile/tracer/legion/tracer
diff --git a/code/modules/mob/living/simple_animal/slime/slime.dm b/code/modules/mob/living/simple_animal/slime/slime.dm
index b0d28bebadc..3354a0ff0a8 100644
--- a/code/modules/mob/living/simple_animal/slime/slime.dm
+++ b/code/modules/mob/living/simple_animal/slime/slime.dm
@@ -222,11 +222,11 @@
amount = -abs(amount)
return ..() //Heals them
-/mob/living/simple_animal/slime/bullet_act(obj/projectile/Proj)
+/mob/living/simple_animal/slime/bullet_act(obj/projectile/Proj, def_zone, piercing_hit = FALSE)
attacked += 10
if((Proj.damage_type == BURN))
adjustBruteLoss(-abs(Proj.damage)) //fire projectiles heals slimes.
- Proj.on_hit(src)
+ Proj.on_hit(src, 0, piercing_hit)
else
. = ..(Proj)
. = . || BULLET_ACT_BLOCK
diff --git a/code/modules/mob/mob_defines.dm b/code/modules/mob/mob_defines.dm
index 40c10f1f5df..3cd864f05a7 100644
--- a/code/modules/mob/mob_defines.dm
+++ b/code/modules/mob/mob_defines.dm
@@ -17,6 +17,7 @@
mouse_drag_pointer = MOUSE_ACTIVE_POINTER
throwforce = 10
blocks_emissive = EMISSIVE_BLOCK_GENERIC
+ pass_flags_self = PASSMOB
///when this be added to vis_contents of something it inherit something.plane, important for visualisation of mob in openspace.
vis_flags = VIS_INHERIT_PLANE
diff --git a/code/modules/projectiles/guns/energy/kinetic_accelerator.dm b/code/modules/projectiles/guns/energy/kinetic_accelerator.dm
index cdc58f7062f..4ba555769bc 100644
--- a/code/modules/projectiles/guns/energy/kinetic_accelerator.dm
+++ b/code/modules/projectiles/guns/energy/kinetic_accelerator.dm
@@ -186,14 +186,14 @@
kinetic_gun = null
return ..()
-/obj/projectile/kinetic/prehit(atom/target)
+/obj/projectile/kinetic/prehit_pierce(atom/target)
. = ..()
if(.)
if(kinetic_gun)
var/list/mods = kinetic_gun.modkits
for(var/obj/item/borg/upgrade/modkit/M in mods)
M.projectile_prehit(src, target, kinetic_gun)
- if(!lavaland_equipment_pressure_check(get_turf(target)))
+ if(!pressure_decrease_active && !lavaland_equipment_pressure_check(get_turf(target)))
name = "weakened [name]"
damage = damage * pressure_decrease
pressure_decrease_active = TRUE
diff --git a/code/modules/projectiles/guns/misc/beam_rifle.dm b/code/modules/projectiles/guns/misc/beam_rifle.dm
index 23b94a5e43c..db1f3a1325c 100644
--- a/code/modules/projectiles/guns/misc/beam_rifle.dm
+++ b/code/modules/projectiles/guns/misc/beam_rifle.dm
@@ -431,11 +431,9 @@
var/aoe_mob_damage = 0
var/impact_structure_damage = 0
var/impact_direct_damage = 0
- var/turf/cached
var/list/pierced = list()
/obj/projectile/beam/beam_rifle/proc/AOE(turf/epicenter)
- set waitfor = FALSE
if(!epicenter)
return
new /obj/effect/temp_visual/explosion/fast(epicenter)
@@ -449,31 +447,22 @@
if(!isitem(O))
O.take_damage(aoe_structure_damage * get_damage_coeff(O), BURN, LASER, FALSE)
-/obj/projectile/beam/beam_rifle/proc/check_pierce(atom/target)
- if(!do_pierce)
- return FALSE
- if(pierced[target]) //we already pierced them go away
- return TRUE
- if(isclosedturf(target))
- if(wall_pierce++ < wall_pierce_amount)
- if(prob(wall_devastate))
- if(iswallturf(target))
- var/turf/closed/wall/W = target
- W.dismantle_wall(TRUE, TRUE)
- else
- SSexplosions.medturf += target
- return TRUE
- if(ismovable(target))
- var/atom/movable/AM = target
- if(AM.density && !AM.CanPass(src, get_turf(target)) && !ismob(AM))
- if(structure_pierce < structure_pierce_amount)
- if(isobj(AM))
- var/obj/O = AM
- O.take_damage((impact_structure_damage + aoe_structure_damage) * structure_bleed_coeff * get_damage_coeff(AM), BURN, ENERGY, FALSE)
- pierced[AM] = TRUE
- structure_pierce++
- return TRUE
- return FALSE
+/obj/projectile/beam/beam_rifle/prehit_pierce(atom/A)
+ if(isclosedturf(A) && (wall_pierce < wall_pierce_amount))
+ if(prob(wall_devastate))
+ if(iswallturf(A))
+ var/turf/closed/wall/W = A
+ W.dismantle_wall(TRUE, TRUE)
+ else
+ SSexplosions.medturf += A
+ ++wall_pierce
+ return PROJECTILE_PIERCE_PHASE // yeah this gun is a snowflakey piece of garbage
+ if(isobj(A) && (structure_pierce < structure_pierce_amount))
+ ++structure_pierce
+ var/obj/O = A
+ O.take_damage((impact_structure_damage + aoe_structure_damage) * structure_bleed_coeff * get_damage_coeff(A), BURN, ENERGY, FALSE)
+ return PROJECTILE_PIERCE_PHASE // ditto and this could be refactored to on_hit honestly
+ return ..()
/obj/projectile/beam/beam_rifle/proc/get_damage_coeff(atom/target)
if(istype(target, /obj/machinery/door))
@@ -491,32 +480,18 @@
L.adjustFireLoss(impact_direct_damage)
L.emote("scream")
-/obj/projectile/beam/beam_rifle/proc/handle_hit(atom/target)
+/obj/projectile/beam/beam_rifle/proc/handle_hit(atom/target, piercing_hit = FALSE)
set waitfor = FALSE
- if(!cached && !QDELETED(target))
- cached = get_turf(target)
if(nodamage)
return FALSE
- playsound(cached, 'sound/effects/explosion3.ogg', 100, TRUE)
- AOE(cached)
+ playsound(src, 'sound/effects/explosion3.ogg', 100, TRUE)
+ if(!piercing_hit)
+ AOE(get_turf(target) || get_turf(src))
if(!QDELETED(target))
handle_impact(target)
-/obj/projectile/beam/beam_rifle/Bump(atom/target)
- if(check_pierce(target))
- permutated += target
- trajectory_ignore_forcemove = TRUE
- forceMove(target.loc)
- trajectory_ignore_forcemove = FALSE
- return FALSE
- if(!QDELETED(target))
- cached = get_turf(target)
- return ..()
-
-/obj/projectile/beam/beam_rifle/on_hit(atom/target, blocked = FALSE)
- if(!QDELETED(target))
- cached = get_turf(target)
- handle_hit(target)
+/obj/projectile/beam/beam_rifle/on_hit(atom/target, blocked = FALSE, piercing_hit = FALSE)
+ handle_hit(target, piercing_hit)
return ..()
/obj/projectile/beam/beam_rifle/hitscan
@@ -554,10 +529,9 @@
hitscan_light_color_override = "#99ff99"
reflectable = REFLECT_FAKEPROJECTILE
-/obj/projectile/beam/beam_rifle/hitscan/aiming_beam/prehit(atom/target)
- qdel(src)
- return FALSE
+/obj/projectile/beam/beam_rifle/hitscan/aiming_beam/prehit_pierce(atom/target)
+ return PROJECTILE_DELETE_WITHOUT_HITTING
/obj/projectile/beam/beam_rifle/hitscan/aiming_beam/on_hit()
qdel(src)
- return BULLET_ACT_HIT
+ return BULLET_ACT_BLOCK
diff --git a/code/modules/projectiles/guns/misc/blastcannon.dm b/code/modules/projectiles/guns/misc/blastcannon.dm
index b831d311654..b6e4b88f6f8 100644
--- a/code/modules/projectiles/guns/misc/blastcannon.dm
+++ b/code/modules/projectiles/guns/misc/blastcannon.dm
@@ -116,7 +116,8 @@
icon_state = "blastwave"
damage = 0
nodamage = FALSE
- movement_type = FLYING | UNSTOPPABLE
+ movement_type = FLYING
+ projectile_phasing = ALL // just blows up the turfs lmao
var/heavyr = 0
var/mediumr = 0
var/lightr = 0
diff --git a/code/modules/projectiles/projectile.dm b/code/modules/projectiles/projectile.dm
index 70d950b670e..2dbf436bdd3 100644
--- a/code/modules/projectiles/projectile.dm
+++ b/code/modules/projectiles/projectile.dm
@@ -8,10 +8,10 @@
icon_state = "bullet"
density = FALSE
anchored = TRUE
- pass_flags = PASSTABLE
mouse_opacity = MOUSE_OPACITY_TRANSPARENT
movement_type = FLYING
wound_bonus = CANT_WOUND // can't wound by default
+ generic_canpass = FALSE
//The sound this plays on impact.
var/hitsound = 'sound/weapons/pierce.ogg'
var/hitsound_wall = ""
@@ -25,7 +25,6 @@
var/xo = null
var/atom/original = null // the original target clicked
var/turf/starting = null // the projectile's starting turf
- var/list/permutated = list() // we've passed through these atoms, don't try to hit them again
var/p_x = 16
var/p_y = 16 // the pixel location of the tile that the player clicked. Default is the center
@@ -37,6 +36,35 @@
var/time_offset = 0
var/datum/point/vector/trajectory
var/trajectory_ignore_forcemove = FALSE //instructs forceMove to NOT reset our trajectory to the new location!
+ /// We already impacted these things, do not impact them again. Used to make sure we can pierce things we want to pierce. Lazylist, typecache style (object = TRUE) for performance.
+ var/list/impacted
+ /// If TRUE, we can hit our firer.
+ var/ignore_source_check = FALSE
+ /// We are flagged PHASING temporarily to not stop moving when we Bump something but want to keep going anyways.
+ var/temporary_unstoppable_movement = FALSE
+
+ /** PROJECTILE PIERCING
+ * WARNING:
+ * Projectile piercing MUST be done using these variables.
+ * Ordinary passflags will be **IGNORED**.
+ * The two flag variables below both use pass flags.
+ * In the context of LETPASStHROW, it means the projectile will ignore things that are currently "in the air" from a throw.
+ *
+ * Also, projectiles sense hits using Bump(), and then pierce them if necessary.
+ * They simply do not follow conventional movement rules.
+ * NEVER flag a projectile as PHASING movement type.
+ * If you so badly need to make one go through *everything*, override check_pierce() for your projectile to always return PROJECTILE_PIERCE_PHASE/HIT.
+ */
+ /// The "usual" flags of pass_flags is used in that can_hit_target ignores these unless they're specifically targeted/clicked on. This behavior entirely bypasses process_hit if triggered, rather than phasing which uses prehit_pierce() to check.
+ pass_flags = PASSTABLE
+ /// If FALSE, allow us to hit something directly targeted/clicked/whatnot even if we're able to phase through it
+ var/phasing_ignore_direct_target = FALSE
+ /// Bitflag for things the projectile should just phase through entirely - No hitting unless direct target and [phasing_ignore_direct_target] is FALSE. Uses pass_flags flags.
+ var/projectile_phasing = NONE
+ /// Bitflag for things the projectile should hit, but pierce through without deleting itself. Defers to projectile_phasing. Uses pass_flags flags.
+ var/projectile_piercing = NONE
+ /// number of times we've pierced something. Incremented BEFORE bullet_act and on_hit proc!
+ var/pierces = 0
var/speed = 0.8 //Amount of deciseconds it takes for projectile to travel
var/Angle = 0
@@ -93,8 +121,6 @@
var/homing_offset_x = 0
var/homing_offset_y = 0
- var/ignore_source_check = FALSE
-
var/damage = 10
var/damage_type = BRUTE //BRUTE, BURN, TOX, OXY, CLONE are the only things that should be in here
var/nodamage = FALSE //Determines if the projectile will skip any damage inflictions
@@ -123,8 +149,6 @@
var/impact_effect_type //what type of impact effect to show when hitting something
var/log_override = FALSE //is this type spammed enough to not log? (KAs)
- var/temporary_unstoppable_movement = FALSE
-
///If defined, on hit we create an item of this type then call hitby() on the hit target with this, mainly used for embedding items (bullets) in targets
var/shrapnel_type
///If we have a shrapnel_type defined, these embedding stats will be passed to the spawned shrapnel type, which will roll for embedding on the target
@@ -140,10 +164,8 @@
///How much we want to drop the embed_chance value, if we can embed, per tile, for falloff purposes
var/embed_falloff_tile
-
/obj/projectile/Initialize()
. = ..()
- permutated = list()
decayedRange = range
if(embedding)
updateEmbedding()
@@ -173,11 +195,15 @@
else //when a limb is missing the damage is actually passed to the chest
return BODY_ZONE_CHEST
-/obj/projectile/proc/prehit(atom/target)
- return TRUE
-
-/// Called when the projectile hits something
-/obj/projectile/proc/on_hit(atom/target, blocked = FALSE)
+/**
+ * Called when the projectile hits something
+ *
+ * @params
+ * target - thing hit
+ * blocked - percentage of hit blocked
+ * pierce_hit - are we piercing through or regular hitting
+ */
+/obj/projectile/proc/on_hit(atom/target, blocked = FALSE, pierce_hit)
if(fired_from)
SEND_SIGNAL(fired_from, COMSIG_PROJECTILE_ON_HIT, firer, target, Angle)
// i know that this is probably more with wands and gun mods in mind, but it's a bit silly that the projectile on_hit signal doesn't ping the projectile itself.
@@ -213,6 +239,11 @@
if(!isliving(target))
if(impact_effect_type && !hitscan)
new impact_effect_type(target_loca, hitx, hity)
+ if(isturf(target) && hitsound_wall)
+ var/volume = clamp(vol_by_damage() + 20, 0, 100)
+ if(suppressed)
+ volume = 5
+ playsound(loc, hitsound_wall, volume, TRUE, -1)
return BULLET_ACT_HIT
var/mob/living/L = target
@@ -243,7 +274,7 @@
else
if(hitsound)
var/volume = vol_by_damage()
- playsound(loc, hitsound, volume, TRUE, -1)
+ playsound(src, hitsound, volume, TRUE, -1)
L.visible_message("[L] is hit by \a [src][organ_hit_text]!", \
"You're hit by \a [src][organ_hit_text]!", null, COMBAT_MESSAGE_RANGE)
L.on_hit(src)
@@ -292,16 +323,36 @@
beam_segments[beam_index] = null
/obj/projectile/Bump(atom/A)
+ SEND_SIGNAL(src, COMSIG_MOVABLE_BUMP, A)
+ if(!can_hit_target(A, A == original, TRUE))
+ return
+ Impact(A)
+
+/**
+ * Called when the projectile hits something
+ * This can either be from it bumping something,
+ * or it passing over a turf/being crossed and scanning that there is infact
+ * a valid target it needs to hit.
+ * This target isn't however necessarily WHAT it hits
+ * that is determined by process_hit and select_target.
+ *
+ * Furthermore, this proc shouldn't check can_hit_target - this should only be called if can hit target is already checked.
+ * Also, we select_target to find what to process_hit first.
+ */
+/obj/projectile/proc/Impact(atom/A)
if(!trajectory)
qdel(src)
- return
+ return FALSE
+ if(impacted[A]) // NEVER doublehit
+ return FALSE
var/datum/point/pcache = trajectory.copy_to()
var/turf/T = get_turf(A)
if(ricochets < ricochets_max && check_ricochet_flag(A) && check_ricochet(A))
ricochets++
if(A.handle_ricochet(src))
on_ricochet(A)
- ignore_source_check = TRUE
+ impacted = list() // Shoot a x-ray laser at a pair of mirrors I dare you
+ ignore_source_check = TRUE // Firer is no longer immune
decayedRange = max(0, decayedRange - reflect_range_decrease)
ricochet_chance *= ricochet_decay_chance
damage *= ricochet_decay_damage
@@ -313,73 +364,221 @@
var/distance = get_dist(T, starting) // Get the distance between the turf shot from and the mob we hit and use that for the calculations.
def_zone = ran_zone(def_zone, max(100-(7*distance), 5)) //Lower accurancy/longer range tradeoff. 7 is a balanced number to use.
- if(isturf(A) && hitsound_wall)
- var/volume = clamp(vol_by_damage() + 20, 0, 100)
- if(suppressed)
- volume = 5
- playsound(loc, hitsound_wall, volume, TRUE, -1)
+ return process_hit(T, select_target(T, A)) // SELECT TARGET FIRST!
- return process_hit(T, select_target(T, A))
-
-#define QDEL_SELF 1 //Delete if we're not UNSTOPPABLE flagged non-temporarily
-#define DO_NOT_QDEL 2 //Pass through.
-#define FORCE_QDEL 3 //Force deletion.
-
-/obj/projectile/proc/process_hit(turf/T, atom/target, qdel_self, hit_something = FALSE) //probably needs to be reworked entirely when pixel movement is done.
- if(QDELETED(src) || !T || !target) //We're done, nothing's left.
- if((qdel_self == FORCE_QDEL) || ((qdel_self == QDEL_SELF) && !temporary_unstoppable_movement && !(movement_type & UNSTOPPABLE)))
- qdel(src)
- return hit_something
- permutated |= target //Make sure we're never hitting it again. If we ever run into weirdness with piercing projectiles needing to hit something multiple times.. well.. that's a to-do.
- if(!prehit(target))
- return process_hit(T, select_target(T), qdel_self, hit_something) //Hit whatever else we can since that didn't work.
- SEND_SIGNAL(target, COMSIG_PROJECTILE_PREHIT, args)
- var/result = target.bullet_act(src, def_zone)
- if(result == BULLET_ACT_FORCE_PIERCE)
- if(!(movement_type & UNSTOPPABLE))
- temporary_unstoppable_movement = TRUE
- movement_type |= UNSTOPPABLE
- return process_hit(T, select_target(T), qdel_self, TRUE) //Hit whatever else we can since we're piercing through but we're still on the same tile.
- else if(result == BULLET_ACT_TURF) //We hit the turf but instead we're going to also hit something else on it.
- return process_hit(T, select_target(T), QDEL_SELF, TRUE)
- else //Whether it hit or blocked, we're done!
- qdel_self = QDEL_SELF
- hit_something = TRUE
- if((qdel_self == FORCE_QDEL) || ((qdel_self == QDEL_SELF) && !temporary_unstoppable_movement && !(movement_type & UNSTOPPABLE)))
+/**
+ * The primary workhorse proc of projectile impacts.
+ * This is a RECURSIVE call - process_hit is called on the first selected target, and then repeatedly called if the projectile still hasn't been deleted.
+ *
+ * Order of operations:
+ * 1. Checks if we are deleted, or if we're somehow trying to hit a null, in which case, bail out
+ * 2. Adds the thing we're hitting to impacted so we can make sure we don't doublehit
+ * 3. Checks piercing - stores this.
+ * Afterwards:
+ * Hit and delete, hit without deleting and pass through, pass through without hitting, or delete without hitting depending on result
+ * If we're going through without hitting, find something else to hit if possible and recurse, set unstoppable movement to true
+ * If we're deleting without hitting, delete and return
+ * Otherwise, send signal of COMSIG_PROJECTILE_PREHIT to target
+ * Then, hit, deleting ourselves if necessary.
+ * @params
+ * T - Turf we're on/supposedly hitting
+ * target - target we're hitting
+ * hit_something - only should be set by recursive calling by this proc - tracks if we hit something already
+ *
+ * Returns if we hit something.
+ */
+/obj/projectile/proc/process_hit(turf/T, atom/target, hit_something = FALSE)
+ // 1.
+ if(QDELETED(src) || !T || !target)
+ return
+ // 2.
+ impacted[target] = TRUE //hash lookup > in for performance in hit-checking
+ // 3.
+ var/mode = prehit_pierce(target)
+ if(mode == PROJECTILE_DELETE_WITHOUT_HITTING)
qdel(src)
+ return hit_something
+ else if(mode == PROJECTILE_PIERCE_PHASE)
+ if(!(movement_type & PHASING))
+ temporary_unstoppable_movement = TRUE
+ movement_type |= PHASING
+ return process_hit(T, select_target(T, target), hit_something) // try to hit something else
+ // at this point we are going to hit the thing
+ // in which case send signal to it
+ SEND_SIGNAL(target, COMSIG_PROJECTILE_PREHIT, args)
+ if(mode == PROJECTILE_PIERCE_HIT)
+ ++pierces
+ hit_something = TRUE
+ var/result = target.bullet_act(src, def_zone, mode == PROJECTILE_PIERCE_HIT)
+ if((result == BULLET_ACT_FORCE_PIERCE) || (mode == PROJECTILE_PIERCE_HIT))
+ if(!(movement_type & PHASING))
+ temporary_unstoppable_movement = TRUE
+ movement_type |= PHASING
+ return process_hit(T, select_target(T, target), TRUE)
+ qdel(src)
return hit_something
-#undef QDEL_SELF
-#undef DO_NOT_QDEL
-#undef FORCE_QDEL
-
-/obj/projectile/proc/select_target(turf/T, atom/target) //Select a target from a turf.
- if((original in T) && can_hit_target(original, permutated, TRUE, TRUE))
+/**
+ * Selects a target to hit from a turf
+ *
+ * @params
+ * T - The turf
+ * target - The "preferred" atom to hit, usually what we Bumped() first.
+ *
+ * Priority:
+ * 0. Anything that is already in impacted is ignored no matter what. Furthermore, in any bracket, if the target atom parameter is in it, that's hit first.
+ * Furthermore, can_hit_target is always checked. This (entire proc) is PERFORMANCE OVERHEAD!! But, it shouldn't be ""too"" bad and I frankly don't have a better *generic non snowflakey* way that I can think of right now at 3 AM.
+ * FURTHERMORE, mobs/objs have a density check from can_hit_target - to hit non dense objects over a turf, you must click on them, same for mobs that usually wouldn't get hit.
+ * 1. The thing originally aimed at/clicked on
+ * 2. Mobs - picks lowest buckled mob to prevent scarp piggybacking memes
+ * 3. Objs
+ * 4. Turf
+ * 5. Nothing
+ */
+/obj/projectile/proc/select_target(turf/T, atom/target)
+ // 1. original
+ if(can_hit_target(original, TRUE, FALSE))
return original
- if(target && can_hit_target(target, permutated, target == original, TRUE))
- return target
- var/list/mob/living/possible_mobs = typecache_filter_list(T, GLOB.typecache_mob)
- var/list/mob/mobs = list()
- for(var/mob/living/M in possible_mobs)
- if(!can_hit_target(M, permutated, M == original, TRUE))
+ var/list/atom/possible = list() // let's define these ONCE
+ var/list/atom/considering = list()
+ // 2. mobs
+ possible = typecache_filter_list(T, GLOB.typecache_living) // living only
+ for(var/i in possible)
+ if(!can_hit_target(i, i == original, TRUE))
continue
- mobs += M
- if (length(mobs))
- var/mob/M = pick(mobs)
+ considering += i
+ if(considering.len)
+ var/mob/living/M = pick(considering)
return M.lowest_buckled_mob()
- var/list/obj/possible_objs = typecache_filter_list(T, GLOB.typecache_machine_or_structure)
- var/list/obj/objs = list()
- for(var/obj/O in possible_objs)
- if(!can_hit_target(O, permutated, O == original, TRUE))
+ considering.len = 0
+ // 3. objs
+ possible = typecache_filter_list(T, GLOB.typecache_machine_or_structure) // because why are items ever dense?
+ for(var/i in possible)
+ if(!can_hit_target(i, i == original, TRUE))
continue
- objs += O
- if (length(objs))
- var/obj/O = pick(objs)
- return O
- //Nothing else is here that we can hit, hit the turf if we haven't.
- if(!(T in permutated) && can_hit_target(T, permutated, T == original, TRUE))
+ considering += i
+ if(considering.len)
+ return pick(considering)
+ // 4. turf
+ if(can_hit_target(T, T == original, TRUE))
return T
- //Returns null if nothing at all was found.
+ // 5. nothing
+ // (returns null)
+
+//Returns true if the target atom is on our current turf and above the right layer
+//If direct target is true it's the originally clicked target.
+/obj/projectile/proc/can_hit_target(atom/target, direct_target = FALSE, ignore_loc = FALSE)
+ if(QDELETED(target) || impacted[target])
+ return FALSE
+ if(!ignore_loc && (loc != target.loc))
+ return FALSE
+ // if pass_flags match, pass through entirely
+ if(target.pass_flags_self & pass_flags) // phasing
+ return FALSE
+ if(!ignore_source_check && firer)
+ var/mob/M = firer
+ if((target == firer) || ((target == firer.loc) && ismecha(firer.loc)) || (target in firer.buckled_mobs) || (istype(M) && (M.buckled == target)))
+ return FALSE
+ if(target.density) //This thing blocks projectiles, hit it regardless of layer/mob stuns/etc.
+ return TRUE
+ if(!isliving(target))
+ if(isturf(target)) // non dense turfs
+ return FALSE
+ if(target.layer < PROJECTILE_HIT_THRESHHOLD_LAYER)
+ return FALSE
+ else if(!direct_target) // non dense objects do not get hit unless specifically clicked
+ return FALSE
+ else
+ var/mob/living/L = target
+ if(direct_target)
+ return TRUE
+ // If target not able to use items, move and stand - or if they're just dead, pass over.
+ if(L.stat == DEAD || (!hit_stunned_targets && HAS_TRAIT(L, TRAIT_IMMOBILIZED) && HAS_TRAIT(L, TRAIT_FLOORED) && HAS_TRAIT(L, TRAIT_HANDS_BLOCKED)))
+ return FALSE
+ return TRUE
+
+/**
+ * Scan if we should hit something and hit it if we need to
+ * The difference between this and handling in Impact is
+ * In this we strictly check if we need to Impact() something in specific
+ * If we do, we do
+ * We don't even check if it got hit already - Impact() does that
+ * In impact there's more code for selecting WHAT to hit
+ * So this proc is more of checking if we should hit something at all BY having an atom cross us.
+ */
+/obj/projectile/proc/scan_crossed_hit(atom/movable/A)
+ if(can_hit_target(A, direct_target = (A == original)))
+ Impact(A)
+
+/**
+ * Scans if we should hit something on the turf we just moved to if we haven't already
+ *
+ * This proc is a little high in overhead but allows us to not snowflake CanPass in living and other things.
+ */
+/obj/projectile/proc/scan_moved_turf()
+ // Optimally, we scan: mobs --> objs --> turf for impact
+ // but, overhead is a thing and 2 for loops every time it moves is a no-go.
+ // realistically, since we already do select_target in impact, we can not do that
+ // and hope projectiles get refactored again in the future to have a less stupid impact detection system
+ // that hopefully won't also involve a ton of overhead
+ if(can_hit_target(original, TRUE, FALSE))
+ Impact(original) // try to hit thing clicked on
+ // else, try to hit mobs
+ else // because if we impacted original and pierced we'll already have select target'd and hit everything else we should be hitting
+ for(var/mob/M in loc) // so I guess we're STILL doing a for loop of mobs because living movement would otherwise have snowflake code for projectile CanPass
+ // so the snowflake vs performance is pretty arguable here
+ if(can_hit_target(M, M == original, TRUE))
+ Impact(M)
+ break
+
+/**
+ * Projectile crossed: When something enters a projectile's tile, make sure the projectile hits it if it should be hitting it.
+ */
+/obj/projectile/Crossed(atom/movable/AM)
+ . = ..()
+ scan_crossed_hit(AM)
+
+/**
+ * Projectile can pass through
+ * Used to not even attempt to Bump() or fail to Cross() anything we already hit.
+ */
+/obj/projectile/CanPassThrough(atom/blocker, turf/target, blocker_opinion)
+ return impacted[blocker]? TRUE : ..()
+
+/**
+ * Projectile moved:
+ *
+ * If not fired yet, do not do anything. Else,
+ *
+ * If temporary unstoppable movement used for piercing through things we already hit (impacted list) is set, unset it.
+ * Scan turf we're now in for anything we can/should hit. This is useful for hitting non dense objects the user
+ * directly clicks on, as well as for PHASING projectiles to be able to hit things at all as they don't ever Bump().
+ */
+/obj/projectile/Moved(atom/OldLoc, Dir)
+ . = ..()
+ if(!fired)
+ return
+ if(temporary_unstoppable_movement)
+ temporary_unstoppable_movement = FALSE
+ movement_type &= ~PHASING
+ scan_moved_turf() //mostly used for making sure we can hit a non-dense object the user directly clicked on, and for penetrating projectiles that don't bump
+
+/**
+ * Checks if we should pierce something.
+ *
+ * NOT meant to be a pure proc, since this replaces prehit() which was used to do things.
+ * Return PROJECTILE_DELETE_WITHOUT_HITTING to delete projectile without hitting at all!
+ */
+/obj/projectile/proc/prehit_pierce(atom/A)
+ if(projectile_phasing & A.pass_flags_self)
+ return PROJECTILE_PIERCE_PHASE
+ if(projectile_piercing & A.pass_flags_self)
+ return PROJECTILE_PIERCE_HIT
+ if(ismovable(A))
+ var/atom/movable/AM = A
+ if(AM.throwing)
+ return (projectile_phasing & LETPASSTHROW)? PROJECTILE_PIERCE_PHASE : ((projectile_piercing & LETPASSTHROW)? PROJECTILE_PIERCE_HIT : PROJECTILE_PIERCE_NONE)
+ return PROJECTILE_PIERCE_NONE
/obj/projectile/proc/check_ricochet(atom/A)
var/chance = ricochet_chance * A.receive_ricochet_chance_mod
@@ -447,10 +646,9 @@
AddElement(/datum/element/embed, projectile_payload = shrapnel_type)
if(!log_override && firer && original)
log_combat(firer, original, "fired at", src, "from [get_area_name(src, TRUE)]")
- if(direct_target)
- if(prehit(direct_target))
- direct_target.bullet_act(src, def_zone)
- qdel(src)
+ if(direct_target && (get_dist(direct_target, get_turf(src)) <= 1)) // point blank shots
+ process_hit(get_turf(direct_target), direct_target)
+ if(QDELETED(src))
return
if(isnum(angle))
setAngle(angle)
@@ -469,6 +667,7 @@
var/matrix/M = new
M.Turn(Angle)
transform = M
+ LAZYINITLIST(impacted)
trajectory_ignore_forcemove = TRUE
forceMove(starting)
trajectory_ignore_forcemove = FALSE
@@ -500,6 +699,8 @@
if(zc)
before_z_change(old, target)
. = ..()
+ if(QDELETED(src)) // we coulda bumped something
+ return
if(trajectory && !trajectory_ignore_forcemove && isturf(target))
if(hitscan)
finalize_hitscan_and_generate_tracers(FALSE)
@@ -609,33 +810,6 @@
if(prob(50))
homing_offset_y = -homing_offset_y
-//Returns true if the target atom is on our current turf and above the right layer
-//If direct target is true it's the originally clicked target.
-/obj/projectile/proc/can_hit_target(atom/target, list/passthrough, direct_target = FALSE, ignore_loc = FALSE)
- if(QDELETED(target))
- return FALSE
- if(!ignore_source_check && firer)
- var/mob/M = firer
- if((target == firer) || ((target == firer.loc) && ismecha(firer.loc)) || (target in firer.buckled_mobs) || (istype(M) && (M.buckled == target)))
- return FALSE
- if(!ignore_loc && (loc != target.loc))
- return FALSE
- if(target in passthrough)
- return FALSE
- if(target.density) //This thing blocks projectiles, hit it regardless of layer/mob stuns/etc.
- return TRUE
- if(!isliving(target))
- if(target.layer < PROJECTILE_HIT_THRESHHOLD_LAYER)
- return FALSE
- else
- var/mob/living/L = target
- if(direct_target)
- return TRUE
- // If target not able to use items, move and stand - or if they're just dead, pass over.
- if(L.stat == DEAD || (!hit_stunned_targets && HAS_TRAIT(L, TRAIT_IMMOBILIZED) && HAS_TRAIT(L, TRAIT_FLOORED) && HAS_TRAIT(L, TRAIT_HANDS_BLOCKED)))
- return FALSE
- return TRUE
-
//Spread is FORCED!
/obj/projectile/proc/preparePixelProjectile(atom/target, atom/source, params, spread = 0)
var/turf/curloc = get_turf(source)
@@ -695,22 +869,6 @@
angle = ATAN2(y - oy, x - ox)
return list(angle, p_x, p_y)
-/obj/projectile/Crossed(atom/movable/AM) //A mob moving on a tile with a projectile is hit by it.
- . = ..()
- if(isliving(AM) && !(pass_flags & PASSMOB))
- var/mob/living/L = AM
- if(can_hit_target(L, permutated, (AM == original)))
- Bump(AM)
-
-/obj/projectile/Move(atom/newloc, dir = NONE)
- . = ..()
- if(.)
- if(temporary_unstoppable_movement)
- temporary_unstoppable_movement = FALSE
- movement_type &= ~(UNSTOPPABLE)
- if(fired && can_hit_target(original, permutated, TRUE))
- Bump(original)
-
/obj/projectile/Destroy()
if(hitscan)
finalize_hitscan_and_generate_tracers()
diff --git a/code/modules/projectiles/projectile/bullets/sniper.dm b/code/modules/projectiles/projectile/bullets/sniper.dm
index a065bc23ee6..7c33e0c4761 100644
--- a/code/modules/projectiles/projectile/bullets/sniper.dm
+++ b/code/modules/projectiles/projectile/bullets/sniper.dm
@@ -33,7 +33,8 @@
name = "penetrator round"
icon_state = "gauss"
damage = 60
- movement_type = FLYING | UNSTOPPABLE
+ projectile_piercing = PASSMOB
+ projectile_phasing = (ALL & (~PASSMOB))
dismemberment = 0 //It goes through you cleanly.
paralyze = 0
breakthings = FALSE
diff --git a/code/modules/projectiles/projectile/bullets/special.dm b/code/modules/projectiles/projectile/bullets/special.dm
index 755eee796aa..8927594054d 100644
--- a/code/modules/projectiles/projectile/bullets/special.dm
+++ b/code/modules/projectiles/projectile/bullets/special.dm
@@ -3,7 +3,8 @@
/obj/projectile/bullet/honker
name = "banana"
damage = 0
- movement_type = FLYING | UNSTOPPABLE
+ movement_type = FLYING
+ projectile_piercing = ALL
nodamage = TRUE
hitsound = 'sound/items/bikehorn.ogg'
icon = 'icons/obj/hydroponics/harvest.dmi'
diff --git a/code/modules/projectiles/projectile/magic.dm b/code/modules/projectiles/projectile/magic.dm
index 2b1ab775aa4..618f9e0f59b 100644
--- a/code/modules/projectiles/projectile/magic.dm
+++ b/code/modules/projectiles/projectile/magic.dm
@@ -402,18 +402,17 @@
. = ..()
locker_temp_instance = new(src)
-/obj/projectile/magic/locker/prehit(atom/A)
+/obj/projectile/magic/locker/prehit_pierce(atom/A)
+ . = ..()
if(isliving(A) && locker_suck)
var/mob/living/M = A
- if(M.anti_magic_check())
+ if(M.anti_magic_check()) // no this doesn't check if ..() returned to phase through do I care no it's magic ain't gotta explain shit
M.visible_message("[src] vanishes on contact with [A]!")
- qdel(src)
- return
+ return PROJECTILE_DELETE_WITHOUT_HITTING
if(!locker_temp_instance.insertion_allowed(M))
- return ..()
+ return
M.forceMove(src)
- return FALSE
- return ..()
+ return PROJECTILE_PIERCE_PHASE
/obj/projectile/magic/locker/on_hit(target)
if(created)
diff --git a/code/modules/projectiles/projectile/special/curse.dm b/code/modules/projectiles/projectile/special/curse.dm
index c4b90ac9ba9..a60bf444c6d 100644
--- a/code/modules/projectiles/projectile/special/curse.dm
+++ b/code/modules/projectiles/projectile/special/curse.dm
@@ -17,7 +17,6 @@
/obj/projectile/curse_hand/Initialize(mapload)
. = ..()
- movement_type |= UNSTOPPABLE
handedness = prob(50)
icon_state = "cursehand[handedness]"
@@ -29,18 +28,14 @@
arm = starting.Beam(src, icon_state = "curse[handedness]", time = INFINITY, maxdistance = INFINITY, beam_type=/obj/effect/ebeam/curse_arm)
..()
-/obj/projectile/curse_hand/prehit(atom/target)
- if(target == original)
- movement_type &= ~(UNSTOPPABLE)
- else if(!isturf(target))
- return FALSE
- return ..()
+/obj/projectile/curse_hand/prehit_pierce(atom/target)
+ return (target == original)? PROJECTILE_PIERCE_NONE : PROJECTILE_PIERCE_PHASE
/obj/projectile/curse_hand/Destroy()
if(arm)
arm.End()
arm = null
- if((movement_type & UNSTOPPABLE))
+ if((movement_type & PHASING))
playsound(src, 'sound/effects/curse3.ogg', 25, TRUE, -1)
var/turf/T = get_step(src, dir)
var/obj/effect/temp_visual/dir_setting/curse/hand/leftover = new(T, dir)
diff --git a/code/modules/religion/religion_structures.dm b/code/modules/religion/religion_structures.dm
index ffad8ef18d1..061d4285ec4 100644
--- a/code/modules/religion/religion_structures.dm
+++ b/code/modules/religion/religion_structures.dm
@@ -7,7 +7,7 @@
anchored = TRUE
layer = TABLE_LAYER
climbable = TRUE
- pass_flags = LETPASSTHROW
+ pass_flags_self = LETPASSTHROW
can_buckle = TRUE
buckle_lying = 90 //we turn to you!
///Avoids having to check global everytime by referencing it locally.
diff --git a/code/modules/spells/spell_types/projectile.dm b/code/modules/spells/spell_types/projectile.dm
index c70e9988333..8441365eeac 100644
--- a/code/modules/spells/spell_types/projectile.dm
+++ b/code/modules/spells/spell_types/projectile.dm
@@ -91,7 +91,7 @@
projectile.icon_state = proj_icon_state
projectile.name = proj_name
if(proj_insubstantial)
- projectile.movement_type |= UNSTOPPABLE
+ projectile.movement_type |= PHASING
if(proj_homing)
projectile.homing = TRUE
projectile.homing_turn_speed = 360 //Perfect tracking
diff --git a/code/modules/unit_tests/_unit_tests.dm b/code/modules/unit_tests/_unit_tests.dm
index 167f9df247b..27cf984fbc8 100644
--- a/code/modules/unit_tests/_unit_tests.dm
+++ b/code/modules/unit_tests/_unit_tests.dm
@@ -52,6 +52,7 @@
#include "outfit_sanity.dm"
#include "pills.dm"
#include "plantgrowth_tests.dm"
+#include "projectiles.dm"
#include "reagent_id_typos.dm"
#include "reagent_mod_expose.dm"
#include "reagent_mod_procs.dm"
diff --git a/code/modules/unit_tests/projectiles.dm b/code/modules/unit_tests/projectiles.dm
new file mode 100644
index 00000000000..06a8fb0780a
--- /dev/null
+++ b/code/modules/unit_tests/projectiles.dm
@@ -0,0 +1,5 @@
+/datum/unit_test/projectile_movetypes/Run()
+ for(var/path in typesof(/obj/projectile))
+ var/obj/projectile/projectile = path
+ if(initial(projectile.movement_type) & PHASING)
+ Fail("[path] has default movement type PHASING. Piercing projectiles should be done using the projectile piercing system, not movement_types!")
diff --git a/code/modules/vehicles/ridden.dm b/code/modules/vehicles/ridden.dm
index c6fbcd8a6ac..fdd43c8719b 100644
--- a/code/modules/vehicles/ridden.dm
+++ b/code/modules/vehicles/ridden.dm
@@ -4,6 +4,7 @@
max_buckled_mobs = 1
buckle_lying = 0
default_driver_move = FALSE
+ pass_flags_self = PASSTABLE
var/rider_check_flags = REQUIRES_LEGS | REQUIRES_ARMS
COOLDOWN_DECLARE(message_cooldown)
@@ -69,7 +70,7 @@
return FALSE
if(rider_check_flags & REQUIRES_LEGS && HAS_TRAIT(user, TRAIT_FLOORED))
- if(rider_check_flags & UNBUCKLE_DISABLED_RIDER)
+ if(rider_check_flags & UNBUCKLE_DISABLED_RIDER)
unbuckle_mob(user, TRUE)
user.visible_message("[user] falls off \the [src].",\
"You fall off \the [src] while trying to operate it while unable to stand!")
@@ -96,7 +97,7 @@
to_chat(user, "You can't seem to manage that unable to hold onto \the [src] to move it...")
COOLDOWN_START(src, message_cooldown, 5 SECONDS)
return FALSE
-
+
var/datum/component/riding/R = GetComponent(/datum/component/riding)
R.handle_ride(user, direction)
return ..()
@@ -114,9 +115,3 @@
/obj/vehicle/ridden/zap_act(power, zap_flags)
zap_buckle_check(power)
return ..()
-
-/obj/vehicle/ridden/CanAllowThrough(atom/movable/mover, turf/target)
- . = ..()
-
- if(mover.pass_flags & PASSTABLE)
- return TRUE
diff --git a/tgstation.dme b/tgstation.dme
index d2186bfcbc0..29fcd1decd7 100644
--- a/tgstation.dme
+++ b/tgstation.dme
@@ -94,6 +94,7 @@
#include "code\__DEFINES\processing.dm"
#include "code\__DEFINES\procpath.dm"
#include "code\__DEFINES\profile.dm"
+#include "code\__DEFINES\projectiles.dm"
#include "code\__DEFINES\qdel.dm"
#include "code\__DEFINES\radiation.dm"
#include "code\__DEFINES\radio.dm"